From e479b6e91dc06846e232002a41fd9110c789b21e Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 17:46:54 -0700 Subject: [PATCH 01/11] chore(camera): remove unused camera presets CAMERA_PRESETS and flyToPreset have no callers. Startup uses flyToAustin, which stays, and city navigation uses flyToPresetLocation from src/locations.js. Neither removed symbol is a package export. --- src/camera.js | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/src/camera.js b/src/camera.js index 3941c9d48..3fd49b58c 100644 --- a/src/camera.js +++ b/src/camera.js @@ -1,51 +1,5 @@ import * as Cesium from 'cesium'; -/** - * Camera presets for notable locations. - * Phase 1 default: fly to Austin, TX on load. - */ -export const CAMERA_PRESETS = { - austin: { - destination: Cesium.Cartesian3.fromDegrees(-97.7431, 30.2672, 800), - orientation: { - heading: Cesium.Math.toRadians(0), - pitch: Cesium.Math.toRadians(-35), - roll: 0.0, - }, - }, - sf: { - destination: Cesium.Cartesian3.fromDegrees(-122.4194, 37.7749, 1000), - orientation: { - heading: Cesium.Math.toRadians(30), - pitch: Cesium.Math.toRadians(-30), - roll: 0.0, - }, - }, - nyc: { - destination: Cesium.Cartesian3.fromDegrees(-73.9857, 40.7484, 1200), - orientation: { - heading: Cesium.Math.toRadians(-20), - pitch: Cesium.Math.toRadians(-30), - roll: 0.0, - }, - }, -}; - -/** - * Fly the camera to a preset location with a smooth animation. - */ -export function flyToPreset(viewer, presetName, duration = 3.0) { - const preset = CAMERA_PRESETS[presetName]; - if (!preset) return; - - viewer.camera.flyTo({ - destination: preset.destination, - orientation: preset.orientation, - duration, - easingFunction: Cesium.EasingFunction.CUBIC_IN_OUT, - }); -} - /** * Set camera to Austin on load with a cinematic fly-in. * @returns {Function} Cancels the pending or active startup flight. From f60b9ef7c87d9e4256faf4f4a01fe7f6cb65c07b Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 18:09:37 -0700 Subject: [PATCH 02/11] chore: remove unused imports and local helpers Drop module-local helpers that nothing calls (linkAbort, summarize, stringOption, summarizeEntity and the polygonCenter, propertyObject and unwrapProperties helpers only it used, CALLOUT_DOT_SIZE_PX) and unused import specifiers in runtime modules, tests, QA scripts and tools. None of the removed bindings is exported. Re-exports such as the ALPR policy constants and every package export are unchanged. --- scripts/qa-failstate-b10.mjs | 4 -- scripts/qa-labels.mjs | 8 --- scripts/qa-street-level.mjs | 1 - scripts/qa-transit.mjs | 2 - scripts/qa-weather-swap.mjs | 8 +-- scripts/run-unit-tests.mjs | 2 +- server/providers/cctv.js | 6 +- server/providers/cctv/sources.js | 1 - src/annotations/resolver.js | 12 ---- src/cctvFocusPolicy.test.mjs | 1 - src/cctvFocusRequest.test.mjs | 1 - src/contextSessionOrdering.test.mjs | 2 - src/data/analystEngine.js | 22 -------- src/data/bhoteKoshiLocator.js | 1 - src/data/cockpitDetectionSubjects.test.mjs | 1 - src/data/cockpitModels.test.mjs | 1 - src/data/detection.js | 1 - src/data/layerState.js | 20 ------- src/data/militaryAwareness.test.mjs | 1 - src/data/militaryInstallations.test.mjs | 1 - src/data/trackingClickGesture.test.mjs | 1 - src/keyboardFocusStyles.test.mjs | 1 - src/layers/alpr/index.js | 4 -- src/layers/cctv/catalog.js | 2 +- src/layers/cctv/frames.js | 3 - src/layers/cctv/geometry.js | 1 - src/layers/cctv/health.js | 2 +- src/layers/traffic/state.js | 2 +- src/layers/transit/ingestion.js | 1 - src/layers/transit/lifecycle.test.mjs | 1 - src/layers/transit/movement.test.mjs | 2 +- src/layers/transit/rendering.js | 1 - src/maps/availability.js | 5 +- src/panelEscape.test.mjs | 1 - src/scenes/director.test.mjs | 1 - src/sharelink.celestial.test.mjs | 1 - src/ui/visualSettings.js | 1 - src/voice/gevActions.js | 65 ---------------------- tools/pano-pinhole.mjs | 4 +- tools/sat-ortho.mjs | 2 - tools/streetview-headings.mjs | 1 - tools/streetview-panorama.mjs | 2 - 42 files changed, 9 insertions(+), 191 deletions(-) diff --git a/scripts/qa-failstate-b10.mjs b/scripts/qa-failstate-b10.mjs index 95dc2ba4f..2033a2731 100644 --- a/scripts/qa-failstate-b10.mjs +++ b/scripts/qa-failstate-b10.mjs @@ -34,10 +34,6 @@ import puppeteer from 'puppeteer'; import fs from 'node:fs'; import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const REPO_ROOT = path.resolve(__dirname, '..'); const argv = process.argv.slice(2); const getOpt = (name, dflt) => { diff --git a/scripts/qa-labels.mjs b/scripts/qa-labels.mjs index 8d26dcb30..fb04d0025 100644 --- a/scripts/qa-labels.mjs +++ b/scripts/qa-labels.mjs @@ -86,14 +86,6 @@ function percentile(values, pct) { return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * pct) - 1)]; } -function parseJson(value, fallback) { - try { - return JSON.parse(value); - } catch { - return fallback; - } -} - function analyzeMembership(samples) { let replacements = 0; let exposure = 0; diff --git a/scripts/qa-street-level.mjs b/scripts/qa-street-level.mjs index 1be1a80fa..0dd841acf 100644 --- a/scripts/qa-street-level.mjs +++ b/scripts/qa-street-level.mjs @@ -30,7 +30,6 @@ import { import { answerKartaViewRequest, describeKartaViewCall, - KV_LINE, KV_SEQUENCE_ID, kartaViewPhotos, resetKartaViewFixture, diff --git a/scripts/qa-transit.mjs b/scripts/qa-transit.mjs index 7d83d77a4..fb0fd0bd9 100644 --- a/scripts/qa-transit.mjs +++ b/scripts/qa-transit.mjs @@ -47,8 +47,6 @@ import { reduceScenario, reduceSensorContrast, reduceScriptedMotion, - reduceMotion, - reduceFleet, } from '../src/layers/transit/qaMetrics.js'; const BASE = process.env.QA_BASE_URL || 'http://localhost:4173'; diff --git a/scripts/qa-weather-swap.mjs b/scripts/qa-weather-swap.mjs index e5b26d723..da7f77bb2 100644 --- a/scripts/qa-weather-swap.mjs +++ b/scripts/qa-weather-swap.mjs @@ -8,13 +8,7 @@ * node scripts/qa-weather-swap.mjs --url http://localhost:4173 --label google * [--layer weather-satellite] [--product clouds] [--headless] */ -import { - existsSync, - mkdirSync, - readdirSync, - readFileSync, - writeFileSync, -} from 'node:fs'; +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import puppeteer from 'puppeteer'; diff --git a/scripts/run-unit-tests.mjs b/scripts/run-unit-tests.mjs index 6cc6aaa94..3712df28e 100644 --- a/scripts/run-unit-tests.mjs +++ b/scripts/run-unit-tests.mjs @@ -1,7 +1,7 @@ import { readdirSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import path from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; +import { pathToFileURL } from 'node:url'; export const ALLOCATION_TEST_FILES = Object.freeze([ 'src/data/focusAllocations.test.mjs', diff --git a/server/providers/cctv.js b/server/providers/cctv.js index 672b9be42..a977f4e75 100644 --- a/server/providers/cctv.js +++ b/server/providers/cctv.js @@ -1,9 +1,5 @@ import { createCctvCatalog } from './cctv/catalog.js'; -import { - normalizeFeedType, - isVideoFeedType, - toFiniteNumber, -} from './cctv/normalize.js'; +import { normalizeFeedType, isVideoFeedType } from './cctv/normalize.js'; import { buildSyntheticCctvSvg, cctvLiveMediaType, diff --git a/server/providers/cctv/sources.js b/server/providers/cctv/sources.js index 114a91979..c7def2284 100644 --- a/server/providers/cctv/sources.js +++ b/server/providers/cctv/sources.js @@ -133,7 +133,6 @@ import { fallbackHeadingFromId, isLikelyFinlandCoordinate, fintrafficCameraName, - hashSeed, isPlausibleLatLon, isLikelyBcCoordinate, isLikelyTexasCoordinate, diff --git a/src/annotations/resolver.js b/src/annotations/resolver.js index 10c251b60..fb5795d53 100644 --- a/src/annotations/resolver.js +++ b/src/annotations/resolver.js @@ -103,18 +103,6 @@ export function createAnnotationResolver({ cacheWrite(cache, key, null); } - /** Wire an external AbortSignal to a local controller; returns a detach fn. */ - function linkAbort(controller, externalSignal) { - if (!externalSignal) return () => {}; - if (externalSignal.aborted) { - controller.abort(); - return () => {}; - } - const onAbort = () => controller.abort(); - externalSignal.addEventListener('abort', onAbort, { once: true }); - return () => externalSignal.removeEventListener('abort', onAbort); - } - /** * Resolve a single annotation target to a normalized world anchor. * diff --git a/src/cctvFocusPolicy.test.mjs b/src/cctvFocusPolicy.test.mjs index 13fbff552..333926a0f 100644 --- a/src/cctvFocusPolicy.test.mjs +++ b/src/cctvFocusPolicy.test.mjs @@ -1,7 +1,6 @@ import { readShellSource } from './testSupport/readShellSource.mjs'; import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; import { runCctvLayerEnableFocus, runCctvLayerEnableTransition, diff --git a/src/cctvFocusRequest.test.mjs b/src/cctvFocusRequest.test.mjs index be05ef7cb..700a70a74 100644 --- a/src/cctvFocusRequest.test.mjs +++ b/src/cctvFocusRequest.test.mjs @@ -1,7 +1,6 @@ import { readShellSource } from './testSupport/readShellSource.mjs'; import test from 'node:test'; import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; import { CCTV_WORLD_CLICK_FOCUS_DURATION_SEC, CCTV_FOCUS_REQUEST_EVENT, diff --git a/src/contextSessionOrdering.test.mjs b/src/contextSessionOrdering.test.mjs index 6b1c90904..32ec10b28 100644 --- a/src/contextSessionOrdering.test.mjs +++ b/src/contextSessionOrdering.test.mjs @@ -21,8 +21,6 @@ const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', imp // - the right-rail entry ignored the activation result entirely. import { test } from 'node:test'; import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; -import { fileURLToPath } from 'node:url'; const src = readShellSource(); diff --git a/src/data/analystEngine.js b/src/data/analystEngine.js index 59a631421..6a31c5a20 100644 --- a/src/data/analystEngine.js +++ b/src/data/analystEngine.js @@ -167,28 +167,6 @@ export function applyScope(records, scope, resolved) { return records; } -/** Numeric summary for the narration layer. */ -function summarize(items, sortField) { - const summary = { count: items.length }; - if (sortField && items.length) { - // A loop, not Math.min(...vals): a full FIRMS day is ~160k values, past - // the engine's argument limit. - let min = Infinity; - let max = -Infinity; - for (const item of items) { - const value = Number(item[sortField]); - if (!Number.isFinite(value)) continue; - if (value < min) min = value; - if (value > max) max = value; - } - if (Number.isFinite(min)) { - summary[`${sortField}Min`] = min; - summary[`${sortField}Max`] = max; - } - } - return summary; -} - const OPS_BY_TYPE = Object.freeze({ number: ['gt', 'gte', 'lt', 'lte', 'eq', 'neq'], time: ['gt', 'gte', 'lt', 'lte', 'eq', 'neq'], diff --git a/src/data/bhoteKoshiLocator.js b/src/data/bhoteKoshiLocator.js index f3917c463..79aab080f 100644 --- a/src/data/bhoteKoshiLocator.js +++ b/src/data/bhoteKoshiLocator.js @@ -35,7 +35,6 @@ const BORDER_START_DELAY_MS = 700; const INCIDENT_CALLOUT_GAP_MS = 200; const CALLOUT_REVEAL_DURATION_MS = 1200; const CALLOUT_PULSE_DURATION_MS = 1800; -const CALLOUT_DOT_SIZE_PX = 7; const CALLOUT_RING_MIN_SIZE_PX = 16; const CALLOUT_RING_SIZE_RANGE_PX = 14; const CITY_CALLOUT_START_DELAY_MS = 350; diff --git a/src/data/cockpitDetectionSubjects.test.mjs b/src/data/cockpitDetectionSubjects.test.mjs index dfe6baee9..8421267fc 100644 --- a/src/data/cockpitDetectionSubjects.test.mjs +++ b/src/data/cockpitDetectionSubjects.test.mjs @@ -2,7 +2,6 @@ import { readLayerSource } from '../testSupport/readLayerSource.mjs'; import { dispatchCockpitModeChanged, enter, exit, _adoptTrackedEntity } from '../ui/cockpitTrackingController.js'; import test from 'node:test'; import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; import * as Cesium from 'cesium'; import flightsLayer, { _setCockpitDetectionSubjectForTest as setFlightsCockpitSubject, diff --git a/src/data/cockpitModels.test.mjs b/src/data/cockpitModels.test.mjs index 36f8b24d3..8177b7414 100644 --- a/src/data/cockpitModels.test.mjs +++ b/src/data/cockpitModels.test.mjs @@ -1,7 +1,6 @@ import { readLayerSource } from '../testSupport/readLayerSource.mjs'; import test from 'node:test'; import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; /** * Cockpit 3D aircraft policy. diff --git a/src/data/detection.js b/src/data/detection.js index 5c5e90396..c2e63c3d9 100644 --- a/src/data/detection.js +++ b/src/data/detection.js @@ -43,7 +43,6 @@ import { detectionHorizontalSector, labelBudgetFor, normalizeAllocationStrategy, - normalizeProfile, profileForDensity, viewScaleForAltitude, } from './detectionPolicy.js'; diff --git a/src/data/layerState.js b/src/data/layerState.js index dd79eb12a..d88235188 100644 --- a/src/data/layerState.js +++ b/src/data/layerState.js @@ -157,26 +157,6 @@ function trackingIdOption(key, token, defaultValue = null) { }); } -function stringOption(key, token, defaultValue) { - return Object.freeze({ - key, - token, - defaultValue, - normalize: (value) => { - if (typeof value === 'number' && Number.isFinite(value)) - return String(value).trim().toLowerCase(); - if (typeof value !== 'string') return null; - const normalized = value.trim().toLowerCase(); - return normalized ? normalized : null; - }, - encode: (value) => String(value), - decode: (value) => - typeof value === 'string' && value.trim() - ? value.trim().toLowerCase() - : null, - }); -} - function enumOption( key, token, diff --git a/src/data/militaryAwareness.test.mjs b/src/data/militaryAwareness.test.mjs index 33d20f98d..64104953e 100644 --- a/src/data/militaryAwareness.test.mjs +++ b/src/data/militaryAwareness.test.mjs @@ -1,7 +1,6 @@ import { readLayerSource } from '../testSupport/readLayerSource.mjs'; import { test } from 'node:test'; import assert from 'node:assert/strict'; -import fs from 'node:fs'; import * as Cesium from 'cesium'; import militaryAwarenessLayer, { _getAwarenessNavigationStateForTest, diff --git a/src/data/militaryInstallations.test.mjs b/src/data/militaryInstallations.test.mjs index 65d1c7ea8..c3f9cdb89 100644 --- a/src/data/militaryInstallations.test.mjs +++ b/src/data/militaryInstallations.test.mjs @@ -798,7 +798,6 @@ test('zoom-out aborts an active installation request and returns non-loading gui // by source probes against the shipped file, the same technique the HUD datum // tests use where a full boot is impractical. import { installationRetryDelayMs } from './militaryInstallations.js'; -import fs from 'node:fs'; const installationsSource = readLayerSource( new URL('./militaryInstallations.js', import.meta.url), 'utf8'); diff --git a/src/data/trackingClickGesture.test.mjs b/src/data/trackingClickGesture.test.mjs index a9a918fef..764c60104 100644 --- a/src/data/trackingClickGesture.test.mjs +++ b/src/data/trackingClickGesture.test.mjs @@ -1,7 +1,6 @@ import { readLayerSource } from '../testSupport/readLayerSource.mjs'; import test from 'node:test'; import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; import { bindTrackingClickGesture, isTrackingClickGesture, diff --git a/src/keyboardFocusStyles.test.mjs b/src/keyboardFocusStyles.test.mjs index fc59481aa..e07cd5e11 100644 --- a/src/keyboardFocusStyles.test.mjs +++ b/src/keyboardFocusStyles.test.mjs @@ -1,7 +1,6 @@ import { readStylesheet } from './testSupport/readStylesheet.mjs'; import test from 'node:test'; import assert from 'node:assert/strict'; -import { readFileSync } from 'node:fs'; const css = readStylesheet(new URL('../style.css', import.meta.url)); diff --git a/src/layers/alpr/index.js b/src/layers/alpr/index.js index 1a0ca4c73..6cbf58f51 100644 --- a/src/layers/alpr/index.js +++ b/src/layers/alpr/index.js @@ -3,16 +3,12 @@ import * as Cesium from 'cesium'; import { LAYER_ID, REQUEST_DEBOUNCE_MS, - QUERY_LIMIT, - MAX_RENDERED, - QUERY_SNAP_DEGREES, QUERY_REUSE_MS, ALPR_COLOR, } from './policy.js'; import { snapAlprBox, boxContains, - destinationPointDeg, alprRetryDelayMs, validateAlprSnapshot, alprCreditMarkup, diff --git a/src/layers/cctv/catalog.js b/src/layers/cctv/catalog.js index 900692939..110c03bd3 100644 --- a/src/layers/cctv/catalog.js +++ b/src/layers/cctv/catalog.js @@ -1,4 +1,4 @@ -import { CAMERA_SEEDS, SOURCE_ENDPOINT } from './policy.js'; +import { CAMERA_SEEDS } from './policy.js'; import { lookupModelSpec, horizontalFovRangeDeg, diff --git a/src/layers/cctv/frames.js b/src/layers/cctv/frames.js index 807770c10..0c1812127 100644 --- a/src/layers/cctv/frames.js +++ b/src/layers/cctv/frames.js @@ -5,9 +5,6 @@ import { PROJECTION_CANVAS_HEIGHT, PROJECTION_MATTE_COLOR, PROJECTION_TEXTURE_SWAP_MS, - ACTIVE_FRAME_REFRESH_MS, - FRAME_ENDPOINT, - MEDIA_ENDPOINT, PROJECTION_ACTIVE_REFRESH_MS, PROJECTION_IDLE_REFRESH_MS, PLACEHOLDER_REPAINT_MS, diff --git a/src/layers/cctv/geometry.js b/src/layers/cctv/geometry.js index 797d1706b..d9db40fe9 100644 --- a/src/layers/cctv/geometry.js +++ b/src/layers/cctv/geometry.js @@ -1,7 +1,6 @@ import * as Cesium from 'cesium'; import { createFrustumVolumePrimitive } from '../../data/cctvViewshed.js'; import { - PROJECTION_VERT_ASPECT, FRUSTUM_GROUND_CLEARANCE_M, PROBE_MIN_RANGE_M, PROBE_CLEARANCE_M, diff --git a/src/layers/cctv/health.js b/src/layers/cctv/health.js index 979e997cf..925acc34e 100644 --- a/src/layers/cctv/health.js +++ b/src/layers/cctv/health.js @@ -1,4 +1,4 @@ -import { HEALTH_SYNC_INTERVAL_MS, HEALTH_ENDPOINT } from './policy.js'; +import { HEALTH_SYNC_INTERVAL_MS } from './policy.js'; export function createHealth({ state: layerState, services, parts, source }) { /** diff --git a/src/layers/traffic/state.js b/src/layers/traffic/state.js index 4edf4c234..595bc2a0d 100644 --- a/src/layers/traffic/state.js +++ b/src/layers/traffic/state.js @@ -1,6 +1,6 @@ import { normalizeRoadMode } from './roadModes.js'; import * as Cesium from 'cesium'; -import { FLOW_BUCKET_COLORS, TRAFFIC_TIMING_ENABLED } from './policy.js'; +import { FLOW_BUCKET_COLORS } from './policy.js'; export function createState({ services }) { const layerState = {}; diff --git a/src/layers/transit/ingestion.js b/src/layers/transit/ingestion.js index 6fd46211f..22b179120 100644 --- a/src/layers/transit/ingestion.js +++ b/src/layers/transit/ingestion.js @@ -9,7 +9,6 @@ import { FEED_STALE_AFTER_MS, MAX_VEHICLES_TOTAL, MISSED_POLLS_TO_DROP, - TRANSIT_POLL_MS, isStaleVehicleFix, transitVehicleKey, vehicleReportTimeMs, diff --git a/src/layers/transit/lifecycle.test.mjs b/src/layers/transit/lifecycle.test.mjs index 8a2d39edf..0719acd95 100644 --- a/src/layers/transit/lifecycle.test.mjs +++ b/src/layers/transit/lifecycle.test.mjs @@ -6,7 +6,6 @@ import { FEED_EVICT_AFTER_MS, FEED_STALE_AFTER_MS, MISSED_POLLS_TO_DROP, - SELECTED_CARD_REFRESH_MS, TRANSIT_POLL_MS, VEHICLE_MAX_FIX_AGE_MS, } from './policy.js'; diff --git a/src/layers/transit/movement.test.mjs b/src/layers/transit/movement.test.mjs index e758a52c8..692d2d3ac 100644 --- a/src/layers/transit/movement.test.mjs +++ b/src/layers/transit/movement.test.mjs @@ -9,7 +9,7 @@ import { displacementPlausible, } from './movement.js'; import { buildTransitSelectionCopy } from './policy.js'; -import { seek, sampleAt } from '../../data/contactPlayback.js'; +import { sampleAt } from '../../data/contactPlayback.js'; const entry = () => { const e = { mode: 'bus', modeInferred: false, courseDeg: null }; initializePlayback(e); diff --git a/src/layers/transit/rendering.js b/src/layers/transit/rendering.js index 96d77f2a2..2309978a5 100644 --- a/src/layers/transit/rendering.js +++ b/src/layers/transit/rendering.js @@ -22,7 +22,6 @@ import { import { MARKER_PIXEL_SIZE, ROTATION_REFRESH_MS, - SELECTED_CARD_REFRESH_MS, VISIBILITY_REFRESH_MS, SELECTED_PIXEL_SIZE, TRANSIT_MODE_COLORS, diff --git a/src/maps/availability.js b/src/maps/availability.js index 30ef3e6c6..ceace9968 100644 --- a/src/maps/availability.js +++ b/src/maps/availability.js @@ -1,7 +1,4 @@ -import { - keySetupRequirement, - keySetupRequirementEnvVars, -} from '../keySetupCore.mjs'; +import { keySetupRequirementEnvVars } from '../keySetupCore.mjs'; import { t } from '../i18n/index.js'; /** * Why Google 3D is unavailable, phrased so the tooltip and toast recommend the diff --git a/src/panelEscape.test.mjs b/src/panelEscape.test.mjs index a9549c85d..e0588e9b2 100644 --- a/src/panelEscape.test.mjs +++ b/src/panelEscape.test.mjs @@ -6,7 +6,6 @@ const radioPresentation = readRadioSource(new URL('./ui/radioPresentation.js', i const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', import.meta.url), 'utf8'); import { bindPanelDisclosure, collapsePanelOnEscape } from './ui/panelDisclosure.js'; import assert from 'node:assert/strict'; -import fs from 'node:fs'; import test from 'node:test'; const source = readShellSource(); diff --git a/src/scenes/director.test.mjs b/src/scenes/director.test.mjs index f72b43dc7..59f3a3a0e 100644 --- a/src/scenes/director.test.mjs +++ b/src/scenes/director.test.mjs @@ -12,7 +12,6 @@ import { readShellSource } from '../testSupport/readShellSource.mjs'; // cancellable — an AbortSignal for the data manager, a liveness predicate for // the visual commit. Several of these assert exactly that plumbing. import assert from 'node:assert/strict'; -import fs from 'node:fs'; import test from 'node:test'; import { SceneDirector } from './director.js'; diff --git a/src/sharelink.celestial.test.mjs b/src/sharelink.celestial.test.mjs index 13dedd709..ad421dde7 100644 --- a/src/sharelink.celestial.test.mjs +++ b/src/sharelink.celestial.test.mjs @@ -7,7 +7,6 @@ import { _claimContextVisualAuthority, setContextMode } from './ui/contextAction import { _initGlobalContextPanel } from './ui/contextBindings.js'; import { test } from 'node:test'; import assert from 'node:assert/strict'; -import fs from 'node:fs'; import { ShareLinkManager, decodeShareCreatedAtMs } from './sharelink.js'; import { createDefaultLayerState } from './data/layerState.js'; diff --git a/src/ui/visualSettings.js b/src/ui/visualSettings.js index a15c2060a..c804ea342 100644 --- a/src/ui/visualSettings.js +++ b/src/ui/visualSettings.js @@ -10,7 +10,6 @@ import { MILITARY_DETECTION_PRESET, } from './effects.js'; import { createStyleParameters } from './visualInput.js'; -import * as Cesium from 'cesium'; import { BLOOM_SCALE_VERSION, clampBloomIntensity, diff --git a/src/voice/gevActions.js b/src/voice/gevActions.js index d8b63565f..49e2ba20a 100644 --- a/src/voice/gevActions.js +++ b/src/voice/gevActions.js @@ -4369,46 +4369,6 @@ function dominantValue(values) { }; } -function summarizeEntity(viewer, entity, { includeProperties = false } = {}) { - const now = Cesium.JulianDate.now(); - if (entity.__gevContextId) { - const store = window.__gevContextStore; - const record = store?.entities?.get(entity.__gevContextId); - if (record) return summarizeContextRecord(record, { includeProperties }); - } - const props = propertyObject(entity); - const layerId = entity.__localLayerId || props.layerId || null; - const tags = props.tags || {}; - const label = cleanText( - props.name || - tags.name || - tags['name:en'] || - tags.official_name || - tags.operator || - props.operator || - entity.name || - layerTitle(layerId), - ); - const position = - entity.__localBaseCartesian || - entity.position?.getValue?.(now) || - polygonCenter(entity, now); - const carto = position ? Cesium.Cartographic.fromCartesian(position) : null; - return { - id: String(entity.id || ''), - name: label || layerTitle(layerId), - layerId, - layerName: layerTitle(layerId), - latitude: carto - ? Number(Cesium.Math.toDegrees(carto.latitude).toFixed(6)) - : null, - longitude: carto - ? Number(Cesium.Math.toDegrees(carto.longitude).toFixed(6)) - : null, - properties: includeProperties ? compactProperties(props) : undefined, - }; -} - function summarizeContextRecord(record, { includeProperties = false } = {}) { return { id: String(record.id || ''), @@ -4427,31 +4387,6 @@ function summarizeContextRecord(record, { includeProperties = false } = {}) { }; } -function polygonCenter(entity, now) { - const hierarchy = entity.polygon?.hierarchy?.getValue?.(now); - const positions = hierarchy?.positions; - if (!positions?.length) return null; - return Cesium.BoundingSphere.fromPoints(positions).center; -} - -function propertyObject(entity) { - const raw = entity?.properties?.getValue?.(Cesium.JulianDate.now()) || {}; - return unwrapProperties(raw); -} - -function unwrapProperties(value) { - if (!value || typeof value !== 'object') return value; - if (Array.isArray(value)) return value.map(unwrapProperties); - const out = {}; - for (const [key, entry] of Object.entries(value)) { - out[key] = - entry && typeof entry.getValue === 'function' - ? unwrapProperties(entry.getValue(Cesium.JulianDate.now())) - : unwrapProperties(entry); - } - return out; -} - function compactProperties(props) { const preferredKeys = [ 'name', diff --git a/tools/pano-pinhole.mjs b/tools/pano-pinhole.mjs index 36ceaa546..42fee8fbd 100644 --- a/tools/pano-pinhole.mjs +++ b/tools/pano-pinhole.mjs @@ -34,13 +34,11 @@ * Dependencies: sharp (devDependency) */ -import { readFileSync, mkdirSync, writeFileSync } from 'node:fs'; +import { mkdirSync } from 'node:fs'; import { resolve, join, basename, extname } from 'node:path'; -import { fileURLToPath } from 'node:url'; import { projectRoot } from '../scripts/project-root.mjs'; import sharp from 'sharp'; -const __dirname = fileURLToPath(new URL('.', import.meta.url)); const PROJECT_ROOT = projectRoot(import.meta.url); // --------------------------------------------------------------------------- diff --git a/tools/sat-ortho.mjs b/tools/sat-ortho.mjs index d42039e46..514bb22d4 100644 --- a/tools/sat-ortho.mjs +++ b/tools/sat-ortho.mjs @@ -32,11 +32,9 @@ import { readFileSync, mkdirSync } from 'node:fs'; import { resolve, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; import { projectRoot } from '../scripts/project-root.mjs'; import sharp from 'sharp'; -const __dirname = fileURLToPath(new URL('.', import.meta.url)); const PROJECT_ROOT = projectRoot(import.meta.url); const TILE_SIZE = 256; const MAX_CONCURRENT = 8; diff --git a/tools/streetview-headings.mjs b/tools/streetview-headings.mjs index 708ae7b93..e9a124d22 100644 --- a/tools/streetview-headings.mjs +++ b/tools/streetview-headings.mjs @@ -28,7 +28,6 @@ import { projectRoot } from '../scripts/project-root.mjs'; import { parseEnv } from 'node:util'; import { resolveGoogleServerKey } from '../scripts/google-server-key.mjs'; -const __dirname = fileURLToPath(new URL('.', import.meta.url)); const PROJECT_ROOT = projectRoot(import.meta.url); function parseArgs() { diff --git a/tools/streetview-panorama.mjs b/tools/streetview-panorama.mjs index c93f487bd..a40d8d287 100755 --- a/tools/streetview-panorama.mjs +++ b/tools/streetview-panorama.mjs @@ -24,11 +24,9 @@ import { readFileSync, mkdirSync, writeFileSync } from 'node:fs'; import { resolve, join } from 'node:path'; -import { fileURLToPath } from 'node:url'; import { projectRoot } from '../scripts/project-root.mjs'; import sharp from 'sharp'; -const __dirname = fileURLToPath(new URL('.', import.meta.url)); const PROJECT_ROOT = projectRoot(import.meta.url); // --------------------------------------------------------------------------- From dbb20c72fe4aeb359f5a6c7d388efef8ed268956 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 18:09:40 -0700 Subject: [PATCH 03/11] chore: drop unused local bindings Remove locals that are assigned but never read: two unused JulianDate.now() calls in the flights and military nearby queries, an unread record lookup in the military snapshot renderer, unused axes from the CCTV gizmo, an unused vessel state alias, and two unused ALPR presentation handles. Each removed expression is side-effect free. --- src/data/cctvGizmo.js | 2 +- src/layers/alpr/index.js | 2 -- src/layers/flights/queries.js | 1 - src/layers/military/queries.js | 1 - src/layers/military/snapshotRenderer.js | 1 - src/layers/vessels/cards.js | 2 -- 6 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/data/cctvGizmo.js b/src/data/cctvGizmo.js index 6e7e18bcb..fa15c5532 100644 --- a/src/data/cctvGizmo.js +++ b/src/data/cctvGizmo.js @@ -316,7 +316,7 @@ export function createCalibrationGizmo({ } const mount = positions.mount; const axes = enuAxes(mount); - const { forwardHoriz, right, view } = viewAxesFor( + const { forwardHoriz } = viewAxesFor( camera.headingDeg, camera.pitchDeg, axes, diff --git a/src/layers/alpr/index.js b/src/layers/alpr/index.js index 6cbf58f51..474ec1de0 100644 --- a/src/layers/alpr/index.js +++ b/src/layers/alpr/index.js @@ -80,9 +80,7 @@ export function createAlprCamerasLayer({ source, services } = {}) { viewportBox, clearRendered, hideOnMapCredit, - presentOnMapCredit, renderRecords, - selectRecord, focusNearest, clearSelection, updateSelectedAnchor, diff --git a/src/layers/flights/queries.js b/src/layers/flights/queries.js index 552adbe01..371b3a854 100644 --- a/src/layers/flights/queries.js +++ b/src/layers/flights/queries.js @@ -580,7 +580,6 @@ export function createQueries({ const maxRange = Number.isFinite(range) && range > 0 ? range : Number.POSITIVE_INFINITY; - const now = Cesium.JulianDate.now(); const nearby = []; for (const [icao24, bb] of flightState._billboards) { diff --git a/src/layers/military/queries.js b/src/layers/military/queries.js index 9c106e927..d23ad621a 100644 --- a/src/layers/military/queries.js +++ b/src/layers/military/queries.js @@ -366,7 +366,6 @@ export function createQueries({ const maxRange = Number.isFinite(range) && range > 0 ? range : Number.POSITIVE_INFINITY; - const now = Cesium.JulianDate.now(); const nearby = []; for (const [icao24, bb] of flightState._billboards) { diff --git a/src/layers/military/snapshotRenderer.js b/src/layers/military/snapshotRenderer.js index 6d619bf72..9de25db1d 100644 --- a/src/layers/military/snapshotRenderer.js +++ b/src/layers/military/snapshotRenderer.js @@ -187,7 +187,6 @@ export function createMilitarySnapshotRenderer({ // If this is the tracked aircraft, update label text // (position updates automatically via dead-reckoning CallbackProperty) if (isTracked && flightState._trackedEntity) { - const info = flightState.records.data.get(icao24); tracking._updateTrackedLabelModel(icao24); } } diff --git a/src/layers/vessels/cards.js b/src/layers/vessels/cards.js index da0a2ce82..64e115637 100644 --- a/src/layers/vessels/cards.js +++ b/src/layers/vessels/cards.js @@ -12,8 +12,6 @@ export function createCards({ layer, options, }) { - const { state } = vesselState; - function updateSelectedVesselHud(record) { const el = document.getElementById('hud-ais-vessel'); if (!el) return; From 504f9342aab147c0a3846f9f7bc5b2ad29a5461e Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 18:53:56 -0700 Subject: [PATCH 04/11] chore: remove unreachable legacy entry modules Delete three modules that no entry point, package export, test or script imports: - src/ui.js, a StyleManager subclass that pre-selected the standalone catalog. Application controls are built through src/app/controls.js and src/standalone/controls.js. - src/standalone/ui.js, a bare re-export of StyleManager. - src/data/localLayers.js, a list of default infrastructure, cable and FIRMS layer instances. The catalog builds these layers itself. With src/ui.js gone, the import-direction check keeps a single compatibility exception (server/providers/local.js to server/standalone/key-setup.js), and its test now asserts that a src/ui.js importing standalone code is rejected. The boundary and format registries and the docs that named these files are updated. --- CONTRIBUTING.md | 2 +- docs/CODE-BOUNDARIES.md | 12 +++++------- docs/CURRENT-STATE.md | 8 ++++---- scripts/check-import-directions.mjs | 7 +++---- scripts/format-scope.json | 2 -- scripts/package-boundaries.json | 1 - src/data/localLayers.js | 18 ------------------ src/standalone/ui.js | 1 - src/tooling/importDirections.test.mjs | 9 +++++++-- src/ui.js | 16 ---------------- 10 files changed, 20 insertions(+), 56 deletions(-) delete mode 100644 src/data/localLayers.js delete mode 100644 src/standalone/ui.js delete mode 100644 src/ui.js diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 442b9c1bf..bb2c2de9e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,7 +116,7 @@ The highest-leverage places to jump in: ## Architecture in one minute - **No framework.** Vanilla JS + [CesiumJS](https://cesium.com/platform/cesiumjs/) + [Vite](https://vitejs.dev/). -- **Assembly lives in `src/app/`; standalone defaults live in `src/standalone/`.** UI controllers live in `src/ui/`, layer factories in `src/layers/`, portable sources in `src/sources/`, and application operations in `src/services/`. Existing `src/ui.js` and `src/data/.js` entries retain compatibility; new code belongs with its focused owner. +- **Assembly lives in `src/app/`; standalone defaults live in `src/standalone/`.** UI controllers live in `src/ui/`, layer factories in `src/layers/`, portable sources in `src/sources/`, and application operations in `src/services/`. Existing `src/data/.js` entries retain compatibility; new code belongs with its focused owner. - Sources acquire records; renderers own Cesium resources. Import `gods-eye-view/layers//source` when only a source factory is needed. Common voice controls consume the session interface; protocol adapters own connection details. - **Secrets stay server-side.** Anything needing a private key goes through a local proxy under `server/providers/`. The browser only ever sees the Google Maps key (which you restrict) and ephemeral tokens. - `docs/CURRENT-STATE.md` is the authoritative runtime reference — read it first. diff --git a/docs/CODE-BOUNDARIES.md b/docs/CODE-BOUNDARIES.md index a23ba3e0f..3362b98f0 100644 --- a/docs/CODE-BOUNDARIES.md +++ b/docs/CODE-BOUNDARIES.md @@ -79,7 +79,6 @@ moves it. Do not label all ingestion modules platform-independent by filename. | Retained entry | Owner and current reason | | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `src/ui.js` | UI facade; direct callers retain standalone StyleManager defaults. Normal assembly uses `src/ui/composition.js` | | `src/data/manager.js` | Lifecycle/panel compatibility and existing tests; normal assembly constructs lifecycle and presentation separately | | `src/data/.js` | Legacy default instances; new assembly calls layer factories with supplied sources | | `src/data/localGeojson.js` | Infrastructure compatibility factories; catalog imports services from `src/app/localGeojsonServices.js` directly | @@ -87,9 +86,8 @@ moves it. Do not label all ingestion modules platform-independent by filename. | `src/app/sources.js`, `src/services/application.js` | Existing default/override API; current application construction supplies its own instances | | `server/providers/local.js` | Existing Node composition barrel and provider helper re-exports | -The two exact standalone-import exceptions are `src/ui.js` to -`src/standalone/catalog.js` and `server/providers/local.js` to -`server/standalone/key-setup.js`. They do not authorize new compatibility +The one exact standalone-import exception is `server/providers/local.js` to +`server/standalone/key-setup.js`. It does not authorize new compatibility back-edges. Voice actions use plain `src/data/feedState.js`, not the manager facade. Settings filesystem hardening belongs in `server/standalone/key-setup-hardening.mjs`; its settings policy is unchanged. @@ -237,7 +235,7 @@ DOM elements and callbacks; importing it creates no browser state. `destroy()` removes owned listeners and cancels pending work without changing saved state or moving focus. Call it before removing or replacing the controls. -`src/ui.js` retains panel layout, persistence, share restoration and application +`src/ui/composition.js` retains panel layout, persistence, share restoration and application reactions to state changes. Map Source selection and Location draft cleanup are provided through callbacks. The component imports no globe, data, application or server modules. Package checks and scoped formatting cover this entry. @@ -470,8 +468,8 @@ release listeners; pending action feedback is limited to its current owner. ## UI assembly and styles `ui/shell` assembles controls from supplied existing layer, navigation, terrain, -rendering, HUD and share operations. `src/standalone/ui.js` provides the running -application's instances; `src/ui.js` remains the compatibility entry. The shell +rendering, HUD and share operations. `src/standalone/controls.js` provides the running +application's instances. The shell imports no standalone bootstrap or concrete live layer implementation. Panel layout, position/drag, notices, recording and deferred UI work have separate owners with synchronous cleanup. Existing scene, share and HUD engines retain diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 5ec660548..7184c5d96 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -3595,7 +3595,7 @@ Historical planning documents may not match runtime behavior. ## Runtime Stack - Vite + CesiumJS app with Google Photorealistic 3D Tiles -- Scene/HUD/style systems in `src/ui.js` and `src/hud.js` +- Scene/HUD/style systems in `src/ui/` and `src/hud.js` - Layer management in `src/data/manager.js` - Map stack switching in `src/mapStackController.js` - Voice control in `src/voice/` (OpenAI Realtime over WebRTC) @@ -3627,8 +3627,8 @@ its criteria cannot be silently ignored. | Transit 🚌 | Operator GTFS-Realtime VehiclePositions (7 keyless regions, `src/data/transitFeeds.js`) | `src/layers/transit/` via `src/app/layers/transit.js` | `/api/transit` | 15s (poll + delayed playback) | | Bikeshare 🚲 | GBFS (Lyft + BCycle + Social Bicycles + PBSC incl. Bike Share Toronto, Bike Itaú (Brazil), Bicing (Barcelona) and seven other Spanish cities + Urban Sharing, Entur, JCDecaux (Lillestrøm, Valencia, Seville) + nextbike (AMBici)) | `src/data/bikeshare.js` | `/api/gbfs` | 60s | | Directions 🧭 | OSRM on FOSSGIS servers (OpenStreetMap) | `src/data/directions.js` | `/api/route` (`steps=1`) | on placement / mode change | -| Datacenters ▣ | OSM extract (bundled) | `src/data/localLayers.js` | — | static | -| Dams ▰ | OpenInfraMap/OSM extract (bundled) | `src/data/localLayers.js` | — | static | +| Datacenters ▣ | OSM extract (bundled) | `src/data/infrastructure.js` | — | static | +| Dams ▰ | OpenInfraMap/OSM extract (bundled) | `src/data/infrastructure.js` | — | static | | Submarine Cables ◠ | TeleGeography public map (bundled) | `src/data/telegeographySubmarineCables.js` | — | static | | FIRMS Active Fires ▲ | NASA FIRMS live (VIIRS ×3 NRT + MODIS NRT, trailing 24h) | `src/data/firmsHeatmap.js` | `/api/firms` (`FIRMS_MAP_KEY`) | 10 min (proxy TTL 30 min) | | Street Level 📷 | Mapillary street-level imagery (vector tiles, Graph API, MapillaryJS) | `src/layers/streetLevel/` via `src/app/layers/streetLevel.js` | `/api/mapillary/status`, `/api/mapillary/tiles/coverage/{z}/{x}/{y}` (`MAPILLARY_CLIENT_TOKEN`) | camera-driven (320 ms debounce, ≤9 tiles, server memory cache 24 h) | @@ -5164,7 +5164,7 @@ pins and share links already supply complete boxes. - **Grounded 3D handoff is terrain-validity gated (2026-08-23).** A ready civilian or military glTF does not own the visual until `groundSnap` (`src/data/groundSnap.js`) can answer with a MEASURED photoreal-surface height. On success the layer writes `height + the model's measured belly offset` before revealing the model. Cache movement is measured on the WGS84 surface, not across altitude, so a stationary contact keeps its snap through poll-time vertical-datum changes. Model existence or GPU readiness alone never suppresses the billboard floor, and ordinary zoom/style/deselect transitions preserve a valid snap cache. **Two states, and the difference is the whole design.** COLD — nothing has ever resolved for this icao (tiles still streaming on first sight, sample failure, backoff after a first miss): there is no evidence of where the ground is, `heightFor` returns null, the model stays hidden and the depth-test-free 2D billboard remains opaque and floored. WARM — a snap resolved and then a >`MOVE_INVALIDATE_M` (50 m) taxi move stopped it answering directly: the measurement is DEMOTED to a bounded last-known rather than deleted, and it keeps answering while the resample is outstanding, so a taxiing aircraft does not pop 3D→2D→3D across a 2–30 s retry backoff. The bound is `HELD_SNAP_MAX_DRIFT_M` = 250 m from the spot the value was measured at, past which the hold is dropped rather than stretched and the contact is COLD again. It is spatial with no timer beside it (ground under a contact that has not moved does not change; what invalidates the value is the contact MOVING) and deliberately a quarter of the billboard chain's `HELD_FLOOR_MAX_DRIFT_KM` — that hold only ever RAISES a sprite, while a held snap IS the model's placement, so its error shows in both directions. A fresh sample releases the hold, and so does a ground flip (which already calls `forget`). **A loading model is HIDDEN, never zero-scaled:** admission sets `show = false` (Cesium's default is `true`, and an unplaced primitive would claim the visual at the identity matrix), and ownership is `ready && show` — Cesium 1.138's `Model.update` has no `show` guard, so hiding a primitive costs its load nothing. - Commercial and military aircraft use the same high-level FLEET model regime: 2D billboards when zoomed out, optional glTF models when closer, controlled by the DISPLAY rail `3D` toggle and `Proximity` / `All` modes. Since 2026-08-16 (Hangar fleet) models are PER-CLASS: real CC-BY GLBs for light/bizjet/turboprop/widebody/helicopter/uav (`CLASS_MODEL_REAL` in `src/data/aircraftClass.js` — meters-baked, scale 1, per-model belly/radius; provenance in `public/models/README.md`), the shared `airplane.glb` for the remaining civilian classes, and the military layer maps weight classes (real GLBs / 747 heavies / `jet.glb` fastjets, per-model heading offsets, flat amber or emergency red). Textured civilians carry a HEAVY tint, not a light one: `MODEL_COLOR_BLEND_AMOUNT` is `0.94` in BOTH layers under Cesium's `ColorBlendMode.MIX`, so the class colour supplies 94% of the surface and the asset's own texture ~6%. The visual direction is clean light silhouettes with only a weak diffuse contribution from the approved textures, so liveries deliberately do NOT read. IR boost raises the blend to a full `1.0`. Under NVG/FLIR (map preset or Cockpit vision) models render unlit flat-white at full alpha and scene fog is disabled (fog otherwise blacks out distant models with the globe hidden); state restores on exit. - The DISPLAY-rail `3D` toggle is the user-facing activation path for both aircraft layers. Their small approved GLBs build their render resources without Cesium's frame-spread job queue, preventing continuous Photorealistic 3D Tiles streaming from starving model readiness; model caps, tracking, camera, and fallback billboards are unchanged. -- **The `3D` toggle DEFAULTS ON in `proximity` on a first run.** Proximity is itself the budget — models appear only below `MODEL_ALT_CEIL_M` and only for the nearest `MODEL_MAX` in view — so the default costs nothing at globe scale, and `all` remains a deliberate opt-in. A fresh boot runs NO layer-state restoration (`LayerStateCoordinator.start()` returns early with neither a share payload nor stored state), so four independent initializers decide what a first-run operator sees and must agree: `booleanOption('models3d', 'e', true)` in `src/data/layerState.js`, `_models3dEnabled = true` in BOTH flight layers, `this._models3dEnabled = true` in `src/ui.js`, and the `active` / `visible` classes on `#models3d-toggle` / `#models3d-mode-row` in `index.html`. All four are pinned together in `src/data/layerState.test.mjs`. Explicit state still wins: because the codec omits default-valued options, `models3d: false` is now what travels in a link (`lo=…f.e.0`) and restores OFF at both aircraft layers. **Consequence for returning users:** a stored `gev:layer-state:v2` blob is a FULL options snapshot, so a session that wrote one before this change carries `models3d:false` and keeps 3D off until the operator flips it (or clears the key) — the durable snapshot is treated as the recipient's own state, by design. +- **The `3D` toggle DEFAULTS ON in `proximity` on a first run.** Proximity is itself the budget — models appear only below `MODEL_ALT_CEIL_M` and only for the nearest `MODEL_MAX` in view — so the default costs nothing at globe scale, and `all` remains a deliberate opt-in. A fresh boot runs NO layer-state restoration (`LayerStateCoordinator.start()` returns early with neither a share payload nor stored state), so four independent initializers decide what a first-run operator sees and must agree: `booleanOption('models3d', 'e', true)` in `src/data/layerState.js`, `_models3dEnabled = true` in BOTH flight layers, `this._models3dEnabled = true` in `src/ui/aircraftDisplay.js`, and the `active` / `visible` classes on `#models3d-toggle` / `#models3d-mode-row` in `index.html`. All four are pinned together in `src/data/layerState.test.mjs`. Explicit state still wins: because the codec omits default-valued options, `models3d: false` is now what travels in a link (`lo=…f.e.0`) and restores OFF at both aircraft layers. **Consequence for returning users:** a stored `gev:layer-state:v2` blob is a FULL options snapshot, so a session that wrote one before this change carries `models3d:false` and keeps 3D off until the operator flips it (or clears the key) — the durable snapshot is treated as the recipient's own state, by design. - **Consequence of the flip on the recorded tracked/fleet inversion:** the 150–800 km band where surrounding contacts draw as models while the SELECTED one is still a glyph is now what an operator sees WITHOUT arming anything. The inversion itself is unchanged and still deliberate (see `src/data/trackedModelRegime.js`); only its reachability changed. - Civilian and military 2D aircraft use the established distance scale: `3×` near the camera and a `0.5×` floor from 8,000 km outward. A standard 20 px ambient diff --git a/scripts/check-import-directions.mjs b/scripts/check-import-directions.mjs index ac1a3f732..3f4fec617 100644 --- a/scripts/check-import-directions.mjs +++ b/scripts/check-import-directions.mjs @@ -24,11 +24,10 @@ const renderer = (file) => /^src\/layers\/[^/]+\/(?:index|rendering|snapshotRenderer|overlay|presentation|cards|controls)\.js$/.test( file, ); -// Two compatibility composition entries intentionally select standalone defaults. +// One compatibility composition entry intentionally selects standalone defaults. const compatibilityEdge = (from, to) => - (from === 'server/providers/local.js' && - to === 'server/standalone/key-setup.js') || - (from === 'src/ui.js' && to === 'src/standalone/catalog.js'); + from === 'server/providers/local.js' && + to === 'server/standalone/key-setup.js'; const portableExport = (key) => key.startsWith('./sources/') || /\/source$/.test(key) || diff --git a/scripts/format-scope.json b/scripts/format-scope.json index 2c78c0774..e313ade42 100644 --- a/scripts/format-scope.json +++ b/scripts/format-scope.json @@ -285,8 +285,6 @@ "src/ui/sceneControls.test.mjs", "scripts/qa-scene-controls.mjs", "scripts/qa-action-buttons.mjs", - "src/ui.js", - "src/standalone/ui.js", "src/ui/applicationShell.js", "src/ui/panelLayoutController.js", "src/ui/panelPositionControls.js", diff --git a/scripts/package-boundaries.json b/scripts/package-boundaries.json index a7c35c286..f2efea33c 100644 --- a/scripts/package-boundaries.json +++ b/scripts/package-boundaries.json @@ -1852,7 +1852,6 @@ "src/data/localGeojson.js", "src/data/localGeojsonCore.js", "src/data/localGeojsonLod.js", - "src/data/localLayers.js", "src/data/local_data/natural_earth/marine.json", "src/data/local_data/natural_earth/regions.json", "src/data/local_data/neighborhoods/san-francisco.json", diff --git a/src/data/localLayers.js b/src/data/localLayers.js deleted file mode 100644 index 63c07b073..000000000 --- a/src/data/localLayers.js +++ /dev/null @@ -1,18 +0,0 @@ -import { localGeoJsonServices } from './localGeojson.js'; -import { createInfrastructureLayers } from './infrastructure.js'; -import { createFirmsHeatmapLayer } from './firmsHeatmap.js'; -import submarineCablesLayer from './telegeographySubmarineCables.js'; - -const [datacenters, dams] = createInfrastructureLayers(localGeoJsonServices); - -// Live NASA FIRMS fires (VIIRS ×3 NRT via the /api/firms proxy). The id keeps -// the historical `local-` prefix for persistence + voice-tool-enum compat, -// but the data is NOT bundled anymore — it needs FIRMS_MAP_KEY server-side. -const fires = createFirmsHeatmapLayer({ - id: 'local-firms', - name: 'FIRMS Active Fires', - icon: '▲', - source: 'NASA FIRMS · LIVE', -}); - -export default [datacenters, dams, submarineCablesLayer, fires]; diff --git a/src/standalone/ui.js b/src/standalone/ui.js deleted file mode 100644 index 780d515cf..000000000 --- a/src/standalone/ui.js +++ /dev/null @@ -1 +0,0 @@ -export { StyleManager } from '../ui/composition.js'; diff --git a/src/tooling/importDirections.test.mjs b/src/tooling/importDirections.test.mjs index b34fee3b9..4352bd582 100644 --- a/src/tooling/importDirections.test.mjs +++ b/src/tooling/importDirections.test.mjs @@ -156,13 +156,18 @@ test('self package imports cannot evade portable ownership', (t) => { assert.throws(() => checkImportDirections(root), /Source imports rendering/); }); -test('compatibility exceptions are limited to the two existing composition entries', (t) => { +test('the compatibility exception is limited to the existing composition entry', (t) => { const { root, write } = fixture(t); - write('src/ui.js', "import './standalone/catalog.js';"); write('src/standalone/catalog.js'); write('server/providers/local.js', "import '../standalone/key-setup.js';"); write('server/standalone/key-setup.js'); assert.doesNotThrow(() => checkImportDirections(root)); + write('src/ui.js', "import './standalone/catalog.js';"); + assert.throws( + () => checkImportDirections(root), + /Reusable module imports standalone/, + ); + write('src/ui.js'); write('src/ui/new.js', "import '../standalone/catalog.js';"); assert.throws( () => checkImportDirections(root), diff --git a/src/ui.js b/src/ui.js deleted file mode 100644 index 5142301a3..000000000 --- a/src/ui.js +++ /dev/null @@ -1,16 +0,0 @@ -import { StyleManager as ApplicationControls } from './ui/composition.js'; -import { catalogControlServices } from './app/catalog.js'; -import { getStandaloneCatalog } from './standalone/catalog.js'; -/** Compatibility entry for direct standalone control construction. */ -export class StyleManager extends ApplicationControls { - constructor(viewer, options = {}) { - super(viewer, { - ...options, - services: { - ...catalogControlServices(getStandaloneCatalog()), - ...getStandaloneCatalog().surface.controlServices, - ...options.services, - }, - }); - } -} From 73e789489bdb830fa0c6bfdbcc60de5fd81bf9ad Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 19:00:35 -0700 Subject: [PATCH 05/11] chore: drop exports nothing imports Knip, configured with the application, package exports, scripts, QA harnesses, workers, tools and tests as entry points, reports exports that no module imports. Each was checked by hand against string-based and namespace imports. - Symbols that are still used inside their own module lose only the `export` keyword (176 constants and helpers). - Unused re-exports are removed from barrel modules (flight, military, vessel, CCTV, directions, transit history, local ADS-B and street-level policy modules). - Declarations that nothing uses at all are deleted, among them AIS_WATCHDOG_STATUSES, applyFilter, isRadioCountryCode, compactText, DENSITY_STOPS, the unused CCTV endpoint constants, and the SDR band constants. - Default exports that nothing imports are removed. The ones in src/data/earthquakes.js, src/data/telegeographySubmarineCables.js, src/data/wind.js and src/data/bhoteKoshiLocator.js each constructed a layer instance at import time that nothing used; those instances are no longer created. Every export of a package.json entry stays, as does TOMTOM_CREDIT, which the traffic layer reads from the credits namespace it is given. --- server/providers/cctv/constants.js | 2 +- server/providers/cctv/groundHeights.js | 2 +- server/providers/cctv/media.js | 4 +- server/providers/cctv/normalize.js | 6 +-- server/providers/cctv/sources.js | 2 +- server/providers/cctv/stream.js | 2 +- server/providers/cctv/taiwan.js | 6 +-- server/providers/local-receivers.js | 18 ++++---- server/providers/mapillary/tiles.js | 2 +- server/providers/openai/codex-auth.js | 2 +- server/providers/openai/debug-log.js | 2 +- server/providers/radio/catalog.js | 2 +- server/providers/transitHistory.js | 1 - server/providers/wind/ifs.js | 2 +- src/annotations/drawMode.js | 8 ++-- src/annotations/drawTool.js | 2 +- src/annotations/resolver.js | 2 +- src/app/embed.js | 6 +-- src/app/sourceComposition.js | 2 +- src/cameraGroundGuard.js | 4 +- src/cctvFocusPolicy.js | 2 +- src/cockpitCloudEffects.js | 2 +- src/data/adminBoundaries.js | 2 +- src/data/aisStreamAdapter.js | 2 +- src/data/aisWatchdog.js | 22 +--------- src/data/analystEngine.js | 44 ++----------------- src/data/bhoteKoshiLocator.js | 5 +-- src/data/bikeshare.js | 1 - src/data/cctvFootprint.js | 4 +- src/data/cctvLod.js | 6 +-- src/data/dataCredits.js | 4 +- src/data/detectionCohort.js | 2 +- src/data/detectionPolicy.js | 3 +- src/data/directions.js | 6 +-- src/data/earthquakes.js | 1 - src/data/geoFileImport.js | 8 ++-- src/data/geoImportImages.js | 2 +- src/data/gtfsRealtime.js | 5 +-- src/data/localGeojson.js | 1 - src/data/militaryNames.js | 2 +- src/data/radioCountry.js | 5 --- src/data/telegeographySubmarineCables.js | 1 - src/data/wind.js | 8 ---- src/i18n/index.js | 2 +- src/layers/alpr/records.js | 6 +-- src/layers/alpr/tileRecords.js | 2 +- src/layers/awareness/policy.js | 5 +-- src/layers/bikeshare/policy.js | 2 +- src/layers/cctv/policy.js | 11 +---- src/layers/cctv/sourcePolicy.js | 4 -- src/layers/cyclones/labels.js | 4 +- src/layers/flights/policy.js | 8 ---- src/layers/installations/policy.js | 2 - src/layers/localAdsb/feeds.js | 4 +- src/layers/localAdsb/index.js | 14 +----- src/layers/localAdsb/motion.js | 2 +- src/layers/localAdsb/status.js | 4 +- src/layers/military/policy.js | 7 --- src/layers/radio/policy.js | 2 +- src/layers/recentImagery/model.js | 2 +- src/layers/satellites/policy.js | 4 +- src/layers/streetLevel/photoViewer.js | 10 ++--- .../streetLevel/providers/kartaview/policy.js | 4 +- .../streetLevel/providers/panoramax/index.js | 2 - .../streetLevel/providers/panoramax/policy.js | 2 +- src/layers/streetLevel/registry.js | 2 +- src/layers/streetLevel/sequenceCones.js | 4 +- src/layers/streetLevel/tileCache.js | 4 +- src/layers/streetLevel/uiState.js | 4 +- src/layers/submarineCables/geometry.js | 6 +-- src/layers/traffic/footprint.js | 6 +-- src/layers/traffic/policy.js | 10 ----- src/layers/transit/policy.js | 14 +++--- src/layers/vessels/policy.js | 5 --- src/layers/weather/source.js | 2 +- src/layers/wind/gpuRendering.js | 4 +- src/locationStatus.js | 2 - src/maps/imageryComparison.js | 2 +- src/maps/terrainRetry.js | 2 +- src/scenes/recipes.js | 4 +- src/sdr/controller.js | 12 ++--- src/sdr/gain.js | 2 +- src/sky/stars.js | 4 +- src/sources/adsbRecords.js | 8 ++-- src/sources/featureGeometry.js | 12 ++--- src/sources/openFreeMap.js | 2 +- src/sources/transitHistoryStore.js | 2 +- src/testSupport/kartaViewFakes.mjs | 2 +- src/tools/queries/panelRequest.js | 2 +- src/tools/queries/share.js | 2 +- src/ui/cameraOrientationControls.js | 4 +- src/ui/cctvFrames.js | 2 +- src/ui/statusText.js | 2 +- src/voice/deixis.js | 2 +- src/voice/narration.js | 2 +- src/voice/pointerContext.js | 8 ++-- src/voice/pointerPick.js | 2 +- src/voice/pointerReticle.js | 2 +- src/voice/realtimeDiagnostics.js | 29 +++++------- src/voice/realtimePreferences.js | 6 +-- src/voice/realtimeTurns.js | 2 +- src/voice/realtimeViewport.js | 16 +++---- src/voice/resultDisplay.js | 2 +- src/voice/speechLint.js | 2 +- src/voice/turnMetrics.js | 2 +- src/voice/voiceCard.js | 2 +- 106 files changed, 180 insertions(+), 342 deletions(-) diff --git a/server/providers/cctv/constants.js b/server/providers/cctv/constants.js index 257c292b3..a26fa6c20 100644 --- a/server/providers/cctv/constants.js +++ b/server/providers/cctv/constants.js @@ -18,7 +18,7 @@ export const CCTV_MAX_SOURCES_CEILING = 5000; export const AUSTIN_DOWNTOWN = { lat: 30.2672, lon: -97.7431 }; /** Caltrans CCTV: one JSON feed per district, identical schema statewide. */ /** TxDOT ITS: one keyless JSON catalog per district (25 districts statewide). */ -export const TXDOT_ORIGIN = 'https://its.txdot.gov'; +const TXDOT_ORIGIN = 'https://its.txdot.gov'; export const TXDOT_CCTV_STATUS_URL = (district) => `${TXDOT_ORIGIN}/its/DistrictIts/GetCctvStatusListByDistrict?districtCode=${encodeURIComponent(district)}`; /** Per-camera frame. Returns JSON `{snippet:}`, not an image body; diff --git a/server/providers/cctv/groundHeights.js b/server/providers/cctv/groundHeights.js index c73b6e840..f20b9758d 100644 --- a/server/providers/cctv/groundHeights.js +++ b/server/providers/cctv/groundHeights.js @@ -3,7 +3,7 @@ import path from 'node:path'; import { poseHash } from '../../../src/data/cctvFootprint.js'; /** Sidecar written by scripts/precompute-cctv-heights.mjs. */ -export const DEFAULT_GROUND_HEIGHTS_FILE = +const DEFAULT_GROUND_HEIGHTS_FILE = 'src/data/local_data/cctv_ground_heights/cctv_ground_heights.json'; /** @type {{path:string, mtimeMs:number, cameras:object}|null} */ diff --git a/server/providers/cctv/media.js b/server/providers/cctv/media.js index 1323bc841..206a24888 100644 --- a/server/providers/cctv/media.js +++ b/server/providers/cctv/media.js @@ -92,7 +92,7 @@ export function buildSyntheticCctvSvg({ cameraId, label, city, status }) { * @param {ReadableStream|NodeJS.ReadableStream|null} body * @returns {import('stream').Readable|null} */ -export function toReadable(body) { +function toReadable(body) { if (!body) return null; if (typeof body.pipe === 'function') return body; if (typeof body.getReader === 'function') { @@ -108,7 +108,7 @@ export function toReadable(body) { * @returns {{essence:string,params:Map}|null} Null when the * value is missing or is not a `type/subtype` media type. */ -export function parseMediaType(value) { +function parseMediaType(value) { const [head, ...rest] = String(value ?? '').split(';'); const essence = head.trim().toLowerCase(); if (!/^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(essence)) return null; diff --git a/server/providers/cctv/normalize.js b/server/providers/cctv/normalize.js index 74f693dd8..f190f257e 100644 --- a/server/providers/cctv/normalize.js +++ b/server/providers/cctv/normalize.js @@ -64,7 +64,7 @@ export function toFiniteNumber(value, fallback = NaN) { * @param {string} text * @returns {string} */ -export function normalizeKey(text) { +function normalizeKey(text) { return String(text || '') .trim() .toLowerCase() @@ -80,7 +80,7 @@ export function normalizeKey(text) { * @param {string} value * @returns {{lat:number, lon:number}} */ -export function parsePointString(value) { +function parsePointString(value) { const match = String(value || '').match( /POINT\s*\(\s*(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s*\)/i, ); @@ -100,7 +100,7 @@ export function parsePointString(value) { * @param {string|object|null} value * @returns {{lat:number, lon:number}} */ -export function coerceLatLon(value) { +function coerceLatLon(value) { if (!value) return { lat: NaN, lon: NaN }; if (typeof value === 'string') { diff --git a/server/providers/cctv/sources.js b/server/providers/cctv/sources.js index c7def2284..a479a945c 100644 --- a/server/providers/cctv/sources.js +++ b/server/providers/cctv/sources.js @@ -1868,7 +1868,7 @@ export function normalizeCataloniaImageUrl(rawUrl) { * @param {string} imageUrl - A URL normalizeCataloniaImageUrl accepted. * @returns {string} */ -export function cataloniaCameraId(imageUrl) { +function cataloniaCameraId(imageUrl) { const parsed = new URL(imageUrl); const slug = (text) => text diff --git a/server/providers/cctv/stream.js b/server/providers/cctv/stream.js index 14d1743bb..da811f5f9 100644 --- a/server/providers/cctv/stream.js +++ b/server/providers/cctv/stream.js @@ -70,7 +70,7 @@ export async function fetchHlsBytes( } } -export function sameOriginHlsUrl(value, base) { +function sameOriginHlsUrl(value, base) { const url = new URL(value, base); if ( !['https:', 'http:'].includes(url.protocol) || diff --git a/server/providers/cctv/taiwan.js b/server/providers/cctv/taiwan.js index 830ed47fd..a14aabf9a 100644 --- a/server/providers/cctv/taiwan.js +++ b/server/providers/cctv/taiwan.js @@ -12,14 +12,14 @@ export const TAIWAN_FREEWAY_CCTV_URL = 'https://tisvcloud.freeway.gov.tw/history/motc20/CCTV.xml'; /** The whole network is ~1.2 MB. */ export const TAIWAN_MAX_CATALOG_BYTES = 6 * 1024 * 1024; -export const DEFAULT_TAIWAN_MAX_SOURCES = 600; +const DEFAULT_TAIWAN_MAX_SOURCES = 600; /** After a failed download, wait this long before asking again. The catalog * host does not answer from some networks outside Taiwan, and every attempt * there holds the shared catalog refresh until the connect timeout. */ export const TAIWAN_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1000; /** The table is republished daily; a good copy stands in for a failed * download for up to this long. */ -export const TAIWAN_LAST_GOOD_MAX_AGE_MS = 24 * 60 * 60 * 1000; +const TAIWAN_LAST_GOOD_MAX_AGE_MS = 24 * 60 * 60 * 1000; // The table publishes each camera's MJPEG stream. The same camera servers // answer a single JPEG for the same camera ID on a sibling path, which is what @@ -50,7 +50,7 @@ const ANCHORS = [ /** Taiwan main island with slack for the coast; the freeway network has no * offshore-island cameras. */ -export function isLikelyTaiwanCoordinate(lat, lon) { +function isLikelyTaiwanCoordinate(lat, lon) { return ( Number.isFinite(lat) && Number.isFinite(lon) && diff --git a/server/providers/local-receivers.js b/server/providers/local-receivers.js index 994da7bd8..f3f340b54 100644 --- a/server/providers/local-receivers.js +++ b/server/providers/local-receivers.js @@ -27,13 +27,13 @@ import { normalizeDump1090Aircraft } from '../../src/sources/adsbRecords.js'; */ export const LOCAL_RECEIVERS_ROUTE = '/api/local-receivers/aircraft'; -export const LOCAL_RECEIVER_FEEDS_ENV = 'LOCAL_RECEIVER_FEEDS'; -export const LOCAL_RECEIVER_BANDS = Object.freeze(['1090', '978']); -export const LOCAL_RECEIVER_TIMEOUT_MS = 2_000; -export const LOCAL_RECEIVER_MAX_BODY_BYTES = 2 * 1024 * 1024; -export const LOCAL_RECEIVER_CACHE_MS = 1_000; +const LOCAL_RECEIVER_FEEDS_ENV = 'LOCAL_RECEIVER_FEEDS'; +const LOCAL_RECEIVER_BANDS = Object.freeze(['1090', '978']); +const LOCAL_RECEIVER_TIMEOUT_MS = 2_000; +const LOCAL_RECEIVER_MAX_BODY_BYTES = 2 * 1024 * 1024; +const LOCAL_RECEIVER_CACHE_MS = 1_000; /** A feed whose own `now` is older than this is reported stale. */ -export const LOCAL_RECEIVER_STALE_MS = 10_000; +const LOCAL_RECEIVER_STALE_MS = 10_000; const MAX_FEEDS = 8; const LOG_PREFIX = '[local-receivers]'; @@ -139,7 +139,7 @@ class FeedError extends Error { * @param {string} address Resolved IP address. * @returns {boolean} */ -export function isLocalReceiverAddress(address) { +function isLocalReceiverAddress(address) { const value = String(address || '').toLowerCase(); if (value === '::1') return true; const mapped = /^::ffff:(\d{1,3}(?:\.\d{1,3}){3})$/.exec(value); @@ -152,7 +152,7 @@ export function isLocalReceiverAddress(address) { * @param {typeof dnsLookup} lookupImpl `dns.promises.lookup`-compatible. * @returns {Promise>} */ -export async function resolveLocalReceiverAddresses(hostname, lookupImpl) { +async function resolveLocalReceiverAddresses(hostname, lookupImpl) { let resolved; try { resolved = await lookupImpl(hostname, { all: true, verbatim: true }); @@ -274,7 +274,7 @@ function fetchLocalReceiverFeed(url, options) { * @param {object} options * @returns {Promise} Parsed JSON document. */ -export async function fetchLocalReceiverDocument( +async function fetchLocalReceiverDocument( url, { fetchImpl = fetchLocalReceiverFeed, diff --git a/server/providers/mapillary/tiles.js b/server/providers/mapillary/tiles.js index 569e7e45f..6e2207848 100644 --- a/server/providers/mapillary/tiles.js +++ b/server/providers/mapillary/tiles.js @@ -42,7 +42,7 @@ export class TileUpstreamError extends Error { } /** Seconds a Retry-After header asks for (an integer or an HTTP date), else null. */ -export function retryAfterSeconds(header, now = Date.now()) { +function retryAfterSeconds(header, now = Date.now()) { const text = String(header ?? '').trim(); if (!text) return null; const seconds = /^\d+$/.test(text) diff --git a/server/providers/openai/codex-auth.js b/server/providers/openai/codex-auth.js index 5f16ee824..4d3f992da 100644 --- a/server/providers/openai/codex-auth.js +++ b/server/providers/openai/codex-auth.js @@ -145,7 +145,7 @@ const CODEX_LOGIN_ENV = /^(?:PATH|PATHEXT|HOME|USER|USERNAME|LOGNAME|SHELL|LANG|LANGUAGE|LC_[A-Z_]+|TERM|TZ|TMPDIR|TEMP|TMP|XDG_[A-Z_]+|DISPLAY|WAYLAND_DISPLAY|DBUS_SESSION_BUS_ADDRESS|BROWSER|WSL_[A-Z_]+|WSLENV|(?:HTTPS?|ALL|NO)_PROXY|SSL_CERT_(?:FILE|DIR)|NODE_EXTRA_CA_CERTS|SYSTEMROOT|WINDIR|COMSPEC|USERPROFILE|APPDATA|LOCALAPPDATA|PROGRAMDATA|HOMEDRIVE|HOMEPATH|CODEX_[A-Z_]+)$/i; /** The environment `codex login` runs with: the allowlist, then `CODEX_HOME`. */ -export function codexLoginEnvironment(environment, codexHome) { +function codexLoginEnvironment(environment, codexHome) { const env = {}; for (const [name, value] of Object.entries(environment || {})) if (CODEX_LOGIN_ENV.test(name) && value !== undefined) env[name] = value; diff --git a/server/providers/openai/debug-log.js b/server/providers/openai/debug-log.js index 956569642..c8151d2f6 100644 --- a/server/providers/openai/debug-log.js +++ b/server/providers/openai/debug-log.js @@ -157,4 +157,4 @@ function createDebugLogHandler({ }; } -export { createDebugLogHandler, omitVoiceContent }; +export { createDebugLogHandler }; diff --git a/server/providers/radio/catalog.js b/server/providers/radio/catalog.js index 36a3eb730..2864c8986 100644 --- a/server/providers/radio/catalog.js +++ b/server/providers/radio/catalog.js @@ -26,7 +26,7 @@ import { RADIO_UUID_RE, RADIO_FALLBACK_MIRRORS, } from './constants.js'; -export async function mapRadioConcurrent(values, concurrency, mapper) { +async function mapRadioConcurrent(values, concurrency, mapper) { const results = new Array(values.length); let cursor = 0; const workers = Array.from( diff --git a/server/providers/transitHistory.js b/server/providers/transitHistory.js index f396f3d8f..4da5a8891 100644 --- a/server/providers/transitHistory.js +++ b/server/providers/transitHistory.js @@ -1,5 +1,4 @@ export { createTransitHistory, - TRANSIT_HISTORY_LIMITS, validTransitIdentifier, } from '../../src/sources/transitHistoryStore.js'; diff --git a/server/providers/wind/ifs.js b/server/providers/wind/ifs.js index 7767d3905..076baf064 100644 --- a/server/providers/wind/ifs.js +++ b/server/providers/wind/ifs.js @@ -3,7 +3,7 @@ import { decodeWindGribMessage } from './decode.js'; import { resampleWeatherSnapshot, weatherScalarMetadata } from './grid.js'; /** Keyless ECMWF Open Data root for real-time IFS forecasts. */ -export const IFS_BASE = 'https://data.ecmwf.int/forecasts'; +const IFS_BASE = 'https://data.ecmwf.int/forecasts'; /** * Select the latest IFS cycle that should be available. diff --git a/src/annotations/drawMode.js b/src/annotations/drawMode.js index 02a501010..fcfe08be6 100644 --- a/src/annotations/drawMode.js +++ b/src/annotations/drawMode.js @@ -31,10 +31,10 @@ export const MIN_VERTICES = Object.freeze({ area: 3, line: 2, pin: 1 }); */ export const MAX_VERTICES = 512; /** Two clicks closer than this are one vertex: a double-click to finish must not add a stray point. */ -export const MIN_VERTEX_SEPARATION_M = 0.5; +const MIN_VERTEX_SEPARATION_M = 0.5; /** A line shorter than this, or an area thinner than this, is a mis-click rather than a shape. */ -export const MIN_PATH_LENGTH_M = 1; -export const MIN_AREA_M2 = 1; +const MIN_PATH_LENGTH_M = 1; +const MIN_AREA_M2 = 1; /** @param {string} shape @returns {'area'|'line'|'pin'} */ export function normalizeShape(shape) { @@ -50,7 +50,7 @@ export function createDrawSession(shape = 'area') { } /** Great-circle distance in metres between two {lon, lat} points. */ -export function greatCircleM(a, b) { +function greatCircleM(a, b) { return greatCircleMeters(a, b); } diff --git a/src/annotations/drawTool.js b/src/annotations/drawTool.js index 0f4e0fc76..9467406dd 100644 --- a/src/annotations/drawTool.js +++ b/src/annotations/drawTool.js @@ -52,7 +52,7 @@ import { import { msg, registerUnsavedWork, t, translateKnown } from '../i18n/index.js'; /** The id this tool claims the pointer under. */ -export const DRAW_POINTER_OWNER = 'draw'; +const DRAW_POINTER_OWNER = 'draw'; const PREVIEW_DATA_SOURCE_NAME = 'gev-draw-preview'; const COLORS = ['primary', 'amber', 'cyan', 'green', 'red']; diff --git a/src/annotations/resolver.js b/src/annotations/resolver.js index fb5795d53..226caf140 100644 --- a/src/annotations/resolver.js +++ b/src/annotations/resolver.js @@ -29,7 +29,7 @@ import { unavailablePlaceSearch } from '../search/placeSearch.js'; import { isPickedWorldPosition } from '../data/scenePick.js'; /** Ms the analyst region lookup waits on geocode + admin boundary. */ -export const REGION_FALLBACK_BUDGET_MS = 3_000; +const REGION_FALLBACK_BUDGET_MS = 3_000; /** Entity facts that rule out a state/county reading of the ask. */ const NON_ADMIN_ENTITY_KINDS = new Set([ diff --git a/src/app/embed.js b/src/app/embed.js index a1485fe7e..f0d6cad0a 100644 --- a/src/app/embed.js +++ b/src/app/embed.js @@ -15,9 +15,9 @@ import * as Cesium from 'cesium'; import { announceNavigationAuthority } from '../navigationPolicy.js'; import { annotationsFromParams, createView } from '../view/index.js'; -export const EMBED_VIEW_MESSAGE = 'gev:view'; -export const EMBED_APPLIED_MESSAGE = 'gev:view-applied'; -export const EMBED_READY_MESSAGE = 'gev:ready'; +const EMBED_VIEW_MESSAGE = 'gev:view'; +const EMBED_APPLIED_MESSAGE = 'gev:view-applied'; +const EMBED_READY_MESSAGE = 'gev:ready'; const FOLLOW_LAYERS = { aircraft: 'flights', diff --git a/src/app/sourceComposition.js b/src/app/sourceComposition.js index 0fc619142..04794ab13 100644 --- a/src/app/sourceComposition.js +++ b/src/app/sourceComposition.js @@ -53,7 +53,7 @@ export const CATALOG_SOURCE_CONTRACTS = Object.freeze({ }); /** An omitted provider is unavailable, never an authoritative empty dataset. */ -export class SourceUnavailableError extends Error { +class SourceUnavailableError extends Error { constructor(name) { super(`Data source not configured: ${name}`); this.name = 'SourceUnavailableError'; diff --git a/src/cameraGroundGuard.js b/src/cameraGroundGuard.js index 5a5fa3775..3f9de0cab 100644 --- a/src/cameraGroundGuard.js +++ b/src/cameraGroundGuard.js @@ -20,8 +20,8 @@ import { ownCameraArrival } from './data/cameraArrival.js'; /** Minimum eye height above the rendered surface for a usable view. */ export const MIN_EYE_CLEARANCE_M = 120; /** Retries while tiles stream in; sampling fails until the mesh exists. */ -export const GUARD_ATTEMPTS = 8; -export const GUARD_INTERVAL_MS = 600; +const GUARD_ATTEMPTS = 8; +const GUARD_INTERVAL_MS = 600; /** Ignore sub-metre noise rather than nudging the camera forever. */ const GUARD_EPSILON_M = 2; diff --git a/src/cctvFocusPolicy.js b/src/cctvFocusPolicy.js index 7fb2ebe1b..e11c1e1ca 100644 --- a/src/cctvFocusPolicy.js +++ b/src/cctvFocusPolicy.js @@ -27,7 +27,7 @@ export function runCctvLayerEnableFocus({ * @param {Object} [after={}] Ownership immediately after the await. * @returns {{ trackedEntity: *, cockpitActive: boolean }} Conservative ownership. */ -export function mergeCctvEnableOwnership(before = {}, after = {}) { +function mergeCctvEnableOwnership(before = {}, after = {}) { return { trackedEntity: before.trackedEntity || after.trackedEntity || null, cockpitActive: !!(before.cockpitActive || after.cockpitActive), diff --git a/src/cockpitCloudEffects.js b/src/cockpitCloudEffects.js index ccbf31831..c9117b7d3 100644 --- a/src/cockpitCloudEffects.js +++ b/src/cockpitCloudEffects.js @@ -174,7 +174,7 @@ export function cockpitWeatherEnabledFromStoredValue(value) { * Weather-backed volumetric cloud pass that exists only while cockpit mode is * active. It owns no Cesium fog/post-process stages and cannot affect map mode. */ -export class CockpitCloudEffectsController { +class CockpitCloudEffectsController { constructor(viewer, { weatherService = applicationServices.weather } = {}) { this.weatherService = weatherService; this.viewer = viewer; diff --git a/src/data/adminBoundaries.js b/src/data/adminBoundaries.js index e9f97cf8f..9efa967d4 100644 --- a/src/data/adminBoundaries.js +++ b/src/data/adminBoundaries.js @@ -123,7 +123,7 @@ export function decodeRing(encoded, decimals) { } /** Even-odd point-in-ring test; ring is [[lon, lat], …], open or closed. */ -export function pointInRing(ring, lat, lon) { +function pointInRing(ring, lat, lon) { if (!Array.isArray(ring) || ring.length < 3) return false; let inside = false; for (let i = 0, j = ring.length - 1; i < ring.length; j = i, i += 1) { diff --git a/src/data/aisStreamAdapter.js b/src/data/aisStreamAdapter.js index 1f7b0ef9b..05e747308 100644 --- a/src/data/aisStreamAdapter.js +++ b/src/data/aisStreamAdapter.js @@ -215,7 +215,7 @@ export const AIS_RECOGNIZED_MESSAGE_TYPES = Object.freeze( * @param {Object} body * @returns {string|null} */ -export function aisEnvelopeMmsi(envelope, body = {}) { +function aisEnvelopeMmsi(envelope, body = {}) { const metadata = envelope?.MetaData || envelope?.Metadata || {}; const raw = metadata.MMSI ?? body.UserID ?? body.UserId ?? body.Mmsi; if (raw === null || raw === undefined) return null; diff --git a/src/data/aisWatchdog.js b/src/data/aisWatchdog.js index a83c281d9..f024a6f67 100644 --- a/src/data/aisWatchdog.js +++ b/src/data/aisWatchdog.js @@ -46,28 +46,8 @@ export const AIS_WATCHDOG_DEFAULTS = Object.freeze({ authProbeMs: 3_600_000, }); -/** - * Statuses reported to the browser. 'missing-key' and 'unsupported' keep their - * pre-watchdog meaning so a keyless install still reads as "feature off". - */ -export const AIS_WATCHDOG_STATUSES = Object.freeze([ - 'idle', - 'missing-key', - 'unsupported', - 'connecting', - 'live', - 'stale', - 'reconnecting', - 'down', - 'auth-failed', -]); - /** Failure classes. Only 'transport' is allowed to walk the fast ladder. */ -export const AIS_FAILURE_KINDS = Object.freeze([ - 'transport', - 'auth', - 'rate-limit', -]); +const AIS_FAILURE_KINDS = Object.freeze(['transport', 'auth', 'rate-limit']); /** Statuses that must not flip back to a hopeful 'connecting' on a retry. */ const QUIET_TERMINAL = new Set(['down', 'auth-failed']); diff --git a/src/data/analystEngine.js b/src/data/analystEngine.js index 6a31c5a20..5175fec48 100644 --- a/src/data/analystEngine.js +++ b/src/data/analystEngine.js @@ -62,7 +62,7 @@ const COMMON_FIELDS = Object.freeze(['id', 'lat', 'lon']); /** Derived per-record field for layers with a time field (hours before now). */ const AGE_FIELD = 'ageHours'; /** Spatial scope kinds `query()` accepts. */ -export const ANALYST_SCOPE_KINDS = Object.freeze([ +const ANALYST_SCOPE_KINDS = Object.freeze([ 'view', 'region', 'radius', @@ -74,7 +74,7 @@ export const ANALYST_SCOPE_KINDS = Object.freeze([ * @param {string[]} layerKeys Queried layer ids. * @returns {string[]} Field names, common fields first. */ -export function analystFieldsFor(layerKeys) { +function analystFieldsFor(layerKeys) { const fields = new Set(COMMON_FIELDS); for (const key of layerKeys) { const layer = ANALYST_LAYERS[key]; @@ -104,44 +104,6 @@ export function haversineKm(lat1, lon1, lat2, lon2) { return greatCircleKm({ lat: lat1, lon: lon1 }, { lat: lat2, lon: lon2 }); } -/** - * One filter: {field, op:'gt'|'lt'|'gte'|'lte'|'eq'|'neq'|'contains', value}. - * With `time: true`, both sides compare as epoch ms (ISO strings accepted). - */ -export function applyFilter(records, filter, { time = false } = {}) { - const { field, op } = filter || {}; - if (!field || !op) return records; - const value = time ? toEpochMs(filter.value) : filter.value; - return records.filter((r) => { - const got = time ? toEpochMs(r[field]) : r[field]; - if (got === null || got === undefined || (time && !Number.isFinite(got))) - return false; - if (time && (op === 'eq' || op === 'neq')) - return (got === value) === (op === 'eq'); - switch (op) { - case 'gt': - return Number(got) > Number(value); - case 'gte': - return Number(got) >= Number(value); - case 'lt': - return Number(got) < Number(value); - case 'lte': - return Number(got) <= Number(value); - case 'eq': { - if (typeof got === 'boolean' || typeof value === 'boolean') - return Boolean(got) === Boolean(value); - return String(got).toLowerCase() === String(value).toLowerCase(); - } - case 'neq': - return String(got).toLowerCase() !== String(value).toLowerCase(); - case 'contains': - return String(got).toLowerCase().includes(String(value).toLowerCase()); - default: - return false; - } - }); -} - /** Scope records spatially. scope: {kind:'view'|'region'|'radius'|'anywhere', …}. */ export function applyScope(records, scope, resolved) { if (!scope || scope.kind === 'anywhere') return records; @@ -191,7 +153,7 @@ const COMMON_UNITS = Object.freeze({ * @param {string} field Field name. * @returns {'number'|'text'|'flag'|'time'|null} Field type. */ -export function analystFieldType(layerKeys, field) { +function analystFieldType(layerKeys, field) { if (!analystFieldsFor(layerKeys).includes(field)) return null; if (COMMON_TYPES[field]) return COMMON_TYPES[field]; for (const key of layerKeys) { diff --git a/src/data/bhoteKoshiLocator.js b/src/data/bhoteKoshiLocator.js index 79aab080f..369465680 100644 --- a/src/data/bhoteKoshiLocator.js +++ b/src/data/bhoteKoshiLocator.js @@ -14,7 +14,7 @@ import { BHOTE_KOSHI_FLOOD_PATH } from './bhoteKoshiFloodPath.js'; import { INCIDENT_OVERVIEW_PLACES } from './bhoteKoshiIncidentPlaces.js'; import { t, msg, getLocale } from '../i18n/index.js'; -export const BHOTE_KOSHI_LOCATOR_LAYER_ID = 'bhote-koshi-locator'; +const BHOTE_KOSHI_LOCATOR_LAYER_ID = 'bhote-koshi-locator'; export const BHOTE_KOSHI_LOCATOR_OVERLAY_SOURCE_ID = 'bhote-koshi-locator-callout'; export const NEPAL_CONTEXT_PRESENTATION = 'nepal-context'; @@ -1247,6 +1247,3 @@ export function createBhoteKoshiLocatorLayer({ }, }; } - -const bhoteKoshiLocatorLayer = createBhoteKoshiLocatorLayer(); -export default bhoteKoshiLocatorLayer; diff --git a/src/data/bikeshare.js b/src/data/bikeshare.js index 15c9dc802..6370c790e 100644 --- a/src/data/bikeshare.js +++ b/src/data/bikeshare.js @@ -23,4 +23,3 @@ export { BIKESHARE_SELECTED_OVERLAY_SOURCE_ID, BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS, } from '../layers/bikeshare/index.js'; -export default layer; diff --git a/src/data/cctvFootprint.js b/src/data/cctvFootprint.js index 42a3ad424..93f2b48ed 100644 --- a/src/data/cctvFootprint.js +++ b/src/data/cctvFootprint.js @@ -17,7 +17,7 @@ */ const EARTH_RADIUS_M = 6371000; -export const PLANE_VERT_ASPECT = 16 / 9; +const PLANE_VERT_ASPECT = 16 / 9; export const SUPPORT_KEYS = Object.freeze([ 'bl', 'bm', @@ -128,7 +128,7 @@ export function planeSupportPoints(pose) { * @param {number} capAltM @param {-1|0|1} row @param {{upVert:number}} dims * @returns {number} */ -export function supportAltitude(capAltM, row, dims) { +function supportAltitude(capAltM, row, dims) { return capAltM + row * dims.upVert; } diff --git a/src/data/cctvLod.js b/src/data/cctvLod.js index 44b2e8ba3..834dd964f 100644 --- a/src/data/cctvLod.js +++ b/src/data/cctvLod.js @@ -94,7 +94,7 @@ export function incumbentRankKm( export const CCTV_CARD_CENTER_WEIGHT = 0.5; /** Robust pool-distance percentile used to scale the screen-space term. */ -export const CCTV_CARD_SPREAD_PERCENTILE = 0.9; +const CCTV_CARD_SPREAD_PERCENTILE = 0.9; /** True only when both viewport dimensions are finite positive pixels. */ export function hasFiniteCctvViewport(viewW, viewH) { @@ -172,8 +172,8 @@ export function blendCenterRankKm( // viewport is bucketed into this grid and every occupied cell gets its best // candidate before global rank fills the rest. Owner-tunable together with // the budgets above. -export const CCTV_CARD_GRID_COLS = 5; -export const CCTV_CARD_GRID_ROWS = 4; +const CCTV_CARD_GRID_COLS = 5; +const CCTV_CARD_GRID_ROWS = 4; /** * Screen-space distribution pass (owner round 2, item C). Buckets the diff --git a/src/data/dataCredits.js b/src/data/dataCredits.js index 12dd7682a..aff87aeb7 100644 --- a/src/data/dataCredits.js +++ b/src/data/dataCredits.js @@ -25,11 +25,11 @@ import * as Cesium from 'cesium'; * follows DATA_SOURCES.md (live sources, then bundled snapshots). * @type {{ key: string, html: string }[]} */ -export const OSM_CREDIT = { +const OSM_CREDIT = { key: 'openstreetmap', html: 'Map and place data © OpenStreetMap contributors (ODbL)', }; -export const OPENMAPTILES_CREDIT = { +const OPENMAPTILES_CREDIT = { key: 'openfreemap', html: 'Vector tiles: OpenFreeMap © OpenMapTiles', }; diff --git a/src/data/detectionCohort.js b/src/data/detectionCohort.js index ed9f46c52..95cda50f0 100644 --- a/src/data/detectionCohort.js +++ b/src/data/detectionCohort.js @@ -18,7 +18,7 @@ export function stableIdentityHash(layerId, sourceId) { } /** Negative means a is a better deterministic contender than b. */ -export function compareCohortContenders(a, b) { +function compareCohortContenders(a, b) { const priorityDelta = (Number(b?._cohortPriority) || 0) - (Number(a?._cohortPriority) || 0); if (priorityDelta) return priorityDelta; diff --git a/src/data/detectionPolicy.js b/src/data/detectionPolicy.js index bf47785fd..0105d3beb 100644 --- a/src/data/detectionPolicy.js +++ b/src/data/detectionPolicy.js @@ -5,7 +5,6 @@ * module so density cannot drift into contradictory mode semantics again. */ -export const DENSITY_STOPS = Object.freeze([0, 25, 50, 75, 100]); export const ALLOCATION_ELASTIC = 'ELASTIC'; export const ALLOCATION_WEIGHTED = 'WEIGHTED'; export const ALLOCATION_STRATEGIES = Object.freeze([ @@ -118,7 +117,7 @@ export function detectionHorizontalSector(screenX, viewportWidth) { return 'front'; } -export const VIEW_SCALE_BUDGETS = Object.freeze({ +const VIEW_SCALE_BUDGETS = Object.freeze({ street: Object.freeze({ 0: 9, 25: 23, 50: 45, 75: 68, 100: 90 }), city: Object.freeze({ 0: 7, 25: 18, 50: 35, 75: 53, 100: 70 }), metro: Object.freeze({ 0: 6, 25: 14, 50: 28, 75: 41, 100: 55 }), diff --git a/src/data/directions.js b/src/data/directions.js index ef5565488..8a871bfbf 100644 --- a/src/data/directions.js +++ b/src/data/directions.js @@ -24,7 +24,7 @@ import * as input from './inputOwnership.js'; import { createDirectionsLayer } from '../layers/directions/index.js'; /** The shared scene owners every Directions instance runs on. */ -export const directionsServices = Object.freeze({ +const directionsServices = Object.freeze({ credits, render, sprites, @@ -49,15 +49,11 @@ export { DIRECTIONS_MODES, DIRECTIONS_POINTER_OWNER, DIRECTIONS_ROUTE_COLOR, - DIRECTIONS_STEP_OVERLAY_SOURCE_ID, - DIRECTIONS_STEP_OVERLAY_SOURCE_OPTIONS, - FLIGHT_PROGRESS_MS, POINTER_TOOL_EXITS, STEP_ANCHOR_DEADLINE_MS, STEP_ANCHOR_FAST_ATTEMPTS, STEP_ANCHOR_RETRY_MS, STEP_ANCHOR_SLOW_RETRY_MS, - createDirectionsLayer, createDirectionsStepOverlayEntry, directionsRequestUrl, directionsRowControls, diff --git a/src/data/earthquakes.js b/src/data/earthquakes.js index f003bf712..aac775d92 100644 --- a/src/data/earthquakes.js +++ b/src/data/earthquakes.js @@ -8,4 +8,3 @@ export function createEarthquakesLayer({ } = {}) { return createApplicationEarthquakes({ source, ...options }); } -export default createEarthquakesLayer(); diff --git a/src/data/geoFileImport.js b/src/data/geoFileImport.js index fc6a5e079..cf7037388 100644 --- a/src/data/geoFileImport.js +++ b/src/data/geoFileImport.js @@ -5,9 +5,9 @@ import { createImportedPins } from './geoImportPins.js'; import { msg } from '../i18n/marker.js'; /** Largest file accepted, in bytes. */ -export const MAX_IMPORT_BYTES = 50 * 1024 * 1024; +const MAX_IMPORT_BYTES = 50 * 1024 * 1024; /** Largest total a KMZ may inflate to — a zip bomb stops here. */ -export const MAX_KMZ_INFLATED_BYTES = 120 * 1024 * 1024; +const MAX_KMZ_INFLATED_BYTES = 120 * 1024 * 1024; /** Most files on the globe at once. */ export const MAX_IMPORTED_FILES = 12; /** Most entities one file may create before it is refused. */ @@ -15,9 +15,9 @@ export const MAX_IMPORT_ENTITIES = 20_000; /** Most vertices (points of every line, ring and marker) in one file. */ export const MAX_IMPORT_POSITIONS = 250_000; /** Most normalized style definitions and local image links in one KML/KMZ. */ -export const MAX_KML_LINKS = 2_000; +const MAX_KML_LINKS = 2_000; /** Longest plain-text name kept from a file's features. */ -export const MAX_IMPORT_NAME_LENGTH = 120; +const MAX_IMPORT_NAME_LENGTH = 120; /** What the file picker offers. */ export const GEO_FILE_ACCEPT = '.geojson,.json,.kml,.kmz,.gpx'; diff --git a/src/data/geoImportImages.js b/src/data/geoImportImages.js index 86f8e98e2..71133df26 100644 --- a/src/data/geoImportImages.js +++ b/src/data/geoImportImages.js @@ -1,5 +1,5 @@ // Inspect encoded headers before allowing the browser to decode local images. -export const MAX_IMPORT_IMAGE_PIXELS = 16 * 1024 * 1024; +const MAX_IMPORT_IMAGE_PIXELS = 16 * 1024 * 1024; const MAX_SIDE = 8192; /** Width and height from a supported raster header; no image is decoded. */ diff --git a/src/data/gtfsRealtime.js b/src/data/gtfsRealtime.js index 96d6b28a5..1fe888d4a 100644 --- a/src/data/gtfsRealtime.js +++ b/src/data/gtfsRealtime.js @@ -35,14 +35,14 @@ import { PbfReader } from 'pbf'; /** VehiclePosition.VehicleStopStatus enum → label. */ -export const VEHICLE_STOP_STATUS = Object.freeze({ +const VEHICLE_STOP_STATUS = Object.freeze({ 0: 'INCOMING_AT', 1: 'STOPPED_AT', 2: 'IN_TRANSIT_TO', }); /** VehiclePosition.OccupancyStatus enum → label. */ -export const OCCUPANCY_STATUS = Object.freeze({ +const OCCUPANCY_STATUS = Object.freeze({ 0: 'EMPTY', 1: 'MANY_SEATS_AVAILABLE', 2: 'FEW_SEATS_AVAILABLE', @@ -64,7 +64,6 @@ export const GTFS_MAX_ENTITIES = 50_000; export const GTFS_MAX_STRING_CHARS = 256; /** FeedHeader.Incrementality: only FULL_DATASET carries a complete snapshot. */ export const GTFS_INCREMENTALITY_FULL_DATASET = 0; -export const GTFS_INCREMENTALITY_DIFFERENTIAL = 1; /** * Read a length-delimited string, refusing one that is implausibly long. diff --git a/src/data/localGeojson.js b/src/data/localGeojson.js index 8871cfe39..61f6c3abf 100644 --- a/src/data/localGeojson.js +++ b/src/data/localGeojson.js @@ -1,5 +1,4 @@ import { localGeoJsonServices } from '../app/localGeojsonServices.js'; -export { localGeoJsonServices } from '../app/localGeojsonServices.js'; import { createLocalGeoJsonLayer as createLayer, createLocalInfrastructureOverlayPublisher as createPublisher, diff --git a/src/data/militaryNames.js b/src/data/militaryNames.js index b26ab04e6..d4b4b8846 100644 --- a/src/data/militaryNames.js +++ b/src/data/militaryNames.js @@ -7,7 +7,7 @@ import { createRetryableLoader } from './retryableLoad.js'; export const MILITARY_POINT_CAP = 512; export const MILITARY_LABEL_CAP = 24; -export const MILITARY_NAMES_TIMEOUT_MS = 10_000; +const MILITARY_NAMES_TIMEOUT_MS = 10_000; /** Share a bounded acquisition; failed assets become retryable after cooldown. */ export function createMilitaryNamesLoader({ diff --git a/src/data/radioCountry.js b/src/data/radioCountry.js index 55d9b36a5..1f77d8e98 100644 --- a/src/data/radioCountry.js +++ b/src/data/radioCountry.js @@ -117,8 +117,3 @@ export function normalizeRadioCountryInput(value) { name: canonicalCountryName(code), }); } - -/** Return whether a value is a recognized ISO 3166-1 alpha-2 code. */ -export function isRadioCountryCode(value) { - return typeof value === 'string' && ISO_ALPHA_2_SET.has(value.toUpperCase()); -} diff --git a/src/data/telegeographySubmarineCables.js b/src/data/telegeographySubmarineCables.js index 6aaba700f..6bee7c56d 100644 --- a/src/data/telegeographySubmarineCables.js +++ b/src/data/telegeographySubmarineCables.js @@ -28,4 +28,3 @@ export { createCableReferenceSweepGate, updateCableReferenceStem, } from '../layers/submarineCables/index.js'; -export default createTeleGeographySubmarineCableLayer(); diff --git a/src/data/wind.js b/src/data/wind.js index 38bc10688..59b9aaf5d 100644 --- a/src/data/wind.js +++ b/src/data/wind.js @@ -1,5 +1,4 @@ import { createSourceSlot } from '../sources/sourceSlot.js'; -import { createWindLayer as createLayer } from '../layers/wind/index.js'; import { createWindSource } from '../layers/wind/source.js'; const sourceSlot = createSourceSlot( @@ -8,10 +7,3 @@ const sourceSlot = createSourceSlot( 'Wind source', ); export const configureWindSource = sourceSlot.configure; - -/** Create a wind layer using the configured application source by default. */ -export function createWindLayer(options = {}) { - return createLayer({ ...options, feed: options.feed ?? sourceSlot.source }); -} - -export default createWindLayer(); diff --git a/src/i18n/index.js b/src/i18n/index.js index d5698aa5b..48b63b2f6 100644 --- a/src/i18n/index.js +++ b/src/i18n/index.js @@ -210,7 +210,7 @@ export function formatRelativeTime(value, unit = 'second', options) { } /** Collapse markup whitespace so a message reads the same in HTML and JS. */ -export function normalizeMarkupText(text) { +function normalizeMarkupText(text) { return String(text ?? '') .replace(/\s+/g, ' ') .trim(); diff --git a/src/layers/alpr/records.js b/src/layers/alpr/records.js index 0d751f12d..10cd88062 100644 --- a/src/layers/alpr/records.js +++ b/src/layers/alpr/records.js @@ -1,11 +1,11 @@ import { QUERY_LIMIT, QUERY_SNAP_DEGREES } from './policy.js'; -export function textTag(value) { +function textTag(value) { const t = String(value ?? '').trim(); return t || null; } -export function numTag(value) { +function numTag(value) { // `Number('')` and `Number(' ')` are 0, not NaN — a blank or whitespace-only // tag value must read as missing, never as a due-north bearing. if (value == null) return null; @@ -102,7 +102,7 @@ export function normalizeAlprNode(el) { }; } -export function normalizeDirection(value) { +function normalizeDirection(value) { const degrees = numTag(value); return degrees != null && degrees >= 0 && degrees <= 360 ? degrees % 360 diff --git a/src/layers/alpr/tileRecords.js b/src/layers/alpr/tileRecords.js index 5db9192b3..0368f53fb 100644 --- a/src/layers/alpr/tileRecords.js +++ b/src/layers/alpr/tileRecords.js @@ -3,7 +3,7 @@ import { VectorTile } from '@mapbox/vector-tile'; import { normalizeAlprNode } from './records.js'; /** Map extract attributes without treating an OSM edit date as a field verification. */ -export function normalizeAlprTileFeature(feature) { +function normalizeAlprTileFeature(feature) { const p = feature?.properties || {}; if (feature?.geometry?.type !== 'Point' || p.osmType !== 'node') return null; const [lon, lat] = feature.geometry.coordinates; diff --git a/src/layers/awareness/policy.js b/src/layers/awareness/policy.js index eda5a46e6..22cc8eb98 100644 --- a/src/layers/awareness/policy.js +++ b/src/layers/awareness/policy.js @@ -1,9 +1,6 @@ export const AIRCRAFT_DEPENDENCIES = ['flights', 'military']; -export const DEFERRED_DEPENDENCIES = [ - 'ais-live-vessels', - 'military-installations', -]; +const DEFERRED_DEPENDENCIES = ['ais-live-vessels', 'military-installations']; export const DEPENDENCIES = [ ...AIRCRAFT_DEPENDENCIES, diff --git a/src/layers/bikeshare/policy.js b/src/layers/bikeshare/policy.js index 14f1edf6e..1b9c1579f 100644 --- a/src/layers/bikeshare/policy.js +++ b/src/layers/bikeshare/policy.js @@ -11,7 +11,7 @@ export const BIKESHARE_SELECTED_OVERLAY_SOURCE_OPTIONS = Object.freeze({ // --- Activation / display thresholds --- /** Altitude (m) at which bikeshare layer becomes eligible for display. */ -export const ACTIVATION_ALTITUDE_M = 50000; +const ACTIVATION_ALTITUDE_M = 50000; /** Hysteresis enter threshold — layer activates when camera drops below this. */ diff --git a/src/layers/cctv/policy.js b/src/layers/cctv/policy.js index 96d7985f1..bc369c02a 100644 --- a/src/layers/cctv/policy.js +++ b/src/layers/cctv/policy.js @@ -1,10 +1,4 @@ -export { - FRAME_ENDPOINT, - SOURCE_ENDPOINT, - HEALTH_ENDPOINT, - MEDIA_ENDPOINT, - ACTIVE_FRAME_REFRESH_MS, -} from './sourcePolicy.js'; +export { ACTIVE_FRAME_REFRESH_MS } from './sourcePolicy.js'; import * as Cesium from 'cesium'; import { CCTV_CARD_FETCH_BURST_SPACING_MS } from '../../data/cctvCards.js'; import { CCTV_AMBIENT_CARD_MAX } from '../../data/cctvLod.js'; @@ -90,9 +84,6 @@ export const CCTV_CALIBRATION_STORAGE_KEY_V2 = export const PROJECTION_TEXTURE_SWAP_MS = 1000; -export const PROJECTION_VERT_ASPECT = - PROJECTION_CANVAS_WIDTH / PROJECTION_CANVAS_HEIGHT; - // V2 frustum geometry (design §2a/§6): the far-cap center + corners never sink // below groundAlt + this clearance, so a fabricated pitch (-24°) cannot bury // the monitor plane in the 3D tiles. Exported for the unit suite. diff --git a/src/layers/cctv/sourcePolicy.js b/src/layers/cctv/sourcePolicy.js index 5ce0cd024..3341a4ce9 100644 --- a/src/layers/cctv/sourcePolicy.js +++ b/src/layers/cctv/sourcePolicy.js @@ -1,9 +1,5 @@ export const FRAME_ENDPOINT = '/api/cctv/frame'; -export const SOURCE_ENDPOINT = '/api/cctv/sources'; - -export const HEALTH_ENDPOINT = '/api/cctv/health'; - export const MEDIA_ENDPOINT = '/api/cctv/media'; export const ACTIVE_FRAME_REFRESH_MS = 10000; diff --git a/src/layers/cyclones/labels.js b/src/layers/cyclones/labels.js index e95b5a28c..cb418865f 100644 --- a/src/layers/cyclones/labels.js +++ b/src/layers/cyclones/labels.js @@ -64,7 +64,7 @@ const SHARED_CARD = Object.freeze({ * @param {boolean} selected * @returns {object} */ -export function cycloneStormEntry(storm, selected) { +function cycloneStormEntry(storm, selected) { return { ...SHARED_CARD, ...markerClearance( @@ -92,7 +92,7 @@ export function cycloneStormEntry(storm, selected) { * @param {{tauHours:number,position:object}} point * @returns {object} */ -export function cycloneLeadEntry(stormId, point) { +function cycloneLeadEntry(stormId, point) { return { ...SHARED_CARD, ...markerClearance(CYCLONE_MARKER_PX.forecast), diff --git a/src/layers/flights/policy.js b/src/layers/flights/policy.js index 2c015e6c9..2ecb0a8a4 100644 --- a/src/layers/flights/policy.js +++ b/src/layers/flights/policy.js @@ -1,11 +1,3 @@ -export { - GROUND_FLOOR_CLAMP_RADIUS_KM, - GROUND_FLOOR_WARM_MAX_ALT_M, - POSITION_HISTORY_LIMIT, - LANDED_MISSING_POLL_LIMIT, - MISSING_POLL_LIMIT, - ERROR_BACKOFF_INTERVAL, -} from './recordPolicy.js'; import * as Cesium from 'cesium'; import { EMERGENCY_TINT_CSS } from '../../data/aircraftEmergency.js'; diff --git a/src/layers/installations/policy.js b/src/layers/installations/policy.js index fae021cf3..92da9dd7b 100644 --- a/src/layers/installations/policy.js +++ b/src/layers/installations/policy.js @@ -2,8 +2,6 @@ export const LAYER_ID = 'military-installations'; export const REQUEST_DEBOUNCE_MS = 180; -export const MAX_VIEWPORT_DEGREES = 10; - export const MAX_RENDERED = 700; export const GOOGLE_MILITARY_PLACE_TYPES = new Set(['military_base']); diff --git a/src/layers/localAdsb/feeds.js b/src/layers/localAdsb/feeds.js index 4bb4bced7..5899729af 100644 --- a/src/layers/localAdsb/feeds.js +++ b/src/layers/localAdsb/feeds.js @@ -5,12 +5,12 @@ import { /** Same-origin route served by `server/providers/local-receivers.js`. */ export const LOCAL_RECEIVERS_URL = '/api/local-receivers/aircraft'; -export const LOCAL_RECEIVER_POLL_MS = 1_000; +const LOCAL_RECEIVER_POLL_MS = 1_000; /** * Browser-side deadline for one route request, body included. The server * bounds each upstream read at 2 s; this bounds the browser-to-server leg. */ -export const LOCAL_RECEIVER_REQUEST_TIMEOUT_MS = 5_000; +const LOCAL_RECEIVER_REQUEST_TIMEOUT_MS = 5_000; function abortError() { const error = new Error('Local receiver request aborted'); diff --git a/src/layers/localAdsb/index.js b/src/layers/localAdsb/index.js index fcf54b5d5..4db1e2852 100644 --- a/src/layers/localAdsb/index.js +++ b/src/layers/localAdsb/index.js @@ -36,18 +36,8 @@ import { } from './policy.js'; import { localAdsbStatus } from './status.js'; -export { - LAYER_ID as LOCAL_ADSB_LAYER_ID, - HEARD_BY_RECEIVER, -} from './policy.js'; -export { - localAdsbCardModel, - localAdsbClassLine, - localAdsbIsUat, - localAdsbReceiverLine, - localAdsbTitle, -} from './card.js'; -export { localAdsbClass } from './enrichment.js'; +export { HEARD_BY_RECEIVER } from './policy.js'; +export { localAdsbCardModel, localAdsbReceiverLine } from './card.js'; export { localAdsbStatus } from './status.js'; const FEET_TO_METERS = 0.3048; diff --git a/src/layers/localAdsb/motion.js b/src/layers/localAdsb/motion.js index 279bef6d3..084f3aa7e 100644 --- a/src/layers/localAdsb/motion.js +++ b/src/layers/localAdsb/motion.js @@ -26,7 +26,7 @@ import { export const LOCAL_ADSB_HISTORY_MS = 10 * 60_000; export const LOCAL_ADSB_HISTORY_POINTS = 600; /** A display correction larger than this snaps instead of sliding. */ -export const LOCAL_ADSB_SNAP_M = 3_000; +const LOCAL_ADSB_SNAP_M = 3_000; /** Chord course uses the newest fix at least this much older than the anchor. */ const CHORD_BASELINE_MS = 2_000; /** Turn rate is estimated over this window of heard fixes. */ diff --git a/src/layers/localAdsb/status.js b/src/layers/localAdsb/status.js index d264c5e15..2e084cffb 100644 --- a/src/layers/localAdsb/status.js +++ b/src/layers/localAdsb/status.js @@ -24,7 +24,7 @@ function plural(count, one, many) { * @param {object[]} feeds Feed statuses from the route. * @returns {string} */ -export function describeFeedProblems(feeds) { +function describeFeedProblems(feeds) { const byStatus = new Map(); for (const feed of feeds) { if (feed?.status === 'live') continue; @@ -41,7 +41,7 @@ export function describeFeedProblems(feeds) { } /** Whether the server reported at least one configured feed. */ -export function feedsConfigured(feedState) { +function feedsConfigured(feedState) { return Boolean( feedState?.configured === true && Array.isArray(feedState.feeds) && diff --git a/src/layers/military/policy.js b/src/layers/military/policy.js index f375e5419..bb1d89127 100644 --- a/src/layers/military/policy.js +++ b/src/layers/military/policy.js @@ -1,10 +1,3 @@ -export { - GROUND_FLOOR_WARM_MAX_ALT_M, - POSITION_HISTORY_LIMIT, - LANDED_MISSING_POLL_LIMIT, - MISSING_POLL_LIMIT, - ERROR_BACKOFF_INTERVAL, -} from './recordPolicy.js'; import * as Cesium from 'cesium'; import { EMERGENCY_TINT_CSS } from '../../data/aircraftEmergency.js'; diff --git a/src/layers/radio/policy.js b/src/layers/radio/policy.js index 85777ed74..0d4ad174a 100644 --- a/src/layers/radio/policy.js +++ b/src/layers/radio/policy.js @@ -10,7 +10,7 @@ export const MARKER_LIFT_M = 2.5; export const SELECTED_LIFT_M = 5; -export const RADIO_PICK_TOLERANCE_PX = 8; +const RADIO_PICK_TOLERANCE_PX = 8; export const RADIO_TUNER_DIRECTORY_LIMIT = 750; diff --git a/src/layers/recentImagery/model.js b/src/layers/recentImagery/model.js index 406dc3e19..3fbcf1123 100644 --- a/src/layers/recentImagery/model.js +++ b/src/layers/recentImagery/model.js @@ -300,7 +300,7 @@ export function hhmm(iso) { } /** Candidate key: `PRODUCT:YYYY-MM-DD`. */ -export function candidateKey(product, day) { +function candidateKey(product, day) { return `${product}:${day}`; } diff --git a/src/layers/satellites/policy.js b/src/layers/satellites/policy.js index 656374fc6..e7ea5456d 100644 --- a/src/layers/satellites/policy.js +++ b/src/layers/satellites/policy.js @@ -94,9 +94,9 @@ export const TRACK_VIEW_FROM_HIGH_SCALE = 4; * attribute, so classification costs nothing per frame. */ -export const POINT_OUTLINE = Cesium.Color.WHITE.withAlpha(0.3); +const POINT_OUTLINE = Cesium.Color.WHITE.withAlpha(0.3); -export const _classColor = (group) => +const _classColor = (group) => Cesium.Color.fromCssColorString(satelliteClassColor(group)); export const POINT_STYLES = { diff --git a/src/layers/streetLevel/photoViewer.js b/src/layers/streetLevel/photoViewer.js index c083879db..0ca4e3eb2 100644 --- a/src/layers/streetLevel/photoViewer.js +++ b/src/layers/streetLevel/photoViewer.js @@ -17,18 +17,18 @@ const EXPANDED_SELECTORS = [ ]; /** A photo download (tens of kilobytes to a few megabytes). */ -export const IMAGE_TIMEOUT_MS = 15_000; +const IMAGE_TIMEOUT_MS = 15_000; /** A picture file or decoded side past these is refused (a photo is a few MB). */ export const MAX_IMAGE_BYTES = 48 * 1024 * 1024; export const MAX_IMAGE_SIDE = 16_384; /** 64 MP: 256 MB once decoded to RGBA. A 16,384 px square is four times that. */ -export const MAX_IMAGE_PIXELS = 8_192 * 8_192; +const MAX_IMAGE_PIXELS = 8_192 * 8_192; /** Where in a file to look for its dimensions (a JPEG's EXIF comes first). */ const HEADER_BYTES = 256 * 1024; /** Whether a picture this size is past what the viewer decodes. */ -export function tooManyPixels(width, height) { +function tooManyPixels(width, height) { return ( width > MAX_IMAGE_SIDE || height > MAX_IMAGE_SIDE || @@ -37,7 +37,7 @@ export function tooManyPixels(width, height) { } /** Panorama texture widths: enough for a docked viewer, and for an expanded one. */ -export const PANO_MID_SIZE = Object.freeze([2048, 1024]); +const PANO_MID_SIZE = Object.freeze([2048, 1024]); export const PANO_FULL_SIZE = Object.freeze([4096, 2048]); /** @@ -155,7 +155,7 @@ async function loadBitmap(url, timeoutMs, resize, failure, signal) { * A picture refused for its size (`tooLarge`): the viewer will not load * that file another way, an having no size limit at all. */ -export function oversized(failure) { +function oversized(failure) { return Object.assign(new Error(failure), { tooLarge: true }); } diff --git a/src/layers/streetLevel/providers/kartaview/policy.js b/src/layers/streetLevel/providers/kartaview/policy.js index 939e0ec1c..b4533c1b5 100644 --- a/src/layers/streetLevel/providers/kartaview/policy.js +++ b/src/layers/streetLevel/providers/kartaview/policy.js @@ -14,7 +14,7 @@ export const KARTAVIEW_LABEL = 'KARTAVIEW'; */ export const KARTAVIEW_API_HOST = 'https://api.kartaview.org'; /** KartaView's image proxy: resizes a photo given its base64url address. */ -export const KARTAVIEW_CDN_HOST = 'https://cdn.kartaview.org'; +const KARTAVIEW_CDN_HOST = 'https://cdn.kartaview.org'; /** Stable id prefixes for picked primitives; every one starts with `kv:`. */ export const PICK_PREFIX = Object.freeze({ @@ -140,8 +140,6 @@ export const MASK_SLOW_MESSAGE = msg( export const FILTERED_OUT_MESSAGE = msg( 'None of KartaView’s photos here pass the imagery filter', ); -/** Under the SINCE slider while KartaView is lit. */ -export const SINCE_NOTE = '(by upload date)'; /** The UTC calendar day of `ms`, as KartaView's startDate reads it. */ function utcDay(ms) { diff --git a/src/layers/streetLevel/providers/panoramax/index.js b/src/layers/streetLevel/providers/panoramax/index.js index 4dfa023ef..e855c9c72 100644 --- a/src/layers/streetLevel/providers/panoramax/index.js +++ b/src/layers/streetLevel/providers/panoramax/index.js @@ -41,8 +41,6 @@ import { normalizeItem, } from './policy.js'; -export { PANORAMAX_PROVIDER_ID } from './policy.js'; - /** * A tile's bytes decoded by `decode`; a body that is not a vector tile * (an HTML error page, a cut-off answer) reads as such, not as a parser's diff --git a/src/layers/streetLevel/providers/panoramax/policy.js b/src/layers/streetLevel/providers/panoramax/policy.js index e9a51c10e..670772c6a 100644 --- a/src/layers/streetLevel/providers/panoramax/policy.js +++ b/src/layers/streetLevel/providers/panoramax/policy.js @@ -15,7 +15,7 @@ export const PANORAMAX_LABEL = 'PANORAMAX'; */ export const PANORAMAX_API = 'https://api.panoramax.xyz/api'; /** The meta-catalogue's web viewer, where the caption link goes. */ -export const PANORAMAX_VIEWER = 'https://api.panoramax.xyz/'; +const PANORAMAX_VIEWER = 'https://api.panoramax.xyz/'; /** Stable id prefixes for picked primitives; every one starts with `pnx:`. */ export const PICK_PREFIX = Object.freeze({ diff --git a/src/layers/streetLevel/registry.js b/src/layers/streetLevel/registry.js index 6770cd133..5e51929a6 100644 --- a/src/layers/streetLevel/registry.js +++ b/src/layers/streetLevel/registry.js @@ -1,6 +1,6 @@ import { encodeIdSwitches } from '../../data/idSwitches.js'; -export const IMAGERY_FILTERS = Object.freeze(['pano', 'since']); +const IMAGERY_FILTERS = Object.freeze(['pano', 'since']); export function filtersOf(provider) { return provider?.filters ?? IMAGERY_FILTERS; diff --git a/src/layers/streetLevel/sequenceCones.js b/src/layers/streetLevel/sequenceCones.js index b18f2cc57..b6c068d49 100644 --- a/src/layers/streetLevel/sequenceCones.js +++ b/src/layers/streetLevel/sequenceCones.js @@ -5,8 +5,8 @@ import { createHorizonCull, metresBetween } from './view.js'; import { msg } from '../../i18n/index.js'; /** Cones closer than this to the previous kept one are dropped. */ -export const IMAGE_CONE_MIN_SPACING_M = 3; -export const IMAGE_CONE_SIZE_PX = 26; +const IMAGE_CONE_MIN_SPACING_M = 3; +const IMAGE_CONE_SIZE_PX = 26; /** * @typedef {object} ConeImage One image of a sequence, as the cones draw it. diff --git a/src/layers/streetLevel/tileCache.js b/src/layers/streetLevel/tileCache.js index d6c0a91e5..6ee0897ef 100644 --- a/src/layers/streetLevel/tileCache.js +++ b/src/layers/streetLevel/tileCache.js @@ -1,6 +1,6 @@ /** Decoded coverage tiles kept, and for how long. */ -export const TILE_CACHE_SIZE = 24; -export const TILE_CACHE_TTL_MS = 10 * 60_000; +const TILE_CACHE_SIZE = 24; +const TILE_CACHE_TTL_MS = 10 * 60_000; /** Least-recently-used map: reading an entry renews it. */ export function createLru(limit) { diff --git a/src/layers/streetLevel/uiState.js b/src/layers/streetLevel/uiState.js index 577bb8ae1..04c6ed6c4 100644 --- a/src/layers/streetLevel/uiState.js +++ b/src/layers/streetLevel/uiState.js @@ -64,7 +64,7 @@ export function keyAlternatives(providers) { * The filter controls a switched-on provider honours (both when none is on). * @param {Array} providers */ -export function filterControls(providers) { +function filterControls(providers) { const active = providers.filter((p) => p.on); const honoured = (key) => active.length === 0 || @@ -78,7 +78,7 @@ export function filterControls(providers) { * note needs no provider name. * @param {Array} providers */ -export function sinceNote(providers) { +function sinceNote(providers) { return providers.some((p) => p.on && p.sinceBasis === 'upload') ? t('(by upload date)') : null; diff --git a/src/layers/submarineCables/geometry.js b/src/layers/submarineCables/geometry.js index 08366dba4..b4b434710 100644 --- a/src/layers/submarineCables/geometry.js +++ b/src/layers/submarineCables/geometry.js @@ -33,7 +33,7 @@ export function featureReference(feature) { }; } -export function coordsFromProperty(value) { +function coordsFromProperty(value) { if (!Array.isArray(value) || value.length < 2) return null; const lon = Number(value[0]); const lat = Number(value[1]); @@ -41,7 +41,7 @@ export function coordsFromProperty(value) { return [lon, lat]; } -export function coordsFromPoint(value) { +function coordsFromPoint(value) { if (!Array.isArray(value) || value.length < 2) return null; const lon = Number(value[0]); const lat = Number(value[1]); @@ -49,7 +49,7 @@ export function coordsFromPoint(value) { return [lon, lat]; } -export function collectLonLat(value, out) { +function collectLonLat(value, out) { if (!Array.isArray(value)) return; if (typeof value[0] === 'number' && typeof value[1] === 'number') { const lon = Number(value[0]); diff --git a/src/layers/traffic/footprint.js b/src/layers/traffic/footprint.js index d75947941..9a241d997 100644 --- a/src/layers/traffic/footprint.js +++ b/src/layers/traffic/footprint.js @@ -1,9 +1,9 @@ import * as Cesium from 'cesium'; import { tilesForBounds, tileToBBox } from '../../data/tomtomTiles.js'; -export const RETICLE_RANGE_KM = 10; -export const RETICLE_TILE_CAP = 16; -export const tileKey = (tile) => `${tile.z}/${tile.x}/${tile.y}`; +const RETICLE_RANGE_KM = 10; +const RETICLE_TILE_CAP = 16; +const tileKey = (tile) => `${tile.z}/${tile.x}/${tile.y}`; const cross = (a, b, c) => (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); diff --git a/src/layers/traffic/policy.js b/src/layers/traffic/policy.js index 81b01f299..c20b15e41 100644 --- a/src/layers/traffic/policy.js +++ b/src/layers/traffic/policy.js @@ -175,13 +175,3 @@ export const STYLED_MIN_BASE_PX = 5; /** @const {number} Maximum tile cache entries before LRU eviction */ export const TILE_CACHE_MAX_ENTRIES = 64; - -/** - * Milliseconds the first dot paint will wait for flow data. Cached flow - * settles within this window (decode cache, 120 s TTL) and renders fully - * colored; a cold tile fetch loses the race, dots paint immediately in - * white, and recolorDotsInPlace applies the colors when flow arrives — - * field-test round 1's "takes forever to load" was the sequential wait. - */ - -export const FLOW_RENDER_RACE_MS = 250; diff --git a/src/layers/transit/policy.js b/src/layers/transit/policy.js index 2fb5101ce..010a2efc2 100644 --- a/src/layers/transit/policy.js +++ b/src/layers/transit/policy.js @@ -99,7 +99,7 @@ export const VISIBILITY_REFRESH_MS = 250; * on screen. Generous on purpose: a vehicle that crosses the edge mid-glide * should already have been moving, not pop into motion at the border. */ -export const VIEW_MARGIN_RATIO = 0.6; +const VIEW_MARGIN_RATIO = 0.6; /** * Whether a vehicle is close enough to the camera's look-at area to be worth @@ -516,10 +516,10 @@ export function createTransitSelectedOverlayEntry(key, position, copy, mode) { } /** Modes that ordinarily run below the street, drawn on the surface above themselves. */ -export const UNDERGROUND_MODES = new Set(['subway']); +const UNDERGROUND_MODES = new Set(['subway']); /** The mode in plain words, for a card a person reads rather than scans. */ -export const TRANSIT_MODE_WORD = Object.freeze({ +const TRANSIT_MODE_WORD = Object.freeze({ bus: msg('Bus'), tram: msg('Tram'), subway: msg('Subway'), @@ -529,7 +529,7 @@ export const TRANSIT_MODE_WORD = Object.freeze({ }); /** Short, fixed-width mode word for the detection card's class field. */ -export const TRANSIT_MODE_ABBR = Object.freeze({ +const TRANSIT_MODE_ABBR = Object.freeze({ bus: 'BUS', tram: 'TRAM', subway: 'METRO', @@ -588,7 +588,7 @@ export function transitDetectionId(record, feed = null) { } /** Longest route text the single-line detection label can carry. */ -export const DETECTION_ID_MAX_CHARS = 14; +const DETECTION_ID_MAX_CHARS = 14; /** * Cut long route text with an ellipsis, so a reader can see it was cut. @@ -600,7 +600,7 @@ export const DETECTION_ID_MAX_CHARS = 14; * @param {string} text * @returns {string} Uppercase, at most DETECTION_ID_MAX_CHARS characters. */ -export function elideRouteText(text) { +function elideRouteText(text) { const value = String(text || '').toUpperCase(); if (value.length <= DETECTION_ID_MAX_CHARS) return value; return `${value.slice(0, DETECTION_ID_MAX_CHARS - 1)}\u2026`; @@ -659,6 +659,6 @@ export function transitDetectionMetric( * @param {number} [nowMs] * @returns {string} */ -export function transitVehicleState(entry, nowMs = Date.now()) { +function transitVehicleState(entry, nowMs = Date.now()) { return displayMotion(entry, nowMs).word; } diff --git a/src/layers/vessels/policy.js b/src/layers/vessels/policy.js index 75e5cdd67..9ab06fbb8 100644 --- a/src/layers/vessels/policy.js +++ b/src/layers/vessels/policy.js @@ -1,8 +1,3 @@ -export { - PARTIAL_RETENTION_MS, - SELECTED_PIN_REFRESHES, - AIS_FIRST_CONNECT_LABEL, -} from './recordPolicy.js'; import { VESSEL_LABEL_GRID_PX } from '../../data/vesselLabels.js'; import { msg } from '../../i18n/index.js'; diff --git a/src/layers/weather/source.js b/src/layers/weather/source.js index cf004476f..5386bfad7 100644 --- a/src/layers/weather/source.js +++ b/src/layers/weather/source.js @@ -4,7 +4,7 @@ import { readResponseJsonCapped } from '../../sources/httpBody.js'; // the weather layer translates them where it shows them (t / translateKnown). const msg = (text) => text; -export const WEATHER_PRODUCTS = Object.freeze([ +const WEATHER_PRODUCTS = Object.freeze([ 'radar', 'clouds', 'clouds-regional', diff --git a/src/layers/wind/gpuRendering.js b/src/layers/wind/gpuRendering.js index 27d71dcd5..0873fa63b 100644 --- a/src/layers/wind/gpuRendering.js +++ b/src/layers/wind/gpuRendering.js @@ -6,8 +6,8 @@ import { WIND_NARROW_PATH_LIMIT, } from './streamlines.js'; -export const WIND_FADE_LOW_METERS = 15000; -export const WIND_FADE_HIGH_METERS = 60000; +const WIND_FADE_LOW_METERS = 15000; +const WIND_FADE_HIGH_METERS = 60000; // Original material: a persistent fine curve plus a moving, tapered highlight. // The integer part of s identifies a path; its fractional part follows the wind. diff --git a/src/locationStatus.js b/src/locationStatus.js index 1d90582c9..2c9e406f3 100644 --- a/src/locationStatus.js +++ b/src/locationStatus.js @@ -64,5 +64,3 @@ export function locationMiniStatus({ return { ...EMPTY }; } - -export default locationMiniStatus; diff --git a/src/maps/imageryComparison.js b/src/maps/imageryComparison.js index 5586bd86e..cf7d266ae 100644 --- a/src/maps/imageryComparison.js +++ b/src/maps/imageryComparison.js @@ -1,5 +1,5 @@ /** Basemap that before/after imagery comparisons are read against. */ -export const IMAGERY_COMPARISON_STACK_ID = 'esri-imagery'; +const IMAGERY_COMPARISON_STACK_ID = 'esri-imagery'; const SWITCH_POLICIES = new Set(['preserve', 'esri', 'esri-if-photoreal']); const leases = new WeakMap(); diff --git a/src/maps/terrainRetry.js b/src/maps/terrainRetry.js index 26fc95576..6cf0fe2af 100644 --- a/src/maps/terrainRetry.js +++ b/src/maps/terrainRetry.js @@ -24,7 +24,7 @@ */ export const TERRAIN_RETRY_ATTEMPTS = 3; -export const TERRAIN_RETRY_STATUSES = Object.freeze([429, 502, 503, 504]); +const TERRAIN_RETRY_STATUSES = Object.freeze([429, 502, 503, 504]); export const TERRAIN_RETRY_BASE_MS = 750; export const TERRAIN_RETRY_MAX_MS = 15_000; export const TERRAIN_RETRY_SPREAD_MS = 1_500; diff --git a/src/scenes/recipes.js b/src/scenes/recipes.js index 191bb1ac1..ba3940a57 100644 --- a/src/scenes/recipes.js +++ b/src/scenes/recipes.js @@ -994,7 +994,7 @@ const PUBLIC_SCENE_RECIPES = [ ]; /** Build the recipe list without mutating stored user-authored projects. */ -export function createSceneRecipes({ localDemoRecipes = EVENT_RECIPES } = {}) { +function createSceneRecipes({ localDemoRecipes = EVENT_RECIPES } = {}) { return [...localDemoRecipes, ...PUBLIC_SCENE_RECIPES]; } @@ -1004,7 +1004,7 @@ export const SCENE_APPEND_RECIPES = BHOTE_KOSHI_NEPAL_APPEND_RECIPE ? [BHOTE_KOSHI_NEPAL_APPEND_RECIPE] : []; -export function getSceneRecipeById(id) { +function getSceneRecipeById(id) { return SCENE_RECIPES.find((recipe) => recipe.id === id) || null; } diff --git a/src/sdr/controller.js b/src/sdr/controller.js index 50fec4a38..019b768a1 100644 --- a/src/sdr/controller.js +++ b/src/sdr/controller.js @@ -38,12 +38,12 @@ const READ_PIPELINE_DEPTH = 4; const RATE_WINDOW_MS = 1_000; // A bulk read that has not completed in this long is treated as a stalled // USB transfer: the session is torn down instead of waiting forever. -export const SDR_READ_STALL_MS = 5_000; +const SDR_READ_STALL_MS = 5_000; // Decoder admission. Every block sent to the worker is numbered and must be // acknowledged; past these bounds the receiver stops with an overload error // rather than queueing raw IQ the decoder cannot keep up with. Eight 16 ms // blocks is 128 ms of input, about forty times the measured decode cost. -export const SDR_WORKER_ADMISSION = Object.freeze({ +const SDR_WORKER_ADMISSION = Object.freeze({ maxBlocks: 8, maxBytes: 8 * 65_536, maxAgeMs: 1_000, @@ -66,7 +66,7 @@ const DECODER_WINDOW_SAMPLES = 256; // raw USB device is closed directly, which aborts any pending transfer. The // raw close is bounded by the same deadline, so a teardown settles within // twice this even when `USBDevice.close()` itself never settles. -export const SDR_CLOSE_TIMEOUT_MS = 2_000; +const SDR_CLOSE_TIMEOUT_MS = 2_000; /** Tuning presets per receiver mode; gain is a separate per-mode setting. */ export const SDR_RECEIVER_PRESETS = Object.freeze({ @@ -1912,9 +1912,3 @@ function formatMhz(hz) { function positiveModulo(value, divisor) { return ((value % divisor) + divisor) % divisor; } - -export const SDR_FM_BAND = Object.freeze({ - minHz: FM_MIN_HZ, - maxHz: FM_MAX_HZ, -}); -export const SDR_ADSB_FREQUENCY_HZ = ADSB_FREQUENCY_HZ; diff --git a/src/sdr/gain.js b/src/sdr/gain.js index 761edf013..94d525fe2 100644 --- a/src/sdr/gain.js +++ b/src/sdr/gain.js @@ -10,7 +10,7 @@ * about 1 msg/s and 28.0 dB about 9 msg/s on the same antenna. */ -export const SDR_GAIN_AUTO = 'auto'; +const SDR_GAIN_AUTO = 'auto'; /** R820T tuner gain steps in dB, as listed by librtlsdr. */ export const R820T_GAIN_STEPS_DB = Object.freeze([ diff --git a/src/sky/stars.js b/src/sky/stars.js index 9fd5aa698..f4139c3c5 100644 --- a/src/sky/stars.js +++ b/src/sky/stars.js @@ -18,7 +18,7 @@ export const STAR_MAX_SIGMA_PX = 1.35; /** Peak opacity of the faintest star. */ export const STAR_MIN_ALPHA = 0.22; /** B−V for stars the catalogue gives no colour (a solar-type white). */ -export const STAR_DEFAULT_BV = 0.6; +const STAR_DEFAULT_BV = 0.6; /** Floats per star in the vertex buffer: xyz, σ, rgb, alpha. */ export const STAR_VERTEX_FLOATS = 8; @@ -76,7 +76,7 @@ export function bvToTemperature(bv) { } /** Black-body colour of a temperature, sRGB 0..1 (Helland's fit). */ -export function temperatureToRgb(kelvin) { +function temperatureToRgb(kelvin) { const t = clamp(kelvin, 1000, 40000) / 100; const red = t <= 66 ? 255 : 329.698727446 * (t - 60) ** -0.1332047592; const green = diff --git a/src/sources/adsbRecords.js b/src/sources/adsbRecords.js index 9c0082700..814da60b2 100644 --- a/src/sources/adsbRecords.js +++ b/src/sources/adsbRecords.js @@ -41,12 +41,12 @@ const CATEGORY_PATTERN = /^[A-D][0-7]$/; /** A previous accepted fix older than this is no reference for a new one. */ export const LOCAL_ADSB_REFERENCE_MAX_AGE_MS = 10 * 60_000; /** Speed limit (kt) for an aircraft that has not reported a ground speed. */ -export const LOCAL_ADSB_UNKNOWN_SPEED_LIMIT_KT = 1_000; +const LOCAL_ADSB_UNKNOWN_SPEED_LIMIT_KT = 1_000; /** Tighter limit (kt) for light aircraft and rotorcraft without a speed. */ -export const LOCAL_ADSB_SLOW_CATEGORY_LIMIT_KT = 350; +const LOCAL_ADSB_SLOW_CATEGORY_LIMIT_KT = 350; /** Reported ground speed is multiplied by this, plus the margin below. */ -export const LOCAL_ADSB_SPEED_FACTOR = 1.5; -export const LOCAL_ADSB_SPEED_MARGIN_KT = 50; +const LOCAL_ADSB_SPEED_FACTOR = 1.5; +const LOCAL_ADSB_SPEED_MARGIN_KT = 50; /** Distance always allowed between two fixes (CPR and reception error). */ export const LOCAL_ADSB_POSITION_MARGIN_M = 500; /** diff --git a/src/sources/featureGeometry.js b/src/sources/featureGeometry.js index 429dbe826..a18d5f22f 100644 --- a/src/sources/featureGeometry.js +++ b/src/sources/featureGeometry.js @@ -77,7 +77,7 @@ export function simplifyRing(ring, tolM) { return out.length >= 4 ? out : ring; } -export function douglasPeucker(points, tol) { +function douglasPeucker(points, tol) { const n = points.length; if (n < 3) return points.slice(); // Iterative, index-based (no per-call array slicing / recursion), so a large @@ -113,7 +113,7 @@ export function douglasPeucker(points, tol) { return out; } -export function perpDistance(p, a, b) { +function perpDistance(p, a, b) { const dx = b[0] - a[0]; const dy = b[1] - a[1]; const len2 = dx * dx + dy * dy; @@ -149,7 +149,7 @@ export function stitchRing(ways) { return endpointGapM(largest) <= allow ? largest : []; } -export function largestByArea(components) { +function largestByArea(components) { let best = components[0]; let bestArea = approximateAreaM2(best); for (let i = 1; i < components.length; i += 1) { @@ -162,7 +162,7 @@ export function largestByArea(components) { return best; } -export function ringSpanM(chain) { +function ringSpanM(chain) { let minLat = Infinity; let maxLat = -Infinity; let minLon = Infinity; @@ -176,7 +176,7 @@ export function ringSpanM(chain) { return approximateDistanceM(minLat, minLon, maxLat, maxLon); } -export function buildRingComponents(ways) { +function buildRingComponents(ways) { const same = (a, b) => Math.abs(a.lon - b.lon) < 1e-7 && Math.abs(a.lat - b.lat) < 1e-7; const remaining = ways.map((w) => w.slice()); @@ -207,7 +207,7 @@ export function buildRingComponents(ways) { return components; } -export function endpointGapM(chain) { +function endpointGapM(chain) { if (!chain || chain.length < 2) return Infinity; const a = chain[0]; const b = chain[chain.length - 1]; diff --git a/src/sources/openFreeMap.js b/src/sources/openFreeMap.js index 274c2ace7..00c288a8b 100644 --- a/src/sources/openFreeMap.js +++ b/src/sources/openFreeMap.js @@ -24,7 +24,7 @@ const ROAD_TYPES = Object.freeze({ * OpenMapTiles folds private access into `no`. Tunnels cannot be placed on * the surface above them; ramps/bridges remain valid only on a motor road. */ -export function isDrivableOpenMapRoad(properties = {}) { +function isDrivableOpenMapRoad(properties = {}) { return ( Object.hasOwn(ROAD_TYPES, properties.class) && !properties.subclass && diff --git a/src/sources/transitHistoryStore.js b/src/sources/transitHistoryStore.js index 9fe29f59d..cf82835c1 100644 --- a/src/sources/transitHistoryStore.js +++ b/src/sources/transitHistoryStore.js @@ -1,7 +1,7 @@ import { validTransitIdentifier } from './transitHistory.js'; export { validTransitIdentifier } from './transitHistory.js'; /** Bounded, process-local operator position history. No upstream or scene access. */ -export const TRANSIT_HISTORY_LIMITS = Object.freeze({ +const TRANSIT_HISTORY_LIMITS = Object.freeze({ retentionMs: 900000, fixes: 128, feedBytes: 16 * 1024 * 1024, diff --git a/src/testSupport/kartaViewFakes.mjs b/src/testSupport/kartaViewFakes.mjs index a2e6452f6..326ba9d77 100644 --- a/src/testSupport/kartaViewFakes.mjs +++ b/src/testSupport/kartaViewFakes.mjs @@ -1,7 +1,7 @@ // DOM stand-ins for the KartaView viewer's tests (no browser in Node). /** A DOM element with what the viewer touches: children, events, attributes. */ -export function fakeElement(tag) { +function fakeElement(tag) { const listeners = new Map(); const element = { tagName: tag.toUpperCase(), diff --git a/src/tools/queries/panelRequest.js b/src/tools/queries/panelRequest.js index 3864c9889..51ecd6406 100644 --- a/src/tools/queries/panelRequest.js +++ b/src/tools/queries/panelRequest.js @@ -55,7 +55,7 @@ const COMPRESS_MIN_BYTES = 1024; * rather than fail; past `PANEL_QUEUED_REQUESTS` waiting, they are refused. */ export const PANEL_CONCURRENT_REQUESTS = 6; -export const PANEL_QUEUED_REQUESTS = 256; +const PANEL_QUEUED_REQUESTS = 256; /** * Each server's panel state: bodies too large for one call, kept for the diff --git a/src/tools/queries/share.js b/src/tools/queries/share.js index 0c59ca7f0..5284e8a06 100644 --- a/src/tools/queries/share.js +++ b/src/tools/queries/share.js @@ -7,7 +7,7 @@ import { AREA_SCHEMA, resolveArea } from '../area.js'; import { cameraForArea, cameraLookingAt } from '../views.js'; /** Tool arguments that describe a view: an area to frame, or a camera. */ -export const VIEW_ARGUMENTS = Object.freeze({ +const VIEW_ARGUMENTS = Object.freeze({ view: Object.freeze({ type: 'object', description: diff --git a/src/ui/cameraOrientationControls.js b/src/ui/cameraOrientationControls.js index 0fb67f1d4..d10bacf2f 100644 --- a/src/ui/cameraOrientationControls.js +++ b/src/ui/cameraOrientationControls.js @@ -107,7 +107,7 @@ export function readCameraTargetFrame(viewer) { } /** Apply an orbit frame, then return the camera to Cesium's fixed-world frame. */ -export function setCameraTargetFrame(viewer, frame) { +function setCameraTargetFrame(viewer, frame) { const camera = viewer?.camera; if (!camera || !frame?.target || !Number.isFinite(frame.range)) return false; try { @@ -156,7 +156,7 @@ export function setCameraTargetFrame(viewer, frame) { * * @param {{pitch: number}|null} frame From {@link readCameraTargetFrame}. */ -export function frameIsTilted(frame) { +function frameIsTilted(frame) { const pitch = Number(frame?.pitch); return Number.isFinite(pitch) ? pitch > OBLIQUE_THRESHOLD : false; } diff --git a/src/ui/cctvFrames.js b/src/ui/cctvFrames.js index ac6687f54..33818984d 100644 --- a/src/ui/cctvFrames.js +++ b/src/ui/cctvFrames.js @@ -46,7 +46,7 @@ const SOURCE_LABELS = Object.freeze({ }); /** "SNAPSHOT" / "СНИМОК"; unknown codes (HLS, MJPEG…) stay as uppercase codes. */ -export function cctvSourceKindLabel(kind) { +function cctvSourceKindLabel(kind) { const key = String(kind || 'unknown').toLowerCase(); return SOURCE_KIND_LABELS[key] ? t(SOURCE_KIND_LABELS[key]) diff --git a/src/ui/statusText.js b/src/ui/statusText.js index b0899d55e..677c9b886 100644 --- a/src/ui/statusText.js +++ b/src/ui/statusText.js @@ -218,7 +218,7 @@ function statusPatternText(piece) { } /** One piece of a status line in the UI language, or as it came. */ -export function translateStatusPiece(piece) { +function translateStatusPiece(piece) { const known = translateKnown(piece); if (known !== piece) return known; return statusPatternText(piece) ?? piece; diff --git a/src/voice/deixis.js b/src/voice/deixis.js index d68fcd61a..25d7939eb 100644 --- a/src/voice/deixis.js +++ b/src/voice/deixis.js @@ -18,7 +18,7 @@ import { finiteOrNull } from './pointerContext.js'; -export const POINTER_SENTINEL = 'pointer'; +const POINTER_SENTINEL = 'pointer'; const TRACKABLE_LAYERS = new Set([ 'flights', diff --git a/src/voice/narration.js b/src/voice/narration.js index a91dbbe2b..ae97931f7 100644 --- a/src/voice/narration.js +++ b/src/voice/narration.js @@ -4,7 +4,7 @@ import { progressLine } from './speech.js'; * Timing policy for filling silence while tools run. Tuned against the turn * spans' `max_silence_ms`. */ -export const NARRATION_TIMING = Object.freeze({ +const NARRATION_TIMING = Object.freeze({ /** Start the earcon when nothing is audible this long after end of speech. */ earconAfterMs: 700, /** First spoken progress line when a tool is still running after this. */ diff --git a/src/voice/pointerContext.js b/src/voice/pointerContext.js index 3f9d09b6d..590e33a52 100644 --- a/src/voice/pointerContext.js +++ b/src/voice/pointerContext.js @@ -20,15 +20,15 @@ */ /** Movement below this many CSS pixels is still the same dwell. */ -export const POINTER_DWELL_PX = 12; +const POINTER_DWELL_PX = 12; /** Stillness that makes a dwell point. */ export const POINTER_DWELL_MS = 250; /** A resting pointer stays a referent this long after it last moved. */ export const POINTER_FRESH_MS = 30000; /** Pixels from the canvas edge that count as "parked", never a referent. */ -export const POINTER_EDGE_PX = 8; +const POINTER_EDGE_PX = 8; /** A dwell pick is reused by a snapshot only while this recent (contacts move). */ -export const POINTER_DWELL_REUSE_MS = 1000; +const POINTER_DWELL_REUSE_MS = 1000; /** * Open mic has no gesture, so the pointer only counts when it moved or * settled on the map this recently: a cursor resting there for minutes is @@ -76,7 +76,7 @@ export function cameraPoseKey(viewer) { } /** Bound and de-control a label before it goes to the model or the card. */ -export function pointerLabel(value, max = LABEL_MAX) { +function pointerLabel(value, max = LABEL_MAX) { const clean = String(value ?? '') .replace(/[\p{Cc}\p{Cf}]+/gu, ' ') .replace(/\s+/g, ' ') diff --git a/src/voice/pointerPick.js b/src/voice/pointerPick.js index 33ccab9cb..e74671eda 100644 --- a/src/voice/pointerPick.js +++ b/src/voice/pointerPick.js @@ -4,7 +4,7 @@ import { cameraPoseKey } from './pointerContext.js'; import * as defaultAnnotationResolver from '../annotations/annotationResolver.js'; /** "Around here" radius as a share of camera height, clamped to sane bounds. */ -export function pointerRadiusKm(cameraHeightM) { +function pointerRadiusKm(cameraHeightM) { const heightKm = Number(cameraHeightM) / 1000; if (!Number.isFinite(heightKm) || heightKm <= 0) return 50; return Math.round(Math.min(400, Math.max(5, heightKm * 0.75))); diff --git a/src/voice/pointerReticle.js b/src/voice/pointerReticle.js index 96729a933..0702d7345 100644 --- a/src/voice/pointerReticle.js +++ b/src/voice/pointerReticle.js @@ -14,7 +14,7 @@ export const RETICLE_MAX_MS = 10000; export const RETICLE_LINGER_MS = 1500; /** Tools that move the camera away from the pointed spot. */ -export const CAMERA_TOOLS = new Set([ +const CAMERA_TOOLS = new Set([ 'fly_to_location', 'track_entity', 'zoom_to_globe', diff --git a/src/voice/realtimeDiagnostics.js b/src/voice/realtimeDiagnostics.js index 630e29433..55e078280 100644 --- a/src/voice/realtimeDiagnostics.js +++ b/src/voice/realtimeDiagnostics.js @@ -1,10 +1,10 @@ -export const ERROR_LOG_LIMIT = 30; +const ERROR_LOG_LIMIT = 30; -export const ERROR_STORAGE_KEY = 'gev-realtime-errors'; +const ERROR_STORAGE_KEY = 'gev-realtime-errors'; -export const DEBUG_LOG_URL = '/api/realtime/debug-log'; +const DEBUG_LOG_URL = '/api/realtime/debug-log'; -export function createDebugSessionId() { +function createDebugSessionId() { const randomPart = Math.random().toString(36).slice(2, 10); return `gev-${Date.now().toString(36)}-${randomPart}`; } @@ -47,7 +47,7 @@ export function sanitizeDebugValue(value, depth = 0) { return output; } -export function sanitizeDebugString(value) { +function sanitizeDebugString(value) { if (value.startsWith('data:image/')) { return `[Redacted image data URL, ${value.length} chars]`; } @@ -65,13 +65,13 @@ export function sanitizeDebugString(value) { : redacted; } -export function isSecretLikeKey(key) { +function isSecretLikeKey(key) { return /(?:api[_-]?key|authorization|bearer|client[_-]?secret|token|secret|password)/i.test( key, ); } -export function createErrorRecord(source, error, extra = {}) { +function createErrorRecord(source, error, extra = {}) { const rtcError = error?.error || error; return { timestamp: new Date().toISOString(), @@ -90,7 +90,7 @@ export function createErrorRecord(source, error, extra = {}) { }; } -export function formatErrorForDisplay(record) { +function formatErrorForDisplay(record) { const primary = [record.source, record.message].filter(Boolean).join(': '); const state = [ record.errorDetail && `detail=${record.errorDetail}`, @@ -105,7 +105,7 @@ export function formatErrorForDisplay(record) { return state ? `${primary}\n${state}` : primary; } -export function removeEmptyValues(value) { +function removeEmptyValues(value) { return Object.fromEntries( Object.entries(value || {}).filter( ([, item]) => item !== null && item !== undefined && item !== '', @@ -113,14 +113,7 @@ export function removeEmptyValues(value) { ); } -export function compactText(value, maxLength) { - const text = String(value || '') - .replace(/\s+/g, ' ') - .trim(); - return text.length <= maxLength ? text : `${text.slice(0, maxLength - 1)}…`; -} - -export function loadStoredErrors() { +function loadStoredErrors() { try { const value = JSON.parse(localStorage.getItem(ERROR_STORAGE_KEY) || '[]'); return Array.isArray(value) ? value.slice(0, ERROR_LOG_LIMIT) : []; @@ -129,7 +122,7 @@ export function loadStoredErrors() { } } -export function storeErrors(errors) { +function storeErrors(errors) { try { localStorage.setItem( ERROR_STORAGE_KEY, diff --git a/src/voice/realtimePreferences.js b/src/voice/realtimePreferences.js index 1d4d1dd23..d3b78a922 100644 --- a/src/voice/realtimePreferences.js +++ b/src/voice/realtimePreferences.js @@ -8,12 +8,12 @@ import { // Voice cost control (repo-wide `godsEyeView..` convention; // the neighbouring ERROR_STORAGE_KEY predates it). -export const VOICE_TIER_STORAGE_KEY = 'godsEyeView.voiceCost.tier'; +const VOICE_TIER_STORAGE_KEY = 'godsEyeView.voiceCost.tier'; -export const VOICE_LIMITS_STORAGE_KEY = 'godsEyeView.voiceCost.limits'; +const VOICE_LIMITS_STORAGE_KEY = 'godsEyeView.voiceCost.limits'; /** Best-effort localStorage handle; absent in tests and locked-down browsers. */ -export function voiceStorage(storage) { +function voiceStorage(storage) { if (storage) return storage; try { return typeof localStorage === 'undefined' ? null : localStorage; diff --git a/src/voice/realtimeTurns.js b/src/voice/realtimeTurns.js index cbc8d5dbc..2073be06d 100644 --- a/src/voice/realtimeTurns.js +++ b/src/voice/realtimeTurns.js @@ -13,7 +13,7 @@ import { import { narrationLabel } from './speech.js'; /** Out-of-band progress responses carry this metadata tag. */ -export const PROGRESS_RESPONSE_TAG = 'progress'; +const PROGRESS_RESPONSE_TAG = 'progress'; /** * Speaks one code-authored progress line out of band. The line travels as diff --git a/src/voice/realtimeViewport.js b/src/voice/realtimeViewport.js index 755913906..e229b6cc2 100644 --- a/src/voice/realtimeViewport.js +++ b/src/voice/realtimeViewport.js @@ -5,17 +5,17 @@ import { import { cameraPoseKey } from './pointerContext.js'; /** The live viewer's camera pose identity (Cesium-free read). */ -export function readViewerCameraKey() { +function readViewerCameraKey() { return cameraPoseKey(globalThis.window?.__godsEyeView?.viewer); } // Viewport-screenshot size guards (M13). The old code clamped WIDTH only, so a // tall portrait window produced an oversized capture whose dc.send could throw. // Cap total pixels (clamps both dimensions) and drop the image entirely if the // encoded data URL is still too big for the data channel. -export const VIEWPORT_MAX_PIXELS = 1200 * 900; +const VIEWPORT_MAX_PIXELS = 1200 * 900; // ~1.08 MP, matches the old 1200px-wide landscape budget -export const VIEWPORT_MAX_ENCODED_BYTES = 200 * 1024; +const VIEWPORT_MAX_ENCODED_BYTES = 200 * 1024; export async function captureViewportImage() { const viewer = window.__godsEyeView?.viewer; @@ -66,11 +66,11 @@ export async function captureViewportImage() { } /** Side of the point-and-ask crop, in CSS pixels, and its output size. */ -export const POINTER_CROP_SIZE = 512; +const POINTER_CROP_SIZE = 512; /** A pointer snapshot older than this no longer matches the frame. */ -export const POINTER_CROP_MAX_AGE_MS = 20000; +const POINTER_CROP_MAX_AGE_MS = 20000; /** View scales where a crop shows buildings and terrain, not a smear. */ -export const POINTER_CROP_SCALES = new Set(['local', 'city']); +const POINTER_CROP_SCALES = new Set(['local', 'city']); /** * Square source rectangle around a canvas point, clamped inside the canvas, @@ -128,7 +128,7 @@ export function pointerCropRect({ * @returns {Promise<{dataUrl: string, rect: object}|null>} JPEG data URL and * the crop's normalized bounds in the full canvas, or null. */ -export async function capturePointerCrop(screenPx) { +async function capturePointerCrop(screenPx) { const viewer = window.__godsEyeView?.viewer; const source = viewer?.scene?.canvas; if (!source?.width || !source?.height || !screenPx) return null; @@ -259,7 +259,7 @@ export async function renderFreshCesiumFrame(viewer) { } } -export function isNearlyBlackFrame(ctx, width, height) { +function isNearlyBlackFrame(ctx, width, height) { const sampleWidth = Math.min(48, width); const sampleHeight = Math.min(32, height); if (!sampleWidth || !sampleHeight) return true; diff --git a/src/voice/resultDisplay.js b/src/voice/resultDisplay.js index 642db2bb4..ac99f86d7 100644 --- a/src/voice/resultDisplay.js +++ b/src/voice/resultDisplay.js @@ -59,7 +59,7 @@ function notesOf(...values) { ]; } -export function analystNounFor(layerKeys) { +function analystNounFor(layerKeys) { const keys = [...new Set(layerKeys.filter(Boolean))]; if (keys.length !== 1) return 'results'; const key = keys[0]; diff --git a/src/voice/speechLint.js b/src/voice/speechLint.js index 9b6c02ab4..f1b1c1878 100644 --- a/src/voice/speechLint.js +++ b/src/voice/speechLint.js @@ -7,7 +7,7 @@ * Phrases that add length without adding an answer. Tool numbers are spoken * verbatim, so approximations are hedges too. */ -export const HEDGE_PHRASES = Object.freeze([ +const HEDGE_PHRASES = Object.freeze([ 'keep in mind', "it's worth noting", 'it is worth noting', diff --git a/src/voice/turnMetrics.js b/src/voice/turnMetrics.js index 13e9c6940..b2c447cee 100644 --- a/src/voice/turnMetrics.js +++ b/src/voice/turnMetrics.js @@ -1,7 +1,7 @@ import { countWords, findHedges } from './speechLint.js'; /** Quiet period after the last audio or response before a turn is closed. */ -export const TURN_SETTLE_MS = 2500; +const TURN_SETTLE_MS = 2500; /** * Per-turn latency and speech spans for one voice session. diff --git a/src/voice/voiceCard.js b/src/voice/voiceCard.js index 72b754287..09619bb54 100644 --- a/src/voice/voiceCard.js +++ b/src/voice/voiceCard.js @@ -5,7 +5,7 @@ import { } from './voiceCardPresentation.js'; /** Hide an idle card this long after its last update. */ -export const VOICE_CARD_IDLE_MS = 14000; +const VOICE_CARD_IDLE_MS = 14000; const ELEMENT_IDS = Object.freeze({ card: 'gev-voice-card', From 86f271a78fb0935c9dcabb80912001251028f8a7 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 19:01:09 -0700 Subject: [PATCH 06/11] chore: remove unused data and model assets - src/data/local_data/dams/dams.geojson held the same 704 features as dams.geojsonl, feature for feature. Only the JSONL file is loaded (src/sources/infrastructureData.js), so the GeoJSON copy is dropped and the dataset README no longer mentions it. - public/models/ship.glb is not loaded anywhere: no module, template, stylesheet or script names it. Its row in public/models/README.md goes with it. Together these remove 960,345 bytes of tracked data. --- public/models/README.md | 1 - public/models/ship.glb | Bin 230024 -> 0 bytes src/data/local_data/dams/README.md | 3 +-- src/data/local_data/dams/dams.geojson | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 public/models/ship.glb delete mode 100644 src/data/local_data/dams/dams.geojson diff --git a/public/models/README.md b/public/models/README.md index 7e9c089be..aa7f40fbd 100644 --- a/public/models/README.md +++ b/public/models/README.md @@ -8,7 +8,6 @@ under the license listed below. |---|---|---|---|---| | `airplane.glb` | “boeing 747” by [zairiq-123](https://sketchfab.com/zairiq-123) | [Sketchfab model](https://sketchfab.com/3d-models/boeing-747-9b16672038ba48f98e6d80a159044ed9) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Substantially modified and optimized for God's Eye View, including geometry/material simplification and coordinate/orientation preparation. The former 24× runtime calibration is baked into the mesh; location, rotation, and scale are applied; the bounding-box centre is at the origin; and the model uses glTF +Y-up with its nose toward local −X. Geometry remains uncompressed so the aircraft asset does not compete with photogrammetry for Draco worker capacity. | | `jet.glb` | “Private Jet” by [Nick the Name](https://sketchfab.com/Nick_The_Name) | [Sketchfab model](https://sketchfab.com/3d-models/private-jet-cbdd1de6ced9461e950eafaa302cc82b) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Repackaged as a glTF binary for the project's military-flight visualization. Imported hierarchy transforms are baked into the meshes; location, rotation, and scale are applied; the bounding-box centre is at the origin; and the model uses meter scale, glTF +Y-up, and the shared nose −X convention. Existing materials are preserved. | -| `ship.glb` | “Low Poly Cargo Ship” by [Javier_Fernandez](https://sketchfab.com/Javier.Fernandez) | [Sketchfab model](https://sketchfab.com/3d-models/low-poly-cargo-ship-4c22cbaf01c1427f8ab60b3a07b1b32c) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized and repackaged as a glTF binary for the project's vessel visualization. | | `bell206.glb` | “Bell 206 JetRanger” by [terran4627](https://sketchfab.com/terran4627) | [Sketchfab model](https://sketchfab.com/3d-models/bell-206-jetranger-d2f7ba1d671549d4b26aaf834139a1dd) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. | | `c172.glb` | “Cessna 172” by [e737](https://sketchfab.com/e0057537) | [Sketchfab model](https://sketchfab.com/3d-models/cessna-172-64cddaee5aff470682659a8c08525046) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. | | `citation2.glb` | “1990 Cessna Citation, Texture Detailed, Exterior” by [BlenderCommunityHead](https://sketchfab.com/aboodgoudagad) | [Sketchfab model](https://sketchfab.com/3d-models/1990-cessna-citation-texture-detailed-exterior-a78839624fe64900a8352cb23462350a) | [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) | Optimized for God's Eye View: geometry/material simplification, textures resized to 256 px WebP, orientation/scale vertex-baked to real-world meters (Y-up, nose −X), origin centered. | diff --git a/public/models/ship.glb b/public/models/ship.glb deleted file mode 100644 index e52d13b2c08a4cac1ef05829cae264ba8d489c8c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 230024 zcmbrGcT`i&*8dT)p<+i6REnr5od5xn+2kBhL`7*9MCnaw0xCUNu#1Whh}f|A0@6ux zkXW%{SHOmX9TgS3{^m@`eWUk!*ZZ#Dde-wt!Y4c5nX_l_J$ojGMtF`!XL zrg3wE;{(G&`~yq^qa#gaoXNj9Lu@`GJTN#aE*P7qX=7@-G5%87$ML5FV}t$U!{-O% z&qhW^#hFCMhMKnXai#%@rkFKlZNx>-iw$H={O7N=3^I<44hoKlGmVH|U>p-2k!T#~ z9~&BN92XWIW6TM(um}wB4>2tjz)}{mpCw%mXYf0Kdj5fd!EtfXv3OQyBh`*+GegZ;sh%TCQ-K4W3Vq=$neNuS?G|xYf6Ot?rp?j|2LpHtL)#vn5a z@v-xQjr3vyVkZX2`$ynF?jAiaG%N~-PIh7g{NsY@=!oc8Puas^-wVgt1P4bvKE234 zKlAUH1sjW_aPsnx`1e2Jq3cBl21d+dpYFf^8Gn`?P;gvW`@FZ0hKf!wGqdoO4@yVh z!k0~oF|py1;cRM>4c$K;dw;;Zc=lzop6ev{iGm5(-Hh}mPx2V&Ic}0GaRX0*>sTjO z?1$J{Je{V|lP0-4VrzDI#p$sL3NN1jnN#u!Jbs)Wu~AOyOjchMrexpPF3&@rUwui2 z!^&c#<8jVp^Ysj4GZPy#8*?*j8?L!I*V>AMbBY=J-=A-@DIhQ`JR&GInEjmjOx8`> zzroVVh8u$ORDe|=mlGId8DM1@62u7z;8<|YtgV79OhN(@e*fBkGylK)Rtv>%b&rmY zcV(yKH({2Fi0sMy{?-4d5yu7kvsrzHCC;d3mgbgb<{Vot$K2A~$lT1t(h{d&b8`+3 z?N&B8ZJL{KY|YHf&1`L~ZEVdgx#n!BB+GqNzT z;czXi&1@{KIrxcKag2;Dt;|fUIQTsaD@$9PqOE?nY^7-VFYd_tExSzcC*yEA^*0~! zVE=|i$=%3|I2HK>aku}8eqM3xf}Qd25fFdF|4{D1bLWM}{*6r4(a}*#=%_@Eb5-h` zy@Lwsq9-*Ne3W`sV+$jyhclzH10?Q)%^+!!DLCGWLrO~N;5u+QdUv=6{ey}r&u{~& zw{J%}L(2+Yo;POVr;L-RoZ-OAo1@@LSS;aNwKpKOz*}g`=Y3RTA7km0S*kQOqYwNX z@5*?0Fp{*qcY~sF-C%>`1j0|@uR%c%?x3Z8H&IhJH&c#P=CTrpHq~SlFHRs&s2jO+pi`$5 z$=<@vlJ!5L&}f|m$o#sU@LlVrsO$NYNR)V$`p#34&L94q`oxWf=~0U%Uk|28ruPt{ z^>Y@$v1@6Bf2DmAnVr3YYSv$%uK(yGJxzU}mi+Vs@2S@%g$?nN<5Mo7A0xvdMCCl; z6CanLkv*@X_s5P>W5)EBh8^5M*`x=6^0RVDV^+4L^~*7Ie`ypfe(;9yF&)a#n!eJ% z@@F44A*W7W@Va0xd0q$3u1C#VPNO?L4pE#<>e6*XHdCg3r-D`Iw~}i|R!O>hb%THg z7nrl_AHsW2$wYn3&ms$rl@!--pmYh-BH*r?21^|uNw)guNk+DIf>w|5kT7f;;d>P& zA-|6mXg7B?bzxRt>8m$41-EMbU~+De#4mi0MU$Eh^&-ZfXNEow;K|ixfI!C!jpQR*6gQ#Pt;-K$!1OC$=d6Jq>L&X*~ zLeS1OBm5S=C7df|(65xU)T%j6)VHhCGsM9(@(Uh&zJ`@0Mrl`2GJolpB*mg2E%uLfY|S!MoHSUseD16 z5VkMfK=_5CG2r8a=X1kFD&c|_ePMSHmC-F87G*pa75v;J$=~&m=Z&8);wcfn_xiC= zqEUrTjlM{|jWDGfGiOn-Hxkx~(orsVwIrbDdla*Ntx5)Z3gR-&2nJ4;N~tdqoSiHD?HN6C8W@TX?z(5J2)6wTPSnCF6ZP|`(W<2WJH`Zk8}wRI+7x8IcT zte(dCIy4Ng7Iiz^T>0gt5GH+JFTOZM$dpv85&d)T8N;GnQ~10*o2nk7N2|A`&|Yg- zS{ZKd!)3q!x!Zp6I6Wa_kRp5i*0~2=JkM&cMTvH>t}h^{)1)EF6}fY^PlsvJ6QP`6JD-o z^Epdq3+H=f1kW0774#9$UoT_^wb~P2b$@qYyo|wT_C6{gUQ0?n*hzi#oB$U(^(0$7=qLE?$BWEE=Oc@C{>uA;QJ4w3p4D$|9#$AfN4U#3+sLE@pS1#UJD z@MG~(!q-@CLcQ~EBKb*tZSDeo%y`11=`kq$ z;VJZSc?LD8vz|1hc@))K*B-RH=rg}Gk|h@ont;g34D#0}6TV|%EJ_&1pe?RDsCxw# zG;c+U0~0U}zAQM5@&j&27T(_|*{w1cMzrY@UfXmMaGZ|+)&Fhw?2xqdiGbTpK@#F` z|9F6lb`|P&?IpDg=igTI)wDL!gwhUe=u7`$P|@9hFGtKds#F0nK{)^J}X8^&lwA7i`^7l*sQ}ex*AF7fx{suiwlvENcip4 z4s@uX8PyGKq)a-ROLtW-qxTM;4z~_&MvqnEBU&C1v$rdpKLmtdcY7NWeEg0wFO*Uy*Da+N%bn;Q zhCQIc$esBpGLZam@`7T43Vil-A$-a8jp+2bXXs}7O)BD~p49s%(C3YPVQ;55l8GuB zlIE(f$hOHH9vEa1eq_adWV)#djnI2a-Ow5#wa{KhXKspxdmcL^qZfFHb9{E9`nO@w zxp6t+cjO&GaR%Se*2H^Mf31Pi8=EZY$|D}|{X}=B!K%AN-KQ5A*}FqR*h4<+4_?m; zP~z}M$Ry9GXzeDxhlkxAV>v;~@} z!#xK`mw1h#PsbzpHq($m!;xHG->6J+d~vR5^9Lameb*xSR7q$I!}RJ=*2`y9 zter8<)M?X!W8-1sgbgU1*(aXmo-LWMCm!5>jv({7MmiBXb!|Z7@4up+ZZoHgIy%vp z2hWEKUsj+4#zEq7<5o$Q_L~o9Rk?&eZ|4dsdQXuWUN4dJhtn&3XdgnnMdX9$>yv05F(^o7KbL}+5rQ3y& zkfBTXuFB3(-_U^SX4X^9i@0=dFAe&OyAGVI9|KvtmWfl>k7ZIn4~4Q)W5UO7bcdj4 zb*Q!AF}3oRElu@urhA+j2Fa#0bicVw{OSSC?8+Mk_c*GAw~2QHwZ6}gG@y)X=!@u_ z0|V)1hgsziOE>uh0ZOvCHzaTsW4yl8DjIJJl|ozVjS+>DjwOXj-yq5$Lj7?A+!I4 zCZF{;=cngU%Agj)v-y|Jr!4;wP8ZFZThB>|y2Ba$CXMktT0wYT;6?PhTMJ_I*Lcg# z)aTp2^w=mn=B%L+qu;3un&>_n#?X8wPm)Wdsq$*Vp5JI+68T@_rzMY++E}360cd z^LW9$tlL!Fi4<_ET_ozT4S+MBE}?b%?o;NiboH1ycd3%I z$xu8IiBG<>kmUH;^1qhh`tZ9s@rSjSzoV`LFQclKvy{#-o$4IF5bEf=IJo#cTHJkl zuEgq6DBo=@uA3l<@J}~)0Ha2RtS{m8b<_)wiFD^@Hjvxrwq)>!o-nR49xaL0WXv*Y z60gb%YSA#$&nO4`gPc#>I+O8E&Xz=5)FR`}nP_WKJdViW?0XXGU4^8+g9q`4{=IcU#o!ZKv5uiUUoq9gX3e9566eCO+HR7Ph|Q7- zBRZn9hf=}Y#gOR9^RGv|6;)_>(k5#5hqKj6$x{WTSHdBpsgLBByHv8IPZ#7HmkMp%C4=pHF#Su&d6g?;yzA8U|$(qs1vluS;g@h4Q=O_!GEm6`9W{-=An^ zTmd@#@&y&MZMxv_C{20}M+h7B`J&>r+r>rUT%HS#=c|t_BhR;KYkvrsT8DmiY@u>* z=L>SUwlx2`5H!|rt{gK&UA%Bju6@5>LTFVTMfk0a>M&%$WAv)SYwBQlLG_A6Te>)2 z2xHaUM9Us55`XIL%1glhmKx(l_#clRqA|T5Bi-T8s4>?vs~v9)qL0r@gUU^>MXx5` z5qkzdI4INshOD>Hd3Bt`w{+~!Y<#=+ zR>;h*$+rJC`7qDo&a6s}%|fPsO#q+OzyHByRMGYt*-09ygB#MRgO_hp^#@a6V82ja zsM;_|hl>HCWSlQwzqPby`KL#QgJZ|bq<)jf!xandAa{TfGv!lfN-@@-{q<}GYyWh{2-sU( zN7mzlhq=_VFg`sO?^|BfSJ+pdKP#%&Ev*F1_ioE0dhS$?0ypXj>W%ZGJl=*Ku>&nf z6Xw{xO#bL5Ll_}32Jc=wNW4u5zJ!*)RRUhj9jfZ<#p)G>9~bil58@c3l1q;+2pNy^tmv|c{}^!Kz9zU%06G$`Q-N-0`Txz;I5 zr9Za|?pub!^L0rQD zpIpE!_*7M){3xBoAJ2&45Z*}{{#L(P=PENh?8m^a!wlhP)eZyh#}4qfeoil3&k9yW z!}8loWd1dmmLP4@;=k^9^uNB4ynp8n1Eb~>{)USW1gRII!VGU}TC@$F{BdOUi(6I@ zVWk9p&<}~neq+Y}gb|GIo=x~R-B5VfbuZFuoiDrq5(LY@;Plx(C4YllRaZ+LrLzZmuXcy8?~W)Tau4c=Y)i zjcSNO=wnp^EH)b;S-dov$nVkkk zZ+PNQx6>H4epUR=-C_xEV>cTnbQYuP(NR>JKp#u(#%?^62+$jCi`l*hO4PlfhK zNwWkEJF887|DrFQlU{LhO0zcnT4cvCTee7kIk>>j~YMO@h;uY=cWG^?^mME2!u5XaMQ$L9OHb%gapAHf> zUWo*S=*x$Eq}QTDEots4O&Z9lUh{n#7}(91+%|2M3>`NKZJw6^zGpfSzSz0~?JwMp z%GxGVtLLdo6E2@WIabLR?u8^vdY!o|@w4_rrwkL|Mdo9&euthCp@9B-(FwotR7%i5 zsc~@D$u{F@(7IkC@wGlJsf{~~cI8FFZU;SLKU@0*irKagJvrk_35V&?b>nO49alP( zln+p|W%Wv|M>1Mp9hjS|u9N;uLa+1u?Pmu7%RgJELqGb1cRoFm5vDpY)URuV??3JW z|5O)sm@CXC{1ePSSi8L}~!{$kNpA%xZq;<4kI zCANZVWxEhQ`J;ZhUZu7;c~jl4mu|p9rRElX<>hHJG!*vPD<${%(u=WKs_}6Dder`uF5>RwNb=iK4R4?8&=ONFB=$ndKcQ%b0u|hIz{>+gLE_bCfhkc zuj0Ty2O*Bk6~Hyt|N}=)!?&6)V{9D`e2@rTiCy z(}cg?Vhd}F;^4cQ6UBQgApMy=H&ew6+&PR+Co9++BxGiF%HR*TsUZHm^olJM7{)RRb!w)`m7q2&>+8Z8&^?-xC~# zDojIFog|{O7qoOKChPao=E-1!w){1oFS>t8GBR@}JXhI7_cYg}#q1^}5;V+x` zJ!htY-3m&ya(WuGFQth;`M_$zFUkl6Nn!(9D?-$P44Tecphc${RPcwsN`r(hl<2_I zG-maQa(<7f^@RUu5e_YS4JdUEMV-1w)0Ybz>9DC%ey{6k@M(^UDDPq#^Szst9~L94 zhr=(0fqKAG!%rs z?>8^1jKMt*c73V%&4oIZVZZHvJG9$pdDcEh!M=R|tGK^r`Q&yzJ|$Dc2d*uu^xGq| zFW+D9=Wn3uqHU#D!IHjPRg6Aei(sy+t4fUDM1t_{L8R@XOZ?N_WjXq~;|c2dU>7xg zHZ2|XIfH()=pWQAC7!u-EJZwTSRBw@*P>-1y$N5|xE5)?`HtQu`BNi}ou!-=OX##! z=BQYj%v|_9MLg6w3H)XoqI)70!c#TL$gATIwEDngYE&kYF8OB!?RYU1`Rrc6gw1{~ z?vl0urrq~Kjo;M?zxq=YQlHiM+y3RNy|(1}2M4smCjm~49d5tVIhiTnNfX|!?+iFI z&Sln!#7xe_-N!NSkrLqI15! zxMXV*|I%b3oH?{kv@;d^!jlDrf0jQTHWe5U{=6yfH)1|BWu17)6AAwqN`nIuzP;!{ z8gu^~L--LpykYY(19E;L-@k0M`XExNl<)@OxZk^RhFCjG$gGJh;IaPDq1qId6`KXYb>;PD+E; zdDD1j2MU>~Lka(__lhy1L!lsrovj*QkOK*v>id>x}?CY@_d=2PAe@xDKl(zAAu zmNo0sS3ZwGCA!JXk(3)^{;?$JU#o$r^d5w-KIDUj-+zQQ&Qhm%O4ibb@RgKX-`U7} z^a4i5_m{Xw@d8jAKMnck^(1_OzB77neGZN4^@@twtVVYku1CLGDTGadH!G!1apEqQ zH`sr?i_a51Wc|0Zn<>~m^d539d`#u6?MX|1-~+ubslW+n63u?5A@P}1&6|8h2vv7J zllgi6n1VrmS5SpVJ@sOOwsgW^9^D()$OFz?kyyKTmK5T>`q&vWK*LkEpY3gb6h%FH zjMiT`LyhaKDeWKdg9^-^217c2m2B3TAt@Z+6|$1XgIB+7qQ7$Y7Gz{_LIOrPmo|pE8AZ@{}lrqldWjV+$(|@bsK3=fInq-Ul+}&Phyt#{vtjhNQQei z`yc_`k?4Ql-wN#*ei}{IzbyD%!>V#k6{63yC+ zlCKA}q30P}_^c$G&wW1t_564M)y%IGIOYwKp1l@R9lmQQoJN|Aa;Xwi@YNA^hU1>w zjU_VsNrveDqTPt=UMG05S5rFgOMJENhSBgg@tx#+VPD2A)*7xi4284XcM(4Qq#@EA zxf{9K*9+w5cRSLyah~0qQ^d}9Q@=FvMSR}Z$V?@#^WXM>;p?^G?Oj)L{x|^lOLr#U zFIs4$Ud-}61G2@@ZbHVXWiWZ|w4(>`-E~R*V~6V>w!gYEe|OQICtbZXr82>nIAYh@WUw8lgeIoT^2_sK;mBjp4|KudvEcsR@zTT| z8Fcg9TE0CkzFFZnS_ zsf?NZC9$d~1-6a8#&??Vh43Bbk3&;k9-$XFzq9e5tp~V=(|HGli>zMaKJVZMOGQII z37P3y%gO%X8{-Y5)bxSnt8ji{c^>BX+BuwH`BK;2qS{s=qcVCA;p2`^hXvjAVG^EC zmS^W@&Y^T(#UvU3>&AD{*@r@Ao8eEwf4VRoC`yO$tR9x%fcXI)&p538+}Yj5o$7>4 z%a|UdpXT1^4cEr&fK`Vl0_bW%9qjo}^_pYDKv+>=f8z(uh?_dt&&2mPUV8T;`AFA# zD5O^AAl};1l*1PziaPCE-TT-$m~iKeedvL~OowlMDg>CndU62aJJb(>;hnZ3Ck-P? z`gvz{*6wN5$EA~?+9gChf)vz&}~96{31IaX_(T4S}$)!#ao&M>E`9tt#RwC zXIbJih&5JX-=BP@s>PoF^BCTbe99*AaE|*u^eST`dXDFl&5!5teExb9zALKgc2Rq!&J~Vf9o< zr}1X@kEpzZ`;t_cvH0C7A(Ll3mguR)ci~>#83F?ho(aA_tfBf=t*uUMz~8xW1+T%! zfvG-OAhN~#{xavOWIvc4Yze*W@cdX0r~JIzs)ePF)oA-vupQo6(!HS*^Go`MKjCaD ze0dlzX4mi4g0pDXqitw**du|XnTqs&>5A%~IUJaONm6rbM{ze(_-2Eq7@!V5V4{hP04>HNV249G^Qg}IzO48 zJX#3z`g@6sacrJulE}{rbSD0D`eHOl$}5Qd{1Rj7(CV2Tmvfr1- zTh_kOLQUzoh~3q?d#+Ya3+~6{PIO}yXY4_CQYYB_qC1KI+a<@5Q|vmT-+6L>=}75; z>Y2w@ROWs^ArbD6W?symj&cXjhR2hROW5(eDl9@uO>6&}f3<&z(1K!r;0kjHUt3;) ztXtTBBPc@mJ&;EL(zj74sd$Rpq?JJ)tlYIB;k_x!pN+I*JWv$M?coXG` zgg=J)n-?b9f4ek{@W(Lkv^)i-Y1Rw2V!jRA@APtqM0Le>?xKcR7=`~ovBt;#!-xtexPP-oGu2ziumL{A04pv}@|1UV6WnbIH3TsG*JDBfzO{S+TqLR`nIbJk-_2bmq>Xf5~pz|owe!($d*3JtjadAu9 zPqMxsn*%dpBVy}^-kM`bf2K4jh3}Tkf!X&t5RC6$9a|X7k8FEF^h}?L;9g1`na|5} zoC$y4I?~*Bw=EpRu`=?fkWt;grLyMhZlb5n%n@c7$aqgvnf(^ApZT&!rc4hNJD7y| zZ0AFji{9@bdWQqOF}^$dHT=g5#m@frb#S*&dZp)o?{VoWl>%&La0-|^EC zc)gd|7b@w}=LZ&4&x@RD+wW_fNc36Ai1^2NRrns+%)7Gw18=f3>>ena&z!r~bjgfk z)w?x9L=%?wgdZ+m%!L#^p4iGkwL-M*f`A!8o#RQd9&`VGME^{D55c|fM)G_Q zyN{vM9s1EfP4`v|#&_l1Uat|I{vl*K951UF;ns=pJq?pU(6x^64ui+is|JsuAEZC$ zUdFj%{Vadca?B@YbSbsq$?m@us3n2g%ctm7w^o72At(CVaYy=9@Fu>MqYx%vF&5pU zg$z%B6aTOiP3+I|jD~gbPtn2hPXdvy6RmeylkVKSn13}!2>gV(qGQ+}N|r6=N9=VW zeCJWo;C8Z}=qVA$?NS)cy4~GC-7RP zzpR|GAXVgx?*gR1bR_xfsc0I#+bWOSi%S_htqL$?k`s(@wB_Rx>#tym#J;9*FC6 zCL#$ET|H2QS*hqkfQQ5{B?(rTZXx~ofh%0WEv1dzPg{cTr)glmp~4Q}l?m*=+)1oH z$dtK#)DG^<*iPm%{MRI~N^c|mRSE8|vb=WZTE6s98oYXXvGUx$G^XL!6@EP9O?X3` zKe`-lBlp>C@qIRy9~`_E`4rBBHM)mHuFO23U@v`pf(4~^&Yv#bm^ZyV% ziyxFCZtW`)-+JQs#_p%qh!60u4aW6A)yB%CK|+S!et_Rr%p>#j)glHq-)KW@zq%0j zt66?VawR_#_e;fbc9kQW)0n${mHZV+QG`#<4};6{*a{*5OVBRKs9sdHQ?R|8GyS+`{;$5pz*;lwwR%ul6~wf_C!$qNCfZre)i z&)(#Tl+ZD}M;%A?&K)8hwsFwOe1S9E(&@~IR~?pQ`woVO`8>$!Bh&Mst2;WnvjC<2 z)TW%Bhe}78eLQ)}2?15plW7=QC7IC65WXi^Kw<+MGt;3^4WWmDhmqob@E`LVh4^pG z!0b-8Ik@&;K02qkq_hW_pX-yR!p0hF$hzuEO)=X+1$WM;H~hg{*yj|NpONtoOr}Dw zY$wPWRWD%qnojxjf3q*X>IO?h*id#sXJlm2j{(;n(5 z?hmu$q6}0ll}dkclcIZ9+Yo zn7widvF~NQ72O=9@>@T_GQ-e>{Y<~?PUZP{ujyaZwPe}PMP&YAaVsh>vm*1~s-s75 zQOTz*6!;9xkIwE^^6KVd!jJ9MiY_m5B>Mm4`?lNnRj_~T(NokA=}7d;9ePJ4!W>}G2rC8d+?_pu+_`;lJ8+WDQzqjAnIt+Oo&`zq^a-r27Lmz!k# z+jjkm{4C5jgxQs7c9-3E>KLa2UdJ7Y|H$<>{b9dM!G7(9@2Dczk@$~X{}Tm%A+~>M zf^CV(iv?tU9^3yw!I!Pza99o1dv_Pw**BER35kO=Ax9kAb*p5?HZ^_?K0h)$&_~QZ zld`c6kgrvR4t{E+miJMi52ih!0;*D=o98pp-d{r{-s?{Df^py1KKV7V-(ebntKAh8 zvF|)J#9oH{P zMWnpop=8LVeBOlHLP%lW5Iqys9Y7cV-hsV8{~_uzbszV0RK{$#jnsd|{e^31n`cMG z>w4oky4{=fA10R#fbskAon`jEBIeon+2N4|b47PAH%-q5R{NNUjaCa8uX@>it@VQk zz_d{&gy*;_lXz?S#DYm}-oy1BY6G>Gx#AnR-~MI*hs` zxKb?t1@qGzc9(U%Xhrni$GpeJAiix=8Q~v%o5*)Cl<{+?;PL*!pTfMfD2RXhsf#A!53}NQQ=*nrWz%**8^5)?j zaDU$jQvW}gv-i)_{7m*FfeM}=|tn!7rqI{nk zI5!0xy4(`^bQ&o+|Nbbi^ELeYou1#x^WB@ygS?NINqzEmkP`7v_Bqb-uPA%aJ-c-M zwQ{Z@*3U1EAbiq59`vdINb1S6gTGSjedhT0xWBbyOohNOU$p2(n(ZW9|BqXLoAAmP z?O}-q{>26MPr3iI_T}|zP3ffqC)_XEq$T@(-w`nnp5FaP)}#FQDJ(zD`ISXm$uI65 z@uQL-TZEw7*PrBzl!`Ox`g3Ix&%M{^(ED-xSL6@8dr*>v`)^TBzJ%8+I)^-_^e24K zWMz68&cBNMpY{)SC`W5f_5W=>vi#I`J?1MPmxOG_XO9nL_wiPBE=A|3cZAk+uGAgQ z4yrPzn!aXiaRPsa3I671yB+(^s9dB&tW}Aue&n6JG+`rT)4u59mDk5e&xrp7U3xC zV%4WEgr9Iu4F>L;4;6U+Ej*R!#V2a%q;`G=<_j>d@rqM?J$W4AbERr9$$T#Oq+}Dm z67&Df{#R~4D8+ZU6VkJZo_h*LUk z^*s2_Eza96)Bp9H3J8wLcvk=Q!dm)PJMYR-+M|0Qf=o#6ZRjLcgFhV z{C~1PytgV0v6@T#ht(5`_1{#`!_McUTijxZm)XDHUlp1T&WGBw;Pv^{WNG00Q+i2Bsy8MS3v1GR5-3dF=76Zxu} zO6It1;0?g}sPBjZ!iP+^1=B;9$?rjqm*X?eMn`(UN4$UCoK$grMz*N4ZZErN98dBM z$_Q@~fotK=X5@D4C3VN~4%JvSgnr^J1Q#l)vZ3#KvETJk_5<;LyRu<5;rnu}VcF+; z)V8cia9CM|R<>PNUBBB9-bc8VWR(FUe7CN$9{bzzz;yDw?%E(|Hrj|zm5rkI?o*+k z>3CK5Pnif`q6HP_k7_W!k2{O5;Qm$T%j1asmQ(~=a<-z;nxiST)t{-ZQqOA5RTJU7 z?R5LEn>3iE{3Dghcuuo3FOvOr)-rq0*|HU7xi<@j=YOC)`md|jC^LjeHFvv3`+#wd zUsyR2^M`bY5rk;G4V{lNNuCz}@*pK<)0Hu`yqdk(&5w>XT%LwUSn>wm>~ z$nsw>KcwwhNe1TExXO5WJY@MPagV7E?e)LPU+ljLJC5ddl-2+8`ij*f=ga+`m*`{r zW6NdsZ-gC1=W{#4yAk=+^<~Xeo}h;yJ3JKT8=bSC`U>Ag(oq+M;(LPkcI_qOjoQqG zj^PC;WpO^$C-o~eHC$ETbUOsJl&*;Oerk|BEXe0g#Q7+*Y%Q7pBO`ebHAhzO$@fR& zciywT=JbKodymdliGMZVJ3wywq+Y3Dv>;M{1hDsWSe~u-9Pe8&^<{^+Mg}(EfzKbM zl#qEga5|~qz)lN(;xjzvYMtN%{>^_}|DlfAM>m#znCl1Q1#RpelgU5mP^NQg6{&yr zLonL5$_qyB!S^9CFTs3c)TXi#v3{`5p$$>E2U<`ys4}JZa(i~(L^_iYZ>1Lu$2_WQ zq}E#XcD#)F;qCkj%+HDsta#{KNzT9ahfhM^y~jWW)&rOiz`R_~IIQOr=1np*DuVK& z2wxizjHXQ=18lyQkC*-4F>OJ%<_%N z+T}YLNttu6LAD3+{cl~hi)8*aaE)wO{SmS8JYc92U4!!x%Wp7UX?X(Ymk&n`tiy3m zswwM3_{r5Vkgn=Q@+XN86Xe?Mpwbq%(8IdEC;gvKt;O6Ftfi#TqxgAtFX9h_7rTMih%l0`S$@W%-}wcY z-?YuMxZb`u;fExQ2Rls}&(=S6IA8xa`+Ks-L-2d=-|9U@elfOx=Zt&t*Eh0$xV+wL zNXVvGJ%^XH&})9IBl=sh{-c;zJKN|%OEqS| z{^xJ0C7yez)QlFg-u*2vSwt^=$K`1BwX(wTXUiVhdCHrmArS8wMtF9-EC1lzuB%&( z!2FK`vV1YMPY68r2>ZAC^KbU$_2-}d%<@rK&jXL1q`n=N`+I%M`p*(CW%?ZUhm0=w z&Dk}%EWEgkt(Ur7%PMklk=08_JF9}vkzg|aEU$w7?N8o+S60!2t}=d9g&M3q5d2%c zy0qQ?@^<@O73|CFRmFPmPy2_IHWUqBEweAL_sINXJ%g}6u-fZa)>}-&<4rE}wxV$T zb4l|JiT}&h=Hl8&HvbNKO7y69{x0UHVEzg^#o2+!{BHFu!kcHsKqltd`BaSOf3vR` z&)Ir4r#=45?GNpF(CXng+`o8eL;U9xuFZEj$^0ib^)dBNyPmI?A6Z0TJ%V4$ttMcf zj&1Ev_}2lE@GeNEU(w&bVV-@ymDGBx&$!+=Q6&5KI^_QLr$2nb{2a`~ANJ+(jpY^N z=fCt+q+4yqda_M^kDu)MtbD&_dCZ*bAFr?OWGloh??YbkRC8JUk?+^o{WQye$NL32 ze*^PLm=Dd}TwHir##ind4~xq^NxWkFxBKvZS)T|e~q{b}yUx8%IwMi*KCVF2dS zF#rA!`~7EyK$j9HV0pGbf2y5l^{{+!sEEU!@rM!}_UB@L zPKas=%vnP0%lqx@d564Tt;p}j{PvSwOLpCn_0Q$~YIZ(ZJ(uu)lhxkX`p#k*9&dI= zmX$4jX7lRQiT#6g0^HW{BF~Gx&u~|PUyk{=;utG!9Ix`MzmWNv_hJER}8%E0pPH_jiHqyxe{%zl-%Ly#H;^TtW1lpE(y)F3R*P)`#yqv{)=;=X1k+ zt4?@66Ix~WE9CXzpY!0BAnEZ5{r4vtdSce${g8#t^RBp-(n2@jkiMo=jxWO zmaLDuwfAS7UfLFCOq1nPdH?73OT4$h`70UcFFDWlTiAMFWRP8PXrqkZa(NKU9x)eQ z;eWq~^`Fhye-wGPA2n9N{)Fp;z+~(E-}*~`@*%Od#oI5-`d7!>`%7$n$krPO*Bjy8 zW|96O{7Tb)@O@7(cs_ef_@9`c)6OShz7X@5hGrMhdz{I9jygRE zsH#)_#zJeffA5=X17#eRh6+7UdN6c=Y>xvijM2!xii4IZL%%o9|1W*CEUo zN4eRm8Z=D(hO%}h8x9xy- zIXV&cFFw`I-TuG}8808N;(W3^wDYWmn!HS82)Sv(2ay^^w;rv{&@xpS^?YuxSez|3`~aM$~8$Pn71>@OwX^05v}*ya(?)y?S6+ zMPAly9*xfxtoMa8$IY)w-f;KWvujB0MFxLJ7!ba6>K=;5{1mm36$kSN^FH9X`DKC+ zvp-0g`I??d`09!!{4*K=6><3YjF>Mm`cC_fxL$EKzbC%}>sfhL$o%|nC{{7IDQE5X z-d)2_nL|N;_XAX4+ntndTOFO*ZzkCv=^im7{q_ePtBZw`WbvwuKMQhq`9f9yTEe?w zer-Fihj})(S&XeNc1)M?>lX#Ux5K`$wQntT#BL`QI`eQXekF=LOZ6zZuzCcHJ;VDoIiJ>U|C)k*WhMy5j-El{ zp?rU*$X8Di?Q>`ESu!WP1toeM-8O`156Y ze$AN$uaCHZ_TXB=+hV?x0)H9vZ6~{x3>YWlZyXPR&m}GZgKDW`oSoDMlhXfcKW|R} zR6U*qo2MQi{3!+gHP*x0A9b)>$q{pz{nLK4ptNohIX{x$m*{+OplxFJt*n1B5!cO7 z`iK;uHm)K5ChyNYXPQX;wiDNHlVWa4el~W74O{rklKw-%Z#f0~!(_kbN>1N_#y=Vg z=)+@D9~NQ$m_VEH+1wRTjboWBLuR0#eUf1C`!mG;uHbx>d|@cjBfpQKsOMg<&HP3h zpWoHx5MF*CL$M!amet?ZCNqNQ*V-Pz&&(1+(}qC8%llERem4JB;{2<~TTTw==Vl9G zPO*&twWR=6&KU}dS4b9ZQH!(5c}O1hw$}th0wSofasU^t64oP?|}82AJVgZ-%ZAEu?poc z+KBsy$^L|YXj*|T7|G^SexA6~M%^|qcsj9vDVXL577L-`n;YTf=ZUO+mJh=9p(xD2 zcGW#%kGVB`d?lO1GW=wEvK!7LtBN6H{mScYFV&qS|El8pT#W1ClSew2T<;^RhrL3d zqP#;Mq#j`TLR{ZYY3I9R{u$=YJv)_{pG_z0>yG9vq_e_<_zzpJPQ!ZsoBi=wx6m<{ z-|xeCD(KmO?I(8WT9W4@yFVkp52M&0c$Er7e*&%tl%K>h_6AL*N|N?}uVdOBUU(9R z?DysScRA128&8FEn5SH|vL4mvBy4=Xep{bkl{x&k`a#bBC;Qh8Z23N%5x>bCEkEvvwzn-1bEZ;C?jxGEBT9MBrg9imMu8+Et z*TEWn_P=?~w$>9p!)g}s=bv`?tv|NV*pI~LXSly-jBD-Jlg*2Z*L)!Lp1eQC`h&dxwn%}u z#r$$p)8grw?+7pNzp?cs%SYgPMsEN4f3QC_KL~X3eNDEWl;@-LcKc5;uZQh#h_fud zllh6*m**qJ{di$k50W2e;`}uj^L~9F6f58OOnCWuG25SE?X&06iacw7_=kJNZteEv z<5kq7$p2|yu17KdD)JiGex}*O;vJe_i5_|WCHo!RuVDKF)}3Yfk?rRk!ujY;X;$%l zb6Gz`-XBmLuayGN{yStHJ7yHG7%a0dAFrYwMgC9way^RqNY3BHdaSXY&;I$v%UlMM z_!Ip)2(k-h`_2Ey*P92_(Ek7b6-A_^L{vhG_I*2NuB&E3lq`|8$i5YdRH~D$tRX`7 zWCe!k_}pP$c#_E;i+yw?`(@;!&dwwcel`yBrJ`-H(k1#ZU``9HYxLXT^s zMAsUp;PK0Ue;4{cti3m|V6%Y(9`Bimm$;4+ZQo{!dEWnl zarsA4Jjk%=zs20&4_SK^X!04e@^hpT zI4;Ev+r#?{as5GfKU31D3Ex*-dMK%!tiPxtPH~>)?-Mwlvxi3e4UWIv%5&$< z@rb|K%J;k13))A-;Qb88|3>}iZ+imXjS&U5+7nOez=)U_ykF(}1GiuGn&DlD-ZRrM zl$9;L7YBPk9>@LsfBk`*e>i>u+Rq62hp%SPRy(5Lcf(njPtGj^Ry&O#_3jhb!9u+>v>N(Fw(T#rFq43c7uqGoRCAj6TjK-k|;PAk(Q>|Mu7-keH_6`ToH1 zA6ogOCv%BEi0^Ku;HPye1AnZI;e`C>e{@8bDgDAtKR1SM15$!{YdQJ!mnSPK4*V8(&zX? zt-S8YRF`JN@5qv4{{*kV*(e`}xQ#u$ehAW^`Ir9hirKI`;<@q2-=Dun`e{g??7e6P{5Z`O^9479}HabI|4)kCu4XC9sb&ijS8^8Xqv%xk! z3+VOwE#^-m{*RSr!GLur);7uoTD+4mzP0hv-&1y3g``cLb9z9+KhL~kW(J+g=An-SkG;BCQO z)JF}U8)H7~*J3#6si=+pJ6iQQp3`?m`U_k2t0I@csn0}syzuiI$A3Wfyg@vf;#sW)3<2HOfEV3-PnR1TnQAFVf}2Az<>L zf%kWfjj=uac|7Rz zTddy?=~K-r1&8uom%Zt<3bY5Ce#( z4CStnN%F@&`GWb_cO_uPWurFx!Uv(k!ERD&yl&wo)PH7Ljl?{^pAoia1>#c>Z-V#> zUCaynp?lwjRw0wCbNsh=gH174tdYK0M!_vuEJ%0`7ZU zUGKKvr66sm?LXr?Tst>Oxpd8$2-R#K>EE9|MV~YtO^nA{^}d;qfNR(?$0!O_aHQ2|rjwxKhsp7MRXUhwgcLjS{Yh_68W0mO&d zta497KR-UDL~!d1?|&%VpKv^<&+$G;e=E}0Y1P+xZ2^lP{{+JQ24_!Yt37e!7Z$m` zkU%4?*Vz728w+^q!A~Gu-xK}>;(TtuE~>hHLw*0x;p)6(Z6D0@>$}k3EK$HGA->Y0 zHgA&HAM-JvXTn`CoZGa||I)wZH5@qhaV)c^VT zuQv5K>K$db_MtrlRKxS_&8lmFLixlv`}z1{VZDznBUNNgG|I=g`VY20nW~Au< zJU<$>r~2Cdd0vy0WfFC*VwV}H|E=1L_4#;Y?)~!ijB4d6#E+R=R+MrM`JB0WW1f$9 z7S0bsKDJeVU#q@P;7~Xy`WFz+KSF!Hb#W=y_!9@~^zyL&-p@ne)WyHB|0>`A$0B=< z4VC9H{>rkY72}JJG)Q30qucm-`ToY)&yRPGKi`=TJ`zyDD2NdUh~b=qUM*Vf4->2`ojLqofrPzpZDMYQEJo24z4@xitk&Nr5^?% z8H4`0ANzjyw{-t>7ibn{kMCPVx3Yml+@Lo5hqNY9LGqkJsmojR+?$TecIlx0aG^lV zjmNK(ih&y(gy(Z^z5aVWE&kmgGc~e+=L={WvwaxjCIUXs60Z9#3S}?o-6u zzHTsqriYpV=L5sl3%HN+nY=EDE1LF40!vCaicS%FvaAQ2ihZgS`3%)ZOo8zodZV^d zQi|N$Q+~=Uk!oz^uOPm|WDS{K`KIF6=vyb({#UOqf%h)=hGS5^3oakyrp^jJ80mBT z>A&=i5|+S5%f4_l;yHV`{5-rqXHThtu}rIJQ?cK5g}#;Q6!-swvtP?@nRxf9$Dr=yn!{truw^G$p^QEQlt>L7b?(obb zJ@J%h?$GkRHRkX4Tqusrt^?-%A9>2x_{dG;w^A26Hxf2L#H{FU2?uWP0KRUuhAsnL zu|2mh28%<_)B)qBAhJtmZ~4<-Td2iZ3{ihY%v2K=aP-P4#8hNk|K(!Lt6hl~m)6&S zBfAch(3_IqU9gS1`QRvF)?&?MWV^u=FIUoAH`~HRhpaHaY}O8Oy?!Ow(s@65_&O;+ z;IfU9o?l1X{xOH{)pqc_j|MQENx-VKf!O|4X1m3E{!{|-{U9<(f2iD2eJf>UphiSQJs_Is3LkE_!u*~$YsGULYQTya_sOmO?c^%G5~;=SN6S(qePy>JB(Q(- zYpKl{bVr$?s8`Y>e;HinVh3A3)sm0x%;aLjM9Ol_2dUA$VX{N$95`Tqlhh?y!t|Ut z0{3T2HXOd(V+T9E4kBwNn91`U6DWzsI_cSl)v|}763EunNRJ0enDDYSn9np@3UAyr zg*$SllH(Sj{M+Z&QkyS7lsYF(l|`ew1f^3-vX{{kCiM4o%pWj`fcKu8!lgmMWM?Nc z`R0`ERG>$(*!VarGc1XNDkwkFWwO6GqpY8Qx(XJ3IoX4A>FL+#`M}bdcawdy|n#A2ut$gY4w96(frW?^A*%J@uS`) zK-b_N8KG<^Kf5`ZlASbV7M&{tm&*pg?q7$~;sIi)ud1joFtFQwar>Yv;JKrm)T$84 zdv8dl;zzuu(@MI*xj(F6*sp1#=+6|KQDlPqf3km$IHKP*Fg)fGIp~8(9;%l}RqCh6 zQmfm^y4Xr!qLQYpISa+KL}=pYCF_>ML7q0SU&=I6^_*DF8m*;1I%Gf zy?#qaqj>5E12Ztcd2%Fd_+ttqjR%pAYwYCBj}afKLY&skm-ToM2g`4DlRdi^$CMW; z?gz$rM!>C(Ca{yLIl1n&SiX45T53S39#Q`(SLRz42k*sol9@e?V@@y5#`<$(BOx)? z1owvt`w4m6ngq%wAU*42$TV5fezfk%JIQ{d_xs&<8s@`aMnXo(01o`|#M9c0kgwRk zmQvq8ny63OB%7s$;)<7O%Ni*Ov;XZn%(onlgs!XmLiXu1@=}t%e0WMS)$ev(aoEhs zOy=u$un!f;Xt}MX8@AWt^KV^9cVgj$3&3vjQ&Md~Pk9F7*H}L<&N2#=)gC}KY&yqF|Rc*nX21m&zx->EnEHG8FoolVYEf&%(hHLzO30D zR}q&MWP|m?CX?>#_2u!Ck}1!lJ(<<>^q7sEG+@z@BlHBzdfImUIQ)Lc=By%u&SnE) zeVmDDB4$;)!?G9OE#a3Z=Lv_#IOeO{eryk)A6S@wc=)Y*qJt6VW!=7_9L=85gjEd6 zh4}G2ZXZ7XuoY=Wjya((5BQisWz6a$-a7rfEbVg~bn)>dd``zPF2)6zFHczlV^mDw zq<~ATWtGZ@5TT{|tsL9`A|pHw&44BMNYPK3$6H^_(?@-JU%3TpiX^ zE{jd3MzFRpbKg_x01Y>oX)PBmeJFv`Pqinw_0E90NI0r$qdzMteU0xa*V%*G=UPA8 zQBiYcQ+A@eA|or}#bh+Ednw}M4DuP`QtU*`4_l`%--_N>1hL(w%+QUgsulli8;bQVcM46cnKqG_q*3d+dy~GrFf5sxUfvTL4(!e>b?E}jraTibDR6)vEfo7P z%L;}#a<3~P><`!DX9D0g3U-K8hcypF7#H9m?&l~&dzg60_XlBrQ(w`6wz}mC8dpZa zQ1iabmsJ2Nrzz^$@%^o5MiY4{L0?{TdK*>N#Tzz!q-DYH453D~4wJKZ9XOaY7|$n3 zoz#hh!wgXFit5E=_ml@6+D2XUb%q4a4d@Pmyu*4G)_C~l$TKx1w&87&$rm!zzrh}r9<54+r! znQV`PH`O}G43?pM%RTSl{&OfK8annigI~rRAnCWH{JnfFHLHKNZ1&AJvfZdIrFQ)% zX~(W{OvdYvn6I1{1u9EsBgo}iMp@ZvU%6yV0e;_OzC+XbLYcZ z%n!I54Fi{%zy$s0o@X^JKF57+9-n!S+^w`?uumw2y$n@%X! zBkO!$`Dii$eUDR8+Nu;J%RDH7t5$2s%%UWW;p0Hef2dgj%X=BXf7LG-o2m?_7!8N( z)6U`kuv0Yv^vpd2z8ncA-Og+xe_hdI=LC*}BNl!WXTUB@x5<~K`A<>)gx9<9`d+ni zAlz(`4)Pj;$g+L9)V!0Ltg>wYoUKll zYkmatP;(v7x0+0F{j*@FC6t#hh5xFD=qZV0yr^NI*Fi;o=2tf?VPNP|c<;*u(wlt2 zemDGdv0uzo=;o?T4}aTCudHeZPR)shN!iPA`$Rv#4oKyrpwFjZGV#S`*2uXVyHa%$ z%nuHibsee7RL)sIoI(4bl#c1x{*m9BKsP!a92+p5>}EBP++*IG9X50vG!vzZdhG1N z7@99E(n9wYsTn`;eaJwG5w!hs7>G@$k>*~%NKel$Y|PB@u$Puw@svrOn7IDQMbi*} zy?Y(@AIKua@Z-635K}glH1blVc4l{BPqrHm=Zz+%hhKGKk`J#dwnFzC^n;h!o}rfj zjLS?1x_76NsbL+cF*m!g=@kL+(UWe{zM{^|Xsh={86PF^2YCSVNgE-$J4|m=kLG@5 zH`f=)hHV?^;tktGOyQbUqJys1c)WC+aaU|>Qq*QXuW99z5$~!gV!FOrB}%!h;2(6I zMA&MbZnM7d`W(;c7YX$F^+h;e3HeFg#Y}MTRU)60iv2l1U;S%+vCV0uCoQpL-mB{3 z`C(!BKH}G)6< z*B72IdUgzE>~cQ>)1t@HG3{4_;`#T$qpd-h|4sBI%(cqf?8lxrt254f#>1p3=~8dk zW8j<}0Am(}V17y;ZDP}(8$dW8N=w(!-CdW%-KWM#uS(Z~4JGpl%hM5<7wM=GzMXHk znQyN>FrZz&#=tusCep}LzM%iyOmSIBH0B5N_$~fr^biQ=&$+cfWaqTd+^E5ZEImRI8F zqBocyx6W5CKD&*YIPtCB)DdQI%Oy|v>H2ButU^y%OqpT+pvQFa6I8EII6v!-DwAo* z;$VGy4e8;}>V*8f;csRREyU$;ZK_zJ~-;A(^)p5JyUEd=yCW%#f2 z?`n1hv|Zx@I()4abMs+T>3A>$sK9@nXOHa)jg* zgS+GZfPd{zE`>2tt?Cu9a-%cm_c#}W%`t!eSugd-;T&4su^l{k*c)>C=DJ1Vf{#UQ z=1*ZguQ!t+b8iFBW_LER(zyf(SM|u zYKb-Nrl4p4fBVUrR=yMBzZF;zukE)L%PuQ;em^PPul_H6e!nWbU*dS%R(qZt(kHSK zwiOe%6#5V6Jtk&W=@6-N9(Xz(*Ow=v`XJxeq<~+u^687NGvMP6S;R`CMbITuv442j ziy`LLpAZZ6t)0a1(I#uzSz9aEGHHmo<4NShRO*A@@4ihJh=JmhVqv|KS12F&X^Dz6 zmk)eUl&Z|O7S)^1QN#WNe7zFkeb*qApWGju!THSN!VP7cZ=!ti=|-66@4JNY5nNud zojO?#?G{~d`?#r#i|SjH(>YpEuP3aF5x6ls3dbY-K>nOn7QZMVZyMgiWkZvM>3Hl4 zUQZ8%8-m@#!`s9!aQ%eSS8df-6X{PPOiGgV-Z6Z$rF zYqUIH-Rct7=i8I(&s==Ucoa`<^M0r!*GAdpv4zxYlxsHhZoaQl73P=U$|I6dZCZ~1 ziR@ou{;LgdgZR4gyZKc?3jXoxT*50S9QxPScyjjlLC)rh7v`TEx#kciDT}Bl2s;TkZi;8(@9@6rPu`Y3WF+{?I+)9-l zL-{v60+I@g_A2t3TP~Re7RS$kXRJccaQw=HZF!Wdj0um}TLw-dC5uTf9hB4i3PoOE`rxp zc9<2F|LK5v>8+7q@$wmv%=9BUd-fxHJX`gnTJ`k>`o4Wef|#|7;IXQ?n9oK$XAh^p z9Pwk&_jF(Xu|+M$R#@NFc^Fu{auM7fnnDJzLGev&XD`mqFV{A^UQg@kw#j~nYa|*k z-&ZQW?nXOz=kESviO8^8b3=f^J> zuf&fRp*{XqDXz!R{NSt8c77;E`CB;qgOL3nH@o2e-+Zv8zyp12+P!K*zM1I_Y>!*z zGvbN&9Go9!8sax0{zEI@gm_ETKQ~_p%wMoT(f{>qI)H4GIc>(fkmvMM1p54V7mgPp z?~A^5r$Yby`wvR7J^Xm#>X!{c^~<>Yy=OxQ=jGLI!#Od7V-Lt8MJQ%iI~D(&w@MBE z2{nOSJ^EFs9=$%|hkUnUrg|=uD#=A~)V@x#v#TV`nn(UJuHB3dtHHEKmhfNo+LmvP zW6r7GBPK5SiO)v^#|6VYix!;!_P^@2-Mk*hJfJQU1GFDtUUTJKSPNV5ey<$m1LyJ~ zmyYOI$ep>0it3AZqW*R)+rKc)&kR5B;5gr3z^L{yp&#`c5i zD0p>l%Rlpz%TPYv>8>(jPRLno&k()E(9OREaP{N(`YAp!IkLOx4pZlGq0^}gam)|j z93oZjhV@r>b%#UId?>7+;svr~&om@VZsawmyqj^%N1ZG}M%iKhR0@Eb4@BX5bNu^y zx>_nLX^3OCq+fHomKMkC_mdK<{XHeXZGWSr&35jtRGX?4+|Qj`1FO znK*AW1oMftmauq9bes5eVLtgUE>G!S3%sGD*HfB%zrK_@xV9GM(ELZf`j)+Uv{7gX z96Ra**1tHlBUoD19TMFicyj)nk5GL>3gxR8J>Ktn1m&uaFLPinTxlYn-ctCVZ|K=T zWSrH;JYV0?P}Pa~@pQjyS$ii`OM3tlbN&|nqVE8_pRauTiYQpDjpOBce`)u|z$|1d zld0?Jrspt#St!vdM4!1Z->vsY;*(piHuV|Bt^8@k_lh)SexB)8_-X}-dA>fQ(0lON3ABuUk zfp>`=fkrs~i1!B;@_qdJFvt4zD10~58}oNMo*>?A8qg;GTFC#c&&OX2>rs}p+H(ro zGqBW{aTVzm8apfO;pv>|8xC{-iS|kRpbZY^9cz3hk5?L^jD)=y?z5GxA7n5c~0N9pNi9Rq`%_Q6Ks#~qvi0t zi6Y+=ACKVw@)F*Uq4qB{M*jcBIv{_WW*nYR_;>_iyueriZ-Mw5tBCx$ezBP6o9NmQN zKZf`w6fd^c+#|pHfwu8zTz})9_XF||7xMLpuV3PkKkK@J=i}YD{=kn%A^*3&s`E=g zpP$$!eqYFcK>C)?Vg8j+g+BkhT)YEs4>wnH?<^hJpNjZn&j#l&v{A&n@%b)<^;miS z2;#dSK5nRQ{)UB#xc&8~zXxltO>9#ymFNFUpSNGwK0MFqZ$|oOB8TKZO;*^$w-4Sw zx8`@+YuydUH@Q3xb-RS_ExsR9%6}Qs0o%jpcM`^1?Q7*-5q|;kzAZ}mvE>S$kGB%W zYyU6(Zrf+VC4r%B;=zUbB}o6+NTvMIT^0J{8Yjbt!$Wbr9v}bJqf8y!&)G8ojTI2` zIpVuygc;9K91?_HdJrA(-ZVNjwToIc<1BYaJ-}-pH+P;U(=Ps z`Kk6DRph7Q^9ynI^YMNEOFu7iHq;6ZZ4=+e@x1=}74c46kUhuO+{gCt&-=GMH$Q4S z#3K8Tyf1L-jNX|M-5d8G-X8qC=)A+{Q{ws4fyGW=e@Wo!VnfXH`ILn5m7G01|F{06 zu_4fSiNb#2^YVQ0+I%M?WY6&n`dFWTUd|q2zNsS%wH#I;`{zuSI=w~q>{?-jc|PA1 zXAd90$=P!h@iB;ZxGi(4L%dFQAIy(mxfq?TTicU6|B8O$cqN{n`yl;{6T|JAlCamOF-xc_jxGJ0N~=kyca#4(4h z&bp|AVuG`AdhZNzuRbC?e))VF=R;fTi< z9}mm(sQnA=8a z5cOv+KL*FwjM2_P{l9RS(;Qb5SqL-#l??N#VI*;#a&D7;^*1j5SIE18*{*wDgfJ5f754D)zxa7u^MQC3W;&za zflliq3LDEpnH$HB7Yy8}_#VyM!>t$meG%2lJ0kwW#Zcyc)$xM$g$mx+wU!tjEN*k2 z<#~rz{c8e!KA#3>KmWW!{!3LTGcxLU!Kf(;eg1ic{+#=i0o2X^By+472s_q4CTcX$ zcgqCD_io;w^Izw|=j#ZW(+T8nO)B#J^5+ZAf0g$y75cl<$8}^QE~5OU_Z9xt12&mK zCO5i`KfEv=VZ6l^*{s+&c;~Da9*-ABnZb6-5%|2b7M)K&q54fx=)B$~I9KW}wG@Au zQy|@Z2+cpY75=OI_eX8ye^`0g8P|Kdu-G2;aovX&3td1U$n`3nw!USb!-p6-xKP;YUS@EJ`26uUN+O*;!h~}k5vJ%gE+KJ zK2lzvoU&FtjeO?pJSUMdRpnR&_ z`lyZWhdI8cmFIHx-54@LwC9(Ce^;o9zUR%t_sJZ83*B!IL-zb^wI>Vl`*MbhHn-Xn zhw_C+dCbE3;)LC{b^_C{%HJCI*Regk1t{W;q2!48;Czbjd6a8Vg|N8 zYMVckM`_bM!-gFimq1R0AsB9#|l<)l?Z;AN+r<$C)AwH*rV!zrq zEEFYa55j!L>#yYaRz3siTOnTMFa4VZi{Q&uL6EcONz+%dxYZtx=k#lv-aEZV`?qxq z75ho=%~=04alCl_U*9!8Cyc(hRLJh zd81~sYk4DC=b6!lr(fK1dWU!i-PZZ-KqyQK9fo<+=U>UIfAN3oo4i;A9hMBk_H=sr zl|1{GJzalQJ3T@A$)6SWjK8x8x&@7Hv)|iLrv88V&6F}1GAqpG^b@*Q0oPCX{hn|? zDdhLep2M7Osi7yDm7eE#em};=i}Cp%xcI>c$A~)I?Y4<2bL-FtJA+1quSw$^O5j8^)G$?JoWc^jpI4} z-XniI9z^Y9QP?&=@ZbA$!*-qU{`~T~Ij&}BLzo5Div2dfKmU9GY=n4p9xpUFJlpkm zbqI5Jaohbf#|zIRoIc0@t(m!bIyIqPkpQK_x|R~5DO2DfE>@&GZLeEM$Q6$HR1!;uTfsk- zONrtU{%z_X3HiVE`T9q~eAGgI57Ku?|B`R_OrgK)=Qd)=$PsPUubdEPr`aqLY~u~F3{)e zNpbe`>!Xl&L;Qz|FV2Cs3jMh`n}{f*iTL|C$8-51x&Ay$F13Gi;-+itn41OuvIWGn z?TUDoS4Vq^M{OIU_@3UH4^zm8A%4%*1%!Vq|75IzILTlPj9i{Va_`F&ov(%b-})t8 zREXh=6}%P72j%=1pNRB3o?AdHXw}y_s!Y788`CBqcvh1Y?msJ0|Cxo}*C?Z}`Symh z@Oh*t%AW}7FaozfXAf6D>#TqeMZA{o*Zgc91<%*d659X2^m+U7`LVTq&b8`u{W%Ef z?;Q9wKgU#|AKteEp)2>r{rSJ@&xSQf7;=h@xWf68m}|Fd)zOg5N(Ds^9(wjrF_uU4 z_Ge30=)k6D)=Wdm3;L7BhY`@?qWFf#{v5IDcigZy|h|xLU?aN{!mR<)O}L?B+M8i@m=A=H?1>#+HbM zuMaSUZ+Sg#&&obZz+m4&%=7j862BkLP|BN+>nHEm@Q^j1NA=~y6hBLxyFeAE9{71j zqx!$x_rbF;GefzRpQH<9vkN86(4`7~orN0od8XH!!{I=YEGf4< zXLhl|e}=D5?T6yCxcJ$n-(n~sALmKPX8tJ8Q7KaJvtkTjyYGhB91#7@vZ$Ldjgi>WGGnFT9h zYE7mQTGLQYv4e{5p|`Kz1LyZE>M3*eWFMgRX^CB$b>c`Et~cKNySq%ITEbk4RQ$Yd zxh?FqMp3Wanb|=uE;`4$w?9`TskkWK^G3qxx>M4h=-ei6zKHvO|FS{w_;-ap%9}Lg z)oQ2NZG8ialct){gOg&I!?6v-hq`E3f6A2R+RH7t2$(M2g6;pWdgUAKmqYf`IPiO} z5$4kmD1lcU4gz8S86Osh``dvWRku}WZSO}d$TRP%$Pdrg4;9uY4G{1Nh<PKX2F0 zw)x(<@x|wp7xI7W^ZDdCp6{RI1o|;ZU(Iu2UeQ>?N zmKvJ>U(TIL)F#i%=h}x~A327bZwWLX3V9908_k$WeBPws&3%`} zCV<1vS=W_{pP>F@nAOX@7WJQW^fRYFtsKH-0~P%tYneVZU-ukaM%at2nPJkhA84%$ z?ZEe zh#%R#UB3N))C+j>TbX+ICX21V zP2{VGS?m+@5&4g^1{Iego(QxoPD1UOf_~=sNxp9Ik4X~fc;$vi=x~}`w|21CrtwyV zk5a|5*dla~X3-?}IVfRn#w+Ss?{_yPN>jcQ%7{PswuFSq3jQ(T;}E}TSED!t@eQr~ z3s+sD=e_Sl&!y|~jv?L!@!Yd>yae$+2b;uJh%Z{J(ANsrAqLKFB6@_b%j4_`LH2Mw zXAh@8gl!U=AU?a5XO;~hR`qWp@O=XM9>>*-Nz=D;L_T>;(Q~yTu06H&_d9Q^RJ>ol zUJS?2aY!Q<3_|zqh;M1-x%YMbP3G(C6#ZaXi0&=J}0?=k)s|rQ}v5D(qQP z+#VL5o7KjjkD{@8u)`9o6__H-4uhjg=I{^9O(zM%2Q z{qBsz%klii-4`*PMuxhwS(I?^sek%B+Q?M?~7kc75SIGsf#MI@M&@Vj`;b=_i$rorqtdd3iElZwZXL-58VD7zYO`Wa_?^Y@%GXO zXzaH9mN27dDM`<1uf_K>U85|(hB^h$&kr1*jQASF|3duX<4V$n0SdlztOb}o9mOXI z{0SS?Y`FaZH>Y<|CZx9|Hcl;Uy+EM?Su9ky0%}P zJC103J{6vSg*-%jT<5RO<`xQ`KmT$z@cS7d|F^zyKf^ySY_*3wk1Hen(__CnzYJCA zPgN}<&Muk=Z(Kaip6qNT4@gy~Rvg!6{B``81XoM?W9%4a&%rLR-P?|M{4$OkfcdN! z#Jw?j?4`f~a*ZMFsA~;QjQ3AVM(d6@eN4-d`Mtsxu9M|~aBI|ke7-wTI~FdfHUM!?Z?PL#S8Cq$i)7!LZcL?UG<>o-P`0c0XsBA-70xPn zf%(%bM#1*I)j-X{)9eqLptgkVB&Rfh2nm=9d(YhsVmb zfu_v?_WmXj_3-Qx&y^d_(4I^PoEFiY{xD%NTxE~}{N1ZDpLS<59CpzWL<}!yf2Vbm z7wq{&CRYS8hkDe~qkY2Y!=9D&IaJTmY)mJb8>15%a=;@k74X~h7CUx=s$9dSfsDxs zWisBaqvuye(&OGq=#vQ%uyofAtS_#RfiWA~gKF12_I{X(9MmR|og3#dYBrnblyj-{ zdiNNb__YkOrW0MwOJ?7B(a3E+ z%0h&U>NC(IT338%d*KB7@^&5FDe9HF&_Q-V=_zgVY@O73fdq!^UyJ#XV@a4^0fG1Va<)`Wn<_anhh$HO zGs&OCGE0-abkU#`>Gg^@D0jMu`IuRr@Tr|K_)=TWX1&p+48G4IduE3-L7fK52F%+_ z$M#w;?W~XL=Z;h4e~?#^(67HSDCtzkR!;6sSr4ftvnHW$rhg(twq@OD@x!HJPiG0- zn6@A5ixa(ILBStlh}l!NcZDT&q2Uf0jNWgScr)1M_D)usy#q{13WG-a>NGcg&E5=$ zCw?lSIqL?yyU~ey)G3gh^k6y@d*v!vl@dk2OSlTUf0z$-QzsA{Up?9nhVL{6I}Sf% zyN=#XUYcx7U9LPSo_t`ncsN=cf7mBUhoO4~`K|;p$7`Rnh2Q<#L9OI%m~V*eLp{E$ zFaCKdMC|%S0%H?0q?(Nq=6(5M%&(hn3wL;Qh6_Ln%bmwxB0gi1t2p(3Zt=q(64+qQ zNIj6gsZ%CCug%(N1KU6D1UY*+{#k@E1x;CT`^@^{?Oov5&Z z2BX?>`NUX0{}Re41~q0McD-ca0+(1u$@+JbFxQ@i$`?&Pdgi{=pq5aPb?EFL{ zx#b=e>bQ|Fv$9&7Sv0_xwzluZSnVAQuX&Hc^U2t$JAu9PdqUs&ID7tjZ@Ff8IyvId z2qx;KIggD%bHVA`dX?6XIMXVle zBVMXte)@zk(Ac97@XI*NuF-d);*PHN*#2uMqu<;N=uy@5@XIwo;xiqJf=SH70V82M zKhJ;m&yti^pyvE|_*r!a&Gol!-N(TCdC?1~`vjp`K`Oj#-52veJoZzRKgW%+ddGPyL8 z;3~0tWh0x2*7pyHHvsy~dF3zQyRHe02!HxmTP`zCO+p}%OP&g{HQoMWjbvE{;NM4hiNX4XfVFqp+%k*P6 z$WAOC%dC=hg0oZ=`yY?uU0`>7SE8oRGuFDDojjdQq|~h^GT*kTF^k{F%WnKsV+uQr zg)=Edd)kjm0}-9N6SjlP*eUDH<)v1hl*b>EnQU#$>>w=ZvkwO_Hy?`OcpC*@pS=(G zY&}kFI&z*3yy_sg|L8@1Hq~HQ+c8XssgK>!nWy#v%+(goZ9)@(b7o_B~C*zYA< z-P=s=V30^{O$}on_@~jIBm8AwEz{}COBTZ6Mw2kVYRGZW5@<-=iNDP*yx31ZsRdHs z22ExBdcLJkzv@Aczx;#tyAl9{ZiHdp-Sh}(+I@=XBzwf__U<9CPIaJ83==b9cLy=E z&uh>_`&%&wOhjcYHH@xw@zPoMi$v{>((C^L91(-13^ZsQx>+ zZRZEO^v$HX{xj;`X>cIYoQPe~#AbHsE?+$k-J#ze!<5%)G40yzlO2rIWG)@@hxh*| z^1;#vvO(>{XB@Mg`Dcgw> z#&v#2@!byFWGh2NVk;TCPkv#K`IASjpx@c+#KrrK?45bX$as;OQLvxmIMAZ0 z$j3D!X*8_M+)niBex7yf38=`a`cx?xM?V>bd;oVdQj7ahFxto;q(yGU?ejZjEQ;?a zC4}o8*H1iZ@8k_st#Q=4bvF;7_3rB89eBO)%cj9Q-#CI>@A!PDVei)vr;Ov6#E1&w zku-`4^E@qsmq+6MFEgXTA^&PI$NMkX2(K;nbh?< zw&$$j1(0>OC$@**4{-K;hL>}LmOOB59$lSVkKV^l?`;J4_dX66LB(-}JzPG(pD4e? z-fc-Dt}e)ouGd9B-QuM-H_M6((B6RKnZ9AL-85B*@?)?ZKmBvU|M9_yrz5sW*T$C> z&;O+0ZCaK@&2aBQpBP_HIS{ z^GL<_nj-7(AYg?Wu*-YK>ap>jYo^~NH@%5r4)qI@sxCI9ukTpe>_DagUH~_rOf#}y0~S>PTD&=?GLltV}4JnK0MUZ1SFN0 zvR5>1*xw$r$VENScu`v<`?Fvdt@A-FGe&vxMtKaz{Gnqlpzys3*tPB%J86-I{LbVN zlpdOf#=m;xiG2PcWJ{#R0 zFN3YO> z)7U*nJ=lFFN}h4s=P(XO7RY9J-lKJ%nab|3kidW`L$N&(2ET!CdoS>-B$W-xyvf>@ zkMIl{K9?b98q)WMmC{?2RA|?mG4SXwDdxrAcfboD3S6sW*nJgc?4*P+^1Q|p#_O#< z{bTSR`WLN6lMONOhu3k;8${j#VI2p6CgnHm$$974^-*qA-ckuOM02z>@$o8|QzuL5 z8}xny&uC-*z`X}xF~}ua+BLJg?;i5p>FP!iRVZe8+uq_KMtU+QIK4RUjs&`Ab;W!` ztPVV&n?+FHksTIJ_3D!E`yd5%&kt3M1%|(1%DO@3EF6wdD!5X4HJWB{0?f z2-vA}lX!u054nvGW5!zK;PtEiiXp5>T}Fh?c))J8RFbRM^ry}|i-RRyRud)HHxL!G zR}m|s(0L*DiHLi@U+(vWi~qzB>$e?YD?X}`-p08e)?Txjx;Yn%=)%Xeam`Ti`X}f+ z$%FYvFoixf8*!F z^()<(-Q6vjfUjyGyk47GRreiybQ{EQ`ll_Beuf_I|I453CST1Zs3qup^ZG%N=;GBY z(oAqo1X0d{>0K1(cjGIj@ZH@EVwZ9g+wJaJvUo->>O?U5?)$t^G;Q@}*`g*xaZWYL zSG-XR+jG^z1m3Z$B#{5r|IO!fPrEa{y{wtDwMoRbvVqLBj~1}n_|8`YgF>6xL#RJ^ zjSi&lZt`VrN9e*U8U4V(ks9!ea!+{Uh8`Y|gH+J_-DN-|pQ&R*4=BrbcI!i3sg7nE zeV)q(`k2#;>#Jpk!{T6Sf*;m5U&(+yr*9K?Vw>2SCED^sM*;Qfd^D4_Pn*u{phMr! z)u4;Vp?v!z6!ktb8gfA#l|{@OvW=}A)Lz~(|A1%O&{<6PN9mp8L*e9HD{PNRW*)eIg8(BvC$l4$ zIZ+d{`>`ERJZ}%t574brg;{>e4jytdf{!~X{K2-2ng!RkANSAm(b!MD;O}kjP`@D^ z>tFma3C_(Ffy-9|*soK>)Qz4eFGd|&OWU6agVlN>y7X2Aj9M52jOSd%?f)_Z)sp?< z4Z2Mx*|R;IP6^yZ6!=}Er zlV@zaO)gmXoc7U1Ii}ou(YxGcG0IzNLGqD11lRwo+ed)>1MNV@8_1^Kw~+e;$UK!- zH_(fh1u?_{FZx%-4CbKLb6}iOgZUfJ0zebGA2TutWwUBLsCyQEWa995GOa(cP}Ipr zR=Y12P7i1xda0&j`v*S_hAu0OfT+lUJyYvRxn8y;w+7L&kb`mX&|s15$A~znJn1~4 z6}KJp$}V$Y=*50u`HKE*yhKcWUZTc^HI1fo(<7kmDO;L#iiQs^d4UrTaxvdw$$Y3? zgmOTfAHx33=r8Yh^gc7m(2Oywab$wpZ=#Jxdop{^^@BUJO>lp39W@DrEulc5vH`2> zYa%zfk#%uTtR6FYoF}6bxr6??*NS5s0cRw%;c4z5$OZf@ODUUb< znvQ%vmznPi=%mvjjBi5$(4Ll!+ov*P2v`6C@YnUh_a}SK_Mw_hZKa`O_ew+2JU0)t zNX5wC!}HI6JRhps*+R7~TJYpUKYSmu8}aV9drKQ4Iu}1jex>Xfec9t%66R}eKYV|p zKhXwS1Zcsz&qlG4C)DNM)}BaNcbNhH6M{f} zkG`zCVHde>4+r*O*hI#4SqQzl-~-)lmLt98WF(CC?~nPcZ!&OKe<9fM*pM|n*Fzo> zX2hEG@n;$i6wr%%Jf=Gt?4tEngu=S{6ER@t0_2lzSc3zZ~O<=O; z70`y0N@=am2WazDQ%whcAb&O!s0t zc&*qbZF+F=wk*$c_RMItC;q7mbJ)Nh23oH7^QabWshX!(BQvRS8pxf}QA|EJyKuHg={X{Fo2~B1@*Rz8=rW zl`AyZDdpoChqcb)gW2lLAJgCBXp|4U$>}-n51$6Oz~TWxpwkx%_QBU0()^)6o8dTu z8Bw`b+(${D5uGz6{uHD7DWk7oe!@U!Xf|R2_-J9t)_YtdJ@v-1FMjzjy63)$hp*_x zlqGo+DJLbcv)Mw-KS^}$kY z%+Jmk1E22==khtQ!%+SWE?((~!CK;OivzQ2h!gy6BVpDbIV2mcI#tZQues01Kr|=8 z=zziO$!1S{KSSS+CH9YTWWJ+(2p2si%z*M#SvO-p%pcq12RnZXhkF7jwx5L;=C9pf zDf1id2;U-p1ll`wHQ6iskUA6F^D5F09+!l}f6dPaS62g(+f=yq^%}t06Ww7XT=8fs z7`nogEh=}SHt5e_AN4b1est>$HT!m8R<^GL(@FzisO}-mFP<jx*hbGZ-)-ih&@4q9{hhKpqqX za{y5cC?e(@W+Y5cOAqFRVpe1oF(4``X2rLvdiwqv-gWQu>>pk~``yzu6;7Qxb&7IL zx1h}z>Nawr%R|$5(B!!2tCRH z=7R-UB;fg~!?A&&&){%!jwqSg@@s&&DZ@m(!sP`;g`|R4qA>ZOvngQe!CK;dc{b(^ zcZUOO+e?Jj>I~++g-A?W4-%hue@Y#koeJ7794yy*oC2PIeL@t~t-<`RjbY$o;s#=i zznJ-WTP$|58YB+5rlhXjOa(=nf66xZO9lITF~mPR*!k9G>twL;&pLv7 zPHv|+`%@Jiolb9rHIz~NPYkmJ)}M56yp-F|a{N5VU#=TU2_e7w7|WlyYzel-XtanA zb9}5yU&+(w;=}y-GoMfKA42(eayq^31gp=*pZWV!i%p)zN$_0ur(wFl-Tb)R!YI8A zo`Ye3iQAw42XpF6kM{~~)heWOF0<=V#SWk9uG=JvmhiII$bzadJu6&Yg9 zsm_W&-2LkMMcEQzkq=tuU2dCbTjkZ`y=$>g1s`608vwyq%?J_jM6HiBeN^ zco|RYyG$e1AEsmdOs}Ej{B2#xENyFMVGm#B-7h`G!3!M}lRGBS1E1Zd)cYmVe&#*M z4O*F)AN0>S@<-rVLU(U1^Q^zVSpK_4G)OarPRo5IHzdszB_>PdgF8ro{n*14>Yrnx z05F;J2i5>>VCHQz5Vy7KE`GBT=42kJE4$u4juK_JFFV!)-mfWR_wNBiNg$k5LFk@4 z&-}45Q^rsJE$W#zmi8qz=&Z63MetEQy6pFG&}hV-=Wb=Tka@aaiLx<|nEK%s%Hftm z@gcz!ddS>o3Q(x2IFk8BaeUT95S=^%w~v$b0Qq9?N#bwk>7SjyDfWII3x;a3>x*q* zJ~^3aCYlT$mfIHz#hx`j%&_6c3g-(6U|M=FMS4{-=-N()3@~8N2L;Jbi;K^M=auKW3gv2WpyV=QumX-o1&m{;D6;oY~2AxUV%S+Q#0GUTxq_ zZn)iz46eITZtK!d`Lt(b`R&826l3xY}1rRNwYs6XPeC1mF`?{=lCr6R~q`L3J z^6j6xD*YbJVJdD}(WjSpp^GINbncP?^w?l45C`kBA^XQY5Rj)DVQpD+OJ-~TuF8OP zW5(jx5W4Z=2L%y+UE#963mvsp3~2Xs%(od6K+cK@{;U7Fd!8muy&Mj%Oy7a|c4Q#= z!ZnnfXa1<%_oAut!}~={&bR*b9mO-nlr=`Q<(&TXLbJX=({m%{kM|--@tSCq&s@%( z-+f!y#r2C_0?xZD{#E(L$6_&huc}?}PM0^K@%Zn* zME9heVC_upFqYS>Y6GU{cf$V9KIs3TeEzmO&bkGZYvB51OaAFhJ%sU%$hpn<-@iDz zEwG=g59T@qFq}P?BN?}V*ZP=Wnqf(0-hgjNbkfCtc3G znV`2!e75iwbtnqf`Eqfi7EDQnxdGRSl>z>^zlD_!CRaChAWA&@Fy|lpE1zA=5N{1_ zr0TFHU-ZeXVbo>#-paEv!!iGQQYeV6?+lPVh)4G9fcC6fa|G8H-F4QGngILHaW^B( zcN`W9P8>4^A5EW^58q-TvRjhF%wKLx$K3zs^L((FcDV>M3E+K!rSVJfdQ|q+5@h8k zkw?BfFAvi=!Q8vKo+;VXhmPH5Oszai&^gm12mfV+@Og@R|!;IG1 z!i@T798)ngfbIh0<)dyG)5SC8^7zy69g?+u@qDi5^M-V!=8z_aeVO5uk;r3cIFo1YHi&MG31zN#A4)%!oDg35Z9#iCSrTa%VZM#|Ol*()C55!>GZ=2sy5$5%{{hhLvn zb`QQ&xG1y_)#1E^rsgll_E&yz1b)^=z^wbL^3nUxiE7=~Gv-$I^gJmgjQZ+Jr#u)) zkjG$tNCkUe^~`A((0)k*DR}a<{G^7aQut#9(}i-QkBn7Q^o8frDdxxE{hbuB@JktP z|Ck@=Na92inMT_&#iN>-RWSpZ`J(akTd{_s`_VSEYuj>a%-t05GhK%HX>+fT>Dy+L z*Qv+lt`|BguP#n!vgUcxi*>RTt^u~R_G)*9@4ZDJ{HDJGjTh@6Ir#^EZ|AHxu!SMbL^Cy8gJA&PhMBbkTehljc=Uz3n?wMlo$=*)~WDeL_ zR<#S>E7%f3dB($hZ1qx~eRB4Et;}gL=ySn=OdEWgy1Pdxeld9p)9vRaxnY+S(DMRHUY1`-!dzVDPn2(l)rdyx;ep%IO-8jC4i4V$Ox!P&vvOyQ0nl~lBOB7?!z03!x?bwDsBqx1yNYvIj@`6LZ(%@ypGJsC|ZvGE&+r4=@vtttiX;bxI-GKbL+Qk(C=2iMmzi#I#a zQTyD4W;CF~a%_p$FuoBnldY$E(9|6a8<2p%_u;#e zktme_zooNq`wyPv3c9CdV!n7*naEmpoQcgl;4>qjMA+{=eAlO7BynaN%uCu6I6g~Rh~$Zv-J z0LmxnWR*bd9^gjLv2X`(MoH*~UD?!UVm8*FuQwFf-iZT$^Y^ypGUV)((ZJyCeP}a0 zk0=QMDPQ8T{r`6Vv10#xBF|tfxL~1=_nV)GjspExEyKJ-%|?8w%L!)T?D5`)KjP$a zn0Ij2p<4JDp1!}~hKkgeSUTrx@yxC?ebyx&zW6!XcR~}0R6UXHddNWpI zK3{VNC|rbmMKv{F%faGRkgrL5?R6c_g&V>n<-xJAe#L@irN4BtFnDA{yCUIRH))1DbHqr7|}(nU-;HjsZk>kt*Mz?=HE4y9#`>= zS_;?FmzBcOGI(zXwZDtK5os8TV#R7|vH@bH$#YAw^^S|By%ni+z_x2h9`X6+}Jm()?gZBxVP2zH{3yS7H8cvOlm3=uM#fD{q*8 zh4`5v>l7N&h2S||BxeE_g1VV&$%V%>;(Sa(v8PRr+dAK z{-cI6KNgrNM;%>S9&b2`PLsV>j0z}Gd>v>&@An=C7F|ll`gR>6$q8{INMTP)M%T$o zIp+4~@{%Y4ed|JJdR>fM@j1IUy~o%SSoN8JdB2R|8h4 zUizp#tugw5;?yfE`df@IIRBF67Zpw-Bi2@n|DCOjZS| zV|&p1+S|2|{pf#bz$jO{QTSsGlj8R9>(ZHicelN$v{!n86Q$kyHzA23xI|TDn zZH>WOOAmnh4`)wbVFbRby?&8)$!ys6#c!5Ki@Qz7{PHwi;CkKq`}?)P^*VRlf4KR-AJ3ktP`};mrP9)^tUc5zU2yKFJLqb5 znQ@$|E!LP6EgF>)M-Sc5onkI+S43`3mWNrvv-O30@qBV^AOR+ywI*MFe8znGswtkF zS|h4aCezZ=NAe@@tQ0mK(&Sf%NkC@kM$Fr{7lL;~)X2}D3z*R*ZN!%!wj1#A{Zx8f z#&Nm-^?ww5{p8%T&_>!8JFEMT0HN|+{!}VSaQ{7m(P*_J71bf%Ox#2JI}F+oQn?y_->( zxPy`iB=jtsQZJ;h_ud5ThVR^=@rCSZ_?iqiJ^e}?1#j@*5BRW?y5|Y+7f0T}e|Dz~ z!E6s5fbuhUjncvXLpU5y9^W-ea|5=@b1fZwjIv<<$;o*9z1pJ%Ae+u7dcpk3wH6xU z%c(Zv{6y$)MAw!{Z!Mt`bIr=e1i|*~$o^h7jou*B_dGGTO#s8y_h|Q~$ZJk!e|&yA zXe->C+C&liy*6NbihOj)>o3Lt#H0IS-{AdsJ|6`6fX-2bh7ZfXvDP7@LxWH~nR3Jz zLwAZ|I?_RIJ096 zuyuv|ad|tj;gI!=VVMK%JR`&BVG*Ds!#@f4T!;7TQ?KIjmHr6;P#RDEI@z1qvZS5Z zrQ=xU;q(!7dY{>X&lgN-j|J7jAD7@816{vi-f%Mjy=r5~e9zv@)J$XXk1b=FqO9R` z^-VSE3~ff!XO@;3z+4pX3}4*-9W4Dojx2`s`BGbcJ-Ulnd+BN%bQsWc zrWgvFVSFGpkoBLN+7AR{R>YH0Lt#Fr<9CsP<`dC?_+;9Ct3v+tQZGe7h>Ls(DFJtt z+pzxdFmJFxN1aT*@rF6y@Lr_lW-WG@4r~6zT`jYwS5gb_zbn;V2;aXO!1}}IXM2E{ zwHLAfbHVb52<7)KfcIY=b`2^OITXkZa!q~0r@?#Ywyb|Ha_|JFukXPAOk-Cy>_4FS zJUp_r_@DNP%27Q8wbPa2j^|xaeO(&WH}He&_4zAmGPg2zz3#Je zAh3QB0=zl~Fo@p@`3@=`)lVQH|89Z0>`0Oc?r)2G`2zYu2$*jgz@YjGa#+uS&m;W{ zJpE*McsI8o2=A}C`VR&?dyqe~r@NZ$><`vI(a9SKd=Cc!Sbq=OqbsY#?Q{Key6bjW zukYgQ<+3ceX6y}_hV9w$)d_5qhG70l;2zOWX-j@g)Hd0MuM&FwoOH|=Nt{5*sSs2@ zkf|8CM`WId?whC4f79O#-?k1*48lCJ-=P&B_%uc)fNOBwRrY|+%CQ|o{quUibQ7tp^X2<(E1Sk_poYkM zl}cglt4m=svHs)`7cj^43h_0n52Lp6tLVg{AV#!i1gzO&D44q0l&&|pCp-@0y(_1y zWBzsz0SGmkNiP1?o0&IUOT6`d5RF04n3jo%3Im+a3HQ z+HbI)c~3jgRuSL4Hx&Rn+!1aTAs=h@2=hH?AF%UBJc<4PxLT3A%Aen0R`2o+&Y!o5 zVIH$!eroLSYeF<8COvpdu5o4OPyT$H4f*eoFPY}&aSqlOo)&i=^W1#P_fOFH{hK~F zA0nRfe-MxKkv*$TM|)`3!#Xz(x3PXgZ3Fpcg*wPTRKWZqe~D}pbq8qNnMUVVzLlFk zd#t!n&_;gdkOWL<7>?U>mz@9{i}fY#p6z6U6KX}1d+H6iyFQvewP!=wl_3umI>FP0 zyOeN@y|l%Ao9iARpR=p0`+cc5^51)zFyLa?^F?oxZ z?feL^#rF$Y0%MNTX!!i)wVrsql*0RZ4KoITeUNX;-6c{OS^2$(ydC7XLOvbFhlO2( z1>c{u_56F!*+lAY84J$dd{~b7ILKR;=6QXLjR2zud?CZ3{89cXA#VVANq#@U=RsRA-*NmZ(r@x0fbxyG_Cdeb;4kC7x6^G)jwOb8 zPlmZ9ojteW{`Nu83k-K5@cPdA!-#*HWKmK?D~b~@8<#NfK7!ex<+%M7C!B%8-HSl$ zOH*R4$WXOjXxOiGJqg#BWdj^#k-y;^W0{??{=5PQaC|qrzVPb7(&{SSBgS5~8S>%- zdYGRw+YV%IXV({AJy`m0`b3f)=suZUUmh&36}fC?*CUQUYw98E3-vclF~#}=jyixR zGlKrV{qk+Kw=RERf9^Rvz+*G)&!-}93Sm0{Zok}mJl=sk+ApW(jPUpf`Sts6V4fR~ zt;Y+;+qKf?#tY*4`*Du{sTJr!K>fDnRal?fkE8bIuh;5@tMK|xIXlZn!|zqvvi<qZU!UM?RDwGS76sGaz=IoXT1V}g1kY1y zPvP;)<(nb@l&fdez1=4q|M~J!>NN-YOV{2ElKKx}zn5RnimP|T=NCYJ`O|^YFRMGs zQGe(bKMeSoO-J=Q$`Oz1HSzh@`uZ@oWU4p4g+JMYXAf+9VEkpUG+&b)FaPFG-juAz z_BTQM`$0bE*cj<9Q?@^I{-l>q0E6r?gYOaWd1uHwi^oVKu6DupFV^)1hyFyPdX<3@dyz_A51ErO)=QLH@`h63>J8av(*&-tH`uGd`V zW`6K0{_s_L3Hp;CPqF9Goc{^yyJCA*E<^QI(rAwFdZS6I2G21z>ay?eD5{;n#7EHp z@#sB*lvaFe{oH6Lu>3*v|L5N#{W;K{@qg3bRAUQtB0g3p#H5|Mw$5u0M5|3GLZ=B3%mK9HKAXZ;N?u zerw$yzN`3AaSNn|a1A!!$-ckC^#|PF{_i~+n&VsR2dr`c`>(L}^VhqdP=7YGXLG+b z(r9Rp_(jY46U{$dJww-H&UpV=wOZRd3XYdw{c@yBCbQ$6U(b-Mr^oTPArG~Ia#yyr z>tj~G4Bq`qFKM|S+#lM*vzUyr0|BhpjpOIH+p_V812L~%Qak?f8sYp{Dun&tV0Z68mS2>5!~WUfPRspi>-7%t63CBirT@S64%&m_^A)Y^nI7_9 z+6RuWe%b8!>Lhal?S$-k1Q&lseF@D!oiA#aY=O3SdGJVjcfx3FKNk;2?a!URbG#ws zpFv*G{;t&V%5cn=obCWdnXvt*HUDq=egV3`Tsx>mJe;o|2=ycOJe7WbJ|63H@o?1s zD4vs{ivO&(`s9)c+o#Jqm82>@v%ki!C+x@TwjgwgW(f)2qD|@W==t&pCJwSf#GVK57 z+S&mPXb)pgnKQ+4k&r7y9S+1JQeY_&(0o;gw>U z%)tBRtQk07-Ch^g8k*xues;Tn{k`rdmy_Ff+h88uC)x}7vD(mYF+f3l#Oj2;@>B7Vj3fhzq@Jbf;H#g8v>JZFzza35); z8yo-O;!AvcX6c0E_)-$|ACNtNs+OOQ{fG4jID26IdF-DwL;r;16IK4kpT?}y-3t71 z{|TE?P0nYgV*e2NCuA!g>Hp^G7p`p}mGY@AzL(SI>}i&*IDOulZ6EG?`SyG{VBt-k znvQ?pVyG`Y39k}Puq;8p7iSORk$(cuNyMEWn0qhz6ODPe9|t<`f#7bY1?Ed9SCjE$ zS^E)>>bKAL)%Es%68=B^;CNrbqnEmvZwu|e9MJN6kv%|V57M7k0R-kMd${jK_MrNH z=>0FJcIMvJ`Z52rr!Lu7piyj&^;d;glbzN~M(=-Ne>DvHtNZe+y!Lc(2Ls@{X7{1~ zji+aQ)Q?u-e6aMf)ud=;7~B5PzeVq}@7q%4b+@xS=0>&~9_d6L8O7t>(8F@xoi zJ;m^QouoS6yM*{n9e~<%T!gP+^+fi2AFXI0CuU7X?~5_qds|hTy7`zq+C#dy%m?Zd zXVSk56y)Vbww{~c!4>5FTO{VMsNOeR-DSGZ*6bJ|JckC=fid(2r|aa8s6yO-7Duii zLu>$g--khb4t$TysCSjmvI2dg^ghgM*&k0E42@HS2U!cz?>oMD9%)e_Lhpq!s9vQE zzBlCFRN`~)?guiWC=viTw>=v_v8=)@0*~JtvUz0ZKoQ9AT*(~qDHP4~doEsYI1%rU zY>qhL_`LsvtI`Dr*!XDc}IM2S9r^@az#o{WEQ@Nhe-r^$*_g0&}NN z!Se~)AH9P6sZYJzV*fA&o);KGe$qE>S?#^9cz&*oae?vT5IkQYe@Pd{&pAF7@;Z=z z{IHE|>jQJlYb3aUC6XW09guJ|qPI3(HF8lyjlTd)^^& z8x{W^^16_hYgoz*%2@liRJZ|mSmS9?$N|i^gS@tif2Y!Cc=`)KP_S+M0c=kN zw1>}YLj9{xEM*&WSbN(1_5vd_0|41ni~YmX&>uTCzygGBYe$Nn4W=#gTuJ*^NpzD{ zGj7kCfBujIqAkH|x(=_Wg^&-JcZY0#)&}HkC+HoNF|bM+OutN@ra=8U=Ug*6I@7X6 ze5uXBE+q-=^U-`jzb?E&2+C6FuO%Dte9`V8yHI?L%Rk}s=TolB`@?v3MG9*V_q}|3biNywd>_Hue;N8oy3k*m_Fzs6 zdk~NOA75w>pP%jLDmR4fbLZ7;%qJYGBUjyF?dRg*e7?0l7Z2yh_xSt-n+mxXtY5d` z!d$G+eJ|ghL0Y{_K1Rospx<|rqCvh3o>xk}Se~;7@yH*Jg8rv`x^2m->PXCI1|OwN zVEwvuYnK0h;SRZ>W49LPp@p6%C0nNkV}6aLF|`ifPdQ%9u4mkND6*gXUhQ-N?2l_r0k9BY$`Tv}detA@(11;eH_m`tup%YNf$lSbx5Jk0l`548-#f z^0!Yye|wyYZx8uU$WItwEA4K|@~0MBf+9b5{^9l)|D~UO*$9xm*!>;1-wEQ`69)CA zV{4_kCagY}FU0qM=HCL?{&?um988=`2RW*Fi9emj`D3egWRqVsd*S#G7k}XR1jxUv zok}0tY~@wq_Z0K7y>iL#gY4M&k}AG2??3bt+vSs&P2Je_9rDP(iskVE-QsEU+>@T$ zyR-RkhrVwi`^4k-Od0O|pb5~wm8krgEkg&7_{`A%>> z=sFYx;XA4EIJrhPC+x*XIx#MQy{(Hpgo#Il#WH7$6 z9qLcu#oj#f>U4r#-&OC2^7$X_D-=;Fv%toTZkQiGU^D5~%<6OR?Vd}p^j==Y`fsDR zoKx)YJqrxi?237z<|^{|FkjXlVwWfuzK{G^)7*Riz{%L270cVwRsDj&;R}!O_pa=- zk{p-ni`x^$GiO16X-U5byuQ4K@kVD@6Cri|V(ImrPI!Lq9pC~+*0Av?#H0A(QWgIS z@-~ovaWg@x=HY^QE`EsOV_bZa(?>kgU&7Pp;*+SqaqD%OO8+I~{UASf_(JKSw(S0e zTd(>2iEKT*zK6l}G#c{86-Kg@^X&U&9M89h<3k{i^don5k=3?i^*MXc{tK<|=sca{ zLsj}lJbmsw9qo5G|AFI?Ju9I8wilgbb?Npk{0Bbo)`nf*QTzwJjeP2Jq0};;eb0{b zH~9AO`Q?!RGwO&`xRTZ9?BT~}_Bh#3RQuCAj82JdxEt+o z$erH3&x_iUa@{aWLp`Nsd)P*jKsmJ~> zx1RFn+q!d`@qE<*&Yu9rwwY&IvbkExxc#~L7PSu-uXxVmAH!I6x^p{OL|-<3&c!QG z`*ZOSjyL!heJ&n?eqU}r=kvRvJ&HBzGTWks*dA^^NAV>TpZNjrgH%1V_gZV=ipMXi zfA0IwV1b%$A3T0DHhv~u*C)5guV~HxygOJ>r^E7Geg!{2VcVaHUV?p9UZ}m1zWr=} z!OY>4us)ZcfYv*HzL4#X*Djyo`Q7h@M?KCfC3MmTM*{u({PmGrU*P{wBOc`=b$Nf& z=G%weCLPzZ`9)m5C7*AtUlU$M4y5YLbQaQwwKa_MczJ87~!7ym*1fgfK& zJc?&pw$kU~ONi&k-xjFsnW|1nFGBq@&8$8be?$ET@e1e^@rq^Dzw!6_iZBXZ>ORD0Kb8PfJql{ zf8gRFh)4GPhW#_P6(8X{NT3nP^4(9se4(6V?EiEA5AwJDrVK&r6@4f^TcI;zA-#MW z`#ob{-X?QDvHm32fB1aJ@qUVkp~>`e_et13uK$P|8*%*VG>k`?*8TDNlpTlv{@!FE z{cBu5df3V(_|H3~E6H7X*62NDT;Jv_Bu7XrdO~EMOre_$3UEHa&iFrM zuC4>-rw!gCnhN-kj&-V=C=R#g{ zppI8G;{}GhI)kaT@I3L%KKaa~qxgHr_W4PEPRTmr>sX2TfmJ`r zd-E+@=r_XtyYqInboO))d8eXb-fnPzasKccJihemoq)w!0p`*7#zA`!UjX@8kPjBl z^1kp9-ZNWOg!y|Dok8q+0lQwn{Te!7;PesSv6VizexdQp#bZA}{aq@1!bbG=M)7}g zearYiY9G#??d5EI!y!i7`zc)ShA>&u0k1V%*u($5wyJo0$WMWFF+1&>CH0^504VH6rI1I@7>1J9|8IJfGtv!v+VlIeQ(8qT4wsZTG6gG zE5uzxqe1G?4T}BB-Q?IwYZX=j3+b>^Y(7a~mv`h1m?MJtw=iEi5%PI$qCrhg4mt4M zZZfQ7GueIC0vf*6gn!@f6YIz>2OaSGi~2v>5B4c^1wSTufbqvNgv?+M;C9rNPLJA& zd2as&--pHa9EAGc`x$uIE_b1=6JY&tcs4lVQDNDcH`{Uh>>KrgtlG}@H}3sezCREM z`I4^(1pSV&@hrG*q+%v zo{_Uv`rP>a-Z2gP1I-fcl1`T;VDF_P9v$rL@ObH6@{H_pzNJ2Vf0q8B?c~}JM9R(S zJzmMsrn&8Eu>A(W4ovXG@7pp5%+sECcizEYk2rgf{;K*xUg$i+?P<&Nh<7JynR=M7+qG_mc>0%c@Z?ju z;wn)>ALwgIS}Ub@ck>P^CIBux>{kqY@{&%@+!sMS8+7qz}Da9=5zjj+$GBt zeB3mG{zt4X+@>5(A3EHHj)gTw(0t47$HPCr$M$!J_K4nTcooPz=|@9cz(Dw|ZFdHj z%?_A|+y6}AeNyKn+kd$E)$e@PY_ z&j%N@OHM<7FU~?owlH)l<~je6-#$xKyf5U}LH^BXU0Heto6jJJeJ9jU0FeEtJ*}br zY5$>LV{HZA`Lq3j^FKLzkp3g6zqq!o?3ZIp|C6s@3Hw`a!Zm64f!=bP&;NM!fc;I| z*$msy>GRu@<9k9rhMDW-4%_sT%P!oWuM(`lq@k=mTzlSt`WsdHJzMGX{Uy$RPQM4# z4}WXtjn4ai%xrnyx9KnUL5kEy3pc{^%KDMRu>BtkRBt|_oif;l}y*!wS?ue*b1Mj9>h z^AW!t@@9~4^IKaG3-5>CS%*f`Fk913i+s3`dt1Ve?EloLDTsv{RdI%f>H_G zQ)#mI>vwc?1{N+_E$UJ8^R-?|_X>Bzc;lA4yYTnY)qlu7y&Q49FE0O(pPy-Dx=xq} z^HKL!uEc!X%gyAzzK$){>#@+Do3do-jzD$lmm4TV3@cgxpP%1|^2boV=s1=BPN)x$V2a;E{hlg)?*1l!{YCsOsE_y$ zU3&_qI!i#{PlPFpNY60T64L!_d&S+8s83<8V|XN`!l!x^7XS+ z{BOudLcYh_xze4p+4Y+9H~95q`8?7$qtm(%C-dU;oU z?!o#PC3A*ip1bdic+UPnmA)U;UkddnLw&JIpSw@ZpMUuLpk#qy&c{@6A-)9bQ;U6p zd~}nLKVH!I+B;Y&Rg6)mHpX@moSX>jKYwTM=W^o(*~5*;Ajt27JPrAC&3b~QXbCu& z&EC)D#v|gn{)~7|KS!nSrPAm6Gv9ya+avgHESNn;0-_hN_rJLD!ndFA|9nopN(k3_ z(9d@}<9JBz{kP;jE&CSZk?)_E^f^bAMmW){UU=eo$RDR>(ml4y{t=H4{hJ}J*LtN+%aD7Gm&FDL1R%iPb`e?q|e(0=JFah?T zd!B-#U+}%2cT)U&jUDL$Ty!;v*1zvo$ooM4ro=-qOHBeQx1MhC`?l^s+f;m}O20y- z&-EX~bMrsyKO8^L$y;#qTPk=jX5S;=?nCkGvvv490jwC?2CR9vn`nDf8+2|6rgaR0 z2=u)tORPZSoH$}Myx)!L=dW&`C4OI=>Jtg)s0$ZY30@4-q^wS?ER$EW^#&(5#DX88 zsJ643TKqOs<)L0IAEDxFZS|-R+gFz9K4AGvmtw*FbLN;w^$8!q_lVy?eZ-%K`p=<$ zH=e%Eh%k`W)*J*vdk~N6+aP;5Uh`Fx-16MYGOK5-{+O9zU|+E@c>c4xeB;WY%7zu| z#jz1GVeW_iV8sm|pnof#+6wPDxXtK~f8Xc>S;D-DU&;Ns_RQmoA%}J*Pa`^= z?h7RFZfC~*bjrR-2rk1tFlwLI1{;Kp1AmdP_uDgvO8u4Vh3mzl`EMwfK|b_OtA1em zpD)y$ZH}PmB-=kxOVfzx@#9(_ibv9ZtY2k{5;a2BHrrc{*4waP13>qY2oCX+SqT zS6H$@4G15EGvQ`~m3?*AiyO@i$Q#?7LD*|IpxBj3oqP*z@ngr=n}J(}-yy%QeLhoo zO{Co6xj`&Hvz&DL(;N8Qb_Q7=^yP;?dV#RtPWXGrjn5b6PuBuPn+q8*M6A3wbA!0* z(H^Q-bANi@NpDb8c7eS5)E2alhwqL+dlnXM7EXDf1(K#bVAl6=R%&$363_b(NUW9E zP`9Dq5NvEr5qn`y*k?1`|9{yn1`~F)0UJ+EV#dyNR<1s=Oq^2jSQx!=616B(0?w9n zp!^dgbi%$+%zM0E1Oh*G1iJO%%)<%2lrlG1A3$%p@Nq*rbqC%PkgYJM#twt`57)8& zOzHatK>cM$(A-qbJaO)&6k2A9du4nSTHg1kcEWo*&Tl(W-B(KJC&>e`{xYA1ps;Hj z@JR{lO+`2>clhRrwav@qVKp+UJ*+L{^A6^6M%@#vZ*vB(MKEK+0LWK$xhq(x$?9hht0sb_T0qNwGP829vvNvjmfytV9c%9hO0`&i+SY2~&%m}o zkX>D#wV;7~!uHgf%JNzji-6T46W|u*pv-EMBbL3$Q)o`@ zM6a9R2VN&=fJ|7QtY^Lr?hm<*TZMr;+JRa(CDWl&pfq2ZElwLzPwqP38BG7t4=hPB zCVjTU+P^E<{;7YkNLc!|EikgU&n!}RRvufEEmj<~1B*{wCd)6|f&$HrT&aFs()1~m~Gdd^1YQxPhQ8)&s{nvfckxgAS1wm>6YQFJYTa++%rLgx@IY(em#V> zWClZf?xfQ0j_i9h;Laj2y}%II|M;i8e=kCrl)p?|ygiUwle3x%>j3XHNwg?mfrNh4 znSKA|xOM_4NHzh?(`QV@S~KO)+-z}=JE?;CQK9tVdQH$aD+~->mZ8`V-%3F3Gj4+# z(PNqcSm7Va_^dEfriEmS2kbEg=`&2|Rl0g$-IIf4``P~}PVX6u=ZCB-3yAIN#-O)r zDAQ)9sj|b2Z1MBWF5uXkKq~!%2k@Jr2||C^(}$z=Fu&6sh03Q+rS^>{<>Ph^4)nJKGYWQuoBTuTb- z%N5&yMuL0p-ii_9X3>Lo9bxk^$oW9+QAg1EpU36t^G%ghO15|e<3u0a{#0(O=M5hB zGa-KUg#Kh|E`juCc_|3y=XxSUuev;MwVASHezth>6;s;M-k4tCsSl>ju^~o{?+sv> z6!&KfMowJ*xSrs*=e4vb1?e{mw1ss5Y;6aFe1$;aS8axQu07Q|!uLl`nkvH!Hi*lI zd(f-i8-M|C9*{2Gl%zm65PWa0#`fDM*9rR^H2|jj8<>hrGv&i08^jg*L+DqZ6x57H zOVF_0PI1d28C;Et!TsN2SDkRiVS^U^%`d4fX?D|#e7z(AG(_|R`LG7~soqms^tZDQ z>zI=7cFLM{+2YNXZuB@w8wGvQ1>C%*4UT>71j>B7WBcg=6~g>udSKI$$4o_sUdmVN zv&HeDuAqJ2G356r{eg!2V{*aBo}kzA?zq1>*xwPh^@49ujNZX0-iwqUl*`0Zdz>Vq zZ@i`QhNgfHjmDIsUn-rb{|eimkdO!-Wat6C>5)vLV6ak>n<1{7yh0dfbcgDYlM4D- z=~IgmQt7DcRhWO}oCMBjcLMP}`!L^L3{f^!Wr!yX>rL&{I7JP+nhF}zwW!MvQ|a+H zS^seItR(R5m?6kCt1Ulvc&HMXEE5lnoKF?}SwS_y{`qjF7Ih5H&vQ-IV*U1qlfah@ z6Y#hC_JjPtDcX9D0kx@YeNl_pDP+o|0P^d%NT%t}T9K`X3DfWXve#qw1K%=URx9Q zhufP&!FO1Xef_Tp=GXkCO!v7Rnf+N4=pDJEsr~QT(x-l?Q=VI3-QouJz5AuhKay|G zMw0vEf*GyCT4wB>FXdaihSOSaIw%6Yn-u;XpHf{Wrhwc>Y<Z^o?33!>lG)p$?MH=uKCR|v zevbsqZ*s@_2SotH-JaOuy^`y8_^u1+d_W} z%L58?!TGw8w&6M2$B73HX$DmYNc@(Xrr5u2EwB!51%9d((|qf)_3Ej{^1 zu%GhZQB@M(ovja?(n*b&_UFrA_fxU9DfI3UrsQ}xHvW_8J%k(*a9a4c{aQOwGF{K~ zBZu5tBt+}4!Nf4qV^*o~Z~L!!mt;Dp%z^BB-;+T6N|(8$LUC63xBb!HYw>h`c^J9= zZaL<)OIDECBg%w-^PhLu2h-cPY6Gv3NX#FfyoDS!M<)E6e_NOLSh1&J8aTi8HnxBC zo!w+l%d^72t*7;CE>f4zr+`TpPGSATEeFXcVWseI_0~>7Kl(vl960;lk3!$uaB{tH z%eYS9Z|@&XI$J?1p}%>Y@BoPK{wGIxXGSL+4@CZ0>-UxXcLo3>@h1850D3#Qrr!1+rk{_r);RB5<1Tl`SP>p=cURUbm@&8D)Q z=UCoCMiUh&!SLJvFJ8gvBOd8n@$}^t)kKD7Sc`ZF;+<6XOl(IIJ)Jj~$%|S2Uy+ZA z9~Rlfmgq<2?)%J?Spab<2nt;W2{$zQbEspV{T!W?auXG=3y?_%N&~ z88s5;^K z`VTYC55V@E-&8<$_KCy!^FLtzUR9r8Vpx}oE{D7(KqJ3z*XYH}f|mpW*f0=zEFqo81ENsd*%W>`#XFuT$|hkZ*u|!=@UUT|Ubno#;sH z*H{3&(0ylUe+108$O~nb~cGEzIQE8zwJv22(_t^N$gmCw*Xq`H!u;5 zdGW9u*<%ImS^04=uGN10N=nJaR}#?n<4u{o(?e{(`OI{Jc^n7Fj+)v}$jA2nC0@nj z&q2P6#x2=L8=UjEDppD*Zk*hFs6nG5cH9mybjLZCfnkVpE9A%6q%k;g`QU$C;me7iTfcA0u76>jmm4eM*IC#_qI-<&zQ%(~%uqF}k9AD{2xv)OCt zKd@%*^=7=j^u4>2{B~y&Kzw8dyzj%bZ^5_L_jq}X4EK(1F&_E)Il9}uI>7O}+J&7@ zxcPxU9^3AS#p8GKx#r?BI9|rR4wKz$c!K*sHy%;F6lBk8Xb;CnLB1IB%=a)^&~KJs z^xlEkTOJL{U_BGWheN*2f9M;YP9sXUMYkA_e4d8-Z|{f6COl^Kx$%hJs~QB~t3vf= zUlS+XDweIn?@T4ph5_-(k75LBhueSxFFdcy2A7A=GAU1>ipzr;_qyaGhQ7Hg^ z?YmKnE=cIU`;u_`2kPqpwcl#sZ|`U6Wv2jHb6axzclJHdM^}SE#?T7k-`=;XeVhW$ zj%+5>!we|+yMRmb2yk~FCHz}G>NTHJfUW2ov8obSodeoOZ zW;=Tqd4q$4oIq%LDhN8anb>@1C+0isjs+1Hj$r-@M+hS8)6MS~LKI{c7BM z-b|(U#mcFxou6X9ks1xWixXP>UdvXVBW7Pur5E(Aaj!g`N*66UM?G3qhxv^Cp`g#V zgum7&zo~Iuu_$dcz0Tk|_6H(j#ANZEE`Tt8RL;EzXQ}NDemu?hx(weVd-UFoF3S5X zk7{(o{!&W(I8y3shUSNXiR0n``hct`wT^%It*l2c;saD4#8 zbNAEJJ7l}`iP_>6O4mta;F-a}i?I$W&5oA&=lp#2je!l={vv44POnb`eyRr9`xcg0f zeKc32zOozI9|ifT$82N=CXK`PaQbNca`O+zqwj4CbBi*4O=b70{V~6_umgzi&eoIU z?BV!-)930x@a^Y#q(2kd<2lquw&cNBtk2ocuNSnfe`kE3#iM49w*k!m^p+aP=1gJl zZ*uj5`1gOet9Vbyqx-5^eLKkfYgxX-YacLNZBC2(#s8&mIKTz0e>1zq{UE+R%xwg3 z2m{%M+OF7s?tT!yudTXI)a9}cw%-fd;|uSnI4rV}6-;37*Kj<#AI;r&HsSG;Ab<2y zH(BSTzF42L2j90+-EaCYeUC~faQ5YFe7}sluf*3!_mvL!=qVfK>4NpS`%1W8jp}|A z8t=ugzraX{xwrE}ec5W5&o=h=)D}G79>kx4d=lgziGE4z;cxuhmaPZE*@JlQKG%Qg ze+zK}{x4Yj`Sq7Leep?48LHRRO^dD9#ILu*=cj+@;4=2|C~PJ(fvMjUk};C@!JP(mzqQVSn7Ao zTaUH_DK}Yr`1P0c{-V#Xzl8RWoc#unPlfGy?F!qT&WqXh}58bF=kN!Twyb zxTd|AO=1EFKQgVCK%A^um_xn3t?W&zV_6`1ejpG_J!y0^HG z$>(owp6#{b@GMZ%ONn{zJ|_BJuAT#*Z>`VObKuvb;Pa6?=6MC$&jOO7b69`Ekqol) zRWJ7Ug7qj`*RN1-SV+&(^H+2q;@^Tt>jfI`t?O5GuUklW81AolwU*_*-%TUEMhjcy z2j;7jw2`cda;vllClYiNh8t7Bm-tsgWG9y&$j^V~=ZD80Jx^UdkxG9te};L+G7{X0 zS%~AGTs|Q`U$N_loAT|o5?Ze}`(DqJ9}!@#Rbq?%1%E#{YqlZPt`ffaBnra%-2MXX zAD6-XBYGb>p#i?P6Z!x6I_rR_miCV$iXA8hD0X2I(!1vjEZ7QC7MNfsc4J|;m?(B9 z!Y;MTDs~GtwqgOcf`RyZX3lxMv-|Vq{`0=~-jCn=eP-v(JZH|#Gx1t?ZA)eFC5=`O z=e6J6&1O7)kE8dqWbAZi?`x;v90`dl=l|4asp}VAPns%|ekF8ud$`$7Uq8@| zyF+JGSp2lD1v~GC z?-!IWDf?+LtgGAHP4;@5{?dLke81q^b83jGo9e^^-XS1IM2cG7i+>Wo8ySEgUO`N!PaAGJr*0N$1)N43Jw>QO@Qy#HbTFC>Ske2*dlsfj-1#S zB4+t`Nb9kXpV0b~`osO{ztf*Q*~zs90>_0=c}olzAfm_0lvZY8m#|dzDn1V>HJ%T*3YSY&9z0T z*6e&9tnU%$Y2FG;>+9^i;V`J*IK0asgY{?1x0HE${_*H$m12>V#J3*xO)N2gpuzg> zD4()!J_9eY^R02fSDV#Nk-ViWYtPRQ3dKRQC7$|8hxK7&{@C|+ifoxzL4C-y4ue>I z1HRs4gmz(@D^QPj!Iov3x{x2%8aax^i}fD){ziW*BKgkT=%H z@qzMuJv8KPZ|!IG&`P~BtcTXM`unu~+4-s-#qP0u>7ek)p_Qtg+n+gm)q5dd%3Pem z@})6-o06kX`Vh)TW#p*F$@~GC@Ai4GdN=UTYu;qMVvh}Z^Q;dEws@a*`BskVj>~JN ze+YP6&^MWDs6R5=hMZg!#`veX`KlC|NBv`!_o@$2Ki{SO{Ko^ElEn+cNJ5?WX-!__ zt1f!(XZoX0Wjo|G@pr3KXsRiI`XrX!YfJrYbyG6>Y6r%fU(QiwaeO}Tzx$eMibHhC&bLw4)BF~02UeAO|Ir~1vd7T3^vGH+_s zVSa8+Yf1u=yBXwfOD>i9TaT-~-w1uXEf+=W+T~e)#r%rT4|zWS$Y^~+y(H1m&z|v^ zAM)p4=y|b8j+4}N{6%fzlFP2AA>NDYrFx!t9*y6x&hxj;^sc3${eN{YWWVo`*UdG-vID=iPY!bie(%!_A#UIA7DI z-eT9s@C-Zomt=nM{0@H}5P1Up>n;o3ZowE7G{=nT<9>C1KRNRM(#QSel*ju!=zjRK zvY#riEt9%{pGt2wDCqc%=Yx#jM<9Q0L*F0I)X!G$gWqG(1?hRm_Ywa0rQ-wT>HA~U zq+{xh&_0b;muLPHZ#O1`ZpiP0^7{qy|I){Lu=IU|f8Rm<{R69Ln!tD!`(q^2$M+rM zd|quy_Cf$&z1Eeva=8^nM(?@5Xt;8n%8lmy}KN+Z>^< zo}H(uR4t6Hx8r>`bWMixO1K|~=NABf;832%t$xm2JuCV%3SA+X~ieGVN>$j^&S+^UTBJ>`c z@|;cfhOzfYyx)hvKgd+(R|B69e1+k8&K;*oJl-F~&%Z_KXFn_@o+;g%`N#Q@*FOmQ z(=OyWYtkkCKRsRxQ73z`@4u9%^#E!A9CTXi@b18M*F&SPCzcuKA?!?$_75z)pBzUyWWVXmy-H`_sU8)K;NT(%YKKMSD$w1?m8Pja5-c0K9$LVf`ntg_-!Hy;E4E}WmrpkKcrPgAdtqlQuQgN2U12mlnV&6QMc-ug|lUpVIJNKFT zbo2c}x$-~Q^V0u_g($QgC05IMc&hx{x~E$n2uS;4X`w&*-BxcJ{v!3mXdiv68I{SW z^-bCHZd%D*EZ%GY)6c0~>-1>n9cjzo9ZBi%UDRKn-dx|b=2ZQ@M)gI_s+OtnyO388 ztBH}qIL6cae4E4fvZdG7(ft7WqtlMJ`7y#xXVP+AT)I~3KO;2OWaBnVgZut?e$hZT z-QZU1;_k^j-p|h8pU(5f`uu&t)K4ay-&rR6IR<`4PIS{nH(wWbN!IUktu@IDr+bf0 zOtQ1Po~|S;@Ot|3QBv9^c!#arSa<9pyxYASk=$c&N%s7{npjV4U{)Ibzllk~cPmxU z;1g*sLnf!ZKW?vpc|{-Veo<)x?*XYRTd?12VCgz!eb7SIKDo8dr1i9lO{?w|o#J)V zPS+w0&H>I`skKUi8iEt;*za|?{3EgOo5VZoqf}#)W7FJA#3o1FIHlYSb=FF%TMKnh z!2NN}Pbq2pKiJ%yyvdgC^DVZl{OMaIkEE@x-zQ~7zow~eSIp2SI_%U9JvW_LMm1r- zSH&)Rv2>KA|F8RzN6#CpKhlq8pS!j4)!jZC-lxFdpY>bj!+@vv9US$@SGPBl+6V7Dpx+Z|eX4r! zevSH+r}`P3KGrj*@lw73zP}((@8=o%Cm&X954G$~$GnZ7|2neA3}Y!h+bx?0Fp0W{1kxrohY7 z(muWryd=FSnLGyKecNZPb65>2-W9_;6S2-{Ha}8+L^Fw}d%Ti?9}fJ*4{Mz-?~wQw z{o0a0<`Pf8N6dop-vjiKr}{aZKHg_R+mrIakZ({P{fq~`#+5bB#g|L@Gv1d$dA#3h zwS^~}zZOINyJROPv+)7%_vG&n&6oK^;3ojTput@Ahs{G6kM+`N|Htol$fLd`r;qj6ssA?e z_eb7OQ_zQ7v(<;bN%^z6vII$eFXe~&l&+_r9#{?ehKb4IbFTV=*7l@qZ8#s7GC~-B zdmn)lwV{9pSg9Y)n8?XNT;-{DjupglLzsV>e+rQQ&-)h5@@>fX+!7$awVaaYT=uk- z-_BmC75b(H806>3F9d!k@C{byIlE*U=I8Wz!TcHdf9YfXO#M?n2%h)(vLC8{8T706 z%X5BsNYd|TmMk0_(~Iu6SK)sC72g82Jzzio3fRy8wpuaW_QHnxjG^9o+J6#ihLg4R z9mtL#8N^y@hrW|7!AuG*0-BL<~43apnrPe z{@x$+*NFv#G^wXxe{WW+>$ znNdIg+;4oOSW}_TveEhp+Y_0e3RdIDs*vjB=zN>B_7Lwjh>q_VeuI?363_=dt`VxKMf(64Hw-{|^7q58Eq zV#VptJrlY$JsogY;up()t|VU5Hp+Y~Je2jvjejI`QNIwU)N7`4g!8cP;XLf>6Ymt^ zu;z0qbC2TKN%+>?;Z;iis1L3P}p2(l)y?g9k%2@Ef z`}`h7_NxfJQ_HuCmu3&x{M%nh6JA%SXmH+~@-$xLn*#4%TH^7%Ie#Afj*)&FP9M*M z!}Iv}eEmHmeNW(57nk%GU4JC#zz=^O9DZM7I`o(}JwdDdN-k^POp4VJID1R`&*UL} zgw&UY=glc!9QYf+_XNJem{?tq%%3jNM;PDDj8vQc{FD~@Lm>2rs3PX|+|yURM4ddQ8arDLuax_LmW>2ACPt zPsow^#=v_4-)qD%;jzr0zg|YvzbIu;Pk`qwfG^o!&9x0>D?IQ}f~RVQt7%c^3__*Dyh#cnRWNQ2BwPs*?4_=11vpXoD5^ilL8%8Qwv z)K5e3L-mh?pWnc*2L08muDCs$JBsPw8aGJ1P%eb-Z&z{q;i0ep_t$$^uilX0%zm#6 z6_uj1j_rq6ar@y7cnf%z`(BpzgRM!dC6+K7!u08VnkjIfCVh_nQ-?f()3@GPOPpa6 z%6NKTXUS@H{~x~t-kY7C`7uAYUb=}VHU^O+fp&sD; zEx+8BPfBNeb=y+niAF;hFM^+y;D_h!VSl@uUn%#W1D-K{_O+5?gT|5{^w0Cg`n-Q^ zAD%z>4&J98zH#e#_7u~{_JRD?6ZhwbY@DE32mT58_j?;bW@?JLU!Q%K@opKNCV2)o4{JLKz?!VgKVLYGTQvYQkUex!H^&>ca+trsGWel9z5kTwD}jD^ zxT$-5(mkdh`Td#Dt#c^*J<(6kjT4+{`-`A|0`l!?D~h>K9BY_w^Z7jGF<$z<(5H7X zccqoYV?NK{$6AzrgM$&mr5MQ%#@mb2-v;`d(u%pqpO^G8Uj95qy1O~^-wOO6hyD2u1Tf8m}- zJ%-p2hMh7emYfLFV3nO!pfnW zh|lGrX_S8t<7ZL&J!@?e=XaEN>Sr#DpOmNLCmqjs!TWyc@C0|0#-U6qt%E!w5YvB7- zvv#xD`0SO?#p;O1gkjOk;%v_Il{$O_$7 zrC7gPLgtqUVwY>DONVuFn~d(PJ#jq*&WCzpeJ!4^pSx9?{sq=?8*ABqlvZXTB)@w- zGU4Ju&+gBvpWgJot{2Z6>!;lfCKH;~CF9SZ@}T@1|9W1_Wk2`9k4MWzx_6_YUb^dE z=I6Ib2w7)mP3wLA_4B2XpTh3MF6YDW+%8G2sdGt>(4SaT+`x}7@E2{XoZbWV!9H{R zi0oRrIX5Eohi8^xy#MpwB%^aZG9LUd8*XuW1Nf)B{r}Lv)gqXbZdlhKUdr17e+c~a zUTLNK0Dg+EHf8!4FYjl?$*oG;ux>6i-jHBBo#Sb!@4P#N#fyIE??wI7dT2as%A=o~pH}HM z1xM(=c8X_yFkaqI*o|(Au$Gx>8t*Ar3*G+Pa0ld>T(-Xg{ZJnJTPVjL>RwSd64u{h zuVgVE`y1aMc-~ka`vc`M-a(*02>j6g@U5z)?nVYYld)f!KE})U&l%Qp*!P0*r>Z&^ zhg@vQ>Z5TRrb+u-c3H)UE_1bFtBcP(ZJ~d5w7larUFOFDUkvzCF-PN~_elKajhn^V zH|#~57e0*l>t@JP{Y9L9>vr2kivji`A1|*D_f_f3Mjnle@|E;m&+igz_-aMIf6j!Q z?ZH&RDg8;hcI1i?$uW1KzHM?PwjMS4#uoA7a;?|`{11lyK>g3+`0^u1Bp(2N+a8IZ z?z>5Jdu?wJZ&CUfFW;V&&y@XKJw76NHRvzBF6m=?^1s)|nCWc4_n(jEnt}!Jo7rrM zJA7>l^WWQikGRlF%i5Fr_lNfUAoGR57h4!XR1LPoow*?K16J-3b2{1^#LM$k{|l#Y zC+rhLbL|Y;6a7&AZ@}-i-4eI@mZbktcTfy9hxX^jtB7oS{hyGmrd2#P^p95L{KlZSgGtE6-2K9(LwtjsQ&1XEG?-A_pc#2y# zPG)?KU;m$CgIfMKD`3R_tK@ zp7AHm|36>hM^M~iRWYW2$#=gHYZ@+Qf&U)RAE*XG96@|5`bVfjMsvmPSvhv$v;Z$0`UT>LzS{a)1n8~D9={yga0 zRGAmoWs0Q#RJbh^8$H&bKhXYJSnOlmcIaV z9C*bK>HQ1irTqu}(EeNi`Z1ti8uWdEUn=XPAHKg){Xf9(0KQ#4`(#JpYZXX-`2Lpf zT9f@=m!pDN|6lOd%z5+j5kl7&X3k63O6$*4w4;T=N0+nqp*-!+JpaLIoG|5?nX~_L ziGNyuq|hdBgXr68faee3qhoRncw_y0<~4=fts6v-_t}h}Fw&6U@iI&(`_jyL<0eV} zMy$1v*LS(W?@RmV<^_f#;X{LH=L*_iSqmS&f#8BKYqI{fFnN{qGE~{eB`djNw!L0vBzBSX_8UE7UG{?;O4b0ulkHt7)N294MUdq$w z%TDIu_tLb9y6OHh+srvz=DRf*D{Q^9fcaSgJoRH+g#JDAI>Pb|3)t^P`7Q8!@qX@k z=DXJhKhI_T3xDbg-}g;17~g1r8x{QAB_BkVjLC%{Eo-fLe zmiep4)0Jmat#mbemn3% zA0x=?Zsi?!){kR+-_B#ibJHu4p;Ll9TY;bLJsx}UJk_7i=|4FH^jpe)u7LkA_`RrqUVqcq zPVTRsMUct1-&y-qdplnoE0kdEiNEik!O^V$LwjnPPn@OSpd0Bpc*;}eKOuCu=oMAT zAYSC5eKgjSX6YmQjC6e4M&b{u0>t#n=7jfSGDOm+Jk{UG=^s4cD@NIxvEPgO-vPfD z>Qg^X6K3fz)fwe@L?`KoR~;f&(MbNWKhW{HzvKA$ozVZI@BVP_(J4YdJ7^>GkNtu6 zAB>mwPs-Eh7=+D+NZv}kD?~OWNZK3)_2Z{fF<*c|O zA%O7tUQzn<`d=06z78P#`vtE*Z^}+x%#lL%1ba!peu=B%ulYSlS}ak8!tWLM^bs4M z>H8Jrd)j?d)3rhO$LbnQmH0Y0&xpAPLm1x@o=2YF4E&TC>Do4}M(LtvNPN@WGh&km z!DL){QPmoFv-`J;)W08bMJ#0z!uUbphv%vO#YyQ})5rS>{RGpt+C@&D)gbKy`8M*@Psn+l!tOz#`VD+XrG7TGxF(kS(vg_^q@JYX zTh{u=G~d$?S{@z`@hbNnN!QN#Tvu3_DDis5IWeY{FXL%{ehED7pM!y??+exA)3psA zRT28bcS5RvrSmz_y}u7xHRbY2>Sz7RJ4NdMd~#JBJXhlB`{_F1dHth%)3pU9S_vg# z-wM@la_y?vuZJ(;^VId^No?i-nW{VYcV`V>MYi|=O`EC4v^Dp%e`M>h6 zCSBP&r@Sr!{v3@L{qXY_Z6D;V)#=KIapt$=7)TLL!Rb`6;G!te++-9ykL}n^8Eq%f9Yd>&gaj_Q~mY2bmg?UKa{nuNPaMX z=HD;Y+?M)3{d3DaKNkGiCnkxER6y^A>}AmFJE1 z8`X>vKGG-*&zJc2 ziGlpQ+OVt2*GBm{ZSKIYG~eK0>qp0$a}Iy2c1#>?l2P41;Dr`P*t z5Azrip*HAu_~v?fVeMqD&##cT0{&(6PTkX%rWz}w{EE+ikpGuH=07wa!~6<) zsvjG6PfL!IoX^+aeczD9d&TUAIIx#tKEv~t zH+9OvP~RzOPc6n{K12OuJ_hZnoo1wePu4%O^_uwWtfxV~NA+g_Py11oiW$nW@V~*z zmQ25o#|^RfWlw|rD!APMR;$j^u|PfK(k-`8n*#dx@J!4m=!frb$U}Roc5?|H_21@&kx4Szc0}K*(l8-f%gBF%}j)s@c!86u`i1^ z`tunv;A%&Me3kNzWqzb(d12@M2=ZiXDC03-rTHP|dqwGEzQ^a2y#9#zH_F8?BgpmH zN~Ztf&>68qZyy6c%S-vO{JCasp?ccL-wM}9g{jISJk_86t+w*pJ^1ewNc|t<<^4dsT4+xf+W)V9FcaQCgdFHw9~Lj> z+x+_!<>B{Y{A-sgg5#YCy{*+a#yj1(E-v`lk>$^r4;Q81bk22g=OV*=o7W#W_lvUR z3#bkFv!nF;%JK4kR+RE%?~m5Gg-%_ztkH$S`{2VKQoQ(n$M-j$U-xyRF7-sA`iIf` zBi|o*-dG>sFX-=u@$&pW?`68dXA9NVgC#$h|LbAD3f-SDzG_|Xwq;C`u7vj|4?Xk5 zo$wqMSDtmhHrP9b_MiP^kQlQx$Y6g9<*x$&2>7wUzcv5vUN&VM<8KTcB=&^ys^G~A z&t8f5RXH82c(;>z)HenF>$3i$H>1Ri>2QAh=?c%xgoCQ+Ki13->R$r=dk$;d-#@R$ z^jF;(B_1CcVBkkrJtgtQ*#5+>ZJt;N-^=Djhbz*(pBwn0^E2*mNd^A7%#VeB|LQ`Z zf|y)jym{$aVs2whdZOI$I(gF(E=+X6ou{AgVl zDn>3VVETXK<^0sKN^zz7ZGe9X@dob?Odek+)19_I`r-HU)aLlFz<1nOGCA$X8j0_* zRP-L$j<)A1Y=53N)<08mq3AfbgF(F1&oepRYoLEDY;|(S1Lg|q2jk`aK!0$f{pSJn zhXUX)DSo*<9@T(7FXpnJlTc5U>u-Pg&%mE9-`;(HqbSBZ1}+uLM+Fg6@Xz%JH`*U) z|DpOu6ToOcPyx3r? zh5k&SKU00m*9E?-tY2EzH~BGBtUf%zAl@v_&r#?1<#UFQa-c&1Ngl2&tp;g_&oCQz6AZZvY*?a zpZa}!a*F>i<_F{D^|O0VA$Lr>iOwmd#7ko*r!MUkObYDZvhzFf!ZTr2H#>vpD+l=d zz`Fo%9zHp>utzXC2H5$C5H464Ezik(Ok336ux1-jT7?gPSEe( ze@~maXcO~~e)#bOc_r{Ofq#4Wp7zO7iH~wFE+&*|KrAOLQhx^jpTIxmcY{9Vss2vT zzc1_88_`Jo6QneVm*>aTo1pK%`<^x7bsnH%&_1;rTvGzwl{CJYf@uQr@)FNhPI0yuAMN?Ni8)wbr`bS0#Onm-kcP*dUSmq5fm`O(E5y zt#!G54E^xW`!C>?GXD+uxf`r?qkBsH^wT}WsLrhno=4=n%KFtg{qxI|;)h8}gLtVQ zY|r+fzc0pGH!@Vx$9VbifzAt+uh=^ge}@xWZ`eD*8B~3-4VBq?*Lv%7;%%Ghq!^43 z`eNr*x~Cp&f3-RABJk0`j|AR-q{M%YKPNt$)Q^+~etAf$s^l$+uL8WsKlEp8yD0Ws z+Rq?f>Ie0ypJAY{KPBn6E`3o965u>JH$EJ!Q`+SPe4iZLAm6=70j$UF`j9YLYtQ_j zOg$yuY#{Mx!GApXM}83S4S{d|{zHO8SqH{@^h^^UjO}L-?*Y)K-#;ne82E;uKSkEZ zc=`6^^_u}-vge0{y`v>R*q;3O0Qw2ke;DXn10NgRE#dbP$xrJ6Ix*III(xpb{r3Y; z{e%Kv2lzJ=x+P>!lKAewbYjx*eyn{k-lFs|UcNnfz82`uN$ZwSW1FOp?a7Z1G{-pJ z&dh1VxKgprRAx@QenyZhDSoUyy(cD%)tXHwWnnzw`UB%fa(rKzkExU*)}GAz!%2*{ zD1D5VZ%>}Lk^LyNk{@hOetal5sJI&1Uk~k{+zsB-PF~uT5SP=6`PU4(Dvt7+&Ymyo zKl`SKH|3%I_0ax|-+yXXLZ{~vKeFJeICFJBgLsS5U(+i?Y`dYK!T7`L`+}d$o4XR~ zev|aepH3HDI9$&)S?r-^GuNVVmB;> zl$MRf$H0%1&+jAu3HTr*{TCU{g$v~*el<5qc^-Xs# zQGQ(TPN2^V=SPG7{r%r5tNh7J9w75r55XwjzkKQGxynw!M;XOyleBg%Y|%8rZF1 z{LcUSj{%>V2X~OYdVrrLvVQRT@j{?! zjzGr~tdFr{k~E%JgP+KXxk`VNeD}jLztz2oaADmKgLtYM8Y-Q8!5&BNDK8|m^ zpEJ4uHr~2Gyk$*ulpi79Y@_z)$A6x$F)l}WANU(a?L+GI{Tbus{n+N$ihEi?DZEOCZ>=R>X{+yu(7%-9z4^sa=y<8W$mgFt zZ#DU)))DvunNR=JKv*55!BKN0Qkz z-@W3nY^5EHw-sQ`nvOp>KJ(*0&#%6ot+WK*c>L%41J4`lV}Ic1Q=VULnyu^r`i*5j z)d#FoZcS+{()0A7p7;v=AKIT>^LwwfgZ{kcht&UZJmKeyCo=z%@_pYahXSvY zd7Llk_>AN2vm*5Q@fOE(odDRsRL?f2|2%K3|0pm*>9t{{!TiGW5xMV`b3xx!*2j2xKNmi~b-@0A zsnQp16^Qrncd7lCw=FA#7CI~W@to%ecKM<$4*V{euiwF1n3w6Or0tLM(Rt8+B0pco z`Zym^9^<7v`fky2ci$`ZFdvnY#}k}i`2NQ8OhADA0Xeg;>k=dDiR`7fTYS@yfOzw5k|(Xhvn@_0WWfBzumr-S|; z&=)|zB&Uz}4^kfQ<3^tOd3Q-0u9%n77xs-(eY}tR`=WJev|ib@pEJC6rF1zC|4u(< zgsr1*RWDs~Yuor8YAN16A+<@pFJ;IGaa|g%SN3{JT?0O*dk5W-7Pj%LVP^{sl@k`~=7kulADnV*9{{_`^y{H(Yx=+ySM@ta^D0rj8Vpf(vX zr6Rd7e_a}_=k~qib}wCrTdaOg)pcfW)u2Ardb1ey;f>OH@N(@HWPVUv!pEB`^VNVi z2Y#(CMxEC|y02v32nBh&#M!_P^8eD`Hp7E_UGGdP*Y{<91{?XY2K`}Ox2o;mSh4(V zc`pw#en4Bo*N>92PPjX2omrCiPuTm}_PIL!Mo-3L{U};r4(r?VybJKv>|Urh$4d9z z=;F$fv%j2JeM_uYxxDT^*V|h~16RSPwH~#=10GFRq39hL?|(1N(kA6Y8_sLcH7Vn`(wvv}FFzSSZN*tI~Zsw4R&K zhBPlQWZB|@!Y?$O{a#IhYzu%ki^~c8kUd-y1c@6KMbR3?u?blnY2L*Lu-wUw*7_Hw$ z>%$L$`=pRp0Z;3d-J5Ku?&;>k_?ABl#Q|}Xn19OeHsY!NHcmfu={K=@!X)Ab{%QTR zd2u%k^l81a{!Lq{$A@=f`r-4wiH@F7Qwi<|#PiMxLv( z`H}JC61Iyb!`m?)&r@rF?Ga*1@j4^kSU-25Cz*S6FnRXdnfcjgr-?Vq$g zQ=mP&e|#KE$Bqc7|3=$?%DJWS&uygkY`r~K+~XZ?Q16uIY5NbXzBGP>om4Mw=ZakM zv{tGQkM&e}-dO)R)RPZR8f5VM@;udFHfdRWpZZdLJp6rI?=?#cwBM;}SbCS&kR7*K z``12V=B9#p%l6--F05Fc{a(95y-6*7J5p(zSz1M>ohtJvy#en5yk+Dj^;8FmZ`sb9 z+zkvMD@(aBzMm0qtY2$|Ct2?kK#o>)VSdIL`SAk%LibJT$ee1-&&ETZrr z>*?WmRqcXV(n=Vwx*dI}&S(_Cc#j`d$^AFp2KQ|tUlsUKz&lNQqQ00O#CW`~ijEIh zPY?Nj>EnG^bbL1UL-kL9{_p4a)nle~WBRkRDv_S|ybbE%n7m8Y{!Txri!2N0yJ4?T z%Nh?9?|*9;kM(f)dNn-X8u*u?4-^3f5|8z2{9!z&^YyZF3%#7V`-qS1J*quYI7_ei zag4n`V*bF_ubZ*#BO9+8!+p4Muc7Wvt%sT=mQwu(tY1g#ztQ`H=zX%&WxgTsdx75- z^ib03zZU{^cn%#HT+E@VrkAs;QZ>y$j>9-sZo?lYi;sc*349xt{7G zs{u*0p6XlA14&9456ArMlN1;ujkj1&mGW4xb+OE615be07xYQGU@P%hua&<)1$opD z=k)RZ6!!drA9_Cv@5dGN8-MSUlv!TNukd~rKHtk)=ECNeWl-&f%=_V>+c!JbE2FMT5LrRL^ouH;Jf&#~S$_Z*MHk~LaImi>*5~8Q>b5eyVYC8 zcQ>hCT&JJA1=ord4dx@{3xKzL4QFB6yj7^|rTRiRAALF9BF!J_^~FHFz8KIS3wa^0 z|3lWtdUt$1J=CZCUhv~>^HwoO^^iR;SWl0ypVo8A90jefnB}!UW$7n4tNn4hBDCoN z#!qYUNGSE-m&n&Q>m~CqfL{;1+n4EzKCh(uY*^onuNOyoT3?RxRDU+7kM-jCdUU-0 zY~U}yo30o+RPr-Lu1Cj@&j(LS^^R!$s~0eSE_qwhz3EBA@tM!Jk*D>hD1ZNTN%sJq z#4lTONNN2R?h}XmvT1(43hG%M0DY`a^<37sk@c}&6F>g*Jk?j`lyomllk{=?=j;E( z1?*t;f@r-px~@N{l#^ST&)?X7A*}zy*XKC_Jgs;16L`A6=-!I{Zc|oOQ_vh4>+{h0 zl-Bd1_2Q^L)|aE}?t?jftarrMQ$l@OzliRqDm-7t&8F5V<_GI3@%3jGWw^M~^$j(w zZ|ps5qbpT;Udqk$(s~8f!-0Is6Z0j?)Abe&@U~TI>t0uzm(or?FH>hhILU!}usJQ7 zswm&mWTDq$S)cM$zYM2eqJKEaEYXPZbbaOmtXJ@Uq9-}(o|T)Iaz`)u349PtwACBZ z^$-=VhwM)7!sgQ$Sg-i7-CK9a(>8v5ytH0n)1x(6TF!>9KdAn$w{U#o<@UNp9=7p? zu@XP3rZw4b+mw8~@5A{0bUj4ox5@fPIsJ?d*5r4512Sy65A#F)^M2k=@YJ>RvW$%MT3&n47&^0Hz-r|Y*KhN(0 ze!!~InroR7f8%n2n7q-O`TrMhtlz%UXYtxxZ|0x+sRs8)w3Pi+0YA4uf46BVO>Cbz z%nzCOSxib~>oY1^|C!!D(yZm4#Ieu)+4{`wO;Af_N0?A!qL%HqwAfQjc$ei**K<_3 zp3~Hbj{&}#%%7`ZDs&nP{OolqTyGhc>_GRo>*@LlZO>b6zN=@IF3bGHHVPpbj|1uZ zvI^I8ke>zoG2lNG`>w8;E46=O^$@baGMFTu^kMuaSZ_g|uKyV8m-*A3_*DodpD*|@ zKPj-D!}G9SK~e{OS3hqg`Dt092ibTtkmhfmxZdJ9dI`->2wk6{@5=!RPU~q z^kCyH){~?>uIEs`H}H3XpAUR&)?4@4-u>A7$oGTpVu|kU=z5C^*IVp&u3*n^e^{Sc z4ejaKx~uw&Sb_b%B_H^cN4EzPSkF=6dJgh*J!d8Gk9Kuc$E}w5H=q5<#hxK#z)@$$ z?}hai-0#f%Xkk4Ed8!`;`Ueubs2y^p`=k<91d*=~2GjKx70t)! z{W#l{PwnaNJ6o>L-|FV-c(*$pSiDPYEl9zhL9Bn`dJgh)Wj?LLn)t>ur1hDft`;Pr zmErG;{2ZBY&FSOsn~>zg{LuI1?Xn-L9|!#*y3gu(e>=%f^W=(T<$*zTy+wuVEx+QV z@v|1J&(L^F%vi5Zd@03iT`GXopA)>nE5GK>qKE>me_Ge_-QDb6DR9gtKu! z)6Z+ZBwb+fp8D`g$l5=Zzfctbtj==0I4irXuB zZ=EpKc{2i1NvGE|ZQp4P*nJIO;eLd}?<2?}7kkap0b17nSf3Ww zTUdM2^%ms60RINgN`4#-`^8tP86Pvgwz#pAVf!QhFMVu(zJ4$ARDU>}t?ZiXqFG+| zG}FiWy>vdM_r3mx`)IaZ?!dm6#lreY)UHPQm`sbLMrEY?4oceG6{7moA+X+}db6sY zS61g6UNIcM(7mDlfUas%OXs%idzR_Dqrx5cnk4I-596N!UzC2;j+ccqrK+>}i2C^n zet7*>C7bBOzE??V^GL;eCb*R;_nx|m>*+(%2$B#VP3~Bnx~_r{Zx{1`OY(kNj&CzA2mKKKl}mrgA}FzsSNWMukQf* z$(ec1$K53TU9UR`bMN$I?TPClD-JYgf$m)#HO zN%PO+;D_hEfe-7Dui2%N_>}OKLSlhGq5dhK3;fC=^ve$N5w^zrv)_yQq4O{5)B6+W zfPUQXJk5;XVa!kR)Bs^rsjh^-e_{3{>3#?69{W?i!T6)NFgwM2hjd><=}C`;P62le z?njsdJY7$y13bOo!4&w|s}hg%5r5xAQTlk_1b?3e)u-zrwPin(LI2(aNgwaC;P3AU zN`9xH>obK9;r;@sV=^`1Rm$C9>Ar)qJ9i12zkD;eUxV`DGH-G!LjN!j?rTs;Jl?Ot z-`7x-KHk^B-v<#W`}qxiZh;>Q|5qtpXGr>Z9|Y7lX7%UiLA~NTgLnO{_Z|)H`7)?k za$>XFEPwm@wWHX*ObD%q%<9j7fc1piz~g#CBY2LFsf#BI_WKy$WoJjx#;un@y?V-H zJ$Yk&tXI$1!{_xsLpx=9`y^*HD#o5)tcTCnKd)W)s~YQp7eKx4dd*DTbrtS29_yd; z^~tS)r|UU%z33=>?})r&=DxL&^gRXZlhgSH>%F5st$$AS>vQ^8@138IYRUR^eaSN$ z_LWrn?biEe2J?gS5&ykAdC>~`{OTv?#=CmKc-4G{ue!%4>3bZ0@8<7o<9T=B)7ExV zH%XA!pO>^H^8bXe&cMbo~~`{~;V70MFTIiw5d^ziifj zaJ}72S#W9?jQ?xKTapXZJBH|2YfxEezUA1}{) zgZ>fXtA27tYJZHE_miWN+J7&!|7AF@q5C>O-K$yy7B7A;;QJfT7l3}4uY+0{Wg_wN z{tCW7@Vv1;_6I&*o)3cd_rJ18J#L*B)5m!E{WgymN&A1iu9a}3{cUIX26d(8QoPH# z{Wf&{>XO-ZuSXo;1^8Na8`N*It1^AO&++@Pf>Y7J=UDAD;EnZfT=OQ^gW8cEv&A&Z z``6s*#rtUw`fcJisL3$NkAupSBzJAc_Wz)tFD<3-`z1D+xl#Vp(v9kvo)V9K=>8MB zohhZPf4o2OO7DVGYk_y1d(!K(tWSBW|C!Stf6SZw)&~$i z-mjdW9B6;PgDL6`h0d&fFkapdA8$L*Uzfc>y)8!akAC?5Jbe3i0RGX!4eIGc;&DIE zsVQO_^&buXdEQt*v4JOf3iaanczHjbpx>qIM)ld>l0L@E`@#04?N7hQ)t|OWeM2cd zzvzeWZ^+a3>;dh++H;h8!-==7{jtCC{ef?PV}0xoe7t=7zkv2%-bbtcb3ht@FkbdP z3*!U5&k@EemVeUmb0PSj_Q^+W8&r|~UYHNVcG-$1 z`2W|>{77m2sxyqA(cr(@)K2Q*meP6!`r+3bd44lV!>YHJ_sNV_v(DjpW|ITWM6Otc{m-nNbA^AT5?Vket)aZ8VAqz{hc+n4g z-f8gUqeLA1g{_tsbC-wfCQu|}P{CaX+{7vTH6UP4yz@PM3r~Y+7=DGD`96xz} zBk+5QuTxu&k@!x>{K)$Afpk3a#PRb)5&EB^{mJMj0mQSJsG|I47_WGK9q2!rxla8e zOVamj)SU!Fy-K>ijecM}VgBiOvI_WXU)HNnjFHv@(GUH8!Oa(pr{mQI=s)_lL)7)# z+-3bom+nVQ7Q=qmVFjKzf5CY2m#6wSIQ?SD{zRDA&LCbGum1Y+g#NS3Ct2;|D?Ps$ zFYgEARYCjH_2gQ`*QRDduP;2(TuQQJm$Cj{!hw>{ttcJ|H<#q z{3`P`!4I9UAN96OIW&Py43#YhtF@3 zPX<1%(PZ+Veq`!Sv+c}3=C}NQ)T*HW1oXAC{s+*nB9Tk*T{= zDl6!EHO9;Pu}$_*?Bg(5UuYef>Im_^>RnUue1^0i75(t}ZEN5|fd37AW8giCTPxm{ zl=4r^Z~6VPc17sp{#ZU3Qp#3*Di4@(- zNbe))hwpE7fPYBa6ZjU;{tfF#igWCD8}v86KTw|Pr~X49`vV^@^3=}-X#eW)=SOYN zVEYF!UOvA%rVMgD(|WQ#!6i~`0`ZRfTq~v7GpRpge#PgvM}a>r^R)jTSy(Hj<}8WF z{FdK;OZoq$kNa=={jt1$1JKV5u}*p7CH=m*KbH4%2m03=Xdh*V25zy?{x_h1>bgkz z1N!0f+uOju2HqWb$HwryK>uIbYAb7h%y0Sqz*L{|3Ryn`^iySh+z-sh%lnCk_CE}N ze%reytp8)Yyq{5Jf)cB>nXKPcBQll7TTQf9To{eh1c{qzMtbo@CD zfBt8eI;{OMUOvBq@k*rQCmjzzf&aJQf5}+seH`;EKA%UPj&FCt|2^=(-(?f?kNGYC zy_n~X_3?W#|2-PUTM@_GwctM={D1Ln$Nb~>Xx>lfgMNv`uP2tj~DerKz|JA2eqo1 zGVG1y2jk`a!1&4LFRS(gU9D{=6E}EX1|9s8Lgx$g!{@h@r{f{zJ2aV07B_(BQL~P< zKjydm_jDK!|MI9mK-S0a>3qCAKO5TrCHN`p)sMA5#>@M;yU@=S=dY;{@58&6iaKZI z{TTB51>fH&kMmaq@S6ooMV6DqV}IlO1M+l!q`%h_(61}&V}Ib|<^9m#D=OSd@gP8I ze~g#UueM|cxYF^5jt`T8zi4ZvxMLylm|yYv{8r#`{Fwy2MLjEpXeRNP-}2x0i_*vM z`~3HJUVkF!k0w@%z6~UO{Ql1S!STcu#^9hww}0s4egi&U9DgXk1NzS?cz&BT6e&9}oJicUh(Q1xflC zFYgE9W%Hxoe!c5+X#e{#Kb9SC%GwkC@cj+iGnJ0#luv{9H;3o9?<#5h#Qw(jhobbc zKk)IQemBtH2kpNf{(Hr$xv}=gc`i{M?jlGfO>HzWj+bvZeS}o;o zOTPz`@#0W}{awh10}u7|$c>=hYGqyy8?SJGSJb9GDj!(S|6D4wNc}mt`j9!U{Yc3T zdsIsBGs5Ae*A>~%7|_pv^{a)g52%~hb7Sp!=RzOSwn>*5GKNv6Xr@41FYyV(~Hxzghy+B=aj+FnSpTIXS z*zYwm^1W9JnGXRT_E!*#NPqR>9xqt?_t+jr>iZ2PwJ+af?YRfqvnc)k&j*su_F)F` zLd8Djhw59v`{SigyVPcZZCLwbyu6=X!7;4;7eKtp;Q!*+gYmD{O6`e$`2NQ8CxO3J z;b8pXd!?8@_BXyi@Vv1;_6I&*F^LGOS+mB)?{EBKYb2 z+f3s$N?O0g{f+$o)pU;U2JPQ!_fz$)0IC1u{?#8l_NX4h`a|ZNgI+vutiQvwFS)4d zL=ML6QTf68&nN3d1O5&4|0?@yY2MmO>$g$g`jGP8-3<2AYTsOB?LTr;7uOr$f9a_E z@!czwWbKK5#IG;de%km_+r5w<0sKwir);$kEFB^$eTB7D5O{$XjMacCF+G&J2WjVgtb zOFrEU;^qDHhWY#dd@m8#TFkarz=yjJ^`_HWB<9ZGJ+XX*}e@3Ku(GTC>#&G;a z;D>BmE_BJeDJbz8k@8EoaJe@DDg8t*hL)H5P$$!Iqck=oRtS4{SlZN9t@^n0>`qw!9 zmTqq3_{~r{ex~7kQ7HSN`litTlN-gVzx3?H{H*kHBQIX|W#?_t4?OSo)IS}6YJs0S zb5r7bno8&Q(GQGQ%s(AJkzWYB1@N6?webt{CBC_@1Mzt>gpQxgKOH}j|Cj#yx{hSa z`A~y+k*E6f{j_^va(tZ%R?I)f%lm=xnYDj!h}Rj~$MfAL^(0?A#-ksYFWC8XI)63e zc$%-KhfPyoY%V=7H--On_XG1CYkxYQ+JK*~f0E+MoRrSXqaVJ%Ay4PW+Q1hdloD_HTxuWeZ+w5? zd8+Tj>0^K3ecnkp@9}p1t(kxH1LGC*PsdN5FAaR! zhy3`x@1*(_rc)iroCr34GXHe^G(Vn+TSU(qUNP*3+5mFzVv*NyuW+~ zUhK4hy}z_@>`UHi*?jT${o?)p-3cFCP1a`!uxa32^t?lR zm#QhDqLi;hA&G0owlr@RRFqVC|3b@_x3j+vjo?)=w5! ziA<&6*WyN2ReaqfwI}-F`y1uY$UObt`fRv`V)8*L|HuBu_XnOw{m-&K_6I&*DF5K$Y5ZeD5te>=X{l?lK^D92Tr954qqC{#q*7f^#6?LOWqynM522fP#tsJsXEg-!K<9?XBFs& zL;HV+{=dS01Z)4Z{ri%HOg3Mz{G7gD6yDpHu%qQ<{i61fB0Yb%Z@GC&^9v;m{P6kw zAK-TazXbT>z%M;(o>KTr;xWIqu6={~cLV<>_y6Om{sGW$A?x2dK9GFZbRvAbRG<1; z4Ek}vzYa1_nH(kQW4yc{Xdm`{P3hps3(s#FH=Z+|j^|Y0j?+JQ-Ia_P8cN5{G;Y3Ne&~F$8QT9D@NKfB^KPr}xsrOm zY`y63`vpAj2{@k6e7<#hT}5=9v|kJT@cBIDalE4WiP^RK3gt2>Kg9eN#uMhBj-RMc z#}i}yn@b$Y!oHz&{`wm)@^n0;`TyG6wH5UqO5X=DUfvIkx9s~19nWh*`^w(Upwc&R>p zzUX{$1=`;V{yyVVGW&jt@$!CPzH`C(l;-n$P3oqECraPT&=23=D39~CIq+Tl>!rlV z`;D=`@%@44>HFP&P9OUNA208x8Ssye)lMllTWbIRBPee32E#rNm?+trN!?my1< z`roZ@xqf&0=5uepnoj&9@;CJ5Pw=dCpw_6`nm>2??ib#CsQiQJ|Fy)A_~#RE{_mAI zB=FgX-uFfSuD7|~H@o@d9P8&EU2>Eec{jM;bM^Swx6D_Y-F)>k@%85CDDxs?aQ*Mr zx6CJA{hB{re$6M!-_@5te?;-X-2t_A|N6JR%vb;B|CRXFfgEMB?+(`E+Uu`}{`AJ1 z=JQVU&nU)!;XT36v#uWh`d0C}|0;ej^G|Z_zx8JX|Nn+t-!h+j{i*wxKVJDqkiU@S zpS7!aphWHJ0e^e_^=za66LP#_rJsA_-?i6Y5BIxL?tc0w?R|PX+m=3+gV(RC$3Nb< zUzc+C>-)r)YLIQq@0)_3m)&^tj|b&<_fzG6;>+*GgTKApum7#*8u5)sXWz2&Sg;<~ z-dy~C!Ni&~!&645s}R@%wj!_%g0 zgx%yjxMxdig)4p?@%w!BOd|i@zB412jp^#;UvRZ{*wEKny;RTQl6$=0H*7=xEyRES z*1*W<$z@-vN56xn`a`JSA0J74%Edz>tBweG@mEg|3hTeWInOeR6-)J;NtL3~qW{QW z=kTDg-&41GslERE(d558rf+1#yx`AYTzmcXEG@amd!Ci**Yl=Y_g)sUJHbY8yt#Vl z{~fOXmsosj;`R5Nl1DF&c<=jlUVP_N=IjpF|I3KKO#i$5Ey=&j@~2KQ zE^Kz1Rxh>p)q0W_`8Hx?_03*>*Is`;KYkZ{9+%Fq>Q5UtCgN1*V7#kG&nI!??T9b_ zZ{qh&`ZA*2`WasSsI$|<1|Do47|8QO+;~ua)vxjC@}IQ)RWD8tTb8!fOYJ@Ci@!$x zU2`u-+^TWf%kSFjuV=-dpKiWF{(9t3LHzHz4n)S;yviF7t{y$VMfHEk_42JRK7`*_ z>f0k}RKH7iy!iYl=ZF1sqIsZiSXZz91B~b8#H;?gR}!D0>WGll8ES9gJC%T|N6#O-`oZGxP5GZz zRJ`g@{JzISVw&b282VM#THgI@&x{3ODMNb%YF6&LvqtuB!Y6EvUFpB_N1vP(_V@EH zFSU1}ubyA%pEfgpi_G%Lj$r-PUVlAbH4WDD5%uu>NzCAyrp9zCy2_2;Jk zImBx|X-fQY>YtKsKDR*%4r^JjOc|8rBn;?-VV@280G;LBe%J@N6ozSLfS{?o)y zh#MMmEziY3*o-Lz-^G{>)KPCS0pWTmFn-6_i4o_U;Gp0zxzhS z#SJyQ_`VYcg?;s24{ttE{d1yYSNgB~J!cFGn;oy~OYQaNS3QY}riwVaEm)6h?<@6` zQvay_?}V*Z~m+IT8FK8(dDJ~`s25e|L^ENku7hQ_VT;-`s>NJxQmxx<2e)K?c}Mn zQGGi-e5oEiA3*izV*KYPUiIsGNlpCu0qLT?$obfdkGH#4ShBDlfqDEszUtTb_s1*$ zY4Z28{Kpg54!ifD%S-K5yt}^g692U1TTx3QA9(p)d;Rs)Y}?t3uTMQ??+=NI$+atL zMCq@~s{dU*`h9%WuleUa;#I%m&)*snbHDqwsB?L@d+|vYgoGV_$D4n=`ZfRf{3Cnq_%S-L`$1mjf$76-ekGeW#xR>9x*I&;kyMy&;KJ3Epr>UjCZo|DEx+cUacQ$L|K8 z-{tD@ulITqult?ipA(;db;pQS6H|HDuUqf=*JC~YBfndZ`Pjm*1_gRu$;HO3%j_Ha~f# z^}hHG#2+L78O#66{y|}>N40wC`5qgr9_3%A{BveSCEZZj%kSFjuV>?(vk|VIzleW5 z>Ga6$xu$yK!PVnmPi`V!?NxrQKNq)(iX6CRr5EqkSN`X(Qcr|CU&^11{5vhbd;Y4w zz5aS05kILx^T>V$x_S9sd;Rsi-Dt8f9BaQ*&y!il^JM-j z|Imjs!#Ypt67aXzAD^a3pP0%&jM#R4M)3bvyY~9)S(Y?y>9D~AVusWVgw>$Etrm8S zDpq*CgNaly*+vY~yj=$fFck3(v^MMtw{6mSaY59vXSI;lR&(3);$idX*>v3fpjx4Cqhc(%TMdtBH_;{Ek}GN5;a;#EJ-e~YQU zDo1FJqQUvr)#G1JDqi&~Ui1H3Dc%m9Hm7(QT`z8Z<$wNC2g|SXtNc}c`Q7uE{OxUT z`4zv2_(WBUhMwNpz{~I2>#rwd#G79IiqFk>zSAQ*^upk~FV*8;Px|8v6Mt}7RA|*~ ze|qt5edT{1l#5q;U(N5H2jy?C;@$bxdbN7kw$Scp?tA%Nd;RsK>z5`%*Vh+ZFNOF# z*sK@dH@0Z_(t6LozIxl@#}VIy^y|N5$v z#WyEDJKux$o;V^b``v?Hyjx#YI@I2KzLw^H<=1@c@+T($4a>hhW%aP~J$c@&uf6{K z4fucAQ~3G9*0H&~{I0$JdPdakUpf`{ujJe>PIA7Mc8I^F|9Wo}@u@9- zDdY3wanqxsc2@P`-FnZz9&_<2$bX9b%gA5a<+t^izr7=U_3-CdJde76Z-S);QG^jpXpye-?8{3#CIhAe)RQ_s2suXKi&Gd z{=!<}^{UPZ5B>P9N-qBJ|B?UZueHOvcW)Xf^26B*iof0BtxBp#&$m%M8OWcwLyFK# zg;#j}Q+8yVusHpj2K@EBShK*Ze?ReBKU^K3sdU!Q%X{kwSC9Ym!gJ!4e=qS`&xG7v zP`ZE3;QHTvUf5E$t5^T~)c?%#D_;4p`0_7mSv9OD_50iF>e*-UDY{+W_|N@@k(%FJ zd;Rs)d^X>!|2XlXTrXEwei7C1e!*zPyLuGwo`+e(;*S#l+h2Sy^7Km7lk&lz|GMX4 zs{SIg=Y_BBlD?9QKW6!_`0{T!Q6;R=!A1dpd#n5Mcc*{4Rr@J2{eN5a^Zfb5X{JWS`njan-ssG= zy}w`ol<&h;{|xGP`Tw;1E&ci1)DF9FAXvX^?_a)pwBEkA`$WW$J#)PEr)#gjo;UXf z$AiYF*2AB5trn85%QIT}Q5z zT6!;+_xxK|kM7s1|7xmCmHhGQpA=1^BPMPR_Rr}1gS`G%yz1ZHIlULJ^X2mIA^&pA zfAGeju$w|CXn>RHP7|8D&I<3|&} zKlk>K@u~ZG@p(JX2zzZ-gTMg3|5rU4|B840qxJ9_U;g|RXM~kJ+vuhC`r}U!KYnl1 zkmLDNdHG#?{q^iA_>GtU0R6Lp`}?oimWHqe`zsg!6Y-NC42kI%{dee~nlrt4_wxeg z!=1Vx)o1=!yt}^2l7BJzBP@T<^|Qh*eAYDJZ?B72{))d1iOCbUMwt)iJo56p_WJ9Y z`Qv;q|4rgs(B8#wmI}Rf^p9YB?dK`}pVxfpiw`CKhlYXBEmwohH^3>QcUR zi;lUx>)qAk|9M$H;&r_oq#phJYv`#%rQcW;{P~yrd0DX^&hBvQ0heFbukv5?*Ih4xw-602x%K6LH%=Pgowpiy2R|*yD6*z@x?a$ z96Gp9a6Rer``1@4ekbwCh+lOscA1q+zVX`Y)>r=L8M^rYE5Cc5VcGp>D@<7SRd_a@ z$LQj}C;w#fH<-9SblWG*y!^+zw+Y+#`pfIR0js`<_=@`P5#N>g$-^R}&c+$CMe8M3 zkAHpT;_nl$@%h>Nr#5X~6kH#=^_BnmkORp-m;4Vb|8(;IV)@;JQT zOK&ML^r2UeYp=ha;(14Idwiu=Os7iw%T4F|sQA@JhE?qsTz|TH6tDY{){}~VLVRc9 zPh{&8lW#D;x4QSZm*1_g{LicWul(+LmHzgYu>4)fAD{n6`A3?OVfUVQidKKR_WJ8- zpDbSKe^w2QIaDM2w(scw;YFW>j|$ zeC8qc_dAsTA^8>mE%Bd`Kab^i&(HL?*PmbOjU4&vh8C~#^7A?U^=$0@td#0ke*OHe z_2_$%Tk{kQjyGHHy|lg}-qo|0_^-;}iY%MXzCXA1WLBPct@vEJnDD(k68W{zWo1O9~5>u?8AV+y_d-Ux8*-i z{+d^JN9H~k*XvK$Ud>mop3Cd&M5vyh$giIl6wmlDGHc^{Uc9Tvzn-L?2%Rt0{~7TW zHoh6vXi9%C-mS0ve=mmiM!0&Ezlblt`+G6|_NpG`S9^7SGd$fHdADNl{JQq~>&da~ z>1J2|G2$Cm*ueiUO7l`Z{`F*b;$1z*iN6rOE;4P);NP!t>ns1?ukpt#|NFlD?(f(5 z+pBt9|0upw*rv#Lx~KK(aqacjQ*HN)&5vmBO4@s!&%Xt;>|=h*7+jCp`qsa`s_Kh> z&ipoH$%3d8?>+PKyY&^06ia>T&J$*Ha+x$gro(hdr6Ea`XLv;iDtAZFv&>`LC;o`Rd>GRep=_ zL3{_kze+rF#J0;fcYFEW`bzcdel(o>k>WMqD*n~{^RCYfoA$Q%eBgiE>+-9fuFRiX zl8@Xr^l@)5ziY3*p3k0qUU~-impAuq3M)$gWXsyOO!w+JUaH5xzEb>`7T=xqVN~&q zWg5>v=Jmf@U-|!@k;|XP^5^vBcYn{w-`-*5pKke|{WBycS)t`+GS_Y4)#KXhujhE% z38j}1ul0aFkAFP3eaQX<)xG)0)#G1Zxp>{*b^mSDW&h@56XSd9L$|*2|9z-q8b33Ry;aG@YyG2o_L2XT>o>(m6W{pBl+Zrk1^+*ZtH;0IQ@lH0 z>xh59;a8zYua)w~k6Z8g*JJ;c->t{|?cMInulX}%=9thPtuuM~U3-^ZyW{=+h-p0k zLF=d}$x_u!3tvlfl`d%H+gW<;kzc)2ElCpYhe@A>_>s}4y!iG1pZr@2bq;)1wprjT z^`yUdEqv;lEC1G`{A;h|DRr(tzf;Ydj09z`)WNk8ZIueo%R+Pb)&=w z^#APlR(kQS9-S{epFgjiFU7|YfBE|fn;y~s8M?0a`oGAOu7L?1+k5@F{>Y>7uGXK` zExynz`Ey?D5=hmljaR?Fy)J*@+p$aa7{Kqu77q5mYwxS|97`NoKi?#$m*2Iw`rSL>Ei=Wc_yf;F)_90@V@Z*PKk4OC* zznb`;Udf-jX6L}7xplqx%ljXNxADcVBmU1jy^EJ4f5s8P=TWpy+cogr*LDA=o_RIW zZ#r=*LrCF&Hk8b8kKd90cA3{+SI^A9?u0k&aVh*9e@{*IyZE!mYHrL%eBz7^{*$sF7ZpJ_b+~%^BXrJIDT$k z=oPu~ z-FVRX^0(L3ldjIgl81XRKU^B=^}lN`^UqG-d>G^Ax762rZaAUO%oXB$^|$JjGcf9_ z#+q+;`sV+CPrI5saA5vNUc7Jq z`8WT_L2WjqXN{G&-lHuVwS6xz z{yw$c?_w0M`^%Y?V@i(XZ+!MG^_|!Mwa4WNEIZIn^XE?A{m6^|zw*aFpF5C@XO6tu z-bYr?uq+8SpXtrd;5(1-`rozp)q33g_8{)38`99;CmkMp?RE7i-Z%fpxcRogi0&Ky zrvDq(3jTeHJ56#1((ew|@0$-}++kdvH=U>d4<}mV^}nm<)$!Ju_#O2BSM7c)@y0#AL%$WAf82OefBMIR z;&p#~HNP7VI$!?wy7*W_^KM=`ke{*59_IDGYcJR1PTzbUqxoO+d7G)DHazAtz>{&o z`*np?c>?D@d0+GGPT&2)i`V_4_QT;DveEy0%Lf0ydbt5P16fFdGy-_@gd z-~B7b-7nI1>b#*F@te*De_yP?NzPa9VEw-PSB#s_)4YJu1GkN{#+WTrf?ta&#Qmu_?=>H-Ig7;rn&#U9j#a~DmQL+m2&o3)hdj0Rl zo5qiSJh=E*^Skk&`u**7@rf&4+f-rrkeF$6gZE$8UjO=P-M2{tH$oZ&=AUf4`D#dm zn3cSKe!5tc=3BSE@~`I=e~tLL#5c=QKPGvVB!LH^$@PC118#l$UYgtC(~jNLqQf1@v%$hl6|MBK4SC8s<^NBycO!bhmmml$r zx%t8Q)~#<<|FY{3!#|+@8sslP{^R7YY55me{v#1x0#}AN3;5exo%p=OA0>Xzu@Yt9 zp`J7e{`Tr|?e*8A{<%)SRLIaFZaIBN^%kHdIIw`14V) z|K0jl{dwW$!|*NEpZ~x7RhRPlB~Kf#e%IdhR!=zfY|malaHU3)K7lY4_b@lkin?HWgUlpSZ5P!T+@b}Z*c=L}3fBdWY z-FWb~*B@VOQb_a#<wni?|N83erlf(Z)IY3fgP4y)aztn2nJ=MfE_m^7eWiGJ zJ-YZI#Bc0SB)T2vcUO%&Uc6i1#!7!X{K4TH;XgEqs_5c}TK??hFJt+aP3#;P+k|KC z6aU(khvBPViJx%o?dUzk_e=7Vm;Zy4T>>G^f}a;$JqI~oXBao5vy6(^#5mG;3V%1v zOZE8I^9R^}n)uI&ua%~LO!YvLz{$-iyzAGkZ#TblJ3PgK8{xlKSXS}S|B=6-QTJAAH@*Aj`%{vw>_P|>>B#NTD{0r zRSWprtN69V7bO3k7jKolL_OKwKkVgq?e*7V{TZ|D;PjA1^y@78>*wC(U#iDH-W1RI zidjzlo#gcclS?NFe82t;um9b6^N$CAyz+m^`(yP9=6B=4-`*eC&-wN0Nl~(Xpg^M} zfsJ9o{&(&5udkR7V+NKF&Yz!mdp&v~_q*s#8@&46`bzP>`8H;N#m9d1R`d(LBQDTn zw-@i$x6Fq--Tcpd7}KA4&4(`kHp^dTaqhs?-#*g(v(wE#%(pRvh}V33;hPs*3UGg~ z_wHgZf0t&t0;_%veqM0(Fki*!el(B!5%Z_Uf6UAea(VYJR}b^gzw3GJck%l&)sLzD zZjzXM9fR|yTi-ID>~!~||H|KSPVT_=(XGAuU3>lU%ktEZnNl-JOoGh8`O~%6U(e-@ zNn?_>x*3s``SbUSiOO!L{}+r|>$TU_qjod(T?r<#+A%*Q5S)_ZRi6UXPBK!0UfkkAJ+W ze_VVZUH!nlN=ahAj2oPP+<5bk2Nz%7?vHwX!t%TE;BT)#enIZ~ff?13#LRCO?0?r@ z|N838ChD>IzhspPWrsW(67%6_PrdfK^_Ak?dS3DFe(~kqie>8(Um;=ee&^P=T)#Wr zeD2~k|Cg}*%`E@%{<#A6qv~k>-09{|fBb7l!pg27|ES4Z( z(eKTzmcXFrUY``TTtHK=cyY z`&y%5dtE(>ck5f`=NNat=o7y}^hn~X-3#8|-TGGbyZe{o-F$w|^1p8RFPzF9*!*Kv z&Hw+lSMlzC5i3wGdN=te-wv+dTzmcXaDP<)c=zAK^y^+;FW)rH>wi~|f4nJP_g~$w z`=+fQlPf$)%omM<jCWjR82Bb7EO$DjpO-N&2s_kz< zT6o*MG##Xe43N>hRNLQyOpw{UGz(;fY>?f&GzaKkPRQlCpsM})r*`M|Tu}4ibzB`s zb;wKeg8E1Op_=5S`5-?OfP&_wg`hC-aKKnR;M4QcV&-}1Z7d!<>1~%Pt|XL#(&nWh z5DI004~d?amID@5vGkue%bS;0fN3UkmfnH$Z)++R2f#qcWM29X41&Qh#JqGU41?kDnR)357zv}`bMw;C zFb2lLIP=mkU_4BKiRPt~U@}aBsph5AU^>izFU?D5!Yr5#bIeP>g1Imc=9`x;fUjX8 zd}Cg^$hIv;mzbAopSER&Oy;H9w-lCv^2tk;r!W+QIOe6XLG7vp3Cv4%UUc3Tz;g4_ z6;KLF!fWQGiJ<~~3*VWSW`r<%?YuMtD91`zWnQ`(!a?s*jJz}<=vr+Gb<9g^Lu-5O zytE~hvwhA>(|~e(0`1I8l}p>yHnl}ws(o=l{WjOURO5RMXgseqFI@*3&+4c3=A|28 zBW!}r=A{u32~n`cyfhlN!ZwI8FWnByVF&CqFZ~{N!EV@NUb+|d!G8F`yz~Gl_8=TG zFV%YvgW~l*dFc^Q>_GU@yj1V`2}Xfp<)w<(F?6h>=B39B`N8I;$3gvh0#2Hjo`TbG z2F{w7sy}~*UqE%pOV7c1_!X2>UU~t3gNty6vbQ(N^=g`Ewv@x`S7q(4aaoS!MG*M`uyj1&@BR1PRn3pP-wuPXj%}cdU{Sk+K zaY0@>6-^2`As(;grO9l25_GkB=_*(WB|*oKm&WHk^2H$+$V(NUfPD!e5y(q7u{|;R z8oUnj(j<@+l0kCw(iHFpq=Z!FrK#afNCR(~m!^fcwTfxPCW`5-?O0M#HbRi1)S2$WY|S{RBzQ7C3! zS{zD1NhoDrS{gzi6v~*FmW6T<1_ATZ@=yW7p`v+dC8!MQBh@M|eHT=->a1d3S{156 zb*N!pS`*%bT2R}(v<{@EFYBW9%u98g`tUyJ9LP&`P8vW%_`tlh5$M>VFO7mN5Di<+OSeG`Y=<4@r90t!*af@IOZUKD*azAtFIDV*_yM#}UaI{E;2<0_ zFFg!L;79n$y!0p>gX3_*y!0fTve(W_)fcC2pYu}n*BLkqKbx2S0_Wg7{Ayl$0e*vv zaLK&%clZM?gZfBbdIhe+HMnkGdIR2ujF6VGAurYaCJR0@WH&F(1~=Ju3vPqF^pf_W z=|J}h4EQI_a1qv)+Om6BY5{SQysSgln2GhOO+=N@74Nfym@I}ZG$^-*Sz!| z+=mD7mwD;m@DToiN9LuE;R!qijR|?F#>F$xwKLbe^f|l$jTyzsOSQ;Xf5%4Sn3rmd zYI|IKJlif+Tzp6X3C&9rL1K6fUNm*#@pkOx$&yj1PU3;7_wd1(PC z2!%lXC@)pN6^0^E)V#D96o(Q}(!8`3l!g#c8|0;GXDF0`vgW1bK*!Q?RG++5b%#L! z%A1!~fN-b?ij$Y>{mDSb(y`^GYPYuObtUuC%J42!fvV=E)u1|bWQa8bdFe+`178!~ z19@pJs10?Xu6b!as1NT$1M|{`@BuV}56w#(LlbBUADNdngXYizJ~l6H39XPid(%(Q4(6rWu3XCbsd;Hf=medii+O2R=my=Phk0pF=mou@k9lcd=m-5_ zfO+Xa+cpRtY+kB;s%Z!eH7`{=v`za|x4d*13wcZ-Vwn^yQ=A~(&C-jFr=B2qou@fP$d8ytr2EKsL$tN!z4U_TH zpfboyE5QdahW%qfUfKv1^5g8av<2Iws$cDpmumYEP#)!wmwpOEL9d;cc7}M654xC_ zs{i5wX}s@J@sFV*sQvQNr=UJg$T^D# z@>2En7ZfudGVoenS`S9sKIf%%VI$Pv@>1QWb?xdJ(={b8)ipd3 zCc$L$(kU<%rh%>zd8w|M=`aIyJ;_V8?@Q45*R>%poe8sGHt2ZrQeE5HHV0ILyj1(N z?JLmSA}`gxxlqSm%WJ#h=D~c>JS8t(06S^-4*1%z zwiSUT=B3)FZ6#o-d8zirr?2&$z%ujF<*)+21oJm*koS186qGOqRdOTKs0QHZRVvhupM^5PV>_5VHfO%J?5o*VIS;= zAIwV+z(F_!hs{fmz>koXnsb1>^e6NYs_P^_$V&@>w&}VlU|yJ z;S&4~f0&nEhAVItu9=r!hZ}GcZkd2<`mzSo8V(cppB|u(U67sXJ zD!dEw(gM&Jsz4$0(t@DRV){I#7$8t_^to4Fs?Wof@$W(vke614YET_&n3vXs z37lKq-_(!t((&kf?5hQ}L0(!1>OwuJZ(jO7G=PTifq7{o_z)UH6Z6uh@DVhF=H{g> z;A3bBt;|bX!y3luI%s2FdV<#*U_Jb0UV0ifLtA3nfxNUmd;*cYjs|&Y2lP|u2%XGJ zJHrF^e-ArBUfPB2x^L`(uI8oPpgZhi`vH)b_COVT2zr{A>ODu;rg+85OM8K0e}rE^ zUaI%}f&K)Fm6s}B$I!8kfxNW0>2dt8woj_&^safS=KBa#{ir^a zm-d0a&=2~XmkxmJZ`Fx6h%&7~08xt*PXtGtpTv8?;uDm-c}<_^)6t$V=zJd{_Wqo0l$x zZ(tEDHZNTQx;N_{eI4YbOW9rwO2RVp(&eD@qw}S6ATQN22jLJLHZMH_Kf+IN)V%Z=1-!h%uCe|I?icOUGh>LSGA~r)KBu# zGjJAuhF{D}&%t^46)u>U{${VW{i1p4CHNiwfXn8kSKumKgX`v{H{d4Rg4^b$f7-S? z=w0(t?bEhm^2jX!y*i&q=pV4vcp zs#Ue5gjD9Gs!8vQ0Uc9bsyyl=#j2lEvrS&AW9yhY$2zC- zQXTtEP+zN^@>2Dk`bOEAtU+@XbzE=W*4_1@qEys0fvyvU%ydPz9<&HS^NyPy=ehd*-FJpf=Qjy5^~h_#?4Ri7eMo-?gN@D)INEs&XdmF2hhm8RCR75Msu0Qh`dze z>qGW62F+3OQqA!~UUQVZRP$>SVw!?-$xA;%wNEjcyX2*smvyaZyz82km+HC~@~P~# zG=(9r>rJnvx^CaZYg{Xryj1zr2DL@|g4WQ+ytFN}gZA)=d1(ju6gomD^U}`H1-gRHhrF~KbcY_$)4a47^oBmr*SxeJ z^oIeU_sC0?L-E=_(7bdI42C?Q_sC22z9FFfij$Y>`qD9n!Z7nv9b4OmgRWtDsrIQ4 zKZ6nGr6XY!d=8_{OVyvML2L4#L0&orRqnAc&b;&s7!MbT`3>Zy6HwJW5hj_JPKGHk z6|_E+mtKQu_~|eM1%&+9 z_FAg-fskKlucdA+@C|+uXk8#J4+&v0EHN)l1Sw7RT3)&obnnt@=cUUaEi4D+l9wvC z?jc&|XrH`P`_(@cV1;?&^aQW9y$Sv!+b-3bv?;n0bPRc^<_>wqYyBrL z)w*jHzBQ}{dFdKh3+rILdFck&2%BKDd1(YhLKJKYA6Qr_|UwxF*Jdu@R50GGiVMi;A8XBme2}XLmTtbw$KjR!zbpY9pF>w2%XGJJ3|-f z3f;_0yF(A?3BAlqdqW@S3;oPX`@;Yj2!qT^2g48;3d77xha2*rnU|_>g!~BeQuWtJ z7zLl3myU)pFc!v{mwo}`VFFAvFP#MHo5?W6ymTr|gXu8Cy!1=h!*!#GcWxT=5YM4U@pi@=fQkf0AHJzE`)Dj5iB+@T>=^lOJSLLsm6q^ zp~|q_ymSR9USsH6^V08NC9DFS2YIO*v)aDewn^8(T383`%}Y1HM%V zd8y8Cw7qs-x)rov@!QNxV_-Y%fSu;0-@`804SUQ>_rgAV?Ywk9`~U~wpn2&bI1ESN zNAuF3K=mJmW9Fr*?>L-*ljfzTKz(=`&X|{~AJ4+i@QZorIXDl$!Uglv-$3oU2$#%D z)z07H54dbzdIfYG)v0>rr8@3axCYnFOK*Vo--KJ{rHa=vb!@d!UaB_VHm`m1(m&x2 z+=YAQrT5`=#(Fwv-(%0a1NCHXCOOruzNC7&Ayi~`015(;+d2LtRr{vZ#lv7@s z3R1(HkjA|9El3M*Lpt-)^q}wDdqW2E(v0v9WP;4*rCA^=Wb=7#)4uGG15}&5RPE5V zoS@p}rP`MZKI6EmU0$mC_5GH~{j1DN^?63$DQbOHjMwtgJjCtB zhvD;@mufCQ2|3xG738J*?(8&_!RtNpQsv0U`%2?WfxI+7s?U1*oEHFjslMNdkH&)n z#K=nvg0>}tLguB~rK;NUU19_?XFAHAZr>r$E zReNi){XDAgSmdSep=yKrU*84EOZDB5@??fuATL$C+OE&R3(ZTvf$v}qsITRv>Ti7} zD@?nJfV}h(D&&ismukH%s;4S;v!?h8gmzNw1{=RLE}xguK0ytFIZYCvWDyC5&sc^2|j?6p+mK*(3M*V13vCgiKx zYw1O{RYx;}<_LLd4MSe@hh9r-!h28)YMYnVfx4jiM)Qumv>x=L&Yn=;y!3r&01e>- z^U_A}AvA_2=A}*HBWMQA%}ZOr$Iud5nU}VPHqaK@nU}VQPoM*QYF^qAIzeaXVqV%6 zj?qU)p__SWcleq%on!kiATQN5BjoinFnOu2BO%{|_sUBbnfAo5FfY~iUTo_PeL!CN zE!(F;2=wK(ytE(m2hDH!%u5HrKo|sr%}a;CP#6Zo%}YOn5ik-)nU{VJqhSn;H7^|p zU%+^nU|u>ACc$KwVqQ8GronWWVP5(r%!FAm+q`rRdChYhe1Hkp@hh6wn8>$x$=OCwQ@jRg>8 zUaE1k1&xNSATQkpF|ZwWn3w8Y?u74QmwD-K*aLfEpLywi_yG>UyVM~s)q4IQ{t&1> zdFh967=HwQ1bOLCpt)Uhy1rwPm+JeaqwG5d$3b3tLa^^R+y;56zKhX!QGY@ljwLTW zg(`L@>@Y7?e#PkX)B*ES<O(`2m)3Le{4c2jr!3As#fR&SoGl z9SL8-$I#NebTq^#CIPfEFHOknPZ;YRpgphUrR^XQ`x3)zATNC#=CUsds{5h5v>SBg zz1>mWPvoWD(4@pBgFd{Lm-dF9?CX!}J@Qh$Pchm*2;`;7QT=RWFkbJGm+F0!ZNK8= zr9(i+OaVj9OLgqAFdXz+UaGj+=m_`>6gT7Oy!4)^sT&9V`~=s^w}vRujQo$*srmrG4?*M<)sDL{x!Z3 zTG+gFA=`?eMWGnTON)bk4mOoM{dm8;bSd%6K=Y;MO?l~ZROd+ju0EHS>im_!mxNLv zFD(r@$(a=NyXW%KTu>ZVg8qNHyj1^xT`~Hn-$9U<>UReNt^NgO&w-W|o&Whe6N=TAP;+1|3Vk)1u#Dk(a8TS?p81-X|~p64VB@ zMgPZMUb+<8(syTheFo&E?a(Iloqjf^pOMK+kHboQQ@CVas`aow+pGb_$xHRQXEk~O z6ellLo>A<}rTri;)jXI!0iPexc&NNIE+mF9wx=~O4Ol;^-?UGCCRJ=QNDA4^OS3{W z$6m#;)_}ZJbt<+TlmU6E-cz1@YO~rXFRjM*&)KHBRkOTQb9n^_hl=KE})`7ZE59*tjz7GwcA$(w7+6X>`#?ZvPv?+W9&7iq?X$$xm zT0$%H($>%h+Cn??()REPbbwFIOFKd*=nP%VOS?ih=ng&1OM5~u=nZ|$OZ!4U=nn(T zO9#Rr7z{(qONYWR7!IGAmyUpuFbY05FC7hIU@VL?FZ}|>!vvUUUOEXT!xWfmUOEk? z!wmS+ymThag4r;~y!0!W3-e&UdFcZ98WzGg=B0~ZF)V?l=B3MEIjn$h%}c+7m9Pp{ zo0qPEwXhD>o0o2Yjj#zeo0mpFBt*d$^U`S83fmyYymUM4fSvHYdFd|L4SQg(dFejb z4?n;G^U{NG2oA#$^U@#TCpZen%uA2M2{;L-%u7$h88{0+o0t9q=iog2YF>H)euIl} z$-MM;_yaD(74y=oa1E}*4fE2Qa0_n3pXQ}^;4a*Q`{tz&;4k8($w%K zq=C20OVh&JkPgzDmu7&B@D5}$FU<^DAS+}uFU<})ASdK9FU<{kATQ)IFU=1Hpdb`7 zFD(p3pePhGFD(uwpd^$sFD(rr5DI0?OUptz2!numX?dss;ZV`Mv=UT?ccF@TX;r8O z)uD!YX-#+!YC&!D(mGHV>Op<;()XbOG=vY#OB=z5&={JSmo|lupcynbFKq!ILrZ97 zUfLSkKwD^MUfLc$fe!Ggd1*)J1f8Lad1+VZ2Hl~Dd1+7R1-+qV=<|?1W99&PX?D=sO6wDS z&XkwthCHCpYh%ny^Fn^m=dUl!OAEkY);fcrho-1iyS^~6Yp8}!grTX5j zEKCD^-jJ7;1AT_m=ecn6(h9Jgwyl6~L0TWv`t>x2JMFGd&>^yrJuk`j=c(2gS>PNbY))+s0H#;)l(blK~MA29#DY% zg`l5#XWL*}Ii;U~~{K6yc2dK8T$HcIaYd1*9kg>4XHUb-EM@IHNSbdUGSOYg!C_U(k{ z=B3Y|I4Djr@=}fS?}^<7yFp&M2lm1~*l%9?0~~;ZaLBy$FdTs&;V1Lbqp*a}--}@u z$V>JADb!}QJq(qX>NSwyg$aNeN@E&<-d`JbaK}z$|Hz17jc7nLK z%}e!JD<|Xvy-!}McpXdM8-$yeR)ET&>q6IrytEQp4{ZST%}aG{oaC6N;55if&%jyu z8GbP@JqPFESGZta`Wy5jpS}yyxRjUnL`$Rn(Z1%TnwKu}o=fmM$V>l#%Wws*nwL)J zJ%#C?LU72uRNn_*WB+xy0rJwDpzlESUFb1l+f8Y^3h9~BwPvIFnhZp9h zdgx|shy!uWOXEQ=cI$UI^?Qc$(%!HE^gV;VACZ^#g>#_ae;jCDIsh)faTsh~Itb45 z8UGv%H!mFqqd>nedBwc+a~KO(LB9(pFC7Q^-M!l|!Mt=lOacAAq<%M2UOE-x)3yYV z5agwa;CuG1hZP_%T@Gq{H|T0!x)ypu4^W)ERQ)i5HtGAEP3EN=AqujBo&g{)-GV0O z*ssCsATLb<3)rV;H|zj;>2g>E-@tD3(p|70#*uR@$V-1f6}tounwRQ5BiXNb#mY+$ zgJLs*&Y!$g@6mY~DTwtvs(2kk#~K6j(xa%5A7WmrbFXtT8Ty!)YFz2r1y9gv=A}Ac z`YvlTs(zH0>U+7QiTPXu$w6M40^Wd>kjlI?HM|LFASdsYm*#*`)TrO1*6)PNOY_28 z#H59{L0*~;(nAKwXkPjb==aF=d)RuGiM+H1nu&dx;REy1hM;|l8BKfSr5fj1h|LPw zKwg?1azIYVWnP*a@<3k5XI`2g3P3@aNN#!Q1kiK6^c=6-=B2k_6315i)pmKQuA};F zYY25fURoQPLLu@N26^d6=qJz;I+~YufFkTKlaOo1ytF%L|8N*)UaI&7px?vQ@#Lj* zVFxwox>#gh`ZX-oHqf;oFI~s>q8zsv6bE@}2`CArptN~u2!uizC~IC?4)lB0dWOa^ zke6!AoV zf}Sm~6iN^yFD(ul%NmCogYr_1>1w>EI@ADpX-#+!YC&!D(mJ3yUe}whHF>G#dYw0& zM}4O)FAe2&UEWg<>Vv%WeP{p;;REy1M(`mth9>5vP2nSG2F=Y&TfoQA5?Yy;wuUy) z7TTGYwueuk1AJ;;+7UWIXXs*H+7-G%cj#eW+7o&~Z|GxQ+86pke;8n1IuHiIU>IUv zIuwS%aQMu;bOelqQSiBW>1Y@OV_}?m=@&2_Ccs4V(n&BGrodG5(rGXqX26%`r88j` z%!WDUrC-5Zm|sr zymSL>giWy7yfgwLAquvbmqx=@*ak7?rQ2Z#?1b;lOLxI;*aLgbOZUNk_yG=>mmY*e za2Sr5m;MMp!BIG7UV0o(z)3h|UV0kNz*+d&y!0112j}5e^U@3O8(f4-=B2;GA8;A2 zn3rCKYj7QIn3vv!TW}lxG%vjaci|q~H!pnvf5G4I(7f~?cm$8(iFxT$cm~hmg?XtS zULPCcKwR_Ecn}{FKtl7gV)VVlR#2P2FcA!Q@|UL5>lC$riM2m4ZLMunik%M zbdcV>Gy`OWcOa8_X=ca*Ss|NwX?Dl~IU$#MX>P~^c_E*9X?`dG1)-36X<;Y=MWL8^ zX>lk4C83mgX=w<7P$*+wS{BMd7zE5q%R>bShl=KE})`7ZE59*tjz7GwcA$(w7+6X>`#?ZvPv?+W9&7iq?X$$xmT0$%H($>%h+Cn?? z()REPbbwFIOFKd*=nP%VOS?ih=ng&1OM5~u=nZ|$OZ!4U=nn(TO9#Rr7z{(qONYWR z7!IGAmyUpuFbY05FC7hIU@VL?FZ}|>!vvUUUOEXT!xWfmUOEk?!wmS+ymThag4r;~ zy!0!W3-e&UdFcZ98WzGg=B0~ZF)V?l=B3MEIjn$h%}c+7m9Pp{o0qPEwXhD>o0o2Y zjj#zeo0mpFBt*d$^U`S83fmyYymUM4fSvHYdFd|L4SQg(dFejb4?n;G^U{NG2oA#$ z^U@#TCpZen%uA2M2{;L-%u7$h88{0+o0t9q=iog2YF>H)euIl}$-MM;_yaD(74y=o za1E}*4fE2Qa0_n3pXQ}^;4a*Q`{tz&;4k6Lf|y=A~Vs8+3;r=A}KM7xacc=B0h1AM}R-=A{E+5DbPP=A}bn7z~Hc z%u7eWNEij5o0pD;F)$X!nU{V6<6#0!G%uY5lVJ)>H7}h8(_sdDX&;6yz(&{v zo6SojAQGZri+O1@Y=vzQV_v!)`1l$t7ZeA1XqMebL-|IPCuw*t|tZwKxS8untviy8NSeRlBS>L)v{2mb!{Va0PV zp1-)=Zfo+}gBQ&IIdJaruaDQhnEF?SXM6sd^Ww~tsdt_|FZ80&p9wbwUc6|L{Q3Ex zpQU}a{zd(3#g6=Xu1AI^!>;uCqes8Fmxk1Mp6Owxi@DD4oit=^-;qz!KmGG^%s-v$ zmQVV)`QJnS30(Yt060L$zdV|M%=Js`|NGBy59MFbKhpnc{kZ-A|IzWMi8raW&b0>JUP|meeJKWi0+JgeM_T zn7eqUV|ybPW#^O+&}~ycSvloeP`-i(l9r zfe1vb$r)i~^6K5a)qcowuVEhCpx%+-3!7nB!lCy}SzQaZJ{bsy-+>ukbJk%wK4nAR zIX3lF8+QKvPvAHz>iR{WOZ)%rXSmY`3(?(xmA}E$ApM93Vyc_GqI^P4^xka- zfvW6NeK=YVFc=J###oTUiCtc(+$_P)Y$%?2)efPjzHW}K9fiR~ir6@wZl%1C8e2zp z8(5iENtFojmBOty{`9`m3Ks%kZ%NUIpX9YBLIn7drShnB%xn3TGdd)^#0Ew{<;xbI z-<;7^lke1c(e()!f>?a+F$g6Oto+Y=3SZ4UCX+9R7XfRqQvby?03VP16)UlajyPx3 z6$~X-XnyL?_JJQCXxtag@mf!j_YnGqSY5AvJkWRh(a$7)`(e^?q2Xw)l*|$WLn%Ge z7n7!n28QM1K@{N{$M+H6^My)d!rtgmG;Tl*1bwLx6wqBT0Xt$ZY_Hage+Ud`%dh?! zYOfej#r2i4{KWCPntr5OjufhxLx}Enox1zu(KtyevFg+N_|3XpT?#V2nzP3lsbR&* z0N8a%63j#Wv&e+Yj-tx+-e~C71{k9e=wek&m8D>jidsCoATekAKgAYmKhfXlq)305 z6z8Re=>eU9)~1hn&g%UVnB2m$_P9)E(1X*@ip`$FTCTwECm|tpUASP|Re8o4$&cmr z*9l0$tkZeL$^w~x%FY<@>rGR(lBL79)$JLmuY1Y?==bM@6TMk^47p0|a*?_#4NvZW z@H-P;Z(E=izH9*pud`zhfjn7nMrSg@=yPhXVR2YA>KZ_T*2jp5ou@x! zpS~uhNN*x#tdD!>HMvMXz%0ueMGnC9=-BFylgvFll0ov7Tcqg1NM(8U@Y@jrQD5Wg zG+!Fr9C{v6X!H`E7Y)V09I)5JuS>yo@~2nwgcEnwp(o^!B4w?l;yyg=6GPpn)c^IU z!3J>vq90M=-AO$Hg>Q=t=QfW;z3ABmW2F4{5+awu2WA~75M?I@DxM4bzGQp=k!e?! z61|Re5OZFnSKuTC8g3@r!yn)87eN2@3q~VZWva7)?em5o3Vr%H(4dy5(#`^E7cJUC zLTU-{cQS$2l(qN9)j{0R(tqxfG}eNqOZ^t%(9Jh}cic{cF@u9mZC+u0bF**8{O!<^ zsiw=an<`#gGPQ(=`LQSFC?$397ceTXyw9{+wR>`S&ZXz63$>;v-+RD){qGJ@pd8If8NHoOgFM| z4b;GWw6Pm@cj!?~Zr=^^XV|uySqVz3&_DN(=aYow2!$_&gJVbz)Qlg|G`)t{L-hW8 zP!(EKUYGS;P2y29p@wNM69LqJ?OxVrxqqQJcJtTefJ2SYG2MpLy;FhPtL4T2&-VB) z7Dwe#%K_SX-qy*{TbZrIN*^%a0qV1zxl4$OOKKw>Wg9f zI01lwg&Uka1!tLgudt9FvJ9X_u331^v3LH?D66*E#30H1XJQ#3SX(EKhlq$NKT4Sf zLmbn0KqW3abYHLSzr%~VSewJunhoTAz-5tV9hDNS0ak!oIu9rNmZnWTe0hpTR;YM` zZ-t@;%3%3TVVK7lyB^xruZ(_Dd_$iF2mX!@knth@Catj%G zE*$tIIttu6rEqvJC{ezxd>u4(w=$Xl5y014(?C6ME162xY<=b7`Tl6JpMeZV!pyM+ zbB!j*&DAS}hdv1o14L%3PO0RK#&;OoYno4cL!Opn!@asyS4cB$-76W;^E031uTP(O^FTO~<&1;Lw~F7=H?;PH5t>$7 z!nQI`c#zDlEpSixJq-v}Vc_e+2rW6iON9ev%?#*9vK#FgQA*fC)8Q^0_#_PZoP!6{ z4&CXYXF0i7xi|`=PMvM~!l?Q6>#9Nx%-K0y!9Hj|Z+BV+`W1q(@6VI~7Rq1MhT$Hs zhLj#@yzRRSd?tu>B`gJwTo{0BlejYiX~es?^itokhek5$!H@J=)7MPi z5J=F7iRp#do-^nQp5p_tHocfA@TmhF2n$|mHD3ahMwsDnn5y~I-BT|9C1s1CG&>FK z-s*YS6h7*TD~X%6Wt(PFXh{DH^>}%`pY9)Cl<5TS%AtZE@V><&6RnY(7V+#1W+8;5 zlJG?_*_fCjO~~`~p>S1CQD4bZ(S7*OtKQ>pVmkZ?eHqwjEL!B02klfMxs+_%AwP10 z+J4(ThQtTuegyOsNKu(K#B7V!q*(q@u2`X;u#XB`;P!u05!xAW(1MIGG9$mj~{@CyZIgB?6LVk=5p{&Z%&)6V{PN&uVI(ZOQd>@xu4+G z(B2IqdMWUQfldKIynfMKKjc<3R| zf*v_H>J}9vpGfV@HI0SVF73;F|5&HOgDn?s*?2@YUpMbze_hdqGmV|jfIBkOBkm!R z9C{DiP&h|LW&x3#e||5hX8d5x?pbR+MUXuQ=AHX9h=;hg_~aJcN2v~2w9^4@H#k{t-h~uJ&%Y<`^s_U0nJXYG{!1D1n9cDkC6FH9M})W@kRw9B zEh%%%#VhPXJ$<{FmY8$}cky%Cg_NaR29d9dDw|KJpSKoc?7p{X{_iuHXo{SoEoF+CdWkd}4e{G1FO7;m})?nq4fI z3Vyc)K9n>ccWiOys*IGx6hgqjaH{ba+C%_E5uW^WfzSgDUcTQ&@z6Ly+>XLbWRO_Q z6oz{c!YR?J-ElTn2-+BuP1IYqC7dnADpc0C%FoJ^b9_sPeiy?l-@xy-+C{ODY1v1= z`W7F0cO9Vx9}TjU-aJMU5Fe(sw^y2>+zve9>p_b};mVP|k6qXvgYNZyIedVuru(_3 zSK&Zh>fZk;YCB>X4|SKjH{EKw#2)e6ET$YyLchDPzPEhQ23-J(*n~xZgIp*m0lW}0 z#UQ}%g&|H^%Ic2y%yA2xnVq4A?49?V7VV*zR)+4XKdcLo>Z!%fM#}0{_`4|en_M&w=^9{goBiCp8%gCh@x^$m z3BPI%QYI^LZXk+SuLz=3q^4@0pQugK)0HLj7FU>-NB|Xo#J-pQY(-U7==g#GJZ2l} z5+*ZJ0;M9&t|Rp*w>49PFqCy>&$vnwrH)rGI(-ml7GInlun(LQ>HuhLttj?*3$DVV zp~$-SVY&H*&HM3A&~OScscH>#{z*BlUEu1C?=OWSjkl3nqgOEY|5u$gfdk;j<6K7# zz5@(r3#{v(I3?(*R&Uk6g6kFwWD`_ZYB6(_A8hIsG6Igpd?vkK783cCzgsE+9m2)e zdC{XYH4?z`lAgF~Dr}tVlC-0)<*5+;g(IM9SX67X7U=ChM9rQk=zHqxqbmpXt&MYw z?IQ{;m7V1Lhb&+iZKpDA+E#@tXGfLVQU9uQn>cpWN2?c}$v4zou8f;~*8jdJe1OM0 zs8Bv07y-QRV*rVYGS6h%Os5t%vGSM!APCZ!YD7i`Zd;h@vhSDjJyBez5M%`-w|9Ze-2H8!<=-0NVy2Cz%T=wb zK7BSx1rVJ8vE1PfMq1Q#KO zhglK}>1;<`wF|B=@E`={oRd7O5+jQ5LhP-1W~kcxx@0fu_^GRgzM__NA_R>44~GDV zaxKoxTF4o#YA}~AdmDKA4wgEHb!w4b3|<*L zUz(j5y#Q4-EOD;N&4#)LQEpA-`*Q(u`|9QH-m^xdXzGn@p}4a1~X zV8Pr0tBh71=)wf{rOY@dxVj5ANU9vagiYMBI-UMFl5L9YZnUOrj}9%ncM;sj zk&w~aQxU~RK01sceCW-|lR9HR#qv+?i%F0LY}{omd=31F1#Q(%3SSNqup>-m>S`}3 z#W(YZ?yx7q=$J(UzYD2rKV9};y#weUqA^`2udbzGJeiVV#xW>^2$zp0AyUqF8u+`l zL;@`UIXc)bl`|A>0)~vzzR2*b7yhBKJt`@!=Si@&5cz%MZ=tf?qC_Mro1+-U>H6e& zl$U!$N!dBElMyf{9FvZ&jpos?MG)6jXbmE+=9rU;3>t-u2cWuyd{!q+l}=S4iHZTH z?b+6dh!|Lzfk)4Z(*|P`;M60)+~A-kdduo;#|oyeKS+e?1hkuxH9=;8<$FKTwaBu0 zoAHp2!?lW-Y|_05E4-)y^SA@|7>ZWZbO^+?%D=*dZ8=o#OYO|0hMCB5fVYt&Ya{!e?|HI=W*b(tQtB-QX@1g~ z%^SXg{{bTa0;|6G(7_5%KRFVy#@?hsu$#ry2vK{Khe%SdL4VXXL9hHG7#DrFlNhum zj+3R3(yr+679ymXE2NtO;8wPm43ZU7Ivt-`C$zNWJ?^W8MRJM9p8ngqu}VNv8#UbH z6%c85Z4zZd#0jAkabO1 zf~|w_QgN+~cJ;EHSSeZ_aC{Lt1aZ)|;A+G;An!0&Cp!}g_W4^Ii1P+Ys4kD&iz<7v zqVXeaGQ`{s0%h;x-DaA~%d~S;i=ADtY-nF%F)|m)eS;2xV3-nQJg zF)qej7MdSncbZS2yo^7bzvqU64tv)||7XCKoFHXu76!cLzRqb~1= z{}qO{T01_L-IMo~^iS_l4{3@Hi&K9-{s`^e9XVnL(FyEjWv%rq|GJy?WVG?eMOrZL zI{WlsI=p8NCNE_;&oSKklSTQHME|LfDT%zUQ(#PlBGw4tMCVHNIm!F=5g65~xhSwX zh*PTf=T6;}9JmTGv1s3T*bYk8k|JunNPMH)gjMELX$4@Ik8@A#pGpi&^2sR38UZ~h@3k8vsbBc7cfh1M9G zuN?y59XTr>K;|)D4?Pv%6Mm3zu!&n-Ex5q}W}|XsRp+lb>x<_@H7jiG!N?t^RvJJ( z3DDh8o!z{-0F9mgsKLS?YRonO{uQgA^{U`*OE!21#-Ud#cwEdgaZJ!0x0jrSkaC{z zA7vcPg$=J8FXXn&2Buvnuzj~f3uT8@iXy85SudZGThc-Qnzgiv`GtJ;5}2$m-Fvy| zx_h|R6ik4L-^6+7UjBRNg+s}8ceM-%W7S)Y|9UCCsQb@#*2)NTJ-qv?y(`9F9_0@O zB`l1>&c%V8SGqnEpTP~9( z)0YN!*n)GS%kh?ppKLq{V{4#Opc%Wn=s}ERt z!(>Jp%s)94R1ON1h<0Ys?ZH86eJ|h6)!A#HM=fJh2fyuN4GDJ)N4zp<;#}mF91XH7 zXdvU2hIHs?6{0%nsVVowt|k&3I!SM{TcZ>0L9dlXMIvjX5O$C}{CWfWOSi1p18^14 z11~9n3RDCVUp;JGFSnP}5o?*iN5`M+4mO$98s{cx1vnt5rGB!se(_e7HDAF6RV zJUR8ieR!sM#)o980z_))Ly?5ho35+Kk8k|qS3O`+;$ciMY7}SXKTSD@@ zI^&U)3H{-FZj9S4qa97@+!b|yWAZ<1_KbUbKAw0S=rqL&M+yygxn``kGw?UH+EcnC zltEm+i0W|(q&9Ojoc_IhDa{^365O81Ch4EzX44EAQ7QqE>l;mDC4JQ=*d>B}skNY? zJdjPx=PIj$Vd-USoz;$BM}W!5T)&Kkucu1Y?x>`!`TvB=u+xv=RmvEvg3lxJo~EK-Vg z3YQ?j+3HHtBxr29kJ1;bIt;$D9-l!JmSVLwg#pEDtVmX4`nYpCASU99T9_$}S&rt# zt`7sCc$)eKSJt&&n(J>nS%t6B;ZtxMYl__0d+7R9zL0A-1|YSJ-LDo749E`hY?x)Y zr<_hT`gcf$d1I*Oij5r4n{h?=HLA5Lehuptw7I2$@~bt&w;V@PHqC7|Mx*E$Up3hO z8L$erA}4fb$=BzbS-jr+o>&)yb$bDv-_Y$(7TJETyu&T|_WqFM)li1GfQ>~#FU!(? z8j=XVLo8*YI+0}{l15`b&0ZG`;!-%^oVpj3kj!tP_9&}L=M$QRJ>|hNa%@e%31H>f z`DnDl1Eh{aoOBHp=ED2{2?wcO`wQdNrl{!(!-~+CI8>ICaPDg#RoPtBPy}E7?4qsO z*E@6sfxzU1(e$|Aa9iao`a;R0xzZn|3M#MmFVtp7^O*fsK=xN&Bba952A3zN6suCF zwv$4=1$$&ijO}mwxp`idPOTApDe;mUopz92UYm4&VsATf9wsk?b^oQmetY|?VPHE; zK=CYN%VIk8;vXY0(iGf5$&EpQY4^cZyz}X5j0`n<<;Pu2+jjo~YO8}Q!dX@u{m;cG zuI01h9A+`zJ}}eU+)u4k5$KNQL=zvYLe<9!wsM}L@GVs*eT>|Ky%Qa<9O2rEuNNK5 zxpVg<9N*A9XT#g?JvB8Aksp~7O9d# z86Pz(d_BY)g;Q&#pU-X{KUo@TD+%fbN|slBG}@`J zm^tM(jtlskDgViQ(o4Q><@&xLMX)?Kw;i(uvpCVQWYSgmq`PS?WFj*>^Pj+)`&l|k zRoK(;86${LsYt}gU)D1Y5Hd_Ip${b4qbd#zuTlIH5P%0g*cL3^<%W$D|2aLkpb-b) z@HiN9TC6ISM^NI}Ry`p_|D;-DaUjB1{XPK48aLOM0U3fXvt%j@)WG5EBPfVriS~q2 zftWT77h5J34kDtTnIK9X-DcJ&62gEfdjbiogohq@?|E0r0@!(ph`x?iT%?khU?6B4 zkyt*q0p|Ox(r?{&CSGK>#=D(yYtJm5&lj9J2wA}ttLGEvQn0@Idrkthalp`hMCUAZ z-34@*(q@LcYN!`D>M z_)JZ(kFc!jHky6j!=I=m_IPfFJkC)hET|U2J~|(YtH{Diut-AQH+{xbLNE24^b!~q z)wga5R@C+87NP2!#B~ZQXy;eMCcyNXe>uoXSBE~?`&wyJ#(OJrEPi0w_VFLmxDXp5 z77Y)6J_F|0I{L3fP3_|>2O9I#0{%qn6Vv}?=Ls>O+s*!(4dp-V&47wbV-JYi(!C(_ zSs<3kzdK(6Na6Y!Siz0)+u61&w6bl+Z!L`14rFRm_jPCs7JApxr*m65D0g#xxhZ_| zlZqjO8>U9KAqJVyPbCqPZM`b19+22`9j-EK^8?b}Y z_4Yd$6;9*X$Jmw0Epl1QLFwAWR+89?@TqeWkT03ymN@T}bF9D5z++tR2-_wjBpAik*2FM?j^*k)}IZM}nA zuLuu{Wk#!}9>QblZ)t?!&vXb`n2ff>`r4~(1p@<%CYc= z<;KJZ2%x`T#$LwDD+?yggG3;k??GB}iQ&zq=l#aGQ7P3wHWh(f=dsRhpiho6M)1r! z_z-ZKg>kJ4=ujTL%<}koTY<((f-}8=)P{?gQ2>Th9=9y_ip34Nn$u6c_n{do*);Qm zhS|TzhFD!D-;Ictm?L)^1SiFrUg4GyxRc$)i&B7jka}fGw7)Yrebg#7C9aoVsa8aY z`8fay7y`iu#iSPYs{7Vm{j({T)|lzQJ~lWK33Xffj?+{4W~SRH93>?T_CJyF6x=nz zeT(GA(_FDuXfO!J`h9^ocFN+f)9p923VD!{6(5u8wF1F*)cF)A#j zD4oW$bK@Q>e|s{<6^!)ZP$2>;4E-4Y#RA8^)=MNEP1>N}A?o5A7%l47hgc);>fINu zE0_dabZl#IZ`Y<~SlvDJ6`#x^NhtavXb;fGcFXgj+rPnIVt3leq7xxEn5u_>XI@QE zx5r%%e`ck2`>&Y)E%-QnEqYAvjH$$_ua(^jQ%HbR&48b+yS-8N1mnc%8zC`!|AZ)= z{XIQ{!XSw_M`(*~r9i4B#_8D{-d=`BYO-R89DvlUrvS1{#FKwcZyx6#DECE-N_a)i z4*6DAdiuLN>+=V;RN4v4m3d@7cJA^uX4|!qPxw}%KvC{4{$9GN>5@;V=IMLSuTXkZZwt)St}I9kV6jct~P<(xv+YNk&}G z*Rr;CYZ*&M$zpm1DJv-Wzg3>Tla+Doban=!*%LCQoU9*XcPhj#Vw)9S@QA*xrB#G{ zSwYDnv}CceCTtL$$?&M?HE)*Y!O}OG!QeLzpykK}0KOH3CY{iE?CREMU(4DrpJh(R z{8^LSZB|<+e8&m-by^6(Yq8yOy+shHbH)%K`ix1%iI?{&{7{|2`-n1qUVxali>J9{ zoQ>tg0`4STpH_kU+OR@Wy2|Tx{753Emd)psdcFG^+xvl32JXSz0D727C&WzGv7%Ck zP@NOdvnim5=!nu4Q*F}ul*5s(Jejl^D6pIYd#OMpzN@`6zNd1B=3U3*$Sx{TaI!H) zyVAh8Zrq4>bKL#vtr| z)U@U;qQgPoyvxG&bIlc=(@`A`E(0`Uax?nOgZCjU`7Yp$3(5xrfoK>%gO&!QBa{>M z8II#17$BH~tld`SKa**AE5QQj=NZyL=NEF%C&I#+*lYSp_Nz*Ggp$qp3!cO`&=*KR z%a`-xxbjGPf2DU>rqVXt= zSNzV;=X#rAYX;zxrumoG?FeeAOCeJal)p*cU}Jnol2M>_^t(74BZVTR?y8oz$H&|b z3{t0ZWvn6t-l>)7KV3+!-MTjF&b*eSte|>jyK_H62@(XaX9D%@5>6(OA9N}POAFpN z?F%})96(xFmYYTmq31I7ofx5cs|cM6`D?dV0<$c#{X>e+3S_nP-Lc|@Djqo0ua-;J zArW{-3P~uoTc4S6twoI(owtK{A4R@$r`6jq+KUAUGe2@2=n6i%MPYY+;`;TN}~pPTR(`(bdnQ+_>&I z&3D-@%55aUy>Y-1-qQ9+Q7PL`659eh*9(sb-OpAyh)YH-5kEh&Ig7(Ua4&pFBiwNQXp$*Re?U+oaP(m263Elt16F=sMgk(iv*GXvB~fDykcGwF@57$( zG|j>~7Y3{I%noW+aCAfymU2tlFR2*Qtv=(gy#P?G>?ILKFeZ1JpX~KKwKbO62ykN! zP~n_9zM4^uJ__MXl+C^ihUznC@KYcJglu^v6w=Q0%S;+NAJGvhV-h>iJSA*Vc7N;Q z!%o_G@T1$p4kkQp2#?+XU3VU%e_%fD9sjUqO`TI`GHKgZc-uD=hh6L=pK8{0{}9{L zMu_@4B1X9TDo^XsU3`v^J%BU6Dsv5>jtg!f#P9o$UeXr|z884;G#v^vb}k4SG_`jt zO92ssj1mmr1(#4=><>#KR*lJLXIhIg3mQ3X2M>?H+rwsM2F-zetqm9YS{weZ(k-qg zMvL?_xGh}ixY4PCI07``3$)>(XZ~^kXXh|G`hzDnfvV1?@nHH;v5Be|l<{&HRKX^O zaUqYUE(!-`b$zHb`D01j>uj6#12FgBlO4FeOWf`I@2fG*Du@tOQDI3~AB=R|4}OVb z(QJTK09$GlsiMz-z@n)UKXAq-pde@SAA6{cGJCb$S8JtCMD%u$R|>af$sa>QfTnww zG$2#G_U(4}-nhEp0-qKInge$G3_9(4nY0#DkDL^X>P6Dpa~UPtp)A{C$Ra~I=Msr3 z_Gsypj9~Act@|U+#ewJ=Yw~^rANcCf47io{PJt32|OkQ1XI}x0yt4f@m zcMGD1&<1y^VffOqAJTwOdAxcKVncx<6I(csFisggGgeO*bPYzP4d_Ircc)4&JeNY$ zINy=atQwd0$S^DT+RsTmC}uzyKRjmF&?>I|NtFoo3l+5~tlfHf>!(=9X)^{qLKAtR z2N&*^e-rEmbl25-R?sdYCA8fRO#CoASfs2x_hv~1fT~=GMPW%vTF&50>$_=KtWkn6 zXUfHt$vyk~1U+3zxZ-FdWD`EA6pwFOf)2;TrVOP3fkI&P^`1ZS-*DP8VGt??Ik!#i21QU&8I*!oOpEa`*9+c!Fd(EXL0vuKW#f) zo=6$4a08SkGg1-|$i|>H3m9DOerqgDM(vt_3VJQjtb3%s<%K{5;!)jexpY|rtYxh6 z8?hz=ULFluSPw^bRgppNGz-f`<8sAZu^_{45;}*i#dY6ca@Ryhnp=}`d-P_J{6Gs& zkqC2`gw`+!M4D__RGJtcxcr-%314$e)$7ouh)2e(xs z#;-m{aUHL>82cspm_Vi~(wRrGPnQtwSmTOtd-Gk`Cy= zRXAMWAb!CE!%?;A!hy<)@}m~EfL;a#TJMdPfyJln4sIrt&yZ`7%c!`zeDmNWP)eZ? zrLYVYTCMs527KcCTlgH*B&$RU6PdD!{UGj_&SLuK5d?Udr{E3(v1TGD^vK`X9hUz& z%MP+BCS)Eub@zzleAwa1FWE;1#KC(Jz^<)^+_~FXaVY4&`AUAU5%@zyT9T7?=8Z6d z<1Z@qm38tV;eyA zZKL$pe{VV>J4sH(Jip}wx01wYBt(<8X^MBhm{-D2KOE%xYjdiv-t$I;*NVj~0n3|d zwP}2dcJj@IdBAZ%HvrWw8dcf#YQgY={+};rnA3%oyjf$IF2ZSB^g~!T-DXa|rW+p= zZnSnbT!SM92i(pmZIPx0v$b^(rbwsxV35AhZ*sDY-qa+?%ykz%;S32AEljll7We4f zsubV~2N?O9o}OjmJ(~P|ynkk4Vy{i|)TUueJ#y0d4MC_b!f`Q>DwNOoPbFmRH+Y*g zU3H_47_cW?*LK z3=IkJ0tsl)S9|cOM@+Xo<8)kt(IRim!+H?;>1fs@dtWn7qRx1EyH3N%_QnLb@`4*S!JjaAaeK4^>f?ORr(a{T@>$y#qqBSi0Ux22iM z*SZ$<_K2pj7Xo4y*aGSFKBk)DnM^le%jE6AyObGEi82xmI?V1&q4NQZgO{o9)Xf6i zvn*`aRYJ%NzV!bZZ>|?=;58ZUfiZ=6@7Sy$R;1LU|1v3=&Yk^}+3GD4n?J#9C7rY< zWiEK_+~p3~2WE9rv6&JbWRV#^VUGKTVWSCXlW~ZNG5#fyj2bVQGmo3a$Ji;OA>H&| zEay{U7J`JNK9``dgU)wGd{|WX75Sk_HqM9b@5V1&ELr|uO+je)!`Mp41N85XGne(w zuZW7!vAH9_6P?#q~ zpGj{W|9Pjez=B=x;#0%hiV3j&FnQF;Q-?=_Lnbz@HmvZKEHt^hmM-52(Y^8W5!BM+ z0QBchB6sT&3&_tTlnfras1>-Peu;c>6vXoB zpmYB_YCYwEb=drfy!Eh8V-9Q*Ifz%Qz;-kS-Fj>@)r| z#m4fh#i{8r+VO)dB(e_2Yc~ut8vEu|e2vY|8n~6d#&Dq*5V~NNpJP9872F zAzMBy7&9JWs~*~qCUe!1GIL21k>Q?q=dQV}*>?AT-<*6}ehXO}{R$$tvGELWpGlxJgl|b=ed8-xvH%xz)CplQZZ5qIx@4GryS~i+eEK0HY3_ zVu3ODLyO0Z&fJ~F3PSOx&G2D|@@t5i`%vIRRa4l*y_^wCHv<_~HQ2;^3KD1GMujN2 z;JM8DjSeAaY9B2m)1a%SQ_~)hxc@VY8C`jD7El!)^=|ds548LnIVmW|=Cq19yxAB) zrCy?^sDjh+a~eDHoy>u5s&|~5xr_kSwtwLo%`JvmdLx~vnrPYQpx7Eh*7B9B~V=s3b&piv>zMrj#Z_dPE+L)bLL^&a&+vX)35LMroPsyf2L}x z$=876z=v^uRugE_U@x*IM@?(XG+L4%GCTJeC|9%g!`Qtxk7^SmQ~++ygp@Pq4$UN? zf7rJUC)cv9EGxTa){}Hj)Vi)H0dNuO^5)zrsPH^*=B~4SX@pSwDFI=?6<7Nd?lp{b z-U<XpooiRBq`$o-?Eb2od48Vks^&OZmYfKqPicUGn-GA{N$`lG zWqQ!Rs@~4{w8tpxFDPBNT9T`>Gfgh$XTHkn3q;;J1D86;7F@_^SxbzXNNzo6jnVGx z&sIA2Q2x(MK^QMt<9b&e_y#>P5-w8O;hoayTz>#DI#V=iPA_*KixglNv)Q`_A%qZY z2dg73%Je9X@T!{dr~K7rjMg70E$CBT*KflSy1Y+^AaA`jDk28jI}NY zzZzkZ*$jUZ7^GR=Y1l0td PPhOM`(LSS9;-Zxd*zQx$Azj}@GUjpu<90&l0*Nr< zTtei4nPP_xyzMbb>}-@GuT?yOiRwr*McwICx~*drNzT$w$IEqP{-H)%1(VR&b;}qs zzqA%Dyf4C79_f;prA9<(e6KvX7^N7PcbYj!7303exXS+sUCmtPl?8e3ty(CX02-&r zkdTe!UC-n8_wQanBlKT1{IdWQ!Kg4ZWy$DURf~SM?RejZIeE9AlZHhr6-^2U0mzVP za2#=9aYN?@CsW3wY5i5UpW8g_&aISpkQyOcY8V(gIcgxajNPws?hMEd@@$xBj0H|b zCC$!}3iA5QiR}W>qESy(-qQ-NJtf@S~R>{dsihQyV-?e<06GQd#6bzzakvHWQRx&Lg#L(gM(7t!SLM*qYFqr-Wda=7FqwA_@UwmXP!01cC}_;Luet~j-BeXAgw{}bvD>!zse#3s0(%01~+|6CfS| z@&j+zytY9Y6Ii z)@xBWp9W5A&EzqAYUWj-qUcDWYy1@>5kxHVJT^{r!|;EvsacD5NzqTGn%%DrpaRg| zZwIXYAPt2oJTQu_ffG@p>9Yx5FII|a5MKhU>XW|5reSJS{F(AJsZAR(D)R@Q=tDrWJHhnSWYF2 zhHP^ZYkluDszJbEoW77o6Y>kTGWV$r!WMJM)@+r<*MMz`gqHxtTp2 z_*Q=wj3#xYqDktKk|U1Ka`7*bV_brbY2eVZ{<-OwUL)EZgR{TF39rCQi5$rlnQW34 ze*`)K3wfr6a*F}Y;%lzXwTLaZnQL;G`E|LcQ%(Tkz;cD1;W#|vnsv7+1>uq_k@I0o z?j)pY$0?+Ubp^2OK5=yAd}c7R1U`*FDJuI~w5ifeNjAAhk@v^htz|(eCvh7ugk6#p zEC2$Je5wXykeA{^UAAWbPmiro8j6^j3BY8OgTu4R!xJ`EZsfj`jQPINMMpddZ41l4 zgi3t@xG5v?U@s2{V#HHCNq3JHca-qsJr0%5SyA50W4nf6@-xn{aj`~CXW{68An}qT z&xBE|CmW)4z;@5?ubAAWFgTQnadVdr9?AA)M{MaaD7sBZ$Fu!Iu}8Ot=SoV9bMz_{ z8ix+Hf~r6PcfFXVG1{3Ikd5nF{NSM8z$6c-<(m)WA$<`7yna`^Az*kE#fox>0-?*xoEc#YF0Ghu6Rb}jg2RcJf zFbXJ{VQ|V7ZWT=cZJ(e~sFnBDeTK4XEFOD1tBH6fD`lPc7-DRN8s@Vwu?^jAjGPPW zzB`hcFQoQ}6ox;`=3ZNxORfgnFO73PLS8(T0WP+(e@BYp-7{H6UV|qr9nrdZ57+yF zA=$x?dk&H7abVN)cl(7fz5_(uRCv%i^JowgId7hqPtme=D8STYlSSJ`xDeVYMWxlO0aFf35&@{S<~EHWm6vh zU%eJ^hc9+4tMO0;woKcOD3Q)nwQB9~0? zdQN&=uItSFAMJ7%s2T#X)GCfJmJ(@CvD!jMVgR(4)IQDZw?33SO~@!xG7&IEmre1P zDM?cHv$t5Zj1ODGQlyxd(2v}T*4soV)ddJP!J^(9#F#}CrPvAig4NiB@B}ZEMB9wX zHYT~3aqoe5-RL;S)V}D)Mf;3xRNJe3@o7&iy`@LbBR(zv>!grcCE3ZT^v-XiH)Er%PMJYA zEm&lG zk6o-4<+dPN7^c;D$Pb~o+P-XrPUL4jr3C`txHWFi_M3>%UM|X?H;xQM%$S#IIA_oH z=JRi(ol%;_WBc|%kn#opl=S=23eMtZ)5=&gOspw0Sc^>2-)zwL;vW_$| zWAj^FMEoR{ScUC`U->GrA*t9op=I#HbGacEdXJV`@3^n7=dCvCl6<>YZf}jwCU1D9 zNeeNBw|Y3sj&QB%MS%YPVh_KTn%Lu?vfkA3#DZ59*0Kl28$}T-Y^bjvjEY&cU&~Sw zMae71dY6}5Pt2A1)u>QTHoCc1@5RMyrd-v*oOJ$3civLhHe-JGg~8Y3_ycN(M014}?}=uL`qD@q|qWaKq*7u5ndv;s;|;aUON( z^BE`gSw_K!*#A_llsR#WBi4Irp_4OlY&k);{>O0!?T6GvmaH)d#Sq2zJyeSPBGJ{L^Ia49a89YuQYrbbE2R9{+S?g> z`av4ADZ)ZNepHsHRW7vi*PpIq->w?mD4-;lPwFPh?Rkj_%rc2MkMqfeCdMZ-D_QO7 zsqg~ja(p35^HiXc#&y8E`@K@$pE}V(Vqv=6CDyK58P;FX%Wmjab?R&7d;yKjilv*C z-1vwYbhE6Ty}D|3A<272(Hgp;Z!eIBP%j%%-{_0TpC4THiJK}c6i78S!;+~TChFmF zCMAW(047TXQwGgn^9yn+uo8$y272QLh!U^Ql$9Z}cU~ zl?jzVN1X?orYuNmk=trjoG$cdo6!-ZqY11sa?T10z;Yf`Bv+w6i>yXm)ZLY(2~v2E zz9LmcYqZ6aR)^<$_ttSsKdf(L$S@ypiFFdz!IfsyhGUj~&f(C);HZ{A z3$$vPUYd~$sG$W5x}2!`v)Dv9WA9sR;+aOkBp9Y{vY*Kl7?n|;ZjG6%dI&=^D$%X7 zw~4qZlYt9=^KE%NeC_te)%9kwU}bGQuk|*Y7Cn0sglB`=a<{-d#EC}ea_F>~DPz`G zlB&?C%xBqP`(2VEGON8Dv#xmOiV=E){%r@b)RANFw$~i%Dy(-y6J+B0}Lb91{t(f9XF0?XM2zeIMfDuzsavvM^xB)Q3l^<;~OBo=5p9}0CT z7RE4zk)BE3175)>hWPV_NSY1i=)F6bCZJ^KQc;Uu_q(NO4=xHmW*86yM)$rF3F&PY zd!mDgx`k})x!DuV3(%v)lN4=r+E{P!g28nLJ53IO54!oU#(_339DLFcD(}5M4asW!gSI`K{e*%>zJho*Y%oFU<4z)*z9Y+BWG+_^I>= z250~P0v-P6L9XJL+5FlFsC}1Ava>BYs)e}Oqr{_t03mL7OSGLKe0)sLf<;iWi3R4P zSV7Kjp~`jfYNWI;x;z1N2*vzXX$D2;@U_o&S(V!&dnOzL*hsmhnN8W^U1!+!dV@!9 z8JN?Ns>AW3Vhx|j{tGL=U>fY-l2qQsVDen)L9HaT3L22W0000dSmD0Pg)@MIcEdV2 z!kalhuSKa3vQ-!^5tE=BbJJ%QHmY zbLaI@O~Q*3`u!-*m?^D=uGRMA@Bjb+XZ6M#^xs{(P$%O$=z$Cbi;jjwfFw{%Q(aD9 z&}4yJ*)90n&x0}@Om$Kijg~7$%euvI_vn%WrJ>`K_XKYYGzAeN@0Jt1U>yhDkJ#nP z!CzjUcJs^t626@yCops~MQ+*=j{0aIV`lSHC6asq3~Z2hp+gm`^R>-f-7+O?TmQGq z`c=RVWAL!3o$hZ25%VyFQZ7|)cz?7^LWd_N9eCqTrPj$8bE zaNQ}VwSn_M{UyN{c{ZAqps$q8DPEZpt3OC`jdg6}g;6UW@d2R#1o*gw+<95VliM6D zG7v(ACMK3$n_P&AfUZZkcY!NCH`yPXCq6)Ji3j-AC0)aw@q-tM9&aTmf0Z!8N&H`@ zZT@=OhI-520EP;sBOM#>BKaBnK|7ZK?{`8cy6NWk?wGgVBg9b^5!@!gLEl96^d^E* zC(f2ll&Vm7CXIKwBjjy94RrcG4%+$thTEAOBLa*gU}M~I=4&HLqktWDurru=j-Qb$ zw#41tpGU7EE1o~EewB?#cc~$mG#_I6%-KbFDYwQ>5-W~wIUnDP$kZODuRxK?o*aA> zH3&vzcw;1@J*(0luL%5u#w^c3EZGnX$6{O+;*_R4J{AA~GHIv)08aM*OmfKUut3{$ zty=m$o%&-N1UO5l>_ujD?5%7a?k08Ga?zhwOaf=LJd4g#N}~-B%x_&?G^CNeEG{H$ z;hd4ml*^mZkp^0$+m02fc2*K7y&u7^(o$s=o#*3rRXviY+Tet?h5t2np3>wk&C)J3 zOP0e{{Gtm+A87J4<22t_&wrfH0C|PGxb;g;JMvB+YZ%?pZp+~d;?5ZEiA6H6N&CI~ zQb8Sc?ABugBq>#+;r+?l)SrDlN|AZ@xgE&gISdD9>T=xT|K>h{k7oS__JzsC{|+8uQZ1l9Gm)a;%PHs463 zLONsz;G`AK6r@j=I{vJI6>=}3ZKpcme$BCPit0<^1}mxZ$*9fAACt=F+QEbGj=mGR zGfCU7M${5N_U9|Xq2#qM_H!qnjxj@>+y%dxf8#VfR6egB=$Oq^5J(|;%th|6q_d(4 zMTNIuO#S*y$yLhbsw)V+IWsE80Y|`z+N{lNbD!T=2Mh+Pfk!({8ePh801~a^A_(a+ zkfVGMz|mrP&cBwxIwqa!ITfSMbDx!JJQ(e6C3;Z ziMX?JYNftZ9NW*Az%Dib001HE5_q1J2~?xV-Zul7o%@gVFbl}?@fK&^KxH%`dX43j zzT4ApMks=T(ISKIE($deW3UzAusY_-wg_UqVCgNr6bo>($o63v+j&R4VK}z5{ZdYx z92y>v78_Z@eH-K#nNmaM>PUn-#)L&tf(zgmmG!Xwf#>(IEr_M3KjW`cubD!Zh7IV; zu<5nAk2&o-6qIUvjdiV=ctWi!Z%fQLD2ZhNY|Ag?)9BXVz>lHH9oZLlM5CwL$sr*o z-uTt>kM9P;yFsvjY#=X~yQ)%140qux;}XP;=RkAbp?_&b+QxmuCZgk8Dk5nw*YnJA zaf4k7QyI-%`xOFb4MVv{q9SCk_k8fI75~uJmb-{W9fDh}!jC0AUgve(b2C=~7J;fl zVK_ZD_w_wU^+8)|mbuh>tm`{o8F&)gBbliNCx}e4Jiq_|FUQt^-8}h}pI^M2s68ok z09XW$B+^Th;UreIl~F5|G|#QpKZ|o}P$ck|9a|gV(?Eub5UuVaK1{n^x%<8)z99Rw z&TxJpfEFh7JYb&F@zRM6k+Up77X}d_4U2isr3@0q;&T0N9ld9wy`eD4sgF7HT&NvU z7IE}$@TR{>u}P0ea^ESNxDNcCfdfDWfqI1m1K*{Bw)84^4bqDr$u(7juOz61mJ)bNt|+1lRd9 zBO9+o$=Aa;$%%dSI1t}*T~EU)f$AO6#!pZsd-#Oa?7JwflPQwK;TMgdSU)GKAcWGP z9~7$Qe+F0Au*6a)qS1K0==8!%@L9BL83NY+5HwWfHH44S=}F4-I0FV$lITYUpsD}ZWM`bx_pV<94d-5+Sj4|2sk736kwNVyOSO07XMD@xo8J`B>)Yc+N8M-#X z;_yg5@>;2=_$)tmD#ILJS7AfYkei}8)6!$9GDTnYD(>8GuLqUV8jo`j7a}#iOX-C7 zI;l6&iK>xikZa{{{!?;XkOF&Wsla?nL-P)`C!X0c!UOB#?MfxK%A$@y(G3r=$`$Lb zYO$4q?doq7?qU&cT^k~kl7Nq2uwt5>Fq42&L*|iM!mPkPYER=Qx^GEq#Jl&wa7jW3 zU5g^K@I)0di(xL3!f`8?M$x-+sjTcOxsvd%^HU|l{=Ed{!Umb)sK^yxFTn2D%#_}j zNEn?Drlrqj`$5?Xt;&xt{N3h1xKwCN7;x1G-2GjQwCkWS(`Lc&R}XVp@T2eW001xu zvW8RRApbN==mNVxidL89sMUbH->fCIz}=>$+|<1FxnlXjA&mk*^`mXR%M}KLAV$K- zix{0o)SIba;7$gOgNY&I5XbMw$TqUD`HZCsPG|rFJehyVwx>)p$*#2NHa!%pl4>W@0f?>7 zVt4g}&;S5vj^~fw2E3kYc{gG1Z$e5wx`GvWAh_xu^9vLijj_lI{&1;HwTCvPIqhKD zolDj}D2R;9am&~hNOHdDl$o@6q8S+5*W*45%9{3>v%Fb8)!=;x9P}Qf-RSW374SS# z+pjmKnmCRK{aZ~-so|WW)dfwJ1V#&`d^tJd^GJ>!V)9HeX4)Y zM#}0v40#lIATjksaArTifB;A8U0!IHt(~`0%diL#6YMb zX!O-qi0$4`Uw}OJ_kiB>K@xbqs`qmq2_bQP%Dh=lPM`BJ>k(A|SBnQPVTDkd-hxki z4kbmvx&*z+M)+})RN-Glad-d46ZC_mdgO`U|1{7ROq!rZpfY zD8P*zQDJ<>cnRdl- zG3dk=<$eO3jD6VzXe>BMX0UF z?-y$Jq#HO2na>PVec$!DZ<#izJ+|pvb@|Bmp4cvMMX+khUDBzP)Gzq){wn2HEuh;y zvTcbxiL?n?zJdvgSA?54X&-)zYTh3W_RYJ88FCMynJYq*lW<6Xrz#x8+Z=|K>~f(@(8f&C~9}PC8&YMG@A!o zN6&n$hsi(SW7Upb=;@iV&Ox#TL>Di40N34~Q@_+x_GVjiOQE4Mf$pH~Nelz_c{puk zV)ADZ|AEq7>d(K+71Os`>Mb9}KJV?P1GS9hx9;GREM7QDU+^Z`19U|`p?HZuP65R^pH-=`HS9CrRWwzUyDfEs_N#p zxd&7Ny`ojfAV@s{Nz(?ju6k8u+V6^Uhvj^EOrQl^Qb6j{ATg<1jfQjeNs0iR&-Zv3 zui%xLK3vCfTW-~!USl{JnJ15wm7bvZdB3a~Rxjby>QYhtJmt!vQd@Uuk}L430el1`$N4CqStH`aXs_T0O)h%*{eJdd^=!)` zA_`@Iz;`#3F(pR5u=0O3y#c4nODSM6?BM~|B)$JYHqP$kmXPu&cKwR zh@)|!h7hB{|G|@dv&(dKp!NY0Y>fa5y?-CHtn#|Bv>2z-vBu~sS;hzvazFUG9|hHF zAuGMNw&onbA&yt8dbn0g_fU>o{SQj;#x2I> z^W}C-WED#O>HND5Q!&3*;+B5|Nb!1;!1zHMG9S9r<%^Zq>I^>V)BeL1`^k^h53Y%M zNYGi?QHD(T?vCyFcN#YuV=w2(>3P^+b3M``FR~nn;50KFHnD_&QK+T(v;iq*>&$O> zKgkPVtN9g^a}cJi2+J}z+_mCp3VfZmdby~+*=q4RYdtl&-!r#hcw>G2EkR+#3u^P% z9(e{vYoJDfS(r1VsXeo1k2T42V<5~CpA@T7kpJ%N8mywx?*o?(B0^==`*+P5YwtGW zywg0{_lZTII(C1)J~>42Z-sHaRPMvAUe$O{QbIG*1t(u6Tb0tgPk5rN=N(O10hSkzJhgbZbLfoPri8Nqvx)z|dLbILGgqJk(#g=4pZ25FV}I<`*U zS?^`NcsQon{5F<0S@r0tO`BN(ZNY>eJ zc(unbC=CSjA(YNWw7CE+D^rhqonZ`WO~;LmGLqP?KK*2Xm59?G?4rWS>F7O$a3~gO zSEo2uO6f+&AY$76*F{;c+JvHFw46wav%|A&cOvE)c7t%*a0~5DFNZzE)pE}LTg`E$ zhxAR8Pu-s7>n;j^&T@CiH*b>L+_poVd>EWNK4e|KwUoeY;azgWi8F4BIVoX}B&_aN z^d`nq+3@VfxG1uFE}zp`0F!FzR3x4w%{$ZV2`6!__^wEAkfk3O}zxZ&aa6M_U8}1;~Lbb zMv3!Pt%m>r000000000005hc$vm1PEyvvDh3*?`=$KAi|Hj*vSao9aK7bCj?!t36dpFeM6lP_vAoh@ zjyF!l#cdzpS0Jv0h8y2%vvD!jzj>kof79@N1AJ53v%ny`y>)f5$ZyneqBR2p{swBY zkaG1Jo6o#744SzJbx!nfRes$es}6|Qwet8%|2=KYmvRh|4Bdq1s(c*B0eoKOZD?u{Y+ zo%19TQx9vJilh%3cDYP=qS*_|NC#lg25kFkLHOCxjI52RZsfS5aZ6c4lmGxMaX@Ez z5bz;mYKj}5jDU>(3rJePw~sP8+AHQ|aVE~5yKWP5@(s-AezaKTD7%^Ni!qs*0#B06 zE_0!b524Nv^ff#H000DO|5_BvV-=D)xzyM&2S#SEjIk$x9o3qRkt*$Xxq}JAuudlZ zd=E-=j{3D;{bT?D0000|Nk&HgE&u>mMM6+kP&gp+E&u?K9RZyIDgXii0zPdvl}Dr| zDWj${Dw!}1i9x{tNu-IV%>s(kL5J{&&|e)$9BZ z{_kR6=3nst`TI)$^8L^FjsDyJd-qS;$N&DYjR1PPm>&MhOu<(~$Yp%eDJhuWIAqzs z9(SpQFyTvv1RTU7$n*`9kigUZ^1@uyU!cO@Xqg}v+?4)uINfv7d9MxCAKcAg@K%}_ z91B?C@=P4GRpQ2#z(_O`nj|YQQ)F!q&J_O&lg{(;5-95QZd+H3Ow2kyOhe}nnAa!1 z{5(-nCNQl!xAZBpv%qW_n!RsQs58`GidqIDGhG-G$?MB-A8>5`HsDTbGvT$lV%e0( zpCcX#slP-Ev!A8C(?b41fQk54cl@*eW_Nd@s&9IuqvFsz%1?b8nqFkdRSG=Tk;$=& znJL%oN~lrfbw-rHXI6xRW^PaALwM)mUWE6wHIUNE8*1w)a#w~WPzd8DiLnb3rbJlR zBJj8x8Lz}I(Z;E8C>3Opan`+`H&~X$tW7@1An-7kITk{S4z5dCYR}+Ehnt>P^PlbV zT4Xd(tmN;|f6SdORCtez40wf1`CmPfX)Qk0qRi#Df@Zl0&tiVv7IbND{G>4D+~?mZ zgJM-ek0YY_|Ieb5Hi%Z&_l))Oco$r5+c??ZEID9tlMc|@(Qms*@;WW|X%+pAFb54h zeA>d6PNLW~Zq=wtR7=9*6$<)p8o4cLS$R5-deS_Oi+$Qhk-t5j7*@8QE(B1n$s=Sw(x9NGQT=y^U5Nq_U`dB8_%ES}J zF0#6A%g|>~uyw4XK6JTJ&0r;>V}XvPiy%NlRtJ%g|6h4Q&=J>MT}OG||JdYAFaWJ4 zco=WWD`FR^McnuO_7b4%G;@UuQLXoB9!EuaXRAWN2Hkynv~2e#Vii(QS~4Vma(sy{ zB73@4As-zU`?QZBG$+ov4eqVyRpN3?YW++klZ<$p+#G=~L}~7ecZc<2DKAE7f7?i+ zao4j9zXRZVE5D(CT6pc7@r&9mcY2a%#O~(%60FB6)FzF^d|hMe<9t9$79^10SN9qe zzL0Fh*#XQ7!(U&=Z^QcH)~`f7>(M|2;K%YGZfM4ioLf{;@*9Zyg#Gh_o zj*3WsR04-+?Ia1-j;626+FK8FbQwqk(nOG-RH0JW5QOZ^6*6xW&2FVky zF`Q~F5jefC8cQyd3_^$V{@zX~BzG8L5#hqg@@$x0dw!MgSOq&i)UIHsJKwll%gg^7 zy0vXnla&V=liU;3?W#Svdw?U1okOQ2g<|S8&*Wo!U{k}%9uSH^Nae=uD2|)1HNR-R zfG8bh*qhemPf(IKa-;fRIccFucgiCg32An-zr9NFUtO+pFOczAekg6^vnbUXYjG;_ z_IL2I)5>5M>m6&g7Y1*;R;@}i-FNXpC29D^#z&rr>~4Vl#^#hMw-IU1Mb+FaX+eX# z5@F)zJ)|<(f-{*5eC=mNfdlDhmuo{mL}z*~e@H+N3}`A$`@FY#!@JNBb@^6gR5l%} zXP(u^b1KcRLVJP*y7q#)%muQciZu#FJ^z{dXv&kRPLpJ z5`b0E3Q26Nb9?LH$nB1yW&PL3PHa==Z`E;da!f$mhX^UgEKhk#P0+a>*Y$peD9xWl zs*<5c^DKf-Y5`O1Iah@y0~uDva+mZh8Xs>H9ZsW!2mT!};0UtS+t5~uOVW+o=%Ler zGeM)cbiG$cGd(2y+ljqN>0{2D@^Z@nAk6u|-P%}jDt4&pC(>ACn){H5-tQ$vOPMcf zGE7w_W!Y}7M;>I_SV@kj!^6HMO(}(x!@8%Y2Wlc@uDK{4#5PK7;H~87YovAM5={vj zb?Jwfa3<+)+Lbd$gToiP`z%hF+gsXExywtB3Ge&E3=u&M0cT*R`BiydOxz2Q^i2He zZ&8Kn2)F*LEVUTb69ZURpchG|Vn+Vb92H-1mG8`A;z zqH)49vm5T32ZwV)6$mYaKcCU$AsKFNke`428@$khS2L&Q|8BuD(-%fE&Vem{ipJO6 z7}R(%%8`*LiwMF^x>}VO-Lzvn`L(vB+^G^zQ@Nesj{Alm^!{uH)VMuFlE~0S_DqnT z!|?7w!3sT_!{6FlrCV8!rv8%@&EE#w!cB57qNO_xNU!IQP>j=WGK~4h^L~YNx8iwCJnhr|KROA4eGSy8gGn{UjjQUGeG}o zAz~KwHRc4ie(Y|48*NH*`a4ZJ7L*>-CCit3w5DNHgn!Hxe|UdD%N9V46VL5es&E_8v-%AgTBLQDHZ^M%V!76KvJ1~cNGLWFo za#0_Lsb-RA^T0YsVwx@=kJp9ATl0Kv$myqRE}P8@W{;-V;tPRb0RH!~Ah6P)yShK> zspppeGMigZrwj&nUh!ZN6KKV3ST0=LPThP7vE~XIv|3*+JWi|YCVu&2+Ke$mCi3D; znbo0xDVubjGtR`qb-YLr!l_L#HvQy8`5M1CUaweWS2B5&8`kH0-^Dt3JUX6L2_)ho6b?XT2uIIF{c*E*SbyPPq;zPex2$2+SiDVYM)|`m zRZRnqyBl{He^2jTH*4_E6WPyJ%=myn`x_7`1zVCcUFbEmAv`GZoB9MzD#Gu+ox$j)e6=X{L7qTPfA*DMUMGzseHih?R!|nadq}Qjb ze#c76R}Y^L{ar#C0Ur*Hbk5P!JtRXR^YD*-C?9oc_DdtAt2SeL`k#!bDJ@%b8wSAu zFNg88u}B@pS$fo-wS~^Vea?Uf_vA1rt4uP)R|+)X&gAR&`aRpLcaLFu+)q1!xJO>8 zI~N>bXhe(@sIDCKS*>wN^wM1zra~@Ya+GNeL|e`P0SJ6#CYZ3k{Y0y=<25uggW@X+ zAUd+1cjvvo*Z=_2+tPx50aJ_Ng{u!Pe)=1tz!e1TH9wY) zy=DJu9p}y$^%q3wXNsSk+PcU93POXQVKsG$Ex8ovDh;pLs?fq=={z#cqUDC>rkLqy z>+k_rhlwXXF=bszWF0tJZV$C-Qbhpn!Ut_=Uh=*VLvZza3=673riQ3(&@yT1fVk99 zu>9Urj$niZ&+(`kU(NK0FFV=qb&EUD?{7KJEcjeZ8o8*aBiH}{0006Z z?WqqJY04jdYu6L000lM@>#X=9xG^Nu(KdMXHn8hzmPEh%-O!q z)8D*-hiH8=JO=<;vu#kO#^N;rZ_fR`?$5o54N(MzX9U;)0000D=svTQhLQ?<0U(WD zye1$iK9%Quk5~W>rVcKTT@VM@+Th&d0VXy>;W=Z*J|jr zcCTMih*RhwM}Vn7uaJ?tLcZ@iUDtK+mh|2M4lT$f0cb~y!k*XwMQY}Daha-J~?H4;PJ;;8Y#g*_mpOwxM%?Ouf5T|&D%I!7TXl0WE}#e1k(q;pEj9lU|8+J zrf@!tRg-fmNOWT;41dKBc8Qifua9zfQ7R2lslW?A_y7O^0EZO6Hg=4@8l{|o4=FmD z#m+=)(`@7~V`UiwFTc@cC{x+`nX97s2bZ4;vFRw28jm9Klh3F$SiUSlJnZS0D59ZmRh#7PcX7{;!ORM7Y#$4(%bUKnW8OlCwv0+)2Ne~Z#dS*Hzq2Xb zysKUK6>?B)NpQxxq8v=Y5G3M! z#c(kR4EcB`1T@A3`~MhdOBMYHSBE?Z^#A|>000000KEe`jBh5R=-LQH3-hy7XmN_t zJ})vV&&8v4a`;a&FB~HHm2}2pyRE*3f&sAe9X#Ct+0x!FS?;}k24B=~>e7!w$xLyK z+SnY=2ErNA%fbSZWj|a-3{gEGLQ0F;8wo@fnt1p?K_Y2qpf)+}lJo?SC0F za^*kW7nMD(8R5*=+W+-rVDDsDfTbMTDusPfon&BUh1pvKoi|B_sB<~*bQtPLG|JJ2 z(jDbYB@rAcu%_*=MSH*?Ju^rON@HZ+M(Uc@DFGiKN?w%uU%~b4;N{zLt;7c}hnTDF zI5J~gt2_54tBbsf7U@Sm);OY3%wObku~|}dvmE97X=i`{000s7prch=slg1^gS%4_ zVK5kbtXbrP`w>CGjX-?<8LrG43V+g&ELM}xnsaMweF|=38c1^$!DPXtAQx6-s6SQj zVA<|Um^dIZXzF>{c#=!x=RgZAD9COs6aucU1NsCMYoRO^s(^k^HM+GA406DdMgRZ+ z0I3rG7OkRWB(~zg1NPh=8G|JH>?5)-v! zlCb+>E;akZ()-C1Uc(;p)9@wBH5Bo0n+I9{s+7AFtevWas-|jEE_J~7IfkLjH*@hm zxQ=L=|DsmdEFh1Z!jxRP4jw#5&jzDntCzBu3s!ymAy&Mhyf12*xA@@V5D(jmznv#T zE-Fb7Xyk(Jof-1y?r{EY;?HYp@dTVI)(-VTXEJ=PP4NQbSBK{GYcDGi$LiDg%6Y$s z!x-WLR1XNot`SCCR^SRccXGu&rjQYHzS|b{?0$?S1be^8J1un1h1WW?eY2aWcM^EI zFGu5g82!faPyK5cAEC`9(3$!!InXGH#f_Ge2jBn}YitkgO!_;!?aKMQCT`LDc+Y!% zha2(c`nf~^0005?mYT+8wissYl{r4kTR_wxBk}SPj4y^WM4Slg1ZILt_!hL;FP%y@ z?}TrL21#cSG{_X2d)Zb4;H?$_teB}`fO@bR$(bnFOrk(FUZ4p;0002C<12`-#h~D^ z(?9?op)FsPvVZ^p07CwvRG^%$UO#((B)n!}B>Rf04H+}q6MFs?R2;_pOY}VS?dlK% zd2*QnBa?MRqD$1A8HFp}xVY?Na?#Olpn1kiUsewaA!Z<9xX^eo10nsr!6HV$3Dz*SKWk$-kQAPqX1d-LZ2({eVrY z3sHN!(eC-peCWYV8FwA0Mj{29tx{%*pk1hm747YBz(2dY4_HvhEvOVT(5Xal`~iIu z9Cw&H{0J{A+Psnb@~VRS+=WR^Z$%3k3`fQrtZ+|P8&Dr@CQF@pQileTzDG>&i^ecK zr@^yDK!sgo=!->a=!73&A1L^yBWWIc<)6YA&q1_-r(;faXskbL+fj!wKA2DSQ2ahnkz?8>J-g zG*-yX=?|&0VPj9S}wW{){I>b~-kQuA7y}lNf&Sk#Wjs zzF=Mokw|$i{QUT%2xo`oA7JDCrmOXV z(P6e_d|X$rVjTaH3ObfG<#WzvzNkGNgiU{X>|xI}FZ&(YkPLvj81El&!r(T=6*b}Q z{fiGpg_swf<47ehLFUGNfjjRg%`ji+)-i$EZNgV&$Sfwxv@fSz zZu}B4(u62mJm{D{qcq`&YK(ks*AyyT8j+_5Jy>Rml`L(Ax*$-1o?W=|Iehr*yD^|- zm$sAOTs?&{IUmU|fS?@V&86GZaN@IsS!<_|O~8Lw1U_#5bwIdBtWsi*o9X9_2`lz8 za9Dz=ZA|p!brW?zhtum@_dl{a)Nd5d=%%1ev5F^Mxf9AmOM`^np~&i2^6yKYAS;Z{ ztP!n5J0c}TNsiWH=9N<%ZckD9fQGv?VgI2w9SZ0YAZJc5ZphuM@ee~m%l63xBF&`d zP%BsgHvw*Swd>wqXIr1@;vPOSrn71dtanL_U^>V@o@5jwSrrk0_9MQ`dm@L5$v4?hu6rYQ{jn@s-y_TH0|AmOn-wjD zWk4MhiNg5GJ6mo*|oY{bngNOsMGnDXV?BRt<; zevE>t)e2d?3c(~nmN&EX|M3`*Dp?QLCfT1oCk!rbGZQyzf06sDJ_J;ZgD8>a%dMnXlhT}M)xJLYw_eL13Q`0E9*-*I7&Wg@;T&Ys!-Gg zNkj%;AE9s5ZSa7^TX4-#-(1E6E5$*rF`R2Crx`e_3q~tFD?E3OMOdvi-++XUBfIi{t$34mNf%h^;hEU=l=LbAdb2Vtu)po zHZr}QnFvWTp+4L~z3fP&M_5!YzJ0Zg9WUsmo1+Rn2lkrQK(Qq^Kpq^F%r@d#IN*;V zDsW>zi(ykcPxO)RWHz!^12pva(~KyF5p0ne)WGo%f3R%ez1^7)b-cX*yCfeHU4EWsmJ3F^{AGXc%F!GJvmp|PkIr`N+&9IAk*`r ztOhs^dlQ?LmU=>1Kv{I+HO1Q2A%SgLi*Xz!4Bb7*9O!{*+VI1;-g>!owJ5xT5{g=^KAJrMb zy`3Tu#4SgQ5uS}Sk?%TH_o`xGK*j>LB0|u9Rc&{EyF}~{<$&LSy6N=Dv3NTm{LNXc zA7I#Gztct_`25%E6Hgzl$3bg_5UkZ*JTYI4}g9XkfrGB8?nkMl9IW@Xk0_i=J!X zT+bQeHn)+Ct};X-YDiZE4Z4JW@bP3&xfR)b^}p_d17JHbOJY@$7~=qrrFWVaQVZjo z0uyZLqJ;Cu%lr1L<={~Qx{%H6$Yd00&gia$tT~syC3JY`XUBR4kkrbi8g4IWw-9{K)L?w3f3-VCv|iP zl~*+X#UF@dT6X2aXN)FB;3kb>VEy)8h8d#Q{&$4+Ap?r-mi%#k{2xcXRhdzd!1@e= zS@^cn$OK0ns>6D4Q@p!Mk?xaXjOCB@hBpAM7QmnSgiUO?vsy^#4#q*F~Pq0Y}?0-b@1DI;jU zFow8KS}(1fzy-)OD*cCo%bDVHkerNRH0K{v{^)UT=1C zX5}0}xc~*E0vO~9JN?Ox;|U)1+^yK!Xw0$@SlYQ!To_ys!=%7oO%8gt>;Ps~awWxm zwKL|Lzxm>Ne3^vOK|vLLOtt1OCeRdqFC}X0G8THJ>u|M`^4|kal~J<|Rq)Go)XKB} z%YkzcL=w;@0g+aoEAgc>!IjNAw8iCH5)!8|OOhCqXEc^)(WNe}q>bpL*eaw6h7b5| zJ_9jWRDPTrz+|JP&ZhQmk-bjC6i<+`9wr%6KX|VaLb)?T-bV2MmWZVi9`+-WEtFyB zm*JMlLiix|eUOg7h!q}bmKZ?=)d~s+Z#)A>m`LYiLMkh~lwCs9(M}HuW3xi70cpDi zL_IJT96DwkrdKA#)wrtKzb$7Grla%*_p&$*G^=&;pH z7XVT1`S9osc9`yw%}`>*!DgN{kG`ED%U5ns@0_$qD{Qz zS*?JRv;IPw;U!D=&x%aK{lD#hMjOqIr-~5T;k#GbK@+xo{YBxobp?q=$^eQTj3KeR zTp~f7BNXp-`%e8kzP&Qky9H-TAI*PVD=96Ib>ibez8S2{9jb~^w!)^mq7#vQc+3Osg$o>CQB(f~g042F@RJ>D$@m#?n>RlqdOqr~P6Yh5?*m$0>wm9yG}fy``M;(+ zyCr0;W_GvNhrf1`B?A_aRg2kMOd^_+g1@$tlEP27c!>Fx@HK5ORg(dcK`j8nVclK^{`a1W2zcI!8YXljWiYqywUG;^0$lNpA; z@!&}+de;De=>tu?vp^S?Joru+g}(hJs+hX-V)wK z!YWnVvCr|ZU*{5p@g(smUVs#@SM#G!DJKwK!N+&A&)p_6F0l5x-H{gG4(1tD@g2kt zgWyaX@BCzzb}t?v;OJY8ZE~9YAaqH`J?aXCa%OMPZkRZ4N`Z{KP1NCDZ?YOe9X#8r zzmDakH`!O-Nh5UlzV@;_&Sh9?OGCp?KcbKgk+Y!Q53!r_<~&54gY~pYU){m60cpsz zgLT9B4Ywn?2m-~NIT=3r>pXA#BiR>1ep`qx%`iW3UGdgFtUI4vLhx#I`oW*;9S~@$ zijOA#k+Hu?1mg9S@bVsynn)W|u4VkEAdg8hbmC?Kj4hToky}E(u76Uc`>p6z5njT( z?O4+gP~RjUqI>PJwjsO%<}&Q{(8;M75JkU)dfXixU=YXiM8S5gekC2mM;?5j4%dly8TxX4Rm=sGR=;;X&hH9|io>JI!sr!%I zdbr#I6`x9B>Rj8Ey;2!;q(JW;`ipo9qY}hvFy^Bj9sow_E-KCVhaE+F@{#;juwwO>`DjSGjVI;7DQ30T4W(l8lg25l6wy!GA9m? z!^YWC2QFISC9@NVjbv8xz zfTR|SR*Bte;m((99Hzr0#P2WLEhOf--#UWNMB9{$)lL+9jWefjXM%FMqUZ}HxiF4430O0wh|okrT|RV&@F zAE#+AOr!q=I5!=>2Z}A=FmK=lUBWu`m=rLCr(-F*xWzY7c|~{%e6!8w0Ak-1_cfM9 zcEUi_F$DTErSV0>LOX+z!i2fPwer*qXN?Kx7WWT$9FJhV+!t%A2N`OIoPK2~pQuZx zpCHrj{Fej2Ym5>ge#u*^n7ac7EP72)0;j#LVyPDIvE@c{2aotn^!)$kM@vhUgiq)R zY);SCla*MTV=H}5{N$%4-J6~BSx zG(#4kC6AVn&y;yiHD0T&s*(gE1a<@U9a<&`KHo6zgOlvPNqT1Hj(V)epm1joP~WSf z0nNZ*=`x>6Iyn7y$jSYB_Ab(%!(D8S~hj2PiRb9kJ+byOAK7dJdNxLmrs zu5@=PbpfSIkd%~0kP=k#f*>GDD$-mO5R?*7MCu~asep7yN(j;o@8Gw7f4uKn@AJ5p zi(xot&N+Mb-siJFd(RA|(yyuTJM9e#iq!bNL1(|{xGhc(Ou@iiCZRU>L!~0y_W2F< z(%kkt9_IEx)t+YE!5`q=8`kTPlHkBUDIpQ(ucqQ3TLopWE(mIt~SOhq4_IkyF#s%iO-E!qFUb4nEt%q^C`Q_ zr|DB{{-9yS4qIv#O;9}tK_iU+D!vYeZ~y%>1?db+Ek6$V9bWUQ>CS*>y<28@xmu8r z`}74U<^5S#zx6;qu05lKZw(77Y&vy!VYZDxk zh}Gei`4yo#XETp4i*H;exHYw0H?|X=k2;+7uL_PkHBesd#Pg>vjCtA~>dl~z*71C}Ao99_h zCESjqSN!hT<*hDUub$&~^WB(#X~krVj{T9OztrzLH6QqLXj^li?VrVL7g zO$j4e;G03*)te&M)avF9%~s~u)qls#IPMx(!oo7Pt-mkuRJ?-km>xAS{jBk~KE%1b zipZdUp5``xFdf)cyFO=XSwZtD9inmQ>1P%d!E@v8L*mSqTV8CWi#(ETnIk0rvp)2| zO{1-2E)+mb__AQ)vx1BkRa%&60@W^&t%WaCMaEgWK*71J0#lI@X|vt5P9WP-_JZuA z!TVo|CX2hhb9cEM?@g+H93=4NHa$B(h+qp3|E9&Ap|p8@|JC8y6BvyPDVm20V{-F* zlx*{x@TiXzwHi~eHp!&Pm+oN*+`N^3xG#m<*fTy+t2^Si*|~u+E1K&+Gp#dYQ@5+; zSm<-<2%-%mdT&soES<^~*7Sk8cPoW^t>MQ&*E{pB?43sylXoP92S*>cP;~^Bh&nPL zyc6`|wbGY06L*JyALczPP>g}Ij;g-2dW3E*3rZ|;jfZLo zrOuq2YoQNpld~|(jM{pb0EA%*@|Lv*pH-Gv*U-Z`sj7^aHn*C&l9)iD5)PTmNgnu5 zs|>Z12K56+y`^pED1^UXPh{9K?#%C3FO4C0v~5V1aVk0O9eTJj$`GZO?>sB;Odeyy z=K9$#UFsMKS5PIxJ`Ae5&A^I^mTtrzWkuUz^$d;=>Rid-2+s#Vx1no@#ua%C8bcC``N$ zWJ-%yOV_{eIQK9S6l=>*8X6|7-?tHs5# zp{KE?Cu@|ekcAHR0N^ZpZ1bAq4()T<)+2>t{*uLT&-Pn$Ie|uK*6#jE z`uxPD4VH3?c_qo2LPR+E*Mucou9JN`T2Xc%VdgPKw9{Q?i|^)VWt)S#H0OU2IB{ zCsK7xAJ+FmIPxV^q`u|u5EsEShX(L5^xTwaQT~SP6}++RPuJ!`11Wio%V-$QYr6<~ z)4gG3Q~n!#-MPpn(v^3YD(kPXr#WPBWsV{6-buO0+^Og$VlU#dph~@CT5gdh zAmKh;cO($X2)iMN>shK)W5_*sE+DAiF$(g@roYsxz;H*XF_HN4w<=M}P{D8RFWk@K z-~XyLS$yBz73#_47{$mEIl6IwG`PH4Xkl4^rXbR2K3vuCxHr z>q}&2<^|kn!$#o;Z=;4>%x8Bj<#;VOlN^qIQ2Wi3La5{Qt*6%_x+RwT+Af%2HNYA7P02H8kRr$vK%wNIUPXu-Szh^Np zJqV+qRdI?;K(}j~u{V*@qQb=H+MXzg#)q<=D^+j&YB>F9Wt_&~&0hKD({Y21#2mvo zJ$wX)xD+;zCs?O^3v$a2t+ICC9ATgFu5J5#-`|m$I?;I4+}wO%Hjvd=87wdo2|*1NZn<)&(j=Xqij|G`Q_9nLDvLZ}OS^UOwS-gLY_b z{xl>*TtSh_>#DNwDumE#|M=vAKBiA7W;fZeiOLBH+!s{b-2HJlf7;wp-q!~YB-^R) zvx)DUjeT*XiESLsiAd_xe=q<2wUScq>vs&hH*6-%H%D3&WlsHeLyK=9Qcm6Tfx*KX z%S25zpe_@Tj{-ifr<{BXUFgjlcD>zPzG4N-S%f%7~!Y?|mPHjSnrQfR@jvy;kzv zm5XE>c}-94q_L7BQ6mI3Ao`3N{!x>WP>ce419NXfy&TvvssaERen2{u@MCDfhB<`3 zvENiNw4N@CB4=NUWhL2D@29yco~>&5R%ue)tQ z&>?y2O7>Ny-MuE)ptaR(8ceLB5?QyyG!pkN1`w7%vk-}U6twR8z$PcS8HsVr2HaN^ zDto(xDt_e_5a241SkW)b;!b>6Wi`p%|D>5B=P-SJHOliA2cXQII9^WOpT0(2V1tEJ6JjFsbhcjie^mv(lLJ?Q zUMtO7JT%xvzy257DwWMX?DXvGd?z7hc&=H*`Yk>;p5)%n+sZUxVqi(cK7iF|63+YitPVr5E zpkMok?S!}%A{LUJ?ScEUiw!iGsR1*^Y6?J#3u>04Y4Za^s5B?d_sm082+y{6PDK2J z84Lpy#{ukl7|WaYSMjjUcr%=ed$)29-jZ?8HH0v_d|gW3FI|>x2+D5`3%UCy%6^Xu za=-W~AX$bmNPTYYe7XN|%!3#8=SR@~Xs!}JN({yfieaML6!K%gWiI}Oe;yONrIp= z)aw;wl?d&T^Hv&)$-=E4=a(etsHTLGDxK=>?ZgZkZvOBtmS^QtSbMjh9}`3#pTh)+ z*4y=R;J_RQkXHn}8E+uIyX)Y;dKlOxiK4(f*?%GMQ|sLHga3`@C?9oh6G+WQWC428 zWnZP7^o?vTYR^21As3|;j`x~%Gk>cO=5Vp%OKeCi4;nJc`vY~BU6tB61K z>jB?@bPO1>`iAhoP#%j19GHxl=vN6+Pt4ghx~B--jY^(&?l`woza!RVKt~F)-y|y> zr5d`$(O@^CS(2nFf(U zY1MA~E6zjCQW>sk1RvYN`Vu{(tlQZoOXaKLtid;2razjqYi}6Bpx-%%K2lL{UI5wi^y%~w+o6}j%1GHwW}lBy~&yGnSN?ioaB&viB@ZZ5WZM>#dn9QnuP=uC0N z=4Rz+l6KJFJQDiR-`&w&Hzg4bl^7rk-Yzg)JPjLS0lvkh`lFfRyJfqJ8^qe9k&#sg+k|#2VcYI9t|9MSSE-qs8Qip>8>iD)x z@^R;r_pVys1I|Tf!?H5JM#d0#=NL-EP5nj3FjL(s@Oaqv0|qZy(EwDCL_~5l=d()Q z2*yi*`MtVptq)q(!)UJcP8?T8#K+79Fzl32x>Mr|e(1dHKE|^<*Rkx$ZItG~s>-Uh z7xA9yc7b)0V1@r9`*pgfLigA(mkFW!kQ?@&TAz+hRPQ`>Os9@gxHPKcxhnQ0DWXKUMRiGR3!17Fk@j52Mr&PY~{=WL~=7 zl&p-gI>g>7{W2nhRf*paB~E*ZX7Mae(4zApXaXNp72tpWBYQT{lh}t>w?OXk6B7Jy zCa*2y%b1=srgzkCeyvR4a=l4ur@i{VM@K+(`|dR-`8Vb%QVfrrEK;t*Ds5g*g)VA# zkLa8gpFW{bx%pDUHRtY&_*1p*wvMxffLCjb=yjP!Bls)F|?7zkML^WN47ItHfbJJKdBJQ_yRLqH0)c9O4 zTOf#8l8X8Mq*_xgGp*+4*T?Qot#u3|@0=u*oGKjccQ^;0BUat^emFQu>#p_TinRZ`gzq}fGg$Ljx95@wFEW3YF9kRVucD5hOc3cw6b~^nk=VbD0-^tm5XXw3y znvJ4uU0S^SX=|$0%$kF{hv$QWhR5X_OptcYn?{ws(`RZ|0a?;d;=Mhe_u9?u3w5?Q zx1M_MNi166r#2;B<*c-m+Wp)?EVxSkcWE-m*8vo$mD80tr;fD2Iem$1lEY^i&#u3O zkJ7W-2O5be1@cS|c{XsJ8ZF1chpX1C0H2X^{tUq|9wvLn4DBtY4{?n>xB3Hab%Z~5 zok({La%;n7UZaSoW5*C(1&qgHHt+c7+b7-B|Hk*8@wN;!!FOOe!_~n+GV+B)S-tz0 zNpT+S@Z7_ujOP)01(?py1(DNZ^p21uxFbR{M)pD0qqMGDcqqQj$j@ut&pKPT{C~Cm zR^hNR$$F4#?pxsHm>}r>wpy1~dZRVwEz>=_75He;Y0`NrTU{lE!YR*C`6bI>60D%j z1aw#_FyWe#6x}si;g@){45@vJ&Bvy0oC8%cS!X;qoO!;!zm}`or(1KQ^{mHqRjZ?KWS(SB%q^c%A4s zDzH@=c_-Mszd!nt<*0an%RT>{_L2^$kVw+75*C5O^}KvB%A2ekTjMD`+rMVMm~~vB z|1788QCxd64xpVLO@Ag-7fN*nwg05u{^JIotq0@CTFc}tfJZF4La+xnCu zrsC{|96RQ;9}g4ON&oS^*$TN&&jaH{7UT6w$kB~ElM_JT&_whJ zR+~IM6quV*sM9Cg{?IH3)FgvkJ-X8`5D-OAQet|4N*mxKp3O@X1*!_k``VeH%$~?H zx_TBr)b_pGuuTiK#aOTdtKFwRPP6VA?Za)!mvM9N`jl<;#G?S&4LZyxUI3sUk%K8r zn_)LLMzpg56n=2sI%vihv&jR$en?_gTYQr;PyWDd!DIRRvcRn9 zyxkX{ZffvZ0yqDgR{%ueC=?@TP#^WKV&KOQ9CKYh^d^+#fr|(QbCoG5%ZX=loF=*T ze5UC(!LEd#7aw^8#0Dij*gZb%P!X$6g_{%$sqPd<(xUj1x69%gb^7hRI_&!aJKuMM z?e{~Q(KuBmfF$C0 zAuy0JJpi5-NwU8DaM}}_WXcc&u&PH4 zXuy`&m;fDM7PLe;D`YH(u~3J?alvd=;8Z5F zY}}3mTofo#K>71v(f*K}k*Vi;!21_)B<5Jrt-+1=gV?1Z zvKvs$cPm$Uo<7G1p-j3h>lLn^#C;4gmJ2$(=!dROU0H zWW0-C69Sh%iyQ0*1Ap3qk@r8XRN(&|5+eT@CxY?63174QcfuzBjag1i{cq#oi**2q zi#5B$AO15HJ08)$){mZ?dK~`>^?xXrzph6eX7~4k(Pnsq{2U1%WqSPRKv9o*BFqpg z4P?NdftCN23;I71q5nrj2B3&0L{UwM3(;e)!yo-KOM4mvfhT}~a@K`gD_n5GIAW!G zf8{Z-<^^Q#Bf(RVP5`8ktD-bij2i7VQ}+;bIUb@gBxY67Y_Z2z8JB)e1YQ!nn{aH5 z8YVM-TNnTxK|3)yWDrrK8N4@4a|!y_T9D{2t%jPl(4*DaKcY5(9-u%WWyhg`PTy0zjoq74bcl7=uv374oylYdiiM^5)KM(Odb^6``>3>4z?6;ldh2i z!_5Q$Y>-z11FY%H-f0M7c^&$e)BUou&FidE66yi$k;Rgkbip)d|*4`ux~9L zs&xkXH$^&P1JfJ$6gbMhk7K5#FHZO_?b`g@f6|(@Dk2YOhV}UvI&0D zc6Dx0x>H zi9(_m)Dn(rukBZRKx{MW&Pzg+qCj*n2oOzY7wd)txH(LiuqY@yYrwzZt4wuffzO5# z8@@X~&=J&F&J_)0lIdL-2`-JoJs&ox``BVnqUVWc1h%U0zWf zn4#o_mYw|%xi|~obCUW{T62;Sc4)hd?R8k@KJ|q$8Him;X?~envRl{!9)maT8)7Z~BQ*fHvHgYQyHf)i9#yT(|xli;O`;_w1A%j1L%= zzZ%c}V*(wsrJPCenMrK3P2czqGEJ2xOup%0F_M*xmGK@84r)gi=x8e%ZJ6WVl)Ptfc9)AJ)a zuPge;<8PS=Ed9)cA}INV4CQJ(?C9msL`3-!s9%n5o z%*+SX+3$()2-4mXMsV941O?F*e98zdzyI4gZDCsSIA{B8d1v}#$gdoi^O4!Bu~WsP zZisK3bF&PeQbQt}!V!7-Gm_M10d_byF3_dXW7A3DNVHSwr|hFj-x+2J0}!TEQrGjfn^?3>77r3MU!fBf0Q&5M<;94R;Obf@Q zgc6;|G1vS0f_C}b{X!*&Bt}{4&uRIvkzmy#ygc>%vc1uHr zDh}YZT4_&xI%x4YCuHH+Ix}dtCUhzCWYF{L0wv_9a|POUdE~(c0D5|Z1z4^&)Ic^z zXyUY174N>Zz=6$-m{nzW6l6>tW!`>LysjMk`mz_Kj|l>nW5|r8&LroB&jfcRz|q68U6Ej zjr&bmo11MpijvUZmLV(W6NWJQ5h%b8EIENDKbFIAR|oJh&#It<{<=$<*(i>8gBOSb zsd}>6rWbQffyrmi)4NLdbXkv+l(yT>e>7$N=+FUvzCQO113>c9oW!;gmj7ovZGz?6 zLldxGqx$PPT3<1faC9*_#)yTP3G$ol32@&=Oc^(`J$%~NVDvaXbT;&Kr>&G!$=mPb zT^pIw-eB$ar~7P}%yE3QG#(t{<(UH-u0onJ51uW9e#|# zq*D`8VvYBV5Vyu0ElDIfgsFYE-w=cNf_|HTuXHvY7c#@tcJFi4y=VIm&oO#Di}D{^ z>Pgd*9u`43k3qE91kOs2IAR^7;#q)v8$?YkLhyT-bG6drs5BJL4V~bEs~=H{et0~_ z=}R4DDyTobcRt(5c5)cXLDq27i_$U?(CIsG(lS^w0gZMnis7z4a3``j5oo6&3QI!G zjJV;x61R=%fFH!$S1&F#v+{Yjub+LlVb9hCq6}o-M1iPB9|U&1>IjEx<|xk}fLF$OPTngPL45~d%>wUk zgUq@UQjugbDv&QRK+NzzJB3xyg0bnR1`B>EQCD4hhIO7w%U=HQ^-AxAsVa~;&IE=V(EgE(&s#iD4;FlC zS9FEz?dymFo&XETi2J3`fM2HTPz_?03*>eRgYaR4TOhTZ;T4PttW%=NVG||R83edu zyTh(&t8uQ2y;d1Wf6rcc|Hwv5S@t!AfuVhJrJ)Gut~uL5*nYJA`!W=`M@i*-{8+77 z%ryoLnau*3qZD?aAH+aZ01OT(ye?+D8i_XFXv{)TKZ0JiYZsoMRvDT({o&gL_>o2f zZwR3v#IB6YeJc}9!@m8lhkK(d`}2FNzj3F(TDcvAtr)3B!T5L=T906n&O6;l7>=t+ zyokE&z^LoPH_YxA)C=U8`@NAXTI6dWI{2Udaew+}2+YYChj+Juq17MAMhrr>^5;PD zYj_P5=ky$N^SNzDqY8*r_(2z_3L@@up?j-`r!5P)Od`aS@NrJB1gMV!^3QIj#@Mue zTeZtDym;l=7P7bI514bqhq7a`@NDZX`zs7)7I*JVh4M_3KrpXB{b{+3565!y%M4>q zR|}|TwXKcW-0Dp#Ds&37x)`G+<0gm7EZ-sqM`EjGSztBwC`IBtD6@L z3U~*{Y~dNTXP@Z?`bM_LXzN4w@G?Ih>*0fAw}OGnjgAd&Z`>_I7eC4Jm5sG;H->3J zO}p5$KQJ^gKL0UM*HC`9PKRHM95X?55q-KHqCv1*`=npoj#vH`BXlMR2>}C_+hDv= zpWNVarLf%Hz=sf-Z#xX51hI!Y2!SOFq`hJXM7Z!Gu991d(qlC7 zFbcHCj3rPZ0%GuAkU|nzMSb!mB)T#_k@qsRy#>-s0Ks8yBOJ7ol?cGd;EN)jt@rxI z$tFb9fDGaAwq6O2O=e6uHsuV>+?O)YMns5uO+C>HLt>|R>-h~1R@WIg0phAyNTOwh zt`Ko>|$mLi@c? zbn|sYm_-RxzSZ8`Cx&Bi4(}M*KjD@%4 zXUYk~S5`8SKdE6AzoHb15pXOG@8mJe77HV8zY`dWM8AC(2%kBj07MNKVBcQO5pzIY zIHXCCKIcS-Y)cBrKb{l~SX>#A{)SmQ{~RgWdrS)6KOXS@-LI-ePvi-9kSD9Y4a~dp}+rNI`H>C^u_|r59C}Y(-e!cjh*e+t{V=#6` zzd($q6DeE*bHd;@UPT6Wvn>Y$5diUV6^c=y$U+bTy|p0uQROM|@t3r`%RjXM5+?E{ z^6aeqanw-`nCzdlu-|8Y+RfvL*NJ1I9N?(y;bNPS;`IFVTz_#aUa>99q?#s4%?#`v ziO(b2{kr~&p6Rh#c&LuU!b5jVQrbO0v|Jrw5F1_}1{oXns!i_RSEtpy!rQ6&SL6=b zn2Pg#9u#SG3$cS$=u1d16Br4C(*^YQ^P^xk*Khl0xiC|AAQP@@zl*EF@J z8j=FD9k(p(v`SIPl+Z!>WXE(EG>V5&O&tt6!bnKbsx>4jlHiw8>ew5rD~QQzeC(Y> z%p7)dAClmR_G@K&GrE&g0Twj(zzWqk(HAsUf_TFAvG`_^P`=BN3qyR4e34;Eumi>o zWY$2s4d8 z@|Y{=zlI}#8Ky3ui#jMdgt|;oBt67%H0G;QY0a&RuqSV9agegl?e{1K#Yb6LAc+#> zE{MKH3(Qwdhak>Uc6>6gZLGq_G_s$=p5aV!#8R2wg zO<+&B+fYMn*2`f9B6-Iijxtx1-$`57JE2mTd~#WmlDCyvZG^mhV;!}^{%4@n(t~Hr z1V*GJr3C`YL;dq=V;%7hmlykoXL+W-z2G)<@K>}y+IT4!{Rw%&Py$KU613pStZo5h`ahfteL-&-_zn&rwCruAm#xJ_8pK>5%$W()R5>d` zN5s69h8Nvu*kAwJSkn_jq9fcl$Oz1X7s2AZd5SGa9}8;;p1BJr5Hmc>uL{K1AfnSV=5YaVNOJL&kdKgM7^g z-#3H{{tIi_^UDiZITxL?(4I4=J)4CS9__dCG7xf1CtvR(Gf|nW$b*x+gq1}vAEyfY z8EHLOTFgB)#@X~f#-(ntRdeYLvlEU>t z$$P&*Y%g7hL&b!8 zTGiC_I^a*44iRV@v*b1LWk?W&rNCISGmb^-J#`<26(~r{CA4<&xYGKhHfUl^FL)lFy#Bpt0e&=BIf%+BVjXCD-Y zj}c+~%DxTQ^&xRH8OiTnH!Dcr)bQQR;~@@ef(6bkj!d<1tIUl?mb3rJs2!oP>U%O_GE|&SL|vXfUEYozP$NzPG#Gj^gk>tGlOhzAZPdd8jYeT8 zz*sCpkEGn;C#8y zK}v&LOT$$0?j=$oWx%>^%XBKB@%|*TUdIjS{0<Ijbd{4pq9;E$U*_nBiz;7Kl1IVfF;lt!3+M0o^k0S=R|-r=&Mdw%mD5MhZKAKR2Yj z_^2^qReRF60&*9IC?SQ6940n(iZx1?JU@Ko&5)b?$q=Ldne)BQX!CdeJaLg{k_!>2 z^5wF2_!Tz?D4xVocbOV=IjOll(PNDcH0)ShN5$i1aRNtEjtH5Q4l+ocQ_>Y?)Mdn$0?Yx*JrmQ!;vU+`eY>Tc}ieBpfl$CWlLaqKc1(v zcS0MJ<@1&AtwiwU{)V<4GaKvCu}7lHj}Asxf1A6v+VcEb(z|10seaefQ}oz;4eGG{ zRx;+AnB=hwLbcNCv+3G6wX=&-aOM|Ede_d6Z?% zAXh0CLeF55-*ba=nMI;4rv+}9T9@a2x_6!q&!>quNQ+NP58G~oSaf1KBp)xk;ce?V zP(4?zz2qO?BT7Q=<8{wx|L#k~f^j4DTPh*_-TboCFjmDl8~MjDB*5Ua&KaWAiOTeV zw-x)UcTxE_`BiKMr2pFO{-C+lqyGxNT;}imw2CWinU7cct`tp8?sd|gNMDgrpTh8O z0e&{sccovM?fCT#@bh}Mf}(wAKE{RyKev)8S5zhv5m@Vv*Dc<^)BbFJ_hjsmYp_`l zNlOOJ%Th3{6XwH^Q2onDV0)Dkav$3xey$jw^QEkU0#{wyOvj=JSzyQP3}1p{1yhpq z`6|ENsaWJ`6dMWg2~gB@i+1Im8Pftp`yZXM`yV-ec$9xrm~$YqAonX`|LNT@Ij4Km zg4@ZTi%r9TXD&f$C6Dl+33txY;{ihmdT;tr7LwkBJ5d?0fpA@^|28HJYq_W| z4Wh{?d;VSblG0`#LCa;mStYnDRJ7_Cze}4+Qp+Imi!pd7VGV?U^m(4Y{kIo>v ziP)Ryc~?UR!gyK_c*d;{%5@wsE=j`3G3kI<7?|cN6o<4wf_9YkV=U;I;W&!z(VE0S z36Lr_{y>JR$D@`5Y@Kaq9)%s&GGpQ8ByJ1h{cGzsE|_g=$b^Jwt+)(+v90&C6!2UP z3MLk_acBq(Z##Gpu;m~8&;*5eP2;0k7*eyM9(KncHvonFAfyu&5%sDiUi%Rw2@v;B zfw@|s5(WbaOD~xP{6C2ElNhGzA_eCn0zz0@H(OL6+6UIx6Z2CxiA+u97c)2c1&|qh z3gNn(v}bVlGK^H?-XtGj3}Q^+5gvjTj&Xr1Vs~^QOL{DcTnspi8b#S6p_RRwXH;dfU%#wZNN(N+6=z0H2b=v@BdtX zk-@;J!gKL>)Yr_pHvGpU9Pqv!4F17PZy^mV>7z(Z*?sJY(OMKY2WK%Wn3CEjMdv30 z5R7J6*aHNoc2k(GO*s$veIJpJJR@zCs8quD9_4?4n1Kj_Q=rKX>Kek|#Mv$NK$d7M zcmue>O+XH$Qq%MH1C%lbw*sgzpR+{EY2AkI|3O<=YvKqU)!26SDzB*v%Ht4Z#Blvk zm@jea;Tfi*G3`JVHWO5o-wgjF+w=kq)TH4$bj$3>3_AbG2;k3wBLQL}Vrnt$A|wS| z%SE>3|0Qc3f&4dt9YIb0Z&F+e_&2c(Ccgj4AQwZ!|H;kTG4TG|`1rpJ?+6m{Pxkt= z-rvb-%%^%9Hg)CyP0Rn2WoQ3y7(?{G!+>h`Phy!D+Wb!@z5IVt;EvdT8?XJBV7KF- z|94Ju|0Ji!UIy_?C^3;Ta$z62POGDq23KnPxWZMAJ2*+pm7bREONdgy1mZg?MJ74`IPSe2WSt%v zLvw%+tID|4Aw~GPrb^$V0;DqkaD5l03CK%--(>7f;=;(9##ge~Rw&AxQ&VzD$W=UH z)(d1Nh8YqVMx;~*wmdIUzJ9SAsK$Sn4i9~$B)%F*8u6V(0K|7+geADPOWu0Ww3XDt zA;iWM{rcVgJDZXpHd5(kuM3uOZ{1>Vqhf5KZfV`szA3r)<_+br8GsR|$7!k4Q__32 zVycuRY9aFp3#ki{DK9J(zs;I6*QH07H4=sFm7M!}p4uF=pC7XYod2wSXmbYJ7zYpM z10RMSO*SbVPo62AvurxtntfOy0cMJc$^Jz18~T*rdTv!(8i=>a9JdR0mvsaw4N7LV z$KF0XV7d0`agZz=C?EsFetKJ(OP@=KRFebH2F6m%1Iibu^{Fk60VoCSFsTg-P>Bv=DssYL~x$E3V|o@owJJfdhC zFy)L^C((YcsQ*HcwX%+Gph%5;rY;ox)9qA{ukn5Kk~7|>^+$qZjgeo6&zCLBE^FiA zVaP>ds0b_-=od;(di+W|_^{-*RPkbj_aI<-PYfi~Juc{<&z98hG`N>94dA`)A~Zz* zAt=kk0Pw3(Nr92HHWyMdxQG8&L=PTc5ZbtZtdAV>?_mP;O+1B$<4hgHU&8eF$18-O z0nr3d_A_@n3E)^RgD;7z(!j;_ULpQ}T<>46l8XTTaUUua^#2yI82{%R1Ywc?x*)dy z3SpR<{&g861QX^Q38hRI5SqjH*ZhBK+<*IsvC%fVLr%nhYaf6S7 zk-#HnP_3$F1oGbC2}J{YKTativNCtUc)Mjoz$A$yo?|*V5Cts!PW9c-#RG};V3;kI zn^B`Wnuwz*OYCswvJEeqIaNgU-3Nld=*e9!v@=IM3!H4~`I%N2Z z!PO)WfoJGh!r}N6GD|Z0pI2ayMFLKK-cY(SU`iX`yEA8`uwL7kMF2Hu0It%e9@Mgd zGtj9wlK7it3L8QPIP6u5-@#XCeT=Xxh?=UV40#yg2@6@+%-f3&UiIdv|= zAPnGbFgSWYd(M6iBih(KAgdD_Fzce|x@7c)dhBNzs~DIc0`oF{D3G?W0T>w|Gjxwb zjf0n}mu;T3dbXrK-PaebiZ**Bqme=&XG8s`5X_1ZNsif)LK(^%o|tXQd_LG9o&K8Y z`t`Vv2&Ze+{vOl>P&u4IRmFx!qB~u1W}A-fUlTmxwq~3_%<4UlKW<~N9<&wS0mb`EB3&K9kF#VDU{i3m+E&Uec&MI}h;2TsbOiJ-Q(T!PbmIhkATNLFF8zXo z_6Sai(A0nf!aE3j3|Y#8Uf6^X3v!ndqe7xFeQ!$oFGUpc!Fx}IxklnO#roF?kGpt& z)EH(I><@t)%ZEpD6&jdGaX7{0gkH`9c}T&7m|hWO6+w)3QdTBnKR z*-y#CKX48c7AHZj6|&{dKK*6OPtdyM$Lhg}IWzeECeekEuRmRNL`ddi&FZ61+ulv6 zj$AkW`+}dkF_ObBt7RQ#NN&4OjcGU|mV79^(7U8(zqKc|WEThg>TAJ3% ze_aa@ffq}%Q0y21CW%==%+YPrp;OZjtnk_J1c=wvfoyGweG!3GA$P`L z3b$s#fg8YJCNS~Bm;m?I5RMjB)_fWymfcUIM6ci5RT{Ab*0d#9g4VAM@TS~|12j5b z#NXO=QYo_!V2PEX3b3~>9QOWwWskkS0YUBT#IMmqG^_nhWUv&98gd6;HS0g!kYMsg z^S2#2844hcA^{K<&!L7UfR|)?t3o35^*Pl0wivrw%9ldG(&?iMI-y_+9HLP z;)^3?U---vx6Lz4lLNz6QQb+JLGB*_imBo zZBy{!3;YZsTr3R~L!IDz6U>y+^ZpLjQqq5nt5AU;WArt1JSzOJk;iK`-WYdw+B1Qh zR1*S*X!=0VZou$D`Q+*{kJgJcs9}MDIWO>^NDK70S_`{Fx;p)q>&K_S zT2lJ-5*@+p3+i}{jO}OQyr#f6x2ND1#3m2WyIg_g6QK(a{5;w9C9`@jT^V++Kj@|n zTjc`|gMn|%%I~hje`~e8GU$rtgVFeqFW0`|gz)-xO%bA?f2l#o4f&E=~%f<(-s{=z9z4!9rU2IfkTRIT@YlKu0hXI7wlIVAxL+a$_8AsQl) zFSs)rT`BJCYYb@7gi?TptPdLUIPt_Fpi-GCKC0tY9lo=_6Z=xO{JGoXk@--*exyHvl%p#LC< zyz<)u&Lf-FB{T*Kx=RxBgWty03-Ui)*!@KTa#*+}=$aEDJlmf?qG)kMlK*RIVXW!;~5V062Z`e5L1R2i-1iQ$V3md6y z4T-YWXSWZFmEa#bi(J~80r+i<=sZeMO~gc!ZFVs{pkU)#gwQ z?Xul0_|Y=&@wWMzxZYhmPai$7&^w;jmI$FU<`TVMj=1oo zly=9f+_8@03-8%2c7kz_lD4F19RYWuv}H zqx-^i zsD2beLZ{zl{`}?9or|=YJ3xG+8$Ek$9htl-yQluiL^EE22)!OF|1Ii<1SFwY#U%cH z(94!W9}-Yw*DrP|XSUiWZH8od4I)~9L*6U_(YJYHI{j7c%LJBn=MOZiIOF^Y|(@=m!YxE%1!NF=yH>H@ZOQ754s8@9w!RW)mj5?bQ+fW_I z{ZEj8cAv6^S}2l=fWT0P*hT3jzabe0+wm0b>J1%vP*>YSslm{57ToR?3|*K*l2+e` z+B{K3mYBrt#Z|c6Z_K0YM}bz?8?~w==YakqX;o7xBYF7dP!3PiLn7*IEM&_yHDiQT zVh~^lw}jM(?CKl42a)Y=4(Wy_)e*+m^BQa?Xoa(?4q5jqv~=_k@X{yEY}iI>eg zW{UZwobKOwfF$YX7rhW!zA{*Lh8XEz|7E2nyHWQ+pJpc=sr?=~MPK`JY1oYK+ejJ1gN&YbkUmd8_rzblljU)IdKUximN|#XTu`wOVE+j4i_z?XTBkNQJivj#VVj2l_L%3 z-ijZ^cAH`S@T1!rLOZ3VSp_lW!BmYKuJDmUPi{J;N71>)_44Hg;ggP^xrCXgh7GubgGBLD4Q0)1!*Q%zkbAXs zi2zACC3eUE+|alRvWjn9Z2F^Y^D}rnM)H8N>UAHM1eCKU^wqy5RaYV`f@&eDr`$eIl3jF+@>+k7EnRz0lEq&14%gki z>%{A=!{u&{zw`sWuJ`=^R;QI7c-#0_w7U*@-YR!1VOl@)JAy#@Xx{^HRVavssmCI6 zCoOO=Ap27LBY>y)^hD3BBESp)QL5x# z5TIK6P(B#M)yq%*TW7Qeuckq=gnpdtoIUoDlc7U6*ywYDMG^eLLmQG175U$FAv_8a z$cxyh6yzQxP`hUqqACaAdYw}4Qx~Sed+^Yrn=_4YI0UgSCcmzkiLi#;clCv4lwke4 zT)J%bR3s#XK%hw513j-OH7i)tTnROxIWe5K#_n70*E4w+?xoswO*wDj;W&zl^b3^& z%k4pqT0z$Byf1Ak#S?V~H8IiL%G6^{gQcfB@1;#!m9)hfar5iHi7A<16qFxM>kE+2 zM*Ki+q%@eys7bhqn>AbMt%yiwn=-~dh(odm;&_r`NA(8=dho}%ZW@QOjvlyHkX_IH zC@^5`@crHz8B%IZk?~2BwKLuqlOMy@?V??;R);kFwWr3i;Ya$+gjnvyUlJJ|IrSgj z@K(+;iz`8{&HcksI&1cCy+QH8%tKiYIT7RXSOmX2LQ4Y%~AD z!QtTE>2EMyTN?I0Sff_&)AjhwVq`v z?Tp|tw<#P z5rpO+7QE>Ng2~2b;Q`{4N(2Jp#)2mqT9=g95|iDnsF-aHsVX5Xaqyx!qlqLa-=a}1 zSK_DTv2$%N_s9K}-9Zu#n%H*qJNbg|THr4~PwK382N-zPc`AaW3XL~Ltf-kD%~{di zF9^HgNWj0uE;i90JW;5&MNRgB=@BG(6s4-pcP@tn?+#H6;sh9bE}CmglHt~WYRCB+ z>mfADEpD?8Eu+_b;4@TborsJZitl^4w!&4{rAm-zSgdnfe2z@a+_$!xGs$Q8<#_mS z7W}n)Z`dQ7{&0?gtd6hHee-m6<#dhY$Qw5YJiXHCi~PP7wMTkMr_pWZ3d!iZVg%^{ zRqbQn%mxSd(%$_FelE5y?bH;r{p-=CPIlLSQ@RSK+c9F_6TR~0-*|6Ks7=dspUoCf zvGz~QrVCgd{n0c0cq*TJSnBCU;@#5@IH(M<&vhxsIOCzh1K?mW9-ZSK*5RSGFJ>)E`}Gf zO7b#`ypG~wp4m>}oRIR*ak6p51`r{V9@I6wr=p>t?F_Gr4n5LUC3%S z65RHlIGmZn4|u%^sL&tW9Fgn03@b&^&Ke<{cf~C6Y-o#S_bFZ#jf`y?BzB$%>B|60 znHp)AyBR!^F9%0O3N0ZE0sAQDY4pdiAR{e`Zjj~AtK)&g7v$JP}WZ8X?#Jppba07 zm`@;@L)}XP9k$?S8}AB$965``@bNFePy=Rk)-|D3XCr;n8U;-OGIrZkEJWnKRp8`v z$BKI=fHBE&0PQ`~UM3yse5UGSXg6)4kR;h!bzKnn{y+Y7h;r!;RL(h3)5GOq*_Lwb ztD&o(>iphDb^;Fdd$5GVb~vD`q9qLdSZoKnmCj+0pY2t?FwALC0VPM~eRF-3&zoo6 zM*!Ok`&sp%^Bos!>>Iu|B<1pS%L;#(x`xV9;NWF(n7Z*S^@TOoRnFxm`^bl=2~ME` z1v;-}3`!zZWL2SsN8m_v395E<2P2wxHcox>LIiE(JIMCU*FjpJU4t{Z8hXeQ)J?+o z)|9Q(%P3%Xf}|6`U!^Gp7=!SpW|kXy*zS#1*|xU1=ZAnd!9eSm)Iw^P?n-E7Bv{-Y zr7{_1i;u2={+v2UbBX_W6uy@46-BAKLfjEEQomY!@I1w}D>4bBoIw~uCNs8!HEMJ> zGcGGn<=N|bW{2>HA?KhhC4+-6?%99NU9$Yar%H8<78*av&*N!;FZqUK+; Date: Thu, 22 Oct 2026 19:07:46 -0700 Subject: [PATCH 07/11] chore: drop unused exports from modules QA harnesses load QA harnesses import some modules in the page by path, for example import('/src/data/radio.js'). Those calls name exactly which bindings they use, and every other export of these modules has no importer: - src/data/radio.js: 20 aliases of radio layer methods and two re-exported altitude constants. The radio layer keeps the methods. - src/layers/transit/movement.js: tuning constants and course helpers used only inside the module lose `export`; FIX_HISTORY_MAX, DISPLAY_LAG_MIN_MS, DISPLAY_LAG_RESET_MS, FIX_NOISE_M, STOPPED_FIXES and courseBetween, which nothing reads, are deleted. - src/overlays/worldOverlay.js, src/splitFlap.js, src/data/contactPlayback.js and src/voice/cloudVoiceAuth.js: helpers and constants used only locally lose `export`. --- src/data/contactPlayback.js | 2 +- src/data/radio.js | 24 ------------------ src/layers/transit/movement.js | 46 ++++++---------------------------- src/overlays/worldOverlay.js | 8 ++---- src/splitFlap.js | 2 +- src/voice/cloudVoiceAuth.js | 6 ++--- 6 files changed, 15 insertions(+), 73 deletions(-) diff --git a/src/data/contactPlayback.js b/src/data/contactPlayback.js index 2aa1c9372..14b6b0593 100644 --- a/src/data/contactPlayback.js +++ b/src/data/contactPlayback.js @@ -181,7 +181,7 @@ export function trimTrack(track, nowMs, keep = track.capacity) { for (const id of track.epochs.keys()) if (!used.has(id)) track.epochs.delete(id); } -export function validFix(fix) { +function validFix(fix) { return ( Number.isFinite(fix?.t) && fix.t >= 0 && diff --git a/src/data/radio.js b/src/data/radio.js index e0a8009bc..954c8dd44 100644 --- a/src/data/radio.js +++ b/src/data/radio.js @@ -49,34 +49,12 @@ export const buildRadioTunerBand = layer.buildRadioTunerBand; export const radioTuningStaticShouldPlay = layer.radioTuningStaticShouldPlay; export const getRadioAcceptedCatalogSnapshot = layer.getRadioAcceptedCatalogSnapshot; -export const radioStationResolutionMatches = - layer.radioStationResolutionMatches; export const getRadioUIState = layer.getRadioUIState; -export const subscribeToRadio = layer.subscribeToRadio; -export const subscribeToRadioPlaybackControls = - layer.subscribeToRadioPlaybackControls; -export const getRadioTunerStations = layer.getRadioTunerStations; -export const beginRadioTuning = layer.beginRadioTuning; -export const setRadioTuningStatic = layer.setRadioTuningStatic; export const radioCameraNavigationAllowed = layer.radioCameraNavigationAllowed; -export const previewRadioTuningStation = layer.previewRadioTuningStation; -export const endRadioTuning = layer.endRadioTuning; -export const cancelRadioTuning = layer.cancelRadioTuning; -export const commitRadioTuningStation = layer.commitRadioTuningStation; -export const playSelectedRadio = layer.playSelectedRadio; export const confirmRadioPlayback = layer.confirmRadioPlayback; -export const playPreparedRadioForVoice = layer.playPreparedRadioForVoice; -export const stopRadioPlayback = layer.stopRadioPlayback; -export const toggleRadioPlayback = layer.toggleRadioPlayback; -export const pauseRadioPlayback = layer.pauseRadioPlayback; export const setRadioVolume = layer.setRadioVolume; export const setRadioParams = layer.setRadioParams; -export const getRadioParams = layer.getRadioParams; export const setRadioVoiceDucking = layer.setRadioVoiceDucking; -export const selectRadioStation = layer.selectRadioStation; -export const cycleRadioStation = layer.cycleRadioStation; -export const selectRequestedRadioStation = layer.selectRequestedRadioStation; -export const setRadioFilter = layer.setRadioFilter; export const retainRadioClusterIdentitiesForStations = layer.retainRadioClusterIdentitiesForStations; export { @@ -86,10 +64,8 @@ export { RADIO_SINGLETON_MID_LIMIT, RADIO_SINGLETON_NEAR_LIMIT, RADIO_GLOBE_INTERACTION_MAX_DISTANCE_M, - RADIO_GLOBE_RECENTER_MAX_HEIGHT_M, RADIO_OVERLAY_SOURCE_OPTIONS, DEFAULT_RADIO_FILTER, - GLOBAL_RADIO_ALTITUDE_M, } from '../layers/radio/index.js'; export default layer; diff --git a/src/layers/transit/movement.js b/src/layers/transit/movement.js index 84128cc39..2c9a61dcd 100644 --- a/src/layers/transit/movement.js +++ b/src/layers/transit/movement.js @@ -9,10 +9,7 @@ import { correctHeight, setTimeOffsetMs, } from '../../data/contactPlayback.js'; -export const FIX_HISTORY_MAX = 128; -export const DISPLAY_LAG_MIN_MS = 25_000; -export const DISPLAY_LAG_RESET_MS = 90_000; -export const MODE_MAX_SPEED_MPS = Object.freeze({ +const MODE_MAX_SPEED_MPS = Object.freeze({ bus: 36, // ~130 km/h — a coach on a motorway tram: 28, // ~100 km/h — interurban light rail subway: 42, // ~150 km/h @@ -32,22 +29,19 @@ export const MODE_MAX_SPEED_MPS = Object.freeze({ * @param {boolean} [inferred] Whether the mode came from a feed default. * @returns {number} m/s */ -export function speedCeilingMps(mode, inferred = false) { +function speedCeilingMps(mode, inferred = false) { if (inferred) return MODE_MAX_SPEED_MPS.unknown; return MODE_MAX_SPEED_MPS[mode] ?? MODE_MAX_SPEED_MPS.unknown; } -/** Position deadband is disabled: even a short reported crawl is played. */ -export const FIX_NOISE_M = 0; - /** * Displacement (m) a glide must cover before its direction is worth believing. * Below this the course derived from it is noise; the last good one is held. */ -export const COURSE_MIN_TRAVEL_M = 12; +const COURSE_MIN_TRAVEL_M = 12; /** Maximum display-course change per second (deg). A bus is not a gyroscope. */ -export const COURSE_MAX_RATE_DEG_PER_S = 120; +const COURSE_MAX_RATE_DEG_PER_S = 120; /** * Longest stretch of elapsed time (s) one evaluation may spend. The limiter * used to bank the time since the course last CHANGED, so a vehicle that held @@ -56,15 +50,7 @@ export const COURSE_MAX_RATE_DEG_PER_S = 120; * EVALUATED, and capped here so that even a first look after a long idle * sweeps visibly instead of snapping. */ -export const COURSE_STEP_MAX_S = 0.25; - -/** - * Consecutive fixes at the same place before a vehicle is called STOPPED. - * One is not enough: a fifteen-second gap between identical fixes is also what - * a vehicle waiting at a light looks like, and so is a feed that repeated - * itself. - */ -export const STOPPED_FIXES = 2; +const COURSE_STEP_MAX_S = 0.25; /** * How long after its newest fix a vehicle stops being described as travelling. @@ -75,7 +61,7 @@ export const STOPPED_FIXES = 2; * otherwise is the same overreach as reading STOPPED off a stale status. Ninety * seconds matches the threshold at which a whole feed reads as gone quiet. */ -export const MOTION_UNKNOWN_AFTER_MS = 90_000; +const MOTION_UNKNOWN_AFTER_MS = 90_000; const EARTH_M_PER_DEG = 111_320; @@ -94,29 +80,13 @@ export function fixDistanceM(a, b) { return Math.hypot(dLat, dLon); } -/** - * Course in degrees clockwise from north, from one point to another. - * @param {{lat:number, lon:number}} from - * @param {{lat:number, lon:number}} to - * @returns {number} 0-360 - */ -export function courseBetween(from, to) { - const dLat = (to.lat - from.lat) * EARTH_M_PER_DEG; - const dLon = - (to.lon - from.lon) * - EARTH_M_PER_DEG * - Math.cos((from.lat * Math.PI) / 180); - const deg = (Math.atan2(dLon, dLat) * 180) / Math.PI; - return (deg + 360) % 360; -} - /** * Shortest signed difference between two courses, in degrees (-180, 180]. * @param {number} from * @param {number} to * @returns {number} */ -export function courseDelta(from, to) { +function courseDelta(from, to) { let delta = ((to - from + 540) % 360) - 180; if (delta === -180) delta = 180; return delta; @@ -259,7 +229,7 @@ export function attachFloorToPlace(entry, fix, heightM) { correctHeight(entry.track, scratch.seq, heightM); } } -export function displayCourse(entry, nowMs) { +function displayCourse(entry, nowMs) { if (!entry) return null; const target = Number.isFinite(entry.sample?.segmentCourseDeg) ? entry.sample.segmentCourseDeg diff --git a/src/overlays/worldOverlay.js b/src/overlays/worldOverlay.js index 717c826a2..ccf3cbd59 100644 --- a/src/overlays/worldOverlay.js +++ b/src/overlays/worldOverlay.js @@ -1155,11 +1155,7 @@ export function isOverlayPointVisible( * Return true when a rectangle intersects any cached UI exclusion box. * `count` lets pooled buffers expose a live prefix without truncating. */ -export function overlayRectIntersectsAny( - rect, - exclusions, - count = exclusions.length, -) { +function overlayRectIntersectsAny(rect, exclusions, count = exclusions.length) { for (let i = 0; i < count; i++) { const other = exclusions[i]; if ( @@ -1177,7 +1173,7 @@ export function overlayRectIntersectsAny( * Like `overlayRectIntersectsAny`, but only against exclusions that composite * BELOW the host — the ones a placement may never overlap at any cost. */ -export function overlayRectIntersectsAnyHard( +function overlayRectIntersectsAnyHard( rect, exclusions, count = exclusions.length, diff --git a/src/splitFlap.js b/src/splitFlap.js index 17d677999..9b78feeba 100644 --- a/src/splitFlap.js +++ b/src/splitFlap.js @@ -72,7 +72,7 @@ export const FLAP_STAGGER_MS = 26; */ export const FLAP_MAX_TOTAL_MS = 620; /** Slack after the last character lands before the cells are stripped. */ -export const FLAP_SETTLE_SLACK_MS = 60; +const FLAP_SETTLE_SLACK_MS = 60; /** * Fraction of a character's flip at which the outgoing glyph has rotated * away and the incoming one takes over as what the eye reads. MUST track the diff --git a/src/voice/cloudVoiceAuth.js b/src/voice/cloudVoiceAuth.js index 77feae8aa..5cf9ca882 100644 --- a/src/voice/cloudVoiceAuth.js +++ b/src/voice/cloudVoiceAuth.js @@ -1,5 +1,5 @@ -export const CLOUD_VOICE_AUTH_MODES = Object.freeze(['api-key', 'oauth']); -export const DEFAULT_CLOUD_VOICE_AUTH_MODE = 'api-key'; +const CLOUD_VOICE_AUTH_MODES = Object.freeze(['api-key', 'oauth']); +const DEFAULT_CLOUD_VOICE_AUTH_MODE = 'api-key'; export const CLOUD_VOICE_AUTH_STORAGE_KEY = 'godsEyeView.voice.cloudAuth'; function storageRef(storage) { @@ -11,7 +11,7 @@ function storageRef(storage) { } } -export function normalizeCloudVoiceAuthMode(mode) { +function normalizeCloudVoiceAuthMode(mode) { const normalized = String(mode || '') .trim() .toLowerCase(); From f3a87777ee2926ce81f61ed41a4f9a34f7dcd834 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 19:08:10 -0700 Subject: [PATCH 08/11] chore: remove the remaining unused local bindings An ESLint no-unused-vars scan over src, server, scripts, tools, build and pinokio, repeated until it is stable, reported these. Runtime modules keep every call and drop only the unread binding: - src/ui/cockpitInput.js: the unread result of enter()/exit(). - src/layers/launches/rendering.js: the unread entity returned by entities.add(). - src/layers/radio/rendering.js: the unread map key in the horizon scan, which now iterates the map's values. QA scripts and tools drop an unused helper, two unused in-page module imports, unused counters and destructured fields, and an unread wait result. Tests drop source files read into variables that no assertion uses, redundant StyleManager imports (the shell-source helper already imports it), and unused destructured fields. The one remaining report is the `_` loop variable in src/tools/schema.js, which counts iterations and is intentional. --- scripts/qa-final-polish-credits.mjs | 1 - scripts/qa-radio.mjs | 2 -- scripts/qa-traffic-oblique.mjs | 4 +--- scripts/qa-vessel-cards.mjs | 2 +- src/cameraHandoff.test.mjs | 3 +-- src/contextSessionOrdering.test.mjs | 1 - src/contextTabKeyboard.test.mjs | 5 ----- src/creditAttribution.test.mjs | 2 -- src/data/radio.test.mjs | 4 +--- src/data/trackedModelRegime.test.mjs | 2 -- src/firstRunExperience.test.mjs | 4 ---- src/layers/bikeshare/failureReporting.test.mjs | 2 +- src/layers/launches/rendering.js | 2 +- src/layers/radio/rendering.js | 2 +- src/layers/streetLevel/lineCoverage.test.mjs | 4 ++-- src/layers/transit/lifecycle.test.mjs | 1 - src/layers/wind/index.test.mjs | 2 +- src/loadingFeedback.test.mjs | 1 - src/locationReadouts.test.mjs | 1 - src/mapSourceFocus.test.mjs | 4 +--- src/panelEscape.test.mjs | 2 -- src/sharelink.celestial.test.mjs | 1 - src/ui/cockpitInput.js | 3 ++- src/voice/realtimeNarration.test.mjs | 2 +- tools/streetview-panorama.mjs | 2 +- 25 files changed, 15 insertions(+), 44 deletions(-) diff --git a/scripts/qa-final-polish-credits.mjs b/scripts/qa-final-polish-credits.mjs index 7e7182985..dde1431b9 100644 --- a/scripts/qa-final-polish-credits.mjs +++ b/scripts/qa-final-polish-credits.mjs @@ -52,7 +52,6 @@ try { id, value, ); - const text = () => page.$eval('#cesium-credits', (e) => e.innerText); const shot = async (name, osm, tiles) => { await sleep(300); const state = await page.$eval('#cesium-credits', (e) => { diff --git a/scripts/qa-radio.mjs b/scripts/qa-radio.mjs index 648075bd2..14a9680ac 100644 --- a/scripts/qa-radio.mjs +++ b/scripts/qa-radio.mjs @@ -1291,8 +1291,6 @@ async function main() { const clusterBadge = await page.evaluate(async () => { const viewer = window.__godsEyeView.viewer; const { getOverlayPaintRect, getWorldOverlayDiagnostics } = await import('/src/overlays/worldOverlay.js'); - const { distanceFade } = await import('/src/overlays/worldOverlayDraw.js'); - const { radioStationIdFromPick } = await import('/src/data/radio.js'); const baselineSolveRevision = getWorldOverlayDiagnostics().solveRevision; const source = Array.from({ length: viewer.dataSources.length }, (_, index) => viewer.dataSources.get(index)) .find((item) => item.name === 'Radio stations'); diff --git a/scripts/qa-traffic-oblique.mjs b/scripts/qa-traffic-oblique.mjs index 596d2f009..6ea1c953d 100644 --- a/scripts/qa-traffic-oblique.mjs +++ b/scripts/qa-traffic-oblique.mjs @@ -80,9 +80,7 @@ try { page.evaluate(() => { const { viewer, dataManager } = window.__godsEyeView; let upper = 0, - lower = 0, - near = 0; - const camera = viewer.camera.positionWC; + lower = 0; dataManager.layers .get('traffic') .module.visitMotionDots((id, key, p) => { diff --git a/scripts/qa-vessel-cards.mjs b/scripts/qa-vessel-cards.mjs index b5ad8ac87..278c1c71f 100644 --- a/scripts/qa-vessel-cards.mjs +++ b/scripts/qa-vessel-cards.mjs @@ -295,7 +295,7 @@ async function main() { ); // Wait for the photoreal tileset and at least one vessel refresh. - const settled = await page + await page .waitForFunction(() => { const gev = window.__godsEyeView; const t = gev.tileset; diff --git a/src/cameraHandoff.test.mjs b/src/cameraHandoff.test.mjs index 8a09810ea..d91b9d8a4 100644 --- a/src/cameraHandoff.test.mjs +++ b/src/cameraHandoff.test.mjs @@ -1,7 +1,6 @@ import { LayerBindings } from './ui/layerBindings.js'; import { readShellSource, shellMethod } from './testSupport/readShellSource.mjs'; import { readLayerSource } from './testSupport/readLayerSource.mjs'; -import { StyleManager } from './ui/applicationShell.js'; import { enter as cockpitEnter, navigateContext } from './ui/cockpitTrackingController.js'; import { CockpitViewController } from './ui/cockpitController.js'; import assert from 'node:assert/strict'; @@ -384,7 +383,7 @@ test('world-focus listener lifecycle is symmetric and idempotent', () => { }); test('vessel and fire layers announce valid clicks and never fly cameras', () => { - for (const [label, source] of [['vessels', vessels], ['fires', firms]]) { + for (const source of [vessels, firms]) { assert.match(source, /requestWorldFocus\(\{/); assert.doesNotMatch(source, /camera\.flyTo/); } diff --git a/src/contextSessionOrdering.test.mjs b/src/contextSessionOrdering.test.mjs index 32ec10b28..2f9bfeae0 100644 --- a/src/contextSessionOrdering.test.mjs +++ b/src/contextSessionOrdering.test.mjs @@ -8,7 +8,6 @@ import { setContextMode as contextModeAction } from './ui/contextActions.js'; import { ContextControls } from './ui/contextControls.js'; import { readFileSync as readRadioSource } from 'node:fs'; const radioBindings = readRadioSource(new URL('./ui/radioBindings.js', import.meta.url), 'utf8'); -const radioPresentation = readRadioSource(new URL('./ui/radioPresentation.js', import.meta.url), 'utf8'); const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', import.meta.url), 'utf8'); // Source-contract pins read the actual Context owners and root disposal wiring. Each pin guards a bug that shipped or nearly shipped: // - session bookkeeping ran AFTER the exit early-return, so the compensating diff --git a/src/contextTabKeyboard.test.mjs b/src/contextTabKeyboard.test.mjs index 55ff429a2..6994459a7 100644 --- a/src/contextTabKeyboard.test.mjs +++ b/src/contextTabKeyboard.test.mjs @@ -1,4 +1,3 @@ -import { readShellSource } from './testSupport/readShellSource.mjs'; import { expandApplicationHtml } from '../build/application-html.js'; import { readStylesheet } from './testSupport/readStylesheet.mjs'; import { _selectContextMode } from './ui/contextTransactions.js'; @@ -8,13 +7,11 @@ import { clearSelectedLayers } from './ui/contextActions.js'; import { readFileSync as readRadioSource } from 'node:fs'; const radioBindings = readRadioSource(new URL('./ui/radioBindings.js', import.meta.url), 'utf8'); const radioPresentation = readRadioSource(new URL('./ui/radioPresentation.js', import.meta.url), 'utf8'); -const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', import.meta.url), 'utf8'); import { readFileSync } from 'node:fs'; import { test } from 'node:test'; import assert from 'node:assert/strict'; const html = expandApplicationHtml(readFileSync(new URL('../index.html', import.meta.url), 'utf8')); -const ui = readShellSource(); const css = readStylesheet(new URL('../style.css', import.meta.url)); test('Contacts and Space Missions both participate in the ordinary Tab sequence', () => { @@ -32,8 +29,6 @@ test('Contacts and Space Missions both participate in the ordinary Tab sequence' }); test('Context transition state preserves focus and Tab availability until settle', () => { - const syncSource = _syncContextModeButtons.toString(); - const attributes = () => new Map(); const makeButton = () => { const attrs = attributes(); diff --git a/src/creditAttribution.test.mjs b/src/creditAttribution.test.mjs index d5306dbaa..894d8f7ed 100644 --- a/src/creditAttribution.test.mjs +++ b/src/creditAttribution.test.mjs @@ -1,4 +1,3 @@ -import { readShellSource } from './testSupport/readShellSource.mjs'; import { readStylesheet } from './testSupport/readStylesheet.mjs'; import assert from 'node:assert/strict'; import fs from 'node:fs'; @@ -8,7 +7,6 @@ import { fileURLToPath } from 'node:url'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const css = readStylesheet(path.join(ROOT, 'style.css')); -const ui = readShellSource(); /* * Required-attribution keep-out pin. diff --git a/src/data/radio.test.mjs b/src/data/radio.test.mjs index 58a4101a1..8bc543b03 100644 --- a/src/data/radio.test.mjs +++ b/src/data/radio.test.mjs @@ -1779,7 +1779,6 @@ test('voice playback confirmation waits for playing and requires a hard duck', a }); test('voice playback confirmation accepts fallback buffering but times out safely', async () => { - let listener = () => {}; const state = { audioState: 'loading', playingStationId: 'fallback', @@ -1789,9 +1788,8 @@ test('voice playback confirmation accepts fallback buffering but times out safel const confirmed = await confirmRadioPlayback({ startPlayback: async () => false, subscribe: (next) => { - listener = next; next(state); - return () => { listener = () => {}; }; + return () => {}; }, getState: () => state, timeoutMs: 10, diff --git a/src/data/trackedModelRegime.test.mjs b/src/data/trackedModelRegime.test.mjs index 1b02df1d5..17fec219d 100644 --- a/src/data/trackedModelRegime.test.mjs +++ b/src/data/trackedModelRegime.test.mjs @@ -930,7 +930,6 @@ for (const fixture of LAYERS) { } test('both layers gate the tracked-model load and record its failures', async () => { - const { readFile } = await import('node:fs/promises'); for (const name of ['flights.js', 'militaryFlights.js']) { const source = readLayerSource(new URL(`./${name}`, import.meta.url)); assert.match(source, /!(?:flightState\.)?_trackedModel\s*&&\s*!(?:flightState\.)?_trackedModelLoading\s*&&\s*(?:parts\.\w+\.)?_trackedModelLoadAllowed\(\s*,?\s*\)/, @@ -946,7 +945,6 @@ test('both layers gate the tracked-model load and record its failures', async () }); test('the tracked regime never consults the fleet models3d toggle', async () => { - const { readFile } = await import('node:fs/promises'); for (const name of ['flights.js', 'militaryFlights.js']) { const source = readLayerSource(new URL(`./${name}`, import.meta.url)); const regime = /^([ \t]*)function _trackedModelRegimeActive\b[\s\S]*?\n\1\}/m.exec(source)?.[0]; diff --git a/src/firstRunExperience.test.mjs b/src/firstRunExperience.test.mjs index 749123acd..e4a6d2483 100644 --- a/src/firstRunExperience.test.mjs +++ b/src/firstRunExperience.test.mjs @@ -373,10 +373,6 @@ test('one ESC does one thing — the radio disclosure stops the launcher outrigh new URL('./ui/radioBindings.js', import.meta.url), 'utf8', ); - const module = fs.readFileSync( - new URL('./firstRunExperience.js', import.meta.url), - 'utf8', - ); // stopPropagation() does NOT stop later listeners on the SAME document, so the // disclosure's earlier capture handler closed the disclosure and the launcher diff --git a/src/layers/bikeshare/failureReporting.test.mjs b/src/layers/bikeshare/failureReporting.test.mjs index 89a87bdad..e49ebd962 100644 --- a/src/layers/bikeshare/failureReporting.test.mjs +++ b/src/layers/bikeshare/failureReporting.test.mjs @@ -60,7 +60,7 @@ test('the layer reports failure when a failed city left it with nothing', () => }); test('a layer with nothing to draw and nothing failed is not an error', () => { - const { state, methods } = makeLayer(); + const { methods } = makeLayer(); // Out of range of every city is empty, not broken. assert.equal(methods.getStats().error, undefined); diff --git a/src/layers/launches/rendering.js b/src/layers/launches/rendering.js index 60e6e0070..6379e78e1 100644 --- a/src/layers/launches/rendering.js +++ b/src/layers/launches/rendering.js @@ -109,7 +109,7 @@ export function createRendering({ ); }); if (satelliteTrack) layerState._orbitMatches++; - const entity = layerState._dataSource.entities.add({ + layerState._dataSource.entities.add({ id: `rocket-launch:${launch.id}`, position, point: { diff --git a/src/layers/radio/rendering.js b/src/layers/radio/rendering.js index 36cbc5050..9b4e6b387 100644 --- a/src/layers/radio/rendering.js +++ b/src/layers/radio/rendering.js @@ -259,7 +259,7 @@ export function createRendering({ layerState._horizonScanCount += 1; const occluder = horizonOccluder(layerState._viewer.camera); let visibilityChanged = false; - for (const [id, record] of layerState._renderById) { + for (const record of layerState._renderById.values()) { const matches = parts.categories.stationMatchesRadioCategory( record.station, layerState._filter, diff --git a/src/layers/streetLevel/lineCoverage.test.mjs b/src/layers/streetLevel/lineCoverage.test.mjs index 4d0cb0408..049d2f746 100644 --- a/src/layers/streetLevel/lineCoverage.test.mjs +++ b/src/layers/streetLevel/lineCoverage.test.mjs @@ -317,7 +317,7 @@ test('a rejected key stops coverage requests until the layer goes off', async () test('a rate limit keeps the drawn tiles and asks again once the wait is over', async (t) => { t.mock.timers.enable({ apis: ['setTimeout', 'Date'] }); - const { viewer, source, state, coverage } = setup(); + const { viewer, source, coverage } = setup(); coverage.refresh(); source.calls[0].reject( Object.assign(new Error('rate limited'), { retryAfterSec: 30 }), @@ -593,7 +593,7 @@ test('a tile that fails before the terrain table loads is reported at once, neve }); test('the selected sequence is drawn and highlighted even past the per-tile cap', async () => { - const { state, source, coverage, centre, viewer } = setup(); + const { state, source, coverage, centre } = setup(); const line = (n) => [ [ [centre.lon - 0.001, centre.lat + n * 1e-6], diff --git a/src/layers/transit/lifecycle.test.mjs b/src/layers/transit/lifecycle.test.mjs index 0719acd95..bcc152576 100644 --- a/src/layers/transit/lifecycle.test.mjs +++ b/src/layers/transit/lifecycle.test.mjs @@ -1406,7 +1406,6 @@ test('the selected card lands where the vehicle stopped, not short of it', async // segment ends left it a few metres behind, and then the render hold was // released with no frame scheduled to close the gap. const app = harness(t); - const start = Date.now(); app.serve('mbta', () => ({ status: 200, body: snapshot('mbta', 'MBTA', [vehicle('b', 42.36, -71.06, reported())], { diff --git a/src/layers/wind/index.test.mjs b/src/layers/wind/index.test.mjs index 3b5e09697..175381031 100644 --- a/src/layers/wind/index.test.mjs +++ b/src/layers/wind/index.test.mjs @@ -97,7 +97,7 @@ const complete = (model, extra = {}) => ({ }); test('local appearance and units reuse the loaded field; optional scalar requests remain separate', async () => { const requests = []; - const { layer, calls } = harness({ + const { layer } = harness({ getSnapshot: async (args) => { requests.push(args); return complete( diff --git a/src/loadingFeedback.test.mjs b/src/loadingFeedback.test.mjs index 01d00b1d6..19c660702 100644 --- a/src/loadingFeedback.test.mjs +++ b/src/loadingFeedback.test.mjs @@ -93,7 +93,6 @@ test('deferred terminal notices lose ownership to newer acquisition epochs and d }); test('share-follow failures use the universal top-center status instead of the bottom toast', () => { - const ui = readShellSource(); const handler = shellMethod('_handleShareTrackingRestoreStatus').toString(); assert.match(handler, /this\.showStatus\(message\)/); assert.match(handler, /this\.initialRestorePromise\.then\(showAfterStartupCover\)/); diff --git a/src/locationReadouts.test.mjs b/src/locationReadouts.test.mjs index 79cc92489..9d38592e3 100644 --- a/src/locationReadouts.test.mjs +++ b/src/locationReadouts.test.mjs @@ -1,5 +1,4 @@ import { readShellSource, shellMethod } from './testSupport/readShellSource.mjs'; -import { StyleManager } from './ui/applicationShell.js'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import path from 'node:path'; diff --git a/src/mapSourceFocus.test.mjs b/src/mapSourceFocus.test.mjs index b7beec325..0fa66e0da 100644 --- a/src/mapSourceFocus.test.mjs +++ b/src/mapSourceFocus.test.mjs @@ -1,6 +1,5 @@ -import { readShellSource, shellMethod } from './testSupport/readShellSource.mjs'; +import { shellMethod } from './testSupport/readShellSource.mjs'; import { expandApplicationHtml } from '../build/application-html.js'; -import { StyleManager } from './ui/applicationShell.js'; import { createHoverDisclosure, collapsePanelOnEscape } from './ui/panelDisclosure.js'; import test from 'node:test'; import assert from 'node:assert/strict'; @@ -8,7 +7,6 @@ import { readFileSync } from 'node:fs'; import { msg, t } from './i18n/index.js'; // Exercise the installed event routes and central close method, without WebGL. -const source = readShellSource(); const markup = expandApplicationHtml(readFileSync(new URL('../index.html', import.meta.url), 'utf8')); const locationMarkup = markup.slice(markup.indexOf('
]*\bid="location-bar-toggle"[^>]*)>([\s\S]*?)<\/button>/); diff --git a/src/panelEscape.test.mjs b/src/panelEscape.test.mjs index e0588e9b2..d3d5cfc6d 100644 --- a/src/panelEscape.test.mjs +++ b/src/panelEscape.test.mjs @@ -2,8 +2,6 @@ import { readShellSource } from './testSupport/readShellSource.mjs'; import { onKeyDown as cockpitKeyDown } from './ui/cockpitInput.js'; import { readFileSync as readRadioSource } from 'node:fs'; const radioBindings = readRadioSource(new URL('./ui/radioBindings.js', import.meta.url), 'utf8'); -const radioPresentation = readRadioSource(new URL('./ui/radioPresentation.js', import.meta.url), 'utf8'); -const radioControlsSource = readRadioSource(new URL('./ui/radioControls.js', import.meta.url), 'utf8'); import { bindPanelDisclosure, collapsePanelOnEscape } from './ui/panelDisclosure.js'; import assert from 'node:assert/strict'; import test from 'node:test'; diff --git a/src/sharelink.celestial.test.mjs b/src/sharelink.celestial.test.mjs index ad421dde7..230843584 100644 --- a/src/sharelink.celestial.test.mjs +++ b/src/sharelink.celestial.test.mjs @@ -2,7 +2,6 @@ import { DisplayBindings } from './ui/displayBindings.js'; import { LocationNavigation } from './ui/locationNavigation.js'; import { VisualSettings } from './ui/visualSettings.js'; import { readShellSource, shellMethod } from './testSupport/readShellSource.mjs'; -import { StyleManager } from './ui/applicationShell.js'; import { _claimContextVisualAuthority, setContextMode } from './ui/contextActions.js'; import { _initGlobalContextPanel } from './ui/contextBindings.js'; import { test } from 'node:test'; diff --git a/src/ui/cockpitInput.js b/src/ui/cockpitInput.js index aed87f483..95fb2aee2 100644 --- a/src/ui/cockpitInput.js +++ b/src/ui/cockpitInput.js @@ -64,7 +64,8 @@ export function onKeyDown(event) { event.preventDefault(); event.stopImmediatePropagation(); if (!this.active && !this.isEntryAllowed()) return; - const changed = this.active ? this.exit() : this.enter(); + if (this.active) this.exit(); + else this.enter(); return; } } diff --git a/src/voice/realtimeNarration.test.mjs b/src/voice/realtimeNarration.test.mjs index c9cd3e55f..e47a888c8 100644 --- a/src/voice/realtimeNarration.test.mjs +++ b/src/voice/realtimeNarration.test.mjs @@ -135,7 +135,7 @@ test('an old progress completion leaves the active in-band response alone', asyn }); test('user speech stops an audible progress line: cancel and clear its audio', async () => { - const { controller, sent, event, progressLine } = controllerFixture(); + const { sent, event, progressLine } = controllerFixture(); await progressLine('resp_p'); await event({ type: 'output_audio_buffer.started', response_id: 'resp_p' }); sent.length = 0; diff --git a/tools/streetview-panorama.mjs b/tools/streetview-panorama.mjs index a40d8d287..73010aa6c 100755 --- a/tools/streetview-panorama.mjs +++ b/tools/streetview-panorama.mjs @@ -157,7 +157,7 @@ async function fetchTile(apiKey, session, panoId, z, x, y) { // --------------------------------------------------------------------------- async function stitchPanorama(apiKey, session, panoId, metadata, zoom) { - const { imageWidth, imageHeight, tileWidth, tileHeight } = metadata; + const { tileWidth, tileHeight } = metadata; // Street View tile grids follow a fixed power-of-2 scheme: // zoom 0: 1×1 (360° FOV, entire panorama in one tile) From 9623bc4d585db1fdf72d47e7814110f1899bfc50 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 19:19:03 -0700 Subject: [PATCH 09/11] docs(changelog): add the dead-code cleanup entry --- changelog.d/1321-dead-code-cleanup.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelog.d/1321-dead-code-cleanup.md diff --git a/changelog.d/1321-dead-code-cleanup.md b/changelog.d/1321-dead-code-cleanup.md new file mode 100644 index 000000000..6fec4a692 --- /dev/null +++ b/changelog.d/1321-dead-code-cleanup.md @@ -0,0 +1,6 @@ +- Remove dead code across the repository: three modules nothing imports + (`src/ui.js`, `src/standalone/ui.js`, `src/data/localLayers.js`), exports + and locals nothing reads, a duplicate copy of the bundled dams data and an + unused ship model. The app no longer builds an unused default wind layer + and Bhote Koshi locator layer when it loads. Package exports are unchanged + (Sameh Khamis, #1321). From f6a4b73459caef81ffe274d84e4947f89c944640 Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 19:26:09 -0700 Subject: [PATCH 10/11] docs: point remaining src/ui.js references at their owners The README source tree now lists src/ui/ instead of the removed src/ui.js. docs/CODE-BOUNDARIES.md names src/ui/applicationShell.js as the owner of panel layout, persistence and share restoration, and docs/CURRENT-STATE.md places GLOBAL_POST_DEFAULTS in src/ui/visualPresets.js. QA script comments and test assertion messages that still named ui.js are updated the same way. --- README.md | 2 +- docs/CODE-BOUNDARIES.md | 2 +- docs/CURRENT-STATE.md | 4 ++-- scripts/qa-cockpit-plates.mjs | 2 +- scripts/qa-l9-matrix.mjs | 2 +- src/contactsDetectionPolicy.test.mjs | 4 ++-- src/loadingFeedback.test.mjs | 2 +- src/scenes/director.test.mjs | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e3833a654..1b1fc46a0 100644 --- a/README.md +++ b/README.md @@ -488,7 +488,7 @@ How the globe handles live data: ``` src/ ├── main.js # Bootstrap: Google 3D tiles, layer registration -├── ui.js # Runtime UI — panels, HUD, styles, control facade +├── ui/ # Runtime UI — panels, HUD, styles, controls ├── hud.js # Intelligence HUD + AI scene summary ├── keySetup.js # POWER UP panel — in-app provider keys (dev server only) ├── mapStackController.js # Basemap switching — Google 3D / Esri / OSM / ion stacks diff --git a/docs/CODE-BOUNDARIES.md b/docs/CODE-BOUNDARIES.md index 3362b98f0..9085a7b72 100644 --- a/docs/CODE-BOUNDARIES.md +++ b/docs/CODE-BOUNDARIES.md @@ -235,7 +235,7 @@ DOM elements and callbacks; importing it creates no browser state. `destroy()` removes owned listeners and cancels pending work without changing saved state or moving focus. Call it before removing or replacing the controls. -`src/ui/composition.js` retains panel layout, persistence, share restoration and application +`src/ui/applicationShell.js` retains panel layout, persistence, share restoration and application reactions to state changes. Map Source selection and Location draft cleanup are provided through callbacks. The component imports no globe, data, application or server modules. Package checks and scoped formatting cover this entry. diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 7184c5d96..714cb30f1 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -5373,7 +5373,7 @@ demand with square-root demand scaling and semantic layer weights (`labelArbiter.js:181`). First-run default: ELASTIC. **A default has THREE surfaces, and a PARSE fallback that is not one of them.** -The value literals (engine constant, markup value, markup readout, `ui.js` +The value literals (engine constant, markup value, markup readout, `src/ui/visualPresets.js` `GLOBAL_POST_DEFAULTS`, the share generator's starting state) must all move together, because a fresh boot runs no restore and those literals ARE the startup state. The `scf` / `ko` PARSE fallbacks deliberately do NOT move: they answer @@ -5421,7 +5421,7 @@ easier to meet (detection is now on more often), but does not create it. - **Detection OUTSIDE opacity: `1%`** (moved `5% → 3% → 1%` on 2026-08-24). `KEYHOLE_OUTSIDE_OPACITY_DEFAULT` in `src/celestialRing.js`, mirrored by `#detection-opacity-slider`'s markup value AND readout, - `GLOBAL_POST_DEFAULTS.detectionOutsideOpacityPct` in `ui.js`, and + `GLOBAL_POST_DEFAULTS.detectionOutsideOpacityPct` in `src/ui/visualPresets.js`, and `_detectionOutsideOpacityPct` in `sharelink.js`. The slider's `step` is now `1`, so 1–4 % are reachable at all (at the previous step of 5 the entire sub-default range was one stop wide). `AIRCRAFT_BRACKET_FLOOR_ANCHOR` in diff --git a/scripts/qa-cockpit-plates.mjs b/scripts/qa-cockpit-plates.mjs index 4e725fd8f..3d3aece03 100644 --- a/scripts/qa-cockpit-plates.mjs +++ b/scripts/qa-cockpit-plates.mjs @@ -12,7 +12,7 @@ * passed with the painter stubbed out — the number was right and nothing was * drawn. Every assertion here now comes from the RENDERED path instead: * - * ids/counts — `styleManager.getDetectionDiagnostics()` (src/ui.js), the + * ids/counts — `styleManager.getDetectionDiagnostics()` (src/ui/shellFacade.js), the * overlay's own published `visibleCount` / `labeledKeys`, so a contact only * counts once the sweep, the arbiter and the placement solve have all run. * plate scale — tapped off `CanvasRenderingContext2D` while the frame paints. diff --git a/scripts/qa-l9-matrix.mjs b/scripts/qa-l9-matrix.mjs index fdac4fd40..8709366de 100644 --- a/scripts/qa-l9-matrix.mjs +++ b/scripts/qa-l9-matrix.mjs @@ -1884,7 +1884,7 @@ async function runBrowserGroup(record) { await step('C15', async () => { // CRT/NVG/FLIR are DISPLAY LABELS; the real style ids are retro / - // surveillance / thermal (src/ui.js STYLE_STATUS_LABELS). Passing the + // surveillance / thermal (src/ui/visualPresets.js STYLE_STATUS_LABELS). Passing the // labels made every call a silent no-op that still "passed" — so assert // the returned visual state actually changed to the requested style. const wanted = [['retro', 'CRT'], ['surveillance', 'NVG'], ['thermal', 'FLIR'], ['normal', 'Normal']]; diff --git a/src/contactsDetectionPolicy.test.mjs b/src/contactsDetectionPolicy.test.mjs index 862d055ce..8fe78e1de 100644 --- a/src/contactsDetectionPolicy.test.mjs +++ b/src/contactsDetectionPolicy.test.mjs @@ -281,7 +281,7 @@ test('detection is wired to the Contacts transaction, and cockpit no longer touc uiSource.indexOf('_syncContactsDetection() {'), uiSource.indexOf('/** Apply a temporary cockpit-only'), ); - assert.ok(helper.length > 0, 'the Contacts detection helper is present in ui.js'); + assert.ok(helper.length > 0, 'the Contacts detection helper is present in the UI shell source'); assert.match(helper, /if \(this\._contextModeChanging\) return;/, 'fires at settle, not at click'); assert.match(helper, /active: this\._contextMode === 'flights'/); assert.match(helper, /styleOwnsDetection:\s*!this\._detectionUserOverridden/); @@ -302,7 +302,7 @@ test('detection is wired to the Contacts transaction, and cockpit no longer touc uiSource.indexOf('_setCockpitVision(mode, active, { revealParameters = false } = {}) {'), uiSource.indexOf('_syncIrBoost() {'), ); - assert.ok(visionHook.length > 0, 'the cockpit vision hook is present in ui.js'); + assert.ok(visionHook.length > 0, 'the cockpit vision hook is present in the UI shell source'); assert.doesNotMatch(visionHook, /etection/, 'cockpit transitions must not touch detection'); }); diff --git a/src/loadingFeedback.test.mjs b/src/loadingFeedback.test.mjs index 19c660702..b9903f97e 100644 --- a/src/loadingFeedback.test.mjs +++ b/src/loadingFeedback.test.mjs @@ -628,7 +628,7 @@ test('the loading ticker never runs hidden and stops after loading and notices s // safety-net poll, so its `if (document.hidden) return;` is correct there // and must not be confused with this self-stopping ticker's leak. const armBody = ui.slice(ui.indexOf('_armLoadingFeedbackTicker() {')); - assert.ok(armBody.startsWith('_armLoadingFeedbackTicker() {'), 'ticker function not found in ui.js'); + assert.ok(armBody.startsWith('_armLoadingFeedbackTicker() {'), 'ticker function not found in the UI shell source'); const arm = armBody.slice(0, armBody.indexOf('\n }\n') + 5); // 1. The ARM guard itself refuses while hidden. Previously the only hidden diff --git a/src/scenes/director.test.mjs b/src/scenes/director.test.mjs index 59f3a3a0e..1077b0658 100644 --- a/src/scenes/director.test.mjs +++ b/src/scenes/director.test.mjs @@ -1264,7 +1264,7 @@ test('applyVisualState gates the map-stack switch on both sides of its await', ( // switch would otherwise stand on the globe. const source = readShellSource(); const method = source.match(/\n {2}async applyVisualState\([\s\S]*?\n {2}\}\n/); - assert.ok(method, 'applyVisualState is missing from ui.js'); + assert.ok(method, 'applyVisualState is missing from the UI shell source'); assert.match(method[0], /async applyVisualState\(state = \{\}, \{ isCurrent = null \} = \{\}\)/); const mapStackBlock = method[0].match(/if \(state\.mapStack\) \{[\s\S]*?\n {4}\}/); From 04f8c7fad0b1d537b295ac7eb9303fec089cd33a Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Thu, 22 Oct 2026 20:47:34 -0700 Subject: [PATCH 11/11] chore: credit the changelog entry to the GitHub handle --- changelog.d/1321-dead-code-cleanup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/1321-dead-code-cleanup.md b/changelog.d/1321-dead-code-cleanup.md index 6fec4a692..e8cad2a23 100644 --- a/changelog.d/1321-dead-code-cleanup.md +++ b/changelog.d/1321-dead-code-cleanup.md @@ -3,4 +3,4 @@ and locals nothing reads, a duplicate copy of the bundled dams data and an unused ship model. The app no longer builds an unused default wind layer and Bhote Koshi locator layer when it loads. Package exports are unchanged - (Sameh Khamis, #1321). + (samehkhamis, #1321).