From 1880788a2195f765ad32000403e3e095de051f99 Mon Sep 17 00:00:00 2001 From: Junoh Moon Date: Fri, 17 Jul 2026 16:07:55 +0900 Subject: [PATCH] feat: port LitClock to a Go terminal interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 Raspberry Pi와 Python 전용 스택을 제거하고 인용문 corpus를 바이너리에 포함하는 Go TUI로 전환합니다. 선택 정책과 시간 강조 로직을 UI에서 분리해 테스트 가능하게 유지하고, 매분 갱신과 NSFW 전환, 반응형 레이아웃, 단일 프레임 출력을 제공합니다. Co-Authored-By: Claude --- .claude/hooks/ruff-check.sh | 16 - .claude/plans/419-followups.md | 403 --- .claude/settings.json | 16 - .gitattributes | 8 +- .github/ISSUE_TEMPLATE/bug_report.md | 41 - .github/ISSUE_TEMPLATE/feature_request.md | 17 - .github/PULL_REQUEST_TEMPLATE.md | 27 - .github/dependabot.yml | 25 +- .github/workflows/build-image.yml | 284 -- .github/workflows/ci.yml | 26 + .github/workflows/corpus-integrity.yml | 132 - .github/workflows/lint.yml | 80 - .gitignore | 88 +- .gitleaks.toml | 11 - .gitmodules | 4 - .images-version | 1 - 3d-models/README.md | 23 - 3d-models/bottom-with-notch.stl | Bin 219984 -> 0 bytes 3d-models/top-back-with-notch.stl | Bin 244884 -> 0 bytes 3d-models/top-front.stl | Bin 46284 -> 0 bytes CHANGELOG.md | 389 --- CLAUDE.md | 89 - CONTRIBUTING.md | 412 --- NOTICE.md | 130 +- PLAN-274-shell-writers-flock.md | 181 -- README.md | 391 +-- SECURITY.md | 22 - TODOS.md | 87 - cmd/litclock/main.go | 58 + docs/building-image.md | 144 - docs/control-pwa-m0-validation.md | 101 - docs/control-pwa-platform-limits.md | 123 - docs/hardware-assembly.md | 65 - docs/images/pwa-settings.png | Bin 90377 -> 0 bytes docs/images/pwa-status.png | Bin 355452 -> 0 bytes docs/images/pwa-system.png | Bin 112104 -> 0 bytes docs/images/setup-1-hotspot.png | Bin 3841 -> 0 bytes docs/images/setup-2-portal.png | Bin 120621 -> 0 bytes docs/images/setup-3-connecting.png | Bin 1813 -> 0 bytes docs/images/setup-4-handoff.png | Bin 5439 -> 0 bytes docs/manual/README.md | 49 - docs/manual/build.sh | 72 - docs/manual/litclock-manual-A4-booklet.pdf | Bin 330777 -> 0 bytes .../manual/litclock-manual-Letter-booklet.pdf | Bin 330630 -> 0 bytes docs/manual/litclock-manual.html | 259 -- docs/media/litclock-intro.gif | Bin 4758961 -> 0 bytes docs/media/litclock-photo.jpg | Bin 410829 -> 0 bytes docs/plans/lkg-bootcheck-plan.md | 119 - docs/quote-releases.md | 100 - docs/recovery.md | 90 - docs/script-reference.md | 43 - docs/scripts-qa-checklist.md | 153 - docs/sd-card-cloning.md | 48 - docs/security-sast-dast-2026-07.md | 97 - env.sh.sample | 65 - example.png | Bin 7867 -> 0 bytes fonts/Literata72pt-Black.ttf | Bin 245244 -> 0 bytes fonts/Literata72pt-ExtraLight.ttf | Bin 244776 -> 0 bytes fonts/Literata72pt-Regular.ttf | Bin 221904 -> 0 bytes fonts/Literata72pt-SemiBoldItalic.ttf | Bin 234400 -> 0 bytes go.mod | 28 + go.sum | 43 + icons/cloud.xbm | 14 - icons/cloud_moon.xbm | 14 - icons/cloud_sun.xbm | 14 - icons/cloud_wind.xbm | 14 - icons/cloud_wind_moon.xbm | 14 - icons/cloud_wind_sun.xbm | 14 - icons/clouds.xbm | 14 - icons/lightning.xbm | 14 - icons/moon.xbm | 14 - icons/rain0.xbm | 14 - icons/rain0_sun.xbm | 14 - icons/rain1.xbm | 14 - icons/rain1_moon.xbm | 14 - icons/rain1_sun.xbm | 14 - icons/rain2.xbm | 14 - icons/rain_lightning.xbm | 14 - icons/rain_snow.xbm | 14 - icons/snow.xbm | 14 - icons/snow_moon.xbm | 14 - icons/snow_sun.xbm | 14 - icons/sun.xbm | 14 - icons/wind.xbm | 14 - image-gen/apply_audit.py | 270 -- image-gen/audit_quotes.py | 668 ---- image-gen/build_gold_set.py | 94 - image-gen/clean_csv.py | 93 - image-gen/clean_quotes_csv.py | 212 -- image-gen/corpus_edit.py | 880 ------ image-gen/dedupe_quotes.py | 274 -- image-gen/detect_nsfw.py | 312 -- image-gen/gather_quotes.py | 408 --- image-gen/generate_images.py | 283 -- image-gen/gold_set_192.csv | 59 - image-gen/merge_quotes.py | 96 - image-gen/nsfw_keywords.txt | 75 - image-gen/prepare_review_csv.py | 796 ----- image-gen/process_reviewed.py | 260 -- image-gen/quote_to_image.php | 536 ---- image-gen/requirements-dev.txt | 4 - image-gen/review_nsfw.py | 279 -- image-gen/time_parser.py | 987 ------ image-gen/validate_time_parser.py | 189 -- internal/clock/quote.go | 53 + internal/clock/quote_test.go | 48 + .../corpus}/litclock_annotated.csv | 0 internal/corpus/repository.go | 92 + internal/corpus/repository_test.go | 50 + internal/frame/layout.go | 151 + internal/frame/layout_test.go | 74 + internal/tui/model.go | 216 ++ internal/tui/model_test.go | 53 + internal/tui/once.go | 49 + internal/tui/once_test.go | 29 + lib/e-Paper | 1 - logo.png | Bin 12510 -> 0 bytes package-lock.json | 2429 -------------- package.json | 17 - pi-gen/build.sh | 82 - pi-gen/config | 16 - pi-gen/stage3/00-install-deps/00-packages | 25 - pi-gen/stage3/00-install-deps/01-run.sh | 44 - pi-gen/stage3/01-setup-app/00-run.sh | 67 - pi-gen/stage3/02-configure-system/00-run.sh | 124 - .../02-configure-system/files/brcmfmac.conf | 1 - .../files/litclock-journald.conf | 14 - pi-gen/stage3/03-install-services/00-run.sh | 107 - pi-gen/stage3/04-finalize/00-run.sh | 84 - pi-gen/stage3/05-smoke-test/00-run.sh | 159 - pi-gen/stage3/EXPORT_IMAGE | 0 pi-gen/stage3/prerun.sh | 7 - pyproject.toml | 15 - requirements-apt.txt | 27 - requirements-dev.txt | 4 - requirements.in | 49 - requirements.txt | 72 - scripts/boot-splash.sh | 18 - scripts/diag-pwa-load-audit.sh | 84 - scripts/diag-subprocess-timing.py | 221 -- scripts/download_images.sh | 592 ---- scripts/first-boot.sh | 603 ---- scripts/install.sh | 551 ---- scripts/lib/github_api.sh | 202 -- scripts/lib/state.sh | 243 -- scripts/lib/update_status.sh | 172 - scripts/litclock-bootcheck-giveup-splash.sh | 17 - scripts/litclock-bootcheck.sh | 271 -- scripts/litclock-handoff-fallback.sh | 41 - scripts/litclock-lkg-record.sh | 121 - scripts/litclock-mark-collected.sh | 113 - scripts/litclock-set-timezone | 58 - scripts/litclock-wifi-reset.sh | 128 - scripts/nm-dispatcher/99-litclock-ip-change | 114 - scripts/prepare-for-cloning.sh | 185 -- scripts/release_images.sh | 251 -- scripts/reset-setup.sh | 432 --- scripts/runtheclock.sh | 7 - scripts/shutdown-splash.sh | 116 - scripts/update.sh | 1414 --------- scripts/wifi-watchdog.sh | 136 - src/captive_portal.py | 226 -- src/clear.py | 15 - src/collected_marker.py | 143 - src/config.py | 632 ---- src/control_server/__init__.py | 173 - src/control_server/_diagnostics_privacy.py | 214 -- src/control_server/_env.py | 51 - src/control_server/_format.py | 31 - src/control_server/_network.py | 212 -- src/control_server/_redaction.py | 235 -- src/control_server/_subprocess.py | 204 -- src/control_server/app.py | 114 - src/control_server/confirm_tokens.py | 357 --- src/control_server/csrf.py | 161 - src/control_server/errors.py | 194 -- src/control_server/handoff.py | 428 --- src/control_server/log_buffer.py | 537 ---- src/control_server/rate_limit.py | 83 - src/control_server/routes/__init__.py | 7 - .../routes/diagnostics/__init__.py | 202 -- .../routes/diagnostics/_anomalies.py | 403 --- .../routes/diagnostics/_collectors.py | 847 ----- .../routes/diagnostics/_copy_payload.py | 201 -- src/control_server/routes/diagnostics/_sse.py | 724 ----- src/control_server/routes/handoff.py | 99 - src/control_server/routes/health.py | 48 - src/control_server/routes/index.py | 40 - src/control_server/routes/settings.py | 1126 ------- src/control_server/routes/status.py | 514 --- src/control_server/routes/sw.py | 58 - src/control_server/routes/system.py | 1156 ------- src/control_server/routes/updates.py | 342 -- src/control_server/routes/wifi.py | 187 -- src/control_server/static/css/aths-hint.css | 172 - src/control_server/static/css/banners.css | 39 - .../static/css/confirm-sheet.css | 225 -- src/control_server/static/css/diagnostics.css | 706 ----- src/control_server/static/css/drawer.css | 540 ---- src/control_server/static/css/handoff.css | 129 - src/control_server/static/css/settings.css | 443 --- src/control_server/static/css/system.css | 282 -- src/control_server/static/css/tokens.css | 638 ---- src/control_server/static/css/updates.css | 274 -- .../static/fonts/fraunces-wght-italic.woff2 | Bin 45656 -> 0 bytes .../static/fonts/fraunces-wght-normal.woff2 | Bin 36620 -> 0 bytes .../static/fonts/geist-mono-wght-normal.woff2 | Bin 31368 -> 0 bytes .../fonts/instrument-sans-wght-normal.woff2 | Bin 30092 -> 0 bytes src/control_server/static/icons/icon-192.png | Bin 20661 -> 0 bytes src/control_server/static/icons/icon-512.png | Bin 11162 -> 0 bytes .../static/icons/icon-bg-baked-152.png | Bin 14945 -> 0 bytes .../static/icons/icon-bg-baked-167.png | Bin 16781 -> 0 bytes .../static/icons/icon-bg-baked-180.png | Bin 18456 -> 0 bytes .../static/icons/icon-bg-baked-512.png | Bin 11277 -> 0 bytes .../static/icons/icon-maskable-512.png | Bin 49950 -> 0 bytes src/control_server/static/js/aths-hint.js | 175 -- src/control_server/static/js/diagnostics.js | 1208 ------- src/control_server/static/js/drawer.js | 711 ----- src/control_server/static/js/handoff.js | 151 - src/control_server/static/js/settings.js | 663 ---- src/control_server/static/js/status.js | 441 --- src/control_server/static/js/sw-register.js | 37 - src/control_server/static/js/system.js | 640 ---- src/control_server/static/js/updates.js | 573 ---- .../static/manifest.webmanifest | 29 - src/control_server/static/paper-grain.svg | 13 - .../static/splash/splash-1080x2340-dark.png | Bin 91213 -> 0 bytes .../static/splash/splash-1080x2340-light.png | Bin 89035 -> 0 bytes .../static/splash/splash-1125x2436-dark.png | Bin 96913 -> 0 bytes .../static/splash/splash-1125x2436-light.png | Bin 93989 -> 0 bytes .../static/splash/splash-1170x2532-dark.png | Bin 104833 -> 0 bytes .../static/splash/splash-1170x2532-light.png | Bin 101303 -> 0 bytes .../static/splash/splash-1179x2556-dark.png | Bin 106873 -> 0 bytes .../static/splash/splash-1179x2556-light.png | Bin 103344 -> 0 bytes .../static/splash/splash-1206x2622-dark.png | Bin 110964 -> 0 bytes .../static/splash/splash-1206x2622-light.png | Bin 107465 -> 0 bytes .../static/splash/splash-1242x2208-dark.png | Bin 113643 -> 0 bytes .../static/splash/splash-1242x2208-light.png | Bin 109567 -> 0 bytes .../static/splash/splash-1242x2688-dark.png | Bin 116458 -> 0 bytes .../static/splash/splash-1242x2688-light.png | Bin 113126 -> 0 bytes .../static/splash/splash-1260x2736-dark.png | Bin 119095 -> 0 bytes .../static/splash/splash-1260x2736-light.png | Bin 116170 -> 0 bytes .../static/splash/splash-1284x2778-dark.png | Bin 121174 -> 0 bytes .../static/splash/splash-1284x2778-light.png | Bin 118394 -> 0 bytes .../static/splash/splash-1290x2796-dark.png | Bin 120093 -> 0 bytes .../static/splash/splash-1290x2796-light.png | Bin 117847 -> 0 bytes .../static/splash/splash-1320x2868-dark.png | Bin 127573 -> 0 bytes .../static/splash/splash-1320x2868-light.png | Bin 122845 -> 0 bytes .../static/splash/splash-1536x2048-dark.png | Bin 143186 -> 0 bytes .../static/splash/splash-1536x2048-light.png | Bin 137835 -> 0 bytes .../static/splash/splash-1620x2160-dark.png | Bin 169530 -> 0 bytes .../static/splash/splash-1620x2160-light.png | Bin 163161 -> 0 bytes .../static/splash/splash-2048x2732-dark.png | Bin 211214 -> 0 bytes .../static/splash/splash-2048x2732-light.png | Bin 205617 -> 0 bytes .../static/splash/splash-640x1136-dark.png | Bin 44867 -> 0 bytes .../static/splash/splash-640x1136-light.png | Bin 43634 -> 0 bytes .../static/splash/splash-750x1334-dark.png | Bin 55483 -> 0 bytes .../static/splash/splash-750x1334-light.png | Bin 53971 -> 0 bytes .../static/splash/splash-828x1792-dark.png | Bin 62716 -> 0 bytes .../static/splash/splash-828x1792-light.png | Bin 61652 -> 0 bytes .../templates/_handoff-banner.html.j2 | 40 - src/control_server/templates/base.html.j2 | 435 --- .../templates/diagnostics.html.j2 | 461 --- src/control_server/templates/settings.html.j2 | 443 --- src/control_server/templates/status.html.j2 | 185 -- src/control_server/templates/sw.js.j2 | 169 - src/control_server/templates/system.html.j2 | 359 --- src/control_server/templates/updates.html.j2 | 174 - src/control_server/update_state.py | 629 ---- src/control_server/version.py | 55 - src/control_url.py | 49 - src/display_driver.py | 51 - src/eink_display.py | 875 ------ src/geocoding.py | 205 -- src/https_cert.py | 70 - src/literary_clock.py | 514 --- src/location_resolver.py | 398 --- src/log.py | 8 - src/quote_corpus.py | 130 - src/setup_server.py | 1521 --------- src/weather_providers/__init__.py | 0 src/weather_providers/base_provider.py | 286 -- src/weather_providers/open_meteo.py | 146 - src/weather_providers/openweathermap.py | 106 - src/wifi_provision.py | 568 ---- sudoers/020_litclock-control | 73 - sysctl.d/30-litclock-unprivileged-ports.conf | 20 - systemd/litclock-bootcheck.service | 27 - systemd/litclock-bootcheck.timer | 28 - systemd/litclock-control.service | 98 - systemd/litclock-firstboot.service | 17 - systemd/litclock-handoff-fallback.service | 22 - systemd/litclock-handoff-fallback.timer | 18 - systemd/litclock-lkg.service | 21 - systemd/litclock-lkg.timer | 27 - systemd/litclock-prepare-for-gift.service | 37 - systemd/litclock-reresolve-location.service | 51 - systemd/litclock-reset.service | 25 - systemd/litclock-shutdown.service | 55 - systemd/litclock-splash.service | 23 - systemd/litclock-update.service | 25 - systemd/litclock-update.timer | 20 - systemd/litclock-wifi-reset.service | 28 - systemd/litclock.service | 31 - systemd/litclock.timer | 11 - systemd/tmpfiles.d/litclock.conf | 20 - systemd/wifi-watchdog.service | 7 - systemd/wifi-watchdog.timer | 10 - tests/__init__.py | 0 tests/conftest.py | 189 -- tests/js/aths-hint.mdns-coordination.test.js | 126 - tests/js/diagnostics.test.js | 1651 ---------- tests/js/drawer.test.js | 549 ---- tests/js/handoff.test.js | 234 -- tests/js/helpers/loadScript.js | 153 - tests/js/helpers/loadScript.test.js | 63 - tests/js/settings.advanced-toggle.test.js | 322 -- tests/js/settings.location-pill.test.js | 605 ---- tests/js/settings.update-banner.test.js | 169 - tests/js/settings.weather-toggle.test.js | 126 - tests/js/status.mdns-probe.test.js | 273 -- tests/js/status.phase3-skip-banner.test.js | 168 - tests/js/status.shim.test.js | 194 -- tests/js/status.unreachable.test.js | 302 -- tests/js/updates.optimistic-tick.test.js | 237 -- ...pdates.refresh-check-couldnt-check.test.js | 236 -- tests/test_apply_audit.py | 634 ---- tests/test_apt_provisioned_drift.py | 208 -- tests/test_audit_quotes.py | 814 ----- tests/test_bootcheck.py | 445 --- tests/test_build_gold_set.py | 166 - tests/test_build_image_workflow.py | 94 - tests/test_clean_csv.py | 49 - tests/test_collected_marker.py | 173 - tests/test_config.py | 1100 ------- tests/test_control_server.py | 2737 ---------------- tests/test_control_server_confirm_tokens.py | 675 ---- tests/test_control_server_diagnostics.py | 1407 --------- ...st_control_server_diagnostics_anomalies.py | 290 -- ...est_control_server_diagnostics_failures.py | 229 -- ...test_control_server_diagnostics_readers.py | 396 --- ...est_control_server_diagnostics_reexport.py | 225 -- tests/test_control_server_errors.py | 376 --- tests/test_control_server_handoff.py | 503 --- tests/test_control_server_logs_routes.py | 528 ---- tests/test_control_server_m6_pwa_shell.py | 734 ----- tests/test_control_server_m7_design_review.py | 631 ---- tests/test_control_server_perf.py | 463 --- tests/test_control_server_settings.py | 1390 -------- tests/test_control_server_system_routes.py | 2798 ----------------- tests/test_control_server_updates_routes.py | 1038 ------ tests/test_control_server_wifi_routes.py | 221 -- tests/test_control_url.py | 133 - tests/test_corpus_edit.py | 1015 ------ tests/test_diagnostics_no_secrets.py | 370 --- tests/test_diagnostics_privacy.py | 214 -- tests/test_diagnostics_uncollected.py | 749 ----- tests/test_download_images.py | 1175 ------- tests/test_eink_display.py | 101 - tests/test_eink_wrap.py | 440 --- tests/test_env_helper.py | 86 - tests/test_envsh_shell_flock.py | 541 ---- tests/test_first_boot_flow.py | 472 --- tests/test_geocoding.py | 407 --- tests/test_github_api_sh.py | 530 ---- tests/test_install_sh.py | 125 - tests/test_litclock_wifi_reset_sh.py | 222 -- tests/test_literary_clock.py | 551 ---- tests/test_literary_clock_dry_run.py | 293 -- tests/test_lkg_writer.py | 314 -- tests/test_location_resolver.py | 689 ---- tests/test_log.py | 41 - tests/test_log_buffer.py | 594 ---- tests/test_memory_sentinel.py | 302 -- tests/test_nm_dispatcher_install.py | 233 -- tests/test_open_meteo.py | 289 -- tests/test_pi_gen.py | 297 -- tests/test_prepare_for_cloning_sh.py | 78 - tests/test_prepare_review_csv.py | 198 -- tests/test_quote_corpus.py | 143 - tests/test_redaction.py | 435 --- tests/test_release_images.py | 590 ---- tests/test_reset_setup_sh.py | 480 --- tests/test_resolve_target_sha.py | 204 -- tests/test_safe_read_json.py | 237 -- tests/test_set_timezone.py | 143 - tests/test_setup_server.py | 1904 ----------- tests/test_splash_scripts.py | 255 -- tests/test_subprocess_helper.py | 505 --- tests/test_sudoers_install.py | 157 - tests/test_sudoers_m5.py | 231 -- tests/test_systemd_units.py | 809 ----- tests/test_time_parser.py | 364 --- tests/test_update_sh.py | 1198 ------- tests/test_update_status_sh.py | 232 -- tests/test_weather.py | 574 ---- tests/test_wifi_provision.py | 253 -- tests/test_wifi_retry_flow.py | 1468 --------- tests/test_wifi_watchdog_sh.py | 202 -- tools/control-pwa/fetch_fonts.py | 140 - tools/control-pwa/generate_pwa_icons.py | 293 -- tools/control-pwa/validate_qr_layout.py | 254 -- vitest.config.js | 24 - 403 files changed, 1054 insertions(+), 92937 deletions(-) delete mode 100755 .claude/hooks/ruff-check.sh delete mode 100644 .claude/plans/419-followups.md delete mode 100644 .claude/settings.json delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/workflows/build-image.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/corpus-integrity.yml delete mode 100644 .github/workflows/lint.yml delete mode 100644 .gitleaks.toml delete mode 100644 .gitmodules delete mode 100644 .images-version delete mode 100644 3d-models/README.md delete mode 100644 3d-models/bottom-with-notch.stl delete mode 100644 3d-models/top-back-with-notch.stl delete mode 100644 3d-models/top-front.stl delete mode 100644 CHANGELOG.md delete mode 100644 CLAUDE.md delete mode 100644 CONTRIBUTING.md delete mode 100644 PLAN-274-shell-writers-flock.md delete mode 100644 SECURITY.md delete mode 100644 TODOS.md create mode 100644 cmd/litclock/main.go delete mode 100644 docs/building-image.md delete mode 100644 docs/control-pwa-m0-validation.md delete mode 100644 docs/control-pwa-platform-limits.md delete mode 100644 docs/hardware-assembly.md delete mode 100644 docs/images/pwa-settings.png delete mode 100644 docs/images/pwa-status.png delete mode 100644 docs/images/pwa-system.png delete mode 100644 docs/images/setup-1-hotspot.png delete mode 100644 docs/images/setup-2-portal.png delete mode 100644 docs/images/setup-3-connecting.png delete mode 100644 docs/images/setup-4-handoff.png delete mode 100644 docs/manual/README.md delete mode 100755 docs/manual/build.sh delete mode 100644 docs/manual/litclock-manual-A4-booklet.pdf delete mode 100644 docs/manual/litclock-manual-Letter-booklet.pdf delete mode 100644 docs/manual/litclock-manual.html delete mode 100644 docs/media/litclock-intro.gif delete mode 100644 docs/media/litclock-photo.jpg delete mode 100644 docs/plans/lkg-bootcheck-plan.md delete mode 100644 docs/quote-releases.md delete mode 100644 docs/recovery.md delete mode 100644 docs/script-reference.md delete mode 100644 docs/scripts-qa-checklist.md delete mode 100644 docs/sd-card-cloning.md delete mode 100644 docs/security-sast-dast-2026-07.md delete mode 100755 env.sh.sample delete mode 100644 example.png delete mode 100644 fonts/Literata72pt-Black.ttf delete mode 100644 fonts/Literata72pt-ExtraLight.ttf delete mode 100644 fonts/Literata72pt-Regular.ttf delete mode 100644 fonts/Literata72pt-SemiBoldItalic.ttf create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 icons/cloud.xbm delete mode 100644 icons/cloud_moon.xbm delete mode 100644 icons/cloud_sun.xbm delete mode 100644 icons/cloud_wind.xbm delete mode 100644 icons/cloud_wind_moon.xbm delete mode 100644 icons/cloud_wind_sun.xbm delete mode 100644 icons/clouds.xbm delete mode 100644 icons/lightning.xbm delete mode 100644 icons/moon.xbm delete mode 100644 icons/rain0.xbm delete mode 100644 icons/rain0_sun.xbm delete mode 100644 icons/rain1.xbm delete mode 100644 icons/rain1_moon.xbm delete mode 100644 icons/rain1_sun.xbm delete mode 100644 icons/rain2.xbm delete mode 100644 icons/rain_lightning.xbm delete mode 100644 icons/rain_snow.xbm delete mode 100644 icons/snow.xbm delete mode 100644 icons/snow_moon.xbm delete mode 100644 icons/snow_sun.xbm delete mode 100644 icons/sun.xbm delete mode 100644 icons/wind.xbm delete mode 100644 image-gen/apply_audit.py delete mode 100644 image-gen/audit_quotes.py delete mode 100644 image-gen/build_gold_set.py delete mode 100644 image-gen/clean_csv.py delete mode 100644 image-gen/clean_quotes_csv.py delete mode 100644 image-gen/corpus_edit.py delete mode 100644 image-gen/dedupe_quotes.py delete mode 100755 image-gen/detect_nsfw.py delete mode 100644 image-gen/gather_quotes.py delete mode 100644 image-gen/generate_images.py delete mode 100644 image-gen/gold_set_192.csv delete mode 100644 image-gen/merge_quotes.py delete mode 100644 image-gen/nsfw_keywords.txt delete mode 100644 image-gen/prepare_review_csv.py delete mode 100644 image-gen/process_reviewed.py delete mode 100644 image-gen/quote_to_image.php delete mode 100644 image-gen/requirements-dev.txt delete mode 100755 image-gen/review_nsfw.py delete mode 100644 image-gen/time_parser.py delete mode 100644 image-gen/validate_time_parser.py create mode 100644 internal/clock/quote.go create mode 100644 internal/clock/quote_test.go rename {image-gen => internal/corpus}/litclock_annotated.csv (100%) create mode 100644 internal/corpus/repository.go create mode 100644 internal/corpus/repository_test.go create mode 100644 internal/frame/layout.go create mode 100644 internal/frame/layout_test.go create mode 100644 internal/tui/model.go create mode 100644 internal/tui/model_test.go create mode 100644 internal/tui/once.go create mode 100644 internal/tui/once_test.go delete mode 160000 lib/e-Paper delete mode 100644 logo.png delete mode 100644 package-lock.json delete mode 100644 package.json delete mode 100755 pi-gen/build.sh delete mode 100644 pi-gen/config delete mode 100644 pi-gen/stage3/00-install-deps/00-packages delete mode 100755 pi-gen/stage3/00-install-deps/01-run.sh delete mode 100755 pi-gen/stage3/01-setup-app/00-run.sh delete mode 100755 pi-gen/stage3/02-configure-system/00-run.sh delete mode 100644 pi-gen/stage3/02-configure-system/files/brcmfmac.conf delete mode 100644 pi-gen/stage3/02-configure-system/files/litclock-journald.conf delete mode 100755 pi-gen/stage3/03-install-services/00-run.sh delete mode 100755 pi-gen/stage3/04-finalize/00-run.sh delete mode 100755 pi-gen/stage3/05-smoke-test/00-run.sh delete mode 100644 pi-gen/stage3/EXPORT_IMAGE delete mode 100755 pi-gen/stage3/prerun.sh delete mode 100644 pyproject.toml delete mode 100644 requirements-apt.txt delete mode 100644 requirements-dev.txt delete mode 100644 requirements.in delete mode 100644 requirements.txt delete mode 100755 scripts/boot-splash.sh delete mode 100755 scripts/diag-pwa-load-audit.sh delete mode 100644 scripts/diag-subprocess-timing.py delete mode 100755 scripts/download_images.sh delete mode 100755 scripts/first-boot.sh delete mode 100755 scripts/install.sh delete mode 100644 scripts/lib/github_api.sh delete mode 100644 scripts/lib/state.sh delete mode 100644 scripts/lib/update_status.sh delete mode 100755 scripts/litclock-bootcheck-giveup-splash.sh delete mode 100755 scripts/litclock-bootcheck.sh delete mode 100755 scripts/litclock-handoff-fallback.sh delete mode 100755 scripts/litclock-lkg-record.sh delete mode 100755 scripts/litclock-mark-collected.sh delete mode 100755 scripts/litclock-set-timezone delete mode 100755 scripts/litclock-wifi-reset.sh delete mode 100755 scripts/nm-dispatcher/99-litclock-ip-change delete mode 100755 scripts/prepare-for-cloning.sh delete mode 100755 scripts/release_images.sh delete mode 100755 scripts/reset-setup.sh delete mode 100755 scripts/runtheclock.sh delete mode 100755 scripts/shutdown-splash.sh delete mode 100755 scripts/update.sh delete mode 100755 scripts/wifi-watchdog.sh delete mode 100644 src/captive_portal.py delete mode 100644 src/clear.py delete mode 100644 src/collected_marker.py delete mode 100644 src/config.py delete mode 100644 src/control_server/__init__.py delete mode 100644 src/control_server/_diagnostics_privacy.py delete mode 100644 src/control_server/_env.py delete mode 100644 src/control_server/_format.py delete mode 100644 src/control_server/_network.py delete mode 100644 src/control_server/_redaction.py delete mode 100644 src/control_server/_subprocess.py delete mode 100644 src/control_server/app.py delete mode 100644 src/control_server/confirm_tokens.py delete mode 100644 src/control_server/csrf.py delete mode 100644 src/control_server/errors.py delete mode 100644 src/control_server/handoff.py delete mode 100644 src/control_server/log_buffer.py delete mode 100644 src/control_server/rate_limit.py delete mode 100644 src/control_server/routes/__init__.py delete mode 100644 src/control_server/routes/diagnostics/__init__.py delete mode 100644 src/control_server/routes/diagnostics/_anomalies.py delete mode 100644 src/control_server/routes/diagnostics/_collectors.py delete mode 100644 src/control_server/routes/diagnostics/_copy_payload.py delete mode 100644 src/control_server/routes/diagnostics/_sse.py delete mode 100644 src/control_server/routes/handoff.py delete mode 100644 src/control_server/routes/health.py delete mode 100644 src/control_server/routes/index.py delete mode 100644 src/control_server/routes/settings.py delete mode 100644 src/control_server/routes/status.py delete mode 100644 src/control_server/routes/sw.py delete mode 100644 src/control_server/routes/system.py delete mode 100644 src/control_server/routes/updates.py delete mode 100644 src/control_server/routes/wifi.py delete mode 100644 src/control_server/static/css/aths-hint.css delete mode 100644 src/control_server/static/css/banners.css delete mode 100644 src/control_server/static/css/confirm-sheet.css delete mode 100644 src/control_server/static/css/diagnostics.css delete mode 100644 src/control_server/static/css/drawer.css delete mode 100644 src/control_server/static/css/handoff.css delete mode 100644 src/control_server/static/css/settings.css delete mode 100644 src/control_server/static/css/system.css delete mode 100644 src/control_server/static/css/tokens.css delete mode 100644 src/control_server/static/css/updates.css delete mode 100644 src/control_server/static/fonts/fraunces-wght-italic.woff2 delete mode 100644 src/control_server/static/fonts/fraunces-wght-normal.woff2 delete mode 100644 src/control_server/static/fonts/geist-mono-wght-normal.woff2 delete mode 100644 src/control_server/static/fonts/instrument-sans-wght-normal.woff2 delete mode 100644 src/control_server/static/icons/icon-192.png delete mode 100644 src/control_server/static/icons/icon-512.png delete mode 100644 src/control_server/static/icons/icon-bg-baked-152.png delete mode 100644 src/control_server/static/icons/icon-bg-baked-167.png delete mode 100644 src/control_server/static/icons/icon-bg-baked-180.png delete mode 100644 src/control_server/static/icons/icon-bg-baked-512.png delete mode 100644 src/control_server/static/icons/icon-maskable-512.png delete mode 100644 src/control_server/static/js/aths-hint.js delete mode 100644 src/control_server/static/js/diagnostics.js delete mode 100644 src/control_server/static/js/drawer.js delete mode 100644 src/control_server/static/js/handoff.js delete mode 100644 src/control_server/static/js/settings.js delete mode 100644 src/control_server/static/js/status.js delete mode 100644 src/control_server/static/js/sw-register.js delete mode 100644 src/control_server/static/js/system.js delete mode 100644 src/control_server/static/js/updates.js delete mode 100644 src/control_server/static/manifest.webmanifest delete mode 100644 src/control_server/static/paper-grain.svg delete mode 100644 src/control_server/static/splash/splash-1080x2340-dark.png delete mode 100644 src/control_server/static/splash/splash-1080x2340-light.png delete mode 100644 src/control_server/static/splash/splash-1125x2436-dark.png delete mode 100644 src/control_server/static/splash/splash-1125x2436-light.png delete mode 100644 src/control_server/static/splash/splash-1170x2532-dark.png delete mode 100644 src/control_server/static/splash/splash-1170x2532-light.png delete mode 100644 src/control_server/static/splash/splash-1179x2556-dark.png delete mode 100644 src/control_server/static/splash/splash-1179x2556-light.png delete mode 100644 src/control_server/static/splash/splash-1206x2622-dark.png delete mode 100644 src/control_server/static/splash/splash-1206x2622-light.png delete mode 100644 src/control_server/static/splash/splash-1242x2208-dark.png delete mode 100644 src/control_server/static/splash/splash-1242x2208-light.png delete mode 100644 src/control_server/static/splash/splash-1242x2688-dark.png delete mode 100644 src/control_server/static/splash/splash-1242x2688-light.png delete mode 100644 src/control_server/static/splash/splash-1260x2736-dark.png delete mode 100644 src/control_server/static/splash/splash-1260x2736-light.png delete mode 100644 src/control_server/static/splash/splash-1284x2778-dark.png delete mode 100644 src/control_server/static/splash/splash-1284x2778-light.png delete mode 100644 src/control_server/static/splash/splash-1290x2796-dark.png delete mode 100644 src/control_server/static/splash/splash-1290x2796-light.png delete mode 100644 src/control_server/static/splash/splash-1320x2868-dark.png delete mode 100644 src/control_server/static/splash/splash-1320x2868-light.png delete mode 100644 src/control_server/static/splash/splash-1536x2048-dark.png delete mode 100644 src/control_server/static/splash/splash-1536x2048-light.png delete mode 100644 src/control_server/static/splash/splash-1620x2160-dark.png delete mode 100644 src/control_server/static/splash/splash-1620x2160-light.png delete mode 100644 src/control_server/static/splash/splash-2048x2732-dark.png delete mode 100644 src/control_server/static/splash/splash-2048x2732-light.png delete mode 100644 src/control_server/static/splash/splash-640x1136-dark.png delete mode 100644 src/control_server/static/splash/splash-640x1136-light.png delete mode 100644 src/control_server/static/splash/splash-750x1334-dark.png delete mode 100644 src/control_server/static/splash/splash-750x1334-light.png delete mode 100644 src/control_server/static/splash/splash-828x1792-dark.png delete mode 100644 src/control_server/static/splash/splash-828x1792-light.png delete mode 100644 src/control_server/templates/_handoff-banner.html.j2 delete mode 100644 src/control_server/templates/base.html.j2 delete mode 100644 src/control_server/templates/diagnostics.html.j2 delete mode 100644 src/control_server/templates/settings.html.j2 delete mode 100644 src/control_server/templates/status.html.j2 delete mode 100644 src/control_server/templates/sw.js.j2 delete mode 100644 src/control_server/templates/system.html.j2 delete mode 100644 src/control_server/templates/updates.html.j2 delete mode 100644 src/control_server/update_state.py delete mode 100644 src/control_server/version.py delete mode 100644 src/control_url.py delete mode 100644 src/display_driver.py delete mode 100755 src/eink_display.py delete mode 100644 src/geocoding.py delete mode 100644 src/https_cert.py delete mode 100644 src/literary_clock.py delete mode 100644 src/location_resolver.py delete mode 100644 src/log.py delete mode 100644 src/quote_corpus.py delete mode 100755 src/setup_server.py delete mode 100644 src/weather_providers/__init__.py delete mode 100644 src/weather_providers/base_provider.py delete mode 100644 src/weather_providers/open_meteo.py delete mode 100644 src/weather_providers/openweathermap.py delete mode 100755 src/wifi_provision.py delete mode 100644 sudoers/020_litclock-control delete mode 100644 sysctl.d/30-litclock-unprivileged-ports.conf delete mode 100644 systemd/litclock-bootcheck.service delete mode 100644 systemd/litclock-bootcheck.timer delete mode 100644 systemd/litclock-control.service delete mode 100644 systemd/litclock-firstboot.service delete mode 100644 systemd/litclock-handoff-fallback.service delete mode 100644 systemd/litclock-handoff-fallback.timer delete mode 100644 systemd/litclock-lkg.service delete mode 100644 systemd/litclock-lkg.timer delete mode 100644 systemd/litclock-prepare-for-gift.service delete mode 100644 systemd/litclock-reresolve-location.service delete mode 100644 systemd/litclock-reset.service delete mode 100644 systemd/litclock-shutdown.service delete mode 100644 systemd/litclock-splash.service delete mode 100644 systemd/litclock-update.service delete mode 100644 systemd/litclock-update.timer delete mode 100644 systemd/litclock-wifi-reset.service delete mode 100644 systemd/litclock.service delete mode 100644 systemd/litclock.timer delete mode 100644 systemd/tmpfiles.d/litclock.conf delete mode 100644 systemd/wifi-watchdog.service delete mode 100644 systemd/wifi-watchdog.timer delete mode 100644 tests/__init__.py delete mode 100644 tests/conftest.py delete mode 100644 tests/js/aths-hint.mdns-coordination.test.js delete mode 100644 tests/js/diagnostics.test.js delete mode 100644 tests/js/drawer.test.js delete mode 100644 tests/js/handoff.test.js delete mode 100644 tests/js/helpers/loadScript.js delete mode 100644 tests/js/helpers/loadScript.test.js delete mode 100644 tests/js/settings.advanced-toggle.test.js delete mode 100644 tests/js/settings.location-pill.test.js delete mode 100644 tests/js/settings.update-banner.test.js delete mode 100644 tests/js/settings.weather-toggle.test.js delete mode 100644 tests/js/status.mdns-probe.test.js delete mode 100644 tests/js/status.phase3-skip-banner.test.js delete mode 100644 tests/js/status.shim.test.js delete mode 100644 tests/js/status.unreachable.test.js delete mode 100644 tests/js/updates.optimistic-tick.test.js delete mode 100644 tests/js/updates.refresh-check-couldnt-check.test.js delete mode 100644 tests/test_apply_audit.py delete mode 100644 tests/test_apt_provisioned_drift.py delete mode 100644 tests/test_audit_quotes.py delete mode 100644 tests/test_bootcheck.py delete mode 100644 tests/test_build_gold_set.py delete mode 100644 tests/test_build_image_workflow.py delete mode 100644 tests/test_clean_csv.py delete mode 100644 tests/test_collected_marker.py delete mode 100644 tests/test_config.py delete mode 100644 tests/test_control_server.py delete mode 100644 tests/test_control_server_confirm_tokens.py delete mode 100644 tests/test_control_server_diagnostics.py delete mode 100644 tests/test_control_server_diagnostics_anomalies.py delete mode 100644 tests/test_control_server_diagnostics_failures.py delete mode 100644 tests/test_control_server_diagnostics_readers.py delete mode 100644 tests/test_control_server_diagnostics_reexport.py delete mode 100644 tests/test_control_server_errors.py delete mode 100644 tests/test_control_server_handoff.py delete mode 100644 tests/test_control_server_logs_routes.py delete mode 100644 tests/test_control_server_m6_pwa_shell.py delete mode 100644 tests/test_control_server_m7_design_review.py delete mode 100644 tests/test_control_server_perf.py delete mode 100644 tests/test_control_server_settings.py delete mode 100644 tests/test_control_server_system_routes.py delete mode 100644 tests/test_control_server_updates_routes.py delete mode 100644 tests/test_control_server_wifi_routes.py delete mode 100644 tests/test_control_url.py delete mode 100644 tests/test_corpus_edit.py delete mode 100644 tests/test_diagnostics_no_secrets.py delete mode 100644 tests/test_diagnostics_privacy.py delete mode 100644 tests/test_diagnostics_uncollected.py delete mode 100644 tests/test_download_images.py delete mode 100644 tests/test_eink_display.py delete mode 100644 tests/test_eink_wrap.py delete mode 100644 tests/test_env_helper.py delete mode 100644 tests/test_envsh_shell_flock.py delete mode 100644 tests/test_first_boot_flow.py delete mode 100644 tests/test_geocoding.py delete mode 100644 tests/test_github_api_sh.py delete mode 100644 tests/test_install_sh.py delete mode 100644 tests/test_litclock_wifi_reset_sh.py delete mode 100644 tests/test_literary_clock.py delete mode 100644 tests/test_literary_clock_dry_run.py delete mode 100644 tests/test_lkg_writer.py delete mode 100644 tests/test_location_resolver.py delete mode 100644 tests/test_log.py delete mode 100644 tests/test_log_buffer.py delete mode 100644 tests/test_memory_sentinel.py delete mode 100644 tests/test_nm_dispatcher_install.py delete mode 100644 tests/test_open_meteo.py delete mode 100644 tests/test_pi_gen.py delete mode 100644 tests/test_prepare_for_cloning_sh.py delete mode 100644 tests/test_prepare_review_csv.py delete mode 100644 tests/test_quote_corpus.py delete mode 100644 tests/test_redaction.py delete mode 100644 tests/test_release_images.py delete mode 100644 tests/test_reset_setup_sh.py delete mode 100644 tests/test_resolve_target_sha.py delete mode 100644 tests/test_safe_read_json.py delete mode 100644 tests/test_set_timezone.py delete mode 100644 tests/test_setup_server.py delete mode 100644 tests/test_splash_scripts.py delete mode 100644 tests/test_subprocess_helper.py delete mode 100644 tests/test_sudoers_install.py delete mode 100644 tests/test_sudoers_m5.py delete mode 100644 tests/test_systemd_units.py delete mode 100644 tests/test_time_parser.py delete mode 100644 tests/test_update_sh.py delete mode 100644 tests/test_update_status_sh.py delete mode 100644 tests/test_weather.py delete mode 100644 tests/test_wifi_provision.py delete mode 100644 tests/test_wifi_retry_flow.py delete mode 100644 tests/test_wifi_watchdog_sh.py delete mode 100644 tools/control-pwa/fetch_fonts.py delete mode 100644 tools/control-pwa/generate_pwa_icons.py delete mode 100644 tools/control-pwa/validate_qr_layout.py delete mode 100644 vitest.config.js diff --git a/.claude/hooks/ruff-check.sh b/.claude/hooks/ruff-check.sh deleted file mode 100755 index 75ecf0e..0000000 --- a/.claude/hooks/ruff-check.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# Auto-run ruff after Claude edits a Python file - -INPUT=$(cat) -FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') - -# Only lint Python files -if [[ "$FILE_PATH" != *.py ]]; then - exit 0 -fi - -RUFF="${HOME}/.local/bin/ruff" - -# Run ruff check (lint) with auto-fix, then format -"$RUFF" check --fix "$FILE_PATH" 2>&1 -"$RUFF" format "$FILE_PATH" 2>&1 diff --git a/.claude/plans/419-followups.md b/.claude/plans/419-followups.md deleted file mode 100644 index f040988..0000000 --- a/.claude/plans/419-followups.md +++ /dev/null @@ -1,403 +0,0 @@ -# Plan: Issue #419 — #416 PR2 /review follow-ups - -## Status - -| # | Decision | Choice | -|---|----------|--------| -| D1 | Drop obsolete items M6 (placeholder bleed-through guard) + P5 (cache inline Jinja template) | **Dropped** — PR3 already replaced the placeholder; route now uses `render_template("diagnostics.html.j2", ...)` which is cached by Flask's named-template bytecode cache | -| D2 | PR strategy | **3 PRs**: PR1 (tests + refactor bundled) → PR2 (perf, slimmed) → PR3 (docs/API contract) | -| D3 | Re-export contract for `routes/diagnostics/__init__.py` | **Enumerate explicitly** in plan with `__all__` | -| D4 | PR2 cache shape | **SUPERSEDED by D9** — original answer was inadequate; codex caught deeper issues | -| D5 | Shared `_network.read_ssid` return type | **`str \| None`** — adapt status.py callers to `ssid or "—"` at render boundary; keep `_wifi_ssid` as a thin alias per F5 | -| D6 | Time-pin in anomaly tests | **stdlib monkeypatch.setattr** — no new freezegun dep | -| D7 | Outside voice | **Ran codex** — caught 12 issues, all verified | -| D8 | Monkeypatch + re-export trap (codex F1+F2+F4) | **Update tests to patch actual binding sites** — drop the "zero modifications" promise | -| D9 | PR2 cache fate (codex F7+F8+F9, supersedes D4) | **Drop PR2 P2 entirely** — subprocess cache already does the heavy lifting; tuple cache had 3 correctness traps for unmeasured perf win | -| D10 | get_logs asc+limit semantics (codex F11) | **`order` is post-filter on newest-N** — `get_logs(limit=N, order='asc')` = take N newest, reorder ascending | - -**Plus mechanical fixes from codex absorbed directly (no fork):** - -- F3: Files moving into `routes/diagnostics/` need import depth +1 (`from ..log_buffer` → `from ...log_buffer`) -- F5: Retain `_wifi_ssid` as a thin alias in `routes/status.py` for `tests/test_control_server.py:2525` compat -- F6: `_network.read_ssid(ttl: float = STATUS_SUBPROC_TTL_S)` — status passes default, diagnostics passes `DIAG_SUBPROC_TTL_S` -- F10: `atexit.register(_shutdown_pool)` where `_shutdown_pool` dereferences the current `_JOURNAL_POOL` global (not bound-method capture) - -21 items, 3 PRs. - -## Scope - -21 informational items deferred from PR2 (#418) /review. All hygiene/perf/test/docs — no correctness bugs (yet — the plan itself had to avoid creating some). Categories: - -- Maintainability: 5 items (was 6 — M6 dropped per D1) -- Performance: 3 items (was 5 — P5 dropped per D1, P2 dropped per D9) -- Testing: 7 items -- API contract: 4 items -- Plus 1 "envelope flattening" decision item - -## PR strategy (locked) - -``` -PR1: tests + refactor (bundled) -├── New tests against new package layout -├── Test files updated to patch actual binding sites (D8) -├── routes/diagnostics.py → routes/diagnostics/ package -├── Relative-import depth +1 in all moved files (F3) -├── New helpers: control_server/_format.py, control_server/_network.py -├── _wifi_ssid alias retained in status.py (F5) -└── Docstring refresh (M5) - -PR2: performance (slimmer post-D9) -├── Module-level ThreadPoolExecutor + reset_for_tests (F10-aware atexit) -├── handler.snapshot() single-lock method -└── get_logs(order='asc') as post-filter on limit (D10) - -PR3: docs + API contract -├── SSE error-frame shape docstring -├── section_order ↔ anomalies invariant docstring -├── capacity-exceeded SSE wire event docstring -└── Envelope shape decision (document, do not flatten) -``` - ---- - -## PR1: tests + refactor - -**Branch:** `refactor/419-pr1-tests-and-package` - -### Re-export contract (per D3, expanded for codex F1) - -`routes/diagnostics/__init__.py` MUST re-export everything existing tests reach into. Verified via grep across all `tests/test_*.py`. Note: re-export alone does NOT make `monkeypatch.setattr(diagnostics, "X", ...)` redirect bindings inside `_sse.py` (D8) — that requires patching the actual binding sites. Re-export is for plain imports like `from control_server.routes.diagnostics import collect_diagnostics`. - -```python -# routes/diagnostics/__init__.py -from ._collectors import ( - # subprocess + cache - cached_subprocess, - _lazy_cache, _lazy_cache_lock, - # public API - collect_diagnostics, schema_keys, - # per-row readers - _read_iface, _read_ssid, _read_lan_ip, _read_gateway, _read_signal_dbm, - _read_timezone, _read_kernel_release, _read_cpu_temp_c, - _read_recent_log_entries, - _batched_is_active, _batched_journal_tails, - # constants - DIAG_UNITS, DIAG_JOURNAL_LINES_PER_UNIT, DIAG_SUBPROC_TTL_S, - SECTION_IDS, PRIVACY_POLICY, -) -from ._anomalies import _compute_anomalies, _recent_logs_contain_error -from ._copy_payload import build_copy_payload -from ._sse import ( - bp, - _sse_registry, _register_sse, _unregister_sse, - _sse_format, _generate_sse, -) - -__all__ = [ - # Public route surface - "bp", - "collect_diagnostics", "build_copy_payload", "schema_keys", - # Subprocess + cache (tests reach in) - "cached_subprocess", "_lazy_cache", "_lazy_cache_lock", - # Anomaly engine - "_compute_anomalies", "_recent_logs_contain_error", - # Per-row readers - "_read_iface", "_read_ssid", "_read_lan_ip", "_read_gateway", - "_read_signal_dbm", "_read_timezone", "_read_kernel_release", - "_read_cpu_temp_c", "_read_recent_log_entries", - "_batched_is_active", "_batched_journal_tails", - # SSE machinery (tests reach in) - "_sse_registry", "_register_sse", "_unregister_sse", - "_sse_format", "_generate_sse", - # Constants - "DIAG_UNITS", "DIAG_JOURNAL_LINES_PER_UNIT", "DIAG_SUBPROC_TTL_S", - "SECTION_IDS", "PRIVACY_POLICY", -] -``` - -PR1 adds `tests/test_control_server_diagnostics_reexport.py` — single test that does every import and asserts `__all__` matches. - -### Test-modification policy (per D8, supersedes earlier acceptance) - -PR1 EXPLICITLY updates 5-10 existing test files to patch the new binding sites. The Python rule: "patch where the name is LOOKED UP, not where it is DEFINED." - -Affected files + patches: - -| File | Today | Update to | -|------|-------|-----------| -| `tests/test_control_server_diagnostics.py:438` | `setattr(diag_mod, "collect_diagnostics", ...)` | `setattr("control_server.routes.diagnostics._sse.collect_diagnostics", ...)` (route's binding) | -| `tests/test_control_server_diagnostics.py:468` | same pattern | same fix | -| `tests/test_control_server_diagnostics.py:551` | `setattr(diagnostics, "_batched_journal_tails", ...)` | `setattr("control_server.routes.diagnostics._collectors._batched_journal_tails", ...)` | -| `tests/test_control_server_logs_routes.py:*` | `diagnostics._sse_registry` direct access | unchanged — direct attribute READS work fine on the re-exported name | - -Audit pattern: any `monkeypatch.setattr(diag_mod_or_diagnostics, "", ...)` where `` is called from inside a submodule needs the actual-binding-site path. - -### Relative-import depth (per F3) - -Every file moved from `routes/` to `routes/diagnostics/` needs `..` → `...` for ancestor-package imports: - -| Old | New | -|-----|-----| -| `from ..log_buffer import ...` | `from ...log_buffer import ...` | -| `from .._env import ...` | `from ..._env import ...` | -| `from .._subprocess import ...` | `from ..._subprocess import ...` | -| `from .._diagnostics_privacy import ...` | `from ..._diagnostics_privacy import ...` | -| `from .._redaction import ...` | `from ..._redaction import ...` | -| `from .status import _resolve_last_update` | `from ..status import _resolve_last_update` | - -PR1 acceptance includes `python3 -c "from control_server.routes.diagnostics import *"` succeeds without ImportError. - -### Refactor tasks - -1. **Split `routes/diagnostics.py` → `routes/diagnostics/` package** (M1) - - `routes/diagnostics/__init__.py` — re-exports per contract above + bp registration - - `routes/diagnostics/_collectors.py` — per-row readers + `collect_diagnostics` + subprocess cache + schema - - `routes/diagnostics/_anomalies.py` — anomaly thresholds + `_compute_anomalies` + `_recent_logs_contain_error` - - `routes/diagnostics/_copy_payload.py` — `build_copy_payload` - - `routes/diagnostics/_sse.py` — bp + SSE registry + generator + ALL routes (`/api/diagnostics`, `/diagnostics`, `/api/logs`, `/api/logs/stream`) - - DIAG_UNITS / DIAG_JOURNAL_LINES_PER_UNIT / DIAG_SUBPROC_TTL_S live in `_collectors.py` - - Imports updated per F3 depth fix - -2. **Extract `control_server/_format.py`** (M2) - - Move `_format_uptime` (byte-identical between status + diagnostics) - - Both call sites import from `_format` - -3. **Extract `control_server/_network.py`** (M3, per D5+F6) - ```python - # control_server/_network.py - STATUS_SUBPROC_TTL_S = 5.0 # status default - - def read_ssid(ttl: float = STATUS_SUBPROC_TTL_S) -> str | None: ... - def read_lan_ip(ttl: float = STATUS_SUBPROC_TTL_S) -> str | None: ... - def read_gateway(ttl: float = STATUS_SUBPROC_TTL_S) -> str | None: ... - def read_signal_dbm(ttl: float = STATUS_SUBPROC_TTL_S) -> int | None: ... - ``` - - status.py: keeps `_wifi_ssid` as a thin alias (`def _wifi_ssid() -> str: return read_ssid() or ""`) — preserves test_control_server.py:2525 monkeypatch surface (F5) - - `_collectors.py`: calls `read_ssid(ttl=DIAG_SUBPROC_TTL_S)` etc. - -4. **Document `os.environ` ↔ `current_app.config` precedence** (M4) - - Module docstring in `routes/diagnostics/__init__.py` - - Inline comment in `_collectors.py` at the precedence site - -5. **Refresh stale docstring** (M5) - - Kill "minimal HTML placeholder" line - - Kill "PR3 lands /api/logs*" references - - Full module docstring audit - -### Test tasks - -1. **Per-row reader tests with monkeypatched `cached_subprocess`** (T1) - - New file `tests/test_control_server_diagnostics_readers.py` - - Patches `control_server.routes.diagnostics._collectors.cached_subprocess` (actual binding) — and a parallel test imports via the re-exported path to confirm both work - - Cover: `_read_iface`, `_read_ssid`, `_read_signal_dbm`, `_read_timezone`, `_read_kernel_release`, `_batched_is_active` — 3 cases each (18 total) - -2. **Pin clock for time-based anomaly tests via monkeypatch** (T2, per D6) - - `monkeypatch.setattr("control_server.routes.diagnostics._anomalies.datetime", FakeDatetime(...))` - - `monkeypatch.setattr("control_server.routes.diagnostics._collectors.time", FakeTimeModule(...))` - - Threshold-boundary tests (at, +1ms, −1ms) - -3. **Per-row failure-path tests for malformed sources** (T3) — same as before - -4. **`build_copy_payload` edge-case tests** (T4) — same as before - -5. **Sid-shape parametrize extension** (T5) — same as before - -6. **Replace `cutoff = 0` hardcode in `test_backfill_then_hello`** (T6) — same as before - -7. **`/api/diagnostics` 500-envelope test** (T7) — same as before - -8. **Tighten deny-list positive control via env.sh** (T8) — same as before - -**Acceptance for PR1:** -- Lint clean -- All NEW tests pass -- All EXISTING tests pass (some test files have been updated per D8 to patch the new binding sites) -- `from control_server.routes.diagnostics import *` succeeds -- Re-export test asserts `__all__` matches - ---- - -## PR2: performance (slimmer post-D9) - -**Branch:** `perf/419-pr2-diagnostics` - -1. **Module-level `ThreadPoolExecutor`** (P1, F10-aware) - ```python - # routes/diagnostics/_collectors.py - _JOURNAL_POOL: ThreadPoolExecutor | None = None - - def _get_journal_pool() -> ThreadPoolExecutor: - global _JOURNAL_POOL - if _JOURNAL_POOL is None: - _JOURNAL_POOL = ThreadPoolExecutor( - max_workers=4, thread_name_prefix="diag-journal" - ) - return _JOURNAL_POOL - - def _shutdown_pool() -> None: - global _JOURNAL_POOL - if _JOURNAL_POOL is not None: - _JOURNAL_POOL.shutdown() - _JOURNAL_POOL = None - - atexit.register(_shutdown_pool) - - def reset_for_tests() -> None: - _shutdown_pool() # mirrors log_buffer.reset_for_tests - ``` - - F10 fix: `atexit` registers a function that dereferences the current global, NOT a bound method that captured the original executor - -2. **`handler.snapshot()` single-lock method** (P3) - ```python - def snapshot(self, level: str | None = None) -> tuple[list[LogEntry], int, int]: - """Atomic snapshot under one lock acquire.""" - with self._lock: - return ( - self._collect_entries_unlocked(level), - self._total_count_unlocked(level), - self._latest_seq_unlocked(), - ) - ``` - - `GET /api/logs` switches to `snapshot()` - - Tests: empty buffer, populated buffer, concurrent emit (thread test) - -3. **`order='asc'` param on `get_logs()`** (P4, per D10) - - **Semantics:** `get_logs(limit=N, order='asc')` returns the **N newest** entries, sorted oldest-first (in chronological order). Limit ALWAYS means "newest N"; order is a post-filter on that selection. - - Default stays `'desc'` (newest-first) for back-compat. - - `_generate_sse` backfill: `entries = handler.get_logs(limit=BACKFILL_N, order='asc')` — no `reversed()` needed. - - Tests: - - `get_logs(limit=4, order='asc')` on 10-entry buffer → entries[6..9] in chrono order - - default desc unchanged - - asc + level filter combo - - explicit docstring contract test - -**Acceptance:** all PR1 tests pass + new perf tests. ~5 new test cases. - ---- - -## PR3: docs + API contract - -**Branch:** `docs/419-pr3-api-contract` - -1. **Document SSE error-frame shape** (A1) -2. **Document `section_order` ↔ `anomalies` invariant** (A2) -3. **Document `capacity-exceeded` SSE wire event** (A3) — verify PR3 client backoff -4. **Keep `/api/diagnostics` envelope wrapped** (A4) — document why in `errors.py` - -**Acceptance:** docstring-only. No behavior change. - ---- - -## Cross-cutting risks (post-decisions) - -- **Monkeypatch binding-site updates (D8):** every test that monkeypatched `diag_mod.X` must now patch `diag_mod._collectors.X` or `diag_mod._sse.X`. PR1 explicitly audits + updates. -- **Relative-import depth (F3):** every moved file needs `..` → `...`. PR1 acceptance includes `import *` smoke test. -- **`_wifi_ssid` alias (F5):** preserved as a 1-line shim so existing test stays valid. -- **Cache TTL parameterized (F6):** `read_ssid(ttl=...)` so status + diagnostics keep their independent cache freshness. -- **PR2 perf is now small (D9):** the heavy cache work is dropped; PR2 has only the safe wins (pool hoist, snapshot, asc-order). - -## NOT in scope - -- Pre-#337 settings IA work (already shipped) -- Any change to `_diagnostics_privacy.py` redaction chain (locked in PR1 of EPIC #416) -- Diagnostics drawer client JS (PR3 of #416 shipped; #419 is server-side cleanup only) -- Flattening the `/api/diagnostics` envelope (would break the just-shipped PR3 client) -- PR2 P2 (the tuple cache for `(values, anomalies, copy_payload)`) — dropped per D9. If a real CPU bottleneck is later measured on a Pi Zero 2W, a targeted cache can be added with measurement. -- Distribution: no new artifact; refactoring only - -## What already exists - -- `routes/status.py:360` — `_format_uptime` (extracted to `_format.py`) -- `routes/status.py:125` — `_wifi_ssid` (kept as alias for `read_ssid()`) -- `src/control_server/log_buffer.py:390` — `reset_for_tests()` pattern (mirrored in `_collectors.py`) -- `src/control_server/_subprocess.py` — `cached_subprocess` (still the single subprocess-dedup point) -- `src/control_server/_diagnostics_privacy.py` — PRIVACY_POLICY (untouched) -- All existing tests under `tests/test_*.py` — some get monkeypatch-path updates per D8; most untouched - -## Validation per PR - -- Lint: `ruff check src/ image-gen/ tests/` -- Python tests: `python3 -m pytest tests/ --ignore=tests/test_eink_display.py -q` -- JS tests: `npm run test:js` (PR2 may touch log_buffer.py; verify SSE client JS still works) -- `/review` before each merge - -## Failure modes - -| Path | Failure mode | Test? | Error handling? | User-visible? | -|------|--------------|-------|-----------------|---------------| -| `read_ssid()` returns None (nmcli missing) | None propagates | ✅ PR1 T1 | status alias maps to ""; diagnostics keeps None | "—" via render | -| `_JOURNAL_POOL` shutdown after atexit fired | RuntimeError on submit | ⚠ Add catch+empty-list fallback in `_batched_journal_tails` | yes | Silent empty section (acceptable) | -| Monkeypatched test miss after D8 audit | False-positive test pass | ✅ PR1 re-export test + audit | n/a | Test infra only | -| `get_logs(asc, limit=N)` on empty buffer | Returns [] | ✅ explicit test | n/a | n/a | -| `handler.snapshot()` while emit holds lock | Brief contention (microseconds) | ✅ thread test | n/a | None | -| Relative-import depth wrong | ImportError at startup | ✅ `import *` smoke test in PR1 acceptance | yes — service won't start | Catastrophic (caught at deploy) | - -**No critical gaps.** (Restated honestly now — the codex pass caught the gaps the inside review missed; this revised plan addresses them.) - -## Worktree parallelization strategy - -Sequential implementation, no parallelization opportunity. PR2 depends on PR1's package layout; PR3 docs touch PR1+PR2 surfaces. - -## Implementation Tasks - -``` -PR1 — tests + refactor (largest): -- [ ] T1 (P1, human: ~3.5h / CC: ~30min) — routes/diagnostics/ package — Split + relative-import depth fix per F3 + re-export contract per D3 -- [ ] T2 (P1, human: ~30min / CC: ~5min) — control_server/_format.py — Extract _format_uptime -- [ ] T3 (P1, human: ~45min / CC: ~10min) — control_server/_network.py — Extract read_* with TTL param per F6; _wifi_ssid alias per F5 -- [ ] T4 (P1, human: ~1h / CC: ~10min) — tests/test_control_server_*.py — Audit + update monkeypatch sites per D8 -- [ ] T5 (P2, human: ~20min / CC: ~5min) — tests/test_control_server_diagnostics_reexport.py — Re-export contract test -- [ ] T6 (P1, human: ~2h / CC: ~15min) — tests/test_control_server_diagnostics_readers.py — 18 reader tests -- [ ] T7 (P1, human: ~1h / CC: ~10min) — tests/test_control_server_diagnostics_anomalies.py — Threshold tests with monkeypatched time -- [ ] T8 (P2, human: ~1h / CC: ~10min) — tests/test_control_server_diagnostics_failures.py — Malformed-source paths (5) -- [ ] T9 (P2, human: ~45min / CC: ~10min) — tests/test_control_server_diagnostics.py — Extend build_copy_payload + sid-shape -- [ ] T10 (P2, human: ~15min / CC: ~3min) — tests/test_control_server_logs_routes.py — Replace cutoff=0 hardcode -- [ ] T11 (P2, human: ~15min / CC: ~3min) — tests/test_control_server_diagnostics.py — /api/diagnostics 500-envelope test -- [ ] T12 (P2, human: ~30min / CC: ~5min) — tests/test_diagnostics_no_secrets.py — Tighten deny-list positive control -- [ ] T13 (P3, human: ~20min / CC: ~5min) — All touched modules — Docstring refresh (M5, M4) - -PR2 — perf (slimmer): -- [ ] T14 (P1, human: ~45min / CC: ~10min) — routes/diagnostics/_collectors.py — Module-level _JOURNAL_POOL + F10-aware atexit + reset_for_tests -- [ ] T15 (P1, human: ~1h / CC: ~15min) — log_buffer.py — handler.snapshot() + get_logs(order='asc') as post-filter on newest-N (D10) -- [ ] T16 (P1, human: ~1h / CC: ~15min) — tests/test_control_server_perf.py — ~5 new tests (pool, snapshot, asc-order semantics) - -PR3 — docs + API: -- [ ] T17 (P3, human: ~1h / CC: ~10min) — routes/diagnostics/_sse.py + __init__.py + errors.py — A1-A4 docstrings -``` - -Total: 17 build tasks. Down from 21 items (some merged, some absorbed mechanically). - -## TODOS.md updates - -None — this issue IS the TODO bucket. Two new follow-ups created by D9: -- **Future: measure `/api/diagnostics` CPU on Pi Zero 2W; if >50ms, add a targeted cache with real numbers.** (Filed as a comment on #419 itself, not a new issue.) - -## Completion summary - -- Step 0: Scope reduced per D1+D2 (24 items → 21 items, 4 PRs → 3 PRs) -- Architecture Review: 2 issues found, both resolved (D3, D4→superseded by D9) -- Code Quality Review: 1 issue found, resolved (D5) -- Test Review: coverage diagram produced, ~25 PLANNED branches + 0 GAP -- Performance Review: 0 new issues (P1+P3+P4 in plan; P2 dropped per D9) -- Outside voice (codex): 12 findings, all verified; 3 forks resolved (D8, D9, D10), 4 mechanical fixes absorbed (F3, F5, F6, F10), 2 already covered (F4, F1 via D8/D3 expanded) -- NOT in scope: written -- What already exists: written -- Failure modes: 0 critical gaps (the codex pass de-risked the plan) -- Parallelization: sequential, no opportunity -- Lake Score: 10/10 recommendations chose complete option (D1+D2+D3+D4+D5+D6+D7+D8+D9+D10) - -## GSTACK REVIEW REPORT - -| Review | Trigger | Why | Runs | Status | Findings | -|--------|---------|-----|------|--------|----------| -| CEO Review | `/plan-ceo-review` | Scope & strategy | 0 | — | — | -| Codex Review | `/codex review` | Independent 2nd opinion | 1 | issues_found→resolved | 12 findings, all verified, 3 absorbed as decisions (D8/D9/D10) + 4 mechanical fixes + 2 already covered | -| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | CLEAR | 15 issues found (3 inside + 12 codex), 0 critical gaps remaining, 10 decisions locked | -| Design Review | `/plan-design-review` | UI/UX gaps | 0 | — | n/a — no UI scope | -| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | — | — | - -- **CODEX:** caught 5+ critical issues the inside review missed (monkeypatch-trap, anomaly-cache-staleness, asc+limit semantic trap, atexit-bound-method bug, incomplete `__all__`). All resolved or absorbed mechanically. -- **CROSS-MODEL:** strong agreement on the 3 absorbed decisions (D8/D9/D10). No unresolved tensions. -- **UNRESOLVED:** 0 -- **VERDICT:** ENG CLEARED — ready to implement. Start with PR1 on branch `refactor/419-pr1-tests-and-package`. No design review needed (server-side cleanup only). - diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index 56e3d2d..0000000 --- a/.claude/settings.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "hooks": { - "PostToolUse": [ - { - "matcher": "Edit|Write", - "hooks": [ - { - "type": "command", - "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/ruff-check.sh", - "timeout": 30 - } - ] - } - ] - } -} diff --git a/.gitattributes b/.gitattributes index 66ec2af..77ac1c1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -# Rendered manual booklets (#404) are generated artifacts (build.sh). Chromium -# print-to-pdf embeds a creation timestamp, so bytes change every rebuild — -# mark binary so git doesn't attempt (noisy, useless) textual diffs. -docs/manual/*.pdf -diff -text +* text=auto +*.go text eol=lf +*.md text eol=lf +*.csv text eol=lf diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index aaf26c9..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -name: Bug report -about: Report a problem with the clock -labels: bug ---- - -## Describe the bug - -A clear description of what's going wrong. - -## Steps to reproduce - -1. ... -2. ... -3. ... - -## Expected behavior - -What should happen instead. - -## Hardware - -- **Pi model**: (e.g., Pi Zero 2 W) -- **Display**: (e.g., Waveshare 7.5" V2) -- **OS**: (output of `cat /etc/os-release | head -2`) -- **Installation method**: (DIY install / pre-configured SD card) - -## Logs / diagnostics - -**Easiest — no shell needed:** open the Control PWA (`http://litclock.local`, or the -IP shown on the e-ink) → **Diagnostics** tab → tap **Copy support payload** (bottom of -the tab) and paste it here (or attach a screenshot). It includes version, render status, -WiFi/weather, error flags, and recent log tails, with secrets redacted. - -**If you already have a shell** (SSH ships off — enable it from the console first; -see the [Recovery guide](https://github.com/kapoorankush/litclock/blob/master/docs/recovery.md)): - -``` -journalctl -u litclock.service --since today -journalctl -u litclock-firstboot.service --since today -``` diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 84f6f05..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for the clock -labels: enhancement ---- - -## Use case - -What problem does this solve, or what would it improve? - -## Proposed solution - -Describe how you'd like it to work. - -## Alternatives considered - -Any other approaches you've thought about. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index a2f24f6..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,27 +0,0 @@ -## Summary - - - -## Changes - - - -- - -## Related Issues - - - -## Test Plan - - - -- [ ] Tested locally -- [ ] Tested on Raspberry Pi hardware -- [ ] No testing needed (documentation/config only) - -## Checklist - -- [ ] Commit messages follow [conventional commits](https://www.conventionalcommits.org/) format -- [ ] Documentation updated (if applicable) -- [ ] No secrets or credentials included diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 752afb2..01fffd3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,23 +1,10 @@ version: 2 - updates: - # GitHub Actions — surface security updates and action bumps. - # Reviewed manually; rebuilds only happen on demand, not on a schedule. - - package-ecosystem: "github-actions" - directory: "/" + - package-ecosystem: gomod + directory: / schedule: - interval: "monthly" - open-pull-requests-limit: 5 - labels: - - "dependencies" - - "ci" - - # Python runtime dependencies baked into the Pi image. - - package-ecosystem: "pip" - directory: "/" + interval: weekly + - package-ecosystem: github-actions + directory: / schedule: - interval: "monthly" - open-pull-requests-limit: 5 - labels: - - "dependencies" - - "python" + interval: weekly diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml deleted file mode 100644 index e23cae3..0000000 --- a/.github/workflows/build-image.yml +++ /dev/null @@ -1,284 +0,0 @@ -name: Build Image - -on: - push: - tags: ["v*"] - workflow_dispatch: - inputs: - litclock_ref: - description: "Git ref to bake into the image (tag, branch, or SHA)" - required: false - default: "master" - -jobs: - build: - name: Build Pi OS image - runs-on: ubuntu-24.04 - timeout-minutes: 180 - permissions: - contents: write # needed for creating releases - id-token: write # OIDC token for Sigstore signing (SLSA attestation) - attestations: write # post attestation to GitHub's attestation store - - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.inputs.litclock_ref || github.ref }} - submodules: true - - # Free disk space — pi-gen needs ~10 GB - - name: Free disk space - run: | - sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \ - /usr/local/share/boost /usr/share/swift /opt/hostedtoolcache - sudo apt-get clean - df -h / - - - name: Determine version - id: version - env: - REF_TYPE: ${{ github.ref_type }} - REF_NAME: ${{ github.ref_name }} - DISPATCH_REF: ${{ github.event.inputs.litclock_ref || 'master' }} - run: | - if [[ "${REF_TYPE}" == "tag" ]]; then - VERSION="${REF_NAME}" - VERSION="${VERSION#v}" # strip leading v - REF="${REF_NAME}" - else - VERSION="dev-$(date +%Y%m%d)-${GITHUB_SHA::7}" - REF="${DISPATCH_REF}" - fi - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "ref=${REF}" >> "$GITHUB_OUTPUT" - echo "sha=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" - echo "Version: ${VERSION}, Ref: ${REF}" - - # pi-gen builds arm64 images via QEMU emulation on x86 runners - - name: Set up QEMU - run: sudo apt-get update && sudo apt-get install -y qemu-user-static - - - name: Clone pi-gen - # Pinned to the 2025-05-06 bookworm-arm64 tag. Tags are immutable in - # practice but CAN be force-updated — we verify the commit SHA to detect - # any upstream tampering. - env: - PIGEN_TAG: 2025-05-06-raspios-bookworm-arm64 - PIGEN_SHA: 49e1078c1b07ff4948d304e0149d3bc698e0006a - run: | - git clone --depth 1 --branch "$PIGEN_TAG" \ - https://github.com/RPi-Distro/pi-gen.git /tmp/pi-gen - ACTUAL=$(git -C /tmp/pi-gen rev-parse HEAD) - if [ "$ACTUAL" != "$PIGEN_SHA" ]; then - echo "ERROR: pi-gen HEAD $ACTUAL != expected $PIGEN_SHA" >&2 - echo "The upstream tag may have been force-updated. Review before bumping." >&2 - exit 1 - fi - - - name: Configure pi-gen - run: | - # Copy base config - cp pi-gen/config /tmp/pi-gen/config - - # Append build-time variables - echo "LITCLOCK_REF=${{ steps.version.outputs.ref }}" >> /tmp/pi-gen/config - echo "LITCLOCK_VERSION=${{ steps.version.outputs.version }}" >> /tmp/pi-gen/config - echo "LITCLOCK_SHA=${{ steps.version.outputs.sha }}" >> /tmp/pi-gen/config - - # Replace pi-gen's default stage3 with our custom stage - rm -rf /tmp/pi-gen/stage3 - cp -r pi-gen/stage3 /tmp/pi-gen/stage3 - - # Make scripts executable - find /tmp/pi-gen/stage3 -name "*.sh" -exec chmod +x {} + - - # Only export image from our stage - touch /tmp/pi-gen/stage2/SKIP_IMAGES - - # pi-gen's export-image unconditionally copies .bmap but bmap-tools - # is not in its Dockerfile — make the copy conditional - sed -i 's|cp "$BMAP_FILE" "$DEPLOY_DIR/"|[ -f "$BMAP_FILE" ] \&\& cp "$BMAP_FILE" "$DEPLOY_DIR/"|' \ - /tmp/pi-gen/export-image/05-finalise/01-run.sh - - - name: Stage repo for image - run: | - # Copy checkout into pi-gen so it's baked into the Docker image - # (chroot has no network/credentials to git clone). - # actions/checkout already uses depth 1, so .git is minimal. - cp -a . /tmp/pi-gen/litclock-src - # Remove pi-gen build artifacts to avoid nesting - rm -rf /tmp/pi-gen/litclock-src/pi-gen - - - name: Download quote images - # Quote images live in a GitHub Release (litclock-images-vN), pinned - # by .images-version in the repo root. Fetch them here so they get - # baked into the OS image alongside the code. The image build has no - # separate "first boot network" step, so if we don't stage them now - # the device ships with an empty images/ and every quote slot falls - # back to time-only until a power user runs update.sh. - env: - # Populate the token so download_images.sh can fetch while the - # repo is private. Becomes a no-op once the repo is public. - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - - STAGED=/tmp/pi-gen/litclock-src - PINNED=$(tr -d '[:space:]' < "$STAGED/.images-version") - TAG="litclock-images-${PINNED}" - - # Pre-flight: confirm BOTH the tarball AND its .sha256 sidecar exist - # on the release. Missing either means the release was published - # incompletely (e.g. scripts/release_images.sh was interrupted); - # fail fast before kicking off a 30-60 minute pi-gen run. - ASSET_NAMES=$(gh release view "$TAG" --json assets --jq '.assets[].name' || true) - for NEEDED in "litclock-images.tar.gz" "litclock-images.tar.gz.sha256"; do - if ! echo "$ASSET_NAMES" | grep -qx "$NEEDED"; then - echo "::error::Release $TAG is missing required asset: $NEEDED" - echo "Publish a complete release with: scripts/release_images.sh $PINNED" - exit 1 - fi - done - - # Run the same download script the Pi uses, so we test the real - # code path. Point it at the staged repo so it writes images/ in - # the right place for pi-gen stage3 to pick up. - chmod +x "$STAGED/scripts/download_images.sh" - "$STAGED/scripts/download_images.sh" --repo-root "$STAGED" - - # Marker check: the download must have written the installed-version - # marker. Missing marker = graceful-exit-0 fired somewhere (network, - # 404, SHA mismatch). In CI we want a hard failure. - if [ ! -f "$STAGED/images/.installed-version" ]; then - echo "::error::download_images.sh ran but images/.installed-version was not created" - exit 1 - fi - - # Count floor: guard against a malformed-but-sha-matching tarball - # that only contains a few files. Expected corpus is 9500+ files. - COUNT=$(find "$STAGED/images" -type f -name '*.png' | wc -l) - MIN_COUNT=8000 - if [ "$COUNT" -lt "$MIN_COUNT" ]; then - echo "::error::Only $COUNT PNGs staged (expected >= $MIN_COUNT) — release tarball is likely corrupt" - exit 1 - fi - echo "Staged $COUNT quote images from $TAG into the pi-gen input directory" - - - name: Build image - run: | - cd /tmp/pi-gen - ./build-docker.sh - - # Smoke testing happens IN-CHROOT during pi-gen build at - # pi-gen/stage3/05-smoke-test/00-run.sh. If the venv is broken, a - # Python dep fails to install, or a systemd unit has invalid syntax, - # stage3 fails and no .img is ever produced. Post-export mount-based - # smoke testing was tried and removed — it produced false alarms from - # GH Actions loop-device quirks. See PR #202 and issue #114 for - # context and the community discussions linked there. - - - name: Compress image - id: compress - run: | - DEPLOY=/tmp/pi-gen/deploy - - # pi-gen outputs a zip containing the .img — extract it - ZIP=$(ls "${DEPLOY}"/image_*.zip 2>/dev/null | head -1) - if [ -z "$ZIP" ]; then - echo "ERROR: No image zip found in ${DEPLOY}/" - ls -la "${DEPLOY}/" 2>/dev/null || echo "deploy/ directory does not exist" - exit 1 - fi - unzip -o "$ZIP" -d /tmp - IMG=$(ls /tmp/*.img 2>/dev/null | head -1) - if [ -z "$IMG" ]; then - echo "ERROR: No .img file found after extracting $ZIP" - exit 1 - fi - - IMG_NAME="litclock-${{ steps.version.outputs.version }}.img" - - # Rename to versioned name - mv "${IMG}" "/tmp/${IMG_NAME}" - - # Compress with xz (parallel) - xz -9 -T0 "/tmp/${IMG_NAME}" - - # Generate checksums - cd /tmp - sha256sum "${IMG_NAME}.xz" > "${IMG_NAME}.xz.sha256" - - echo "img=/tmp/${IMG_NAME}.xz" >> "$GITHUB_OUTPUT" - echo "sha=/tmp/${IMG_NAME}.xz.sha256" >> "$GITHUB_OUTPUT" - echo "img_name=${IMG_NAME}.xz" >> "$GITHUB_OUTPUT" - - echo "Compressed image:" - ls -lh "/tmp/${IMG_NAME}.xz" - - # SLSA build provenance: sign the compressed image so downstream users - # can verify it was built by this workflow from a specific commit. - # Verify with: gh attestation verify litclock-.img.xz --owner kapoorankush - # Skipped on private repos — GitHub's attestation API rejects user-owned - # private repos. Will start working automatically when the repo goes public. - - name: Attest build provenance - if: ${{ !github.event.repository.private }} - uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 - with: - subject-path: ${{ steps.compress.outputs.img }} - - # On manual dispatch: upload as a dev pre-release - # (GitHub Releases assets don't count toward Actions storage quota, - # unlike artifacts which accrue GB-hours against the 500 MB free tier) - - name: Upload dev build - if: github.ref_type != 'tag' - env: - GH_TOKEN: ${{ github.token }} - run: | - TAG="${{ steps.version.outputs.version }}" - # Delete existing release with this tag (idempotent re-runs) - gh release delete "${TAG}" --yes --cleanup-tag 2>/dev/null || true - # Clean up dev releases older than 7 days - gh release list --json tagName,createdAt \ - --jq '.[] | select(.tagName | startswith("dev-")) | select((.createdAt | fromdateiso8601) < (now - 604800)) | .tagName' \ - | xargs -I{} gh release delete {} --yes --cleanup-tag 2>/dev/null || true - gh release create "${TAG}" \ - --prerelease \ - --title "Dev build ${{ steps.version.outputs.version }}" \ - --notes "Dev build from \`${{ steps.version.outputs.ref }}\` ($(date -u +%Y-%m-%dT%H:%M:%SZ)). Auto-generated, not for production use." \ - --target "${{ github.sha }}" \ - "${{ steps.compress.outputs.img }}" \ - "${{ steps.compress.outputs.sha }}" - - # On tag push: create GitHub Release - # - # --target is REQUIRED: without it, GitHub stores the default branch - # name (e.g. "master") in the Release's target_commitish field. The - # #209 auto-update resolver reads tag_name and resolves it via - # git rev-list, but downstream tooling (and any future resolver that - # trusts target_commitish) needs honest metadata. Always stamp the - # actual commit SHA so the Release points where everyone thinks it does. - - name: Create release - if: github.ref_type == 'tag' - env: - GH_TOKEN: ${{ github.token }} - REF_NAME: ${{ github.ref_name }} - IMG: ${{ steps.compress.outputs.img }} - SHA: ${{ steps.compress.outputs.sha }} - # Idempotent: if a release with this tag already exists (someone - # pre-cut it manually with curated notes, or a prior workflow run - # got this far and partial-failed), upload the assets to it with - # --clobber instead of trying to recreate it. `gh release create` - # exits 1 on collision, which used to discard a freshly-built - # 1.4GB image alongside a 71-min build. See #242 for the postmortem. - run: | - if gh release view "${REF_NAME}" --json id >/dev/null 2>&1; then - echo "Release ${REF_NAME} already exists — uploading assets with --clobber" - gh release upload "${REF_NAME}" "${IMG}" "${SHA}" --clobber - else - gh release create "${REF_NAME}" \ - --title "LitClock ${{ steps.version.outputs.version }}" \ - --target "${{ github.sha }}" \ - --generate-notes \ - "${IMG}" \ - "${SHA}" - fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..743a09a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + cache: true + - name: Check formatting + run: test -z "$(gofmt -l .)" + - name: Test + run: go test -race ./... + - name: Vet + run: go vet ./... + - name: Build + run: go build ./cmd/litclock diff --git a/.github/workflows/corpus-integrity.yml b/.github/workflows/corpus-integrity.yml deleted file mode 100644 index 6589458..0000000 --- a/.github/workflows/corpus-integrity.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: Corpus integrity - -# #299 enforcement layer. -# -# Goal: prevent CSV edits from landing without a matching image release. The -# bug we're guarding against is "edit the CSV, forget to re-run corpus_edit -# ship, merge anyway, ship desynced PNGs to every Pi" — the exact path that -# produced the 940 at-risk buckets observed on test Pi 192.168.2.132 in May -# 2026. Local pre-commit hooks were considered and rejected (codex-flagged -# bypassable via web edits, squash merges, --no-verify, missing-install). -# Required CI is the only enforceable gate. -# -# Trigger: PRs touching image-gen/litclock_annotated.csv only. Other PRs -# skip this workflow entirely (paths filter). -# -# Checks (in order; first failure aborts): -# 1. .images-version was bumped vs base (otherwise no new release was cut). -# 2. The expected litclock-images-vN release exists on GitHub. -# 3. The release publishes manifest.json as a top-level asset. -# 4. manifest.corpus_hash == sha1(PR's image-gen/litclock_annotated.csv). -# -# Auth: GITHUB_TOKEN (ambient, no secrets needed for own-repo release reads). - -on: - pull_request: - paths: - - 'image-gen/litclock_annotated.csv' - branches: [master] - -permissions: - contents: read - -jobs: - verify: - name: Verify CSV matches images release manifest - runs-on: ubuntu-latest - steps: - - name: Check out PR head - uses: actions/checkout@v7 - with: - fetch-depth: 0 - # Explicit repository pin makes fork-PR runs work: with `pull_request` - # (not pull_request_target) the runner clones the base by default, - # but the fork's head SHA only exists on the fork. Pin both. - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.sha }} - - - name: Resolve PR and base .images-version - id: versions - run: | - set -euo pipefail - if [ ! -f .images-version ]; then - echo "::error file=.images-version::.images-version missing on PR head" >&2 - exit 1 - fi - PR_VERSION=$(tr -d '[:space:]' < .images-version) - BASE_SHA="${{ github.event.pull_request.base.sha }}" - BASE_VERSION=$(git show "$BASE_SHA:.images-version" 2>/dev/null | tr -d '[:space:]' || true) - echo "PR_VERSION=$PR_VERSION" >> "$GITHUB_OUTPUT" - echo "BASE_VERSION=$BASE_VERSION" >> "$GITHUB_OUTPUT" - echo "Base .images-version: ${BASE_VERSION:-}" - echo "PR .images-version: ${PR_VERSION}" - if [ -z "$PR_VERSION" ]; then - echo "::error::PR .images-version is empty" >&2 - exit 1 - fi - # Mirror release_images.sh's format check so a malformed pin fails - # with a clear message instead of a confusing "release not found". - if ! [[ "$PR_VERSION" =~ ^v[0-9]+$ ]]; then - echo "::error::PR .images-version $PR_VERSION is not in vN format (must match ^v[0-9]+$)." >&2 - exit 1 - fi - if [ "$PR_VERSION" = "$BASE_VERSION" ]; then - echo "::error::CSV changed but .images-version was not bumped (still $PR_VERSION)." >&2 - echo "::error::Re-ship via: python3 image-gen/corpus_edit.py ship \"\"" >&2 - exit 1 - fi - - - name: Locate release and download manifest.json - id: fetch - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_REPO: ${{ github.repository }} - PR_VERSION: ${{ steps.versions.outputs.PR_VERSION }} - run: | - set -euo pipefail - RELEASE_TAG="litclock-images-${PR_VERSION}" - echo "Looking up release: $RELEASE_TAG (repo: $GH_REPO)" - - # --repo forces base-repo lookups even when running in a fork checkout. - if ! gh release view "$RELEASE_TAG" --repo "$GH_REPO" --json tagName >/dev/null 2>&1; then - echo "::error::Release $RELEASE_TAG not found in $GH_REPO." >&2 - echo "::error::CSV change must be paired with a matching litclock-images-${PR_VERSION} release." >&2 - echo "::error::Cut the release via scripts/release_images.sh ${PR_VERSION} (or rerun corpus_edit.py ship)." >&2 - exit 1 - fi - - if ! gh release download "$RELEASE_TAG" --repo "$GH_REPO" --pattern manifest.json --dir "$RUNNER_TEMP"; then - echo "::error::Release $RELEASE_TAG has no manifest.json asset." >&2 - echo "::error::Re-cut the release with the post-#299 release_images.sh." >&2 - exit 1 - fi - echo "MANIFEST_PATH=$RUNNER_TEMP/manifest.json" >> "$GITHUB_OUTPUT" - - - name: Compare manifest.corpus_hash to PR CSV - env: - MANIFEST_PATH: ${{ steps.fetch.outputs.MANIFEST_PATH }} - run: | - set -euo pipefail - MANIFEST_HASH=$(python3 -c ' - import json, sys, os - with open(os.environ["MANIFEST_PATH"]) as f: - m = json.load(f) - h = m.get("corpus_hash") - if not h: - sys.stderr.write("manifest.json has no corpus_hash field\n") - sys.exit(1) - print(h) - ') - ACTUAL_HASH=$(python3 -c ' - import hashlib, sys - with open("image-gen/litclock_annotated.csv", "rb") as f: - print(hashlib.sha1(f.read()).hexdigest()) - ') - echo "Manifest corpus_hash: $MANIFEST_HASH" - echo "PR CSV corpus_hash: $ACTUAL_HASH" - if [ "$MANIFEST_HASH" != "$ACTUAL_HASH" ]; then - echo "::error::Corpus hash mismatch — release was built from a different CSV than this PR." >&2 - echo "::error::Re-ship via: python3 image-gen/corpus_edit.py ship \"\"" >&2 - exit 1 - fi - echo "OK — release manifest matches PR CSV." diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 5f1df4a..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Lint - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - ruff: - name: Python lint (ruff) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - uses: astral-sh/ruff-action@v3 - with: - args: check src/ image-gen/ scripts/ - - shellcheck: - name: Shell lint (shellcheck) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - name: Run shellcheck - # `find ... -print0 | xargs -0` is robust to tree shape changes — - # the prior bash glob `pi-gen/stage3/**/**/*.sh` silently broke when - # the only 3-level shell file (pi-gen/stage3/02-configure-system/ - # files/wifi-watchdog.sh) was promoted to scripts/ during M5. - # Without `nullglob` set, the unmatched glob was passed literally - # to shellcheck which failed with "openBinaryFile: does not exist". - run: | - find scripts pi-gen docs/manual -name '*.sh' -type f -print0 \ - | xargs -0 shellcheck --severity=warning - - test: - name: Unit tests (pytest) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 - with: - python-version: "3.11" - cache: pip - - name: Install dependencies - run: | - pip install -r requirements.txt -r requirements-dev.txt - - name: Run tests - run: pytest - - pip-audit: - name: Dependency audit (pip-audit) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 - with: - python-version: "3.11" - - name: Install pip-audit - run: pip install pip-audit - - name: Audit dependencies - run: pip-audit -r requirements.txt - - js-test: - # JS unit tests for src/control_server/static/js/* via vitest + jsdom - # (#338). Dev/CI only — never installed on the Pi. Runs in parallel with - # ruff/shellcheck/pytest/pip-audit and shows as a separate required-check - # on PRs. Branch protection is a separate manual repo setting; this job - # being red won't server-side block merge until that toggle is flipped. - name: Unit tests (vitest) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 - with: - node-version: "20" - cache: npm - - name: Install JS dev dependencies - run: npm ci - - name: Run JS tests - run: npm run test:js diff --git a/.gitignore b/.gitignore index fd47462..2e66753 100644 --- a/.gitignore +++ b/.gitignore @@ -1,80 +1,12 @@ -__pycache__ -*.__pycache__ -*.pyc -venv/ -screen-output* -env.sh -env.sh.bak -*.log -.vscode -*.pickle -credentials* -*.json -# #338: package.json + lockfile MUST be tracked despite the *.json catch-all -# above. Keep these `!` unignores adjacent to the rule that would otherwise -# swallow them so the relationship stays obvious to future readers. -!package.json -!package-lock.json -*.bin - -# #338: vitest + jsdom dev dependencies. Dev/CI only — never on the Pi. -node_modules/ -.vitest-cache/ - -# Secrets / API keys — never commit +# Go build and test output +/litclock +*.test +*.out +coverage.txt + +# Editors and local environment +.DS_Store +.idea/ +.vscode/ .env .env.* -*.env -*.pem -*.key -*_api_key* -anthropic_key* -test.svg -.certs/ -.pip-packages-hash - -# Claude Code - track project config and hooks, but not local/personal settings -!.claude/settings.json -!.claude/hooks/ -.claude/settings.local.json - -# image-gen backup/intermediate files -image-gen/*.backup* -image-gen/*.pre* -image-gen/coverage_report.txt -image-gen/literaryclock_*.csv -image-gen/nsfw_flagged_for_review.csv -image-gen/parser_failures.txt -image-gen/downloads/ - -# #192 audit run artifacts — keep the tooling + gold set, drop per-run outputs -image-gen/audit_fails.csv -image-gen/audit_all.csv -image-gen/audit_fails.meta.json -image-gen/audit_fails.progress.jsonl -image-gen/audit_fails.batch_id -image-gen/gold_results.csv -image-gen/gold_results.progress.jsonl -image-gen/pilot*.csv -image-gen/pilot*.progress.jsonl - -# pi-gen build artifacts -pi-gen/work/ - -# Internal agent-planning scaffolding, browse logs, deploy reports — never commit -.gstack/ - -# Quote images live in GitHub Releases (issue #82). The .installed-version marker -# inside images/ tracks which release is extracted on disk — keep that tracked. -/images/ -!images/.installed-version - -# Scratch dirs and locks used by scripts/download_images.sh (transient). -.litclock-images-staging.* -.litclock-images.lock -.claude/scheduled_tasks.lock - -# Internal planning docs — maintainer-local, excluded from the public repo (#82 PII decision 2026-07-12) -DESIGN.md -PRD-LitClock-Control-PWA.md -PLAN-LitClock-Control-PWA.md diff --git a/.gitleaks.toml b/.gitleaks.toml deleted file mode 100644 index 7f647af..0000000 --- a/.gitleaks.toml +++ /dev/null @@ -1,11 +0,0 @@ -# Allowlist for gitleaks: these test suites contain DELIBERATE fake secrets -# (dummy ghp_/ghs_/sk_live_ tokens, a dummy OpenSSH key, PSK=hunter2foobar) -# used as fixtures to prove the diagnostics redaction strips secret shapes. -# Scoped by path only — a real secret anywhere else still fails the scan. -[allowlist] -description = "Deliberate fake secrets in redaction/no-secrets test fixtures" -paths = [ - '''tests/test_redaction\.py''', - '''tests/test_diagnostics_no_secrets\.py''', - '''tests/test_control_server_diagnostics\.py''', -] diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 8f916ac..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "lib/e-Paper"] - path = lib/e-Paper - url = https://github.com/waveshare/e-Paper.git - ignore = dirty diff --git a/.images-version b/.images-version deleted file mode 100644 index 02a819f..0000000 --- a/.images-version +++ /dev/null @@ -1 +0,0 @@ -v7 diff --git a/3d-models/README.md b/3d-models/README.md deleted file mode 100644 index 943da6c..0000000 --- a/3d-models/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# LitClock case — 3D print files - -Print-ready STLs for the LitClock case. The design is Arthur Gassner's -[Time Teller](https://timeteller.arthurgassner.com) case (v3), lightly modified -for LitClock. Licensed **CC BY** — see [NOTICE.md](../NOTICE.md) for full -attribution. - -| File | vs. Time Teller v3 | -|------|--------------------| -| `top-front.stl` | unmodified (included so you can print everything from here) | -| `top-back-with-notch.stl` | modified — adds a notch | -| `bottom-with-notch.stl` | modified — adds a notch | - -Print all three in PLA. For threaded inserts, the USB-C adapter mount, assembly -steps, and the full parts list, see -[Hardware Assembly](../docs/hardware-assembly.md). - -The original, unmodified design (STL + editable SolveSpace source) is published -by the author on -[GitHub](https://github.com/arthurgassner/timeteller/tree/main/3d-models), -[Printables](https://www.printables.com/model/1398618-timeteller-a-literature-clock), -[Thingiverse](https://www.thingiverse.com/thing:7130877), and -[MakerWorld](https://makerworld.com/en/models/1744549-timeteller-telling-the-time-through-quotes). diff --git a/3d-models/bottom-with-notch.stl b/3d-models/bottom-with-notch.stl deleted file mode 100644 index 6a82f69c14ce1bcd3f9082a0cf2133b73c3795a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 219984 zcmb@PdAwHB_s6#+m6WKIC}|)=)NMlD=Q&S?%p`s3YaSycks-rLrZgkT)Lbf!_udB8 zy|)2Haic+#B&0#EAu{$`Ywfe1^V#P-&;9!2cV9n!&+pmmz1OhU9?m|KG5^1R&8izS zd&qr7=Nva9muo)fxYLVg4=F9i<9eevCygm?VodTx{$2n4B0fUmqY*n3Oi!-Dnm!k(C-$pAIFc~llDCC>2Y4>=(6V;A1n30qRYhCOCJ$SO5R_aM18IwSW-W+ zR)`Ou_${K(GV$o5w_`^y5xQm3oH9H|sjlZ13$bJE{4xlX%Drxp5T{L86d|ysUMP37 zNR9mL?J}|iGb6v=av`o9I3tOzY5mr`LIQOl?Gk7e=G8qvRZVjHkKgw~8Gc;9tC~nP zt~W4=`dl4oFIs|a;}Td`UVe#q?q4)Mne6nT9OqAdCt?*6D24i59q2iF+v?sU#iv;p z4v6SL0;Mntt`4*crO+p1Zhojwf;j(`&52yJ-5!?|v$w7vHxaS0U;F%7L^N1CPhv82 z_|hOgmKZU7mi+!s%~j;b4MhBX?XxXcrgd1Urq|98DPu~KapJqK^Kw=Zfh~nrVPCO+ zjM{%n{_8|wpFk<3tyN>z^lG^-;s^GkI8ouGHZ4Di=s+o?jk%FL|MHDNIqyaMz=$Gk z32w(iBCy29s-&1>g>f_H@cmaw3vL@wC4ud1k4uWVf1Lg7R}$&Iy~a^{*grd7VZW<1 zu%Tq%%Tu38Vph5Y<`33utu2*|$xfh!NE>q~`Qg1-qt#CG1M5O5^vRgRtmkHXgg_~b zp;+bXc!ec@KX(dwUQ&Eh?TtZnZKPd4kU$;B-rPj!8rLpKW+y8Cm!n%!eB_j6$v+#_ zk>hWBtSp1TdXdhq1M5YfTta6{o(Hkt;hdPAzzoO!pPfJ{)aMd7*P7Kmw)EKUW88N2^|&w^(8|{RoaOk8sQ_+ngvwDSK>tkxN)D zHdd$urLb+Ryn2o$NMIC%@RdNRDT7yt?LS}jQ!+*nX05^!BrtBq@I1Jp_8l!}ljo=d zJxAKkQQaR(w0fBctP7=(Hs;`9gex6Tr3eXoTvE*K?6#n`2hAUj4y+exW2%vk>dUWc z`9)fXtrxT1)e+1%=F5ykNip{iq}>)oOK^;Gt+I@54=iaq;WQaJe;vCusbetfb9JEh z<#|k$7N2&0O>cg)xzhd@)=xwTESb0U0wL~x@F>qexKxOR51d8Bux{-pP?jX*BSgJFHE*fu6w(DlPXnaM2HsxtH$+w<24nh|4lX@aC&l2^MjfkxOss? z8#6K>o?h4Q7$kU>Ik8u1#fZz z%o1$>>|;AhVP<3}P-@(tr%GPUT--c9fB3=ZKTcm0hUguq#>?UXQ^^fZxA<$m5>fh-=slwLlq<`Ey>c0^kXcbENubv|o_Wb_h z2!ZyZA6*`+EQB%lMF`Y^^&)Lo`rPgx(V80TVySPIY%k_m@$Ywe_DAZu&&1sxogkk|iYvF7niTPj z1doF(#p5on;jGUMja$(_$%BF7yHCq}G7;>ZW? z6{*2{Ur!>PUB|9I9fjC^``by3n@eB`64|4RR-x@kr^hRwcRw(qIKR5l?MiFWFW#uo zvi+7s?ice<65A`MRwYpiX=9G3c3^LoQoEoI%q5q=eFn6bkG~#xR1=Ps>-jH1A2g&3 ziKypF+cWxmw*A?h&*;$-tQYkeUV|pOHLNq6t;2e;ow@zTJ#l&yZhvDwr>MSt(X08q zD`nfmX5aD6Z=<-C7Iz(UXR`A80TM$xzaioSA~;q&DQ%~_QOh9+q;}LL!@|g zR+{y(EdSY26zftcwnUGOc{ORpl?Y32Odd9Jr0B@Gad?`D`=JDvWFpGeQRRWrqJuL7 zX|XEphw9+ETD$Zpv^c-g& z^vTtM1kO=Ny9CAx>_6ZYN@REBydDE z<~kzS^YWwvOY{ta$6DeX+}83S z%EVEWPbZdtE}yO+1f!@OEhk*pSMv2-b97|fWgm3BLLFG|-TUoXaf4e~hyAmo50)UI ztHJpd%S8zE9QDoJ^St=+={%l4PzOq(z8K-FAE*PRa13^JV7+L2xGt?7u?nSX?CB@A z&&WO4ixFw7PzOq(g|1atZ_PUWMMu>|8FUb4+at47`M6bSBDAEur{@DVJYLSz^MuRy z9USo-36u(dkH!i|XPou3kJLCiqf}wJ2PBVI-gr#JDkM+}M{S9&udzZ)kU)Jxq+^9r zIJU=hqzTl4eGqFhW*_z1>jqrjibgu8*V>5kS#fO->Fida)LBP7E3taLOT9>}P&-PY zPjo*VSygGo;@DV%`HB|0R$*ORn)DV+PWkX0&z%Q(FJ{Bg+ycJa0;Qh&zMt%v@7d-> zi1l_9^^eLx@LH4!q+P2pK4?2e!6mQ+iBs$H`FUw^=Qh!7N2%)R#UWDJ|~*Oj~c|;!$$^&{=#}w9W5#Z>p5IFZmT+|KJ`Bzu<;jnU)E?{y zw@26pR!Rw+ON?o>HBq>DL&t&+0ija0=DbPqDJh&*LBZ=JtqHr;FB?CG{;tM~cf61`W=wER z*mSJNDnDitd_ep;1X(8g@t$4#S$c3&(*5y2MrjBcR-*N z(#E_T_;JO6Zyh4cnDR2h>DU!|UbE-Z4$=F83CXrE^lEZj&kC}7)e*z?s6My%$)o<^ zj8|wC>NCNgoel6RGG3|HT?%_m*dms|5+v~2i>#Ikk9v7Bua=yv8MVKARc3(Nf8CnI zD;us=Y%k9le4QitpZDeXz3CSuk#-3z+3@Q$Ax_-hJvpVbJ%0E7yOZ|Lx%-|im(ODf z5-1h3%GVXLwTfr@8VWf?@Bv`{;L+ybj zNT}yF6KU6=h&9o!K?0@LT$Ly7(dWne)3J)@E?R|tU`@tsBpo9TTv$Lm!j6u{Kd_F{ z;;-NBmHcMjC!){Z&p_+;qPJF^pT{#N)`e1Np~Q;*v2BhXqLls~Z*!EYf1mZ7=ktF@ zlnz=Xw)5R>NQ+gzT7@M@d{DbU=)1n3k`#jev3_6)5|w^lAjGlPMxT$u5+t75XDvMR zl<2cdNTAeX-xY}tyOJ40Gv>-;D(3O%gID`7!*QlI=8s?v$GdA$DemXI_Hl`d18pY$ z{M%It>_yR$LDwk8@T~mz*+0(aSsJCV)i~;7t354O_-Pd$WPAB^UX>JcHelPh1X&~f zgRD!S6t;~^@XTMc;~9Cp`a>y~ZQ;U^P{nthp{$LG%2Q^(!NnNeCi`=-8;Ud!!q>uVG8xjj&7bqDU- zrN!-64~P)xhwa;?^32EWwLTM~Lg&HBTB}#fanDPKBkR{zHOXBrf8b=A&bL{6R zg?+^(umlP0D=vX0NIYHV8}h?GJ(Qh5DYp+=#Z#iHYv;|r6n4lF_9;sJ|= znDXn=2!SO?R9?SS2zzfWT7^;t_peGzWhbz0vCS{J{wtzOi(B`7o2>eY`rzV=Z<-Hwzx_@pAn-J*6R|K<OeX>aZ|_d#gc8$kIJsYx8B<4pqg&8 zw(q?1o$Q1`578>5U8_(B64%~m<2EU8deSAZ1c^P*TH?T3COT3Jj(+*hR?kukVyrMe z*qX*nqP%+LgT4iQD6g3P>CCI1%IP6}4F~nq+Nop#1)-&%f^C6wEu$yN>yIJn{tfT z^L*DmpFgo}{QMviD1|<;EdSXN4oi^0amC6r!W8b};LZ)saB{UztfH%Zxj2gA_~_2H z?D^PAN9NZj0;O<#jqh~Ipu=93Lwy=68(m$I@s$|dBhxdP_*EG!K|)t* z@hfrRnOAM+YLBkYI0~*+r~~U&qM+tK6kYpg{m3Ns;%$GV*X?lvbzr@E9JdPFz)IN` zblam&urB$IdM`?0z2l2!N}C^h9DBZ&R!e7g9?`M}j}TCT#2Z&E6r$7Rd>KYAx?FDYK+{qDp^X^vYE+@KF4p=)2>mV?DH>$%Oo^EynFb{_qDu7|X16<#+$eXfq5 zXoSAI+(r4(YZG{N0k2cIR-twzTpiXf>p2p)B&{Di$Pc7lKafDrk#-4;72D2zg0Hb4 zot+py%8s>7o9B8+yTn1SSgHB1<$9}|*?qCgu4?HG{cEb|+g2@?`e2Hp`(f#pR(!t< zdWg2W(cM7=Y4W)}&|b8}C3>CZ<>s&HJ%8Noae|)=V$V^Z>j!%K;u!1G?`PzC=$}iV zB}iu{umt1t{0^@5e6=o=Z)8e&ZhAbt)|`S=-EeXqP?ixvrT*yE@Pk)PcH% zNbA6Q(GsLx9hg^`OPI&54$LcTXQW*nm>Jm4*#6lGl)}E^T7@l$nSr#c1EsK?*KXh) z$kO7<_1j0X1bZi1f;G81Pzo){PGAXI@=0fF)lp+2d1X)v+uSAk(|lg8&+s{XM<9+@ z*n+M0txVqsBG>5Ux-*ZvNT8HE?pojNc!d$gJdWiqL6$&2kj_qEw!b`qcY#ZbpC8uF z!zhSNzK%ZF2QN6wt|q(oYv=Sw>xEi{I*>qpE`ffa?XFe7(OFUDKXTe!LD9uFz#PL! z(_NEm6NSf)cza$U5h#Tj;}X^`8(q|nwjXl1joZ9ala%c($f|E%I=MC9n~L>fE@jt& z^AN1r)(G%C4*^-}p!SfH4LfV+%x`EF-^+@QhJfE@PS%%zOD|-NZZ!MM}f#)K| z)FW$h=bxYd0TEbR{O1)pE_ZMY+8cOc1epLQUNutfJ3`2GUjf3Wu&(DPA6?S46; zCGDOEXOQ+RUUwt;Ovs(pc)y)>RLw}(Cx(zFS^2a51GVd3Mwd#Plf_+@i61S#*pjs3 zn!Am!usgrMF8b7wLfUG`MBpxPOb21sDwM*#p6mp+2S!?VkmB!?K`D$vb^@i)lI#Rp zg;IFN>gdE6%6E!;a4jb=X`&3z2qpUA-rC7OVVVWbo0FZ*E;KmbAG!=^<^*BkL1| zPnP>*?r56Ta3;gq(w)_;@3w!S6xx3B;qQySOV0Eno}*Op9ahRLNO;)^EJ5P$fp%{H z?z*TBlv=XRNX5rO>K>M_>yg?db5a zZIM9Tw*7Sez?fjYbCNb=s`Ln=>nnlvq7>4ORR#%;D6hi#zHp?CsZQq~2lts;(2d#y z+W;+e+v9XZtJ=EvwK&#LaY+08;9cam8UDk=dVZWYJhE*tq7K)CvALwnR6E{e` zjTfI;hUe%Z){8#5eqad_*qSbZZH{zy0^@^z^!xpD@#*pR&WvOU5-5dnbFIRRLrby~ zD21NKh;)0P?d5ygy!!8z=Xz+NSfzx)k{_B`Vp98ay{tMM0(Bs5S9`39Rx}B0kH=ow zEBhDxJ}S4@E}`{dzlB@pG>8xwH>Bwthpu2P9ow|cH*{x>BLx}m5?e_}>0M70(0Va} zwxg8m$C*S_?Y3=hA0jX&D222<&nfHO=V(t{&olU56+BOHuf7J?oqc|@1D>&<6w+2l z?yOCDy(5I`Nh?77;tbOy}raWswCcy54FNXyfiMLW-{p2PQ9;=O8m2FZ6D z>Uk~S8z*mnDw=ZUNwNFLR0rP`hBV)&pZR@#_>R6m#+~ftp898#Ru3Q3KD^8Os9jzi ze69eckT&Mc;128`n>4A5_v54O|9tpP^3wl2(WLGLZ;>BLe0hA`x+sOVyTlcD+BX$` zedh~rqE&iVImZeuG^TZ6)$#!cABR$U&o)QYB{)kCuX@{Tev<%R)yFKsNEE9;;Xwuejb6C<13j>zGd@l}1jS6*9__kD0b$mT9T1wr>fI=dgL zgZE@Ky0%C0eaO^5^7!dx+{Hm1NQduFe5&@S*U>7xvmfa&6MMa#dL2uU(1`Hc7enH` zP81lp1Q@PhK>CSQCDqrAz2LGfSIS zd|e414N!{DSWjv>BDf<(i3Q(WK(Pw%RY4st!DBiNa6XOk34KQ&ziSYmujFICx@g~q z7!W2Cp;BBEA9GFNyTUTP+mP3yEaejX#w@Z*=0U!;&2{NBoT>vYWLf^RD`cz-ZP%LO z?`Vv^Gf{2-cLYkQZu`bX$`bK|=cr6NSc(abf+ZljMOcFMs^=a1N8g!=1WLJYT#V{q zZ&|y>it9z%#x@#VCeU-FT>`aZz1KFpRy=P}elTST=M!^eT@gxQy$!CpONf%O(KkmT zfl{sxs@_*W)OLK*mor24#ozsj1YRG~-{X0ub#X*dN{=g78$zD1BG1P^QESdIf#=*F zN^oYd6#B$b;y;`1=pjn&`{GWC)gf1NbnTxdumlOVO7$5NJRxo932A8$w2F_}DwklB z)GGCmW1_KIwQ^`W3f6Oa-v{3T!SDYl!n%}Zsqd@3B35d)mTS<#XTr;QOEwH_Gi zAgLGgC z5{K=#_ksL6ZD=G*kU*&icUt1dT}cDzktGcdE6b?M>i#EFh&shgYwCi$fNe)>ucCkHvMyz_XQMm-(=LynUEvj% z_~|iATuKDe#{8a}EKKx2`rT>wyTpn?mhiU^@sKvA9JNQ`3n#SUY{w|JKIcPegKthB z;@x(J-MOvOo_o}lmA$s9t@Ke;_lKhM&uh^D8>2c1)R`DK?btlicnQ0kRH z-!>s$IryZ)L(ccx-+ahpjt-PU3ynF3tZIDQsC+&HLqEnHZEZg*cZi2kFy@zOxrJ|@ z*zxQKpK*yU$6DebB9J!bv0v$$@RiTaZ`|7@Fg{4TnW$D_?($r+;k_-gUTOd6T^V|m zW;;q@=DRwygzLrgWu%Syme$nF@!N-@=jYt-cqT(SeFp^JdFDh{^>LeH`@2@5cCDA^ zE2L%q5UY6pz`9V1X||AQR|gU(HL&;g(JG|r`C9n)8NLHR z?<(hSW$5vz>z+y>;nREd!?Qb;GG;kRrT>yhq<`gu^cT69z<0-sl&>QkzAt?8Nn_+~ z+&kWVC#CxA*F4Y2R>^pI_MueilPpD6FBN~36#S3dS@q43Ri$Gp-;v0`}AFV ziyK)BmZk8#jh@>M&Z91F*2OuGqP9sBc)nxI0|7Dr#PY|uB_ZLS6>*?~SotqJUpIIU zc}eo^zwP(cUOQz%>VnBf$?tDm+`{H=n!pky8vRgFh)>=d5XpAb(c_gWw%)r3cu0ru zXXsz8?+Dl4&=2>FUZXp{`GQ)ww}7#7cP5?>V)dU3AFt!q73y$nvaMwE3fmdirEZ%e zfl|0G%}$^ct{JlvD21!Z>;#YPx(dUU#g5hX>vf0aPDuB8>p4o{Iw3}+vjo>7D1|Ey z8!7FzST9N;?GmU1R~F5_ER=Djefy{%xSGKEH9LXzBAuPUdT~as^Pv4|&pBrYR;6Qw zGcrnLpPjK@ltS8#6|O&UUGmQT%OqBRwT)VZ>m8Ir+SP%6pgyEs0`nEmQnKeRO5s^b zb^@jFoFhAdQrK5qLU*IC?fYeIcMK2565L^ynGrI$6<7;~@wji!JM$WKp{;{?>L6$%%T#IBUP-^BxI|IyHIlzk%zRox}PoUJt zvn@UJqv-sB^`aEgZeHOm^VhO7WX3u3_va!r4#rCFr{=dq;1lHTb9dPYl)|UXU8}GJ z34F@jC9nhud}iDwuw=zU)x`6szdA8^63ISs>=Js{3%|1h34D^-C9nhud|uThumlNw zp4TPtsZV@b_TLdGm1y&e%y#*;#wWAuK&hJfKMCazCAd063tb&pf&^OV5}1jYyJ(?Hpj9Y^`dk8~ z&NjzOwm;k8!=y{_)ePc%e%ci8V#A1L_XF>(Ln&8>wabp8NT3wnec}3nB}m}C6fS`! zNZ{3em%x(6pH`As9duet56`Jx0!xt4bI$l4GU`C9kQN=jW+Ikcv*LjCySckBmvdY_ zyU$M06R6_HNhO^;q+LJIlC!TlR)|jhDtM1gJX(%F>)$+b#)4M8wA+rNxmC~hPN-E* z2(SCKWe}rYu;)$>c52}vo!u%VP@KrhY zY)Ri>)^gI?ktl`dZ7wk)SjlW$RpoWO!i!Sb&%Cgo<5<$=$0pJqH~(HU>3WU?O5rHs z5-2tNggd3)H=Dej%uYCY1)~qxmr`(GRrhf=>?-KjK%HkU%M= z?G;7R;pvS%Xe(Eed}YoSLKX|{6-N@)z!nMhi!4kXYLv@kF#1A+A-oz7jK z_r#-?aC@*NNQW($DZ1JQ7-<_Q^#k)N+BVu(9Ky$W3BAQ!a6LyID24i59Vms~#t6b} zd!SW#Hi;IxRw04soJeOU@EjLum%ter&r;pl*)rA-oLO-GxOwGr>A{tko{yo{5lTTtAxF#gI3|S30Detqqt_Z3in=dFEsmZE$*bD6#8T}s1Dpo#hp#m=MuOB ziMx(Sy9Dl$;eHa*LimAIKK5<29cfnw5_ru6X=A1Y*`A7IJAElK-9H8|-EdB1ZD%abByq{&5=MU)aUBJ zdNDJQb_vW3y}CI5qzS2g*Zd?q^ycJAc4Q#T#tI3Py0GA2S+^H%e>*~8z3oSxEmAMe z9AAbGtasA+{7t>m;stFcrn2imt57Pt=U9To+e>SSz6yUwJx2ni_T5@ph*SQW9Pu1W zkXSj<5{LcH#2~t(v0Jk3yz(0oxmcq2CoY{kDHVPxdP*5Okah`vF9WyyPFjUyNmPej zU$YJ%;9J;J#1fU#dn4O-d?#WR#tNhGR^2U`*+P^q~QajgAJ$pjEv)R2AZ&b#qeo+n}YzuYWi%Vigi7g>iFR5KEB2xVZ$D zAhBgYvBc`t5!@d9{w@yhIW+p9lszsH+g*Z3pQmo@*Xk=Gump+hEr?bj?Gkz?IiICs zi9P0baV|*xxN4U8(IJPkIm?bvSt`4kMzp(q&;yEc-N7di$GY!|gP$hv< z`t8&>!B)Na=bToVttu6(kfyuTNk{QHRp+q|^aG{PC-KAA9)%IrFCd-GQK078#;0TB9kJ;l|g#=2ud1VaH)vs=)^qNBlqnrFyrxRK~~K zo+&1x3-#I^AMY-BneKnZ7DUf6Zgd|qk5?Vrw*HIyITGkO(#G6I#BskaZ*@g_EV2C= zJx5wR_ubqrE#@fJPuTn6zJF*@%8k_tK|e31eK@Sw9+$*uXY_IWKmu!W+XGh?Xvy2v zwo7}oY*!R%4re1DY#6uPM``4w#YgQMfFk@Ul&~}V2TIdoe zb;`qsijL&^!jwz!^QhA+Y|H0;QH&@OSd%e7((}P@Z#r+zVf4H(65m#}^ZAn}7NwA; z`z%RE?>}#Db52AD#s_Kpd>4DeZ*D;yI*M{ck#;>t56?a1aPfRcGa~+73+}S(Xx7o^ z&Bj7Eo>^84q7=4fcB?QxuIHAqVA)RCRQ93B2(O5!v3{Nfaj zU9MF~V0;Qj*z@SI4S9sKf7WvxuW(+%TyP02(Juw>E?y91Nl9_M58}9sW37IV>7&z*XPMB%45%NpBJ>d7~56XK4G%OX%J{ALFe@Am1}fX3a#@QJfe!2KkCTKdaV zwlqvsTa=3JEm6R1ba7___cCyo!WeqOLe{&7(z954p2=tNe9Y(0e2n`6ZhPQv#HCNw z5pTciZ)qOwn9fe1K34~SPv-7(zRvrZ)}pvmfKq6os{<{$vZ$k2^}-wD#iz_xp(T}C zwi064-U+Fl*B>v(Uv^eD81iK_A-`72$ah1 z2U>-GpoOmIXbDQOKK9&N^(R@w++Q@tV~IZP*1p~N6k14oq}C6A8Ih<1X_v5e*-S(a zQA+jE-Y&%|x{HH5H+;-HHyRPUi*rQ~-A8Etz!KHRQYZa-l6bzP`m{(UB7ssTm2V)# zzL_xs_Ff$NUlCZZd$#=#wZq0iXUzRA@I$@T=ytk!dKrFTz37Ll1KR_oP=`xE?n-pg zDn4ebkhU6h^x+*Zmg2V6_TawawmDjb1WG9#-?_sQBs9m^LSx z8*ofeN{{WxfU!bL&=1t-`hg`#6igZna&l(I z2@0_t+p$mFHn_c9MZV>m$5R+LW6t_Akvp#HmbtCSbBqs0RQ1_dVVk2A*1Pt^3DO>G z*Ku@r^K;w9FV5keFCpjF&9R)e+&YFDeco%MH1l;wx^ZKdN@q3t)Uw(p1Qlq$;) z)y}qKy?PH@$IJNY60J*$*c)D#U`+H*9v&r-c0ES|tfN8s64uB*brosyNEhfhz{x#2rG zo-~fTF|E!jOoyk2=%!-@Ja)&Mf54b!}~^OXWS94dy3s*S#f7UYV74r#Hyoa6-4?6 z5;cb9h}86I1u3LmtB}C!A4m(~YYUTkYd27YIFRcv(7{wz^NG+O;c4p*~j! zTGhDo{X!gQTaZFJJAvLJ?Gl)~*e7(~hp&aW1ePH2`SquZg|8mV#DZW=-GtVnNcb~5 zl2l3Yz!3%MJ(;);tQY&8tAp(FHA}EPZvW~`vE=G+%IdXP@4|e0Mt{kl1*w=0RwFY4 z5~u@dV@8wb@0{^b>oyV3v0mE;ONzM%OLWtI*jSYmb4+ep)>LBk(A)0@SFr5$FCo%a zA%W55@4IuP&zKXvii`wGp*~j!UQt4+FXo>lIvT8bHZIhkAo{BU*M^9f|toG38Ij_s?OJ%nT>qV)5r(@^ymxzw9yU$CZ zJ~viq)hl1$D8wlh=B1F%PVjp&*^ixtb5lsW1lo?4xPAn?oMtJtz0)4h2K@U!=hSFq z$K4y~JdiZ`t7xAZ36#PS)!vuUbnEl^yXb5cOOU`3)tF1H4%3C+8HOc1ijKdf0^R$% zz!_1)YpMJ_cN}YxaL3)wq~o(Twen}vb!RL=0V`ximLP#wXp9+0 zQGNBv4LPl;M`7Dyt8r^S)vQS)ZcStO`|YcGOw6P2xhF8&vDLUW7w)Rxi1V1<0!1SN zx5uG0Vn?Mk+g;*gyMJX?{CZUaGXpJj37c7V&48;{+(X2btTF4zns!SsZQGsBilTcm zeD1{OZAi;`6#Kzv!ZCu+qnO~m8l}Z1Usp0%7fQKS2@|Y+umlNQUmH`G?)uF8mPM#pxnE42LM%{TP>!5FJ$w*D# z+)6}e0QKCR4Srv1^D2+>3R@7{#wE_88IxV7w`?X*3Vq_^8_sXi$i`|teT$Br$xmlH z-5bSi3-NJ@Io1!;l=k6tg&f|uV(&=6J4cLpg@_LIdKKiR2`h#7Ot^#%Z!qJ)ebabn z1>U1!OhcM+-h1Z7*4t?Q(DOmI3JKIlGbi22xS_=jb0p=YUlcN?y1+?5Qr z2SyjAnoc-PderHEMDMdi0;TSFvY8M+9v8i%5ebyi6=%r>Wf3T)>-h(|@m()R4Xu*+ z_@yOnFWTp3iO%hOZ%p=B;hio>+}7Q`*ZG7J*1^zI$3>)6X~5vf`;i&IEv*HO8ez2bV<=He9Imy?}9OOU4b?*{WA-B(?N zR@vi{Vva6aNbl1R)}nN_?GW);S=zM<+qu$qzE8iTI5BQv3Tc->sn<_FSBR!pEDH9J zGwDDnT`doLBm?1VuSFf`d3LMt{wq6D^NM3}O5Z?L_8nU&^-gF0#v#3V_)5-__B5t9 zSd%JvZEKV1J~yixKhE<*&C&(m4*6Ac6hSIae#h5^kH32lh7Po{H^c%zk^u zVhmlK!4f1ehQ{#SBBkHYX~B1kUdiQ?2ck=Y9(dc3c61s{y z+KUp{=BN)X6wiI_=SZLw`sc<9>qQHZb_tY1+ii4drABSz5-3$;51%iW6tBCZFue*- z6TbR^I>6&`V6NTwj|KF3NEr^7! zyx+OZrCl8upPF_0ORUB|lp$7x*Tbm*R@O% zfh9;_L}l55h3z!F@6I zTzBiZ|A+f<2hmyGp1oJ({Y)h8+@TceGv?_>?Y++9>0W1i!X7QD_}_-f&tIzAXv~Ft zJ*wv;mY7WhmLTEkSQO~!Km=NXezfa#YqIShr#AYmIiICApez}Y_d!efRwL{VaHRFD z5ou#;(+=uCw|~fQu20}uBcIV*cRRb;xbnsM~dH)g_Nv z&u2`UeF92hy)F^7c^<)Tt|k{%BS7E+n)arCdL5rEeB{e_Ysh47DH=TCd)>#(TX;+eqnLi+*6e=#xue zd{D}bRoVNyc7NXzneJz>?ZGRrf|P#mu-AsUl2^KmCsz6DhtB%E7mB-_x~h@gNJi2G zmLQ?Cn%z%C0gfFxhhXqOMKUh5;hZQ z_oHawp1!>M;Slzn!uCE8q^(VA71o7P`aAn%olpX$_)V4E27G1g-w~yQ774MqbY2Q+ zR|l3L@j>kZq1R83UfaeJBr5&P-?1qvUUF6RN;OJ7wa;2L^T_$B7~!kuD8+Bw}~r6`?=CB>W#*fuUf-;@<%8?DGt3fsmd4$wS4 z|A)hK_7Q|c1Y1>(_5z|(+8!G}y6Eq+Y8(i_W$~eYB>g zIf`wom~oCOm3tL&z7jvuR@pJit`pEgeQSRD?oU6HA831wNbA57B)Sf?p7UEnT&r*% zMB8y*aS1Fz0_PQ%z!D^I_HhX;L87=zF||!e@f90`dC=Ebp_G2VxY}3qQ~!>*c)((* zw|nB<6w6g2=o)RUn1PRqWoSym? z9lpj2vs&LU&wgOWxCE9U;pSB_&75Z*{C=Af+GDb@vV8)jFbeWs1L;xx9a|(&3ZrgJ zgt2`^F@s!!IJtq+J}e9b@GZwngk%g6)A)NE>qz&B{#mr0eLIyO@3MeBOzO zCFSSkZ=|zTl)~)0v+>g8xO4Y4T=^dF;?UDtMa#Z@u;o{LKis{xA8>|iJ9F-`wda*D zZD{j~_A@)=*ViCg+9A-TH^qloqX(febWudcd?_jmcs>w|?`=F*v05lZPe&)2iHr^Vj^p#)o^ zUmZX8duhFB_nnaTn}M{sA2Pj}W7MKfG?pfG#r{*qu_!fzVK5$Y?i085a-mNjOZJ)`#*{u+Ldo->1^Q0v!)a%B`FV$2T8z;*k& zx2#L&2-t#1U^}H}1E0>kim-%RaHzLNt_fpn()m*`8~jKFYDXQY+igMXd8yoAFuKks z(u>Z$q$q_~PqGu(?~tauaVff2AACmJ^B}t1w%G5GmU};V-1Yh156ldm`?#kf?OKI( zp_FSC*RLZSW;p7=JQf{k0^_dpAm96gH3>og*qIXvoqc#Mg0xFuT_}YXx&+#bQW(Q@ zUdh{&Fg{3FefDi#u8!eHeJh#q;P!>7zv_P{$N9S#27UnHtLJ|oV$X^Wec2_jCS&+J z!rN;MZFM2-v><_=5B_+k=o>hN*Kl_eQFHLs1WqodeeS2u3nJF zaV1J= z5=T#U36z?0$ot7;y8{i6yNHS2!Bv@?NC(!7mLN@M@;6x>A5NKl0+#UChsF&2Ue?jB zj#~m9Ye@&zi=HD*(G7?jBLqsJZo0uKAhr^LZHsMw%G-x~Eti-^U$o*g;j@D*sX4*D zGK$wW@A>Cq@4_J`HyTzhpNLd&&1(_m6-x1HS+Cc+#Gt^c&14l?g01OVH8UU*qywce zqAqcCKs-e{Pzqz^62VpEmi+y5wq39N<4OisQMT7s*>DK=+9F)zAi@6WswUlQXY+kz zxGq5{Y=2`G1%A9je&BULygrE6BaOKrXu-KbbXEH`AFNLv`qP?*)5PTm1 z#s{U4rt6e}RlgB|+R-YEA-%cfaoayGrM$utB=9P!F?9kRHL1<9=^gsfwLTgm`mkia*rxCE9U@nf$Fl27&$h zr=2$`6(bmul^^OrDSfwyeb);sQ6ke@Rai>D?PT9nSSL91`j}QSSQkq1?|k*d>NtXk zPg6Ezyw?l#aJ z;S63M#4B-lg-)!Zf9wc{1ZJh1iI#~ID24i59eBkAr7&(n_-Yl#9i>p8OJE5SsLv(r z*)M1Kg48|7?3JB@&lfF7xw{6p(&&?O>npA9rO`*v-1%zH{%`GG#!Hp>y1{Ou^?DS& zQ&z^SL+IK>T*~Tm39`#q&oQr7L6)FT;s-;b2cwHpNDGnHfke}F-6RtmovC|EnZBEW z`z073jGLlxdcB+#Y*U!R|czWJEMiFLs~I`caopdY*#Q!ET^6t-swpDS3y7L^A9|0#9a#P z^TzBA&RBAYKq)-Ob%`2*4qhkXIS!uJxaWhz1L89xuwImMbyN%pL+f2Uqr)>6)Mre; zfEW|_p?mB|U`-NTpPC~Hc55hdbjjHvn3Oc|8a2& z*Ii;&n!pkybXK#gSJZ(eNMIC12SYMmX&>ZK6yt-m5Pl{C|l2*DACGM;xSGtI!XW!i;eVj5|gYBkdAsFX})Gm9Qfm_Q4@bYNYJy zMf)Jj@@S{6$4A(U;=Pu?=(#K-!K~KHQh00$mf%0X2zBh5*3D~r?eSFSveauMS9_i2 z`GF4p0$7LRX2_c(EMdPV3dhNi=9;q*D22z2r2ky2LLJLep$_&VvyNrKcUZ0G|Atr& z_2O|DE87S0+wEZs(syApe8-)g@r#hKewY)v1Zn>N`OoUWZ`R@$Z@=04p^QYeyFZf@ z@ic+jAAk2-k@~K~{shu4fjY3>I|hF&^np>IMsy&7QfDmvM2Mcdcb7q+)W7=|>cD#WnChfaI?_MVIxtqK z4dXL{%TsY0u5`w9-fIe5_U}Vf!Fm?c-Rb36$bvmU0P}##pH(oFz)5J}a&S zW(E?hLu(Qutpn@IM8wAut&2;LP~Ah0Y-szruU4Ul+BPhOO(qC zXhhKxm$1Iu83(nalrwe(!LkH?n;gfe4Fk4I53ZUwHZsB?fl~No^FT!~58@RQl-iMR zuh3kVh+h9d0;TYJi>m{#d7xDG>mPWH1*Py>h^qt7CQ$0OJB_RWo7S!;YdE2N%9KQ~ zPT(Jscs&Bf`6L(WpUwt$MHa4iGRy|PWmJa{jt`H>Y#lH>o#9L-&v4eCF$Q`&je z$~TfYGYG+uoU!0+fKuF7c)wHDC;%*cs$o0rLzI&H2vF#kI>{ek(CW0)A zj3wv?+Kv{AAM}s)9Ie9k$moZk$q$Y$`+*kH*#*r4tceM%i!EW#xdmOq+GVZMF)G^T z8WEBu&$k3wQjM;IXqNEEsWcy}e>Lhgi;O-vk|CX)h}O$~uqA4h)e*IdrPLBWW~uBt z&?-j?R)7*IA6rl@(TKh_vU$WRVS+Zt)gb-dmy1%!m-ldtyRR)pUo2V11;(B!d~f*Tl(c>@B?k0Y}e68eX=7Q`l0o5tWc``#$P4UeSY~RDI=VWpgh8NFYPp_zFKVxGp-FLj@Cw-_&?<}-TB6q4_J|T} z3AeLGfu-CmK?3s%rM^DlV40m~_2jd=AWMAB63r#F1oc@B+8$`Tdds5((lWwHoAU^V zb)l5TkZD(k)+?C-(M8=ZfhF1=ypq+Ltd6o;kR#14sC4|yE0YeE`t_uPr2WYv@`LY5 zW!tW%D?eP9){9dAPDdufuG_B;bX-UGq^b@s(R%rqY1VD6VvDo|F*C4j+$^Elfc@~b z1$Fenh-Pm=tQVzR9W{d>#*vF+SysLzYw z#TG=}t`00g0`<8BuGMk>3THT%z!D_1cg5#HEI~qhL41V65+wY!he)JX_pSCnMmQaD zaO`sZP#w`R>bY@tyy{rFGU*6LIA2FN?%PW6G1Xbd2!}ebUhMN?m9KqJ362SR8yiK_ z1ePFy`a}o)V^@7xf<(-!G@&iXqZ&$Kj=4He3Uy>Bump+hxrA4wkHOxF^=eI@RIHHRXRy)DB&9kyE0xG@m0A+j5vMg3 zB5T_O>(%2&7FS3iO_8y8CJ+^7J7*jd+Y1IJp4$D7M3*y>=`R`O$Dj|+_WE^O7WP`7 z&59B##lL^B?E>$!;`dy_j}qs+k>_<>vPS$+YsFz5OJX{-Uao2UeK%44%UvBYLZz5! zaNE8AqCK>4b9*?k(kvkzZkDJN|K9%Tipg|zvl1#LZ9c8ezaZ)cQbJIUW!8bVU$yum z8l!HOyiyA#x@r}*N9bW#uO-yNIH4s`LM?O%iItYb2=qhSoUE#m+T6d8c0IXIxc%Jm zg9-VI5-P>N+vA%vjBqH${X_L-C$JaAiAawUKajYk_TgTQMn6low^;ml^37|W%U~4} zD#d=NK6V&KIFsTr>gF4UQN1Sh_uj_du(of=@ra5E@0g)eZU4|Vr}l8A#^2X~B;NiX z1WK`v9v|fW4+5paQPiZeTg83*w8RTi@6L%=c%9#TOOCk*7rpvo20xHcDfZ9y`R6kb zTB1^1FCVu#?=ElF`ZxXu(f7c^Uhn6|3&F>1ZQ1Ri-m(t0Hcn^>OChmuRl++v=ba2X zw1kOAcb@A#mY5a9>H?CEtl^~1QO6<2U*xqJK2C_!9=_aruivN)I*>qpN|)`2*2@-Z zYdQq=50gTvD?hu!TU>pVSe20wKhTo#_gQbbri@mJ4wNb`yvUnTeU9j0V#QsVqN^ob zZ|a1Lz5Hr3(y==Gv6*hHL*ZbQ4O~%>fk4~QbJbV2Rj7~iEprQ^ zRQ1=d_AWcgj&K=?m>+68`*Gl?u3lkYzUzl%iQ3Cjb|hLZS)z2@4 zi1lgjDw|L#CO8V&$3)ekQtH#*(vhy`604{beZNaQXKOPP=!Z(Nj9ad$WvQzo)=_@XquzD@Opr`0z4|#> zqsF7FIm!g)u4aCmKpomAP+y!-?I^{^N8b8~S7qy@44$iYmSP<})=v~aR9{?&mP84S zLS{ni?Rohl-m6n5iH_`qmaq;avireS4LfPF)XP5cdPgn%taw%i&$+H&j<@FntfO0J zd)(Ley+NaeLGsKC|5SM*om$eWi7Y?SUnHykhXc zKpijDe^w$|wjW4*)q99nc*1naE2ZOBA)&Uj4yEISj!P_sBa!BDoIt6buRbX2Po}dI zTEbFDaKDN54{1TvVWsR!U+K72TEaSz$f!fcb}rFgt5QsJPV;VLTn9?oV{27LLi|7- zD#bc9V;mhqXbDSkpU^!3ZB2&|KafBjN;`z;(EMQ?o#qWKI}=3*5}Hd)_a8LG>t1hq zhFBq?QmD_dN_41)QO{LhoY0aeq57gkcwZ>*ksaF7^ETBUL_Mlv)?EXa;By1JcTzFM zyUuw_xfi=TMJ}OItb^zFN1uA?e-J9gM5ViWd3{^+_)a=>AC6kkz1kD*Ow~x~H64eb z=(@z@TRM6E59g8RHC#Wk>Iglrk=_Y&bi}L*J-7SRx{JdU|M^9%J-BjgDpaYQYI^Ni zvQvlFT1f%uAC8IM*~`ax)xq%`R!c6SQcUxeh6hg>{y*A7rI^r2mu;0wF`+qTJ*Y%? zzU-j8UN~P-3Xdy2*33)I$z&B;qEb<-jL%On&!#&9Sx2}}$=Mg~KDidq-0yHovD@K1X+A z212Exe%O0nchkMw(e}Vtg?`i>xufOHq+?(PLZw_k9y>5Qhup;TDTRizKmF!12 z;)FZqY87AQU>)?GD#}8>pPGqq2GCst-qCSqP9buYu;;QYWmjZ6bL#K(%;|)|1wTM1NZq+g(>SrKA3qzW{NXJT|8_r9i?X2UX@>hFDe#=)gc5g1qqu!METFlrb`FrvFt#~H1gzH6py2EO%qLDf=EcNNxIjHRoJ&V+x7Z~9*b4->>a<^n*G2l4Z6p!KIx8^ z@&6(p=)Q2#p&qhSc&s^XM`~k;C{)7c67Mv#=bW#0*ID;^tyOFhw<24mB}|0J?vAiA zD{0*AK&{9`xc6EGj>EmIw2lP7%bSVN+i<^CeX=`{1CQ<9YCe4vfZqeDWa*N(7^*_@ONo_Ne7VD7`!wcZ2AP4jWOf*T&6WF|l#eomBfK zbM+i|Im6vZ8@F_u^VrTKwOGY{O>@cWW21Pza9e1bhuLm> zt!9b-&O~}1l#v>(;yID;pmMEZCeR^Pg`+{(&f25&3YLrv>9Hgn4R9>c9+hb<2}gra zigoDkY*RYh#VQ;bREi1xT>zg~$2PhZQ-5u~%iCM$1L;v3Q8&8O9y$CL=r|GfiHyCL zJul*ta4bRGq#6XD1PX;D>`h6tvBrRp*~9l`g# zfX_#K9`4)^2ltCs-e1pK{LB~`uiU%akWeYENmt}tb9O?dn9y}_*@Q|lp?A}z2|f@% z!u77L*WSI8Wxs^_M_8hkD4{;3b@+57gqB1J^(jrr0ZVBKANRPtt5>1re9FoUo~H@) zHXN@qcoh1@oBv6OaX&pWvVO>!jOgMg|39X9>)PZYJcOMQ3l~R3a9Yt&r|M9q_ zGbA66>fhP>buwS`3dcv5!;!%ya28eCAt+Wn0_ghF)uFUQh*e1FY>5`e36#=Vrffp( zWho?LW1>&m#1ABFr0ptGXQH@ONNBw_B6eLGC!+Q0%xQm*Tt5$DrE6R}FWLM0+&Ri^ zL7bQPSP7h?Tmr39OV|&k<9=veD8iSW3A#7(Sn#u6N_kkFdq1dgIO?&|m$C$vPz6}wulk;*uVQtrBAE$Yy9 zS6YY9M|RAx9^&}H$9jb(O~^r%(5pTCyXs35J|A(3YDXzs6X&!&|8Uzw>td~?eX4u`Lt7;_$Wr; z759*~egtEQWV_nSI>K=`q%Bd4`p2h1|ImKGvBH^Jf42lj_lO{OHGj0#Z2t%|#v!CV zH12MDU<(rCmykuZgS`xF4?G^d&i5McSVDP~SN=b|L!Y1>dhtAyl#V6o7W8>{g-1D# zJH`r+N40ptE4;uy8AcOOKcP@DrTWqYAJ9sMN`jd1vDVASs?Q-rht|bXs3SX}Im!g) zu38({p(Rm5^*Kb$548j>j1$;u7=^F}qj`mE23;qxjy@m!=Cz!XDNAr=q5X~trQ=qi zCF(5`*{wn;jBa)UEy0l?yHzN~(Y0qRW%+?pYDvs<=Nu=DRgKh^f&IM4M+~6cwJUPH zvnIV?LTw)I)}Rz0tA7$*)gc6rqPo|EIvj#~l%PB!yJvUyK2)5qL{JIR;S!nVD z-}(`r6@^(@!|j7i_{>HQfD$N_5S<1q6xc%CNE_Jcov$(wc?KSh_t5Aj?*WGN)h_C|UG#s*JqInhPi z(R1x-X+L~E;(Ei`IgAhg-n_%B-qsgBk+I#46?&`nGNJn7I?yU4vg^=uORl#;%fG$d z7Y~(KX-`Y*kOP*|k|?2lB~EBbl+dh96FwbDmS{RZLaOi zH7V^7VwKi~QhcnDP7^*I38D6~6h>EbEKSG(OKAxof7A0j@9sqlq&?I^5!A7Sbhyuk zOx|U$NR_ea+NpGLDiB+ndEt!@x-+TDnEs|F+UE9@bwElWas8vLS zap&WZaIe0)YwGZdVkm{Q4>G%M3O#4MK5w&i23myfR+w8?8Y zZ=Kr*CAt{Vw;wvn+q-FmywO2v(ZN~b^M4#4l+v{!#!U$IoCw#di&r0#dcT)_w}`%f zAWj_H{3dVPhj+;P87e(oE|vQ1W653K$EVAt*5Sr_%lKxhf;c<6~kQlCv-B}8h(9p2v4pbxS`6x|xB@0ND< zw!izZ=uj#Cz4xxeQqynaeFh4uwz&|u4(;akexRoi1ud`i>c0P|9LETsQLYP#u8k_C zhMd1rtZH@ronD)#?$6*y{n>YVIi>eYz4zT&IW^#;kA(PV|DE2^m-Gq=Hcza&xx-!F zi0QqAIJv=H-lD$txYD7ArS2Z`Q3gLy$IeZcdoOHwTRbgFZX_!^+ZUJ3hM{jzV^?{ymkkk5UZ4C zIcX1{54c|Rd`F+Esm4qA$rqZbr(WhA)a}U(I{amKd8_~PfYf{7r%Sz4PxIwi>9h_$ zkm#;%*WK&4&l20t?(TK`#U59wcX+DP(htK}rHNH%b@#sb$x01x)7_i6*B(!+b9n0S zWkC3Zh#zQGLFL0!c^`f#etebF-CMQufzXdMfmSuYty-#4-S>q!?Y?`xyH0$Bh|7X6 zH7%nrHQ{&rqHnVz8{|z|^uO|F=Ma3iI=T^gYtk-^?q6`0nqIggB%-g8zbdeFZb1s^>;!s?bod6dY4koa{H7L<=fIITQ4s zzE7X|<uKrp~_p~2k6?86*mIsUT*=IiqM_AR6z{WCp!;~22`~|*xow!XcNDYhlW1QQ?ZtXgpBpRm7745=t;1x}!SCf{OTurDL}P{V z!Fp9+`;PBMqKgDdxzXjjOW7N;3clbn&#?q)#}6UwJ4NTE+k-7}36890B9`b_(&^@f zDc34!^YoWbv|iT7$4IjW{Ac?IMv?VtboDLvJfd20B``Bk3TYuS>c~XI+e7Q(5+qbN zN5R#BeGubw>pN>@X4sQl6d_OwCWm9N z5I&33{{>$@;jfaoqiFa|lW-Je($#@qtYaOjk0tH72kXGPa6Aub)}im^oD{reQ{P&L zGa2j9H`s*)zxy*PrSqUmh+RSM;_5}M`r@q-DID{yJbTSYnFxNrB+@MBo*Up^29JZR zLy7d;DP?3}luPhEclzEr{(V8EgNpgxeQ_N~u#U%)i$#a(juTp&Q%~hf1&%AFG9qRp7ae zk4hmegtj0N;kd-dBWqRkzUuHg>Co(nWQh>kQY?l0GwertcTjbZ=V4t@9ZJXjK+n;S z^RJ(lJYvf*iEg-Nz_D-F)&I!cclbxMQ@E049hnJ^5BEDB4UiUsA@K#*&OAFa!7F5U z6~3QVl+67@&&25r$tz2p&zaEqJbpfi1lEiCj0tr3nRKXCsL%BSwWE|Cr`J9iJ=ZVa ztXersd{TY%MQD1)i#6Rt>qWFT{5FdvWGz8aaII499G^@?+$!~h*CI-@lpZf#8mxVM zZ9zPjK|io%(|-7w{9td{bF@(W$f#q%xqBo_Zr^JOrLz;5uSgp+XhWiK{b`@feU74w zwxbl&VRQ>O*>7#pb9WAbQZYZsG@I=jMUE&+>2Xd@VWb7MgyW++xDE7oOF;iff1OKL zle)5G-5jfQ3#P5Y)ui?nZU2yn>cD#WnCp${NVi}n&)IhU-C70Np8gJ&)~h<$bG9lT zEA#^`aaYJu9at|PbG_MhV7(~i>VPasf7MFsRUMj@*7KQk9>^KY=Y`SiV{f?!Yy0yt z%krPiE6oPX5|+~M|8%@;E)mp%GUoCKr`lOczgHt?_fEE>4%Ci>s{;wN3Z=9r>p2oA zr8+nhU7{>MI9BY3((%~^d?L@jZa<4eVE~n&0tm>dZFw_?<`okFoOrx1%Wk|I#lILVyH9 zfRI2akxp_Gipjlaq)V@&QBe@2BhnH6M*#t;q4&@`p(dfprHKe4AWb@m$WM@F0W9Eu zzVGaJ=Q}%lPPorQnCI;KnYT>c+1c6P2jUj|nDVb32EO^sYj?hN;-CQ~)LU8`bVzwr zUxTX0hkgVCd#MjSe(RaNejoDY=Lbv9IqWW{Qt>-2_0=e~{mjM7uVqcV$I2u65uz1( z2K7U2*PM!Co3cFq(_j_yh3#sgX=t;3s2^&T>Prc|qC{#{*^9REjywAj^rG4uur-Ii zM0$qm8*aFw1Y24t^OC@<&j^kNTIMOC(N*m#_0SyQ>vdCJsJ9OtGnBsT)A?<-{P{Zf z`}VPD*_XWJdu%O1)u9#!n^+zyrP2LzR;eZFsHUFly#Z(Xx|Y$r2B7!ns8_nq6z=G| z^$dZyv(}fWRaz6Z6`j69_;T7x=U}zA)Y106&wW~}wJ%XRjjrn8w~LDB?{*sL&V*W` zv=MEVph{gcXZVuUfmh$_IuklBu{^MPHiyYkgdYi&kk~s)K3z5qtoL^$T4|)74t8oy7co%GOI%3U9BU ze#tajVfk_d-lBZv<4cVo-CYOHqdIH8%Z(u2oxoncm9M?R2+~IM;a~sf{YY25xDJ!W z3PhWPO7-nCUu*K;Pxd-$YnCK@bmJAImH5sHJ*@+7I>qL2)t-rrpcLr6o}Irn|M92J zhM*LPT{m2ywdm8cBPa!8`jHE?uKvgD2ugwIU1h=6^gT|Sja8Ha(OPrC*5`ZG2rCaA zmna3|)}OrCf6v0_L><03!rl)hCYh8@8^`rau%5~^2B%N`;^5> z`$U#AtYUAM5T-*3-K)sjlMtB>Nmd9Hnw z(is6AMrhBe{qEgsE!g_U-nE`OBPa!{=pS-UdpOlWDG;C^fA&E(e^2e zuJ#Ptmt^BdMQGjCz9btzDnje7)^?61-j>(~%_^;lTHCYnqtc-@QENMEWv+v@8u8KE z&e5kLkg)RLj#v<^eU|Rn!*PU!6um{7QOI@Z$VqQOFbX-L<2lO!1j{BTIHp31-XhH? z4dN{S0agYJ-ThM$Vv%!*37p8!G0wtcm)LBqI&$ab4*9p`?SGAN_Ef@ z{668LgIaSPT)UevKU4>18ATCvP+xbe)DO}X(MAf|HCBu$exG~BiLK9mcUC%{n^o$^ zN{jE=x^eYj74=m*EP=e#TP=?j9^S8Y-So3(qvM-b?bTXqouGsIavf?FBMRN5b3#3* zw;*U?LRhTS584g_Zx5mT6T)Jp#CH}wxb^Iz!4F#4onXGuLE4DqK8p8CxOZahLz-{% zlbv}08+38Rz*4IWIZSRqfZjZg4_5LkrsnK?%N#~y9 zeJ&EQ%p+|C?w#!qao2kT-e=->zWD^fYG!F=t^}n(@XjJ9be^E^E_I$WaBhHnDTOrY zT!+pw`1TP9&Ny;{S0JSX=MNQugtZTyWpHKzn(zNqMBAqmKObcv<0?)QhC&I_X&32#+OgtK1*fv?emhV{Cpk8{Ux`bCh6E09rNPN-B| zf$T=mURb#5*sryozBBm5kto-p1Z`J3AuLv!FTRR7?~$|9LG8#_iG3z-(|YCm7dFI7 zb>Mrsi+%MXD-Y6%RaPFVgZV-qP7eKRZ<7w@1u4}*I@h66IQ8`7H-hJ#30{E?CGbsx z10HU&N(oAJ_d{c)C5UeTzrNL_vxzRHpyS+cY~0%HyVFv_%3SkBq~em zaY0{3SlxvsxKbyso^$Kgp2M0-Pzg$bh93(rTO+Il)si>P+orYov`bAN>BMs*cm??? z(Wt}x;FUUoHaPULnpH+?7p1<0u=3y)YDU zV9|x=xB@A7d$I3vkVX`?_*bom%Lvs$I*FALyn=j{n0Me1>mL)sqO0>A9dS4kC4}kF z{zD0tO+uIsmK3a_6w>$>%#BYr^;f)7Cs?wH4zr3^>I6$OCwQe!Fm5@)D|G^W2}bIi zU@rqgdmo%m`{#GVDFx~5mF)n8whxpbW@m{Gi>?xs0?l3`AuLwB0x2bEVNR$N&Y~` z1b%;IMw3-aP)dDDbeL6;8naoLh3$6F;jPct3~9t|#4x$XX@f;xDh1Cc^>iQrWW*Eg z!&~Pr5D3zcvzpjO!lEdB6g=n5sdIm&y$>SF85!wH2NGtLwy3YHeo$-I14BaAHZ4kyb@ZJ-mAwt7Kqb*dE;x!Ao4YU@a&Gi8H=}5lr$h+-${~4kX)&RzRc+vR!w@h&bp2z!7lpuTxGs~h_)yHa&vhGGHMijHY zI#I^TN|1R$M_fS&QQ*o%^{qxDqEv`a@F{!|Bb50;R}G#p-M7It$~MTBgLXdO_*QG; zM&Vpe_VXcD^PfCu;BWrzGgzX8rb&aS`V#BgnO9v$=kbGA>IC%#tD4(jY|eYtA+348eWUh$0_=m_(~(6;e)rj2)OYUlbT zn%IBDGJqfaJ$SCYPp~BPyOavi4ZQ@2s$SVRs821`4_errpcLXp|0=7h{wnr3Y^lLQ z{vHVJuVRm*{Z(j#=#?rR=yxr;+T#R2LeIe8D*{$YnFp!Z=4l~&pVBHLR4T45*87rj zbnZi$bKFLJP?vcB!E~^y9G#8e6%fIXu=dJyMPU3ef>&4u)PeHg8ksM3-(PLv$J@G9 zY9US-Kl3|R`sn(8YC_pxWdx|NJk<*sZW_xbjb5tPy>Ku1+~ zt3HwMT_Fn3Z*Zd@4Z=z3!H+tjwktska~-^bd~v$%KH2vS=7*Mt zrg56~PaY{fpIP<{89^!N&=RaXw^pn1QLCu0yC3Q~>4Y#HyaG!=eE+-GnCFc;8VQyM z>0Af%s_S4BDx&RER_4r0I1=!aQF?UV}Z8vfL}Cs$})x?H&;+>xLZXzEL>G94PxI1lO;U3!Ro)pOFh z4r&L1@21ST1>OLyy>~YN4u70YaE8qJ4t~eiU5~y#U#E_!{mK&uL*!@CvOWL48J`owxBHT8_3_jH28_-?<$fzLB65h-6265-YWeQXusHquR4wS{24rNGZ*y4zNBQ zYgvCl#3v20G;@Mw3*z0@_N_y=#yqjZJJoGmQ9?%?&Nvet=7$oLQhhn0`9A)wZCZc0 z7`xlTJ1x97sA6SS>D%4vNAjjO%ESINFQiq+oJ(!rT5@A~o4k>rR7XF|D)l4O{19F0 zL)K{t@(S`*g8C|=4HQzmQYRRNoZyu@(K%MUQYVs`KBJp?PAR3$ssa4#|2)xCeG33- zc+S}?YmW(bB&N@ILTlbJGkh(HwB=p?)An~Pdd>t-e}2@Z=Q@|=(FT?h@zenPK5^xn z{MU{ivfigu;Y%_l{MW3f4Du2*$<+Lpto<2w#fTog865!yLO@$Zs#( z6O*`mBPIBBxhQ_R;#YdN@aq_xy%wJK!CvHr(V3ROD&|xa$8NZ8_6=$5@u(AgD(k;G zz0tkn>R(b0dnPas!gmzIv+X#`nEupaca&DyS|6<9E(`9gfIiasSEr*?*o6{yStx;0 zKf!zMzlB|{C(@EA1<(1EyeJ-eaOs)#uTFzoOEyFx#JAfsc=DJ@|Y%u+M6BapnP~ZFI$~HX&f$zBQds$cuCmnS7)u=wd z8f7|^&@^cfI^T_NRMf5das;J7Q(v%Zg16}MvYp3Pr0qPmu0Y_Fzx9%DVWSM9R9f4? zl!TQz>yJv2jy18g%3{UYwn`z*--}|$t4__nAsyz?;TzKXe0IL|qN(T0`nvmYxjTV1 z4)M`6z90)4UUV)G<_r3{QK!V!4~{s#r;c$)n)!AlY*bSzwJPfP`&0jK{4RbCZf4n{ z*ba~{zBGOC@3HSS`c&;Tz}K^_eZY3M349~@fHT4=P_cb@9e0#EvNnisFK_d3IGc*P z5v!tI|9Uo`R5M>#McRlW|9WcbZ@Pxq&1C4@y+2}*&czF<{3 za$2m=p2JFWD9sSJ*smDT9_D+7_xvfe=+%_aG`=nUo4c^;{O*KNc&^sxpQ~jArC=4y zDd?zw$DJieDG)5p#B-~OY6;i(Ku}*!@Cu}qpuU8#=;|zNhn+8NeR||=Ruf6*1oMIp zB|7_|b%|0+=Q@-~bTrixiyVS3EfCv+tVyT1Hd+jFX_so%g zdV|AT2TsLvwTAn_MuP9!DQ$w#(MAeaP=Z{IK#k#A#(XQaTi+Wir;#)gltNC3VYM5V zewYr;SN$LjThe}1 zq;nk_A6f!}bV68k)pjkxu^*n?dT9P8&y}E5qvvgC!xgQ&j3|Cb`$wJ5tx|$gAm+dH z)Yiz>>s$P=<^dlj9^J2-#1n}rS-|TF!yPw zi9jt@O0d+F&UI*geeQ%)TB|-2bTHBhVLCKlttGTDCzuy3Q5`IsifAJRDUFXtmt%WH zSfWngTego}i3FeF6Nn6(J;o7*OC5T9ekvjC$*=m&Oh@*3~1Ud2UKG#9* zkZK)ueQT#j2Acga6jF>L(l?&*Mr&Zr8;oE*&UNrgonWMMf>-JUd zU=)lPz`yvzxUKUNUH9Q^0CG0dZzBOh&tg6Bu)&TqK++X~gy~RX+dJES)!<(+-ix|C3=e}D1qGZ*R-}Y%3BfEmblIoBq5Fz^3t#z35Z=x0bl*a#aV*A5?1Brx&BrFO&spM5iS_uRAPkTV5 zyicJFa5efDWUIToHs2)uTlb1 z`~Uj(gw6@nrPca=VNzQ?JH$Qtjf*WkcFIsUaNZdWI+UPPuy&}o_JjrkQn*4XkBo{Emn*Pq%>A9EjYjX{&%M~=-`z)LI1kPN_E`5=McC3 z#g{bbU|!HcDWnhmi2Kzs(;9Rf_4ftc`r~daqdT~&F{Hce zP`WdrQr|gZA@`HMTTS1J&oAW09(B`ftfCY=A3G&j*xf36t8q&RD|5{kw*O|YGz#5` z(|Ze@qYOCqc-SqUwr?V%=46>l*|D8E$i~1_JGa@MXH#0 z&;sRrH;ATp^!x2U&GG>~eduUk^TW*Ymaop~MlAl**;sYtuRm>#zaC?hKlyLXx!lm1 z`HU>}gHj+E1?Wtzx^CBPTkQ`w^7#&Rv;^tV<3De`xB2P8D%_>E@gJ_t^@lTCH~rvB zBPfO6-~Q46wf6btJ(2K0Al7f4-#Y1XjQUXu=^ajaxwZVA&o>av3sQQDU_x-LwK7jj zjWgM54&M)B_!ml$<)NhpeJmSDTddkh;EKkFQurOC4`$~%!7G|C(pO)xh}-OwpBrJj z$p^5T9JM;mcYBiboYUrWZsyrRM@P?D&M5P@K3>2bb?!+Ggi2{G=}f4UTBt-(?CPKP zolrhq(z~KK>qmKnyCuZRzGFJ@Y580&N~KZY?|~?F*wZEE1#fje$8;d@MJunvo|y0o z2%W<*4Wb%reVO+-ZFh6sHY+Sx^$h;s$+ezRaH;JybTCG*7hEE?*Gl}wj6n< zkM5T5J=?2q{VG8zl*gB=?OKA20&MEsK2#s+?!-GQ&FfBj_HL`Yq`MPrMeu`kLRh_G zUMht&qmU3*9?VPQ(~&S8N~l#D-J-yb7r)1(*0+Z{udt#M`*~PZ6gWKqy#vZTjvrxm zALhP2)$bKCnM9a{h50UKOVq*N!w!hQd8_6qt%^E&+?-nsaxV`LvnAHbpaZ)f{4R)^ zRnZc}iZqC-mRPKGbmo`=g1QsJ%7fZ9U!+MVM5crJf>>;hMctx*&R4wRd{Cv3jyef5M8dx6zzAs*VqM-dLHt{Y8`&(eY@{-|NH!{fAYkFCBZB3R^!Hd z>JY1Ue4MZ=1$prbh?r&z4(p;t;R&>JBSJZ23`a!syW-%vz5yEVeg{?St#B&V4Qh*0n!g94Q>+*tt;a^RJyG@yJ+5!o;_ibbadJ7^2Woq)`IU|aqR0An_Hb$Y zC}U;qBewI{w&6K{uRMniBjRdQY}+9Cdqp5tM#MH4dLOny{vL?3zcPZY6ZvvI32FWg zBE8$zwFSNN9{%q++?I2l7HT3^Ch+HL;!wvcNMm+(@G58f==!%VKAF?KGo@S?O$f~w zH8ISVVosKILZ}YZa6GkAeHGDevPyN3P6$7fN!Lg;3S0@v36)X{Gr~}f3DTG?hdW3- zuZ{QT)k=|BrTH?Vpc{1_D}=gg@h+DO4(UO`%kWaLbGA0;RS znil3dw9M%(h|CXIWu7xHNF9B)_cDDz(l>!`uoN%#Kj0zKM&wNi$|gsMK0+aktl9 zvm;ch&kc7E{^Z1F&r#+UUB(^pQR3VOhPxeaYASOjCyAdkId0Tg@AVoiD zI|$x?!ry8ZZl3ds<{Ny9GvtbB@gply26cme6y~eY+EN9iWX!dbs*bCeGm+`d#lw zI@8l$8~U32E7h^|S&O=P$KPe`oHLxtDoapDSf}=1{St!XV@0%m%Gax*j#rRYf@5Mr zm>)Vu={UIS5%as}){+?+bYujjppPS5rNa_>1!?^PSmQWoe$d{!AGEO2(e^1Lc%@Fz z!ium&-I5DGU(h{wbeJc8bIkniw2gloYe_|@)KLo!a<`v3J3^)OyJVepsMHR>8RQQ8 z#Y0w04%l-6chcjJH&~@o|9Ntd8-M2P2$ka3%Mg)99aiQlwdCIhxp!B=>Y$&!&bOf3 z=QOdZ?Sp0I!MNAUBdaA9!7FuwHO7j8t=4q#N}XUi^?KGc?2ss6|)5kj98Ao$F9bR0nnEgzC_*jLleJA$R*@k2TaQmC`Sn zb^i7lqganF>vUyR+o!Aqd8JOUPUi%#)Ctz-LqT0dCWjajN(vqT#-RP#cbQb=dt)N2F5 z6&(jTT87`p*o?!nPIM@7=2w?5pWVh0E+^EIhnHN`?Yw;W#v#9uSP^ZXvREmhCqcN!J<9obt`q zm>0fd@5d;?wVH}(1GVy4>wB#?AKSwSO5yj;D=hlGR7!#1oG90^>ASnP*8BSYrh`%- zvY8A}AKjtOox4ZtgVhc&f>QXMV^^+Yx6^;vI^>`q89^x!jdKRm(fge}TN8hFpb?Y; z!QM63pQpA*P&lCrxXZ|a20`s`SJa)?bSMWdeA{B z5bP@}qU}>w=KB3}N`Yt`)l7$eZIeEU>#PVh-nPVh>dpif35V-$ORjStd{TcX34N91X?SpN6Ypb6+ycL>;QOQ~cpQT~VD(rx`>?Z8e zi@PCmBJPtY+`GFk<Nw>1E5rC9C74?UDH5AohRZ>MB;v(baqpJ$Hm#`HZWK=&VBt)j>M9idX7Zy|~{9H~6d9bchvg zUwZoy?z|hWF{?TgDmC}cBix|ZyAW1O)GC$%EbQ#L611eT1lv9arb8`J9i;PEA)-@P zzRtWwY4SZU{Qk`SP46zj^B-=1ZB@>szqZ@b?$QryZ*7@XN>B=Zz}kO|y0)P_*q+r} z5{Pu}hY}hE%;BpwvZS4>)YvH_$`#1MH4;^HVJ|G%Ypzl5{ZqmlZlrUo=phL8oODhw zFA$F{yzG~-N{Jucv%I@+x#?yh>0Ad_GogcTa3J03-Hgz~;aw7yLK?l@xVLX^C=abm zlu|mkN(ov5V(HzNbnh&7TdKp#L#0A}4g1I%i2`}44oczoF15YX9;Jeg&IGQoO;8Fn zYAD`UOguL~l%Ny{)|iAaKX^s+MY?mnQcFTQ_(6TS4y{+r7dlwS62kmojf2$KDd7$q z>Bbgiekk#)N0xVcj|_E}bfP08XbE(X&Iz@h-qNb3{q501NGZ+rfLw=KLaA<6apr}2 zNsr@>Pn|i{b%c4F|FyqPpfs!ZSfqzb2})7N2WOs~)CjN5R%Q_aF-f3t!c zzRBA*3)N>VDS??K@K`H2~-($er@Lx zG(z)LqESbIeAOyS;dhp19$l4E9gT!prR9uLo8#g?nI9}o(}6yyjTG)ibr*`h+xN2< zR&)DpaCblC()(K7$w4XPbn@G)xecb?+d$|}4oZO-J7x7)6BDa+CkLfKynfT_Zq8j_ zZP1}RIVc5U*xjqUb$5N;6RTgj%8YOMednX?T+UdlQ3j09X%7x@BYy7N$7r0gvh0U* z&REg!#J$Jf9#CS4``>rQyUzyCu;{XE3jDPTPyC~SU@L+S(nb`$-l{F%z=^%7j;HS( z=Z1deey?MQw86cO`Jne`Pi*p1PSAD`q>EyO!@fCVNpIEfN~>tET7vYU3-`J^uDh{6 zSaql;`WgsIfnZKXw4dxs8(2+IR5gS>)mN;`Z1T29R z2$l_OQ5{3X+aYfA6~?e%JuUN`8-ea-V5=bPx}{8M=U zy=eP4c=~YeGAz-$6w^Z-=_FR|lo;Zeulke|^bk@9oym)0frocNt}p(5Hy)d|{;-`RI1gn6z~w7ol_Rxw`?v=BPN>ECZ= zH8J)ds8=k1-&6NLzRsWjy+hwg$K!pqw3fuO@jK<$Tf_ZjJM`3l+4-1>XZSj`c!Q32 zj#$HeXOrvtp`&%u8g9Z*uk}Q6^sO^r@n`R@V(-_cIw%Dq_Ha)fcG8`%{BwuCiKpdO z{dd1LT>HhKgZhf%@y~xS^Uq$#QVlw2J9LotR{1~wHdWt${aYjOT)ov90Dp(P^*(JR zkeBx7w7uqe(qE+nEdfCbyX&C0bw4^=rO_pw= zKi}P+v8yM(-9XS=SVcPM_{6utx$T6FFe77pI>xHF!S^F;+WXEaF|f-7_tfiEaH?R_jtaZ`Ybw)vGr0;*1_?)bP!B{JXa*?yZJZ zDWOuoxM>ad`Q>x@6_%lnd#iIorQUdO4Y%+2=P`nNt8+r7-W$56`|F57M!a?6Qf|>- z${8t!Lc2CUA)y9ADL#2K`_c(Xk$H9+V z=UUbsH>9Sc0HIR*Z@;!X@%5nNUqhC4=M1Y6rbDIB<4n42s9E*yiOagXzE&f=j(GQz z*0DIBvtnR$y$-Zz*4Nm!@jGizMUq2wajT zn8r$NrxfVeC#LN@#zdu%R?kzb=%Gp>tvy^yFt54}?RQf@R0s0~y}(1gZjU$r(~k&s z*1>#1u%E98BrLjW6{Q;eFceqRsvE9Z$9=Kw+`a^3X__G0hv`s)wu8XB-(5X(re$t* zm$d;>s$-R&wcWMfTp$uqmg!)=N+(vC4vnaeQM9lqo;&iUS=t1@+n&=_&r6s8C z>{rZ~!hfx~ZwVU*RVv({vG<9@bysaU>kdDvz5ZN2s_A&e5w0jEoj!Pi|H^pZVGZM8 zjC36Bf>rne#!WT@&=MT#-rsdi_v)VuYlEcyy}{YHPI%7G04CTBASHft!kTW|U(aC# z>7p31)h$N!HW2%aS<4MQGK_4FW)v?hTYq@;#{J; z4oX2c_2q<0>HLASkBVp`RriAt#qYGRA}oO`D#g0golq&(rS1f^LrRIUyRGH!y`d-e zS8bRDDPE})Sxp3CWv;POnsGzUIvNahN~ndTbAox*E#yqCB9O@ZpzWX|@xr84*4g2> zL47;_7iS@9FPhHh*dB|*&AGz(mwYd}XxWQuzU(=ngR{ZJDvOm8%r~{F9xK`lDJ44V z;1v*R6=$5eRVwxR7wfv;-?@afs8_FB&b@t5`Mj1hQhg!vamCgy~rBX=i?u4Q^(ATMr zY|T=4e}*$scs3Pxf)>Tpd!8EqFF*Phww|baL{kF0lW-<|QSAH7k`qcDRugr{R@_ZB zsM^m2KZ>H%VMN^N6s6D#NC)Ca{$!YMzl_z7cv3RnkfkN~9qj{qBa7mQp?A!D!V^zE z6NtD6G9|dP&d;wtIwwuU)@!Z|R2-x<4`Q*i+)FDf^8t{iqR9im}4pucEl-kukkxbd7-L zVOLm6u$+P)&1diE{=0Y%PaxtsMcldksI@5QVl;l4NQ1Pg7R=jo#-;$Ag7 zA-0bv#Kv}~d%R+MEQ-&6wQ28n{k_5CwiX_DW+6UtZxmW{d{)AYJAAOLby==ANP3EXCiLNK&d*(><4RYkfbRhhy!2nKa!j;%2l!!Gy5dJ)} z0iM3c8kg4HSpErNwS=`n>nrK%bg{NS)v*uXwuDtITX?SNt@mEqt?&-cVZpaE{mGGi z{^ZDvpp=$>;`sn$5_W%TUy|0us^7JqS}j2zyy!l7Pq@cXA9SPyuRux(mTXb%|MWZ) zmi9h8X7_68IH-L)XpAMn4}TVGpFfM${D^fo)>r5wU7d~Fw{dy?E^8d5M*U@Z_w1&4 z-yqUK2hIcZ?a)e42~Cr(&iL)~XZ%h^bhS-fbo27gErMOeX>G4!Wg{oYBDF*Bd%fSsRe=)=Q3d z_iY+Zp=AuMJlf@KDI=6dExGZ;dwngLf5;}|{da!*@V#G@N0XH53p((R)Vwy z1bxcOqpstryM9@gIjxfRVI^26*rMPE=v)W2!xFVBM%U}$Oy9TKbT^egmE$`!rXwME z1+)^}Wt9`W0z#kTahFw2@Ct~y%01L^msL*i3J9)QAC;^5tuabtHmHMH7}Iw%F=KZ}oaH*Y5ACpMPH zWur&B>EAfb_a#H!xaCK>Uw(6T1oH*4(b6ORJ0PL4%!YKq`MQe1O$ERPS6q%jC6N`@d42~YNT86>Dl=~ zDG;+farJBoN`YAJgpqEi*Jr1LQXnv{RNrtzO*Dsah0jcper=7BZsx#ARZIHHup75Jx-CHxuZ_&}JjG&bHbhQ6M;u~R} zSoK%thgzkc)56>;+FSR7tt%&(msujw{lU*hy2qc9zQoXazLdi6jXKN^Z4q071< zOVq}X%&PepTh@Jd@lda5VXlLH26SkwvZwlO&zcU6?llW6>sDPX=pbF`s0j8l&_Uag zW-n0@NSNnJXcS22gqDZ)SDpP(&nX3~NLM=AK4qS>#z9IwXGAIj3G?HqXZ<+wL|8-C z-iLixMYMg&bZ9@weG<^unP3zlr37=TbRc0?skbaO^{FD-Bvc2>K=oAw5~f3?So=V* zR_27pir#kAVX;y|`$R@M*TJW($d~(wkj`eWK+O+6YlIZ{5h3o-TcS7fL zq`MP3>m!{Ld=3b!I5$L^baz7MyQFhMpLuas3>~C%g3lTurSo0Rmb(*_0!@A037zj! zUry+gI33$bcPDfPKsqP%nU{_Xq`MP3evr-yeM+W%SLYE&3GLxXch{l)3hA8SGdh%- z_E+pNx)YQF-MKGOKjBc2kD&9XOP-=F$&!Y?cqpQMBAsVPt=(n zpKZdb*E-Jncm+~Q?9m?OrvIsFp2#b8qW^|bZpPE^glOZ6p74hrEEIPuq6Et(AxwwbuAVx&0=6A*NsI1}@=s5m?72J|n%(NaZ^SmNdhte5g<{@8}ZCwW~O#CpyE{&(fN{z<8LUJ*Lh>X-;RTlX;?O6WL98d;~cUHcCuxLc?q+CX8i5|qO4 z92s(gS0JTCGJc>9RywpUajz2S&h?6UK}reclv~BT>I8dm#rv>o*LWw&R>Q z8|j?j738bLyl3{?+tLYPeyG$=uk}}Fi!>7EN8Azbzv8>{ME8Rq&Fg#Wa8gLMPoK5K zh`7@}CHDS!9rw`CdBSMWk)RZGgwmWlCCm@CtSBWF<`?`DW&p%Duxp}Sxr9j`-zpXoCm81KMVznn8 zzkQ))?wR!G_m3XHl~8xX$iQ=)FsiW!N34qC{iW}j^iD=ZDLp$Q5S>mV;W-1)%@M9B z-o5ggaliGuQLyh$uVAnHo>#8p=AU*!f3y}S8+qb(Puxc$_<_@mhPe6HS>GME)%iwn zlqiZPj@qgBbH5Mg-+r%5T}QMkp6(F*INuXr{VXT+?1botXWIYqe&AjDx(-UIPkt}w z$8X(!mM6Yn66#0ve2DwvrFGoE*Dvs%6Sqo`6yZ~yB0V%48LfxTpPf>I!uQ?SZMcYx956_rBz>RTq2r+7TR z+R%xAI&ILvUj8(Mx(-T#*zE2}?xw}VNmE7fiqzd$!{h8FukH68amwTkdkz_Z4z>fN zIeP)YbnwI1_5q%~qkhm5(40%)WTT(qWTOF|Y{V zJ&9L9Y|_(m|7$&1wvRB{L5Dw~X@Dm*sUOkvAvVjvIc|aQCo~Q4geE04{nP*S+i6Wj@!z-p z(0iySo-I#kYLe2p1s&|+v?tS=fplm$K|Wo}&=VFQ#nXClFN)J>z*Izmym*EAk_b9> ziG-nbscm2EbLaML<;$E_5jgFyyNYKtM*|!G(M^?j!|`jSL&W~^htDBne$4W$owehC0dta zy93Q~DvB2l8`OJ`_dJZwDmB!NIAD^SwcDQk-8@eTZRe!nM^QA7QE^;Cq&ap$UfUpb zpym&CoOvV7v&nLTSLy^j&rUG2d_zov4qBpgu7i206w-`BMYMgYtyk*AEhGEgS}*R< zpA)=NC(eGX-)%O2_XdJj>ck(0wcONi*ZOvERh;8!IU~(EN@bNL>QcM?!P$v>b;90J z9N-CNvE2<#+92slN86{&Dr!f*ltP-bft=u#I>FgMMOXq-yn^%_z6@}lA<~=;hR<)7B6?%v? zZAY5=AXgOrOuzx22^hWgPfVn?lMcive>MZor$W19--mRp`Em3qikI&=d*Y&g7Jio9 zt7#2aVyL@&(LQ(Zxd-$Y#o_P#XXY0E&6;1?{a{K^3i{64vCmE2<;R|w`SaIiI!`>& zKu`+AI}i7{!T)!tCwiaUc%Au995nFPf8mZL)j=r`eShk6`>l1jCwBbtPP2aPbsW;5 zgHj+i8{6*|o9`!{xazHqCNAMUpW5I#r9k}8uj1YM28goE+eqPxMwfjperI0^IUSu- zLiBb5j|8Xq*-GU9p;DDjJG#xxgYi%Xt84X=LGYr>tIe5frNRk`LZs7 zZq#7}^Q!A$PKgflgI7STbc=JhZg!ISL4E#gxZAhuecjjg!40*YQc9cog+GC2z@I>q zwL#Uvo~XN3KY7@>>sJX@k*MM%%zv!Jf)4#X<1A8+}`&z$GV%XQ**$bZLiP&ow#VNm9Xy$Uhr+&ch=v8W{ zTltxmx4t`V$Do6BQ5?4J*)yj--*?{)4OV^O*AS<@h^Ih9+|1|K zcdNaQ=uZ7`nF$ZP-gn>n4Ft;o1nHt!>Fvd4{ORSs`}S%edZ(@Lem;M&iWd5FQun^_ zj;;N@ntyjY^MjT^2Wj5|ciX%7+b{LqcT`E>PLVa^gss2o*8f@XgBFHZP4uz)W&_bg z2g0AUFyP+;HLFzWn#(7-ht|eZvT&CH-PPIZSRM55@S@E5%xj2SXKkN;{dwO8_1Rr{ zwmMcIO$X-GeA`u9>(KREw{4*D0Y@@Ibz`13_G&nX3hQ7DREY_aFWRS!FI;ETrt zq25vo#B-~(+$Zn8P%FlQv z;8LFWZ396m5G<$QdAX8m(WQ2^1ZnCkiUU@jFyktp|M;>zv|h0cK(LNMM>XOUxWZH2 zkY=xjwDwn>2}*&8{Z(B@JQFTGwT^vPWmOv~s^@A6!|ifAJRDPE})w6G$OFoN1as8zAQs{5e?r6N7t_YC5P>0nQ$Qb?0d2=ko% zE2J=64yW&FPc2quI?uGeAnCGMu9Y*j(S}2Hs~A!IPC6mX zbG4mPAjT{-+5PXS5mpnmJmTnuny91N@Dp3^swrVqtD@_zLcNM}21+TNSY?EceM)d7 zN(k>q93hcb;+vPX?E6R+frJqp??C7{c=j7D_tvd%_d~Ao+^phw2ZB;cCxq!xg0?H2 z6C5ESr6cFv_fB@Jobg3}PG~R6xgluW4>q6Jt5Q0%%m^sUVns{T!pf?O&>4XG#GNV? z(MAe+sU=E3^Ec-{+e%IzHWY+R`E)m$XZlI@JgLv zTS;_SnQNO+x{)xewAHE~jC!R53Dcnj^HmEgqU}?rgL$bG(k#u2K*DtB$~M=gm9B`k zPnizp1u3jXgtP7&YDpuZo|DdXu*TJOu#SQ7tJ{9w9?pS`Ee8Z^A4UVF{psS548C)I zfA_fWDdALL<^?HQqIA$P%-)05qh`X8wplLhO zXZ~ffd-b6(L#}23zI~)+Kq>srxK(~w;p-L67ipHi2^LxtH9lG^`Fl~^cG;|6t%{?apDyacYNa9*DnjF<1?qqk2X?}(sHI0e!p(T4ctM;FV|2W)DEd{&b6U? zdU80cn)(tQR)R{XAEXn)tWryuZ+ELyiuraVOoz@UbuQYuUMWE-=*Z@xaL9CMeWer# z(z)ka`?T&hmWSz3DJ{)N{Cn!QGsYhG-sG#c+|{>Q-OCsEH^VCKPd4+5KOOttg3TxxX0%(?Q3q+c)Z4XV1|_@Cq!9dwGXe>7uy*oLy!- zvgNLQ*B<+xt$0PLxXTwho;zV%ckBMg^amYZ^~Au>1F`L<+q%E?{=a_cpj1j+Huusq zrg>uWae?6OVGxm49e+Gx(HYzH9MZSj5vQ0{G2b3{@7&wD)!#YY>l^A8cz-+h+=|zi zR&C;mD@!6u^;A+h@%H;i_np4XJ->t{%nRwUQ?|D!)xY~>u__%r|A#NZ+p{u{zo~~F zC+x*YpRsTrmQX6CGnq)#M>X_U_);XSq9sW4NmJ!{+o#MAKB=k` z%qb_BSDoNfr<~xGI>Dz-Iib(hSZYca#fh)p)a$>ZG_bLq3aqj6-wgX`LwD`Y_xUKq zIebyfIrI2&en;5AEL+)DDgP{e;f>s)yF64Q>d~dWY6;RT*~$+~=oO^r-+v=_-0x>u z+0ep@X#13v2lJ}yAe|6JJzp(zw*QJi!U$@w(;3nBfLWykr9d-o$T^)~@d~7r$m%Xo zvr47@_N`6aiT`+^AM}?arcc_~9sRd|7(v>yE^hUGdtcVKr>&NCtSC->;R^6IrqqVakw~N-;k8T?zYA(}4dfq94^_dk&VwJ9^INisI~13r^U^ zxAQsddv~#pf?!?JoXnUa_PeQ$NFZ)OUr{`;z_b~7n%J`EQF><@rJ!%I3paEtU;bf# z&@s{zqyHHEpuLdV?t%^7gAaUYb(g>U^WpD#a-BzhdgQ<=bLIr|RT?^~aj>0w>sQaE zMMXcP1dY&qpL}^Ecf()s_3UV2rK1gHdIf1Eq90;a0V$moQ%dPdhb3@@_9A_vw`!Z$ zf>pG(BHBnnidX6cEzAkEic+92du1bc56Fkl$D1PlmbBub3#3*?I7a3yV9~Q5}>5 z!QRe{Y15acUhS{TY+-8|N_5siDe3@iekAYL9{ZaW-0)4_?!ydO&)mLZ^U?0|v(EQ3 zW^YkPf>NOQHMpGM6-e#9<7hYZqVpZn$#>!s9lQdmFCHB2#&3MC5!}_2>);g-Z@x9! z?X+1S_*QdH@Cu04E?UmrvS+Z0Z&4b7Rp&NRh~i3X4|m@`@ewNnJ*DFKyT`fb@7O=Y ziiFj6mEsuy$SENb&--RA?mk%Z*IoziScdvM!?zA_2KMiVo}8A4*U{rrB1qwP>W1}c z3GzzN5eewPncF!LPwwb(FI+atUAkz9RdUXD;)m8Ho{kQEtTDL`m14f#2|XR1XPtwf zg}DxuLYilicPA*t_;lByRxw`?jA3q7>_5Ei|618?^7Ct~w)1{BCs;?3@3b?Pcl-Rf ztnD4&`qlgMVM~s2CrrD`bnsq1*TE~$p#-hX3EB$+v!!L0dC+vQx627$sT1tGj6jRB z1X>Q)4UyJ0ph@S9cjNXf+egv{b!|!MjHq@P#u(lg`_R(#40{5hLq~t z=is&6MQ_{|`;y8k^;XA{Irdx2T{wJ}5$LPJ919U;6sK)9f0@3=>5Lp%N1|!O+4|E< zeF=s!isMH`v^B3uzlb_CCsUF13|bTQ{d3kaBUt7ZU5!;Z9seG)iYKfjg!Q``A4Gv8 zr^c$9i<%DR6}-i_UV{$y+R(wj_Q1P0{ji1JpeP=87o7jJPYcNc`q7FAmPO)W7t@bgi=u8?WMz_A70N%i(1>>6a;s=bq=Qw}_~E15VVb@I~{4I#<35PUVHn4TL0X? zZJt*;e0iigp7STwzq(l%ai|X-q=ZVXaQLaMF@rBmEHomqirQg`T1EeIf>-Lq;PXyu zEwahE!H+}-uhfaLAD-NLX#S@86{CnK&|8*AHUqF&U9`YLZksL6Hg69+Y_NO$v~w)I zz_N?D_is77A-YOXO7-Q@RjI}1SkxW6ZqO0tDCacjP=Zq3b*L1+akue~K?kFdmj|O* zkM3I^FW_E(JGZKE14qu|Le0Rpop5U5B|cV#Tk*jK-F$z#B=aMc0{zOz=o7mUFD*E~ z`@>G>nbg-G8|0q86E(4LESn^{zD1>0(GvX5oD#y9dAx%3d0Px}k50Te(bt_&9i($Y zZGY+Jh1{k$U1T~I967{&{Kkb1v7(2tO6d!?8sc90evL5CRqDBehPVSB4zX$^%yT7Z z3G|W9{m@cFL|4A{3iE?e$O+ZK_&~>{+b?7_raM6?5R6++@Cu~1{Mul*&1Rtl=Xi9m z+w$#;8ltO&>L8u#;1#tU>9JD=ckn~=eReR)}_Kt-4p*ol^i0gj4gq!!qnvRU16bPJ?+uUk3U$u&K z5-amVrEp>|`r7VRu{JzS6v4! z%!#|N80N-q7xF#oy(QecI|i$0VM3Ty+H>m2i9V+HfU8ogGJ;aDkmaA~Fh5i(MB!@l zypb>+tUsDB(q|p9n7jFwrZQK8QXrr&t0iU?wNt7nf>opwKg=pELFNmZrI{1VtFD7| zPVh>d=&XZR>ICaTu7g+V#CP{z!fiRDsmxV}(w*y-N}aIp67I^Iud*^{t?X_UErErk zLEtVp+zIo33HRY49q-S1uMWTn{Lj|E!(DZHPaf&oov<(S9;f%2{l7o0b@1;oLyq@t zreXm9;ty9;YTG+c_xH+}Uv(o?YT7@hwobnUbDwU6O6{=orL9koylpmwN{u=B_0|n% z-C)FNcm1Qa!Et$u>TycMd#WBc=d}6U>H9X_*D66N_|fSeC)GhI5WIUuOw%<0)j``q z%yr*f?tt&foVpDZSCoi7QIBI3y6a%R$ca%f0&4)4z@4eyrADiUxIpqYk!;)jqUB#W#!iw#->WIbTK z4)1k&f8Nn5mH~8-u5`40$~;#cv0e@Rk|lWNcAvLa>%$CSsAFkXIxK-Js)M%UchVKn z_NmM&u3vzlg%yEBQP;t`gx^^<31K>7f0dTUjc2^k8d&p&bUgP&k1N_UuVpWK!ZSnM zUDu(f?y5N-SiDPE})AJ4j_b@-*_sMx0&zx5@Ed2Hwx8>tMc0=boz$^(pEAQ(Bcpmt_DQYd<-+ zJ83nnGkkI09y9PA_thFokokfjZA5QLWIC8H{a^_eMHyXR6GJTt<*a4ij9@7Mo2ulsNo?hRuUprLAmMreGLpoKZ1Iu5yU zklS&CFn*BkPUwt`bV3vw6QpSg(u_h*s1z*$p*sBA-oC{yZYLpV&k)z~sRL{M&{4F% z;!hm9!_KTJoUV&%-+K%72{)tlK$|eY=jVMaO?>1DaK!3r5E1N|32T<-2Tsj4Kf*{4 z+Y8$p!>45YJw(^*xQ{x*6|B9X3_`x3xjq+&w|y;Hp!9=RK*V(6xQbU1eV#C@Vi^RV z!a7s%DXiR0*>u(&$9T^%dP2Ldk;T<7S{PP}isEyBzK{3Ah$zJp#JW$NC_R^0rE6C7 zDOmN$r>o7l(|f*h>4&aHaa{}qJP-BipsNN?nC^)+N`mzVt!PgF1Z(0(VGj$(-4H8` z%os!9xe}Tt4WjC=tY^@90`paU-3jIkg8G70&Fwt4ZFtUl9Ej$29@{pEq-W^R&STpK zk@gJD?L4+^>Igl<+gGkM^B})Jc&Ds=^f-OOn|i*LKRxNd27;{!u_9d*UdIHlV{*{J z*>>Ir`Ls7L4}FSEOHv(Rd>cfI>SO!BQwuEvq}fY^^3c8{w(TetqN{z0_6)IYvlRt> z{5|L>t+Kis+j(r;v@rI&rBz16c1|6k)$;es5A;_?1V4gQY>$zytU`Zfgi5ic!YckA zh;kh4Lv%T&vQ6L~kZqIqgVp$91WGMPvHZhIYOogfYPTJJMX#TSPWH1)BX|Wmuvd{f zxb7FMYWu!qi?YnsDot-TaY1+Hcdqn0?wPRhg!06!%nwRI-w_8aHU@l8m4f2-cX~ z56xHC07x5=+@Eu`26x*r(hciAZLpBiwVrtYfjjSb&zbH#i+6WX3OSKZtjcsSUl6=! zsE9UFxT1cjg&6@LBPfNO7zN0se#Co&C7?UG@ytKF|)r9xs1(Lb=HA0jCEc(x6~0o7C{#MyNYx2OH2glsOV^%F z7@_fDKM108taKe;d(`c>T-~`b_e87AJmjT1LKHB+s`a}`td!tr8T`O}w_H6ZVScD3 z>PKSL08GMP80q7R`L^Yb=*|pXZzyo2P6*SH6JdoO&v76B_gyho zZLsiA*W3Bb1HbdxlO zVXUw(7-_z5k`wwy4W&SIeji8QsM2?^_;yvUL*MgazR z>rOCV=pdaF`kohU2f=r~azfv(`TN7mxv9?vtN5l(cY^ss$Lc38=gv7U-$&-Z;#t0_ z11aW<^t$)0=)Znk?j%pjTnS2nIQr97+_(F~3S`xytbM2-v;+iw%KhNGJ&+o?+88(O z;?M^9R#Hx=RkQ^3xM5@5JN_LbBiN#>w)2gOP{ZS!7TkfK>);a}yT0I!wY^L zqMOtb)j=r`)R*hf5@e}?pgto|f^GbVQq%bkOAWtsoX-hffs_&)wR1ve06%+alnd{h z#MwYaAYt`N3FfQ%Dx&RE=7$n$74_u=uOQ#^NBcMRmcd&v{+r%wF6TDhx@?0ZK$_dpm*QB-F+mnJ0 z>dOgz`)q};t>xBRHxQ(|6MXLqR*}vLEdz}%qu!nPPHKGgEy^HILd!#?=pWu!3<_8tN~jdmC{43S%7f2`;tGB5 z0T=k_#wTVu5l71)MdGyAmUct-$!9XTjvwwF-YW|CBKd47CAia`R=qYlUnxonJ_eYd>ZPGY45r9jZaM2ByKX})hhKHS|tZ}6OSLioCyMihQ9 zR^uL7%Kc}vrdaU`X%egcafF+BX_MzHDG*8@cH^?P#?jp>C74rAs1z-MzD!5kvlgpE z&mG}@v0ErLN`WAq>rmpw{YJRCo=oYSP^r0h9^oGPvOP(pQ(&OKMDrMDm9{`_vRic!xA=2iEj zGr=n$R0qo`*Absir*Cs4Ykf&O=NlE!!9DFrcP8H7akzWo609K>?#gY3yKhVjY0{Nd zZJ)AwrBe4l?dd^*K<&ABSiT=FYOm|ah)RcAr8=m)vI+^SiAvml#BjIog~2M)Il-9J zWA){9aQ{DO>Z^3Lead1*4-4%g^=}^xp1){T6Xtm)w_a)uGkCj@|_N>K+R~s=bN`-am!Xc+gJ@UqS+p|XS zO3)G3rd3~}!%9$fs81Nxvb}>ws1&`0j?RSYpcDwMy(d8I`)r&_>tf7@@>C+fCS#W zg%0M6-{CptLH^9l#qON>geRVSCJ@>Ne&4%=CpNP)8e?8{9i)q5#TPG| zIQozy2j2Rmt|R6Pt5~OtV({VXk1uuDUe4G)@O(J-4DbVeOz^zaVT9K9NJP3QUb}wd z-qQ1$j@W;|C(=c6^8Fk1meDnW+Et2C5Btbq*F1io%sDwz2P6v^_Df! z$~;QN(UOr4I+nX^+un_REjgjAB^*;>NgSPHjR~>x?=e4nnO`!uC23a0hnm4b-aG9)ll#XXXHRV6z>t_OV~#?+Ib; zL;EXAk&f-d@63c%wb~x*E8`Y+0)Ay+>Vyft?p{$6u_lJ+5of=OHv?l7isFjfj+ywJ zCvN#g(4hpMM}R=AKo`YLbDTBvV!yNPr1H%dCG^=H>7v-YxMJp6o;bgO&?j-Ei(=5| zy=PwJiIW?M*W07r5AF@m)#9_eV>iEe;z|BH*cbWlVb@zKrQiqk6~!92{9wjQUdQSU zI_`OLwA<&ipo98~;=$AYGyZ>`_-8|O@7iuT_x2R5c=b5ael)o0@^K${Vn~AyeXd42 z)RK2Sv3LWaktPiyZ6B=FTFxxNlczR4?W?y5))M5@OsEb@K}Rg}CaaJaEuoIi1fS%B z81w3A_rXD-_u-S++;b&ZYD(t>^HM3K`6MdOhWol?*dYe_v;O0Z^hw@L}- z3xafR6+Kibq&wFuKKoTEq**I-9ZFCN1fN>x1g~hmNRRr<@(ypvcPDhL<#X=t1oMRs z(nT@XqK8i?dxk#rC1@Yv|y4HUmIJ`A*zLH!ttDc{se?Pa2*V?NUcZG|_pim=|nU9eiG&6Dp-T zl<@t#-`PK<+>IOWisBAjL>;pX-fi0(Rek=ADk~3OfmN|=VmZO8qFv5pGD7LC_g>ol z=B_J!y(-@^>GSWHWCW#DU*fsHyNmmck><`Lq^U0_cm-1XKeCJ)JtDl}Mtuq4?^1gl zuha=fAtzLaT19;cVSexmbf9m?eki0n>rg^2}bn!f7?if=dy8MFxou#y`uK;Ez@+@J@-a{rWDeok#l(F+@wQ^ zZ|&*N^?pb8u(X>9N{DBD%~ARso-C{udN7Q9@t+nw{QJeI??uXHQktSW~Xe8?8(Ol+Q278~roW0i$9f)Cbd1whz3Is>H%BnU} zdPVa^dcNO|wztiG9^tWrc_b-<7eJVnw zm~SHi6=s#{V7?$a`=Mp7d!tyImNjd;#Y&~LG`}37u_E1Fhk8!BJF(5WYr5Na!noV5 z%yl0b_rQUmf0d55Pg!);64k*-R|FDStW*c-oZuB$qC}>n?OC%*_bPE;7w8qQUE3{o zZ@GIXsk^)aDJ5RJZM<9e7x{jlI4i=4tn-rH7GK_tzq8!GI{wtFWzsnv&K1t`JsgBn6~<8ch{ertWttf zsxP;SduUY(>220u&Mo;wlMW>)1%i8dyX&C0sxK$hcJ&--ludWyll?}ysTcp+qRU8k zCzvm+3hDAJq@>K57o=1Nqwr;Pgr{VrbAow6$F6=4^{5MjADyGC1oKro*P;0;(b;oq z*L;!gOz6&4?j8r>wprUv*`VB2%;@^QB;CzFcFH(k#`jnWemMf~X*{)QIAe=+?zvjU ze4%@tBgebr7p_@lZBPkHfjIHeiS94=g>PD{}y1VOOzR*EBCwN6IL7G10 z1hs3v-3hIIe8&L9AOC-2X98taapv)oGbnmE+8H}yKpnReOc+6E`muIDx_UryK@8%) z#AOsU?h6qIb0w(6aT!rZVJ1olh#Fj?G`Ns{aT%f}qF@q0Gm23`L>Big!TJ7G_kZiF zd++NreGZ&DufAV>OD(rUW><)LQ8~&)m?t$qRTcGSydodE-eIIB(x=0 z^&M_|^us&W>R9DkBDL#skw&a;y)vi65!!ZbRegdv+7hJEu6ng~#(p>*+7CTS;Cqmx z29}M~T8iTbbZ7$c^5+eZ6QsgB;r#Ez#iFve*6C*yIse3rgU`pwj>){f9Vg1l;P>6v zPeVWO<#P0Cu)VKqvJEqz6g|)GKdE_lS@(I_t@~)A!G3efV7q0!U%%3A*O-2yW0$wE zk{S~$mNwa6bbC_jydmm?COJuToKvDB^r)cEm_>7YR;G!L&(;)m(01scJ|Ee!Lj2O5 z96)q~w>nMQ5#~%GE^uqeF|pGlO?K_;7^(5qM_-bxa$fdo(^s;p_V?5e#spF!RteRW3J7)@*1Nq4ZB?i>5CuP=B0-M!eBlm5?5J(HI+qX$kUYVsEATsM$Yq{89okz; zfrwgS656h|D%2VX>MQD?huRXPsXHTFLgyg;r&F5jrd|E$!$@~cTrz90t7p(0iMl2z zrS(OGi>{X97*$Fn!n){c+clxbtGGVsp)MEcFkYoK=*-JYvD{K3vx;$tlpcK;H&=8O zKHwjuG{Kgk=_0|hv=q`DA2Y%wkfRAo;dhR~8IefJS*7bk(}9?N;x3imGM4Klp#ErAZw@Z6Ylg*dvz4_cBRMYF4cwB1I_=N*yOI@mX%Atv-edw3_IZ46&ds*YJoW|TP^<9;E&+DKRh(nQwnP&wsc4msk0uy}j6lM*66U}XO@vskOaIJtzNB_K#cgE3D?D?H7erV96DkgD%TPiea!x-+RUQTw6H zSYPy>lZJ+DEOGsuGf312Mx1|b2BGh9@&xaG@&w-jlGmZ^ zt8iY?d%%bRa_sT#X*W~sgy{-A}81$%Ej-D0_0+=Xs?z+8tW)mZcSGW zqIn7Jg_O3cpu;K4OX>bmAQB*XOEgihwFeJD@APc5-A%qRfpn=K1syC`)6RT)?g*Bp zrI2QS%m|l2jxLu{n$Cy>DdcD!n&`2uV(-|d>cgRVg6lB&ecWFL+s`(qYci1^y3Vz} z0s$S)Dos!dI{XTx*O9%$)b$Td;0kcS?byxmS5Bmj`Sl0CZ9YLp>Jjn=S6^1RcMk2i z(elpQ8B?bZhJL5VE)dz6D1L;yYNT0iDUpCh4#z$%g*5$xT&5$*6G!!JvOACWo|nF% zsi1@9q9oGLAuBmDFTp8n#7ax8{d<$`v(S$d!J7#w~CIo5*@k*NqejMudKGdCFjp!m6rNj--jv7){3mX_o_&!X#M zrKM;Ih+|J_w)u9-h`16NI0wb&I*+K=C)yWT@U|82fnMJYr>$Rl2CN|7! zv2Q%$3DR8?XP-UFUi2eRknWoJx96;V>QR4>LpmZ{bakwd=6gS`x%Xe}Gt<+1oEkX% zVP>g!Ww;ZA-}y!l$i@3OE+*;T%?n#by4@ZSq>BV|Af<@~Q(EoJcXB&F;zulX_>t9Y zAE%UXejNM8D7Rk&`g0Sz?G{KUhSQQAp|3Nw;oafAe4o%dzO7Bf@cR|xd9V|;rA&uHB z-~R}E6KQ$3zp90zsePhe5zBI!Q1G#EwHp*e`DP1h1mT3_o&kWxC9F z=eo7NSVyob-jy+?BJF`n=bv;uzqZ;r zAoYV$)KW+v{l6oWRmVwv9A83E3It2?R!MYgj4pGu6w>!T(&F~mj5+9|&xhPTt7om} z7OcJMxlfP+ap;pR_Pp)5Un?y!{dP@EPzoin{~L4u@1I>Hd-OH#(K83-9{I@APy+nUs#S*CA&OYI4?~ zRmWXRAsy-i%q>E!Ta9&wP*Yk8Y1WM~KfCpw#c#@+5XP?e1Zzr5Aw6_Zo85YT`qnzr zEcNZb|B?1i!*>nh3Wv1lSk{`}-F^G+0~cQ*KHyn; zgbayQu`R(rtsQCJ10v0m;73NtRW0P?3F?aor$d*^yGGFU3Fbg*!1xyX>{frzdDf>R z?Il%px1GQ|T^7<>2kFQv=LgHm>*#e`t9|7JrNg19bKdJCt*;-v`eZsvi8JO5wcUU0 zuMDI!B0(yzBeXH!-bSy>?e>Z1u~qt7LVcMIBuaIlT$DsQBAgDE1u1=1<25lO5~LtS z50Tb%fpBPEik4`75HyMO+VrbE+Vil(2ZC{P09w0CFo)i5=`}K|59)J*s(*x-fYtI3%D4~|hGP>9Z(ACFo#pIa=lhdZ8@XWj(OFOR|O)|!5oyU3C@EuKR`Gg z%*hj+2W5myV88mlzX+~(vvlzEonl|&!l5lW)%`*l^&hLIEdeajySrFR*!0ZAF=jV zi~PPpsSb&5Jnrh929{*ZwE35Jd?S8*nVu=qez07WM19VD^YYC-YkSLiHAGPpyFPF2 zbxXZf{9Voq?2MkZbJDX>%+ci{P1=}C-@0|^-#(pK+x`~ZtLc6x&)U6j?O8AT z85wKSm|?g!s;_k&tWo{&`|9Mh1`TB3_C zqX-Libakv^LQBzG9feFs0%iGfZ|K%;Us>p*OS(vC&o#j)6bY6EeVSlB6$$3#3F^)W zm&i*oqWHbkD%U@>CEVu%K^jHtJ$ikn$gh532LmlMoHa<-;jnU7!@VQ=S-` z46{AX!_H5-)`yilNBEhr-yg#KfjTzqI@0cP42U&{-q3uXw5dVTwxC0wnV==Gil-rp zegv!heFAlm_5_}1kdt9KKN>7;hYprx%$2JLRwl2lwl)>~Xt2E=9A?K|nD-o7?pa-J zomoPJCGFcq9;fj^M`$B40-y)YUio40^-+8{H`A2!&*a{v@z@MOddN~Rv@RQ zI&?kH7(Y_JE8=yK_MZ3iYnD!jCUly#C(e`8bC0Iy;k49(xh?j%?Y$2E?ucm5E9@KS zr-ZmF;-3=AW_>xU^iETQ{r&V-d3qVE=jk&)SWk2WW-{E5!u)|J4<{Ty%6{u%obmek zUpICfCUHvMD6TDqtFvEi^QR5}cHhfMdoA{dHL!aW5{2k8q9}=UL^!K-tmrKW(ynN(x4TwCdv&>JB|*pOtG0GLB71jN zx@(kfSB-YfhKhadSN^OT&#f8Lt^M@H6J>P}Z&yXRq3t%>f4X9*oxj2pq>Z_1`sT%d zmc6?PB}A|=&|UcvY|poLWAsT5JzjYoc!NL~yQ1hyeZ=SNcm^)$$UQCV2rZ?fKz&6W zT8a_vT8EZmM7t)ml&+70RSE1v4eFlCt0jKNS&9q#T`nZ=S~N63PfG2v<_a$G@a={!g;Qxcnt?Juw#^cutS|aPe^DfUSI1H z&JQi6uhaz{P~og%+_mjUaYkftlv=r_?@#I4( zk$}xxqWvIk%-oxQGh~Cztxio(71Q3B_UjLP(jva%=$5G0$kMIgDy1HG3Sfw?XSw#=?I!HrDwvy_uI4lbUr7$Kk?TJpA z9s6%6gcb)P&~bg>{n0&ZiL4euD%4=3UAUsv&ixO6RpamSM!TbL>-bFG%Duqdf5gPs z2UYFFi#kBVRy}`$a8nK``z7L++M$R;Ap#PvVUIf?&a#>u={sxGEW@uy zvlr2lx`gXdS_jJoap~lPt@&-44oy%B1nWQZ!zGZTTM1hRerFpi5{wC?-WYUWIgY?k4y zD$a5Jo$#j14zvB2r02p>=lFA0X@XMFS2{y>1alxoOSFZqXzFlQdBR)5S_46S8IjN` zt%GNFwZ5(imJ5P32xH!sv+W1%^;C;rv*fu~IYrR#X#1BPmlnR_8VLRUQj|+Vf29-} z;`-3fnCP{4?pbtA=oNa>Al!4QHNL+DqZ+=k468y~&wVz`eZO+3+;3jv-qsUxVxrNG zy>GOgdH~+z^__{IE}kO9ZE`n^@hZ%af)waaR*tsE-UVX7cI}mMLbR3WpcIIfK4a{P ziBAgAYvMJFajtIN?O*2{F={js(h37IVBM;klv^-PEd#U{0Q3+=@Dw zlP4IrBEg(I!MGI(=H!V&bTdDglP4Hy=mZZrWID(@L^o6v39_PygW>yxQ zvrqM;lnBq@#01amd#m1+@oMs(`SB{8&4)go#`nad;=`xYH@b7LJdY2b!a4oMEbWOy zWmWsXruBIp&j|7Tln7EW!IB(d zX{zrxFG7+pqA40 zUm%=A&ffISU6)(eDp!L#y0l%>@@&S#?+#us%B^5bq8YeBI7eRRu5(J*p?;OOd! z*)md3kgHC9tktcAW3VxM)-G*+Mu^7?V|y6Sp@TGZxcW$tKn~lew!M@9>S~Z4LW;I` zO=v0Fo+sq34$=y%>D54AmvkHB@31BIL)RLmpo4VR1mgpObVQihvsSeI;PIplZv_qM z=Um#qzRJucmk6ZN! zM~^}fcUAo?vA{{8zZ}S%0 z{(Fusnl~Y$F%j1XOC25(VqBVivf46RWGraxKeOqg=WoNqKb`wDP5zIT;{ValL@XMS zW@n5f+MVB6h?Bn#5AUzIF|Fcf>mT%`2tcXUt2Wx=(R5qvDM7?9M3k$rG3~)3iR+O- zsp7YNW(%>O{_)4Abey1ce3!rT226*MTCh-v?T_~ej~}PX1Is`^XSA|Ho_*A||8i~d zQEW(vLF<}^N4*psZnHJD!J;i!Y`bw@d(oc@L#y}vY};EZ*crzYL&g8TW{cIoThb`; zRdUntcP~bV`TvkWDgAu%#3!WVThdXSbTFYU;b$#{-?N26d`CnfBJOXWkg5p|K>{rl z;!cu2K-7lu+v3K(mB$fB!&b%*hYeywje?GN;yo*)=kKH=KM^O0Sa|&LxJ4DC9I>SE$Uj!@e22qQm79plR$k)n*>@?yl7=d zT)M1uJb#aqS=%!iFKiIhCjPR{k|fm_pnZ9d(qY*r&f3LAYnHMPpD6F>Xa0Wwz2C_1 zsa{de4xk+Vqjc^Dy3{zHZsCfKSoRCm3L%=kSH=+&@3p4hQ=RI=G$JMt!68U2xwn)f zIySK~o~~%=@45VixRQ82{SP8OxU1iK9TJQeX(8gro=-0y(Seru&%GYBOsnFlS9qFw zhiV^3$N5sEF12R{YXdV$E>?M$KLNO8aKn{ z@SN)v);_G^NM~x`+K%zmTIJDC`sampN%6N0lUvC4 z$Is&mJmNnKQRVK9X*?Dd>Get+5-8O#_8~{C?is)T$SA+esk{Smp^nw_Blz_)x=g;KTGR!ad>!Kv1c0UxR|b#K(=Reji1_N zw1u<~UsTzcb}*s?3H{8LAkDGqj~B-N6>E62CPN+ZS36rv>fc@%Yc-aCrFrp=U2JkT z1WKJ8(#To#(^M(-ePoxR{t;$398LNjjbL@M^+Z>W@`;Y{-18g6h3pyr_L4RnT z2=S^ksn(UV4wOQDITMBySkkP>5XZjlRa;x5e^Y-2Qlf2V$G%_ni8&L7RF&tQZu{Pm z@$M`$$gvwt$VP`@68W10rV~7=lFcHhulsnW+OqGKN!XPwkdg zbr}A-Bl>KX&nAI7kj_GQ#L+)H;}Uu~mGa!QIlhyref*Ju|6hM-G=N;h z++C8VKUuWj){^?S7shzelALv*6s99v!lQIp4Hx!zDxG_n^Te+H%j?eNHF6GWul}N! zld-m${9al_(y{~%VqE(-9J^#zS`z%jV;v~N}fc(I*L#&W5<>Ek~p}l0e!d z=Dt4IXePv{U)!@Fk^z#`h8l-QOXzBo9bNNR7E~NUwy1o zd_rCMEQ!bua|Y15avtZx+m&4o#F5%3JWhF;<&FLI^3-Cs|D18Yv131?*3^<9Y5sT? zh9J@K$HyIeL-$OpJP8niQu`AhbL_HDNU3ZIqC5+wiq~uFOntaj_JmSFwH;H4DbIFZ zLX?`l>Xw7VxV`nAI(^8C6!=hWhY%!ihJ%nf_dx>ZD<**pabJIXz}ROzTxKzewOxnua$-lmAL)(in${k^d}2ZdP?8$v(X#VK6!3s zx*5WZBG8{KL{%b+lh-+e2s0#;K(B}p|7>U!K284J@fXIXXXoE#pQZQ1aEo^VhOZq- zd38S#dB``q_Wam%y{=_%EqZ88V*UC?;e|vzP6X;eDWrwCeMRXodxd8n=$ww8RP?uc z|DLwkPkka6&$F8nv4V)JM4*)R$+5T2BrY#69llP)vjH6_rMTMQIEx|PaaPn zPyR%!d~`ad7QK$PsB5#w`-dZA$*R*&9Z$cTh=~=V)6vuD`~AoxXcDzrA5Z_0h=%2& z(=lG8O=8vMQPKlBdE(|Yj2C@~C-c5#vmdrcp^0QwyFW*zCz1!U-R#Y2D22X7lgR&g zZ!7h<6>61dc?dme=!fe!X_U==zstv1$AuQXtqXrzp(44Lhj8V-XzmD`TU4_?6CI?5 z_@Q`nYvjZztJbsAL+D9EeWs2tP_x5Fr{rk=%k@d94^9Ab9 zJR~yS*eZ7fo|rYw!5sna_tgB2e`5#|qRCzUJczvK#q(Nc{%U8v^N1Zn|5csI%WPi# z)_(GB2mMU6=S6unlWNI5fm*_;Wp6E~7JX+z3@(w^O8c{&C7RhG3_-&5k+r=&uQiFR zif?LKg;Gch@%7^|p$2aav8vjM@n|om1J~_B)O;c)bPo|#oWytx;k+VWu+6#4a|wQ? z{G?CQmNDrqh$vtsreO#Y=s^%-P5YSitpkQwvzsQSVMy!F-^twVcVvcRtlLGSVtK7Ng2Ukh&`R*IHHenxy{_m4NF0^^4 z=}?C$RQA!!R;9f{-t!ut-r9sx=>Il}(MMOM9U$-d8+k+;N}}~FDLphP2#pzF`<~ZhJ@Qt8$>B?E84TEdZH*trQ;21A3FvN3HPJ+fl}yo zG>JYX^M<=!YZpHJm}(!|D)b<5&FAmAbqTS)X-xVIvg(gUiD?)wdMQj|1+|Zsl)Fco z+UXcCuI{-WbFOn+p;-pCkKr^L)TX*?cqXXs@+iY~7rhBWy!LqSaEGt0&_b%aSVyN$ z;kDU*`{}efj_H{wLqruC)w&RYAxQjnn0!ke&)BL)-9f}$BHBmVhxV7C6t@*^p%A+% z9ZM)3cTqafUQ8|Kg3?2SjogpI`ij0Kv+iPTN2${t7EsD*U#jTLP|gHOEqHd0BnYCi z*lIJa>Q(C}kB6qV>S5XCqeJLn!L`$pn9|Zqk=}Yf#j-9kI>*!KjPdfbm(Y#lCLPk# zBARviAR9tUF|p`xxx@P(gqBkJqz5WM^lE=RRP*s3)|86TA@t&)?LUKmMu??P9S_x~ zlvk)=rW^^3Nr;puj)!tT(ZkA9E;@v9A%Wfj_tX*BSTaaHrpMw3|ZTQ&0U-X=}}=O|ARC=p4eh zkieLP7`nW)^-OY8>zl7ShcGTAVorNbrSi1zDRt25&dU2zw6)^=*bsW7&;zw-Onv9_ z&y}3$fs2Tub(D2uT~n*ofzF|p?~HWzk7$@B9Y_nYVnu1I6A=#{>>NVvXcfjJ#O%5+ zTJH{>99r69Nhs#%Os8Axd{W2N!84pX&5FtA;s+haKJtg%v;^10Z{BHV-I;hkbo;RR zA(Yw^p6V>BLuWoHwbkb~mh^&(@6-R^J*BU8XT^>2o1R-4sqL!vRrr3k)9uC6l>fT6 z(>g)c_6LYKP*xErRpGVS(vqyi2};M@!IMMeXE4%%wj(XXsk!Z}6JySYCJdb)>P@3B z=X=n0Op_2VQVFi_6>WVLC_yYWjEQ1{88Xg<)hN?ij!Wn@Gzknr0)2o&R6n>Xp7!Xi z6Mx@oj3HC={baMRZ|7Gt9rOXx+Th5l_-}}4kXyuK2omUXqBAhRj|!D4IKX=4?alER z;;qXk{l<->=Q`+fqBeMGRA?y?Z%)`8k0D54|0q))`C73s*%Is%~&%9_zri+f&Q7 zcjnH&OFrjJR8Nd^mY-Q6rEuNm6ZBx}z<4V?*2(GCY-OenkHzUfd3s0N8}-{wXTMdR zrG%MR{EhmU1m{n;uNzCE?VhWSjw$P!Q=b=O+n=vkUs__WIcXdWA04u#POv;G{!xS0 zp`QC%e0K+PT9wD{aUE6%iD&z+56zu9!re=Al0msY51n5!!W}&FRcg`6q~kf)UDq&B z=BuxM9C~2*2)9tp*Bqpkj_QYI)UyJ_(V25YD_$7kUK`%uL0S=OUwdgy(5hNbjSp35 zF~aS>_jLzpMfAz|qjr#}(dCWM@LD6>GUeZNkXGrq|Kp=;8wIRtezmf7;pPbU;iMi8 z=9?^!@QBxI*LMR%;-4j~$F7ZVi+AqnEWKLJTJ`q`_vAm%Q(mP|UX9+lYF+&Rfl{bX z>DY7n*@xGNrm!QQD;WDPnCJeL7LXJWYwqx z8|yzBAiBg|3&kE8;r{i;%MQ{iugV?Yvi6|>(WBSdP~$H~xSjUD;vlWcBP}#JO9_7T z$)Ql?jU!yAMjr=hMeIBNR+d_VwGV08mOT1N|ID4y(+#ik$Rj^EN7dgcFAL5M7jK&D zcn9d5HuWVq1I$Y3SznNd>_Ku2>f@1BmZ9UZwN9mOCu|C~kyhgL(_;cd76XB^OrKyV zj5{N^W72*|Za3pw;pc>=JPT(Tr+=8{RQgQKGR)DLBUk@C3uiJo>L>4>>3E-3U>!F7 z^FJnmQm9*sdkXR_9O=;#w9qGrG6|Hz{Yamn2hYNJBI?VTz!21dX_7klhsQcFg(!uz zNnqJxnd3}N5){AEfguI~^SJ;4AxNNwno#ROY&i>}ugH4T`s)T3uHiHxtuoFIo^oE0 zBN|KSy$LA|Kj;J=`1`cI0=YJX8tF?`$T#7vxDWN$8cOPtb$v86w|E!hBZq`Tru66zVgGFrHh$USiDr zXxA&?*(lA=bjkwuWpVbBr7!UmOD7;;XC3!BH~oFf*>kFhoTDgNR=_F8ozd@q%jcHK zC@iIrHVHhpP;E~Ex7$2LU`(3GT6b|YsIXPlpcI}K%9%hZ^vUH+pcMMyvJf8S82zKs8BRK(6w+qyB7rR!=>P%e zy0E2|n^MSK)KK~Z5MZl-K&idGicuYt{mTC!P-^(kx4YS&ZbKa?HGO+YSO0wjDlx4> zsT#G)x|J$_V52lYn*>UI^j@|RpPqu zjmnlF&dlB1o9}W@mYiyHgxQTBs)xf~4f`vVBg_h0{*3%EbzrHXK7+8n2HV@NyY5t~ zaGNZH-9>V_`V^WZJjHnyrV!~Y1Or4-UO~K}IeA>|%gUBu$k2gOal`Vs8J%lqQ%le) zl*&@C$RhrD7RHNKU29&z6)S3FqXP+)LVYTB_0zL3-lyKr>t-ygEaOF5%6gi>xKQfG zfTC`elYm5d7RHNGNUPYh(}BeBuWoY}-5ZlNZFZ6I1A1p3sHA+_f^emLZvz48G?ctWGL_Mi)AD$*~?t}UU zcj)f6wn}D0V7%yizqz@mTQ!>dcVWxeJ^7Q;fl}y|&%d>&>s88*Kq>Urza0Ovd*KGm z`g|QIg`V^&D_?c(wb>EqiN{sg;0^=aVs~-CuN@~ zkd_3+&mUT!!(IyRU~oFbI7o*l0U}Te%Z7GC7Lm`!D$F0$XA)Si zX7>BXnYrOrnR46_&=#o{#XffJSMPp9xT3Q1(4)8B&|&!W$F$1jmN`~|bqJ($Cc4ka zdpHFx}VuG97?RQkxSJD25 zT<-qw?*0#=)5hHHxco{AE%bF{u?piw+9$GDg|?%G24VCi*nh;O<#G4lugYH%6n|!! z>wT4{n&owM`D4EMIvA3jw6Flc$^OGeL~;oa}RG-{Wr{0X`k?dM3-45+^g*i%6f(L{~&ta5>lTMWpO4@YR|Mkoke}r81;W6aFjus?vtVOUArbkg%h6t zW<7==q0`soo_L%7DxOZyHJua{e(d`E2B;lrlUPnVE|CsSkq)6p8)^N$(!rAS@i^sJ z=4+4DbSqWKqsjxyMoX*yihIkU*GsXKNWYNRZ8A`f;eyAH3suX=?7N(Ic#Eyi5%%mu*JDrqW7GZ4xMjxr-K> z1WI9Qvk)F}^v^6oBrqmZ2gZd2_o55R26C6xgpok0F_R0r9gg0SP2EKTrK(LY>@I#% z_K#>3>A(;qt}Q5(Lz$x`D1|gCF?Ar(b7v91ZRSj%RQJnK?#~j{6itkTBEHmrnaWPiLK38A{=tN`J4)Ls`V~p2amo&yaBTa$su#ch6ik z<6ueo@t8mVosol>ObgQSTGB62hwU%zRx;Nkw(xVUC zuD@65fWE}&8BmIKa8K>m5{}a2GRGMtT7}lKaAs{s0%wy*Q_AVD+d5KSwc6gb0fuxs z73FT5u3D{`aw_vsq|6&&2om$|De8Md%sx@~YHTW|R(~HUbLbPX??Wm5y&@i>o@YLt zq33vY>!e4c$H_A5^H`d57eh47@oqUn9-Z6oFv}wg(fHaw&I3D2NvWQ93-_N7@604T z77yycGDq4U4YKql*ox3Xid~gC|FSRyiHn8EKYI3d(<(_|$}zQAHmoAEULk=}m?lX? zDAOvm1ocV6qo4H8voKFEUTj@L)TXia^=$eQtd*Dxz7AGnVF(gvp-)Jw&|Z|nT##0U zf>v=|!Zx9wRo#_ZA}L2pPzv?=R?&kRKXjjgQrK1^8X`JS>fHx!b7K~$x|=N_b)df$ zy{C`eRoUH^D?8sSdW7@eUDj>=b3Zu)Kp&8q4vZJqvfC0bI(fZvlFpexsoqH!9eeA4 z5h%rVdP9#&|Dgk=nv}odG&@K8Q`E;ut0F(h-wtbXvm3Y)@|pk^p5u@HPmg8XkUs6jLEbLZEsofH)qLvDz#=hB055yPtV?f+BMBOFyDk| zOX*lgV+d=*5G3?7TPwuHf7*p#8x>{UzrWXm7{Yy5>;3o0J_;@5nB~Xg+K#m(@8aK` zlb7z5W$@kXGtU0G)v_rMBrvr|M>Is*2Wr<=k%W!3Nni*PrjFb+M*T*Aoylx=IyiTc zmO4E8N&ieQXXPtxea|NPNF~8Pn6R)7-q=>wy?o;}nLpSbb0+TeqTMn(hD!ozpI|kH zj&g5CyW=J)V#1(k_v;VT^Zy`Tr@fnzN~#t;_lrE|OyG`EIiPbUdXm)4M5O~Q%$Yz- zkj|OF)MCCFL>N6n2R;^V%xh&icg2_obGd&_&(2eX1bT##&RNH2Ny05zPw7C~Cp@lK z7&7>na3g)K$W})d0{2yr_I1#M7seD~yg3u-t-*9)js$ezZV&9^?06=(E8eUqbJ{07 zwhDJfDqqg!MkOezocD1s1d0Ef4xYW}T^XcJt1twKY;_Q-{8u_&+{dZ>SswTFitP4^ zP>SbQ-CmcYfykDSI*`EKo<`U5xy$cSdsB21F#Xm2ir=hIn{}PmV@YB8>oVuwPKXZV zE9s}k4=+0@&Kr~1L^|SV91Kt07pFs53TqvAP-f?QhlARROCb5jxoa4wR z^In7dQgsMRVX5iTR6016J|5Q++&{xH1NZTybmWH@#&Sj}v&^+P>%dxqQrap%evh8$ zpBKh>dG6cmtg4Af`xHZAj0>fZmITG0RR_;;b!|6^1R9z7@6VadE=T!$ty|@8WJNCt z9l}x=uYOhpc?`nrIbbQhe#K{&3dI(MeS?TUQR>ifF@fpO&q~J^m)nIe%qCx4;RYL6 zhwd5Pr&HYvzE?dQ>&R>$5f7x)q2tn4X`D;>i~M_j}+p)FynULBa*UA(BG?4$Jeis(){j*O2A zuir5%RfjO4+dNAlEyQ2cyL}j`yIP8g8+-G*yslwFf3I{1{jDO97UHQdtxyH> zy_WyHP6H%#o9B3umP9%e(h}%7b5ALdsUz~eZ&f;5^~E7CR>v#pyaoMqAWd(qC=R_ zy#Y%hO*aGm(Js9HZ8dUgDJI$%iSk>N{$A;zHW=nMSi4o1I32Z?gae6L*F z*P{;h6mh%b)M89RETOvFoYJwU?#5IMK?3zjt0HMJ@8ujx=j;wg&e>&qO3&lI1W)PV z$vmV@9e5HDr<`avuNOFND6K*QrEoQAT7@1j^m1XEe1aZK z&jflSkTwaF!alh2&b!>a4b)oQBv1-_yPOG>!g_2H?8VulyivxP3HHzM2}7<|+8?A^ z)T>0yqWj_4J{>}jhE&OKFF8Z6-z8~39UkWu#)~7ej!Egbn{?cH$_l+!VcU8g!a6Ws z?LksRWEViTS{=fKer72>5-B2Lm$XXj;E)ota=BMqs?izsMdal#Gacyp$26G)>Od(y z&S$j>wO^#ue^*c3Eprspl(P@Cr~{=?pHFzY1og}UN3xuA7k&EYKFH-3T%+bx zm~W;IBy#7;?au742&8i+cx>nS9Oo#~CV|>{bMjx}#W~IA zeX^Dy5@^EG&!yBKf1QMuevMM}$fv(-VUXJK4OU`(bCvoTW zsM{nk1PQdzCs>W414|8QpYWK#xKIkyB#Fom&%&C41nTn%se|Lv>!ky$a=CljtDXVd ziX=#yKdNWY?E_oUmYJC|awV&NngmMe*`QIcvgkl5Y>&PUkFCOV;0glMP}9%C^is7H1tCmzKiySr#2k(1-F0*MLx( zIO|uk>Ze)en4{SeUQh>0nO0G44@GLbr$b1mQNxiI;vTwTycfN1YdhUAjyI*_z30km zbw4@nxYK>%H|foY8|e0LeH(?mRUGc`#+$%}I9&I|@RxKO`*ylp-MH%-Z^|}qWA8|} zW*4M)_wAyax$$moBVMG%b-Mlgce?$%E8YIB@5|sj!toYsya}A{5Tbjw`E7!)(LLM# z?H6=YHs0low7L~|2Kldow*upR(dLc4|F-OH4Wu<;GrFZ2@1e%KoRKD<%x|O8JJD^^ zohNTj)At(48>ZpLRvf`(!7gxI^C?=mHa**25ufk+wo3e z+IOZ~PwUXSb|T$+iuVYkl+vf}6sGrc(*4wPx$h0!PmMQBV=nl)8?F|(Z5r=*MVfBY zrTbn_kdHSUxbGEjY(-j#znjN|deJ&(dAxahtGee--Pnq>5QQI$NoS%`ym_-K-lmGQ zYAf7g>B+d!8t))egu01Oh`xtb#nW5(tPgIPcj=)N-jYW?vQ{zaTpo*U^Uh@^^u5k_ zH!{5mlkR)vGEb)aUh(z|l)`(9WqE|Tw%-@Hs~Dy57Gfbv(p{`ierJVV2;BFIQh1Xq z?aR}>&Rl};(Jjr!Ewpg+Eao)boJuw z$`bUb@T9K~@2tVQQB_XsyI83&2~l5S4WPSN@qR(PcMxw5rS~+@Et{9<&deTk%O?J1 zF-qb6nL<23_dwEn8str$_?M+9g?B>AyTat!oNjwFZopLOP`6%UnuIt?_gPX;9r~2+ zv&0*k@McZC+mddsr#mxu-?cG*E8Vh*cV3|s>Z7wbbdx9b?V(M98!*v!)JG>8e;XB| zKGC8+(YOZ^+XUt`y?cOeV2dmHbIK~Z@fOF7N*R{zwc2Ve8$Hg!H!uj1n~00!OU0!J z2plsETI3~S$)$XXfUyt943lU=#H;HckKWeHOHvhL`;}eJUKN$pcM9Yv@=4)w5Zal z(}+MR905$?H%iC;leJG@j4O3(qdAU22zevtXscTfX<{6QStJ-v1vr0B~0*N#q;gY zrd5vXCMtzguyBV3_ z+KvQ{I7kaYZ;eR*yLgGUK?3^^q^VD=xGMdvG12SCkq%5P_UC5GYm$yDcYYSjbr(ah z$6>4Z4HfF1ZB9p{sLCm96~>EJVNA4_Ld2vYOH!6cQm*6LFzOD+p54ItWAQu(`!0G< z16x&leHN>*7exz&h$Eub-(zFB)nZ(z!|W5g6H#h>VcD0Uy_gQvN3%Z4-7@K|V-iRQ zt_KF>UupI2Kh)j1Ig#%ap^si0d`3D7efDwvBqpH0+IizMt7Ge-?t?`~IT({FbB-bV zGN(82(ap?gp%8bGjviNGlyBCl`&AB?vd-qZHDrUTxd9B1^r(nt|mn z#3V{b*Mnyz`zs`HJV%=Tc8Q3N zrDIbU2MA0D((2Yzt|h^HYx%p(+9$;3^TS+z@0O}hN0x41MPsgf6OFzJ_<^hSY?|B2 zJASbhC>_5Yo>n(R*6|Gj`i8G9&$JI6xow1d=J^2*-jl0{hOPdsX9tLRG37&J_Y8Oc zzBtfX^IpSH?MuVmIiJuQ2Kfg5aHlWlXSwecrBI)`Q?}x`zH1u?bd;|5PyE@Bhr1_g z4R%nU(y`~QRG(J&Yf_JHIW~G~%r~Rg7Cf45xb<(n7XvzW3@vTz zyQ3E_C}r!mBI~a8u@}3xL4ZK1n;Cc5neBt;BfDR@qyB3F0;L`}ce|}ymulxteoU>y z?Hp}Ksn^RCw{=^g)i$M^YaQ$2-@5sLES4FBif(1ZPlxp##U9Hf<1%Rbw)GDy7rM{R3uXqdZZ zPlAKA5Pe9;eSJr){v?o&GljcZeG3h9*L4`_AgzeWwv&bEIDf1)@W4>F=&6wo(rWfP zby0`9JbUFl(Rp=Z!@Jg@cZRyldL=qY(>a;DSEc{*^q!cfm;j$%Os5oUde@=u8y6BC zbN*0;X8O6uBqsk9Ah7l!t!8~z!#iVm)`wPIja_PeGkmBU{xH!&3x#;H9L-B=-=Ff( zRFgnUkXG~DF0aI{qg9?!udpsvXj#nGJ*w=ltn=?ISVQX&gFva5M@QMZM^$xq`N$4y zXv{STl=^U6VLP)I{eV&&-@9i0nE?W&N)#?+>pobBFcBr@6khjl1yct~{nf63omum_ zN4PZR^Exd8I#6o%l6u~e(-Zt=oza zyneb~YFh190piUU|MKhZp@Dz+X%d205sjXlwT9;JMmmlSxaqg3OFjSg+lnglJl;F2 zodAJSs83n7sE1p(b$~$IQJ)$O9$Pj&%ZPK|b7gJ)UI6U1>D_0Pj=`6nuHQSL1ILWZ zdG4|GE%rieBI4*zlhzdt5FMhb*!t%B`lX}o%r_$NsCMT2EA=`B2#gnLAxE5IsTJzK z5Fjw#eO2$a^&1$3V1Li(1xxC+4-hCdDE2N}zkxv!Tu2?PGBHAFM~t4sjnoKgz+jLT}U|$tx$nuS5PH z!z!$O)8}9DX>%QgI{3Nzh9NG$Z4vdUnGE-yH3B*?UZmBGj0rsp*S9_4yE5=S9a#x| zZ&Z*dS-B0LJj?ue>_Nx3j|AUJqUZDEr=~e|_rn)c2GTgf+I3uyteasMJ(u7<`}tFL z>@9v*)kwEwwI}KKBq9n9%@F*fBS5QDR!?Do;$Q|KeFm5=@{3h zVrs8|j#IBpwr6B`?vIbYX(KHJt?I)4ceaTgAJB1b+1vIrwcG?@EyN76>WPm}#9j;#pKO|LTgwvMmrHc8F{kOTGl}@7d-0Tf z0phvmr`yT165OeOcC?YEc?%KS2Ys266v*8wg{Iq+5)#}i7oM?^7Gf3=Ws80vdp4ls z=CWz_u09E_bLCkZX&MKK__hDml%)Y3c^;c)_wAJ6-d~`bjkGF{?j;LnDG&SXR6F6( z1oxTyyW2?1HBNZL%-Glw0jsu;m}=*WO>hTydEQ1^jvwI>*Biz>9w4x0AWbK&DIL3N z7fEdtNXMdav+XJs65Xk>57T^lwp}(h(M=ieQoTAy#1oUMrQRMOl752BQFgovF)99}gzWp_XaN^N>! zw*6F|MEB7@9wuT66FZN`77FMV+;}P zTjq;v7|?-Ixtil|7(Ah+{c3~zQA_h)=FK!L35wK@KsVWuu z{W7CmI!-Z=`$ghDV8@l}fmF*}Sm> z1L=6B*ev^Sr;+aGZCcnTo6oXC{YSdD7k!L~w}_}$D?V;qAa_v;_0gL!iTJr<`P7&| z%4hGJX(vBC(k;58rH%SjPd%n?!`O)dt6rKs)Bg5>k?zVet!$))c!hLyP3RK0J75*2 z7F(AP3yC-%Ju3E-fDRn{E=p5u3h%>vYy*;%QXA^#GQ7llAb%`Ngi_#5HV@tGqE#h{ec8ZeRKVD`|aC2 z_xDpXiI_-4wToj?%2zZAlq&n;=XSp$o?CjrY$7%g5obFw(E+PaYX89<_LTyjySeaO zB9>6fH~neF4h`g0=_`}$i)%bL`pjE4j=SWuB;v?{l5x2L<-lI0**+>hqm8@#<{DhRB z0wws>wF&mRp`P3P`2-tjdea;cmzOS&T^g|J+1Dr7+xvNL|1U?{NUJ$q!jXsS(~8AN z$DkL-+wXSr+(9cw+YeqGZ})n^bM1#lQSP!;_a{$C`6XZ#N})bAstp|VN^C+v$IH9N z+MDA&w?l;q_Rx0Y>|ISg*Lj!URZX+-aH(neQ%VJh`X|QOFSYjEQ&q;=XrWxuhuaU2 z&oXDgc#&4V*Fn1$tRw%Gk@AwEv38qS&pnqk(MAh}SU^@if8P(W=>g*H)nn}8H9dD@ z<;gbEay=NXGJ&GJl`8<%0H~Nk8`wz5GjZxDRK8&3m(DCf|iGKfqw5lZ+ zJD1B+OVDA6{E0K=vx*vE5w?!pH1ZA=rrrIOMy_>Rdbe+1G|_8-q~n#*(B z>|0%3TfG2S(g`lo_s6N zC!W6Vh(8V%JAc?8uY};;m=7PUn*4Tv==j1Bf3!qeh|h^wTC01^-ay}8D$h}W)_3Ep zBQQGWBmdi<)w;wEDsT4f%j+EV=Y~iNF_3gzA6+UXF)$i@+Ucl2H$+;9uZcL-er?RS zfK@RGNBy}W(sJHzoop~aW=VkPk#xkLWneBSZ&bko#gfMb@(NRnv@COL-)C);smzUW z5X<0)C5QdlfXdxsm4?S02v~(uy?Pvm@tpl{>&nef-WSk;QY*?GranZkggqjX8b_x* z9mw6Zz2Ew4=l4f^>#vIC+Q52jS?Sou0jn@xq=nc+I$C!a6O$4ka-TTr&!f9+JqoLd z0YqFFS~_J)z^XEfkNWEmEKMQuP`z3h*D`iYpk86TNDJ{S={QjH@sv_`o8=+?JnGNY z(Ly0)-JRGXc5pyPnmX|V#`41kWx}@gnXrWx)S!Hq;NGTq$YJSy2{%U)`j&J;Rv}(0yPyQP-Hc(4Y zYRC)U`0Hrpd%b*nkNOV;YCB48$n}lCjuzqw<<c$H(1A7lm73rB9vvYj5b zbn=uy$}zR^^S}1}0m_H`%Y!x6JsBWSs(ih#!BfOL4R>@Ln*2_HK&kFK4*Gs3RUVfX zOwUptC{?w?LEq1$=Iv#7eObRsKnF_o|LK75XHq+(Jqu1)*D*k#RF^jnfLD!2wa534 zNS+ZOQ0m6buY8{z-I`5AwZhwCjs?o&+KhevEWCM%eg6DfrDNrhYxS!JbgY=R-(MT- zx?{gT>sLgd(!XaRPzv=4kwjJvXn8z&eLx3V^39aJ{v2M;6Rii|-Vt*mkPehuQ)n;D z+u5^yTk#e#w+D3mHu6hBxLrRH2i^6z+ z@3#kL`sClW?r8E%Ectg09Vm6`%oqMTL5M^m8W)_L(lnq0r6zX#!k@#_z2`&}8C))Q zQ$Pnw71*=epTnzKa`1;MF-HOfO1%=l8|LkNqGsu;GHYq3Z=?gI_O96F&-&FY<8Y68 z$)f@UO5w^|u4}ClTk9p$y4KKvHN4~4uY7;9^hQ}9ec3X$T_7DO^>)#({8h2)6Dz!K zrBn>$E=pm|7vdnL{BT0Q*hv8$xF#=Q@9@_NLQE$jI`Ng1KLY7MsjHuC_h;mC-fsPM z`dG}>fDV+JT6()bBUdxBSNax8p_Q(ADx5#i$+L<5 zEbEzLsUO9f1X_YL$1Xn}TZM1CnOe;Dt-^hyhQN;%Rw6UFPbb*45@ z1kyPZl^*NlbZe#vqz%G+y9(3{eA9|S80E2lTAZJ9EE}Jo2eSk*e~|VGkBO;e+dFgT z-z94X|3yS7`2v~*XjI`SMxoyhNDMbSW#>+cRU)P);*^l~#bi7ufdR)nX zRVc;&_UNYbt)!#MC*4v%4_Jlq zBCU2au8zo;qIWZN?(*KmmdW$w4hHWuRR3_k+}TjO8Ivnpsbc~<&=RDD_>rudy0=1H zY#`+rFYisX+%#YAY^dFg-SLf5CIo6bO7XtIeFx{uoei}&F?Z?clw|>ecN#YSGGFdT zsC|kBkv)3eo7i{CiTyQT6~>FSItMUk>2I-v0tCj(yZXPES|HB^sB-`{?<|m7GLTm& z#k=~`?q49!1gLWWrM~+jc0oV~O7TwhxAp>gCP1A7_;_vm6un2UOOSV8qmD0-`|`Zw z{^7+1a=%{f3D3V-Jx1@*YXYTs$NlqL3*>&i+Bev|KQTt{G-v{)c*p(K;z@G9UhUD} zJTX35@6l@lrFg%7Zl$Ei=>$4|6|VjH(<%BqgCzzXoav<|yy#Z*I3ho(WLr0FKr#5v$JuXsb|)clBGoyg;4_ zQ0D+jm)#%xZon#(;$8hWhA)t30@OKxX%|kWoC#QkQoO6*VEO`iCP1A7IFoxv?6QCk zl;U0eX&)|-X9852Z~mo7mNG{v-qjzmVSzjopnUps@-<8u9)~5-S zV$YNw6KPIO9UV&8KWkm6zUUVCK2ByJRJK=fM5^39{YIzBXg|f zGpTvjEsPl*AW#Y|6ykj%YHj%{W^Ap>=)HNC(=TE zLpmz_J~CxgKnMGG^%#!1AZM0Vlak|O3kULw{kwWxLRxtZ&P>>od@G=X{kwWxLR$I4 zmy2;J69NS057O#9L$zIQ%8Z+4bh21@bigzYaS$+HcLc=D^6SqPNk(+T?qB+=^5v(?FgehHIe^?3$8Mxhj+ zPIx>qNuF&`d#RtcS;bx)xqq$+l;X1n551iv&o(H3$(k=i>-2dBP4MZ2x$~0b83sN% z(EY^))1=4=2PM%KD=NUACQhe56j;FNXb zH84sLrTBD0`;$rXG=!S_3_I38MW2DxI#7yF4zxX+Bu^x%^9&gW55?&744U9`08_6f z$#X<}I-%HKNs$vme4e3iqs1xuJcA}siq8T3awAEeZBXYKI)C|ntUk}63ABVyC#*f6 z6geA3do1A+U2Ck*(g5sGPd|#S?U!^VVacpgWsqR%sE9Vo@86IR`#wE>-N zQ0E!0KRqs1pJ&hnN~yCA?^4gOhzK6l?oZedyDiYS^Eon|qwEXMEOQ<|ay7h}W&GfC zWI9KYmg_-l`r6(41A7E*Ak@FK2GngKkzvjgJ93Lu1iQOpMKK5A7aJ^2;Pa) zb9EfA)ZX1oVqJay`w#T7@=lyyK_abAa^!1}kI&*51nyITYAh({2;TG1qb1U6 z4maqYr&E3m=-|COJ(BTmU*dVf&QUU^SYkFQ9p zzpiQC=~OcRbq&^b-XGMXCDKBWchGVQU5U*fC=cEr)T1TRYSy>z*=s3@fpqZxpdKxe zmL6fNVTHT-l(%6O?+@zH5^1`Do2+{7-<2s_13GwrP>+^Kt5uPA&xDlVs)+Xo^=OH- z%H5}WudJv2?K*dPe^8H>NXwDaS{YrJ&siGjz@8InwRiXFeMM8J2GYShaeC~--cIe^ zP5r5G+?9Y1-igzr4ASb%M4^r&`2?L|74O99Q3h!_;#l^M5BLO~LGVtT9%YbLv+(=d z4doMb2BG#Q)F^|rfREan&|?(uO?dS1I#z#gAAD+yYp-cV=^aP6Extzgk)8ZGW4)GQ zOEW-fb(M2=+nBAc_u*I~S`d*&#Cb1z0}?33H%dBnx4C?yq!4R}u!xZHLP?>Ner6f! z+h2+fH=ujfZf}t%6+=$O{B76jwapb3Muw)=xM_bhg`VZ-Zz9SN!T*DVer8LMR+Reb zg)v7lO=j*6B^@J3$Gnlt8em+gqjjZ!?dXJUZnwBJPB{^6iLh>D7z9dXRLSdxh4o0F zC7NK`%q#BU^bRlfoJ-2qbzc5S@9<_a=AJN0nP(=DzaQv(!6-*$Nj0< zv?wG{s^Nr?^KANm5h&Gg*Tc@7r~Z?GU467LGZoy<(JG9$VR8#6m-nAKKx+S%_D-Mh ze-S9vY;VW^Ltdd&nai&@KQ`27SRyI$C><7_FTj%(*`75Z&`deTTf9wQ=i1Ys{znO- zRL1bvoT@87`ws%8mgMPA{jMIJGfTv@3Z*&>e;vm2oCuWqXV_$?!x4QJCqj6fS9mG} zPgZn$ucI@$r#d;}Ymfv=@wtq98?WNj+Bp*#g2deRT^#rA>~vrV5`53)C%*te7V*ck zf>!C%B1kJ)_0t>&r>?orxvSF#S!&oz6{6i=^^qcUE)a`eawzQ3vJ^ z>hsg#u~nFkX1VG+_1{;vW3TNKG93m1sYTi(FfNq(AEN>E;n=rVdOPIw{36>v5ebm; zetXy%wLCz`l;a41QrK&o>A(;q_-5rdrw4S%yg~w{vK-*BKT1{U|oY!Nc&J0g6`mV8W&OP8l-b3xQ5TEMXL$^0PPd>;DvD(rsr2Ifw^E3 zD1|epoC%cD^PQ|ZXzh_sYY;gLLMgs&{OLPCcLm)x?mtVDP7(2DHiV{mPBkv!cIQB& z9d5se#_k%(qWN~!Vh#!K?0?;RcxW3 za*yj3wj4~YQ7bhf2@FAE+}`@mhkYXT*dTV6f5`d%Ulr2q$;Qs+d({fNOsKKbq@-SL zYee?D5P}5vc6Zkf5YnoQu;cvPRE0FW^r*9FmU>=t>`~{+O96sZd0`AeLiIiYB8!eX zi<>*uUf3#IEz+5(U>z7@5OO9P&>^it?MRe4+{(GxCp!W|kl>kYW?l_x9+Tb|ZhY5q zq12-4tu$f#>F_umShiT^Sh6O8AxL0pngoU*k#m`22oeQ1R%dNlYudG3~c(Qm%7>`;R*BJ>>K{{kgmUwb2xF-?1ChR$drxorwCkp_XFJ zDAg->V`t2n&*|OqOeB+z%cP@6&xxr>pcGmt(-HaMg>ikxH~#;*K+VW-O%)(Os?rxd z{rP9MgvYsyQq}YKc9uTzKXhavrrzl7%x~wQm7YnU6za&9$YNEqkNP-| z7Ex;hq)i>NMq>wfVj0>eYSDoZ+wbo@?rPC3{zOS3r-C1`tGJh~ewTKp?RL`Kt zJ0#>AIIaigB?Nk53_*h1zds|7sNi2-xK2VpC(lK-w&eG8%xR}q5Unrvb4GRvtR}J$ zD22WgUxmjyxUK1x2@%i3Rj2r5E<+Dw>v}#;JFQ?u~wXQ(|t@W*v z1jdB~#$*y07ZR8YbUK>et}=(-u987-S1EY&N$15MW!c&|lfjauw?Pqci-=qS0;SMe z(<-cqXcgw6PtXI^5}X_IOfNE?BkdC&6IfEc>m>O^j$XAe<_QuQldnTsg#=oHG~G-= zZ`65}-gLZ$-l&7~LCjsub*)3aR|faC@h`Rb#zo%8<@*{<0;SM!rwjPeN}Oe-elH+ zc1Cy5o6L+5de;H(hUSS%aE$#=@i7q7LZ$&8@@qrltb)<}CEkq34d@h{E3U%f}{Cy*fk$zuW170D)4bKd}tG z&r8s|onEB(d11Wh3+4B;a0xc+!#au)af*n}M4$%}rI4n3o#(a-&mMn1)Rx}*hAHGX z`0yU>@UG40o`U;;vr&>ke-^CaTaM&ZwB zXSB+NvGQGXYzc?(n}K+z@2wRYfA+rMGi+jlA_udzaC_XA%z8#F3Mt!k>4ZF6)&Z&(Xif zZ>Zr}#_2|rsa`E5;yAs}D^KJ-aoUH7-WHQ6LMcB*DIY?4g(<|8YrmG-wM?dW1HMH* zvZv|YfEdDWQDVAzmXL<4A`~IxqwYe-$qM za8<61N`H#{R|o0cfEdE>b7HUBp$=0V^nTI3=YNb!|CW4Yho)^#!w@9U`z6HF->!;3 zPG?L8(>1?1X5imA^ZRjlj8gB%X|-=v{52wqUcJ>ArEoXPB-&gV6>34}KQ8O{c}4z? z8uzB;J8kIuonOtQGQazeQK9eX9LvMgH^-wC?oF9QBE2)LH|cn3(&l)S!ZnpiT%xxk zR-`xN#nRgZvG3!$#5*xu`*7ER{$iboIYiW>_b6fr66W5-C-la`cm8Z=J=@Fp+ROYua0U=263^{Y}t~{i3iWnEaafs&$ zt6I%*aA%zE45K$M7CT^t&P3kssCU+p!1a|74T$J|#7cjY2n^vjC-FSt>9jcxuENMe zPc5nn&8gi;8`Nt{^r~`=;nl0E?PaL$3L-{E-T|up>gZK9iMFI;JQ1%_t3@gFv75xk zl6kHBuC)t4*1`^<6na%n;=xB^LLZV3`59^-D1{zRRV%}hUi9rwF`=0QhJ@{A>dmCu zD~tY1ldv9(3B5o%#?iY>Q3}2FCXqmIKV{E$9wJamwJzlaHiRkX_>6s~CM58nB zE_Ir+ISt2D9BZ`~SM4rc+rLWskemH=YZHdZGN6?(_i*SPrt`bCR;TSFAM%?IiZl#C z0=>gRy!q9tv`geeevWit2ok0b`3>q5Gsp+wfM_xMaES%3KMj1y;hi`ZWn& z_t9!z`rFa3j+XFy(AXcqZ-7(c5yC?PrEslo5*UI6`xYZU2b*sAd+Kz4tXAowc+h?|s!?S@cHo`_=gS z^w#r9$LtlQtzL8v@}+~FLnwv5MU$vWZ;H)LXZu#39~(j`^eyrm+1Se&uKq6RShT*8 z6(%Bv2$Vvvqe;9#I#TI0;8`M2syEF+PoGkLN|U%Wx1F_P%=z>_k@vo8sj4NXIW;TE z(|@6r2f2M(Q_{&wX3wOXoV ziCIokqTHLX`7Lw&{ep_`la3#Wc$4;p4-$b={4U#9-CjcK1n-gv8} z7GIw2{Pwfl1+e)&iu`@k5?pud5;3gQ#nT}koSYzhDRP2`R3k;U&Ll2 zDz7}PY^!=qt8Ljnf`pdB8g3G@g^&(zRd~Nog{m8zn8!LuPczf{^j1V({xZ+QUAVl% zDf8(1B8|Y8Q5wOb8GjryNt; zIR7tBUQeb&=5&^nO9G`jzwj&Vne%O5;(rh*b#=>S=esotITI9>nY(EFyS+-gm-D{y zA1OzvF%_={nnG9<~BD<6`O=oe4*fk+4obdcN>oG?)%~D9y z>W)?tS7^n;3F)*kw>$LuKv}l>nF(xNY>WJO1cb2_U0YDdZ>iW;BJv6fV+ayhD@_7J zkm$Lyh;J>OZ$D~<2GLrLSP$*h&zuglmfiwRex;WcAuvQgGlBYKUWFrhB``!kGhtdq z(qYbFP7#LaXC^|iMcoTkhsa!)Dfi@0s#jQo*rM7l`q$}Dp>Jl3%9cQ>-u-Sl+mbbr zOm(UT)fCo)I#5b~&lU6t$=Rc@|nyqf0J#?m|T% zO|m%nLB067hXPu(eHcKHRk>)V@@hHozieKh;H@(zH)&{P-c|G;~{>}XE;YsRQORGK+ zQ=9*(V(#36T_ue%nFNL)f%;4WLy$Q1LjiZxyV+TVAxPZlL8J5f>*^P^lN%Uj2CH>sQIN8sz?MU2IIxD(ci1OyWor! zx=JJ4?4hF?pcIykNwgrJ&q+$h6JMT5MJX&BlYlYGm=Ef;-;PqOgXf7V z9kXa>sXJLUx_P+U$K%?LA&sy7 z<2Tk4Vb1^wd~ikv4T;2ok0aHb?z4twJeG zlhnaKJWdCOU`@=OC$~GZza9-DOcnyA&V7)}Ex6{t2$bqRBbWR0Mj$ButX5%dM+>D* zOwh-(FfP=AqmPnhaVF4q9G9{XB+Vbs!g!I+mhgf)(00@(L~WYGy-qcrwdgiTkG6j1 zx{Kw{aaa{+!~qvt8Fx0M%XaMj+8U>7rwARbC1b^gkwnQp+E? zq z&9~VW`SCa%r~~P2J$I5oDU3H~0;Q_%`_q}!joSLxHa~E?#OY?746Ll`46pse0Kg}+8F(iMnLhh+M6J_r5I3M$SfRH*cggtDBO31b? z*9qC{Kmv6jZKeZ5kl;~!Wh14}Cp=jb!4j0>Q9FxOG94I#1dr!B4}G7dEJ0mTtQqr7 zKdTaqcn2k+Lzuu^(9ep9cn2k+LzqAd^|K;y#iB!)K-=|m)_R3jVJ*R&CZAPii}F2C ztV6dIj@PX1|J9p^TEuMW)ZKDFPIBaZ%FrK3A}ypD{I3a{TWD^-WE zRl00g3TYw!q89sM!~?0Nn7FYwud7FA{k_t`q3WajqCED||J!-H{^fW>)pK(Ax-;&s z(eisOt>&UA)%oEYPT@WOMWEEEO8+>|ms9cPtOKQZWf50L_KEg#>u+b}Ds5FEO7%}P zI+rWgEY27QVLZn%k=}Sh9?q3Bl|NCB;hb`m(%-ARDmx`AJcoRW?-v@=P=_#~tzs$6 z5g~piKSMX#1(??Kasv!O0&|3J@}ymcQ&dyFSaxbXh9H4C;+F?K&}<3vL=$Y`mb`i0 zlDCbGlwe`-eqdah;B@F`rs<#mQL8&$2LF$-^MJFW*8X^;Nm+W8<_dxe2n#H`i0qx& zpn?jD;I0i3>7pRT1}KKUpdg|sDlT9_Y=CqT_fizQ5A}&%5qx$96nR$spOZ{xewlmk z^M60?WxP4xoO9A9$z+mI%a4)E@=SiXIzk|36-sgaAAEdCU{x>==Y2n^d_HPtsdC$9 z>H2~CX#d@Zv%J1v7bV}U^73X|s#&kC^Xvzfd?C*I)JyaU#nt7=?H!gttw-yUfZL(*myNvfo5m}Kai%KBdO&%h3d%o>@^$GwW1vFtLv+Ttpx2; zwAU-kCml_@56D9umS$of=%Y2McV#78|7@OrSShv&~^TG>xNETB4)*K$`L68yA>`)wRA()1?mFGa~scbKPrNMOA7 zcViV>Kp(|53QL?Vi4+#AQ7mpgZXIO8mhf)ds>x3G>G7v;rHu}(wJ62(`xWLz?XI#q z;(fW{Zqf=?Dt@2S2Qb7%!%q5WyTWwEP?K8g%;_H^1; zI8n+{vkMT;acFUp=8y8~4gaxlZT@_n2bi0ga}WuXD!!$vTtDd!H_sL8Kq>UrCB{&V zUG$L|3FG+OUbE-*-aAlOMyHPRHQTR0igln=yP<~*zqosfmdZ@l9L||_IZhMO<&k##}P@n6TF@W$)Ud=VTFol!w~U zDz^`3q>UdqXNhyYNSnx60qejJ>`kWVAZh-%R-u&b4TgRgAV#26okhpUNh{LjIeLy# zne!Y&e*F9x`TMozK8PtD7^MH9`T->lbs%j-4HwOqu!ktM>z49T-fQ%QIuxfBYR5Zr zN}dOpJ4288f%)X>zu}0`tZtFa!zA8<#*S+dGTrrY6WD{wNRQLITsx$g(&SD1|+}6oRDr6OD1$5=G9G-^@OM6-ZrTE&84R!2H(4Xlo*|@M&rVxy20;SNW z%mhkd&(bBRp4%R>vLU2X_i?T5NV7rqzp%gwD8>zexOQ=fS5&J-k=mM}W>uHrt@9JBqn zV@@Ra+SV>BZS>%nldqo~V%~y4Iwa^qC9%ap+p(35Z zKdRG|ec+GDLHjlk`oY&WZ;WhUmB4s&_Z`sj_IkF=OQ*x+AQC2ClOISMbBv}`tE?qR zTdVkAx7}U%Vm)bhO1cickt^ZB_6UDC6PqS#7=HukbFKnLsJjrwK*h^k+P` z9$xd!!NO51&6{DS4yNm6V49$}NNXLeLbpTMUSa!@u7xvJq3u%+mXkjoXZj48{Ximd zZ+U6o!9!YGrM+g;P&-VXz2}h0}j~{OAol>q;?)`IU;q?F3@jj|hDr*fG zPQ(WC{O0U->pI5>j2CI`dGhGFbvyTr5g2csqgB1-l}qJ=TSkzMjilq+Cvw+yj1eex zdzJFur0S)zU!$vtc!r3PRcd$c6(dk;VBV3$u)3x4*Xu?Rv7U%`dz4Q}E0h{C^2@~F z#-;Mo-B%OAdEW1pr#gBu9V?#Pleo4;sVrnv;_5;Gm+MIJdw{)OkioKak#y2bMU`E=lD^cZ=sS{ z?zszJ|k^(>?v!Nn;Rq6{MFjG`HZv>C(({sjp@9i9q~Q3sD{}ZjQec~ z@d53B^#<*jwIjY`7VfbHJ6q8z1lpmgGVRdxLTnc(-2V!9Yoezzo4X}XrQMSr;Qf|@ zQ)2WEO|Wki?xZ9{d)g7`1=7+FKSz66;C?G8#rsS|d%aYqJs?KXbG2c#ql4MS zLEm2mO6k@v*e^whO6134njtKX?dgQOGNDiOoRj>hOHcN{kL}RJR@q&dkQSl^?QF#R zo*x_AJqc43cR3Pbd9ZiFVA?yu+1Uv8Tf%)5!n8_miR~1E`*5IzvfwT~R(;g6%y2*jG2ZTvov-<_^`+zj1?mMgXe#lS%?(XP-wwt?;+1){ibKY59 zz^8yU{^RfNxMLFTO=JkW-_jD=J&E?2NM1RAdjaldg!>F2O;z(8?T2>>`GLFK7~9QG zI=Fw0PTeGz#I3Xk1nyabQto~-RN9kV+N;nm9<|`47L>xBVraiU+LPmRdZIWewmS?; zxqEVC6EU9NAYg)Z*j+qO%H1i1%5IX&?homEoVLR>5u$p#sz_}&d{Qzkps?y#GooKg(d9-%|&f?(i2S`%}(w-cYAIS}rAGiYv z+K#1?dp!KnJu>C)s=<4AnbLA&voxO+6lpom(g~lO}}KxN&VM$ zjrB6HpRnfeV~KD5Qd#BHaY3J9|FYRB{SuV=@b1NlHhoIvktyQ?V%WA@Q+gRF)xO`H z#P~}~<={6b1jO$zA6gafm!MRef!8Irk1Umqnp__ckDb~kClT`lrFL9aoTztGsr=8- z8v^3%+pbQr3Z6P0%@>$qiUhF*_rS8vdlGu7*sr=)|n~30E zUxRTg)^v{%DAlmjp9R&QDwUtLpA_imdC~UOanDhz#bY}PE>4!p+E-5wh(nh?kV2r; z{@rs5?t6z)_u-oZ;)!o_I>gforRI!nS@7o9rE>Xuw*EHe5qouO+ z*jtHsl6rj=_FTIDs#tGn>5}@s&4XPxH}s<%q_pzBuk;|Fx9#-dj&7^#+dM$poL^g` zhFU)%Mzs63wr}$QX)`LiYU1&0`3z9(y-sLU%eQ%eH0?uAIk%|$Eo+`2>yY65&#t;u zkHU7I_V0p94VKD9$0_oB3=yqA?)*%gK&j%3?@yF#zf@*D&BRzD`qut$$2fsfQ~vxb zvHz^4vRPs(5p2~3ZEJLh6DW1ZFCDzc1}&A(yv_uVCfMe9xpH$Y@=2eu?oHTLnk!;y z)wFjF-bQ;9=l(`#nPLw9T6C;$a}8-dy7QjCwBy=yV><3xS<$z- zhO`i)DL;Phc=lQ@^{4~$;6GIddA%ktmFq5^M)|?S>ej7N2$Z_yf!W>*_bipw_c1}G zylmBXQ#w5!(}7Z-Z7%iduUjgg7&$#CCEKUvBLq?~WdE7G*P z7!kGqdwb{RG2-&qgl|(6X>$trhI!?lIWI=+x#^eC_UlLd3ZAba9S@$hYV8Fv;-edm zhIxSc%qif%)~?jKU5q#|{EsjXkT&(>&!bgRN;_JDw5jJ@YvVb{zuCJFU)z?*w7Y+P zw{|_1FQ>F0EZ@qva|<|P6=ExSUa$Q3xz%Ds=Y}Wvc5dO~td_o=b1-A)!xx^j{<0W> zQmBvK1|uDxd^3Ihh#2v&6OZ@pYy|2PVl@#n9x`o zIP?Q+QNyP`@=mO{OfGqiYt+51SC=)q=WrJ>-z8A0XX8)2Q;%IH552?0&!l76sTbxh zjOjqB*AMLVmS!!JXMW4Xfc>k={@bherf&DUI#6m-lh3_Ri0JVh6HimzL!a)o_R5&& z3m^C?l)~CYEBnbRE}L=BFOsl4-s$v zb!Qiyxf5Nl|xv&f}*&3#N9~^#cf$!c-9AI3kiyt=Yh{ z&*(WyVJZl5DiJ4iXugr22Bdl(?BDH<=-V=Jave=zi-R*BZtK&Jh}JXqrPNxC*PR1k zs}6M7v>|R4O1X0Y#YFVFvMfahj{7lh=>2mdc0XH|yNWzF>0@Tk&~~&?h`~hkn^QH9 z?+RE-&=S-q1lPeDE6(lAr3CW;=VaX3C>lAIz0&j2_1vdM0`nYcQ==*eH45_p=abMU z+NGIv>=^%e-eaT#Ly&OimOh~x)oSD;8()kODCMSAGtzO%8^bqpTY~A1>BD~5-K}ZQ z8Y0eXd&~xIsWAizoc9yrA+l=jhdnp`iwF!cgxM7q=ly7|i*)R1^HCS}97B+B^TW;- za2-t5VRqI;+VlWAFF8GzTWZWfjMpXj?r)z#^0QR)|1?KlAvau&&~)cd`uE)fgzhaw z1co57yJ1@w>uX+dv8>i}`kDm8k4vBwuge&8>jpm(r7Oiccz0vre@bVS@H@Q2LeJ4B4znMX zEVsRx+*@yf&%dl#-dHYc@SwgP{;zDg_m4aEHHYbsVjabk%gJr;37wKmY`g9lecgC| z6}hbI7rqe#L|u>9LtzXV7bDw#1{HB=J_aX$}SQpg|!PQ z+R*{(9TJJfr^vlu&T(}R>Jlhbwcu1Li6#F)pwx;5o#dddGabULfZ!D=q>Mj4*1KI} zyW9Hc)02i<9w+HfuX@}+u*Zo$Q-Lz(qA7%8M zHiw?q_NC_s7=i?^HDQ`{3-KL2r@e0E3EmQV4uT=8>W-4N=5{QsN_$iHd#Hlk_C$+- zc!G#y=?U*CL|_OKzu!<@(?-YnZw)Bhvi>YDi}u^bkX5%{t;^EWw8u7m;*ZpX+mAy0Xi^t0aaXvG)7C)LN?vtb<6OwY`t@YmV_bUi`Rq5T&qPaS4nU zEkW8PP-;ry=~BFRx6cwTF_~5jd`3GioJr?^v}u2w%x>|pmTI3fM&AcpQr`;ii9#%< zvn)o_+x=7MbP^0f0$V#Fl0;lYL@5y%QhfTgIz`W;QysAF65?EcdOgzz>KuHWig4596OF}~HKq;(uX$eAA z5<`%;Z9L%LR<7-n6;yo4nC7d_hN8#M0S%WkEMD^c_ zlK0j!IcR$?c(-(kC3J?+IC>`hB|RHPDb5?4KAbmv&ABPW9j8w(D53QX^Lm;bv{JAB z-CFurm^^R^@|r&8{(<)lyuZ5l4?`FVOE|Ytwh!l)b|g?Lak-Ss?taYYaBh9}E`L;C z^Z)CQV#`o!+WIrY{%2Z(FqK3dD3#eNltLZdJ~&%u*Iw;wYv4;IQEJQBv!q{bo0iH< zpwz5#J>|B%m;Zs7Ux~DTwna-JF=bv)YQ0S#9*iQ-(GRpF#dAd*{i!7CKmzq?9cc+H zB}lu(+PBY=%WnM8=U<3zfQb=nZ!I#Mo+q>sfuy@WBLJ>lLE`d^LEsawB>Cd;P zHgVE{09inb%-5o$%TwH$sq@6li4Z>O`>@cu}N})cN;IqOCXn&C;r7qqlocjl*H2cRXO*_yKv4`6BB3coMwj=HO@fQ&<5;2+x zj2F|5^Cr?Um~!w0x<@swZF112E~cnUoKI^8U!%7UE7CeF9QSXe9n5*$KZjOTxpM%G z={~rQO36-IhlL?XU=K%#d8FfmJ@f`M>A*2Ej(!K9&HH%o_7~|sHmwFrNZJj$LCh+o zUE(4lexTE!Y7&9*;&_=Y`go8dAK}BcDd+2+d74lm2go+{>UmUq@?Ele8u^m)4Wv$OK0$7?ThO=&Z?=q~k9- zg$Uz99XJ9YJ>kdP4QxAzJ$-ELB5|sN9*!Wk4DKQq^f*u3o_REZ@eW_yR5lt@k#f#$ z=R+NIp^_+dWJzZ_p`zaZCY)IVOrNyl57yzK6sB8d0;MoTGZVbJ&5j^Zim$Ww!w52P z-yL(J6km5el8(T1$NXR-XE3kB;c^vyl!sD{fA20;{`p!eGl5c{=J%j>uLi&R4tU;EV(kZIWYjNnBje(~$mX;b`;5noeo z)87+)R+c?{$-KnEyb&ITlr#Y2VkUVcDqr}g=bC(lc~L2EiO1PPoE zB0c((I=6)50ppVp?2`~4-3_jxyTOmtiejsecLN-;Qs0MK>Y;SUsS<13u{}pR)RANz z*gE6b$!)1GAssX6Zcs0FH$W-WC&VyX*FK5b-A8GyFt#PwQezuT`#n6&#F$dDg5J$IFq;|seQ4$m^A__QXYoh}m6DzG zHV3`c;glRq&Gi-cze?`i+`91N1|14<7Eg%RiFku%2a96_T7tAK ziTtlS54Yt(A?tNIPPabT4>ARp#hJiaHIzbH6Tz2CHeXy(zRyoh=!5fR*e`Vn)KPYQ zMR~^Csk3-70;Eu1S{;h-YG0SXp>R!uGj!|RVt#jdU6Ei5{k0#RDzjUb*Vmzr6ynw4 z3Ay;l-zl_1IJ1Lzr^fqyNYI6Hh`|T5=oEwoI^ABaUQ@2G)>vO>CQzzPQ3JX62tS!| zKdugxLJN^mNt8kzqXx8Mm;a;4_s779xkrTQocDoq?*2_ z{|`UPLn+>eWARcW{t*1b=A)tvdQn zeW`jHtB}?NN%Kc}7#B*lxS_t>T3^>LUyBA`E`d^|)IS+8Q1>#4puF%XPjr5u4wSOL zoBW_1-#yy#J;^)1qtrPq8pz&DOuQ~(O=3UL5+pFD&=17~rq#ytPLTdiU5k87w~(L< z!L%)Ru>{TXj`KKM(G>`9S=P|ShgE$q%b!#>%e%Cc8SFkCKdK=Z1Vi*cSB@W3)32)Pa#1U z%ELUzl5yz|Lu8}uSX-hr0Xk3$^*K76aV_3`=E`>R?pAdE#z(|#baqe6T6@#kjd25GHhU!jfwbBWS+HD&G+BZagUrug~eT7{O_KBJ>U#R-%`efl&(&z>e|rAVP~ zuQ^`S$JXdlqF4vUg@o$|o>*v0AI0L^Yu4e`K|GtomJ%lHH4{ivn6Zlg30t4I)TpmI z$tB;JoYh1?xdfK0a^>mnJl^D{CKP?spG(+M$@-9X&$~n0v4q=erYUyt13g6g$10ua zTT7QoC7m>u|=Y8?w-tS$%qZB|*i1jbu>Z*{rkWJ71xfl{b1Gl5cQ$=t81%ayOC zvkD26O3|S>p*XD`+E-03`LwzA_U-o?%4@0{&nq3E{`*X%}-&1N7Wp zb6GYl7$W-6ooz##_t&H6>FBV2aP;(1^dv4S4K`j5`LwXD^hcOd$^Rd=S4oT)rI2ZJC?r?Qd)*e?8vhGvecQSj;QPa5@bZ+0sFTKQ9vv13@C4+yrQk-j3 z&aEzQzt7M@bfa?hNpPkf5-4SVH+haF5pxQq?C*x4o{~pBCU5Wci}H5utS1LdY{B08 z_q3`fb4JnnD7xnR70-U8lq;|GQ?=w7>rd8FvxZcc+wxD**L)xByStUXrnzuhq4y%` zd3JS+uG~+!W{@kz@gglmBkIX~O}naa`e2BSmkE2#7P6c!yZn#$;lM0;;Vh#A^}Ts2 z%^tknHn2)F`b#-}Ac4B+t>5>&#DlcHgrRgg*eaw$4T`P$bW0sMbfHO;;`$9`*U2ZP zNh@MVnm?{p=q=JlRDZ{GeE42ndHY_IgD!CqrR)((;k!1p?21-lJ|nG16CUT{)S>Tg zLISNqn$~IlPHQYnvb;%6`fbLzPzuYK@qpcC&-WEJly)wxY-dCH{>i$Y`^5(v$OUb6 zE9%pGkdzmEUJA1uuN{-s=+aQW(A~_W+22jsrBkgv?pO1vBp8AO>K5XYev`e1@5sb0 zW1s4V+J8FGK;C_-vBVNa$1S8|Af2|ES84r5lM2`uRz5Sj1#54PzI1Q~reXbv~ z$@9C&^OKh3Z^E=f+9k#jaS7eeE0+x0gz=&!E_>Sx(~NVv+|zevodd+3r$(vPb6d!j%OO7$A@rw`wQ6qh2mQM{ zw>Wz3;F@u5^mU7$E|m6~!}LdSDZ!Sk-;Cxm`wpK8KW!_j3A`I1fida$gD-QolRw(D z)Vwo$1*NU>TfE#%R>&Wx^|=H}p(QBpW?EV_EMsbI-^~ zB(Z0K{e&N1Ya{P|%k-}u9Zru7`&YBhYpeSYD9zVy9}cC`_Lek(eK_pzpuSL#VjbA` z;hDn2pP9Z7_JcALJa!s1L6<&(v`g@)sHBpKw?^Ib<yn7NLxL`ple&4owvi*h zF;A+|r_2QA!K9DcP#H^?ADDwEh5AArimk$YzUR2Ma>0vcT!Z>t0zKrh?8q-oT4B0{ z1YNi~Fb_~)NT_%Yp4TB)4!HgVow}$mB(x69kAA)AS#EusBDDNHN2FdBJ9p743XQ3w))?`kWv&oCp) zP+nVwQY)TmD_`Gdr2c;d)(NCT9dtpS!&3`P>CCB%1WMT%7L1>-UC+^WCT!hAicbfQ z(gbbKOrR8co|(W9B+}Lo6)){rUa{m0v55Mj6{#=Eu48$%*L-)zbQ5Ae^#Bh3;3cxp zJY|!W;`G3l)0Sz|zbd+EvbX+aFLBD1v+_|2+bEZ4PW_U0G&3Pwt@59u6x8hu&HjW3eLh#VqAQUCB|MG0%x{u2oqSA z?6n~{)O_6j751rk4lo+mT6ro~9zMIlXY}rK9O_vnsb^VsN!tgz+Id`xg`QfOXa8&o z#zgxw&>TPqGsaOEf`q;1m?E3lDqPQiw$I(!P7dvq!*>QE0;{;cyBXuMJxjI<`=xs1 zmE?0LSsR8RVXuu2TKkctwI3d@{lE}w73;v1=9u+I#r>G|kSDW$=&fzXH6ejKi8FyA zNN_LQjuQp1VM+43B4$xLT4k?E#AiAEQLF>wLLE4o4CDi=Fa!xZnjmT4B`^dD92Lh; z&yISKw$5|vx;2Vsir_r@=OyOEHN=!EWwuyvl}MLK>$cXYq|{mu5_ zEEm%8Q;s77+YhAUryNJU;(b$cWY_IxCJkxYvu1vDvNe`CewxAaW$eea4%Acr&CaUv z+ACgz#pMM)`xA-Sk!pwbJ%9K@35{ zUZ?mGJMngye1EKDZ=JC^|G)hx4@bW^GVQjlKpt*(wVPIoJ;#}eyi?DYz3&;SrBdeM zNSZ%1Qv`E}D223D?RBq?LVk$NBuwftqsfI(JT=f7g*~1*`+-@gJH~T@HQJ5I9@^KM2r)bV$&J@-Qxx z!ukD>P)wlImUT7h4Ep1A*-arx+TDfdZ3V3)QEQx~6rV@e0| zfmP@SN}+|Cp!oS?&V%H!4BN}6N!t5|&40%aO`sIoZtuYkq2f9)-m3enhW8S-hEtY5 zlpm1K=y{3`r2!%^MbX<7LMvb$NT3w@q+<_tV7zD{(#9V8CMxRtaGIbL`lJbxHUy?F z5}0n9;4j5GFcneCK6Nnp0jpjV&NSL2#F)MXmejo!$m`;$*6ZBU4^aj}%WM%ttm-JgM4&~nRj8xRA8o^$YGX?8hkXKygsUS# zR^3Zh<#(vt#m2=}p_J|0rPpEe9Q)Lm3V|1CQWw|XAng(u7xwPm)D0M=b1;~>V|%gZ zot7YwPqsGW4<=9t>JF{a1m*|E>-NF}M#Xawd)}BgnRQ^iNNa-P=MO#qfD(tE*I7jK z;=RpS#K@X&mq01hr)8BTuw2~1tga~xBd*sTuMZr9eK6w)D~Y!32_ zDd*tZt7(?A*m#RRWhQX6g>*>Jh4L^iluAjf08<`Hp^mhK(mJ4SV?Jk2T?|1Y@0Jd7 zTX)Eh&?=O|5nWnBTZK}S-s>Q5s%_%^{}DKXL|U(>PSUCrJsz~J44J7{|M5tVbJLEm z_*%O_7MYeBOCqL%jVUBF#;J=TNTm1?>OcaeGFydG<@V;tdcT>ma|#h!g&|1r$cvx1 zgnqD9^ohot9D-7ItY!%JtoUd=i27W@#BWl!4Pcc@*83f_F>`M#=Ag zPq#W6iGhbl$zB10bf6)~LFdgEh!@{!aS7D1>(6Us1!1g0eVK{*tHwxwt+5?xm&pA> z6=pX%U3;JiJh-)`wo1Rze}!2?&DZO1 zG}kWC`@U&~yKn6p63eDXgeXZz@Ciq}M<2!GW4wHAX;%lP6-(K8`P%yA66;^RyHGvb zG}O_(Uu0EI;1lfxO>56-RikbhxQyEORqQpFaQCg0U{$duFvMOnfjbsxs}z0HpZhKV zzF&*0<6Qzn@EtW%qtc}=5~u@dR|m$0QkU+_lP@exR|ioC(zpU&@XG##UD=OP7_a@^ zv?b5b>Y#~1e(>GEu83zT&Ks7p*V+ds2W=j3h?T+|G&*RNQ-bEN`0aswwB-I)`Qf`6 zu2q=ln739R$7_E#I>-QJ{*;G0Fm+KM$r^(Hq4gz^B}kM#lL+6n)kHwio5k?v^-mA? zkSmWdZv$o~PzOrM3%iH!4AVYE)Mt<0ERL)~DI1gV+TusIjyy*xn<6GZ+&7COdr^l=ShL6~TBjfFy;8L>Uw%QW3^_dQ{yy|O z-9OG*TbMoQGoSuPx9xV<8E&a<8%$y5qnN<5`}WOe%94H+Sci}4ro~Cxw4!J`O4aC6 z7_M&A1b?Zx4t!4(^|=Iw>?piU4p?d4;Y58dfgwojD=gHbdy@7wq4YO-&OIc4qn~pM zTO53c!pPD$Q9M33Z#6N2wJ3!k>A-VnMe%(MY#&KhOY46;l)`v_9MnY)=vy~UKM~_{ z=yZg#e%SVW1t*L`1ev=m?Arb1p5F`d4%+otzgaoT0%JtmV z6_!Gu!naIy>e@7+7@YLMbmQpLr>>=02kO&AK#^5Y8c+(;Eu0E!ig94IXaSD z8tmFJTO9?O?2XPGr{!*_R;Ctej>52tmY6sMbA(UzIO(txGpXvHpa zMtigJ(6tH)l;XOXepMJsp$?hVUH;i1ogWw%(kX;uw{6+A+EEHqA$&Jn*C;!~!rozB zvDc<82}VVlupvxfD%fj7w4?dmXM$O0+g>qYuUQJ~4V|M%`%GR!Z(6J$yrPRE1$D(G z&ZIlRK{KaMZw5v6&R(+)EN?=zBI3@^y+pGun{qJ(2`q0yxyYLzuvL?X@8|ls<|} z39eklHSM@6P)mmd)@>|Tnd>df%~<>q&cbOd)hljmer8C&(*9@PbjeHA)|D{GJzqQPFTET4#` z-3R0$VM`^)i?k3I-IbMW{j=FYz)G=IudeSLJ}a`n8>`&60nKQFLpX1uw*jNt9Df@y zx@RCU<=lLD8_<2XFS-NR5@Yq*nfv(rgOLu57il`NkXpDqXkAgVQJ*}lgGYAe$py2` z)3@SkdEv8k+K2g{}j?QK6}jun~I#eD1|9a?_iPiBlNcH zzwUj`vC5Wwju$QD*rGlh=eb=K)M92wnKPk{q;S97t-12#=56)0CIZTiKT@SQrf5x3 zFvG$tgf^iL?oHYiTc}U#Q1nfIG!h0&EJ0y0t9Xs2T~m~fV9$|`zY`aY!*SGTdz>ae z+;`#(!68VvqdWV~DA_AGF8hwsr(3#*Z#dfDjgH_=J$_P6^*kCWHt*nv?~mEv4dK3n z6-n86DIyw8nwSjXzGZ6ifTg$}ZJ&S48j>HrqZ)t9Gzvk&UUN+JXUdi097Jz}l&?>Y9-`Wca?FZ_>wXUd76UuPP!}WVuQ&UzOh6GAsYmh>)3ZmRM zC)pCab`0NkWMTbLOkfBSc8vx}`c|6%HS?nO4QVW|DOM@X5rI-zN-`7ZEw&$-35*xZ zZVF+HqFx4$6zyG&19?{M!XfGQ@j@x=g=HpC3VSb^36#RV4-*xHnfD7^(Elh8XS{5S zLqSgaXhi8-aSoys&XHkj=Mor#gstZ!?YjhqAc1+~5*UI++8k8c4_K{?&r;pz&RUE0 z90=^`AsxsY<7iLv=*c?9n?jI9{81i;pboCh>7Vdoe-L|mXFb&^eEM!8X9e1G=uO%_ z3b(Z>1Y?>&9Vj*TXh*qtdb*VZ+~2Y5GH{m5)`GBY4|QN)jYk%|lL|{=KZu3xhu1)w zkvmIakL;&+^1@XeT3Q)Kd1yO&ZtH(&RY+jZ3oV&;c30W^4ZC8C;tvV51nCs3*eIfC z7bxg|V!W9NOdllBLXJMN3cbbtXJ*e)3RBwFO((6iA6Q1Q#PIKSRk^k*u#8GOjD)Z( zWhPL{w(V?*OVD}-+p|hN*0*$4MV9||N zwEm+ld5#{A8aIJbbhrQ1;#+<9%&?9`d_(iQ@AkgN!w@9!Y%d{35HXn6{A803JcSFT zkQSmB5mP?(%Kjo9XgkubA2zi(z1)%!_3qtLasla>O?h59<~ioMYgJ1sSL>-nAx)=}(CJ(QsaD+?JDm&9^FmsPJL$}@4zw2M%GkMNd@6}OWejN{?xJ(am>3y5 zmkdvLM4HaLrxPTnkX6HCCrIK69!LvuC8gD4G~Zq;mV=l+Zhq7;X_ch&qnwh6Qtla5 zJLw#(PwDx@Idl#do|c7D?#Wq0=sd3xG?UtZ&hx@Eyim%V=3-9(n_4ivfQd%uxMzl; zlzWciy*>TJUu0F2*jbGzg(s8<@i3h*PNh-Z-E4K@NTUh*^uw!%&erQPf>Nrl{WcpNq zCdll&KK0vOG08_Mq=lGDI*PY1?b45OaMza;WZ$M03cHTGTIaL1{h^lEYd?Bd?m}r* zm5BMTHOT)g+a*w+tK(lp9NT;Ah82|Or~~6|d@7y#_sg+`4?pq%={RA*Snt8Vr{#Uo z*!2VJAo?Uk1tR*t(5Z`R;_5&GEi|QM`-AoKM^WmcRak40rt@#ekG+*fZ8{Y518qlI zhy_GkeBUFR%2DcK2ohN5g?NmJmAS{{DY6QsFs)qTIU8*ZI&Ka_pQiTXtE;-ciMRU9UzITf|pLV7j?w zS2Ox6Rfmn23F}iNZ|>)jAEmzxvu7HJwH-#^7GaOp|z8+4`u7;7fUNDXdZH ziT?Rh<=&T%XM6QM$`Tk;s6(+;NVIx!x~#RwoFI89dz#Fyd6teTB(4HcIcHpvCI?Xm(*HjKr8uU= zP40vWkt+gS4d^8;;1I=$VP>M@88hYXd4{;9$4t3w zzPZj!Xyj3_;=GL(wTLj?MP=P-sw)BFE%pREa` zX+7NkXiNOLeDbAfOKiIm>QG#3v8M91mHO?D5;>@#NYf!f7cTKvui0V!KnpVy&)zs& zuDRKq`-*gC!dpFCj$LNXeMLGmQLS-_T=P~xZ9CGL3G^1}RKlju?0-@E+-7={*O!&Z zWgVes8DJ)@EP+y}FSCw^m(Gy~cVDXgKz*5s8~4qT**OC=fpkbHn}gOW&U0HzkPZpD zaC?@Avgyt##5rzA#x^;u0maQ{m(nyu@u?-pEHwFnzAdCmk1DLo|;%Xdim-{a_z@ zltK%i??U?SwM0jn?Rp|uDT_KI{El}h;FA+Z#t zG=V&JNp*1JuvF*0zJkXPB zYS9fk-H@iYWoehqu3wsxh(vU)Jfx{-Or`xGt!m`r5=jY>KcBq4W|Gzs{mxb`A6@Kq zdcRuIOZMFog$QxSLK;UcH$;EB?ptcE=`GU=#okjBvy$WPU$q$t&VvQpjMSq{KV*rw zl0{z8=UK`7N9{TdiF@y)TC~VWy>tg%-)f0fHH*EyBeIg!PHz1y5@^W>M61P?xS&O` zH{sl@#*8M zE3_T;(YpXY_w(i+TRXY$gvU=u9hg3t(wuMlqvG|vb^(pH29MG?n6@n;OnDfB#7=*j ztaXQ>ZT$SNOQ2NpduoZ~Xss_KRNN|TaZsNo_)8OL)sW8AN;Ed5o!iPm{w-;AAc2;& zTt|NByJ*mg28vuiPzv=0!huy766KBZGFycnMy;CiupQGn$ZO*{rYK6e?Uj)>1nNKn zW6G?9{V=l;%ENTaOrRx5y9CCCQtsUV{Xha+PHa&#>%e%Cb_q-?EQy#mnh3t=J^|$i zEzE2c5-8=i?U5E&2TEai3*{BpD3roGKxP7^@D7lfKq)NkX$dkv7avZh{L7zeEzz`&&YiJo1r!LlK zq;=0}%M7>mA&*=F+jjSEz@qd=AD2KWyq6eJ{T(Cl4uE%t%mhkd8fGTY61frNYKH1RX(rY=fh-elH+>4W+25?C5gDy3X0&5>0|pwygh zGi1s6Q{8e!luMu#&v>b=B@tm%5P@^AJV$DulJK>ixz)lHzsV1j;yf^85#j&028};z z@cmr)^rxluKYG^L#*0$XJ@|hSD0S^=Q{~w5*ZqSIlv;Y(R4E?RV_cuDiE0%3fl^Vq z0;?3KE)pp9+8I-2<%c*wnBd)K4$wX+tSd?%xBq}X32_AxZw9-{MZhjP^JWO4rX^xC^)P2`0+d25(n~*?nk*4?3XmocLy=6*9IjI}@ zBzzpP(*D#W%@h+D5?u>Fni1x<9dw9D%H)SF?a}Y-`8D@td94o=c_$}_orWQ!r%wxK zpj;gXXvToYQm5tLo`+Ho(-?Q#PBRbZ5@bJr6#eJaT6{`^B~(iXU!2d;vlL9L6oTS6 zIx(Z{uF$dGAr?;$gvh-nzgEA||v3Z_zDeUF(7%}k)@D1|<0 z9TY!*f>tz`Lqr{zPezu-nLr)*JOZuN1W6kLLom-#pG#l}5-C=t)qz$at#t%n%EJ&O zqLRot*p15Vl3)&iQ_=RL@{dfFBX^m0F#4T|H z#Cxt)D3wn{t$!p?3R6Ld`^HrDHWXi!%)aR<$B+6KO_vu3`GI*O#DvSUylD-JlW+7s zU|Q;+^c{B@zt~yZ7 z8#S;vS$F4a-H^c2hcvC_TvpwyTv(JG_39Z;xw>&Im8D{%W8AegbJyScF>+y+x0Bu` zf40d2#}Ds1Doa-wDWqw|R$eu)-k##*&T3=bw1RkV95Y?M+}G;py*$hN?VF? zi+-RKmP#RZ&}p8pP(A;C&QvD{7kx)<$$i`_7Cu@iMD-;e>w?Jq0+p*0j5*FVv7iU^|F(NYI7yFfNor z3ta-GP)9~WuOqW-qj)WjT@S_U8G?NvBNd9R!X68z5C5Kat(wz1vsBjpDv~1gv9*ho zunr8tc(KlhgtiJ>8EijLUr5k}@-Qycfj+qewg61n-Pf=NhlFAs*w$j5VA}4u84`4% zJd6uD~JZxpqcC7QR4)hkKkPZp01MikBW%owK zRy!mVTZQ)`yhkyevg@fPFbA=1XCm6eG}IEyL5vsmMe;hW;7*V6qCS^EKbWwdV~V&0 zw&6_JvV{2*5{j)t0^3@qQ}%n+1WI9Bo0&kVx;i|Srg0#6hPzu+33Bk{td2W;qL>=hI z=e=gg1Y~{)XZDpkMf+bqTlzPd>qrCZKmw)4<&?<2U#DAhhXhKwR#94& z*|b7Go@+2GOe>5@h|g$zQ-kv3iudbm#E{<(&XR*}G zxUZ6EI}#XEAgup!36whK@azmLOi>3)J$4$ctoiwBx3rTMl|-pS*(KqM6qjf+CoB0G zJ=y=yD`(}SCFt!jzs-WOOC>#NS6LvDd}?;M$^vx@(VyPMd6VA3dh?TH7YvzLl~y4I z>pjpy>JShSt;>jPXTq+<;F#=lbs=t?ndSBPv&hR{b#N2bD6DrA2F#G#y4gBdnN}v8 zYi6UIT8mOqOk8Vw(OlwqsHzc0T zp%sl!8Uig8;;ChRqWqZ2-u8j=G$gK|7H(|N!g(EN)#THLCab331|erzd7UHQ8HGQ#m@&k}Jul z-GNU+tUNV8@zST0y;|*L@aK7fT=J4V9AjmM3oAIo_L|^Q}{MG2Tt2BPZ0c*}A>1^^mOx=Lsf6NmsJ(Fr??#!BP<)siLZFK07 zVnCo2K7D6l{ZXs~L$KYA`a$e@@JS)C-9=iBIVgbSO?2%ENkqQb=n;8BTfF${>NQosqQ!wi(!# zAgyJUC9sV`DWqKjbs*7^T88KzW#Z>=*AHw(k#=>UhbV=uwo9OPB;5N4n`8dm(txc2 zwh~%L@a5(?>c~uBJBS391y={!iv-5x5_tE*^g&t^%5ciVRv)FXEVu;TGmyZTTmpL& zNMKCH9%~g6=o8Ye4y>Ihg=H)x6qiJl!nPgrKQn=CJJOknTUyYFMUQ1|t7Pv{#E?bU zUYQUi-1`TNQdQI<(=NN=Xq#4eIHS}r-}K6=(7J85564j$Z}dC2?LBC}%w{y2h!ZGv z(L1wfd}UWIkH0f3IrMOmSN_C}`H>Xuh6++RZWChDw5(+9qeb4bi<)%BQ3^`ABbNHK zQ|M2>6nR;zXKca|3rbzvbGF=8z~kEZJqlAavQT-L3hZx`A6QDB|FA?Jtb37;7h?(u z2Ay(+cbw>*hQKOi^e7MGLLF#fW*rzW(jh^5l!tMl6y}X4_)8NQQgip5aCJQ9jZ0t% z5|}qaBq`6cD9@S29K>9Ue&@8xG)hG&9I?{#kE2D&r>16k1CEiKag>Tue{Z3;d44c& z5(sfSJtt~I&x_b9Bv2~)ovjMSwR)X?6oNFZ_=iz3?NUG^;qD)sGw*SXj8f6>pd(l< z5{;cv3dhl5S|zO?c2vz4nrm8NN~3$b;#;q8PulhEk(3Y^Z_nRp9wk`ouGhCG?fP~k zP%8R8DD7;J;*yBDR%IVq@{Gv?%o`Tg9~CF=8%i@!-|~CH%DbRWiQGHF=3piQrBGiW zA6SLjQ3~_lTEzb<4?~b(&+S@XR|nPutRGnNT^*QKD1~%LD7FeMLC@1#MW~yKSQ1kR zir?fQ#*6uZrIKXLN5yrZ6za18|7+%Vd-c{&-}IdB-oeu-&&`OevM|^D|N5ht;F(k_ zwfaJu7rkR<8seu#S>D(~MaiwRdv4~trCkww{D0_)Yd^C(mhYT?2c^rH5Z!4;cp}Y{ zb-(TQJe1;bg0--q&kVV_nUTPG2iv9VmtQoI)rzi3v;-v@lvZ0#<1Pr7+$Uf_2cxT?2=NDJ65=mRd1^ zC61;1P1Cg$`lKTdzETO(&YSU|r9(pNz%tq)Z(4U*`Q7R}eEy0kU5w0vu zDM2Y7tJ^iOBp7~_hpC9^Q*$?Z9s;BTrO?~V1g7Zz1yQ`1Zd!vfddxV=_9jpY>C6O5 zrC1f}!1Te?)pHJM-@(E%is_b_z<7~PA&ldkx-gbS+tH^$*d%~MpcJOM5jC8f=ct26 zUQuZev?wlDm{w?e#&YGXHRz#A=sn><)Yb~&u9*mQ&_C{41AO+6bSSTwz!2QcC`AWJ z^M~Gwh1oisDM}f0YH=pGgqt-!%0np}8|uh|FRFv#8V2Q|6w)D~SO*e^vgd|x6XPs} zCP><}3>bn0&QiDpmMbJM|1}YOxg`p7+m(t$xnm0m4k=Y4SO zg!vy5ijpgdWeKIQ^n?Um#E39Ad3+w*F)P4mTnkcBN~a;nB9nvIeqj1w?Fx4UQt{oz z&=TJFC0Y;1?~w(knJawy8eI!t_aF2wM|_VupVm6p59Y%0Z4M-&YbM5g?Iqr#XWPq{ zwhf+Cn`e-bl<;x)EFrqmnSH#=_y@V&gQww!Kq+_UuZzC&5}%Qdtl{My9Vq4Q{Phy8 z+@Tdb-n<+7IXY0v-IeR}ihg1gJ#im=N;5|X?k9kU*O z>4e0OFWu}-?65vq`(ZrCy}R7KsqXovFp)cOvUk~+uLUbo41xP<;eJ~}^y%d%3dT?N zMjYAXScOukk4{nA=Or3YTHV%oP#%^Bw8Y*2>U>%W_z)2ffAgI~V2Zl?czJIo65VG^ z_L}x73RWi=KTrx&T8P1yCKAuine08be}O|_ief4V(U9_d09nNq5&b~hqu+TAJ=Kr0 zpnf<6O5u|Y{a#TrcpFe)NOUcHU2B6i2ts7P<(2(NR-JHNy{?gz>7Ci##qtl*Q{;)9 zB5YM-a|)k#{m^?hB7stymd}!VXPaH}=q$sRyt3i+%xlm6U*#i#Qlmek{`HrZIN=+w z>=mj}q|xyMrJ|VFs-J1i-38P#)S1$vD|(1h?mk{OQcwR6TG4pp&T?XpaCL0`fZq5GX1usPx1)wXmecy? z0v<1$cVEr#TuO%i+e_>sBD>YMc}P5S60Pxh*bqny@z$?iqRI`Elb`(d@Ma`xo?n109i+^41m7)9{(bs_1vt5wt$qswhNxoF6u&qiZHgUnU)N2Izl= zdz+D%^ER~x4UCS57t`pzhq<+K4`CbcL`}B9^CGgJxMw? zZ(i@{KuZn*t>1P^^4m_7xVkN|w)ZI}KZ5t8kQO3`T82|-w)OXYG@3eeATZ*=` zb-6;CX31%!eUzSDjx9;(7S8JMs+Y+3H_y@p()6z7^sHpxzlxF*Pujj2bxb(7M2@U) zh}rFE9rAEe(#*umG&8Zg^M(zm-%d`}8{S*@ODus> zXA|*tNZjytud=$wPEJ0)uA5%VX$h3Vl20d}d`~-zQAy0HSms!Toq$LQX*+F=pFKQ)(kMDw575uMj6~>!FD8nfarO+y` z4Xu>^^tLp9pcJ-hDMY9PLy&OWD|W#ADGv#h>Pao?%Qe%bE=ol$k?>Q9PzOq3$>>Q> zunwLx-}RggbJ7YeOq;r-$Mpj($xNUWT9`tRMf_17mQj?#x?*HmoC%BOrO-j(TNTLvQ?_yMhc)Y=I@g&>GOt{-SS zwh~6xd@Bz_kiZh7WfgtXAMH5+_dE0nQ^6%Lt&mP3G!{>*kM5(DV2|9P%WGOfTZNWj z`XKFEg;JQhTzYawKb59_;2jdBkal$-fp<%!T>|fScpt3RwnT2;aaUv&dCed99*(q2 zto*$w`R#183JK@lu-6}5bKCwn-DfA#ljJ@B8km=!5I)Wt2~kXU&XGL+c=4G0C@$sU z2*Ta5w-)tF5;Vqndh_WHfl|2Nuim*Oc@pj1!oH&pl!|_5KWOxr40fOqXfH}dzcax- z*I-8=hd?QJj*NB$N=7>ZMRSOelu4^-eiwB_QaH;=GezNSBwK|#*b*BTN=3gj5q*@0 z-r|nN=u;@KbUlY%lW|Am%mhkJm^oK2DADipK`9BWqWDc)f^i{%K802>A zXRB_Z`1455@-aIRK({+aV`{7oo(Ti<&W~P=Z*d7&T~rS%5ZGJqAbq@ zN};!(ucS8dQQOlu(k2IyK&iAk6zjlvF?EsF8c5pcKq*Y05pCwkTAglpTWYNXOEs1y zEXP_$@P#Eq_!uwha|zUe@nUXjLK%))1{jm!NT}hBB~s13+x3GWdW!}|beNkdgi#U8 zbIgP1R#6T%H8nU84pJA>9sNMsCD0GF#Jvx)1BRn@>`;C%H&I`xgF*ca%G6ZhqZH;% zNGK*SUX()GB``%Xeb6U|aQ5WjXA^cG4%~CyB`^dD+||=1Fa!x)U*-}Rf&{KFa|sMV zB3jiG%9~Q6-_^G(&yc_sn<1kJa@KMpfh!MP0z;6%=U*W~7b=M%Nbt3D=1YK4xq=Y{ z_6<`;6HF-j&mpjni*yjZ{zrM(-$f~;LqhAo9xK)&q+J4AeY_jsUDPEo1c|iugHY;C zLQfd$1k$b!Y%@>_Ygb5U9Y~-hNV^1Dg7Ky!QcrQ!dvcfv{WcejlziE^T{mibw+5$@ z)T9%R;^#BkGeGeaMp}nz2z5H0kcT0BZHax)Un>W7o$0^U`Ym6Gr-@j4T*q~nlMb{5 zPjqyxx|fJ1GneFjNCfJ@c##%j84-8Ze`C|ifUuU}*`i3(%q9{2=iQdOlFlE*5F}h3 zmlDxy{FaR;A5Z5&!fBL9;JK#szB3U6pWKvpkgUR!*YK=9*1dN~^+JbW_Ek)ErORX;CSz3BnU53Errg%+CAPha`I$EKf%zz`%* zAFbq|Gndby6S~`%e=2e5ied7MLv^LtGOs(IYgs`w&~ z`c+^lI_Yn)?CaIj*Ez8iRt+JmkU**SPYjkfjW+t|^bsQR=vn79A|85tuxvNhgdpMi@j~Bg zyzjbuiK$JVN?=?_U`&DE`XA+?hv>PhBVv?y-moF^k#}loZ_#t~FO*lDx<~g9mR)BU zV&D%$WTR}8qL;$8F^9fWh~YO3v*(xkNNa+>G=Wwf{cecNYGHJsg)V{KV)|tCT#qK~ zu6}rasow4H^KYn*^XF086K4v|4)Qggc)BGqLarQ8i_XkkR}lW5SApJTe5)kOtM$8{ zxl>^X(j=9*W~9v8RYUswYY_1cofm!$y*0A?@`Z_zAgL9J;d1ZWwd8_3t9xx87_QT; z>va#1EzgrxmyuPg3raVK1nCG%30;4AbQslUD#sU8ETA2MXr;(IRCX!Ak;2*)(z>({ z{;9~DdbiopIt)RYq+A`l$&UtPRlmy?CNKp3z;q)&s+{fR-j(HjJlrKheWU|vw%&eJ zGL)j9@WfkeC4{({JfBFO4=<_QHBQ*J1XDqXk12KAk{_?MKAexKi$1x2+)tiQCE|-q z-5f-zw0N?)$B|{Pt#9n)IhLhea+LgaL3QbWbS33< zb0Ypj1jm(5$F|b>3DhUV=|sFmM32KqHzI-YT7qeg*?v?K?X^-I+Ue12!m+xQ=6{KR z5G3{$UJJxG^v3T%%Eh0b>6|~j#Wi&HQI=GB4h9{mx{WmL&_w0x6Uvjy0~RK*?ZdJZmT6sUbLoy#iB@OUp*s$SUTu1hi=jh>)+E>}K>$|Bd&&^F-LOHmoV0r=O$J~uqN&g;Q z+I`GT8nN`vP1Gmiib9vbc#)2kD>(TZ%b44?M~qt%vGipoP^$fxQF(O-lImrzBR|Ih^*qGE$@H`i$V3?#?SJc%$YNc^BVN=nxn$ z(n5@+(*6pS_8(%U9plB4Px}|tHZ|%rs!`a-!M+dHE}Au{R>7N1`}O@^;3qHy2}}i@ zx}xN*3%sRI)=6T|3-w`4bShrAv%Sk|WO$X3+!fhFlc8Mmv za}#~2T>Y7tUSLZix38E!Zmqq)M{eRJBF^gUmPC}=^7AM;@^aHk&}ynBJretuPVz+A zolea+d=jVMP{6xaT!*Dy9r1Xr z6w?1&2NLg(mD<8khvKw~kGy`JM!OGFuFZPLqdii3>+6u9i&P@$i|P@pr9-RqGsw8- z))M|bA|fs399}!d!TS=<)<^PeReXnWyC*r9nDo1t$M@E@yMHs0vd^~m9Cw>$DZ9fo zUt8L>3irBZDZ3Li(k_8}d9#$Ygs-hnE)m~t+U}H%`dk8cF=r{e+caNW+9hzeX_m5Y z)FAB=_++G@-BgFLv_mA_yK{7Z#r$^(48hjm(6VV(p8*t?5V}+-g?^k`-x9v=znIZH z!$_c1iVlum>p&^gfvFJI58X20b0;j}>7Gp(%P@8EX^*Re?Wd1o%TVg19dm8^_?ZcO zj)Rt9OgjFwI*>qpAwd^GJrCwAFhwyHgm~nRu}S6@G+SMUQkc^GdpR*JXyM)6t-L3z3|)85z_zQ7r*{H{M=v#%{sIHl^VGO zN|~5;`{voiX3{aKQswonXbu_YUW@x*CHHP_UHEZ>4uxo~5Lu+7Z|^D_FC@<~1PSy> zh>Ao!QQ_K6lZikbtLl!DwdQs#tom;2LZpSbn5?Sv&U;-t(|kL6j`3nDgn3RE%KP}9 z{}k?Ctosapiw?E#wQwOmy$HiitN*rLn-0~KYmcM zSF(ptRAk9AGnRSoom1J8QV6MJ&rXsMcV-wfBa*U2izp-{%UI`mXpvM%6w;=mREkQa z?f?Cr^PGD>=UMvyo>!hZJ@@-Q-|zSA=iGDdJ#eMG>Jq+3C@k975l1}x5v`=(JTi{< z2?}_Tv?_Hnu4>Vpr9{p>*UInbD;=Wv$^r75$0n)YZK8AIQgTJU8YhT>yGzSSe~edB z&kn9Ef4X370s^H%Ey~L8ubTM(5EvH{`6tWD-pvzPg#=3V-C0gv;5`y=l~eroa&ke5 zd-;mHux?qo;JFbh-0OH5`NkX__C5DFKaePXMOAttzRnM%sm-8O33_{$YP0Z)>{?!4 zaM2h`-;H(CT_LK@qG#{WDTpN{bJ9?%>74R%L*qn5)GlQe>Nx*KRr%9-U9K>usJ!~) zIY^)%Nc)7+ftD;edzoxr`2pp}9QQJLT}vHKPONKLQ64<3{XklqL*GgV+KZN;ZXy1p zIqhGxAC6hPf|i)yb?WN)S>AI{3N3l|kBYMQJr60LSduN`kLREi`e74vBE{j8DetAN zZH_un%C_n>&6`J0EfZ<5e{jo=bt=hDPiyTcg|s$@O=7Ez6#c)8X@wRlg1_}MjKV{-2aU@o>5swYpRPhof4v1@OnB=#@aA0lrq2jc^);* zZsB*Q#z1NPPE#QE`O;pR1rw~1O@2c1hnCyW3^=aRuh;I<4#^HS9!Xf zw+ZY&^vqNfn9|9KWoP=!H|hq&1ww~?!egsY$K?A5$f=uj|ABOJ0(&x~Z2~>-+_<^l z!(pH36BMx13e#uLiAM5;M^$Y`AcjCG&cUav{(l5Y<;-jpPXbI+Eey93! z!JL@Xjq?NJ#Z*YH1LN)cL<9NFHeI`X!sE2URK$4C_Pk#9Z>4$$AK-xP2gciL+jahk z0}(HRQaPO)_@j*E1f~^AVcrN)lI~=jrCx{4LMapG7RM%DrPcF?sm)>nub40sn48KE zkEQ892gimu>YH$19lw`Q1b@X5D7CC>9hv(X^lAwSlnPy4N6vmv)pM7H)yIpq3Z?AP zYayLzy!ez8?%Cj(G_>S+Mr~^09+B)h>ND~4UwRiEc!-{#*j-mH9K*%J>N&?-ug`U| z$JB>a855#6om)CiPr+m1wa)ktzaHJK%UmokgxQZ6LPChv%kyVYU zWq2T_WzcyLjCV~v_x+&w`4hB>d<9eYbjfCZz0sm9&II~_DatxL0ri8V;|Y|)R3KR; z9q&1&52k`5q7?N}kUrS@#O0u;_1FaJK-$)UWeL*#Oqd6g8W$iTYGRYJZ{kc%fA$%=wQo32`|Qy^lxdgJ8RnVgmKilRT-$ zQv0rIEw4u1SS(Xnf>I{zmqd2o>Sh2?>lTIf33HEd-529UhNh?rk@g7+*sC~biJ9r4G>5$iN}(mm z3Dk~O@mR}@41}O@i4z@{co-MzFkxNKXb)SLx$%>SCT_xx#c)(msJ#kU-rw zfme_)R{7G=fZZEZ@@mT}yL6wyGk{(5_AnnABj6SMHOJ4oAYs(#5)jBTC z=!3be3A43Aq!5ut&&}N4{K-^YZ!=Py&$x;wL{ECgU2l4x-mdCS|1kyHf;J%5GkxyrYmlrwN zsyGq7H)5EDdweloC!W%o{rYp;tM% zAKYx><#a~^V-jM*BNHP%dpn`AOX|0(_F!$9`>4*_ft1frX4_>~*TK9A6C<}RbV9qI zUe_}J;o9=xQZ0ov>ZAA36R~cI6H2S~d`m1pXuC~_p%Wu_ed>msZ(iEkh%6ZuEpaG? z77Fp{1rs8J2lou+Rx4461nNUti1`mpjQrWa4NYA7#uoGgrLYFmTLURSM$xltDMbZd zLBfPN)mhGb^csL^0qW(~kq7td@m2DAj#502=D7jI=h_6_uZ__CT8Hn~qIN@Yu3=0< z@E!o(3&6JF6(mfU^|74!*dsEuvZhv|LTD!c#q|N1B#l33G=(wad2rF=eBRUMfR55z1du0!t@y|g)}{@n4YsuyS$YjMv95!lbgt= zKi4xz^SjnT`!XW5FT>$|8JJVN8pX3omda{ePkNJdiJ?7_D}4qtV1c62`551*5+y?o3G z4 zWBz_+8%klkHc{|_6W&W!^;mw(X4HXFvmR+EOZ3<2Mr%t|-SFhGv~o~ma7(;`1g1Kr zu>N?I=MkJK;tUe!yGop-`Qte_Q^Xl|@;t|u0j2C(tK-)?uq>ez(!RXMR^h7SlDx9A z|BHIn5!V)df&w%`hP6o~%(|#`fLL4(c!8JuKU^qUO@s=-6rq~5}49HK>^Rfw8GRy zpA_M-I1@FymXY5t&>rHN?z+=u*B6;efB`Ae7 zMXh{Lf1IK}mh{IWNPPNMIsbQ?m{q)_>{ByKl@gTNns>1*u_jynu88Ot^*B)akU)Jt z;j!nKQ<#I8H#UJ+kU-svp!jtT;uR#&LR$x3F{^`|H*uc(I*>peNZWgHaE;xBS%+CK z!Yi`vB^lW4wN#pC2bYQO&^c`v+md8oWsNvPvDjEa~t^W2kP_h>T`Yw zqn(1*{M?KH<8@$Mr@v?*&yUn4#>8Y16s_%tS&d|=R!`SgyVd`jKq*WGw%&Y#)hLb& zrOf;)UI(TXN@2R0_;r4u?M#^agJ_{oP{4EWjx-V&lTDx$`jK1*<_Fr2dE@KwOn#Vq zf;hX2%X5p*Cz7pcaR@S z0qekcao!)N!zb_x5|}sI9%B_=K?3u}-yx)0AL9qbpmqhB`_5*z*IeMFr zV91}Pa$HF8%%b_vkXEdNJ~n|;I2RFudmHX`m@(QZsP^KR)eoCsN&0xK@=^xqQhNom$mM_Uxj_bW2=xT|57)Z)=uApL<=o~p0^b2%|7>ZGk<3{?v(Zk3ea9> z*qe<6?tAtLj|r3-xVweFzu6|fhqrT~RVK{&k1^RL5wDmq6R0nCe@S48kzyj$BGunp z?faoUhx22&vm1BdD}ub%r36nRV98imH`PCNA$W&r$m}p>Z_QbWUdL1XlUn9?wvgV+ z9_+yO_j2+TOe+&s770vUlRkXKNHJ}G*E%#8??)o5NY;(YC-5A-MVd}E(yoE%PT5qv zf`sik?{bRnR;L|8K@Otr=6Bx@k8GoVd@?LZcgzpjwT*vv&G&;L+Vv8gD``5Xjlb*K zChRkDfew^1y0umInYchZ>adBZUCIx%s?In^Zm+1$CDMpIuu#d9wDuecYz>fB1b=z# zIZAb`_D?Fw&5u?03DYkz{mWB{`lzFiRg!(` z*7OM@#l-kSiHKOMj1*dEEaJaj1lwBdGd|osK&Jhc=(H?Knbv^gN=jHgC-$#wRmZL~ zlz$w9`gokW*w>;I_Pa)!|LSoGpTgyw!d4A;v?@Z0Gl5dr8vHk5_5z!p+UVmmSbiz- zv?kBt(H)=XGE#=doMhbV}qCQ3Nmm*LKTk1H1r1|4HNT9bSH+?@eKmtVOp86CU6fu>M&uJG6cuue>~0)EK6H2ZzoT$(z`SW?TkU*d4KIex{ zXc?`)j+~p7f+f*f1Ax5^^v+3o;$n5$=XLp8O;V9CslYjiv?9Vxuzf~~!$oIZ@2|s{ z-*qV=tHNy6J0E&2%oRmnKqs7{yDX5V_f+fmJ4e!Q>79bafpJY`_>f+|I1V)J3W^f* zueJ%3H*5*gw5RBaGLd>e>r-1simlqSp_za0#{91Rh?aJTrMOh{Q%?8_)}sp{q(yZ)X;E>17dmMXU(Z>pRNGWpCfl5};IlFVh zgPvo$DRECjv@!vB<&L@h4I>ieM(4sOR=btrMcgN7HWI69?u$C;x2C-8TbT4 z)|dea>=P}*!96UvZ!N!AXIVV$1s=V+Hi1&Oqit)+9| zKq=g}w)Id?SwyT!K%f+^y_fnj0pW4kMbFXOgnMx)34*%-^gq^)w&Xf+KNLzO+z+R$ z!o5*_FTTt77I#^Ze*N*-D!hUO>NWuXrF)RD#|w8>$x8x#Hi1&u&L<~O3irUp5uVoM zIVgoa!|_frR`~=4q;)tmGe3CU-5jNuzzL<2~$C};Yh&(xgQaEBwPM{Rl z?SurSm%a9i?G^SuQKv~fmcUjGX+@AUf9yUA)lDVRb~vweJYOIivAdkmbE?f?Xl;kEHSYpFBSIn;ds|) zh`=!}683mk$IqPS;D{Ba>fhhW-?67Oc9eUjd%`9p}shR z0itM}2y?P}wOh&)i*#9rKT@(wac4Fy6S5sIXWaD24i>!s?GMi6+n2olXgM z2*J!ki)xN`%ECMY=L48Gz7CJALMhY{N07Ak9CHn&Fcp*zpRlCBk2r!cqUZ^KAob~6 zX@1E_PT))kOFPEI(R2L#@f^$pj2HE3S^Z6?m!ULZu8phTQg+;ssP#b|7%$Svbzr<^ zRO`=iJmTn|UfDK#BXBjs^uZRvkYhM>UWF)`3@$z;@my@Cp*x&f5fDK?2LM zO<<`u^FdB4EMtoBG-uZ+j2CHJ2gZd`ab=fv&_5czLd%H+mSg<{%wYFtum)f}r`TfV zwP|z;={Y)mwzA#X<|dz6>U_hH+|WqxJw|>}*8qC~L%INb;JNcmf!1O^Wq)Apu zYXUt)DYVePE5qw>o++agwo171&$lY7$8%5z#%mKHN~`($q@HK4unrUEc#-Dl&BuN^ z<-kS_-T>=8HAaQcselvEtyu{aYb zg|pPNPhBD3?4W6q^*>%@;rHcf*0_!16_dZYR5txqhfm*pnVir~hXZla5t;L51$pky zEsCh$sH&{fROi9ED=W(xX`9sVK0yI`lNnq=V%hW6Sb4Kw^Nt&P<{-%fO2rRy3*)$JwJ@JuR{^bDpZmk{?&2SysxU9S1PRN zI)7J@)pr&q(1Aq371iZ;<#s43q074gt7{yP)9;S>A)*U%&SpD z79W>L2c`+8)qkWeq&pIrBDNoR1qrm!5c+*y)gSsPgL80yy%(KvWn20=h_oU2FRe2` zJBYO_uCzxnn?R|)Gq03i?k}+OTS$?3WnrfFV$-!obstAPE}D z#1IN&KTtbbrSk)U*J3ME7+sOT^D?8X5V0+GQtKi<5-&R3;KS;+q(y?+=D+l96 z+ScKFLTQE1*~ZZehs`sKe0k+Lof&!jZKd??9tq70BV+oYX zx~8%Gc^a&@`vhJ=LcLo$6$nznA1{LIl;~~K!QQ zG~nF@q-_H4G~nF@q-_H4G~k*((l&uB@wh^dv`yd&J+7D|Z4=h&Ijoo?tq704=^ve< z+c?MOmB)j0>ecO}&9yxI{Ye3KDVoLGf!HNT5{P zp4~ym@L4+xLnX#VmY>ycOgH@pmScYNJO8fV{C?@L|876`!{o@-`=W15kERv+q~2yu zzw>+Jg=kC&Aw27`{l}lDFAgj<4%p2P(@;dslM*!Hrw%vJ)oG@ym z-K!DhMNq2zQ%&V-t8|Z?oLKjLia(l|oY7j2xhRbqjCaIg z$DctY*Mae(l&zyUy_fS#nx(#YNrwo|QW`ZGp>$l?>1P*b8T3prdiUogG`B5U{sL?M zfdr16gm|F68=6K>*m#==ywbh(NR@7#dmnRg&{C=m*AR(kgxwjw2+b4Z(luX^7x0N}+}HRxT=6U(-IBrBtqP zZh$i+E-~ymmoXtOBVrd3n}|RuobT8~11gF2Xg|*JOLa*!sfZ(4oA{E5cj&3T1F0mU z6pl-|jB%cGsTAU|*4gQ~wA*QBJ6#e@X}~d~O>ipdkKOuUy*t&jvwZ%feqN)I=D#|1 z&0HVr`GN5_%kh(9)&LZNQlY!0y!m0hGLf9%95gym3N5s)!g#Yb+~lu>AZ-&UwRKcm zxj}4IZSB0r+splxbl4_PYVyo>)Gz6EnB)ZNK&j+b;miy30LP_5)K1SX98RszOC6er zu%#Y1WwdIqn$2A4V(Ut8=x&ppK8}d*+cytmyEO3bQEEoFbL{Ib_A9jNL^=i%(YRyt z5MDt7bqle&og4m^TG3YY4rbJjR$(t8#3mw65Rpy(um?W`0K8EjKq0W8Kzyqx%e%+|mvMc@o;&|p zt#)`x=YEk2T|Nn+6t2nedm?#Nb#?Q~WYt?ltS92b+dc{5Dh^5^EyP{q`F-?6E6xv0 zA6(yKpSVV8&$soS6PiY6Iy0i4o4STQwM}fgcTG6_#J0k+Ge_pNh)URrP%iTnXWKXa1E^Ao&fVLhQSFMR@vznc*+4 zA7uGaA^dwrUnG`mnAD@hGjswV`#mBDB_r zYj2nbXyN}?qjarh9Vr(Us@nD6gptDBq#V~DkMi9*Gm87{?6dl>Q~iE?Ple_u=v}}3 zMnChu#DW&l_b5g#Oiy|))xo#Ovy^$;JBQ85S!0wgjd=2wrM5SnsA?^qIQ8G2EsD|G zfzc`^@U*NsYy01Xk+QAAcQ424Ft#&ceEM%4=43Hi=zC59&x!RM36ly!OrNFRr0l(V zrR_OeXx?jydBZyNc!5JG#bNWdML)c@cN>yAG19rB*!;q!FRf9jYvM&ITgTfcM>6*~lDShScjXKwm7}0Tn+<%>VZZ%ml_4G(*;v;hlUwqOgP%7nSDsL%<_?>iw z%gisVPdZRL>OkE>>?c2t-Z{T;)Qh$bjQ8wq1LW)}Mo0F)Bb{G1&n|rEA)9F1Wq{nC zt);Rkd^pDtH-9tI`R4q*!X8U(0;R^?F+i?*&Jdl5IQR3s!u2r(T7vrMIV|M)Ggamn zzD%B%_xhT*U%D8t{a(w;q@zvo`Gto`2bVZgmN4DywAxT%l=I%O{K7?DZ9h;4>K5Xb zC4-zl8|8;@cy@(l64>nA%c*H=)g(N_cH zvD?+}R7%o+?dCjQ@>ICzjzt#H@s|Obe)6q0R15bhBR4}!O z!%0r8Lt0__AZ_C3zjVeO;zhzvtAL?*bAab)duVunIj)j^R<$qhv5q0-`b&4T_VC#y z{pD*H>!&SrU)W#fr|GayP{4CAUZnpw9T;zN0zF4>lcz39VV)->lpmN@D224|xyPO( zfwm*92$I%44odB6H$Z+mN~dTX!5C5Yogcj0gS{vVtB=Q4VZ7_^9w2u-qIJaW*=9r$ zxU(BANlswAz3v<+^Doh5DUP7{wGNCIEsP@w;18dcwfl@qTIf~{TWu}N;)XC%*ur5O zOs4_qy)xUr(Qku79T>0wyXvESEo2qG1(>CbKA#AL6Ig|opgtc`exMF)+fkoQ;1z7o zF%@hAbzr-T`fLJqpcLw}3DkitHR`j8bMFt3?^e_;^0U7VkX_g4@Y&A?$P(>MOC4oA z&U3tigx$8c8#>Xs{fHaxdG~EwQ3~7k{GSKN{!2+m*D#Gs9;Z7QRcLO&x_n~AKW+SQ z^TTb9wi+n6SDviaFNJt!{6r`GniH;n?T`2Pr$LVdmtWff{isREj@hN~GJvuMTnd77X1d1_J`UO}RG z#ldp#SWkLSM$&~iO(ij(&TWr8u`CUxcGViJd$p$zq}#;7AG$ds7wKAygb8~)PNgGl z`+?q$qbK@xAEis8p|$i=v>wxj?k@fQ;I%FB%BlYM%N3`!x2N_DmX)@X9~)+oAL223 zFUL9kJOGOTDf2s9Nb8B<2il8LChWGom}?a1K$gk41)YgZG$T=M-eB;N|CPqIV z>%e&LsXSD6T&M|*$pE^A!z;6%7%J;5(*)|Hw|UU}AeYmc{zt86Z^A1`{BKqjJUdig z@}Aa#7W!6Ez>A<&D24i{$04hpBCC4lE^C3>QEJFdL*@8KGv$u^)2Nh`$vqLeVSS&- zv^`gckU*)TR}Gbu7iG#mPacgaS7-_Pf%=5Fo8Gm(;g4>SJy)FGgjec6pyTz{+!aE7 zLNuIH#yJ&zCI=EG>@FJ{LRyHI^iv(x2*5~j3KldPCQon3ly0{+A@e)aG){QrM5stX zpU9EkFXLP@k{C<8p<$c6{$}S?8pV7ioG^Q}&5aeX4^i_t~Wc(@Ct%qm~YG8eEef?)J=z5YFOoENZVn{!8l& z-5TVFN5&8+h2s?={$4c5X>xad_>x7o4jh-_Xpr6kN;)oUk{_NKqXS1ZNDI+^@gV2h z^!#v*7y>OpT8J&Aqr`Rj;a;>}g0ob##2$-YHFJAYEG%MH$>_WxG!XSXSM`s zo5=D`gqm&a6B$gs0a}7R!>->4n%vLKe|`78bkq89-7c3iMD{wOy|wH4 z#$Gc+tJjZ>w0wsMXvvVU+pDd2%?wQ;V$XXvf$i0}uZNhni>x|u=gd&;4Pzr)cfXg8 zaiI>hR*3S{51wAvCo=hcyM@D6&GvlBZV`_}A+%L^g$a|obHqa`y#K~s)JOH$T$ujm4%)?9tyDUer8-?k$fiH& zyg89IO!hf%YOPpWm|kk<HWy6E=@|MqZH<#O_1*%_vM@hm{vu<9^=Q0dBbx2i6=1Kkk;acz_?H<2>548wOINsUt36BYs z!V;d4P&%;0VaaIvz+m~pM!z=*)Ho=G~egdK{*00(%_nWso)m|MeWyj#iz$ainZ>xf<2DKH;%1I0wZ0gSbBD6BOY4YW8U9 ztDTG0I1$G|KB07ATr5?8xH_M2t;yL0u8|>uYXdfcSCGI_luh6jBybdE6LWIHxyfjv$v0-{g7~KE2@a@*Fyh_eP40{rLga7G%%SCl){!MIe}8x z;v^)DQt<76DWmkR6;_P5~w+D@fi>LmLN?# z&mJ2U8NEDpW3!sCgwwCfq`hNh)QENZ_nCUE{^3K;nZ;Q;tcu9a*`p!{e_z|;!OAvK zWOI}diyfqec!6{3aD5#f*C@VXJVz&XAH5=sD6hfmbj^ z<8*lJ1F;@TVb2*yu&~NSBrtuDHdgUp?gsw*e|U`Ps{^u2e`h3FU)Wvh{ zoF8VCfukCqpg=rfo*IC(A}D_T&~s9I2IrdbymP|y=S;+nmU4IN)MXuCMMq95^5|DQ zac2|tlwS3OCPUifMqF;@U21urBI)( z1LMWAgtQ`z4iC!@N+E6QKmyAW(l&uoSeB9#D1~J#Ie}7GmTY1Totq%rydE^lKq(H_ z{!2dv$R>;@obPzv6;JwdTVkH1CBzkUesC%6e0h(~58_GBO06DHC+9{!I@QH`7L$yE!cxOf6shQlxDic;*do3=E=D9gPh*sHzZ&yr7 zM5{O)Jln84>8MPlq-;zn!E%N9Pw$VRvimEQ-KS#8E}mXSn$B9%`Pxruzgowb6ViC5 z8fhVh(5c@+bZ)yMo%+S|*{a^rv%9!GM_Pyh^ehV|YS6PR@N76f%K~X3rqNRh{wAwx z#5^wpPXHoK`(!AsmQ($>l+p_G0Mp0LkB=zNTTq^ljmdMAvd;;>uG1>QX@zHzQOZ8O z+)t-fq!OhSo{B~(?YTKqovG6*QiakAPhq2!{j`OtI;|p{R`^5*l)`g?ESoRozm4 zA_AqVH5{X!ys-3zMKQ!vMC{(Mu3%pRtBfU>oAf*YA_g{H-0W^5@Cw?Fxk)SZL~Qly zrZyz2@Cp*=syraSez8yv?LVFB`7$C-{xoG{4(UMKv4mr~(T)@%-ngz_N=+j03KDi{ zpGm~HhDA1&t!Wb|b-Zw-%(J6)rIIy#D-U^ zHLr1nogYZp_53Um`>(87@YUsZo?}WQEyM;QK3|oY+KN0!?HDhnA+1Od(c`HXQj3!3 zD1~Wd6U&L9mek9N>fk5Bn9^rT38(s{hYF*!yZ<7Ll+OR1QDPS97<^Ojf-+je8}bPMqu5r1X=o$@kSh2;uqY$fO(BoP(fnNjd;j1G*~CiqNvt6EbF+9vc|+m7vu z5F3g3@WXcsnh}8>Vq0PpoK_n@zGu_q7#%2OTZO$E_g=UTV`hz)rqlDtJLwgKK%RSN zB;FdFEeB3t!FeDC9LtttTkCLgqTS_F<&7=%`o)nJQ|0FSlC5PJtM1-7RsOU}&1(eG zz78+esx1ws$tL$|BH?-op|&4r$-dNSvfPuJ_I3D#SpZR291w`nf$4)j z`G^;56DAI2)Q|A+j*e8$|gbf~pHvnIo(5@(QcD@ANo9AV~}NdLDE zGdEzu(8-C_bY5`JMm_u0^~Usr0i)B(lyEM`X2k?O1=Vr^FmUBJu9F#(u-r@EKJ(HaF2{Uo$_G~GK#S%76Z_c8zRri@?oQqZz z*@QY0(;P2-JoQbMd@wqbdKs`svgNEyy$+YKT*VPwGUk1!38XoC_3>hLuphJDcv-c(NJr%pSas($ zQ{{DO>K$IHr8cxrP{1zjXuDl@1ICM>)R|h@w2rU+_}>Wh7U@6(TZLDUusy$nW-EJX z=C${eNDCzHPtBGo`CMx~$F|BP;=a%HSxY1^UZ&rDkbUBE6|LtHwhyH+Ke&{X@4hg4Qa^;0BQ&0+X@a4C&WtkNw2czw7gwqPIAQ6PwbNYDt zpWW_aYj=ju`{=q7C77a*-6CV#m7G8+Y`c;ZD8=ccdo|BVNO)`&Ucr*l^WQ94ZLO&v z?127f6IfpDl9-G@tB@x7Bswr&v``UI%C-t^=l(IsbJT%XkhuKYEV<(pj&<2zj_YV`G419J&!V8EP+ySI!Ky7o`d$H6w+Fj z#hE}$uw6+=C>?kOiMZB>6)7E9cF{r}+s$vpcm86_6-uGL1lHJ3@!Eb@7eUELf+{Fo+}+VgD5-NlULPN zBN?^gWmYNln}M2ss`^@Cjj0D@%C1lG7+1K#@4^-0QtAhr&`F3{Wv(p=&5(mDaM|;(_VfFA*z-?l{!x?W zA1%8UOADktXD96_zM!f0v9xr+vN|iHYMg<-uDtfwT}` zp6li;ry0vv6N*^``hm0%{Pv-F(Rmc=Kudz(Id!{|^!-F#J*9mCUOBlhOaA_Z-UncN zewc{Mh`8bG@D!Aq`(3tNazwqaC?>5aRimHyd95iKJm)ksRweAwH~sS*ELYBwOj)y+ zPEjmlHc@0=rtG{>OX04E4<5~wBlej5(D6s>Ib5*_f7Zuo6?{AgEkS*8Rz)GtK`FE( zIe~eLd5#vU=so(Tf3{U9g}QA5OWfQqGiB4pI(@Lj*aS*piLr?xRBL~vlcsE0V7onj z2*yO5y7v;Xm&UuSEx4jbfM#UDG3hW9^N8qv*a`i-;-=KZI)sa@gb)j8Zg4?mH%`aI zt1@MNdEH(Gx><)uI_e&E!di;vD4u}e)HS_K5N4~|(z)$DbZ(n<1Xnxoa?8SiM+hw#Afv4P}eNEtcZR$^~nxdvd9@t2c9?RRYK*5(NW{B zESXYT6G+o@)qd>e3|UYn@<;QOmZ$^cMGMIfIsy9FV`U=!Iz5zzM34%?MSV2qm|ey> z`Ad9S4bRS!ds}L&kfyVJl)B$j>R$FrpOoMVrmk?2=3FozDjCq0EV*`yEa;(iB(LY} zhhAHvehGRqq)q(%*Vch_Lc(L>ohsBO9@4#KP>zLr`H!9~^@T3^whpueYZ1m|6DWl` zk`t%{r6NZ>Idw-Ot56DUPflR%L;@}J{ZMIzL{P?(q?ICqQeu~k^vo=oag)w}TL+dO ztf|Qfyn;l(epzzY7cn|WnZ2@&tJy_N5z!7FaiwZx%ENf!D7>7nP;^Q)R&w%vv`^;>S+S$i!zT7ZDTZObwC>^-#7A-;g zzx^0rpj%F+jZgngr2nbg4<-!#-^2|k^fR!TFtp`I1n)IoUU!hXUM9Qs8x8c(AI$j`hm1hc(Hz$}lsq+6 zezimsm~M2&{Q)P`Z_-3(-35&{V=AK5d#9($zn<4LJzM>PhM_rKCOM~Gsk9AK5mWch zpQp;2K5@Z3HFT|+6P19{eg z1WMs}j_#R%x?%P!hJqcZhyY9)-*>iEx2=q4hvI#f?V0;RCz z3(>J?^HB3*lbjBRnr*{!g;MuYT^SLrE0hTbozR;)Kj`FK(2}84@H<;|Vy_b}Np+CR zeQ*WCo`dCB)eld7Q%e?%g!TN~tkABmHBGKnyO3B%@SznbWfhMmX>Adtf{`o`9!sGP zByj(PuR#%b1nZ362A#v4a?n)4ha zPzvjctpl$h5!9P#*<~H{&o1FXk7IJy^v+ru^ITs+!mfk7?}PVd5Gk<#K&jw&w(162 zeXK!uO=1a@y7Pk^Ird{?Rp(i>Q=RUC)UI?(Y9K}Xy+8{4E4x?2J_@CR-`T2Uy&6hk z569MXI`PL&U92C|_vOOfB`vK}7gIFwDM<_G#p)I+y%+D#^LNvswTd8V{?Hv07>l4)m^WIM z#hJjkPzTaBfme`7sDsr+xD7YEzi?L=x31;yP;2473J&OTVi8YY;Z)ePK!@LKl_OX7 zHAHmW9$^M`V7%NL1b28P-CyDN-O;}kl;YOF=;Jmf|9;lNrF|2q3M`+j(wR&;WK-t(gKc1E`x z+2|o1-gj$`Ty~GibDtps^8o3Dgwla!>EHG_^5VrhUZi~uaRf@CZbgu^&U1_x{Xkj~ z(JwD@?QJ=7Uev>#=Ug}A>IdoZBIqINz?hQjKmzsogwlb2T-hf_KKw#_-Y9~k zwdW{>w#VsUf<9gZ36w$$wJeJ>fmSVjBuD1fRqJp9WAX`xEFDOog*Jhu9i@;K;{16h z{3AUFa>x@^Qn55(-n`f^M}FFi(~aAbuV|;?SL5`PBQd8?$DJc`_@Q;P@J(eV zMTX?uv=!q;x&aZ#e1h6Uo~6duTIv~0JBa%H8Kfex6~&e__?`Wrd$kU~pL5jvS6i(p z?u|iOh>K~3Wq#BuYk((rwntYD;t1>kkXGf&Q{PkqcvfWYPNTPh zwd@CdJoOzzpcL*eQnEe~+~@Kfl+sqk>_Q?+uWad7#&a-zkhXQ;6(lgFlM^VFJgra% zN^yAq52k)7Em~Eaw?>MIsSkqZ9s%@EkLY!Ou1Aw5pOfprE12hnHFNw@sm(zh#uAi@ zvr1uXRjlX6b|#8=#*h6}n>Oc{aT*_+Q-GuGf45GTOA7g}42=e@yOt%1;CB|YqQp|M z1kF~sKMyg1aRp(|!SRC-^rq%W8+vyp6F8bcso-}m?NJ@!SOTSR{NT@6R2{^YItc4= zuxaho4(y28fgMN*fuony@zY_aEbYLK(8-rL9VivV#C}9)MT)>NP7wAS)TefZ zKuQSow%z&Z{?2u^TRmcSt0RF@!SB(wgcH`YO9NRl%Z$!qbCAXLhw6tv{`8zF<#MHb zo$r!q!i?vPwAO*PBY_tB1O;@7GdpEb3TZ`nnxk2fKNj^I%nzQ6nEf-VHwbfY@Zh#$ zo3VGsI*2ux-Xw6q34cuG_ui)-C_n=HaHJ^*zjea9s6RjX%7-b~J7bTGv=D_2Q^Q|< zIx&)WYl*Gchhwjgv^{3Peme-$$tP>fz&oi-?;J=`4TcfG^N}3b#Y(+%#PrTcpj7ZX zlq;2km?r3L&x@wZ<7##nCHx%3wh!rBXuR9(Vjj=M5~v-mYWOA1yd?Anz79+iq!U_2 zl;>c&qZIl?4*MUQKz&FnaZf{fwr%=av^^m~l;@zgn1e{`*o_XXA1H-%asn+uS`nVn zfjNk|#w$9B_c~+mg>)R@>%fwMR`J@28AWM(=$k0dK?0@FLPe0YCNQnA{Xl(+;4hDL zU|gsp&W|X@YZ<|F$8Z+UzX#8g(eX26ulwNY5U#dFqX$3qT}wU}5Udp8$UqSuD_|XX z1qmD(*aTidf_ofunnw}QFFr+QdKn>bH3Cz?)`4-M4(zXd9Ul9EBMX!YbVGZk2)u$s z@*W4TAaSNvzI~#K;tx{Stc#!&_E#k9f9!q`^&#zxQ@|cgV6PuXcr4BYYDXzf>7eH% zY5sUjU>+cWsbHivfi(x?#kwNIWpod+H?4)wIZ(O&d??>V8rXVKa3c-7+c~3R_5?rzGc?EZ{m{>TijC0*zdc8fkLU%S`msapQ6UFJB zNSUPLz_;%gB-SBZ^hsG2j#?GHcc=XbXyM|zIL&@(hxY)|F?ny{M!bT=9Y?3hU-EUj zsk0f8=-CW`L=YA(=SOgw!h3Z9Dp; z^s!SEiR7t^Y4z|`d2-l6y&n!~9XTst9e4!^TSv5hs2K~ViRrgFrOiAu2y>nv{niQX zpz-cQ!_F5V@oQ7sC)0{&rowGpcB=flsSXQ~vegM)_^@7KK?0?M-&x1oRBMOPD11g} z(Z=A4=ZwytDj)5j<3)XRDw@tSJVwOi{oPWKsQcwq`QBZcXs~vwtZ=)rYAm%S{fVeM z>`zMvN})b_{w}pw-HGVa;}y$uOdr%I#8vy8aPjfDw#3>!=hEfVao-1+w~IZKq>S#Ie|J*YJIQi^2URSbf6UKh$EEy>^WXRB4O&P zv_b-(0^36#3^tvvZcN4-Z} zh#FLzI@9S);sp{Y75vURq90F{5_oDd?(D|?QOT3E?g#M-64)o&1YSX6V^*FV`s#Qka`s zmc^Ms?MPruiXdrCU|io8&y#z8)qY_9`vgM{5~u@dpYWK#D^KL+$);cYhYm$xyqH!P zldppUwhkmvpHCtu?}KN+eEA% z?cr(c!D$X1zfN8B11&+H=ozLoiiw`cwc0DBaaOEOYX9|zJlVFiK3^OB&O{#? zxfZ_Vgolh6t9k>oI`HNd`Lb4VeOBh}D*5uY`sT!8{_u$rccmMyeRq?sL0o(f!B{9< zr0EUTWYr&ZPWZ`hj9U+x`fbkrb19(Tg0-^^3Jnz0I{&?j~3H)2lx1|u2W z+L<*(ZuxYWJ*Sh!5p%K_tqQ_=?rdlwex{M@EE+4mnOA935EngdV|1RJt)GA8_Ds!_ zd9!()nssFV*)6gtr%YtPl{slhG@G0!S6!v0kfu}7WL3-Pd1oXr-r#rEv7D?bLgVTQ zFO=FGT+!u9r<-tJ-kK+$-mUXrh>xg0Urjp>D^!`AiUdlbh5vopDY~-ha_urFpU8Xt z@wjEc@{08iON7FkWtS2pFhvIq&ZDPy>bXIX zBAmMP@$^5>!O?rBAphg|v~@o}&&P4a{qxpJHy0UWxHEXIq6j zkhXQ8hv+%dieMe|kJd}TLnPvK_ynddmhd}<=gAeTW710LKmw&uAK3$*W4u^iFWZ(U zZ*8yB%_gucVaebUlklXT>IY3NLaF2>5v@W?&?nm}^bnUG0(wz z$9|aJ66;D-II1I-Ff}S+t!2X;Z1qtpxeg>y3VR0ID!hUO)_+BKn)964d^UbC9hc_> z*-u>LCwZFpip1T=(JG>Zkz&HUmE^w(BgKSygG*Av!Dl4kvka0xk<`H_2;egSm@rQ| zj=G@!(7q*j!U9U6KAXU&253w4lZR2CPk3BPkiaJhAZ-&Uh0ozgND%5dXfOJqi3Cq? zbLByzq&Gb40iVf`kWe~MDp>0kZn^yv;!{@t%M$8*qgv>oX{nnE60f>KCZgnA>Bu^pwj zO-z{Q)>CNFTioSubf`Wmf>)3Tb~pO+X2}WfJQ^DO^uZPM{Qy)sqt_h2yq_gi0%H)$q<(!cw9Ll*0R5$qAIgyJX1;l)^h> z$qAGS?i*2lBx-$>A1H-oEIDCGLFtPlJW@a(>Z3pk^8?4%T9(C`z*#ECYi7SV7x4*( zECOc`Nc)7+fpMW!oK+;vAKMR@kI<9SQ8O=(N+UorP|RaghaQr-runZ6Rl;>N`C3BO_FJ!G$N?5tWC_m%m*jv({gj zA;ih^|876^!{kWw4_-;fchRZ0U_{?PhculGqqku64t-g0b@aU%=2?s=g*16^dF}9$ z&ix`4x_lBE)8=ZWoqn|}Su9^gq+xvC_ zzwL&5ePb2Ii?k5@wzO&9f`Z<$#AQR|&b_b7QitkgAT7jVBHpd|-li2q;M-^LT}$?x zXwDPy*=K80H`7~U&?=NVHGYsxxv-Gl40Qu}{wfii=k20anNbmrWSHP`rHQpftX%Zy z#x5}gO5r_OUmagBU(9JnR=q?-ttQtN zJQzcqdUlW;v1GHX_(4Vn(n9ole3T=L)=jHl;g!(Vro(05+qcRY(=W(4SAV$NUui2n zN$`S<0&lo_A5UJRBBbLT(oyaLr(iR^wWn`~;pE{qId@jEjP5;#%SB_h$&6D)GY0-X zOujsRn|!9kMMUu1hx+cwE69trN~O;>l|E>ppSr@Nt|=v$2Rhv}EyRtp=-p1^f7x8U z?kge8HOzCQX^$6qo^z~G+M0^C=iLj3$#+U^lUp7yX{7?~bRs{_{V=DXBE5YG(+X2z zPwp_edCE3f>4Vbb`5#Y?au&?))-oh)qSRZ%(*KU19TqT6TV59w045J`rza_1#p5-r9rjBEx*%+G2e>nPf~YMN9PpEN%gs&KS;#wmyJoCL-|oKWSFcwal3r6 zMp@Mwn0S$4%rnHc&~#ceO1$HdKD}&vyB3%4He0j>m{7SM^{@angZ)pzY_b z97g+#w#(*MUqV*xm_5o_yS>Mjv6XE<&_dg)i%EwZ^~R>1WEJYbcrkD2tPBxvP5&pY zI1v~x5@;dak0PSWt1qOMq;iF+i{4@?2$4sAbnDw;%hD<~G4JwW@~tu3WryrbGEldE zBU|75-e|#ZWW#+|d#~D@N2LVgLfbKC zh3HAd-FeS#UPU_4Ui1UgO^-{Sb?R*5aS7^}Fk!GPaZ91Rd|+Az>J#EkO5NUh4GWG? zX~&$xc(JYs(UPnh*>U;id#X{bh4O<|*~ARe@x&e1ZmvQl5pxi&LYj6P5|KN1OWI(v z3JJ6wX+4tp{m1-`cg1*)IcO97wzTFaX1CzCrJ;6|vi;acM8T^HhB%*hnCph<+~}ocJGw*5&2(K|D3}>GE(y1)DZTF3C^NRD_>keeeJ+Iv4u4;1B4{M@Kk!M0Lt$e^i zsb@~kbwAsZCA+L_>sBmw)DLT-(zL0e*V{hiY#%Vsy}ekrZ1?L8F6!IT>!`0=6Ei!H z3Vn2Byt6oWo_l3=dh=6@8(gHDXCC!q(!_~=cZLq!G{HHse4bmiMYi0SCtakgyl~Wy zNfVdc8wzFSO?2{KpXWC2oGlNW$#9Xr=k=q0Oqys|@ug5`>1YSN%`BMfzWIEXyn1sx z_x$@u{IDiURo@u;dCW)$rM}CY>wcA&C2JpP@AiA{B;qY2|VdWiXqDWVBeN>B>R z5|$oKm{NkJ0rMI2UlS(JF>f)~FgG<}$`!_oG?o}mnEHXXqlH+GHDU4`^A^($%Yr6M zo@1^djb%X*Uhx_=oC|Aqbnfdv$K5bDQ_lXar+a0|IqsvGney!Sce#tM`p^$+Vn?wm z&W`i9I4^IX<2oxd<&g3{T%?ao`OuF^6Z0$Aapu%*>tMY9tbE*kdw8bIID5A{Zr33{ ztcitx)pHt`k`780>-e}kvR9^Ta8Vz3;($YbSQC9loe!O8f3NdXyC>b7DrL!$^KNxp z{QHDU`yFM4dR^Sr)eie%O}zg3m!Zp?0nWUAPx@u|{+Um@A6=Iv*Hr23ezD@PAJ#-! z`D3VU*S-!)p}xWW4*R+_Vd@9g1JsB0MiY~F7Il_Z>g}NIH?4odEqf|c-u>3?ZsFdK z{IDkeY*xY<_gW7JrRw*8!X0@qQBR{N(Cufy)oLjm%DE0HDPq^1cGUdMX z?(WcrANgTTj6GD|xp6}m2cSW4>iwC-=RzBw5 zb!n!o_S0au@2(I0uqJ-EsD<-KkrvLIQ)atGT4c%z_w;v>zP#x{KPFAA-ka`p*xkUv zcvrP~%xx($PugA(ATYr=XN!4 z8tqSau=F9lxyT_uCQbZPudQ>WVjbuFJG0z5^sM$Pw+wer^qA!i{>+n;t{>&zGxmTV z)`Ymcqw`Iz8V*WjeL2(JzR#0+1!LUv|Ge*qHSrhKzY$aqQR?{6neG!iJo#n22i>02 z-}l2&9c85R$oYB>rVm;;=Hvn2T15y`&#^vZK4boC!n7sWW+08NgeFYOfGq&h*kWkH zv<%n+AdM}CCQOaOnuDzY)-+9+8ih3nX{>3QFf|Hm4$@fDG+}BKrYO=_(==gPIJ6yG z9Bhd+VOltB$*{%2mPiw(g~ND}#+FYLrbc1S!8(C;MH8l;V|_*%>!T)29mIGs|FLdr z!n6$70$_`SErupcdxcVHA+|)CFtrwIC)RDO&6+T^7HcQcSev6dqO}%lC;Ei7Srex9 zK}(RvI1&=r%|2WTG zsx|F|yF;BuKUO3ANyfV zbh`QL^k4f;c2ElS-BJ2uU$-VE)SHsN)tlmMsWjjH_1$c_{TkOreW$Pa*w?Lz&+Ao9 zzwW>kr+xML?!zatW#dw=i}dwPKlWqN#GP$AwfW}!6z9sC^IajP(w#%sMS5rI$9_zT z5GHl4RD|@g(oG>IbuksuTTBJ5!=x2TVTxiJYQp3PN@4n7x@p3sE=pngV7h6-q%KNf z`e3?g!lW)rEo%9(Yf>6hT@z;Jg=2M0X)MQz@a|aO#d-I`@(%WvmCruvo_^JnwZDGI zeXQ*JepnL&Xry!(jhIlX#kfb^{YyQ0c;*Cm_2c{fuqK*S>Fpc~7j?S)KErK2&y!DQ zJmO9sKf}E~&!ZCKx#ynP=Z7_M&l^3QvrS4k7%$RqU$)+76s^J565?tUQOeRGs2$Gn&29-6h+4{PF=&;Un%@ns05 zTAt2x2M_S1+j^?|YRSERSQ8^(8139$?xoO>J=5Kt>7MLSJ>RVmn(p4w!jq?-nc>dp z`Hmmf#E2CSIG>k$CWKOtt<7;KH}vGTIy2pi-reJeHL-2xgHF~PvqLELbL||rZ9PxU zTsg}fn!d*mYvOU5p;o%*a0qRGcu=1EY%fnfyn32@ChI*vtcfcgF}#dd~JYErjc_wBZx%y>4}{dv^8epnM<&}@D<&FWDK z)9v`z@Azq`2w}=DmQn0WuuN;hlwB;NNMo7Sgei$w;*iD?stHrtu~Z|C6V^0Mm{NkJ0rg?&(S#`_SQ?PV(xVAe zO0YB_jipBurVe6VLmJDNCQKc~x`s5?O--0Oh;ccZEVIb@!*)?qe5w^5^4Ex`+OF%MWW}(F>EDZ#vWsq12L_v)xgpJz04it^W;r z%MWYfVjB1SdvTc%O7+~F<(|FBlRHZ-aQA<(%MWW}Z~w_o#f!gAN2!HZWx16v^yHew zPr2pYU4B>-ooS?0Y7vc?KeUvzT~5F5hZP}=A80Rni~ecC_<{B!js9uE_<{B!js9uE_<{B!js7XZJA2bZ z&b2e9hOn2h%JJ&A{W7g}n9`1=dM`*JtqD`wu~b_$)D=yb(vGDXX{-gBFr^(!HPToM zG+|0RmTIK27HGnhcFbp_u@-2;q%Nipwkw$Gn$X|0=Pqt+xKNGAT(Gmo!&PCOAMBLP0;9dJI@U;ULNC4uewmpEVNaqgGR`OJpbVFu94z# zbm?Xb)$9dnt%FA4g*?aMF}0E6v3gFYg=$WPv?gf&5$4$)&nBK*u+X2~z5nV$b)SOg zALe&W(A=Pq=W0CCH}Uce;@1HS)m#nrX@ce-g*;c|*@TheSxVO_3)S2ZX-&`^C(JWQ zp0OAyv;=8Q&`3Yb_gHvzZ*1pP`ICDVsyj41(l@_rg4XoId>@DB21bfka)!hDyE zSN@F@-@!Q8aFM!8hO{Q={zsValJR{5BgJ<~YPVga?vf#`3A)D;=DSOL$HYkSy@J;F zEK+xukk$mK^T-6f1}iRr2r5Pq5VE`%7RS1b2rg{zy?_?M zDn$W>qH@K$uob9rDXypkBg-HP#8e>mo_jdy_x0Y)^uIgF`P}s!=l4C|A!2p873Hfz ztS+~rd^L!*;?{?+qOexn`tVg0){0vnzKX({$Liv{^8J?qtIMq@Ukze)xfSKBL98yf zqI@-o)#X-{uLiNY+=}wmAXb-KQN9|)>T)Z}SA$qxZbkWO5Ub0rC|?a?b-5Mgt3j+T zx1vl(cnx`8)CKNVGtW9>)MFH~7$*7MnQPh?HG@i098PbjK3fFVj@a+oMbJy_gihKjv74DFC<@>KF-n4tWzW2cQ z$G71%d|86;@4mS&OYk+_7c`EECJz!%OCHqP8&cMTc28~`EKN=|c`On|9@G_grL515 zG@rvbSeo2$a;+qaJg9Mrly$?AX2LjFn!Igttt5&(sKS{k>un>=gmJJmdE4Y#NfbF4 zGZv<-w~aIt#+%UOrIH_!`(>)ZOI^4xWxZ7LCgkT#82>|)GfHlTM3GnU!LKRnjFSH$ zKWD;tESh{y@>OIfV^rEYu1J%oBQr7K%U#S$Hxqrii&^PrqAzzbE8R@= zM#f)*Y#Ftl?F>aRl@(MG?%@SX>V`gB+@O$_&5vvQck_lf8Vs*I{<;z{% zm79sa+{Gw26O9Myy`;=>yl94_`T6->z+_QS;kD5KD8#t1fy_0>RHR$ zOql+zCdY)j%U4cKTbBgaqh7D9&4lSwYjRAeyIeFqZCw(isZ%U7F=6`Dnj91AF3+Bu zwk`?M)U%eEm@s{6P2L3ciia&sTR#G6>RHQ7Oqg!1CdY(&tW!QrTbG17v`aRm8$1>n zGhw>5nj91Aq^#bSwk`?M)VYM z#V9wge3^(*ZtnWB9i!Y#^yMx_xtZw8U5s)w(U-dzlEAI;=7iuVK2*nmiWj6))S9w!R9|)Pa_nm@r*uO&$yNitpc_w!R9| z)Pa_nm@r*uO&$yNil01~w!R9|)Pa_nm@r*uO&$yNid)vDt*?SKb)aP?CQR2^lgC1i z!meM^)>lEAI?ysx;{--g|5|2Z!nhflTp98px*kniCkEGZ3FBsHa%ISaSY4mCP7KoI zY{*Q!hH*1AxiaKIEdAAdiy9{eX>vAXCMJxVp~;ouessnI0+PZ1>d%M!DJUs}GEFv)xx8 z7=?MvcV!$5O{3o(>TdomNXEJum@)3{l3QYcD)c#+W-kPN5xYj!DTkA4#@0(O!l=s* zc2RrllhMWL;o;8*dc|eT#8Wj%Rl1>lF-E<3u#;MPI2mm}G9qkwFh4G1OT=UioC|k? zYcnBh;9N-K+D!a;T2ienC@+5RV2}9awaKXU%_FmRf;;4M2!X$u`g0p4G|t3CNz0_V z=Jln;7}ch}x%tzA$tb=0&Tw>X_qdFiSbkqZ-TB5h#TfN?&T%StM>4vu*;)8CxPIyTa9R@3>ngWLLO4(zshDWLLO4 z(zshb$NV;v)Zsbz1e2P45bRl;jLN3m8xAd98O(k<8EyVoX}IN#&T$zt(c`fP)m2lb z1Q_+o!9_ujL^8_hd4D*m&u`;0W@7l)^r7cZd6rJ2Nh0CKkV3u6EpC6kyZ~FMSepc_SHx z-N%F_xn1KjW@7RKW$J~Kt`9H@Z~FWWzmLnUTOuaA!qxFLakos!u5fjvakos!u5fjv zakorJmS8p@jb~y)vIMgMY0MrbBug+GkjCsewyR@Vg4uvHW)BmRC72CJWA<30s-U)1 z?YVVXfVuR}9|s59S|y_$BPN8^sdjN0GtqL{7`3DGngF9-UC=i;_lHC@>!k<7`nTK0 zWz0mEfp@FDtJVh?HMe(O@bIQYba>yyu*dF`<1%JqmL8>+_x?D*s15~3igzwbL|^BZ zg%5OWZT^iMkD0i!)kxLxsqFzqby@UI@wJl^(aCq0ho@c9GA?5#zAYNA9{$Ik0HeO! z`bhESl0-D@`HJwahjQaGW};{R+th;XwE;#wa$nct5&4N|!Kans!grg;Wz58xJ%_6E zzmF-5%Ih`gqEnkBqD-@7*rs_-T*j7&$$Q|v<7?tOF(L1P_l`8a6BF_tc<)H#J24^e zf%lFyz7rGj9(eCa<2x}S?}7J@G`-^uab@D`ZxogD8CZ-EKl$?@Ls7MLjfYJh6` zb7%GWgnBjc!-}ZIw^KsApCR)eZuoiQ1l}~#O!WWuVpa3cGt>_w535h+R78InH8sR} zzh0GT___JZEtASFXtiFcJ~O+igMD69A15jzy>Mzc{?0lz^VW)J+}^2SHu2ULwX*Nq zO7%{4Qxi`3t12B^5v^OBP2fy+S5>n%C{^}kHqra0*BaKq_4piv9xqb$LB3k|%W5^` znu@4ji)mTALb~yj<4xl&Fk$}sw_5R3H-)R?y=9-=Fjd>0EK;Xmn7yl#ij8VT*NSM! zm}%iB6Av|vW3lJgj(+{E=|$?gqHF@AaBbez;l72cZhe8md1uc1NnNtDJi2(#|E@v1 z@)2cu-!u?N^BVol{4e$^R2Wq?eZM+;Re2O%@=)j!W*JA_p`v4j#Hx?iFgi+;RwMQE zsL_L@&rln>#KfAU-e1?gm^4neCK;>GkS0wfGw~W9PD|=RP0Ovu`Mhby>L{d1k;zPr z?+SO~uHoAiM!9PkjiX6WlFnbtUeC4K!&r`l5exPR8mp(gQ-1PLMGZm1{=nDg8dS%B#i88LQ8bCY>NNF>zkY zk$U5-?N)h}=#158qz8m1ogiZ-+7=JjKd#$jmDijaQ-emIksc75bb^eTFxo(qR--Ta z&EIFNJ|jIKH0cBxGch`UsBX41X207H)Mu6q zZ`b!880Frs?>#WeyNbAC@AePT^;e#t@Ha?guR)AaESj_$>5L}%)eTxr<|XwaV_u`M z(;$6hYimV1)Cbf6zPmbw^cW2HK9qp$e0N^2ksYlg6CsG z&Vf<56Fgt`IWP)$g6GRV2S$;Ok~87?n3(<6K>b`SPm%h$w7j}Oa|(fUHi5q(9VIl* z#KfjA2IviSofWBucb~0p&}u>;&4f{2nv@r*hIY%V8TfAN^P}EyVSZj>RsF)vxSUT&S16SD-fiuw7N>&W|qHJuO5!o0wU$yzjZ^7wTPG zvI&eLef6c8iF6yE)?TAB8v? AJOBUy diff --git a/3d-models/top-front.stl b/3d-models/top-front.stl deleted file mode 100644 index 7d31e585920a420daa9006ad87e1d1ae6abd135d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46284 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%0AatTYA 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 YW_y`1Hwf(kjQh{pDz@EP{Zgy`58$!+{{R30 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index d322047..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,389 +0,0 @@ -# Changelog - -All notable changes to LitClock are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com/) — dates are ISO 8601. - -## [v0.220.0] - 2026-07-15 - -### Fixed -- **The Control PWA can no longer crash-loop itself into a brick over the port-80 floor** (#15, #16). control_server binds port 80 as the non-root `pi` user, which needs `net.ipv4.ip_unprivileged_port_start<=80`. A field incident showed the persistent `/etc/sysctl.d` drop-in can silently fail to apply during an OTA (the install error was swallowed, and the update's only warning checked the live sysctl value, not the persisted file) — so the update reported success, the next reboot reverted the floor to 1024, and the service crash-looped on `EACCES` with no recovery path for a keyboard-less owner. Now `litclock-control.service` self-heals the floor with an `ExecStartPre` that re-asserts it as root on every start (idempotent, ~1ms), and `update.sh` verifies the drop-in by content and warns loudly (with the exact re-install command) if it is missing or stale. Reviewed by two independent adversarial passes. -- **Personalized gift welcome messages no longer get their tail cut off** (#17). The "Prepare for Gifting" welcome splash rendered the message at a fixed 48pt capped at two lines and ellipsis-truncated anything longer — "May it always be a good ti…" on a real gift. The splash now auto-fits: it shrinks the font down a `(size, lines)` ladder (48→44→38→32→28pt) and picks the largest size that shows the whole message with no ellipsis, only truncating a message far past the 280-character input cap. Short greetings still render at 48pt exactly as before. -- **DIY installs no longer abort on a never-copied systemd unit** (#14). `scripts/install.sh` enabled `litclock-reresolve-location.service` (and expected `litclock-prepare-for-gift.service`) without ever copying them to `/etc/systemd/system`; under `set -e` the `systemctl enable` aborted the whole install. Both units are now copied, with a drift guard requiring every unit in `systemd/` to be wired into the installer. Flashed images were unaffected. - -### Added -- **Captive-portal probe logging now records the response side** (#11). Each `CAPTIVE-PROBE` line logs which branch answered (CNA bridge / redirect), the HTTP status, byte count, and the full user-agent, so a "the setup page didn't auto-open" report can be diagnosed from the journal instead of guessing. Diagnostic groundwork for the iOS captive-sheet investigation. - -### Changed -- **README rewritten around the builder's journey** (#12, #13): a real product photo as the hero, a numbered Build → Living-with-it → Give-one-away → Under-the-hood structure, phone screenshots of the control app, and the first-boot walkthrough — so someone deciding to build one can follow the order they'd actually do it in. - -## [v0.219.0] - 2026-07-13 - -### Security -- **pillow 12.2.0 → 12.3.0** — picks up the fixes for PYSEC-2026-2253 through PYSEC-2026-2257 (five advisories published 2026-07-13 against 12.2.0; caught by the pip-audit CI gate). pillow renders every frame on the device, so this rides the next release. - -### Added -- **"Factory reset" button in the Control PWA's System tab** (#510, #511). Erases everything — all settings and saved WiFi — and reboots into the first-time setup experience, distinct from "Reset WiFi" (which keeps settings). Guarded by the same confirm-sheet + CSRF flow as the other System actions; the env wipe is fail-closed (`--strict-env-wipe`), so a partial wipe aborts rather than leaving a half-reset clock. -- **Print-ready case STLs in `3d-models/`** (#521). The maintainer's lightly modified Time Teller v3 case parts (notches added to the top-back and bottom pieces; top-front unmodified) — Arthur Gassner's design, CC BY per his Printables listing, attributed in NOTICE.md. Builders no longer need to fetch the case from a separate site. -- **Owner recovery guide** (`docs/recovery.md`, #509). The SSH-off posture's companion: console access with default credentials, enabling SSH from the SD card's boot partition, reset-to-first-boot, and the read-only Diagnostics tab. - -### Changed -- **The appliance image no longer auto-updates OS packages** (#508). A fielded/gift LitClock must never apt-upgrade behind the owner's back — a surprise kernel or driver bump could break the e-ink stack with nobody at the keyboard. The pi-gen image zeroes the apt periodic knobs and masks `apt-daily{,-upgrade}.timer`. LitClock's own weekly self-updater (`litclock-update.timer`) is unaffected. -- **README rewritten to describe the current product** (#516–#520): the real EPIC-#383 setup flow (WiFi-only hotspot form, IP-geo auto-config, handoff splash), a Control App section with phone screenshots, a first-boot visual walkthrough, the quick-start booklet links, the exact Waveshare panel purchase link, and an auto-playing one-minute tour GIF (`docs/media/litclock-intro.gif`) rendered from the real device code paths. -- **PII sweep ahead of the public release** (#82). The gift-message placeholder in the System tab no longer carries the maintainer's real first name ("Love, Alexis" is the new example — same 32-char length, so the e-ink wrap docs/tests it anchors are unchanged). Sample weather coordinates in `README.md` / `env.sh.sample` are rounded to city-block precision (`30.27, -97.74`), and the full-precision latitude that appeared in redaction test fixtures, a code comment, and an earlier CHANGELOG entry is replaced with an arbitrary example value — the repo's own diagnostics-redaction standard (2-decimal rounding) now applies to the repo itself. Also: `docs/building-image.md` examples updated from stale CalVer tags to the current SemVer scheme, and `pi-gen/config` now documents the intentional default-creds + SSH-off posture (#387, closed). -- **Example city decoupled from the maintainer's location** (#82 PII sweep, part 2). Every "Frisco, TX" / zip-75033 reference — README and `env.sh.sample` samples, QA docs, code comments, and ~100 test fixtures including the `is_daytime` solar regression suite — swapped to Austin, TX (30.27, -97.74; same timezone and similar latitude, so the pinned day/night assertions hold unchanged). A test comment identifying "the founder's Pi's location" reworded. The internal planning docs (`DESIGN.md`, `PRD-`/`PLAN-LitClock-Control-PWA.md`) are now gitignored ahead of their exclusion from the public repo. - -## [v0.218.0] - 2026-07-11 - -### Changed -- **BREAKING: the Control PWA is now at `http://litclock.local` / `http://` — no port to type** (#343). control_server moved from port 8443 to **port 80**, so the URL a recipient scans from the e-ink QR, taps from the mDNS bookmark, or types by hand no longer carries a `:port` — a smaller, less technical-looking address for non-technical gift users. (This supersedes #343's original 8080 proposal: 8080 would still have shown a port; only 80 removes it.) The port is bound by the non-root `pi` service account via a one-line sysctl drop-in (`/etc/sysctl.d/30-litclock-unprivileged-ports.conf` → `net.ipv4.ip_unprivileged_port_start=80`), deliberately chosen over `AmbientCapabilities=CAP_NET_BIND_SERVICE`: the `litclock-control` unit runs `NoNewPrivileges=no` + setuid `sudo` for the reboot/poweroff actions, and adding a capability directive to a `User=pi` unit has repeatedly flipped the kernel `NoNewPrivs` bit and broken setuid sudo on this hardware — a sysctl never touches that wiring (and stays safe once #82 drops the `010` blanket sudo, when setuid `020` sudo becomes the only reboot path). `setup_server.py` stays HTTPS on 8443 (the iOS captive trust dance needs TLS) — the two are now different ports for different phases, no longer sharing 8443. The port + URL are a single source of truth (`src/control_url.py::control_base_url`, which omits `:80`), shared by control_server (bind) and the e-ink clock (QR); the mDNS probe in `status.js` derives the port from the live origin — so the QR target, the bookmark switch, and the actual listen port can never drift. Installed on fresh flash (pi-gen), manual install (`install.sh`), and OTA (`update.sh` applies the sysctl live before restarting control_server). - - **Migration (one-time, recoverable):** on OTA update the e-ink QR refreshes to the port-less URL within one paint cycle. A PWA already pinned to a phone home screen, or a saved `:8443` bookmark, will break (the origin includes the port) — **re-scan the QR (it's right there on the clock) and re-pin / re-bookmark.** No data is lost. - -## [v0.217.0] - 2026-07-10 - -### Added -- **Diagnostics: a "Download full logs" export for shell-less support** (#416 follow-up). The Control PWA's diagnostics "Copy support payload" already hands a helper the system state + a **3-line** journal preview per unit — deliberately shallow so it never blocks first paint. That's often too thin to actually debug a failing unit. New `GET /api/diagnostics/support-logs` assembles a single downloadable `text/plain` bundle: the same redacted system payload plus a **~50-line** journal tail per unit across the `DIAG_UNITS` allowlist, so a non-technical owner can hand over one file (or paste) that carries enough context — no SSH. Off the page/poll critical path; deep reads use a **distinct, line-count-scoped cache key** so they never serve or poison the 3-line page-preview cache; a wall-clock budget bounds the serial `journalctl` loop and appends an explicit *named-units* truncation note rather than silently dropping units; `no-store` + redacted (secrets in journal lines are stripped). The existing per-unit `GET /api/diagnostics/journal` also gained a capped `?lines=N` param (default 3, max 200) for pulling a deeper tail for a single unit. Surfaced as a plain `` link beside the Copy button, so it works with no JavaScript. - -- **LKG auto-revert: the clock now self-heals a bad over-the-air update without SSH or an SD reflash** (#209 follow-up). #209 shipped the *writer* (`litclock-lkg.service` records the last SHA that actually painted to `/var/lib/litclock/lkg-sha`, heartbeat-gated); this ships the *consumer*, `litclock-bootcheck.service`. It answers one question per boot — "did the clock paint a frame since this boot?" — via the tmpfs render heartbeat (network-independent, so a merely-offline clock is never touched). On a persistent failure it self-heals: fail 1 and 2 auto-reboot to retry (a single failed render may be transient), and on fail 3 it pins the last-known-good SHA and routes recovery **back through `update.sh`** in a new rollback mode, which does the *complete* install (git + submodules + venv hash-gate + units→`/etc` + sudoers + dispatcher + smoke) rather than a partial `git reset` — so a dependency- or unit-level bad update is actually recoverable, not just a code-only one. If the recovered code also fails it gives up (a persistent `bootcheck-gave-up` marker + best-effort "please re-flash" splash) instead of looping, bounding the whole sequence to ~4 reboots. Two supporting fixes make the recovery target reliable: `update.sh` **no longer clears `lkg-sha`** at the start of an update (the heartbeat-gated writer replaces it only once new code paints, so `lkg-sha` always points at the last code that actually rendered — a dead-on-arrival update can never blank the recovery target, closing the DOA gap), and a `blocked-sha` suppression stops the weekly timer from re-installing the exact release bootcheck just reverted from until a newer release supersedes it. Runs as `pi` using only the `systemctl reboot` + `systemctl start --no-block litclock-update.service` grants already in `sudoers/020_litclock-control` — no new privilege, works after #387 drops `010`. Hard prerequisite for #82 (public release: SSH is off, so a recipient's bricked clock would otherwise need a physical reflash). Design reshaped in `/plan-eng-review` + Codex outside-voice (plan: `docs/plans/lkg-bootcheck-plan.md`). - -### Security -- **Closed the pi→root escalation paths so dropping the blanket `010_pi-nopasswd` sudo is safe** (#387, prerequisite for #82). Each was harmless while `010` grants `pi` NOPASSWD:ALL, but becomes a live hole the moment the public image ships with only the scoped `020_litclock-control` allowlist. **(tz-wrapper)** `geocoding.set_system_timezone` (the IP-geo + A18 browser-tz fallback) ran `sudo timedatectl set-timezone `, which `020` could only authorize via a `set-timezone *` glob — a hole letting `pi` set the clock to any string reaching the resolver. It now calls a new root-owned wrapper `/usr/local/lib/litclock/litclock-set-timezone` that re-validates the timezone against the kernel zoneinfo list *in root-owned code* (a metachar pre-filter + `grep -Fxq`, and `timedatectl` resolved via PATH — never an env-var binary override that could cross `sudo`); `020` authorizes just that fixed path. **(C1)** the root NetworkManager dispatcher invoked the **pi-writable** `/home/pi/litclock/scripts/litclock-mark-collected.sh` — pi→root on the next DHCP event once `010` drops. It now runs a root-owned copy in `/usr/local/lib/litclock/` (no fallback to the repo copy). **(prepare-for-gift)** `litclock-prepare-for-gift.service` (root, startable by `pi` via `020`) exec'd the **pi-writable** `reset-setup.sh` — pi could edit it and get arbitrary root code. It now execs a root-owned copy (with its sourced `lib/state.sh` shipped root-owned alongside), and the gift-message step runs the root-owned system `/usr/bin/python3` instead of the pi-writable venv interpreter. **(NTP grant)** `first-boot.sh`/`install.sh` run `sudo timedatectl set-ntp true`, which `020` didn't authorize — a fresh clock would silently never sync time once `010` drops; added as a fixed-argv grant. **(C2)** the dispatcher + mark-collected writer run as root but write into the `0755 pi pi` `/run/litclock` and `/var/lib/litclock` dirs; both now refuse to follow a symlink at the target and stage via `mktemp` (O_EXCL) instead of a guessable `$MARKER.tmp.$$` path. (A residual sub-millisecond check-then-open race remains — closing it fully needs an `O_NOFOLLOW` open POSIX `sh` can't express — accepted as an explicit risk: it requires an already-compromised `pi` shell winning a race for a limited-content root write.) All root-owned helpers install root:root across pi-gen, `install.sh`, and `update.sh`; the gift-mode `timedatectl set-timezone UTC` fixed-argv path is unchanged. - -- **Diagnostics redaction now rounds compound-key and odd-shaped coordinates in the support-logs / journal export** (#497, #498). The diagnostics redaction rounds coordinates to ~city-block precision so the copy / support-logs / journal surfaces stay safe to paste into a public GitHub issue — but `_COORD_KEYED_RE` led with `\b`, which cannot match inside a compound key (the `_` before `LATITUDE` is a word char, so there is no boundary). systemd logs `env.sh` verbatim when a line uses `export` (`Ignoring invalid environment assignment 'export WEATHER_LATITUDE=33.1234'`), so the device's **full-precision, street-level home coordinates** rode un-rounded into the exported bundle while the structured field correctly showed the rounded `33.12`. The lead-in is now a zero-width negative lookbehind (`(?` endpoint (validated against the `DIAG_UNITS` allowlist, redacted, `no-store`) **after** first paint, so the page is interactive immediately and one slow unit can't stall another's tail (the multi-failure case that previously blew the client's 10 s budget). The tail cache TTL is decoupled to 45 s (> the 30 s poll) so a stuck-failed unit doesn't re-fork `journalctl` every poll; the copy support payload appends the client-hydrated logs **inside** its fenced block; a no-JS browser sees status chips + a `