From 4b91ebcd02268a1dbbffccbe298013263b046db6 Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Tue, 21 Jun 2022 19:38:51 +0200 Subject: [PATCH] Add grid, camera focus --- Caps.xcodeproj/project.pbxproj | 75 ++++--- .../xcshareddata/swiftpm/Package.resolved | 9 - .../UserInterfaceState.xcuserstate | Bin 61444 -> 115721 bytes Caps/Camera/CameraManager.swift | 30 ++- Caps/Camera/CameraView.swift | 7 +- Caps/Camera/ContentViewModel.swift | 4 + Caps/CapsApp.swift | 6 +- Caps/ContentView.swift | 2 +- Caps/Data/Cap.swift | 4 + Caps/Data/CapImage.swift | 8 + Caps/Data/Database.swift | 212 +++++++++++++----- Caps/Data/ImageCache.swift | 209 +++++++++++++++++ Caps/Data/ImageGrid.swift | 82 +++++++ Caps/Extensions/FileManager+Extensions.swift | 26 +++ Caps/Extensions/UIImage+Extensions.swift | 11 + Caps/Extensions/URL+Extensions.swift | 10 + Caps/Extensions/View+Extensions.swift | 19 ++ Caps/Views/CachedCapImage.swift | 79 +++++++ Caps/Views/CapRowView.swift | 3 +- Caps/Views/GridView.swift | 162 ++++++++++++- Caps/Views/IconButton.swift | 41 ++++ 21 files changed, 895 insertions(+), 104 deletions(-) create mode 100644 Caps/Data/CapImage.swift create mode 100644 Caps/Data/ImageCache.swift create mode 100644 Caps/Data/ImageGrid.swift create mode 100644 Caps/Extensions/FileManager+Extensions.swift create mode 100644 Caps/Extensions/View+Extensions.swift create mode 100644 Caps/Views/CachedCapImage.swift create mode 100644 Caps/Views/IconButton.swift diff --git a/Caps.xcodeproj/project.pbxproj b/Caps.xcodeproj/project.pbxproj index 8b463db..84eec13 100644 --- a/Caps.xcodeproj/project.pbxproj +++ b/Caps.xcodeproj/project.pbxproj @@ -8,6 +8,13 @@ /* Begin PBXBuildFile section */ 88DBE72E285495B100D1573B /* FancyTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88DBE72D285495B100D1573B /* FancyTextField.swift */; }; + E20D104A285612AF0019BD91 /* ImageGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D1049285612AF0019BD91 /* ImageGrid.swift */; }; + E20D104C28563DB10019BD91 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D104B28563DB10019BD91 /* ImageCache.swift */; }; + E20D104E28574C7C0019BD91 /* CachedCapImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D104D28574C7C0019BD91 /* CachedCapImage.swift */; }; + E20D105028574E190019BD91 /* CapImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D104F28574E190019BD91 /* CapImage.swift */; }; + E20D105228589AAC0019BD91 /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D105128589AAC0019BD91 /* FileManager+Extensions.swift */; }; + E20D10562858CDFA0019BD91 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D10552858CDFA0019BD91 /* View+Extensions.swift */; }; + E20D10582858CEBD0019BD91 /* IconButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20D10572858CEBD0019BD91 /* IconButton.swift */; }; E25AAC7C283D855D006E9E7F /* CapsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC7B283D855D006E9E7F /* CapsApp.swift */; }; E25AAC7E283D855D006E9E7F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC7D283D855D006E9E7F /* ContentView.swift */; }; E25AAC80283D855F006E9E7F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E25AAC7F283D855F006E9E7F /* Assets.xcassets */; }; @@ -19,7 +26,6 @@ E25AAC94283D88A4006E9E7F /* Cap.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC93283D88A4006E9E7F /* Cap.swift */; }; E25AAC96283E14DF006E9E7F /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC95283E14DF006E9E7F /* Database.swift */; }; E25AAC9B283E3395006E9E7F /* CapRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25AAC9A283E3395006E9E7F /* CapRowView.swift */; }; - E27E15E1283E418600F6804A /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = E27E15E0283E418600F6804A /* CachedAsyncImage */; }; E2EA00C3283E672A00F7B269 /* SFSafeSymbols in Frameworks */ = {isa = PBXBuildFile; productRef = E2EA00C2283E672A00F7B269 /* SFSafeSymbols */; }; E2EA00C5283EA72000F7B269 /* SortCriteria.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EA00C4283EA72000F7B269 /* SortCriteria.swift */; }; E2EA00C7283EAA0100F7B269 /* SortSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */; }; @@ -45,6 +51,13 @@ /* Begin PBXFileReference section */ 88DBE72D285495B100D1573B /* FancyTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FancyTextField.swift; sourceTree = ""; }; + E20D1049285612AF0019BD91 /* ImageGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageGrid.swift; sourceTree = ""; }; + E20D104B28563DB10019BD91 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; + E20D104D28574C7C0019BD91 /* CachedCapImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedCapImage.swift; sourceTree = ""; }; + E20D104F28574E190019BD91 /* CapImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapImage.swift; sourceTree = ""; }; + E20D105128589AAC0019BD91 /* FileManager+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extensions.swift"; sourceTree = ""; }; + E20D10552858CDFA0019BD91 /* View+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; + E20D10572858CEBD0019BD91 /* IconButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconButton.swift; sourceTree = ""; }; E25AAC78283D855D006E9E7F /* Caps.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Caps.app; sourceTree = BUILT_PRODUCTS_DIR; }; E25AAC7B283D855D006E9E7F /* CapsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CapsApp.swift; sourceTree = ""; }; E25AAC7D283D855D006E9E7F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -85,7 +98,6 @@ files = ( E2EA00C3283E672A00F7B269 /* SFSafeSymbols in Frameworks */, E2EA00CA283EACB200F7B269 /* BottomSheet in Frameworks */, - E27E15E1283E418600F6804A /* CachedAsyncImage in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -114,11 +126,11 @@ E25AAC7B283D855D006E9E7F /* CapsApp.swift */, E25AAC7D283D855D006E9E7F /* ContentView.swift */, E2EA00CF283EDD2C00F7B269 /* Camera */, - E25AAC97283E337C006E9E7F /* Views */, E25AAC89283D8666006E9E7F /* Data */, - E25AAC7F283D855F006E9E7F /* Assets.xcassets */, + E25AAC97283E337C006E9E7F /* Views */, E25AAC8E283D870F006E9E7F /* Extensions */, E25AAC8C283D86CF006E9E7F /* Logger.swift */, + E25AAC7F283D855F006E9E7F /* Assets.xcassets */, E25AAC81283D855F006E9E7F /* Preview Content */, ); path = Caps; @@ -135,11 +147,14 @@ E25AAC89283D8666006E9E7F /* Data */ = { isa = PBXGroup; children = ( - E25AAC8A283D868D006E9E7F /* Classifier.swift */, - E2EA00C4283EA72000F7B269 /* SortCriteria.swift */, E25AAC93283D88A4006E9E7F /* Cap.swift */, E25AAC91283D8808006E9E7F /* CapData.swift */, + E25AAC8A283D868D006E9E7F /* Classifier.swift */, E25AAC95283E14DF006E9E7F /* Database.swift */, + E20D104B28563DB10019BD91 /* ImageCache.swift */, + E20D1049285612AF0019BD91 /* ImageGrid.swift */, + E2EA00C4283EA72000F7B269 /* SortCriteria.swift */, + E20D104F28574E190019BD91 /* CapImage.swift */, ); path = Data; sourceTree = ""; @@ -152,6 +167,8 @@ E2EA00E6283F6D0800F7B269 /* URL+Extensions.swift */, E2EA00EA284109CC00F7B269 /* CGImage+Extensions.swift */, E2EA00EE28420AA000F7B269 /* Data+Extensions.swift */, + E20D105128589AAC0019BD91 /* FileManager+Extensions.swift */, + E20D10552858CDFA0019BD91 /* View+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -159,15 +176,17 @@ E25AAC97283E337C006E9E7F /* Views */ = { isa = PBXGroup; children = ( - E25AAC9A283E3395006E9E7F /* CapRowView.swift */, - E2EA00E2283F662800F7B269 /* GridView.swift */, - E2EA00E0283F658E00F7B269 /* SettingsView.swift */, - E2EA00E4283F69DF00F7B269 /* SettingsStatisticRow.swift */, - E2EA00CD283EBEB600F7B269 /* SearchField.swift */, - 88DBE72D285495B100D1573B /* FancyTextField.swift */, + E20D104D28574C7C0019BD91 /* CachedCapImage.swift */, E2EA00F228438E6B00F7B269 /* CapNameEntryView.swift */, - E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */, + E25AAC9A283E3395006E9E7F /* CapRowView.swift */, + 88DBE72D285495B100D1573B /* FancyTextField.swift */, + E2EA00E2283F662800F7B269 /* GridView.swift */, + E20D10572858CEBD0019BD91 /* IconButton.swift */, + E2EA00CD283EBEB600F7B269 /* SearchField.swift */, + E2EA00E4283F69DF00F7B269 /* SettingsStatisticRow.swift */, + E2EA00E0283F658E00F7B269 /* SettingsView.swift */, E2EA00CB283EB43E00F7B269 /* SortCaseRowView.swift */, + E2EA00C6283EAA0100F7B269 /* SortSelectionView.swift */, ); path = Views; sourceTree = ""; @@ -175,13 +194,13 @@ E2EA00CF283EDD2C00F7B269 /* Camera */ = { isa = PBXGroup; children = ( + E2EA00D2283EDDF700F7B269 /* CameraError.swift */, E2EA00D0283EDD6300F7B269 /* CameraManager.swift */, E2EA00D8283F5BB900F7B269 /* CameraView.swift */, E2EA00DA283F5C0600F7B269 /* ContentViewModel.swift */, - E2EA00DC283F5C6A00F7B269 /* FrameView.swift */, - E2EA00D4283EDFA200F7B269 /* FrameManager.swift */, - E2EA00D2283EDDF700F7B269 /* CameraError.swift */, E2EA00DE283F5CA000F7B269 /* ErrorView.swift */, + E2EA00D4283EDFA200F7B269 /* FrameManager.swift */, + E2EA00DC283F5C6A00F7B269 /* FrameView.swift */, ); path = Camera; sourceTree = ""; @@ -203,7 +222,6 @@ ); name = Caps; packageProductDependencies = ( - E27E15E0283E418600F6804A /* CachedAsyncImage */, E2EA00C2283E672A00F7B269 /* SFSafeSymbols */, E2EA00C9283EACB200F7B269 /* BottomSheet */, ); @@ -236,7 +254,6 @@ ); mainGroup = E25AAC6F283D855D006E9E7F; packageReferences = ( - E27E15DF283E418600F6804A /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */, E2EA00C1283E672A00F7B269 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */, E2EA00C8283EACB200F7B269 /* XCRemoteSwiftPackageReference "bottom-sheet" */, ); @@ -266,14 +283,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E20D10582858CEBD0019BD91 /* IconButton.swift in Sources */, E25AAC7E283D855D006E9E7F /* ContentView.swift in Sources */, E2EA00F328438E6B00F7B269 /* CapNameEntryView.swift in Sources */, E25AAC8B283D868D006E9E7F /* Classifier.swift in Sources */, + E20D10562858CDFA0019BD91 /* View+Extensions.swift in Sources */, E25AAC94283D88A4006E9E7F /* Cap.swift in Sources */, E2EA00D9283F5BB900F7B269 /* CameraView.swift in Sources */, E2EA00E3283F662800F7B269 /* GridView.swift in Sources */, E2EA00EB284109CC00F7B269 /* CGImage+Extensions.swift in Sources */, + E20D104E28574C7C0019BD91 /* CachedCapImage.swift in Sources */, E2EA00DF283F5CA000F7B269 /* ErrorView.swift in Sources */, + E20D104A285612AF0019BD91 /* ImageGrid.swift in Sources */, E2EA00D5283EDFA200F7B269 /* FrameManager.swift in Sources */, E25AAC90283D871E006E9E7F /* CGImagePropertyOrientation+Extensions.swift in Sources */, E2EA00CE283EBEB600F7B269 /* SearchField.swift in Sources */, @@ -289,10 +310,13 @@ E2EA00CC283EB43E00F7B269 /* SortCaseRowView.swift in Sources */, E2EA00E7283F6D0800F7B269 /* URL+Extensions.swift in Sources */, E2EA00D3283EDDF700F7B269 /* CameraError.swift in Sources */, + E20D104C28563DB10019BD91 /* ImageCache.swift in Sources */, E25AAC92283D8808006E9E7F /* CapData.swift in Sources */, E25AAC96283E14DF006E9E7F /* Database.swift in Sources */, E25AAC8D283D86CF006E9E7F /* Logger.swift in Sources */, + E20D105028574E190019BD91 /* CapImage.swift in Sources */, E2EA00ED2841170100F7B269 /* UIImage+Extensions.swift in Sources */, + E20D105228589AAC0019BD91 /* FileManager+Extensions.swift in Sources */, E2EA00E5283F69DF00F7B269 /* SettingsStatisticRow.swift in Sources */, E2EA00E1283F658E00F7B269 /* SettingsView.swift in Sources */, ); @@ -427,6 +451,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSCameraUsageDescription = "Take images to identify matching caps and register new ones"; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Export cap grids to Photos"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -458,6 +483,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSCameraUsageDescription = "Take images to identify matching caps and register new ones"; + INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Export cap grids to Photos"; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -501,14 +527,6 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - E27E15DF283E418600F6804A /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/lorenzofiamingo/swiftui-cached-async-image"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 2.0.0; - }; - }; E2EA00C1283E672A00F7B269 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SFSafeSymbols/SFSafeSymbols"; @@ -528,11 +546,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - E27E15E0283E418600F6804A /* CachedAsyncImage */ = { - isa = XCSwiftPackageProductDependency; - package = E27E15DF283E418600F6804A /* XCRemoteSwiftPackageReference "swiftui-cached-async-image" */; - productName = CachedAsyncImage; - }; E2EA00C2283E672A00F7B269 /* SFSafeSymbols */ = { isa = XCSwiftPackageProductDependency; package = E2EA00C1283E672A00F7B269 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */; diff --git a/Caps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Caps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bcf05e2..3547906 100644 --- a/Caps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Caps.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -17,15 +17,6 @@ "revision" : "c8c33d947d8a1c883aa19fd24e14fd738b06e369", "version" : "3.3.2" } - }, - { - "identity" : "swiftui-cached-async-image", - "kind" : "remoteSourceControl", - "location" : "https://github.com/lorenzofiamingo/swiftui-cached-async-image", - "state" : { - "revision" : "eeb1565d780d1b75d045e21b5ca2a1e3650b0fc2", - "version" : "2.1.0" - } } ], "version" : 2 diff --git a/Caps.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate b/Caps.xcodeproj/project.xcworkspace/xcuserdata/ch.xcuserdatad/UserInterfaceState.xcuserstate index ee68d55921c2f8e3c9b688959d13e9fb5030230e..65ecd566c36dedbcd2aad6eb3e4ee1eba3984328 100644 GIT binary patch literal 115721 zcmeFa2YeL8|Np-;+iq{K?A~3j=K_Wf34~sygd)9!4iZ9gKp-R`2_0eIfMP{OrK1D{ zAwfU^5wIXdK`be2ok~d;ZIJ2nG z!Js=p^d84oRJv|<6*pvkEzSlV^Wyzb$;@WvFn2O{G50a^ znEA{@%tB@<^BA*=d4efrHZq%;ZOn_z9%e7IpE(P^F1KNl-p{LMR^bFd8cA`CKFWQIpqXX!5bQm2+C(uds0s0Vqg1$g!(Rb*3Y{6D+ z!*;A-6+5sKyKo$KV?VBpZ^DVV3a*7~;|91PZiE}-rnnhyi`(H;+!go4y>MUL4-dgZ z@o+pEkHh2f1Uv=Lz;p1Ocpje5x>-LPU=!F%Y!$XDTa&HDCbM@93JwmaLK?ZftG2e3ofq3m!rgAKDI*)i-`Hjm9`i`WV56m}{*gPqCFX6LYX zvUjofvGdsZ>_hBAb`iUbUCtJ>E7>*d6KpBFp54Sg#cpMvVRx`Q*%#Se>|S;sdw_kF zeS>|IJ<1+qPqL@jciH#YGwes~XYA+f*X%d!dG-SP1N$TUEBhPAaERkMfirV5XXg~o z#l>+x&d-Io1TK-Q!qwnva!Fh=SD$OZHQ}0aExA@)Tdp0~k?X{D<+^dbxZYepu0NO0 zjpquuLavCLz)j>Pag(_z+*EEFH_e}Z4juj8NMpXOiX z5Av_^uk&y4Z}Nxu!~7BcD1VGU$-mEkz<Kbg*rl#&_HM?v=mwiHw&$W&O#TVtI$p8F7y!k3j>7lLV-{y6bTcA ziNYjdvM@!MDohh*3U>%|g*%0L!u`TRVUe&{SS36ztQOV?PY4@@O~R|fLE$ywb>R)+ zP2rGmSU4ga6^;ofh4+OIginRfgtNjq;k#b#n_v5k0(*jel%_7(ey{lx*|KykR3Ax;seiqpjD;%(v#ai*w= zAkGqJi*v+UO>ri-sh%mtRNvIV)X>z()Y#O- z)YR0>)ZBElse`GbsgtR@sfVe*X@F^4eQP>v`qA{0>1PQ^Sn^8&DJaEDAt^zsBvqDfk`kpVQVprDR8MLw zHIZ6NZKSqRJE@D*RSHWZrBTvoDO(yNjg@kwaZ;|7Cl%rcq^Z(038Y!l-O@eMz0v|{ zp|nU^EEP*Dr5(~v={f0n=>_RUX_xepv|HLE?UnXRZ%S`T$EEkA_oWY{52Z8Gm(o{e z*36lCvtSm@CbMKVn`N`bY&EN9ui0l#Fjq3yFxNEKGS@dZFn2X~Gj})lF!wa~GWRz3 zF{hdPn){gtnTMN4nn#)Q%=zZ=<|*c>=7-IXm=~BAnirWDo0piEnwOcEn;$i=G_Nz4 zm^Yc9GH*BUFz+<)Grw$p#k}8q$b8uRh51YKSLUzH-L?R{0ru zm;92vTizq@mG{Z7%Wue^%Ad)f%U{S}%3sM}%iqY~%4g+s^7rzu7G%K|vqiR8EG|o& zrLLu(CB;(T(!kQt(#X=-(!|o#(#+Dz(%#a=($$h?>1*j{Nw*BQWLQR6##qK$W?SZ1 zZnxZFnQOVza+l?9%RQERE%#X-v@EhLwmfE8VOeWgXDP9iS~gp@SYETdZh6D;_1Gpx5;@37u$z0dlHb%AxE^)c%T>$BGF)*aTJ*5|CxTVJrgXx(Lf$-3M6vh{WA z8`fjix2*44-?P4N{lNOU^$Q!aVH<1XY`jgdi8hl>vYBnN&2Dqsg0^^DRa-S%bz5Cq zJ=-m|&bBVLuC{Kr?zSGbp0-}L-nKrr{upB+N;^C+iTcs+H2Ws+w0hq>?!u9 z_M7dk?Vaqm*gM@(~$?V26zv+T3&bM5o&58EHH zFSkExUu$1yFR_=}H`}+^->|=FKV&~_KVm;>KW2Z+e%yY-e$xJq{UiGq_Al)h?BChH zw_mdVp)d+kB*m;$QK~A{l`% z3{(awgOwplrjn&hS8h{gC^HpJ0cDmlTbZNWuH2#AtvskKR2C^KlwxJ2QlgY9dz8J( zKILWQ6=lD2KzUU;sJy1Ut{hfQD(@-pE1xN!D_DNt}3dkI#j3XQsY#& z>Q^hPH>ow$nrez#Uu~c^RBu*Wt8LV_YCE-y+EvX|v(&IUQXQp^RR2^L9jA_0 zC#y5mnd)5iPW3MJL3O@bqL!-b)hE>r>PB^w`jq;#x>?<#KC8Z{?owY;Us3m~ht$LB z5%skCwt7+hLH$wvN&Q*JI(j*JJNh`%9DN-F9qEp+W27V3k>{A?nCy7K@t|YA;~~exjz=5| z919(b9E%-G9FICyJJvX!a6IYQ;CRNd&GD>byJLrAw_}gvZO1!~cOCCJ-gkW9_|S32 z@sZJJ1x#?&g#w@&YI3z&f3m8&Ln5Dv#ztAv!Szv zv!%10Gu7GE+0EJAIlwv4ImkKKImDUi%yQ0f&U9){aL#hhcFu9$?!3b}*LkP&F6VvD zhnHu5oU1KIMGcx!JkX`JD5(^Mv!H^OW3Gf=hInT#`$5Ib3yINv>p9T~|Ffvcgbk*l$*xvQ3F$;$Di|9k(ZLZ`{7Pm*ZZEI~aE)?r7YpxYKcG;y#M|IPUAXZ``8WuXV3;m$*ya>)lVfH@G*tH@kPZ zce!73A8^0wKH@&={>J^S`>gw%`@H*t`#bmd?u+gp+&{X1^~VTro;Xi} zr;?|#=O#}rPi;@Cr@g0xr=zEn=N3^f&>Qa!c@w;qyp_E-c@w== zyfwUaz4g3}y-mEWy=}a0y`8;XyuG}=y?wl6ykosN-f`YsZ=N^bJKkI1E%X+7CwM1% zCwV7(r+BA&@9@s`-s!!|d$;!<@4eppybpPodmr^a=3U_}_Lh3rdpCMt^zQP$KM)^kjvVCKGV|_WkalTw%o-f}w z-dEr&^cDH$_-^;z;hXEb(|4EeZr?q=#l9uJrM_jp<-SLKt9+09*7~;iw)&p)?e^{Q zz2ZCQd(C&qcg**e@3`-@?`_{Zz6-wZeBb*n`hM{J==;g{v+oz*ufE@Wzxyuv{r-SI z=#Te@{0aU_{>uKF{E7Z5{;K|J{_6f({^tG`{+9k${+s=+{cZdm{hj<>{oVZC{k{FS z`iJ?`{lonk{;+?fKgU1LKi*&9pX8tIpX0yXe}{jr{~rIn{`vlg{0sey{A>JA_}BW^ z`Ahtp{9F7x{4e;A_>cOJ`QP#%_n+{e^q=yd_P^`@#Q&-PGyiw~@BJ73Klp$2|K$HU zzy~Y=CEyBp1HOPi5DFv*>IUisQUdh@4FU}VjRH*rEdnhA?Efjt5QzP6o~dE(CrK{1QY#J}3mu zK{;p-I)k2IAea!W6s!`g8f+bG6Kor77fcPd4|WK440a0M66_r866_J|7aS5C8q5lY zgL%RH;P_xcurN3+I6ZhRZ-{>;ep~#D@w?&=#2<=35&w4lJMkaHe;ogL{MR8a z#D|2C7&3*VkU1oWEFo*i7IK7qp@dMSP>oQ{P_0n?P=ip*P->`4s7I)0C@nM~G$fQB z8Xn3Dg+tk)aiNK!+d{KLb3^xp?hic>dN{N`^kis5Xk%zo=&8`tq0ON!p{=23LOVh) zg(IH-_o1IcKZkx#kP^%Za)Kqnn&3fFA=fV_gSh4~qo;r4l% z6UK#ei_(=U?dr8_kkX-9v!v7pO`9b(Y?sdVJ|ubSXe|JTu;_6qmaz>(c5JgP3aTP&nqA`VmB&hKz43c-lRfO3jKd= z3kouFN0Gbb7PSwLm@uky?#MiHd0IG!%pZoc{=D4VC!9&H(aUUPMvE*N_1K(g%OtO1 zS}-k{R?N*zYo?8+XsYJWoSI9ETf?+tQknKl2c{#_Npou{TDta#wprUpE2_@G%)G2{ zGHJ}N*+t1^?~$BJe$^*0C&##Hx>BQ}%i9(fhKo`&iZXKYMx`rF^h+b>2E7XM=&Wek zgnU{lf4xtcYtohG72Tv)cvME=gu)(qxgE1}!f8`T^^Z%-n^2G$rln0+s#lb_Ti&S4 z&yrNpmC@T=&e^EzUd$jSc`ehM>BFQkeVKkte`WwPQ1fVhEvSXGO4?0Y6|LG@W-v2^ z8Oq$s3}e!n;Y@~BL#w6L*6L_UTC!GGtEbgG%48Aog_)7eC}uR3-v~m_5b|$9IETm6anGC9mRCU>xIDRMK$g$L^Y4Hx$7kwzF@Nq(I-ut!?Y z$fWp{k6ajeyukH)yax5I`gr7HS$^G0OiMs%qO4p| zV)C(3;wEKFT)!+rDUHe&c~+#zmD804|3#4_w9^xnQMuY}(4gVvvZgftvwG?0(ZW(* zF5}Q3g(Qgl809^sY+2_<%9@z2Jp7NBwHc{3qgt=fCHdH>)-oV8`)jQ>E?d%jBPFex zuH64$lr%Ljmv(Rcv%{0h*Xb3_A8GQ4nYg0$jT(%6Ziwx&`M8qS2P1{7em(WofYh2% ztyj{TVpMC2QIV1Irlc6P94&D}TCKXr^f2=Xle}VuR#Ufti zo+|sn7G`VN51wUqGRZ5M?aU6XzSeLh^BnWM)>!LWwmaG#X^jTyO4E^~k8NJJZbcoA zZsQ_-e_`_EOyV(yiwdu7+gBpFHA+`n#*o|P?yFpmdfmUu9Hss5LFP5)b>~(VA+_wB}k1t)yW~TKS4hw-kOpXF3QNxp-P~AVYnb&@#YtVM}`Xu!dbe1 z(2Y2O1G2M$D^CjUYidI^xdpe)%_%%hQ$k&h0v0u9q2l~x6PtmsV;i?}j3K_XH}`ymta8ZsjpS&$XkkX`FW+ONCT zL+h#a(t2xs)*=--kdrxq;*cA8kXK97`e_45`wi3vX}4;_Dr&#~i#9_yF`pHqM6IvZ zh72B(>$c6$@0^*Jo1BrKpH2QHnut^#s!lJfq4mG!W&f!ejfzb|$z>}x1vMb8hw5vC zi%~;u$TeFJHDxXpqh{Jr?Uvrj*J!nyQR}jqw?iF>*+8kNy*6AMu@ZGeowO`%N*S|( zxTByC zrqZFYF?_oE^?fr6Munrpw&>+mB14X{m#6EW(b1otwK2-N>Set$GRG4CvCz0wAHS8o z)EGe;L!EAUnUUcuDX%M9fWPFT{Iv`QW!Y#S$b=kchp7!_!F+T>$s0-A^> zF?KW=O+i!9v_ZOcrAuUrvd57@bjG;+Az9Ih0x~qv3bgTBzBcK<#wD@s-a4bF-!o^X zQP`Pi7KKzppcQIGE75E;N1LEc)N1zEYpcKBBE&rW^>O>s;Zww(p?lE1M1s+3(nqa- zt;bd9J~WRZWA%zJBR)a<{(b+NSij2y=s_~%qcZ?gR-y-(JmV1_Lc}XrhaN_cpap0l zT7(v(C1@#HrcKqRY16gav>Dn=P18V|wGKUs9z!coFHLiIMy+{@CJbFR9Q@d*=+J#=y?$++17zFj~)9A`GvLcO)*>Xs==^Z;U z3<>(LjjGk_yM1Z?r{aySEFGE@@%9GT9hNWn~wVULZVfM9viABohmlIW~P{0U74E z&dHdNn>jiyTu7G)_6d({)gUD^Gc(*EGcw4}9NUpzbxTI!=vFC>8aAunETvh~CQa%$ zYgRv{UZW;t973~-ToXxKzd_T68TINL;}3KZGfZ*``T_ljenLN^U(m1UH}pHYg#OSL zYfH4H+A?jq_Nexlwn8h`R+eCd^*=lout@$ZF;%ry+7pq#YqgEq8uGtQWImNto?rK9 z>0Fx5K=PkvoG?`Wm!tD0b9 zpVT%Cq0iZwxbTz1g~N>Qw_7-ORMBV^{jB@&OK_8wbfB8};}KOKQ}hNymou8@F%TzHR#}97UWK$!A-+d^&V! z+@XDH<0eV<8mF|Uafn7qZ5uRfnv~MMUcHX(+SaSzuw7fD!2R(+B6~bQ+g^+ZX*;M6 zeAny_^oG2ZHtaBMr#AU5;_^(y8E6t7fiszrWCdyuOwdsDkyBqX$4{us&Mz87rvJl} zdu0@j)^=%(N!zMDcNAyiF^ruoTpf#Z(iOEwTBM=qHF^t0)(`*r8J*qIp4VPHf^$it zJe*HwjkCyysSL+xFGSiB7vREM{~RyU6syQbMND3QG6_L`(l45bCy|-vKQms5Co_3h zexP496;Df7+$1z`#k9HMORmDx@om%xy2kIX?)(@PJ`=;A6+TPbO(#-FBnsb7EAawdcoq`hd|>LGlLN#3Zm!4Km{@B+LL zFT#uQ61)^I!^`ob_%XZ!7vq(96@DDA#%u5scr9LsOK>S(kDtUF@J74|KZT#hoADOB z6+eTw;b-x7yaVsV&*A6s3;0F63%`VS<2`sU-iKetui*Xo0Dcu8#INDk@f-L}d z0#pZ3GN5{Z>H}&Bs4<|XfSLnp3Fu}(Z2+|c)SlMTTlhFWfluO7_%wbSzk}b!@8S3H z2lzvL27iP<#-HF%@n`sR{006Je}%us-{5cYS$qzk#~1K-_2Jil~^+?vliCM+E_cQuqx|doh*5jj({=&O#!qB&{jaN z0r~jbtsux)_t4{RQ=^MG9m><(ZL0s9GX z2sk%zb%1LFTtDElfzyCn1l&g8UIFeS;C=*N2EH2b&4KR)d^YgYfqxkI65#g&{~qu^ zfM5fm3J6U>=mkPH2s1!f0K$_X>;mC52;YK8BBS0iHjed0Y||_0N~>50v32Q8Yhm3^ z1=(4b=UlHacWfw<<$-jiSqxcTo=hVO{xj%sWp;^?><*?Y?f+JGdMG0r(YSg+YeaH> zJzYutTRH2`Pp3}WO(-fN6DX0W)s;m~ie&s|y3#I&jP=RMXmKMBRhjt9mHF0> zX&XbnmuvPvHCU5KwnzWFdLVOn7SDw&=uA~C`ItLNJ`lJALh zrOSU&5B;-8kPfe?qW0?;$^TTk(&=yIUk15rHe%OE=5MDfJIZC=jTWt7O5OJ1iQ$~Q z{BS|t$dHHpTtI@YS+sME(2|sqRXAl*R#8@A&ZL5|qq8!zMoyfPmzOa$d)&Cmb#=$n z81<15Y&df)aZU^CMmU|2os>yqtyxJKg;R1flZanDDjZEi!?W~PZRu|!VcBvYl!v`n z#AXBa66!)q3)5G>I%bD+u3@aRBRPNe zcTtbZG(roJX{6}oi=7+E{EKv@Ma&gzFi!t1pP?R+|0-Q+6GMiV4WNCA}$R@GfNC>3D+S;z)RZLe}$9&i5 zBXuY!$Sa`DQT|z8&~ug2m0mIAdbtg*Xy;gH-=p6~PFFg_aGNV-N`UV{W+OY0`1X{m zKf-=JZ)>{J`ETY;V$?C1#Mkxw?SH4@b(2fVe$D>jh@Pt&TL*Eu;%Ugd%$k=g(LbSQ z>ijS5R+P*NF?>hAMOREeYvjh4I^*hB|36>{;5eyhr{4JEJAc-I^?{h{YfRre{kge`B$re=Tyayqqbc@^aHgXXHnhA6{WLIH#U> z?bs~nU-SNt7MkU#DKVjFl}rP#C!Ad9`Oj$a);_eQZ5UeEmsx&Ma&Gnb-g-y~h> z7=uO8vyamKAIJM#V?Fa`vCSG?uJ{Nmkxu1`wd7X<-a^l~MQrx^%2MlHczH(J=sC9f zJM0-M*PV{5H(v)m-`26MJ2Bw6`j~+0qG#5YyzUTlV_mb0;(F@2rN(9@%8^AguBZp$ z`s(?2_&aF*2cJE%(uy0vWirX7+(2#+H<%m34drg-hH>fKa4v%z0jLw8TL5(i)CEvi zK-~a!2h;;lPe8o@^)BVI^zbw{8VlSQ8lmRKF;xNe(L>aL`fD2j4WVIbKpFpgaq1c* z#HI&#BkN(pljx|Z?S#qMIoV{wQvXdPX8r$%kTy4+1nIcj0HsA^+MGsW+8l7R=mDrN zpnm$9#I-ra6OF@hcX9WSur_x$paI3)y?_SB7TD$<;2t6&XzoFr1Y`wY=}PWlvxSiZ{fU*FE0gVJS z3eadk*?`6X8Ve`~(6|!rg{X>lb9=bGcVZh?nH5TW z8@@d;^?X~t9iIy5K|u2XJrptZe5a_XkF0|Ir%iog>n}QOa83R27KJw|peNraqAb2Q zRo21^9>hp7kk2B@;s^1A`62vJ{#Je%QPyxigCD_@H4uvdEdjI?&@w=z8+$aOtj7SY z07QDRm8E<*s;q2&3_q67A|%lY`F{9{xs%lPFyp=kpk;#P0c#j-LgmZz?VUvyb4f`NVs|D-OK z^;9fdDtHhh#b*8m;u!H;_^tdi{5JksemlQ|-^oA6KM&{`K-&O43urr_9e{QMBHj}D z`~^TSmhdk|b+U)w%kLv9d4;NEm(l<10kj{`0VHHL(0!R3gWL zEJfEHBmNYX$Z67_kOR2!aLHBrsh{y9DVN8g4Jnxrh)Y%}hO|Xo|BeW5y$0M{A+6k$Eh(JC9 z^l`+93!S1y{FCcz#1*3gdJ4M7BSbu&&ntKkBgH_0dOX4)VX!bn7%JQ<5RXR~E@TKJ zgiJtR0{RNj*MPnO^ev#X5wV;DbRN(JK;M-LQIAK+=JyI?c_3nmcs$=5V)-dz%YP?5 zE)fgHH^g!s#6mqDfp|O@4Y5#r5wr zFuJZ9F$%I)D2;R{LJ8?kFiRB@ng98%oYajJPYGoHr&M@a*eq-jwhGS(+qhxEc43FG zQ+N(AVO{_%0yY7b0GsJlDwY9T09yguN`>eokg!|WBkUE3wwoJ5`AtF ziCjbk=;d=ubAz*PVfM@j=+ z4RCe9H2~Kv6+ODUB?hn{#?w%PSc&SRmZ6U%5^BKpBcTS|*#Xz_p|5 z5bM+7Ky|SpJ?K~f*U`^JA6BE)n~N=pKExJ)lZ(Yxfa}Jl53#M-p715MBhdp~kMtg5 z2NFGqyc+k{=z)q~H*VHd>`7!Hb`!gcJpeZV+z@c1h%CfD(cYu+ztMY$gT$e_I)+en zG$rZ~J5Y6}lvB4+^bz7XGDsFP#Vj!_juc0Uqs44-j5t=z0o)ugslS$hN&VdnxHaH5 zfZGCY2ROAv%#8}8fVYZ8L>3dNEZQ5gxP?yA;_kH1=y5|8*FhC?s48v;+`&-AokSJl zUEz`d@PE)oL~*VD&(5^!(L2z-c}#LZNaPl-%@OaWMO88~W0h zThlW0jJ+(yz2YlnuNL=<2LKNQ@?Pf#G@rOahfj4v~pTqB0p*UM5{vM{j1bnN*sng-M|z$*Uldk-}q2BtDJFYx0@=rhqAE zis!K@!Bojq*+g8K@qh~e7XmHfcejZX=*{hG?Bh_dK64kYXYXJjj1g?0N)09hJGes_EZ8! z%im(^Lclb22CNmEx&nq6!R%?GGcTrI)UTOE{2Egl^=oEdZB#59DliQ)-Aeo#(_qsO z6R~fkv$+HC+=yRe8Xom)?!2ykjndqB?$M^Px<ul17C!yPU9$6yr_PiNQ7% zm^oEZBUQlAXEh-N%&7wgv z)9q9o3k`8B0Zd|QG(v_~+)&1KP{u=48Kf~58Om5lm9Yqm^Z>k=j)uvJ=pxZ*>&HyR zR23@#FD*8$1iUOpRje_sqpEmbS5`bCW|SP zC6viWugT;N(>}`NPSbOy=S?q|UNr48y=2;L+GE-acopEs0j~y39*?lK7VtX2C4fmk zvcAOha+JweO$Q@PzCoFM(qNL@eruG;XKpZg9hiKFGWjmx4F;1RP$oY#ouLO{G6k?n zKNBkLN(+O_FHB^%rP%Z(;HQgCUjyD8BbVn)-%+xXOc$sdL4%#wTCx`XU|t+6^fGc_<|kbSjpr0p1&hQmRRylxks- z9)R~zLrG2qNwd+`DN+Lhq*Nd9E5%Yn!24qZQfex-hyW=yr$8PcR7tIMQk=`bz@xZMxV6%^ah*P zfz5o%=6JxT4K|A?n-ipo^Z@)e;CJ*hq0?ow`gCaqrIYx+?-ffk0h9P#?9`N9wg4KG*7x;B3|G}fIkNO$-gj{ZQOr}v|Ja(GAfGC zDk!v(VwJRk8tTWT)zTX232CjgPAZW~rS;O2fWH7tdYP{PlV0W}B8oHrY zYzAW=NuTOeenP4Iy@K>cimxRS6DpCuk-nAAO6R2W(go=|>3iv-L?T3g0LuW2fW^SF zz;eLyzzV>Mz?w>=pQ2R$rcaES8A2s1MX6*h(TOouy}{%^&!pK#m^9mgHAk5=I|!3z zr?{UUz{+$EG;$^eIxgDQX1_T|s5A$FwHBM>fwjd(rMa@XN`y*tBB7GC6ROPB2$if- zo=R*iJU7=iCr3~;ClM4`M+J$D;x#mrU8qaUjm(YBP0UTr&CJcsEzB*=t;{zA>jE|o zSU0d9V7E_$aGt4v1ni;rZ=Go>s=G)D809ymtn!wfq zwl=VJfF-Xfbq6*X*t)>h12(19d}kEKd$C}iN4;0`gA~X529Ax0_sTYpc(3fuH;B9r zL_S7|TmftYgUD6XIFg_^;+muW~nV%t(P;9fSPD1rHZuXpc7x7-r&zoN`zX)s#U|Ry)>fiHT%?He{>7qDD zMbVmy;u$KWX61xrl=z7GU22?^G9NdeFrPG^GM_fTZGH#Xw!pRnHWk?Rz;*z( zBe0!-y#?6Lz;-DyzZccR8S_UGHGE3d(A7{w4`9=DE%d#ih3lXN5-=+^Uj(+Bp@pBQ z7JkMeJ%H^_W*O*-D8U_VYMGHSQG!hTzMjQ0@%wtkrUY4(%@nF6S)$gtH-SpFP^kKp zgGv?6iMs3h=hu_S4mmD@r0k+d_NySZQOu`>{ z0p~vtr%Zxo#c~^9M?~S2Qwf};DI$MK#g~1DQtl#mqhNLgHmg`By>2*0Fnh~=buiN? zm?J4#{VABE%7clG*$jDzoK8%%JXF3_Cdgz1I|kUX5fd$s(4%K;&UKBR8JK6wIl3^$ zQeos)PqfjoAC&&}!N%CYOjH&W8dAfWXu=&7_2ets%LST!4oe&YmL|`WY zI~mw1rSi!6GUR2d6_on|Ow z2~mc;R9+@8mmdXoI*bL#i)|?@mWVQ_h~EDw$JWCFWL%ZIm*T#PT+oe9_n{`B{0pO!i@(3+$c1-lfZhyp=_lV6%5$ zSGg#uMg_bqAJ7G}p9+S)VS23xF;cuKpC- zNG<@o#1P5FX!jv)l_t>6gG`H(Y3e`6o$_z;B`S^Ifn8QCli981u}QNL>zLDu?Y< z4!x*`NKpMR8@m`K&aq4)R@pMnl55GcLrbycaoskrqPF=U za}uQsm>jVZ7P*5vQkxWdo)1%?eT*<=ko^Q#U1+XDr(+&sw%yc35^= zp0hk}dBH+3JPhm+V2=WO4A{4TJr3*%U{3-|+=$a9mY1Sb?$dn*%K=K|+Xj^+sI8mk zk8Ut|9hf{pnIui|j=|*Hgh}4cSEWZ}etNEc{?l8qoUwdNiTnuI_lhl_02^aG+wz6w zYn{liD3Kqmw0vXv7T6DgJyQespu_(u^%iy>$dVIO6A8DX6xZn(sKfCSaX73k zsl)N})f|rGMaIqASvwMk!b8EdaHGqHQhSgnqeJb%>)hu#{x%oz2ku+FQtgUnShgk zBjF*r#2S57-I}c%cPrh|kh4VP!`Y+8opb%~Dd+!wS03v$Du?O7Sq(YNq;k-(NDm-8 z{jQ0eh!~tkOV735MWt{ja7wZDZs63|+zsnI>w^*FZhe3lcaDa&tq&2V$d12%bz@cR zHEy=Z8hQPyg>?x9k-oNjtwctOV(WSe;!5i(>*Lnd)-~2AtZS|7tR+@*Qx9-n;C#UO zfeQc^1TG%95O4{=RVuMQ8MWk3$#blmi5=fcL9A?mScT5raW#n%=W5+R@t^lIta~Vm zdx5*jK=Bod;(mS>J%CH38$?CU+GG_!W;FMk*29#>L%>xnwjKelT8uOvx1OSg`2;b{ zTy<)gPZPt;)wr5r9w@7LfpNbNt;DqX41MGE9s)~~JK zSiiNNwVtz{w_dP*XZ;>Ha`R;1>H=2}xD?>(1J?k!hQKufu5pR=hbWN02z$6$1jtJi z$R-Am&FBUNs2>ILMlk$3Op@6w1W20|xTaAcZ3+R>R9o0b5Au!}*&0GW|LOSJJT@Pp z(MF6+^I{t@GA&}G(H62*CREuHD2*)%jkcR8jbx>9IjU5CUlc4`4O{I9indx5MH-5> z-9%ArRW6Z{BE{B{plGXaYhY_=Yh-I|Yhr6^Yi4V1YXMwa;MxI~3S4{OIsn%ZxK6;` z0$gX{x|G;j=_uOT+S-W?F}8J}C>o(?t~+pj$lK@?#k3o?@t?Piwls=jU*L>bv~2)E zk*|tHdH|YBx@mg44B{}`a0+5Na6O7`89+A7j%iN77PgJ1AdaLU_M#wWQxJP!4Mcn< zu}!GVoPaIgR;UwMK#A;2iOi-%_P!>OQ*3i6kyCBcY}0MG*=E>g+B6&3X4z%~*B`h6 zzzqa$5O9Nm8v@)=;BF;LihxTmvE3de@-E$NwB1LE9BvrL5r*41>IRS3fybqk$7R4} z7^aad93njGvjPOgKc@vQ6S>B=mJ&%!L}syV9dKDO68WTU6J;vNwvoDxVan9gl&O*B zm{Rdh!_aNB?a)EoPC*=9K_Vl?E*trJ_7dAmw%xWpw!OA}wwGg2aJj(c0hbTlc;E_vD=e|S76tK;?Xc|#F^$J4h(!j7lYk@c^=;HN&bWc%bwKe9 zMe!rxCKxDwN>H?YhDCY+H<6BD$cc<#78-5+t?e8|@hotYi*4tDn-U|67i~Z3DE>%M zoJy(sg`zmEJc_P|6WdhQjiaJP?`ubPE<&Z9?%~7DtRTIS!fbbwxhcDBx7e+Ao84|# zxM6mO-D!8(EPL2Kl4xSI zJ=;FUPOS7|;FbWl6gbj5EeDS5-b)_qG2m7JR}9?BQu{dFT-(QE!CpvXy>_~R4`=KJ z&8;S}UT&Q(i6?JZ?0?>3+vyHIc4DuLouKV>2Om>gc^TOQqBhn?R6_(~UO2 z-%jR^itP^ox2D)WAGjxCl*9u2VnUaFA$4ch5_eYaPp4l;UB#U>?(~>_71hHEd$D~b za3#PIDXsq(^|0Qqht=#ftj291dbrk#JEO!~?JrS1JY(Nxf7ZU;zQewg=;3+$3-%Z7 z#C6{U9LeWt;5Gxd1vv7yJki55z-U#p+IL6w@G{ZE{)iq9Qa$W2`iSSK9$wP* zuj(mNL$IGd%DS6U%DKJZu@Ms`Nwwhwpy|M6X2dN zwtoiP3o+{9EBm)p4_{L~yh!w5KTC&qyRM=K)%3Y>vy1khsU&`||7iaSxZS|*0dDWV zCkX{Be58v|I4X&kX%}&p_G`P!^=r`*E3)Dtk|2xwtcs0nOQ0w!j}@olQsNXhaQlHf z0Nks<9R%(*;K+&w4Y)UedlR@rz#T4Cyt*Wmps+^?DG3-Wm8m3-7?L2bTa%gOw}JbB zI#D0q5XC<)3Z*U)g;Ec=qft>P4Tva|hDsxP0Cx?)M`%EMF<#IrwNtULnTg%}mFSRt#5 zs3Mk9MSMqzBKrdY_kDRqur&>+Rw-+A3|CVOf2bglkz&2FjbiwuvO(FXY*L<5o>n$1 zTa>K|@li-`^D}V20QW0!zXA6Al19%2_RH8f^#qhZ(hPw!cJdR?>b5RU==>~?^ z0mC;ahHnDT8W6c)nP92Y4Yy5I<1Jj?Wat zGXz9lq)dH6K;%v3K}1H}Sou=f~8uAVi_casdXuq@#V3!k?&7QE)ywH8>!8R>!3DPo2X5JuLR_a z#Q2;31)o|OWIT7O8u55qs2!+6s#Z{bBSklL05R2SceRJwQ|+bpR{N-FYG1XV+8@X` zg?#53UlaIRz}E)84)969CjNKi{slYcZR;L5sC`LVK>TF#PWS33g8&jTcr+Of7 zga2ifx{BHuNZqa87ctZ7y%fS`6(llJJfxB@tSC_*Rv%Fps0-Ca>SA??x>Q}JE(g8^ z@GXIF1^msxw+6lq@NI!_2ju(i`1U0#`BGE*M_nat;eh=61ck7J0bysl7ZB=8jq{Ct zyRXA)3Uw>R@EPDc8W?V;#(9UjlO906gOI;PKNI5|QVfq$-L3AWIPL+yOR>5S_^vVH zctEA^>8r1jnGU`indwkpCo>&<_p8lx*rpqB9aWF(IKD-3>`8Haoig0L9K%M6cht{` z*Py78$=Gqf5o-qkuVr zj!MK)aKt-84&pp!0-ptZIN~Tc$ci{R$H0%gK64D8j*R><3R24v@poD{>QHUakovVs zZ=`78xS5!0M?*&=M`K45M^i^LM{`FDM@t8>&|`of3w#dnf#M0QkZZ zM{8Xcj#S-WaCD-gFhc75M8jX0dc$A%=lumoKdK1g(is7D#~`8zzB*ZJL=HL&P1YKb z(`7vjcZ{HV$N+v)v4apgIYvE?DZlsKa6iyU`3?snXR zv4gHJ;^!KM`5xdO0RBNLzxg*Pybcu76-JI_z>}aywv$v*g>eeFGdzOI-b^9e2TI-kCL^8+GY}a|Eu@v&=T92qDwm- zH1508@j}FNa6C`Re5iu-MvA?THz}F>94|XwaqM>-aJ=d`=y=WXy5kMt9|3*=@C$)o z1pH#)mjJ&Mcv2a}$9=TKaVTmXk4Y}aaYE)vO6Fq*nPeC6gTT|}9sJrGWL^g{$@-#V z$7jH=Fv$FplKGV|iypv}ug&{WKa+LpsDshm7aZSHFuwzSRk7nD@Q=p`=Fg7bbTG+R z`T@V1f^~_4xu!grodYUr9KD~Db4I?9qlHtTK(4DGosq)o^buFqX>;10ic@tuoK7A) z+DFm+)4OycBWiDSDs6Gj&ZXd&OXGMb@p`j za`p!P1>j!N9Pf3wtC5LL%SqK?TC zbxfn`IAo~fC{@QvT^;ZI@2TVesdq%3cT*wV1N>n_NaVGAvLM6x06l1MgD3m1(=!oL z&;VqCa}gC1>9mg(J4vVgR*XVg?p&b@=`kv#;}pe}R7fYv3yCvEh0Z6OrBq03o$H(> zz@Gx1L{;8i#T8>r*193G3ZAN|X1d}Sn3r%}m^?TyZ(O<(Ec;;^*;uAO z`E|Rz3AtH?=}MsNuX|fa z=Wgeo|Bt=L2MLJ_-0W5I%=C`G}B3#@|DvI{C2m7}r7*kemY zqe;{>(|hmTm|jdTruS;{`<$I!5K*u9_qzA~U;p1L-}`+Vh25FwJm)zxXUfj9cX_PM z<87X3!|I`# z;=2!3@OPq*g_ zPg&EFslgs2bc|;`B$X`X|ZzA${5&4IR{4+Qxj*G~@MC9MXs2niG>Vv}6 zzR8ECj&i4B>cXHf8bX7@*ht>qZItKuuMx&(|BQ+M{G7|wmoZ`LCyWuHF<~0Wn4l|y z|0D7BFRqw|nUWa~rs2ZaxW+U>7$eVaJeX2UJil?bX$)txjeNu2?w0b~S-G*h;pYRx zrIl`)#9%OGm@-XS!l(&jlrT044h9o9oXhiu#-{&^sg`M`i6>UeQIW@}Fg9mY{EHjB zO+}^(j*LxnOmj`erV>-BsmxSvI^X0s1%$DsFh&bwD`9LcjBSL`B#dTZvLit1B1b|u`!p2 zBk4G&ypff!EjC=jyeWLAhH1NrTeNFTJA~0)W7;K*p0gYhSD5(fv)!gE<&cPBNSLTB zVT?V+kZ5Ii{^U<8nyxb))T{Dda$$i&yp?>F6Iy47@>>2}i{ zraMh{neH~-W4c!usj`m3*hv^W3u6~y>?(}igt5CY5*lv_ebc<@Vf}2j>2X<=3E`^j z5jvY~?DJ1e{ugTUh^)z1gfTH(ldn^gQI*^;2RT>aemVZAJ)3PhYIPT-M{KvL1WMdORlUvG=L!v2n9-QGILrK`+DaWf}IBWq3@MAs5|GI_(&K z$8Y9HIc50W^oQwB({a;ZroSU2%m#CWxsjRgcI_{W1B7v)FrF)ngM@LgoH7{C6UHIJ zI8+#i9WWd9Eu^_glMl_!0VeV{)h> z&27&#WiWR(ccmW9Y*t3rnAxn1I=gx_Czz8cEpwu*$I;Z|>7TA2DlKzAGfxNDWA1Mr zV5Wk`2;*2`Obt#M%;$xs4CDS6QwH-$b4qYjm`BS|Auq%Ki)ZYc)67#D73K-%iRN^3 zhB?!m)p&+^vN_wFW1b?66NNEd7^&+_VPrBCybN!gER5O0m?Ml+4w$EfM#ansqd6}) zDtvNOgfGJz@k#-IquK~W)$;mh#m`EoPeRsB_x7vTA_sNkQq8*1|s zbF~~8ONDVpjadleSzd-WuQ0Ec17oEe7&93d<~4Fd?LF0o+VE$1Y-})Zkz-?{d6Rjw zFy;$mfiUWq;mzAZW8;5v8Qy%kIe5Bgp!rHUHsob^^BTFwXm}GrIP`tyo0+(nuQ6Y1 zzRtYge87Cre7*Sw^Nr@4gmI2A&K1UDVJs2GQei9;#&TgiUl{$u7}#$<6dD${M>Y=* zio4~Y2w#Rb&gYbizMr_@pP9>le&%9+QVxlygfV;_-ux^>qIHe-e(M_Zi}La(-@eM9 z#(B)9r-nQGh?zT>YRpV{7Sxzu6UMXrXuJ6>^Sg3Tye$XCLOCdo%0aQ{RD&X7W_V>E zn?KXH)1S&(Tr6wxsI0|?pN9{><7@Npay$Kv`CIdM=I_lvn15tD{j>QO^RMRLgpr}l zbcLb3Oc<97lVlXa?h_E!4CCRt7%l`@EI$?NE z7&meSPX24_KV|u!FH1{n%F@E2_uA0(#bTi>E!L)&HQT~1z{ZQ@Ri;Ky2m_zqjqVic zcZ1p zgptklmjB&JK+8Z2A3)=Dhh>l)6PGY1{>AsTTaqnlY@aP7EF&$WETb(cmNAyGmQ>3) z%XnejE{q%)?-WK3j4u_&%Y^ZAVZ1^ZuN20s_FE?C!@-hi$+AphFl5WYP!k>uR}15H z!nj|KgaiMKg#W@wm?cNTY+>9T9tm^gNSGT{DG4LrYyOu0CnMp-aDDnM6>=m5gmG_; zg;#MM|7;%CY^kzbAVe8}UZmX|7-I1ncy=AAY%MF%|mQ9w;mMxa8mP;(#EZZ$qR+)#VlX2@lI|S(kT)>+$!W4HpJu{^I= z;B&G9AD{x8UP=Y>p3|J`upF_x$85p!isekT58sCxww&)6)km?}y6pV`dAVMn1*{@|Y~dXTpW}LU6WlgfF3$ z+sN1dsmA|8HS(QlH5R6J&xWhYHZk5IG_~4v&aW!<*(W{}~Mb z`N3cv&tR~o3F9ZB!C*~iFhmyf)I12oq2N>Vsd6B266>=X>oj5f{458; zOl$C}Tbwmd4uoT}rhIZBe9>?q7z)E>HOE?_7h6`>mm`O0zC+w%E$8 z*49BQnc=hY$*vNPn)?!|C8-< z_=|9?HP$_{7hXHAGAJXec1Yl^-=3%*2je!DO96S zRiSD^jS^}Tp*9t2GodyYYK#5WCqvuCXG7b?7b!=zWvCq0R-x^pYWb%a{|m+Vt}Mo* zLX8d=BhPd^{gl=Ex%CTKhR1~3y2kpYPTuRZK_SPMcJCzn%bJ#s5HA! z9YS>q)g@H7P(4D85o)YZ+Y0rZ{kE2R5!%{BKHBuMhzOfi79r0IkpB~EM`7Umm*kT~ z)o%Zk;eWmiZ5=2>TSuX`50#;<3uS2Q+PvD<)wEHlJmjW@{L|=Uo2Z5A!`8#rlVY?b z3AIy=t(Q=F4*b~^qphEfhdeSRjbJGvkGj(Y$JljXiJvG z*!>J|3Wqn=Hcp0@D%AKI+jyZSRNpW?C3(bnZ;3CX!e3N6J9D_VJP>|$hHZvo*>B6V zW!Wa#Cfl-YIkqXbskUjh=|b%xR2tb+s5G*-Q2PkAuTc95wZBjY?6>9GW-1*h*_pNi zo6k1OHaj9hr~`$1E|p5bo-5QrLLDsB^MpEta=sy_!t0;ytH?+mn;UJ?|DMcEs`E{f04Je;-saemgRdZ zipom46IOraP=9`5QH3wRqSDWDLqF8lJkncIR9uxCZ9Z{jE)X+Wt6Yf=P$}HO3Mg$%_3X15`VSr0^4HS65CRt4ioBdp(YD;#ML%oTV`8s zyHKbjg*r;8qlKCxuhUxS=%M))Me~X(s?vSsW&VoHq6(Q#TZ3Or^7#Y7G&THHTDeRh zxlU~GuHj`R<;6bv>x{~h60g5XFR2XQ>=IvTMIdWfWl?d#q#iv-_DD>Oo)i2SmuR6#txa0Oj&av&X31K)WR7!ZH zY+J_$!PHy#1bduf%qUP+|{9lqxN4*GH76wTg($D=#hzR3s)IF+?7C~Ho)hVS6-DH-Ew@!SYMT|AXv`4=p`*G z(_dCrQEk}Yv{@UI%iW=4_k^tWrIp3SFE?+|GP+gk?C}|)=-cNPdjo-K<1>a0RS zWLas&5l>8Pi?-*qYtO$kiWd4<&#aNf-q`{EEw?i5hIG_FE7wYkt~OSylx6(Ta>^#7 zDp27o$x4w68OD$}+^KVyu2b~jN_k_p{#Tz|kbKSVF5OsmS}+Z~HZGnPBo|TMWu;W! z?znFJFtGF!3u_)=ZM*F@+wH+I zkRjAehJjGCggR+o>$Oppw!3ZjL{)NAF@iYmED`4_LuERy>hUU}H|DCPf{?QvOK75uc_&_@${UmX2<6QO1w zChf(xCv8vB<#O4(ZBHs?^jwaQQt=mft75$su{{z8^y;m@@>$z+Gsb6(tE}*{ml*2z zd#m=?o;7S#%Jfam3+&I8*3~?sDxBb#ZLcWt!JpOGUKQ%}>Mr^#->_{C{**0|{)2aH zo3E<<$9smr{)iiFAJ{&$ePsLC_KEFN!xy&CZO5vA7b?R$PpAb#oh8&lq0Xt^Ce*n? zEs^UiP+BTJWtKAA_D!`?&D77E8$D!JQL%48LPA1TfPtQnUzjk#UlttM335jq{3DLd zzWy(g;7yQ!({;kE1SWw|BmD~G3LRLyjiFdZITiuTu zuI4v=HT3eZe`__t=!1(N?#(YeMf`v13!a@DJ@nsNu>7=2AYqihs6hU$LBRG#`f`hM zqX++6%Vn2NVk%C#P{TsG(CYFN@^9zcQDwIl=SC0zx7MS_pORlzI;^syqO7!G3tH(* zX5KRH-&%5AHzvrHoT6KsD=n0kc9Y$u#9w1K+bwph-DbDj9d-^%%7uEqQ2jy;2(?0} zl|r3&jooecL`2wQ?PfW@6Y6{s)sGon6w|(PIk%G=oWX1+CYSl=2FjVAJ8ooB^6-BoSCXy8$t*_`pZu)Wc<;QT+4^)v{;2k!L;XJ5*@_=!RTLE$ zRTTM{C>K-|>hDY`4U{v1DD&67toEJ3mrauelkCf@oIRUq7R#~pUPoMI-gCB)!_=c1~pwd_SQMrb{&!hr53e?{n{BwA5S!F@pDs!W|)-F1u ztkR$F3yqnql)4wi4=?nV&X((mt$lI*z-vpHdG2XmGK_ASTV7Vgd|YpE@PO)c4a=x1 z$tz=frhf*oVZ%l;Oi%yY)Uw%U6KuFjD(x3A-LcQJ&$lnISJ@ZZ86g~Jb6~Jps9S`( zRj8NjvoE$UiBK6TLT-K7yKd*6Glt0x`ld%7uQ1B<=gMOdKgWc0TyTD#8*SxR!Jf_Z zd)eEuVGnK&h|romY*cOUhc^21Ku;g%a*U;w7FVTLmX@-q8CG1DKQ|z^QK4;6I%Sm` z?UpMJy)uQ}2J5ci)uE+DM}!->&c0cR-)q0vzTUpUzR^zOb_jK+PIIo`RP4Ju8$QmEI<5;lh$Fk(Tux0Gpl3KM3+?-2g^&|*5bph|{OI2(~jfx(BZt?VaaKDP?2eEiSGKFK76KEPY7JfD1WB^ZQCe@9~6}U4QscG@+HZ z;|0Sj{k)?h)mvGbUznjMJ>55p9F*q!I4%j!q3l;GpYAqXXs@ZBC)6vdhvdllO>Bn_ zu}S)`_cG_Q?-Qz=XT55_&c5G%fF098`}N8!`;EBSe#m|c+pgrY{7QK^89tPsky60F zie?r0{1d!#>t?PU1?e0CjbwhM|D1Q0c#BHwUY^S)o-{5XKW%&!Kj3KHJ?RL_i})g7@M2x&Et4IcRu^O+;VSKahbOu zIOnxLYJZFk8J(aX`ssT)c_JsckO|%*|8D!^a@Kst*MvjigPZn;gnCWzC}MARW}z&o z*rd>}4HwEttL0bdsC&b2c}f0EsQW{~+RiPI`;6FtUIww8yNQ)OJ-E%%zxDsq<9^Nl zy1u`zm(99HaDsv%L@tBpYj)e;kZq9r_p^F^PR}0s9J}_+Pb7Vd7A1C%Om#&;Gvs1N(>ekL(}YKN0FpLcLk2hlF~IP;V9L zZ9=_WsCNkU&geJPu_}?`_E##rQ{OhRdE)8}yazD^ub4(88RBt%bY(|@} zKpxfeGr7|XZ{~yNF7*>8{6_96NBI3^ezuV8uE^zxQn~eDe=dJjyP*2-l9OU~c#O-2 zZd*%Z>tcUu|(apgfp!*!%9dV9$M}i~K(L<=u3iUamJ}=Z4g!-aTU)tyB z84=;=jlObP!8bby^%eQYtNOH}r3^MSfz0vr_GWXvrpFDKRP>H`haAy0wS&MGqU z}2BVH4^Y z@8FJAxq$Ez(;Yk+_-aRnBh!)Pm?YFUg!-mX-xBKES39yDIgTj~CjIXS^-h*yJBC%sqGRGfO{aPT6}4H^CuNqb(pkZpJ^95peHp%jtd#n{3dfb_;4TOPB^k2D z>n%5<@J z{ZObMq(xtQ@`>K&Ib4J&3C+2pIUidwl-*Z=am>-GZ$=VR+l>|Be!Rdy4M9$n86V-6QJ68 zVQ#oj7dRHnx~y_6bS!dk@cpq++0TC})X%PVEO9J#R695p|6HiYg!%=4`JdJ0Y6oAT zewKB4v15a*OJ*5g);Ksk3O+*PwAC!@aw~PY?O)gB*J-T`i|ZGr#p?dQFU+fr&7xZ$ zR<)?6ndMB(b2wD=Ef~##XHApfMaF35Fqa~lGNo@&?>>2eBM;qz-ARG>a9#DpfMqPK zRny%~njCIpGN1kXhTRfez@Dgmb9UPtyY2S65#n^6FhW+Z4px1Qht~uMC z<>G{XJ}!85z^TM5gOst#1SLzE!WrFJoT4sK{K^Vtt8%%rTiL7ZSMK9lz$?nT%KOSk z%2&#_%5R1ULt{gfp}9V)YR~+K?#T|1IfFA?raK*1a*W8H>QcvL?6UY5% z*Y|rJSIHX#9D5vB3-t$fQ0%9E6zb2iT;)!uZq8M^fzvmPX{BsHi{zmaJ9&B3c!pQj zP6kZ8u68mY4>*Nx9(=gq56A^+Z;SRD7>?}Q>eex zz4ji*z4c%Ffa6Ie{#wU_j)xo%J05X7>UhlYxZ??-GKKnGsDB9cPoW+c>R&?rTWHF) zOs_baRys81Vc&)a9rE#798z;oEuVP7<#o*XY!=gz57bjy~AV`2+IsKeWpa zo)D?`ve6uQdbv2qE9AlODHc?FK&mgN?WtcioMFIGll~gV39l)U2e3@h7!7iocG`He zO69@6yf}UO^@R2udXP+{dyC0*LT#R_`?HrIKFXw>-dhNNv(;{nhLF%T!STaFi7(=^2^G7!wbvGxM0G(Uv4`_cmq|LW%_B2 z3g1K-OfZ>KgK^1A-oY#W!GC3N){_wsz70UmrH6Sd@(bm$Q>Jf0MeyC3v-sDmj^7=B zIR11Tcl^ah^lzsU(N3A=Y~*a5GPN5l*ok)=d#Qcd9kt}80D1U zuY;iot+mjaYn)NgS_!Sq5v8Tm=v1AWGfJ5iagLL%nVF4VF_-u&l8XZ6#oo}h^K9~2 zBKLTi6$B@^X37#;uvK?9ceZf0%;Bn8AXpVbYsqYh*XUPrVnY{lf;(_$D`)HAHR8~v zF};rV@Z&a0S?z^j{iSB7ML*M2n-x7Hgt6Ocl{@x&DXabENk6YkWhwVL=!SU{6>1sDgSWEKismkVp$bOyR}1lSn!+?X%Fsuf|F2rZS=&KoN(f=?nQD^e&UOQ zhcCmbf(vL9+@Z-ccC~X7#-7|-ocN9tkE`mIYswEF($#zODYn18n5zeeud~kymn)t9 z^?$S*Qa&WOs|hD>kn=n)emVyWEw;uvL}+bgHrlcY@$+yuPTu+1mN5C8%~}82%Sb87 z0eub0w#|E;Bb+0hqnx9iDMC9(Xzhg7UTE|{N6z5oM#t+%@>#_dex_q(+!qpGd(9!d z-1xd%4r))Z{fjpQr^Q8i@`45%+|IS93*>o~hRdH=S(HC_w6BhS0jyC>_}EkYH!I#@cVL{ z)7acPr#Pnyz5rG0zFY3@<$hi+z+Ah4RLpaq5R#Lm~2`@a~ zS;mKGoCQvwbCz?qv(Q=Ooa3D9EOwSSONACMv;?6g3ay9Gl7!Y%XuX8iTWEcR)>ml# z4misrRHxq=;D43QdCvLH1+%x6)5B zGp^Xo1+S@c(L+BJmM09jT`N!~=O?~gc_}=U9Q{&~e%RMCG(>~bLT(+HQuoUl3H3vD>J4JOTwe}W3uU1HY&6}(Hjp23-DkS~ zGg*;6dUl&CyI?GHh2U1+&G@SR2O%z(X&@%QoU*gi%VxeUQuWD_Rz1>=-VQ3%}K(M=+v4v}lUk2-nJ$rH$la8YN z@K5x-@ins7INx-><$T-uj`LmTQ74nPETM5dd7aR<3GFtay(F}+s~5cP{LuN4^JC{H zB>Pk6XU@-^$DCg{zjS`({Mz}A?PKS6%7RmL!1X=#{e`^#lkW(jq+&}LRMj<}D?Wpml7X$EunYFGFQ z+HRLa&bdzYD<+yQm&@()Op%B1`n*wS`9kAni?h3DDSm-`;%qrLHR(~bb)7Sn807wv zqk_WSu69bcr(>g?*`>gwv|>h6kj#k&$* zi7s|n^fC=D6k3tc<_K-B(29jtA~g1EWkM@I;Nm2&{6AM;V@p?mV=Ml4F1OTc=Lh$# zOb7$=6QCLoGWkSK5C0#0jLHA6p1bTC%ME)jw&woO4fw7!2AHvEBLjbOMd$GkdzyfrLJnO7Sv7zPS{25b_uyPIpyyf>}g!fonI)kTq~VlE3<4LxYi17 z1>0_UjJu3Yx4iy7Q)m~}+j6_sxh}5Ta`TSmCv3Tc+hNxh%EYzRb%|@6i%HLgLR%@c z)k5P5B%#fyYk7oAZiiP?@0FY3lkTVtZHBMZU$ZLsnoRkce|IDNjlL1~1~gHgsZk4%;xWRR&&^Cs0b#X9P+O7~7p}Km z?>N799kqSv{7z`Qg8TieghsDd3yo{`c_;7pT_3tWs!Ivyuy!;^iTwHw*B7oYU0=Dr z7TQjsF;Tu;Xjjzb<;~z-60A<%YrU6iNvFMQHdIPKN4AvZcA5MXrMh=Y?bhet-mU+r zR}?#7cI%-nky~}Q<^yJK%^l@#;%@40=5Fq8;cn@UcDE8*jnH-rZI95d7TR8+?GqY1 zwrhoUozV90cel~^hig65-++;T|2_ySPWm zy~{1^U0VFkj*&N?{Gr>)p;6Fa!{ttMe&L?zPIrFoPO}}81NE-pK)qjRw+9C*_suX% zt3N?^XS;Li2I_r6yW`}6>dqxw?wM|{JI~F4V0(DC&^Qjfw|1bq(;|!IxVuY!mR=52 zvz$veyb1c0qx1}0xKquz?XC2o$z1b6~y+)LfnZsA_$Uhclo&2H`i zp*<)x!h2X~j|hzo@MA)ITxd@S?aBS_RiUAIk^TW>_XasMc`%9mpU|EOeE?Z|;h%x| zpa0~i`*JxjuMpbP;elC0sWq>TP$fS0DF-Hh)D6sQ-STV3+-y~!t#LDfeC{j<=8bOs zYsTF2YsR$a8C}h{$kD||m{0w@U-zBvd-U;mw;Yczp5Yx0$KgYa!$;hYG7cZIeI&=> zE5UL2rqB)t$KfmTlPf2W!>8TP)Q!V8g!XcSarlxPhlkxSyN|e8;1Qv{Dzw*x_Ilko zd`Lf{eMLW_l~s_>wEd=!KwrHP_?7!tIRd|Sf8+kv{hj-J_YdwL z-9Nd1cK;%@w}ke#(B2W+yFxoEwD*MezR*4p+J{2>Xuq4gwB-M}kGubpPgC<4g}F_Up-`(KE<1xNbE5EVOS=9*v%4(&QQ88R;4287(xH z`h(DZ6xvUaXByY109gCe|%jj7bd|_0?nUB80L*^3CRcszT+dSJn zJ3Ko*yF8bAF7sUOxx#a$h>8?ZeC@F+qBIc|C8C;$sHP&SnTTpGqFU_t)aaW>&t83k z;^E^c6db_QUg@=l!}7 z7bBt^4MyCja>RY+`P_3%L^(y2OGLTX^nck@m$gj5vl;S9u*Ms%LeV>{Q>Z(Dl;bo!{Zhr?rVa|Mk9W=p-7 z1eY!^8iix!(E&<)O$=StttN)NbU(u-&zKHe@{H*iLu2AZRASw=!I-Ww?2;Q?@{CD{ zh=}PSqT)GxisGp!wOMU9WI^JI*Dz!H#0)sq`v)o=Vg`w*9$dqWO5%YfwV7@=Wc8Qz z7BDO(SvF(1i0V}nA-`=)Uj62he1MxE*nx3tiGD%5b?CCO?BC3?;PPIMD2mviw2aso zaZW^W^#kF=q{fU>LU(AJ>+aC*(W@69Z0VmUZ~Kr>k%*ZnpCS<%TmK$TqZ;>r`xJ?z z(QUM-CQX~^PmJKEON-TJcQ{>cIx6;@*!CSdcIq74wOiNj)5m9=@PS=E${%{LNGm>l ztN$XME1}%<&K+Cs@Q+4vjg;HseZJD#>uD*;JX%6tgO%6+`6z!%;Di^+e{!9bX8~}c zA(`8jQUc?+J&&Ua9tct7m-ku+%FDRP)IW86M)0O9{raSpU)QaQi?yN874xd#BM8QM z19MqOZK!&$P_51r-CwLH9QetdkY>!@viL(3otNceV{U40ddjGEX5AGctIwCnac|@<| zxM9PF4~^^9V`T5RVSR@Wi|ao$vB%KfJrny5>(?`jwhic47Hv|7D_*5s2`I6#CMCA^ zUqks>Id{Sal=(^#_rP-NgZ$`5Z9nQw)i>(l%L{ck1-{(3U;hE;4nD=xL?-Fa_^26J z9aS@^x_Nc0mxm1PKWun1Q#3j&SSxz(WM!o!>sKRLbNye*hmKS?scu@`jK=oto7}Ho z&!nMoNy&Zs#3d%t;$i(q_KoW|vS;GZo{2q%CG||4x~Og-9Uh%BX6&gRIzs=Qxc=Q^ zQfnVRa%6m3{|OVjre|>eGTh60lqbF;^ig(C{a2h^I+RuY7o0Gxs#{h^SGNd$7$YhC zfi_tJp-lkUKJ}(ybjsxH+Vax_+dVmzmQI;k|B)$EQl};^=$X`aR+6`Oeqw`%q4t_Y8kUI3tEdjE)FItd6)U;+lvXBkqoPDB_Wb z$0DAHcq-zVh{F+IHR3Tijf{=7MhT4uHX71sY@^IZ`Hf~ZDr>Z$(XK}KG6(UCIKv?O+Myf0T$u{EI~Dv;X6`W1aVrjFc~?Rib4=OPc>I85ExAR(iDOjJ~j|xR`@Op;>E_e`&J3x*ckK;)^tti}? zr#Oo+7bPfD6juv4!FyfA>xu!naSaBsy3#?6u9;xnE^5NXx?Sgk^}AR<7flrx?R723 z3T(y}Y{fS0!PTH9T-V@M+y-jFMgO`U197^Zz*Be?Z{Zyr#YgxApMkn@{es`{2aYR> zo4#@rt9umaGdF$aUW@Ix7W;7!;@8Ugt1M(3|f5(!OwhEe~HB2C;wobSa176TJ9>mv{ z_}UU*+Y&6q1z3V=u>Q6W<2lgxZRz{A^nKe`Kpbu9pSILrTk5avXE=s0@im)Y55}Pa zwC@~ZKZkxi=T}8(7YQR&M1lC)Sz!bH*sdKqqBH2rb^|d2X&`UyrXv?#;v^#=NL7eS=Qk3>)xNt7g!SdU)%=Xkt`%0|FTCBr*Y{X`4#Ww7~ zE?kcraWm+<_P2pp+7nBAVrhRL9>7ERLgBtiq+kJVz$=Q*&ZjI9>Zb04D#LSReT6) zrPIfdG5v^N@EiUFbxSBp=jLdMRv_Na2Yua{ws)rO zo!`J)cn3%EK3IQe*4>$Pcm5poLFcdVjiPjEhHjv(UCzfPAfH`$Zx`a~>IOOKIvgW0 z8e>3Cx=zCk%tRgvK)-b@#2m~8ecW{s7NZ)=a3N|y-n!lb>ZL37(v|jfr9E9~PgmO0 z^?A^auJ3`kyM7Jw)~zw<*KSQg&braR-Au5+1`pyu+}+N@P!NANV(&)m-BK|gQ!yR6 z@M0VGfWGZU-*&qW?}B`FW8K}p#Si!i#L=BT>h3}e+M*q(q3&amg~`al6p*X#An=yQui$&-`%gkKI{ke)SY~Ee*{nCCA^7GKpfp!FHKY8sN=X;^ap(x$MWN7M;z^l zqaWhvhdBBnjy{d6z&y|waTj9)SbyARYz6I(qup`0<4)X-Cln=~T*Z^Ccybj#0qMxZ zBxHkj#k1ac+7&+&)IdDzj%VHRtUG=VicyMk_(3f3#1c;|@e6SQmY^EcMm)6<9G_s;sqSWD|j7@m-u(_ zK0X5DB>ot_Qj`SZP6&XQ6RJSG3G`nAu_mm*YFvc%Ag2lBGhsV+;c{FBa+|OZ)KkJi z+=xS=emsOnLGBZt#&dWPFXL6bfwyrKAAm8P@ENF!gs<@(egrj=@H>tx zN}_^BFoH3Y*bFVv8fMtwga_1cVtaH#SHyt@BqrfJ48?Ga0Buj)4BDGW-z4t95s<^g z4?%2+4 zyGhhQ(iI@LNz_3S>q(;BNk8LP(Dt6ZzGo&TAsbV0HK>uEw}9ODq)vKOKwvp&Q!irc zbp*7d7wzapJ9@GF-o(F#jUX4jx8im1p5F33pM$>XL;m{E z_CB<|56kGoGWyVtKD46`?dU^0`p}L(?}3`@^9|_BK0hc*U)I}~_w?mGeOYf`*4vl$ z^`*A@UWpp)0d?D#_w<$T`3clOKVILD9P}dx{m4N-a?p=l_qztyfpOFCdXTgJkx&tZ zrWlW@n2ua{aTD&t19%9J=!cgBn)6?3YCtQr!36NW0laSj?;DVhTks$r1~oq52}K#$ z1XkEV4GyFR2hPO;EW`y^iuXZX4Ez?<#X#D3ZeJv0Bt~N_$mO}T>0H`$?u|Hv-xXyL z?HWY82GOoTEkGUy%|;Pe-=I>^wn4OQ5N#Vo+XfNiU~)3JHB7L8I0qM^67#|KU@$Qb zCJ%#O#9>f7gJ0v2+ksB#f^LXM324`Oyyv_n5MX`hu}wPfd9aN0URIPLW}Jie=m?fE zq#754x*4(w)Y_0Y@hLvX7x-FHhQ?t4&c$F1#TsnG4(!6^pw@ zhNWOE#$f_3!*w`->p`A|QDeguM4&Oq?{MBToEjTG1=B#^58sbFa2M{u{fd&Tf%YX^ zK|YgRpf8duFc0*7@*>T~1^VBI5G_ehp8Dh8}`6pbE5y^SJAqf(KE zbYy|HkD7`Z@S*^;V-#_XBCb)(a3NNK{us3m>#-4Aa0#~KR~Nd5XTte7_%AVX3P!{$CxWngR5~3sIf7Op)tfWhMF1kIEZNsF^zd1 zRd!QHk;5>{5@uw1h>Uc~5 z`AntWQ}f_MA?Bh4Yq1@df!I@tJ(YE(UW)@@Or_on+K~Drh#{36rjozZckwJ?W7u&H+JkxE~LJdYwQ#6Q07epk61uh_5(Z+YIDoVmjvGGSH5RNAZ`U zq&q>}>7CFO-O(H5EuFqjPX=+Pk3lM^)$|FNj+vk>>9bG-YBhZ|h&z23SXcU$xEka% z{W?&q>34&CraunGRQj8E2k+rS90ReZe}nJwqoQQA2D!}`i8M^a46yzT#!3eLn^6M# zH^UF=E`z*gY{Xt%3)Y=+18&BxAeR}$l0hsPYA5?f5PLRtlTF=ZGX}F?1G&v<3KJ}7 z2iBj{6>&&HFZ4x!kk_0n6obCb@uLz8un6QahknnY-*f2q9Qr-yVvw7hYj7P7;CkGI zTR=VJ5KqoMxF6I+&cis0zZ7K(d6|-oLeL*mXvY-#V9K|OGPOB~d#W8Sc+d&tZE8>S z0dY?~7lSbb!;pq_(3Yvx^3XP zr|>LZ#;bT8Z{Z#M!R00g63`FS=d>|M1#wKHHm6NO4v1sg9Lxu8oyPj7ZN+x%0_&f4 z74~2s$mcZTnMOR*h+`UYOnU~;;RPJVDg4jr;H*=-XU!noCY|p9T5M{RE%m3lLN8H~1bu;urje zKk%2LkOpNY@y(0@d7GJs3&D7t`8;@^w-u;W?>G>QTQ9&zMt##WHiJmScsjd{0$81jfA??t?fS3wMUZ-eoj_W?e}XP{5> z$yq)*%O{?EBcjj@EkRuQ#FbB6`8M=MCKg~9?#D-pQlNotK*30`zyfNZpbUOgU@=x= z4c1`;h`)gN3$|eg_JTGPu&pYfMhd8rf?Gg43f=*67sz#eiZAgszQYfS;v;@vB*>?a zczxv1M-F`+v_nU9K{pJ*a1g661!F;;ebYe!`H0_Fh`A^Q_28oozJ*u?;`b51kNAD$ z-bWsMm*YxY4cg$NAAEP?K0F9&!}lbf0e$0p0WaZQ$oPN8Z}=08v01cd7VVj(p($E` zzL-TjX3>sW-4KTaB%v4jpdSWe5Y7X2J!=t&ZPr8hSW#xPyxF{O_IQ+ldYZij^uuiG zX!fO`9kZ{;O`s2E-;TR*4`|cu2k$>Yy$ODNFNl^2Zi)O;Z@iJa$iWCg~VA%y9*z| z<9G_hT}a%8pJ`{qs%qhkB2w)?Kd(Lj`#WgsH8*wwp{hWvK7|8RSH^4Zaa}*!o zb9{-f@g06pl)0^th!Gf%37C#N6ks-rK^$|5V=i&bU4{*yy>p3S?tak!bLs!N#4wls zpG*JGy%*$kE-}r04R3;Y=2FLV-@}La1jIG>3lP`bZ}GjN6t_e?$W3ttcHn-{juN(q zC9xovCFHV%T$ZGQ_)8{ZCh|cZOUPr%9MFc6Qc&L|^i_$#g;<3(*au=Sxf9fF$$cR1 z5^`Dc1fIg{cncqa{FVHSU-5^cuxO<;0+CQ*0sUXv4+Ak6Ly?S8pl(a4+fwpfngRO0 zl>RPdL8ayJgLaqB$3iT|QY-^;m#)Mf5MwDhEYr{(~4?wQV ze#9^M9mf^!CKa3Kb5K|9LFg1F0BS9unuU>b7a1q+jpH!UZ|@{4dW zHiDSSw__JB!vT=*^5^g(UdF4SZp*2Y@}nT$@{jQuh?^2r&Tjz8mT0Al`YT;ZQw!&bOy0k5_@G2^h9qA!w8JVSd7O6lwcv2g19S*yONw& zUWE17h$}(-l{eyc+=YAb0EnsbDLjMc@glxflzHTJ-Z@A>FZ2QJnMdu;8-n2=o_U$@ zVjik+0f=SZGOWOA5X(GbnMeQ6+Y0hGkNnNM0pxEUeLj!;&7+Rz-Glq^ARfk}cwgZ@ zO|-{Iuq~QTyXN1Dmq9%&XbBsf@E{glkOcC#fEr#f5X8TL_!o@B1Y}?maxfLt!2;r5 zuommE0mQsu8+PJSP_GNF$L%2I1&`rrJc}1_1h3)^yoF= zgZ?-d#Jq&MS(1Zkn1Pwd1MOZyEi9P>Vqa2<^AP~?FWHQn@d|!Xl%@3V(xKpeOD_cd zvGhR@`_gAY?w7uVH$je8#1-hXh24M(@yLuGH zAQj^=9Wzma3d{#Nu3ijcs$PXPScmmE2=ZF}9=-y7Tm2(wPxYT*TnGb*N3=!^dZ8}{ zfLH{vh~XHC6cCF@LpsQxAb+A5{{^uLViQXta0Twe`--xRbuR0KaUk!@ zHsVG+3v#}UT3E*RV%a+&m&?fEGU8uG{L6@c*>Cs*#}#FH6EsIO+Q0%EdV@SJ9|dAx zPVCDkA_J3<4cf4rHY_LZ<<(e$RalD+*o3XvhP^n1r$C&`U%+7y?{eZ@PQ1(C1?^k@ z5kAE+MY)h1U)UJ*`Gp#Y_d?>mka#a7-V4pJ!T}dN7>FrYf~)W-Sk?+^Wknol%ZeiS zQHcd0-z%159q9iRq=r>=|(I%p)0y00X>igYGNhvtR$Y5J3;@iq%A9nW97BjkLy7kEAI!j zx{~#;Wc@2Y!x#7(a{cuCO8R}}@1W1=B4rivtRjw8#IcInUDX!t&=FlgJgbOj74fX< zg+AzqEL30z?#8={vYP%`-5H}W3(G;=t2clgt|o`8F9-QsP5xFN1aYrEgj;bt?gYzQ zeJ`HEvv>iAL5-|_4L>T%8tP$<5!Aw(rXZ(lT7x=RLvGg){~F?7lLWS5YepgksYt^l zuuWSt6*DjsOF(>Uh-b|`xDQW*^{;seNAL#d=QXr<&3pI;YzNl1f(fj9E%{taKG(*A zT&^XSwZyWvI}(r#asi-&`e*$E zcnB}zWf1dvVqH(H>&f%_Z}AI$$DjCHQ8pM5gTWx44aBj59Bv?o8|I)4=c59|vtb1` z;~MP8^&plF#IoUb(Dxhe1+i?P1~xo~H}MXR;(d_E4WHn1ki!k+aKm@_0Y53q#@6V8 zEX>EHpj{h3R+LSR(GEk9f>fk|de}4t`5=Frick#V-$eYI0;tAvti&3u!+Ma|IKj4P^B@rC=HVEL6r_T9 zH>ZR4ZKi%VPenP1adRbT^Je;eGyT4K35a|1GLZYtt3d3V_krAQCY~+S-#>}Ru5u9JGPDiac?Eh zTQiY^X~=~a#J`nwZ(RuD-ntazck3o>!8Tk9>UV1m_TUi6_tsbO2HwU|d;ntI`We0e zF>n12KZ4jVX@OQ~1LD2J3J2UE-b>n{1L%WGx}rPC{UwDUrb}+XVf?Bn+j#A^QJ`kF zt-x+j58HUnwwrMa?!{wx63^gyyo8tW3SI--v~8c_7#M5YzQy;7vb`l-Xp8pfh;E2S zB9br^$(RW0VLSD(eFn(uc4FQ>7bPIa+x^&x8}TR}2XSnF9dCmE-cJ6u6VGkrLGE@C&yLR&WoI+6yq&ynXC=0ReD1s(_u)Z24Dz}22*}}1YG5Zdu=8EK2imdo z8+?zSKs$E+f#Zs@i?;0Q1Y+MsE_d}n9}xSlff$5Tq+uG!<*s}X>;Gx#zQejK)VP5^ zzubGKi7OIE3J%=jEZa7158KPMy@%S)vdngh;y?_-G;!cUR3sI*5;zc)BuzyT#ew7f zocDdtb$!mcu5;vhzW4XOAG*#te>72_6ZJV!pA%o@L%v}K2_%w?jwjAz0jVq{jg`3B ziSka|O&;!cqP!CiVE&2jV4^#iDF4KgAedB}DC~IBYy6BJChZG?*npPw<5fN|FNGjmaq5?xnuPm>pih6a4)fcv5qWuA$zQ@V`Yui zbL>e@bDoPqFgZj8>LcUiZuF!N{TRr#T+dDDe6r3b-^H^GX9O=I?_{%2ev@~2k5R}! z`6J@c?_@nrsYp9+<5eawmyPItiteZEVK44{%5h35<1F$|kw2~mx{s?zLmJbB9>^W1 z>$qFcb)3v`_i!H%pzF97cpE*(jYiJ6&(U+-IKE*rNvvQMYgmhp<2JDsHy)RR&f|0* zcLFyZcN%%)%8_@fzNeafYGtY+|I}KvN54}?GM*H6av=z&H6w=mc^u#5G~G{o9^FmT z^R!Xud73+!_9z3ce-1d{t}-cwL^-e9tfFI9|?p{l>>Li$qd!FY&v`L%;Eb6mf_n z9ODG;CH_nh%&bLS>Lc&WM#wwU9n3WQ%y1%*e`Y(b<0-ztoz8TVGfxIV!WH!7UVN7c z?=zZ@(0_vN67-z#D|$|t!c@$VV1|T0na?8hpRgRalduQb6N>R&CipHBl)QOhS&NX~Z*&G*+Uw zB)uiclq6Hq1~#*m?dU5hmtC9=g5MiqufLoB_c0{k=YDrD$yI5MJ4o(AH+o?I$v1Kf zvM0-)d^fsGev)T+o)MTO`4zrF?qv6xJcF6Ig=AeO&*4w(Jz0mzI!xAKvW&@j*njfB z{4diHii2QwO*$};Te%AvX5WWdW)I_WIqv`2#s-&u0;v z*oxj}XCu>WnP%@HpM4Zk#6kWGf$Y1a~=S1uNNt{BsJ>;T##~9OXEt zImdY}1;N}9Es<^RP#)uHo@F?8IoB@dzK%R|KgC_nO(Yq6oI4LW=BBcgG~}3@!C$Or zA9gxdZ*%oFSEji#&6R0xDP^3YoD0bIXES1Wln=4DKeIR%1oLVW#m&e)?_M52m-BQu zZzM1C8gkFmRB14LsO!1AReA(v9Hor057>Ydef5i>W_q+vuZb2Ka z!|$dAFYqGnV8Ls+#|7?S!54hV*XVM=1l;F>X~Z*&B$8Rj-|R;A1+p(Vz`y*5J{Oc@ zhJ`hdbzyyEU8uu_ZegK2T^LSh`f(Tca6k6Ga2Stc{|ldGIPPHK%gDP>-i7im9EZ*q z%DYhS3-!M6XY{@>7Behdik=tBv*=0&<9Unx+@jfRKN7l#zEkB+)pzO}e8T7afL*0dLeA6~%;YzcSWG55 zPStU$eWtpPR9RDXoa)<2l{r=BRGCwCo@&O$axRv0v7C!_y|@LsUL1kki{)M%Nhi7> z`{Fxz2^}t8z)mg&!ID<^>)jH&STYK^mwbskSu&nqm_|Iakb8+vm(1Z$(pX6bf3Y5Y zFEPWCGGtzIAqcD}SXzlHR7dYin(zeAR#iTLi8 zdic`iq_YM&mddeIj-|WtJ8Y>8%Vbz)=gTTn4H=fz!S0vobeT?<$+XPAm+5R-cY4yB zzR0$0Ao^N%9XD_j?qJ!c$g|AvfMv&mV7ccl_jAkd$2Ys&Ei9kRRNTSxnf$>b+`)1) zEH}gQ73g#MCbqJJ9NhQvJ)8}KH2KqfcWJe#i+80p#yzCDhqMlKBnElY zJ9qLJBl!sV(|mJjU-1nSkU8yVCL(*9>}d-~WeLkjL*6v|Pt$waI`p0GeY(EW-9oxsNY{0`{OR(i-^Lw0f*I1E#17J* z=LO7>{v&dy%bgxaJhQN)^xu&`eL3zVeKWdF*LC_%cC(iP>@xj)5UgxKBbv~RmRv<^ z+M)B6I$zm^Zrp;LE9G2i?v;0Q9}n;_!;pQY>?@zb68MfO#)ui8f;$0?%j-nIM^rSa^>Cdgm zzgo|$pF+mf!x_ozyu~}b&nRM%ZS@v*Qp917BFE}eoaP*IWYpjaWXR}@Su=VdLxv0) z18}<;Za3pbbef^F44q}@EJL0QJI@%wi@eOMyn$>PvSs|l0_4dk4}vxJw#M_;__;Oi zWX&ux*ofR~wxi26I$ZNF|8WF&u%-lEW(HKicbHj?n$)HPz37MRnX+fzz)cLnJ!I-K zQ-_%^@d|p(lr!@^beK7Y@#r%1Pv)}-_nPTmGt;s2Od&GYv4PF}gS?rC(0QiLGj*Q% zzr1#zc?R=mUf@y?{MC#Y9_2%3l7%_fnq{q>ti73`+{*(zggaO}5?!x-jW>~ht^8|0 zU^L?}!`kookzepFt~JM6U9XjUt#_^6#CG((HkVzdM7dxq!H^ExxH`eQkx@@L7PC4W|He1lmX=tvj3atCr} zJ%PTno@E3t;%2g5<0E9xa>H4&X6ZM}&1A(9PXdYj&JtwIa?@FUFJ#G@^)LS+bC%3m zGG~=x#;o&P3W5!dXo8tHG^Zt35kXsYzd`m5vTx{&-ys`rN6rm8+~9UM*y{#8Z1i&* z2VoZ*-O0w!(BDS4vhf>qy>TitkbR@<8?oAb` zOf_l{hWwk_qvK88=t&=B+BBH!xrtkN9=&dwj4Yc{NJWpE`$m}#HV@%8+{NaR=xno2HgChu zwlu?^-SPtNcuOKW+T!=Z7Jp_-HaQeU<@B4)7GyU&v*QQj9b@IgxlMChI3p9f^GWSW{2CV zAkVfdXiZ-Rpr37WY?EV~eQndtwmXqy+kHI9BfQRAyo29Q+hp1%(>9s5ea2Y6;v2q2 zwrz9R#EBr--jH4l<0Gb#P9Ab^KZGu~7o)=+0Trl>+&lERqc*;u9W4kag0@`E^$g{H zWZoh3jwg7EXL+8tk$uM(=x>LNJAULB;+Rf6vqe_{d3`@4j5TnK_3UFKYd?*3wGt#FgM zX35oKZYQFVBllW-tGQ1jL#_rx4Y%py^f$Ox`xKkv(rGWkIl4&b_^Ph|ze~UVZI77zFwDksm=6ozZ=M zcl4dFn%r`p(yP{#3jx|2LA*d;U^n&fkoz`LgEc zlFxqr;a?5~L4j-qZO~`IV7$A)UJLHWECqTjc!H;qqu@>4L%~FRX9eD0VBZA^B;x%A zbC|~h?7u*s0yzrgD9~Af&I)$3mjVhofJ_DdafIR^*yj%RwdXco<_DJG=k{F)g8lAc zzs~o&f&JHUBR4aYhj|p)_shQjIfnBV@A3g-Fw6c=nU381eRKO4v6wX6!T!~(!M*O6 zf4~0rA4bOgC7h(3i$P$Nps)g!XoYNr_wg9+vG7%1M~=ex8O4Wu%y+~gL!k_XW-avH z6sEJ9OxCdhyDijdq0S0*R;aT=c?yqHikmDv!#Q+Scqs_}3DJUnxUGM@^PgF4#2g3e z(upD5gBv*D1`a&JGrWZS2j1Xq-s1yCGmi0?;lPjl!bHq*AOpD%$bDcF+xeSZcHs^W z$baB0IxNy*Q3a|}om$jE_M#5xwCFnQyU2G_i{vhvzz_U{>_rRM!I>cVH;n!~#TV%8U%U9X5V`+7#tBYxAqWmurW!So z`(S+<(uh{H;c6o3L>J6(P}c`#J}C3Sr*W?b-ORz4c!e>1jhqL6W+Ib`N2Y^`B=ZLw zaHEHGc}RbUWH=itWacoRg)C+% z>&f8=$2h?$+{oc_=8mN4g>VkzU+`oJZt2 zvILzS^}M5g?&uBJ#nF$^`_b{}@8}P>gQE%jh8d2U;ix+}I+x|7vzkoSk;OseKYES} zTnd6>?<$tPxEeJGqb-qiMBZXqi)Ag=b8$b0@BlCK8gHWGV!z*tNAnS%@&#YvUWz9m zYw_>QM&HHmpjhX{i!gh!+{NyucoiAQUR)Ri$K24dHr&Vvba2d`j%9N)2#(i7?&HmI zBggfByaV0nNgw1sK9Fk|%=HZAUd(d*VIIZr$>XDt`}ntb*YO{jNGx$oL(j)k(DU)X za4W|*vIRGDd>4DjXCIe>pu`QAxZ4srO0L6xOK#?N?&NOdDRIXouktxx;$}-^DfynC z_!U`7WGR`BJ1$v*{z_J`61y+aU&&fzE7`ziwz8e$L2#lD@|ACS9r5>uFg+@)@! zG>OG5!>yKXLXV|#mgb_%(me7h#=i8LhdqmQzmbjyk$A;WH);;f7yQi;a|*9b~p%5SEViToYu+d33%RV zKX>|E5S+1-Gd&o&d7dd z8TNE$BU{Kr-ZS!^k@t+g&lFQaX%L*fj5FHL4Tg$L#CqhbD6Be&79kW?$7D_Tp>m1 z`kegdh$7W+=F*sl=b2hc=yHUc!81p zLz?Cw%cP+!Q9H-c>ZNC@)n=*H9zqylbB2rvzfy@7O;qP zRjUb97Oq6>Yeh zNIDVCb=-hwRk6z|cOY*Sd8<6YLk#0FysOG*OvE0mtVH%IdZ^;PRVz`S4s@go-ROyX ztlE!(T+8*`#1QmU)sCvZz>B=X>%7Ige83n!#`~*|WgOoy9XYCQr!)vvtA}@2le^j| zlG(t2L8y9FWUKCetDC#}joiX*+{xYC$5TAZa7G|gb(yN0zq&iB{w+ylkWXCrE#&1SSD9M7%k zc{M$+rcP_>w5DCv^z${}Mz)$WSb<(@%2+d#b?Bky<{(t70-j&X^J`r}Q(EBpwOY{` z_f=~UgR!ex^45BS7xfj1&0&YWp^7mvb=))d^9NDpbe3b-Hj5 zda5&()f@~$b=_>;?u=v{c3amw>*jDAx$DYZ*SB5wJePt{y~}X#^{P^X+L)_eL!!`O zy{>p}J-e#c8=cm(uX+P;3-xqcPq+1Mj-ycu#`~%wR5mGLQLW@eeXLu$Kn9Yj7$EHPlx_JvG!*!+PkcVPmeO6>YehNIG&I z`e^tnUo(+On6Kd!7L$p)ZYX=h&1_=_Zla-ExcqY5_vKCTXD|0>F8|-Ze<=txYKP}G z^4vz|Xp}%A$;=@YeKpcoBe&SdEjIFuMtPX6adlb~jh!^^Mi1^}7?1N5evdRB&Imr| zTV|uT#wkIliJxmSh#}n0UEIU{Jj63R&kKy?WnRPXo7i&`-$xUBYjQdWg;m78hshRJ zi@MaOC3+4EN7rGUkvUA>Fn1Z&m;Ts!m>glZB1@PX3-it}?+o+KFt-r)4(~G>Hy8FP zKk*B?46~oGDNIAJVKZ@qVM%PIGzc|qg72WI`)c|j{!CMSH(kjp)?k-SeSb}Nq4TEu zIlw{w3qs8*=-YyQAifxB|D-yak=`+~#*+U(M~Sxp|sDf}J(jS#zB={~YgXJ{|Yfd>-zq z`F3(S9)wy{#lBj2M+?6zTG&Sm&uKB7w|JKi7{kYW##r>;;uj_{nW=bB3v;!Y#d^%s z(s$QVPc7wbISkKi=~*p5L>Dc;<{Kt35kK29iP_90g$3-y9$Fqn?v|yLp|6(bxfF!1 z?95Y)XCwa1mHxBvYScuh;W`bsyKwV{yZ>-|3csG4u%mF_Q20>v8h#%yAzQe+3YQ~1 z6&;4Bv62k_!o1<$8NP+>{Ee*PvWB1F6lW;sVi3A2ggswXh3eEozN>sASKYxAjO2ZO zUUU;i zzE*45jk|1BjNB2IQ3*E_VXqOojJTY}gwYJYKO=f_4THHA^F-)0;$EKQWnM$R2>Bx9 zi;yqkBgXR`KjOY3CZYR?sZ2+{2)#$-AX`L95NchI23$#7+7m@*WNF=pehfse*7q?S z?`{1l`fdF)?yt35ZtY#I_0n1|trO8p>kS+XLTzf&g1(rm&BMHlZ=}r_KISvVqVG25 zX=9!?p3&wH{$xIjSi&;&+vao-@?Y&kZ7WcjYIs-M+BC&G+lCWCSIpA(TCT?pw6*`X zva}t>V?2qT+rG-{yu~Mc&X>s6R>y7SYCD%qHnWR73W88O`)%jl?R45Ml3v`#LwIhx zH+Y-(7{!NpM?3Fm=N;|7=3Ble3A<}I2OYFaVIhmrLAw>$Yr70&Y3Kgh+3(dAX~)gH zh+SRn&t6>^gxX(*PTKe92K<@!x1x*o_wzVU@hrm`$xFPCd-L}$3bnWY_FwQNv#^`? zGPPey8mn1@Jnh%BlOm3CoKniT5QO~K?obDrI#i+x&4}Rvp1`|1*hL3--{Bo})?o}E zBS#0bbufPi@940fvs?^9k-o9W9`vR!=8YW0VC+BA{v(I*G|%w@x{7=S?~C-lNb^RT zGx96GVFKP4`4clp;5Xbuq`Qp#3-d;9U^6?|hu$L(aFA2D+ekSh&j+C>Hyc%xI@CkP zD04^26(v`c9Yondl)H>_KT)zpxyvXw66HqxeTza-zY`aVIJjibldf5blY_} zBN&SwyV`SC-$=LmcwRR@*Uenr^xjR!-E`b-HJPl%J#^be9{HG~n>o6fqgyHF=w^;? z=IC}Q2z3vk_wF6gb9dRh%ig^YeHp+Y?m*Ao{eI~F6we}O_ZNAE*Z7DTB#=llbC|~h zblzR(-Q7ZWx6obA?s9f7ppXNYwfkw#aUlr#pUOi$Do_a>^thZWXi7))+e5!S268QO z_PC3?xer-;JcDj~yujP&ug56H;4XShXD0gY;Wm2w!Jo`W$32#?oOH6uMfM(h*vme2 z+~Z%|N{=I$qsQ?e)Uy_m$kp>z{LbvTi~{uEOZUCZ&}%R^a5F==hheynUS{Z}?_O@B z*9d&uz20XuI`8!b&*-tg9{bDC|2O6^j|HT%1fBNRY5xsuMvwjXQa~ZT;r{>2 zbc!?RWq?crY9iMFxdybLExH`w9tL!#D}5QjHC%_A7;rN~d4V?>!`J9+fX)W!Y=FE2 zbT;63W+V52KS@FM0o##lU;`rQjdu<7u7Tb)(7OiSz(d&CK>Hdvf|szjfunJ61MOBizJ~#T#^MMNj7FT6tT>)f?aH& zs3a66h!lG-*b6F%y^Fo~uHU&cI|(6?H@<)M|9$TdaW~1#-2Oc0o^$ST9oS+E){M>jr9=RxUUK8FF-WJ{w-WNU< zz7)P9KB5pmQAvOViAHo{5R)_~El3;EmUJdbqzmazjv~ECUy??KlB3BmGMbDbW63y@ zO(u~kWGb0U=8;0OfGi|sBuZjrIRSDeSxL?(tH^4ymQ<5X#KM+3@KN3F{KM_9_KNCL}zZQQIe`W$B z%+FL7V9i)_)|>TV$*eC+VX3SiOJn`n0G7^%uu*I*8^?0kv1|&P$!4(_i?d3$m@Q#T z*)nz_Th2~mCo^Cx*h+Q|yNF%P*03wtRct-m!0u)DvHRHr>_N7RJ;WYnkFZDCW9)JE zG<%-yV=u55+3V~L_CEW7eaJpxpR(`S59~+w6Z@I{!hV&QBuO2lBcvmxj#4M7vy>!t zk-AFVr0!BLsh^Z4^_K=nL#3moENQefMj9)PlX9eErA1PSR4SE8QK?)yL8_2qQe3K( z7E32eAe|pt9{q_ zuJx_+Rr#uYn|xb+H~Mb&-Qv5)cdze0-~GNPd{6qG@;&X_=X=5TrSB`>*S>Fj-}=7u zeee6h_oMG8-_O3^6-fywL8ZCULTRaVR5~f0l_aH?(p$+=Mk`~KvC23lTRBD%l~u|m%B9L>%H_%x%4%hea;0*OvQgQj zT&HYRZd7hnZc}bo9#nQI4=E2TPb<5X50#IUkCji9PnFM<&y_EfFO{#9ua)nW-<03| z%rE&hzwS5uZTxNh?fmWi9sFJW-TcG-Bm5)%qx>2EOn;Vtw113$tbd$;fPf0=)Uf2IE%|GED2{O9{G@L%Y^$bYeamH%@8)&6Vz*ZMd5H~DY$Z}V^W z-|4^0f4Bc0|HJ-A{BQZ+_P^tQ*T3KYp8tLS2mTNJANfD_fA0Uz|GocL|8J_K%BoMb z)R5XtZLYRZJE%vfgVe$55Ot_}v^q>3u8vSgs-x5lb&NVuoup1y^VO;9T(v+wPCZ^- zq?V|qYMHuNU7}v7UZh^Eu2L^iFI6v7FITTnSF3B(tJMu^jk-m>NxfOUMZHzMSG`Yt zQGH2$S$#!)Reeo;U427+Q+-Q)TYXRcRQ*i-M*UX(RsBudJqz)gXh1GfZj4cr#EJ#a_h&cMBahXaoU zo(eo2*cW&q@M7Slz*~X01HS}*4g41PJ@7|R2$CQTia{2Xf^tv|TES4Tb+ApaQ?PR| zDcC2N9PArR2@VPl4(0~)g89Lz!D+$i!5P7s!CArC!8yTjaAB}Gczm!tctUVl@WkNq z;7P%=f@cR;1Xl*n39brW65JZRF}N+bJ-8#dGk8<*=HM;CTZ6X+?+QK`d@T5Q@VVfg z;NIZt!8d|$2Hy&P82m^RHKs|LtobxW^J}UW(1Mz#Sz2rD2<=F%yVgVNsikTCwEa%dV9U6-bYW?`{`-=Kz)#Yv_4Fq ztmo**>QnSwJx|Zqr|Knosa~c>^>Y0Ly+V)aalKMMQ$I^TTVJ8C)X&k+)vwT3>udBY z^{e!2^>z9t{W^V%euKV4zeB%Mze|5qe@uT|e?os!e@cH^->tu*zpcNgf24n=f3AO_ ze`AOSGbBSce1>B94b^CAv@%*7ZH%_Y5yp{5XCukzVx$6EN8^;?JM$CvCmBwOYiLul;#W>YC+gM?&G}ap% zjE%-7<2vJdW3y3XY%y*ywi-7YHygJYcNz~F4;zmdj~b5|j~jc9=Z$^F3&xAaOU7%) zr^aW-=f)Stm&RAd*Ty%-FUGINZ>D6nFgux@&7NjoGsWz04l)Ou!^}};hB?MO#++nM zHgnBs=5({%Ji)9mV`ki}G#8so%%$cs^F;F$^Gx$x^E~rX^D^^l^BVJ7bDg=t+-lxz z-f7-z-e>MIA2lB{pE92|Uoc-c_nRM@ADN$-pPOHs-wbs>Em34=8r*)Tgw{?$ouXUewzx9CiptZ|-)Y@%5Z|$>Qx8AVc zw?42wv_7@Iw0;T+AzvsEGD6KltwOCs?L+-T148McfuTX6!J#3cp`oKg!$QMD8KLaZ z&< zg=Rv_X_Io37e$uEX5!C;CO)-GVM#a^s}?LFWK&zL7Mcq!Y}98m?0p4h^X#nA)1noN zV&&n&NLIA4vNTc_FEG1gq-3O}jvhR?PiESn!F~E?WM=glH99r5PfEY^eq;Kl^iLa{ zG0-cyt`Fx6sGdZ3CDL%nz(BumQl*feivS*zSV0%czKi7nMhg%i^(7h4JFWXg3rUm~FEn z^TU-T@zF)a@o0r(O~l4TEAppIh|P8$i{utAjFd(&1NYAR*hrR60N%rJ+WGO;)|+)b7dj-kcEiz|*Vn-VFHR$z}9Ugu0JE-Q*IiD50=-$qqb zgv%CSX=U-O$h^u0*=6&i_;_xl1nrytSF4|rg+^nQOFaLHwlx3$wH2BtT08$wOiV4>~?ks z`$)UfCLv##Dohim3p0e7c9PxI?q+wld)PhgE_;PJ$dkE3fpDA<=1iG~13e3WRzyk= z7>gseV7Ing6_^(;?X#5kYajkqfju~+Z+;9x(zkG7-$Yl$`eug9>wQJbID)1YN0ubV zmK4vAkBG&?74ZqhWszZNgSt6?WJXJd4eAytEAp=5ul!oS{@r4Qyd39xdg71C^XKD$ z3>%o7vPUQqBEo!O!AzISW#Q7ubobXtEPqliqNo~gFPuIpcd}!CAK20or&VnW%(eeb ztFWYsFt(Ndm>4b#FNjn$XkO}ol)Cl}u5BN`>@_g;zy_xB{&3oxXkx$Qlmlygf>Y!6 z|74BRutI!zc<&!jW4xTGacaMMHBR*!kf?G0`ZZqc)cA-3^Xh-Bj}OGqfi-sUE+N{PPwi!-PJ9tLAgyv|J>Sc?a|+$bCdA6(P) z`ZcwknszQQFaO(XnwEyltF3B!ZB_a8L|xMm+jZPbOZBMgbt|u{I{}|AoFOEyTW5E1 z>3X)XqQGo6DR*Kec2h|ts~9JgXj!;o*?Qq@VWqHe_N3gpYhE7b3g@|bco!}dE~zfi%_l>&o4#+JS*nR z2K*{cz-Cb-9xg7)M9IsAXQZOQY+YUvnIEaBh!nZ=#sr+#rxh2)7tV{WC1J$JX7*v7y?2ix4iK16?G0Jo2{vJokdPI11ojuSU;Kzk0a10Z} zn;VI*7akX$6c(a9#y{N0pLQNEFgs*tWM?L(*2#%n>#ChJvgCe&|L67O9{kNgl6T<+ z;UzwTFWSSZgqQ8%yo-#;((-UwQKZP}qO6!(;OoL0>+BJ3{hk!ws^%4%>o%2l1#gy@ z=exrG`h|QTe8da+(9Wz9KDI~KhshTXOtK2heoci5;sa;Ta?~8W62BIH<$B^b!neYA z!uP@t!jHmF!q37l_85DtJscb1EW>i=&mX#GLLvn#r~0 z0<)K!OTAl(oDS2xNo)@lI5J~8LiOkdfJadO!dEo`IMAlr*C~&^aE`Oft zKh?$4Mm^nYLbT9Pg|VToPFTO{UPHr$i*UyGs$zEg^9mf;$i#RPi-cT+A))J~tgEf} zXiL({d(?CvZTL((a)gjvP1=(VcAlMIO^zfT?Wy*34uke?nq0oFfa?u79hP9Xx!k}e z9_ilpy4LO4tm0UCNqE^r1gXOv(v@^uXHRpuLwb;&s33CHepxl?AuP-tRcmOG-lUIP zS2tM-=~hKj?OFD;=ShDufTRls8At|^!DPq`SB&vxl6Y|`8X)1)@>xX*BMX%_d#*jl zo^2QYSFB1J_0W-JJ7saK*N4N&DBeFK$Vj`uKCYT%kW4#l&$GKsbsId@9V3)|2lsFu z$CumiW5{?ESMCD|oF9yt^<)B>C@lO>kKyE(H8tiO*HJu!{U zaf@0Xr6C~H*K0V*e%@8olQgM3DhP9L3>nLe;jN_uJ*H-iWC8I{(5 zP@mMSl$0?Uqf+|y&lu%ZxR@+L`ALqqOR7kTUCMFU=1+O&VyB#A=LEaVp0*EBGl0ZN zI;kX!g#xmagvp6GRIZfILlYCVkmB&cSG~{`Le}tZz0zLXShvn6Rc_B6Z!h6Jw;rG0fPIHOXCBa$ zso|2!h)L`UZ*;FCPYTJ~%p`I>*-UE47IFjGN^T_E$ab=W>?Ajlo5?NYR&pD;o!mk0 zBzKX!$vxy=av!;$JU|{KyU0W2Ve$xhlsrZrCr{YR?B({!_Ng}5r`u=RXWJ|7bM5o( z3+;>TOYF<+E9^D)Rrb~PwRV-g-ri_mXK%K**jw#w_73|d`xg5)`wsgq`yO7Mr^wS} zH+hCUOP(Wp$X@b1*+*U=FOrwY%j6aEDtV2(PTnAIlDEj)+xOY8+Ft@ufE)>=Kag=i<^nkt$Z8-r19=?CyFdk?ErIq1IuhtipcOz@ z0$mUE9-w=GehQ)tVkZ#OK^za_JP;RycpiwGKztCyS3vx_K6SrwD2Esy(Nv8hVTSe{ zT~QIOs57(=14Q~z#P)#47vJU&tG%kShREUJEy=L)Aalj zo|3w3xn@z)u&y1Qyw58zGn$-tqB~J}k4JEr8t>GuPQDiun4_AUZ#`f-tZG=@qnvCn zDloH~oUL13G=k9Kx**1tGBiDk3#UXAp0NXay04S{s=w0HiR{r4>CUc5-Nt*QzmxZ+ z|D`a<&;F0XfDU#_xV*qDXmaP&$rexCpqf=4sfaI|Tv3dGbky=gNgz7hDRgy#Ij+ft zdISBRT$$D*pIr1-Nor`0giJqoL5j4@s z^_ssTo*ZGqzvk7tuMWiI6eq`Z1?I57SkJK)#YK%beVUVH^?xzhjj6y+Z9LQ2PNo|Q z%;8P0rz=Leh3hy}8q(cWs5cduSxwH<9sUExsPWpmD)#jS=GZ3Z?7`L%tqFfgvcI{&9MEKVpgB>y9C2VjtaP%v z^{=Gx}JO7GcaO;ZV7R6!XnXYy+z5Bl) zaiq2b8qfJ^C+B&a+%w{_jW zeA&=-ek9krV$fUE@aDqkl8MD-#iil$0<%-2w|Q2X+#`$z0(z7heI~nXadE5|o&D%@ zYM2W;o1&HRI!+XrNey#xmKq#CR&vx?|7$EKcR|8^sstAp|Ex))D&q^I6uE#|)2 zG0~DD)X*C1ttA!Va=sYpx|QjJXi(E#_LJN_h6Xl$gml|LAEl4+kNuSWwDXx}wlO6o zVK38X=^mkF6@AWrwuis z<9puTH;cE^aWq6?b3GTD>)x>FU5=kYSV`Zh<|`{n_kHQO({W3Lm+$-ZBcAUE^h5h) z`;`Oo&7Y5*aBz-ZsxRp;9H3v(ujx1RTlyXSp8i08q(9N0?bqzr?KkWwm2)r-;Jc;-GATbjmCk_^eh(pDr#bM%bafCQh93^JhU)f*V-`L;U z-`U^WKiEIoKiNOmh*=59jT5uQW9U|K0z&SW1mykz!T?uJb_0gICP6^O_2kNundyYrc5t~Nv7@GcOGIqnt$5vs)FfsiI5uS`6_MP9kR2MFajirG_v zhz+q9vXe$C3Hf)iSMG`8$u0~}vR?+`J4AXf#p&V&9EfL#XNqTuXNxPumEt+#x#D@^ z`9SfwTnD4oG_r$_|G? z`450{BM0RsAk95gZsw@0p^xzokQVmS&KF9<9B-_5h&ORm?gY}RNW>n5V|cJVGm zm3Rkz2}m1zPqlbAcOSP!nb<%gYU7edc1)mhu2=3u;-fAqAK|Dxf}`?oj_S4z(t9bM z7I7iGM%*nvBR(rWC+-pViqDJt#23UDfpi4Y2}oxkNkF;)LC|#r(j7<-AU$iumlLRb zJux@G%~5%jhsr+g>4rCyZK|lrr83JS|kfVVN12VjZb#j5oy1GDQ zJvk6Zct9NG0ddS>ApZS8WCIb1xVspa$X!c@4MiZLG3oq6718}S%^T$mmW3c>nLskC z*l557^(L7yESpVmK{%d+FpJ}95(nYvMnE{yD=e2ybwQZVK{)mhiM$lE*#Zv2IczQ~ zV8^jAo5u=S5sR?-K(c`x17tjq2|y+SnFM4qkQ^Y#0+~|77A8Pgk^o^j2Vt%U!f74| zXCDUPKLEm0I0$iVHO~X#X$V4gIy-}ZfaC)~sPJ!;hb_#`-e{l8&gWn}56JW?b^+kN zh^B#Y6}!v@w@tb4#qi$NaUs1$aV^62gy90l_ zi(~S550gV@l?>H`>{~J6) z>%_!;&reh_6PKuVo&+}eUK`V2Nlcrba1p@*l6A*jl$*sFlMUz&#E zH`zM~NcI-jH7iipWbd;5Kw?1R4Pl(T+Hq5{kDMJ{Nh7YB}OBboKsv@hA)foJ%f=Y-k!7CySeDj;I7V$ zXk}Ruy4*tb6VHrBOLLM&c#f%6UR{2Z1ea%jaGsq~&Gxf*u}#>r2F*$&kz~n2sW15? zMe<9k6p(_FCh3wPnLunH0OT|vrvpJzawd?ofSe6v1(20BQpn|))KY3CwWeF8wwz(- zcnrG`$fZCosW);)>LVrNCj|%!_VbV^qC5Eq$R-=@a{NL-kM{s_x^yN7GfJoHt0a_8o0|ycb0k!M zaA`m~kAt}eH4^DUuEuO>h-H5~X=J~8GGDq(T7xoQx?H+KS`FkzAlra!{}*Jwnd-HB zy|l@Z^wLJorJD{>e=o%i(rsMOZx!o2g-whyaD7*AaDH( z^4TjtLmutWLeAo}cn4`A_vN&BtwE)|6yxP-NDFy_JW-w`PnL7!W2G5#uAC?5%a{&r zllOqU599+N9|HNvp~c5QJ^}J6kk7Wr(-X9qEzgnX%Bc3pVNQ$BJz9JNp)<-{ll@`Xo?7$-&?iLqE-0_01F7+(ST8i~=}Jj(3tjs3~;shk?80Qt5` zMw{!qCQ{=J`D{*&GdVTB=hRrisqsT&)ClxQ8ri;{4kKSEU&1+Zk$kbd3dqkuegX39 zzrYc*qu1`M<#jGguH`KGgR^7>XUPwL%92eodWCA_>*VX@&2o*rMZQ7aD&Ht?leYsU zK=EKQ5hw#H0hNLJfGR-!K-C&K;SiE<6)%-hpONoGdeA_E9#rSch!j^9fi^$Pjemd} zk8y514m9X-<0;OK#qw_c0rC}4&HYAh)JpN^>jB#0Q0Y7fe6cAdfl>$iE+uJ_`Zq3jyt%0Hm)40unc%?c^WafcB#E@sw&`d*2ZVMIR1emnt6) zVArNW(U;^yt*6@81w}ONh9cV69Yr+l-k6A{?<9>pBH=R868Q4<^|>2KId08Iwk7ibF5RG|HUrUC5_ zbO6xw8ed}bk8hl7=J{~*572=gA_phTJbYo`@SaG1P|)AH<;=%7|M+GB9pvGW@BZNn ziDD~hFQO6moN+s^7LXC&0uIReK!;TM76KjGL_n7KqK<6zm2ug4GzZiP98kj=0LmgK zc$ix3TjosEzNH+6BMy|l=y@CRA`e8u+ z0|IhA2jm8z6Ffj($7k%@eVh3QbtRw^-EW++yL!XE&9{T261B0(Rlc1-bDD_CTYY!9 zsJxw{@>mYAyErPRG(@EsPI850?Ex3Q2Ye5sChL39x6Ahs(0rhna@xP3$$G8c?R(Cp z$FrOsGY(OEFU5<#cR4*?^1bYP#rLZ3HQ(z?_-A^pK*FbJbElZCkQQZ*-?I&9sdA3e&Ou+73h4A9e*Um zH5b{AxaK0;lh%r?D98&14U&ab3K}HEP2+{4DJCLI(Yg9?JgN_h#np#Jjj0dB+kdCD zQraOMl-5cc1vSr7pk+X#{~jHbE=qTY4oWvphZB$vip9yir~#Q1bWr*zgHUHtl9j$n ziju1IQ___F$^a!@83;566gBrspo@Vn0lF0EGN2~{T@Li58fCD{1Z9{qTp2;PDx)|L zPWE_U<3ZIx&*GEB*@tQH_tQX`j5JVkfS!_|fs%_fQ1avg{sDR_`kUNudzwW?d&50T znZwzDG7hQ~lyRpukqz^dh%-wlMSPYx9qlt^fn(R5(EwRicDtmJo!yDTBI#bqWs0-$ z`2(e#LvzKUa`2KYQBFrutt?fRDJLq+m6Mc{r5VbpimiZh8c^&q;Mi|VUgQC)QyqyGR#ui_Y83-mG% zqt|kbu2ZV`2k7NMuW-Lnkzr;lZ``j}YB)$y)mc-eYyo;@6G6I7*~vxrb}q87;v)Md zF0$7)BC^fryt3|4?%@*qPUSA;ZlKoyMG&p~7bW&1%HyuYevH$inoI1PxWr!DfLC6P zpHW`rtaw&=PT8aERi0P&AuC=~UQ%9GUIB_~<3^yHfL;gmdZ3#fR@4B+ZoC2L)-B3w z30Ay?ta!&^#e19;H+rnt!C7&u%Zj@Wv*I6M1#X$HQoaSc&11z6oE1N^XZZ){cE01v z`9@Z#-l+fKC&&pup4_st%1?pb)HF``WxwAM+_%VonSHP)>`-JX$=7X0J<%XAhI&?aPA(>Ns%%uiMMrvrVuiIkY_FK{U_ms4Ul2UVC;;+ck&@Ok|6&-df|UijXr ze<4TWbB9ReRWIsaj&j;x?mxj_;g9*_{z_?ve~Evof0-Y3=)FLn2a5i6l(yJ3Tt>o) z2XjUd{wmPdw)js7UST`@w{S_l(|?oyW}qJc{SfF!|DO8dzt{hOON;wCEk5D2*uuH=UIQ+9 zHGUM=e2^B8`5*T`;eXQql>cd@#WVhA{m=Q4jh_Mi9OxH7zXXaaQ(rr@_y#CWm)`;X zevAM41T9`dTD;=W;&o1oA3R$8%xUq5Lkp1|ro}%%i%&Q$J_Y)tM+4AmF^SNy{j z7y6U?jkM_DjrtG%pExOg1o}&r|7W1THjxy+tEl!=`~Tpi_zg*+QcjBB8zTkzHBp$V zs6ix!>Q_}Y0HOdQ0g*bSPz_go5ygK)eNkJgZ5(!}t&ts~gzQi$XUFe<#t!vJwHLBO z?WlHAJF7`*7qzQ2L+!5iP^F$k`!kAnG6*Aevj$-Yz@T6nT!? zPeqMJ9f0f*tpq#77JM&?*cQYi&@qEA%^zM)`rDV2)J)`ungwDg!4GvT@sVAz- z)sxhd)l<|{Ra*u1G!Q$3m;_=M5E1O%KI!wGe>HOAJkE*U z9w(APOv8Dc6QciNLj3&`hI%C@#8n{n@d$AZ5<*H**7?`)cZSh-cR8w3{H0-Us5h$D zaW3G2qkXH?>p@IuA{TB@w;`z1tvFlYF&#KtY{%I`?APdQvAcH3-z)Vt^-dR(cW_7! z;E>$TnbfZVle`r7t9X=bjrxH4pt?(aNPSp+M153!OnqE^0>ptJ4gzs7h(kae3gXcq z4g+yGh$BE8S))Fc5Y*2~tsF7^Jcr~c50Y8@1s+#MXCH>-KLE*hI3(W%Jd4GBdsuy+ z%jggMYxoC5TpoYR{YDv`;f?m^>X#gqUw}BeO8pANF-=6}cj}L>g#Lj`=&|VeR&mQ; z9K>-AfMxk^^Gf?8Kpiv&2uI^Fhf0-55>Nuo5RL(VKn(-}!GIRf14h6MSb-3T6F{5@ z;v^6!gO~#%PBT+L#AzlE#Qd5-a~F<*)`2!$rw+8|aGdJFaVCfbu10;_VKn~zXbc>M zXbkiMaasb6fn-FZcw8U_KmF|AL7dLNW*`;~bCx&Q0|SE*jRBM~v#J6pV`euEje+5T zQHZO+2reV%po|P;a2YwbF&RmZOB&g{-pJY0~uUK&TRl?ubxu^ zb2un-19^e`z|_FB!1Tb3z|6p`z-$l;K}7W<0^)oS7XTinBNhW5rXwx_v7{z2Hv!6d z?xv`~d=AP|50ufwrYJFf7?b}1CgU8Fl_274zs@VTfu)E^lnu^56bALrR|uRM0FFi* z#PX^DDr8N388~oOU?oT6*&L1hW#GU$9F2G)QUg3$(r+H5E(%=YipEtOgq0kG=Wr1I z>6^BJHGvHrgjWWx3akxW9k?cNZD3uXDo`C*58@IKmx8zq#1lbW4&q55o($qCAf5`M zT@%=t0O9694IXi_HL#U~5Ihi`0pd!2;0fxE=NyLNKLEqKI1KLw@iY&H_aO`eDS-$0 z2L&UDr@P-M9EW=Ncr<`(YE^;9Ks>W5fU4M8O%#p01I}immjci7iTZ4gtGygoD;k1P zR+C1i))$R02VQeg`6@@{xra#Y)$^Ue=Vlq3?hHlv9DC6)%1w2flk@FD#;~5HvI)K4zY-+H-!=GRp=g-DNRN6~1Bsd276C4^m zIyfviJUAjalHCx@2xbPef}=sa4n#B^H-lIM;ua9`W%CG#TS2@L#BCsM-x3__@+UYx zI3YMOIEiiz=5YSt`{?{1h_`@vCx~}}i0|8ic>n(%gZ}@2#Wsjrt*e3sAnx>NF%M}G zEDRR$4~RDbo?_>GBP}9k2XEvT1xq({flHF}IY}NkR0F&u zmj>5ymRuIRJa|QLb#P7aN@+%LZSd;gHNk5^+y&x8AU+J@BOpEsBKl+S;^QFVwD}~6 zPi+ZSC0MeN?g(DTXPICPXUWqZOYppA5TAEvnZsu`duLStjYr}JZ|5Aj1H|1PNA7l} z4rQI#imyZVME@DSp@1GV&wm!&6?~Y}0|)lmsvr*Rb4{ej6TznuUBM^0Hogb7@!)Q* zjiV0KkR8g09=@Ir;z^F{gZqLn1YZPkABc#D7ykuPc;&wx-0xE2T~3LY`Mj~4Pj-77 zRN71NaqxT2h);r_20sga9{eKsB{Jgc;5WfN4WX!?VWUZxv`dGD0&zd_Tbm4Nr~6 zgBj%A{Dbf6IIlGF?^;S|ZM1es39T)NA69AYLHwv`l+ZeANzRO+b>=h1$A~ViD@PZe zG=1<{(d@wlz_g>ZWTb@FOY5!m0TFfi&q4g+-=l;!P#fYW2vo8YoR%W=Il zS{~;DzUuDkP=6%6G8caH3m!PhY5ADYUH7F48X6R%w@L zmui=3mupvOtF<-2RA2#ML0}p%9hd>k1ZDvX0c%#HU8SuRx}wxxtF6c*1+1}rF}Cnt~ADd&CSAqn$6wcv+(G+!blDVo1Y)8D4kgx8(mVo zfX9o#@P`LJksX^>j4|mWJPxf{o6XcPMw2OvAC#LHND(b8_sLNh!UQAF6vitnFyF*Q zx8yP5(&Cb31!ju_3vXEshsC zP3q%b95SFVy{LcxwDiFP=cmq(3>ui8Iw-9uy(pYAAf>1%C8cQ4zyYc01LmjZhbv0c z`%f*-;rY&Vf25}LPW49VHtlX9d4qPlc87MSb{DXAz}f@r0PKhj+CAF6+I`ynfG6#; zj=(wr>&)#-9zSvvhS*+Q9A7piQjQU$@`~fUXInS8F%=_8W6-e1ZcQ%dLAtTqQyQeo zjFy&{MELF8%Fz1Ze&3zhV6>wXH*uK6irP{8c-Rrd{wWqYF zfh7U!39L7;{$4Ae)t=|_{yA-rwij3zU$QE_3)(%vx&!NhjpS|X)}S)=%Fd}M zj$*JO9N1omelP>$`Q}E-!xdqC8@9mgdFZFzGMqq|Nh4j0MmV;kSNS(Jd?Btu<=MB| zd)oVG_X9f$STDOvq0q4_6*@YhMzxR08IvvXr+MW(>UK)X^Mr~_l6~s*E*MvZ56O%! zJpN2E{*~7y9xb2VB|7hTL~<-7kh3rz7ltv#=)OEUxBJj@ia)O+8jagzo7pB1Y}UMe zhweR+2Mr$5dFY6-NOWb30ODYL`MD1`8v2kBZ}k zd6n@<%%*m0Tbz+wSy&i}U}){VEn4cW+O*^0Q5Qv)#b)BqM1`ta+pMasE!oX59_kp3 z`M2+gBRh7|J12GN+6}Md7M~cwlID*o!9dPD(0jy<-il!>C*^Xhh`uYukhG9MGheA)8?S3U5o_ zw07%LQv2C{TeSo0`wvLBgSKY3u$wu7>Un{@;^R}Di^*qoO=GP7Ovs0`b%&FZYtwq=KSsqHX#FXs{*=x9Y< zuy<^5?cKa(<&nBOreMl@CQO_(d8RvtW!Rz9-CrY4Zx^E+h?XHmt0(2)Fidtpgej)v z&aDk;JBr74UOy!-$BXlf@N(PAJKbptrkFO}2@A`+pswGmXXG?;6`!6pJAsDkSvmOU z+ya!p2jHv7MPBuBn7onef+AQ#TcL~4Q|KoQ6taa0!m&cG5EUwglY}#c^Mp0RRl?Q6 zwZdj$yKuX(OL$m#RNx`!pBA1M-W1*!z7T%JSm{AxVC3y)qy&Ct(q15pYv?Q$SQ`HY z->gT$*!j>BjKk(XK7^6U@S*O9&WS&i8(+rflmGB!9tQe$p4_7o!O*ELa`C~#e`CPz zo*y3B?S?#H{hWYBx}?hp7@l{vE(r_g=0%Ib%Q}YR9s8vYNge1u1ms?5tLSjCa?+9b7r@NVr_$y)e60yq;8F*D6>SjFZ?+cmfgh$hd z>*%MxP9LF<)JN$VdZwPGj|M!8o@D{UU&a6%3v3*)?CbQg`Z&5(KSm$HHB?~7fV38M zQVB;alWV7(D#H=r)1noNV&&n&NM3keBAjALq@o}_Bd!17erW^xjOmw^*{6TMf&Kc7 z8jzLJCo^qO`k>6rL1X%*_Mgj>7nc=9mpCy9U7f_efSF~4E2f2GQ{CWa(~9E@b5ZXu z;l&h~e8Hu*U^hjb}mI*2sY{Mpg4j7^zKAzourJ+G^xQqb2jg71>^? zt~PgIjcRWtgpNK<4-3hg_38QyeWpH3pRLc)=jsLealpm{n*eMgut~rs1Iqz+EU+oS zayJWI^+LT!kLdID1v;LZuODygz>vb#z}5rX0_+B0H*y1D2k&JQ>&H)mo)lhOyuj76 z_@&xEMg%>kJenODgJAL67v zF;W_>ST+`ewJof>>eZpP1a1zw9!8$8*BguVlTifgOZ27sGW|q-xqcF`d|+s6O#?O^ z*bHDZH|eM7r|Q^Z{WSe_V6%WN0CpmZzdvFT6Uf--^WYo48@{jW^hb!M`Ayq_TUbtak^MOSsTU4#D znxis315L@&#If@xD$Maws2j&(ks>~8&2^8^&WXkl$DMPX|=qn_BevAC{X0v*>J9se4e{ zZ*s|Ff4JJI{J@9QpR0TJsy^zd+GA0wd-q8i8Qt#aNl7DDtZ;r==jelYFoZBn7%$`r zGjLK|D3k~%2up<%g;Rx9LX}V>+#u`_ZWitko)TUa_6r{gp9$Xx-w8hxjkG6SNq5p4 zwZJqohUAmEWC1yzl#<0{8R~)Ox=LBCuGTl9q$Pfk^IYv?tBzr6yNzC2Ug8St$(3;) zh>>@kYlLw`C1AZ?-==RDP?Rc(mE6;~P)a`c3-Hz!m~4uGVkG^=Wo|Vs*OB36+tG zWjSaWm!iX?B33s$&T%=N6OQBcGB3Nk^#`~lxktZOzfZp(*dkyhz)FFYZO|Xocj*u5 z4+Dz=LvPs$z$&;TX_;Ug)-SgZBYtNtj7DQzinr&GA03V@%Zp~0A(4<+$MV`_;`P`p zr()c(!5tPM=dWCxBrtq)p%W_H6wsf8LL(y_=b>xMit-{$<4*d#`S{lh`ZM~o`g8go z41H+m&+GfhQlUV9Nq>1pxU3L^gJNxFIGN@}DoSUR@=SQ7?U~%BDst*M)4A+1Ys0&beyo3T zP#3t($1s)ciG{-(@454qlO5_T zlgmn$O{pv^!;a1V1 zLpRWkimJmIz|I7A)&|2eLfEk8z|ID?f;VaNXCj+b+< zzJ7t?%$dC$6Uk^NJX(c8pXS*EXQZVKn%^%yG9bmh)6q4akk-qWr}XZaa&qsE%li#T zOCHd>*cyJ&;a3w1Lvl7U_9sAZ^k zt>WF)xp8&JQHzpYod=`D9O@yb=>=xjLp_7!txUL)|3u4k3v~6kL#tqi)&jk*7-(b( z$u}5-jKRhbW2kYoG0Yflj4(zTql^p#EuTw)T?XuOU{?TJ4GewuR|2~V*jiv{>s$lu z+8d0~#u(vc0mJ<{pRRc5I{qf$V!}FLRrYZFx7+#P39=73zYe#%G(bAXV*_9yi@4^V z87(OZm!k^gOh+!~Jis~5bl2a6PjhCCuBeDsxC$VGaC8}$W<~C;+KlR_;x-QQ4I$q! zS$keDWT3}+@Y*IWAhEd*0;dqXR$H($eg`)TOXUHq8v*u! z^pj6^;eVV9|D=)SBOQ&w8^9uCJ~|(a2(S%R2HFZ6Ia)ha;yj0ea*(nWk)lkT-xi?N zQX2?6JIjqSZ7kAuZ!}7bQlrd>8aQ)q0(Kp+>w#?sR)e030yEi#Yko<*0w?+?y5o~; zZ6+_@CaM8F(35E(ETKlLM$-69G5iQBj zZlqfEp1{!cDBNmObJM9|L3|);^+KTY6FbZfNcY|y_zq;BK~(f;i#afxxMfqdNw>?UBSc-;c*R$#XQyB*ja!0rTg z7qGjx7+27p#v0>F<0@mVakX)cajmh=Kw{s6r#G<2fjt2jX^K4s(lU_F1nEkUkn&IP zdRwk^NBa!5jyP|1fq7I$1XtHER(mNfbQH&;WzH;@j}T0Bj%y0LbDxoz5^)A~oOUx0 zyxB4FM`cO8m^7AQa7_#^z&X3JC^7@P!fi~_ z$kSTalPOOayBjO+8M@VY4%nmI_wpDwf(@rPUEc`r$)u5S)5&P&oVq9Ae zB?fOoWPUs^I;D8QLR3XDs=e_tdPt2|fIWRcTVFT!qrPUmVZ3R)WxQ>?W4sFt3w#Ff z%pHcs?*X=VlkuMMzVQKmf5i2*=Yj15X*&N!TFUuV4^)=hN~{bz%@VpuGxyp!9BmYg zjzo{maj$Whrl$3p#m8kl>hq5Gp#ze#vZRPpGY6NprbJLhkHr!3&Pt>ydc*y#@dM}l zcgFX?UI6xDweh3=E3lVPr{n`4;yp4dmMGR4d9;E(XyHu%VHS$%7YtK117^_FOda1lGEK`2kvq-i1!kL~XkjIQ5#vbCs$JVbu~+7J*%CP&{Jd4~E-z=6SA+a16EL;+ zSKUNZgtbG%DpCRL4Pc)D`xMwKAQ6xR?*0Db1+%5u%505oY-6^?P#Nvb4(1W&k#r|^ zTUMfV-g^ApI_z^oqNu2qnp1GelFN4YI;JcQ7njvNj$P$+{N(vwo^|(ty$9?yU>^Yc z7}%@!iv?!i+F7Jt$GW-Jx#4P+IcGf0B;4^~b}_q}-OTR5UI+FTuy=sHi++6!bkGZ% zs445rNUNTe7erV4k59^!*t_A3%%?TG*RGVqs zS~}!2UJVBD8UTCSEijK4n7BnU=}t2l^>Umb-t-=2S$THS4~as3g{XB`-z)8+zTM$GbJiF^`>v<$E}&Hm3*+54_+$m1pL=9`t%+ zTi3hwL0@f7<(20i_a;4$?JTdHhxWQVhJ9ku%8Ej7vM^_uMYy@koN3N7XPa}(xn_ZR zoEbLfnT5bU1NJ$vF8~85vaf)9Qg8w9TVUS-`@Y7ExSPAoV)u+svy^X@@y=Rdc+SU} z?iru#H&=gY>*+>C5m)LZ$8h&c!spSx;Z2l#6N}4mB1RRgk=^Kyeq4ka0PZ8e6=_ss z8qFfRY_YpwffH)OT+n$Dt&G>o27D%|VJ?NJ?3Fk|JUKdlek_7Ea>Jbd?Kp$xa$FNM z(WBrUwPK!%WN-Y8&%{h&o@K5;ud;bIu%D~UmB4;!+CE3~eDfk_pQCvp?sH_n;&v@% z0dCi#4bS)VO1j)!<81IUSKDZl{eFlP1!kXG(Z&^I7aAU6lXD^q!m-L&-Jr~gp$ni# zS!4+kZ%9vLy(Y~;l5u`OFP0=a z(?h_G!Sx0<(^w}T{sKfbDSg-Ns$KtmFkif`QBY0%?(vcu_1gTSvrMPm?3R;@n zo^G{Fl!H=d$LQJ(Qa6x}!nj*}p|;myIr#VQ_qL8eIcVWvBqg@FTAdMpR+80)e?Y?j zaX9!lz9p1tCMU+*>SgsoS!neJse6@$*y_W{Ke>WPv~ev8Y(hB0iC zM)q(F6Axn6(bfn@C|bi&C`!E#k#u%paYbQCWMVjmW_knOT4T7Jva(If8c!bOyzQ5e zQ+*xU4z|(wt}myo$yQDsZ3h8HqOH>|9XVx9<=r;Tnr_XoW`dLoQW{7DKuWKrtu@A_ zZ9k8;rn{=wKvr4v^j|q$`#WCkHxrA0hoGvp$ffGQhE%=4p(jI%SWB^2P9zWVUK#F4vn-H?I^8nOMzigpZaKv|wXRz-K{~oYx17nlE4y0_5aM#Fq zkS2gM5u`~q){P0_wUd48{|bfItz39b_Jr55e2*vni3_j%!@}zy5MB>);q@>`IiB#s zfLwoa){G~(p0V&n^eXFFkfu~wD1vgED7ap*UPkF-y@>ntr99lHul~&6-x}A*K7FsG zH!U8Foqk}w&E?fplvnD{DC!zHng$h@!e|*P;_)5@L(u;lt>CH|dRwfI(A#2t4AKnr zw(PM!!z-U#U!Y^W&|U7S=Mv$*36N0uENMV)6IB)K8|zyn_xA^?Dup1;a>%{7p{ip2 zZ2eM4?s*{1KA7Agg1sH0Au+^45@6(NX)Z_wARSjrZnM6sl3?sX`p6-89BS_JINXrO zjVK|Z4k3Kax+Zi)=*UpVP^VDmP*SK%sB5TOs5?kSAVol$57GjV7J^g^((xcI0;vR~ z(wb0Dm&c(#ZXCT(D(7*T$776s*bbw@a2_8%5ydk;(SRIP8OPOewE7yFpWKcggL{N= zIR|&u)Y%`2)Jt$T)&$%m;Vx=~Mj($vI4wjIJPzSOe}583FTt+RF(EuHrz(WP_=Ktu z3ge2V@i=sBC=Xc~n!syD!4WDa-8O|z2vvk)p?Ih=v>2r2Ae{`-DIlE+k_{5nlGOS>bRyR`@ENp+Ydu9Z zF7*Nq^*rYOf93P}Zl<_Yk0m+or&maNXjSNv(50cv1S51g84$W6w3_yp_lK^e`|1AB zRiU*)L1=C08Z<-Exmn?^rI%EemQAWGoyWI^gd$}eX1HE=z(uQYA?{Nt#O3!X;j#sh z2@%{FV&a0mvmIn~8N$;+rs<~TP^`_)E9Jn82WJ;8v@bPLYd*v5W(&>!Nc`uR)1h^t zD!!9oY98M#;aTFI>ui0fI^@{YfA&D*ZZ!{!8$&g?3?AAPx-N8mXft4FcNet$`9f6`Y#s2R{OV$%K;#e5^u{&ZaclZ58KkNz3F`pZ58 zh&BH@_-A&2PWlf{0OVKuSCT)^z8VStyR3(mj{>rd@{zo?)$Pi^hDUwRDLC81(+fYF97I5ClJT&l6fGVn+uEz)&E#7;0OfZzCc@B1H!a)J}}BY ztRhBhQUCTau3K;U|B4vh-U8aNanokGpY#w&1@IcGYiMd|uUZCm^>qOQpoyujxrI42 zltlS2e>C9v{|i4;0?f`o5B#(|uMTVQ-ygXF>J0^8>|zrB5eGc*0A{qR4z0?Sfb-vj z{a=YzcL4djXIjjD$pZdxwI`p4Q zG5)b}1F8O$Jy-vo1Wxi#QGX5`pkr$NcgCH7W&yNQ9Q$XH0iEKX48U~(v5^py07P6@ zt!h8(KuBi&S;|!@DLNgf%+DL~b2$L z)CET5Kl?!E_@4(25!QA{I~Tm2rM)c*X^C({!Y%D_ZZ4K~E=X$x(iVqsLpW1`$~o94 zZqbo)k&2LFNwKALbycKvrKkXz;~^Cfd`6dwk+P6-1^k$MfZq~;6byjYS>0LzKWV^T ztP~ItUUgjj)4V_Lz|W`S-xKI+WN$T)9Fu$j1;A@gm{nrre*kBH;cD%Uxf=ndN`P?q*?w${$J)^MDSt#xqLf5zAORi5sD1FnE({qO5{5WW6Et}c>xpvgYw zz7!w?|99j6EaiXa_zNh2{5!?}>`_2(`*(u>8f~joS+2sLmKB@7%~-1)=86JZx+{r>^AlBIH`N~8`-i2)$% zSE*Z452c<^cJ&|1_t0Ai; z3zpT9b(8g#^_Pv5-7T9b%aP^ER>_XZo|kOiD2A1N9k)z8o<>KX7a%{O& zxeU2%U=5sFxpBF9xnJcs%4^Bn%M;~e;nozfX{dgaPlqyPTLiQBMQGL99KB0a2l|p-dA{`@Jiv0 z!aK!cz=6`N*rmu<98sKBJgj(B@tER?EzB)xTQas}ZQ&?sE19jv`IKNvXOwOz-BP-v zbWb@$nX6o)T&Bze8G&p-wjcxu3BrP?pb!uPlnP1%rGqj-#h?;UDX1LO0BQs^fewQD zL442vXb3a|ngxkLbD$HT)1b4U3!qD&%b*9KN1)$8PgRUmY*buSTvZ4v9x9$H-YPp( zd{ihZ(JHYjyHuDeaViNaER|%HJu0ax)hY*7MpTZgTv7R5<(ukeRTWiL)vciURlKU3YOHFaYPKpzb+2l!YK3aG>VDN))q2%N)dAHB)k#&M>VoQF)r+ckRqv_Z zSAD4ZTJ@dk2i4E2Usb=WZB~<4Q&dw@b5oJjP*>WS(s z^W%8n>TT*B>RswR>W9@ItG`r#rT#|!tHv6QjT-VAni^Uf+8PiI6Ag0> zsD_mWR>MPsq7kMMp%JAKqe0g=sL`!4t|8PAY0PRYXe?>0XdKZvrEyW?vBoovcbcl2 z+L}(9zM5g0;hGVeQJO5xB+X>aJ(_u%T+I^AGR-Q@eVPX}Yc)GGyEJ<=`!sKAKGl4t z`CRj*mXVf?mYo(#%R$RY3!@dJ6{QucwM&bsm7}#^>!4PPR-0D4R;O0C*0|OYtt(pB zv~FnK(t51*LhFszJFV~9a@rtmO>F~hm^MOtyS9gRq&7o4U%NrOTf0x2uRWwaqCKWP zqb<>%*Iv~ARr`weHSO!#H??nTKhypJmIlj$w}1n|!QfDE7&rnP1&#sJ!3^+ja6C8> zoCIcrQ^0B93~&~h1I_{GfeXMz;9_toxEx#ot^)4^9{|^a>%ooSX7C|!EBH6?Q}8qJ zbMPzhTkr?)XYe)t<$P{E+XT6S!j+&0Tj;2nyPP|T{PLd8=XGmv8 zN31iav!L@ycb)DA-A%f3x}LfLx3-4uru#$BU5~2g zuNSBntkP5OTNQTj3ZbbW^Yxc^AH*>^B@Rd}{c?@RQ*e z!*530jEF{FMkFJ$(SD;=qjsZCqi&-|MsJMX8GSJNWW3$j+jxhuk1@sgkTKtQ(0JH* z)cCE5w27?AT9frAL=%6LK$BpTP?HXm5tA_!fytz)l&QR_qN$Q8$TZ9}&NRW4Wtwct zHx-(SOlM6cW>RMIW{PG?W*{>^vnaC|GrAeWY`|>VY{pD%HfO%pT-jX3T+LkF+|NA9 zJl#CgJllM)d9HcBd7(Miyu`fBoM+x{-f7-#-fP}(K43m%K4Ly*E-;@o7n)x*e`B%L z0&Nj$QDD()am3;wR0g^kssP;rRfcLo4WY(RQ>Zx<3ble-Lv5h8(Cttcs4LVRN`!hr z8PHT{7L)_cffho!&=P1Fv<})0?Sqa%r=auDMd)eh1?VN{W#|p)E$AKSJ?L-H50+~z zH7vC(!InCfdX@&3MwTX)W|kI~mXxYk z&GNgIp_RK8-HL0~X*FmyVl`$Zuo7DxwmNEc%<6>IDXTM9=d5m7-L<-J^~mZstEaH_ zFcp{vObZ5v>B9_R#xPSD3g!SKz`S527#T)|`NINX!7wH)4aS32!m45WVKuNiSOcsH zb`aJAYlBU~gfJ0o7AAqs!b4KcZR#bUEu_{2iy-H2oHgW!6V_(@H}`W`~bWb-T*%YZ-sZjyWj%& z6nq}O2tN(K0KWvk0>25r4ZjC}0Dog6Wut1NZlh%bvC*?Jv@x+Ux3RRbwz0KwxAC;` zw%K9hW3$u7-zLZ=#3sxp!Y0Zl*QV8G+2*0`Mq8LI#WvY?pY5=%(00aFVmoj9i|skv zi?+YoUbVe$d(-x|?K9gKwy$m9*?zSBjM$1WM3^C<2rGmw0*SCkI3PR_UI;3}9}$gU zAa)}X5Nt#WA{~*5C`HsE1`xxDF~kHyh?qf05DSQ9#1X_X#4W^K#C^m=#AC!$#52Tm z#7o2*#5=?XI~6-yJAb_7j+0FM%}TOu{XB& zvS-;>*$>;Fu)k=3+5VdS4f}`o&+T8@zqWsC|K9#1dM$bbdNW!9t%L@ltpGKcUUqoL< zKSRGjze2x3ze9gSe@1^re|L~}kabw=pzq-95aUqnFyL^`;hm$3qpc&^G0-u@G0ZW- zk?F{GOm)m~%y!Ii%yTSoEOOlMSnJr}*zDNi*ybp4JnneL@tosD$E%Ll9d9|_aeU$U z+VO|e8mDzm8=VxKwm5;DRGkc+;7;yNo=zkuAE%v8{!T$op-$mWQBJW=45wVD0;eLU zVy99ko>P_6ey3WedZ#9*gHA0@vrgBXzHigrhTq2AR=Z8K?aH=e#U_3CMm;g*LhK7m2L}Owx1(<3~Ev5m}glWZeV7f8Am??}1vxHg3 z9LAi+T*utQJjDEl`5p5b^A7U?^BMCMtBFNpN!T6O0BjJJj*Y`6V3V+^*bHnob}zO9 z+k_p#j$&I!&*&U>8GoHLxWoH@=p&OGNT=l#yL z&JE5@&O+xE=VQ(%oKHJnaQ@Z#s`GW{r_O&kzjywKTZ`L-lfx8 zOk6x&l3WhDEV$fu`Ht7Z8{$py=6ERH4v)b*<6ZD>cy~Mz?}ZPZeAgk@QCETMlZs!g>aK_n{bctnDB(~jPRWB&3%)* zp1YyDiMzSGrMtDetvk{k?e64`b;r2}x`(*a+{4`?-DBM8?o9VM_XPJOceZj&f}WLeUC>TPduJ^yzqGC@y6pFQHHpdxPiEt zs6gC8v?MwZu|ymZPxK&q5lKWcF^m{VOeCffGl<#593q!kN-QT<603>B#8bq3#23W3 z#P`Hc#P6O`p0b{6JyksQJncOlJu#lno_J4!C(+Z}lk7?H^z#h#WO=eZQ#{i=Gd#0B zb3F4si#&@x%RG6Wm7W8h$335WZSg{Q1$piDI^-qxy6Sb$>!H_gUQfN=czyNy;Vt96 z)_a5ZCT}@!1@A51U~gS-18-w*Gj9uTcWn~rk@k~nNOhzJQZuQA)K2Oo^^p2Ve9}?UqaB-eKzERLr0!_iA=+_e z$GaWh$Nn6e)@fWj$pRMUJ9C0aJ7-W)usGJ!KmO zOTkeH6c36Qg+vLZFey2dd`c0egi=nar0k>AQ0gg7ltYwp$|OZd5m9C-bCdxc8h`?>kK`+542{CxZ8X{-gc^|0(}z|3&}f{-^xU`d{$>)&GkBHUG!{PyPS!f9e0m|6PD` zfKGs6fN_9XfK`BXfNg+X06u^aKn@5Ehz_6!FazQP_5`E_WCUaf>fwaJgz^K5OKzblE zFfK4LFe#87m>QTKm>F0fI2Cv;@N1B6kXsNls5GcA=y1^KpmRYNgMJOV9rRn!)1YTT z&x2kDy$<>jEEBvocth~!VEJJ4U{tVEFgDmZ*e%#2*fSVd>?SxYI4+nSoEn@FoE=;c z%ndFHE)T8<9tb`jd^`AA@T=fA!S92=1b+*W3Xus>3W0>!h9E=GAxNVwMp#5xMc75yM>s@mi@-*NMr23qi)e}HjOdQ&ix`R+i5QQVh**v|8}T^eX~Z89 zFC*SWypQ-4@ipQ{q)g=6$PJM?k@}H_k;ajxkrt7bk=Btmk%&lCBs$VDG9WT7vN=)` zc{gf(ltmOViV?+)s*2hlRU1_w)fUwsH5fG#H6Aq?C5#e99f>*?bu#Kq)cL53QLm!D zN6SX9jouI~AFUXz9IX;<5N#Z78|@IiEqZ&jOEfXsJ9({PC!$Y9pN&2reK-1fjBL!hn2j-VF^VzDF{&}@F0~;U?oSV*htPM^ z)9F?8{q$OT1HGBvLT{&c(R=B9`Vf7Tew2Qkev*EgewKcLeu;jCevN*Eew%)met(zL zE}dO2yJB{g?i$&3Vb^Ph62pptVmL6iF|Z7Gh7V&W!=Dkv2xWvZA^`H41$c^685xXh z#$HA5j$#}qc%y`Q9gYlN}f$@p)mGPaa&2(gvnW4-` zW;Bz|jAJG+lbCE~KC_D1!|Z1cGDn!>%t_`nbCx;BTx6~=k1}sD?=tT*A2J^^pE92@ zUoc-W-!R`ZKQce>-ntvPJ7{Q%PwVtKI(qchadMrbh1>y} zK5LLQ%ld_NlXZu6pY@3Kg!PQ|g7up9j`flCC230%C`mPGYm!Eic9Kq#ev)C5Ns?KT zMUs0GJ*hltDCu0%hh(i}hvdNI)a0Dx{N$qK;^gY&#^i&^t;rq9UCBMkg5;@WQL;FB zK6x?uR`T!3FO%OSzf1m{{4MzhTbjLv4PtAv&Dd6KYql-hp6$Ti#>TQq>|i#F&1R>v zGuYYe9Ckjth+V=iXIHX2**)w&HlIDn9$}BOC)q;w3|q{eV_#=~-UHdQeNXhBvOU9l zPVIS?vN1(DMKwh|MKi@91)2g&u}QH@u}^VGaZ2$>@k-f|;+sNE@lV;4l9$3wDM=|$ zsZKeNQkT+@(wo9h5vGV!=28|@j-(t*Ihk@gy|NwrHwrJ_?EQ!%N|sV=FmsqU%7RIgMgm+iY3gZCX~AikY4vGsX`N|3 zX?zUe#D{nJCzY3UK^QR&I)Iq5a&_32INhtk{9JJWm8`_l*0N7BdBkES0_ zKbd|y{cQS$^h@cN)32uANWYbSC;eN7cEu7nur~7MX}lRHj3wQ>JSs zDU+P(o4GU7FEb!BCUaNj?#zVDq)c{Zb>^YWj?C`N-ps+wk<777LFQ8C;mq@ymou+s z-pIV0c|Y?}=5LuFvesm2WNByVWa(!aWtnDKWLagwvk+OREU&B`Sw2~mENWIjR&W+A zD?BSID<+GcRhZS8bu{ZqwtTj2wtseNc5U`}_H6cC_G0#O_Q~u^*;lf!XWz=cn|(j~ zVfO3nciA7azhr;sNO8a%a}JCH=O8#}jw1)d+0NO)@#O?_c5&i4iJW9k8YhF3&DqPT z;52c@I1?NpXNDu;EO3@NM>xkgCpl+0=Q&R}&p6LHFFCI{?>HYgpE+MS-}g%ImEF5` zul`==y)k=>_YUkmxA$v~ZVonwmXnoJm{Xinmcz@b&1uPL&*{qP&Ee+^<_zb|=FH_R z=B(r#%{i9yDCbSi$DGeO-+<*4*8qy%Mqm*|tz6SwSguX3U9NpDHW!zR&n4t~Y_ z%|dXYZlOV;aiLiuv=CNkQ)pLcU$~>tw{T~nUtvIDaA9a+cwuB=bRoTvQMkLXq_DT} zMByJrN=2xm;G)c;#-gdB`J$zw!$n7n&K6xQx>0ny=x))2qDMtvGjB4w=$WsHD&9|HkPTE8I?JdZ7bVe=2GTX=27NVwxi6qj9L~@ z7GK6HOD@|}mRgokmQ}_n%PGq*D=g!db(SraJu2T&4lVaBPc5%4pC~_9ey#jw`JM86 z<-eD|Eq`DBvHWxS*YfYY%{&F35>JJmw* z(5cX`Fsd-AFsp!8z$$Di>?-Ul94eeD+$usVc2(q6)Km;qEL2>o_@i=NC8$!nGN6)H z8B@us&dn@}Zmnv^oeyp;nf>+sBIaIk+5v#nb$W@do zzpAJzW>tI@tBPHfS;eW!ttzN0sv54kQN5vBsTx$RTWwNpUTs-zU5%=CsNPn+y&6~T zQSDo^qsFI(RMlT}@-n!J3wu<(l7Wm1~`9U2DB- z{b~biBWoG83ALPBZf$Mt!P@THzFL0mV6CWDTsv30Si4etr1nhh`Pz%Mmuv6T-mQI5 z`$wHj-I}^}bsOq7)ydT<)G5`e)NQTPtkbSDtFx%Hth26T*X7pb*A>?sruswmZS|e?J@u3I)Ah6UlKO@E<@&?*N9&I@ zY--SGz%|4-R5c7V9BH`F@TlQQ!?T7L4X+#CHGFLN((t`ex>2Q3tx=;YMXkSjy7Fvy4iH6>3-9rrYB9Wo8C2jZ2Hply;-VxYqLc&zB#Np zt~tIrp_$cO-rUgK+}zUK-rUvP)4bSxy!mnS)8;=8eQ8n-*6vnG>r(6W z)`zVxT0ga|X;W-dZc}YjYg2F2YJ;@F+6ZmrHfmcyTTokQTWlMvjor4Vjnh`uR@c_r z*4MVscDU_k+q<^U?XvBL?Pl%7cK`P1c6vLrJ-(gQ&Tda_FKFksm$sL;SF~5R?{BYZ zA89|9RMxv#UPv#xWt^LE$zF7qy8*RHOV zuF9@`T{T_xT}@qwy4t!ryL!6%y9T>Py2iUsbe--x*L9)mQrG3Kt6kT-Zg$=2y4Urf z>rvNlT_3s?yA8Uxbq92_yQ{i~yCvP{x*v6a?g8~^_keqJd(3)}J?I{%9&8V;2j8=+ zC#fg9C#NUBr>Li-r@W`KXTIl6FQhl5H@~;Mx1qPWx23nex3jmqce(d$@1@==z1Ms1 z^xo@z(EF(Od!KHfWgote&`0d^?j!e6`uzF=`$GD{`Xc*c`m+0S`tte;`-=O@`YQUW z`wsNg^)>b#>}%;8@4Mais_$#RPCu$Yw4dEy*Dvb-rTW7+!4h^*p zbqrk`x;gY{=*iHtp%+81hu#iL56cZJ533HV4{HsZ58Dp=4*L(M4i^k_hf9Zf!~2F? zhTDcahP#KyhbM=H!!yJ4!zYJN51$*pFnnY9_3(!g`4P1dy%EC^lM(6&ZG<_JIFdC| zI5ILaF>-t4-Kfmy)=|w-@Tl&n!Km@5*(h`rHfl3!H%c7!9^EnOGfElt8x0%{8KsRz zj7E*djOLHFj~*U+$5T(HexLed>hsh$p_EWoxK6l1Xe6`|qJ(Inlh9e{B6JhF3;l)R!gyh_aE~xe zm@V8Z%o7#}_X}Hv3&LgL5#cYw6T;KNbHWS4UxhD(uZ8b~ABA6p->0RfH%=>0t4^y= zYfVF@b*C+-t*33Lk<%X2e$#=|A=6>gvD577l8I0wh}MZVisVF!B4v@PXsbw9WF~@&U?Lllod_kuiwGj3 z$XgUCN)#oFQbg&ZB2lrZRKycCiVlieMD3y;kw7#lnikE7j*4!GZj0`T9*TYw{VsYg zdL?=*`XKr|qd22HqcWp5qducG1DVmAF_Yr z;w@sZSYK=?HW5R`R${o=R*Vz-iNnN^;u!HRagvxPt`hGT3&c}mkytFA7cYqqi;s$r zi|>mciJyp{iC>6ci{FaBN!Cd=O5`Mp5@m^sL{DNUF_D-{P!b2pHpzC0w`7OJN3v5A zA&Hj6N*Iy^NtT2o$(7_wsw7>KUI||^BpHvzqgr+cVcNH#c{0UV2`4e%rj;yvMxPJZav4K72lEK6ailA2**c zpEbXCK5xEozIeWLzGq%AKRrJ?FPUGOKRkbQ{@DDL`G@mQ=bz2Ln14I}e*V+^mxWCW zDhuERy9I{@--YM}&O+`&!2)-obb+@}wa~E8z0kKXurRzZwlJ|ETo5fBSva@L}Qe;`+r+i}H(G7Qu@;i+YQOi||FnB61PEh*_jB?p-{v*t22~@0PwVOD)SRuUTHVykXgR*=0F)IcJ%-T(!J^xpui> zxoLTLS-31-o?l*CKD>N>`RekG<=e~mmY*y?TYkR$YWdCb*Oj#^$}1Wx+ABIM`YUED z&=so{_=@eywiUM(%8K7g;7Z5}Z6$nV&r04({z}10(aQdnnw7zo$zw17@`TC!+$w+i M+YR<}d+hc90aZnaKmY&$ diff --git a/Caps/Camera/CameraManager.swift b/Caps/Camera/CameraManager.swift index 5dc4bf8..801850d 100644 --- a/Caps/Camera/CameraManager.swift +++ b/Caps/Camera/CameraManager.swift @@ -20,6 +20,8 @@ class CameraManager: ObservableObject { private let photoOutput = AVCapturePhotoOutput() private var status = Status.unconfigured + private var camera: AVCaptureDevice? + private init() { configure() } @@ -67,11 +69,11 @@ class CameraManager: ObservableObject { session.commitConfiguration() } - let device = AVCaptureDevice.default( + self.camera = AVCaptureDevice.default( .builtInWideAngleCamera, for: .video, position: .back) - guard let camera = device else { + guard let camera = camera else { set(error: .cameraUnavailable) status = .failed return @@ -169,4 +171,28 @@ class CameraManager: ObservableObject { self.photoOutput.capturePhoto(with: photoSettings, delegate: delegate) } } + + // MARK: Focus + + func continuouslyFocusOnMiddle() { + guard let device = camera else { + return + } + do { + try device.lockForConfiguration() + + if device.isFocusPointOfInterestSupported { + device.focusPointOfInterest = CGPoint(x: 0.5, y: 0.5) + device.focusMode = .continuousAutoFocus + } + print("Enabled continuous autofocus") + device.unlockForConfiguration() + } catch { + self.error("Could not lock device for configuration: \(error)") + } + } +} + +extension CameraManager: Logger { + } diff --git a/Caps/Camera/CameraView.swift b/Caps/Camera/CameraView.swift index 2889865..b25e1d0 100644 --- a/Caps/Camera/CameraView.swift +++ b/Caps/Camera/CameraView.swift @@ -87,7 +87,8 @@ struct CameraView: View { .overlay(RoundedRectangle(cornerRadius: circleSize/2) .stroke(lineWidth: circleStrength) .foregroundColor(circleColor)) - + .onTapGesture(perform: didTapCircle) + .background(Color(white: 1, opacity: 0.01)) Spacer() } Spacer() @@ -116,6 +117,10 @@ struct CameraView: View { private func capture() { model.captureImage() } + + private func didTapCircle() { + model.continuouslyFocusOnMiddle() + } } struct CameraView_Previews: PreviewProvider { diff --git a/Caps/Camera/ContentViewModel.swift b/Caps/Camera/ContentViewModel.swift index c506130..68f999c 100644 --- a/Caps/Camera/ContentViewModel.swift +++ b/Caps/Camera/ContentViewModel.swift @@ -54,4 +54,8 @@ class ContentViewModel: ObservableObject { func captureImage() { cameraManager.capturePhoto(delegate: frameManager) } + + func continuouslyFocusOnMiddle() { + cameraManager.continuouslyFocusOnMiddle() + } } diff --git a/Caps/CapsApp.swift b/Caps/CapsApp.swift index 7fe3d48..61273da 100644 --- a/Caps/CapsApp.swift +++ b/Caps/CapsApp.swift @@ -1,11 +1,15 @@ import SwiftUI #warning("TODO: Add colors") -#warning("TODO: Grid view") +#warning("TODO: Rearrange caps in grid view") +#warning("TODO: Change main image") +#warning("TODO: Load/save grid images") @main struct CapsApp: App { + static let thumbnailImageSize: CGFloat = 60 + let database = Database(server: URL(string: "https://christophhagen.de/caps")!) var body: some Scene { diff --git a/Caps/ContentView.swift b/Caps/ContentView.swift index 333ed9a..f5bcc00 100644 --- a/Caps/ContentView.swift +++ b/Caps/ContentView.swift @@ -245,7 +245,7 @@ struct ContentView: View { SettingsView(isPresented: $showSettingsSheet) } .sheet(isPresented: $showGridView) { - GridView() + GridView(isPresented: $showGridView) }.alert(isPresented: $showNewClassifierAlert) { Alert(title: Text("New classifier available"), message: Text("Classifier \(database.serverClassifierVersion) is available. You have version \(database.classifierVersion). Do you want to download it now?"), diff --git a/Caps/Data/Cap.swift b/Caps/Data/Cap.swift index 43a293d..0145399 100644 --- a/Caps/Data/Cap.swift +++ b/Caps/Data/Cap.swift @@ -27,6 +27,10 @@ struct Cap { String(format: "images/%04d/%04d-%02d.jpg", id, id, mainImage) } + var image: CapImage { + .init(cap: id, version: mainImage) + } + /** Create a new cap. - Parameter id: The unique id of the cap diff --git a/Caps/Data/CapImage.swift b/Caps/Data/CapImage.swift new file mode 100644 index 0000000..836e495 --- /dev/null +++ b/Caps/Data/CapImage.swift @@ -0,0 +1,8 @@ +import Foundation + +struct CapImage: Codable, Equatable, Hashable { + + let cap: Int + + let version: Int +} diff --git a/Caps/Data/Database.swift b/Caps/Data/Database.swift index c0504f5..576dae4 100644 --- a/Caps/Data/Database.swift +++ b/Caps/Data/Database.swift @@ -5,52 +5,23 @@ import CryptoKit final class Database: ObservableObject { - static let imageCacheMemory = 10_000_000 - - static let imageCacheStorage = 200_000_000 - private let imageCompressionQuality: CGFloat = 0.3 - private static var documentDirectory: URL { - try! FileManager.default.url( - for: .documentDirectory, - in: .userDomainMask, - appropriateFor: nil, create: true) - } + @AppStorage("classifier") + private(set) var classifierVersion = 0 - private var fm: FileManager { - .default - } + @AppStorage("serverClassifier") + private(set) var serverClassifierVersion = 0 - private var localDbUrl: URL { - Database.documentDirectory.appendingPathComponent("db.json") - } - - private var localClassifierUrl: URL { - Database.documentDirectory.appendingPathComponent("classifier.mlmodel") - } - - private var imageUploadFolderUrl: URL { - Database.documentDirectory.appendingPathComponent("uploads") - } - - private var serverDbUrl: URL { - serverUrl.appendingPathComponent("caps.json") - } - - private var serverClassifierUrl: URL { - serverUrl.appendingPathComponent("classifier.mlmodel") - } - - private var serverClassifierVersionUrl: URL { - serverUrl.appendingPathComponent("classifier.version") - } + let images: ImageCache private let encoder = JSONEncoder() private let decoder = JSONDecoder() let serverUrl: URL + let folderUrl: URL + @AppStorage("authKey") private var serverAuthenticationKey: String = "" @@ -108,22 +79,68 @@ final class Database: ObservableObject { */ private var nextSaveTime: Date? - let imageCache: URLCache + @Published + var isUploading = false - init(server: URL) { + init(server: URL, folder: URL = FileManager.default.documentDirectory) { self.serverUrl = server + self.folderUrl = folder self.caps = [:] - let cacheDirectory = Database.documentDirectory.appendingPathComponent("images") - self.imageCache = URLCache( - memoryCapacity: Database.imageCacheMemory, - diskCapacity: Database.imageCacheStorage, - directory: cacheDirectory) + let imageFolder = folder.appendingPathComponent("images") + self.images = try! ImageCache( + folder: imageFolder, + server: server, + thumbnailSize: CapsApp.thumbnailImageSize) + + ensureFolderExistence(gridStorageFolder) loadCaps() } - @Published - var isUploading = false + func mainImage(for cap: Int) -> Int { + caps[cap]?.mainImage ?? 0 + } + + // MARK: URLs + + private var fm: FileManager { + .default + } + + private var localDbUrl: URL { + folderUrl.appendingPathComponent("db.json") + } + + private var localClassifierUrl: URL { + folderUrl.appendingPathComponent("classifier.mlmodel") + } + + private var imageUploadFolderUrl: URL { + folderUrl.appendingPathComponent("uploads") + } + + private var serverDbUrl: URL { + serverUrl.appendingPathComponent("caps.json") + } + + private var serverClassifierUrl: URL { + serverUrl.appendingPathComponent("classifier.mlmodel") + } + + private var serverClassifierVersionUrl: URL { + serverUrl.appendingPathComponent("classifier.version") + } + + private var gridStorageFolder: URL { + folderUrl.appendingPathComponent("grids") + } + + func mainImageUrl(for cap: Int) -> URL? { + guard let path = caps[cap]?.mainImagePath else { + return nil + } + return serverUrl.appendingPathComponent(path) + } // MARK: Disk storage @@ -185,6 +202,7 @@ final class Database: ObservableObject { print("Database saved") } + @discardableResult private func ensureFolderExistence(_ url: URL) -> Bool { guard !fm.fileExists(atPath: url.path) else { return true @@ -395,6 +413,14 @@ final class Database: ObservableObject { DispatchQueue.main.async { self.isUploading = true } + defer { + DispatchQueue.main.async { + self.isUploading = false + } + } + guard !changedCaps.isEmpty || pendingImageUploadCount > 0 else { + return + } log("Starting uploads") let uploaded = await uploadAllChangedCaps() DispatchQueue.main.async { @@ -402,9 +428,6 @@ final class Database: ObservableObject { } await uploadAllImages() log("Uploads finished") - DispatchQueue.main.async { - self.isUploading = false - } } /** @@ -595,6 +618,82 @@ final class Database: ObservableObject { return Classifier(model: model) } + // MARK: Grid + + var availableGrids: [String] { + do { + return try fm.contentsOfDirectory(at: gridStorageFolder, includingPropertiesForKeys: nil) + .filter { $0.pathExtension == "caps" } + .map { $0.deletingPathExtension().lastPathComponent } + } catch { + print("Failed to load available grids: \(error)") + return [] + } + } + + private func gridFileUrl(_ grid: String) -> URL { + gridStorageFolder.appendingPathComponent(grid).appendingPathExtension("caps") + } + + private func gridImageUrl(_ grid: String) -> URL { + gridStorageFolder.appendingPathComponent(grid).appendingPathExtension("jpg") + } + + func load(grid: String) -> ImageGrid? { + let url = gridFileUrl(grid) + guard fm.fileExists(atPath: url.path) else { + return nil + } + do { + let data = try Data(contentsOf: url) + var loaded = try decoder.decode(ImageGrid.self, from: data) + // Add all missing caps to the end of the image + let newCaps = Set(caps.keys).subtracting(loaded.capPlacements).sorted() + loaded.capPlacements += newCaps + print("Grid \(grid) loaded (\(newCaps.count) new caps)") + return loaded + } catch { + print("Failed to load grid \(grid): \(error)") + return nil + } + } + + @discardableResult + func save(_ grid: ImageGrid, named name: String) -> Bool { + let url = gridFileUrl(name) + do { + let data = try encoder.encode(grid) + try data.write(to: url) + print("Grid \(name) saved") + return true + } catch { + print("Failed to save grid \(name): \(error)") + return false + } + } + + // MARK: Grid images + + func load(gridImage: String) -> UIImage? { + let url = gridImageUrl(gridImage) + return UIImage(at: url) + } + + @discardableResult + func save(gridImage: UIImage, for grid: String) -> Bool { + guard let data = gridImage.jpegData(compressionQuality: 0.9) else { + return false + } + let url = gridImageUrl(grid) + do { + try data.write(to: url) + return true + } catch { + print("Failed to save grid image \(grid): \(error)") + return false + } + } + // MARK: Statistics var numberOfCaps: Int { @@ -609,19 +708,13 @@ final class Database: ObservableObject { Float(numberOfImages) / Float(numberOfCaps) } - @AppStorage("classifier") - private(set) var classifierVersion = 0 - - @AppStorage("serverClassifier") - private(set) var serverClassifierVersion = 0 - var classifierClassCount: Int { let version = classifierVersion return caps.values.filter { $0.classifiable(by: version) }.count } var imageCacheSize: Int { - imageCache.currentDiskUsage + fm.directorySize(images.folder) } var databaseSize: Int { @@ -648,4 +741,13 @@ extension Database { db.image = UIImage(systemSymbol: .photo) return db } + + static var largeMock: Database { + let db = Database(server: URL(string: "https://christophhagen.de/caps")!) + db.caps = (1..<500) + .map { Cap(id: $0, name: "Cap \($0)", classifier: nil)} + .reduce(into: [:]) { $0[$1.id] = $1 } + db.image = UIImage(systemSymbol: .photo) + return db + } } diff --git a/Caps/Data/ImageCache.swift b/Caps/Data/ImageCache.swift new file mode 100644 index 0000000..8fc8946 --- /dev/null +++ b/Caps/Data/ImageCache.swift @@ -0,0 +1,209 @@ +import Foundation +import UIKit + +final class ImageCache { + + let folder: URL + + let server: URL + + let thumbnailSize: CGFloat + + private let fm: FileManager = .default + + private let session: URLSession = .shared + + private let thumbnailQuality: CGFloat = 0.7 + + init(folder: URL, server: URL, thumbnailSize: CGFloat) throws { + self.folder = folder + self.server = server + self.thumbnailSize = thumbnailSize * UIScreen.main.scale + + if !fm.fileExists(atPath: folder.path) { + try fm.createDirectory(at: folder, withIntermediateDirectories: true) + } + } + + private func localImageUrl(_ image: CapImage) -> URL { + folder.appendingPathComponent(String(format: "%04d-%02d.jpg", image.cap, image.cap, image.version)) + } + + private func remoteImageUrl(_ image: CapImage) -> URL { + server.appendingPathComponent(String(format: "images/%04d/%04d-%02d.jpg", image.cap, image.cap, image.version)) + } + + func image(_ image: CapImage, completion: @escaping (UIImage?) -> ()) { + Task { + let image = await self.image(image) + completion(image) + } + } + + func image(_ image: CapImage, download: Bool = true) async -> UIImage? { + if let localUrl = existingLocalImageUrl(image) { + return UIImage(at: localUrl) + } + guard download else { + return nil + } + guard let downloadedImageUrl = await loadRemoteImage(image) else { + return nil + } + guard saveImage(image, at: downloadedImageUrl) else { + return UIImage(at: downloadedImageUrl) + } + let localUrl = localImageUrl(image) + return UIImage(at: localUrl) + } + + func cachedImage(_ image: CapImage) -> UIImage? { + guard let localUrl = existingLocalImageUrl(image) else { + return nil + } + return UIImage(at: localUrl) + } + + @discardableResult + func removeImage(_ image: CapImage) -> Bool { + let localUrl = localImageUrl(image) + return removePossibleFile(localUrl) + } + + @discardableResult + func refreshImage(_ image: CapImage) async -> Bool { + guard let downloadedImageUrl = await loadRemoteImage(image) else { + return false + } + return saveImage(image, at: downloadedImageUrl) + } + + private func loadRemoteImage(_ image: CapImage) async -> URL? { + let remoteURL = remoteImageUrl(image) + return await loadRemoteImage(at: remoteURL) + } + + private func loadRemoteImage(at url: URL) async -> URL? { + let tempUrl: URL + let response: URLResponse + do { + (tempUrl, response) = try await session.download(from: url) + } catch { + print("Failed to download image \(url.lastPathComponent): \(error)") + return nil + } + guard let httpResponse = response as? HTTPURLResponse else { + print("Failed to download image \(url.lastPathComponent): Not a HTTP response: \(response)") + return nil + } + guard httpResponse.statusCode == 200 else { + print("Failed to download image \(url.path): Response \(httpResponse.statusCode)") + return nil + } + return tempUrl + } + + private func saveImage(_ image: CapImage, at tempUrl: URL) -> Bool { + let localUrl = localImageUrl(image) + guard removePossibleFile(localUrl) else { + return false + } + do { + try fm.moveItem(at: tempUrl, to: localUrl) + return true + } catch { + print("failed to save image \(localUrl.lastPathComponent): \(error)") + return false + } + } + + private func existingLocalImageUrl(_ image: CapImage) -> URL? { + let localFile = localImageUrl(image) + guard exists(localFile) else { + return nil + } + return localFile + } + + private func exists(_ url: URL) -> Bool { + fm.fileExists(atPath: url.path) + } + + @discardableResult + private func removePossibleFile(_ file: URL) -> Bool { + guard exists(file) else { + return true + } + return remove(file) + } + + @discardableResult + private func remove(_ url: URL) -> Bool { + do { + try fm.removeItem(at: url) + return true + } catch { + print("Failed to remove \(url.lastPathComponent): \(error)") + return false + } + } + + // MARK: Thumbnails + + private func localThumbnailUrl(cap: Int) -> URL { + folder.appendingPathComponent(String(format: "%04d.jpg", cap)) + } + + func thumbnail(for image: CapImage, download: Bool = true) async -> UIImage? { + let localUrl = localThumbnailUrl(cap: image.cap) + if exists(localUrl) { + return UIImage(at: localUrl) + } + + guard let mainImage = await self.image(image, download: download) else { + return nil + } + let thumbnail = await createThumbnail(mainImage) + save(thumbnail: thumbnail, for: image.cap) + return thumbnail + } + + func cachedThumbnail(for image: CapImage) -> UIImage? { + let localUrl = localThumbnailUrl(cap: image.cap) + guard exists(localUrl) else { + return nil + } + return UIImage(at: localUrl) + } + + @discardableResult + func createThumbnail(for image: CapImage, download: Bool = false) async -> Bool { + await thumbnail(for: image, download: download) != nil + } + + private func createThumbnail(_ image: UIImage) async -> UIImage { + let size = thumbnailSize + return await withCheckedContinuation { continuation in + DispatchQueue.global(qos: .background).async { + let small = image.resize(to: CGSize(width: size, height: size)) + continuation.resume(returning: small) + } + } + } + + @discardableResult + private func save(thumbnail: UIImage, for cap: Int) -> Bool { + guard let data = thumbnail.jpegData(compressionQuality: thumbnailQuality) else { + print("Failed to get thumbnail JPEG data") + return false + } + let localUrl = localThumbnailUrl(cap: cap) + do { + try data.write(to: localUrl) + return true + } catch { + print("Failed to save thumbnail \(cap): \(error)") + return false + } + } +} diff --git a/Caps/Data/ImageGrid.swift b/Caps/Data/ImageGrid.swift new file mode 100644 index 0000000..596f166 --- /dev/null +++ b/Caps/Data/ImageGrid.swift @@ -0,0 +1,82 @@ +import Foundation + +struct ImageGrid: Codable { + + struct Position { + + let x: Int + + let y: Int + } + + struct Item: Identifiable { + + let id: Int + + let cap: Int + } + + let columns: Int + + /** + The place of each cap. + + The index is the position in the image, + where `x = index % columns` and `y = index / columns` + */ + var capPlacements: [Int] + + /// All caps currently present in the image + var caps: Set { + Set(capPlacements) + } + + var items: [Item] { + capPlacements.enumerated().map { + .init(id: $0, cap: $1) + } + } + + var capCount: Int { + capPlacements.count + } + + func index(of position: Position) -> Int? { + return index(x: position.x, y: position.y) + } + + func index(x: Int, y: Int) -> Int? { + let index = y * columns + y + guard index < capCount else { + return nil + } + return capPlacements[index] + } + + mutating func switchCaps(at x: Int, _ y: Int, with otherX: Int, _ otherY: Int) { + guard let other = index(x: x, y: y), let index = index(x: otherX, y: otherY) else { + return + } + switchCaps(at: index, with: other) + } + + mutating func switchCaps(at position: Position, with other: Position) { + guard let other = index(of: other), let index = index(of: position) else { + return + } + switchCaps(at: index, with: other) + } + + mutating func switchCaps(at index: Int, with other: Int) { + guard index < capCount, other < capCount else { + return + } + let temp = capPlacements[index] + capPlacements[index] = capPlacements[other] + capPlacements[other] = temp + } + + static func mock(columns: Int, count: Int) -> ImageGrid { + .init(columns: columns, capPlacements: Array(0.. DirectoryEnumerator? { + enumerator(at: directory, + includingPropertiesForKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey], + options: []) { (_, error) -> Bool in + print(error) + return false + } + } + + func directorySize(_ directory: URL) -> Int { + return fileSizeEnumerator(at: directory)? + .compactMap { $0 as? URL } + .reduce(0) { $0 + $1.fileSize } ?? 0 + } +} diff --git a/Caps/Extensions/UIImage+Extensions.swift b/Caps/Extensions/UIImage+Extensions.swift index 0bf66e2..71380da 100644 --- a/Caps/Extensions/UIImage+Extensions.swift +++ b/Caps/Extensions/UIImage+Extensions.swift @@ -147,3 +147,14 @@ private func saturate(_ component: UInt8) -> CGFloat { } extension CIImage: Logger { } + + +extension UIImage { + + convenience init?(at url: URL) { + guard let data = try? Data(contentsOf: url) else { + return nil + } + self.init(data: data, scale: UIScreen.main.scale) + } +} diff --git a/Caps/Extensions/URL+Extensions.swift b/Caps/Extensions/URL+Extensions.swift index eb550a6..54f4b19 100644 --- a/Caps/Extensions/URL+Extensions.swift +++ b/Caps/Extensions/URL+Extensions.swift @@ -22,4 +22,14 @@ extension URL { var creationDate: Date? { return attributes?[.creationDate] as? Date } + + var fileSizeAlt: Int? { + do { + let val = try self.resourceValues(forKeys: [.totalFileAllocatedSizeKey, .fileAllocatedSizeKey]) + return val.totalFileAllocatedSize ?? val.fileAllocatedSize + } catch { + print(error) + return nil + } + } } diff --git a/Caps/Extensions/View+Extensions.swift b/Caps/Extensions/View+Extensions.swift new file mode 100644 index 0000000..564cb56 --- /dev/null +++ b/Caps/Extensions/View+Extensions.swift @@ -0,0 +1,19 @@ +import SwiftUI + +extension View { + + func snapshot() -> UIImage { + let controller = UIHostingController(rootView: self) + let view = controller.view + + let targetSize = controller.view.intrinsicContentSize + view?.bounds = CGRect(origin: .zero, size: targetSize) + view?.backgroundColor = .clear + + let renderer = UIGraphicsImageRenderer(size: targetSize) + + return renderer.image { _ in + view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true) + } + } +} diff --git a/Caps/Views/CachedCapImage.swift b/Caps/Views/CachedCapImage.swift new file mode 100644 index 0000000..036c989 --- /dev/null +++ b/Caps/Views/CachedCapImage.swift @@ -0,0 +1,79 @@ +import SwiftUI + +struct CachedCapImage: View where Content: View, T: Equatable { + + @State private var phase: AsyncImagePhase + + let id: T + + let check: () -> UIImage? + + let fetch: () async -> UIImage? + + private let transaction: Transaction + + private let content: (AsyncImagePhase) -> Content + + var body: some View { + content(phase) + .task(id: id, load) + } + + init(_ id: T, _ image: CapImage, cache: ImageCache, @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P) where Content == _ConditionalContent, I : View, P : View { + self.init(id, image: image, cache: cache) { phase in + if let image = phase.image { + content(image) + } else { + placeholder() + } + } + } + + init(_ id: T, image: CapImage, cache: ImageCache, transaction: Transaction = Transaction(), @ViewBuilder content: @escaping (AsyncImagePhase) -> Content) { + self.init(id, + check: { cache.cachedImage(image) }, + fetch: { await cache.image(image) }, + transaction: transaction, + content: content) + } + + init(_ id: T, check: @escaping () -> UIImage?, fetch: @escaping () async -> UIImage?, @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P) where Content == _ConditionalContent, I : View, P : View { + self.init(id, check: check, fetch: fetch) { phase in + if let image = phase.image { + content(image) + } else { + placeholder() + } + } + } + + init(_ id: T, check: @escaping () -> UIImage?, fetch: @escaping () async -> UIImage?, transaction: Transaction = Transaction(), @ViewBuilder content: @escaping (AsyncImagePhase) -> Content) { + self.id = id + self.check = check + self.fetch = fetch + self.transaction = transaction + self.content = content + + self._phase = State(wrappedValue: .empty) + + guard let image = check() else { + return + } + let wrapped = Image(uiImage: image) + self._phase = State(wrappedValue: .success(wrapped)) + } + + @Sendable + private func load() async { + guard let image = await fetch() else { + withAnimation(transaction.animation) { + phase = .empty + } + return + } + let wrapped = Image(uiImage: image) + withAnimation(transaction.animation) { + phase = .success(wrapped) + } + } +} diff --git a/Caps/Views/CapRowView.swift b/Caps/Views/CapRowView.swift index 3e66131..921e233 100644 --- a/Caps/Views/CapRowView.swift +++ b/Caps/Views/CapRowView.swift @@ -1,5 +1,4 @@ import SwiftUI -import CachedAsyncImage struct CapRowView: View { @@ -56,7 +55,7 @@ struct CapRowView: View { } }//.padding(.vertical) Spacer() - CachedAsyncImage(url: imageUrl, urlCache: database.imageCache) { image in + CachedCapImage(cap, cap.image, cache: database.images) { image in image.resizable() } placeholder: { ProgressView() diff --git a/Caps/Views/GridView.swift b/Caps/Views/GridView.swift index 043e6bf..85ba0f8 100644 --- a/Caps/Views/GridView.swift +++ b/Caps/Views/GridView.swift @@ -1,13 +1,171 @@ import SwiftUI +import SFSafeSymbols struct GridView: View { + + @EnvironmentObject + var database: Database + + + @AppStorage("currentGridName") + private(set) var currentGridName = "default" + + private var defaultImageGrid: ImageGrid { + .init(columns: 40, capPlacements: database.caps.keys.sorted()) + } + + var imageSize: CGFloat { + CapsApp.thumbnailImageSize + } + + private let verticalInsetFactor: CGFloat = cos(.pi / 6) + + private let minScale: CGFloat = 1.0 + + private let maxScale: CGFloat = 0.5 + + private let cancelButtonSize: CGFloat = 75 + private let cancelIconSize: CGFloat = 25 + + @Binding + var isPresented: Bool + + var image: ImageGrid + + @State var scale: CGFloat = 1.0 + + @State var lastScaleValue: CGFloat = 1.0 + + init(isPresented: Binding) { + self._isPresented = isPresented + self.image = .init(columns: 1, capPlacements: []) + + if let image = database.load(grid: currentGridName) { + self.image = image + } else { + self.image = defaultImageGrid + currentGridName = "default" + } + } + + var columnCount: Int { + image.columns + } + + var capCount: Int { + image.capCount + } + + var imageHeight: CGFloat { + (CGFloat(capCount) / CGFloat(columnCount)).rounded(.up) * verticalInsetFactor * imageSize + (1-verticalInsetFactor) * imageSize + } + + var imageWidth: CGFloat { + imageSize * (CGFloat(columnCount) + 0.5) + } + + var magnificationGesture: some Gesture { + MagnificationGesture() + .onChanged { val in + let delta = val / self.lastScaleValue + self.lastScaleValue = val + self.scale = max(min(self.scale * delta, minScale), maxScale) + } + .onEnded { val in + // without this the next gesture will be broken + self.lastScaleValue = 1.0 + } + } + + var gridView: some View { + let gridItems = Array(repeating: GridItem(.fixed(imageSize), spacing: 0), count: columnCount) + + return LazyVGrid(columns: gridItems, alignment: .leading, spacing: 0) { + ForEach(image.items) { item in + CachedCapImage( + item.id, + check: { cachedImage(item.cap) }, + fetch: { await fetchImage(item.cap) }, + content: { $0.resizable() }, + placeholder: { ProgressView() }) + .frame(width: imageSize, + height: imageSize) + .clipShape(Circle()) + .offset(x: isEvenRow(item.id) ? 0 : imageSize / 2) + .frame(width: imageSize, + height: imageSize * verticalInsetFactor) + } + } + .frame(width: imageWidth) + } + var body: some View { - Text("Grid view") + ZStack { + ScrollView([.vertical, .horizontal]) { + gridView + .scaleEffect(scale) + .frame( + width: imageWidth * scale, + height: imageHeight * scale + ) + } + .gesture(magnificationGesture) + .onDisappear { + database.save(image, named: currentGridName) + } + + VStack { + Spacer() + HStack { + Spacer() + IconButton(action: saveScreenshot, + icon: .squareAndArrowDown, + iconSize: cancelIconSize, + buttonSize: cancelButtonSize) + .padding() + IconButton(action: dismiss, + icon: .xmark, + iconSize: cancelIconSize, + buttonSize: cancelButtonSize) + .padding() + } + } + } + } + + func isEvenRow(_ idx: Int) -> Bool { + (idx / columnCount) % 2 == 0 + } + + private func cachedImage(_ cap: Int) -> UIImage? { + let image = CapImage( + cap: cap, + version: database + .mainImage(for: cap)) + return database.images.cachedImage(image) + } + + private func fetchImage(_ cap: Int) async -> UIImage? { + let image = CapImage( + cap: cap, + version: database + .mainImage(for: cap)) + return await database.images.thumbnail(for: image) + } + + private func dismiss() { + isPresented = false + } + + private func saveScreenshot() { + let image = gridView.snapshot() + UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil) } } struct GridView_Previews: PreviewProvider { static var previews: some View { - GridView() + GridView(isPresented: .constant(true)) + .environmentObject(Database.largeMock) } } diff --git a/Caps/Views/IconButton.swift b/Caps/Views/IconButton.swift new file mode 100644 index 0000000..4e42297 --- /dev/null +++ b/Caps/Views/IconButton.swift @@ -0,0 +1,41 @@ +import SwiftUI +import SFSafeSymbols + +struct IconButton: View { + + let action: () -> Void + + let icon: SFSymbol + + let iconSize: CGFloat + + let buttonSize: CGFloat + + private var padding: CGFloat { + (buttonSize - iconSize) / 2 + } + + private var cornerRadius: CGFloat { + buttonSize / 2 + } + + var body: some View { + Button(action: action) { + Image(systemSymbol: icon) + .resizable() + .frame(width: iconSize, height: iconSize) + .padding(padding) + .background(.thinMaterial) + .cornerRadius(cornerRadius) + } + } +} + +struct IconButton_Previews: PreviewProvider { + static var previews: some View { + IconButton(action: { }, + icon: .xmark, + iconSize: 20, + buttonSize: 25) + } +}