From 2c6548207cfe38b3187203d358e0bdecb4651c1c Mon Sep 17 00:00:00 2001 From: FedyaLight Date: Mon, 28 Sep 2026 10:46:50 +0000 Subject: [PATCH 1/4] Give the Windows app its icon and no console window Release builds use the GUI subsystem, so no console opens behind the app, and helper processes (FFmpeg, ffprobe, the AAF module) start with CREATE_NO_WINDOW instead of flashing one each time. align.exe now embeds the app icon and version details from Cargo.toml. --- crates/align-decode/src/aaf.rs | 10 ++--- crates/align-decode/src/export.rs | 2 +- crates/align-decode/src/ff.rs | 27 +++++++++--- crates/align-gpui/Cargo.toml | 3 ++ crates/align-gpui/build.rs | 71 ++++++++++++++++++++++++++++++- crates/align-gpui/src/main.rs | 2 + 6 files changed, 102 insertions(+), 13 deletions(-) diff --git a/crates/align-decode/src/aaf.rs b/crates/align-decode/src/aaf.rs index eb35c57..bccedc7 100644 --- a/crates/align-decode/src/aaf.rs +++ b/crates/align-decode/src/aaf.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use std::{ path::{Path, PathBuf}, - process::{Command, Stdio}, + process::Stdio, sync::atomic::{AtomicBool, Ordering}, }; @@ -198,7 +198,7 @@ pub fn read_timeline(path: &Path, cancel: &AtomicBool) -> Result) -> Command { + #[allow(unused_mut)] + let mut command = Command::new(program); + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + const CREATE_NO_WINDOW: u32 = 0x0800_0000; + command.creation_flags(CREATE_NO_WINDOW); + } + command +} + /// Locate `ffprobe`/`ffmpeg`: explicit env override → executable's dir /// (bundled sidecar layout) → PATH. pub fn resolve_bin(env_var: &str, name: &str) -> Option { @@ -66,7 +81,7 @@ pub fn ffmpeg_bin() -> Option { pub fn inspect(path: &Path) -> Result { let bin = ffprobe_bin().ok_or_else(|| DecodeError::FfmpegMissing("ffprobe not found".into()))?; - let out = Command::new(bin) + let out = command(bin) .args([ "-v", "error", @@ -291,7 +306,7 @@ fn video_timing_uncached( use align_core::canonical_frame_duration; use std::io::BufRead; - let mut child = Command::new(bin) + let mut child = command(bin) .args([ "-v", "error", @@ -311,7 +326,7 @@ fn video_timing_uncached( let stdout = child.stdout.take().ok_or(DecodeError::UnsupportedOutput)?; // Nominal rate + dimensions from the header call. - let header_json = Command::new(bin) + let header_json = command(bin) .args([ "-v", "error", @@ -369,7 +384,7 @@ fn video_timing_uncached( /// Full video probe: header facts + timing walk + timecode tag. /// `None` when the container has no usable video stream. pub fn inspect_video(path: &Path) -> Option { - let header_json = Command::new(ffprobe_bin()?) + let header_json = command(ffprobe_bin()?) .args([ "-v", "error", @@ -423,7 +438,7 @@ impl AudioPipe { ) -> Result { let bin = ffmpeg_bin().ok_or_else(|| DecodeError::FfmpegMissing("ffmpeg not found".into()))?; - let mut cmd = Command::new(bin); + let mut cmd = command(bin); cmd.args(["-v", "error", "-hide_banner", "-i"]).arg(path); if let Some((start, duration)) = window { cmd.args([ @@ -524,7 +539,7 @@ impl AudioPipeI32 { ) -> Result { let bin = ffmpeg_bin().ok_or_else(|| DecodeError::FfmpegMissing("ffmpeg not found".into()))?; - let mut cmd = Command::new(bin); + let mut cmd = command(bin); cmd.args(["-v", "error", "-hide_banner", "-i"]).arg(path); if let Some((start, duration)) = window { cmd.args([ diff --git a/crates/align-gpui/Cargo.toml b/crates/align-gpui/Cargo.toml index 422c818..23fb185 100644 --- a/crates/align-gpui/Cargo.toml +++ b/crates/align-gpui/Cargo.toml @@ -24,6 +24,9 @@ velopack = "1.2.0" [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.59", features = ["Win32_UI_WindowsAndMessaging"] } +[target.'cfg(windows)'.build-dependencies] +embed-resource = "3.0.11" + [dev-dependencies] # Same renderer the GPUI sprite atlas uses: the test below parses every # icon exactly like `SvgRenderer::render_pixmap` does. diff --git a/crates/align-gpui/build.rs b/crates/align-gpui/build.rs index b77dac6..86d8d45 100644 --- a/crates/align-gpui/build.rs +++ b/crates/align-gpui/build.rs @@ -1,4 +1,5 @@ -//! Build identifier for the diagnostics panel (proves which binary is running). +//! Build identifier for the diagnostics panel (proves which binary is running), +//! and on Windows the executable's icon, manifest and version information. fn main() { let hash = std::process::Command::new("git") @@ -21,4 +22,72 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); // Rebuild the id when the tree changes (best effort; timestamp fallback). println!("cargo:rerun-if-changed=../../.git/HEAD"); + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { + windows_resources(); + } } + +/// Embed the icon Explorer, the taskbar and installer shortcuts show, and +/// version details from Cargo.toml. GPUI links the application manifest +/// (per-monitor DPI, common controls) itself; a second one would collide. +#[cfg(windows)] +fn windows_resources() { + use std::path::PathBuf; + let support = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../Support/Installer"); + let out = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let version = env!("CARGO_PKG_VERSION"); + let mut numbers: Vec<&str> = version.split(['.', '-', '+']).take(3).collect(); + numbers.resize(3, "0"); + let [major, minor, patch] = [numbers[0], numbers[1], numbers[2]]; + println!( + "cargo:rerun-if-changed={}", + support.join("Align.ico").display() + ); + let icon = support.join("Align.ico").canonicalize().unwrap(); + // rc.exe accepts forward slashes; the verbatim prefix it does not. + let icon = align_path(&icon); + let rc = format!( + r#"1 ICON "{icon}" +1 VERSIONINFO +FILEVERSION {major},{minor},{patch},0 +PRODUCTVERSION {major},{minor},{patch},0 +FILEOS 0x40004 +FILETYPE 1 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Align contributors\0" + VALUE "FileDescription", "Align\0" + VALUE "FileVersion", "{version}\0" + VALUE "InternalName", "align\0" + VALUE "OriginalFilename", "align.exe\0" + VALUE "ProductName", "Align\0" + VALUE "ProductVersion", "{version}\0" + VALUE "LegalCopyright", "Copyright 2026 Align contributors. GPL-3.0-only.\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END +"# + ); + let rc_path = out.join("Align.rc"); + std::fs::write(&rc_path, rc).unwrap(); + embed_resource::compile(&rc_path, embed_resource::NONE) + .manifest_optional() + .unwrap(); +} + +#[cfg(windows)] +fn align_path(path: &std::path::Path) -> String { + let text = path.display().to_string().replace('\\', "/"); + text.strip_prefix("//?/").map(str::to_owned).unwrap_or(text) +} + +/// Cross-compiled Windows builds skip resources (no resource compiler). +#[cfg(not(windows))] +fn windows_resources() {} diff --git a/crates/align-gpui/src/main.rs b/crates/align-gpui/src/main.rs index d2e2422..ef5c9c7 100644 --- a/crates/align-gpui/src/main.rs +++ b/crates/align-gpui/src/main.rs @@ -1,4 +1,6 @@ //! align: native GPUI desktop app (Metal / DirectX / Vulkan via Blade). +// Release builds on Windows are GUI programs: no console window behind them. +#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")] #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; From f54380c6409d6ca6f9a47073bba354ec0cd8d105 Mon Sep 17 00:00:00 2001 From: FedyaLight Date: Mon, 28 Sep 2026 10:46:50 +0000 Subject: [PATCH 2/4] Build and publish every installer from a version tag A vX.Y.Z tag now builds the macOS DMG, the Windows installer with its animated splash and update feed, and Fedora and Arch packages with the dark GTK installer, checks that the Linux packages install and run in clean containers, and publishes them with the source archives. A manual run builds the same installers without publishing. The packaging scripts and installer artwork used for 0.1.0 are now in the repository. Bump the version to 0.2.0. --- .github/release-notes.md | 78 +++++++++ .github/workflows/align-rs.yml | 195 ++++++++++++++++------- Cargo.lock | 11 +- Cargo.toml | 2 +- Support/Installer/Align.ico | Bin 0 -> 54619 bytes Support/Installer/Setup.gif | Bin 0 -> 63239 bytes Support/Installer/Setup.png | Bin 0 -> 33170 bytes Support/Linux/PKGBUILD.in | 34 ++++ Support/Linux/com.align.app.desktop | 14 ++ Support/Linux/com.align.app.metainfo.xml | 24 +++ Support/Linux/installer.c | 172 ++++++++++++++++++++ docs/development.md | 15 +- docs/distribution.md | 60 ++++--- script/package-arch-gui.sh | 42 +++++ script/package-arch.sh | 37 +++++ script/package-fedora-gui.sh | 37 +++++ script/package-fedora.sh | 69 ++++++++ script/package-windows.sh | 31 ++++ script/render-installer-artwork.swift | 92 +++++++++++ 19 files changed, 818 insertions(+), 95 deletions(-) create mode 100644 .github/release-notes.md create mode 100644 Support/Installer/Align.ico create mode 100644 Support/Installer/Setup.gif create mode 100644 Support/Installer/Setup.png create mode 100644 Support/Linux/PKGBUILD.in create mode 100644 Support/Linux/com.align.app.desktop create mode 100644 Support/Linux/com.align.app.metainfo.xml create mode 100644 Support/Linux/installer.c create mode 100755 script/package-arch-gui.sh create mode 100755 script/package-arch.sh create mode 100755 script/package-fedora-gui.sh create mode 100755 script/package-fedora.sh create mode 100755 script/package-windows.sh create mode 100644 script/render-installer-artwork.swift diff --git a/.github/release-notes.md b/.github/release-notes.md new file mode 100644 index 0000000..d8680f9 --- /dev/null +++ b/.github/release-notes.md @@ -0,0 +1,78 @@ +Align synchronizes camera footage and separately recorded audio using waveform +matching, timecode, and recording metadata. + +## What's new in @VERSION@ + +- **Much faster synchronization.** MOV, MP4 and WAV (including RF64/BW64) are + read natively instead of through FFmpeg. On a 22-file test set, a full + synchronization dropped from about 11 s to under 1 s with the same accuracy. +- **Faster media export.** Camera files with replacement audio are written + natively, in a fraction of a second instead of several seconds. +- **Smaller macOS app.** macOS no longer bundles FFmpeg; Linux and Windows ship + a slimmer FFmpeg build (about 9 MB instead of 19 MB). +- **Refined interface.** Round timeline ruler steps, smoother progress, + a real drop zone, clearer search-accuracy settings, and Ctrl shortcuts on + Windows and Linux (F11 for full screen). +- **Windows fixes.** Premiere Pro and Final Cut Pro exports now reference + Windows media with valid file URLs. + +## Downloads + +| Platform | Installer | +| :--- | :--- | +| macOS 15+ (Apple Silicon) | `Align-@VERSION@-macOS-arm64.dmg` | +| Windows 10/11 (x64) | `Align-@VERSION@-Windows-x64-Setup.exe` | +| Fedora (x86_64) | `Align-@VERSION@-Fedora-x86_64-Setup.run` or `Align-@VERSION@-Fedora-x86_64.rpm` | +| Omarchy / Arch Linux (x86_64) | `Align-@VERSION@-Omarchy-x86_64-Setup.run` or `align-@VERSION@-1-x86_64.pkg.tar.zst` | + +Every build includes the desktop app, the CLI, the MCP server, and the AAF +module. No separate Python installation is needed. + +### macOS + +Open the DMG and drag **Align** to **Applications**. The app is ad-hoc signed, +not Developer ID signed or notarized. If macOS says the app is damaged, move it +to `/Applications`, then for a copy downloaded from this release run: + +```sh +sudo xattr -dr com.apple.quarantine "/Applications/Align.app" +open "/Applications/Align.app" +``` + +### Windows + +Run `Align-@VERSION@-Windows-x64-Setup.exe`. It installs for the current user, +adds Align to the Start menu, installs the Microsoft VC++ runtime if needed, and +updates itself from later releases. The installer is unsigned, so SmartScreen may +ask for confirmation. + +### Fedora and Omarchy / Arch + +The `.run` installers open a graphical installer in Align's style and ask for +system authorization. Run them as your ordinary user: + +```sh +chmod +x Align-@VERSION@-Fedora-x86_64-Setup.run +./Align-@VERSION@-Fedora-x86_64-Setup.run +``` + +Or install the native package directly with +`sudo dnf install ./Align-@VERSION@-Fedora-x86_64.rpm` or +`sudo pacman -U ./align-@VERSION@-1-x86_64.pkg.tar.zst`. Choose one method; each +`.run` contains the same package. Linux requires glibc 2.39+, a working Vulkan +driver, and GTK 4 with polkit for the graphical installer. Native packages are +updated by installing a newer package. + +## Sources and verification + +`align-source-.tar.gz` contains the corresponding Align source, locked +and vendored Rust dependencies, the FFmpeg source used on Linux and Windows, and +the AAF component sources. License notices are included in every installer. +Align is licensed under GPL-3.0-only. + +Each download has a `.sha256` file. Verify from the download directory, for +example `shasum -a 256 -c Align-@VERSION@-macOS-arm64.dmg.sha256`. + +Every installer in this release was built and checked by GitHub Actions: tests +and lint on all three platforms, end-to-end synchronization and export checks, +and a package installation test on Fedora and Arch Linux. diff --git a/.github/workflows/align-rs.yml b/.github/workflows/align-rs.yml index 2216cb1..b23a3fe 100644 --- a/.github/workflows/align-rs.yml +++ b/.github/workflows/align-rs.yml @@ -8,7 +8,7 @@ on: - "Cargo.lock" - "about.toml" - "script/**" - - "Support/aaf/**" + - "Support/**" - "docs/**" - "README.md" - "LICENSE" @@ -21,7 +21,7 @@ on: - "Cargo.lock" - "about.toml" - "script/**" - - "Support/aaf/**" + - "Support/**" - "docs/**" - "README.md" - "LICENSE" @@ -104,19 +104,32 @@ jobs: path: release-smoke/ + # Tags vX.Y.Z build and publish every installer. A manual run on any + # branch builds the same installers as artifacts without publishing. release: name: release binaries (${{ matrix.os }}) env: VELOPACK_VERSION: 1.2.0 needs: check-test - if: startsWith(github.ref, 'refs/tags/align-rs-v') + if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch' runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [macos-latest, windows-latest, ubuntu-latest] + # Linux binaries are built against the oldest glibc the packages + # support (2.39), so they run on current Fedora and Arch. + os: [macos-latest, windows-latest, ubuntu-24.04] steps: - uses: actions/checkout@v4 + - name: Check the tag matches the workspace version + if: startsWith(github.ref, 'refs/tags/v') + shell: bash + run: | + workspace_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)" + if [[ "${GITHUB_REF_NAME#v}" != "$workspace_version" ]]; then + echo "Tag $GITHUB_REF_NAME does not match Cargo.toml version $workspace_version" >&2 + exit 1 + fi - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - uses: msys2/setup-msys2@v2 @@ -127,7 +140,10 @@ jobs: curl diffutils make mingw-w64-x86_64-gcc nasm pkgconf tar xz - name: Install build dependencies (Linux) if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install -y nasm libxkbcommon-dev libxkbcommon-x11-dev libwayland-dev libasound2-dev libudev-dev libvulkan-dev + run: >- + sudo apt-get update && sudo apt-get install -y nasm libxkbcommon-dev + libxkbcommon-x11-dev libwayland-dev libasound2-dev libudev-dev libvulkan-dev + libgtk-4-dev rpm - name: Install assembler (macOS) if: runner.os == 'macOS' run: brew install nasm @@ -184,54 +200,126 @@ jobs: name: sources-${{ runner.os }} path: target/release-sources/ if-no-files-found: error + + - name: Build DMG (macOS) + if: runner.os == 'macOS' + shell: bash + run: | + version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)" + python -m venv target/dmg-venv + target/dmg-venv/bin/python -m pip install dmgbuild==1.6.7 + AAF_DIR="$PWD/target/release" \ + RELEASE_LICENSES="$PWD/dist-bundle/ThirdPartyLicenses" \ + ALIGN_SKIP_LAUNCH_CHECK=1 \ + ./script/package-macos.sh "$PWD/target/macos-package" + mkdir -p installers + PYTHON="$PWD/target/dmg-venv/bin/python" ./script/create-macos-dmg.sh \ + target/macos-package/Align.app "installers/Align-$version-macOS-arm64.dmg" - uses: actions/setup-dotnet@v5 + if: runner.os == 'Windows' with: dotnet-version: "8.0.x" - - name: Install Velopack packager - run: dotnet tool install --global vpk --version ${{ env.VELOPACK_VERSION }} - - name: Build installable update package + - name: Build installer and update feed (Windows) + if: runner.os == 'Windows' shell: bash run: | - version="${GITHUB_REF_NAME#align-rs-v}" - workspace_version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)" - if [[ "$version" != "$workspace_version" ]]; then - echo "Tag version $version does not match Cargo.toml version $workspace_version" >&2 - exit 1 - fi - exe="" - icon="Support/AppIcon.png" - extra=() - if [[ "${{ runner.os }}" == "Windows" ]]; then - exe=".exe" - icon="" - elif [[ "${{ runner.os }}" == "macOS" ]]; then - icon="Support/AppIcon.icns" - extra+=(--bundleId com.align.app) - fi - args=(pack --packId com.align.app --packTitle Align --packVersion "$version" --packDir dist-bundle --mainExe "align$exe" --outputDir velopack-release) - if [[ -n "$icon" ]]; then args+=(--icon "$icon"); fi - vpk "${args[@]}" "${extra[@]}" + dotnet tool install --global vpk --version ${{ env.VELOPACK_VERSION }} + ./script/package-windows.sh dist-bundle installers + # The update feed lets installed copies update in place; the + # generic Setup name duplicates the versioned installer. + rm installers/updates/com.align.app-win-Setup.exe + mv installers/updates/* installers/ + rmdir installers/updates + - name: Build Fedora package and installer (Linux) + if: runner.os == 'Linux' + run: | + version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)" + ./script/package-fedora.sh dist-bundle installers + ./script/package-fedora-gui.sh "installers/Align-$version-Fedora-x86_64.rpm" \ + "installers/Align-$version-Fedora-x86_64-Setup.run" + # Artifacts drop Unix permissions; the Arch job unpacks this. + tar -czf linux-stage.tar.gz -C dist-bundle . + - uses: actions/upload-artifact@v4 + if: runner.os == 'Linux' + with: + name: linux-stage + path: linux-stage.tar.gz + if-no-files-found: error + - name: List installers + shell: bash + run: ls -la installers/ && cat installers/*.sha256 - uses: actions/upload-artifact@v4 with: - name: align-${{ runner.os }} - path: dist-bundle/ + name: installers-${{ runner.os }} + path: installers/ + if-no-files-found: error + + arch: + name: Omarchy / Arch package + needs: release + runs-on: ubuntu-24.04 + container: archlinux:latest + steps: + - name: Install packaging tools + run: pacman -Syu --noconfirm --needed base-devel gtk4 pkgconf + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v5 + with: + name: linux-stage + - name: Build package and installer + run: | + version="$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)" + mkdir stage out + tar -xzf linux-stage.tar.gz -C stage + # makepkg refuses to run as root. + useradd --create-home builder + chown -R builder "$PWD" + su builder -c "bash ./script/package-arch.sh stage out" + bash ./script/package-arch-gui.sh "out/align-$version-1-x86_64.pkg.tar.zst" \ + "out/Align-$version-Omarchy-x86_64-Setup.run" + mkdir installers + mv out/*.pkg.tar.zst* out/*.run* installers/ + - name: Install and run the package + run: | + pacman -U --noconfirm installers/*.pkg.tar.zst + align-cli --help >/dev/null + printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | align-mcp | grep -q align_sync + pacman -Qkk align - uses: actions/upload-artifact@v4 with: - name: updates-${{ runner.os }} - path: velopack-release/ + name: installers-Arch + path: installers/ if-no-files-found: error + fedora: + name: Fedora package check + needs: release + runs-on: ubuntu-24.04 + container: fedora:latest + steps: + - uses: actions/download-artifact@v5 + with: + name: installers-Linux + - name: Install and run the package + run: | + dnf install -y ./Align-*-Fedora-x86_64.rpm + align-cli --help >/dev/null + printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | align-mcp | grep -q align_sync + rpm -V align + sha256sum -c ./*.sha256 + publish-release: name: publish GitHub release - needs: release - if: startsWith(github.ref, 'refs/tags/align-rs-v') + needs: [release, arch, fedora] + if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest permissions: contents: write steps: + - uses: actions/checkout@v4 - uses: actions/download-artifact@v5 with: - pattern: updates-* + pattern: installers-* path: release-assets merge-multiple: true - uses: actions/download-artifact@v5 @@ -239,33 +327,28 @@ jobs: pattern: sources-* path: release-assets merge-multiple: true - - name: Verify source bundles for every platform + - name: Verify every download shell: bash run: | - for platform in Linux macOS Windows; do - test -s "release-assets/align-source-$platform.tar.gz" - (cd release-assets && sha256sum -c "align-source-$platform.tar.gz.sha256") + version="${GITHUB_REF_NAME#v}" + cd release-assets + for asset in \ + "Align-$version-macOS-arm64.dmg" \ + "Align-$version-Windows-x64-Setup.exe" \ + "Align-$version-Fedora-x86_64-Setup.run" \ + "Align-$version-Fedora-x86_64.rpm" \ + "Align-$version-Omarchy-x86_64-Setup.run" \ + "align-$version-1-x86_64.pkg.tar.zst" \ + align-source-Linux.tar.gz align-source-macOS.tar.gz align-source-Windows.tar.gz; do + test -s "$asset" done + sha256sum -c ./*.sha256 + test -s releases.win.json + sed "s/@VERSION@/$version/g" ../.github/release-notes.md > ../release-notes.md - uses: softprops/action-gh-release@v2 with: + name: Align ${{ github.ref_name }} + body_path: release-notes.md generate_release_notes: true fail_on_unmatched_files: true - body: | - Corresponding source archives for each platform are attached as - `align-source-.tar.gz`, with SHA-256 checksums. They include - Align, vendored Rust dependencies, FFmpeg (Linux and Windows), and AAF - component sources. - Dependency notices are included in the installers. - - ### macOS installation - - The app is not Developer ID signed or notarized. If macOS says the - app is damaged, move `Align.app` to `/Applications`, then run the - following commands in Terminal. Only do this for a copy downloaded - from this project's official release. - - ```sh - sudo xattr -dr com.apple.quarantine "/Applications/Align.app" - open "/Applications/Align.app" - ``` files: release-assets/* diff --git a/Cargo.lock b/Cargo.lock index 7d52cf2..3e034d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,7 +44,7 @@ dependencies = [ [[package]] name = "align-cli" -version = "0.1.0" +version = "0.2.0" dependencies = [ "align-core", "align-decode", @@ -56,7 +56,7 @@ dependencies = [ [[package]] name = "align-core" -version = "0.1.0" +version = "0.2.0" dependencies = [ "bincode", "blake3", @@ -73,7 +73,7 @@ dependencies = [ [[package]] name = "align-decode" -version = "0.1.0" +version = "0.2.0" dependencies = [ "align-core", "blake3", @@ -99,11 +99,12 @@ dependencies = [ [[package]] name = "align-gpui" -version = "0.1.0" +version = "0.2.0" dependencies = [ "align-core", "align-decode", "dirs 6.0.0", + "embed-resource", "futures", "gpui", "mimalloc", @@ -116,7 +117,7 @@ dependencies = [ [[package]] name = "align-mcp" -version = "0.1.0" +version = "0.2.0" dependencies = [ "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index b1e1ef8..2f74a26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ resolver = "2" edition = "2024" rust-version = "1.85" license = "GPL-3.0-only" -version = "0.1.0" +version = "0.2.0" [workspace.dependencies] align-core = { path = "crates/align-core" } diff --git a/Support/Installer/Align.ico b/Support/Installer/Align.ico new file mode 100644 index 0000000000000000000000000000000000000000..ea0b36418505c11a361b14b78918e3f83110c6b9 GIT binary patch literal 54619 zcmbTdV~{36vj+OUW81bpJDwfewryKGw(Xf6@7T6&+xCum_d6%z-t*(e`Efd;qU!0a z&g!Vh?#zBN8vpdseK?VR&FaQ95|Nq9k^Z~r{Y|*=z2b8@jk0-U)+utVOxZuPVRCu+@nF@!+0Nx9 zqs}_P(4}m4Q2T6zU=2PE41>hTyX8Ti?PU@nePrJ?{0m%NaUiPJ7d6EWbCOhoGs ziHP2uN42P=YG%kCHr6)K$1{@BZ(2e`%@u}V7}BT;{ zpLv1t*|8`LZI1go z&^S&Vdwm@AP*M;(T!`ikYZHDwzs_&ILF$L*&HU^DRsS(VNl`hGY9WKb{~zNg`X?s( zFXMOi@_P6O{?Eow^goRMKUx8Casp!j0EF>>j9&w~Nln&XRTRr_y6q(UeX7g$wU)FBM1_`}CnP^)5Q+y%TI7+-$qy z?2RJdwc6Cgef>~`F?07vU=%?$-aK+=hodbDXcRpxay;!2DchGWORMw5?q!6k&NrJF zGYH5`^&KlW^_8LRR(1cfcJ__lc)5D8x!mPx9_!9fI8!S?5U~BCk&O?8s}J z+FZ9dZpIK9no>@Di&pRrL$RI-YF*7VZZ8;Mjgg}~k zw#JO;qNTQrrFya;g|A9&S!#OOU*Bv#PfS>qQl&;aJt+~gQoPq=>mYCUFjP%(gD06l zXLD3Y6v|wlK!QT$or;HDwvAgKsp)J5c&sXc{US(y8|4VFlv&|XHWJMU-p!A#{f97U zhHsW^|6IM&IK1OCJk&-)Z;3LuG}}DI4UmWsX1W<(cU1=wiP&a^@>=@4R*)Ux#ZyC@ z>>@?su1T&IYiBvIVJyrT4XOd%edZ{yPEY6!xgFt|gu2-H-}KCUi(V-VyaWkNd@0Dk zP=1quqfoDOlyUG-q0y8np;H&2uXAW*gMg%f>e4@gZYKGP=}dkmPJGO`IZ8rR|Pv!L4;+PVhv64?51D~lYMWo1PHr?I?X=$dc&2W zt8Vik9)8lTfGS|QI-}n$l)Y66NXAQwqsS$~A;ow%OLuV%s%?wT7wT_()(jdoy79~B z1KE5@Qja?NlcT~&nt(6Joya;IhmY!u=TRKwMd}iZLLz%z$V!o9Cm1J5-G&dei}84L zll`}Y1o$&V$2tv`)LnMbKCD*U-zS8*%oBqimOp{!Q(xpRdNtZI{AuNe_3rtTq?^Mv z9x7lZx!_hhg;2Wp$O`W|2($7s@3D?iLJ}ZkmA#v59h>o1nlCyXZt%`1sYzM}>5f(_ zcngw3+A2TN{c<(+euIE5sk%%{_CLd$=`p3JGuid@PoCVXaaQL6+wn&GbSHQ!O;B3# zVy|i~esE4_a|L=f!jMHVK&E4HSlF;$pPD2+RHw*OG>JGEPyew|66jr$^4FDDSNQX? z`irPfV&R0A(kNpRJ7Y}ppgYtHGMnG)8Qb5gpKkFDjo0>XLkn5r-e2S4j`9&I#!M=v z4RJA&ZNPzD$Z(|2G>DZRHRj8%M|!3VR>D)M^WaeKADUV!opJC!SXIfpv4?i6yZvvUzLD#oXZ6#G(5x0!kyC_w_I$O<1q1 zU0F56E3`zV(FGp@h z@i79dv@?xVj%-Mjm9eogW6}}O&L{55E#RjVGSyP+)W|XC1|cRUW=~#5#+pm_H_#`; zp+dG-aq&NH=YMf_|6)e~(|8mAmr}vYgO*IBxcaXrh{94$LDwUz{Zc|^j7WHZKW*_E; zl`pnTf1`%LC#fcseHRJuQsZWjF?WUFS2g5W%w|^w(G)E}b4_Pn^?p1&w0XuFVo%!o zfWFk#<=;Yh=ElYkno0NY-jRvI_eV&qmSuq4tevI_^dS8wtyf`&69}05(LPSAjL-Hy5ks%i+7VTE+3=mM~Rc z(!6F8H1+qZyPdIY1F5sH&A>yo{B*Cd7GKAO4f9JsA&d9rTC(L0 z)3UjC!ZJg4qTgFdgbha&Anqcd@5|M6)MM_pXLFuk(S*be!cYU{(^cEp>9kkVR#g?+ zwA$a5G~4#3WtYPa;kiXr%aOWe=7tnc&e-{*c48ug+yYF@K63()ceDrR@j@k%uC7QL z;*}lSSDM#e*>G1gO)Uw+ z+uQ?zV0yU~!cQ$dQm6g zQWfSwe_Si3_kWqm)G%Pd=xlg>mZ2w7F4h6mTR&tq6sS2IB-tf&Xd zoo!W+WgkwpwQ`v`iF%FhQ-_X%;jAS;^W9u0jQe9a`QQF3i#&hOQ^jziSr6oUX$(6y zaZErm%gGt|iyI#-#dk&~THOb9Kcf%FSygjGLh{Fp=Po<1NP!d)0Skl_q8f@mwDuq} z;)3$!-*%lfbrthf{$`m_o}?a%3^vnF^3BT7ZwbaKU&`_*+~1z-T$cLQGsOi$Ll^Z6 zlPx}{?~n;Z%0T~CA&B2+VkD`z8dXrGv~3DdHCt)585_*v|Gg%dYXarOhU~8ZF0Y2Y zlKz7WxLHg_)RnkXaVmT)P#^#x5E21wfyN^z03|A77HK-+AR@vP9|9-&0WRB@3rA5! zeu?ekG?K^lb36zzdixYM<$XN;M$6d}wr++4nFsW6YYr$3Xo9Kh_yh`@PZD(0YN^lo z27>uo6E;qEDX#%bBd8aN4Jk^6kmU=wsWUEB?Bui@K62h|?toO!6j3f|4ZINu9UTZ^&uvKj?4sx%wC`FA@0 zFbii2___8QM=M@#XmN+H9)P%P0okcTHzv@zW#HJrl^Vma$&UIa&K?l0kcU&1Ki{yiIgu*)PFNni) ztC=tQ3{gb`83>714JykGG#Cmp)nbQDTTRn9F0l7JzdosQKPn_^-=U}yYo)pc#KRt=jTpL;ChdB&k+_%+ zL6dZjoxq5hinYX;PY4s&24{Q_RT`a-hgY@k)sDdsrz5m&iC4v0E48YXsm*pHQEXt>4pduvBkp(XN=PcZ|qPRI(WeIf(O{!EqU5A*t z8$XH`W&KE;qp*nK3oLl#mJ?7UDJJWU`lz4RL^iWrNrCtTl()a46I>Mf4-+R5!%6Iq zTQ7LsU^q{m`T^11J|B4VjWgy?dXqSmsv64#xSPJnb&d9}`}ks)RWvc<7#R1o5`1l& zHqjvT;^NxYWs;shGw+#czhBx>Z>%PECp3bwdE=;CzZ=0PS8hC#G3_2rbp;Vc4;&k) zSXoh>s%LD+!FL^VM$a7xxE6113Yi`!(ZEC3%e2$~Fp_oY-QXE64wF}Lay6Q*tQg=6 zfc9EP27pon!g z69tpzu9P`NLEZj7xw*O7g>=ddqz>-Yh0FfUlW^{S2v6mQFn2qY7q-St(2fZ5 zmFJW7Gz;kM0Yp>|Gs3npr#kV+cIBwiNLwKmk%T5OL1!_E-73|d1(aRQ7fT~LNXQMi z-HU}NZ(ihA^+W7hfBJp@E-o(4OJ2Dm${#yAI`WpN*a;n_Gyy7IvBxE&Z$T|-H-W+#nA-Xl0lKFB zX6jcCiYD~^gK&4I>nQ{3sYL9A=kR?vuZw)4h9KK{Qd}=r(i~?$A)MlnZ_I%{2)me1 z+RyqAyoZ459BHxt#M1vu1TXMUEbw0ue7U9L_CN4{j^G9UM+E;LtpL*ugUEkGaQ}ZI zcn{!K)<)cxD*DKCTV9*|M#foVj*x6na;OR_Ndy?Ul?Tx-p@BqrJ};KfZI1e*E~>B* z97;UWVm@(zSUx60fCKer7nA75mW2!A>^=LNAle+`_H^HzSq z9+lr8W!B=u4Rqnnxk5`Al!8GO+CQRnW}o)|txp};8YgVp*xCay&xD_0ANgKQrg^@% zUiK)^YJWweUF z8HH|Y1zWx9=}JgAVIaHy>`~pC|9HE`tLaj3yuqt#GqxwY9_m?SXL%?d^+%tK+1UJ9 zUgHe|_`I~2x^5Fs-TXZwRjqZl16@ph!m^e;ahR>Gb*Ya~$R7lz#?;}m`pV&AoVj$V z&#Uj=W;Cw*qCb^_@dgK%eADtkYqqmz*B9htN zbSHOwk7%mXJt663@rHhN&wuD_{d|>J>+&i)|N5|4o0_5V386wJV5#L?mg~}YX8i0v zj3val$@_SMcMxFFpoo@u0o~4?z32dEL7Ap%P?>M9rccZEJ_{LijP8Ea+WI^Jy_xM6 za503bsF0S&wR8;lyWZ9r%L}2uKbjQa4~a^i@+yxCJEcQ>gkj#~p8_B>=St-SH#b2S znxi&-y;!}7z+Dt?>CzXcMY!im@E5^*7t+$j<{~YwcZ+bR5swl3MQ={vV{NwMZ)sxo60>|?r}dGPq#gJ=IRZfz|}fA z`zPMYESE#*VmY15?GOsK?9Xw4fRBMBHusm;%#9}6BZ3#nFtT0cHq zNI-1RM~Ou&Oe&V>ez*A$&;>NUF`p?bU2`^u<%q8>J^>UmFlmuGXD$ze%Snb_1a#!6 z=9i}HFW-vm?z^eRj%V?V;W5KVbx*LJ7AHE(1+UMgOyN6k%Jb_T!qE#S^gVI2-So19 z;7nf*bXC?A9BrpRvd#=_t1fof9tHiyWF65^VwHctx7(9+)bWuT;O$2*Jh# z|JKtCd4K6_;q#w!jQ4bBXy7jUDXl;O$Qp3WX$^D6#7C#&r=ej^6RHm6{Eah+vR&7)um%OZ&)V*Fa!L?k$-v0DJ*W#mS8#%+YqNcC4ER_);h@G@> zF*F3ntQNhim_LN+F4h+o6jgQrC}8qva3QL_T%oAmZfl)l?yyeWoDe3@6}Pb(A*Fike+U{3?uz=l%a*@c>0 z$QX>V%~6(6Yl47P;#uESFUn+%mp`ds=<-}H{=sub>?fJTYQT&$Xha>zCN8W((N2Po zd1t))*bC(RSt0A_;x(;09uX_fB_uAWi;O8aG9R~}moSV;Y#dFFnJM)9IyBT}Ybhn| z2RFrRfw%cfvB1xi<+hxv^?E7`2$r_N7hXUo1FGSnMe-hC4CSpCpfd?4)U)nZTAS+F zM)Sk70Ru09i2q?9XtgG^9~tBn?RP&k$S(CDE>n?`la&wlH*?badZk7^Z(#++mvP-U z#+mS29m1O120H!16zzhLCz*Qc$M`IJX46lP$|Zj3hqcLZi}Qz~#%V`?w&LV9`e=p@ zV9O~Dc}6Ay`__XuK?+Xv#O#x9XBemY;^5sUfI9(2*+l~GQ^QRYghi;gn`YPhsQ_x1 zCM8U4;{LWidTin}5F$R|oFnLR?*h`lp@8U5{)|tdX&# z(H<@~ZIkltC6hW;_g`kygKGm(0|(E0w6Dk0dMidgvSf;>syGyMTA!-z^|d@}RY^tU zy)D$vEx5h{)S&F;24grntf8%a;|JFb&C;Olx{}*aKr}tFaw#PFyD(pk>$C<-xxvjD za;1e){Tc>ggM93d)YW6?&!doRg#0;2yg1k_kEuOb+rvGm17{;9hI`m3)#te}@P#^+ z@^)*txHU@@hb%J0n^ds|Z}38At(`2&b2~$Zqh=YayjiUSbnFluL_GG@cr&|}g0Ql+ z=7EcW3xd`YR=uAN09O`yI6^E1S+MhDw?T+piJ7{ca(?dhiNc`Z2^ZlbAGMJ1GRt`@ z?jGf1Vq+^_FEN3jph&tYOS77tG|uh`bkm|PJi~;d}=`DnIV9kkWCY zXB)$2A*lG5Z_XBd%e=U!Su=`P_QJsV>be%7f8 z)u1dLq8A}Z=E!!qktk7mkH|JVAeo-VL7CeGER+py+hMTX)zrFn!?Qm&snGirkUx|W zjKr9U&eXW{9^9a#uI~ZIDb8L41^w08KVvk~#1{13mDlZ$A75ks(JchHq`rLKTnz&R z+a?R>o;c0bs~2q_3#kIUsK(+p%83Pb{)v+8;VH`xF2=J0B%YEp0Pv2rqO%8tTc6Wr z?NaV|(xl4jk%wgw@$?>|I5_DzG|_GiF+Zu{7#FET`@brKqD;%Eash5eHY4*lql!6c z(e!VC-lHJR`Opi<`31yE7N+;dtBvlsQVyEf5ElauV@(iHZn?R>w8Ao2K53F43zI*Q zG9xU0Bof&EnUS59zN7a)e7u-kVd(J{qYUEvkg-28nY}f|rG_%VW2yRKjjOs{SRrYy zuuU@3dRm9)nt`0iwj}#jC_G2a1#7fHhCTFGo19mRT@bi&v*>7E5MQ1G;8SLDuh8&~h7Y?CX)17}4DF_lpQ`&5^J+4aUDZXwUUp_QtD) zWvmGm|b7{iw0BZMmJacA;(GY zH;5s+C70p>f$E&8(0pKbLPBL$SNa^JN|$`}$+PZ5JVyMSrggD-1j+wX>c zOM`?kG#^7%QkQgLb?NbXgol=1`awV35TW&}Rpwm`3@baG3pjHCO|&g}IrNrpM`*w0 z-bj#n$jk}}{e0a3pc$#t6<$wucIN8`hq|LN%}SxE-qJ*A(_Bq7L)D?0fE`TXIs$4enshz9GXoH z6K6z{&hc6-M>@bH4Hf$k65>JXUf{#Vm=*eKD(l`(=l9QQa=~zSHf|UX zW@na6tuaTPYm(Ns9yBKYNs1Q;5?%OHd39LKVNpy4t&Sz514EZU+niuytJ zJ=@zes2mxo_rcy4Uu{J~UaMm$GyIb(N<+%@zTDjO^q11*esOHRcO4@Z<2wG+JB@YW z=69k{E|~s#a33OTMoG(t2`#Wjp0bW7H5!dRIJeI|Q7b(ugd-FLfURpF&f%O>+lOyVno(~$6uJ^C{gdMGmxOTg zd+&5cK{wvg)@{6>CdvBxee~H4n%j*!EjPlJ_1MUl1)}YFLTqe$KCHE_;w{yWp8JQ9 z`IG4!9t}DS`?>Y-PF#kd9XOvJsd!DD@-}Dl$4B)GFROBcgvA=49-scQxHf4T?s6 zZj4ksH7$)0XScy7F9Ipb5M#n^&dtlYqb*kFfa*tARVIl&-I;B5i<5ivEr$NC*fw8hNEw)DctN{;}z8B3xxcaioIvo_sH z*$3;jw&$r{>v1+SJ2v^-$(|}e*01nKFBxD@|?V*mf*QU>@8C;ox| zGneB3Ke&|tXa!h{QOE)SboKwurCjA$*V<~juAHo${411s{O;g zPL+&KRtp{nr2)6%vq%|ce1qqIeaY9*GF6!kE<&B{FY+KG#y>?u3c#p^2gx~RcX)q# zpMN;un=vN*?qNRGu&#Dp-ZsDa+upJ_dEIoJZ5+FTXFNHQX?^8PIZsgX`1qKF;;!x3 zrFlfSMr)$DplV@SSJud}Z}u)N&wtM)i^_9m>mv+SBH{pqFk znpr#ofhMcm>mCtLDPFel2O-o&2hm z^zaaLJ~`o&U6(Ugu_v$D4rm4nvIp9Jt<0pe&0P_b%!2`N`ndD!`F_%?d6tWK?TaaR zBIYp&sEHi$cKyr*2{bjq<%XioZh?DLAY~QXBXl=)L+<*aa zeaE1DF}iFu;gdgEw%7zfioD9BgXOK#gl(%q9le0-ek(Qh)L8GlYBTRo&l@zoX1wKV6bE z1-|y+_D)=Vy;PRO5CF{I)Xbl*x?$p`-Er>1bhp_>Ux9(Ug@qD?)PTc}JhTSH^L^}J z{97LwyOq0DFoAKlCFdnTqLUqZ3c|T2{(Fvw|ps_y$Vp-ER3VeE|`+k3t8MgWlL(*45dZAYHb z7Iw3W^vxadT<8QPKm2uqmd4xPwuo(I5!CGss8RTcklhLY5E%SX)eBbWImDhxPO>IEglFJFVbAMwtL?QDmrhs z9v|+$yA!8&x zi*KG5?%Ci+bmcIotBUP^F3Phy<6Oac=(0Lf|NL0;dwXj8Vvz2kUj6R6v{p7NSBRKt z)l*%3F7o)Yt~iS#)R#-ky((P`iFh#A=g9GUVVpV*n%P4BbrzERcKKz=OM*> zW?+2r-aFCW0ms=JlW&z7AmtZAuU<)1+~We}byse#rwx4-9V={Q>CCS~FR#wx|;Gy$~e~_jJ94$sAuwo8g+{8 z+FpaBEm)%g3WEc75nUNC+eyB9QviO7tJs^SP<4ijjV~vJM3BX_n(ET8bB4Ae1L?Z5 zYu9J{B3_@EnF@f%LCkEZsUER`x-cKScRYLe8URZ-;v`pB9?{qJ=g+%UX^NuB6Xi@8 zte_hFt_q$o&CGHd^^lyTh20;MW7^5SJO8#bjK48e&qvSvq=CBY>ss%vO+QXKHtYZn)muEm#g5|>XXV-Wn1g6@~ai=ma5-jhEJTaIOBd-3b;A0gEiR=R5wt1og^ zGSp>CI<|l0V5CYt*IqrC2okj|k`jrbDgRD!xz(&mSEu4|-g5)-NA1N;bLdPoZM$S} zeBpeC{I($XeQT+SG9h_93Wtq*SWrHBKcnyYk!5CPJ+s>gExH-pxuoo=`#pz)bub!S zR@C4GAR&y}%O&ZFu=L+K*@+MCIiNxBB|a#*;8u7`lCN3)JzvCUoH3utTl)9w@2_?F zIG^I}_#t7+?m7XY6;UD^dTI|5?L=h;^U$ihE!1*CQwRfA2jiphZD+swet~$ID?jh`rz8bH2rpVJG?Eq3{rUu<`nZ)E$#;@ zbZ$Kk(SX&a!HuSo?k#yp3}vq)MA?!M&?$th!b4AIMD|9*!aIBa5-r+wm7i}-O)jsm ze%xLqX?C8)D)#l((cGDr^Hq2;$p`0>2mvEMEI5&(Gl(LhK+4_g+Nx8|KEFNbKWpTl zug(nAPQaf(JQ%yKu9C)biH4IqlutWQ0Gzi6gnE*BpMzRjD+vsRSCeZ?4=vkVNpji4 zn_PPU^u7_GZY<>Ad*{!GG5wy@+_%)1KijAS{LU*XZ*@If<}=&72Ci^3q98SuxU+VX zFgl(T3ZA-({;`7E2X=aq29diVAR2Sh#!fpU%k!VlRmEJs4jin7KO?s#zOHW`Y%x3r za5q-7Cbp=fbrU{n#m-B=dRKJihD#oGJ{Auy&QN>05|^XYudjXd@2HbVpb8K!rJw0b zDG4;Cm030$84z^3zuHnBM!zk)*e0DFIKktz*7|{R*CyGv2s2kd=WLgdIJ?a;J5`UX z+aZI&sOCx`dm3}i=5zzEiqpX6;~4oBo)%}HADWl^5LNFt2Yh*u2Y|;4kB(Bf4U~)z zsEq#OhX-?aouUUxo?Mi<#d&XP`VVZ;6nO711CPfHw`*J4Je;rffr6jM)6W@y5Fjk) z(G(TnXt{I7=HUs{*ilx%aVu%mf`0AR>uc({WgDWnHQ1_y_)lxKxC~@q`A1pC)%+p> z$20l7@i+_+ShKjNQ)C~Fyro4AB@YRh!iEX=*UqBp-MZTr^HoZvx{FS*JAK)Tx;Ptx zCyZ-n8J5OD^}x^=onZ-tZ0j49HVADI@K#%*^>3v026!P&56Q6SOP?GuXk~$ibEtXP zZuVQ<4n+njfUp%pvk_^Q)nh&08ok9?;!&oY^>e(RkrHZ1$DPpy*kk;A6F=BLV(-U!VGYUDa>L}-xhSQaD8q!0eXcoz|PU9cZV@sW58koz8RI2Cf3un zC^d)15MXoh(v=0#$`KS__A>w>*nxu|G`EyU;}83PFV0%N2Bf@>skd*>kd^i6e9xa7 zp_eNEvi4|X#ZWJTNO6(Ksvlh$+{c&$(>MorPF0>w;AC?>gl@WfN)&%@f432y7ave+ z>DDRc#1MD0&+Qwyw*O!?4FaYO*??jDDQDzE!pKT%p>nM_06O7YM0Xclw=1E;Qotxv zEfCM>cZxC97S4F8OCtWLURios?uBGLq%cmG68@Snd4%S2q63(~UPPUzYznd7(qSPB zQHq#k37%KE$d0Uj-+VtcT^2pu^H9!TG2RGR&Dk_@y8%9C$}D-S4~&~A_}LCv ztV$MBq0!9}v=g5$hn3y9c{*&LUJIX%ELSk>6Y;7k-o8c^scTKJ@MA zNUu~E2OBkbb&^VbZ**0h%}2G4Ch^p*e*F1k-yqb`qQIG*ouIJ*#(2LQ)&F2YA{cnY6|mkIMDVqL&lLByJ3x?3h;n{^&)N&G%02J4oY8kjV-eO( z!93&ko`qW>R)nAbA?x#DDs06yI%l4je_r`*H0V znk^Kn0v0)+IK|_fjqYQE|CS=00xaL05+^=8yIRVLTC*umTv`>13}Tey?In*gvxpX1 zqqEWEEWx5q3-LXaDN9+k?!p!0Bs$Q@INN0QTu!L%gbqbmP5dWTL~JCCxB@XFfGyfR zS~%8-y9@cWraz0Zx)^xqG{6ALyj{g@PEvg&sQ?)p{8XS6?*?-Q z8DqLaJr-r`6Wprx$DU%C)C-;rO+a)#oC<45#Uxtg+=UZ6y-qY)dnh$z6)h9${rMX*{ znGbp=bt35~bbr+>Y&}eBRq)?|y89fw(6+~_o;im?cfFQ{zCg@v|GXd_y+zO%A|kdU zGZ<4to-mOq%Uv5JW#SpI`I^8ClbQo4X{K>ggJPZYV;#@22Q&sC3?0fLXTmesf>)*i zVqG(L#ccL~Vbd0=ab{0O^w`;_|wZtWjR0d9yR~D_&=>QE#j=sGb!!m*wRC#c^Qr9;$s7 zvpF4Wa7YW{hqZ_Uq9&sF)+)7?;DDWnZn#9pL zk}O&=NqbjpT>|QmuX!1gV|FoW6@yfCX*oEw;pw7{`~fi@1>UZ`!jK8S{W+b^wY0M4 z#N38V<<1?ScJHJu4HIS}NTGcGuDW^9F^>*Er7!|qipKfR1vC*BcLldsLBUwUP1m6R zy4b{M)aW6cmOip+C)$l1=*Tl=0a@fg<(c5r;yj>y)%+nNHZ41KK&z7N`}cp%z?t<` zDoNVI1ClF$rs3yzEJ+hxqpo&jA^VHm9~C*JBN-nan4OT}iH_{gU7T5d(SdeGSNzS@ z-^UdL5m!o*w3xZ;yOW~uNm=*E0miA}9y$f<&e@iDljbm^diYBUF zp97&m#(E?GV2P0PtNNQ^c}7dw*Qt#l6JO^WA3=@b?IzxJiuf;6PcR5tP0nWXbd9wb z=|YB1S{$^fB}*DG3oG;+6+=0sLkn@B)A^VG6cA%D2ROK~sIm$SSlE&pd8$wzD{Uu51r@u>mBfp(0j69= zC0L#dU$lo`uK1RGH52IUcqjVt#EnuRI(>Ecr2P5GwiNz(AoQywX|yOq1?5_GV6`Rj z!Ly8@Qj=2p0rkYE+MK#q!&>7i^1y3>-rRN99`Zx=r>N)eMHK=BGramYy^vUsQGLt}b%eT>{#k`*5xCX62O0!hc zaIQN}GPKf)zx65|u$6dL405unv`6LpA7w%M(?AGRUTdrKx`Ucj|H&ZD#kACD%bdt< zkL{-wGuN_SYh+mhq~Rn0VrD;03Re^*ozNL*wNwy@lz!KW1>u84>XflZ5~vciGRYJ0 z3vR~Ki+k++L43Vof~(?!sDgD+7q(`@hmAKm#A1`_en=3ll`1*vOBJpue*;hPpF|I! zv7i-i$MU4gGs7zkvj2WT;LQjaOA$Db1791jv{UL@R40;^vC_)=SGN|EZF;A*>>SSK z98}}3yXNgr^R7f&4NxQ0kEX8wH2uH#gIyZCJB4afJWGV(sIGUoPmpqm@ zKFykHr$&c_eh<216oK@xx#R5?7Co`diYi|L+i@WK3iuntYC195z;pWgQVdn9v(4ZZ zJ!NgfSJTsyueQJPtTC8 z^hCc;T(aXbgY$f?8VBsAyM~zvRFKni!j41JGBRb$n=_ZCiJFw@k4D#M&+7d|X7yb= zw~MwB2)c`E60s^eKgu09befgSJqfr{kX8Tu25C0Hk2@6QCK{NnR?;c{3VX2Ot(wC6 z^}C{{q{}}w=nQmap%1lNkQIly%ma&&gWWdODZn6;&xNh3y5?;mv~yM+OpdU-yd_lT zicI|2F3k@8#y_A$#QT!4_qUBJj*}DKnKEYw9d72HImt3PN7RWB69;TGoY?oKAYTqg z*%ZR<>D|6{E~Y+D9u8$dR-OI#P}C;9ax$XGri~xRK%jv$-p>cQWl}W+sfn-o@Ep#? zeqy9!Q?Tt0^Ix7Asz;T!e56Fm7WXptQqrSE3^_Hpj4i(%(808fmREe8Deo!x4|=D9 zmXeWdBgAQ4l~|!pV;F(Q4bfDYg#YWGocT1hDy^$NfUjNbz@O-QMn(v(ej|)i6&0iVxl>6*gNDgBZFd^CQ0M zC|iAz^OtN3RC1*4wft0GBFZNRLo8S6QlviOT_G|bM8@^eBSNP2Eu z-s}$cC(m12;r4|-@b?Ec*Ejwzb@80H&m9_hMLgR=>+3dCHA*Nd51vLfH8GYC5D2@b zzl@{#b&}wjk+U$vg9a*+F;alt3Ijnkvd$8sJ$};q_DB$M1@Q7ST~&)b3D!3Q(>DG|4afl0dg=gVcKSj43JE0j ztlo6B4o)##{o>B!>g4N}faK|)lQY@N3pttSsV;s>es%p8$iJww!PA1rOCLg=Hl?F>g+i zO!;=49)5QNClcf zVb_fHzGnPbHw@k-VZKjyLoClrdl<>nIA(f^hIj;W1txLZ2w5;%l6LqJP;OXRwIgA% zzC~IZ87;q>Z)B(hG&IuZ zZI~^OqLoaGziA8@RDiQ~SZb83?=0i_A}6uA?U}JTiyVu~s^C+1mY?J9VsucGs@u_yt1~-$L(+2F(E%fm&&sbX&*gf1tZ$lG zm{5Qisl&Ry?96=w>I;ogLyFoM8#fNGLGaAS!c+TrkopXY5mblEEk6EMCsVeZ9tUdO zo)REM_CL!^)kY{1hBTMqI2DszLQx<=%whql$@Y@M3f0unn$vP?(*m)`%hi=MznN9< zLWQwciB-H{<3Kg=5@w0B%9xu@Pk>xqub=bEG&*p9luNu3MATzQ8Z%XEsW;IEz--G= zsBw6(F6=p&{hVgS%BdXVq*B5_E`XB}Xbr=kcu<>rbii^ioceUcTd41&z16W#8y@Ys zT{Q(S;fqZ%s@2dtD}}{mD1GJ0fad%#BE6dYM}vnV=Og`rg~Td6=|x!p9Y5a2?i1nm zun-N%k3q36wryv@xe~3PE=SS182LW$+n<7P{*yEOWI zgN2XWm8$*#`J=k>bcDP6!R57V%1v5YAWO1iy37>j(RJK;dC`+6-nlTHxNu}V3nGv- z2ItSXMpT?7&hfhkf%vQ7cmaDZm|%L90eG!cZ(&U0H>DnWXbWZ#cMB)FS7tSMHzw=} z43ZO6AXV7kfK^;RaMnOtUx8)SJyrNXL>X@PaEY+)lsDd_*aSKNiBfw>G=oMKKWAUi zW2EKZ7fEahQ+9>uee$Uf@!0cX`DPav?&u*WJ$PcUvTM$Zv+%D*<~X6|X9WNg=xrP? zl1gY2ykTc+f`8ut&911VkILfkx{^SJGsGS?4LrDPgbb2uwBQ6Lry>PqU!b*G13GJJ zw;e|Z64GGM&mD8#TiQUH^W8)BtuQ!JP{s!pJNiRlka>cXKG5zG*#7@;_7-7rJbl>i z;4Z-(0>Oeqa1ZWo;g7q!%i!+r5*}idRrmee z713@8C(Mw>GpfhemS^8Jg*Ih^b536bREg&)tYv{JKOcL=GSlcB%7v;nuK4+5qkIPz z?{0Es`LqDrdW_W)%3^!Er0InjVD7qU{kpLZrNqiRRdXvf zApfNUkZFP8bIPU2TvaO-f4G;3Iy#28G-nV#S5b_sv^K9?F_~7{FVa#dM*IHjG9uXm zx@DHx@pR&ka{-p}H1vjPc(Oa|5qOqBMxJ}EZ^Il-6^`rye17Pj2S23#j`yjBMh_Cp zJ(yn7LRlz#Leo=m$x<&`jSHFXeXryV&|NpE%rMSe z3$U%UloR3Auno_SjZt`r^pm%;++U`w5Rv z?`+&xddTRA^LMtP&&<1(yRa{Yv>ViN05@6ewj-HDEwnUBI-X2%EeY6uYMufFBepM* zLPhzeSe`?TGUKd`($0<{MMy7`cfi5InwVya4$Vll$2%L2f-=|oC*~O5L5lW&$}#yg z?&QOq95!c}qFXs4OWs`@f7f~J09wk}2X`|?vt0F~0F&n0}5eHky#_UoUsepJp0xbUv>-0I$OBJR0i z@x4x@jm2EP!gRLR38RVEHza}T8~E!8_vc|^Ui~KTXQq93?v{dVcAH&{+AB{0rq_VR zWG*#voMo1SedjJ|aWf9~I$YJbhDh29j17;&>v+XMeUW$P0%I?GFHQW%Xuq$cJKFfK zY@4MKBWX#T^=95hH&*!APohd@PBIvqn`OD~;Wns^54OWWA~?Skb5ygKe&~^YgI6W0p_2qlA=D5wd9kc^%i&il6+Kk0Uen$w zS5{(_NHlDN&NOt3=gzv5T9ZDGO|+kU05Qw~PocfsWw%L-FQZ?0m}R%Ozak`Xn%g!g z_zA>humAo_=PrjXp13#!|0fWElZ&-$-V?cQAd@YEA8hr#b=CuIL$=)=cP(dGCU3zB z(*WAB$Ep-FB&g1wceBnJOi}qT%BNauNT}roqlU%{bAw9Vc+(9ldW4H;EH1~?V|h)-l@_29$p`)+a%nN8 zS@dW7cHp%i{GbD|Sn;$n9Vv1C6zw{*+xXL0q~avzno$%eG30pBZ1MwYY#=>`3!CK2 z7tg6pv$=5j4ci*){pUVjE!6R!O9y3nW@oZSD)Q=Tz7b#EVf|N+Li-Q#xre zq^(Z1SsDeK7uJD9qN-nT>_~^c%+7kbYXa-M?dOG&$Iua`(?#y@Uzxsg%twsLvsoUr zriZwb5)vD|QcA}OIr(zwy~p|QrKD!&=gXEUopKf?O)SjxK1gNo_B^|Y*fbCa0u*mb z`7fDM-CRtE>32D$z<=|>c@Co1%E!5Z|CzEx&xU>=-khB!5DUcSlf-wn{b`}Y@gfeV zG5qE!K@<|6w;B@75;&ccrxY*fPp#K89qp{o@(imxJ5j4|=HeNK77WZ>;Cf)2CDll7 zI&W$+QW5rYRwNIUKksPfjJ5rUTpFUEDB5agfx+r^H)BIHNlE<``v*r7N_n-;8pE?G zkz-tmlHmon=#FFR{2F$y z0|rp~PJOvjFL){WNs-9#Q)%vp*W%OmXk?23p}p-UGi*sloH7o~+75uuwa8EV_ZBJ- zYK84a2)QOSoK*h2j~7?w)3gZdXC$ww7vM*}tn3#J-iiJIDfFr0DJVoUOfbHrc($Wl zd+uS-LKbAm5%j*Si#NZ3UweAb{ek4idw7zlLkc%TNB3amIb0i2mEfS`KW$w=4=|#j zm^=)`>HcktBBF0Dx3Pdn4@yMH@g2ZYW=Ak;(7aG2lah1xFg;l4uN-i`GFtj*ZA3#>zeMc6WCp~6#Pw=C+TU5r!nTLCDcK}l%ca(P zy(R?1buBbyx7Hc}Oj5Iu6f^C#OO-f*b-B4YNwNw?7+s!TrOe+;E>FljjHT0>jf6-U z!*%H>uzBp0Ye9YK4wy4JnJWwMz-q?_*lM^5iloUN+o#Xvgp(dIc$sG_AzMtRozPgp zAdAca;j?Dfa0%T^DMF(8S!#*|RF0jnzV>wrhFuj&q4RG_NHaa49tV?HzH-`d6RF1m zc;b3y9F2~cy=qRNdtd98x5&)MyKMRnjr{t)$|Q0NJUZx*JxjIyAS`*7j$~z->g?bF zzmr*?bJ7V>+r0kRK)a=6@Wd^9wgD>;txDcV$^#OFMmEd zc~;A@PM*=}3COBRj(_q5NsL1kozb#Pt8e^i{a|i;bSXv!F@-o)F*iqnq0*0-;352D zFmm5zxvT}oMw7$`lU@qlS;dl$fsAEew7?&6)GEqp?YG z_s#YDw7gz#wmHT{o7uL7s#}qyN8?VqVDl|y3iHR|OP$WkYg}pO$?F(JBdM_H7~l7BDj4!A0a}(^{DkhKJ9*?xeR=HB?jkF2b@As01nGP zHYW#kJvxS~wj!8Mi zoHuj2wTN|aMk;*wWjY38m7~2aEiKLH_xOMO&O>xJBTQgjFvJ<)(l8qYs}p_&(2n02 z6H(iR)_y^6`?_G-+Y!2dN6Po-zkLswF8y(fhyC-+%*>g2&TOcENa05lpG!|b_0^R; zu2`&fRg1Xsal$q(PrPF{^nm*;y2HYS_tyIkdTgI+>-uva$%qa8;l9bzwZX4n-bBoY z=b)W7!gMGu8QYxcoO$%D=gTNW_gRbPV14ZqAu9420lifo4_5>ar#2mmD;c`uQfG98c+waEc7Bqfm(_iG7uL$JwAOF<{+wY?%>GaEG zHC*uV0w2d^qE3B&B5;qw72Fb5UwZiqLaU*okG4U0>mUaVR8g3fqy7V zAc*sH6*-!{4lCUIQ2w*x`twajJlQLQ^}*=J4ifI!rA*PUN!!Zo`ynJGikWM4LS$${ zcCQWO!Q;AxNcywzeg|`0j3ce<#~-p!&4rp!A_{Z2pA1?x0^VxESaIC5VQVIa#M3mh z_PqQS!p^&1xk}SPtwa8bW%>boZ{jS$bxpd}_U-W!4PMjyFW%-|PC@3fp z#ZmZg{p0iqpaMR-FNv2G6_rAW!eA+%#bEB6B!ead{|>>k%cpV#zZ>ptYt;Q4kB54f#POU+GQtnsMCP)DH^XgW8+<6w8 z*0z2rn71*e8&x-+$1!a!7tl2sWvkN0!$9+AG3NSQwUF(B1;{E5qd zO>^Eg6^8pRfwPK?rw?P3%fG4lUd6G)as8dUvh`51Yl;JTr-#8gc5j3Kq5WOJ=FOM%bqrKMLZ0_og%%y+)rwo? z@ABdemYoAU(4f);xM&@`<+5SdnoC@4e~g7W>aFLg7zfd*{yLKyA9jcXZ{I=o5PZk= zY5vHS&*k6Y*U0xk9p8Xz*Rg%ns^df4qvx>Tph+&dQwoP~u*;z*%s=@8LeGT~p%Ax1c+d!+T#pl|sM=1IrfEgyES@Al$0U67zG} zzuOJPbVs0!6Jl4yLoP!F2Z&(tS(_00a|kMHuE{w*EwDL*a#(aNI+a>#L~-b7_qZiN zSf(b=w;UI)J^sjF-q?zd8+RzCE-)<({>|@JcO#_dFpG{|Xm;=K70=YGgEpPM57A!X zcZ<7^by6qb2N>p&f7xN$g-9zQs$mJlP5Nz;HUMaSNmi{eKZy?^wzEastWLVf0fwHt z{h>XS)SrL3$UN$K7uT^B-d>o01?UN73$DFG@qYemLhDRN3#GFzR6y_h6-e*;<-H=}`{wz|@oWtM}I|PvkbqbPH=j&Oo`;I2Y_X6fP zip<~8X2Lr`aCKB2J{RTZmBzP1hu15vKqr(7u7nG10PHaWx$jLtX)!kg=5}fGDVc&6 zZDxZuSz89-lSO!)CP(_z#S-(=ciX3?r(e$#?&O|FnHE<*kCg^f!lRugRwFP4i}E{o zIcbdsW#?pl29*JAYArO5LaTxP%QjwW)$Be7 zx171s7yasEinqACnaX(IB9A<|7M@zd#qQOu=xzuwbOvK8JbrBRe-!ionVtTh{u2uD z|FYBnIhULN@9O{3843mX|JmvP-4y^3r{r<@7jg&uf9&){m(?uuh6dU4j&C=|mRCXz z%_Jr?8_ysC8zG@_ozoDFQ_TLKwsEI&S!zXT-bl6S_~InlL#Z5RtPbQ2xDZ%dTaHx< z)G!JZnJ+|%ut}KGmU%T{{L&r*$G#?c7S$gY`dvOAJs;>huG4GBuK!}`H&x9eWILbB zSsMbRX29WJ6Ekb$(PWwIT0jHa0fT!QCNt&Ov%G9e;$(*d)5xIxN-Z zzi{lOde;k>Vw*&`HswmhuSxU_M&8D)#lGN}4-zeu$#WGmxce9iM_x9r3I$#ItqMh5 z-dp(~zoZ+kT*|XLl&O>V;Wvey+@OVgI@=nAUlP}S7}*-cUY4wq^>VFz3;*15wP@*8 zP|rr`-Cu3v=_KFqssA8rQ8H2C5%PlHu&oae6e0a74V&*+T6~UR~#H1AoNYI`NK~NtDaFn%E+?B)P)i3TQ~fN z{^s8FFSi}_AM4xO+qG907rvn1OQ3#6Kt_Z>ETnPOA9JI!{?fRE^;FS7n%Vf{IOYW& zet)C><06)r+yCY6a3cRNV(|SY3*aQgK}_ru*A|GrF|c}7s%9O?OWR8?;pT0hRt~|A zxJ$45FyQg=-8hvH=qYpg3n~rHxSt5N0(be997%^+PX|@J%9&S-W8SjZ zj|Y5pR&fdb_I`dJKyh;an{T93{X&g!fZWjzFoe3LC-Cx9Com8H#f}v`EvKeG%A>gn zQ7ZcK$!!ZnK!M7q%<<++h6BWcN0b+OVlVj>yM|jM2XHY4)ofCuiJp8)ZJODN999761es2SHg+kmiR?264VX?6P3$zc!5WzY9lQ zYW!K$9*7$=1@a$zcz-w?cjs!i#*1AG_fb0TDIqJqV*!F+dPL0hqc1g98Jz;+OYZ&6 zSs6a-+f^jr%-GCdc#!@YYj&5%NB@FIoa?SDSoYKgOxYAZ=cl(+GUu-n0)md4CwKav zR`AiX(A@O#gYQW;eqZ{n*#APwao&NDN1XKx3Gig#ZTbE)vj7SLYbayLN1pJHQI3r* zYmEU_(EG+s%X=!@^macA^B_3YU@>i2eeF)p^E8GYSP?VuZft(va`t=G$z_73lV$^Q zjKs#Bz{7yfuwz>Hk_B6sGoy4V2^R#aC0{ht#iqon1O))?OECArVY_1x&93=Fy{6Q< zlb$blm;rOM=F>#Dsvr|Dh-ZRedcKv&hA|9^FhB33@i@;X&c)Wu$}Am_!RVWPop171 zlPckXe{!`y-(gZ<8^|4adu@~m(hZR-OU;2Yyt~hWkQ;aKzocI!G57Y)6;!&my3#fS zj#q_cUF=W2S-Joe2~6U*CaZtSKIEi4@j><|n|6T>aNz0~awV7Alqn5;Z>WG)2rjXM zYEDN8Jf8>KP=F#|ck*z&n@&g`)(%dtgs%ZkcH-+j4gc}Bs#OIX;QA55>1hDwg{UU( zzz=(@pZf7;lq~|IrtMg9;44{+K7pimXE5JZI52I_=gMti|F*DHh z`nFx*Mi(5IV-JYn5+Pid&Ck^IZ`$dG-pJm4z;*HYuNS0f@J+Gfv+U9#@T=2r8vMG? z{GY+Tv`19;f9E&T0hT1&4{r!%oG&`W;H*m3)@XJF$_1+xNYh8IB(Q?14BQ^@cpL~O zFaFD05~X8t1qVoj;Bsw;c4jNK!7+nG1a|RsNIuam<)+6V-rIOt=d)3`N4g*ocb#6| z%AC%r%V7@;AF$5r`r#FWYx0sF_|jo|x#8rTx60hDCJN!vj5b26c> zf7<>HH>Ku!sT{omf6&g?ccID5Is6bwe1Ljd{-ZZcONr2~;Cx=PxEhgP)buwC+>+$M znzr+L%}YqAMSQjr@S@!7EZYD6CQxcQD;-k!cr@NTw>uks-b7GY1F;(NLx1l?Vtbci zgnkpNk9p!D`kd)cAb${ewi^Fe`nX&%@L_^d+DQ!wIv?HMrucUpX9ey{N_hvhj^eVK z&C&3Ko;Q1zJ6}7vv#!D0Q|9;TJ@UfJg&h~EO#uJe`{3;CY+-I#t~P+7NN#iyL>Xi; zme0O6&EB1L-K9G+Wdq6oIO@9QvE`a=v^jrg7}o~W-3ov(X=8kR6z&yvTISwEA)b|M z+ckxoAD5C{8q=K5t(aRLAVxsfhLQ<4)QkDte`TLJ1kt5tw)l^K%eQ8`BJTeS=k9ze z@Y>-=&$=XihtpK_R~LH5L(GFyeyV9D;q3WD;o&5Z7IPz4&L0$-x#X&VR^}UV1Dl6D z`-wf4GXu&|Uy}2wk$p4!>SLC!$T+Y4??oG%r?9(S=hMxB7GDEwQKUXw zKZlL(Lr3oKSQ~ZHN#BnPx5oKW4(lZguB!MZgSn4vMAt9|fNnKfnU4MV{`!ptjUDFy z-X@_3{_v?O{4Y31LQ3;LnkTTi?p``7@$($AcKk3LD3#^IIJr`0O*2 zg#LZt&ZdJt80*s%|2oR3;f60W`CkCAYsq)Orp0l;DnpbZvKWQE2)kq4B?bMdOR+FJ4Yw615kvut973CdZ48B%bnUMlE%K?O5bx$xb zC#^?y?g&;Xe-L}gs*-fxe?UIotB7N4jr*q#K@#J=NiaUUg4Mxl;x+Kz|Gv-TYGbPYQBe+j0IFbh$nkscLYTVl?0&^L3~EYN zvR63cS^8^EWbpHlp+?8{8CNVHc{8Qn?Ry~O@c~OIZ=On&(ayU~LMNY`mb^S6Y@@6B zm_=w+obMNh0Vk;0imu_VyQiB7kAiwc-Q(fxP-~-$HEZQ(G@3R+9il*EN9;l z#6>W?CPV!?@ekv-EZn!B1$_~2wM)X~IrO~$dst(T#g>KPe|&s=XYU_z)6jr`ILEYs zd%FBN0PjIrPjBCz1ezx)mBuv3KXWo{H)?jFt3JGb^Cw=*4uBCq@IUf1R4tZDv@gen zpH)81pJJl52|)cb;YwM{{A;NyqB8}3K(8-c2+$R;H^bMOD&{nHY}j{(VSfwA2xT-` zhQtmS{A=W~fN((KSsCGnOJEkUy>WRl-Gw1B^)_1MSGn~&cBth~B==F#KO^TNPLBzn z+PQF8-><187G0OY7YC?)v;Hv@070iv8*?A!%c3N~0&y}E)+z6e229n9771rZ}Ef_z4&QVo;fTP__ z)?i0)gZpZ$dh06_uzTotv@@CP*H4kOXZVfRCljI5e`4_c%Rb)l%O|4dsxHJ3K<364 z5Z-6La@Gp03@3H}q)J!m1nuZgHxu;f)Py@FldJGCR|_8Tj#ap8<4RfLJ9QET<_|bv z`k&iLO^pcoZf^GObpG{rtA5F<)wAh-(Ot=UBPWTjiFtZYQ7YOxs=917M75`=5$i%4 zwF#=F8T}rV%h{{7Ych@eyy!;>{_!kG8&)cu`}bW@dW!V`={ zMzNZiJdRZe=%l@8s1>yRJhX|=I?fx&&cM7GX1LwHT`qk|?^*l-{A98F0etVE=M7X; zp^!U@UIxmvx@1xf*Y*+k^2P`mp#X%4Zrq58iM5V@5~}FC2gHlymrDnCljvvR(4P6C z-wds5H7^W0hv%5)C}~Q~J`K*wxri_RVaC&4c%KyJv-Lm3kH1)n;O9qT`SH$SFuXJH znK*E?%;dXwq;$-%vV6;1XubpyV8o0_bon^1^XvbGl?)-)TJx_ucu^uN`e|mRp<)p8 z8vm%=p&esYE&G(`LB`qhG$D1W3+J|o=9`$bF#~xpx*v09p%`;UuOE0zxm`qHJyFbrivSbhYAkxcn_x$KWKG5bi}z) z%(tb=KFuyPiRJ`_oTtDbDj^jyM$jZ)WLIDhZI7aAWbUP>qiv=sOvDwkLH>=<5H^Mu zox899-5Sm0zYDi@H#XkWMNDG^q`{RXn-Lr04S&Bm6&o8iB#7AK@OTPf{SNQFbo(XC z;HfK+och*f`(9o}@w)|U79bv zz%gxpv%vUB07m*atOYI0dGTQEp$3R0Hjua4DtCmCumW?G;I(32;HUqA2YQyU=Zxrm z2@4tL8@L5hGGQ}SRJ(53{kfDqMO6#B^^O~Cub8K}TqHj9U1!m;PY`;t9tkCJ|8VV( zsb5^`81@jcI<3F9dtk*6I790-&yWYz(`XW3 zJivzFi+3^_-*qLHH4wLdT9BM2lZ@u{p*;cN{}Xf~5-N-NK;nFpWd@)KLg#3V;llA0 zqVInXc2b8|HFYvZMU(f=(SY2Ro69+D@!0!+l-YB|?<;xQ?Wt|WgeM2`#=q#U-l~EW zdfF_UKIg6p4Pe#N1?Cnm8u&~s9!n6|>j;70CYP3W1f6DHQ)Qf7O^wsWQ|}BRTCQ3{ zd?@)S!=JGvat6&2w+*QWfL{{7@T81bi6n}tz7peF;|H-+Ab< zpVx$uryq0Thrn<<&pk_5s9FAYWfo+YnE^13@&fw^hKZb2W}eo`tNuq5LDKc8JksA` z<`37R?7WJNLKbe=F1l#FVo(6&FnQ8ZxxSR>X@JAxndI-g>Jyo~5Qd-8%MdG)sh3n^ z1;wzk$n450i|2^=+EJm;$f7WA)JpoPx+~c6W8SsFgVn#@HJ)iRa*JmM`(8@lfJXvD ztRU73RB3#R6%$>i?LU5uWVt$fwFV%iekLU6z$w33C-NC+?LwY+J$KcCnjPQwPY~Pm z3BS}~@i1MJP72)(BM5JrD6CuC^tD7Oa%JAVZe-2pc3?lc9O=380mJ z-!Jn}D}!Ucr2$#)(NFN|HcRg3N|iSP(1qkkfOMXcHRO+aidXcdQT*}cv3+SaTFm(`F{e%*vh>3e>CSdcGtkg-IuVE@^zimFcQb{M<&F3(kFNdl7s^i( zcnJDn*Yj@8Gr5jU_a*;Iw$D{IKs6}O&};m^?B5(p5!sD^nfrsDz@qI&&u_!6{WYM0 zFDuu%tzv~UiR6%C{AcjCoCnvq&8iPm0`#W z2@8Rw8dsO406lOrz2M`}{t>qUaMt%hEPN_@#tF=Y1{C`w(2sgKy56+yK09|(5ui%l zmog)cW+YGvkghNsp^RrI|E&Hv!l)CB?%^1faF4n#=# zS@l~1Z?M3*<%F$a5NY%UJ>aqV43NiUouVS=rU?E8t1BAFYvE*>lj&ecujLssBY-6% zG7rX5t(R63cH4Z96b38JdOh+i(v zi9H(Rh7To#rT2u4q@D|uVEBQ`U*;h9n)h+s`xst(5`;eKBvKpMJoEkpER*k(@mHJ< z;1a)%usmGuc(f|L>Zsx1go%koJdhjM;e9doh#Poiet%GQvrJaQ>%|0&P@7}fXX^I& zIIBKd@qY-LYcyVnSTS7bt&R1O)ZU*6Hw_ESy<%eJ)^G4wKP(n(PT{GXzO^90l@@D% zurxcBcl?}z{1dGiMO>rYz&&FzuH9>ewz8W0ze0qp^#=vhMTd|+nH$C@;Xne_|K-0N zsN6RS|F!6W-59QxLo-!S7UJ69aPE0If1BFSkJsT&o1}}0gYB31ob!!fA!QctdAaA# zR_cVzDn7n=z~hqNyODT-Er#>J4}~AqeY#vHrdw0EsdsVelPTF4-;0SYzS%^#RG8pT z$wZUG4X0B=DG5{??Y#|4?q{fFx|YS@8r#bIUp4;QjyO#Lkt{;GS$`i_*}ZnPM{)$t zfMUlzdWu*~Qb*o%Jd+uB3zi{bjvV*_Uk8$p(o;d4kF^k#cTVzmoD`3)t_fCIlbyg} z&-&wI2fr=R`-5YnA87?FE=Px%0b|7p7Sg0Q*w@0ALUFMxZzhq^i`e;mwn8^)geWNz zN7Hu9VMsFbErTYxOeNYAf#>a)I?#`~$08)K{*fR)!Wk7M&l#kx_6Vm$V)4?H!)lrE zf1%1aCvgPxUvy2LtjB$QXXptz0|VEP8a^E_gu6wQ!_|E5fCk9O#mptNvgV#kzM!?x zt5GLj zqcfb;pG7#C?xdqF7bYbN)zt03*~ArE2k(m9>3or2JmJy-NJ;&?D2u;!SxuX;f1+|f z`|-%{oFpLUNwTslCUZuk#C5Qi7V`aBQUPk5`?))$G1iU5Ey=2Ij_K}X5Z^D)r=2W+ zMcPNoG%{IhY@9KG)!kgWXo~uIEdM4c_ZI6?`@e6#sx64LQaZK)t?l%sAjR862|YW} z5-HhmuT1%{-1jezviYezv?}^fqrwjI4<1H%0HM467z+#1P_li>-rshSn(gy0gIahhg-1UoLn82sF0n{AwS7@wxy+OPbHiKq_x&1yP4n-NA}vDlkK8fn zUfD^#FWK5B><7ygIc8Z_aCXtqo=>Z`O$*4>yGVQ~BV`h7lZYGjq-e%Lvwf3tjuP`N zy_JD7EaUHV`OHWKy+~o$$sxxUP%6Mw*RtzmhpoB^Ua#sI18N$32M& z3#3ezN>Zy)qR*fA>(3IV2*U6n9tIt{^t~97?vvH8fsQJaASa|?Jemn)eKPUie(SFk z;*T6(*BkX#oLX)3B5$C#hrpAxA0<_G0;iq?1{uC%6K5l7LiX*O0Xv)e5y8jaS);FE zQ2Y0m;pYs*6AX4Vs7DrwuaEh{Q5uG`3{KOgP#GF#HS*1}4fmV?@j6B#lTk~u7tV}P zBH34lF)mmu0KaiYbzn(@rd0P zMJ0?rZ2@h)WiMV>xE6`wi-IP&M+}yU?Hh*hDW99NPOu{UPOHWGnq8Kb-;(#eALL5% z9@jw60P(+MpZFT=Y$6%i2$mIYU1^>qn3T4L)KVXLH~f2&L+hOdM5{tL!!>}FaGG`U z&VyB&{N)3gD;ZD0nA&mNt`wnIeGwd!$|%8Lk#TR%7x%CdJ(jrMoKUrDVJN18i(%i0 zrTdjXgl5TnMszq!ORMSu0Y6&(;Z_B@Svk6oZ;+pqARiw`R6XGf(w)hL!kc~Vq()!C zvv2WPN}!4;342Xx)^D7j0V{Vy}$IqTF&oi`E^k@x1*y~Bh9 z+^}q$_hk%cqIqq+$OhGD(Z9zaR_*hG%!^N`JV+3Wu!D8Ta8icsV0SJ(f6D+y4(4Vq za#O1nxYJL9rM@+yZZhkn(iE(5BBastMxDFd{lzd(bl<9hX#cin=ltjKauX&vw}jP0 zbbWwHz$+aX*gwLD?E2r=V~}yDJ2oBrJcY$O;u0Gy?#0gKe?$iVE2mF|F6rJ3xQ_7Q z7|Qs*rzPR{pCv4+CB>$y6%Hit<-AmN=fjn`lguak>IjR06MJT%mE8?t|D&!BW0fbt zn|~i$8Q?isvTUXsJunPM5n%C(ET0T^e0>rEE4vW&MShH$(L( z^eitroQ2}&R5Dt~n#pnU6h5@k3g{E$CpCotJVxy45kX^XeHl%CD4BS_*OzLcVEvwl zyV;+dvGTFEJ8IFnAs0tL{P9QrXVcxU|Mm`vb1Lw8*_&Q;1OHSq9!>c8kx$}hHbt+( zrpqOw=s`2?-|7fJx+2e!vOSYo3vC7{*J0OhkU}uW zj!M;u0W>3VUO55{*^Q;nOB0b(aB-eT3|8X#C5gUoSdmE|N+_iwDFdtZqvd7KJ=o;` zj;%XJQP3h=wP|75_d5Ew^F8R96jMFWgHir^bbi!%ZRo~j%>b`f{l5*sf##%Evb2D2 zlJ`tI?e@lLND#b+63FnKZuYu8*jnb$(%sU}WHELNWC zUO8Hq?eh5)QHkvF!PvvJwWq;4ZD;+(SZ?wx-#O^gO!BmcGGJG*9UcS&g$}+z`qFnS zvH-zo2`UG`sMkD}wrO`tH5H)#-DXJke(2~b{^ zujz=$yo&W~uLDlbM-3mb+>oYrux4j30H)wD^^CS(eO3765Y&Hy>Dss~jNPT(gW7HH;_Zm+vxVt z_Mp|*x3IFXcP2=kH8*6}V9oN1-hYOi!HD#v>m(N`i)SzSY1=(WFK^N2GTF2< z?OAPBV9v1HJRRGWV)DDV;cG~-a2D&A8RNUXexFJcoiEJFG1>kRjAbzT|6M>Oa`A_c zTByROIG!|W!*JZGWL;o%AtwVrBwgBQ2SaQ?G0{81JhoRpV=hylw6*&)KsJhqK59h{eH%BB+;LdEbX6sc+>3@%>eB_m$gH2;2F z0fLT~T4szapU^LxD=){Mwbj3o9UnED8VZ0WxcA{@BbOO5?yyP}HwEjxRoy5&-6;BA z9a8PmJ(1sZ=ZT8MOVm_fUQ%L9TDxA#Un}`r+>V`j*a6{f z85(~v&N#VsBB5h#hG$dOp?zM_Po-)Ab%JTN?=N25AP&3Ia*7{g7*H4EMxYm$yIfG%kmJR%Ry3pUxGcWP{oZX~yT z|Bg9>scyiLJO?OUZH|=GnIMOD7V6Bhugo7mD2o+eJ~u4FoPUMy7TPT4_U|oIXn9Ps zLWyQvM`Z$xZ8Dk-ARYM{WW->=@3)2qHCd^n0niAI(zGGuVrMbmy-Eu|YWia^Pp zMOI$e7S*nKVvw0Vc4|vlAZa`n(u6m36JK#7rg%aS*CdXb4Qk0CO!1t%QXeuLAUaZs zaoObURkX3ir;=CyIR9a9mppzj?#BCaThryH-`%BiW;IsfTt?=rD(^hIJ&VAgm5Y0m zohh|Rn7pc4QdPw5SJq4pexL2xt%3ObRgY!hq>-r~w8CTfMP{QOIzo?5o};DQR`JZ3 ztQJ<%1(Zn3`zJ3YK?CdYeG5u?Y&rHTtF0;0j03W!2=oLzE?$Qm#+L1qlf^rx zg$UXV>?%rn!@Mu3c2`PV?Buy)1fM{%6OCC`Al5 zT@7a88Zl@y{tUmx5uaDijiy}dC+{D>3o$ag&;}~DG;}wsZK1b7@r+!^Kr={^oW|Ly zP^b?lFcRuo7TAfh5+Z|ZBWWMmTfw*{{m!JV=|WB~XyJK4de-pBA!#DmT~`gcEcy;; zBW*8=zwZLq8tJJc<-=d=EOnh!Mi0OJ_zXd`?S~g(D5{SQMP?v2%0#oPj~W*`nkR-{qUV34R6vbJf<2`p?x)=j%ojL}J zmZ^6=HIA(@M`%i2sHO^P!d8mbSR=a7ag!jXCR0pBNulne1&9lBhcR7LJ7D9z*aG(~ zEBn4o9Bhj2U|E%_Ym(Qm-rC6y#d%ttONXo((R4!T6nZm9xx*`WEYRiM5!l7zA1W4k zEkT(k&@kLG$_cC}y?wK&*hdIcr46&Bs+!2Ppi{X*)?*ytQW#0}=fh@wACx;94f9Dg z0O*fwIOMGIGWR#mn(NQ{UzYO@Z{6-5Jd&opiKGiCnX>-qr8c49n&nMUxWcMb`I80~ zTHS|}kER@VjZ7xU5}#H|Rs1=SY{cTBcXraG_nMZhBrKyydP>bAD9$d6%Iw2B#Ut5B z!j+J^xc)^=Mmvh_{;T7Toog-|qr}0%Op;8EK!y@!Q9Kd6R|^1>={t&^bnl#=8JrUAwHN83B|t#WzN)Cy2OUa~(eE!lyW3GeMB z>*X&2#C=rxmQqQ~;uJ3^AOAy7;0!)s_h9qEZ*WdgmrGF`fg?_NU0Y92%%oND^GyU- ze?&dhG{uBn;McdwZ!>4k&}d2PmPT+?IO)zkpImwuwhd~eO?JPrL%=PFumZ#7JcnBC zTrX7m{igDZ)g=v=#p!IxOfQ>FtVq$`Z1h}rlvANuX16KHwGug<(sHy%#WvZ-5$)77 zWH3=<|BWYV-A(kCFz8~eA|O96lj&cyc=ws8Fd7Y$Ji79xs#0xq!q~a*(F*QM0A^$6 z_LSb|rzP{ou%z#jA19MN{tKC|r>2>*G&zdI3_Dz$2pN&kR7`>MIzspCpYVa|u$~9q#ON_>(!(~b)%9N;AzuPQhn=4I& z-4}sGLmTv0K9?DHp)uE`+(cbnng>2Z%J60@=z*?3vLyH(bUWV`_7sGYrd)$q0R#A7A=CD>6iCgLP+N zOwJH~NASm3Mj1gyg)!>bIQuskdNci#3&NfWes@u+mKG2iJ)B=9`GawGeYY&-r8IZM z@h>d3^vKMm?4|%Z5%$bv7$0G4xpIHpydj2g!=x00wUocES6vYEMi*RR{;giLUoCXK zAT(j)^*RUKPVi*S2*W#|*m@R!=jum(G)p9Z(peD!vtjYJr zR?MbAzTg(?LVMtp#vl$LN%W2D#4?8!EuR03f&qb=ZPlpM7`Z0;+4aUr{stxu7m&c8 zGb}+Vo{6ftikYRT?8?r{2Vm+c`2}Y~8zViT`}AMRE5|-e%q7kQbk!5kc{+w`To<~t zn1exCO|ISc*p}81zF+Ycf^I>Y6ho&JNuszaW$;3|-Rb+KmMJ(`jF+~1o^N}g&3gw0 z?APyqzqr3bVP!V2GObO=8C#wPwq_Zsk`Q@17BqNXFhh5&jdF(5PaBH=%Su^WGN*bX z5Uf;PA{pK3e$bqz7o`~|#y?<~XE~w4MHc_Ne&p5CUoV68D-FETn`aRc#BO90k9=aR zikiRMl-R1RS~ic^KHaQjby|#%y~wxCd_F57ox+R|Q_M+2eItV|1==}L?90oy#^wG4 zrN7NIKC`+5iYwS@G`fRoITr&6F|M`3|ptl$FpgXBkYGt|RT)EXWXFz$R#>6sn z`hr;jbor`VN=;xg$qM)Lf=1()#mBIsM-{?4XaWC8)J4Vk(V0V!!5ZPVp6mOS!8@0Z$rR%|7UA+Blj=G$$ z@Fm04E+{4cISJ37{g4vCMP`|2=ZK|J8p~eVkoSEg$93~}E*L)TW>Agnu>{}cj9!BH z{{fOfZNKsC5+Ev3P?n_}Wu;EPOd$L39bu!S4aP82I~y0fR)vicy)q`pGJfg+>CjGe z>Ngu|&U2t{d_GO&f{OrttA`2C0pM}-a=^x#%8=FZ626;xT~p|k(42|3hV4b$OBPtv z>=e5t$Fo5iw6YEfY`LziL(n5>i+wgoLMkPmW!bJ&MA<6hb~MXvA23J`CB??vVu7OR z%ZM&B76||E^@7MFQe2v>t+#9V6xrG)ey$uoR$|(Q$z4b+qLX*=7H4yOslaw0te+-! zwuH#)Er#KEfqWJfsdL+XtbfaGshV88R?De&v1%nX-I5o}b_URzuaZ(HB}^81DUh0n zT+=Xq(wUfX4Cd2v;uw(QnM}OIufehrsH^A7j==%t+RTlPc@3!IOLkOGaArw5mos6a z&d&5Y$}YAEgAq1l>dRVy%8E|pqL0v^*fNZ2i$SMC2EDEwJ;kkJYpisHg|VPGfo#&J znUso&=XyF4ht|vvws!oD%nayoUKE&oB&$u>lsAghR+`Xef}I`h)2ty+i-XbhWr_*- zgtIu27j<=l3jkZflN!@XDA?(Ahne&$yaeH;py?X__dR3_I4%WX6CLxk%}jIpy3scqJnl&BBV< zrpq?7cB4cdAR&7mum~)gaC2Z#-TL0_Q!K(jPAIz+qg-O-zI-Q!*|s}!CCS(TD#MC8 zrHp873&TPHdiOwj`%{yEy2`Q!Qv4 zWo~L#z-6xF7UmBuwGgFID;GL-r4F@(5ox>+gKySt-+u%5$8I%y_%)K;u7SUP3AaIS zZ8wL{?SikU-D}pf3Us2NLQYu{&9jsQ7hTtC#}JhLzgJ0g3Y}0!VyCdg>p)w9ntoqF z4NqMrN-T+if>lRlQ($~aEj#g}KXs*QM3NgX3(^AN5P7dUX9kG2{)kOZqe7r-a`8%> zvP_s7Pw@H2KvWW_aw(PhEO}^d@4ON~Wi&QBHME~3IhM=-(P<~zQkd*1vpSH=|DGlD zwqu&eN#^YDw!4ScKpn2*R9TuRn+Hhf=Joc*^CPWzziwA9Tam zg0LKG7Kv1oqS}sz20^(%%z&8PzHm)AGAt?6aa=|{mIPtgCI!R3-W=kTWqb!AqVj@Y zI5K10;+6f->a=rByqFK!Nhiu9O?g?K#mjF3)(quv(VTr44Oi zZ2XReBU0W^DRv059b#mdo~lM8@j)Nwt(Wk*vYQ9^3K5=fhPiK$LS$R4@9Z~Q_uN42 zTm&RZu31j3QX-8u&jGicm4uzK7H16QGJ^warBt>{1JTlHomDBTr|B~8)~KaqMtQwP zHu<5(zgNJB+}3HcF2Jlh#;fDerc-|dIlEn608TOlwQZgpMWeZI4Dzucf-vI=2Qm|< zGC$(U6ilkf#4#Py2Su~Q%h!5j85~#bDolqdl@yBEa8Qu1`b(!{SQK(>Hi{ukM)Alk z6Bo&&xSkVd`wFi1pTk#*!<%tktyF?8|M17gm0S3vIW7g7`tV4hWQelt4VgpEdUc{} z%~}{I6Uho?TLkJL9d2w1(f;S&RC0Bdlt&QtHDz{(AA^HSv!5+gEE3DcO$0jUbiIdH z5_Gp?2}zE%Qxb7vl}+i#zml5*f}Jo7BH;+c21qis4wO8Z$4nE<^vni37LauY8f9Rc zo*YG>M005H2wKcnvoKh~J5<35HN{ zyQC`-8ekJ|$MNNuLZhoM7oA)n9cB+FO090uOhJj4%Q^xlYw;r(Y1+v)$-HQu632C{ zzY1L`SX)Cz9or;O9_)<7&KCWHhB_5P&qTpSsvQigxvuP$3&YDkBFIw`nR#MJVJyd}lYgqwG;biH?;%(_h23gz z_*uPK_k=~SPPj3iT=yZsY@gaVV*|zi8*VnId<%@rKHWuY+8w8 z6O5hlG!g02QFUl`;m6d_fVIFJ7uIWmGnBsdRGdqgeNhU6^GE z$776YJ57E9litMNc38(BJ>~ZW=>F&c-xk341&)vW`vOB}0OcHtJ`}XtMrHSzgEMxi zqc~*~;@PfgRk@Vz|iG4`#8k-mKc-iT0Cy#2-JaRnmC&!E0Rwg z8-GbfW0C4@@=_a{OWYsuVaqoYiRXVfld-Ta)H8wkGks$@o}+jyR3O)4H7fC?G?H6A zGbDj>!4PR0IT1>H=4vm=y}sTjY0Y$zH6YtAB{itZ$n#=Bx)V=OA_-gdgGq>G$lemh zOFN6voXWH3wuXY-#uuD-@53(#w{Rns?}r+eqaEY>0{buQV!LGh<&d=Z`Xv|DjPaL? zL#a4c$1xdkBp>pz^gRGMD1$cV*rj&zmLfAV)D>j5NzLDKNIBQ`Nr8iaP%Ls{#X)4{ zYz2aJF;G~fE&tTjCHxJ)Yxm+0SmL_(==jiH=fo#_o14#X=1uu%ydg|;iiTW z9uY~$q_VoEgC}7$NOVmUdBPhCFx4LQM^sRDTM#s?0IAP%C{ZUfLC~ICN`Vxs%^@^- z>c>`)DD?fM%SX+9FMqDtJHYs`9~r@dF+;^ReBF8Hp6Bf^As;?*B`*Tqe(fRWejn2t zll2k1oM;>AsFmLXh|E>f>L+jLfbQ*RjYu7n2XT$eSRl$pfcjbpY_KF{*_3#k{P#a} z(AM+^ZFB0XWcF-Ah8;BPBS}gOwm+To zlkX3_;)UiAW*m~ zfddvv$(U0umy=#;YGqSQ3L&E{|ETIN7N(oWH~cRqb4KK$hqgfc2l#`V8~5VNHuxhW z`c-3qNo46c#TON09H9FaQMsju^delVU8kg5=G5*~IhxxL=zH&TU=*9JTZo8NnrD93 z9@uL(c5(JKGWogadq`XaZZ;cOxGzeYdjS|am53D{tA15RCN}Hv2-Kn7()Zr0Msw%P zR=eP7GPVl5raQJH|E)bc-e*zNHJC$OdvEVI_q~KK$5=OKT@ulCq)nC>zHqs2snY>d z0ZO|Vc>4{W-KT*!WOsy_zkS~gEKt1DKcofU5K-FEATRP0ZLuqsBB#B!s3#utn8c5~ zlIAPnYFgRHoHF5v-!6s$ZKVEl&V2*rb@LT`Utq9Z5tp6ug%H;9Cglb`HFi-_X8}+} zl@C>7OztusE!(ataRw#U%Y)K93R-BM6(lx@Ob1@+TjhHnz)}C09pxC}TV8~4ghk*o z?h)+l+BtYfRR!~1#6X#1wM8J|T9#;!S}0!P*~S`9@A*eYEd@K0+FXb`4H$Ggh)=VK zZ6Djes}D6WY;%CTryi5?9K5s3V&W$KWW+3PEm&3uPWE6-m4+4pQ({wenu8ZsvP{@^ zC~m#`%Rfn`OgP9BT%N>=OZggZ2wR^6(x8QbyjnIu+@nE%#3VfONXK&2n};6581o@J z<-MDL5-4X-y3;S3GRdP%Jod|m-BD+SYt>@cdGo6i9GiFH)ktpF!4FV|3uC{Zo17pc z#m;Hn8Yjn_Q1SQb_w43gzRRDm8IM`GnbGvMjvGC@*YL54&?4N=gvc28t;z z1TtZ}sh;+C1MzFUBl{A8S_eo1lab|V0fcP(3O;?Eo?uAD`=f0PLU4;ImUG>sLZXSq zzog3~oSa9Hq%S??uF@)6Y^kWeG}9CWMefbGdDmMc%s7c#_*OsFxPRXtDun~~fwvjz z$UebX%o}yFu^(fyBKc%8hs`E_cfn8$qcljy@*|(hlh`ux%3M0aV3|5)D&?_EU6v=^ z1S}1sqneD@PWRl`G*_-P&9l$?q>eVJxs0Wb<~8h9=->AM18cQ2e&R`A#vBlSx1Ahc zR%CA`W{`{%EalHUgCHx()WJ84u358bf6TKq^5anUnQSy5G9CzZ5_w6j%>}?g$m!1p zA#IA@xq6J~qbo07)8l4A+(WikWxswc=W_pR5`al`2vTKa&3f=K4~S7 zI>}@sjHG0CQ0&-y5M5r-@94^zatetj=h=?6DwwiC&bk$$Sx~$evw_3p5$*>aVsf)v zsAotCj&n~NWjy#Psm+v&uUvfP;xiYo%%hHI>8M=hQ66bjY4wQ8wmTB$I{m7&%3klX(kA^i4cy*~CJ?Ge&*%gH>YLHlFcfqn^p9 zfR6=tO0pAK02n(b*Z2JyK4V8`azc+Z2p5d)ou>KT?{~W?bthbe^aZ1U;Fhq6X2t_fuso?JB$sj{-dhBW_$nuKmbWZK~%gw&2GEE z>;h(4j=g0V)CFjZ;Ko)inGo4275Ee}7O99`!@Nx&z>F zA1^)KxYQinO7Aa4?&;)H=EO2S*wm4B{n*TK4g5q4boV;^an=byt3KYVY3*Zkk`Z=&OIzhEDWg%a(At6VbW9Ghm5%-HZuEZVp=hLwzO z`aYtp>`W(R%RIq#28raYls=qWz8jF(odrZOmF^WttZ(!oIdj8Ra7mzIj*mntC_duR zRysi?p|qJ~!j(jQpt^CebK^W+<3SoZZDX>!VnT3I@Zp5l-51YR&(An`1bygbcp>W$ zlb?UeD_sV-UR&PAOEv^Lc=58eKYh}-;uCHe3;E=Ey`0MBLRszRxL(g~yee*|`&bON zcKih~eLct)0JMdj0oYAg*A=Y#Hs*@c;!2Gg>!27YFZnD-d8xy4Oi@=@k!NQBVkjR1 zm?e*PqG_{-N7S6~-}ODm*nHrmCrWn8PkA|n}ZYVOIFpV8C{Nn}_y3fiI2 zwTc4;wCs_KEcK&=I*y!h5<`5$z|L~$62GNIxd+OGm3>mB$4vT`xq{k^EONLpMcO$& z0iAXQt22CLo7@OK7&(#om`jhkc>ksXY!lul2NnRv%v0q6m0V&$z>7}zKXL;zD^30c z=hgImQieU=wA;Oj#g&%=2qHz<*0%E*{+ZeUbHuD>}+75oOzNZ8)izh zaWBQ>j0y@a>AJq`R7wV^m}4}X0FW@Kow)@BWijz)7jN#WUX-zatxrJPq`@T6O)~27 zjG;_)vxU>;tM}e&UcBKQC)dhL8`g3M_X+T`JXN?*fhGLWp2I^ps#`3ZPO@g-D*gz_ z*zM?!_-#SMjN#GWbRnZ$8|}!SIR=9Ry;~7j{s=fSPLMve1z=Tmu$x6_^lR4-aBpEB zufXDh42^z#yx!b+VXJxe%Uigd;9yc$*FERXPHNJQ*Zy#0$z+9I4V5a-r2B;0bLGw-nk_(UJOL*ytug~UGcif5*_t45!6_peO@oRZa2;Q@z2||==BdwLX`X%(Z|w13rKe@1wvEkf>dB_VXQz@8 znF1g?@<~HKWhP^M9i3p?A%%mS6EY@=jF-SWyr`UM*Wg~d%B7-m;V$cEJyXOH7-s;N zu>k1qH}274(FHy)2LQs!zJ0xU* z!2qOTK$*GGNwc(&tZQ8_nQ!i}8C_Sivw;CoW^+M#=9yc~=RS`gd)?&UX6Qz;v$N4W z`sn56zI%73jhT!*KXG9e0Jaea%VV#69wX{jx@j1?0&*; zLZk~dE83z0rJ5dj;#Rp@#@F>r159NKH0dN68;=;fqzg;^c=K|no077>? z?rScnm6|EXW4hc?fUUNm_R(qJNtn8f?BKI7W-O2uUYB>26( zqvn&Jdd|)e9(vGkcuK~_LO1ioTPE*31t8G8cNu9j7L;Vlsn4V`b0+y6svNKJA9*Rh z$g+VWzkVcRmhH~^5qdmzdhAs%U`4nNKYX0z-7lZCFt6CT_ZCAk4UjAu(gmM`+m3Lh z5y8Prnb9_QF$g#iq^`KUEyQhOR1DTst=JTZ&7_<}Y*UjNuhq%A(GGNkrky8hw-T24 zb64I6V7)on`SOe17)xgWcEksta=T2R4d^m?96V1$Y;a+DQgT_>f>G4?St|%5mgMBA zCPu3u1>|kpk#>au8#L?WkS=j~+JEE5Ve|OoF8~{{B>z1d@cFjF=RWshb9r|QuhjFS zG(Pb)KZqwfM$*QgVo!QeUo>-cQ#T19a%;ROCtd1jLPxz)CzeGkr=2j^Si6^xn@jjy zb?5KCM{v+;NY_7Tu3qNPX>qp-lTEnFtp|d=Y*U{YmaQ8BSLzufF?R8s_{ue`>`#Hg z&cntSxCu{u9SwQp#>36A=vnRS5fyQIXttMxmaR2KZTp#G+C?4F5hK!) z_hK*scdbVBF^gtBV_l{H#FIC1wsd5Rfp|kX--meOsT;U2fWd`S;<2nbQes41(aIy1 zEdWta0xepOn30|bPj+01uk+`wcm+hASH*#llTRz_{SWP7;NkDS&*;M;@en`yCbr%6 z#w4&KULNS^uyjgnlOKG$D-J!LLi4rK+8to?$-xtyB-&*t#6|#9x^5MPE~8ww(j{c1 zJLN6}Mco9s-5A4B-7aO2vmLCRA)|Tj2Nc*2dR%XrF|Xd|9!(~pP&)V?3bNbdj| zUe~i*XuQ?z=i@2j){5=Hn|{yZhMz5@t!R+q_;}%k16we*HoG@)WwdO}y)ys-pSOIS zUtvYpY0}sY(xRQ&scdLHuHjF3IVozLJv1Z<8THLY@Ds>R6Y(g>(_$X|7#rUd~o(^*X$S;h5$Nu9{-yWgil7aeMEV@Ai$P@&# zYMLd_Mwn#K6^?R>Bc02s;&x45DEQ(~Fy*IgF!5*PW+tB_y7XGQC>JX$L&?rc(6$*H z#C!H2KT3g?pU~m^=0buX&sIDU{C%sLwl(T(!B;w@f4XGWxy2%$+IN@fc zUfYt9280bD6ni=aC_9N0`s#Hbbc~IYUpL0MDA=OJ1t6UP_~S7m7w7=V{JLurUF`B2`s6NkCBnzdq+&d^bmK<1UvQ06~u~MUn=f9EV|y%i4GRDh z@fn|827u;ZwMS#R{>LK4#i|^{UjJ?s7OH_7bT`N>OtR!Vlt6m>FeXz^HrX_}%VlD; z06ofN+!3&>Kut+b?kd~h7t_+TIKAb~u;Q~?m4c`(8-^>t~6=K32{_+Bc|YG1B;@!>$G7{>Wo=Bl;8N%T^06fBSh3Bcit?*a?>{H$_WT)B!fFtZ>oMQ7x%QFTgVfvT_h*GF3 z$sp-W7^y249%^fSN?*JLFaQoZY-~omL;Mfk2^A1Qm>7}Cjcj8F}>X@D>MGXh#8-jdE ziIbZR%r@tlq8}*yuPgC>3>Rd|_$Of%Gqi0 z&9ZBF9BK;y8bhac{CgQ35$sHR%!cRFdIOC|0}A~})5dNM+7{~+l`w!b7^PH}#pi(2 zX+2FXWdbG0@jvH0it$3xYTnc5GeB%K|w$gP$E}aMw)R&b34 zuZ)jn_Q5U|V%`AenBdW!pPRD-1r#(+s7IPM@l5eVDzz+VY=~vC3i}JO0I-v|V(;vF z2XY{?P3$zA3jmUius~8r)tg3T4@xQO=5VSs$yh&%Xr_92=x} z!$QK4Pt^-19dgJ%@*~YFMGbfYq3^0G^V$BnP*oje#_r4Anemnc51v`aGIi=npHQi& zS$pV+#zF4`x>dGh z*si)M8^>FTJ8!wxUyBb&1wU^TtY3XTi*IKDaS)c1Uy4yjVMLznB9D6FlP+Z{Q-^d- zmwYzn)Fv3yYkg3QL#4d|HQfX2VYNx|x<~GvqO6$!G2l_ZY(xoS)GARdDNMP>RQCm zT&dZ0d=(0%t#E7sh`OaBWfpu?s?b9^L;MIKoidL)^U~JPsilQW45#Cea%8IZxh~T_ z4jl3#?pU|hI=senOvJ%=8tl%rd*<~k7XUkr1&eX;M>yMj^1!DaeB=rJGI3Db1_Aw* zK4Y15tiRP2%(9NeTPAh!S|q_LZzXNqF+P)5g6Xhezb z3q$L4sPIWgezAx#Le587ocw&X*cJeMtdfIO1EmvK;ifHmZB=G&Hy+8xCVmI&1Pk)V z+#~Bcliau{bjn@Txk`@HMbiXvpjtF@xqxyPe_rb;_T8ALd8jS`(@Zal78}0pKu!=% z66IbK>Wp_17msm`)wBukQeVMKMfAlOc?MvgqQ*kNvMm5S14u{zOUQ>GW%+5RB8%}y zT5{&)5i!bEONR*9{B1Y&+F2s&GZm`A1k9@Z?3m{r0&MfzVD3(bd0Mp+ zONBKX(T8*2)ZcaBZyBh;__^6lz;Fh@MSwQ|xmb|qHk6JCkWx!IBbFoWZ5BC02;xu3 z2^wONh2rO#jaOQdHn(_S&fN6bI4ew*uogzIwVJKIi^jaD-GmzS9s|;5%B0lwMmc#) z+aW#qQLdXV%E`}(7pB!#%^EWZzc%j1I^3#sH7s>kv0kI04T3?C4Ao}L`PL$D0&eeM zVN|W4$2SLkkor@CJQ32ww+WK;6`L0wg|3o2q4vrYNN;;Pd(ZkUQ6jaHK?)|_xKtls z$ZRpg@8$WqH~hY<6D;ed4Ih8Dm%`}B+l#WXSW`qpGu$fN)`ysVJjy5dNUj*X$L6>; zrYURPxMnS;T3w(QRm*lP<1OV1tJEXk&H!o#CC@668YjvkO+e|9j;Yj(GSZSOkMi7Z zY=vBi8Woy)vDK_7b}y z<9w7}ScjsFl?PFWoF(rlpHiXUruBxL9Zuh_k3Abj8htcLeDZTVcP3@0(CS8< zG9H$3{AiUynU3a4kVuPiTy0d6G%BJQtbPn>QXq~UobBv#(i0x#^?m>AI}Dp`pkGpQ zA)pGMehj9?LmY%GdSOIwOtfrZi`m!aW8_U<0LH3z5oNhK6T!^C8RrZypKQ(AqxNe2 za02Nd*g^Cv8OtUaDe*9=Vj|mgB#%O)2L(ON*$gfM_;?E@w14#$_cS)vnp^zw4&L9V zCtl&$yZacacAo=G{@7>rX@&J#Ar$MmVz^Gz-QEiTj~E!u`SdGQJx_wz6li6hDaZMs zhF4DYNJsNU=v873`)`lE(O;$$!ORv95Nw~s?!Z>tt)nqtfp~0h{h2Y|(A>s%ANH^S z*k2lg%oYIf?bmy$bFSb9>A6mE5S{Bl(>;K59-~d-`jDIvd2JNWFc}jM!7VZ|{A>f> z3gdWsHWo`M^Vqnrj6Seq?2#sp$_$0HdbUTJg6%(Ts4p|mMKSPaw5D^CVte5I+2dL%h0RFoQhZUU#|*Nae9Um z81k)LCVrZ#rAb`Rqpo7}!L#7WEdV)=nS_^paDs!cNv^?8c`Z7-8Au>08tuD`8F=HQ zJh-J|%Qo`^wx+7EaRAgQXSGR045JrgLKxyl2c^!+7l7#OywbD!fsL(U!8+uD2|9KJ zW#=l*F~-K~F)SOFth60cU5_A)E{;k+$2Qki+7X6P>(VGY)>RtZ>hGAD1nJSCXZ{#4YLiE+r5 zlfT@`%1Ex8P*<;`_hy|wlpr(8Wqej%V34>;&9FGppD}lcP(xugSoI;L2ymq)33dSh6^Qra_cI;`(H`k*YgJ$En;jH7c*I-^Ex^Lk{2r# z4ql>3C$=aE`qM}^?%+)`>}e50b4+-iFBM?NkMKLpNrnDIH>@Q>D`Qce8BU~!!W+`b zPXQv37KI_Os9V#C_R%qE$s#R0$x2MGJX()Ltr62AW_D^R>LnA3+c+q|A4Eu#dIOK1 z^h-O-6FzPDc#4mu^fv~$7$_Qh}kX&YK|f8 z4&VMdC^)JHa>b_lcJyOm$Wf^Kkczd3u!6o=vvQ7`93NxP?F%|LMJ5V3RhDdDRN8UK z1|U1?B=d=Js{Jyi#a6*fuzs{tA>~P3SWzmTa>}wV(B!Kuomd{c2`J^7v&gH_HCyRL zS)?g1%d%m>BTZKCAQ>lI!$4zr`}?o$><>ub41m8sIZA(I#6G))vBEP1E)x7z-X(mU zhq@d1m7eSg&v-cw$Qw;F72MkK(mJ9>YZ-y@0~E&v@ETJ^$>Me`~&Mg<5}= zI)mkYpPK3bdk`iH2IJAX5|u%Bd==|KAuTM=vZz2i(R)A@)~QFj!WisCW3W?~uUT~g zSRQuu2%GqkNbiU>>V^~6e>-1OyM#Y7at(iKj4z?^sL!|0{Ev;gHc$V#0N5D-eyn1D zlK{LE?2B5MbMZLtG&MSgiRpt0bzqswRv8I{1z0=-DwEfJg`>N>{FLr)t1{be1C7dd zwZBp{v35LjT$rqg&GOcYzqY%4nSAiLfwZJC#WH9U zGdKQPa~YRB1yrw`kmktbd=8U&R9XLXocLRZ-AkLzv(N2U7uYg%AEz4_gX<@D;K>UB z2cB*Yl!2%`rlWy7gfV6-{Gpr;v9q&|yYQRMGtcbVq#n{GpZc4d>&?CQY=iC(3bOb< z`DK1~%Lcqpe4YvLegG#47XbdDN&6KVXilVtTb@Dmhw)lH z5w6t>)1U3&bw&<8Z;qBtSC;3kZM0uzJPi3=tOtAm#Mp%Iyv0z3!CZA^8{dEbPV?Lg z__^c6bpL%j);G2ICPIv=*#e+RqCjgFQ*>x24CN6^805{nP_XeHdH7Ou-q7S(49i^T*0k6Gn~Q**;qbBt8^63mn_8Nu{#|>IT=mTHL1}iKx=1Y9Ej7EA zVV&B8mu|EXQ|%*smHTMUi8yUkT`OS;qbp4~$cUxHx_Wud4q^}7f2n!$$(w_1Ognxb z;lBGWHP^3iS)Y+7%-nXV-io`F(?&@$m4%Vd03;|C#gDlhk~gF;2Sf59e#C4Wj(&I+ zu+hByWmlTdKK^2J^X4H2ZhMVSIWaeI_wEr){`>FS&Zqv0rLBWceEkYd{I<)SlRFoL zE)5i)FjWuQmiEF`9_3L#W26oxQq+}wnM+4Girz}B zvRnffo@BPrs8GpE%vkPWS&V)lV_QD;)GdrruEzf3aI=LDFI%}tJo2dj?zx7eP$W}M zex#*0ly}|(7*eY7$DEAGYFI}HB~XE8qd&cBcGu-LCj|0H}@{%Q6`^kFz)<^Mv@~Q?d_%~ z&j4^p;NawCLukSYo@S}^Yejtf67Ve3AU7LM80)M)W>B3(4j~YK@%j&;IwsG!cs6?t zm$YAgX1{q6XSE#LocNb;kh}NZ?dIy04fB6e<4m}{Gk{60c7{sq6gJdD4_#^=cwh%N z?!Yh}2Qd#@ShI)Q{|qCqxvm6SVbv^4-k2F*WV&{>J+1er9{BpMubp-(%8AeS9C((% z0lLY*HMsJI>_*8!xQ!cJ_92jw%eu0zJKG!RYo1n@lEun|(S#7fs<4F@bI0b**$ul< z&B@PCx7&RhjP27L!xwG=P(!lOn6_|dRh?CKqIAn#9Z>QomE~-b^88BEPX6n-tiMi! zrNMCdSA(u%0pM8@6L~i1(gMJvu$=`Uw<9v-TNoMM!a{PpKDU+6qH0aY$$$OYcJt&@ zH+!+y@Q85_u8V6&=&Ai*!(BbEY`wk*Z;y1z<)<{gg{W_}TF!-C0Mrb%K_%H}KD&Ra z<_nrtvB+HQp!Esfb%w9(;Anms?>%smYvQoo)OO_M-$SIBcuAlYpti7deM~5gG~9zY zlY8#@gJy4!_jQJ`xM*Fyy4~D!PkUo*So@46BlSJw$U5#j3xEb=Jc`85mz)UFnI(_j zWKY{@SL%$(sT*x0UCJWv+%!9|7>t@Q!gAk?r%;rFavKcTm7{y*gQOocW9F3+KcyY- z?3Z;>XsiiOuBfZ<)Yq&|X{RcV=WYA2&6tGA;?~w$^Rk!jHlKO?Msw?yF9M2(y40cF zmCIYrV=uV`TVCe*eSot6sozP-pYbASN2x7DJjL2dr%KAg`NL0Pz+GClxQFxX(l|N-!kg&geC6URAL2&`B`sU!tH2oYF^oOXc?xQ-d{Hb6;NU05YWy$|g4!U7<5 zfV?x$b0J3I@P?IuB3^c0WoBuYpf0vB!PXXU zLbU029!FaP3NfvI0^W|(>(8g)8@*R&F&FCSE^VMGJk~7S#IYXrG5rIg3=dh zhW8~M4%N!7qcBi7%ZbztX@NHH0-Rduj$HuwE&+zw91V?MW#JGzJjmw3YTefG_-O1t zybMy4wU$nCqHJ$(+MRLUysM`{!3_1*dbr0u`J8^9~cX2Np(A*2av_V-PB+)j~ zu@W_YjTdbep?B?V!ReMVb3BbV&@ zc}UOrwYfZGkahdMmzgL+8|zQ6zchyeJURYYZ1Z@n;fSZahF8kUa?NHDo^bO`z@Zxv z;wz^!8Iz|ZURjn7GV{l_|%@_M-zwPQ(t7N%lTPCLe^I@ikNMR~NPjHy=7u{zdd!j&%$Z$DFX zA5-?U(eL+#qn!5gAzF23Io3fo;;Ae<<}pc+yyzq9)aZN@aL7n}OP+z7Sv;*u)tEPy4w@@UfI8mzRhS;jD7}ykqIGRtj)O zF92-GV;n`V;4QQ(_`NLe2k_U0Tzwu8Ou0tflIPA3o`A@99s z6XWDk^MxmF=6gn52;`3WWqh)ZXNud~CwH(oiMFpU0KU@AINzBO%`A*ra>7s1nGwb$ zu{x3yo)ceX<+DIcZ-L1{lQFZ?s{e5PYRl7D#KTB#HQknVXNvcRhS8Uo#J(=uowEDx z+p=#_e2Kr`e-W=L+a&>CSXytcU)^f%xp&j<6;ZdeUnIKIs6Tz=1)vN%1~4RJ#cCZS zedTI^T6qkEGTKOI5&K$v5x2xv2Z}FU;>f>ZiaMm{PhPj0oqQ;d{;4lM=~^8nugPdL zo>13ti;P&p=|r6RriV3fo*dO9X3riB)pw{KvR6(FkF6777di$o7-K?-bvC;-yS9JH zBRjZn?o^oNhuNi%!7#UWgg(J7IESjop2c9W1Io}TsvQsV_l^I{;MOv|or(CvW zS$yV_DX+;Qk96|4$3G7Gl$@7JQ_FO$ZK_SFCwu10rd43f7lVzW`An!Cs;c_W57M`Y z^=ETl!DrOcbm%LKL&dc5CCSUMi8xY=F=yTop=O!Tr>m^L#i;rW#kM3r!dKg=f#1^JUBOch{@iwoD?a+&3BsD&u>>ns8$CYsxQw+2v*j z?>F$}AwT8JXZ^M?T*6rk`l@m4(h zd-y4K#x)d^VrEWzrq~B$@iO3*ue{Pci`R;uf8o#`Wl@ihvaa&gV!Xx1lR>>*SogK! z$})f=mGxJ-R`jGFSGD;5V5AjFAf>8F%m16&jC%I&@lUzCZSOh-I zSAoB{x3_0}8Aqi^3zR(RYLJjulZ!8X<>bdC9OaZrw}u;Kq9dN{Bc6E7BR|rVDG673 z)Qdbss{b;bnX?Oq#Arbn8n0$s=V7R(87G`Fr=55;49Ut|y5hIx7LSvji8^uOJGZuHyfywX zPm{4`R>npY@stlEx4Kb>mpB~Yvy$TBO;5KWUe~IOVOBX>m%33Vx~5a(Nx#fX-Jx>H zXZzsv|0eGLeV&g2p9JvJQyQ%d;BI*ZEihrQuE5JVfGB3y2}H!#x_OzyUwW34dB`1q z>3Q|)z$4EgqYKVYbN%VPmuV26ZWiUQ__eS92y~@G1=?jQ3pMv~N9dRj!>0*n(|(TUadk>9y(jw}U3!>wLPDj)!^&+p7mBE$rXj|&8q8$B2n%D~uAP&$FT*QrZP#GlW)gJ^J5G;kS zp_M8tEah?;%aN8`c}-UG9>TftLt;FM_|`i?6_x7}4L5Ogi)A zLs*d?=_%Or=%=~Jh+V^tF%8l5FC1Yok37=XudU-P$lc~9zNEyzGr%t5BTt_4`>#IPXIh>5wTk|u z&!`#wM*h6CVlRCPM||mtuY4#DwU6wn&$*5p|L-Mj3jmaU56tIrYH(E!lv63vESG6W zKE$W}R9UGP%aJZ)QaS3x#*lnU9x+P&EVDHLyW#OvnBoj5EgBgkX~c4*DURhxNASpx z0p$@I?sRRCM0v!BbgPq|7I3l;P55X?ntGUfS|25DF~w=~C>x@s9pxpD*Z5q2F5_da z{N@9b=`$qW{S$yV0vdAaR7l1g|x3;!^k9Kwm zaPPhMej0*5xZUpq#KvId&pu6FEmjihS=L{R-AXDlCy1>ok*n{UJR z46hOX|Mspn)Uv7!Yjbt(OgVbz&i5ygLCjG7C@O=p6f05e1Dg34QPGd;k3@<^6cQvF z7?wd0QU7#^P$4G~zZlWcrl4X9lcHwE@y_UsjmTVe%;|Z~InTMzeeYU(@AEn5+`AX- z^RD&2&-<~~-fQi(*WUZ=I(6OxT@M1CyBa@@Mbz=fX~q^06!vZO@s}|G?3b+F+c$06 z#A|{->N0>8^-T(-&W{bEvM{da`JCMOZp(FO)Jt%7t!t6FYKirv+zUc;SWnF-lw^p;I^H zeCDIf%bt&V(uMD=4e5)z4d>B@lJn(5vgj*fg^T=Hb{tf;rFH`p`Ey&UlklRB zQC?V4@uedg<-|!Q>O9%W@rw9mGRadhQKqE0L?d72A}U?Z0O0R_Z0+6Co&nGRg|oA> zuQI4%CI#4wPukE{WT%R+DwybluhV93?Jwy3yaUB|@B8(=0mWNZ|020;uwI^z_3WnH zINCZqq&kT~SkYXJW%8WfAo;_JiJhN)8~*P1`pg>url+T$0bsu_0~}cA^DV&JOK-zR zM0l^6_nwOqPwvYwW0+ojLF0^CgdIs)``poCbPc_3l{@rR!xyvw`!xGD`?w3|4FEXv z^Syw(Ia<`Ivld{JZNyi4C-HAOxj}%PA1`0vJA?chQS~?0I*Vj*XvW`7YM=bO)bGY; ztuwy`*r(aI*~eY7pr-?L&*{^rUmT@}f)JC(Jd$~a$gjd|-Le92m`(6<3JuOD&N!uJ z$o#xG|8i@^c~h5E`-9GrtY{ZOvHIG&P_I>bYytLVO#e^7%ze>Rdl68Ab?es6U@`C> zyes?_3nKE|5xHgdD$&PeLqn~-x_=ak5+JYz%Rz~8Fc2G146S+shf&V{zT_|%-;`DE*vRkOwoRkPD7#f z16vt7r(Bp+MO}wD={TQx*@*dwOO;($F*uJpDZi1IiZ9xcf6fM$FXxk$&CW2_>6-}G zbGztSp?Ue}|v_ zrBKtQ-2&{;7vpQWlasAyH;OucYrq)WJ06Q*SGOMTzoXTs7t|;@01$=ig$FQo_yb3f zI@K+p7T}4W4>4`rfKOrL-Fmx16r@2pE^xN64u8Bcj(0rfRc1^J?7L^qoOv95d2g-? z*#Q6}#_O1F+}7!oBj>2*FP;`)=jQFsEx6+`h5yuR-eQ`6$73b#cx>K;J0ce^tvep0 zw3k~m_}kEz8*l!lVk=yoIB?*=1>3i8e-Ir^I>_)f}IY*)dQ`Rv68cm3&KqyM*?7)@Ul!u#cRb2sNXdJNYY_JO9ZxDo z=k?-`XMe2xKfZSD+K+cV05F)DnVBnL>;Wt!H2+csJOxIoqz3>$?r+Q!?pMUX&s(mQ%Vp4Eao|pX*8{d9)Gb(iP6BY><-Q zax0bdxK^N}LwaptB0~%l<1WKHHkvBZm0r$|xpL)hJ{PbUQmz46x0Ec!8|QW?s!vLm z;??yZ-4;M!Jr8|#5ANpeE{ig|2H=WVNcbM66aO8hrvp8{PV=eJS2j|z6i@hZq(${f z$x^&=thZt=s!vLm;>p&iQT9>v)l(R>zgs1?N&~>=&6}UWg2FAluBcPp0(A?FZVT|) z9rV>7;@bZoRidmk0MHPwC+@(s;>mgtsM4%8rfz}Zw*dPl&i+rcuU7S5X$?TejvhVw zemHp)Q=+#H^C2Nl!FU~7ma5{)?2MAUYMqEOk4|0zPD_CNgk7e?eB#Su7MJeY&Eu8B)Mtt!| z#dBTdB~M|Z4cBp*=OadTqaW!=ET~a@`WJ}cTN=GtMCh{g8leUY#G?!~JAr=fGEBOjfBg*|!lZQEl~Af z8sRGR6xw2~KhOMlM&}S#|388kehzioNF7}Q$T%ILLBE7`fZ4@zYfyDttW;CY#ah3z zdM(EKv+uDFvM+Yi`PpV&4FFWc3kDBi7H|V*0Ua+qQNK=g3)C&peG70#!vO#@@Ncm% zc5A8Y0e}v1&GA#*a=3kRasyqwTx3tWQBQo)D0jPVKjw?{Q#Rdh6}jxYU)Ph|dCE(c z(sfx%E*p_L)@?*ws?0}y(dX_ueE9HGJ`-q?;!`A+d%M|s&+*?qVT&kHME*O5%L3WnH}{i-%nx+$LQITenS$?M`g&!_aA z=lPtTW6JWh;c-QLN?z5kbVc2^+wi<&m`~{nlhbQ;e*U-rZk&8y5x!g6mjQszF&eGL zwB)yVQ0VerZx41`nObi&TA=8_9*qbG8^xgN&(4oa?#FQU|J>HCTTk|*7Cm1ACeQ)S2<|ZkTBoJk0!3%trO+yasJ}Wt`&hp^Kler7W&ql;M~)ns#w_61*siH(0&10- z)-BL|3uxMpMZYg>*|LTI@87r7w;6yUVt}|kxSNXE_6aNw-imLMU5_V~_`pJY0PsOUcv5wcoLrb9 zx!dbko^jD!w z@}hm<`pUa~*A<^Q`Ib+YNiLb}c1mSaRQf7Ar7Y!-7^Re4GN;ilWz-d)W!a5<%9w-| zZE~Bxt#|roF_XR#FZcY!W&O_2)xO`B&E9j*J$Dr<^>fSy-j0_ROfovz)Q+eUUAKBw zY>xw`sQyK=aj09B7^~E=3P!#-<;RTq-&pPcD*lM)LGcGghcW;#c>I^ZcFY9s#6WNj zD%ZFq7*wTdyLAhUTMO`gek`2x>Av4#0Qfpyz5mZJ>d^N^z+BL*(7}TTKZ6S1gc0#H zD_f_!1?m>SHx}?%F9v{DuyFqELx&E1c37RCZ9lXbfbVEr5_}k!13$uc85TMCM+Ppg z^K}a>i5B1@AFuauQhXeJ;G1}^Kl%N>K{m$FW&mCZT-!W>%YZAfaW-%=-Y#@(zw=t? z5smh9eBsLE!i_3b{Gy{8=b++Jy+xy+96#c^DbqGLt_ie``I9*7--LefiF48US%-5n z1MtSf+Q7Ro{rNt&&tr|?Eoh8+tF>_f#mmeysZnLuccSpZ3NNhl%VaK(da~!f#d8|t zDTG`H&-oBWwq#d!Bp07$w-x0n zJ?TnDn$vTP<2*j{S>~lLX1#ABzX#X)H)Fd0_ozS2@;M&>RM}(4j$MUD`3|qMPzrk$%5K|;~Y`^kVeOATg;wr0uul7@Z%WRg(WwR+iDAdmiNBE_08|;gWw-^m4X?!junW8o z;oj*A4hFo9NLr_*)dDyvwAX}kHLrrn=~m&hv*Va-bb6faXW?%@czck)Z`-zQN2}O4 z*O;*x0KB2_e!)(Zug3N%jJ)r|-GfUw?cz&EyiD)`A$Jgvuf);YQ%*-1$wkE%m2I~x ztYpGEMm$pCdXZCCe%-d)XFmFnj%Z5PZATo-(XKF($!7E|zHq(BJx19TEwb;ix_@?f z9%ne-8pjxBqA$VTpTOUXWA693m3L6~$A!*6^2a4=i?j8J@dwcHKL+*(vAq*JX%$Ci z?nn&?92B(EsqT3{P@GR3ljP0P!e1REDcyzWG^fmmw90%H*~#fjM^wHs(a3^ z^hv8=s^D^VR0rCKu}a4=>g??BMPQj|AAHjSM+$$^18R09JK25oMu(>!g`PMyZ51=z{C-_+T; z|5}%nuY%7%-rzL;S;+qi*CwQ?vU<~?h-`lZa6eZ{k@<6 zGjl&L?wMz2U+nDczS-H&^_^?4{mRHm^YNNYf}23sz<(OqGYYEbwA3_obT1ef7+*5I zWP8cV#lp(L@#@X%H#|(-;%|BQdHDob1SACngkK7A3yX-0ioIhIR}q(xl9HBZk>Qn* zRgjlgW>Yj$Qc~AZ`m3U#vr!9_xF7_d13~I=5`PF3e2LzQ ziAl_lxi0;D%@yAv5x?>|J~bgBg*{O&E^#L@aVIBnvpn&tI`NV#>BrZkt%9VR(!Fx0uIevGwd(ip^$p+exNEc=Yk$_)?g-TB)z`PQ)!&OZ8hbSU z`q6mT(9|K-Z06lO-`w0T*J9P%a;Ma4+t$9_-rhadeyiT$sMYBb+PR?HWnef;>kePtMM+F3%rsZ?2!7p8j2a+9xb!DNT)c;;K@wIk+I8|4iLNQBZ*=fbBnU z{09>N{Rx1eQ_5B&xX)@R{aa6s25E{!kLL zMybJ2O~FVSm)UTpTy5dlS3bA>#i81wiEm;dm{jt0#Z!5*Ni2rLbtN-Js(JER^7W;& zWjeLCOT+bLzp9LTqNo%a%752d&y*UDG*m1!I&TbTDKu6twR)ZJFO4)-t#ra6SkD!k zs@HlV2w9Cro4#)h#J^C;R&1`>8cBa+w>;WhyEB1!7yVqRrEYJgSfk8ntfl_oSGCzl zwo+@u(L$5k!SYyZBi~dyJ09+C&JR{5Iy?UzB`WhO97o9B{JbVZUc$;~R!1D4SV z7%Hz#C2PAAZB2zay{g4uTVx=PT8O2ljvB{P2UU1ZBlzsF3R36CF{r(q<-e#^vpP$9 z5$v%yF>X(V;2+%dgkj%gG&yyaChLxMk@0fVi@tcCInbm)HubI?ZzP^G@(AG^j7EHy zq`;wOn=c08=AGOTe>20@B}Qr>5smxoeZ&hk<%{m`jnaOjcrPq_^(F}E2L4LpFe;S4 z9(gpc=|-Iu`GOC=K74T{;z$$PHGc`wI4%Y8phcJnWx7&v%IK)(tTbl(Txp?DIg;rzO>CsW#t5F!N z5h_+f8)7h+3=};Spqc-H$L0LyS>b?WDPK3v!jw2!9boC-B~CCji7Et}LYE#5!4Ed> zwvP|ORw_rsGev-3s!v5xi*;jV4@gC;0uCJM3G>pgq}MSvKMi96D1PBQ?&)1n>mF^E z{(dYJGKjaSLU_yl%>Y5k4@fXQJ+=@ZR^UYrDqk`E3#uUjSr#s**;;x={-z-^?sF{m z2r{-}bcp)gE}aqqg)llI?E6w4z(H>Y7)5mA)+q(#is?h4XHQYz9eSeA+(56UAhPDU zVqetU3$Z(P@Dz9wBNQUcmqiN46wi#4#wdG?ex@q%zQ6;@I7H)sHa1wD-|4qdSKR zY`xr=p9CZH-W5yY{8p<+phOwDmYm9h@oa%wRW>z3jd~WmYFoN!%^z9*EC=&MaCPsv z%QCZ(pH$t&Dh+W;lpaKb76aNSEs1DFUP{mY&LhBNPszjNrZhdO2MLWwpG@;jv* z2SM{zzVO&e&9OlTier4dm)70IkCVL&w0G|nryHAMr5rgb5hzS-y!k{fpg*ROxPczp z<>rg~^TXQlYRNo>W_jV_KUo?S5WMXS0-w;To8ppB%S=Bbz{G${H!IiK^GoX<^1;cN z`{Q5KO_xrl-ti$ow0KjRK~GSve1c&dJHDfA4F$l$qZ#|Q;LU2O_z&kbBu5X8;uSKy z+bq{S{Q%{1{#WEff+>q}Zl7uTiSQriA^Z9CC+vH}b@Iorq0gzW2OjytV{V=Ch?JrB zZwe?s?RXo$e*_%5Ukq;cqb`4c1{pD%IZJJKqtm%xQ$JWu>l`d!e`6LOdEGP+Gqh@{ zL_^w$hzlp}6+@wZcSTh}BgKkbvx;OBm^7=+l&3u$CPFA-pq{=|n|E}e)I9k6f)OC>Q6$>S_&Z$t^smy_ssn-%Yza;4I7vb|Myh_r?J?mZRpTr-$`vp6&)))ugWSh8;gfAX!d&r0~GvOyi#gbyYd=SyO zHTOoP!GE(wnYUwf9kkefIp2NxB;2u#_j_jf=C%oq3HLk3#|txpsFU#1H;=xS7a^PT zG~xFZaens9-)`{}luaS_7^em&n*K}-FvPXsECMQ6>tpFwh5qKkms~&-q zbM=wMi%=9{{)!&PHZLQu^PD&WXSxgb&N=9q21mo%aa#40@gz!mHQu3dxG7)2bbR<7 z@{=`Mgc*9cOCuhiiMi?11{xYtetyT;A>z3cS-6{ybB1$-XK{qvJc{3Lctk{SC>bup zU}R84cyt8eI4{p04Q>}bkD*rN&S}`hBu_;bnqM_mae72(F*;&Ce4i_-v{WS2A3W^q4^F*x6m2 z@60>`ATZ)m@L1JjnlH9V8gI}o=8tTQGjn*K7-l})Wg`M76B1s)7cs;aKj?;cOCu^6 zB%`Q`-iE{sKthP)r7w5GPQrM$nQ{KedO_vzwy&`FjbqSX#?cJ%Um$R=eBz*Z2a+6&21=qAB!~IKgp?6vjcgFi4021~!z65u; zE2!`bZmaH>CYuZ-7gL=#gUho__mZz|w_iGjk}D%KJ(|<|Y#20Av)pvUhhd4Mw_hi0 zvfP^SOt`X)pKAD*U#1<$<2LPm9R@PD_IGXZ6 zG=PaSMgT0InFWUGj3I{!Mp;9H-T-+>Ksp1Q7muFij8Ti89~qC{r416_N6X`i_S{61 z-NtxH14MQ~W%ki3Qu2}XKwhAIK<@=$bzf+BMvlH;{|7we{9BUoXjOKDaaVsjq>pq z{aXw|fU*&JLgXm3!~ji~k0TeRBOFi{km<@mH+)+Pp+yI8VQ6td-iU$FxG;OYF}4I$ z#Q#EU=wMnj`CH_u^6}^nK1D0?^1Yf*2no4_SBdrVe6KMZyXbr9o#Bgb?XNLr8!$q_nO4 zckpS2ReG^dX)($w2DxfI*rdpg0zd)E!D;~WUr0GU%n@EF{8dzf97M#0+2oTmr&ou_ z0S4spknv4XtxYitO>w?oKi@S)d~M2f!@{-0FOMoaCkJU~fYDdM7k`RhA_0WpcaU*` zs53yth542lmSzkAb%F9GD?B$b_-q>lSkN(@L5}h0$lgLs1mHo08VLhoinZ~cS3Ewe zh9opXzBfWA8>c8x(O{s2)soPKXHXcb?c3&ryMOr)MMrWg9O zEj}*|Sw|4v3Yr8-_+aF|gptEK(-6>X8Ze3)3Ou@1Z@PP9KreQd)#50DuoW zLJr!%_^7r$rR-;br2wXmbI0%2XGHPZgZu64`af36(vhkdJS+sy|G?N4VJLs`A+R14 zae(s>hO*jCz4#0tc)yECWTfnkDoflo#?}Wk1n-%#0kD)amIPaGqbiifI7iN)Pus6g zC$mqlt1pJ-|@cRnR38YXu#cIz{79AD|5i7 zZNP7FAmDxgPB|DXG#F|y80I$^kvSOk)HWEiI2d<77*9ErC^VET)SoJkmYzA3Q9fkd zLWCbbbXx|4AW+wx>0iAXE)X(GStQ{MwUeGF3TNaYoq>IC!&`t6*87c=jSkD)4ojGH zn#jW;Y9qCyFiB<}r9T7%ZFoa%Bi2*HLE9tZE~vfIu--$M08;8z!dT7WsQsyg{3K{y zXnb=KkQ5&ivKsrd2m`C4^gCm)whuGlDUMqyB{Nhmx4 zDn7~X1CS#i0yMek;*)|J03#9t{xpdy3`4KTfg*J8s(5tZ6Kl>9>ZH;P2Dyv92!>5KMkhAjq0H-!4fcv^|0ZCsTt47SOT~@p_nGW-jmEx75+-z!1a%XVInb?KmLPx z2p&6!nlpC?2Q*XXeaGg})j+wEaa%N_>PzyP4x{=GuuLW!`K~$BnR?2jUh5Y?8a$cQ z!NgR9E$C|Q13f(XU8`-yi1}NdDtPGGyE^bIpy@$ymkZ4}1=d|LD`mad8M25D0$W`R zhG;LCCV3D)dFb^BFL78bfmETD?RZlxy+6rlVNt`>bW~+Op|6 zgYr?t77`-L6aRdoCupWPE$fXDj8((+Rn(W&D9b6T`AUrfzxqO61JBbCzeQ8kGY#r#g^_SnM@shdLse2!(Mb0 zPn{Ty3KnggJ%<~xvZuOVR0%ug5ryV}tT&ib0`_Lv4(g7hAXCb{hP9${%j!zI9G`Np zcz8~9jm+FY;BNCfU0AOXKdK1NHrv{X63j~GPlgdpE;~%`mOAI4XOfue}9Tg zMol;~d)Eg&jWN~0K>EWJ8%{6hPCksm z828Si1hhnv*uJo}`YY2;yf30K6vP>bYlpY|+(QL+p8eNtZOzh=7Il&47kBd0( z%L30QR35D#7fqf&qP}{Rse*BI1qW-xW;^kIeKX;G^MKO*==9+}&G+6B4@L&Yrq`K7 zB>H1~K!K$=e& zd%eaJOiAd&tz%3V0uWGTz?&866@z)M*B0kb^Hjpc5=hKdEr{A8F(Mg-^gM_|2j5o^S(UZS|II~46?^xRPe$Bwf{gzf1cj-wqo30 z$b>*F6BhI;+zO{qv8l#*B$QKhSW)&3T)hxPqriy%mUglex5tnENsMo8u%x)>*iU|r z{gXW55>fsk(sety%vS~nCTK$Z$34GnPk-s}$HU+CPCLs>ch_~}*C>_>J}V62SXfbn z`l{WmH+dOn*-;&J@4UG9PxSkz85GYIXB;{PQ+*u}4wSh};E=;soC=VZUDewa5%pCP z><_eE2-|5pXU2PMbw{>suq+p?I}xiaPWU1|AVg-C>>^EWS9jXq+WW$(;#rNnHEM{C zdxStRI+qG1SoaPcib8b;DP*UVALyI}GD{f4)hWKy{JKhDiHYo0Go}Hx39vM>d9ud$ zLQDj@8vL`_28*k(O<(5>uM@fa2x_@^rCUVx0mdcJUyBnx2|Vv|QIn?lc%ddsObDq%nw9(PuPMlsJpXEpjCcj1iFYtH|{i+O@84`9%URmF6QSUrM6!#w`^E z2OTe>Wyvmlyf*aONFjZB>NhIi9vu7?oh>A}=DLBDA>WBbpoRtQWlqhXNo@nH=}T@4 z6r-dOOp^!Cd4KSymp|BFvf(p+d@}qs6O5U?yDE%!)ieeez<$#QUnoU{c-uAHT4uU! zmo2BAG8BiDbKe!C^Z9cZ!i{F+`Eq{8+sdv1WDPZSPljpo8M-B?89CPB(vfH4+}?(% z>z_^JyBEtHb1w4=u5B{(Q|`*S;1yCLd59`Pat*7FyQ@2=Oq^9c^V{ka0`}FW<$jcq zvr0+!D@t{u67W5<&%3m4o?RO|v|&H$39xe}&%&|pR|IssxIfE%_}NyugrwBL`J@bQ zal;Q~nW!MO$uGOvwuJVM)W_&6dGcOABJ-U?1vgFb{O21dzB~J$x2%p_f!aM#zH`01HzCZrkz`($joh2; z8cT*ZM0P=BPclr2^KPWL!qj4Xx&cnd?q~^SaVcH}fXZl1@W-OrGku-B)?u%H?m3qj z-%T^>hbCb#jo9ZvdUIMFZ&^u3^@M11^A}XNva-_ZiAnk94D8-=ik9k0UpLK}gm2|k z!_<@W=q+9<^Ge~zMj+nb}@>X}4x8W15TtEQGn8|&gAjpfrod{LS>fN)wa5b-2 z03KN%LwrB44{8@w|0gcw&^wZanU>(5(%SIKdkS(7Al$@@g)~~!!~1{6qbr3L(F{vw zS^N~db7zv1PC#$VbmwGGu~8$%Q1Py*wIM40!63$>jC}*jdWnRnmad}0#JKH1@o;K& zSeU}7G{6HdrV!8(r#PI!6GbKigKwkieM-wd;Mf7&u&{4NZbaHCGg@!C!rKUX0eRU_ zrbX4DdR%(2aeF9*u`TQg8be2b$~*|rlomJh)PL7C+h-ui2?gP90s+-U)Y(KJ(afz< zDrQy)OZ9}LvwOM45sjLfZ|bPPEKa7wzSTQB&DF4YAd*Z9B~!i(Gv)`O32?v+1qZ8f zsi9qC1Cnccp9DtPj5dyp*6`YXYz{?VNJXX-BD^c<>PiY1KV+SOm?~ zaQiHY84BO^jBK3*kjDh_5y8TN`th5V)iiI&P}KhD|5M;~DFURV&QMt^80JBw2A6dW zGB;#9oXK`%MjlN1E+J9j#m{lOLdIB4Dz*7KBaxQQ4w+&&7lb~q(@V8cCi(p)R{ z2oFMW-PX9LT$>aM6tKXga&Tr&lS;=zZ zkhkXjtd}=YHeHu@1z0syf@vTvT^uTglSD0f{emGh&ZCdw?G6c0S=28US2kcJ%GuKL zEzVssL~2`o0{*8&n>&wCugqHI?&n;gdn3#zDw`-Ux1Tg{F!PJLJtuR_RDn~}9nsE*dGW6;D z3d+Aq$G?S^)Dg){MXsL(Nl!rOZJX>X*ORqEKo@BT0bKKLfzasg{Iy@A_#aI|(ynZ` zo9@6!#|zbO9|7A3kkJcq090I$UX9cBcTEr4_1EN`Z20l@k~h}xv90Ry583lw0Wb4xve?wJ0_GU;t)`8d*{pmPAC^b0`2JErv(EDMr~Wk|d+^pc9!Y1;;>2P-!BbXcapghDwj< zUI&W-NJ0RU45hzY;uArU(VJq%%_1%`!YFW}pm`?GVFPbnrNS=ab-uVv?=vt=2t3%) z%nUa>ARIP{29p6z5pWi$$U@u=xA5jz|Uz{ml3LOG-!6EbVm51_G_wv<;!|!$A z1jwKld%q0c#&;viGFOEa_nh^XlxaGY;9C&V{?=Mp|oY7 zylt<%!>u?Br5!OJw~HTH7gEqjA33a3Zd@F1x=?Ov8-D>zG}5ay5~-Zqk6f55T>Knw zwpVF3n5Yk(*eF*SzX;kHRe4xcc{CWEU(DGlA3r%9uS-@z=1YJhyp2qc`rXp4$tWd8v zP-7cYXa6)x@=+NSJ5BE}&G1S6Rm$`$_8Bf=ji!*anPkxJ^GKQLRA2lUsH58U+M8`Ch zl%~WA#$gqj@}D$SV}Gh0%_{iM)-G!5W@+sl{@kL}q@tR6_AsTr zG|R-Gsd+g|k20r8tj)}zE%U&HiB zTPQ^hcWF)q<=4B1ITtDo_gU?av2zL)+Fr50`Y&?!+O&?%-}_lixR3q1U3~w-U%`z* z8-Dqo3r8iGc>Y{S$8%QWKKXsP|9sW`1b)WY({|lhd+6L)uTIy``OZz9&WrgD=Svkxn`RK^foW2ljFW9-Ul?BaRsYG2#j z>PH=jhcRl{G8T#P*3t?#<0|f>F&?f7fygSs_zIY0?NMuuAk2`+(HM7b729Kttbc_p zU~R5_4Gq@-<9MwnfAyF72d^b#lGHWAxiz|e69(z^7s{rLJf_S>rcBxEjFs!m0j4h- z*4aANspj;lA5E*FroV{RZ5d42`%Ry?3r#sh%-$}W@+zD0#BIFI-r#jK;|p8oXwc_c zHmfAsn4>qdeQPGru_2JUA@ST?P}%$)qq(%Uxunr1_V`B2mYK+v*&KA!hS;1uZc`d{ zm8rqx?eZo8ss%rZh0yUPC5Necz@}!yy4JF(#-ov3p}zdGnSzm$Vxg9}kp+s!7TNI@ z|F#7rU<>+uTjkNhOxwcT$b@Wr^I5hedBS<{VhvE z4qq0-Bs{lC>|g3W zT8t4{>}}7lLec+XZ<7`#mmX)6mT8k=DfhL*CS}wnOIj}b*e3XJm&QmLof)DahLW#r zTVP~c=xAFMU|Sq#Tas;CT4`I>VOu_KTd{0gd2CzK5c(PpPkw%2V`yXh+;A%to_A&2 z;AqzvVAm99*PLzFVri=Yv#S}mE5KQ@)gFJ1I4~|gD8RMvR<`}YVXw(|_(R*i+w!o* z!@iUQ(w=>&XlRWBUd|~TtGC>DLf8&U?~iBiPp0lqNPn7&+n)*BpH@C9vUDgiawzpU zDhzX&x;mVTb6D`O{rzbBG<@tJvSf{?_hGBep@;FfP~>Py#Nm&KV}Y_`fsy05qvMA1 z;hvH0X13#QgX7-Z(N5*@5vs%C)p7o^L&=rH`S`THwo-W5=$^;%>7(P!@$q%G({|y> zO@mWkhvRXB(}VQ>eVEhHw$sg{(;coek_5Rfjr=R(jN0k+6y|j8=zJEpe_rXVd_=oe zICd50jQidh?FspadWxHd#3yyZ!*e0xJS7r!A$D>h33MR{cPVdhCSRGjP#(Js7{y6D zCI0L}&3j6H;zC31O3Ud=ig)&c>5QJ&^*NezS)~hel@|7i9nOjqy^||jrz`vWGq#;G zcB?ba53a9HTxmP^pO{76w%SfdMqLILPf@O2_ph8G;cj3jBtPkyz!OrC^Hk`wvjFF* z@HJAz(@i+gm0Q%^F6%7pxg+c6b6&g)-U&xBYUk6((|-j|{-O)%@QYn#q|7(>`^pQ6 zDqBg;%b!Aab#sSGfwqcI7r#g@`QJO}c%Hrg@KOKUkuImR_QXfQl}r5{2MxTde&{hu z>CX2{yDsTNwD(ujaR-*+2UdXx)}9A8PPRiN9`-7VBOQlQMN0MB9?p24F4Uf`oYw^% z2lgu0Kj^RU2=-qw+UD_idVllusq*wyv90d6`?zvl!F?eA*9O5T2gmbDp!5nxlM4~` z`mE^n$;m4`(CaDUvsdIduc#`o=uWSg39r}{uecMh&re?Qc-{%r-ie&vNuu7#D&8p{ zyi=XL(*nKIKYM@q=ABXH{k79ObHY1o#XI}N``eQ@0?#Lh+9#<~G8c~n2Cr>JLd?Vf z@am_5H55;zR5Jh~)X6If%w*op%=^au!wU<$2_^O`rqO&j`VCelGK(q_3sn}~Q{oO_ zCKFJ-702wa@{A^$46S`E^6L32R>(Be>c;JF^_U2S8A8Y~bxq=TpLe{Yz^}g_>45 zO9bH3p}i9<9d@h*RZki2^T{ERvG4g(G$(_ga%e`Xa80s%!YTCmaNj&tI9eCs%5St- zWJoB0?s9!~lm}Z~70147ApZS+k0$sNsPwQ*0^X`kKGUA~8NG?Zd952|mIvEODES;8*dQUL~-O2#dnFfDW zk^>i4)pZCjBE%&5M(!eyK8Pi)jzf6ApC&{S4TE05d6qT2($1>RA%kEa(gjk#Y1Vuy z5Ccf%ac$8FX?7=b-8vo@ZEjz0r$U5}^%>znTQDOD0W*wo*f<<5ulN;gb-IHNNw0uaEzI%*Uc{5J;#){^`I3ZvSz`eyJlOKr}SEar6xYCscy z7oH9!G&u02Z^xJzUvrJxM~+2q+LGGAWszqn1N2?nwt*<1bMvQYr20#|xq7}F(UwOWtePn-CN5aKKjyi0BCagQTTC!mzAujg)%r%5pG1cbwkZT~=kV{r4130o|g2GuyVT2IF+lI$KF$E^UBO z`YA^pbg(bT#h}AllU;Ku`T_jgYnPhOGJI{FBY~Xoo95lw3}}jmkT!#{g_b657c+50 zK>P@(%boy?Z;RxYh)f4)JTO-OzDcI0h68)GWkg}6S_l#Vj&fgJge9)LG{;j~kBnJr_#lPfy@6PIcT+EUGDf>5VFyyB?S37@^# zRdZZ|!02c2sU|aDTt6D+sgh^x&+MQ?JzXc4NIU>X`; zW`M=SC4`y`35e!bdo;d_Aou|X0F%{VDcLSxGOk=|N*c_bcZ$Fd*gH%EIuug+Tr@~m zXgO`Dw=f`3I>Tb|i?wXpEN^YXVC%&EM1N6PNO66P3j=Dh#n|QS7WW#Ws0*TZu z*H_b^LMck3)W}|JpJ1KjSDeQ6l0uH+dFVa(cvnPY$uxot~wnzUeAg4erk%dqJh9D)#O;VIp0UhHAY_3~D^XoMJtZjqA@W ziS>4}xlR(eBT@xPyeEilG0z1dt>5w55`1pS&w=P*$h|0?PT>yyfXp0vyxPKLsjMsH zX}8(pikitf`^i8$52k8BfHIr9a2%N>_I*l!7QC((s zgmu1~prJZUn9Y>1Y`*Vx!}mCUHuD#(zlYQtYEs+TEZ>y<9&>G|&3<6Be#iP}DzTxi zP?+6TqwLRYO+$U9KfApd>%#BphK7cAc1O3eg{8BG#*PPe=MdJ#HG;;beqj#Rq_V}W z*Nx5N{v7UktV?_9jV*KS93Hi0OGmDat;-J_UOlYKr-_Yi+rpeaGiA${HI41Z{+xar ztSh(EjU89*oB`)$D-UOlosSQka0uHfh_I;(RfH>;uzYm|75ZP(gFC#Q$M>*pTF)$MtegH<=jxujv7!D8l{AHVv&jm(zCdt-6&;Hithd3TExD zH~|M!I=5^jac?2)6G`ab_|kVJ&SJZXmHk>S_ve!|eg$OtdhD$2l!cT5q_z17kTUFdA`2UnRe0<;QjY{=b#ZU z@X7yzprnYv*J5jcI^cn7_d_r$&){kZx^$DVA0SO{EDyEirL;X-YFzV1`EkAGLKU@3 z5l$=3w{CS_(eyQnDOktE$LsgiGc(TR!7s6#>w|d?B48QQC~=XZJO>8c-wQFMh7pPC z5)H(&aaKx^<`S;Nn)%6i4v`k7dg^o zMHWiSuGRm{hNH6VB0|;a2^F!3OLuc)c=SkR-|(~SOU&Z0*Ej#S(xL5`^nPRU=PLS@Fl>x?xT(s;_J{ zoG%yHYB~5R*z1Tuw6@?5^35n7n4JD(11#t+z7ZOE5Z{cGAP8&0_#PeK0&ez=Z$s_A z!)sr0738Sj+V`#O+&gRK=prPhV1c54vEyvtetmfI1B%CX+J=Wk>C{WAQtr@)VdhuW ziHiqy86e+bt?8pq%6!$wRMvJX6&n%7HNx6y;MPOAa`22!e`$n2)c?{-!nv9^P0PCaZJT%GvEW=A zTlG#iM4)!ebM~ec_>-{LYAAJF{ir_a@y%Ko69@l#vW(H~Mv{Id z|Lzh{;w}VpwGX^#z93;GKc(z)C820}|@{bojY!ii}3a*8gXjo39i+UgXJxktI{UVE%SiI-jtriq& zFZ`D5qwRQ!)(gv69rx9_@DJ1;pIEf)bSrfxg0)zADzt)42;&||uZ@@TpBHgC>~*p0 zc7p%a$OUvNj2nz5NS3|Hkn2IQM=a9!q_0+w~RkICGIdUPQ)P;L`YGTo&}Ef?LVzt z9e4)|T%Gvui6mMMMEsn)uU*Sed%^D&PY0oanWsavdG{0jbVY|X;@36hH3N_#gV#e` zU;WO>l|aXGcs?K+(*<{W9z0=@f_ZcZ(1H9e;V-m zUyhoYK`+Xz0^2YDXf@(oE?Bfu@h%$IDm5;-?HD#L`mT(R@h#jOpobORf=VR+e1J*bG1JN@3+5?B44xuB>0c_Xv9#&ER>7U^huP^4D zJ_t`tY{Utbhg$`z-Yi4>glDT2GDRkidrmm-pgEkKe^1^}cRrGg*s?32X~+Q@ue%b7 zVDxDBvS8sFu@HoU_C!=FG~_M29{zzI;pl2bsua66%Kt~K-Ts$YKP9XZSp84L2{sgm zoL7OtaEU8Eq5nD*&uK7{UNaSQ%VJE)e-RGs$CgjpRm>CfC(+WY-RtJVa{fV2>uy}4 ztc7PZHk@SBU4h>%At|qV_^uXXNUx7P^+>16VktZ2E6K55<%c~wO;#AYM5nT7VzOLF znNHVJ@0n70W0ihy)R6$sebcLrlxW)bg)zEk78PH`J-69>T=xRMuv2RvdAO`b^I{}D z#$@d6|87+JW%Omi_+(kycV~+#!vneA^AXY*E5vcS)AISdl*kP+O9%`~0&m&W1MC_b<8=k27UCvZr3tIlQXm z&9g*Z9@L9t{jqBhF0ig;DoM~Hx%Qcx?u%YjE&JY59GL(2x6htZd(YxDr9Byv<+Pg@ zY~E&X|A~hY|0d8fW^`W>Qy*ub|Ek~d#o~Q!^E0)twtCvI?7%%a*f*QR$SLDjd&#ru zOthGZxzw-r6Y{9UhZFLHZK1P~oCWk&1^-v9|Ag*oVmpxkU$K^RuANe~2@d9oCHU930m6=&7QzOlsR5iNyj5@M;oO-=oI4xzp0h*tX3m&gEfu$Cg(W z3Q4VRy306PPNseFVH?YspjJqq1WqT~kV1SXKq2WY3BJB_l0g|A{@7?)NwRaWn^Af&GAEzvf zsNS}H$Wv5c5?xp4oypL2NhoHF;7P~Ytf={ zO`8etmYcScKZx*e>%XV>stS$Hyxq-oa`f3MvWxq>U)rebvtL#Fymgzzf81`Dug9q6 zu(fsh?y&2LqwTo&+(_ssyVij9IGj{jXeDbWPT=CBplEx$kA4;ZrMJ`Phs8e%MEqbCpCxBk%_suPayPXZPX-m?cDBWKt>9@hpYp6>6;KHS?D3F#f#!5M3*ht#Fx zpG>fwafoOn1+-ykbTC?y{DpU~ciV8}C+S!r+RX;)-&2$08JL9RFvV+re6E{(!O4x@ z0Z$Qe{teUJfh^TI4)&N?*y(p+qW@R*UH?}|kW-ATwZi*fs!xN_|0Qv)D<~dj!!7yW zknko)?hBzdN$94mm14Gt3HT$k+rN+?!zpeH-l&!Oy)rm7pTnrpRA!S6ztVOaJQ9V!h(y5cxxl zFN4#Pl6TKlR%_GNYQI+NcDtar)AV={;kU|rPy02W63)(J10I<_;SPCE4V76!7iUv5 zc^?8h9z3p2mcD*g3GDE{JUjjMYh~V768UP0#~=W?^qwXV{dqA>5cY8+A_Rw=%p{ob z$Ib^SfR5++CzuCwPB>)%vuOmq2w!d_rID;!*t*t0mIuURo-*dQR@48X?5!KB+}G`I zC8QhaMoPN7OF#q#>F(~XDGk!yAt0c1H%Lf#cc*mcWIngcwfA!E-&y;d=N-&{?#~?K z8soaY1P&ZfSF7Pi-+4%*%BE;3BLxv}DPu&xnqh=7A($Cij??W>##emjp|U8p4xuuY zahd*Xsoq#mWuf{KE+rO|b?)8!cS4h-qWbewsj5beyfo=rtzU8toNDRtvfQR`P1W6( z8)#CA@Ntyhe9Y*J&7~D3h|ME=dl&PWME@hL^xLWF_55G@-5_sU4v`hc?8_heogo5} zNBjT&>;Jz<7?q+wtRB)(45FGeDFV145+mZ)q4A-=cGG6z71Z}>RvWpZo66GHhpmK4 z%^YL=aV;l}nq{qcPoEx@A@uS`)6c_up|`?dSe1(+$_1Q!gO!`KlR!h_V3oiXRz4NR zr<^=(f55q5?Zi+ba_FEkOt0*w5pOP+aDLdQhe1+kKJCTY&^jBUZ?du<5iT2W9HQE1 za2gOLjb|SvIu3W5kSMdP77?e{=9u6T7UYzQ=v|~AQjv?eoH9s~=WLUc&fu7|Tu5`7 zwQK`NEf{qUT`j4ndDrziZ$J*Kmo*16To(y}8{= zG=03;j&am!*h%(By_F0_YT{W*buGQyDGFZp_)*+*)Ob)nV&i#OUnbu;)0`RUa8wq? zj~N zA|Io26@UahG0UR=UzZKG+c~Hdy0O>GR|`Ob#*fGZjh!=&U`CPdq*aq)* z1`?k6iUt-Y?6!Y~Y2uTl<)WDNHEyu+c2m)t54Odq{b>!f{T+{SCgoU;H7Y@$eKy~5 znsip!d|GDQpP|?O?n}yV6|TlM@LiG4H1HtV-e?e2v}&?B$^X6!w-8t!vPU7|H0XhpZMgL~i_^wr zZWshk!K?Z5e624{A*vP74TnEdP&B})-n1F78!YL&4d&*^| z=eM&}kY&Yui<{_=Bvwcsge=s%7DN)1ycYZ-m1a%i-rW_rIs$ejbLneI!WFGX8H4BW!t8G6Kp6@lg3G@ zNWI8;ndmhtHh>0VhPUlckQib*FjybZy3pS~M0ep(#-EDf@e_@8!|Yh1_ab;AzwLpS zhJkHs^zlURqHQ~1?PUqmI{St`abQ14Iby{o%DM4S)h>jD!ZgCoZB;Gu3R=5*P5R`B%vwY>MmhlNMp(uQ++=W7qhgwxfcG^BOIVx-I+{H}9E z=y5-8q5s9j%X<%yHZ-{!+TY^E6?%)v03jKDF6uxMdO{$ zsRkVi)}(kAZDbC8#J9<8@#&+1^Z~s;b%Xt!qWUHxd1?Op62iL^wS||erBF~%e__E6 zz=G3%U;zgAzhYs1^+R7YEG~;v$X{46`H6)_>~TAVb_FQ!VVFCG!q+)ch7_M0oXiyS z6|7}BX}dOkle9c=U~n$UXL2yoFdtGc$mYtm;W7j3uRf{Om=ASx-l*tl2DqY4gOg_T zR~SRC z^E=BOco zec=c~R{apUc(VOrfr&u?_&p4X>^dwQpduXb)`Cfbdi8=S{nT8+%FSeeJ*cPdXY@yBAIBIr~TJ{ZqwN1^v27-!~JIGd(>dt4Tz z_yXHRsp!P{Ca(l)WN0%OGeTqY$loHyekRrb5tAR{pgfgB_9gFq!LQk$Nfa^?|DYWv0XtDMdxj`MS!lF>SO-0&|FpC_ag4Ek*M|_Qp)=@1e*@#* z8Co-2pPcRMtLb=V`TOBX+m>?!BZR^>G5a!+Q5d4JHfSoZa#2*dE|v~NaqQEM=U7e^ z-|;CqPK61Du`7Fsl^*OwDM>Hv`_YrEM*ErMIBY7mNt{>*(R2sThM10v%SU$BthGq1I0KxMvIkEH6|&X;WR0y6yYSbKh99oC$4bFIRiN;s+uwc5!X)Waa+60y)TpJ znkN%KyqdNjS>l?NorwS@1{07WE-7^^&GRV_+{f!RJ4&0nwNUQX`gL#VVE_wqr~npJ zBX74sy4W}6{zVzQTL2dLc2adq0ojlg*|=ZSxy(0x@QkpryQ?CWBVk7D1p0 zFot!{eqgEn_Hi&=?I?##T4);+v%AQbmKDGP9_I7f=s-Apyr-aqR;M{djUCGAS_!vK z5&}6b_J{W(z#XK2iv=5i3XcCl1!$Vr|11_9$nCQ?eWPL6IsQfkQF3a9TBF`~Kr{e# zJ@|m{L^}!+Gscv(s4g7Ol`&+Bu&A<-jbo%K&XWyIPzum)BT3g_VVDRu`9knQj)hU8 z4s8xK0BL$(J-`8WT(I7iakA273JPaG$!?|HwZt#a_xb1k1Z(JP`Lyd5qds4Z<1b#k zW~O=R?9x{Z9yY6^L97L+ANeYbr%LG{#KeTP8gl{94ZGAUw>C1%WU7Kvug){qYYQlJ zBb?mNC%UXHU3`cdPj&{2bZ1cc?ymM~?F}KMAbu~0<4tGB%@X!a+tTeir7Y4c0q@(( zhW$2MVPOdP5jJGy0~iB7+ZXc16vMOCT5`b`RtkPC0P>zTI{=5mjXaRhBt$=$Y&(Bd z9GP!B+ngZ(=F>-_%3b|1`fm7*aOP>eoN&r_$~m4iKb$jtSMdweozNb+H(e#SU5z|s z5JNM4L}lw=#LLUxZtC;OR#Uz@ey_f4Hz#>(?5gkaMcvZyT_{CLjj<_FszE02LaKQy zuW2G57f!57^w_P6x#1Fix@D>;42{MIoN4M$Xap~56Mecb&2j=|HB7WA)@3jZTMj1+ z6#N>MQwl%Ta(w;rImhYi*Dn@7GBX8yAU}TmauiqYI6~9YT-is8Wc?QZfzR`FfEHrqG;n>j z$T6nKPFyv~%1_MJueMTDGxb(j-f>zvss*qW`>(I&tm+n=n{+#gDkbh!77xZ9nliX% z%z`0@HCjuB&!~ zJG|Ws(?z}eQE0o|2n-oA8xKlHNbV17cXR-;u-!anwAq?0MQnTGo6zNVC#> zG{Dc?a*Xp7t>olAB^vmA-jx)5HQ_k|zFrSj5V~!n@vd7N$Xj}%P-?YW{cR*X@V2_WkeN%u^|4Un3x^vJfeSTxk zR3mfw2|bQawAAZPK}|VCC(MD7CqZvE@7YKsm6}WTe26W1?Q^_q{Uv|LCZC!nTwC21 zgSAJh`)ix|_V)uF%xLO*| z%^N>4vk#`JQ5yE3kXPZfXkL+w8qi!v3NCVrua-pNX0@c#pRF_{3C$2UH(V@DCaA6q zr}JOT)L0urzGpl%U7c@F2Khub)tTvzOHF+n^S5XPuSzhql zNvpnyKXXN}h=hi3x(l zkE(<={*gu?PQ2pf%>S82{8w%9FN##5$Lk}!TuHRF5lxJL>RZ+)#&Xnf(rww-*q`W& z*uV_ySSEkBE~nJ?aHNU#4I1g8tQ{vICZXb(-~y!s4MS_R^HEIVR00#w77~Qg@uwoh zn#iZ3q)6Cj;xusrXTo&+%?_eeRS%Wj>}c4I1DNWSb`|utUXCMBbqDOD&;5xw#-PAj z)qT>;jO?wlq}3IpP|}BNt;*sN7gMkK1*>N?4XfEJG) zbaSn*791=iY8I@IA&1=a_UH2FD{iXt*NXvRL!}iV3*J0y?)<2?8?mZ7H=D_tB)40s zO2fB1X~9PgyD6Yjx5@~Ap}QZS#CHo!&QpHv{} z3Y>nUEb~0;;XbZTD})SRtyaNh@?5rS3b%}XvxK;;OnIXET&){MHC?;f`tVh1Y6?Cs z9i^@K%$~_X6do3B(RjeOpeVt|L_*Rh;GQNUPAe~xjjTzFe6D**7*iO0GAMEi(F3Yc zUkXnhnCHEmp~~nKeEY-|2cO#`yBKtlk^2y|D?1Xa(W#h~^$}5WSTIlrw5h_AP)j(O z2{Gxl;1)lj!xbekdC<|5{iQH00XEqFoeh5f2R1;H<1$#`{pCBTdjU51{bB<&;5%L_ z0=@$h-YxMJn|dNu!CdMm8=~WAg-eZIDXNVEY>-NS$zt`cT$el+Y+7qRQ;jxLD@m`y zxLE0mGF+qSy1E<;LhB#WWU-ryHZS)(Q0FOJtM$KMXT0Mv+pMys+X-xLs50rZV)X1j zm^s}QOiHLN(c*C47|Eld(e`x9U5^T9r`baGMB87=_hi@hPOU##{U%DNFigbhu<||Y z+~$$c<9Kg6tu6o2>*jo()YcO+sD0OX0kFYQ6&OM|T<^{d1dlwnfNw5#R?4D4Z7wHx zp)(lnm0PCSAh@dnG7$2QpEFBpJaRv5z@LK2E)?71ZiS&7-E{ z{36S8kpRL8=AwE{tk%2nF6Q@;dF+$cTPQ6SDd$3ZE8;7ht_XUt5C@AWnzWbVcPx1U z^lnUhqL^+dcjTB}Owt3EF02?Y^j=7zRrCOC1v2}0TDcdFLwHmy%;kw{iw;BF_k$G! zL~!2rLlV%|Y(rv{5$qFC_~6P3`6l|yz9VYu%W*>CCFg0K;9{pK>3abT7=FmSU4M z;)g%T`+lVgwQV)PRiSA?mAlR7Q8#Ku0FqWk{KW8Dxb(IRv_qf0Fd8fjO^xn*rK#4L zdd&(qjO>SwpMChU{#(oxqY2If87w=VSigR`5wTY<>{epO$K7zXOEz+8wP#oatzAJ( z?fP;~c$g%9UBR$2$!B;T-%AlX_Cw}lRV$jOm|pP3XzaYBsQ7(IVGh8;`tPvt#rkhT z!r_yBwozXc);fpNZ?M?zp`%v#sKv{oSm4WGdlwv(>)px8+drJJYpS*-S)os;Q7I7MM(R8}nZ?NwlHuKSFy& zvn2S2yaJD>1I1p) zCCKMu-c)7Z#>H5FDwR&nFKYu&SYtik5JA7Ph|g;YWS-Khr5=3eQ`2#}Baz+>E@P|i zb&w^WQ~~$To2PfJ@LD_FYsg_~Xd^AHuDjQVBRy@p!r+G|SGcPnD4bWTz7JG&B$9{3 zWQ#_RRKSn|8Rt zz{3xCl5?%=%y3t~@KNjyT;2!CcQm>oGSfJlZo*8Vx^al>YB7<=r^!0@m4c=(lIrL} zNu#wjYCpT_B;rz~n)q<%IT(MvrPgXu?;|rJw1y=&w8g>RiSUx8DKHLuKSvz>0dOKY zAiBGetcc+)(`4U|<~veRuyqrmQ9*O@1&Y2u-eu%|oCgY5sUBbC)XWJ|WYhnIg~h%+ zFYoSKF{JMvzl4MzGL_T`_`Nu3f{x(g9j#W!|-G4(v<~Q~vxqZX3}}5x3i6^l#j^?G%QumfX>r zxW}^{j~cde3IID&T=qx6@h>}4cKxR5I9D#uf?; zLGa?EA%|C0cY!tddfbrt@p98&*ZXEG*Y@#d)l#>yeCc|a`zEwX2y}5t=mR;vb!-*B z0LLvtv~a)mo|ZY>i4z|$N;eyQKXy#oPha7vtA8pLZG)pl^z1pU2&&DATg-j-HJ_UR zMr=97f?;Aeg<18eq>&j6>l#?2w<8_amW*wa429lIJofpE;GTswWUhp(Rz2838`3rW z*YP)))Qw%CjJ^i1^58LF;%pkAyq<3H>MdjXIG{TstNPIVO3JzqDSY_%tke`hiRIr> z0(z0(^^cx}_h(l6p%0qGoWtlhlu*CIqCC zlO$&S^0~=m0aq1Hcoo@>6ec}F;twP2_1Q|avX4~zlMD-AVWv<87Ou#(>!G(0o5P$L z=lwk(r_T2?daLa&D_28umvrm?Q8l#;?hd*AK|Tpmt)6Q3+k$B;E=*qB)uUt1*R7l- z*fo>mnL8hC9_CJe%zjmvNR+>Gv0VzJ0$qMRJ+oXX`O@)Ng3r4#{x4<_S??5_)jjpRVAa5n68cyxhV?Y741yI4y5p0M0=AfIyFTPj@pxPajS7 zeBn`P$$Sw>X7&7#*=Se1(5Fmejc>M|t@#o0;pqnxdcd#yzwn`54<*l6TMxl{=N28v zXKX~|NuQ&Z`;mH`#~_mXs&^xb|CzaA7!wW*Wl-|gb*96ZKxKmy_viKtz zcdCT98hd1k#h38ZHj?Ag>EZbAR6oa-nj`3>>156<*{=p76vVn5<&$Sqc9j3hUvo722N_KlmlXbO%6hB2;1mr_tb z>*QCtvRVkX<*O<`<$dY8)H@7S)0~w2Uz3e44<}k_1janGia+WT*pUGi;XmsWHM9Tn zB)?5IcJqsX52_YG$w@c;ar1>u+7s-=Y(LqShbzx$Dr1poi&})urm{A88ir_CXkDa$ zR#tE&Vi%{(YgTz2((uzRf|2+Nc~aFuRw-u=D{Eq0w639EjKJ5U{dA--jy-SVCs6C>X5%kjsv{5t+ZT4aA9;@iFX9z=1q6%1>9OKXDTua*Jq6BKj`PfU&r#a9B zD`zy67`bK))4(KJAcubp-LTay_dD_5L7r z<_+Ir-9l#LQRR8${ZRuP+QUf;l5X=R9$4PGrj*9|;kZW{4Sdn63TeLVQUZam<_wv= zH~XrNtj+~3R|KzDa&3L?cIrkR@ArF-!N+UNq;6}yZILZ|tNSB@kPPvq(kBqoQy$E)#-zXl!9gt)YSgUU}cV}adaht{qDP55^+qm(7?FDCeMD*t_m z*qhYB`gdQ-zoNC_WLTWgiB`z;;Yci8(5U%ZO=Ws(CfaC)xRRz4C(r1wG#c(aLzSyr z)u|7JEoix8i%^Nj^NcmVPdZ!!CHR{#dAK{i8}Z~@m=Z{J-23h&gC=F=!MQ!*DH7%) zkAG#{O-O1QU(Dm8m|tUF^e7Yv2xrB@&;;`uDY6AGI^Rtd!O^OJ|I2Y zUHyQ@1&m8ERp7}2aW#7N937tHjrro9=IaH$urV_T_-ST>=q(D@!x%bdb%IHRU~(iW zOlEYQ*uU11MG@Wgl178&3&>-7OM+imJ!PU>jM*Pk%k|wM?;Q`3yy7BsU%*e9OdP%p zp-R><;ia}R?hne3*7s?kPPAy`GM404nS7t}?$LNQ&4-E$J;m*o4m~@dZ*MwovxI6_ zIN-{BH_MA4Zzmm>S7l35K$~|rhivI8wt&fZKDLlNW}dDf-Y z2*{lZ;%ok>UCwiimqf$+#Y{ZesY3F;7SU7P| zv$(>zm{5rY@~1vw`MW-O*3!LfIm-Q9nuAr8h~`1D7mfeoROE$RJP@b&FP`->rxBeC z(~B#gi_;)ERfz-RQuZNs+(}1KVKm|xDm=I*n?4aFt&2gr>-4h;ihZq$$>&N#PLqnJ zOU_gBRbe#~y0+fVa}cwZ(>Xnbh}uz6rCa85xiZF+S%az!?m5?ei1qdIhv1Cb6*r_| z&J|x=AGcKu=Eu4Iqnh|8CV z4<=Y-_vknE&_BxTP!Cq_FK|VRN}~L1CsrVADk7oEb|27hSE0m{MU#Ca%&0ci7oSmx zp~RN^W3Un+De++BNgk^{H6(cD^9+lmwJVtEse#;!06a{JBfCJfhFKJE<1(zH{m|7P zNk<<7nIc3_KQ4bvYK#B9uWbN;!}ND>tbRjQS_i<<^AF(YP7z!GAQOi#!=m*!H9|o1 zRzoX0dQC(+{uvjbMrem}po04)?i9>l=fBYx6C~jHGE{^?v)P=i|9Sk)=Wrny`suYv zU!#&pl%i>?seG7;4sFfpRdrv7T)^1OST8lBKNW7iS11?nbXnn()QV5p^C?NKwOXiG8hMny0gO#7<2#c* zjXIJ1a&cS*9U}L`*6v((;YSxfKzd~0UN=89xHR{-yFzSrz`Q`2GZz$9I>Rls%14hX z%nasrxxeWtXe(>;0X^Pt{WLar+^fFNsNu8ywi~XORXR$X2>fAGfG`#hC;3w#woc(% zFpp43WDA!7Lx~C_|Y0%>bib3!f2+`Ki#r7`_yDAxtch)-a}t zF~rD5HnDCgK>kWvHy*yWXG20r3f{=?!Z$H5=JO#JRdSuSw26myZ4h<5=~+Itr`6Fm zRh;#e%T_45%H-5XyYWB`QO6^mPYE_3@zp{Hu`O|&e%|?fn-OZWA|51?b`X-S^ zQsC6QKrX8m+u~KOpYjjs0*`o$d{Xo0>4}<^f0j9mq-pa*FvJ8gT>mgO#i5jvS3k=C zDs%o9Z~!@rnkZ1|D$Mo+)Civu0yaVycQNc?BjIuD!=s^0OCzeLbQ4R-*?{ofxDiVS z7i0R#YgBHplTJ2Ct+?;FPxWofZ*i&-xsjyB=V6LM6F(8^6#_F@$wJM@!` z95@Verag=f&}_U$AK_m>t{P@Q@~#}?zkkg({(P$9vem8hmUz~~kG0*l%nV6xH{ErQ8kVvgA8*!_JzhEs{>TKm5fCmObO@c>0gb|b3>w7YWi5khIe`eB2f7K z5BSxlz;rc$1U7Y&_H~|Y7F)y}=GTc-H6#aC_1GNr1Q=uTmv@Q`>cJ|Vk=7b6jPL!- z;ZlwF?T!|F?P2FzzFt{%HbbszFD=o_R~v0^*E4=x*opV3t23?2vTk*>%8)N`rsJKH z4B7Bv)gti~nl*YXCBE_Cd$~94AkKcZsK$9PnQz)dVx8t{(-FhDe!w?YxxZXo(0Y}D ze``7?nv{zQxEzwgY0LlOa-PH0@jMvWmpA>dE(dK&72l^`w0<=RRjT(>5V6nAY6wY? z8AS+fEbV##HkZ+wchXkDdMJCoT22JTvf4%@-&OKP6i4N(fhe;qV1JJ3>M~7{J7!2? zNQjy`*X11?ku@5p1-enRA2)fvon-%)a4Zdt z(E@)J4)r%!hXJ3mnJVUEix}D=h_S__V?Sa_K6_NZPccDE`7?RJS?Z#fgg=$|RT598 zcz$5ym@-zPb*tn&1m2VxPpB5@oAEFZObr(BRLwn10vvx@JfxYKXmE z>t4WVPCJ*FYtDM-zGlLleyDa{ug99Z-LVN$ay`i+J=r_eX%cb0 zdfBa&U}f=oGulvxcm0E*4et^;sI+c3HF&sTHrBNCZu<*R3mx~vVk%w>ZEL+b{1Rv5 zc~tMYe1Ft>{&TttZQFd*3(0&q>7j?zOZYQ4GaQyO>o#8wu}6WghAmgXmn)`dt=F4T zwn9>4{BnXnb{utEZ%&$4d~VKq6d>T^nJBR29(R+_Vsg>)u^O((ReRKjuUKT~=xz}!3hXXm8a;XqsLv9uonRPh=fK9`&R zzKo&+aK`XA&e*L&k(*!9E-L-coFSw7)A&5ceepN_^3(Xd!>)`55?<)%<3NhD`!mJq zm-xy-t&}C_FXsHG@ySWAul&>aX#6rhnXvn#2O`z-NFDF*;||*z&`*V&=oQPv+fZ(o zYv_|$MB6_gMg25B(almZ>&fA(>Ps^?$w>%4V%y4z-nXH%#7#4 z)p3D6IIY{mxrH)m)t%oBl!GRQtvp@q*D69Obz6hKoXrnr@uQ)7k2tROl%N$v3f^5E zEY+Ao3exV^Z9-cF{db0aT0D=BzyEBHJzid29QVv*k+yk59>8^3`dcziI!1!G)OsN3 zp9wFL6y9pU6BS@%6G!aTTHxmMn^9l%&(iaOgcix?;3=$NX>o~hq z=Uw5@#~QO-A0}BERTmr}&-pNn|K5sa?JwiQXZ}ikk0VAFA@kSf=u692Jmv|QVkN_W z?3>AM0Qt`5{(ml`{DXe6k^IG!aA2B(`jaUgXBJNz52h`TMIyc-%@sS3-UCdzk;D21 z1>Bl!p*}DKlz4g3M0{6MXgvaQflXDFOG~Af7%V}{cs{qjP{~Zf<{`It(zd~ zK(Qaazrtzz;#sP43B`cdDOd4Ev1|9CCoc&vH7*?QjUdDwGp<8|80UgmW^@N=3n zq8jz~q$&2u{cMqcrR8D@q|kD+=4mT*vq=ML*)6k(;$2^88|HmDRuyjBK0Wt=tX*j? zyMy}{R#tjxMApG06tfcv_ur)I%d`(|;Jj9a`xAxXxlXK~5Rq-N^ol%8Mfu??6TaoolBW~PQxc1l3LwhViNz>Us$U z7+=!Obc0e1eKkCI@>7QC8vYE0xgw{2=E2G)>0bJ3I|&XS_i(b@ze?f7d;j32&9=0O zS4%@Zy~q=(Zo&Q{7gwun{wm2Yeos1$`flufI9J}g;LKl(=<^tFIBrQ1}d( z%YrqOpEVYgE{t-+3S+Z~Bcma|?ZOg#js6`|pD5-#w7GzNC#pR|bPrk#5nC@LxtXOO zIW(PZ0Jl>hdf=s;5kg0PPB`lj-$v2du%O&x#fT_USJfybR>Z}?J;@<@xA4aPMgP6j zlGB(nO$*1UrXg{5Gq0;Q=d>cdb@kk9;Ru&G)u_YTdD{X;*KbyxC6`M;ouzi!dIMrz zvtl+8!PVtu8Q}QEvjZkzE6HoU7z;+YMb$*!dhJL#rQ^#!5+It_bi zS&z3rKGl^r?tkftygL9|BkU5nG#Vafpj7DS5b~_oht<=*>{>)c%`=%M*gWgSLtxJ$RG|=Vs%(3(1 z(t@zl#qBxiV=_$bJGwy+!Uf$vn}W70$p9~QB{DGP?zbq$5@XDGQVEzs(O~0IlWk4R z7N%W{r1q`=X3;0@1U|D(Bzo}3L`1+OWnS_ z{H-ve39v!?FE;Q*!z2m9U!(ui%bX|^um7+#!D5&%VScwX&*EtCO%!FKG0A=D|Hy!M z+kgz1BJfM~?nv&N(CPF$7L?&24mVUINtv(Wr82=zD>3E@DsV^H4;)^PE@5-h%AVN<=?di_GFc`>t zEg5!OxILEf)?ejN#rDVahqcTg`T45-sq*KR&2!0XhK*&{v?W6xXs5aHJ0x&Syl!XP zqvrf*LIQ3^n-lhgbxZ`F7l-pnx)8YImS(%HMWB+J;kb5uIBAVVJed92qcgiuI* zF=iJw#ZZbL^CB}wu^%TjYVbM2wv|4$J4K3YVkmXe_Ywq?IGbha{PdkmXDUf`)cE&N z6?5(s683BX8zR1uaJxxP^UrfMLatI4vfMcN%yMFI3JSAaHM-P1VGg@?beVmB#1=Bf z^v8Z7`*aucHO0p$K?W5J!BH*xwc2jeMN)u-pZES(aKrntny#$t-+QnBAT|C~Nrn2E z0spL|Vod3a+SX3D1C`Y60Y&(WT7r%i=Dl^m>D_7$?3t>BT#QMs-|SHia94)bz9CRzqJmZLYRsvSQBOQS=OTd0RJ!^l;b(r`vqo3!U|F(#@T9C)sG)-!wNsTJ~@`sCq1LHElWqzFH1e;JfJ6;q_kd zxkmNc^ewXGyWdl4Yh6ywT@iSQZ&KiU3|UbC-Je10N#?i~oQpq9c7C)HbBHEi6r z!emNkZH6#C?4)Me#4o>Z!Knij(fW%bHzR*249tI`2ByCz8PG{RoYyL_3xw<=+3AFK%xZEH7IX7M(Q_RNg?q?5@gtzsSRdUghlHnV$7 z9|8QYM(tLg#A>zh+0}6?!i zdBd&5UDR1D_Xg&YFnLCu_6~~kN3{yvUd&7;jMOeaxF7G&PfSBv-4-m4GMYL)cQT2a z?=OEWbb^@MgzoQ8PbX%C+uFbnYZ1~UHsPO=@IjD6xNJY`tcAA$g)Cp>&duy=Gm^bm(wL#fj zE~zqpPd3eOa!)#o@9yn;V$6Vd5jnpqsTvdV_Dm3?(e%s zz`ks$+nN)a&DFXWH`Db-kSgQb`7f37yesKw%e?EEKPeLZlOhSZkGFfp1wRc-4=Vql z**Qb@_M0LbV0(UK`Tn>O4#IcRi8Ru5+Dds`FPU6m>3a5E8q^H@LR;_4Su9Y?)f^${ z?Rn&S>*addF%$1?gB-|bZ7a9cXMMUYs^$t9#kQ`-6_DOv*GAC@O?p&m-Y?_uJpF;@ zW6Zz$fcWa$vq}XJ0?`UI6`tsmH=};fn^z^i6|g{?ytc#%fIoZ|E80Id=>MXMNL044 z4U11P;N=T)cuq61xFNd$w7HadV8=Q#JzR$!#sKYw>RD$Zb_|vD3j=gYzw-Cs5pBgy z95~6X5^|~2k0WfV5E$&=F6*cP;86cJII!zQUZo1c!~NsK;7Akj_P|870~QYJKfwVV zNcIOfGT8xeyad3J46M(h+$otWr7Dsm(z%O}CuJb{3VIhhn{|CgY$CCyz1pAlH73vZ zhQ~Qnue8OleO^DUzZB#MSu(D#GhJ-e-;gSid9P2D;M;P2fpKgE8(k*NCcE328YfH-V z0Zi%ws|(~hqY4bUgvDF&hK5qh{%}ED2vkNeXS028wX1bO*^IlZfzO0O$btw>dOrn{ z1kIBBV5o4A8w{8SFWNjtgrvGLzO2u&Wu@k!45jObHw@%DN;Zr-PAD7;{g#oa5iv^K zXBaz5E~9NH``Eh~_gx+#J?`5U)eFgc?LY%Niv}*mcss#8+yny^(|j=_zu>tPRfmM_ zFtgGWN(t-kdMau2gMx`9jaqD4IsZ~++E>9o@rBZ1M|)$+JQ(rgX_3EfWHrXf--DG# z^HTq~k#$5jF8@B^w;Ngi_3TDB46qaT0yw*Y3CN1Ck%?mr)w3pmte8Z^@2Ft{cH-*U zLgj2;Z;K@`$?p$O+B9q!7|G;OiP@KmjCK)@H9>EAGK)XTb}_f!i2uK9aKHvVoZ#tFr;deR{{9 znGS~0A-sw7${|ijy5lH6R)nL}Vk|@TcXqTxjtNL{QRSGDFmcU@; z;pMn~)Dq_;Us6QvtYzE~r<9DX`q`Mp4ESnEZ^OEF$^H6AOWm?H-0<~^4-)FlY7lPZ z&AP2Fc3rfiWd_gMPiwOAkrY^;O;?R<*h%wW<{e*>!fWWr1^Kw|78MLP{wN2!?EBSW zt#uopcU(Q%^yE;RCR=ikcu!RBeRxjQkwCm>&V*$==jNnk_2=I;FWf?##D}WRh8)`j zkEi@WTuYPOg006}6X2HB*0MIYd#PMo@1un|OQ8-UcCQMtqX7l`M@%Gq$U5#fFiIY1 z(H|b_dvM#mwvmzM9nNbK9Hyt00e+0_tFVmqm?$pM{wOK9;MzDr>l7CCnT7F6WVa5S zD_IT7#W%kPDyqQeuln!L{~<|VHeJy3**|~&8?S+dxI8gT_}@Q&gjlIQXeeAFW!_PY zJdl9l&jzZQVzLxaJiTMvdtJbhE0(SotJfcxA&U{1?7S~t2;Ic?{i}1aSUJ>pDP<&# zL*ZJqH9jZ$I(_XRr-9O`1sA%`CZiTvYZ=TP;dppxn&u*|vejx2{C(#^#_ta3GH_7-HyP(hfwmuIoXFKgA)&x>sla-c0<8j9(g0M<=q1cysIYZfPcdaER z?y+Uz!e*q;eLvEs+0AikGL$_EmEYz4{BTtmM3t$f`Sg5bB8s~#Q{e9Mc&W}54dml> zceY=3B=ojchRg?M*)-c5{s)lOBH|>k`l0d7X4^k#Z?5R8!qSolU`h3o1(Vs( zt_M?km>C4qroLYj$A!X6blDH&&URijcO#A1?0ZEPxwWUZEz-{mFZq3`NW&&ki9j= z3ktRWby(}AT&1^cwCJZl2c4;TuL8fk{QF_8f7A_{Z=n=9{65w!x)8=t8LR2hgAmO2 z4;yy5UL2*0Sl!10C3SwR=CghYTjlHV_~Yi&^@U?e=WrxV;!D)L2zprV>M}7nnnA@* zOhzk~4op#^m@X)FKr*1|KcMxX`WBzGA%#A$e8-I&Wa%fYTCC{D;%9gZ1tn*E)OX}z zRr#Gix43G6)fBmUl(+M>lehpHxMD<#`Ot1$ft~nrQhI}7bVQjyqIy!()EfhaQ`*_F zkM{b2Y4ZI}SnZTXopr^$F?|cLLZ8Lxy6Cdwec5Ii2Pvsr(1OmqT=czeIa{HYgK>#9 zB-PZn`<0zP zPi#z--tRYnBKbD_M~3f@I&eY!r#)!ELR<@bl-E&PS$*TS&yJ4k)YQI@_re^gwPkMl zT;Bb<3Iw`7-G+m>UOa!Z!ZodJczh##QiUP}mR|vZz?-H+z}20BmD|mpMhI|{^MPFL z;oWvk@QXL(&$P%SD0*uV5EV_%tMs77^Q!|DSv(2ivn9Z3oDe9^*torGFYQ%Yz;AIy(zQ2FfJROgG|GY}O34J!Xe!ypUQaHb#>)<2>F zVZ)Bxe)WSGCYC!fJLM~dVp*BM zrqTLl`ql3z@;@Bi*Nm+uq~ys*?2b6Q*DRc~+7mbM{#Byd}0FqX>fsRG8US&iVZ zB6!x^=%6%MjIlmV+*oZp-VE}$VN5?eS?z^TEy*{zS#Q;4rFjo~H8?d42WsLz&Kn+d zG(?3#Oy2-64IcCj-w zqOoterxKmKYe(ZeQX1moEldzp-Oyocsnd;pcxh71=Tp#A_4g1=Bm=KmJQoh?Eb1r@f#+%h`9|DgD9pQLW>;JiN=atA&$lUg0%zV{lRfFvbg|DH@Y^1 zodo(Uy;>~_9fLzZo@Fsx59xJL`2b$oqWu6F{YB*H#o*HeygfG991lXqJR!ey#o52gX+K@>M?!$v%?#=1-`yyZF(!+zwx zldZ~hyPFlJ)3}uv9C^3*B~7SS(#y53VWfDb)MLMB#inV$d1smb=-crT|MqNaM(we~ z#t`puAMT^;-cZ{c&)u;iu)zA1syujUn7NGmAcdc^Y3Yrw!nLrpf-U%LkMfPneSF(U z_1*S>ZtHcAskbnAH7%rU95yZM;z>Qr)6ysdb4~b%UK}=P4=Z<+Mbf8~Jng{(EaXTp z)Nf2MOOsfvw4TAN^{Si!dxy~h|Ka_5{;()_x(+!ZgRTlF43S2o9@9-yMi~#0)~%BKdNX( zv&7=K(jBrlv2zivG5mfgzMKk@z|}~o=QNluRB8yi{K%oN8e&T7!^!^#3t;n0?xxo? z8k`A--+y!v?TbThIs(sJGOaZSd!jNCaaSx?g!K?a7UQvQ6h%Ud*D^G)8aF313!}EA zI4N{`vGd(n5>$)i`0=V{9(oiG&z7)n_;@#6?9Mk?PYf4N)g29Y3X_teF1Vd;cBi2! zEZJWQ1CKrae~g`lUzL5f_Ekhc8l+1=l?(Xj1ba!{`_tt05 z8E4*^GxIO(&;G4@t#w`Nd*L9CxHj7Fu2%bjHK>7=!^Ki>9!o3!ll}EcflgH@UeW#S z5nu#$Z{=|TAHU2``|>z$Ayp$F!SucqMJ=hy@r>;?Px z{#^m2gaHH9ewil3*UTva=Y8MV=FbN)p2w=#kzA7|`+X*7!3yG?vD@?% zq29}gNF{{X3Skw~U6K{xW}M@b5~|AKQ)>|X&ZlJ*fEgzf%0~RnFfeC3oTEW0N7(I2 zXHq`DAX;C{Idkfhh`Zand||JVd1P4*zbVEjn+bt7rTAAZpbHQ{{dXkY zf2svyM$oqD>wq3ACI^KSJpkoh*3EoC$uIq35QA!{B?4YesnbS<3+Y*gKX zc2Wc+G*M^q=T_WGYW~fSQ^$?4!xS=2hYb`F&B$vHh^4rZWJncacE-LRJpcp#q{2*S0)JI&KTsjC&raT|SOTs-L zARkPkAEMt-1=wA(ahAhFv8tCNjJ=0ty=h2kh+>)G)Aob z%*D?G7|Y{$+-|4hLfzJtOA*VvGhuVgZXk5rIiC=UORu`FY0NE-V8=D~ZlNlFyp$$< zHKhLZ)*xb$#7CLxEUx;Vr9CY}z>~aPQ;gAp_pQ)tHE89TN}<~#gEeSH^bZ+tb!>}n zNl5;XCw{$rx&M9n#O>e1+Pt9uO&6fk!6^F?mzA|Uz4z}7ZBmBd4|?>3Bz&}x%R_hO z`ci4M9xdV5qyZD{u)h`B6ActQ#@1YTzxMHTW6}#2n8OHWS!I1NeN0JYH?zvUF!?@ zbYWYhM5i>E%*)?$Ad{=!nISAI#T8e*I+&ouP}0<3_ocp6(7fg-!Crr%(FtIQ+37ZRd!}`=yf_^k6b3Bd zE?2jc2c>Cg;OB>ja+V0^;wSwTkEfTrD*#8wKiva{yD#1Qm6$-fRSlCqjxVecCa&L> z3RySjua{5m@d4$(WN15m1$urWU_fC0b(kt= zU<}9;LDHq89+ML^g{R&{75&>)MU}%Hmqk_MU*&p5vauS&nM(v1P5D4V@2CYKS?a=F1sP=clSU zRd-3dIlVltnuYJBz)6Xzf;Ur|Mb=Zzl5JWs%c8?gS@X@3`_8casvEK7ILP=d&<_OD z9M`RfV>|;`PE<3;4XaL0#_13OHHMu~=VP12%t287l(d%=`+m~KgZ&;)o?G2EXBw{a z2;s`I)f8Ky8T(1&LulhsBS(reU;+P9HH}E<7F{#wEZ)>Iaf4uS)vjS)dIh4%ZsLdg zblH537{W8&K97%6F7_56@rWIDg1N=cCV6=HXDfgX=y5~r(7I|t1Ns;%d`9c=Mnca5~k2SR`uraCNhK9s_G}dkH+#S zy%`pLml5txkMXt;#?r7W<4p?l_j{y$o&v;j_Nt%NTC)jqsBZGuHH5PjaJ}T5I5MW= zA%+MJIX}@UR4SkjzBU&k6)t0nqtGm;ky?#Q1;s6jZgKdHI2{kK${lM zaDfraUDIPvU|sj5$n9AOD1w|>2hKX6i5B^I)TOpp%yWN8=~N8+&;5sw$qACaJ7WHTjVShKgCWYOb@Eb#_j zATrruyI?0(z@uv??Jp2My3uZi|Ht1T{9m7Mp??J7Yrp2jinRfzd_>+cJgSUHV95ui zq-~Vdo(_(bEvg(aLM^Uc*heX8+!vF~m#td8aZCbE}b=&b!>h;1Rxe1hg3aiJA-FEsP=FbeNAF;W!)b& zf~{J~CL68#hB^}w#ndc2(gLE|#Dte^i9_f~a z4!lz6DzzS{=vO#l@fp^{anvss0xO$X1d*UvtlMd6O3wS^2@r01q0PDLcH(4H>$g%a ziY)iU)0Sy>Z6ESWdTBW>op!MY)y??wgPx_2t5F-B4qG5-9{HL$x~Pk4)267z1}V~- zM2GpoE~4WMuPOEND}Hhnl-S)E{#^76+bfL!bHx6KcZv7Dee3^vmmOrNa4m`Je}9)- z{ajej$T}hc;o@FQ=FfwUQGf}b%A?}l&q~q`MsO~jt{@rFq(5*B?ZbMdHRUdkr)TP5 zdoYoU)adEDYn!2%r-PT8lQ?70&qdgZPZ?x;sO%%Z_pLdJ)_k$v&XjtJBleWH1^Q{A zn+)?rqrit?mJ3u@s@v@a(RhVveWu&{8oy4(hAShvOjT=B=J=PRhyA7C`nbdTeuPcm)+cRdRhk1}sk z+!na%aD0}O6tR8B3=I@r@zpaU}CA<&|Kzsr1?_wfJcyZqmP3d~=WhipK# z<45Z*xLu6oCh5^K^p^B0AXOQ*CHuol7GS4Tx%29FDrK$;5 ze&DL=i-P3Uw5f=yMNMx1H&tiG2i^Q>{!u-$@bA^Z)GSKk?gpTG+0zU zM-T-%--~E%qT7iP4h9D6pj7tZ^45|1185y;wqyRNP{vM}vlgmTXc(G@#$Lo$lcOW* zw8slrtT6WT$x)S)?9I&RC(*^EDOb@I=XY+x8=q_r^-4#6KL4!t)h91H=mi%PC;jtV z{d*$}jKD$vS0l`2=qNQt@G94t)?%&L zw;Vyr_{L;C)gOi;l;f^Msz3Y;{|$(hUS~LlfTM>J+j6Jdk>l8MEY50sFo^;TZ;E|& zuvlr%aV>#Ub-a+#dwk8+#A31$>uz16#_4!5UT&E;QrA#zy$XDKds_fj!|`gfKA2$T zsqyxFvolE}?V0m>?_J>o+7N!T>($Z0M(asSs~h+ZvTw!xbq5B4@r58u`i}=D=v7Z7 zc12BZM6o_iA53hAblZAfKUKd6m>Yc8SAnGX{)maBI04rMq$!?+9U{p-pPBM91kdZg zC7RY}BSrVAPy;VVaJOOFX zpx@t6bhHVPnEw`q8=$C2&nyFuWPO zt5Q<8X9R@F9HBgxw4Gr~AeVbq43@SW@=2U_zWrok*@8;!V&09TQ54>ZeKT0zhYxl} z>Rv^m1ac@!L3BNod6XA|aGh9ZeJsv!6(g*Lhn2l^+i(|yA5ifwNBJ>|FGmC#n;0j+ zxN7HZoDinPV+8z1C;UvZ0avriermRp+EHBBllopMcJl_nH+0dW@Tmr5S*a#7%Q68p z!p^02M>ilBsAu~nuN!!WHBSPmiws}XlDds>mfsN++-&RdJW@AH!ThG@8(&eM*fujL zxY(C+I705HQ)S`V4`lW6nGQ1>5m*lkWTF&!bD4mlhzF7s0((4_wFzMS)9&c^|hn*@)@yDO<+{G@k5TqRKW;*wEM~e{zTL_dIoiQvSb1B zr9zAiljPq@RbuR>-_lWhD<^D^m465~R<1Ce=N3#FJN#BJxA(TH5xqn<##t;i`)%yW zVzZOSx_r^J#ZpHA-U(IHT*+du+b5P1xP(0|-&i)5P)cQnweeKCG-mjQ3ypAR12_S; z*y{1I1gyo}U?+PsPzy3FWpPH}{#%qi0J@@we*z>i(0a-$=AUYS|I; zw0-bj4e^4SBn+}{xK!-#4{ysy6w!>o47vYF^)C7eY_$7;`$P(4a$uvq`O=mYAj`vN zted!%zF0#cZ>c*+tz8~?eO$W~iDM?Q5eRIwd$m=Bs;9at4jW)IP);gtgrd!w@Az~} zo9-tH$69Y+9hSDiAQzdpA|7$jb|R3uoYo`afM~m>i73On&@*1WtQn`zF6)HRc^vKg z7*=%FPqs5?+5aM{$#RfxvgvGqW>mGZn|4LQa+Iji zm~lcjFX((iPT#e9R33-QW=g=X(RNHT%=BvZt8s|koSF`B9;ELzY(D>y#`(y&)SJ_O z(WUT8@@B=g6~1o8qto6Cd+raJuDRPEH!MRd9@tRIj?YYp0wl8FrR;mXlo(&gW7JFxEql^(3c{uIFfQRU+NPn7`X<}iT4 z$D7ad;~y%Q99d+~uS!7vZV!3_P`SRpdaBR+B$LG#^d^MOCU?@G=+h%{yhlc#R8IPF z`%kpJkzyr6b@+#}J%tkOb~`ofucpdX-(!?*aWG2wD@~7>aU1PQ)LV7aD&85QkgU49 zV4QbT(4Nf~g4xP z(vE8^#T9OKrJt3?WsaZ9&SbG(p$3&^0@|^f>5Cb3RabGcT%3vGS>fh#vOONI+yFo4 zFP`4)t|k;>q@{A*-^?!cntvg9zQ4aX8f+~ka6dmojp@a)xkKwq^LQZHP4j$W0`8NM z*nvDTI3nm(!B^LIO27wRk!qF3AP?&_ey3M;JE+XR&xgTXY%kmFlE(f9fVAr*m3<}M+o4yJNbz43fzx*!`M{HY3?xFR&8D7iv zd)d}h%=2FW=k0E39I)B0n05QjO}RfV-zj15426JZTdKQIcRA@f2z>-tj-^c(Ef*u^ zryKAqPBX)U@U`oe#^$!?OwJlCHx)r)PZ!&@cu#9-QLRsdQyY3*H$q`FcXw-FOu^Ip z&PxpsU4;bCE&41kug%>J7~ZczJFqm6nK|vgorB=WZ3{vx@!xL;%fSowBAZMH ze`KXcWQX%6c}Mv0d{Yt6C8b7Pt|YG8hk{+wrIL+BzXX zd4?zxC8N98x`6bLHTXaIWb9YlfP=vQ!zXj&FG1Yp(E%vIf7s+;tg=NtA$`0-7bmEa z5nS%)mu#q{G6_OHZ>ZR#0WqA&cAc~B`wovLvWK=${M7!8pK>Mh75TN%d=^Bf)z;m# zmV%E_dj;1ZZOLN2aa>0?#q8l^95Ez1Cz4;0nupd(?pEX6;hK9u@9`A}*1<-f6Hcul z=Z%$qf8v2#F0L}2{zM)XgA_a#qj6`U3Wwd3eU?b7EUZFoF+1t@aFZ7}_Q;y`aK6tO zg8<&8?jpCxo=3Qav)>skWpX1z-Q*75c%Zq*%*dKWOu@Xe)rBNB-aTD;_geCIJCX|U#p{HsiS z2sP05A<5j;@4L63CwUk;wLLEnY1gkA05Vm-6dorXwr9 z-;A-K71ReMN%T8i2O0Iey)aTEg?$1k&+S&s;OBkoTG=05yb8=ss3#MYIudv8Wqb8KdvZ+K9Nj04r10%(f z{)&=ph?M+<ppA@mO|dlB$}H8@F>%W)5c>mf}YB|a@RKS2_xVgNi9 zY&}c~yLsNveKJ%w%$ubO6bE39Hp2Ji@nTqt<_TyR81QVnrTv00I~9yms>V?>lCp=? z6^bh-`6;+g=D+8K*v)=3hqnRQTr|}znzl;XwwkzuC7G7lF5p>b-4F0?mwZvxSw`Me zCK1lN(U>``L__>ana~Gt@;>&|^*eE1CH1?>?!XJpV;Cvkc*$47x?dKS>aWA=E|w^+h?($CQPcmizDI8V_7@Ggc*S%w=|0{|Bd@K1i? zxV6#$!f~f3+-knE?G@T`oeRaoeBCV$?|yGVvFx@f5O=~gXLqsitgg@V0-?lB5jJfH zI{q^_;ENj_s^TW&Oxw@BQ60#x_;&g}s_E)rFXW5#(a)^(WrV@rWDFVI(jv%mZHJZ} zDxzRj`p(#?a1xg2wF!edew^zuofkMbXDCC00;Wn|{AbBCQL?6s%}M?|=J_W#twVY` z#D6Rky6qjFP`DrdJm$&j<1&;$5Fq=I-S&m)<`E944rA`0L(o$k8ObJdn6lWL=B@SH zV<5s_1?9(3$a$y{`nt|Vugw*_sttU)KQZC2)tm3Qy8A+!@->`eKoq^UnN>@8wh z1DQc%s_b?byFP_CEvZ2C*~w&O+DR(6%ftD>L9071r_kB!qZ&N-*V_u3F3{_7t8Qwq#L1RM{a-`OMo0OK;Jd!g{LYW+&A%Jlo-t4QQSs_Z~0)eupeVKHdjX8<0X ziB{;Hu7OyIzr3$POu&qNrYX!$+>ieH#3XYqn7I^7A;;O|?D+zWv@nwsVYNb^Wss^#qn!fQnSM~%gPrIT+Azig(8lP2HaCCM6y2_Bw~$AD&U;qdRqbWv$tcKUWJ8T4X{Hwb~;+(->|k{e!|7t4+145t<-Sz@oh+L1~j?sapmx!j*> z`~nX9*?jJ>vQlbpf1D!ixP}wJz-flh`EYT47Bo8Q-s*n*VtH2M$2~G@`VTN6kV^&G z0!1Kr&ZQ@<+s0(D=7B-)zvhc=SGVf-);FHepEwYD*$Z9J0OWlTvrFXnp~E24kD*6o zBZy_dpZEvqx9s)Lm-6|^&Xd)3T27oZd4!Q!(<15-Vm*DjX6$-UIpN|cj@n_EPo&b} zO-uo)aaK?8*SQgnRxlZo-7gbjlHBHF71FYh zoiH*B$pSI6S_$8rwu%mpVocUXBt(f{-BWb?Y-=UVCM)IQlzd|kl@8iDTypkSlps6_Qp)miesc6QQ z>uq|x9+Yk-JaziqMm=2^M8Sv^d@AwwQ}94L0{;+jxH33(DwwZWQONuKF-VNDdQ7~$ z_kEBmy#NNP+(|!TF)sZ8G0kR0KTR!eoT8XRG z4rxvvT@5+!fYr(u9D1o_n!k8q+pg;L4ztWzjE0=92QsAIZW{AY*Dr<3E?aEJYqYR- zB@?T&>?CPWv+ucurP?k2gr&AxbHk^$J4kdkbKa-|Vk?guXGb26TA)ffPCLP7O{dKm zVUPQ$$R&k`MxSW7&L)2y_f6B7yI#-9o;+Qz7@u6PO7Xb2wdDD&aNQmTwYEHfdP`d# zk6O)JXE!@XNG^+R)ZF*##0hRc>o?3xhW&WSn0|vK!uFCn%)?%G^#xU2+b&ue(TLZ= z9eW-u*>W1d7d$xBl4El3sNj&}?iy#lig80+SC zT*+(WB0YW#I1?rY++Bo7rg{SL^=5{WxI`#E{_W>nVVYEH%I#veC{%h8K`PlQm};y- z!MYNfIG1tQcaUW+c&lp0!8#55wlwO0DiZ&EIehx#7oG9n78m#%e^8WApQqm&L`rGs zzoX{2dm{{`=p`IKL-R(w;+X~_u1F;_h4hK=8a|QA<_RaESBs*p_i=IC=jxJ$=hGH` zZRJ+IXP{K{(8i0z+r2t$ts>0%cw}T}qE>B%QBxf>a|CjCKD*xHs6NzeH!tkznh(!g zF7w#SRHI-kU#pFH$v@(ZRkGc0PXU|tI8VPg>?asCau~z3-;pO9hmYrUy}wXw+4@|l zxG~*BM&O07&RA=*5w9_c5D2oq*s)52{UYVUcC|i`r?L|I7euRkUw%NuTqv|)8ko0vH=t0lir6va%}pto85?tM>2svU5>rx52&W~7}J zu`rLUb9m*wE2~BQEl(o5{oTHJR)gHnoZQf8G-ry8|ACbJ{ID1OiO=~jNJ-$TJjwk2 ze+el$ICu$UQ3TZO=Heie|KbZ2ZKf6;H(gS99XFO@%Lz#US$S~sPtV4(Z7&{F%G#Ka zIm_BHt{Tl-p!&q6TUSJ~%bN4$n#_988G_4ukYqq~9jMk#bbW|wKtwJ=Koea*L_Kar zKSL*F_z>A7pz4EHRgoLZYI*_EKyOjyIQD}}WxpWJaK)rF;&4?j1&hGtxD3k@!;H$O z;j3v*iY41A0vf#QSy>(~;8wSg%C47}K+|@?+S$~822SIMdCA!87i!-AhO4^GY4(X_ z+51ARX4Qj2wQefp;;3$2o*`AH)e^_-Zad7L`few}&y95_Jq*aA$j)+W*vt8Kx}(|& zU%p+~_{+Oro62$6)O!4I)Bv@@ahlH(EVdmEZfQL4VBmQ?@8Y0wy`17lY(DFwp{}`d zlnrIP2!JYaJza}Zb3EQE<>EfvGhg9c%qlg19_<}X<$SyubZ?zG2DY2iD~kjRO*703 z@M9*4lPkAxNk6v{(z?fV;&**OZ%ZVh)i>(IgwT&|k2FQ{B5u?i)ZXDmPLwtxz}12Z ziRBA?0k4hd95ACv&yV#=`XiH#D1u4_fB2=e5uJ^E)4P2^0jDKc^{q@z%euo*oJ?1W` zk1t7XT_BzQC14>L8(Due1xo2HCwb9+TM1Ii{o}s;`#C>tC@ZRmlOmCPo#6`GMcSip z6-RO%gbdQ3Uu$R(1MaenId*&bwc5zw#~gvhHN7jEC)u znoHf~W?#l9me41*v!l;>JK*g)oWl0QmFmJTqp9@|`v)stVTi4+u6LI^i?d+>8X7@GWC#q3C%Po3HS^6)`xx+Brw0v^7vm>FJZ+N|$AFtGCHcz4xUGknp1?Kp{Z<1+$@ zN_L67aqUD<%-=ipf@}wULX8c>f_>5V3ZZ~bgui+)0GkScarHtqvxWb)N&G%X$0*sv(f^w$TK88777ZY z?ZhH;39zf$ati(yk-31Z&7R;F&R6sMICvyrPPF}Z7xJGG15?<#fU_tZpcx@&c#08A zf6y$f2WUp-{Kw2hG{tYGkx|JDAL-HBUM&iow!I`)wUC6uNv0J#ekF78@`2tvkOCwz zRo0DZ3<5%HP;D%G-n)a2J8`2xCH+V~oMV0Xg&_I?qRL04q4$%I6~hQ?F6SLggQ^jY z&*!kbqXdnV;iJ4X!xucP6!2G*GO{UEBT}#_S2MTj@RuXXUnJ=#HEBYs=R^R>V@^M6 zxO&lmf9ZMwJ0r4W+|WAZd{NXHJZwKFUr1Fq4+)iO5Aq#U1H4`g&n#ZonS=R63SkCHwYCK0gP`^Ig zOLB8NKKk;^y?1u7#P|?2=ng);N@{I=XnaU6;$vy9R~vCVh{6U<-7rH)g_#9%fF1|( zK0{NNStTc}p5Q8S`@_(Fdl$7j)Z+UYc{$jN0ye2zdafd`INbZWF#%SvhY#y#xG$}@ z${S$=zCa{XZ79+Oaq8?hs6+^7CV~R%$Xf9NAE{+r^20G(5i~uVmB!WYeq!$C`1 zHu|=@P-nhFpG|(XqSoLzPk;1aU#i+J|AgQ3fOsq)%W^|G_jAH@qdpW3%FNlWTx~P~ z{E_;RrD|t9l}SU2Dv{QB%t=Lsx!KWnXCdFX&`x6UdbJndeBV3XYRhybwD3F>zL8CT zbI6#6hsyTe@?h*hdnKfv!*(Yv#~IwK=IU~LxL%zERU*T>1<^CAlV+aE?eMU7kp)Cv zxj%t#Ugm4Kzka)`@#9c!Q(d5YSnn5T{&v+H8J|?k2T46Y!v|Nd56IM{62av=v-AUU z^qlgEe)Ssb0kpZhL_zPxKCJV@tLpfuxf1xaD6FCBG*Tle(WeZ}s- zWeKvdM`yWkx#eYf34GjHh?E+r*^2)f7(f!Ic^|8iu*x;RnV@^Uy%miqKQAq&7GW4~ z26^W5QS9Ke1ek%H3=Qm`d70)YZw)hppOr{+Y`(pl*M%dRnzRgP-prF7 zZD-t<^9oG-DW0dtJ}shb3l_`#8>NJg`KuIZPPX)4loFd|xe1H^mQu27X9U!dY=BY% z|5qKMJ>*4i1Hg+A{fqw1?f7!N;^!sj;*yXA60#l8S=ttzh%|>~9Sn3X5lx?8yS!+7 z-G30sjI0ErBPZ~apzlKp0G;%Ij7vW6f^!T$=ciA?J?o>M96BFlDNU~AXS$FmA7y}O zt{mZleOD!TjsSPai_|q#F(5`DdC4QL0bB@wQ&6*-QTJ+M0&Kubw)5JZsz~!pm0Z;e ztQdIA3ty8`7?*%h?hBAk8d$PM;MoY*c3JO4(tgn??}>KZmm$?|Es)^QVJS?(jddsr z)$DE)P9OhfE8M!-VLjTbg>5_A*o3VAoHi)yWW=8&4LS!t6P1xCwQbEPlYQl74+L)(M!73dtzU#+Eu@i zIyEyBf4@S?$^yHO=yg50y7hUk?!`=)x0OyhSX!~+)??HbxA6Uh?+kAW7PUF0%RIbr zEI&>zk{@GN5+biDy1+^Qm;o2#0)j%UKp>-};wxfID6I-6vgP4G-Y%l9irq-^L`og( zL5$X(U26AkT2ClNg~qo*|QpHaJszLDK#6%@u%Dp7V7=wEGr}fF(Lu1;K<#uz}6Es|TD(#kl zO5d)2tIUA`Fo>F4wRTFQH-$HBDcSKzzdoCZM=0c$QNQCSf93IE!{z>Ls`+AWiXF4b zBmk;kdTO+_SPda@?kb2{|2|&tC@iS1dT+2X;q&zi?nA@X?n1c&_(qDm;pTKD!(2GD zrOD}RyIpqS`I+P4?lgwx(>%zEZ)mBz7T*P8LHM@~LmjYT0OpHV?3je!a8kFc!0;lH z==P3oXWIQrOL5H~B~nByfD{0h_>)!CW%^Qe#N+!Bo!t=fy-3_$52ZiP*9rUlOsX5q z-=g>-jBzG@GK9j1l)$wGvfy2GUBaBYixj%UcOyYFN8AY6ZH%0l+u`^RLip@Z>+u?T zP?&Mr-{x`5q-I!DHLV6>cTy}8MU!2|ru+4*Y!KM-)AchAvl5{_&`5M`&I(j`9mtt? z(!%LK7No{ni5Z&4xUrKNB%yr#nU~$dwx1PXC4d#hH4J9PmGD)P$0@FDk)tSyI~Ox3 zlwF%bi;92wH@>`E&~fw_A2$`Cs!V+oCzkq%X8ixfasC6&6$q^WFpB{CxUf_h`WHX? z;SFUOe}1|)z>hWw0zxZT4o^Qrszh2;Lr=eyZ7+P(M(x3#?K?7Wvti~^PX+;YSUKwvPE<+yVL^zLOMYH3L)8TLjm!Cn z49*k79d2l6VCokQ=|)4{HF zyE(>Y_UrkzsZ9H!rRq-Gl{;Mb8_W=oS1+T|02V>qECR6Mz#w=$?Z5%xXak={9|6oF z@VoSNy2)%d-f%x%PqVCWU4ooxxNlcvd0Or^(+J#7$#E#_j}EeE+#fIFG+OVkcf!EW zdpBvUo5zFrkFWB0B9y|co@DVJwrjW{L#geaiI-lw#o?_nEC{O5upQIven49p)n`$_yYqAY7UZezQZ}k<3ga*9Z?l(fD^Ou_t5CPmHtfS(*_P8{)tBq6dFpT$ew@0qd&%CqX#A+ zEL8G8Q~7`i$ax~YD*yaPxCb$mTPBv=>KKrYgBO!vV>py6a04B}{wv6W)%{$q|6Jp% zmSWD^*AGbK(&gYj_DAfRpPy^KkN;#cxGo0O=$w(0Km&J0b!N@aR~!~*?G2u=2TU$# zA4W>vIiJUHu%0PaNj)e_soYU)_Xm!mA4{;6>5XO_g)&Pz*{YAnDdjCw7TKC)rE3(n zq%gDWw1u#&3*ac_oGg^Y#Lh@M-g59K+*&UCk zE4{viwzyb?b<&98o3}jQ?B5LT)VAUSm*(KJos}OiP)LEM1wVh)4bJWcU+By9u?oGM~Yrj6OD^3ltwjll$v_&1Mm=(-2vAY_8Ca5^> z(nvU`?)t8WHQSW+Es1(KJ9f3MH4#1=j;#>4C`s^F`d;F|4+X4*L9%A{su9cOEFa^C zWD>9<1v2X~Ty@dw_2N0`@34~1VqunIvF;RA`P2;U6u)~7F)B$~>@}ov6%?lja)vdR~CbusT7OmN0fd=Sz75xdpI?#V#yNuVIfY;Q3h9 zmS6oB9%*r0hr_V|iNE9b1Z2jREZWWdyU_d>={U}4e81Kw0A%d|i9BEN>zmh86V9bA zuQ47GTVr2PQt^Fn>pJ0SpS!f^Vv?k!BS+Fmw(Q1~m5A!bJX<{NfP&*3>s%3eP0veO z7;HI!TL8Gw&kXMbk6vhE=oTV;;h?iq7#|yBW-H#=PaNvV6UbtmAgG9V5Pcw~^-e?68?l zQ&PW&rFGlW98aEJ{PTFsPAd60dFOO6H425;e1H$Hy zm;JI~k5^s#G_F@5oz~`?nNIbH!vJD|hnoezu;=^z+7;LPlS+cthgFEt$HN?mRQLO= zotDA4xC+MJ9iI0Hn zFY<_s?5fw5PbK5aq>A2&yIm6|Z~0L$i|DORWOtm+IElu)g}wnO4^r&I;A6h{4v}v( z44G#kTYPh^xqqTv|3yR&1T=Ewe`sN%;c!rivXTGZ!h95oBo;N0WAgw8qiEdmzYRwE z|2r5Bs4h(9%KNcyQ1<12-D%6oVds5KaZM;+0PYLL!?ZJ%OEVeN4mpS=Q?AzA+jf1B zKT-7naC$zliY2^F?oc;Wsw}6|v8*@gUHOq)>+Yd=c~>g73>$+9@WjW#a}0X*iNY&a z#_Wu1lV5m)u9Ffk_D5eK&-O?rRv*tdjdL&iPabp+l&W3$gfuek@6;GV=u4^9ukTM5 ztCSio)nD$`+3SG8Elm&R=d+E7vx>>ae1rAl#(wyz&$svIN28;o1g+qQ7soegKVAZW z1osyu3Np^o&2b`@_Z}RwQ7kla82lopUavNNLuoX6m8a* z8BBwXo+&`A?42CYp0Se>2%5ji3gW%L)`|cUI|w4D;6>j@tye?mgs@PHL|U@PVy?LJ zU&m&L{p2Fuj^jG7`|hcgxTkAp4IeNGtCa z%}Mm8X30(;CUIKI486ZwNcO{4#>fegclsn4At^+}$E7xdCGmqjhqS<_M{ZxPAk%3- zH#-uX9pkLr@jDCn=Yep9)?EP~+!6U_ivxD3jZR~|3`&~f|3vBvsOUEY0v-E zlaY#t5+~qyM(^{lG*$zV!T-L!v2S+D~!U3}ALevp3$Achfv*d-c zW;o+V^Lh;HL-FA}Z^B9nwh|!E1JU!+b-J7nyor&x7^ZK9ix`2Jeq)%3+dMLyBp`)pG`wrgkId z#9a2XhP|e>3+i#;;o3pgF9PPThKN4cF1mjiu3a`}8Dd@15^iChk5WLW-wIQBWL*sc zf-kl$oR`_QvKpxz_guYN?66sfh3Smq_*>rm8`yBG0|FYRc4}uL@ydQ6n4}B7nQhv!9Wj3p zUj@@*vcnT0nBzsizhtdre`z*mLn;A++REkIjS06&4q6i=#pDPZE-@ghQhKrz>S!ZF z@MFPUKoC!w=}f%~_h);zj$Hdq0FRo^me&Qlnf)7Wyv=YRWmo1()vZ9d>KTcKjVPw5 zZ(!Io?Mrk(;TZ&Sw`_~l3{|sm>ji0?*9!k3g6m)9`az-K$G`tD{4cY{*?`U^5*9w} zEk+N_-|?`hANsjuS#tq**{X<#2mA2j9nJ0-*Jq!B4@Nd*6PaQ<^dWDpjfM-6%#R7n z>9$^Z3Ly6n+{;nUXKN0Ix?M7vL&1^z0hyXN~~TQ}wox9kIbXI-{Wb*AZ2(1?01CW;?}sLYleHqHg39ewC1iWfH@ z5TF~$M7>8Cy1T}-ZKu7yt*2|Q4zrc4BIUTF;ka-|lG;5zuWw;9!9w67m!cFA`9yX( zZQ}fW7dw5DmpL(~dKzm_HF{tUM>|FACr+Nrn?S>y1fPb2%=-|zghZGtuuWnT4cfP9 zOX_)Lijt_afyk1gV=)v3W~s8Te@oKw?J_8bXZqc_IOl%*s8sl0jz}D4K+PHVZ+QXb z^1FxegeG~g(kuW#%R_qG?10Wu!HSX0B10zyDOKa144?i#` zjRCA! zjR$;xkoE#XFuJF;xulh0CA+f{FyOUOL$weB#S{C+*E_eeC zacxK+M?6s&wN{|>c&Ag$e8{>~p#*yy(brEbNr%=}wMn@#Y7l%$1y*m=5XLI%nc)5L ziq^)Z;suDQkps+SwP)fnfe{Hgu%&W+DnDMZl?&M&^ssQKa{CArm%tHtH6Cg0`=vJT3-pq=D4!HUly7>_ zf)x*ya$P90at^O5HoNp-tW)7wt@X!j1>^9GvCRzz3uTPad$5_uM@wWEg@T+JG<$F? zQ+7QR>gb@q|MMeDE*S^EG{OmFIvy z#us|sABNol*&9cYIn9^StuDi#CUTA_m|$vD*l6wbA z)EF?tPcf_M2bn8>ozf9fwv0*8ibgidP4(5APl~S~3Ea(e7?CH!trTD_!r+ zi4F_>m$Oo8k~Bb&KmnFA_}*^;sacD+=3*h~r0q3q)k*8@$JYW956=~WlJGpLvRz17 z>lWP@Urfro7-d|>TWmFxr28O`1_ArLqeOT=sc@Io01nP&#bACioOKVyEQnr!;R4sX zpS{zBVVD(h>VjYR;K6!?{{h4}A&!H`DD?0#{PbmPA8ysaS02;T8Cix9`dJO8-Pa!y;S|{N`O@~J4I&^GkPUnI3#i5oMTh25GiJI*e8#%Sd9p8;26t zD_E`BKNrBpz+T4uf4~$lxFTig?VkTlts(puwZ`o~D${TOjWT`MUZv9S8xw@SuUKia zgzf!cex%yu9)W(Tf5aC5y|KRFm3P5Xb1U4>iJ z3%V9X5Rgs*>5@ho1f)d-6qIfxM4B0pZUL1>Lb@9c-Gg*FbmtI)#09WVtI)po)@SB27kQOX`q5u37FU+QnxV+p~@G5Dcm&&!@w9; zXQnr{dM3bD|6w%*Ab`z1X!Iwtlp!GnnQlXjx=fLKTQ*ElIpO!#-|=W;=R|NyV;kEL z5EPkxA%`|3c`}fMu3KwAR(b3CqNr!lP1@}NOVXQ9jh0cCyqjx@hQ)fUDZ{cwG4ZrT zxifDaC=zI5Jt{3X;=Hj=trNW7Y0qbR{zOr{H$Ht-m>qJP(>k8zk#>Qcj?onB3vcIv z!h)(tCtIp%u@c+4xv}wE@i;uDkN!R|`ssC%IvwxCOCVIcy;I@B=0*a1Pk$1?Rliq@ z|Fb`iB%TP^$+Pc)Yp&#oa%?7h>9tf5)bL@khHb zqxUEvEPMI4BC-#m3Isadl?rY77CJ?mJ}GPRHhb%XEgNDHHPkLai8@@0-Iqx^60 zE?vh@w_P$4`BNk>R*{g@AZK?2cqNUy`3tLKW%q-$*D|3(9D>A$yDl`CW@k259iP5) zxe|M+4BuE-0weykuyjTrkJt*3&0$b6yp;8RAYgc&Jxw$Y?qYM>F*=x|tp2M8Z4ZEj z^Zy2mpqp2SpY`9rLN|v=C|-E<9df!p|9?;|8;Y@B!8Nb9oM3bT!5pJEuMnU?TaySE zlWmP=t61_z*jC%hrgF=3raHZ0VAUoL_r>3d&(!^cXgD@x?qqqU#&}dZb?y|t(C|L_ zybEu-Y#|5)J#5_(<(u@gy_l<|CSk{q$E`-3q{+@tliZsxJ>HSE@4$bHG6ns|~ zh*tnHJa?V_0oDVHK&gdyC#wuxeWg4F;0wiqK~^m>x$0#&tVk?l$-*7jV8*nAx=NGP z&PYXpaU}@dKa$QURV$nb)i|{1DA%C5PAtJ^H2uQKC@ua2oUYF7(I^c za8|(Y_Jw80#v(#25_p9~6GE93Nvm`-JYbFW8J}6G#pgqxZ!4|6mR7>Hh+7fROY?o^ zrkv*}qfNH1%tNAQ857DAo|ojw6Pg?H?z)cco23MbF2y-=i$1Ng^%1S3k!KN;58lrr zD(|9N@s!Nq2l~|6{s$f$4j%<kY8x`cNp3S&-3jMYry)tY851P8@!IJ1da0Jvr1J0>eufuBgJ= z{{S4c=+7{;Twy!G22a4SKEr~!wpQ|lCJgkJ?|fRe~qvAVxzb(nig-l9NtcY{!PCRFlfl)W=Np0hjp ze>KE807&BcPe^iyki(FH1-~Pdi1~Tyv&!u5&s2i83w@Qqj3@&R;z6=fSWq-3Ih`(y zC~q*4-(nzDr8*xP$c)St-_pz@it)nq%@)_>7|ld~9OC|VTsWHd?CZc=&XfE%nO?rx zuRZ+nUIu(;c#MA$&8eRImN=4}=7d|f&T+8r>YNYWUh9%U&{aagvycyhQ?GluBaGWT zc8tNv)t1l7BheA8+z-lTzdP(`rrYVltOmmfgIc-uongZ{k~)zF)Ks?6Vv}Y9_=B3I zxl-rOp;k{(ST~I;lTYd6$D0#!Q7H~(-Xe#&P(ewgl+Ss=uL66mf=_9{GQ>igD%!F9 zvgvGhxLp}e@FZtHy?fLVt)33KIzBmA&ZuaUL8G@(lnnkT;)Z4adlb4xUKFkMZ@iaq z3Sq(6nt+>y3UIRo(6A*j0w9t^7ep@pELE<@hr>LKqN>pRGuMqA1oT>G1+r+qWqKCf zZ~f4#wZCD>gE-J;DNLH;0}FV~2WVGTK_GMOc+^!4A|*C3=|4$}co;+*^A@Cl$3hp+6_4d(g69|a?Mb4+!muE);Ua8zuA#>pDzy;0@ur7a@oMp{>zUIA;3~-Ed$ZI%}NCn=o}aJy9?iPSM@VQ z8NTd$lygbcd#Cc!rJsJd#i>^)!%$F;zd6NqP?CP)NanH5ae2$|m}*tOJSALkTxRY5 z2~bY4rc^kmFgHJ-2L z`8fLOWY)1h0z8L-P0#@IW2bZL_}IJHun-^^DYg`?;jq8vt0R538lsVoT8nXq09hS_ z8uu~m3DxtBOap|(P6ZdRYhE)**St|T4Y*mVHxZJ1^+d{^R8FEf8_G6Leh&-R-Jfr@**)=UZz)$q&ZYq2t_65{yncP&K&B- zlkyKLe^r>B07wD-6Qp1ga)AC%Na+iVzDrK$^skVD0w5(7Fyk+ff^1%WQ$XV=dV;LR z^3+q7QaN4LhtqKl2wvcFGwD=l-t`R;J6+KVvLw!z`atokt^DoX4q=+pW&IkLcFyh# z?LEU<@LtoE>WPIqFaMiExF_-t(^U~E(&e%^AH2Z1{ zLgN&g`r)wwp+5p8svDm69xMcZNrXYq>&z3!Ds=Q-; zU(fo;*j_)dmo-8wu+h@lL_{U?3zjX|!mzthG0$|WU3~L1c&t_@!P8cEHqChhXC^(c zDx|>5|6C>ITT4W6Qh*MlEm_uMAFQ}MKC|uEd=BRUj%VVEKuVa~$9B7@Fu%B1Q5;T2 zT7+NrC!}bVu+9FN^Z_8{KLmvTTqXuA_!JXa@Ol_Q@N?#;EM8CVt>n}2a$3@sM6>c| z9Y*nr^s73molZ$sK!7%VKFEbJfHl7;2H~=Ya?GP`cUfEZWpI_d_+<%H?;mttX?j<7 z6Uz%#Hqdy{9?H|>y{_zg7)R^W$;=F@8h8+t>Y`Zq)NHr$o}=NR4Bw!g%b<*Mi|eo~ zrTxop`8zSv2C1k|>E@oXh-SnYR_ ze#1t%E6}E&=}vdPnVqP9zI8iCjeE#4B@*?kq8Z@^5|!H}5tfheV*ogoEd> z`U_wHy_d%1$=(n4<;%k%U;t_Tiw1t{N}#H*7tEJ5N)p*7L(l(nX@@|1_NTiqiK1xyEzkkF6(^Y4E zqPx>pia>ASP@51Z^j?|s*B#xz9vsd9I=KB4I$#lUun4dc{D*^Mmel(B$LM?HbeREv zd0pCLyq@*F7Hfy4`$YpP@dFUY%Phfgrmsa4Hlw-9_Rm{YTx5TQNTa`QlO1eMmA}IC z{E6$#JyXC`8|!0JXE#%0ve;vJ;bv;kumY z2ialtUesl!AI+k3oih8_y80WV-{}iIq3Mw{Wl2a23Iv^~4jgIF+FlxB4(Q*}{hG2!(s{+ydQ{%C7}vi8V@{dx-3xyzHd{{+AAlluM$1>YN_}eO#Si zqWg9Ryom+<8)!W)U>? z$Tgy7O2R~TrH$8Zg>vKowh;1f!{%?(R|QK>i1+JRU^`Se~=yBayX_;2-s&$Dx^*{ z=9gL!-X_Bh>#%0&>z^h^zSnc(g7{*ntvv`Z)Kwxavsz(NBaTSRh>*Fo%lWoA<1U8A zkM`XXG(8)ke0tXn;eXqZ0i$5!>81do4dI3U&=N%z!nri?Vd)}vr_$42`;kD#T&jB( zY#lLWPF8dvMjYaq>I@9%M~+9|R6#Q&bQ}A2lE6II!;rJi2b!g3$g1xx2~_2t?Inhr z=GkNw-MsFKf4wJM0K{PgHoriioV+WAY>vyNb!PwPL)g9PsB)R;B|Cdq15t32p0FzwKqa zdog8YSm!#r6k>pc=>@!-JqU*5c1l;6J>NU{U zzUa%J?x`LWSx8Ou9UP!(@JhHaGYBn91=I~{141vURTymm`Ga31fhz(wSszJ0g=K}a zL;{GxT35JgN84dJ8L%DFKn=Ph!M5s-nAK&B8bv*0j22(oV2GjF{=fpf8z|;D{vHm1 z*9a(F2Tz7@$Wbb`JO(aGM85A;%O zw{R#j0#QYjnH~e=)`aT|x4-oNZ)tv0L# zN`Ak%_R0<#9*rvU;U7bl?!bk{UTQ2JkFZe!L&0)f(Aw$8j*I+b+Mzs7Qx<+wF)g?T z(IPW4aX!Ken5t!Uiy$p1-;(n%8Y;Aib)*Jb@}1^E&ik=nA1{R>)x=<5E~FY(Rh6GO z4m(Jyd#nIiKVW^smG^utCGHyaE3W_u_hnT_o^Kb6`V!A4F%w+C%X+UpZwg16cW#)` zz0MzOE7_5f;y0`%c)xs`0!{SQncnm(fn3_Fp^9T>l+bpdSJr5w(AJU63Gm zSL7@(`a<#6QvcC15bl)w;|_K{71``NHjNA~&w)#EA>g_Y2p=$?Dc9>3^5cc>5K_k;?{7l-lZnjr zr=LgjO*`_Gb*5~^!PYam;ip4Vso?#|?_e68$jka8SW8@+?@)^?XnQ|3&3Ow%Lu>` zX~+z`gXpEFe7TZf6ifvq{em9I75=?-#GKvi8QGFZy32H<(sUrtwwqaiz$Z5I2xvRU zgp(jvn1vnV^#^mMtJ-6esHflRm}0p&H<)Z6LmKAoMBK3R;$9giuf?lA*UJOHc0lFD z${W9CQGEs08@I9v>B*0GDSBkBWv1z7mf9F_l5gxVhr@2@6Z_LTnG^%-jXH=f&*oKi zWpG|jvg{m37JK+H*=y25g{}P5n>ItzG2n*C{M}|a#zLZ^nfK>)@te;0lNkN2qWsJ6 zVX%wwbYf+&|$_hyl@C~^xv-;;@9A=9#L%b zb{$jo`%(iD28R4vl;oi`3b0ig-oaNUSAwHzi7)o1wE;R~W;^|1^{kNwz=_&#+MUig zSMVSgpA;CmPk!h{^Hj`v-%)=x#p!5|oDI7u6Pxi7OmA5G+{WXv7AIruu^w;j(72K0 z$a~iRai{5UB`uKvwUwAN)VQ6Vl77BZ(zztDQ{HRr_^UCJ?gPAfaOh&M279l0zYb~q zWWO`io_M8syZm@Dw_06vZB$3P^{`pBLiA+TpiSy@*vnVyc=5e7WH^Zrm@^+5Ry#SG zboDvDfNA)ijUP@&iC!NpYsjEh;;82a5@c>QMftmFWlRyxRHC8W0r+oC`^vHSuixCa zCI>NKq1*)UHKT)wxlOx7YL-;@|&rQYTAEXC=ByOgJH2f^1 zd;Hl$KQ00IJ)V`m&x+Rh>+b>j59q+p`ft&J6zRl>L9!ZfpY9jrE}gyPau*Lk%qgxo?gRpjy8 zhF}57@=}z`T0qp^?IG?Y*QL*17ujU?dZsUXa`6Z0T7i0zp$66YNHs5a_(F?M{Wa3F z&U&pO939M~XYUAWOU^(fHEnHo_vOh`LRydqcSl zs`t&2sV)oQR1D#Z$s7!y=89(MKKC^i(tTl8r(lf0mT)(B7IxEp1;#VBwutNuVY6^m zbO9zMIEp{4TdEPvGQ=r8SILWQXJxmHm)68&2_t>_jv^NOO?b4CxS2|R9H^*h^4s0y zNBIU0f!$`Q;3}|nB2EWLD>=XDcmGHwNO08q@zm@+#vK5}~B2rKM z-F4anta|*HU8jIU_3$Q*M+P`l?+D*kyj`#VeYa*@5W*EnqBb0iFTi#Wj(2oB7V#xv$5Dru zV-rfErK0Of?vk&Y{GBQn=Dn>F{*LoN?t@+`6T8E9W&=Z~ey)sNhk=LHEq`<|5h}R+ z;1#v29uQmu!oZRjmo7>X69WPxGK6WMK7KwPL7+O!kayx4au+nBqoYw*~8IsrrR?Yih_vx6P?VU4|?jW&Qm7yQ~9+Twx4ryu3PK??RtnbD1a!iImvcf41g) urF)w=iR7+GbO5WfDJEl5GxK$yfLM zzqfAfs_vp$&`ahTbB^)ISb;K9qDTn12oMkuNMFQ+tDYDZ!yCjV+H?! zw3QPTfG8co-vj@`uCMaNKvEKd68!l!1f;()1kA600l#s3<@(i3Iaaj&kL40 z{ja@&_Z;920c(d|t`hUt?mS^WW!$~_>s#;<{OI6qS)M;L|31Wfe#l|Nzt6)HCX<*S zk^q((^TU4~1;L0H`oF%20t<<-`$@av)8FR?FT;O<^Y_IPdwqH>!!HUMEd1rajslf5 z`sa#%?Lp|XCln`w3ia3j8Uge%w^#o)OvDIK=wq_&-(>%5NWnwL^w+q7&+~tW?th2w z|DRD8p@oNN z~(kXBr6AMe!^-J8xzh-qlDHH{bdJRQMX07JzAz_P4|L%B}USCAVM2)F(|Hz1g zCQ&NELXBxWwQ8kMuH=Kpvd0Vbo%yrdj`4ByU(uHiB&cq@R+?~9U> z5|VbsO-D;KApHr&V zm?*MnM#hu;b)gVm+2~_DE_U%dX|76~j{7lFj?VMvTQ(ycI|H4*!BH+L`1pU8vHC#% z3Ev9Sym6K^kLQMLtWl?cQ*Y0TZ$lD)FM!j6#046}nerwjGM}N+I({ zsKOeaL7IP7L9eeQ5eiVaXs?jjS3SQ%MvH}N4`Fkm{W)L+|1_EyF469Ci|e3&^m17R zy*=z*T5_zapzJwHs_te!1Eb|~bD@Wc6YQU5FaIY@R1cK<7DoJRf|J&{X$7~#fraz! zwwo$@UA73}N1wf>Af;VgozXw(V~Q5)3rU9S^`Q>tm%yu;*urg1mNqe^A)2TtY^C8e z-W)O3QnWi=^8&YPoi|9`3um4#U4BRy<=TH|uzb#6JLwH0ph`?kWROp~u>T>ER<6zq z1javJR?K3N1D$r+Yuj6%^ErcR)}l{Jn-3hVgeI?p|Kn;kevJ)W0rK%~aBT zy?Tj$r+Fi;E7P8@TbN^HbyV+y2H5KIe}hT?U->g17Yd#(15{&9rP*i~7#t>ln#+#7 zZo*=>mADe$*HVYk4=TeH*ZI85c(&i_E=##-GM^+G(azpeybUX&8# zRIc`t&X+ezJPI%;Bm3BKwDf~W3mFq)Cb^~mgfo8$ zdBiD{%VNFt*$V%3g@}-Hh9M&2SN(yDCjD~{$HCVZvjvNKKVOBRs?HsAA zcXdh!r#ZTOU4cFoRl1UY1w{aV=P2s|$Z;j82ip!fJ0e#Qo%(H&)@mYXM-HQY1m0(k zZcdR>mBU3qemPF=&Q>z8Aea>hP@^v4%*9=Mqmxp&MUp9t48@`Fb-ZZawpPsRUciHI ziL3{i`@JGrkWV3wvsN3>os16Ouz#jRU0g)O4?u;-W)YLh<#@m4%V{*38@l4tTVp!= zl|&%PX>!^1gv>An=*Mg^Aw=GKlh(|3rs*1_{?{2Z{JNo8Byc)h<~ET-Nm}pCK{d$f=i?;lJmjv6gH7ElVxYb=|EePz2C8NfTfN#ZP z3n~dd>nL;Ax=%dIu30L}?gVF9@_I)nIX3Hi`zw(U1v*`R=l#9uu<*NvX{p3FOOBQe zWJXi@6>hvo;%$txnjR(1PuJ??bmC7MX^E%B%tmAF_(X1F9hEv8ukNlvmXG_Ve-$x> z$^4y7RzlEbgX9=L)6x6Gj`n-Gn^Z9O;oxgm-US#f6}n@qR-P-5c2YK7eBGL*Lws+k z9+=I~ePHZHoW%2i$D3Vpc+}X1kjPYr6^WxwMta&d2~+F}Xyvj;S`#q)C6YQ7u|ent z?L#QGwL*lG$cktfe$E@})Uhl`|MnO^?S*BSK?iNKlQq#db$Jbc$~7-sGMOS6JFk2C z_hAf%A znEPbJ6A1quW%S-7nmA3znH6dwxxvaJP{M^r+F*2nwrro$zv-M?zc?0G-PGqwg8#C5 zubCuZp+fBeS?8p7N=aAp<_K?jmNTV3%nl!q)hxpGxJA8usnxxy`DVH4e9b?av}DBQ z&&n;M4-+*5=ki39bG|DmTxsoIaOlBbnBop>tl*4~$-lzcR~0Up?qR2%ajVylfj3pj z;h~(TC@isJ6_}-plSN*~f$zkopey6YWnLjK;A|_cuIK+atzRI_CCq50#rLxjneFZS zdb@=t1H`MOUzBjVC`@%n4&Ujv4EJN+=^F$-vPg9e z6^Qwo`tEF4gZPnDoPtTT6onpOn5%7{G)XyrM#;q#eBf6(CWpsa1kda2P~zYX~C&t5R%xG@Y)=VXvw~=r?4L zp@M&3;z>)(=%*E)sA_K**=ViTIZ*UXwM$V>YZG% z2}jnm#TQAs$O%&O_GIoHZa9>wd_?89dB{xXA+CPPE?F9WBhlYI{XZp| zDr7zlk2OILGH=f`^g}fD61L=+#+RmbxVELc?ZY>m2Nh+@=Sj6bj%SJVQfH7Dp{{i$ zrpR)d@!s1^hO&vb0iVp`EFXIB`8I1o7Sn)tU$+WAreAOA8g>V)RoczVMLUl2+zhOC zy?PiKP2rG#23{$ik9|k(*K$2N!ZJbjMaez!!z+!ZYkllg_UHOHv}^qN6GWqX)j#6&V@K1jn~ylH69auI zfh1`q>aXr0Xw3L(xnUT(nS83@T0g>QIXFa$tjmbkjE}0s{M^9w#z2(q)jfJkXu3Lg zRMDcPXx1BYozDH5>I4hmMk}8yb=|a>@!9{&)BV-cIVq2reQTf-73}ua$qGOVxEC9- zj-yDQc-H?hEvo%_T_Y&#Nnr@nKHCooug`Xrr;K;`$-A2=h{be9q9@!#B#d+4NR;OI zUjRIX3H3Zc0x62rfz9eH%t$>{i+;^Ry3lCa{x0K)EG0c7#xh78NvHE>(uGM#pT;VpQRf)bIlDS$^Crcl+IdQQJSTzg89V`I0Y`RA zjUPUD6S`lDwH>=%T`BkE=nSBHI3oG{z_Z?z1?p2BWN5XxoVT|Tq1US@#GqKvB~8_s z))6DR=HJggUf^oRY0l~!{#!BSE5f|f;@_}PEh<_PSjQc#VB=1$U^aa9z`(Jd^%5Oy*31H;1JPEZY1C_B(!%z!OK~mRE^4_S`vlJ~M)_c; z&8G`%+6PAdOt2G}gpC(V4tCCPM zH#1^Ek-9YR*Lc}Y(+&n2{c}8v2X+^}AC&mvMMdL(IMCADEoG4>brZr3cRwCYr z{uTU1S2z~LB)y9)0FIPw5;Zq=1&@9J)~ zKbW~Yr_}uzqv*U)J-XQ$Onb@#$de*)-ngoJBxk#qQYx?d_7j2QEE`SlVp}qI6HS_% z)^WwX44>&3yDf=;%i)r18}fH8&VP<3gA=dOk6^MpTNy5TDbOx(yDncmV{BFvgF5W{ zlR{1fM(0SjJl}M1AJ-?XRzfxA%d~Aoby{UXLsRy9%`8DZbTM2~$6Xxe5@BX%P#5jH z0Mj_lX%u}P>zuWl;=A1XJpB#2)Z;T>k=DIZUqFcy@CfAa>S&G+!ST@WSe@wv6c4+N_T=AUS=ngb~pQqC7~43b*pzhV(k9#uVH9N1x9{=lK` zh+7xrB56~tMFBcoh&uH^Tm&x<=&@UuLRr$5%D;I`<%5IP;l_2y9;4CRD% zMcB?pH&Oo$yAslf7%1HJ8hz82@IsA>W#J1rYG}&_r#aZY=lqWTn@bKc%BimQ!w|qR7p-Z>6mK_7cu~O2}j?KN8=juRTLN z-KsP2v-1x|;#Mkge*Yl13hNC&nRpiG1GZvhSK6t-Z!oBT7CB-sdYE_1zGN~o>-Az+ zqgoGf100;`W~cO#7j*KFOJYCsMdm$qP?d~s8PsV^XoegPIAqB)dA5iHWtI6Fg$ctX z3D2)|O9t8DhzyI_jWS(SU`s%UJ(r|~^7g604|ul~w%Q2WIhzV3vUh=|CLWC%XT*9ZK}Nrq}{%0`j+5zVez z@KSwit3!1L^Lywa;C)CUPBD!_sAT9DHafb*_4}+a>QIIY6~z_V4QHvM4pWv)LVL!h zyG{39g^P7p5cJ6kTkbyThbiuhBBdPJAzQw*$jB{JAIepew214>;ox3k<9NIzA{qET~v?h z;!`hBy&^&z_yY%6wLNhmte4#9m9|2hMW*!4kd+EMo!%N5F7vtt*VSIE*07^>b>_pL z)B2}O6NJz0f)!L$NU4;~ujB#_bbiPXvAgkp!PL-N-%{#8N0Zq0i{x&}B2k8mVq?SgbO@Rq^A?)4t;{SvWq) z!%nU$KzA@Hf@=6if9NV-i?#f6h+-iEgCw;z3m_ypszH&Sa!KiHHyFw2IfMZEiRVALG zVC+`tfZ%hQG1Ty_Tmi!FH+$*vXV;mBxW~u+vuOerPWCrT*NZ!dGmn!V(#Ve zwnhlUL`0TY(!?anCVV4m}-i{ zf(^83NN1xlA*j#63$F({l)C~9mwwLZ?O}OyGEe_}vRd*BwI+zEA!1(TxIEtTV(UUb zi@QlFjmdwXEDL-0NQWoj8eI@c&S*XOr9 zb`gNkT653<$g+jXVmfI+}_ z3i(`^t+3K;VMT2(AUIOI7eqzmk!*L+N1ITBeE*_-CkiM8NUE9r zjpqt;@zVB!8mO&RiQYct$YpTV1(Ha5@3sxPWYoxdKc*j*B?MNY)?#U2*VdmLR4$U(enuP@H*9Ub- zlK!UHj>x(LwX?PMEt(bPoDX`9Sk!6mvonQfxxDVz=cZ8R(ogknH%RI~#5Ul8_w#wz zV`gj6OJHDjwL{BHw9}u;b>ZeuGP)C+D*CxTaM5lu)MltcH(`8~CM+9toTF z@{>f%oJ(gPNmumF{Mn12FZMbXLk^Ds4lRCKzwfEN->d7KN)Zx|;mtNF0=qy9miFAj zgdR+iYbdnBAI%?QXNlCw%Y6s9c<*A+HcSp14@yzp1ag*L3!|MAU!%oM^m~gw9wNR;{1@f1YQxb&RNbaH9E$hZKOwq;;|(?f-%nk z%S$usr;w#iUqpM~ulK?a9UFgY(e7X8Bg!qu$FX1R+wYt9<$Is-E=M>~Rn?no61nMa zd{P{SgJEO}qZWbL(r&nLZ^l8UZkJDx_z^qiWZhe)`ZHwV-1zS^M!7d6I^dvO9_(M7*ZN-COMi+iKW z7cj9_v5Wh2pJKJxx0b5}im6ftXT^KtpjJNHPn>k~ZauI_#!qmB3 z_LA1R0A#OAlX`!`hAH8^=0WF!uU%y@EU_6Ovwk1z`k8DYy+%?JOu8@fSZ}H8LVd(=#;Q>YZn3FYuB|;w3^#aA61nGc?E@PT zoD86AP4=#8X1qTgP$5GWi(BU9K$jVK~P4_QSq?Y`qAYcU~rsFwD0+WNV|9hOoy|8mi9N^=CVb?0hC^Yfd2=i~72~lm zc(J=SON5Y`ba0t-=%=xth4pmcj~&b_^#^AJYKV(qn=O*0IHz24ODeAgr$uq3889AH zZ`axH3(pM596g>VVqjzsoV8n`ei)~>rLCg{XxHAg;}QR z*PMh4GQS$rRuXO2W$iJ8$KvXAWI2*JhE}so@nm9fVl|8?6v{F%=7c5A#gn2;D)~H2v#oQ~x7NhLu^u$M zEnXv>eYb-#2pa9{i}(^v9;@uL^QUC7D z4IB1gx+=~P-??~b?&5KBB>!p!caD&!d5UcN75sH7Asd7ExI((uL5T=yorH+Bc}Rx# z=mqJ)yM!F%c)=|yh_zK3?;i^GKOtdicmB(9{JP65;5iQG0?rhbZg)1Ri~e&XvC$!0 zY}rz86LH*RvvR)=atha0y*sbSN90`_U?21c!9kZppppM!zKOjG!LR}fmlhEKys9as z4*1Z>Q;HT_AsoP2jH^8f&FiISkz;L|~x*A99}YmCeD5n*Fe{^k6cpc@%g8i=QmJD(w3R+>#j@ z=HW8nABjy&%Gc0+kdvdH$k*}Pboq!Q0+Pz!T7%|wK1X-Hs+lD#c_-hHl7hcCRS--| z9=%g#$7V96qQ-3b0}2>iHXKI4bpR~TPCP2rY!aW$9nQAiAkUQo0$a;8@*BbIQxvc3 zF^y`X>i%TQ^v%2VqRl1ax$?-vX+t8-vD`4TACkYsc{Fr2U@Ti0Kz($+9-rRI>OVtDH8 za(`OA(QeN`fY4&4b@;NycBeEq?P)6`(8gpkH`Q$6GQPFBhDCp@RC3oNjSzIc`vB%$ zv!Z43jMHoH&dr@I{!Lbt@%(bHjfhnnOkzWDStf1;xM=FmR?k%;vMeF5R-;mrlWCFc z$75Xr&@>HhOUpm2L}%eZlOrE5)QFe1t&!h>IW{UYzBDL7WFp$URF_t_kzL_&WqBTZruPF>&p#rC&-H%>ZGrjYx}vcqP{0xxcSN<^%qz!HtLiOavs*=k zU^B?IKHno^BqZ8AJ7W#*%5G#gAe?_kCd^M4)PJ_TkWd9|G)`uG7HoKKRA)BR*2H1d z%ZJykx0yyTM6L4ZxQoX|<#fFs-L9_+dZDVew7ql!)6nOdcu(wCF-##Sytf%%-oV^E z!@l*Oh>|>9e#sd6 zt!C5wH-li)<*PFFVIUF4BgzGpGHtx`#dZ=m*DKo$fYUg~MTo6_Pl)B_aGlmg5u4$$ z8CjV5ycW+*L>y&GS=$;>kIho4rmH=wiKWj=vSkY;%01sByq04%I;$?Htyrv3#X7~J8CgJOMh1WDgF|Dr#S-}^ z8w8C{aWv{C-AuS5ZYSx$q=Hh6qwl3v4o4?FjZermGo*(rV10lbu3!s0b0*PX_B zbHs%mUfi~|=e~72ovr_@H&t&@s8xO~6y}Uey<{GxP4E@+w67KUeM1+&3Y+D!+*=|W z9}3Gh;?A|CZ$FvN0l+N}^ifQdx_>TOMi?gQE5D$GhT~IcrBnaA^ng-Q3zlVStvA|U zn$=DfI8~Fqb%mUDFF-g1AJYm>hcdv6zuJ$7UONhETOh0xs>Jq6JqZF!7eQd2= zUY$;xeGDQ#FV7FSsjG=5Zp9=L&}fVJakQG$<=l>PSivHpxcx(k7>GvI(Pg9Zsl}YF zEO;ms zOl|Y0BtKqvG~#`_pDn}&tNQMt81s!4b}~gbL>|Q#7x4btgm$|q&VowngcGToaU zNO&{Jhxb=_*pY8Z4ka2Iu8{I(wOso{EDz^4Hp@Q?ZpJFV7;1>un$1%mFavK$uNrD~ z9cR+ZHc|?XKUr;+!_zU;6Z*8Iw!;I=>g(D)lK!Ouepx?aVHZf%3aQRgJ)V8fmmHZc z^Um?piHj^8-(G%PHhonKX&uC%V}?!EqefsH&f}7NuXCXGB^`;n3b|>mLditlr)}DYF>y&$LT~OQ0 zXU2?|4ui_HD(Zx|d<=ZF6RU_)Fkv;M@3#nl~Ea#Orj; zt8$a9sjN}Vu=S4lGke#5h!GxInzNEc+tb_7&hmx@k?)`ItjccB z2aUILAb_o}N#Gd5ckw7F3T(Pj$@J#N$U=p$$x;_@F6aoUK9QeY9k=A6c9kCjZfa@?EVl2`Up?UTI$=)K>msX%56_u`C1{jp8lqeIZsE%2V}RZ1_M ztiY-`()bFP&>(n_HNhY zi}`PvTj$_WG{c?O?H|0_&rkFMKRTgI1;n=+p)ty2wp0 z&ixx+;3}QXON}C<2#>K$^!I6k>+1+^m?%aB(ep*ZFrPmZwphD4*r2@RAUa3%R~-$h zjF#c)aCM~fUyx!^#1m@4+|lRhZ$mTNuh;U0lq%zzMzVyaVo}*Je)*kOxk%Hj5R#5A4){3JoyQP+YNQ=2BHvwHX)2 z8k8jqi_;&B8;F+XC8^pO3d@C8j+30RZg|Ja^?Tk=^AofFR7D~P-hPLKI~9L1!o`E%5qNvk!vCP9UG&OaUS9sg$B(`705!^}__{6)kl>eyi@AwIXfUSlG8KwhiC6igN zwwfQ$#ce=Gb-xC*wDJ4|IL`NctDB0?X_Nt|Xmx)WfW^{y?~lYOLEH&7Ih4qx-^sMG zbupR;n-(Qb+p0j@a>)qb6*zLw{&u(QdW;qzpA1&B&=NVX(2NVb7jg^a^ZNAT0Vc@b zYr#rlwCi!}Y&^1;>nREJX7FfR)R2V7S%`q^Nlo1&^8uDTVP>^uo4el4@lxyk2tmlj z{efi++O+?|owpYwm%=IE#UayqH(EGXQNojKmdGIV%X2uL$MIr=&7t*%Tkj92MPRSF z%}UG7An_Jr$F>BQ!(l|z$z6Q&-8SBcS)2P5$3wC0=1|qh`Gz6b@N%tgGuq+^#^k!2 zxycr_^NjBIvo<3Qh7Vzbpn^c_*x7Ve^M$^nx+Ri_eeneOxvaOxN9!Mg^*epe7>y_3 zfWrzsOsuOJ&|B5=(#S5Q8!h3xyIStoj2_|sM7+#;VuN7AR3Dg5_(<1!$=7Cm_S>eK z`C+w;J+mZLZ_98vX^$M%kIfxHmLfTpDnz9w<3p9uci}-;m!ElP=W#vE*{`=Zpd4A*yZXVYIZvdF(~c# z7JoiU9CEXrJq5OQS(~i2+-<8rY6!S}6g=dYNmKqzeENTBg_g;{<`*ubxSpnuq zuuuq#@jaIN^uwq82!mA)_KT1zfz-mNW~W|^QhAgfv>rY(FyMHj+)YO`x*?3EwK-T+ zXh^+$7Dm7%Wzlry;DsmkelJE|iiJ+<2M{$Vn|JUZLYaS}(drHNrc4ccP0{JwjgJQu z^5lq*E~pk}gK;!KDBaz19Y)b72k6Oi9no9;j!M7utH{+8w=N{MyHwE!tG0yF;MzmE zS${}eL+21${Pj!g*2l`ht&Q+JgyN2^54T$*DR)`m{*dll1}qGWflJF=JisWqS^|HFwgK!4=3N~ev*B<0mCZ~+ekx^-^NEu$o$!&N|jHbyAwIYxF$G@Vp=| zJ?7?e3$g7JKC4lAip_q(gw|80IWCV&G4~HD0aC>tCIb*sO{jiz0^F)yNyQPZdaVTS z!=e1$R(v|_S@$T%j)w7Ah8K^yR?1uMQ~pE-T|c_;-&RZ%ieH&0UgO>J3meJ^zrBY_ zn@&q2eE_`kEyrB0j$nAi%i|8D>n`Y(8ZF=buHcGKjl2{uOW!b5Ydu}_DFEQE0r%}* z)}Ew1u6$^S8pHl0Ub;GR{!kmU5meRjothbL7?Y1?5OjGtDV5Ba&(kFUGgR)AzXg#ad>5APm5A8t zX4gNrGX0?Q!cW~Hn{Rsz5`XGu+W*Bo8%2?l`-6KOMw#JFsUCAv;m2i48}|{?ooaW8 z%L$!Ufvzg%AeA0P*&W8Iw)sQ>)oG90HP_Tlj?cW~hLgba^8wOQg=r?t11C{Jo(Yck zs$laQ*sj<1&|Sq7sB-c;a#v#OPMmAdI{lxb9SZ*T1&ig>jD}m}aCf>$IoZm<;kKN$ z3)dKJkuzmRT*cC`2m=eg6dGk|#3ulIZ9*9Uq`)$Xd7G@w@-v%KVi!@uicY0rLYLw# zxBtcpFgeeHAb41M=9-&aU?hzRM2L{-&Hj|X)_J}r8tt}2rMnYM$BR7w2HSkD{Bx3N zK1Z_4$+&v!!!egD_B3YPfmKDT;po{Svf=l7w%5~eJz(+LA?=+?-&!jgC5cI&FqY;! zy}dVzwT$QA2$Y3qpSR)x%*Cr|t=|H=y?uPy>U@O#lhTHYHuS`1Qyf$=wE-xsHnCVhDReo z9q%HqH)Yq9L0KP6Y*nm-y-ib6y4QUo>LDQ4a0R;#&(X$I!`K(r_I(C>zj3^7!=hZ% zC(|&Q$fj2KK~q)kjoqQ`(f$lKx31V2YOjG|zkdK$Ukmcmww2$I;u3LN@)aT96{j2E z@I9VuO;JAlx=NKAMtT~JdV2an$2YT?qC~FUjza{$OQYCdr6!o($C%+%MJ>LW4(+1I zSbGI2i^wNQU5WZBd-)|}$URH7O56I?%C?bQmdNvrtU4Q?#G<|{p?bhw8^FHCvJ#tI z#(sCK!?1zSrM~Ufu`!Ju6lmW@%J8-bp#P!X*GJrm0U=&{4Os#a%NI-QQ?Rfi)QPJ$ zUaUR-K|N*TbG31L!RUp@=GRaFVM4oJ!2Qt1_fz}RV2`J(Ss3ebXnB1;-HNcfL^6Y> zqm4`(m};6Xntw=wZgEfEF_PD`fL0CVq2vQDXsU{u#ZJc=ktBXE4%=-7Y?iKB8J0tB z2CUl(&S;EbusR(JE|>46NaT9*I6De!b7c5HJ9_aY1K2^L!aIV~*1CkGChfCa^tra$ zfSd789~@r}2=Cfnn#fOM>bG#H;Li>OgYN5oHv;l2qKT;?+BL6|OS#QjdV^5+d(IY2 z%LdAcZ;)QT_=7D=%40dBlK~I`=cvY%Cx*LX3;ZgT&tceOC@-VDHz6=0EBpy%R^s8J zHmO217qjUeuk2e~!wEk}qo(?z1d^&w_~SCXBDzH;vhGHmD6{0NqSVO%u=ltwuZ%_Bu0`6mev-E{XD$2C1Wx1|!-V5%U4GpHkJ6V#v zI6lrc7d(B~k%xgvB77<`Ug734IqZzNCe(ZC_B!xQA*|$YJ89lq>HoYTh!k9}*XUG4 zx?-%#lPc{(6wdcam|ubtX!oUzH*!vRydPph+r&xKZVZF>3&3z6s@Hw=t9$`Iz`+}_ zaqIp#)>%U6ezQRR7pVD-?^|YOA z>85H0Y9FxY;Lzv0^2iE~hq-_3ly=z~z-V=u>+q&nlN*dHVIDd4a3|{arL{~vv^rwH zSThWnt7HAqf@aUcYbe$vnf%O%s5{4qVJ<&8K}i`!zhga$Nu#zo3!ttZ*DQ(uF+otb zQZOhjy3aei)8CA*53(;QjXVnNNsQasE^JTOY=lqSg*`Ss9KSWa`xOtw|Urt+M zSC&llF7_=Oq-ABP*{%ec2!0MQuLG@48@1_Rq-kitQ>8}sY5DiF00>ZssC~_`&aG-u zk9aj?Que-uQhuYc?CX`j-IA=PqV984if3&s52|Wi>4)Lw579O3Y;k(5_-J=EP}^$O z%={#&i7gU;0kuhWc%JI*4K*)K({FXu0oRtT&2c4DP+#9i=UPPJ=1K{%0Fg8pHQM^$to_PEDLC`n#Q3qk};K%9 zahdG#e`?+LNWaQ1FGT7X$6kRunk;U&_c{gDh*Inz&-!1tzokc@^`ew(E3N4XE8CNZ zg?DWtoXG?IwfBHn`8SV@v*J`S{Ti+>77W<8hHN+Qy0gwe1`X5{czR+vzPVW$eXW@4 z{2zU)CIv#1oze-Sv%%PBux_~JVgzJ!G0b-VBxE+~yFhw;?2DB~cU#a>`3|%Bbu91g z>UWuWpmV|{yYsmy(D@polL}{%uwrAtT{dV3CpaGs6+J1FR$iqQ4>3n5iqK=-y5WAm=rO2Nt7YH>n-0!&rucgB zUzeK7uV&!2-rY<6>hHmJO}5HT%E7?7Lag1(-&`#Wuv5q!xN#%ON z5}8-@0!cV;LDSqfLG=(JA>TT3C2p`!2{5CK`y&&4>wH|xfu!ZCZ!4c5qwM9_Nu!C^ zaH*)43C?E1!0-j+|5SbZz`{`4&O5ge&LS{4XRSLtr>*%`!pWYbSJe4*rSO+;!=JrNtE(1~Z8EfvN$6yv5T9zC#nZ@_ol)5YvLjkbokfBDo6 z#n4O0Mk^6IHm~B2O3QEx>n~03a}Ze-rsT<1peY4SGq%|6P1pbupn4TmM^&_&&hWcd+RQ-? zJ$az!v7GFDY%Ma=+0v47)p`r`LUbn zeCLG~IoU^AMFbo`-BFYmIKf-@Yx~;AP{sW-nP5R9-;K8OT@b>>e^xRm_%SE~vG-o# zsAV@ngJsOYxtGUFT%XQg3DE<$4c^u8I^71Y!y*_Q2eOVCHPI}NE9qdVOG zgF_aCh$+ZXXL9i{2UXTaoqH1VOq*9KV=;7%KhEzj+6+qCfPKiu3PF9;{jc@?jgY;9 z9pB@{tMqgkQe&DF3;s`LipYmC@{*e3!pd9;D5N)?p+<|Nq2U>pc&%OxoCUYfu||Fn zP$-yIEhlfgJP$UQzp!Q2V*SSFxHln8ANfXBb9Do8@aggHLXz7`NO8W|S-!-e$FNvw z1^71YfAB;5uhPs7U5wZhA~RIaz#vIwR>cZdAo*V<)DwmpQZ0i9-r`HUIE2otv%mW5 zy<}i^p$Y!IH~-`s;y2(Yz0hkXNd6_^@I(CoKi9=Svy=OuiuON`J9sVltNOt<_}5wb zYh~vD1b$}Ak9_*@-=f6-uv4Aj{QMT$Z~kBR!LJV}LEuN(e4-rs)A$OuNrRh9K4o-y z>ahN$P=VVvNWqV(`TyXlPV8^Zw6P2|_zXyW`9e|G#tx88w86{Q|9As7z`WtsizwD= z`HFyWuD?01tJm%+xLLOXj0HcoV|LPLqCT+Naz!?Y&61>`pdczfekX{x&SY9dit9>3 zUyb;H?NK(z9SVApEySbC?@BIpwsgX)!up>Zkgo%^$Kv+%wc#CULE$L<9sQ^QolFsh|y>a1x%raxm_G7 zSL*ezuOiU)8&5Wt;CnpwPvnRzw-J`yxz{vbj3ie&oe8u(7IGWSmVTcqQkKS~zLoqc zEGMJ19vmPJs&U#O-V72|L%_pU38*UNib;EgzN_2aP95XfC#<%3aCWaLj z!_m8+&3*1qS6Hph`wIpArpmL}&c7G${I^hFbiDjnPHJYgq6TRnnnG}Fi`A|T21_-$ zhNWjerdS27v0JZ$D%~GyDRmKUP)zlyPe|7Wdy(t zHkHj>Mpusg0qed^xmfFyMI%YMXxXyOScm`DS=yzLaY7GDdkCZ>*%m35fTSiscbz!4`C?zc*-6@^YASvCT(%mK9 zD2>vMfJisewMmiKY`VL1)7{^VKF_1)#~J6(_wT%8F!o@~amT8;*1E5G&1)U`=E$ek ztP53ZPK22-R4`!+5EAn{#q9N>H#v~bdt5sMA+PxTVL{J5^5A?$w{u&;?DBG@{Wh;l zXl3Nud9{_72~+`~I~524X%2{ zf9B&JX)=24m(b7d0LNmiw@C+0$nERNR%LBiZ?q(|sc_sPTkw2VwqN)j>-6#5NC;&J zFJjjF0gwo*1sal25k@7DLd(^4RwVlI35{^0LQT(yL5qn}5Rof1be-V+NuGPiKx)_f@3&8cD&6bvgV4)N|Q5ahJ11d8oxI1W#UL#F{oANvlzJa zX=EU}Kdmtd4^@tvklii+WT&xZL5U3cUA5&dxZ`4`#wHJYxj!b&ogY%SJz+=I5lm`w zlcS7r>yPqCS0F4b4Dy_i%hLGgoC-dV?dW6`!bCWj`L;0(9KjAGm7h+eSxo}qalGWo z74zGE1%x!#5Kg;!?WqRm@F`cHwT%sM-y7kS;xi9oDlmK>f6?{8GBwI+j&Y)Uv>(jR za3rh%n&&HPDD9pf74AA@yQmYj4dBb@S1;P(Me7*TdjZGvX zQGpM|Z*z>Znu(b7zW3SJT`Yr9X+!Qae3MP~?lGl44Vqmmsj|dubUkzOTJm{WrlY>% zsvXn#&$<{D_aO80x$%+kM(u*TSU=yfHl+}T_oCS8Tp*n|-_nKZr_C=1eX)|}6?)h4+)#_i>j1lCqZ zAlfk7fz9EZD2QBHkXtGlq2!;n7Pj zMp0|pFF40#QFqfI|K^|w2t#@K=a8(OX~OCa9UIM*nZKJ_DckfA_}Aw`6&}=|Y=0KN z5$=Y)CN-xCFL_t1A#`aRRxec^(SC(=ucbwA^I-=;o57 z8saa~l}nBC`g1Q*7ACsqJ$F}%!h|}eQA8&TjPPQG-FvArWZ`~1gh7mpv&!w$?b~~D z`$h{$@ee;eIU*$26*PsZqB#Rob{tP}nKpS7r9A$& zhkrnn=MolUhcrGuzK;Z%8hLtjtLuCdFC2=`?d+2vbZs_3n#25uKmgNBys*8OJQN+i&!p???`F=Gfa09{oK?n4T<-9v>e0y@^-b;u$pEFgC zO_@f!8ZvnUd-VF@F~iVRC~?}P{Y{GLP>EtINtrk$9rGKyXUZMC1K|j~?Q4R)XIQGD zlh+*l5x+FmGwHL_?&1C9w_h}XBe`(XX7*tf8t&&FvdypR-SM1Pm=iVauFVWX^vZ}{ zT!G5H8#9E;&D6xpb64BGD3|VlBTD+_y@gyHX(S*O7)at^*PrMi(>5_pa}8%0N$KuG zfiuV@=)+I*FGZY88IdZ-5AE?ng9E3SW*js$mW%W5UaX}JGXA&(m#tewR{}O zW204u5|~eo6HCbCTZII6({B&x0%(QL5En^+a40zfddx?30=gL|ua}z26-%d!SFj6f zt$2hNPxsfVPs+6Go4&1kZS02kRG7%FC7WfOtj!Q#a7~DZtzK@8_8g-~3f#Wmxmdx+ z-uXvVmCtk*+9tTJ(q@eZ}PXNIf z+Z(4Lsf$sx5u@z*wI4P?!kncxluAj+S7V-$LVTj-Ai+=W{ED}YOIU0!PzSzf`>)AP zA%TLd76ZwwY-q7;Y;CB@a*L{W^FM0kFzYJG*7B{ST6_?mXBfs}9z4#hHHBEN1UY}NnlCk``d)*lpr?? z6iwRU8|m~6=^2JH?QCE&xQlZ|P5k@}kI`$E{)3G4?S%6Esmf|~auAyu_^({Ub15x>U z&(jXRe@zItMIHGJ7GP;60AP^_aj zK9Lm(YZ+@uh&2;4_Og{q$uD6-vhF>iD38N3Kf~8ZuB8!c-s0z>k8Q1cm0NU+okFDP zgtx>M4uqvOMa4h(G7leiWdP|gem@T{kQ9@eTFYp;ILT-X+I=tfd(B2R1_XuYLu5jfyf6~+#uuBt&yTB-GDcg^ zcAdY>#27~6u$MD>d68czs2xt1Sh?~#EgIw!7;3a+viXq@M`3{A$ol~%l!Z3IB6^cj z;Fc`GC2TbR(Q&x?Gas=hXWcpeWXYG`$lYhw#e``Oh5`o z=QOmWG-zJ~5Kr@)Nrxue-M94fd6P}g7GJ7O=Qj5#3LJ)nhJH@!cM(|hXZ=XJ6QI%* zH~B0gbpP@&;#TT&c$!5!0*Pq=hc5F$cxU!&Z08TFQZ@BonU-THNA)y8EeNPD#W75W zD!vOrGGD&!uOT&mehxeC^$uwBu7v8{DWiqz4uRCO1{cDf3(9(7QNJY7e|C{bGGsX> zMcg?^)8};!sI^b2e%zua9#nENR&c)(t8+YD7mFe%cyeV(DFI(&0bX6(J;1K$cdU7; z3rAM-O&+m6Xh-aey5xti!Olbp_7^-ePQ?vYB6J<<9rdh;{gh}tH(|Rd0VN-qX0?i+ zJFN%Lbis?Tww)R+VHWBL@LY6K*c%$>S8$_yi!4wkKegBqudF@?HEqzQob|qVH-C`@ z5?<2@{+9#V3W2vQ@pCzrf1S`Q`gFl%XO6FcWrVTnsEgF1wAwek)x{y=G_dQy$ z8!y_w5QYCVATrzFGP^-Q2z>FT^}%r;bOmye;;vR(`SRE@xWpKLc8ENKh? zq(O4bn*2*m@P9@9U#tFatRinR)S)X1@-`#CHHrNjt+WM%vUh_TTZR5vyMAfZQaymB z0rl$8zjd+w2mwEATw*Rq|NDNV2e8z@7Ki_D&FjS{fFIh{p+Fz~nPs9x9>7wCq<5VW z|0$sX@2v9&_?3ivaMu8E`rls^EBr8aZXS*kCpERYqvN)Whh$K8ZmzmggQcRGv8l}M zxS+9Tq$*X50}FkRrCK$f@y_p?$6k?f`Boa+q_EVZ=tWn}r53KGG$A5U@%hKZ7}7H^ z0t%nU*;Q3kL%~K@8P5Q*qI#c?k_=zw43t>pA#tA(7ASM@Ph*zJm&krkYXb247*k{0 zWK(laEEZ>w>Sse^Q!f`>1GjvE7l=~*C%T$`}9EAi^lfT_-HB{6bF>Ume5v)8o!>+ znO0?KwAMQ~92t#DO|>2xGhL5w}I@dW`LYr2nX20Ajh%2GzT2W)oI0 zf39&uUv9aCKfm6nd&35e(R(^bq#l!;VU#rcTe~wK?%`sXc=^9OnTiD%PphC>BK*I{ z0}e1AO5yUOIWPNoQYq2kQZ3q^8h`mq9k#Mhmnyx9*{E za`Scf6~hkMmtfFN?JM(u(;9yWC;;sUG{lhef+k;yuI=qRZz-gP*z^Qui>t^b^UTF65t8M_cbdE7l22E1Oiot zgBvs*G#|7awC;(TT{*{~m7k4jYT9+EQQ;jsJpNHrjUq8VPmvryLiO%Z+POUg{ zODbJVEwx2gkAJj?4VB-^L5aXXQ%szn{w_n-*u)(i;TkSPxkf6s8X7l4m0L1V|C)6f)0vc zz?D`%D$f|JmBGlJ@Aud1eT?j9V6X8Oh{=VI!Xrg?OHex=`GAZ76$8KbrE0GSH z4)p>Bg}^yLC+Q+$lrdCMM0WLO%KJ zaK1C1Lt}`42FGWv_Bsx8%U5gej@0Lj5Na7jST z$|bs$0Pe1IWB;9m41*Kz5ekHQSoA?NN3x;S> zf9>zSvI7S$XwpC!%y%YEXN+u5PwTCWN(n6_&RA4bGQrV$=FHLSF?!kATEA1G{#8n^7o4X_{(U$ zI^iZ`cV#4=W@B(y#r(r;-1EBCIfY5G@0X4MfsxLHnyjL1V%3S$#EIEOH`~$FzB|sD znUSt&pvfzo5VfZPg@h2UEc%@$-0O`@QJ?-i3J;o9yuPxU)wt3WT57QAM8cBe7b>M9 z#AmqAqzw$Lue#*F#{BF0adkmfJ?p~RPOg`5d?e9th##5UFK5X;c)b7gJHmSxTpynA zXK;(%ugOA@M2bW!sl{ZFVRhcVrI@L)P55v#$B?bF*q_Nv1PsC#)Uu{XzF~4f!q^f1 z@X}ukkro$ZZc;;i`Kj$>)%7BFfjFibC9FAzIj1?7d5y93-l1nmERC(SuC6im-g1*# z;DgJrkA+KU+&8vYF#fj+1OG&6!2@MZ)9Rl3OYNG>Uz`Tmymz}sdjBVl@-Qj505kts z#2383cl6T6fHBnTeA4yr&A%lrFj4GwyNmv9{C|7sYwVxQ|Icjzzx2lAU!LBj5hL_1 zS~`#Mb&otjU%!DPz&<5|MU1m$!FrR9&6fhm8rWz>(yd1HnY&eN@{TF*2j02^Z1AK9D>&c}U10X_|&*;`T6Q=jej~+S{ z6}D+GRj>0~!H zM<}-!fSWs9FGy|j=hbhk{QSiz<@!BHpX0m$Jtr*fx8&MACVDv@R~6S<91=kXw|5$ohDJNY(&RpUF_yv?ck;Ztv^3&S=%6aoC3 zYsB00Htk=1;lKURHMf0P4Tb`^ozg67s_g-{;fl{=NiQX?o|gYUU_fLI3s{P^)2%=M z<;G+^fE&|ZqOKqO?ePqWfXA;L6`zdjz7wh+o_T6*zqj7${|1w^(wAZt?{EdHg6uNz zN4wnbk%0G*CpiQupeJXu1IL8WmHTG0MQ2D0u&4l@(K4t={oh3NAX>qQxs|K-R+~W8 zm5yo|8*5d{=b}S)9S@XrXW*d54))-WsQ*Qz8F4Cy?9NL@uLv>fTppjeM%^(FFT8eKZm*ufI3t3EFMR|18Nw+!q9t#{ zjVCkQ<5?#ozZ(@2602Ds-?Fjm;ZEUI%d6#W+)Rh~Q1)Lx3P>;6;C9s_2cN@Zh`_); z{P zw@5DXe-D6lg-854w*v75&>BFyHFAOrjGuUK? zKbBccGC!9*77L``bpk5phYplih)nhI9}7QV**l?T`ctL@Q9qbz1mnVg>J3!WUJ9_J zHD^=*X&@m)43TR;Ih;S1J;2g9l2`@*tF<0}fV#n%lSUQ5{jm@O7T2qog#Q{E@q$bQ zX7>@h`X5UjC16p|8^-$6B&0x-{I8KJG#r4mpbKDLE2=;INr>IdZbk9$fJM}THG|ts z;j#+#xm8_@Pt=R2kIj<-#A&F23^I0$@7NxaZFC~zYk}M zb-_1_WlKj>sFZ5E_k7{4oOM!_DNxG%Fss|}$=kP7o7YAujoj!byxqQxM$_rI7rWyR zy330!a^QR$XzR;8p|$RGu^3uKW?skDx|l*Y^xwmh{P9VlRjypJ7_%8H+(d)OF!+Tu z+_z*1o#W~D*xF#`M|Okui=z1~AUW;z_Gp30{oSo5sN{lsutEZy)CozgjsimIGC-iM z&&pqM{%(LQS;(jXIuCkrV3JPx&|unGa6EHPu}=vu<|RCZEl*f4_Zsb3JOtTv0cp`9 zC+MKW(b@~L6dJeX9}aICZ9n&=a0#*%zBf%}N~z^`kYaLQfoQItFDOpCto0?oyAqht z%azYye4)YUoLf@L`J)G>K^;6kTr`nYVLSJh{!K;pGiGh^1QtCSN~zCrKtjn>^N3n7 zZHAjn7|sh*A1&WRK%qZ{baujq0MKAR`@RDle4&KPtVN|LbQ%DF`ejF6Eq5x_mR$V3 zL^YIhk`YAYm1YACs{`!-!<{@31t+2Ae9j1Ni<8|J>V|}J{W^wPmS(I_J<|E;FLC|yT3`5+q;HzIfNVCM=h}ViSaK=9slQO8gO$;HsZ`UM8 zBAnat?uIv+|6EnrWhJF}C0ycI-b;Co$Z9{~5LCS1a0|O#lw)%H=~NhpB9RQ3k7zch8(kHPZ}Z>P`o}S=ORCI9y*f%AT57&c$0A+= z0#?uVH1?dT=iKbonr1PR~OO%XWxZeqIco+hpWf}1Yaw^z+nq-Z`}=@^?rc5_!*K!eS{ zl;@(n+S&C^MT57TZ4q&K+HfS2=9kv#~;Q&slKoCFYu}ZKuT`UP{I^H82X_n@w zEr?bt(-`@Ii4P%5A=&5x_r%gdL^M^gM~uUf8eXUuQm8X%a$Mh*!tWf<)*p3)Xjlh) zWYMe&vSthnW{fTm<=f%E^!THV&jPt2GN3ExY)OcvBXpAO7`Dgi(Sk~j%uMA8kf<-a zPfLO=|NR2c=`%hhxMl5+1BC3mY;C71a%iG#>{MAa%HC4n-Cl%bNJ_sXxwav^hPw}K=-(tFmk(g5R_&i4lM8c%!{ zx#{FHz}WBo@c4KaVGpemq*kEPsdM^m|Al6$^+z*#Hf5Gu0uIv?iJdK!Ou3Jxec`_r z;%1c1eQ#8XQ`s^P%c0O!iE!Ll%3yTx2|0cFc2%W%4lS4GGrv%k&>i??ZQw!I|)hE07Oi{_ARgLk8X zse&?FvXFNes`b8=XG^l5-X!ygXg)FyLrx1S!tpx>d5LJ6thvmng|a z{bDAE%M!z@rR&@x3W>~VPCPbE`Vo06U6Bb!T^$hdD-v!PP|u#GDMvXjG49sa_`N(` z%(F9Iz2+O!78>x`?z^0hTb*pT!x;7vcn~;vcM0n8 zw&9m9(r`_EvHk+$r;}7|F}F=}xp!>`!C4746?swXv~v|=Q(Hq{a(N_^GHPMuIIwbd zd3lT{CpRL|9!bJ=*mvzX`4pqZF7cpVSC?2o66GZ?+^+MUrXEv8RskPe$rcfi2^>>A zRnY?oZmo_MsB(K7jrDR>7<4=tayIi~@9^A_h1;+}#5t(KxXc3ra}%FrHY#AX z@=dPKZi%dB{1=Qlv$CWjNszpHgwAzGhAbeVNjo?L6IqSafrWC<)QRNmK3`sc3W(9NXmlUsR|lA+sj(k7i~16$(ZDRY_I!R3tfCc~Cr2TgXhawC(+dj$AL;pO$X>5NPf3fdp{rytLaRjX_>%U0#pXGML+PUh=xb zpv{%UT5Zxq=Ifui)@MF97DGlYPwF`!R6n8Xd#WDazP453g0PA$lCM1UiCqk$?gd&- zwm2rEw^|7wxy5o4a#?#xxac~!+%@R6yWAK{1%rA5Qf$9-EmAGvRYu^>?aSiWWq?jO z<@212JZ%;%2ZaH}JdTjM`FDLQCWtSIMS>j%y;gx)G@0mI&ym-?k4n8G4!*GM2eo1? zwsHJ4-R2>c;nkNG2VR?lh$4Z^=y6nW$iwVW1 z3CVTZ#P`Tw&avxbbsf{H;Pe=M`#urj#`EiFvGRz5$5RImw8kDtD)&^^c*)z;o&>PT zz}_59@KwChSfH0+*;JQ+yJIR6dXe>FS^bEPNOt9JnyO*HtrjqcM@)X_05)xuyT4wE>`vOi6J&cN?+|Y5IyhrD) zvYpNjgmUxF^I?@{t@gy`h4iG3uiVrUM*MtkE+by|J=D&Lhho{45Mk5dY9k{n(u`m= zUC@s`M0iE%gFz74V)wiN*5hQEhnlhBPV+i!$-F!QZWv6h4T@n_#;Pr*nwGlR13kL!_P z=0iRHDbwFOvNg>MbU{JM+GaF_G{DTzZ#pbI?|JS`XgyJ)I#C+?{C;arepd8cRhvG|oSt9m9+ zr^$51fiG!5roY-Mn0V3LVlOJ1Z3VJkD$gay*CCRMPPD+D=9XVS+_rZJ9AqNtIT2#3 zLs~ve{mj`*gw3*$pKna}nkbFA5Tn}9Q+s*3>e z95EXC6gt;ghrD`-wmSaJ0=mg!E9`oWo#Nz#6CoJFZJ7jfJ(@v}=U;%zN2z57*%q=G zIt49*aycSF-)hf?mYJ?^j_c1HRX`1=YwL16sbIyTUA?EH%ExsQQUj+kNqhIU9F9a< z>yQjHyGG4Q4Vze1ZvJs0_Z@f1fm3%4TxM;u_#(CV%5`oRAR{We?W1rE-7!nK+YUnW zdbOgNkJWZ)I?j$qS0rg|Vue#~d2?=U*9h1oF$Dv^&gS<=9F<(F?By}ZO0#UFyzY=P z1W-Rp)`yZ5=rpE-2!rOM+f!klTTbn+*Eppl7K_$Kj6T7=B^EaB4j%4-FGqLwTpd`r zr|nC7iQA2*_XF*#GiQTc2Hx&s)O@tj)^MkbYF5`%j;_XFc|Jp5!bK~kX6s9pM!Co$ ze~FLF`dzWqrtb*yoA0Jub{&Zw#=Oi>y}CXTliR#quo~vEnlTz(rN=gu+-YHt#oie$ zu#*}BHEoN?LW<6MH`yrrNEvqwj(%QG!Ipg{Y^jVzI-MlxRX`|^dDa5y<3g^zoS&~* z7P1udcV>EXRE>HuQ@vvzdG6skMJ|RgQ@fQW%>bOagMRKd`V#|?l&^LjKdQl>+-R^1 zZ@C;!C)x~+jkr)u73yhLL_icfEvjWAF+!mNM{~GB5$VqyH(J2vcYI%c@=wle+QQcT z-AR??r3S%*DXOa=T&SSt_4FwH2sHWf0JEcyB`uH}RcxQydvEqlfm;1H&@-26XZU74 zRVK;LL`JArsc{KLGzYs=mB40qTpKs#iqx}Mb8(wIqbSnDofeMoszk!Odh`4WOvkos zEpZe40AEGv!=`EFwb|*HZ-RIyD_Spk=RF)qovT#e2XZN)G4g#V%<5%nnxxHDu*0CY z2W{5#_VgXDmbK-!t|7UXx>63UD3=>46sk?Hx2#LL&y1|Z<+^Pia+6D(tIjp8XfJw} zjxyU5`VX8wFS^Ei3A4d8;m>JfR(^Xhen?V%Uv=yM{=3tgS65>*Pa*q0@;y9bJ zF4{&a^@v}~L)qVt%ekM%-&gw9N!+`IYX+eaU)3TEGyrLk8Fvo2Y0|t7%f@AVsbvC| zqajKAET`h$a8iiNK2a+^z6HDA0kjcVJq{nk(N1-hFilqI`@77ZTSVdUxHY?_4T=`> zU|^pMCLV**p9L_qeK6DExq68sxQV?D@-OSE&Cs;OtGjL8@bEG-fRsZ1aIb_?7( zeom9EhyAwew0z!K7%okXzg{l6f<_{{0WvU*xqnKd_{PpGQA=XLIyGt51VrD2n}!2& zJDf13=w=C3r=F2`02}N2;ho}Ck0VaGEuX=cn!v(sTGa!AjXwz6!sAkKqy0sxT;Lx> zuOC44_($S7|3dW80sKxluHex_J%53it@wb&tRW`o4}Nz+4&Zme?9P8IFCP#+In7~= zfA4~U6$TK!TcupIKNd5?sC-oaiRof5D!TfF-#!oBU6c_&mU# znR3K`EdOhy|GSM8f;!y*0|RsLT3l4^|39PZ2AxEA5Ik$stPj*3=Cy=^c=2.39' 'gcc-libs' 'alsa-lib' 'fontconfig' 'freetype2' + 'libx11' 'libxcb' 'libxkbcommon' 'libxkbcommon-x11' 'wayland' + 'vulkan-icd-loader' 'systemd-libs' 'zlib' 'hicolor-icon-theme') +optdepends=('vulkan-intel: Vulkan driver for Intel graphics' + 'vulkan-radeon: Vulkan driver for AMD graphics' + 'nvidia-utils: Vulkan driver for NVIDIA graphics' + 'xdg-desktop-portal: desktop file dialogs and integration') +# The input is already stripped. Rewriting PyInstaller binaries damages AAF. +options=('!strip' '!debug') +source=('align-payload.tar.gz') +sha256sums=('@PAYLOAD_SHA256@') + +package() { + install -d "$pkgdir/opt/align" "$pkgdir/usr/bin" + cp -a "$srcdir/payload/." "$pkgdir/opt/align/" + for executable in align align-cli align-mcp; do + ln -s "/opt/align/$executable" "$pkgdir/usr/bin/$executable" + done + install -Dm644 "$srcdir/integration/com.align.app.desktop" \ + "$pkgdir/usr/share/applications/com.align.app.desktop" + install -Dm644 "$srcdir/integration/com.align.app.metainfo.xml" \ + "$pkgdir/usr/share/metainfo/com.align.app.metainfo.xml" + install -Dm644 "$srcdir/integration/AppIcon.png" \ + "$pkgdir/usr/share/icons/hicolor/1024x1024/apps/com.align.app.png" + install -Dm644 "$srcdir/payload/LICENSE" "$pkgdir/usr/share/licenses/align/LICENSE" +} diff --git a/Support/Linux/com.align.app.desktop b/Support/Linux/com.align.app.desktop new file mode 100644 index 0000000..75ca85e --- /dev/null +++ b/Support/Linux/com.align.app.desktop @@ -0,0 +1,14 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=Align +GenericName=Audio and video synchronization +Comment=Sync your recordings. Keep your edit. +Comment[ru]=Синхронизация аудио и видео для монтажа +Exec=/opt/align/align +Icon=com.align.app +Terminal=false +Categories=AudioVideo;AudioVideoEditing; +Keywords=audio;video;sync;timecode;multicam; +StartupNotify=true +StartupWMClass=Align diff --git a/Support/Linux/com.align.app.metainfo.xml b/Support/Linux/com.align.app.metainfo.xml new file mode 100644 index 0000000..b39ec8b --- /dev/null +++ b/Support/Linux/com.align.app.metainfo.xml @@ -0,0 +1,24 @@ + + + com.align.app + Align + Sync your recordings. Keep your edit. + CC0-1.0 + GPL-3.0-only + +

Synchronize camera footage and separately recorded audio using waveforms, + timecode, and recording metadata. Review your timeline and export it to + Premiere Pro, DaVinci Resolve, Final Cut Pro, or an AAF-compatible editor.

+

Align works locally. Original recordings stay untouched. The desktop app, + command-line tools, AAF module, FFmpeg, and ffprobe are included.

+
+ com.align.app.desktop + https://github.com/FedyaLight/align + https://github.com/FedyaLight/align/issues + align + + + + + +
diff --git a/Support/Linux/installer.c b/Support/Linux/installer.c new file mode 100644 index 0000000..e04244c --- /dev/null +++ b/Support/Linux/installer.c @@ -0,0 +1,172 @@ +/* Branded Linux installer. Define ALIGN_PACKAGE_ARCH for Omarchy/Arch. */ +#include + +static GtkWidget *window, *status_label, *detail_label, *button, *progress; +static char *package_path, *icon_path; +static gboolean installing = FALSE, installed = FALSE; +static guint pulse_id = 0; + +static gboolean pulse(gpointer unused) { + (void)unused; + gtk_progress_bar_pulse(GTK_PROGRESS_BAR(progress)); + return G_SOURCE_CONTINUE; +} + +static gboolean close_request(GtkWindow *w, gpointer unused) { + (void)w; (void)unused; + return installing; +} + +static void finished(GObject *object, GAsyncResult *result, gpointer unused) { + (void)unused; + GError *error = NULL; + char *out = NULL, *err = NULL; + gboolean communicated = g_subprocess_communicate_utf8_finish(G_SUBPROCESS(object), result, &out, &err, &error); + installing = FALSE; + if (pulse_id) { g_source_remove(pulse_id); pulse_id = 0; } + gtk_widget_set_sensitive(button, TRUE); + if (communicated && g_subprocess_get_successful(G_SUBPROCESS(object))) { + installed = TRUE; + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 1.0); + gtk_label_set_text(GTK_LABEL(status_label), "You're ready to sync."); + gtk_label_set_text(GTK_LABEL(detail_label), "Align is installed. Find it in your applications, or open it below."); + gtk_button_set_label(GTK_BUTTON(button), "Open Align"); + } else { + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), 0); + gtk_label_set_text(GTK_LABEL(status_label), "Installation wasn't completed."); + const char *reason = error ? error->message : (err && *err ? err : out); + gtk_label_set_text(GTK_LABEL(detail_label), reason && *reason ? reason : "Authorization was cancelled. You can try again."); + gtk_button_set_label(GTK_BUTTON(button), "Try again"); + } + g_clear_error(&error); + g_free(out); g_free(err); + g_object_unref(object); +} + +static void install_clicked(GtkButton *b, gpointer unused) { + (void)b; (void)unused; + GError *error = NULL; + if (installed) { + GSubprocess *app = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error, "/opt/align/align", NULL); + if (app) { g_object_unref(app); gtk_window_destroy(GTK_WINDOW(window)); } + else { + gtk_label_set_text(GTK_LABEL(detail_label), error->message); + g_clear_error(&error); + } + return; + } + // Only the package manager is elevated. UI and Align run as the user. + GSubprocess *process = g_subprocess_new( + G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE, &error, +#ifdef ALIGN_PACKAGE_ARCH + "/usr/bin/pkexec", "/usr/bin/pacman", "-U", "--noconfirm", package_path, NULL); +#else + "/usr/bin/pkexec", "/usr/bin/dnf", "--assumeyes", "install", package_path, NULL); +#endif + if (!process) { + gtk_label_set_text(GTK_LABEL(detail_label), error->message); + g_clear_error(&error); + return; + } + installing = TRUE; + gtk_widget_set_sensitive(button, FALSE); + gtk_button_set_label(GTK_BUTTON(button), "Installing…"); + gtk_label_set_text(GTK_LABEL(status_label), "Installing Align…"); + gtk_label_set_text(GTK_LABEL(detail_label), "Approve the system prompt. Required dependencies will be installed automatically."); + pulse_id = g_timeout_add(90, pulse, NULL); + g_subprocess_communicate_utf8_async(process, NULL, NULL, finished, NULL); +} + +static GtkWidget *label(const char *text, const char *css) { + GtkWidget *widget = gtk_label_new(text); + gtk_label_set_xalign(GTK_LABEL(widget), 0); + gtk_widget_add_css_class(widget, css); + return widget; +} + +static void activate(GtkApplication *app, gpointer unused) { + (void)unused; + GtkCssProvider *css = gtk_css_provider_new(); + gtk_css_provider_load_from_string(css, + "window { background: #18191b; color: #f2f2f7; }" + ".brand { font-size: 30px; font-weight: 700; }" + ".eyebrow { font-size: 10px; letter-spacing: 1px; color: #ababb4; }" + ".headline { font-size: 32px; font-weight: 600; }" + ".subtitle { font-size: 16px; color: #ababb4; }" + ".status { font-size: 15px; font-weight: 600; }" + ".detail { font-size: 13px; color: #ababb4; }" + ".footnote { font-size: 12px; color: #ababb4; }" + "button.install { background: #0a84ff; color: white; border: 0; border-radius: 10px;" + " padding: 12px 26px; font-weight: 600; box-shadow: none; }" + "button.install:hover { background: #409cff; }" + "button.install:disabled { background: #2b2d31; color: #ababb4; }" + "progressbar trough { background: #2b2d31; border: 0; min-height: 5px; border-radius: 4px; }" + "progressbar progress { background: #0a84ff; border: 0; min-height: 5px; border-radius: 4px; }" + ".lane { min-height: 13px; border-radius: 4px; }" + ".blue { background: #0a84ff; } .cyan { background: #32ade6; } .green { background: #30d158; }"); + gtk_style_context_add_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(css), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref(css); + window = gtk_application_window_new(app); + gtk_window_set_title(GTK_WINDOW(window), "Install Align"); + gtk_window_set_default_size(GTK_WINDOW(window), 720, 520); + gtk_window_set_resizable(GTK_WINDOW(window), FALSE); + g_signal_connect(window, "close-request", G_CALLBACK(close_request), NULL); + GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_margin_start(box, 48); gtk_widget_set_margin_end(box, 48); + gtk_widget_set_margin_top(box, 36); gtk_widget_set_margin_bottom(box, 32); + gtk_window_set_child(GTK_WINDOW(window), box); + GtkWidget *brand = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 18); + GtkWidget *icon = gtk_image_new_from_file(icon_path); + gtk_image_set_pixel_size(GTK_IMAGE(icon), 64); + gtk_box_append(GTK_BOX(brand), icon); + GtkWidget *name = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4); + gtk_widget_set_valign(name, GTK_ALIGN_CENTER); + gtk_box_append(GTK_BOX(name), label("Align", "brand")); + gtk_box_append(GTK_BOX(name), label("AUDIO & VIDEO SYNC", "eyebrow")); + gtk_box_append(GTK_BOX(brand), name); gtk_box_append(GTK_BOX(box), brand); + GtkWidget *title = label("Everything in sync.", "headline"); + gtk_widget_set_margin_top(title, 30); gtk_box_append(GTK_BOX(box), title); + GtkWidget *subtitle = label("Your recordings. One timeline.", "subtitle"); + gtk_widget_set_margin_top(subtitle, 8); gtk_box_append(GTK_BOX(box), subtitle); + const char *colors[] = {"blue", "cyan", "green"}; + for (int i = 0; i < 3; i++) { + GtkWidget *lane = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_add_css_class(lane, "lane"); gtk_widget_add_css_class(lane, colors[i]); + gtk_widget_set_margin_top(lane, i == 0 ? 30 : 7); + gtk_widget_set_size_request(lane, 500 - i * 55, 13); + gtk_widget_set_halign(lane, GTK_ALIGN_START); gtk_box_append(GTK_BOX(box), lane); + } + status_label = label("A little setup. Then you're in sync.", "status"); + gtk_widget_set_margin_top(status_label, 28); gtk_box_append(GTK_BOX(box), status_label); + detail_label = label("Includes the app, media tools, AAF support and command-line tools.", "detail"); + gtk_widget_set_margin_top(detail_label, 8); + gtk_label_set_wrap(GTK_LABEL(detail_label), TRUE); + gtk_label_set_max_width_chars(GTK_LABEL(detail_label), 76); + gtk_label_set_lines(GTK_LABEL(detail_label), 3); + gtk_label_set_ellipsize(GTK_LABEL(detail_label), PANGO_ELLIPSIZE_END); + gtk_box_append(GTK_BOX(box), detail_label); + progress = gtk_progress_bar_new(); + gtk_widget_set_margin_top(progress, 24); gtk_box_append(GTK_BOX(box), progress); + GtkWidget *footer = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 16); + gtk_widget_set_margin_top(footer, 24); + GtkWidget *note = label("Free. Open source. Yours.", "footnote"); + gtk_widget_set_hexpand(note, TRUE); gtk_box_append(GTK_BOX(footer), note); + button = gtk_button_new_with_label("Install Align"); + gtk_widget_add_css_class(button, "install"); + g_signal_connect(button, "clicked", G_CALLBACK(install_clicked), NULL); + gtk_box_append(GTK_BOX(footer), button); gtk_box_append(GTK_BOX(box), footer); + gtk_window_present(GTK_WINDOW(window)); +} + +int main(int argc, char **argv) { + if (argc != 3 || !g_file_test(argv[1], G_FILE_TEST_IS_REGULAR)) { + g_printerr("Usage: align-installer PACKAGE ICON.png\n"); return 2; + } + package_path = g_canonicalize_filename(argv[1], NULL); + icon_path = g_canonicalize_filename(argv[2], NULL); + GtkApplication *app = gtk_application_new("com.align.installer", G_APPLICATION_NON_UNIQUE); + g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); + int status = g_application_run(G_APPLICATION(app), 1, argv); + g_object_unref(app); g_free(package_path); g_free(icon_path); + return status; +} diff --git a/docs/development.md b/docs/development.md index e797e94..ff8c645 100644 --- a/docs/development.md +++ b/docs/development.md @@ -109,8 +109,8 @@ open desktop or editor applications. Tests that require external media tools need those tools installed even when only the native backend is being developed. CI runs the same acceptance command -on macOS, Windows, and Linux. Release tags matching `align-rs-v*` also build -platform bundles. +on macOS, Windows, and Linux. Release tags matching `v*` also build and +publish the installers. ### Performance @@ -186,11 +186,12 @@ sidecars: ``` That build requires a C toolchain, make, curl, tar, xz, and NASM where applicable. -For a tag named `align-rs-vX.Y.Z` matching the workspace version, the release -workflow also builds Velopack installers/update packages for macOS, Windows, -and Linux and publishes them with matching source archives in a GitHub Release. -See [distribution](distribution.md) for the source and license workflow. The desktop updater uses those -packages for **Align → Check for Updates…** when installed through Velopack. +For a tag named `vX.Y.Z` matching the workspace version, the release workflow +builds the macOS DMG, the Windows installer with its Velopack update feed, and +Fedora and Arch packages with their graphical installers, and publishes them with +matching source archives in a GitHub Release. See [distribution](distribution.md) +for the installers and the source and license workflow. The desktop updater uses +the Windows feed for **Align → Check for Updates…** in Velopack installations. Its GitHub source is configured without authentication, so release assets must be publicly accessible. A private repository does not provide an update feed for ordinary users with the current configuration. Local Cargo builds and the diff --git a/docs/distribution.md b/docs/distribution.md index 89f3598..3d5d6ae 100644 --- a/docs/distribution.md +++ b/docs/distribution.md @@ -5,16 +5,34 @@ licenses. The full GPL text is in [LICENSE](../LICENSE). ## GitHub releases -A tag named `align-rs-vX.Y.Z` must match the workspace version in Cargo.toml. -The release workflow first runs the checks, then builds each platform's binaries, -source archive, dependency notices, and Velopack installers. It publishes only -after all platform jobs succeed and the source archive checksums pass. +Push a tag named `vX.Y.Z` that matches the workspace version in Cargo.toml. +The release workflow runs the checks, then builds each platform's binaries, +source archive, dependency notices, and installers. Fedora and Arch packages are +installed and run in clean containers. The release is published only after every +job succeeds and all checksums pass; its notes come from +`.github/release-notes.md`. Running the workflow manually on a branch builds the +same installers as workflow artifacts without publishing anything. Each release includes: -- Velopack installers and update packages. -- `align-source-Linux.tar.gz`, `align-source-macOS.tar.gz`, and - `align-source-Windows.tar.gz`, each with a `.sha256` file. +| Platform | Installer | Built by | +| --- | --- | --- | +| macOS (Apple Silicon) | `Align-X.Y.Z-macOS-arm64.dmg` | `package-macos.sh`, `create-macos-dmg.sh` | +| Windows x64 | `Align-X.Y.Z-Windows-x64-Setup.exe` and the Velopack update feed | `package-windows.sh` | +| Fedora x86_64 | `Align-X.Y.Z-Fedora-x86_64.rpm` and `-Setup.run` | `package-fedora.sh`, `package-fedora-gui.sh` | +| Omarchy / Arch x86_64 | `align-X.Y.Z-1-x86_64.pkg.tar.zst` and `Align-X.Y.Z-Omarchy-x86_64-Setup.run` | `package-arch.sh`, `package-arch-gui.sh` | + +Also `align-source-Linux.tar.gz`, `align-source-macOS.tar.gz`, and +`align-source-Windows.tar.gz`, each with a `.sha256` file like every installer. + +The installers share Align's look: the DMG window has a drag-to-Applications +layout, the Windows installer shows an animated splash (`Support/Installer`), +and the Linux `.run` files open a GTK 4 installer (`Support/Linux/installer.c`) +in the app's dark theme that installs the native package through polkit. +`script/render-installer-artwork.swift` regenerates the Windows artwork and icon +on macOS. The Windows executable carries the app icon and version details +(`crates/align-gpui/build.rs`), and only Windows installs update through +Velopack; Linux packages update through the package manager. The source archives contain the exact committed Align checkout, Cargo.lock, vendored Rust dependencies, a Cargo configuration for offline dependency @@ -60,15 +78,11 @@ license cannot be resolved under those choices. Adding a new license requires review rather than disabling that check. Automated collection is not a legal opinion about every future dependency or packaging change. -## macOS DMG releases +## Packaging locally -A standalone macOS release can use a `vX.Y.Z` tag and the local packaging scripts. -This does not trigger the `align-rs-vX.Y.Z` multiplatform installer jobs. Match the -workspace version, commit all changes, and prepare the corresponding sources -with the commands above before packaging. Pass the same AAF build directory -used by source preparation. Install `dmgbuild==1.6.7` in a Python -virtual environment and set `PYTHON` to that environment’s interpreter for the DMG -step. Layout metadata is written directly without automating Finder: +The packaging scripts also run outside CI on a staged release directory (the +`dist-bundle` layout the workflow builds). The DMG needs macOS and +`dmgbuild==1.6.7` in a virtual environment passed as `PYTHON`: ```sh AAF_DIR="$PWD/target/release" \ @@ -78,17 +92,11 @@ RELEASE_LICENSES="$PWD/target/release/ThirdPartyLicenses" \ target/Align-macOS-arm64.dmg ``` -The DMG contains Align.app and an Applications shortcut with a custom Finder -layout. CLI, MCP, AAF, and license notices are inside the app bundle. -The packaging script checks the bundle signature, CLI, MCP, and app launch. -Run `script/check-end-to-end.py` against the packaged CLI before publishing. -Upload the DMG, corresponding `align-source-macOS.tar.gz`, and both checksum -files together. Include the macOS installation instructions from the README in -the release notes. Verify uploaded assets before publishing a draft release. - -Without `RELEASE_LICENSES`, `package-macos.sh` only makes a development bundle; -it does not collect release sources itself. The app is ad-hoc signed. Developer -ID signing and Apple notarization require separate credentials. +The Fedora scripts need `rpmbuild` and GTK 4 development files; the Arch script +needs `makepkg` and runs as an unprivileged user. The Windows installer needs the +`vpk` tool and can be built on any OS. Without `RELEASE_LICENSES`, +`package-macos.sh` only makes a development bundle. The app is ad-hoc signed. +Developer ID signing and Apple notarization require separate credentials. ## Component details diff --git a/script/package-arch-gui.sh b/script/package-arch-gui.sh new file mode 100755 index 0000000..f3a042c --- /dev/null +++ b/script/package-arch-gui.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# Wrap a native Arch package in Align's GTK4 installer. Build on x86_64 Linux. +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +PACKAGE="$(realpath "${1:?Arch package required}")" +OUT="$(realpath -m "${2:?output .run path required}")" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +cc -O2 -Wall -Wextra -Werror -DALIGN_PACKAGE_ARCH "$ROOT/Support/Linux/installer.c" \ + $(pkg-config --cflags --libs gtk4) -o "$TMP/align-installer" +strip "$TMP/align-installer" +cp "$PACKAGE" "$TMP/Align.pkg.tar.zst" +cp "$ROOT/Support/AppIcon.png" "$TMP/Align.png" +mkdir -p "$(dirname "$OUT")" +cat > "$OUT" <<'SH' +#!/bin/sh +set -eu +if [ "$(uname -m)" != x86_64 ] || [ ! -x /usr/bin/pacman ]; then + echo 'This installer requires Omarchy or Arch Linux on an x86_64 computer.' >&2 + exit 1 +fi +if [ ! -x /usr/bin/pkexec ]; then + echo 'Install polkit to use the graphical installer, or install the separate package with sudo pacman -U.' >&2 + exit 1 +fi +unpack_dir=$(mktemp -d "${TMPDIR:-/tmp}/align-setup.XXXXXX") +trap 'rm -rf "$unpack_dir"' EXIT HUP INT TERM +payload_line=$(awk '/^__ALIGN_PAYLOAD__$/ { print NR + 1; exit }' "$0") +tail -n +"$payload_line" "$0" | tar -xz -C "$unpack_dir" +if { [ -n "${WAYLAND_DISPLAY:-}" ] || [ -n "${DISPLAY:-}" ]; } && + ! ldd "$unpack_dir/align-installer" 2>/dev/null | grep -q 'not found'; then + "$unpack_dir/align-installer" "$unpack_dir/Align.pkg.tar.zst" "$unpack_dir/Align.png" +else + echo 'Installing the native Arch package through pacman.' + pkexec /usr/bin/pacman -U "$unpack_dir/Align.pkg.tar.zst" +fi +exit 0 +__ALIGN_PAYLOAD__ +SH +tar -czf - -C "$TMP" align-installer Align.pkg.tar.zst Align.png >> "$OUT" +chmod +x "$OUT" +(cd "$(dirname "$OUT")" && sha256sum "$(basename "$OUT")" > "$(basename "$OUT").sha256") diff --git a/script/package-arch.sh b/script/package-arch.sh new file mode 100755 index 0000000..33e4527 --- /dev/null +++ b/script/package-arch.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Create an Omarchy/Arch package using makepkg as an unprivileged user. +# Usage: script/package-arch.sh STAGED_DIRECTORY OUTPUT_DIRECTORY +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +STAGE="$(cd "${1:?staged release required}" && pwd)" +mkdir -p "${2:?output directory required}" +OUT="$(cd "$2" && pwd)" +VERSION="$(sed -n 's/^version = "\([^"]*\)"/\1/p' "$ROOT/Cargo.toml" | head -1)" +if [[ $(id -u) == 0 ]]; then + echo 'Run this script as an unprivileged build user (makepkg requires it).' >&2 + exit 1 +fi +for bin in align align-cli align-mcp align-aaf ffmpeg ffprobe; do + test -x "$STAGE/$bin" +done +for notice in LICENSE THIRD_PARTY_NOTICES.txt FFMPEG-LICENSE.txt; do + test -s "$STAGE/$notice" +done +test -s "$STAGE/ThirdPartyLicenses/BUILD-MANIFEST.json" +test -d "$STAGE/AAF-Licenses" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +mkdir -p "$TMP/input/payload" "$TMP/input/integration" "$TMP/build" +tar -C "$STAGE" --exclude=./AAF-Sources -cf - . | tar -C "$TMP/input/payload" -xf - +cp "$ROOT/Support/Linux/com.align.app.desktop" "$ROOT/Support/Linux/com.align.app.metainfo.xml" \ + "$ROOT/Support/AppIcon.png" "$TMP/input/integration/" +tar -C "$TMP/input" -czf "$TMP/build/align-payload.tar.gz" payload integration +HASH="$(sha256sum "$TMP/build/align-payload.tar.gz" | cut -d ' ' -f 1)" +sed -e "s/@VERSION@/$VERSION/g" -e "s/@PAYLOAD_SHA256@/$HASH/g" \ + "$ROOT/Support/Linux/PKGBUILD.in" > "$TMP/build/PKGBUILD" +(cd "$TMP/build" && makepkg --nodeps --noconfirm) +cp "$TMP/build/align-$VERSION-1-x86_64.pkg.tar.zst" "$OUT/" +# Retain the exact packaging recipe and its input for reproducibility. +mkdir -p "$OUT/BuildInputs" +cp "$TMP/build/PKGBUILD" "$TMP/build/align-payload.tar.gz" "$OUT/BuildInputs/" +(cd "$OUT" && sha256sum "align-$VERSION-1-x86_64.pkg.tar.zst" > "align-$VERSION-1-x86_64.pkg.tar.zst.sha256") diff --git a/script/package-fedora-gui.sh b/script/package-fedora-gui.sh new file mode 100755 index 0000000..0756337 --- /dev/null +++ b/script/package-fedora-gui.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Wrap an RPM in Align's branded GTK4 installer. Build on x86_64 Linux. +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +RPM="$(realpath "${1:?RPM file required}")" +OUT="$(realpath -m "${2:?output .run path required}")" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +cc -O2 -Wall -Wextra -Werror "$ROOT/Support/Linux/installer.c" \ + $(pkg-config --cflags --libs gtk4) -o "$TMP/align-installer" +strip "$TMP/align-installer" +cp "$RPM" "$TMP/Align.rpm" +cp "$ROOT/Support/AppIcon.png" "$TMP/Align.png" +mkdir -p "$(dirname "$OUT")" +cat > "$OUT" <<'SH' +#!/bin/sh +set -eu +if [ "$(uname -m)" != x86_64 ] || ! command -v dnf >/dev/null; then + echo 'This installer requires Fedora on an x86_64 computer.' >&2 + exit 1 +fi +unpack_dir=$(mktemp -d "${TMPDIR:-/tmp}/align-setup.XXXXXX") +trap 'rm -rf "$unpack_dir"' EXIT HUP INT TERM +payload_line=$(awk '/^__ALIGN_PAYLOAD__$/ { print NR + 1; exit }' "$0") +tail -n +"$payload_line" "$0" | tar -xz -C "$unpack_dir" +if ! ldd "$unpack_dir/align-installer" 2>/dev/null | grep -q 'not found'; then + "$unpack_dir/align-installer" "$unpack_dir/Align.rpm" "$unpack_dir/Align.png" +else + echo 'GTK 4 is required for the graphical installer. Installing the RPM through DNF.' + pkexec /usr/bin/dnf --assumeyes install "$unpack_dir/Align.rpm" +fi +exit 0 +__ALIGN_PAYLOAD__ +SH +tar -czf - -C "$TMP" align-installer Align.rpm Align.png >> "$OUT" +chmod +x "$OUT" +(cd "$(dirname "$OUT")" && sha256sum "$(basename "$OUT")" > "$(basename "$OUT").sha256") diff --git a/script/package-fedora.sh b/script/package-fedora.sh new file mode 100755 index 0000000..d6dae86 --- /dev/null +++ b/script/package-fedora.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +# Package a staged x86_64 Linux release. Run on Linux with rpm-build installed. +# Usage: script/package-fedora.sh STAGED_DIRECTORY OUTPUT_DIRECTORY +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +STAGE="$(cd "${1:?staged release required}" && pwd)" +mkdir -p "${2:?output directory required}" +OUT="$(cd "$2" && pwd)" +VERSION="$(sed -n 's/^version = "\([^"]*\)"/\1/p' "$ROOT/Cargo.toml" | head -1)" +for bin in align align-cli align-mcp align-aaf ffmpeg ffprobe; do + test -x "$STAGE/$bin" +done +for notice in LICENSE THIRD_PARTY_NOTICES.txt FFMPEG-LICENSE.txt; do + test -s "$STAGE/$notice" +done +test -d "$STAGE/AAF-Licenses" +test -d "$STAGE/ThirdPartyLicenses" +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +mkdir -p "$TMP"/{BUILD,RPMS,SOURCES,SPECS,SRPMS,BUILDROOT} +cat > "$TMP/SPECS/align.spec" < "Align-$VERSION-Fedora-x86_64.rpm.sha256") diff --git a/script/package-windows.sh b/script/package-windows.sh new file mode 100755 index 0000000..316c522 --- /dev/null +++ b/script/package-windows.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Velopack can build a Windows installer on macOS, Linux, or Windows. +# Usage: VPK=/path/to/vpk script/package-windows.sh STAGED_DIRECTORY OUTPUT_DIRECTORY +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +STAGE="$(cd "${1:?staged release required}" && pwd)" +mkdir -p "${2:?output directory required}" +OUT="$(cd "$2" && pwd)" +VERSION="$(sed -n 's/^version = "\([^"]*\)"/\1/p' "$ROOT/Cargo.toml" | head -1)" +for bin in align align-cli align-mcp align-aaf ffmpeg ffprobe; do + test -s "$STAGE/$bin.exe" +done +for notice in LICENSE THIRD_PARTY_NOTICES.txt FFMPEG-LICENSE.txt; do + test -s "$STAGE/$notice" +done +test -d "$STAGE/AAF-Licenses" +test -d "$STAGE/ThirdPartyLicenses" +# Velopack needs the [win] directive only when packing on another OS. +target=() +case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) ;; *) target=('[win]') ;; esac +"${VPK:-vpk}" "${target[@]}" pack --packId com.align.app --packTitle Align \ + --packAuthors 'Align contributors' --packVersion "$VERSION" \ + --runtime win-x64 --packDir "$STAGE" --mainExe align.exe \ + --framework vcredist143-x64 \ + --icon "$ROOT/Support/Installer/Align.ico" \ + --splashImage "$ROOT/Support/Installer/Setup.gif" \ + --splashProgressColor '#0A84FF' --shortcuts StartMenuRoot \ + --exclude '(^|[/\\])AAF-Sources([/\\]|$)' \ + --outputDir "$OUT/updates" --noPortable +cp "$OUT/updates/com.align.app-win-Setup.exe" "$OUT/Align-$VERSION-Windows-x64-Setup.exe" +(cd "$OUT" && sha256sum "Align-$VERSION-Windows-x64-Setup.exe" > "Align-$VERSION-Windows-x64-Setup.exe.sha256") diff --git a/script/render-installer-artwork.swift b/script/render-installer-artwork.swift new file mode 100644 index 0000000..7a2ffb5 --- /dev/null +++ b/script/render-installer-artwork.swift @@ -0,0 +1,92 @@ +// Rebuild committed installer artwork on macOS: swift script/render-installer-artwork.swift +import AppKit +import ImageIO +import UniformTypeIdentifiers + +let output = URL(fileURLWithPath: "Support/Installer", isDirectory: true) +try FileManager.default.createDirectory(at: output, withIntermediateDirectories: true) +let icon = NSImage(contentsOfFile: "Support/AppIcon.png")! +let width = 720, height = 440 +func color(_ hex: UInt32) -> NSColor { + NSColor(srgbRed: CGFloat((hex >> 16) & 255) / 255, + green: CGFloat((hex >> 8) & 255) / 255, + blue: CGFloat(hex & 255) / 255, alpha: 1) +} +func frame(_ phase: Double) -> CGImage { + let bitmap = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: width, pixelsHigh: height, + bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, + colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)! + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmap) + color(0x18191B).setFill() + NSRect(x: 0, y: 0, width: width, height: height).fill() + func text(_ value: String, x: CGFloat, y: CGFloat, size: CGFloat, + weight: NSFont.Weight = .regular, ink: UInt32 = 0xF2F2F7) { + (value as NSString).draw(at: NSPoint(x: x, y: y), withAttributes: [ + .font: NSFont.systemFont(ofSize: size, weight: weight), .foregroundColor: color(ink)]) + } + icon.draw(in: NSRect(x: 48, y: 314, width: 70, height: 70)) + text("Align", x: 136, y: 335, size: 32, weight: .semibold) + text("AUDIO & VIDEO SYNC", x: 138, y: 314, size: 10, weight: .medium, ink: 0xABABB4) + text("Everything in sync.", x: 48, y: 246, size: 34, weight: .medium) + text("Your recordings. One timeline.", x: 49, y: 213, size: 17, ink: 0xABABB4) + // Timeline lanes echo Align's actual blue, cyan and green clip colors. + let hues: [UInt32] = [0x0A84FF, 0x32ADE6, 0x30D158] + let starts: [CGFloat] = [0, 68, 32] + for lane in 0..<3 { + let y = CGFloat(144 - lane * 22) + color(0x292B30).setFill() + NSBezierPath(roundedRect: NSRect(x: 49, y: y, width: 622, height: 15), xRadius: 4, yRadius: 4).fill() + let shift = starts[lane] * CGFloat((1 + cos(phase * 2 * .pi)) / 2) + color(hues[lane]).withAlphaComponent(0.8).setFill() + NSBezierPath(roundedRect: NSRect(x: 85 + shift, y: y, width: 380 - CGFloat(lane * 42), height: 15), xRadius: 4, yRadius: 4).fill() + for tick in 0..<54 { + let h = CGFloat(3 + (tick * 13 + lane * 7) % 8) + color(0xFFFFFF).withAlphaComponent(0.42).setFill() + NSRect(x: 94 + shift + CGFloat(tick * 5), y: y + (15 - h) / 2, width: 1, height: h).fill() + } + } + color(0xF2F2F7).withAlphaComponent(0.7).setFill() + NSRect(x: 85, y: 95, width: 1, height: 72).fill() + text("Installing Align…", x: 49, y: 49, size: 14, weight: .medium) + text("Free. Open source. Yours.", x: 500, y: 49, size: 12, ink: 0xABABB4) + NSGraphicsContext.restoreGraphicsState() + return bitmap.cgImage! +} +let gif = CGImageDestinationCreateWithURL(output.appendingPathComponent("Setup.gif") as CFURL, + UTType.gif.identifier as CFString, 48, nil)! +CGImageDestinationSetProperties(gif, [kCGImagePropertyGIFDictionary: [kCGImagePropertyGIFLoopCount: 0]] as CFDictionary) +for index in 0..<48 { + CGImageDestinationAddImage(gif, frame(Double(index) / 48), + [kCGImagePropertyGIFDictionary: [kCGImagePropertyGIFDelayTime: 0.06]] as CFDictionary) +} +precondition(CGImageDestinationFinalize(gif)) +let png = CGImageDestinationCreateWithURL(output.appendingPathComponent("Setup.png") as CFURL, + UTType.png.identifier as CFString, 1, nil)! +CGImageDestinationAddImage(png, frame(0.5), nil) +precondition(CGImageDestinationFinalize(png)) +// ICO is a container of PNG images; preserve the existing icon exactly. +var images: [Data] = [] +let sizes = [16, 32, 48, 64, 128, 256] +for size in sizes { + let bitmap = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: size, pixelsHigh: size, + bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, + colorSpaceName: .deviceRGB, bytesPerRow: 0, bitsPerPixel: 0)! + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmap) + icon.draw(in: NSRect(x: 0, y: 0, width: size, height: size)) + NSGraphicsContext.restoreGraphicsState() + images.append(bitmap.representation(using: .png, properties: [:])!) +} +var ico = Data() +func u16(_ value: UInt16) { var n = value.littleEndian; withUnsafeBytes(of: &n) { ico.append(contentsOf: $0) } } +func u32(_ value: UInt32) { var n = value.littleEndian; withUnsafeBytes(of: &n) { ico.append(contentsOf: $0) } } +u16(0); u16(1); u16(UInt16(sizes.count)) +var offset = 6 + 16 * sizes.count +for (size, data) in zip(sizes, images) { + ico.append(contentsOf: [UInt8(size % 256), UInt8(size % 256), 0, 0]) + u16(1); u16(32); u32(UInt32(data.count)); u32(UInt32(offset)) + offset += data.count +} +for data in images { ico.append(data) } +try ico.write(to: output.appendingPathComponent("Align.ico")) From 6b838b793678511482495cf169f3b7931d03b134 Mon Sep 17 00:00:00 2001 From: FedyaLight Date: Mon, 28 Sep 2026 11:24:35 +0000 Subject: [PATCH 3/4] Check for updates at launch and install them on every platform Release builds check GitHub at launch and show an update card; the About panel is only reachable from the macOS menu. Windows Setup installs keep updating through Velopack. The macOS app downloads the DMG and replaces its bundle, and the Fedora and Arch packages download the package and install it with pkexec dnf/pacman. Every download is verified against its .sha256 asset. Other copies get the release page. --- .github/release-notes.md | 18 +- Cargo.lock | 3 + crates/align-gpui/Cargo.toml | 4 + crates/align-gpui/src/main.rs | 1 + crates/align-gpui/src/updater.rs | 539 +++++++++++++++++++++++++++++-- crates/align-gpui/src/views.rs | 240 +++++++++++++- docs/development.md | 22 +- docs/distribution.md | 5 +- 8 files changed, 774 insertions(+), 58 deletions(-) diff --git a/.github/release-notes.md b/.github/release-notes.md index d8680f9..228cada 100644 --- a/.github/release-notes.md +++ b/.github/release-notes.md @@ -13,8 +13,14 @@ matching, timecode, and recording metadata. - **Refined interface.** Round timeline ruler steps, smoother progress, a real drop zone, clearer search-accuracy settings, and Ctrl shortcuts on Windows and Linux (F11 for full screen). +- **Updates on every platform.** Align checks for a new release at launch and + offers it in a small card. One click downloads it, verifies its checksum and + installs it: through the Windows installer, by replacing the app on macOS, or + through `dnf`/`pacman` on Fedora and Arch. Copies of 0.1.0 do not check for + updates, so install this release manually once. - **Windows fixes.** Premiere Pro and Final Cut Pro exports now reference - Windows media with valid file URLs. + Windows media with valid file URLs, the app has its icon, and no console + windows flash while it works. ## Downloads @@ -42,9 +48,9 @@ open "/Applications/Align.app" ### Windows Run `Align-@VERSION@-Windows-x64-Setup.exe`. It installs for the current user, -adds Align to the Start menu, installs the Microsoft VC++ runtime if needed, and -updates itself from later releases. The installer is unsigned, so SmartScreen may -ask for confirmation. +adds Align to the Start menu, and installs the Microsoft VC++ runtime if needed. +Running it over 0.1.0 upgrades that installation. The installer is unsigned, so +SmartScreen may ask for confirmation. ### Fedora and Omarchy / Arch @@ -60,8 +66,8 @@ Or install the native package directly with `sudo dnf install ./Align-@VERSION@-Fedora-x86_64.rpm` or `sudo pacman -U ./align-@VERSION@-1-x86_64.pkg.tar.zst`. Choose one method; each `.run` contains the same package. Linux requires glibc 2.39+, a working Vulkan -driver, and GTK 4 with polkit for the graphical installer. Native packages are -updated by installing a newer package. +driver, and GTK 4 with polkit for the graphical installer. Later versions install +from inside Align, behind the same system authorization prompt. ## Sources and verification diff --git a/Cargo.lock b/Cargo.lock index 3e034d6..a98b2d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,8 +108,11 @@ dependencies = [ "futures", "gpui", "mimalloc", + "semver", "serde", "serde_json", + "sha2 0.10.9", + "ureq", "usvg", "velopack", "windows-sys 0.59.0", diff --git a/crates/align-gpui/Cargo.toml b/crates/align-gpui/Cargo.toml index 23fb185..7e42596 100644 --- a/crates/align-gpui/Cargo.toml +++ b/crates/align-gpui/Cargo.toml @@ -20,6 +20,10 @@ serde.workspace = true serde_json.workspace = true dirs.workspace = true velopack = "1.2.0" +# Same HTTP client and version parser Velopack uses; no extra crates. +ureq = "3" +semver = "1" +sha2.workspace = true [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.59", features = ["Win32_UI_WindowsAndMessaging"] } diff --git a/crates/align-gpui/src/main.rs b/crates/align-gpui/src/main.rs index ef5c9c7..ed96049 100644 --- a/crates/align-gpui/src/main.rs +++ b/crates/align-gpui/src/main.rs @@ -360,6 +360,7 @@ fn main() { let view = cx.new(move |cx| { let mut app = views::AlignApp::new(cx); app.data.add_paths(initial_paths); + app.check_for_updates_on_launch(cx); app }); cx.set_global(AppView(view.clone())); diff --git a/crates/align-gpui/src/updater.rs b/crates/align-gpui/src/updater.rs index ff8a83c..7fcca6d 100644 --- a/crates/align-gpui/src/updater.rs +++ b/crates/align-gpui/src/updater.rs @@ -1,10 +1,25 @@ -//! Cross-platform application updates backed by Velopack packages on GitHub Releases. +//! Application updates from GitHub Releases, on every platform. +//! +//! Windows Setup installs update through Velopack. The macOS app and the +//! Fedora and Arch packages are not Velopack installations: for those the +//! release's own installer asset is downloaded, checked against its +//! `.sha256` file and installed in place (the app bundle is replaced; the +//! package goes through the system package manager behind a polkit prompt). +//! Anything else — a portable copy, an app run from the DMG, a development +//! build — is told about the new version and offered the release page. +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::time::Duration; + +use sha2::{Digest, Sha256}; use velopack::{UpdateCheck, UpdateInfo, UpdateManager, VelopackAsset, sources::GithubSource}; pub const REPOSITORY_URL: &str = "https://github.com/FedyaLight/align"; /// One-time donation, the `custom` link in `.github/FUNDING.yml`. pub const DONATION_URL: &str = "https://www.patreon.com/fedyalight/posts/support-once-170786295"; +const LATEST_RELEASE_API: &str = "https://api.github.com/repos/FedyaLight/align/releases/latest"; #[derive(Default)] pub enum UpdateState { @@ -12,48 +27,106 @@ pub enum UpdateState { Idle, Checking, Current, - Available { + Available(Update), + Downloading { + version: String, + }, + Ready(Prepared), + Installing { + version: String, + }, + Failed, +} + +/// A newer release and how this copy of Align can reach it. +pub enum Update { + Velopack { manager: UpdateManager, update: Box, }, - Downloading { + Package { + install: Install, version: String, + asset: ReleaseAsset, }, - Ready { + /// No in-place route: open the release page. + Manual { version: String, page: String }, +} + +/// A downloaded update, ready to install. +pub enum Prepared { + Velopack { manager: UpdateManager, asset: VelopackAsset, }, - Failed, + Package { + install: Install, + version: String, + file: PathBuf, + }, +} + +/// How a non-Velopack installation replaces itself. +#[derive(Clone, Debug, PartialEq)] +pub enum Install { + /// The app bundle at this path, in a folder the user can write to. + MacApp(PathBuf), + Rpm, + Pacman, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct ReleaseAsset { + pub name: String, + pub url: String, + pub checksum_url: String, } impl UpdateState { pub fn version(&self) -> Option<&str> { match self { - Self::Available { update, .. } => Some(&update.TargetFullRelease.Version), - Self::Downloading { version } => Some(version), - Self::Ready { asset, .. } => Some(&asset.Version), + Self::Available(update) => Some(update.version()), + Self::Downloading { version } | Self::Installing { version } => Some(version), + Self::Ready(Prepared::Velopack { asset, .. }) => Some(&asset.Version), + Self::Ready(Prepared::Package { version, .. }) => Some(version), _ => None, } } pub fn is_busy(&self) -> bool { - matches!(self, Self::Checking | Self::Downloading { .. }) + matches!( + self, + Self::Checking | Self::Downloading { .. } | Self::Installing { .. } + ) } } -pub fn check() -> UpdateState { - let source = GithubSource::new(REPOSITORY_URL, None, false); - let manager = match UpdateManager::new(source, None, None) { - Ok(manager) => manager, - Err(error) => { - eprintln!("Align updater is unavailable: {error}"); - return UpdateState::Failed; +impl Update { + pub fn version(&self) -> &str { + match self { + Self::Velopack { update, .. } => &update.TargetFullRelease.Version, + Self::Package { version, .. } | Self::Manual { version, .. } => version, } - }; + } +} - match manager.check_for_updates() { - Ok(UpdateCheck::UpdateAvailable(update)) => UpdateState::Available { manager, update }, - Ok(UpdateCheck::NoUpdateAvailable | UpdateCheck::RemoteIsEmpty) => UpdateState::Current, +pub fn check() -> UpdateState { + if let Ok(manager) = + UpdateManager::new(GithubSource::new(REPOSITORY_URL, None, false), None, None) + { + return match manager.check_for_updates() { + Ok(UpdateCheck::UpdateAvailable(update)) => { + UpdateState::Available(Update::Velopack { manager, update }) + } + Ok(UpdateCheck::NoUpdateAvailable | UpdateCheck::RemoteIsEmpty) => UpdateState::Current, + Err(error) => { + eprintln!("Could not check for Align updates: {error}"); + UpdateState::Failed + } + }; + } + match latest_release() { + Ok(release) => evaluate(&release, env!("CARGO_PKG_VERSION"), detect_install()), Err(error) => { eprintln!("Could not check for Align updates: {error}"); UpdateState::Failed @@ -61,21 +134,331 @@ pub fn check() -> UpdateState { } } -pub fn download(manager: UpdateManager, update: Box) -> UpdateState { - let asset = update.TargetFullRelease.clone(); - match manager.download_updates(&update, None) { - Ok(()) => UpdateState::Ready { manager, asset }, - Err(error) => { - eprintln!("Could not download the Align update: {error}"); - UpdateState::Failed +pub fn download(update: Update) -> UpdateState { + match update { + Update::Velopack { manager, update } => { + let asset = update.TargetFullRelease.clone(); + match manager.download_updates(&update, None) { + Ok(()) => UpdateState::Ready(Prepared::Velopack { manager, asset }), + Err(error) => { + eprintln!("Could not download the Align update: {error}"); + UpdateState::Failed + } + } + } + Update::Package { + install, + version, + asset, + } => match download_verified(&asset) { + Ok(file) => UpdateState::Ready(Prepared::Package { + install, + version, + file, + }), + Err(error) => { + eprintln!("Could not download the Align update: {error}"); + UpdateState::Failed + } + }, + Update::Manual { .. } => UpdateState::Failed, + } +} + +/// Install a downloaded package in place; the caller relaunches. +pub fn install(install: &Install, file: &Path) -> Result<(), String> { + match install { + Install::MacApp(bundle) => replace_app_bundle(bundle, file), + Install::Rpm => run(Command::new("/usr/bin/pkexec").args([ + "/usr/bin/dnf".as_ref(), + "--assumeyes".as_ref(), + "install".as_ref(), + file.as_os_str(), + ])), + Install::Pacman => run(Command::new("/usr/bin/pkexec").args([ + "/usr/bin/pacman".as_ref(), + "-U".as_ref(), + "--noconfirm".as_ref(), + file.as_os_str(), + ])), + } +} + +/// Start the freshly installed copy. The caller quits this one. +pub fn relaunch(install: &Install) -> Result<(), String> { + let mut command = match install { + Install::MacApp(bundle) => { + let mut command = Command::new("/usr/bin/open"); + command.arg("-n").arg(bundle); + command + } + Install::Rpm | Install::Pacman => Command::new("/opt/align/align"), + }; + command.spawn().map(drop).map_err(|error| error.to_string()) +} + +// ---------------------------------------------------------------- GitHub + +#[derive(Debug, PartialEq)] +struct Release { + version: String, + page: String, + assets: Vec<(String, String)>, +} + +fn agent() -> ureq::Agent { + ureq::Agent::config_builder() + .timeout_connect(Some(Duration::from_secs(15))) + .timeout_recv_response(Some(Duration::from_secs(30))) + .user_agent(concat!("Align/", env!("CARGO_PKG_VERSION"))) + .build() + .into() +} + +fn latest_release() -> Result { + let body = agent() + .get(LATEST_RELEASE_API) + .header("Accept", "application/vnd.github+json") + .call() + .map_err(|error| error.to_string())? + .body_mut() + .read_to_string() + .map_err(|error| error.to_string())?; + parse_release(&body).ok_or_else(|| "unexpected release response".to_owned()) +} + +fn parse_release(body: &str) -> Option { + let json: serde_json::Value = serde_json::from_str(body).ok()?; + let tag = json["tag_name"].as_str()?; + Some(Release { + version: tag.trim_start_matches('v').to_owned(), + page: json["html_url"].as_str()?.to_owned(), + assets: json["assets"] + .as_array()? + .iter() + .filter_map(|asset| { + Some(( + asset["name"].as_str()?.to_owned(), + asset["browser_download_url"].as_str()?.to_owned(), + )) + }) + .collect(), + }) +} + +/// Compare the latest release with the running version and pick the +/// installer asset this installation can apply. +fn evaluate(release: &Release, current: &str, install: Option) -> UpdateState { + let (Ok(latest), Ok(current)) = ( + semver::Version::parse(&release.version), + semver::Version::parse(current), + ) else { + return UpdateState::Failed; + }; + if latest <= current { + return UpdateState::Current; + } + let version = release.version.clone(); + let wanted = install + .as_ref() + .map(|install| asset_name(install, &version)); + let asset = wanted.and_then(|name| { + let url = |wanted: &str| { + release + .assets + .iter() + .find(|(name, _)| name == wanted) + .map(|(_, url)| url.clone()) + }; + Some(ReleaseAsset { + url: url(&name)?, + checksum_url: url(&format!("{name}.sha256"))?, + name, + }) + }); + UpdateState::Available(match (install, asset) { + (Some(install), Some(asset)) => Update::Package { + install, + version, + asset, + }, + _ => Update::Manual { + version, + page: release.page.clone(), + }, + }) +} + +fn asset_name(install: &Install, version: &str) -> String { + match install { + Install::MacApp(_) => format!("Align-{version}-macOS-arm64.dmg"), + Install::Rpm => format!("Align-{version}-Fedora-x86_64.rpm"), + Install::Pacman => format!("align-{version}-1-x86_64.pkg.tar.zst"), + } +} + +fn download_verified(asset: &ReleaseAsset) -> Result { + let agent = agent(); + let expected = agent + .get(&asset.checksum_url) + .call() + .map_err(|error| error.to_string())? + .body_mut() + .read_to_string() + .map_err(|error| error.to_string())?; + let expected = expected + .split_whitespace() + .next() + .ok_or("empty checksum file")? + .to_ascii_lowercase(); + let dir = std::env::temp_dir().join(format!("align-update-{}", std::process::id())); + std::fs::create_dir_all(&dir).map_err(|error| error.to_string())?; + let path = dir.join(&asset.name); + let mut file = std::fs::File::create(&path).map_err(|error| error.to_string())?; + let mut reader = agent + .get(&asset.url) + .call() + .map_err(|error| error.to_string())? + .into_body() + .into_reader(); + let mut hasher = Sha256::new(); + let mut buffer = vec![0; 1 << 16]; + loop { + let read = reader + .read(&mut buffer) + .map_err(|error| error.to_string())?; + if read == 0 { + break; } + hasher.update(&buffer[..read]); + file.write_all(&buffer[..read]) + .map_err(|error| error.to_string())?; } + file.sync_all().map_err(|error| error.to_string())?; + let actual: String = hasher + .finalize() + .iter() + .map(|byte| format!("{byte:02x}")) + .collect(); + if actual != expected { + let _ = std::fs::remove_file(&path); + return Err(format!("checksum mismatch for {}", asset.name)); + } + Ok(path) +} + +// ---------------------------------------------------------- installation + +/// How this copy was installed, when it can update itself in place. +fn detect_install() -> Option { + let exe = std::env::current_exe().ok()?.canonicalize().ok()?; + if cfg!(target_os = "macos") { + let bundle = exe + .ancestors() + .find(|path| path.extension().is_some_and(|extension| extension == "app"))?; + // A copy running from the mounted DMG or from a read-only folder + // cannot be replaced; it is pointed at the download instead. + if bundle.starts_with("/Volumes") || !writable(bundle.parent()?) { + return None; + } + return Some(Install::MacApp(bundle.to_path_buf())); + } + if cfg!(target_os = "linux") && exe.starts_with("/opt/align") { + let owns = |tool: &str, args: &[&str]| { + Path::new(tool).exists() + && Command::new(tool) + .args(args) + .output() + .is_ok_and(|output| output.status.success()) + }; + if !Path::new("/usr/bin/pkexec").exists() { + return None; + } + if owns("/usr/bin/dnf", &["--version"]) && owns("/usr/bin/rpm", &["-q", "align"]) { + return Some(Install::Rpm); + } + if owns("/usr/bin/pacman", &["-Q", "align"]) { + return Some(Install::Pacman); + } + } + None +} + +fn writable(dir: &Path) -> bool { + let probe = dir.join(format!(".align-update-{}", std::process::id())); + let ok = std::fs::create_dir(&probe).is_ok(); + let _ = std::fs::remove_dir(&probe); + ok +} + +fn run(command: &mut Command) -> Result<(), String> { + let output = command.output().map_err(|error| error.to_string())?; + if output.status.success() { + return Ok(()); + } + let detail = String::from_utf8_lossy(&output.stderr).trim().to_owned(); + Err(if detail.is_empty() { + format!("installer exited with {}", output.status) + } else { + detail + }) +} + +/// Copy the new bundle out of the disk image next to the current one, then +/// swap the two, so a failure leaves the installed app untouched. +fn replace_app_bundle(bundle: &Path, dmg: &Path) -> Result<(), String> { + let parent = bundle.parent().ok_or("app bundle has no folder")?; + let mount = dmg.with_extension("mount"); + std::fs::create_dir_all(&mount).map_err(|error| error.to_string())?; + run(Command::new("/usr/bin/hdiutil") + .args([ + "attach", + "-nobrowse", + "-readonly", + "-noautoopen", + "-mountpoint", + ]) + .arg(&mount) + .arg(dmg))?; + let staged = parent.join(".Align-update.app"); + let _ = std::fs::remove_dir_all(&staged); + let copied = run(Command::new("/usr/bin/ditto") + .arg(mount.join("Align.app")) + .arg(&staged)); + let _ = run(Command::new("/usr/bin/hdiutil") + .args(["detach", "-force"]) + .arg(&mount)); + copied?; + let _ = run(Command::new("/usr/bin/xattr") + .args(["-dr", "com.apple.quarantine"]) + .arg(&staged)); + let previous = parent.join(".Align-previous.app"); + let _ = std::fs::remove_dir_all(&previous); + std::fs::rename(bundle, &previous).map_err(|error| error.to_string())?; + if let Err(error) = std::fs::rename(&staged, bundle) { + let _ = std::fs::rename(&previous, bundle); + return Err(error.to_string()); + } + let _ = std::fs::remove_dir_all(&previous); + let _ = std::fs::remove_file(dmg); + Ok(()) } #[cfg(test)] mod tests { use super::*; + fn release(version: &str, assets: &[&str]) -> Release { + Release { + version: version.into(), + page: format!("{REPOSITORY_URL}/releases/tag/v{version}"), + assets: assets + .iter() + .map(|name| (name.to_string(), format!("https://example.invalid/{name}"))) + .collect(), + } + } + #[test] fn transient_update_states_are_busy() { assert!(UpdateState::Checking.is_busy()); @@ -85,7 +468,109 @@ mod tests { } .is_busy() ); + assert!( + UpdateState::Installing { + version: "1.2.3".into() + } + .is_busy() + ); assert!(!UpdateState::Idle.is_busy()); assert!(!UpdateState::Current.is_busy()); } + + #[test] + fn github_release_responses_parse() { + let body = r#"{"tag_name":"v0.3.0","html_url":"https://github.com/FedyaLight/align/releases/tag/v0.3.0", + "assets":[{"name":"Align-0.3.0-Fedora-x86_64.rpm","browser_download_url":"https://x/rpm"}]}"#; + let release = parse_release(body).unwrap(); + assert_eq!(release.version, "0.3.0"); + assert_eq!( + release.assets, + vec![( + "Align-0.3.0-Fedora-x86_64.rpm".into(), + "https://x/rpm".into() + )] + ); + assert!(parse_release(r#"{"message":"Not Found"}"#).is_none()); + } + + #[test] + fn only_newer_releases_are_offered() { + let latest = release("0.2.0", &[]); + assert!(matches!( + evaluate(&latest, "0.2.0", None), + UpdateState::Current + )); + assert!(matches!( + evaluate(&latest, "0.10.0", None), + UpdateState::Current + )); + assert!(matches!( + evaluate(&latest, "0.1.0", None), + UpdateState::Available(Update::Manual { .. }) + )); + } + + #[test] + fn installations_pick_their_own_verified_asset() { + let latest = release( + "0.3.0", + &[ + "Align-0.3.0-macOS-arm64.dmg", + "Align-0.3.0-macOS-arm64.dmg.sha256", + "Align-0.3.0-Fedora-x86_64.rpm", + "Align-0.3.0-Fedora-x86_64.rpm.sha256", + "align-0.3.0-1-x86_64.pkg.tar.zst", + ], + ); + let UpdateState::Available(Update::Package { asset, .. }) = + evaluate(&latest, "0.2.0", Some(Install::Rpm)) + else { + panic!("the RPM should update in place"); + }; + assert_eq!(asset.name, "Align-0.3.0-Fedora-x86_64.rpm"); + assert!(asset.checksum_url.ends_with(".rpm.sha256")); + let mac = Install::MacApp("/Applications/Align.app".into()); + assert!(matches!( + evaluate(&latest, "0.2.0", Some(mac)), + UpdateState::Available(Update::Package { .. }) + )); + // Without a checksum file the package is never installed blindly. + assert!(matches!( + evaluate(&latest, "0.2.0", Some(Install::Pacman)), + UpdateState::Available(Update::Manual { .. }) + )); + } +} + +#[cfg(test)] +mod network_tests { + use super::*; + + /// Hits the live GitHub API: `cargo test -p align-gpui -- --ignored`. + #[test] + #[ignore] + fn the_latest_release_is_readable() { + let release = latest_release().unwrap(); + assert!(semver::Version::parse(&release.version).is_ok()); + assert!( + release + .assets + .iter() + .any(|(name, _)| name.ends_with(".sha256")) + ); + let asset = release + .assets + .iter() + .find(|(name, _)| name.ends_with(".rpm")) + .map(|(name, url)| ReleaseAsset { + name: name.clone(), + url: url.clone(), + checksum_url: format!("{url}.sha256"), + }) + .unwrap(); + let file = download_verified(&asset).unwrap(); + assert!(std::fs::metadata(&file).unwrap().len() > 1_000_000); + std::fs::remove_file(file).unwrap(); + } } diff --git a/crates/align-gpui/src/views.rs b/crates/align-gpui/src/views.rs index c653a1e..2ce4577 100644 --- a/crates/align-gpui/src/views.rs +++ b/crates/align-gpui/src/views.rs @@ -827,6 +827,8 @@ pub struct AlignApp { timeline_motion_generation: u64, progress_motion: ProgressMotion, update_state: UpdateState, + /// The update card was closed; the About panel still offers the update. + update_notice_dismissed: bool, } impl Focusable for AlignApp { @@ -865,6 +867,7 @@ impl AlignApp { timeline_motion_generation: 0, progress_motion: ProgressMotion::new(), update_state: UpdateState::default(), + update_notice_dismissed: false, } } @@ -891,6 +894,19 @@ impl AlignApp { } pub(crate) fn check_for_updates(&mut self, cx: &mut Context) { + self.run_update_check(false, cx); + } + + /// Quiet check at launch: a new release shows the update card; being + /// offline or up to date shows nothing. Development builds skip it. + pub(crate) fn check_for_updates_on_launch(&mut self, cx: &mut Context) { + if cfg!(debug_assertions) || std::env::var_os("ALIGN_NO_UPDATE_CHECK").is_some() { + return; + } + self.run_update_check(true, cx); + } + + fn run_update_check(&mut self, quiet: bool, cx: &mut Context) { if self.update_state.is_busy() { return; } @@ -905,7 +921,10 @@ impl AlignApp { cx.spawn(async move |_, cx| { let state = rx.await.unwrap_or(UpdateState::Failed); let _ = view.update(cx, |this, cx| { - this.update_state = state; + this.update_state = match state { + UpdateState::Failed | UpdateState::Current if quiet => UpdateState::Idle, + state => state, + }; cx.notify(); }); }) @@ -914,17 +933,22 @@ impl AlignApp { fn download_update(&mut self, cx: &mut Context) { let state = std::mem::take(&mut self.update_state); - let UpdateState::Available { manager, update } = state else { + let UpdateState::Available(update) = state else { self.update_state = state; return; }; - let version = update.TargetFullRelease.Version.clone(); + if let updater::Update::Manual { page, .. } = &update { + cx.open_url(page); + self.update_state = UpdateState::Available(update); + return; + } + let version = update.version().to_owned(); self.update_state = UpdateState::Downloading { version }; cx.notify(); let (tx, rx) = futures::channel::oneshot::channel(); std::thread::spawn(move || { - let _ = tx.send(updater::download(manager, update)); + let _ = tx.send(updater::download(update)); }); let view = cx.entity(); cx.spawn(async move |_, cx| { @@ -937,7 +961,7 @@ impl AlignApp { .detach(); } - fn restart_and_update(&mut self) { + fn restart_and_update(&mut self, cx: &mut Context) { if matches!( self.data.operation, Operation::Synchronizing | Operation::Exporting | Operation::Repairing @@ -945,15 +969,52 @@ impl AlignApp { return; } let state = std::mem::take(&mut self.update_state); - let UpdateState::Ready { manager, asset } = state else { + let UpdateState::Ready(prepared) = state else { self.update_state = state; return; }; - super::icons::cleanup(); - align_decode::media_assets::cleanup(); - if let Err(error) = manager.apply_updates_and_restart(&asset) { - eprintln!("Could not install the Align update: {error}"); - self.update_state = UpdateState::Failed; + match prepared { + updater::Prepared::Velopack { manager, asset } => { + super::icons::cleanup(); + align_decode::media_assets::cleanup(); + if let Err(error) = manager.apply_updates_and_restart(&asset) { + eprintln!("Could not install the Align update: {error}"); + self.update_state = UpdateState::Failed; + } + } + updater::Prepared::Package { + install, + version, + file, + } => { + // The package manager may wait on an authorization prompt, + // so it runs off the UI thread; Align restarts once it is done. + self.update_state = UpdateState::Installing { version }; + cx.notify(); + let (tx, rx) = futures::channel::oneshot::channel(); + std::thread::spawn(move || { + let result = updater::install(&install, &file) + .and_then(|()| updater::relaunch(&install)); + let _ = tx.send(result); + }); + let view = cx.entity(); + cx.spawn(async move |_, cx| { + let result = rx.await.unwrap_or_else(|_| Err("cancelled".into())); + let _ = view.update(cx, |this, cx| match result { + Ok(()) => { + super::icons::cleanup(); + align_decode::media_assets::cleanup(); + cx.quit(); + } + Err(error) => { + eprintln!("Could not install the Align update: {error}"); + this.update_state = UpdateState::Failed; + cx.notify(); + } + }); + }) + .detach(); + } } } @@ -2597,6 +2658,12 @@ impl Render for AlignApp { if self.data.show_search_quality { root = root.child(search_quality_dismiss_layer(cx)); } + if !self.update_notice_dismissed && !self.data.show_about { + if let Some(notice) = update_notice(cx, &theme, &self.update_state, self.data.operation) + { + root = root.child(notice); + } + } if self.data.show_stage_settings { root = root.child(overlay( &theme, @@ -7168,6 +7235,139 @@ fn export_sheet( sheet } +// ---------------- updates + +fn update_action_label(update: &updater::Update) -> String { + match update { + updater::Update::Manual { version, .. } => format!("Download {version}"), + update => format!("Update to {}", update.version()), + } +} + +/// Floating card announcing a new release, with the next step inline. It +/// works on every platform (the About panel is in the macOS menu only). +fn update_notice( + cx: &mut Context, + theme: &Theme, + update_state: &UpdateState, + operation: Operation, +) -> Option { + let version = update_state.version()?.to_owned(); + let restart_blocked = matches!( + operation, + Operation::Synchronizing | Operation::Exporting | Operation::Repairing + ); + let (detail, action) = match update_state { + UpdateState::Available(update) => ( + "A new version is ready to download.", + prominent_button( + cx, + theme, + "update-notice-action", + update_action_label(update), + true, + |this, _, _, cx| this.download_update(cx), + ), + ), + UpdateState::Downloading { .. } => ( + "Downloading in the background.", + prominent_button( + cx, + theme, + "update-notice-action", + "Downloading…", + false, + |_, _, _, _| {}, + ), + ), + UpdateState::Ready(_) => ( + if restart_blocked { + "Installs when the current operation finishes." + } else { + "Downloaded. Align restarts to finish." + }, + prominent_button( + cx, + theme, + "update-notice-action", + "Restart and Update", + !restart_blocked, + |this, _, _, cx| this.restart_and_update(cx), + ), + ), + UpdateState::Installing { .. } => ( + "Approve the system prompt if one appears.", + prominent_button( + cx, + theme, + "update-notice-action", + "Installing…", + false, + |_, _, _, _| {}, + ), + ), + _ => return None, + }; + let dismissible = !update_state.is_busy(); + let card = div() + .id("update-notice") + .w(px(300.)) + .p_3() + .flex() + .flex_row() + .items_start() + .gap_3() + .rounded_lg() + .border_1() + .border_color(rgb(theme.border)) + .bg(rgb(theme.panel)) + .shadow_lg() + .child(img(PathBuf::from(&icons().app)).size(px(36.)).rounded_md()) + .child( + div() + .flex_1() + .min_w(px(0.)) + .flex() + .flex_col() + .gap(px(2.)) + .child( + div() + .text_size(px(13.)) + .font_weight(gpui::FontWeight(600.0)) + .child(format!("Align {version}")), + ) + .child( + div() + .text_size(px(11.)) + .text_color(rgb(theme.dim)) + .child(detail), + ) + .child(div().mt_2().flex().child(action)), + ) + .when(dismissible, |card| { + card.child( + div() + .id("update-notice-close") + .cursor_pointer() + .opacity(0.6) + .hover(|this| this.opacity(1.)) + .child(svg_icon(icons().close.clone(), 12., theme.icon)) + .tooltip(hover_tip("Later".to_string(), theme)) + .on_click(cx.listener(|this, _, _, cx| { + this.update_notice_dismissed = true; + cx.notify(); + })), + ) + }); + Some( + div() + .absolute() + .right(px(16.)) + .bottom(px(16.)) + .child(slide_in(card, "update-notice-entrance", 0)), + ) +} + // ---------------- about panel (Align menu → About Align) fn platform_label() -> String { @@ -7224,16 +7424,26 @@ fn about_panel( |_, _, _, _| {}, )); } - UpdateState::Available { .. } => { + UpdateState::Available(update) => { update_actions = update_actions.child(prominent_button( cx, theme, "about-download-update", - format!("Update to {update_version}"), + update_action_label(update), true, |this, _, _, cx| this.download_update(cx), )); } + UpdateState::Installing { .. } => { + update_actions = update_actions.child(prominent_button( + cx, + theme, + "about-installing-update", + format!("Installing {update_version}…"), + false, + |_, _, _, _| {}, + )); + } UpdateState::Downloading { .. } => { update_actions = update_actions.child(prominent_button( cx, @@ -7244,7 +7454,7 @@ fn about_panel( |_, _, _, _| {}, )); } - UpdateState::Ready { .. } => { + UpdateState::Ready(_) => { update_actions = update_actions.child(prominent_button( cx, theme, @@ -7255,7 +7465,7 @@ fn about_panel( "Restart and Update" }, !restart_blocked, - |this, _, _, _| this.restart_and_update(), + |this, _, _, cx| this.restart_and_update(cx), )); } } diff --git a/docs/development.md b/docs/development.md index ff8c645..0aad850 100644 --- a/docs/development.md +++ b/docs/development.md @@ -190,14 +190,20 @@ For a tag named `vX.Y.Z` matching the workspace version, the release workflow builds the macOS DMG, the Windows installer with its Velopack update feed, and Fedora and Arch packages with their graphical installers, and publishes them with matching source archives in a GitHub Release. See [distribution](distribution.md) -for the installers and the source and license workflow. The desktop updater uses -the Windows feed for **Align → Check for Updates…** in Velopack installations. -Its GitHub source is configured without authentication, so release assets must -be publicly accessible. A private repository does not provide an update feed -for ordinary users with the current configuration. Local Cargo builds and the -ad hoc macOS bundle are not Velopack installations. Distribution -code signing and Apple notarization still require release credentials and are -not configured in this repository. +for the installers and the source and license workflow. + +Release builds check for a newer release at launch (`crates/align-gpui/src/updater.rs`) +and show an update card; **Align → Check for Updates…** does the same on demand. +Windows Setup installations update through the Velopack feed. Otherwise Align reads +the latest GitHub release: the macOS app downloads the DMG and replaces its bundle +when its folder is writable, and the Fedora and Arch packages download the package +and install it with `pkexec dnf`/`pkexec pacman`. Each download is checked against +its `.sha256` asset. Any other copy (a portable build, the app run from the DMG) +gets a link to the release page. Development builds and runs with +`ALIGN_NO_UPDATE_CHECK` set skip the launch check. The GitHub requests are not +authenticated, so release assets must be publicly accessible. Distribution code +signing and Apple notarization still require release credentials and are not +configured in this repository. ## Repository conventions diff --git a/docs/distribution.md b/docs/distribution.md index 3d5d6ae..cd60ab4 100644 --- a/docs/distribution.md +++ b/docs/distribution.md @@ -31,8 +31,9 @@ and the Linux `.run` files open a GTK 4 installer (`Support/Linux/installer.c`) in the app's dark theme that installs the native package through polkit. `script/render-installer-artwork.swift` regenerates the Windows artwork and icon on macOS. The Windows executable carries the app icon and version details -(`crates/align-gpui/build.rs`), and only Windows installs update through -Velopack; Linux packages update through the package manager. +(`crates/align-gpui/build.rs`). Windows installs update through the Velopack +feed; the macOS app and the Linux packages update from the DMG and package assets +named above, so keep those names and their `.sha256` files unchanged. The source archives contain the exact committed Align checkout, Cargo.lock, vendored Rust dependencies, a Cargo configuration for offline dependency From 583c2c3516d9a1d65a25504747e49b5c125278ca Mon Sep 17 00:00:00 2001 From: FedyaLight Date: Mon, 28 Sep 2026 11:25:16 +0000 Subject: [PATCH 4/4] Build the FFmpeg sidecars on Windows The make targets carry the .exe suffix under MSYS2. --- script/build-ffmpeg-minimal.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/script/build-ffmpeg-minimal.sh b/script/build-ffmpeg-minimal.sh index 34997da..099832c 100755 --- a/script/build-ffmpeg-minimal.sh +++ b/script/build-ffmpeg-minimal.sh @@ -47,12 +47,12 @@ JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.logicalcpu 2>/dev/ --enable-muxer=pcm_f32le,pcm_s32le,mov \ --enable-filter=abuffer,aformat,aresample,anull,abuffersink,atrim,asetpts,adelay,apad -make -j"$JOBS" ffmpeg ffprobe - +# Make targets carry the executable suffix (ffmpeg.exe on MSYS2). EXE="" case "$(uname -s)" in MINGW*|MSYS*|CYGWIN*) EXE=".exe" ;; esac +make -j"$JOBS" "ffmpeg$EXE" "ffprobe$EXE" cp "ffmpeg$EXE" "ffprobe$EXE" "$OUT_DIR/" cp COPYING.LGPLv2.1 "$OUT_DIR/FFMPEG-LICENSE.txt" mkdir -p "$OUT_DIR/Sources"