From 621d581d87799a9dd108c383c6ce3316e2ca00ca Mon Sep 17 00:00:00 2001 From: Rob Ekl Date: Thu, 4 Jun 2026 17:14:58 -0500 Subject: [PATCH] Fix large GEDCOM files failing to load or freezing the browser (#296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(upload): bypass history.pushState size limit with in-memory GEDCOM store Firefox caps history.pushState state at 640KB; Safari at ~512KB. The upload handler was passing the raw GEDCOM string through navigate(..., {state}), which calls history.pushState. A 10MB GEDCOM caused an immediate SecurityError in Firefox and Safari, preventing the chart from ever loading. Add a bounded in-memory store (gedcom_store.ts) capped to 1 entry — uploading a new file evicts the previous one, preventing unbounded memory growth during a session. navigate() now carries only a file fingerprint hash in the URL; loadGedcom() retrieves the GEDCOM from the store by hash. Update embedded.ts to store its injected GEDCOM under the key 'embedded' before calling loadGedcom(), keeping the data path consistent. Co-Authored-By: Claude Sonnet 4.6 * fix(fingerprint): replace 500ms full-file md5 with fast metadata + 4KB sample hash The upload handler called md5() over the full GEDCOM string before navigating — ~240ms for a 10MB file, blocking the main thread before the user saw any feedback. Replace with a hash over file.name|file.size|file.lastModified plus the first and last 4KB of content. Runtime drops to ~1ms. The 4KB sample captures the GEDCOM header (software name, export date, submitter info) which differs between any two real exports, providing strong collision resistance for a local session cache without a cryptographic guarantee. Extract fileFingerprint to src/util/file_fingerprint.ts so it can be unit tested independently of the React component. Co-Authored-By: Claude Sonnet 4.6 * perf(parse): async GEDCOM conversion with event-loop yields and onProgress callback convertGedcom was synchronous, blocking the main thread for the full parse duration (~1.2s parseGedcom + ~700ms gedcomEntriesToJson in Safari). The browser spinner could not update and GC could not run between steps. Make convertGedcom async and insert await setTimeout(0) yields between the four major steps (parse, convert, normalize, index). This breaks the work into chunks, allows incremental GC, and keeps the spinner responsive. Add onProgress?(status: string) => void to convertGedcom, prepareData, loadGedcom, loadFromUrl, loadAndPrepareFile, and the DataSource interface. All data source implementations (UploadedDataSource, GedcomUrlDataSource, GoogleDriveDataSource) forward the callback down the load pipeline. WikiTree and Embedded accept the parameter but do not use it. Also skip the sessionStorage JSON.stringify for GEDCOM files over 512KB. The serialized TopolaData (chartData 7.85MB + raw parse-gedcom tree 25MB = 33MB) always exceeded the 5MB sessionStorage limit, wasting 10-20s in Safari on a write that was guaranteed to fail. Simplify loadGedcom to (hash, onProgress?) now that the GEDCOM string is always retrieved from the in-memory store rather than passed as a parameter. Remove console.log timing instrumentation and window.__topolaDebug globals that were left in from debugging. Co-Authored-By: Claude Sonnet 4.6 * fix(ui): skip redundant chart re-renders; persist progress overlay through D3 layout Two related issues caused the chart to freeze or appear unresponsive: 1. The Chart useEffect has no dependency array, so it ran after every React render. Each call re-created a JsonDataProvider from all 23K individuals, re-ran the ancestor/descendant layout, and called getComputedTextLength for every visible text element (~500ms per call in Chrome, longer in Safari). For unrelated state changes this froze the browser 3-5 extra times per load. Fix: return early in renderChart() when neither initialRender nor resetPosition is set — the SVG is already correct. 2. The "Loading..." progress pill was mounted only in AppState.LOADING. When data finished parsing, React transitioned to SHOWING_CHART and unmounted the pill before the D3 layout (the slow part) had run. Users saw a blank screen with no feedback for several seconds. Fix: replace the state-gated pill with a persistent fixed overlay driven by a loadingStatus string, visible through both LOADING and the initial chart render. Add onFirstRender() callback to Chart; App clears loadingStatus when the first D3 layout completes. Wire the onProgress callback from the load pipeline into setLoadingStatus so users see step-by-step progress ("Step 1/4: parsing GEDCOM..." through "Rendering chart (23,909 people)..."). Co-Authored-By: Claude Sonnet 4.6 * perf(search): pre-build Lunr index during browser idle time Building the full-text search index over 23K individuals takes ~600ms synchronously. This was deferred to the first keystroke, freezing the input field for up to 3 seconds in Safari with no user feedback. Schedule the build via requestIdleCallback (5s timeout) after data loads, with a 200ms setTimeout fallback for browsers without requestIdleCallback. The index is typically warm before the user types. getOrBuildIndex() still builds synchronously on the first keystroke if idle scheduling has not yet fired (e.g. user types immediately after load). Co-Authored-By: Claude Sonnet 4.6 * test: add coverage for gedcom_store, loadGedcom store path, and fileFingerprint gedcom_store.spec.ts - Returns undefined for unknown hash - Stores and retrieves GEDCOM by hash - Evicts the previous entry when a new file is stored (bounded-to-1 behavior) - Returns undefined for a hash that was evicted load_data_store.spec.ts - loadGedcom throws ERROR_LOADING_UPLOADED_FILE when hash is absent from store - loadGedcom successfully parses GEDCOM retrieved from the in-memory store - onProgress callback receives all four step messages in the correct order upload_menu.spec.ts (via src/util/file_fingerprint.ts) - Returns a valid 32-character hex hash - Produces identical hashes for identical inputs - Differs for different filename, file size, content beyond 4KB boundary, and image file list Co-Authored-By: Claude Sonnet 4.6 * fix(ui): clear loading pill for all chart types including Donatso The progress pill persisted indefinitely on the Donatso chart because DonatsoChart did not call onFirstRender — only the D3-based Chart did. Add onFirstRender? to DonatsoChartProps and call it from the existing useEffect on the first render, consistent with how Chart handles it. Pass onFirstRender={() => setLoadingStatus('')} from renderChart in app.tsx. Update visual regression snapshots for macOS (darwin) to reflect the new progress pill and its correct dismissal across all chart types. The 12 non-Donatso failures were pre-existing snapshot staleness; chart-donatso was the one newly introduced by this PR. Co-Authored-By: Claude Sonnet 4.6 * Fix thread onProgress through EmbeddedDataSource.onMessage to loadGedcom The _onProgress parameter accepted by loadData was never forwarded to the onMessage event handler, silently dropping all step-progress callbacks for embedded GEDCOM loads. The progress pill stayed at 'Loading…' for the full parse duration instead of showing the four-step breakdown. Thread onProgress through the event-listener closure by adding it as a parameter to onMessage and passing it to loadGedcom. * Make gedcom_store tests independent of execution order Jest caches module instances, so the module-level let variables persisted across tests. Running with --randomize or in parallel could cause test 1 ('returns undefined for unknown hash') to see a hash left by a previous test. Export clearStoredGedcom() for testing and call it in beforeEach so each test starts from a clean slate regardless of execution order. * Document intentional pre-animation timing of onFirstRender in Chart onFirstRender fires when the chart SVG is in the DOM, before the D3 fade-in animation completes (~400ms). A reviewer noted this as a potential issue, but moving it into animationPromise.then() would keep the progress pill visible for 400ms after the chart is already visible and interactive, creating a confusing overlap. The current timing is correct. Add a comment explaining the design intent. * Pass onProgress to WikiTree and Embedded data sources at call site Both call sites in the loadData callback omitted the onProgress parameter that all other sources (UPLOADED, GEDCOM_URL, GOOGLE_DRIVE) already forward. WikiTree's implementation ignores it today (_onProgress), but wiring it here ensures parity and makes future progress reporting possible without touching app.tsx again. Embedded now receives it and threads it to loadGedcom. * Use const-scoped handles in search useEffect to eliminate type casts The shared 'let handle' variable was typed as a union of setTimeout and requestIdleCallback return types, requiring unsafe casts (handle as number, handle as ReturnType) in the cleanup closures. Each cleanup closure captured the shared variable by reference rather than value. Use a const declaration in each branch so TypeScript infers the correct type automatically and no casts are needed. The early-return form also makes the two branches structurally symmetric. * Remove window event listener in EmbeddedDataSource after GEDCOM received The listener registered in loadData was never removed, causing it to accumulate on every loadData call. In a multi-file workflow the second load would have two listeners firing on the same message, the first with a stale resolve/reject pair. Wrap resolve and reject so the listener removes itself on first settlement, making the registration effectively once-per-load. * Minor code fixes. Moved test to a different file --------- Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Przemek Więch --- src/app.tsx | 96 +++++++++++++----- src/chart.tsx | 15 +++ src/datasource/data_source.ts | 5 +- src/datasource/embedded.ts | 23 ++++- src/datasource/gedcom_store.spec.ts | 43 ++++++++ src/datasource/gedcom_store.ts | 37 +++++++ src/datasource/google_drive.ts | 3 +- src/datasource/load_data.ts | 83 ++++++++------- src/datasource/load_data_store.spec.ts | 45 ++++++++ src/datasource/wikitree.ts | 1 + src/donatso-chart.tsx | 3 + src/menu/search.tsx | 27 ++++- src/menu/upload_menu.tsx | 19 ++-- src/util/file_fingerprint.spec.ts | 52 ++++++++++ src/util/file_fingerprint.ts | 19 ++++ src/util/gedcom_util.ts | 35 ++++++- .../chart-donatso-visual-darwin.png | Bin 0 -> 41269 bytes .../chart-fancy-visual-darwin.png | Bin 0 -> 109305 bytes .../chart-hourglass-visual-darwin.png | Bin 0 -> 42282 bytes .../chart-relatives-visual-darwin.png | Bin 0 -> 51546 bytes .../config-state-default-visual-darwin.png | Bin 0 -> 73656 bytes ...nfig-state-gender-no-ids-visual-darwin.png | Bin 0 -> 74432 bytes .../config-state-minimalist-visual-darwin.png | Bin 0 -> 67428 bytes .../details-complex-name-visual-darwin.png | Bin 0 -> 10653 bytes .../details-events-sources-visual-darwin.png | Bin 0 -> 23638 bytes ...details-immediate-family-visual-darwin.png | Bin 0 -> 22832 bytes .../details-media-fallback-visual-darwin.png | Bin 0 -> 14192 bytes .../details-photo-render-visual-darwin.png | Bin 0 -> 107000 bytes .../intro-page-visual-darwin.png | Bin 0 -> 262113 bytes 29 files changed, 420 insertions(+), 86 deletions(-) create mode 100644 src/datasource/gedcom_store.spec.ts create mode 100644 src/datasource/gedcom_store.ts create mode 100644 src/datasource/load_data_store.spec.ts create mode 100644 src/util/file_fingerprint.spec.ts create mode 100644 src/util/file_fingerprint.ts create mode 100644 tests/charts_visual.spec.ts-snapshots/chart-donatso-visual-darwin.png create mode 100644 tests/charts_visual.spec.ts-snapshots/chart-fancy-visual-darwin.png create mode 100644 tests/charts_visual.spec.ts-snapshots/chart-hourglass-visual-darwin.png create mode 100644 tests/charts_visual.spec.ts-snapshots/chart-relatives-visual-darwin.png create mode 100644 tests/config_visual.spec.ts-snapshots/config-state-default-visual-darwin.png create mode 100644 tests/config_visual.spec.ts-snapshots/config-state-gender-no-ids-visual-darwin.png create mode 100644 tests/config_visual.spec.ts-snapshots/config-state-minimalist-visual-darwin.png create mode 100644 tests/details_visual.spec.ts-snapshots/details-complex-name-visual-darwin.png create mode 100644 tests/details_visual.spec.ts-snapshots/details-events-sources-visual-darwin.png create mode 100644 tests/details_visual.spec.ts-snapshots/details-immediate-family-visual-darwin.png create mode 100644 tests/details_visual.spec.ts-snapshots/details-media-fallback-visual-darwin.png create mode 100644 tests/details_visual.spec.ts-snapshots/details-photo-render-visual-darwin.png create mode 100644 tests/intro_visual.spec.ts-snapshots/intro-page-visual-darwin.png diff --git a/src/app.tsx b/src/app.tsx index ca0f580..4057ff4 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -36,7 +36,6 @@ import { getSelection, revokeObjectUrls, UploadedDataSource, - UploadLocationState, UploadSourceSpec, UrlSourceSpec, } from './datasource/load_data'; @@ -164,7 +163,7 @@ function getParamFromSearch(name: string, search: queryString.ParsedQuery) { * Retrieve arguments passed into the application through the URL and uploaded * data. */ -function getArguments(location: H.Location): Arguments { +function getArguments(location: H.Location): Arguments { const search = queryString.parse(location.search); const getParam = (name: string) => getParamFromSearch(name, search); @@ -204,8 +203,6 @@ function getArguments(location: H.Location): Arguments { sourceSpec = { source: DataSourceEnum.UPLOADED, hash, - gedcom: location.state && location.state.data, - images: location.state && location.state.images, }; } else if (url) { sourceSpec = { @@ -261,6 +258,8 @@ const googleDriveDataSource = new GoogleDriveDataSource(); export function App() { /** State of the application. */ const [state, setState] = useState(AppState.INITIAL); + /** Progress message shown during LOADING and initial chart render. */ + const [loadingStatus, setLoadingStatus] = useState('Loading…'); /** Loaded data. */ const [data, setData] = useState(); /** Selected individual. */ @@ -412,28 +411,32 @@ export function App() { ); const loadData = useCallback( - (newSourceSpec: DataSourceSpec, newSelection?: IndiInfo) => { + ( + newSourceSpec: DataSourceSpec, + newSelection?: IndiInfo, + onProgress?: (status: string) => void, + ) => { switch (newSourceSpec.source) { case DataSourceEnum.UPLOADED: - return uploadedDataSource.loadData({ - spec: newSourceSpec, - selection: newSelection, - }); + return uploadedDataSource.loadData( + {spec: newSourceSpec, selection: newSelection}, + onProgress, + ); case DataSourceEnum.GEDCOM_URL: - return gedcomUrlDataSource.loadData({ - spec: newSourceSpec, - selection: newSelection, - }); + return gedcomUrlDataSource.loadData( + {spec: newSourceSpec, selection: newSelection}, + onProgress, + ); case DataSourceEnum.WIKITREE: - return wikiTreeDataSource.loadData({ - spec: newSourceSpec, - selection: newSelection, - }); + return wikiTreeDataSource.loadData( + {spec: newSourceSpec, selection: newSelection}, + onProgress, + ); case DataSourceEnum.EMBEDDED: - return embeddedDataSource.loadData({ - spec: newSourceSpec, - selection: newSelection, - }); + return embeddedDataSource.loadData( + {spec: newSourceSpec, selection: newSelection}, + onProgress, + ); case DataSourceEnum.GOOGLE_DRIVE: if (!isGoogleDriveConfigured()) { throw new TopolaError( @@ -441,10 +444,13 @@ export function App() { 'Google Drive integration is not configured.', ); } - return googleDriveDataSource.loadData({ - spec: newSourceSpec as GoogleDriveSourceSpec, - selection: newSelection, - }); + return googleDriveDataSource.loadData( + { + spec: newSourceSpec as GoogleDriveSourceSpec, + selection: newSelection, + }, + onProgress, + ); } }, [wikiTreeDataSource], @@ -531,12 +537,23 @@ export function App() { setFreezeAnimation(args.freezeAnimation); setConfig(args.config); const currentFetchId = ++fetchIdRef.current; + setLoadingStatus('Loading…'); try { - const data = await loadData(args.sourceSpec, args.selection); + const data = await loadData( + args.sourceSpec, + args.selection, + (status) => { + if (isMountedRef.current) setLoadingStatus(status); + }, + ); if (!isMountedRef.current || fetchIdRef.current !== currentFetchId) { return; } - // Set state with data. + // Show "Rendering chart…" while the initial D3 layout runs (which + // happens in the Chart useEffect after SHOWING_CHART is set). + setLoadingStatus( + `Rendering chart (${data.chartData.indis.length.toLocaleString()} people)…`, + ); setData(data); updateChartWithConfig(args.config, data); setShowSidePanel(args.showSidePanel); @@ -740,6 +757,7 @@ export function App() { data={data.chartData} selection={selection} onSelection={onSelection} + onFirstRender={() => setLoadingStatus('')} /> ); } @@ -756,6 +774,7 @@ export function App() { hideSex={config.sex} placeDisplay={config.place} placeCount={config.placeCount} + onFirstRender={() => setLoadingStatus('')} /> ); } @@ -806,8 +825,31 @@ export function App() { } } + const progressPill = + loadingStatus && + (state === AppState.LOADING || state === AppState.SHOWING_CHART) ? ( +
+ {loadingStatus} +
+ ) : null; + return ( <> + {progressPill} void; } class ChartWrapper { @@ -365,6 +367,13 @@ class ChartWrapper { resetPosition: false, }, ) { + // Nothing changed — the SVG is already correct. Skip re-render. + // This prevents repeated full D3 layout passes (500ms+ each) that happen + // when React re-renders for unrelated state changes. + if (!args.initialRender && !args.resetPosition) { + return; + } + // Wait for animation to finish if animation is in progress. if (!args.initialRender && this.animating) { this.rerenderRequired = true; @@ -561,6 +570,12 @@ export function Chart(props: ChartProps) { initialRender: true, resetPosition: true, }); + // Clear the loading pill now that the chart SVG is in the DOM. + // This fires before the D3 fade-in animation completes (~400ms), which + // is intentional: the chart is visible and interactive at this point, + // and keeping the pill visible during the animation would create a + // confusing overlap. + props.onFirstRender?.(); } }); diff --git a/src/datasource/data_source.ts b/src/datasource/data_source.ts index beb8641..dd24fc1 100644 --- a/src/datasource/data_source.ts +++ b/src/datasource/data_source.ts @@ -28,5 +28,8 @@ export interface DataSource { data?: TopolaData, ): boolean; /** Loads data from the data source. */ - loadData(spec: SourceSelection): Promise; + loadData( + spec: SourceSelection, + onProgress?: (status: string) => void, + ): Promise; } diff --git a/src/datasource/embedded.ts b/src/datasource/embedded.ts index 37eca69..c93f2db 100644 --- a/src/datasource/embedded.ts +++ b/src/datasource/embedded.ts @@ -1,6 +1,7 @@ import {analyticsEvent} from '../util/analytics'; import {getSoftware, TopolaData} from '../util/gedcom_util'; import {DataSource, DataSourceEnum, SourceSelection} from './data_source'; +import {storeGedcom} from './gedcom_store'; import {loadGedcom} from './load_data'; /** @@ -45,6 +46,7 @@ export class EmbeddedDataSource implements DataSource { message: EmbeddedMessage, resolve: (value: TopolaData) => void, reject: (reason: unknown) => void, + onProgress?: (status: string) => void, ) { if (message.message === EmbeddedMessageType.PARENT_READY) { // Parent didn't receive the first 'ready' message, so we need to send it again. @@ -55,7 +57,9 @@ export class EmbeddedDataSource implements DataSource { return; } try { - const data = await loadGedcom('', gedcom); + const embeddedHash = 'embedded'; + storeGedcom(embeddedHash, gedcom, new Map()); + const data = await loadGedcom(embeddedHash, onProgress); const software = getSoftware(data.gedcom.head); analyticsEvent('embedded_file_loaded', { event_label: software, @@ -70,13 +74,24 @@ export class EmbeddedDataSource implements DataSource { async loadData( _source: SourceSelection, + onProgress?: (status: string) => void, ): Promise { // Notify the parent window that we are ready. return new Promise((resolve, reject) => { + // Remove the listener once the GEDCOM arrives (resolve) or an error + // occurs (reject) to prevent it from accumulating across loadData calls. + const wrappedResolve = (value: TopolaData) => { + window.removeEventListener('message', listener); + resolve(value); + }; + const wrappedReject = (reason: unknown) => { + window.removeEventListener('message', listener); + reject(reason); + }; + const listener = (event: MessageEvent) => + this.onMessage(event.data, wrappedResolve, wrappedReject, onProgress); window.parent.postMessage({message: EmbeddedMessageType.READY}, '*'); - window.addEventListener('message', (data) => - this.onMessage(data.data, resolve, reject), - ); + window.addEventListener('message', listener); }); } } diff --git a/src/datasource/gedcom_store.spec.ts b/src/datasource/gedcom_store.spec.ts new file mode 100644 index 0000000..9d5fcfe --- /dev/null +++ b/src/datasource/gedcom_store.spec.ts @@ -0,0 +1,43 @@ +import {beforeEach, describe, expect, it} from '@jest/globals'; +import {clearStoredGedcom, getStoredGedcom, storeGedcom} from './gedcom_store'; + +describe('gedcom_store', () => { + const images = new Map(); + + beforeEach(() => { + // Jest caches module instances, so module-level state persists across + // tests. Reset explicitly to keep tests independent of execution order. + clearStoredGedcom(); + }); + + it('returns undefined for an unknown hash', () => { + expect(getStoredGedcom('unknown')).toBeUndefined(); + }); + + it('stores and retrieves a GEDCOM by hash', () => { + storeGedcom('hash-a', '0 HEAD', images); + const result = getStoredGedcom('hash-a'); + expect(result?.gedcom).toBe('0 HEAD'); + expect(result?.images).toBe(images); + }); + + it('evicts the previous entry when a new one is stored', () => { + storeGedcom('hash-b', 'file b content', images); + storeGedcom('hash-c', 'file c content', images); + // Old hash is gone. + expect(getStoredGedcom('hash-b')).toBeUndefined(); + // New hash is retrievable. + expect(getStoredGedcom('hash-c')?.gedcom).toBe('file c content'); + }); + + it('returns undefined for a hash that was evicted', () => { + storeGedcom('hash-d', 'first', images); + storeGedcom('hash-e', 'second', images); + expect(getStoredGedcom('hash-d')).toBeUndefined(); + }); + + it('returns the same entry on repeated gets', () => { + storeGedcom('hash-f', 'data', images); + expect(getStoredGedcom('hash-f')).toBe(getStoredGedcom('hash-f')); + }); +}); diff --git a/src/datasource/gedcom_store.ts b/src/datasource/gedcom_store.ts new file mode 100644 index 0000000..bb9a7ce --- /dev/null +++ b/src/datasource/gedcom_store.ts @@ -0,0 +1,37 @@ +/** + * In-memory store for uploaded GEDCOM content, keyed by file fingerprint. + * + * Avoids passing the raw GEDCOM string through history.pushState (640KB limit + * in Firefox, ~512KB in Safari) while keeping the content available for + * within-tab navigation. + * + * Bounded to 1 entry: uploading a new file evicts the previous one. + * A typical session involves one active file; bounding prevents unbounded + * memory growth when users upload multiple large files in one session. + */ +let currentHash: string | null = null; +let currentEntry: {gedcom: string; images: Map} | null = null; + +export function storeGedcom( + hash: string, + gedcom: string, + images: Map, +): void { + currentHash = hash; + currentEntry = {gedcom, images}; +} + +export function getStoredGedcom( + hash: string, +): {gedcom: string; images: Map} | undefined { + return currentHash === hash && currentEntry ? currentEntry : undefined; +} + +/** + * Resets store state to empty. Use in test beforeEach to prevent module-level + * state from bleeding between test cases (Jest caches module instances). + */ +export function clearStoredGedcom(): void { + currentHash = null; + currentEntry = null; +} diff --git a/src/datasource/google_drive.ts b/src/datasource/google_drive.ts index be3a07e..e5dca47 100644 --- a/src/datasource/google_drive.ts +++ b/src/datasource/google_drive.ts @@ -35,6 +35,7 @@ export class GoogleDriveDataSource implements DataSource async loadData( source: SourceSelection, + onProgress?: (status: string) => void, ): Promise { const fileId = source.spec.fileId; const cacheKey = `${GOOGLE_DRIVE_CACHE_KEY_PREFIX}${fileId}`; @@ -88,6 +89,6 @@ export class GoogleDriveDataSource implements DataSource // 5. Parse and prepare the file content const blob = await response.blob(); - return loadAndPrepareFile(blob, cacheKey); + return loadAndPrepareFile(blob, cacheKey, onProgress); } } diff --git a/src/datasource/load_data.ts b/src/datasource/load_data.ts index eaebeb5..1efb31a 100644 --- a/src/datasource/load_data.ts +++ b/src/datasource/load_data.ts @@ -5,6 +5,7 @@ import {analyticsEvent} from '../util/analytics'; import {TopolaError} from '../util/error'; import {convertGedcom, getSoftware, TopolaData} from '../util/gedcom_util'; import {DataSource, DataSourceEnum, SourceSelection} from './data_source'; +import {getStoredGedcom} from './gedcom_store'; /** * Returns a valid IndiInfo object, either with the given indi and generation @@ -23,20 +24,29 @@ export function getSelection( return {id, generation: selection?.generation || 0}; } -function prepareData( +// sessionStorage limit is ~5MB in all browsers. The serialized output for +// typical files includes both chartData (~7.85MB for 24K individuals) and the +// raw gedcom entry tree (~25MB), so attempting JSON.stringify for large files +// wastes 10-20s in Safari and always fails. Skip caching above this threshold. +const SESSION_CACHE_GEDCOM_LIMIT = 512 * 1024; // 512KB raw GEDCOM → safe to try + +async function prepareData( gedcom: string, cacheId: string, images?: Map, -): TopolaData { - const data = convertGedcom(gedcom, images || new Map()); + onProgress?: (status: string) => void, +): Promise { + const data = await convertGedcom(gedcom, images || new Map(), onProgress); if (!images || images.size === 0) { - const dataToSerialize = {...data}; - delete dataToSerialize.images; - const serializedData = JSON.stringify(dataToSerialize); - try { - sessionStorage.setItem(cacheId, serializedData); - } catch (e) { - console.warn('Failed to store data in session storage: ' + e); + if (gedcom.length <= SESSION_CACHE_GEDCOM_LIMIT) { + const dataToSerialize = {...data}; + delete dataToSerialize.images; + const serializedData = JSON.stringify(dataToSerialize); + try { + sessionStorage.setItem(cacheId, serializedData); + } catch (e) { + console.warn('Failed to store data in session storage: ' + e); + } } } return data; @@ -121,10 +131,11 @@ export async function loadFile( export async function loadAndPrepareFile( blob: Blob, cacheId: string, + onProgress?: (status: string) => void, ): Promise { const {gedcom, images} = await loadFile(blob); try { - return prepareData(gedcom, cacheId, images); + return await prepareData(gedcom, cacheId, images, onProgress); } catch (error) { revokeObjectUrls(images); throw error; @@ -135,6 +146,7 @@ export async function loadAndPrepareFile( export async function loadFromUrl( url: string, handleCors: boolean, + onProgress?: (status: string) => void, ): Promise { try { const cachedData = sessionStorage.getItem(url); @@ -165,14 +177,13 @@ export async function loadFromUrl( throw new Error(response.statusText); } - return loadAndPrepareFile(await response.blob(), url); + return loadAndPrepareFile(await response.blob(), url, onProgress); } -/** Loads data from the given GEDCOM file contents. */ +/** Loads GEDCOM data from the cache or the in-memory store by its hash. */ export async function loadGedcom( hash: string, - gedcom?: string, - images?: Map, + onProgress?: (status: string) => void, ): Promise { try { const cachedData = sessionStorage.getItem(hash); @@ -182,36 +193,31 @@ export async function loadGedcom( } catch (e) { console.warn('Failed to load data from session storage: ' + e); } - if (!gedcom) { + // Retrieve from the in-memory store (survives within-tab navigation even + // when the GEDCOM is too large for history.pushState or sessionStorage). + const stored = getStoredGedcom(hash); + if (!stored) { throw new TopolaError( 'ERROR_LOADING_UPLOADED_FILE', 'Error loading data. Please upload your file again.', ); } try { - return prepareData(gedcom, hash, images); + return await prepareData(stored.gedcom, hash, stored.images, onProgress); } catch (error) { - revokeObjectUrls(images); + revokeObjectUrls(stored.images); throw error; } } export interface UploadSourceSpec { source: DataSourceEnum.UPLOADED; - gedcom?: string; - /** Hash of the GEDCOM contents. */ + /** Fingerprint of the uploaded file, used as cache key and store lookup. */ hash: string; - images?: Map; -} - -export interface UploadLocationState { - data: string; - images: Map; } /** Files opened from the local computer. */ export class UploadedDataSource implements DataSource { - // isNewData(args: Arguments, state: State): boolean { isNewData( newSource: SourceSelection, oldSource: SourceSelection, @@ -222,18 +228,12 @@ export class UploadedDataSource implements DataSource { async loadData( source: SourceSelection, + onProgress?: (status: string) => void, ): Promise { try { - const data = await loadGedcom( - source.spec.hash, - source.spec.gedcom, - source.spec.images, - ); + const data = await loadGedcom(source.spec.hash, onProgress); const software = getSoftware(data.gedcom.head); - analyticsEvent('upload_file_loaded', { - event_label: software, - event_value: (source.spec.images && source.spec.images.size) || 0, - }); + analyticsEvent('upload_file_loaded', {event_label: software}); return data; } catch (error) { analyticsEvent('upload_file_error'); @@ -259,9 +259,16 @@ export class GedcomUrlDataSource implements DataSource { return newSource.spec.url !== oldSource.spec.url; } - async loadData(source: SourceSelection): Promise { + async loadData( + source: SourceSelection, + onProgress?: (status: string) => void, + ): Promise { try { - const data = await loadFromUrl(source.spec.url, source.spec.handleCors); + const data = await loadFromUrl( + source.spec.url, + source.spec.handleCors, + onProgress, + ); const software = getSoftware(data.gedcom.head); analyticsEvent('upload_file_loaded', {event_label: software}); return data; diff --git a/src/datasource/load_data_store.spec.ts b/src/datasource/load_data_store.spec.ts new file mode 100644 index 0000000..45eee03 --- /dev/null +++ b/src/datasource/load_data_store.spec.ts @@ -0,0 +1,45 @@ +import {describe, expect, it} from '@jest/globals'; +import {storeGedcom} from './gedcom_store'; +import {loadGedcom} from './load_data'; + +// Minimal GEDCOM with 1 individual and 1 family so convertGedcom succeeds. +const MINIMAL_GEDCOM = [ + '0 HEAD', + '1 CHAR UTF-8', + '0 @I1@ INDI', + '1 NAME Test /Person/', + '1 SEX M', + '1 FAMS @F1@', + '0 @F1@ FAM', + '1 HUSB @I1@', + '0 TRLR', +].join('\n'); + +describe('loadGedcom()', () => { + it('throws ERROR_LOADING_UPLOADED_FILE when hash is not in store', async () => { + await expect(loadGedcom('nonexistent-hash')).rejects.toMatchObject({ + code: 'ERROR_LOADING_UPLOADED_FILE', + }); + }); + + it('loads GEDCOM from the in-memory store', async () => { + const hash = 'test-store-hash'; + storeGedcom(hash, MINIMAL_GEDCOM, new Map()); + const data = await loadGedcom(hash); + expect(data.chartData.indis).toHaveLength(1); + expect(data.chartData.fams).toHaveLength(1); + }); + + it('calls onProgress at each parse step', async () => { + const hash = 'test-progress-hash'; + storeGedcom(hash, MINIMAL_GEDCOM, new Map()); + const steps: string[] = []; + await loadGedcom(hash, (status) => steps.push(status)); + expect(steps).toEqual([ + 'Step 1/4: parsing GEDCOM…', + 'Step 2/4: building family graph…', + 'Step 3/4: sorting & normalizing…', + 'Step 4/4: indexing records…', + ]); + }); +}); diff --git a/src/datasource/wikitree.ts b/src/datasource/wikitree.ts index 809f9f2..d52574c 100644 --- a/src/datasource/wikitree.ts +++ b/src/datasource/wikitree.ts @@ -87,6 +87,7 @@ export class WikiTreeDataSource implements DataSource { async loadData( source: SourceSelection, + _onProgress?: (status: string) => void, ): Promise { if (!source.selection) { throw new TopolaError( diff --git a/src/donatso-chart.tsx b/src/donatso-chart.tsx index 7885f05..4ebc886 100644 --- a/src/donatso-chart.tsx +++ b/src/donatso-chart.tsx @@ -17,6 +17,8 @@ export interface DonatsoChartProps { data: JsonGedcomData; selection: IndiInfo; onSelection: (indiInfo: IndiInfo) => void; + /** Called once after the initial chart render completes. */ + onFirstRender?: () => void; } function getOtherSpouse(fam: JsonFam, indi: string) { @@ -114,6 +116,7 @@ export function DonatsoChart(props: DonatsoChartProps) { useEffect(() => { if (!prevProps) { chartWrapper.current.initializeChart(props, intl); + props.onFirstRender?.(); } else { chartWrapper.current.updateChart(props, intl); } diff --git a/src/menu/search.tsx b/src/menu/search.tsx index 8f7299a..077d564 100644 --- a/src/menu/search.tsx +++ b/src/menu/search.tsx @@ -51,12 +51,20 @@ export function SearchBar(props: Props) { } as SearchResultProps; } + /** Returns the index, building it on first use to avoid blocking the initial render. */ + function getOrBuildIndex(): SearchIndex { + if (!searchIndex.current) { + searchIndex.current = buildSearchIndex(props.data); + } + return searchIndex.current; + } + /** On search input change. */ function handleSearch(input: string | undefined) { - if (!input || !searchIndex.current) { + if (!input) { return; } - const results = searchIndex.current + const results = getOrBuildIndex() .search(input) .map((result) => displaySearchResult(result)); setSearchResults(results); @@ -76,9 +84,20 @@ export function SearchBar(props: Props) { setSearchString(value || ''); } - // Initialize the search index. + // When data changes, reset the index and schedule a background rebuild so + // the first keystroke doesn't block the UI. Falls back to a 200ms timeout + // if requestIdleCallback is unavailable (e.g. Firefox 115, Safari 16). useEffect(() => { - searchIndex.current = buildSearchIndex(props.data); + searchIndex.current = undefined; + const build = () => { + searchIndex.current = buildSearchIndex(props.data); + }; + if (typeof requestIdleCallback !== 'undefined') { + const handle = requestIdleCallback(build, {timeout: 5000}); + return () => cancelIdleCallback(handle); + } + const handle = setTimeout(build, 200); + return () => clearTimeout(handle); }, [props.data]); return ( diff --git a/src/menu/upload_menu.tsx b/src/menu/upload_menu.tsx index 41e552e..bab7b32 100644 --- a/src/menu/upload_menu.tsx +++ b/src/menu/upload_menu.tsx @@ -1,11 +1,12 @@ -import md5 from 'md5'; import queryString from 'query-string'; import {SyntheticEvent} from 'react'; import {FormattedMessage} from 'react-intl'; import {useLocation, useNavigate} from 'react-router'; import {Dropdown, Icon, Menu} from 'semantic-ui-react'; +import {storeGedcom} from '../datasource/gedcom_store'; import {loadFile} from '../datasource/load_data'; import {analyticsEvent} from '../util/analytics'; +import {fileFingerprint} from '../util/file_fingerprint'; import {isImageFile} from '../util/gedcom_util'; import {MenuType} from './menu_item'; @@ -51,9 +52,16 @@ export function UploadMenu(props: Props) { images.set(file.name.toLowerCase(), URL.createObjectURL(file)), ); - // Hash GEDCOM contents with uploaded image file names. + // Fingerprint the file for cache keying. A content sample + metadata is + // fast (~1ms vs ~500ms for md5 over the full 10MB file) and collision- + // resistant enough for a local session cache. const imageFileNames = Array.from(images.keys()).sort().join('|'); - const hash = md5(md5(gedcom) + imageFileNames); + const hash = fileFingerprint(gedcomFile, gedcom, imageFileNames); + + // Keep GEDCOM in memory instead of history.pushState — browsers cap + // history state at 640KB (Firefox) or 512KB (Safari), well under the + // typical 10MB+ GEDCOM file size. + storeGedcom(hash, gedcom, images); // Use history.replace() when reuploading the same file and history.push() when loading // a new file. @@ -65,10 +73,7 @@ export function UploadMenu(props: Props) { pathname: '/view', search: queryString.stringify({file: hash}), }, - { - replace, - state: {data: gedcom, images}, - }, + {replace}, ); } diff --git a/src/util/file_fingerprint.spec.ts b/src/util/file_fingerprint.spec.ts new file mode 100644 index 0000000..f06e5d1 --- /dev/null +++ b/src/util/file_fingerprint.spec.ts @@ -0,0 +1,52 @@ +import {describe, expect, it} from '@jest/globals'; +import {fileFingerprint} from './file_fingerprint'; + +const makeFile = ( + name: string, + size: number, + lastModified: number, +): Pick => ({name, size, lastModified}); + +describe('fileFingerprint()', () => { + const file = makeFile('family.ged', 10_000, 1_700_000_000_000); + + it('returns a non-empty string', () => { + expect(fileFingerprint(file, '0 HEAD\n0 TRLR', '')).toMatch( + /^[a-f0-9]{32}$/, + ); + }); + + it('produces the same hash for the same inputs', () => { + const a = fileFingerprint(file, '0 HEAD\n0 TRLR', ''); + const b = fileFingerprint(file, '0 HEAD\n0 TRLR', ''); + expect(a).toBe(b); + }); + + it('differs when file name changes', () => { + const other = makeFile('other.ged', 10_000, 1_700_000_000_000); + expect(fileFingerprint(file, '0 HEAD\n0 TRLR', '')).not.toBe( + fileFingerprint(other, '0 HEAD\n0 TRLR', ''), + ); + }); + + it('differs when file size changes', () => { + const bigger = makeFile('family.ged', 20_000, 1_700_000_000_000); + expect(fileFingerprint(file, '0 HEAD\n0 TRLR', '')).not.toBe( + fileFingerprint(bigger, '0 HEAD\n0 TRLR', ''), + ); + }); + + it('differs when content differs beyond the 4KB sample boundary', () => { + const base = '0 HEAD\n' + 'X'.repeat(8192) + '\n0 TRLR'; + const diff = '0 HEAD\n' + 'Y'.repeat(8192) + '\n0 TRLR'; + expect(fileFingerprint(file, base, '')).not.toBe( + fileFingerprint(file, diff, ''), + ); + }); + + it('differs when image file names change', () => { + const a = fileFingerprint(file, '0 HEAD\n0 TRLR', 'photo.jpg'); + const b = fileFingerprint(file, '0 HEAD\n0 TRLR', ''); + expect(a).not.toBe(b); + }); +}); diff --git a/src/util/file_fingerprint.ts b/src/util/file_fingerprint.ts new file mode 100644 index 0000000..c755eae --- /dev/null +++ b/src/util/file_fingerprint.ts @@ -0,0 +1,19 @@ +import md5 from 'md5'; + +/** + * Returns a cache key for an uploaded file. Uses file metadata + a content + * sample instead of md5-ing the full file (~500ms for 10MB). + * + * 4KB from each end captures the unique GEDCOM header (software, export date, + * submitter) without the cost of hashing megabytes. + */ +export function fileFingerprint( + file: Pick, + gedcom: string, + imageFileNames: string, +): string { + const sample = gedcom.slice(0, 4096) + gedcom.slice(-4096); + return md5( + `${file.name}|${file.size}|${file.lastModified}|${sample}|${imageFileNames}`, + ); +} diff --git a/src/util/gedcom_util.ts b/src/util/gedcom_util.ts index 436527d..5abbe24 100644 --- a/src/util/gedcom_util.ts +++ b/src/util/gedcom_util.ts @@ -278,12 +278,31 @@ function filterImages( * @param images Map from file name to image URL. This is used to pass in * uploaded images. */ -export function convertGedcom( +/** + * Yields to the browser event loop, allowing incremental GC and UI updates. + * Calls onProgress if provided; does not touch the DOM directly. + */ +function yieldToEventLoop( + onProgress?: (status: string) => void, + status?: string, +): Promise { + if (status) onProgress?.(status); + return new Promise((resolve) => setTimeout(resolve, 0)); +} + +export async function convertGedcom( gedcom: string, images: Map, -): TopolaData { + onProgress?: (status: string) => void, +): Promise { + await yieldToEventLoop(onProgress, 'Step 1/4: parsing GEDCOM…'); + const entries = parseGedcom(gedcom); + + await yieldToEventLoop(onProgress, 'Step 2/4: building family graph…'); + const json = gedcomEntriesToJson(entries); + if ( !json || !json.indis || @@ -294,9 +313,17 @@ export function convertGedcom( throw new TopolaError('GEDCOM_READ_FAILED', 'Failed to read GEDCOM file'); } + await yieldToEventLoop(onProgress, 'Step 3/4: sorting & normalizing…'); + + const chartData = filterImages(normalizeGedcom(json), images); + + await yieldToEventLoop(onProgress, 'Step 4/4: indexing records…'); + + const gedcomData = prepareGedcom(entries); + return { - chartData: filterImages(normalizeGedcom(json), images), - gedcom: prepareGedcom(entries), + chartData, + gedcom: gedcomData, images, }; } diff --git a/tests/charts_visual.spec.ts-snapshots/chart-donatso-visual-darwin.png b/tests/charts_visual.spec.ts-snapshots/chart-donatso-visual-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..edff05dce1b7ee3dc8901dd9b37524fe02dc3836 GIT binary patch literal 41269 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 literal 0 HcmV?d00001 diff --git a/tests/charts_visual.spec.ts-snapshots/chart-fancy-visual-darwin.png b/tests/charts_visual.spec.ts-snapshots/chart-fancy-visual-darwin.png new file mode 100644 index 0000000000000000000000000000000000000000..79932096508a012023cb89082eb4600f7a79e761 GIT binary patch literal 109305 zcmYg%WmuGL7cGc{v?w4AqM&ql2q-9xG(#iJ3|&L1NT(p(-JQb_(p}OpfYi_(Lk*na zec$h#^M@aEG1vXfz4u;wueJ6QqN*%MfJ=#shK5G)>Erv)XlNMOXlRe}aUP*Qnf0!9 zLPH})`}AH)!y|PMiT#yH&XqlKd10AppGEGS^wS^c4A0T1#7z{|tzs3yvtni#qXK?% z>3&~<={1gjDX6GYzHlX`J{_QVl0~gb2*qVB(@LqecC)mE?_RrWFKSF7ha*o$_QYfK z#HXgF7N^7~3Y0UN+?P!JeAtC7Bs=0=8$IkD#N?0e*s3<9behS9Ebwl9#aWI(EAo2^xt!a+!&F) zR*(B-k6~uS;+nC;kQ()qN-800b~d9p*Q>SIc!oZsS64afd^5*5fB9yroJ}{HL?yg- z>_JJh(J$E9L!dqPO}iSrJlZC;%}-Q!`l7xDp683dJbi51*!6ya`{_k_uM&gF}0s3qPj(<>=MGr_l|cfuGV7_ zz9K^Km#J`($2nG@5tnu+{Q^j&94_il8nJ@5{#9O$Bo}ubzvFbDS&5cy8IO{n(I&}r ze)5mUaU_`}Yp4kais9+=`xuoJU7}h(CPriuAvW+$d#4|zO0Yt zTT9Q0uF7UZx7cu8*2(~t^BAZ#?MYFS<(7czUk{6F)%Xa#nFbP>?TD%KXsX;Igz1{$ z7ZdF(4tt7k-c)$^#z>tvQyknE{x!Mmq$Uh9!*1UY^g}?&?+I|}NHH7avq5JRuMm$u z3p)x;*|M%bv6S8VFbdk4}yW8;@}xcC%Gdp=U<2GY`SB5y~+$NcF?0# z#usa6k9(ZFf#n4I4eW8FBwOe!$MKg91aAJ}XThs~Z5@&eHO?7!y2>o~q`^}1bY`-q z*qp6<=`b+^wAIv<+5K|agGo0hAbjCG3g#7}j)X)KN_A(RqIC^@JM{`)kbfKA$B!<#B{dX^&b13*Y@rnle= zNsh$i9(RR$%!p3<)9JBBk^$G_zp_ll?6Vgbbp-icpbh8Xh`c15aT%CCc=0&_7J*Cz zUtQ#@`nh>9u*&sfTGJ}YcH$}V6>C}VNpgbHjpK43h+qN;KD&{;gC2(rGt->5{YKp~ zHL;rwfmVmXX2kqAw$E6BG^02jAXW@!DC(}KSLQ`Wgw{?}GLK1dEcpjGs2n^4bUAqv zK19;U-cH*zv3+H+L}j4pBG~U!ej8Ky6D_KE^S@9=Vvdo{j6TR1X_PqjHs>KQ5l&^NM0$`Rm4EC&Mmdu8)Hn zFFHCy9M>F#sH1q}%NkFp$Zsi_RHTdk!Us4kk~vtR5uZi>dNffh4fTwD2=%A>p_G5w z`246|)h1RZldTF29K*ra%&+0GWH;rsx(TpZ;D7`?*J`gc%$d&~(vW%9fIG67akGa36a3+%lf`YN^QI+h0&?MMA=>41fM3;3 z3^M)V*QS5xN+L4x+wV|;;KVW`Sv}&X4J&oAv9SyFz?8&9tN3BCddwGtM2Z;i1=WSC z(@jqOMu+zAn8Ve&_VOMU)e|sXqNv)xi3>Wd*FB?Y*MMi8$KH3bPWpTY0iP!{6TTPL zcZ;CSQW#r*V`7pX#6Nw5cWboihZcRW)U8&Mms6(x=jC(BK0wzrk(<W;;;z9TA&E;}LqTiZj?QK9rfMvtivGPI zQgh=KRV19 zrYp&zbb>Y#ARAh-{-I`;3tC-RJ@K zH?!#u>v9YWS!ed%vR%DkdriJqd<|+2RFP1R<5eHTBI2imesDEt;^TzIgz<#k=Kws- zIaE@)y|IH;U)K~MF9bRr9W(W{Ua)JJKXEpSjQ%kAt!Ic5|Er{>Nq8#S2Ny2392axI z8rD|W5oTJ<9SU7Wa_`PwTbc43`wfaaj`6kXgt7^6A<4X|Dc@Z{89eK^D5SOQG=biQDEQo93|dl*4;i(V_hT z(*u+Rj0uuQeEU4hI1!q>Q29r$a7~X-iJu@e8ILfivr5L)sc}Ed^0s}4z}-Cc?F@*Q z#@$$F?|07xzb+BhPdYQw@Rw9zU8eOWw#Z2(ljqK5vb^)(Z?t^GJd%BYo^?pxu5Etf zwRWp3kcMlGVT1^W)`k%JaN{E%|Fp?q!=VS&Os* zCHo0TNDxM`Bx9Mgq?ABVIMv(rHo<$Z9J?GnSypzOgr#KAv~4vQlPWB^+@@v1rePxP zHxBdfH{wF_6=;Fj?n$g8nfic2d07xmr}(*?O+$2zCr8mM3yOllKJhWFqJ8tG)NG9E zyP)TEYa?;mg8w{%GTJU+OOXB5t9-mD7LwlmWafXn5rKP}Uw7FuYg-Iabu*Fifa5Q~ z%np>B4U}j|7chPeYUdOM$f)={9^@%7sbv%by>LDIDaWVi-lb)_1N*+xC3PXcv1id! z{NjTKYKEY(rw@8107&ryBhxm&cdpD@bN#7B8*hl_Gw{$jR+WhnM%~6*h--?L_f1FJ z?_YHZHA0omvprqiBZ9}iG~|pJ@#ir1=qmB(Tk)JJ-IBwT8R0>;5)Qi8>KGSAl~(cf zkI}%hT+F}v21*V}g|=2!v%K!W5#Xt4gvxxv^Rr^!(~6R9Q+v;|cdE3J^+BySIc-ag z<^-sONP8mAH}k_R@#o*`-L|{kwqvR`?%3o%r3w!bkdvX=sYpG|0;-$!4Ux)P*QV*3 z2gs0P^N`#8h{2tRkD7MRs<7BHtL>sGQS_@DRI>p^eO_1@@EIq$9>q-t#SMt6hOUdQE3`K%dq@<~u+%-^1$O8)6#b4FFmqxRe@cKOFsd|o3z!cl0`a(|U^ ze+3!!Dc79?Z6pcq{sa3X&}&{qSxd+>G2=fj=;ObP<9an!mB>Yp$v%(p?iL07oM+!0 z;XfC39Rh3ah2I|tmT9hBubR(Sg(qdd?cbO;eCc`zVn6nVB zqEUN34;#^c zU!_K9cjIzIvV4$!s8ymk*UpFsKjAeaV#lV1$d%?}1?HN~ z*Y5;oL9gBA$TfSprHg^(6;$ThJE_j+1<76n4~;ono1GokeDAif8xM6Yk-DK}&r({! zBSG{<6^R-;caI|H5**bZ@j%-W$u&O(@~X}h4O?Ov@A@$M*2R1FpWR6*gxz#HCBtjbArPpBt zQvV!uZM*5?VQRzr02NPaf0Du5WqKzkC+93PT0o0U7CuwOnngzo%Xb&(*wkaCdo741 zPOrH^_^_7{yXZp>M;#iT2~yQd5O1r8N_Q{mU(jyhGxPDLI*o$L*QDE*^zy0V#<~uq zv$ijOVLTt2J(7Gpp=G#qpjr3UMXvHspOw1X@-L#qnT4`%6rSxJvF!|xiJ5SBM54ty zyL8PX4aUl(YH&AogVpm*V=IIl0{_+~SQoV54I?^brHDG441$`rk&RvO8q@jtr2w}O zRqVGKv=*t3rya`Rv_aNHnpPfy8S|Gw4uvsi0DpTXHr%Zh?4^E{B!ymrtrWThySg2&#r z@0a$QoyR$cfp)BiR6iE$e}d|Mc(SXqHfdK8cJMoc-O(JSYmuVcxv?>kq7o9VsAglaanwm{&@3PG$iGRC9yV?tUW5K_9>q*%yav(i`KNW zx20>8^`xneC)zqhQu z0FHHn?U0h!YP;)SCIe<)*DE!T_GZ!~j(?)bWtliNnu;OYPMR*i8y#bN0$xz2d8r*y zpbp@l*@Avf-|j+>oygRlP#WTQnjUkQPILI57VyW(%K0%7)l|LtZ}w+|4Ao2ZkAoN% zi%k}!n**wv^;%XA;0GC(sqSA68(-~kt>lSFrrk^ z#jtXJ_SSN`4LzxzhJw9X1*6(mp=HrD^O$UwTYBx;ns8Plq&DoW{ekZ({y)!v;+d#4 z{haDMC#izpo5Wr967Y#yKiaA>cSim_COfl|Aa{LutS;T7@Zi9b(s7#xRS%5o8NBi* z)3KSUjRj&=hQt@vGaGMaJ59q*Ml{#QUnQ=%?-rQ3cu49Ul!#N`^-sXw*P(TJ^pOc}eq2@X3!{D%HwjIh^(RQjog$V@%CtVfd+pxQ z1TI4_bNZGWy;`j%1WM-Tw;&yJHz3nIqcxdGUp0fW@m zW1Xg1LFkrZah54_W*;QE-}To(cioIi-jHvgU-K=3+BU@TuYzALVy2L@hoywgCBB*x zbZ>4Mm26z6OWs6B9jxD#W2`51^c#anWQ!hEW|6wz6+oBQ$q|TX?J4hWSt`zOeuMXg zXE%*DEg_r%wCM@YVbQF=(2VQdAT$@pf0vy0bUEv$KuQ*)#fxt9+a~L?y=8JVK3%Qx zIVq=m2-XzZST`Y2M-if2-zQ@7S^8E(-65^{Ps4;1xki&G5J^(?Z+$#$bp`^3nTHzLKa zIo4AfQy7(Mmt0yP(|}Uu{b$j9qpD6?BK}-El-y>u!g4{?)Rn10Gvh8eY35^LKQ_+9Ti@$Cpko#|`29GSY-lvT(@LCQ{Y%xP;|H025hI{yJ*mZ*(=ynT z?G1vU!!;~NRcYMCzzqe97wM4dFGWN`Hm{5a zq`;(LkIECT{w8XlCO*)LC{9n!f>EpE?mT?%0bCo79I7)PW=XrQ%JinoYtEWww4Wyu zUx&Vs5dKOiu!n7LhF-lzJF70IA0*cN(-D<2g>K@Nck!eHFR6F__RA-XgTe3lV2AYk zQt<%bS}c{8y-u5rj5sxDp&DrF?c}u3#Q{C2_CRziT$gmI{0C-yUX_{}?ExtNGNz9VzP!EIAyqvH>09`|%QM2x1psC!{KgaW3k-(N$A&kYk^&}MWg{F)ej+b^1#Sn>%hUlnfQEn{ChbS&7e-!kf6j)tO2F&;_am}w+nCIj5 zEw{$W$r6>nO=t=3)U>8}m26kyQvDsT>*}2j&6K^cRzOmu!bVaof38qnqTJFxOXK{b zno;Zn&%ba5`<9IpfMEIJVW&Db?1 zSVn2~q)|XP0D`f}Vbr#Yf8?SEY~dfC26Y-w>Fin6rm<(0#_GvLWMc+MPVMw07S-#~ zIUsik1P#CnG7dB4I(SheObtp)zUP17%NEG~Ajxg(UTWVwb_}U9)4Jctp6?$-!MM}_xob+vmVU43^3^8&I;mZa|0p=o7jR6Y-<ru7u%c}q8obH`UUYiEWT8)&8B;vly$na(QgsS zN~bRzD|-Mn>n6WDQ|N8Igm8k_o`l7-$s0b}Ey6sv#(|>bDuUGw^F;yYGrdK@M1!m# zLEhad{jRc^_?x!S^dG81F5JKf-l<31t^4A*?h+2b)7rhZ%`6mlTS)$c-H@mZ4sqTX znGoK*0ooTdB^gW3nwC}trm?~F2+@R)>DV|pE9cziw&JO)hz%(J(X#tZsB@cBJBE81 zrE9+=VkpCFkh6JXpScbB$xHGHEiNGp@f*IpNpsb_?7e6&Aerty>{(#oJHMTf;X5HN z=*nup*nD1kFM?>oEBEWJXgT{TLr9FQI~srF^f>Toa=EOUF~k@IAVL4-6PlL^DL`~&^LG-Gv{s32V+^nZ*u5X$gWZV!h+u)WHhHp zz4}55AZd#}1u(6dHdnU2$b0oD99}dgG%&O1_@LFz5fwa$FdSw&DvK^NSA~&))r!)^ z>?x4-6a9jLG>mpI5W`r23SUI+(QliUvC)3|u#D&#ZjJy8VPWp`{V2b~>oCw84u?DA zyP&0utdzUc{MaaA3R*Kh>IRc0%lpMS$s1nDg+=hywzltmY(e1O&@jH(o1Q0V5NE~` zjMOLJ7tk*b;Xo`PuMUEjS5NccX_?W71EH;0uK&#|>%F zJ;ncW*EOT{z}^>WPc~wN6Gd(ahAD6&yYeJ&OXz65x~jVK0Tqq=gMR0AKtO$e5@Owc z_e;p&=4O#0UZ!~TAr%i&bTO_gg(0rMJdS%_e>pBGp!7VM`LV;`rT=g&2I?B)|1HPC z#+_+(V)ouYc|v3^Z&~Pf^!fOzoI*14G`S#_Njl%AOzR|S1i2uuq|49YVxv2aL=u&E z3Vz2^aqbeT`tx3f$n-%8$2ndiUna4q9>b%p@SXd{Yoety>B6z%}hz}u2gIWCox9gxZ5^8=rU4dK_ zoT<>WuP8BDxj##}Kigiucah(@c)V(Me`A($o2Eh*u&X6;In{>jiB|%w%ljYcM%yy7 z6gM3PH$Fo@QTw6h?IlZIh+vJ=c6-IRWLutb<#NhB3LNC5$e_kRO_eFf4lmgim6Xza zF6MTp%XSx=5KCTdd*+-ef}>_as6=Ic;SO{6#HEjGeb1=i-IMY4%z!F7Em>p4D=)F` z6Li>g`>EE_~De?=ag~Tdw;Zr z%nkn*zUzDX_C{<2=yzU!-0%1nRtuVFxZl1xyzN`y4ztDEGoJQnaAjta;bEE-pnGpH z*vieB+%-4VNZ^UTsJE~Vpjd;y{QNj@0925Y_z`MkIYp@WC7lj#U?_Df6d5i1=r$} zNQLVj604aC{rIJp?dkhaYc-TaL2z1km?LwV%b#LRa zVwb+4c$%$5Co{uiY=h3n!?9>iogK~{yeRF>63F$Q%kw31$|m*$CF%KpvJcUNWPo9| z+m#=;dssOCxAEu9k=^xIh*3>c0TXsXNCYNV1lb^O<{zQ}R>8M*^pSuh^E`QnsA=d~ zlPKpc{KJKln{MSG(fv-4C{wSJ(E3*y<*O2P<;%2ZDDE5A@i2TL3Fi@GIfUCgb-E#y zpZR{W@r-K)(fls)x#k#Fz}u$#SX^eaKSu&pP-L-h@+2xX5)f$h?avl}P#BaipYx#- zU+`(3&`=9HjT>GQA+o9TiivjCAo&W!uf^dLhHb;9`pr&3A+#iOoHQIOb;M*5x$Apm zFIhYDXYV{h64^5zL`NVhfSSD%E0D)Cp&f_F-M6;drT%PxxDdi0O2)>2gue(+p->ba z(3%W)JdVtTKI_`R{}5iE4#mF~dnW;xvT19z-KcxOSoVK0HpHB@{7ciHf*I+XtfpQ_ z*uZbQrE0@v_Fr=Z;fUwqGxhG)uBOFvY#r0XfVN|z<@Dqsr)Rh4(0g!ilCyxg@9__# z+rj(ug!(T|3+sZC7imDVg?T!jl@xI6GKDFaWN%1J^wGEvkZsAh2mf0yiKb^o$+(^b zJHWc3&RrB$HhBMZB+%-}O$F#)?yUdXk++-hok2?UuryxjNb$UlFoj$FcYanYn&djH zWYLIo+kx4NBT~TO4o|yk95tVt7bxqr0O7 zllm1Zzv~FI$UgYXWrt%W+e4$7aZ`mQl|8zMCxVBX8pYJV<4+Y^*_Nc|-9?LmmexBJ z-bayD$L9|rVg4s1!odPPk91`5gksiW+*00pj$OrYjEt|}@wGoi3qOo!lKNKnOAi=x zv46_T*G=xttH34d$8`^&yP950>O-h?&D^i=e(3LBMt<6wP;HkrtPv>>f2K&JP<7_O zC2oJQ5ntDEs^i4iBe|2u^nnhRj*=G)&n95EL}NLDy@k3cBKgK)_e6nS8;<6guU0f0 zp?b6K7+ zgIK(W!znT=XC{BP(AWZ2QlD(wbWPAAw>%YWY{7aZA8-girr6W^$|E5IZAM(&9JFrJ zdOh*G9+6zMZD=tTFgskl;9weMzu8VhZ~{U;eH->^aun6Wb~YM%ylYS9JET`JWhJ*L zmkLS}XLzU5hw@AxRsho8P%!gQ%8U5j-4+$S_4qZ$H^n#0#~OqZXjVM%D}hdfJyn|G zEHvvzwWs#0vn3EG3HHp(#%gV@8)bvA^9Rz2hX~@bABV*TV^%fSBw@S95R8&M%IlM=Sr*a+h5reioHpOeP=kq#@ z?v{bDpHIvcVPBIXaWYG1-~u^;*5Zz=NDiXKOZ<29oTY}X;Egn*S%u3jdvBM!<}0q7 zDy#NvxSn1A3EmM*toTicoe-dubjr@vAjG*AOMjNuA5wKLfih z1Wr8LKvg=8JOcRH8QUMVtAR(Uh4SsS%k=6{CJQ^D-fp43jG@%kAc(krp}epogyXPj zx7=&5O0MP)-vZx(4T=DI>{T|U`C&;l;>pGE&?=#se3We|aVden9m`uxh{``_`k+bFr2JeSmy&j%4ypkn`WTlZI|2%-Og8vFrPe8q>X2QE0 zYwa&SP_(EnQ=}XV?cFcPeUZK?r$*0}D#ZmX-hM&E@envCcsv&~;|}-OI5o2NYu=3C zc=k;FlpH@>WT{pCbH=3VGZo;0Gsu3X^4niU`sjAcVuov zO*~bdsLK16Wbj286tu0^jpCtWhch8&6vHaIrPjm0$k})I3dJrSsn(+ZmL3p?2b*0i zni(Ymgsj^9BSp>%sScW?&1C!dVU+UZ7SGT*%d5%&|LAxRh*xRE3p!Y1Fb-vjwx+4t2&8G4fKAiu{NTd z*(MFv+mV<-oyUo$!`DzLq4GunnUBtoy3~j%985Lhwpv&3_`vLBVBl+I7%@*geyv?p zfLfiB0!Ng$0?qTHG;_(8!E|p1?-2^V8FTA{>fHm8lgonG6H0#V$yuDACQPI63Q-Z5 z`!!Jw!K-`E##Gk8q$KE4XZTkL`kS&W+&Md6LE@QLuS{PLJFWWAN@CvDF)AfsA>jgp zf%5_+d-zX4ydY

r+o^*bs#{7}yZLt<;-y_#Iw!*K)&%d>eD%TmpSu!@q6EOCJaoVeK!ee4Tr$H9 zn6YdBU8Ldkq|`!1Y&jvdAmdmilczI4tHpD-2@Xk?OpMb_povVQK-!d-zKX&9xj2&g zm#pkbh3of+#$u~Tz|5s>lXEx!-3Mp2i&{QOKPo4`jJ^L2&J=hi!gIwGm{p2*n~^(mSF1>z;ssB*wb6u+4tM9?<0#{HMJQ2 zs|1uIJmQFwLd-(@i+~oii7a(uREqJ&@K@H}`sw0W0`<>d_~NTx+kR{0{BrD4BLrPV zye3B2v<5EM%%|V7rXxb7_`eE=lGB}<-mDdG*zyfXN$=z}_kMRA{yrQK$mo=X{$TaYMl|eQeNCbL|G#HL`7NjDso| zO$zm&{Dz2K`Y(6H7M;1<1B>fak!S)zT)YM&9@tU`lr`(KCymU~&O#OpVoDE?{1h=Ta6`(X;q9EE}cU%x_HJ>cqxkKqx2P^CR|bLh!9-z*sq(3oEPEdJ|4FB z=|e1jBx9Z#9(l;fQg(aD)EUyc;>g5_at!L{44=8ystIY#DZOTp(0-WAsQ#VI0?HNy zxTk)36OiyuiGW1KH1j3<8=TUzI3Zs??Bm5tzJ;3dygpEg*1f-WO`o^hu|w4mEa-*& z-tFnAKy@^W7T3yPV-zR=q?1A_&i@%F9Ufx+fbr*z5G* z3Lp{=L0JLZ3tU7GOGktzwx1W*75MT$8g}uscKW>iuCqN+cb#$kH!?VnxLODIXC7uv zIgsi47k7PrAs4&YtK^#vPQR%$qkN zvRg{zvn4|?oMwVt-C3<8=YG~zLi|eL5fSe9a54{ZvnpL`tX9UfTboRfacFilyf(y? znuvJyR-iN@8>S3={P2u_A9`7$dSbGvh^aSb^ai`x2<2SN)vNU9QfM10$#H01NQ&=i zb4;O`EPf}X=@tlEtcvnw_rV_ai*-w7=iZ*4yLFYj+@A)|dV!Uj>0Sb_L)Oxr2HtXd zui#~zms+;mombdT&S|Xp)7}1kpyNij-9~n9o(l_0Zs$|l3by~O1bruO{#%$630}Tg z66BVm@mPxxY`v(f0OC@mUE&#CN;^I}^xhOzkwl&~G;gr?(zwHRrb;ZGM)Isp)~2_; zYi-a%-nj`_+LVXrGuV_v+DhlB?8{+qwKmY5&S}twyx$8StdA9-EVP>0yOg>AX#qt4 zXr8GZ8o7*^08B;{X}3-enk0LQl6;szW;|agvwA+`2>W&H@s$RcP`24y>m^nRA_A#E z9&p|a2+AWr0|7lYiF}XzQ$S7IqxZL?_ghhSZ%9E4_4}Q4cSkqN_jl%l@h!Ushu6sE zd!)va!!oDuahPSQqwEBn)#z%V?JA}1)5c%|=l#QyR{jxS`C2RIndIeP{|n;#hp$v8 zwCvd$-RQ|AAQtQ;6Vc@-q;uer7vp+V!5_%JNnKTWkb3Z$9vG{-rF82U$FsRZmM3C- zY27EjqdM3M%13Y*dRxo8swS2&pz*`}5vyM=yoV$*j|KGD%?-ZX-VSyq;Klg*va)gK zXXa^PlkYN0Uw5lOB4BhDfr0)KYZRkza3!z$Rmyd1@^w8C(B-tkP3Mdon1TCZT~R6v zU)LEbRXsH|E5K7Flv8e~H6r3Xn2OJi`h$sIa~CMsoK+OfBKWIeu0_}LPi*>Gk6`QZ zq`FFgT;S(n;2Q}l3kA9|59dK4d}7%WNcIeQi;@{_$@m|i$9)IYOT(t&lhi$o>?juS z^q*M4{5>$Lg=H-SNVs2Z@U6cY!zoSdoglU6ptmyQn7aR747pBAYfhi$d`h1vY-7U3 z85ScdoI0*wj-}!?KT!^WCubl1Qdgd9bY$$Z{!-z!xJ#LSy`$aM^waEF8t^D4q4_-v zo$I7p1I$UG#%TfAK;yao^K*J3(EkQii!_HGa-HERvaQ!s6%weufTKR**gtPgv9bEh zmcO59JB`T$MHLj~Xk~||67lB=Z7+23d5G!oGKHS~GQpS{^=5lOF$Qnkn>Nw+mw?|*gWt`J@BR#B6RgzldLm<~cKfGqozc}_`J1(J zFSbvcc!6c6Da)l_s@T8iGc5W4dZGkRiWvAwCL~9R@GD!=FBJaBdGmVJyH@Ad$opo< zwZ1S)-p0^=uo6-`n%g^!+vG;aQt(RQfw#7oiY<`mqngSx-gy&V&g6%!v+3S4&L$Q* zmB#IIlGo$f&8wshOJG|T&!cv1DkQ0?(1Q5I?AlihLaS-*a(&Mc;Z4Qm$%mCL$-6a4 z#C$Y4FD8v;(q?)md;R1AXbN;Mey|Z7` zr0jHz|9p)|Ev2x8K@$(@?(3!^Bx5X34T`zN_E{~crVx{Ngl!#1~U3WB!Df!RY%cDX~~=8~+Uy=rRt2Ki5`ru0lOgFZGBLgA9E8{nDCv|d8;I3vL(ks90SW5`xx+y-h z^f`vvj@}og-um4h_%$Ea9r_0a%L0MVVS!O(8%it2T#25dE$4JLj1zyCRjv@r*9prp zbWuM2+?-yU{ig4&jAR)qL+EEQyOhrSa8BA9ZN!Ke3Nbo<&CZ`!uCyrmb^Gm_ya;qF zGpLZ!?@>~+4j$AKXxSyD^XpMpLF-)$V?=ku=p7+6DegY>l6HE7`Qe7SYHsNc&cu$Q zkV0^LH{BOTVa4Ogb7n0@d3nX-U*|f1Z^*9r4?UsG3{Ii?Eo=PYTp+h%ecnP(f`ngt z?C6xE7kH$)ZEcT~3tN+(sja->*uUTN!YfQ5wj-;3yl=Aa?5elW2zloYyY$IZ8qooZF7520^)QxaE_(Kqud3-zO_{ z%{>wB@Q(WFa}|GO)>`$ke>CF%^{nQ0>7=Az)=do01IESi5 zg(GbHZHVW0QdRXFK0V=M5(24nV{VZ*80x?mnyhSTP-AD%Q-%DD5S7EG8ITuUWkRa% z#3jlJMt6Q%U>N^eZ2|GyCw>oNn?Hj!X831`w-qq4(Bt}5B%9c_52uaOYNQ4RCHd}Z z??2_E?&)BUBC4$aHP9hCHvLB98R;C%U1}UHC_i9$2KYt{D`u3<`2Z`T*jA5s!~jQ^ zuq@NiBYxz-|xrg?@Hk;ewsXX zk?Eo?S&8k_Vrr-2Ak>NfwQT+e*E~QLuY-+ul*5k-J3@>Pd^WGNtCx=;kc1lQ^oxHB$9C4Mfw?4IzUuiZmMM4PkN|=u zxHD@;&X!aP%eu2`R(hmLG4N4eb_3<>7ah%4r-VM!?H9KyZKmZ{WVpQ@S#ouy++UWg za{BV`cBgFpBW^!rdcmkN7``qkxfb+syV##>@KWi)X;`-|>oOtR%+DaJ3|<~8S)o76Ul%d?^k+G&|Ynad&T zj4DY~bWLP`=6T?(d&QRzBkN2w#0+-!_j@cWoe8{z_!60OZ!nD|A86%&TK3Es*f~NL zZ)~mkaixSoZSu=~xl-2CaXkYrlfYxgh}W}Q!SC>Itb1uq5IU{ks{-H4w~4HmT?h%s z!(=aD|5G_Si|4wXoCMG6#NP^*$yH!_VV_>&M_>6k)6kTaiJ;c{j4fXWwzzrmJlJ3V zmP@%w#Uf#8Tna8t9Jri{t036S=s_=2?8UA*$ zlmvDKPXhG2Ym`JzD#HDYZIl! zbL@`1CB8n8EuBM0MS9DoNqjAJ)!I5eFW{8?fwuw5v2^}6N5@W;5I1M{>p6=Z zrv#`6q$%}2WfARHkhuu=e%-w~0+~ckZOyroV|24+HyS*hdN1jHN@v$!<9lFvA!@a3 zhacUogT@;=X87oh+*7M-y8MU&+uJ7_w~{&AtHGPOi;efRK@YDHr2X&E2l2R*v z&a6rzhXm+uQ4B{C0|)t(G}Hrcqt)>$TM$W9>awcY`Skw4)(^Xe!tZ9PjXm)?#9k~5 zX@oGGeStUT$CJH5#mrWT zP~7!WDJNQf{t&Jb*|uM?ETG^Y)^c94G@sgbJF7qQr0vp~5}nayZ9W;=naLg0FWt-d zSe+SKV?1CUBF>ofn`7zB9#9k5`gD9nMVR*gH;BHBr(^HA3cWj^sNAc+#J)y8`2g`1 z-weh@8Z8wp>($y(H$e}oE*leQZ8_IFG7K-%?-8_#Ko5juD^%DSaB{0HVSjuOR(43H zF)<9I512rc?beC*nk3I?e{cM?qaSU|`F?Pm&>4ntA!3S3Y19hb|3b|~=y!75rMD9^ zoM`?=p{tUDUk-;Sh%IG1L+7m@H__Y>?=uSes42ACsp=+Gr`-&%eLOM_Lo3epQ%m1F zqt+WvF0Z5GL+@r`OTZufag^;UHi{T%K_76@bp8+$`&-<4?K{KlC4QRcw=^7l2D<&T zMWo)y%M$gVi?>cFB1HBt5qfW1gimu`eyTao7G^)4miT0LLz8%@`R;qHx##P?%dbhB zQq+VU-ZDD19xJv>Hv_ABzV|3Md9P$7aPBKyETx7W*XX8In}0@A|E;HGSp~g$jLg3H zAZYF~wbv5I1p4$ve#@XR#(@|+Xb3ef^Z#d5%4E1G-r^JFVb3_E+4YETw+_oD=e<|c zx#82!#P3M6-^%_QcncMag?41&QOYDhTya#q4px}k*zs}zLDvL+7lA2<5DGUtKFiru zOSQ`r_m=fw0VtOwSOtCI84RNCL`yF#WZP2nD@gUX( z{^Jk|j?X(IBwn65()Y~*`+hYy)+z5{i_|2ayoP@m8hY29+tN!KRL&#pC>pK&WSIHR zOj%;yaAfv!mR0g>z#PHpPebZn*8Gw>_Wx=Ds43I?gL8WQkte5QJ%sq6;{@D^b1y4k zL2nP5mxNNR-@gOk{&rV_G1NKeI+pkO&^f|t?{Zr*4tn%ali+`w9v^!KjK6etcv5(@ zFumqHX3(>s7W9$A)*}mLQxz7aSZDQy?`pOPOtFv}EeSfdCLjmGh_#mxGFsIBDyMJnrz}C=?`i~V2sDL`X||#wAx1` z=+)Ce&$LDLlF7i|t^0Z!hG4xv*DER#gb#z;zqe)Sc&VOF-n1w1C6x(4iB;-adO|6$ zg?ptH+aRzmvG{n(*8D!bV!sFzWm}GL_N1DGVhc)Mt`aq0hFSVOb$jJ~H#C}Y7R1cI zX@N&1^yS{@8bulJxi8+<<$Cvu7fNy=Ccj6LncmZrA(yGl7+8xM+?SW zCt{Y4UEfA49HN`+&(HSiqn_m5=WOW6hM5ORj}%HX@n#<}i{vO)Y$U&sZV0F!xy3y7 zy~429tL`Q7Qj6{EcSjvleaj8qvcdC%rp@xD(*(ssMT@X)%LSjNBXtI;2tj5MZ~K_Xhw509u{7P@7U-Aa1Z|r+-zjSMknF^ z^Ly0+ROkbTGmB2gT){8#dZh4v{ZtSxQEH2eHxQK0nlbGY;t)(YHqfu`T{XJuTe$EX zkm$}s?$6v^kl&w?H-y`NV$W_?;C06u5rR@u-IZl?eL#{*nkc zK#2uasFQ_c|6G!Fb$l6R2B=t$n+*T|aP?JDaWz4=NRZ&cH9!auoZzm(CAbZPyA19Q z!6mr6JHZ*;A-E1M!6iU&hdX@#{nuUVzRbfsozthft7`YIy=gy|EiA{+*_UyY@Vzmf zYrJ<(LIw9@w`P3ZmoRfKv-iDkx6XQT0w%L>mdu|Y>L)t(R`Z|!6<|K*_+2#ZWlD|F z!w(brX_f_#orwyDvw0fyM(L3IXK;;xCR?c2pPyn9k|%dVu7z&HH2-7R{m-JY`R;Av zxB(lu{W|a^@x~U8VxQ0iD4CFHn?{UkK4q9*%5E&t?OAu}RYcU>i3WINpR=2|7@=Mg zO!!@%b^W;$5Y-7XWk~5bpWJZYI?Hn326Fdq2c!CV8j9O*Tz)^(zTX@%nnl&SZs&gO zInJmL78{B-$R`=mwJ^>4G-E4*Q?$tZn8soMQXjiE)KEE2b|&<`H$H`NwZOyizag;y znEFmcL00SRnD+<=GWi-5S`{gRvu5o7LF;vuioU7X$=a@nJ z#+zaMpM5dpF!q6EG8H}Cy+d&|HaUa@q0fSjpTK^P-G7V3Qy_cSid7*|H}>pb8a-ni z(Z$*0;g`wh`~CLo*Y>huM6_wehqPHCr+@qyzQ_6<*NHiqY`mQBf6{Bz=s!bKUbbYv z;N=K=8vqKo+-VA3+?aR#OUoZ17G?%g;nLeI!Kdk=LMf{J&zUiJjR1$;kWZJ|OjP)- z^;7#JRc&Z@;O|aDP$ZGS|HeA5?_8<&{U#Tbv2VfAzH6-Sx7|9qt}(;9NzarJ3d;(X3(y&UCDI;hGWdx{Wo7GW ze;K^da!@R1N96MR@o&>4;JfEBzy8AnfzWaAKVitD;-7{+%HFuj!1Ef+yR8^!?^NFKMq@q`qqsr5G9RxtyXkxMrLD-J8e= z4D?4kV3y^vUxM4a%(Oy%r!uGoJH--VHhI|JSZ2auTThx2Kg@Ba{F%}N^^P3Cd&2O_ zetbW6rRB7JNyp6!}+KVnCmhbbOtRXCn# z*@}{E6g(cOZ#@M>zLnmQ8x9wGk7tIs00xIy;Sc$s3bB=I_u&j%hnDC#<-?0j_Cujd)s(oOE?Ovd8leh0A6x?hMYiM{{GxtM=#71iqJVbZhp* zP$-2@aR1l7z$?+@hTY_I?sKs|cVW}L+sR<##aVGxM$^hfC(`NVj9a$%KXyzE4fEjG z;})zrPHT6N{%Ux;uV9^Gs^oDMb)c3>h2Sz1Q--Di!?F%yCSB+2zU{_}S2KLi|M$%J zPnXT>FWn(Uf8{9@QW{svzgQNnggkMLwu}AYwWt;B;9{8$)SYEQ(4DuDssO9FL5WXA zB|wLPLrnz*#UA4*YAF5RJd30B*ixirP&B0opV zCZAc(#HNBhD+I&VK39)Tp-E_R^MfVjM})55X56%Hj?57>U)7rHK;0 zn{b{tOP{|M8NFfI%gyTi0iWBIuJ%69O5=dss36~{+^s1(dg@Cj9Xk^X0D)UEh0Ucw zh++UG7s5LmFo#L1DM#9&!shqq31{P9?|#=)%Sw&5=kadyn#-&px=WB09&`-nqQMx$ z@8SGA|I>kZw1UXqpIY5~8!nOLs+6SV)e3{BMi?JGy@G{39hTd6(uCuo=B&;O3dPUw zBH}U!h8pAWo%NPwhMrj7=Eo5J%P5Kg=4213US}_JE*YoMx*GK+hJBvLt<%`Bd|m84;77b&-yCB-5zy>2~hbCty!%`Px>m~dY${&Wr>V+T;4B5+Bv^!8Gt<~VKD zXZSVSuDPzLyFnR7CvI!*w^TcAUZ-8=Z>;>&F!-Do+=5=6lt6XBwEe1XaCB90wsbR> zSg<9p8=F1ZN!`o>>MGXxLbn`=W{(YZr(7Dv5A?H+N6~R7xVKIn8pF_udb%m4A)KF{ zkGCxMk6-@GN7JtyH2Mb>o^gFP_php3d_lXWNaB7KQT=GcDcqtr6h+~#6jG!+$h{mFN})K~Ur`4LhLdg*YxPWM`<*6TF8d>CgxtboT@ z?=o!u&VL&AoYR`~C!Lr{OHPDyA(q$qxSFBa{uI(cApPFiSr=rU&R2*;8?)kDS;_g4 zv_Hpl1b$9e`13tCeGl!&YYfa^oAzVW|1&Ly8pD6lXbwF8eO~r;D+ZQogRI|lU{tb8 zLQhsEH@>x6XHBzFtDv3@fbb`2CK>>dsxZ~MK-;W@C-^AMp!#+C*-~QGFDr;n zmBDR{0tZA$Ct&+Sdv6#<0N3s%qSxPts}e02eAWd}G(LmZ`=91O$iL;-S`##l+qIY& zKnvAe7wXnH!bj3r$chbF1SaLq$JNqpM~~qVr^!>)oM*`1dst}88oH@H1x>b!KX76^ zA9HoO6P%Nd4??XSU93DtVCB+`Jpyq`70WWQDHxCn+=vypnQ@xok0py7zu>AmREB_f z8HyQ^#TRO=g-%Gx2NYww?kUs`c-Gu)v+3hlRk{WaR z*2m<3d}gtyVKS0v^o9aB80m!7H>2(LEYFHHE0#4FXHlcM-d_n_Q7fIrFuQ*A|3l## zqkg~ifvUNJoDg*|V7avK`q>!4i{^y@E>Y@l|99kBRvm~xzb5^45t+$)g)uz8iqfr^ zHtlB(z?YWrt(j;uOi&g?ocPb;#a@|3Hv0d2w29z^pU;gzf)l(kfkLbxMmBMj2c`@U zCMSIj*I5l#&qCigbM>Sq(^Ul^Y#GO+9_D}zQ-!lwis;dYdX^X1Lv!9s4MhUVqW&B7u)nzuWrm*j8?5Xk-<@gIXG$OA7?;_LaVa#6{w_Pezr<(C z>@xWiIB??VTd&(r!A!s`EzCR}e;-*Qe43BY_R{OL;j(N>ZnqsqIe-xvhd-RLG9i#a zi0T5gyjo4Qt++naC~ME^nhAWW&Oq~R!7=`e1>+b6z(Vzs{p8WLTaC7#X1y+SDjljD zGG~|?3=%uGS7)(~$e#xiuvB`FC{|pSdw89>ZgzToY~A=6`nP$X^6}I|y1ovIirgp( zKUQ)B9k&-CKLukKts;H`nw>^fDr-j)p`EGv90Ko+7+{_4bLGrf{kI{Tr+49@23y;K z;|w3Ji0j;2DRrsPOc+1^ugIP}3q9B5D{T58A={pCmLXjztxYY>`!%t(c|o3&urc;tg)8k=i%Y z?EcdV_zy=CNKY-N1@N`~IhD2oNVd852?A)iELUO7D%91r&G1JnL~7xFa5NpOzqa;s zi~RAx+hRpquxU5H9&EGw>Yzu!vS^zlhyl6-je3%twhd9T_^iLa>><2z?}i|21q^CQ z$2o{cRZz4yRp*xz{!GnhhOz7j66MPY+;-&zu67oa7<1a|1|H&RM|6nz3?LniWc)8L zz{i5UwzgN+Hr=OB4PC-{@ptRKR$g+gR4r#@cm}S+FrKcuiFR92ht(J-ekQec)|#U2`uJgxb|_ic{`o?k`%P`c zOLK#Vt5Yb6D!;T2y+FbA#4{pI!i9b!6%{Y_A?`y{RPqt+cpc-<>)97N49#+F?THi} z@|;!gjr5FDpp)tf-(jRb{Dc#PHJd)H4z+T7Fg~Kbc)&3{8=!6}lg`XO0or57Ax~K{ z&pR|bvTH>wn2XpCKv;xHE4DTc{jec%!BxnNLHr?Veyhv?&nfCp%M|OJGp;492}j`8 zR|B#IlQNiSf3qu{?IqiKPLU=&k!Cntui&v7(#jE%niRoVtdRqht^TTG`5yG1F~vw~ zXc0JO9ODk9FU9cw2e@xj7EJ%vs05oXMN~km;dvmYZkw$U+K&Z|q!v{A#IeQzZM$OC z!9lh0Dl~qZd(+I%4D@z7HTi*nT3739iOZ_4{HhBCfpcS}9P1f4B=rHsS;@vMP5QRq z^%;b$g~uq6jFq}SkV-%W+ql$Oe}8bVDgR8~D1brPs!_r4pXB(iURC!&Pnloov+Pe) zLMrLGR4~5ImfVR6X$MV7&9qx&JfCK|L`|di)J17?FyH936Z>^@ld_9~Lc#~8$V{bj z@YYC?hgr`AmHg6FJ|;=IKHvH4+ll1%KVT?w^78s~x63w8M#Ay}%TG7M&V?r09`asnI9GYXTKsCMSiIRQow+4~EoCRR3fGuH&GPs==p@LxB~+ zIJQh`y%ihVMXolNf!#7b=3m8tAvBD<)Oya2V(YHIB#M>VAE$1T{vz}oG*a;0H9H>EkOLUkN#{z<+M0fCjxs{oqqm@0Z-M{&8diPo1M zKzZ4rm+`)6G7=y{duMF}HE#xs0%o-n%Z9y>gDJ8GwA8g$hYr&LJg?wc&b<+f>cc>X zgTXkMYr<=D2_44bOBwB{Iz7%2j?x$c`)-Wvx4oST~}HukD#(;3f%8z zoMpy#FQ94eGCk)}@a>I>-%evG%u{{0A8CzVCI|h34h?NG zBQqG8mmVDiy-5mnmUtaF-G;vmEIfVruDWJ@B>HLi3*rN69cqj+Ct zdi}4~xVk_^$4ryxA>n47fU&*O+7if>D61!X7BBHnsivsiL;6|MBo`45wReJ_K68Pq zKwj-4s&%%d<}reozYBGNUv>~-59gJ}jOED4r!s#|`_HS#TQ)1*Ifz~ZBY>65OcmgO zWXc6=>uDcr17YT*uJip@_qN*oHz@EwxFOF%`lQhG*WGy-7>E2;Z{-P_bNo zIt&<~O+R5EHbF2`#I>Se=Fnl>YptC4TyRqnWgK42Fiof<@yoOpk5DivQEc7^FcE=1 zL^fBMDw))5*l@YEa7iy|K=`>vEUJ(s?bJX-*@dA(MqFx~ddSw4!-n;Fup^Ei1SWHZ zk+YNN3U#5w#W0Az$#pBtP7TBuwZ$N6njZ{WVVl$WBq1`We;hRm;WPRm#Zq@6tJy=$ z`svYG`y>X1w^8PJ_OQja;m1x5S^0JoS32l$yvlSU?f1EIsBz9sdJga1x3{lI_VyK# z!V<0-YQpk?hNfyO{R#A}4%pJtC#{Z@p02;)W4U0)KN}-x z3bu^7%!93g?Mtl=LhsK15;p+@9Cq3G|2iBrE!%z?lNpkj4lzc><8^ka)+&OIy+pT) ze09!<%sVhnGY=vJ9CgS_A{5R=$fAo%p&oT|?w38`^&^Z%&^_|OT=)dYwg^D%4j7%M z=dylTF^_J5FHpu(BTG@|5F?Nlq5@_2C5f|lgz#H~{?gJp>l%=yv?F10oWVQjPCHYj zjCX)TwrP^wx&$$Np2NwZ^u1b~wd z_|Z85W!(%uT$7-qz59_y->%}xXskbu^ml9wYU4r6_}37bNHR%U5bNoRme8EEAtZxj z)CEGkwdvP+Re-w31Z7$N-z8u-x(;cpfeLdhtU5e|)FOoiaz#lJGR6Bf8Ie~p(#PLL zalJq4ndjT;tXeE#7>^}S*^>@X=Ahi4CFPg*sTkAP*8O^L=KzbUha7^SA+pNnbiigE zWsa@YXnRlJ^wn8BA{_9wzJ_jwX!l!~Q+pIS_q|l8%V^YBNxP}q0Q!_d@kfPxq9qK5gp?kt>iI|9V0@pdsF7IG~#a&P`@ zOynVwaVVbTm_Q~@g&#DiUK(V^h6+w+08@T?AEL9h7th^Xc3Do){_5X>f%GQ8^5187 z%8RpOY1zA!JL|&!q$-1@X(83tNbQ3GR5!_cng9{$Rf|Tzp$IRxX+-58&h|%);@%wH=YmZ@U5Eh%PoB>?{^a~;O~V=3*{UY zP0BxNnlTxBudA*+WXsf6B_=N*BvL&v7%zL#Z0fr@ase`*|F=ZIUYxmiry3a(3bQs$6GD+7uVvTN5Ks^ zg|+$!S)aiMT+o;hS1CKe-pRF2TPf={LFlYGfiMMkfeyRGKFumVgwiXfwu}FC{`8EU zSfml^E~^qU@qZe|ggKf;aCJx7$2fu%A!EXtzGy^cPO&b)Vrinvq|qcJ(gcd))k~}4 z@uFkXLN!yTK?YmG6u(yHqwrg5q8JF+eYmexzSQNK5?8f2dVGT|$FCTLv>K4`@h2{M z1tvfL#Pm?ib~z{yeHUoj>U=iCcQf8ve_am_?>RWo&e-DmYK{m0xzvmD9&;{(ft35# zBjl*<$ku}EwATGqU+Df#)a`PWH4??INLINIWtY)A;rQ5C*_}@ToJ8xt&Ywt#8wWl}AQT$%hN_;wrm>Uam6jb!8@3>=A`+p-P|uqkqS08Q=EX_JwOrY^j$!!G;kESflnJ08(ES6HHv$pm+>82zwlhDy@!jvQ3(a*AP;SgX;7 zp6JDxq&e?D35#fJQ>T>l^6rM!U|zd99fW5i^JO8eW+8Q70JXnfAv>~4yfV0h?bCSl zwe{vhbq@vA! z%k(~~;WIfC%LjF$DmK9+Yxc87W!%wd9taA{Zqnf)5@njE^zC;MlD|a@7lrvs}ZsRC0CO=t%63 zx2pb1Qvqd*2gHic;6!MT;P`Pqhv-z&vQM(QZJ9j|B8qod0uCf`tqJ#%e@{7K@HtLZ z8GJen{Jp9X_0@l3f(;0rwas1tTX3u}egjBg2{U^*)kf3MfUHN*1aM2fF@44VYy>LB z^qX<8dkKF1PnhOy?$tQPz{u(;Kj-BZs#0TEp6##OGkbx=SyE=7# z23IPCR3yGIro!f{+u%S*Z^Sxvp0p=tILSGLqa;;x=PVPcPQU_Hq_)b`iNIEp@Zq*h zK)171y)Q;KDqO&Tlj^p0UX>Ki3{7NVvP%9P#IS4&=$iLmxd=g+m2c$fVjB2RA%?$8 z5Oq}PgWNkX|EeUnv(~*_HMkl)#-tRtf>-}SnsFo~P(@{MX#(jJv8uJImYf}6BuKAP zEz`4JYwgz4E346|?4MHjsJqkd>;g06utJDBog*U8EnVDzix4=vV)lu4kJR$<%;B!4 zeakp#WWyN>(V@KVy7BD6RRcZuh*Erv`jU}iDVJ>}j@d4w7EqcQhSaf6G(HcF#Qu?Q zEc$F&c_d36o;13`u)VJ!ty=|`p{+0{HCz7M8y_}LqWxdI? z(3$TwE%Y+_4f{3^ev1cJ zc1Ehg)|wPoMGK}O@R=nepqZ$Fa8%_D^nPa~+Zu~Zx30Eo>pG6E zzglf@H=KpiRjamI&|PAOTZj=DciH<6W(Qy-ZxzyJX4)GLv_!H~2Avj`E3Eg6dWxB> zJ6%u7yIv&7*Zn5N-(=+z|4V%uHcL%^Rx5-q6EZpxPNbZat+eB)f%p|-OX`vgad^`5 z)WV`tZKloQ)bo{_a?F5=wFth*-=P%Qz~ypZ-HI;>t{M4XrR76xK&1PM3DgEZ0R*Ty zpdG;}m&h532#8#@|7ZTD)duY}r1+t{aa+XSX%yR`Co6WKe`Gl~7ZoE)VPHS|!Vz+E)3xO;+w!lV4S+}4XL!W4M|}#~xF!899>GK}K2HEU z{y$4yPg~o*#^=3)Z%NSPzhw~OsJWE0dw_s7eXK_N2Zs~FGUG2aYmrjcng!Q&<;>W9 zny0MWqGVlGw=#Ghv47d`YV->6A}5W{M&4J_L5!#gf_B8%J*K}QAynW-;HYgKVyBeb z$|H_B>;KZR%RJ11!~P6RHlo?g-P5%Q@=#u)PHH~+6R<;znn{$pUs^QQ4PBUOT@4dD zPjN@i&$c|{nzLtj7i;>D^+&S{BBtE#=P&C2B4kffo8Llnl;~A#7B{Ufv%p%!k?YWpIN<&fTI%P(QuG zIdAWk$5`KUNb_1|I(|I-LRs^DLJbCo3i1z#BL~apfCt0FqB+V#=V_O#g%T8N6w2vG zk24z#DIrM`GYHNY#0#&A*9Vs?$AHXwi&jZbJRl)Sb)NtZP;sl8+}G4Nc()8bqc+7= zXN;|MPUW%X7TtUk2CKamlTo!hq3lVMYQCqTo|I0XN}0NTK$cgc z6U8K7Z8<*Xt#((z zVFq+}q1VwcifL1%@`*)HI+!$3?oP5&~<56A({7x>M243C;lLB@Mr5 zc>=l$?Y56-#|XdCr!RofjDzdC=d>KAoI?-MwheKc%e5mgfqD|NI*zQ|)KERMhcxn+ zy?RW+`$pQQo@_acH|%-g4Yo@~knZVaZa?j!3EgI@p+3hS)|NvpSjfNLqfre~H=$-@ z)+u4yX(c1v#p=Hvr7th-ZTI^~q{+GIj3BYPsyn1!ZG)%lZ<(UQzJr0nDzql1jl0I7 zG@gEMbo|A_LpS0Bgl(YV8)2+*v!Jjeaf2?4Wdr7w zv}vM~Ww}Us9;PxDj=UtKP8WTmEFH;4!{?(H;p)>NM=tFoX;wsQ!_*q3ZbldVB%n2( zp;OV#q%RCtkie)3 z*@TSy+8&13Wq7s4{f^{J=oP9*#Cs1ud$`|!Ico2D6CM5!?TaBJi+*=K!2bHLM}YyG zTOmqApk>&hQZ3F0d0ls+DR^o;ETlKwg-oFmQ3Wecb0c^n<;wD@g+dYOo>-9)D>2V3 z4PmC$Dij-atX0Am#9m*+%)!Z*iL=x_lrSb>-_|uBN5JukWKo^#V zIo~iy7GwR=-OC^qO|?E|=*mRvk*enUO`pEcz)*I>!UyPt*4~#lt|LDObK5(O2#O|6StTEnJCL`Qq|x#5->!uR@H>M18@cxe!GY zT*F}^1l(%es)=5(!j&R99P7u2;(QBng78`|nwAsnD&CeG-R$}KkI5O<9+r&X%?17Y zrG7VK?WZ*=PKu6r;Li`^0#XJse6fBq|ezGd|&IapoyC zaO$rZ%j}tX3Ym@2Gs5O-ZO+T^7#9tT-xCC=3WOfOIvn3UwHg5t$=$aYs2%w6kWnY3 zB{yZMjd~Y~QXEa*Y8RSP+JGw^;^=R7pv|Jeaa%|NYW_*B=v6Y77otnZ)GFt10kLwY z%gdmQv&ZJM&SMvrr#=FKOZ2y;dNbxB<^R&80J?v;CkNl3qJBWPZp>5%f(>xBaU>`Q zT8}RGiZ9}c#KCA;7qJc$eN?C?8hc*no(K~CFn~~>W(y=ycFX3}i1p_O310 zp}#H+Jm1X|rAn5?FVSfj!Dwhq7pvOaVpo9+jxvJCcIEBKSv!fN(G%X>;io&2lK19& z1jhiZKZoL4Qqm6@7_r5gFSNR=uCvXhz7M!eqyBDdlHad{<43)h?tTI_O|tT=X9iED zGEhZ`yQT6lU*p{qeR{>fCCFE#c6pE$k@`+-U++?$eRn$G=QMgj-UT)7(co;T?^2^yMfEhivVE`jK+_0f_A-k3H_6?rA@| zRx#5jlJYzVh5nzUoa07gF4b6em;~QU)1L_s*LNm~yf7tWPE z2C>OilQ4jNvHJDBljvRb`gKrJdfdQZ6;mWxzhF6++qcT4j}($!vJOl}dioJ1{9yrD z*tCk%{aUp6At)Tp;}#7^lrcg-(%ak1%_~6ZofAP;JEfgg$F^7>S4nL~W0CN(kZR~Y z=D;}#8LWLkE;cn>$L&fkSij)Q$>76>^Rf>|M}pehD47*f4s0o`CKixI(rR_0^^;acQbXDMJqehP0$TZSlVoC_Q5ywK0)k52Vzm$1$UEO=@=m zG_M9sa+8qTVDj5FBzvfDsN(P=zJSeGfV6HbH+Rl-bWDwO?33|0+!&vAFz7$v<-78S z`9ccU;%5_`bzNzGB%Y;sc1C03*Gn{{U@rmFgSldP-j^;dTaBknS}|P192Wmprl`iO z*d#{OT}m_5lG|ZX_E?Jh((nr+BZYfWBZZ%duHtnlo9)B|tC#L<;kS9Cw?%Yd>CbIC1 zMhHk$r76%a@(73az3pn-ZQXA?!|xAOB*7`h+YD0QJhaD>>H#v;Jn!3e_#8%z%EZZX z)Wc1i%FI$XjkFdk*E<3eI2H;uVHvcP5q7yGU^!3fuO!N==0UYe=E@BKm1!k4V8vcO zWYV42uM5#Q<)HGXj_Xm0t4NsV5z)*q8NaH0eE!shD-p&+tL8;Wcsg@c246$vU8|9Y zS1fFCDQb9CQ#~ZsNk-{vi+*OV9Aftz4wlpaN@$?xpT!%EbOH{{tSB&sqR>s&)h(8c z4RWk^{JPVv3!eeSVua7gptSgOX?jL}l%vHuQfg$UDv3BwW(4}5=?q^mC0$1(^=(=o zA%8^J$Ka%n!m9Pd`ZKfWPv$A*>Mj=3X%qI;@I9-d@b2XTpIrUD6~nq5=ErY!?^rf` zr7lO2|96SD!=?HVCOVGl_w_HAK7;d=?Pa=7rk%mM0^nNGLX4zQ`Upf_mv(UJ35iDqR2EHyhCvNILEofvEWUFS?4nE8IK}m zHmcNulTzPvJ{pN`xqjVeP!RygZsbrJSZ77zLcnj}qwtF_Xn66{hg8|~{f^3Jn?p^p85p=Z9B zf9`K1QLu04K28*r+2OnW^y>BKlcH==gUd2D*gubKG-A=ix+cPFW(#&lwAkQSdEa{O z3|?ge0tq^cs5yqnrA)ugvMh&(!pi#A#f}$?4Ijb#cz+codu-Fd#upocD(->^HqGC-yxyy19} zbJjE}^fo9@yV|g6FA%5IcE0ZSJW;a_J-!5kiY93eQ4-#7nbv z%~b>gZ-gdNFv3zw*HQlUXNs)%XV&{2)HhhSsHw-58o>y=QwHuHU<^hAS{6&n^{p_U zB8-2FB1TIiY&TY$aWLcgk)R(u1nBwflzMCa4|&)_fqX>e2Y#jT6|ew;Z&&Pvt6g*u z(KfpohdfS1DhajHc)2`T>L9*>7Ic9lX`waQK}@!lDD|1rDn}d68CA;hTND~>TM9fb zd%iU@?Vk!!`Cy*;sa41f7LTyDG@pZkE$8mu?vk;0|4zTc1E_R9049H}I4<0@s9atg zo!B&Al1M_S9ocZ@w~`2^(G$~V3CFYE;(eSt7rcr9@6No8S?Cp7eF?S3JC2hQzHt__ z%VV(Fwj;EXug7`AkZI9wt2+-jx?ZhGnk&Uflmk`~Xb=NS>x&g{UiO9&B|IKPZ`6gX zI>72Bz6jtmNIyWNFNiwm$2lSH1gw(eXk3@4K;w#b2+`bh(StJp9nZhumwLVk{1Don z1$Jey6B<%Pk;wB#^^oJmnM%$-8y^Qs%>H`ljRwW>E@_uMm=R|9*4ZEj-U|;~E_oRK zw<&5r0sKD#55r7dz51YJBgkNt3nS);l%Z82JGVT96~+p@NB!tv9h25g%02ags-8L| zD@HVr38w&Y%BQR?NN>LCb8^Pug{UhRQb|6o0SofHX}&os+mcZP_?*k4BqWS=>;29S6mg@{ zyH$wc+Zd%kQXK&GE|D1dnC?o{3f$5xMWr2E8%4-yoL0#{T0b=-{)>Nt-)U2*Ug98; zKblL|=eAJO*{bQo%a9U^3sb!X(Qsv+W#Df0`y3Z8Lj(w-osHBKxc~7s$aBAorWjoM z)NO(};lp_uI4H#-}Jw$+eu&c3M$JM&vJ)h-U}e)S}DZeYc_A$1bB|5F(=6<Q)Y741Bm#@6h5R7GQ|Q2~1seoXNjwIz(KGoKNrY zavXPy3oASE8oMM(e4x9HPo=~gUJSW`!?A<)l1x(Vv(*3TnFDt27uNDIOlWfqbzS=>MyHa$2CfR+D2={YUdWYGNBkJ@6No-h4ndc|GD z6IO!n2kp8A=8qb2Z~fC8IysmCxnm>+-d(s^R;@1RF%sqWQ((^>b!lZAQ6B53y_~J( z^z0qJSa+sHx5KsOP=1?{+eUy1nsIR2nO--J5fl1VaQ*!(i zCdVnE29x!OXRZNj-o{z~Od-utohS}FGsd24QAe1xQ2X05d(89loue*7cGrL`YuPVS zuK<_TrXO3ZN`q_^+PqCT^QPVXQOat191|$=9&R60od57iU~SjJ7M7YM{z3a9R2^Ln z!$I&%wwAzRM@dvcbo$FOv6QAXc*?}gP~ycPx;gd}E5#55+2yFO>9)&vJ;!FX{b)bu zk>PDuzJF8V_$bW5vOxZ_V_c&5a9&vQ>6eu?PqoVdlqQ37sOl(0)H(V{T(!`^p2gi< z&Nlh!c!W+;>!+*Sy1$TLl_z%W6Fkg{VQbykHrIiCW2ejbPYKejTQVh%_cIOw?5fDt z@*TKN++W%xc6$QOzTu5^3M5->Z|*(Qd4et0^Wn@kW)n8J2O25Nt;mSD`IV;aZ%5qK z88$881I)4me)YxPSUMI~b~B7X~R*Zy1a$;pnL^?7?s?cDAPGl9!6hH_Tv!ustdD5FWraf>e*(Sf-|=7OzP{3 z@BPyZ`&Joj;}90U(JVgEaHowK$(?&AB&SKBL=FVIT0b@NMkb|isfZjL^sJc0F{@Tycat6^U*J~n2jJIUtHL~oJCe?VK zx$OzBcAZAjtosOgMK?PIM^#YW>5~DYz-Tg|BWw3O)r3|v?~MF`8da^h?^AS zdNa4qbYf7q1f`v}L%yVqsi3XRiBSxs{8WS<-qtc@(F^f)0)>!#I8rx7VgY--sM1Z9 zIj(tATiyX%Y4*$b7uj07S!qW4rSfZ)tJRUYO8M})vg50<&uatYdM!GxE+dP{sid5KT?4DIgAc>5XerZtjjwYNwhqcn@zxc9OL*IreujwyU~-Qb>7wc1Voz<;s~1 z@JT3<98rt(#+aLocOk+67e%5Q749p_496O9c;mvK_7=| zRs5Y%K98gu9Yd~*ZjHpQL=0Q9_t@<0s+qL`6BxgSDYa;@P2z^uJ_-Sn%4>AWie`yH z>Th_Y=0`Y#2EO`CA*}iLWRF&HUti-;bb^$J_#~#_s~T5pV0h0$bRIuFTu}e?_6C2< zN^ZfR>jog@^=`K0=?v<~_jb?H{RWx~G-(Onba(l^t}#H?BJRDo$AuH@o58N7=U4|> zOmX7sDZ%ZErt8r7+!F%MJJd$yh>+r@4Z%1!t}t@J;g?ta0>J#z3k*CXeudZZ;Wi(n z)E6sxcbFmcEq_=Dc3U#{MZP|+e9ZQ@Joh-eaSrq1y|+KqidRq82h|x#dSm<N%`BJN z4ao(t^V(pQ(G7`96Wd~SG!^r-2*5MW7p~&`Jr1^*&Z}2Ian6D}QOQW8WAfw5eF_oz zj>-z3M5$A}z2{kV<%%!E9a)qsv_}84xtmnyjCk$PxnPGoY;6&0u5kzro-?Q|8gzPg zQTYuHh!LHnohg49{OiBQXfm;y~a9hP#$vD*)q=Y?enyEh4z zHfkj^C74 z40T1)48rZzx+oj2FcPxPB(DaMurxZLm9_G?U7;(YqOuM7(O=V&x9)B+1c)IekU5Hl?|vDR0{0_Zi78 zS&TwAOo@k)!_-__RGdR->C19D8w^r(hkxcYx1%m;Na-of2GfB2CSMiT7)mV>%d{%3 z^2|=zEG<2lYOE#f6!%hxD7sV_Nx936&`iFdE&rQObNd{B#dV{C8oHJg%Y0DA>EqL3 z*UD2JRI_98{O#(_U7oeFq|<$ne6{%oA7Z*q(r{$;V`-(i&QAAYB7#BLr@3%~m|4!H z{BQL7kh2F3KV6H*(>p_w{53PU>Uod{ZD9vYRdL2~qX7}?S(fE*Zz0#UFFme;UwKc{ zO5N&WBGm`gcpSx8L0GJ$U)CbKbt=men&HQSf;WDx^zc*#7pNlE2n4d|aM`SU+xcrP zI!G(P@~hawsA{F9n6TBQ{_Eh>?o&6ham7+b>;GZ_dF2wfMT;2257vM#Tf<{4`{fc> z{}z|{PXf}J?_zZ=a33`imqlWN`9e65HO_6T&85FOUt7bZ(|$IPs+SVkZk^z%J^!1( z%K!UcC?uip>qWWl5S|47z?rpo^8+9}HJZuvy=#BGb*uZvaz^KywQkqNTX3=d7hFCZ zJDVi?uN!S2TM%W22FBc0t~ar}M16uZw$LR=ltphisV$C&y*lU{*X8m07$LAp{#HfW z?{}7&OuV@dYhYHUt&_m`w)x>~G}q!ycC5SoX*{Q}63q(7x!O+rQIKyBVmAwjSQD}? zU)JG}xuKlbC4~wd_h2r4Vy~3&NSuwTZ!28cn-(g+jy=sria1=~Ne=K0?rhmu{eB;t z2+T&pH!p`ZC8%)6NbBFVb%9hRu==%j-5d58w3xCL3Yb&gTPzVMTXTGkpcuUO<@ANy z*NLS0lb(&}p~y6gV{P9s?)q=wDk{XmV=i=$`;!A?9s9w@fRn%lEnIT9 zh%4ZI9n`Uun82>h6zc5ibKEQZz2J=#y4~IP!b|#7sMGI!3C)?r>L9=i7Fx^X<*Wd3REwSz>}x6O2+FJgrL5sR)emB(?VnIPNbig zzATjLI;KL36n@CYJ?@xFIn)#taCao>E0_u@1^=gQ zYWKXD#{4!~*+un<#Y7`rRx8p}li9oxSt+j{W1FXiZkeIhD#BC_JbR>--HzVPJI1BZ z)9)OIZ(lc9OXqs!_!ppdg0E1iYjimtiYh(kg-l`md8bW&5`gS#k|aQw=e^ETl66C9 zPE(+VUYaHu0ipAOgLaDPK4~TbiNFukRjS@-oofW@)TorlJq*? ztV3WOt<<9M!)56oC>y-i{m;i5{y*?<;YHZ;mj6b0_RGHmeMjB4M|B#sy1iq#E;Qnz z9Ai+4hLYrU!X%HAAfNnZz^26*PP!EfaOcYOaayo2I#Psa3+CCd{XiSEcdDS6`9C9v zEg=Yl0#?Xt?4%vHfxvQ*X(@Rffy zk?)8iq+tJ3s-QNm_ZXpzb5XTn(TbZB;OwcaZj1)p%RMSmvZVhyPW$ug-Lwl