From d9a84ee2df29a34d2f993f9fb8effb7ae84512f9 Mon Sep 17 00:00:00 2001 From: cbaucom Date: Thu, 11 Jun 2026 13:49:33 -0400 Subject: [PATCH 1/6] Add aspect-ratio dimensions to timeline-core Co-Authored-By: Claude Fable 5 --- src/timeline-core/aspect.test.ts | 23 +++++++++++++++++++++++ src/timeline-core/aspect.ts | 19 +++++++++++++++++++ src/timeline-core/index.ts | 2 ++ 3 files changed, 44 insertions(+) create mode 100644 src/timeline-core/aspect.test.ts create mode 100644 src/timeline-core/aspect.ts diff --git a/src/timeline-core/aspect.test.ts b/src/timeline-core/aspect.test.ts new file mode 100644 index 0000000..f8af248 --- /dev/null +++ b/src/timeline-core/aspect.test.ts @@ -0,0 +1,23 @@ +import { describe, it, expect } from 'vitest' +import { DEFAULT_ASPECT_RATIO, dimensionsForAspectRatio } from './aspect' + +describe('dimensionsForAspectRatio', () => { + it('16:9 is a 1920×1080 landscape canvas', () => { + expect(dimensionsForAspectRatio('16:9')).toEqual({ width: 1920, height: 1080 }) + }) + + it('9:16 is a 1080×1920 portrait canvas', () => { + expect(dimensionsForAspectRatio('9:16')).toEqual({ width: 1080, height: 1920 }) + }) + + it('1:1 is a 1080×1080 square canvas', () => { + expect(dimensionsForAspectRatio('1:1')).toEqual({ width: 1080, height: 1080 }) + }) +}) + +describe('DEFAULT_ASPECT_RATIO', () => { + it('defaults to a 16:9 1080p canvas', () => { + expect(DEFAULT_ASPECT_RATIO).toBe('16:9') + expect(dimensionsForAspectRatio(DEFAULT_ASPECT_RATIO)).toEqual({ width: 1920, height: 1080 }) + }) +}) diff --git a/src/timeline-core/aspect.ts b/src/timeline-core/aspect.ts new file mode 100644 index 0000000..09f50f1 --- /dev/null +++ b/src/timeline-core/aspect.ts @@ -0,0 +1,19 @@ +export type AspectRatio = '16:9' | '9:16' | '1:1' + +export const DEFAULT_ASPECT_RATIO: AspectRatio = '16:9' + +export type CanvasDimensions = { + width: number + height: number +} + +export function dimensionsForAspectRatio(ratio: AspectRatio): CanvasDimensions { + switch (ratio) { + case '16:9': + return { width: 1920, height: 1080 } + case '9:16': + return { width: 1080, height: 1920 } + case '1:1': + return { width: 1080, height: 1080 } + } +} diff --git a/src/timeline-core/index.ts b/src/timeline-core/index.ts index e3642fd..bf13005 100644 --- a/src/timeline-core/index.ts +++ b/src/timeline-core/index.ts @@ -1,3 +1,5 @@ +export { DEFAULT_ASPECT_RATIO, dimensionsForAspectRatio } from './aspect' +export type { AspectRatio, CanvasDimensions } from './aspect' export { getMediaType, isSupportedAudio, isSupportedMedia, sortByFilename } from './media' export { moveSlide, toggleExcluded, filterIncluded, createTitleSlide } from './timeline' export { resolve, applyImageDuration, DEFAULT_GLOBAL_SETTINGS, THEMES, applyTheme } from './settings' From 52e680954deb708482f4c5132112f6a98a30d7b5 Mon Sep 17 00:00:00 2001 From: cbaucom Date: Thu, 11 Jun 2026 13:50:40 -0400 Subject: [PATCH 2/6] Persist per-project aspectRatio in slideshow.json Co-Authored-By: Claude Fable 5 --- src/editor-shell/slidePersistence.test.ts | 10 ++++++++++ src/editor-shell/slidePersistence.ts | 4 +++- src/project-store/schema.ts | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/editor-shell/slidePersistence.test.ts b/src/editor-shell/slidePersistence.test.ts index 696c495..b0cc483 100644 --- a/src/editor-shell/slidePersistence.test.ts +++ b/src/editor-shell/slidePersistence.test.ts @@ -53,4 +53,14 @@ describe('slidesToJson', () => { const json = slidesToJson(DEFAULT_GLOBAL_SETTINGS, [makeSlide('a.jpg')], 'track.mp3', null, null) expect(json.soundtrackAttribution).toBeUndefined() }) + + it('includes aspectRatio when provided', () => { + const json = slidesToJson(DEFAULT_GLOBAL_SETTINGS, [makeSlide('a.jpg')], null, null, null, '9:16') + expect(json.aspectRatio).toBe('9:16') + }) + + it('omits aspectRatio when not provided', () => { + const json = slidesToJson(DEFAULT_GLOBAL_SETTINGS, [makeSlide('a.jpg')], null, null, null) + expect(json.aspectRatio).toBeUndefined() + }) }) diff --git a/src/editor-shell/slidePersistence.ts b/src/editor-shell/slidePersistence.ts index b2a3989..9ca5ae2 100644 --- a/src/editor-shell/slidePersistence.ts +++ b/src/editor-shell/slidePersistence.ts @@ -1,6 +1,6 @@ import type { MediaSlide, Slide } from '../timeline-core/types' import { isTitleSlide } from '../timeline-core/types' -import type { GlobalSettings, ThemeName } from '../timeline-core' +import type { AspectRatio, GlobalSettings, ThemeName } from '../timeline-core' import { SCHEMA_VERSION, type SlideshowJson } from '../project-store' import type { JamendoAttribution } from '../jamendo/types' @@ -34,10 +34,12 @@ export function slidesToJson( soundtrackFilename?: string | null, themeName?: ThemeName | null, soundtrackAttribution?: JamendoAttribution | null, + aspectRatio?: AspectRatio | null, ): SlideshowJson { return { globalSettings, schemaVersion: SCHEMA_VERSION, + ...(aspectRatio ? { aspectRatio } : {}), ...(soundtrackFilename ? { soundtrackFilename } : {}), ...(themeName ? { themeName } : {}), ...(soundtrackAttribution ? { soundtrackAttribution } : {}), diff --git a/src/project-store/schema.ts b/src/project-store/schema.ts index 3129af6..0c0e03f 100644 --- a/src/project-store/schema.ts +++ b/src/project-store/schema.ts @@ -1,3 +1,4 @@ +import type { AspectRatio } from '../timeline-core/aspect' import type { GlobalSettings, SlideOverrides, ThemeName } from '../timeline-core/settings' import type { BeatGrid } from '../beat-grid/types' import type { JamendoAttribution } from '../jamendo/types' @@ -28,6 +29,7 @@ export type SerializedTitleSlide = { export type SerializedSlide = SerializedMediaSlide | SerializedTitleSlide export type SlideshowJson = { + aspectRatio?: AspectRatio globalSettings?: GlobalSettings schemaVersion: number slides: SerializedSlide[] From c6b29961a313cacc136c7df33e4464a334dfb223 Mon Sep 17 00:00:00 2001 From: cbaucom Date: Thu, 11 Jun 2026 13:52:24 -0400 Subject: [PATCH 3/6] Wire per-project aspect ratio through editor shell and player Co-Authored-By: Claude Fable 5 --- src/editor-shell/App.tsx | 9 +++++++- src/editor-shell/EditorSidebar.tsx | 8 ++++++- src/editor-shell/GlobalSettingsPanel.tsx | 27 +++++++++++++++++++++++- src/editor-shell/PlayerPane.tsx | 10 ++++----- src/editor-shell/useProject.ts | 12 +++++++---- 5 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/editor-shell/App.tsx b/src/editor-shell/App.tsx index e3c431f..1f78f43 100644 --- a/src/editor-shell/App.tsx +++ b/src/editor-shell/App.tsx @@ -11,7 +11,7 @@ import { createTitleSlide, } from '../timeline-core' import type { GlobalSettings, SlideOverrides, ThemeName } from '../timeline-core' -import { applyTheme } from '../timeline-core' +import { applyTheme, dimensionsForAspectRatio } from '../timeline-core' import { plan, slideIdAtFrame, startFrameForSlideId } from '../sequence-planner' import { AppHeader } from './AppHeader' import { DropImportLayer } from './DropImportLayer' @@ -32,6 +32,8 @@ export function App() { const clearSelection = useCallback(() => setSelectedSlideId(null), []) const project = useProject({ onFolderLoaded: clearSelection }) const { + aspectRatio, + setAspectRatio, audioTracks, globalSettings, setGlobalSettings, @@ -106,6 +108,7 @@ export function App() { [globalSettings, selectedSoundtrack, slides], ) const totalFrames = renderPlan.totalFrames > 0 ? renderPlan.totalFrames : FPS + const canvas = dimensionsForAspectRatio(aspectRatio) const handleFrameChange = useCallback((frame: number) => { setCurrentSlideId(slideIdAtFrame(renderPlan, frame)) @@ -168,6 +171,8 @@ export function App() { void settings: GlobalSettings themeName: ThemeName | null onSettingsChange: (updated: GlobalSettings) => void @@ -26,6 +28,8 @@ type Props = { } export function EditorSidebar({ + aspectRatio, + onAspectRatioChange, settings, themeName, onSettingsChange, @@ -51,6 +55,8 @@ export function EditorSidebar({ Settings = { classic: 'Classic', @@ -19,14 +20,18 @@ const THEME_LABELS: Record = { const THEME_NAMES: ThemeName[] = ['classic', 'energetic', 'plain'] +const ASPECT_RATIOS: AspectRatio[] = ['16:9', '9:16', '1:1'] + type Props = { + aspectRatio: AspectRatio + onAspectRatioChange: (ratio: AspectRatio) => void settings: GlobalSettings onChange: (updated: GlobalSettings) => void onThemeChange: (name: ThemeName) => void themeName: ThemeName | null } -export function GlobalSettingsPanel({ settings, onChange, onThemeChange, themeName }: Props) { +export function GlobalSettingsPanel({ aspectRatio, onAspectRatioChange, settings, onChange, onThemeChange, themeName }: Props) { function set(key: K, value: GlobalSettings[K]) { onChange({ ...settings, [key]: value }) } @@ -53,6 +58,26 @@ export function GlobalSettingsPanel({ settings, onChange, onThemeChange, themeNa +
+ + { + if (value) onAspectRatioChange(value as AspectRatio) + }} + > + {ASPECT_RATIOS.map(ratio => ( + + {ratio} + + ))} + +
+
diff --git a/src/editor-shell/PlayerPane.tsx b/src/editor-shell/PlayerPane.tsx index 040951a..780a1cd 100644 --- a/src/editor-shell/PlayerPane.tsx +++ b/src/editor-shell/PlayerPane.tsx @@ -8,17 +8,17 @@ import type { RenderPlan } from '../sequence-planner' import { SlideshowComposition } from '../composition' export const FPS = 30 -export const COMP_WIDTH = 1920 -export const COMP_HEIGHT = 1080 type Props = { + compositionHeight: number + compositionWidth: number onFrameChange: (frame: number) => void playerRef?: React.RefObject renderPlan: RenderPlan totalFrames: number } -export function PlayerPane({ onFrameChange, playerRef, renderPlan, totalFrames }: Props) { +export function PlayerPane({ compositionHeight, compositionWidth, onFrameChange, playerRef, renderPlan, totalFrames }: Props) { const embeddedHostRef = useRef(null) const presentationHostRef = useRef(null) const fallbackPlayerRef = useRef(null) @@ -93,8 +93,8 @@ export function PlayerPane({ onFrameChange, playerRef, renderPlan, totalFrames } component={SlideshowComposition} controls durationInFrames={totalFrames} - compositionWidth={COMP_WIDTH} - compositionHeight={COMP_HEIGHT} + compositionWidth={compositionWidth} + compositionHeight={compositionHeight} fps={FPS} initialFrame={initialFrame} inputProps={{ plan: renderPlan }} diff --git a/src/editor-shell/useProject.ts b/src/editor-shell/useProject.ts index 133898b..457cb19 100644 --- a/src/editor-shell/useProject.ts +++ b/src/editor-shell/useProject.ts @@ -1,8 +1,8 @@ import { useCallback, useEffect, useRef, useState } from 'react' import type { Slide } from '../timeline-core/types' import { isTitleSlide } from '../timeline-core/types' -import { DEFAULT_GLOBAL_SETTINGS } from '../timeline-core' -import type { GlobalSettings, ThemeName } from '../timeline-core' +import { DEFAULT_ASPECT_RATIO, DEFAULT_GLOBAL_SETTINGS } from '../timeline-core' +import type { AspectRatio, GlobalSettings, ThemeName } from '../timeline-core' import { addRecentProject, enumerateAudioTracks, @@ -37,6 +37,7 @@ type Options = { } export function useProject({ onFolderLoaded }: Options = {}) { + const [aspectRatio, setAspectRatio] = useState(DEFAULT_ASPECT_RATIO) const [audioTracks, setAudioTracks] = useState([]) const [globalSettings, setGlobalSettings] = useState(DEFAULT_GLOBAL_SETTINGS) const [slides, setSlides] = useState([]) @@ -87,9 +88,9 @@ export function useProject({ onFolderLoaded }: Options = {}) { if (!handle) return if (autosaveTimerRef.current) clearTimeout(autosaveTimerRef.current) autosaveTimerRef.current = setTimeout(() => { - saveProject(handle, slidesToJson(globalSettings, slides, soundtrackFilename, themeName, soundtrackAttribution)).catch(console.error) + saveProject(handle, slidesToJson(globalSettings, slides, soundtrackFilename, themeName, soundtrackAttribution, aspectRatio)).catch(console.error) }, AUTOSAVE_DELAY) - }, [globalSettings, slides, soundtrackFilename, soundtrackAttribution, themeName]) + }, [aspectRatio, globalSettings, slides, soundtrackFilename, soundtrackAttribution, themeName]) const loadFolder = useCallback( async (handle: FileSystemDirectoryHandle, savedData?: SlideshowJson) => { @@ -119,6 +120,7 @@ export function useProject({ onFolderLoaded }: Options = {}) { }) pendingAudioRevokeRef.current = audioTracks pendingRevokeRef.current = latestSlidesRef.current + setAspectRatio(savedData?.aspectRatio ?? DEFAULT_ASPECT_RATIO) setAudioTracks(nextAudioTracks) setGlobalSettings(restoredSettings) setThemeName(restoredThemeName) @@ -249,6 +251,8 @@ export function useProject({ onFolderLoaded }: Options = {}) { }, [globalSettings]) return { + aspectRatio, + setAspectRatio, audioTracks, globalSettings, setGlobalSettings, From 5c471da5683519ae10a5e89e8c1beb28ea5c77de Mon Sep 17 00:00:00 2001 From: cbaucom Date: Thu, 11 Jun 2026 13:55:37 -0400 Subject: [PATCH 4/6] Add installable PWA: manifest, app-shell service worker, icons Service worker caches only the same-origin app shell (network-first navigations, stale-while-revalidate assets). Media stays on blob: URLs and Jamendo is cross-origin, so neither is ever cached. Registered in PROD builds only. Co-Authored-By: Claude Fable 5 --- index.html | 2 + public/icons/icon-192.png | Bin 0 -> 8180 bytes public/icons/icon-512.png | Bin 0 -> 40807 bytes public/manifest.webmanifest | 15 +++++++ public/sw.js | 69 +++++++++++++++++++++++++++++++++ scripts/generate-pwa-icons.mjs | 45 +++++++++++++++++++++ src/main.tsx | 7 ++++ 7 files changed, 138 insertions(+) create mode 100644 public/icons/icon-192.png create mode 100644 public/icons/icon-512.png create mode 100644 public/manifest.webmanifest create mode 100644 public/sw.js create mode 100644 scripts/generate-pwa-icons.mjs diff --git a/index.html b/index.html index dfb7022..2a00bbb 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,8 @@ + + Slideshow Maker diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..21c70e678d05f3b6e301023dea0fae2f9be41f6c GIT binary patch literal 8180 zcmdT}^;Z;5v`3IG>0G*5dg+i}Lb{|D=@ulVyL0L8RC)nPMY?0jB?TmwkQ5M59^do+ ziT8e(nR{l=y>sSs=FYu$K5^QbV0;{E92683d{q@i-RH6IzlMeS+;4n+&_+Rdg`%n` zr~k3w#5}0Z*2usjp!mUV)2m8oKCGX#O2L(LJcAEcr8Ekz93C1fz;zwLK`DTt@hUG`MFE?~1zV=aUu14p%TL#eHOR08eGf&pB54w zMn4|!Y`yp8LkzP+)kAQTS4N&ZZd>Bcy!p)iEsA!-23{bGwQhB!e30?HK@^0)c+-o_ z(`8N@#x5Qi4t94=t|%f>&V1S36U62IPNRz> zYI1|s`osan5;y#uJQ7{4fZQ%ORyFUD35F@LpamVLf=voR>_riJsYiOFVpXA(Qj?N{ zi{mJ1I}?>(yuh%Yv%Si7q?exhFvp_GlvcY!oHT*R7!+P9i8Y2Q?uX3eWvS9!i2%Q1 zt2~C~WZ49p^b6E!vS_E*K~sv`H-OgA-6^ZX zy~<@6V~K+fqz0`E>oF<+aKE(j8TVmnR~_^cFGaQy^6|{^-&BJ(BX?pdZ9ux5K8Hgd z>D;+lKf8ztOSjqRA`Mt5)#Z(> zs=RW*qNEFhNZbQN9lhV|yi!R=_qHVdDYN!4S(5DtDEKiXLv&|ZN^X0+M}mEg$$Hv< z$7vSnA@5~LjQi!Cch+8ssv(!O?@ev_+){a~*CPLlLIm?{6pak*!?hPzIal0@I^dK& zR*5)LC>&_>HIOk8uRlfhQ~IQu5+*p0nZY_Oa3s*`o=M^Fp=Ww*<|f4kb9A-qyVSwQ zzHaZC>!B35J3kBR-}|bR!k>^#^d$Z&9bs!|wx0lr0X^Qm=RF~0 z^M`|N70GAZTJ$7;6Wl)*eHY{V{tnCQCVFFfJ$;UYH8$w}!N<<~XO+q5lv2m9#bNh$ zAw+_BQ7#L%V*v9$s>)GrIsi!C44Uwx|Q8n#vfDNSHDva znXcPdvP?RtTm@JqWq^?3iQEC(iubV>&_X0jBZtrEXC3IdM!bdY()pOy(5lj`S(z= zZ}5UZ78BKxB%EPU{mvYWQmRB7^1s+}X?evIcMJzbE7x398PX+r{c<0Lb<8uEyHQ|2 z`)C4UiHL{htLQ64#`gM>_^5PO+E9fwO1`-M8zG^9QcEpY0O7TLk zn&gUuRnRMVwMIbf45!r7R8A;KKp;=@JUOe+1~4xoC2wn0C&63A*#39%v@ANZ!z}?J z?jIg-CV9g&EpBfP_VMYYN_ZDFY)>-k96Q5Ku zVo#zYi_KMb#Wf7q-|$C`?FuGBc=g7H6HYsN^+7;3Ku=SeIdO)bl>{D|r%ch=LE<=R zGT%3p#vi`Ml-)V{W9N5348@nmzC_}~Q&X9*e%r;^`6mCX}!4|fSX?bb*s%e;d6dmF6%e=&rhsZHBG<;IGPPwWgexGAl4#9BI)9&LBj(Nkx#i4VvS^HlYXeCD@{mXds7)rf%!ay`i= zG@dka=r3A!CGJ7vX)-eJ*&4}CRhBCy7z~?PSYOke%#)}$A^8Tene*F&v&1eev;M0sU47&KNUIy6FDGy?(BL z*MM@d@pP}!vQK$fj2GGJ2x(P7?sZ(EfPp6UfKb1iMFrK+cgC4=oHvDlfumF$pk{{u zdHjnQb8ddqmtF6U*|jJO*=i}|vr80AhUK!<%rq*@=DuChHgR{|mb*HnC*s$@n4m0d z_BIBw*Tv+D&GXmb?nlXv5(0rI8J>^n*Ce@>k=vHjJ~h2TXC=EgWI2qeh0XuWXRZ!n z=b_*9|5nEwb7rdH&STc$AgBx3l!5X7$6i6oQu+Lij?S!`i!C-NV)cR!YiR`OR7ybsk z;WTbc9j`QpnuXv57L40u9J0PW^~K}5;G~W!7Z*Fj-hj%J=>Ug5|1Z~8iL6&1x&{n&L(S?j>t<{gHu9pN z^5&+*PKH#fzl{5qZHiD$f;xYxF8)R)6GdI$Rp>zWjNbZJ)X%CVu)Ny>TF8{<8l$4m zpk%*E>!oHlPN%`9VmI6=cxDwn6BN5$uiv1^?SH~!0R#XVrY5ogVWt((5c^{4rPWwa zxsrN1FXz-Bp2s{-gew)G8&A(ZtIOr*z-?gCKE;1KfX^s|cC`a^4WN^8AXnAow?fm5 z`NcmxaAIs0C72yz05CgyOBzw}3uY%H7M+IJs1D$S9ME(0w~zTm*a8$LMue);OZK_I zN!PYM;5MfmG_oIK2z>^mXEbH`f>J>U7;opet^z@!&WdPgZzi|vO#e=-B!^(T$gbf? zgV+)6?<}Of!M6SRu2j!Mw85aI9TCh(M;Z}xi~YT%*1j0)fN|f7I1TxAPXV<27Jyo~ z_jFjX2fL2?$U8lxVt&qggR$s6@dM$HJ{>4}k=|@R* zu9o3V;$hGD6-j9(D*GUC@?K{*!cl|6SyS8d1u?{!NtQUm=7m&wdptQYs<(;Wj@i#a z?1p24FnGGmMJiJo%w2mjVB<{!%t zt&T!}C!0I{Zm_RWxoCLc4+J_A+r!!fm7X%I|HiQ=J@L|rfaH5oB$1j*^3kclA8MEq zsH}|zDNs(Ts-q$yydfYrc zAE)v8EI~uVKNvM`W;5vsuo;2kt|U6t)c|?(@a}|jR^o*tu*X|nV>xZD-HaL+aX#jg zWYZqbeI6&*G4|42iBudus?;U0LS_*&_11u7cy(44d z-b4y?E-DUES-E$9j|)suru3pIbe4@~#Z51bv{qG7B#Uw+GR z+QTcGeu1|Kzvl&aS#B$u;MXytdUyOhEUv?9HyZWf@<243Li$^inV3OyX-=H>{<+?^ zt;&!J@r&pMd5{Ceg~uBw9VsR``#E~Xj`H7q*l`5p!G{W0qQ< zo;c6n20WE*9d(RydVa?qyB2dGXd3Tc*G+4Kky_|&Z&bEXrJdp}g`75xLtz?uZ-ZyL zTvYBA-oy!+~awJz}7HfVmgaV0ton0P?2ZABUldZC~Y7XJ4apjx*) z9D=5(!=^ev_9$y4DNHMfaP(oLD+mhlY_gQ58$A)f`W(<2(%QuPLK=PA z8EdJEp2uQLFRCE=9#zX&+&jhfby-S~v#>g=bAl{;gJhD6N$w25$tGgpMJ6++<$TFr z(d;VYhuj-_HJB_(cZii$t%ekqik9SC=FU0+Vx(T&kt&8NY$ED;h8|}{U=jw|E#@aJ;e|u{97vCZmA70?B;vRwDk6vedjtw>^a6Q|6t1UfkTxEI7e~hmIG>dn&V%#g!2#&*0Z7%wj{lC z3D@{^>xV0`b_s8nXbQrOn3+F8>@&voN_BW`XuyPXorZy8xmKL@HGg)d1EWiR4t+RN$1Wa3!2t>>6C<~{z#<_H-H1xhF2X{^Lgh9Hu zdsoA_Zni#=X!`Cg_WBvKCnKx8THI^tEX zU3K&<>m4T)=Fv!#%bG93xD3^btY<*iItDW1ozvl)X((??n(Kv>N^7QAF@eP|R zrFn&|zL{? z&PYeBCZK;xLZPVxAp9409#RMTa}iY)?gw;KI9UL8pB!Cq1PCDH4f^lw1CeEj;uC?_ zFC_+gA2Kcw8~EoU%wY-Pot%uyqK3_7xiu@qti2htlS>yXpXReJne@9`%p^j?#di}O zoPlfS@c=c>Vr*(w0;SEq1n-}cH~W<|XOAdTQu{HojgCG-1yEEptDVb_aGdOwn9%$7 z+Z?h5r^&5V;=i)(W4<7xdOL>7G*%@61n9l^MPnOrZ44OAQ6e}oVNi?RH&S}nCZG(R z_(t~c$>Zv_b0g(21j|#t5>)TvIxX^6g{w}$dHGXtyu;-J ztWU|gc0oHmcWyj;6i`aEpq0R66#C1N#BJElJ05L#;@1zVTexo{b?7P4BwE*jcctahpUt! zF?x#?Fp28zM&~=w=hH_k6}Gt&sJO4;$L%IztJ`94lAFZEh0VTqrnQns@( z*BC#o62A=&*(bF0Fk!))3MmYx0X&YMixAN?q5JRM5f)$F8N+L>2hKr~>sA^i?N$|^ zB#~-*0z3l$;$1_2DzBIP;T?^40Hg;FigMj3@%diFk zH4pm}egAatE7D``6|WSI=M=|&mZR^;0nb7aBJiGGqoRnSq0^z{p$RWj_CS~-z@+*r zyP}XP0lZshj~J7!LRProXC2o9($3c;TUd*AXR(N@ST~!N(Q9AeE||i1KkU@F*R<6U zqBazqIm(vne5dn-96~mH{=&bmVUj#QT-w;JyJ14A`&1IW2wn%Rlz|&+Kc(v7+b+2d z3`6{IU^SwUXx!$$SKpBy+uH>6#q={qG#GtClHx^RlSnhvF1P+eof1u|BBXjf@NQTg zvnS+;0lU@u5vK+2Us^bp*TeED?YR+hGOn;~e7#og=98GTK>a6UMh^gO?A`|DS zxS#|?ON~!iec#(37`Y_~rifh3g|E_Y9sD&FIa#?J)UEaUmu&*wr@_~~z)tx*qm)_X>S8b3f>5BV$4B+#6T0dB6- z$MLVvku8X13O%|oE?DiNv;6Gq!`$(h8Ra;0=S#aQLf|d40Z+DVsuCpa-AU1xEp_k> zp0LByx0;vtsvRfaU*jJhruz6}Bl_bz0N4)ZMkaNHcp4WvEfB4T6TEttvlO+G;>dvP zvyY&4dVcDH(kjGWcu&KhenDPVwTMM|O_J!#kE=-m2QE*Y!lL`PPq>;$SJNEY_+*;VWCSNh;wpp1;iKF|pgoGM_Ks$h2gQz=B;PT$FMYZ~@`j0iC z0WOGa$#TOgOsF)RaYC6#z~P4N4ZLPoo4w$yCopyBrncr~9!A}U4)e=^a=kzt)z&mo znThiK`%?0?>awmT&e$P@6^dI!LYRaIhFX`hO)bXS8!E8r`>#%0ugDJ{y`I%kkxG+L zBUK`KT<8R~!;pSb;lr7P`yM*z8I(NK8%@EvRi~t~9J)}DQ!xJ*x#^JY7x#@5Gi9C1 zi?Uj%4)!)+zqzu3J2KJIRJHYXUzsS+IH$0xP~4bGnTcpHQJ1+ngoFolv?=!QQfub3 zJFgDUEa;sam-Rbi+3wg*k$lwm8_Nk_PCGC9FdgwrOiGZc*!XNgE@u-btMT&^L)Ud^ zTdN_bk`AuVYn-!|0Kb8Wx)OHHf%DSpEiI#<-;1qw+SnlUf>m+@Hd8TWlHO!NptAiw z&y+#E7uP4;eAMFvZy7#|*5tR+n5M{kG^0u5XW#jRo`-rMXs)>T6OC(hVLLzjtb6Z^ zW76|=bNsO~yWR9L7CuTTARalUuyHlERi3{@7f@qT!j5Lt!2?(~c6r=%JlYLbwCMHz z=NbK)d=XVcK|o-|`Yx4{Wlp*fUBZ94dP#oze58!nff_#BvykaML_g)3)?aNN+Hqx#y3<)@YsQljNyH9)1|QxWPoGNlsU_C2^HiGdA7V zWhf=S=RRD?R{z$i{J$8710rG0%wc&p!NKk`?8|k}*gmUkVkwez7Bk<5AU`pOydle! z)!TmMS$mCrJ;;pN*zvHT5B(1=p4U5GYhw< z7sgXZX?*_&e%jkUpS=?Oy7%Gh**E$>R%FxM+L&*ixnyrkvR-X${W;uB+5exy`L}1v n{^==MOM3TN!1%9FM?^D2r#(+4qpO^S}-s)FgZyv4ey+bcbhqf zbr)h7%gfg?aqKF#Vm7!E*`Qsx7%Wi;3Q;WMHc7ekqgGvnYKw^?ruB4cDMD0RkG0># zzb~Q^PEN##3tL~6Wu9d8(rxrebpILuG{?Y_!XRS9rU}7=3xWbigOXB|{GI!8IWe7P zw{s;1Rb_~Lm8XOjPKS?qr=Szcs+nCS3Gac^$0|c3NaY7OAGxE!og?IyE;>ZY$SPbC zCt&u+xanTQ%==lLL|VUwLhU2h0hDQZgY}&I#TLFWGaQ)&R*Jm|j->x8-%Ecr8klM{ zD>D<#vn_h}cl@4^CTaM6w?DgulHIXbFG8)v^~vy!DUp5qK)_{kt+j2)@8C|axtqAI zmH6$>DC3}9_-;M&bCdM_!YF-|7{&j;lr{@|`h6Stae4lk9@0Jl*rtUdcO=4s>hUjL zw-L;>2Kpg{Hfrdwk)cG{(aaZrRw~Z5`a#DKeH|yTpG)yGrlBX;pj|JQQSCo2jFkuF zaLMCZ^vO=LvCM>GJI|J9vWU!w-~M>z)M3so`Eiv8M17|V9%tX_qoOB-Bj6_=dE(4x zp%d1mdiq^&Mvdw!M{BML;q=exHRCENN7GDP}k1rd9b8l8U(NalvA&VI9Xe^(MM)|P553R-8oh223Av-l%P#8ER`TVP3dDV*GGrDJfnEm5&raLV@4;QCHdtauo6JN9; z6sQiYpo2+c2L%Ng83UtmjbJXy!=NnoTZH{p{O8(brDRPuDgrVaJ4 z`;HUF0BXWwo7ni&((a3C#4Hy1G9dI6O0Y%ZK&lUzZf>0MXKX#n$nF+7Ts?gk$`J%T z^ebh6zf9pGcQ;x2E1xN$5YkSd%RX$Ms^K&8C#=m@qc!*%t|3q0%=^PzhI_d!HAjeP~68VF1oAvn{*-Wa6I~oDTEzGLelzd7puS9f+j3mD{ zPq<_-7X^7)Jl=E+0mp$4w1|V&+I58=y16iyWr{>>O65P6My~JN3+fkY6rfA?h*ftr(^vp8crloQ&#!-m=BK`K1D{2gsToos?x;#()Muk z>CG*VrH>+B0G%1kcL4R|^Jw6FheV?vB~DU=ts9OqAfw1h$z5zA0i(lM&j$4pJW($y zIc*6#S~@FPxW*G@v-*f8$8neiEsj_)7EZ(=uD(i2B95CTQ7wH4AlAca+%Csuyi3^_ zus`j^yg=gLSO{1Hrf=v9EsS^a{c|m*UYEGGUO=;!wjBchpb7QeNadEoNn9MmHXr*cG zF$DfwK-?hE>a!C2z|3#0Lhb~)06C=>Ic2Q1AvXY#yQD_pn<*$Q%zHG>HnhGS7r*z1#+UJ^>Tuj4#j8($?vW4fZ80BTS5h9BmDL3$nk z3t3!m%XDW$MarQM^TJ|VGk!kLThBv-tlaL0_F?GHjl~J-BCYT7F?kQvOMa?-%XiB+ zM_#+X<*|XsG`da=l<&khJ4L1%!u~sY*zB>k66GAOpC!laVvD1X`c~ml)KG`#L7@fD zby+D3)cTPhUfwb_>v%6ouA2h#Yn?9g_EV`!YcJAMY3(XOGPm?pH@+{Ezg05(B+4l@wM`9(@ z6o4b;TNVU%1&4|p4XE7@+(%zErKu;9LNg28-; z#PAyb&zlQ=m4v@X?LC-1uyI`GQ^^J4GXt~%P3xa1(&fS1zq~u%`_JpP<3u$%O{qtr z3iU|H)+k$&nI>qSICL9kFy`WVVgI6&|M_)JCa}FI6zdoTqU0aNb~m9eEmYX`8z-*^ zdLB)|4rdgbz9jd;=G#7FT$jw;{|Kx;p>8(?<03V!$G=JJ)=ET}HseRkSoDo+Vi zPl1uuAwf#%Ncw%1#ZQ;Ecp7jx&DtQskIALFyFcr)WPLp{TmLzBbrB=##nlPx{O!O) zd*`j&ZYLxW-R$K7|2&y*XwS`Mg`Y-In~5gIp*ocI&x$R`Mt3=jvMnH%-cFS-Z`NW7 zW1+TWZ!JMpOK&W=@<=`l+!6W!mGY(c$&J^-S$s4HjT)EdgKN*-$C487+~ z=vH0iQ(~b#WgfzLb^p=mxlicIr%9xPU8zfu*%N<$1n&ZlFe}t6J#0$#0b^HBtpn>7 z5%3QYS0Z4S9(|;EZ1;OEO;i_SfgvmJ?!QCzM0-u3Fr|)%tPOmg0aQ*GR8vs1M!(~8 zoHY;`o=)nn=?-3=jfGO%s&($1-ioVQ({9Gi zGqKn9wE#8=xj)Vb3Cz1AieAAXKMRe_<jZeNNTt|6AUxCR|80j1W%TVBRSkU ze2@;G!C4*=U8~P}^zk~xpFNbS4S2g7-)vl8J5g6ZV9;F2`1Y^PqS$H!EOo+wQd5{l?2e&TY3cyvd zmBd(mQibfv0~%0HDnrKeoBO8$TlB|iB(HQa>8|!X8U~?GFO5cSd#Mt5BHjg3j^WHT zG-C=b*>CqJ!f^;{Jg|-IM7in0JGuz1DP&;}D>xr#o6`J?`#)Mh`1Qq`bBM?btaM-j z=Et%6XU(ttF4(WqgWW6jk+y2+o02o%qZwo*K%JQ@6#^9T;%}zQpBfYuh_nzza2T() zAz%XC`2LXn;bFh0OJ>Z%o+J<3#3d~{N!O`D9M#=q^+%L}>K)Tw!4T@_aTq_8^hTLh zZkl(#F0O`c>zp`CKutLwIknNxu(7^ykb$=tWn@_gvJBt_Q@U@lbdXNmn_QVbkmU3c zF&g1vc1)%Zy!X@GLlw9#;&;4b{m>UX2jQ%}=drr&j_{e{H+4W$E!l5p7JdF~TnI@< z?)n_0P~6qV%cPu`q>l?B^+SZa$tCBvXm^BG@d$F$32~QoQLg$kUeQQw0lY01Yr*#2c&Brtps4=mrn@ z*LFrp^rzKIT!Yaa$Z{?rS}8)RIs8Rb{3^Kqz~S04m8>8k=%ET17E{B>xaH+ps)hyUF7{B_Yv4u2))ZP&~`@ZeY*T@l6o7LhWOJ{L$bc1 z#KXi{UnW{}jM*37Z>$^fNuzXJHEO)S1ADX#oy(&ZKG=9?1{U%MU8(64_4$M&_h1~R z3`0EG+Zc6pFY)u^ZD)bA6^N7RJGS-O!x1In)X*Nim0U^|_bc*2&W&;p>o{#=Vqrk+#Qy9KLB#dt7vnM27et(Rc+Iiv~C zs?^`98|YrqN^zsJ-b}Sk$WM3U^i zg_@z^*@y%@klPv4@%nd7&7)=~_h1r3)M9=g^5H)_Tcur%)z~$H&Iu<9lzh*S_K{+E z$cTSu9SpSDinj)DKe%x?F!Fy`&LHO%id9ZSLTl)jvtTqNVUEhD{O+)kcmyD_7hdvD z*-g3(p_C7sS%HzoBpP%c9Why|u_w84%ZmaJP3{SssG=wp8jN#_<$*g%j6uIyYT(Uf z@#PT@>PtiMM6j$(TH^eT5ID0{?g;gZ&772jqnz9{EVK7cCLDKnK5wFV?VHT)`C@0-Y zz5Pf-QmO8GXyb^JXbjxpWAL?|na00&pH;-+cxx;=$b^?=f3{nKGG=<_P=!~ToE76p zL>zVb3-tr*H|r6pq~QbV_Wif)_bbD%%9tDfCDJT5*Y#*nD|+%?nZ;fiujGq1=8>Yh z5`~J(GPBgx1*yo8*IPROTOYIf6swfeDjyrr`c7;{EU5E}){6;+tF4@_pQq$l3cR>K z4#+3BUx(4x99P}TZGSVDn$EC%?o=_Y=a*BPRRfvLAD|N)tDs|C56eo3OpyY5&5O&k zJB6LHUMp@#u}DL=@vjgG#ke%wT$aAzr}gojwAAIX=&L5OecTfU9s#tCVt#1wm8-;{ zC%u`eA)ORH7P!dLc-ddySm_p3ceuCiG2vg>j70nWIBjw;56@mm{rxsc7;~;@>Z!l6 zf~>@)3uXDrd+p##QK{({mmwX_!ZeYUA}scseM#&ca#{CN;itIolP&|`7&boA*X*WC z-QZSi#MYgFI6O81+AmLTUbN+x?LvL>b7-(pROWfS-scwrc@;zNDvfv%ef)IlzKz(G zl=Dou(#yaWG= zAvC<&_m`jg=c+i!&CBbdpXx%8?LyKD&}4P99P{iA$2k-qV1T6^p-P2#i`3%oD_573or zl=9XXu))SRLj9-!qp_3PK6jT>45~L>K}N@koYgU*-+VxE%ZhcW2pQmB@%bjUyY>MZa{m`74HK08Eydr1}7E26Qhn;vV z`YkqD=vyYv2##3cAP1@A)=T7Y=Ac>D`i4Gp3z!Q8km`Bg&Yjwi%ldU2V@@Cy2XqfF zfnpREBeEM}(nF9mN85C{EDa;iSi`NnQWZkLI&v224Z5FhvKE-gF)LCGyqcB0aMFm< zx0Sa#)#K}PTtv@J@@GQJ5A8KNt(_0Ul@UT_r=oC`3O%H;@x?NC*-Izs7m08}m!>yv zfbWX7n=8k8FFjPBA+E`k2!6K4lW8-yYgy!XO1}Zs{1z5(C6>Hcnf2(w~@^gI+2j8wU>3t3tb1-$oR+K zC`1+;J|+GPB>V}0@pce+M)aUwIUikc2}!}vHF~uPacF+9+=x=Bzw?*Mt*$pUaMV!`ZrdV>3qUSD>&#=F^-IBAE9iWYVIM< zK}}FtjA$uQ2?2T1S~~Z4x;pw&ScceDqt)ayiVCfDg^8*n?;>6d-4J21xoi4?op0-T zlLpUV;yo1Pc|RB6*=&&cWa;DMPw=fb>|*E)C(E>boC@)^5Z@xiy|F|TII-RoVpL!F zE4}ttGaL7igN&DC6!2Bf+D|C*rBS2muX*0gR$sShHy}DWuU~u}g%2hbl=NoQi=TR= zxii%{7%~p)!!ZY5hf7GLNQ7{dC@}f8pQSYyHlkuhzUH#NI`)wNUv9yU##uNSi zbB+@Fz`D=)Q_Dl7TUn`ELOse1#{GV~QuM-@mT;rw3+_;OFuSI4_2s^~oMk^g4( z>%;d;5+n}Vak)no`F9rmo|eKp@tTm27F2?<7e%u5%{HRS{0%Rxx*TF8#wR7Sp&p-@ z5g8hn*@xF@bBnGTr_dy-opEDkia`jaqAGPdosN(`k+LryRO$}h`xCV0?T%zw^Q!xM zKB^eNtr|5LmJ0Tg#*!JIRf*R7HjS=L0v1VVc7Kr|Mb#uEjxW}Uomf2pCdWLss1kIQ zc;OawOx?5B_C8^F>|bwj(1B-#I{6E0lHq=D#+k{IbB=C zTO=i;E=o=>aY_$$Vlf3W-Hc&rSFAn4Pm{0I$NdfIU3D<{2HZW1AioKdV$;nS=djps+ zApHoIx9~)%{Q4>Ad^BQ=Hb1g95U~JP+Z^)wDzj8$vjKN>t)JtOg_qM4R%LTwWbslg zo0dkhY2C08RXqU;=p;7i0WVexN|_In3rSjszX7SZq~)EgB#DI9dqb?=D&-ulu4$_m zb-PUNIVV7oNsJo3wn#}F(6lxk&f?vOjccRA7rTY%?Ro&-#~?Oi%w z+h@xB2^Lv$S)VCWvOSjCG<><9csuQ$75XAmm9ba74^DnV_g7|^q$^?i`~GwT(S6b# zYY+scLoy7enl#o}upTgSZ8CcY<$dg{TF4X*>?Z%C1@Q9ef(q!V^zWxP_3u5Yip`iq z{@_>Dm~6xEP8cbeT6;=fyJwwI@U@&9?Qz@ssGk&&rQA;!BnE(vJhx{H-1y#n!_E%- zL$5^O?t3c0ON&#aYbUCjn7OT$)Y^SEtZmKT74nqY8vGQJkh?RDo}*ho9=~+3aypi^ z&-=&j)6w=FZ&9^a^NhNFx|lFh)6O6F^7=-pS-A7&ZU?0`O;&6_wc2a36szc+uw0CI zE;VD*Wma;4l6DoCKXY^0rxwebB(AZm zEOx%)jZ7EBh3xR|yjw%ju&IZ4`tSuY12*pHT$u)|(g@3eb*tduGmzKc*@Y9fP<&6j z1g@Ir&A^m3b80?^5OKTmN0|}X^hATziYrYx4iUiBS^1bja!eJMqLob8Ng+!suMw<0 z4)}u(^dKInduim&-tINOVSn0c{--RtOnFA(keiG!ZTUNQNzNwVIMnj5l)XwwP1%|B zE%gaQrTdtcZ7qO#@kWr_f%Xu-db$(=)0lRJ1E?56TmmlZRDwf2bk?5hHdpfOXeki; z`N!B+Jsxm$b93!8Bd}?`Zyw@0b@;0LLx!~dR(zztScA=1w1hQt=126NcpZNLg(uJ2 zk&Ouwfr}0rXa{JV4|}wNGG1z*N*i+x)sp^eTOy7SpTUy z`^x2Nxx0{qgsJ#4D|4gJF52Za}9@4E2MaiSf+u>hom8nrV8oVlw(Oi zm4k>&swe`-UV;5K#IAYYr&WuI&)}bkO8#@Hoi~b?)ao{T>O^9Gt2dhka|oxn~b`HPb8`#yJPfDQ?+hGLcaV6g%#Y(Kl_DL;k6MLm6;3dcphJ8M7+h3ekr z`N!v#{eu&)E?Azc_j~*|k@sr|K#C?765VhRhCM_8yBeYbhV}!CYdIhyi?aKww%KJ; z9VyN{xdxKlWs}UmSg3UyE~VR&2T}-<>)!Yj*TLCSfE6X#5c!4l;6g;_`?D%nLd&px zPMBX{DR)=g3%Gn{S>U~5jrBVAl6)9{BlUK}9! zgCw<{PUenDfYw^Y;4?Z>h-_XKTQlI%o0I%kwNh4u!ZFTOp+~Mzg&#G(=O%#6-nBP) z)cPX?iV}Iu+bnCAg`VxG5MWVYxf9IK-zxdOyeh`Ih&5<~E6}&+f2$y}49t%gN9)kthoS5tb2_g<6=cPSW>Ux;XBvhVu5w4ynVkg7x2|aWddEmWk7I$?Yb{0-k zsVnl&bG}o2w}TVCzu zQpei`F6md?{>q2>4|7Ab4PU?QT(@Me-Wg8FCIL{PpaLR!@T$YLoOT_^x@cyUTe(b*)#7Z>(8xR0Sy?K*iY3;-|-GY|gMRul7jHdx}ON0|BNjRxJ#syiWwcVD|^ zRCQbe;6V{;@L_xl;(7+Nx(ONx1OA-ca|#SR_UD=T!Uv53!mA$O)!y^qgAkm7mSOr! zTWu#l57w^05uQcUzvSc}iIY$5?OKQD-I#Lp!1u;R&lj@9_p{ zpOavH(Luad0?v^LyC!xGt(9ssRAz1hKg)T})9jFEIG?RJjuPp_$4GNlD0pk!9Y48J z)t`t$Xs>+K&~6*kLoD)-9d{ zvl^HGA4TXh9KW|(hYv2tuNGYJ*?ak310;;Mc^*k0E!wi6H*2t}=A&1_a=Ak|{ zE+idvN`L*jw;(EAo`2bejHO{pcaOAB`59rqe*GzEc%44;YQIIm45lo+?L7OX8tD(; zF z(WzteF-d%Cv(UfYpfpBl-|is+t>oO5`|Ykyv8TC1Ry7*##k^JDJbEQuLj(6Lqh&B% zdN=o%4=|RgR=bz?l$tcj=q>Zhxqeb#*qiy3cOH04t!?BmZ7pA%tUCg8t*pPuk&tf{ zokke)Vq?OZyaWb_z*mV-jLIF7n%H4XJ3kiR$Vl0EuqRJS(M7@7PvDt-rrn2#)O z&a^b7jW!zaNMp3U{uztQn7^;oPQ`>CEmew30=UnGpBHP|ZIrT~+ty0y6O5L0yYef3 zx-IN5)e?xeogl!Wq+}tPaINW?tyZQk&rj4SqdWsj18%G~U!2~)W+~Std%pFb6W_M} zmD%|uo^Y%%=sw(G-0MkoRkeu6uFxL~_vXL^pwEPE=A2rV&Gk3Um0*AG8&!GQ#u?Q> zC@Lp)${de6*-AeoD&J+a3n@z?grt^u6mLRhA9qeXZo;A}7K5%Ije8MXuLV!~bW7?K zj_QT6|C!hF5w6p1(oJWmOhX`w#NwSvANkQ8QNEieX~;Z7sS03`m1Gq+8^;JJuozO> z`($6n&L?5(^%=H9CZYldkI6SbO|isI@3~^? ztuQEN$8(+AWML%LoNo0Mnb11RYFx>*bYw9O2p3Z8za3q5PO z3djTjP)|G-n8KQGQ5jgD3;Yg`zwoT@Yw(vx%a6-i#KoDfyzw@jLKwBVroCC%+hyN|HBA08RE6aSzP@tjVQ;FezIzCJzr8YMM} zr$$mNus^y~qutmO;;r^k!RM~JeuKtPS*e)?iE%y0Y?La8?5n7!1irOOAh+d$Okr&g z=uMhqiTUPpHE>?nkqK4xWHXy028l^&l-azH(RAR3AfL4#v;4)7rdO% zsze88DRmmUhzzUZ+JL@0w-G9)#COO5Ty7^&7TJC{{GVC8Y_m$~&QTbn&3tOJ!15q zR-YPQMXi_NE6?D)-|xTe;LT!veyg@igOKZNl#g(svpCT3Cb0^031%Pt3$-_=-VC0Pj{yG#o+qO2JDB5P{ogG2jc|M5(Mt|lJ*{7$L(?}ej8cUoi7c5CO-`v)eCT;WuLbuiJ#LQxG26!e?b zm1V24ZA+O?*&D@A8>V5AP?N^x{}w=?+{sc%H9E6CuTIzHU^2`==;`J5o?WfG6HkAn ztd^pQRic~shl8I0zBg6i4r*Wb6}~rlqO-xc-JFJ#CV$oDf5L=D%$-=nm@%vRpKX1Y zlhtoKes79bBedsAVG|Ps$%mAunKi(>hJ$K?LlXhlh-DINC%W_%^L$f^^)hrFta~hH zuhvCSlT7+3QknGgto^7Wh~v!v>2z_Qh1?QHBrS`))%FLCg_{8xJgt0`t)>>`RHplI zm-usx3X1&5P0oB~sq*YvApCMO{>@A+$^mmg@YCp%s~Rd?BUZp)oKoI3yM3mDlb2~c z#w@O;_h7G<`<*9(IiVn0l=|L>a?(zhe+8?d^fh?MdsTHN#W+r5Mx!pBl3|F&yOQT) zIYZPVUX9G%A=Vsboq(f6JJ&+GOewE&?Y!_i~j-&tIf;qhOmjsK$s+^yCJl?$jn?$wVw(FY0$<@Gt2;_U@iizlRY;bM$O^NA$raTC#0yPIvqV7 zpHc}1^*ak@o`AIffiSncy)-BG?OEE8aBN=~r57`M#W;aKT(!q=DnDcAZCBu4^-!_N z(^Lr-oh0siVopAGRaM6=8?GcsCq>AQsuIsgHV+5|?c1{LddcVOak^QV>gLfAs6v1i zuH0s3`M$a@vh!KT6G&jh`Gvi3-G4UnfhT$2C_CM;49v+U_5?TKo~nofFIK0amr6 z5v@8+_eXk~MudmUCr%tGeP?QW)W)FTJk-=kN6Ef=J}-_zFqP3%p@E3P&r$L@m+0~z zSp5v|P^)e})9ljC8A@ux8qq~d{^7NpQzqNB+UDv1kecLNh^y*=rWh zzSGJd)q6DTfb6AVXgOxq#w3nXkQu_+QY( zgM1QF(9aPDFu`*8orNZOL5V)CENC-FeSK!$O*yQ)TJzkj5SnUZmzv6J=J%ZdTD{-b1s>7vLC$*0vaF0#|n(kg%L|UgMri&nK%FT z7u^EowqGy*^RWDvhkKt4v_F62lJ;(zv+JQP>D_i6whis-j9lXAzkAg?B^`kNVfNz;mIv zFl0p0cAMlcS&Pi|OY1NDPqFj+PSO3|@yo|jW2&+3wBz?CI7GE`GcF62n0cSim#FK{ zH|-9FYS7Crt89Xf2i&Aj)02fwuOa#4h0vAFdEd>+SEt6iAM#Gx8SpPgA2UX8Pf*s? z5sBQf9LkB(yChLKV>01hb3tQ85|e<6t;lA=+F#%WZegN;K(%k>#MUNm9F!8;g%{cK zLBg#|vadVCQw z_OE>smtl$39l!5a+?9J;pBae=S0N{BgB=Oh%Tu<0RAo6bugfgpWl=F&qI#iUmqu}S zl=3($$0}wErF2Xc5F3dDkE1Q#X@9}T>**TsO1R$ook@1~nra0zJ=U56t)wTV$2ehq zBel{~lec1+I`YJGArmiA=}=-`ZF5SgU})@yQa6SKEKJ^-Kqp7zWjJXQX53zdZ=_C; zk4SX--YfOvLsxbWPJ%Rdio0WoJ6`uYU1iFQi-Qb;Mv7pS0bsNrW9;@^=3$5lgus}i z!JBmQhCdd^SWhC$5y?lSqn_wn;@WCTgX)!;ofMh;ne#3q^U|E!DfRvzxEuNfmCCRc z;t3=Yo%>v|e8>_fdi0^taUu=F$U;jkx@-%qd?&x9SLKT$!i+o@xc!KMU06)H?8FX2ZXE11E2H1>sxh9K8U;Dgb+chgD!XNzO?<63HXF z5?tsohkcIbUi~(UAeAW|+%`;|ps-PNgBC8u4cEX2TdQQgJ#;OC8xw+#IM)b!FYJqN zs?x|QFF)%|o?<%+obxBSSMWOo36CjEXvm{N@D0em?jIhO-psqwwq(+xVafiLmv0Uq zJbL>>ap;W@C<@4*31oe`H@N8jOu0?Szv`YH{VSFTzGdmRlNR_na?s_sX>G1-?Fmxj zs94kebPc6oH`-^^xa0EkRrJ!KgvV$Ml$&5G!;D6n5(6d`RGr|*wSYu{heJ_JFg@1= z;Su6Fy#yZN>Z{}#88>AKCF8eU#)G1pT!^fS3}i9FDwDJ0p|q)H0*>RQprR9axv)*N z=z(V?-AMv9@=|I;AI>+reJ^31R#?F@o>kv*tJ_Du({Fa-+Ra&UWzSZBSY_K3SK0y# zYClIEBp$&WX}|RS!s>5hG}Qx+TEfCb&U(HuyBNZXR<$6y)X)l|Hk!7)mEE&TwN0#Xf2gOA)ULVSSQ?qw37vUu1{Eai*MF`YE{Sj^l?PIEt%Iw}i?m08H zkzP=whc^vO-#-#W-DAdkfl^Bdb5lXPEDW)3;kZED@4(=RHeL}4F+5oRN6$jQ)*R{5 z8P|t)Nv%_7$aY{-{D)w{vV~_DH?nZv_17XT-|3BRiT+3XeU#-YWtQ_X67Tc{=I+mP zwJvvA?zKalt4I>|7a;}Q$jcE(k*xUojp`@)qvL?Wq0iC3Ut>y#A*rW8?)9BQ!C#u` z(KS}8G!*g*=lv>TfBn_7ZcsY`vue45CwxTuXlNYXdc6nZA}H*|7pw;wHld z(MV@Ib6}yT-l2TolgVsgBD+z8-5%k&ez~y;W0BZcUqU8Up_?Q1Gef$jsfsmu`mxl5J_FO=q5{m z+N!Q{4%21Fx(p#}4*6C}?!k1EK59_FrGHAmn{A_?CQrzaG`$Hn#pP0xN)@PXFqHEC`m# z?5!ON|48;mQiW)Ts`iUUL3e6}96Q+J?(fKo^XJ z(a`tlAM4x|)_!}%Di2zf<2d`YTg+0-Nd};Dml9FEmrkI=@R>;;Cs^djM_0Pbhepaq zU6FcWsZ(j|JAI{ejB~i4J*J<`5MC?PQ-GB94_9(1M$*2fAedZnVBNhXZI5Q3(%FCw z=->DjAT=)C$HO~s@yc#E#+YJ?mF}{*y18U3m0z|%7MWcCj)A0+jwATgC5zM!&uE{C z+(WD*L4Jt4;{o`5joSq~_g@frD|-KZ`d3f-4b{__K`_)nIQ|tXU6tH(@lJyrS*I@j z((n3JN18Z3N}!C)x$xV>2e>=ME4Yh;ya;w7Zw9Ar$#C)Hjf9hE?B)l!Zn>XU@yTmy zkhzDBbug-RqV6o6mNaT zfz3tX%>&BhmVAql#kC!-$i2|T_-i>%)mhy)QU*h{WDfP^qL?D&rnPoy*c~Ma}ILkgco5Fm_C4`!G z;;9k62f_6=7D6Mj$olxCr>&1fO~hm(>aU>O@W1W}{TtGKM9T}UCyV9)o43ig-Oy6n zKLF*-G>s63n~li4Ddl1p!ud}dA^C14)|$_1WAryfT=YxuC}x*7U)GyisjAnbP|fkW z3rAe7)?#5tmzNf(4isz5dSuKXMF?F8SieZUJ`8Q| z2*-fChVV!EA1wg(_hIcnoe@{Y>I{2X$ex<4N+>ex z%UZXPO~ErIt>r4?#m7=Z$g+iNVZz?hs421o6cH92PLZG5JQOi1jMnX9l|bNA2LRbM zpM4RAW#*s?xOz+KY6?)EW|FX7#AN za$)+u_qKi5=qbjIl!vp=aF|m8ukDw#WA=}0_>mPJs0B`me$O6-fL113C=7q`>GNbT zrj`x~MF1{&xWG6tFDIFJtp464?N}%EU&p1}9>y>Em{SUjqt-o`8={qnFQgB+GEyv~ zUJL1<;q}pkv%GL~yC*MiJbfSXYvwJO{MaRm*7M#@z9Q6EjAo4`$wt)>we(}H%48@# z)6H_qi+mZey5n^|;Q5+F&hMU6eGuai>-ZB2-0rmI5vRCUx(c5ugnd6uBQREjgtUqz z^kziHhnf#4j%@@8rH8>a{}lwvM;#3f@s)i~)upi0mSES-j%J*GqR_msoG=32#_o?l zWA7x+LNp&56{;Hj!VOVpG7Fr#9|>tsSyHwAWGdEpuEj%dUD+J%WN}T8lLS#}RaNQ| zM9A(0zUab*UR3@V`QAKAA;t(j4J)XmU|^ULN%FR%Q0(&hr61vei4V&ZK{mSC7;l63 zHEbgCG;%VO%&!)yCJwJ>ChLBTw1Yc3%KrI5DrcakO#^uTc^x|^HFMz&5T2ek&uuzX zfw8=)Qg(|UwQlJc1^aXBW0K0pt({CkaUA6ciqetfO3`e?tyu^b}8(YIk&7+a{btzpTU+-PPy76zV zO?EoNcRRT1^UV&@luki$7Ezp-k}55erELBqsdl=Iit*Dea!wgW6lW6O2d0zZ?0CkP zlf-k%qP)%Gsrq1*Wlxen-1sb8+%`_X|KE8~#omM0id!Qm`2HZ)eQr~W zgnWqb0v1H5NPKu?vKn0Q`Al48F4IakMv1&8swKV*uV(!=1npFIrThK~=(efpB95fnc2*<}Z{n}!VN2h&^!C0wbZbW0_*rC5A<0+aINZCfFB}0Z2^8m(b2Q|V-L|w_m1Kc_4WjK?Z z?kdky1cbmuI>g#9l%#JL8Cb9>z6yCJ=~PM__DLUfPQ7r%vE8(FTk4f0duObriaGWLdkVG zIa`aGUy;C+68ELU)Ul`V4@a*|TJCi>KU1q`Jsp+tRqxp&og9dk3v z#05vTu~%@U8pCA4H;3fbspd2%zS`=~uc{*ZmJx@6VjO+(C%-F=Pq*B7xo`Z!Jp3c$ zuh*vB!nn5iw4N7FlRqtdmku`akaZnR56lnXA(=7P+T(!+VC`M{8+_eFi;At9lU-tF z^TccB$oeFNU0ufr&%sK)PXYrfyZa1jA5*HLeBM;oIy7)AXEt!bo-J{5!yl_Yf0S_1 zA(9fH4~SEhF=|S%C_DMh==IK(x!ejI)3z;PgmMAeBi)2`Zt0v~W|0tf!kw9>6 zOWEWs-sjY5>OZxn&``#o_|-P$Y4rw92EI^%*nzTP%y0cwG2aXFVe>H?VosKz7)cbW z2tvmcemm>soEGo3rS%)zg(c_zWo~8sh*>UU{Wbc7;_RK#Hr4~{7j?Ana@?Pd?^0-J zo4y5G)fngc$=z7?P%PFxp zzY+^sA8?b|8;oUsi~7kf;!h|tDDF+U88uS22osh&-(=k-u~#aIHXJ3ZUN?vQl=r$d zLVnX9ymeJgycKly;uo@!uyv2#ca2>(`(#{*E%`*}ByQg=*Z26m-21OxSm}UgkDbVZ ztHIvsbLBfaR=nz%0qoDA8Ix~j=TEfEdMw$Pbukz2*-HIA%l4;(71)V6i_fd#s+N&f{AL;lqU0e4jBjyqI zzZ1_*o||oP4U)G9QCuM*FUi!G2g&Z}P=y{4m-&4Q;AP|Sy)n}iLRo=bIK>f+Px>@a zed70@EPI0rlk7n)I1SpKygnfrA0_vK58U_UMZ!-= z(|poC?ey*#{kt#(hVvjeGP@B>axmRNXtQVPB$IKay?jlMP;aS&fgOJypoF-}-5`Di zQfxaW60X?m#ApuZQ=jG~R`S1dw0WpHqQBBoVhpzCP0%4VjxjkUtmex-!aTH_dCN*q zMIt=frSMRhI=`(j4-Yo7C?6KXt=h^_>ZtN(0{$WvdTAO%Z|FT7E?OQ7P=iV*iQ{%8 zg|&xV^mT~3b?^VJ30fpjN76GB7OLDL>Z_yx3Ij;AKCF>ySFlTJQDp}TlA5|A%U6_e-F=ys?sUt(qumn@FDYKLqR(eEI!-=Jr-iWz74zI< zuvV{t)-bzzobL%VmPbb^tN(nVS4jCqt^*@Y9(*^C?AA_I9)~q!Ggl_SAvrsm&YNbp z!EnncWPYpKw_2qIqT+x_V8)_;+6BG8T-#Ux-vkV>mVG9}<)uC~F+5b(BisJ%{hX^* z(h*W>SAK$UqI$>74RdzgfQkunNdUMs1rgvDizR}q^llY(#Mv>6lZcoEq7&rH=t*1a znW=#L1Ia34e=?GN;EW0DQfZb0VhTTwhe*H5%c!j8fOwpFfxCpH9>MlH9tAKPh(;B! zMU*riLGf{{J-!#79N1ELiSRc<_jbv|W{|da;6;PVo^}yp;q}+uJ&TZK_^6}arx(8h zNk{pE`%4Q>5e~9s*G^6XCk-?(C3nz@4Ji%Q=Qm$&785(=`{Y&v#l?_O6v3P-+y-oP2{g>ZAg5nWG<$@HKv2-NocXVY{ev_L~Ro;M9IyPH}ZI>^4ijc@r`k9Gg&il5TQsPvxC4WRh6=Q!U9$rymG&NRh zjT>5?ep8t5iOzWC#fLjN&zO-`CV1ywilrDbjBT&m*3Sr`qua5gnTLvj4BN9ss?z-V z2;fu;$cKWr8^z-Sug@O11q*#aNm=!P&IN=e5z9*|<}cA`bl>udfBOvu_)gu+PLq55 zl^)|D>F>AtwH%%4n7-d+kupwF$%)(Zr5G3O%Q)Ws$hZ=^(rs0LQAOM|jY|U+Pudrm zfGHX-Ro24#08|l+>4XNWGj^f`#TpCP>yRET*O3W>o7M7+#Uf$3ylC zWT?sizZqevA(K(m?}JOFU46VO=aL_Xp6~cY;WLNM8=fKccJQST4F@toEgxpE;T8Q9S`*d4 zlZrqb*k{tzeq@`;NvkjFVO&hZ<6~1D!$^p_O2lj+$-3G2(V*&w`U+&^dlRCPJeE{N zf`;ucDQIBUhs>8rb8RS{vIYrA`wx{wI*t*74#%k#2+#0qdoi(6>_AX5$aDqkwuxb1 zAYlqFZzFuBEU0K~!ft`GNP%tkZGuFYgw#&@Q#O2=Ls%WdG+Q?FTE10!J;7pI^Oval z4-qQmv?q=4JRnm!E437&I)l^X*RWw!a3sb%i^~=o7Y!+O!{~7F>Qj}!S_0|D&RVW0 zYt0d#5~|cA#kMSV(9{UaYKIgyjJR_P41Yd<(ADl12U7zb*0jGa=e$-^yowP^D>GNk z8DZAT$MM(UDdB$5a+!+Y?loJNWhYYhq28J}E~iSBw(&CMYMd(-Gv% zzErshe8-5cnqny~;^OO<_jwD{yP>sq40Flb<27{B$JgA&6%q;;fyy zspUN4P4S$#?$<7zPEQy~Ahn1hb~Sg(OVql{jh6)J6Hg%&drEgQd%+i6e;qVUZ1qFk z=opxJ39GKv4Cit>PgBy&oFZeQTgzG)E}EM9&SrS4vwt5MQ0a@vr|hH5br&*F3rJl3 z<4_R}Jqs`-{2&y%m4NTb@MO$42Rd}$N(7@mB5Cv5k6qJz#+{-6Q zs*b1Ary)OqSAfEQ4q!_XIWE6N)G}Js1x=KxEt+Vvcb&$E__8G5M3_btrTL8s%x^9f znDyYJDb$_+!g71g@_HLpr#US>WCz4;zsd5iv93kDd^YCZ|8OEO8)My?<1L~_rGl}u=JDwsrmmS~07Z|bAroT2|Dlf`Y9 zyli{c(P(XAjY5_OQ({n*QS!&1@g1>lhyFx8?BSZ6r-p{|4HX~}g7-ZT0D~)mV2L#s z&mXjX108d$qHE&~0k3x9wxRx-2i-ha{9AztZpZSIY2ulGzBjofolYtxnKWD!PRxj2 znhp;x@;8lS3$1St9S7(y55A(AGj}cw^(2xGG$my>+YXe-Nz@G zSVK2}kv_PE^o2)=%Mrt~GDU&M_7f`=A9D8I+5{bD$aL|Nl8MX!wK&%1xBk0K)x~R1 zc!^M)B5C4<^MnKV;*R!$AZ2G-{j!&$#P?gAEeoLB=56>?7Qm17W|9P{S0t$W^8!h}`50jEb*dD9VeY-cNNlsroZVxuu;-C39UiFo6-Aj^Nhw z_}}mn&-LB2i71byVK^&dOtJl|2wS;xqpWWd3gO9A9Dl;Uq)r|EKKR2Ok|l(+!3u1b zbtWYputj`)s}jNqnToe}$tUcJp2_c`EMFYIE*Sq%nhwLjRmOnP^j?07sr%qe2`2Ab zIR}#e7l7ujID|s_S-2lb4I}0-(QT|YZzveTjimdhT6fkj3VY>qH5ph?gz}n#m(pK3 zqUIH+PxNvo>?*q($~beZ-qqZE)`D#h=M0Bz@R&s>Nm0K|>97Onz7c`%_T`j7tP-vd zBITUB2Cn(jvfp66CKpk5Nor6wT0)3t=afgZ9^I;c zzstBw#a<-Dnfsdps!cvGlx^8Cxjrp0p!OZ27|m&8bLO~^xx@!anQIEC?bvw7&Y6mo zzxyNT(~LQJ(@j6+cHiOyJDJ}$&HqCp3a~2nx?CtLRXyDY3)XwdJiBq2zDjGc5QcW5 z^y6I-mH^_NBq;zpHVNswi+YNugonAW3_xclK8duIl-q6R4Q7ngIVyLFfxyp2kjB?_ z6PLRkVlro;6rCo&&Rv&FSM+0JlITbG{*Tj%zsn&l()A2qxEs5IA5xj%0zQjk z|5Y`VXk&}g$%KQO4vquc5e%Lf3tSGn|M~g%1xWFqMe(lw1VTpPqqfQw!txJPm{ysS z;*s;vzWp|ikt{`>tK?BYZ57^g920(F2V2mY@^z_#XPtNn$JUkU)r+C`Udf({OGmKFKr?Q5;!hWDjCrEcy@u%u=idjE9!8#&`hT5a2A=RspYbkW8 zy_jM2hLBhC0*_`Se-R;Z`ad}ba_6KqUzCxm$;UB@+#96~eQCTYH3i$!mR(<>OZYZ= zYlBe#>^=7%GzxRM?=9y<{`&^vR$WZLg*9&=Ch zKE~RcU@Qs1ECgXNBST=zm`j8(y(~5LN3N(k3n^1VRZLMN`;2?ZH>}|Y9tGmWBh)mT zeDN3m&2@LDW|oh2mG5DTYkWTiK$R#;zQ7=Y~KNvtO zo~@(VvjXi5lN4d3DVu&C32rA6`omZn$k~bAKQosBZm_&BXx8bT^v7ua_lR-A##){u z)dOs0_+E$mwX#7j%D_$SXc#iLK*3GNj9;a+$0CFJv6etv%wp|Nf_2Fb$;bX-$3yy& z{Z={FOhp0jD1z0R+^lx^HdNx+mMT74H(gKCeFrOPNAF$Tuf&1dj~;UWdBCrli?)~WyWM_Rw&@_W-wEPnQ&&xDViYwfsLMB;lSW``#Z5z(`GKA+w?_dpMBFTSbcN|N|C*XX)A3Ol!cQlLP@I+ttiB8~_<(2F?!I!=0Z zv2(79p+!!Vl;)3NX;HlGk$zpsDWw>UD|ta1q9mp0Kl@;;|hJYqSA1 zJl((kjUPXu{ ztqVZVE;E@Xg1OHntVCzxbEe;N4UklLmflE)J7XSpQSNt3hq;?7VV;9bkOs~u7-wNXiz3Mb*=j4(w%UZcE$c1_4x`U=07y@7=dy&yO@ug-HTG}ElY5w!xj~a-{OHW_;;YY&N|80A4UcnJ(oQ+8Yr?Ap7CM$6OonLZKf8vn^4At+Pu?0*&9^e> zs?suZiYPSuzFk}`5l-Yc9N!3YB;F-1n4(hNH}X7;A8`V_fQ;X6GVFjlpEJ@N0+#@8rx6?zcxV(*O5J@{3pOi@uk_BXq3tIzIF5${@zK!D?=U=*3P#I^ToV+hs}h!`D|Wl zf5f&Jgx5shP zJsN&#^VdXvhH3R)LSR+?S#Qnp(EQsCR05+w7#b<5EV@tg0S}WxA?^Jw9+z7Ui z;eK6d(#5}gB#Z+!NOsX5eXVNq%!+Q zV4(*yK2)h!)b(fOS^%C#BVP+!6F96XA(sg!q8c7~GHWH1FDWY42j`&DY5@b0cWYBh=j=&?}tdaOX<+5 z_I6`qAZS%0n|(FPR^nq0T3rHgJBEKn{MzX>Z(Y>b;m*n0eCHakbg&!AF*$>2y-WX^{I#B__1*7uPoT7G$hUE)?J>5AvZi(t@L>w-eIIM_Q?Irq3kwdMSsj^2M?E+ z8l+!>tK_3dhOGhv8`sE}VLWNgXk5=f+L&$5q8GQST?jWDyl@;fVx63AY{Z}K?wrA0&QT4{W;x{mz}UB|KKctC zXcV&ZCUJBYqX0GL^TbeN@F9>^AV zIgpQsT{Bhtf%ybL!Klj-EJqxSUe)nlxVCGmTP>3K={u=iMvmSseyaJ?y=Yu_Z1O5DQ!pxtZXKt1Q2@j zrS)H4F(v2N@;i2=j#=CE>V$x4c&{-zog#vgq^w#8(pEPeDu$jCltz%PP7zYYp`67! z^e7Mm zzvCcgQMBX4cpqbn)B2fIvuy*wm4)CdfECKitcP?T|M>vb5edYez6YsHNr68Zg#)m0 z>Jnj0?Ipf*Wv~n0l#CSdv{>$+R0OM+BL0sH2>$Av1Jzhsz_2Z#J;P-*qAn}7HfH?y zwQ~RKW*qL{d9tK^+@a%2+rN%WK>g~3p!J+0%OEqR$UOcNrJ+h8>wJ|uGv&T~#Jzr* zvX7CjesnIWFMm8|_fVr0iJ}yBu;ps7_u$v8*(Id>%@! z16q7Lht0!WX+jPt1${{Cr$FZ1{Ta0m1K3=##G!@T;m@b(U&S|GORQ*d!vZuT{sQy< zS{HqIt~XDt0Tk}I~IYUfbi#B4^fQgR+BVhyYGF1g#N|9xHD9XGq2s1Dp3P}jU zPC@l4c-w$)aVEfxk$rmX$NtOI7-*6C?>~UradM9pUQp)it6rd_HDQwVR0lzoDrTV{ zlaM~5Azk*~_S62!TE;gZ5%DzpDiNy4CQSU+7nZ#AJ&jF``mqD^KLg-{}ejHSBUZe6WPC~D{Wi`MEDFoNHARhmc}oogU0;#a zOU#h?Dp8upl?gq__a6+!H4w(RbNGoKbd1D2vs>U5*4XKb(eAvHE6!-&--G_x=KeU@a&0m7uzrPi@LjS%SdGFCn zpU=R1ar$}36IUq5ca;7Qo6LAZ1c9S7`N-rp4jB|uQe^t^;iq0$(w=SB&RJgzN~&-& z_(l1)ELh#-laZt$Op3x7WlZ*_kdByvc<$aVxXt#NMm?z34Ar-f%f(F2M#9YX_=Qfi zA1$39&@(zOmy(9%nE;9!lRsTa2T-bci}w*}R}{IdP{G%M40ZSLA+J#fQCqL@bN)@x zy1nB+Y0{{CU;F`=$XmSl)PdkEHXa%7SmBAIaOHdWv^WWjfvzj7T^Hkxs17*G-_k6v z;y7G^Pk>BIeFhiu(!dC9L+1A|=Ipa1f_Yd7I^_ z#v1r$Y;B+d9}gxbjv40_wd%@ENY)PR@R2$fM7Fm&YTrS#23t?G z&nsf04?q81KbKLm?bgo}14<3WnkLQ=Xeqq#mfIQl-mU!U8Pr zbRh0){)Z0xHtz0ckg9O#@A=eEe)$~tnCK9vV40YMiF?Jwm6dSH%%@j1sfOHMqWxF!8<`Wx)7wZ4bn(<{BX%;YVZ zs{3~Bh!#AI?X}lQ`xB${h9BPL2>c&LOq}Ok5dSM6^1s@$_GuHkmu$C8CHg5Wbcy6g z_jZfLgy)K{o-p=ql0Z^Jj<}~AV}xUIL>QT{{v>N$#GeZX3wjXGO;yUe1Su-M5#ugd3;l^a^D>ssIX=X?9e!^Ks51%Ami>UH)SsxvUu=&dyhe1<^ zQZg2$yCC6!xsspa+f9K(Fj&A%Njh0(=8ToIK!IpzC|L)~c!X0Fmu$Bm8QL_QSf)R@ zf54hH2vUN~3F>wy4PlcXv0npLsE=N#s=VwdVQ?4|lAR|Gt{~P&4!8Wkn+8YZ4Ej>)cxeR15#cHsut0iK+`%{r6%e{P}CJu*{>ir-$Re z$UD6o64~iisX<6?C<)^=p&DNoLX%gB&R-wt?T?DFjLGOT17S)o=t>b7`DDJR^}V~l z1U&zqhJt1V#eX)Xf^J1dJRbAyd}HCjIQ#LbOhK=P+6FZWQQx1pM1?&ze1Vdpr0Mi) z!+yHl$#V{FyW1~RmhL^k9ga8l#c(nI=Ls`;Q`%yAdd?8>Z(&}CDDtBPx+_|J(hTG@ z{31(p#B*gOmMo?`vSmpnCs{3@vVh+?TFxY z;gxpyjUJw+Xa(%Aa;I}w`syGRH2*ymkN6z&LM|l1u)x1cA3EubNnc`9H0K^7Met6+ zLuILx>iUbhXtgg{r6uoj_0Dj`?B>Id@0LzJIq^h$J!D=#ZK1OecRVM=w@B;;-pLDu z$yvBec&GrjkM1Ljqcwqa8%jLL{y$r%Ul((2x1E1)B^ znGqjOOYCLCtudcSRqb7}=F68Wy*7G23mC~7cq#B+J$8gl*sdkmM(K5vM$Yl0G|frW z2`lZod5EQ<%8+UlSbc7p`>qA2!#)P4OU*(1(NBEdYW%!GS!Bq=WDul>`fs46$4$joJ8fbhJl1;O#9zv6Ja04QMX9_Obv35KazQmEzwxv|smbTn_p1jP2 zT)cgH1IYi!)$%`Ox`E45pPOl|Z{s6(!1?drV%(2Jt@7il_*+$!1#!3Z|9>2?*>Bmt?#gZ$KseJF{QD_ z#ao|dlnz==HWi!g$5<;wvRj0G#(f=;x;A`)b$G{S3+2p69_|<{_3!iF;&Y6YH12h) zhTDfKsz55X=7uB-x}eq!n)JVK1>9pAm`6Dpm(}{tc!%Ip2=Rs<3_c<`SRhP=#ABOq z1dvEhRK|5|6sd?>RdiEs=sYpk)6nuYeGKgF7_^GJ&ikR)#QW8Lc&~g`keE~xKv#Q`UY^#DF>aG^#=E3)x641@82?}Hx8=M3NGq!BP zA6*hjnBN0+q){M&DLtvotZ01kId25@ApDnjCdw3Zj#mnvZSj)sFcZ$%cm_vwg?QKO z&PH%DQ5mT|>Z`x+t@NHN2=J8r4WHpB6C2^VOGd%>?O-!Ic#mKW##>?!Q%t|Up;83e zI~XP-9>SX=%8fhXeCi**=)bXhKVomMBhp3UeBmH|y3@Sxztn{;Y(n>qvCRT+6Mn&O ze^I{sjU~)N8mGz2*{L9&6Kn z{IsSumEhs`PU7%=F+7s3HMi8ywETdM<9meny0OpKqmpsf2xk#Fy2B;UT)fnUp#Fei zK8>=4Jd7j6Qq2HKYk?ELLzvJ&mCB3$+des>Oo(R|2uu1&JkvS|g9_yYjTkOmoxoUN z_Xx4~KKMpxb8$%W5(b*#^%_Hu#mE}CX9Qn*yO+VHuXxdb@(BjVuWbkx6gcuf!v0Lx zMUYjV_d!)3Td|-rS|ZBdwq|XpS`T8l`6**KMZDHsVpuHk0jOZFsvnDWJHiK4zY*Q(oE5siuF*60k0fpBQ+&`3i#pMp;ia^EJP+41;I-DSghp$XDE&5IgL-d^t6 zY^pGR%#(a6(sujpr_1zdu-wSIAJTvRNoX$K`+9HSo<+m@K0_Z19i$v~4>O>CpEzsiJ!vV6W9@Sz93lTlx*(sWUlu4=!y+>(CAVxch=XaYVN@wC?6!i2vmW5>?w2;(5y zG_Ybmho8;YXRmdeHtg^z%5Tvkv!vVXyctNNFuPnTIL2QaYb?pzI6U2jzvSsAJLia$ zT#%9Ov-TuYASJa#?2kbw05HI?bXDw57mAm_n&{18zi;?gRQuR7^;!k3@bYhd7Cp&wiq4L(-Gq#;=cm^7 zZM=10(okzpBRAvQ+dCVt8p*WAGf5t{Z^E>{Y6}(f^ZXRfHq?t1{={=F8^(>xjxBab z2g@Y?vlrn|?+;G8%7fw)XdRgBQ|iY)g{_g@eiPvTN`zXtT7C5PC+2Mp+=&m3_)oF^ z_fLN7U=M?RgeGscmE{*A-0lNQsTomBDzxGg@tST*p+`O)1yqrB^=4b|q3x#qV1)_J z^y>Y9#~ks%A5BGc8ILMnc!M-1)}TV$A>W-iiKx35#=PK;XBiy=N|dU1tr_nr`|Zro z(!z18-k-Lq#pgd#`rj*G`eA<()I|(1bLOwLqdZ6qQ>%8n8>_ZiJ^5Jyftv*{JN6JU z0$H*IlbxX#F$>^OU z=C$+)3jb}p?HbbRQ<_&&)sQdGXG9|9@hMj%#QRTk4jY?)Ywhi91_^v?Y%{B?_ zw@rU~0X3@Q1THiHk6f-qgtA!}{TEb`{ZO_1>`CEMyz%Bm_&5l`0*F6|20o(cT`?B+ z1-TTt+u=|?W8ho&ROCHm>9GZhR#0j61e3i4gZE!9FW!m0j2xG?hbu74V)F?M`a){! zy+Uy+s2MJQ}PbE{zAP)6Qp8HL*RXDWV zYpsbQ&;8UpN%Az^aWqE9>RMu=N70mgTMI2WT4o$c=nOIlIUpEnR4akG>~q&EeKs*Z z$D9yO?Y}5UghH89LuGX;}Zkz4^L!DUIdIXJLPqS2O%g0Ldpf zB}}*4VNXu4zI8ax?v@hX43_PHOOj=qbH+6FuKBz5{*_WjY|T(#%ORf0d;9~Gg!ICF zkBVl)#B+_B)I&@bP^uu9o@YfSL!ud>F1=J2{eX$n!D4q(w$6+@-;~m z3-8YrMvW}p&K?X~u#pX{=!{6f(7nUAoy~rZK}d6i$S}2wZZPBtEgP^ljs5_w@iqQy zeNk)yI|9@%B|h0Dnf&TBZfrJObmdk;d@`$^XrdPM6s(;gMBwXWz@iGsjBq%o!H3}d z%g!xp{-kb>s}`QA`Ca*FhyEGSliSxH7v&V+AA2v96WtDNa$O{yVD=UBxZSbNlHS~6 z)RPz5K{(&d(ie!1OiaDg)F)vEJGusHGP=^yeHpjOSO~dbMEPV<4c2WR&iDAR#(a5W z2E5DzR;)s%6tUrih)lmimiNthZVXfI=+`sUhD6S&SFD$5=BSOwo1>%F+CJAj5^6-1 zNZt~(lydq|-|e0##)U+w+^^xLD$?z*_(O-yLAPhidg*?a_j-&v;b(tIDJvKuT@ z5^qa(-%}RwQog}eTiSr06;TZ8_q8Xagf1?v4+%IOsw7B0_uJ77J9!Z-E0xVyu4^zV zukvzA4%cBDCD+xGqY~mK6id~^@;Qm7(O@^b9^v}Uh-VhR8hR;&&oHKD+Glu4wS<&r z80K*EP1pT+(_fWTy3Dr4^l0xFR#(a>E|S?y&TWK8B2aC8d*r9<;R9>7oRdW`S*8>c_tJg4(Kd(b>P9fJI2L{gk89mM#q*lRWo(r^`pJ6;i65Yk(o~RYk2>9yCOz= zzGMP-r1<6hvCgoF%vyf3LXxxHABEOQ=2SN{)x z=x?cxAHxPMiJ}83+9pNezqIV`da=N3DzJkYLveonV^=iuSqFe9=CB1Z+79+K7Z6 z1Qj!vfV_QZ`T;1vH=d*ZbHg%!UCK78>}$%W!AvThQu13|)6lv)^Pn*vEl)2Kda?0^(p(q=aQE81(*# z0^vsBcYq)C7`1N#`U7wS;}-dRS^@@kqXM^~OYSzsJEp4h8(LwMwR2I;@Gy@!>HfsZ z#MzW3|4?=%dTGU<++R^R48uNo)rY3%#O^B-y`!}4!P#LkHVm8xaJ(%{w3;mQ@iS}a z)0EeE_S{u76gDN**jc9(PVWpza3cx`s=?`H&HArA=8C$q7{YlL!VeB>4!PJcQVeb) zBs3}|lh6NJsV}Vy{5Y^4H~jkeePf8H4c>b9AbY1Bw92gqUt9 zE|KSV$U4>j@Mgo16WyH>fXeLn_w1~AW=>g9pz=zgiQ?I+S|o(q;(7cvTzft=?93#A zdYt|brA|eQ#>KbEd3ey^o_+sQP)zyPtigC<00!^}GQYTEQ{L*UXO-&F=Se zotfW&>hAv_rda^MgAKG*TQC0z^AHzx2zjX9jE12E%YXZM$#rPy2oRyadiM$&TW79$js3QOO-ZUefg@5@?Fodetp~_h94jGfhd>qr*hSCiwJ6+hV>} z4go*@8xO;!n4%q~e1lZU6Ua8IFhhtY5N&a*`&LeOM#qWe!EX!R2(b zGmf};e^?*8`HW|L{t;Xw476|$gh&&=c7LDc3-gOLpoXl+{Y$s(Vwp~=WoCfIC6FZ- z)TN)GD4AzuyF@)J^FHz0ORFOJC zDm;9Q&#nx+*Z#k;`0BE3^n6e}rzeQ6T7h@0#ti&x&WR~r-pU$a1?}U1B*BOXCrQX^ z1T%HM2^vjB>?z?_@idQFOoy9qMZev32x-9auR5k&KkDp#$n4u-*JkxhSjFrt$6hl1 zZHeJ$x@>;{7etZ37fUmybr8EC|KB{vu%Dl}GU0&l0snWd>;9OkpXxdo)ctI^EP`z& zl^KHI0|z}^GaEbOV)p?(@z<_tcsL;X?q8aSjr7J?wGNK}uYLdDuuhLJ^{>aR`U zs02arvRrlfS`wWp{iy-k!Y^!(cfy@U*hyMbRjaGBN^R|T(_2bk95H~$&jhCXtUz{` zC)cS75BrPmeM0t6)SwQD=@CY&Z_hS(J~Z3H&on<&?l{&T2Jmb`z$?9ZNx4B^p^dzL z!<1f?E+G=NDf93HJf~E;H>d34ROFvw@Mn-LV%xnCdt~e4z+_*1lV?*B4R7Vg7QS1> zj5n+IZuR%gX((sM2|Yy8dzHfldAsiB#R}`sjW@K{>ME|k5SFBpa&d|iHP~vTQOo$O;y>8n;Z_;Kz-yO30>B#7Z zmMWYgrpOrIgif%f;^w#*{w*?+YB-znPHc<5!r6aW=-HXOjxe> zFza-&m~3r6cD02`R}jY?XM0q8j|A@Ir)CJM_~%R_^ZC;)PN1Vc=AVZDN*AT;dICh^zL)_^;4hNr~uuG(kj8k=8e1k5AGlgP%olz4fP%$zOsk-5Z)bx^q`3K66^lS@QyrW}F-k1&4DgSww9{bI0+^P*#eMqKo zn1D@O;+}V5SN<^E?p-O*#s$6iav$p+Q7vugDo2?6Z#}sg4ZKA1Gm;okDoRa-OWoEA zyUjYJ^)J3nqUq#!;j=~4q~Mi|XT=K8cBy>Walpg$l$P@JML_~@^jFIl@Uevduts98 zrW*cureL##<>{Oy^?6yVQFTGGYhq!(0}Ao|8OQOv>s}dPV@T33iYU}am&ymRO(JVtNFJig$1>YvuSB~uHwN&$mfd$}-7)*fnLm5o^a2V zRUsohl*I5}wS4HM%W%x)&_Z%5=&Q;B8CTA@4`Sa@PVzu=vjW^_;>mD;+i3KVe}kb0 zyaqtAQ0kdCBH)ccW!S)oGN8&;yYKS4Y0c(nF|Y$!Yd*A9ymIIwlZ-_H&=0I~;Dr&m z3ERrQZIx|-S%34Nbbm(_v?1zxOepmfkW37J6DOJfS+1vz{JAIWGN_NE2jTH$Bsz&2 z8TpdB*`P@I%87BP{})D$+1!uVMg(IrwFcJ-F&l0XIk>~0wjkO900C@C)yQb-b^IK+ zm%X=`0w*GVh=#8t!cNd1c!f}_R!vs1N+xIJShVkgIumZc@9p^Ox`Ai>Ma`c?F6pic z2Z#DE-5>fd&1Y|6E<%F#z_95xxQ)j4vMPojG_*g)ctt2PoOoRaJ1C4-nP+6;nPz`@ zU=SOx1)XM#poDtm3OWM}IWzOOZqn&7K;`bV;EAsbIA3$Aw@U{2$XR_xvd@q-3sU39 z(D+hSZrNMA^wb5X9K4+d{Dr*{JB!K>;5foMEd(%g(WWE+Uwc>n57qbo?NWBKGnKrv z%U0QDkhKvNLbjM9AyUaUGgS5vSxe?EWX+lsGPdlDecuhTgc%IOET20*-=DsJ!{^7j zb07Df^ID$gocmgyoD%%amhseFlzTWN9IqG7@@DyNeE-Bdd>m-Q6?JTQrg0`=6-k^D z5D@ILwpeeq;jbK}PtA)q2qhZT`<0z5A{VlO?go z@(Nrnt35nXUT8hp%D^PcLH_-<%(Cha`M69XL*+ID;w3NS-g?(Lg~V^e@Xw9hJt?Yt z>^~Y`E9f2F?y5tjH@%CeX8eU^GS;rZT{${LINIo!1O)njqih;(A_xlrA4=@^&@F_S z7H6-s8m=anR?_(LnLqp!gVr!e9?)EEb>JNNF-N5aE~u znu;CcFs0O#Z-p)+5B~i{zY=XSvfOU-M=ol>w>WtW1dlBllF``He6Ra+@nb;0&Qib_ z-CMQ8`=L)*#jDB_P+R`!K<0i0c9cY)kJhDoDyqT|3vHbl$-Q)<`;C1Yw}uZ(C2qys zTE~1%gNf3<7dz*Z2>eAv3lWbp$h|$CXQ4JxWpNQq_vfZ;Bk9`~N$Dl3VY`o^5Z(1x zf&smAo35xhpiU3)-n}l1ki~Va3**KP+f(iPl8jbQ^m_Fxo^c=Lp87yw9T`+nGAab8 z5E);0G{>-M5;`XJVL#sU9QdUgL~A-bu!}rnx4FdNiU|Vhdgh;ZD$`wq1aRNf(-dbE z%%WV=B_|=`yGtIr@($}i7uHDs$pej!OZm52lj9l3-)z1-F~8UW$ARU?uQ*%%cK}*~ zgSv5d*!44wiaV&8Lo)jLvUkH*5;K{i=gl&if?p=)uYkVzK;~W_mxypu;%_pWQW z=^-(zIEX~wvAF?x(Icn)Nxh5eY3=lsQwt`mxD1u0xB&$@nIgtOh@{d(4{uWfWIoh` z(=gq-jXy}lty(B|Bj&4=vVq*dqsm=Lbr$XqdJP$E;k9E+MY|H zy!Gp=<|_$mBBDXJ4}Z=7wV_T9mDTV3bf&CWe!b$fcC~jygscA#cdLuGLugm0NWRby zb2+uUwdPX%rXw%j?Oiu`#5wsr@;WbCi=Ff*@qSZ~{k^kX$?|rBE{V{&FIjLaSVV5# zmW@#Np=_{>rAlvGQamdh`X;`@&e3yk>|$sp-?^U>p<+p+#+x-&!gR#dVf^*kPb;iZ zqxg3tszP`mzjq%!G>XjmZ)b{4Hi$0?4O!~);VZnuE$VG2etUB*>?+?spzCIJaJB3g z+`GPS(=|VeqP~3ZWto2x!IL7Y-ZttvF)G{5_x6eZB%-N$yM7!I(O`0?MWRXdkwRv% zjF8iDcID-n5se4Z-pe)Dl!D6^Pt~n_KK2_gy<&d#-m4?;in)wj?P_-N(=%XDu5nT*6~*r3;z$B-y@ zrZs8*E7XDo>@b~@y&C^;tijD1(}`5&Qs2A%pg?ktTB9!6^r$^|Q)B&5Gh%$sMB~+l zM@HI1F17@{_3vLAHuE8*QS>o<@DcYU!{h1R!<{tBS{-@^;PL5TRg|XujP&`Sq4qYz zJM|X>6hnSvwnm>Pn|G9+dS7}xRpIzHoEd80>FHl-xFk2O>*f|gdPw=>N?$>hd~ld7 zlPoKBqtnoy_{HXzBNz`MWnd&AvT-ax)#lTKrS}Q-2Q$ux_9!r7Uc;t==n?6jVZ9!I z3ug|rx8JE(kjXK+Xrt60Lsq^LORU+M3lG#jFpKXk1o^7$S+ey_OSlX=Uwy#8Cb@15 zQ>4`6|K_`*whyO~3UO^Qz_)!T-}8*lnW#X3(Rc1yKt1~fCp3hTd=!~H@9>A`M8fjgyE2v$f!RBK9!=+^@DcMRns zd@8#HnaZKJ!o@)nAsTue&$qf$UxO%}36ytgA~is=6wLW>xDd4CsnkIEP#k0tKu%8Q zf6XMKrA)`&2^A{3$;xJ$x+{O=WcMS2m2+7290=L`O~c?C#zxxm(Qtbwg)4zApZ5oG z*8*9x8lv8CA?SiF>}9}#Amx|~u>gu4(tIosMz-W~jT*m@FqC^bd{P?SQ4q^rSmGqh zImZ8CHg6=d{DqTHB|Id=)vdJXDVJn>zfoVgZ0)l%wibmnwz|H|L4zHyviwT%T%Q*Iy2L6R)=#YKo4clrTQ^-yz^ekae;D;TWWAGg&Sv4Lr+0wELFGUw5J{?KPWv{!o zdd7>wgy{;P)*p2X6zw`JJqPWS>*N)=FNs^DLccYABo)2v5JY~1QQK@{0aK2z;JNbn z;Nz_<3;|y9M_&=L4k^aghdKOe%+;h4K4fLdE4NewmlN852fMMOL{=3YPD9;55ds4St3f%l&HnFTD$ zURdkdO;Q>Z&TQ8UY8ZwhS6X^0ab;%#ixF>Od3($@Hk&H;QH0L6>dE*uJk)m+bmsS4 z&Bkjsk&Gcfll6nH5Ip@f9!6|%xuuT1+i6hLFNHs5GN*^G+*}-QGA_*+O+Z|hk@2ZZr z0d+<};tx0$zL^axOSfOMSYO>@h>huZJa6(sNq5gLmF32b@;zRx)#mOQ$ml3?>GsL1 z$9IH4h`wB$eJ4BcF@NW_#_E$d>$bQ6;ryEu>0ab+*JV@&)W{U<{wcH;*43GUB+zDU z90w>3;qqYGc59$_5GU257OM}X-}@p=N5@%iYM^H|%5WOl7V|~w@1X~F{}4|?Mho`{@eM8~z$U=*>?Q_8FPul8td z-iy;JWCgzP(Q&bY1^U?xk<0s|{0S~}_Pb)*- z=~m@*B06?dEL;TT2-jbT{SA5gR3-7v6UY3w4+}D`IuW^OZbP8?F>FVhE_znCl`<<@ z{<|RBQui;}n$G>1LIbgE&yQzG(i&Mwq;H^`5#}M^y6_VD8@ZSp0 z>E^{WsK7grM>22BkPRD;iht47^0xij2q-}PgzGQ0j zPJvzOu()InOWwJ~;*knvoth-sb#yB^^RKC6c;hYv?n(uiBNBl9ut;!ZX-{x!luS@o z+p~-&TyHL;obX=d&oU@dzaYmq9z{_C_{NI)w05XJ;y4nx;RU&AZ1XZpvO96%JG!Lsyi+bI~XN@Ry?(mBcyLxEW0XB72ScgpToxk+TsI(i)~rim7EyG!fVN* zxmM*(9ly~p*5ili-OHhKX_{Vh@F`ZT4}=t)HxQstd$MXtl5bM!%GccRUX;&~A+fW@AAp(nv~-cI?BZDTE0(a4u7$GOi#^z|T}v|C z+;z!D#GBZrt8g1F$-gn;$sEZzvCMSnEsvVr|EU+`p!sMwYCSDzs8aOP(+f#7)uA@uZfbfj-}< zNX@=?_H$r!+Oyzfw-^_)NpFF(mvU@~?x83|k%~qE@vpf2(=@F`So&AiXWk@N_l)&e z-I7{ANN~Yz5Uoe_B}f8U40{~tiy)*$J0&ik8@Rj9=qKU9T7d%JOTm|alAQS&>FfrD zBiUZaD_+Uk$1KCPy#ewKT2?=7tP!g;Q_GM-crOo{!^;?KB(cU?jiLwV=`-`@{@lUD zD=c@p-ISzL*_rd>SJxnKi z-s$|*fV5pEyfxb~UJVWARib<&4JA|q_IZN_@$N$P;%1DaVid!8G~dxJ{LxqQCc=(K z;}k{^X+-G^K2at>@7osda(jJlo*GaO=|00t(I`5XxqQFufyTMs1f|0gQgdX`e9co< z+M4L>&`Q0uj@rYV6WF?UM&IEBH@9`DTKwBMF}lNWpi0eWsShZehM{leOgIBn@$s2t zPiyXGF5iTXa=ry*t%p#GJdry974QR;b*^N2)lt)2LvTY{P^nDMVjBdwG_C; zPK>v-i#iXzvT7Uo>Zad>(rFSI^UFE;0A2zBx;U**OT3Hv%vjo%itNt6<6Q5JbJvey zuqomI;49N%C+d=>h$T|Ygx?*yJ}|_@a(sL`Zf1Jf`S4&2(J+C2dWnYf&f{0ObX7?Z zJ(<${&(xx9`Xo8M8*Z<_$GiB^1WF_@@{>miN9;S53#2nXBuB2WY_zf`1`q_XGw5kx zH~3QaM3vsJ8}pAKAEK!m!ari_2TJA(NbeoP^5J$8lx5(I_wq9BF6JBP-w*C@oi_P$ z^vIgc@ORW-TjG2^6-^T)zyTLWcKuUw!&e|WD~3a4O{bVQsA!)mMFb(2Api=Rx1IG+ zBb=@Y{c>05$53hF@~0YDUmAH*lYuM5jxwr5!xQm@EqXP7b_|1Sq_JGvL0-d;$po)v zfN*{L@BtU;$`;Fsc;7QL4z=HL#tG*kIu6i{Q;-p0TUN&ev|}X1IShK<5I9a2Vk>_D zH(Wy|DpA{}fHn6e#|ip@cRPY1KB)Lb%nhl3b#Ts)&BlkQ+ge@D$9Z!~acwiS+AIXF zxcvZift^uN8k{3yeHz-w zf_X|v536l^es5urI2r*UEtx>1uFXAuo4Q~@?FNzY_O}FvRrZH$UK6JUky{XIEBXR3 z+NkfLA{>DjLCVpk2GY(DCnl&ZC9Uz8IxV?P27&k8=kOEMVl|PMGJ8#|&KKj1Byo)? zX<*-gCl-6&oAiLbjB+Y0_k?tF1|Dtl|KCqwdLnUc<^TvhQWMcKJoCRl|0jh1S;GHw s2Pz*O9UYZA_X^na)6uce&aRAfdAhZ0!4-Tjfh%;ThUNyP`uAV`2Lo=K)c^nh literal 0 HcmV?d00001 diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..81e7014 --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,15 @@ +{ + "name": "Slideshow Maker", + "short_name": "Slideshow", + "description": "Turn folders of photos and videos into beat-synced slideshows. Local-first: your media never leaves your disk.", + "id": "/", + "start_url": "/", + "display": "standalone", + "background_color": "#18181b", + "theme_color": "#18181b", + "icons": [ + { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" }, + { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }, + { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } + ] +} diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..cba1b2c --- /dev/null +++ b/public/sw.js @@ -0,0 +1,69 @@ +/** + * App-shell service worker. + * + * Caches only the same-origin app shell (HTML, hashed JS/CSS/font/icon assets) + * so the editor loads offline. It must NEVER cache: + * - project media: served from blob: URLs, which never reach this handler + * - Jamendo (or any cross-origin) requests: search/preview/download stay + * network-only and fail gracefully offline + */ +const CACHE_NAME = 'slideshow-shell-v1' +const SHELL_URL = '/' + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.add(SHELL_URL)), + ) + self.skipWaiting() +}) + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))), + ).then(() => self.clients.claim()), + ) +}) + +async function networkFirstShell(request) { + const cache = await caches.open(CACHE_NAME) + try { + const response = await fetch(request) + if (response.ok) cache.put(SHELL_URL, response.clone()) + return response + } catch { + const cached = await cache.match(SHELL_URL) + if (cached) return cached + throw new Error('offline and app shell not cached') + } +} + +async function staleWhileRevalidate(request) { + const cache = await caches.open(CACHE_NAME) + const cached = await cache.match(request) + const refresh = fetch(request) + .then((response) => { + if (response.ok) cache.put(request, response.clone()) + return response + }) + .catch(() => undefined) + if (cached) return cached + const fresh = await refresh + if (fresh) return fresh + throw new Error('offline and asset not cached') +} + +self.addEventListener('fetch', (event) => { + const { request } = event + if (request.method !== 'GET') return + + const url = new URL(request.url) + // Cross-origin (e.g. Jamendo) and non-http schemes bypass the cache entirely. + if (url.origin !== self.location.origin) return + + if (request.mode === 'navigate') { + event.respondWith(networkFirstShell(request)) + return + } + event.respondWith(staleWhileRevalidate(request)) +}) diff --git a/scripts/generate-pwa-icons.mjs b/scripts/generate-pwa-icons.mjs new file mode 100644 index 0000000..d5c0bb8 --- /dev/null +++ b/scripts/generate-pwa-icons.mjs @@ -0,0 +1,45 @@ +// One-off generator: renders public/favicon.svg onto a dark square canvas and +// screenshots PNG app icons for the PWA manifest. Re-run if the favicon changes: +// node scripts/generate-pwa-icons.mjs +import { createRequire } from 'node:module' +import { mkdir, readdir, readFile } from 'node:fs/promises' +import path from 'node:path' + +const root = path.resolve(import.meta.dirname, '..') + +// playwright is a transitive dep of @playwright/cli; resolve it through the pnpm store. +async function resolvePlaywright() { + const pnpmDir = path.join(root, 'node_modules', '.pnpm') + const entries = await readdir(pnpmDir) + const cliDir = entries.find((name) => name.startsWith('@playwright+cli@')) + if (!cliDir) throw new Error('@playwright/cli not found in node_modules/.pnpm') + const requireFromCli = createRequire( + path.join(pnpmDir, cliDir, 'node_modules', '@playwright', 'cli', 'package.json'), + ) + return import(requireFromCli.resolve('playwright')) +} + +const SIZES = [192, 512] +const BACKGROUND = '#18181b' +// Logo occupies 60% of the canvas so it stays inside the maskable safe zone. +const LOGO_SCALE = 0.6 + +const playwright = await resolvePlaywright() +const chromium = playwright.chromium ?? playwright.default.chromium +const svg = await readFile(path.join(root, 'public', 'favicon.svg'), 'utf8') +const outDir = path.join(root, 'public', 'icons') +await mkdir(outDir, { recursive: true }) + +// channel: 'chrome' uses the system Chrome so no Playwright browser download is needed +const browser = await chromium.launch({ channel: 'chrome' }) +const page = await browser.newPage() +for (const size of SIZES) { + await page.setViewportSize({ width: size, height: size }) + await page.setContent(` + +
${svg.replace(' + `) + await page.screenshot({ path: path.join(outDir, `icon-${size}.png`) }) + console.log(`wrote public/icons/icon-${size}.png`) +} +await browser.close() diff --git a/src/main.tsx b/src/main.tsx index 6029a4d..7354f94 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -8,3 +8,10 @@ createRoot(document.getElementById('root')!).render( , ) + +// App-shell caching only (PROD builds; dev would cache Vite's transient module URLs). +if (import.meta.env.PROD && 'serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/sw.js').catch(console.error) + }) +} From b73505302db5703a6c371044fc56e2260f0dd102 Mon Sep 17 00:00:00 2001 From: cbaucom Date: Thu, 11 Jun 2026 14:05:15 -0400 Subject: [PATCH 5/6] Add issue-19 PWA + aspect-ratio smoke script and screenshot Co-Authored-By: Claude Fable 5 --- issue-19-pwa-aspect-ratio.png | Bin 0 -> 43473 bytes scripts/playwright-issue-19-smoke.mjs | 173 ++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 issue-19-pwa-aspect-ratio.png create mode 100644 scripts/playwright-issue-19-smoke.mjs diff --git a/issue-19-pwa-aspect-ratio.png b/issue-19-pwa-aspect-ratio.png new file mode 100644 index 0000000000000000000000000000000000000000..313b63808d2720429407db2c3e340a6965893a66 GIT binary patch literal 43473 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

URQzam@brA=Q7F*#GCK?EiKM2X$COIN}x0 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 nVR_l6Kjs&{VI5H!QIjHHb@6W5Vm3>B={c$Me [name, fs.readFileSync(path.join(demo, name)).toString('base64')]), +) +payload['slideshow.json'] = Buffer.from(JSON.stringify({ + aspectRatio: '9:16', + globalSettings: { + fitMode: 'cover', + imageDurationSecs: 3, + kenBurns: true, + transitionType: 'crossfade', + }, + schemaVersion: 1, + slides: [ + { durationInFrames: 90, excluded: false, filename: 'photo.jpg', id: 'photo-a', type: 'image' }, + { durationInFrames: 90, excluded: false, filename: 'photo2.jpg', id: 'photo-b', type: 'image' }, + ], +}, null, 2)).toString('base64') + +const code = `async page => { + const payload = ${JSON.stringify(payload)}; + + // --- 1. Installability signals: manifest + active service worker --- + await page.goto(${JSON.stringify(PREVIEW_URL)}); + const manifest = await page.evaluate(async () => { + const link = document.querySelector('link[rel="manifest"]'); + if (!link) throw new Error('no in document'); + const res = await fetch(link.href); + if (!res.ok) throw new Error('manifest fetch failed: ' + res.status); + return res.json(); + }); + if (!manifest.icons || manifest.icons.length < 2 || manifest.display !== 'standalone') { + throw new Error('manifest not installable: ' + JSON.stringify(manifest)); + } + await page.evaluate(() => Promise.race([ + navigator.serviceWorker.ready, + new Promise((_, reject) => setTimeout(() => reject(new Error('service worker never ready')), 10000)), + ])); + + // Reload so the SW controls the page and runtime-caches the hashed assets. + await page.reload(); + const controlled = await page.evaluate(() => !!navigator.serviceWorker.controller); + if (!controlled) throw new Error('page not controlled by service worker after reload'); + console.log('PASS installability: manifest ok, service worker controlling'); + + // --- 2. 9:16 project: portrait composition, all fit modes render --- + await page.evaluate((files) => { + function decodeBase64(base64) { + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index); + return bytes; + } + class MockFileHandle { + constructor(name, base64) { + this.kind = 'file'; + this.name = name; + this._base64 = base64; + } + async getFile() { + const bytes = decodeBase64(this._base64); + const type = this.name.endsWith('.jpg') ? 'image/jpeg' : 'application/json'; + return new File([bytes], this.name, { type, lastModified: 1781145972852 }); + } + async createWritable() { + const handle = this; + return { + write: async (data) => { + const buffer = data instanceof Blob ? await data.arrayBuffer() : data; + handle._base64 = btoa(String.fromCharCode(...new Uint8Array(buffer))); + }, + close: async () => {}, + }; + } + } + class MockDirHandle { + constructor(files) { + this.name = 'demo'; + this._files = files; + } + async getFileHandle(name, options) { + const handle = this._files.get(name); + if (handle) return handle; + if (options?.create) { + const created = new MockFileHandle(name, ''); + this._files.set(name, created); + return created; + } + throw new DOMException('NotFoundError'); + } + async *values() { + for (const handle of this._files.values()) yield handle; + } + } + const handles = new Map( + Object.entries(files).map(([name, base64]) => [name, new MockFileHandle(name, base64)]), + ); + window.__mockHandles = handles; + window.showDirectoryPicker = async () => new MockDirHandle(handles); + }, payload); + + await page.getByRole('button', { name: 'Open Folder' }).click(); + await page.getByText('photo2.jpg').first().waitFor(); + + async function assertPortraitPlayer(label) { + const ratio = await page.evaluate(() => { + const player = document.querySelector('.__remotion-player'); + if (!player) return null; + const rect = player.getBoundingClientRect(); + return rect.width / rect.height; + }); + if (ratio === null) throw new Error(label + ': remotion player element not found'); + if (Math.abs(ratio - 9 / 16) > 0.01) { + throw new Error(label + ': expected portrait 9:16 player, got width/height=' + ratio.toFixed(3)); + } + console.log('PASS ' + label + ': player aspect ' + ratio.toFixed(3)); + } + + const aspectToggle = page.getByRole('radio', { name: '9:16' }); + if (!(await aspectToggle.getAttribute('data-state')).includes('on')) { + throw new Error('aspect toggle did not restore 9:16 from slideshow.json'); + } + await assertPortraitPlayer('9:16 restored (cover)'); + + // Play briefly to prove playback works in portrait. + await page.keyboard.press(' '); + await page.waitForTimeout(700); + await page.keyboard.press(' '); + + for (const fit of ['Letterbox', 'Blur fill']) { + await page.getByLabel('Fit mode').click(); + await page.getByRole('option', { name: fit }).click(); + await page.waitForTimeout(400); + await assertPortraitPlayer('9:16 ' + fit); + } + await page.screenshot({ path: 'issue-19-pwa-aspect-ratio.png', fullPage: true }); + + // --- 3. Offline: app shell still loads --- + await page.context().setOffline(true); + await page.reload(); + await page.getByRole('button', { name: 'Open Folder' }).waitFor({ timeout: 10000 }); + console.log('PASS offline: app shell loaded while offline'); + await page.context().setOffline(false); +}` + +let status = run(['open', PREVIEW_URL, '--browser=chrome']) +if (status !== 0) process.exit(status) + +status = run(['run-code', code]) +run(['close']) +process.exit(status) From 7bcc47782de1c8f01230bf33cb86986aecf622a8 Mon Sep 17 00:00:00 2001 From: cbaucom Date: Thu, 11 Jun 2026 14:10:37 -0400 Subject: [PATCH 6/6] Validate aspectRatio from slideshow.json at the restore boundary ASPECT_RATIOS is now the single source of truth for the union type and the sidebar options; isAspectRatio guards untrusted file input so an unsupported value falls back to the default instead of crashing the player with undefined dimensions. Co-Authored-By: Claude Fable 5 --- src/editor-shell/GlobalSettingsPanel.tsx | 3 +-- src/editor-shell/useProject.ts | 4 ++-- src/timeline-core/aspect.test.ts | 17 ++++++++++++++++- src/timeline-core/aspect.ts | 9 ++++++++- src/timeline-core/index.ts | 2 +- 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/editor-shell/GlobalSettingsPanel.tsx b/src/editor-shell/GlobalSettingsPanel.tsx index 800c99a..bb9a42b 100644 --- a/src/editor-shell/GlobalSettingsPanel.tsx +++ b/src/editor-shell/GlobalSettingsPanel.tsx @@ -10,6 +10,7 @@ import { import { Switch } from '@/components/ui/switch' import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group' import type { GlobalSettings, TransitionType, FitMode, ThemeName } from '../timeline-core/settings' +import { ASPECT_RATIOS } from '../timeline-core/aspect' import type { AspectRatio } from '../timeline-core/aspect' const THEME_LABELS: Record = { @@ -20,8 +21,6 @@ const THEME_LABELS: Record = { const THEME_NAMES: ThemeName[] = ['classic', 'energetic', 'plain'] -const ASPECT_RATIOS: AspectRatio[] = ['16:9', '9:16', '1:1'] - type Props = { aspectRatio: AspectRatio onAspectRatioChange: (ratio: AspectRatio) => void diff --git a/src/editor-shell/useProject.ts b/src/editor-shell/useProject.ts index 457cb19..7c24fa8 100644 --- a/src/editor-shell/useProject.ts +++ b/src/editor-shell/useProject.ts @@ -1,7 +1,7 @@ import { useCallback, useEffect, useRef, useState } from 'react' import type { Slide } from '../timeline-core/types' import { isTitleSlide } from '../timeline-core/types' -import { DEFAULT_ASPECT_RATIO, DEFAULT_GLOBAL_SETTINGS } from '../timeline-core' +import { DEFAULT_ASPECT_RATIO, DEFAULT_GLOBAL_SETTINGS, isAspectRatio } from '../timeline-core' import type { AspectRatio, GlobalSettings, ThemeName } from '../timeline-core' import { addRecentProject, @@ -120,7 +120,7 @@ export function useProject({ onFolderLoaded }: Options = {}) { }) pendingAudioRevokeRef.current = audioTracks pendingRevokeRef.current = latestSlidesRef.current - setAspectRatio(savedData?.aspectRatio ?? DEFAULT_ASPECT_RATIO) + setAspectRatio(isAspectRatio(savedData?.aspectRatio) ? savedData.aspectRatio : DEFAULT_ASPECT_RATIO) setAudioTracks(nextAudioTracks) setGlobalSettings(restoredSettings) setThemeName(restoredThemeName) diff --git a/src/timeline-core/aspect.test.ts b/src/timeline-core/aspect.test.ts index f8af248..d67bc9e 100644 --- a/src/timeline-core/aspect.test.ts +++ b/src/timeline-core/aspect.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { DEFAULT_ASPECT_RATIO, dimensionsForAspectRatio } from './aspect' +import { DEFAULT_ASPECT_RATIO, dimensionsForAspectRatio, isAspectRatio } from './aspect' describe('dimensionsForAspectRatio', () => { it('16:9 is a 1920×1080 landscape canvas', () => { @@ -15,6 +15,21 @@ describe('dimensionsForAspectRatio', () => { }) }) +describe('isAspectRatio', () => { + it('accepts every supported ratio', () => { + expect(isAspectRatio('16:9')).toBe(true) + expect(isAspectRatio('9:16')).toBe(true) + expect(isAspectRatio('1:1')).toBe(true) + }) + + it('rejects unsupported values from untrusted slideshow.json', () => { + expect(isAspectRatio('4:3')).toBe(false) + expect(isAspectRatio(123)).toBe(false) + expect(isAspectRatio(undefined)).toBe(false) + expect(isAspectRatio(null)).toBe(false) + }) +}) + describe('DEFAULT_ASPECT_RATIO', () => { it('defaults to a 16:9 1080p canvas', () => { expect(DEFAULT_ASPECT_RATIO).toBe('16:9') diff --git a/src/timeline-core/aspect.ts b/src/timeline-core/aspect.ts index 09f50f1..75dbd0f 100644 --- a/src/timeline-core/aspect.ts +++ b/src/timeline-core/aspect.ts @@ -1,4 +1,6 @@ -export type AspectRatio = '16:9' | '9:16' | '1:1' +export const ASPECT_RATIOS = ['16:9', '9:16', '1:1'] as const + +export type AspectRatio = (typeof ASPECT_RATIOS)[number] export const DEFAULT_ASPECT_RATIO: AspectRatio = '16:9' @@ -7,6 +9,11 @@ export type CanvasDimensions = { height: number } +/** Boundary guard: slideshow.json is untrusted input, so validate before use. */ +export function isAspectRatio(value: unknown): value is AspectRatio { + return typeof value === 'string' && (ASPECT_RATIOS as readonly string[]).includes(value) +} + export function dimensionsForAspectRatio(ratio: AspectRatio): CanvasDimensions { switch (ratio) { case '16:9': diff --git a/src/timeline-core/index.ts b/src/timeline-core/index.ts index bf13005..0a14e88 100644 --- a/src/timeline-core/index.ts +++ b/src/timeline-core/index.ts @@ -1,4 +1,4 @@ -export { DEFAULT_ASPECT_RATIO, dimensionsForAspectRatio } from './aspect' +export { ASPECT_RATIOS, DEFAULT_ASPECT_RATIO, dimensionsForAspectRatio, isAspectRatio } from './aspect' export type { AspectRatio, CanvasDimensions } from './aspect' export { getMediaType, isSupportedAudio, isSupportedMedia, sortByFilename } from './media' export { moveSlide, toggleExcluded, filterIncluded, createTitleSlide } from './timeline'