@thomas.f/prnext 0.1.16-alpha → 0.1.18-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI.md +63 -0
- package/README.md +35 -503
- package/build/ADAPTERS.md +27 -2
- package/build/actions.mjs +6 -1
- package/build/analyze.mjs +340 -0
- package/build/app-config.mjs +20 -8
- package/build/app-static.mjs +41 -14
- package/build/app-styles.mjs +79 -0
- package/build/app.mjs +99 -31
- package/build/build-lock.mjs +6 -6
- package/build/bundle-runtime.mjs +39 -11
- package/build/cache-components.mjs +1 -1
- package/build/cache-handlers.mjs +1 -1
- package/build/check.mjs +56 -11
- package/build/compact-css.mjs +53 -0
- package/build/compilation-cache.mjs +269 -0
- package/build/compilation-store.mjs +142 -0
- package/build/compiler.mjs +51 -4
- package/build/concurrent.mjs +12 -0
- package/build/config.mjs +50 -13
- package/build/create.mjs +239 -0
- package/build/css-modules.mjs +65 -10
- package/build/deployment-assets.mjs +89 -0
- package/build/deployment-config.mjs +18 -0
- package/build/dev-compiler-health.mjs +11 -0
- package/build/dev-css-update.mjs +85 -0
- package/build/dev-dependencies.mjs +44 -0
- package/build/dev-discovery.mjs +28 -0
- package/build/dev-worker.mjs +14 -5
- package/build/dev.mjs +64 -6
- package/build/dynamic.mjs +1 -1
- package/build/edge.mjs +5 -4
- package/build/fonts.mjs +1 -1
- package/build/framework-imports.mjs +50 -0
- package/build/html-only-config.mjs +53 -0
- package/build/html-only-graph.mjs +67 -0
- package/build/incremental.mjs +8 -4
- package/build/index.mjs +140 -112
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +37 -4
- package/build/instrumentation.mjs +30 -0
- package/build/loader-cache.mjs +53 -26
- package/build/metadata-routes.mjs +3 -4
- package/build/migrate.mjs +60 -57
- package/build/migration-audit.mjs +1 -1
- package/build/native-type-config.mjs +37 -0
- package/build/npm-packages.mjs +3 -18
- package/build/package-manager.mjs +308 -0
- package/build/persistent-cache.mjs +112 -0
- package/build/precompress.mjs +62 -30
- package/build/prerender-cache.mjs +70 -0
- package/build/prerender-config.mjs +29 -0
- package/build/prerender-pages.mjs +100 -0
- package/build/prerender-pool.mjs +165 -0
- package/build/prerender-process-worker.mjs +58 -0
- package/build/prerender-worker.mjs +10 -0
- package/build/production-builder.mjs +129 -0
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- package/build/scan.mjs +4 -4
- package/build/source-analysis.mjs +42 -0
- package/build/static-adapter.mjs +13 -5
- package/build/static-export.mjs +4 -4
- package/build/static-input.mjs +48 -0
- package/build/styles.mjs +17 -3
- package/build/transform-cache.mjs +37 -4
- package/build/transform.mjs +1 -1
- package/build/type-command.mjs +41 -0
- package/build/typed-routes.mjs +95 -0
- package/build/typescript-dev-worker.mjs +18 -0
- package/build/typescript-dev.mjs +49 -0
- package/build/typescript-native-worker.mjs +55 -0
- package/build/typescript-native.mjs +18 -0
- package/build/typescript-toolchain.mjs +19 -0
- package/build/typescript-watch.mjs +22 -0
- package/build/typescript.mjs +11 -8
- package/build/webpack-graph.mjs +170 -30
- package/build/worker-memory.mjs +9 -0
- package/build/worker-preload.mjs +9 -0
- package/cli.mjs +162 -122
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +12 -2
- package/compat/cache-wire.cjs +28 -0
- package/compat/cache.cjs +2 -1
- package/compat/cache.d.cts +4 -0
- package/compat/cookies.cjs +21 -5
- package/compat/data-cache.cjs +12 -8
- package/compat/flight-delta.cjs +150 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +89 -10
- package/compat/link.cjs +12 -9
- package/compat/link.d.cts +4 -3
- package/compat/navigation-authorization.cjs +9 -0
- package/compat/navigation-stages.cjs +19 -0
- package/compat/navigation.d.cts +4 -3
- package/compat/offline-store.cjs +11 -0
- package/compat/offline.cjs +5 -0
- package/compat/offline.d.cts +2 -0
- package/compat/redis-cache.cjs +201 -0
- package/compat/redis-cache.d.cts +37 -0
- package/compat/router.d.cts +4 -3
- package/compat/server-primitives.cjs +31 -11
- package/compat/server.d.cts +15 -2
- package/compat/static-generation.cjs +7 -0
- package/compat/typed-route.d.cts +17 -0
- package/compat/use-cache.cjs +3 -0
- package/docs/ai/compatibility.md +275 -0
- package/docs/ai/data.md +288 -0
- package/docs/ai/deployment.md +269 -0
- package/docs/ai/manifest.json +11 -0
- package/docs/ai/quickstart.md +171 -0
- package/docs/ai/routing.md +214 -0
- package/docs/ai/troubleshooting.md +155 -0
- package/index.mjs +2 -0
- package/native/diagnostics.mjs +34 -0
- package/native/resolve.mjs +7 -2
- package/package.json +44 -7
- package/release/ai-docs.mjs +48 -0
- package/release/verify.mjs +2 -0
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/ai-docs.mjs +202 -0
- package/runtime/app-client.mjs +226 -68
- package/runtime/app-content.mjs +4 -3
- package/runtime/app-partial-client.mjs +3 -0
- package/runtime/app-partial.mjs +1 -1
- package/runtime/app-prefetch-constants.mjs +4 -0
- package/runtime/app-prefetch.mjs +2 -3
- package/runtime/app-render-key.mjs +5 -0
- package/runtime/app-render.mjs +100 -28
- package/runtime/app-routing.mjs +22 -9
- package/runtime/app-static.mjs +18 -8
- package/runtime/app-stream-html.mjs +10 -53
- package/runtime/app-style-selection.mjs +34 -0
- package/runtime/app-styles.mjs +154 -0
- package/runtime/cache-components-rsc.mjs +4 -1
- package/runtime/cache-request.mjs +2 -1
- package/runtime/cloud/archive.mjs +5 -6
- package/runtime/compile-cache.mjs +8 -0
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-ai-brands.mjs +26 -0
- package/runtime/dev-ai-style.mjs +99 -0
- package/runtime/dev-build-reasons.mjs +25 -0
- package/runtime/dev-cache-control.mjs +53 -0
- package/runtime/dev-client.mjs +245 -46
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-console-copy.mjs +19 -0
- package/runtime/dev-console.mjs +106 -0
- package/runtime/dev-dependency-graph.mjs +40 -0
- package/runtime/dev-element-picker-style.mjs +27 -0
- package/runtime/dev-element-picker.mjs +213 -0
- package/runtime/dev-error-overlay-style.mjs +41 -0
- package/runtime/dev-error-overlay.mjs +167 -0
- package/runtime/dev-error-report.mjs +121 -0
- package/runtime/dev-file-changes.mjs +1 -1
- package/runtime/dev-gateway.mjs +44 -0
- package/runtime/dev-host-relay.mjs +66 -0
- package/runtime/dev-indicator.mjs +191 -0
- package/runtime/dev-issue-motion.mjs +214 -0
- package/runtime/dev-lazy-tool.mjs +34 -0
- package/runtime/dev-logo.mjs +2 -0
- package/runtime/dev-notification.mjs +81 -0
- package/runtime/dev-popout.mjs +34 -0
- package/runtime/dev-position.mjs +107 -0
- package/runtime/dev-preview-touch.mjs +122 -0
- package/runtime/dev-runtime-pool.mjs +45 -0
- package/runtime/dev-runtime-spare.mjs +57 -0
- package/runtime/dev-tools-client.mjs +155 -0
- package/runtime/dev-tools-mcp.mjs +126 -0
- package/runtime/dev-tools-metrics.mjs +31 -0
- package/runtime/dev-tools-model.mjs +42 -0
- package/runtime/dev-tools-panel.mjs +341 -0
- package/runtime/dev-tools-result.mjs +37 -0
- package/runtime/dev-tools-server.mjs +256 -0
- package/runtime/dev-tools-source.mjs +104 -0
- package/runtime/dev-tooltip.mjs +94 -0
- package/runtime/dev-ui-icons.mjs +8 -0
- package/runtime/dev-workbench-style.mjs +106 -0
- package/runtime/dev-workbench.mjs +428 -0
- package/runtime/dev-worker-logs.mjs +31 -0
- package/runtime/development.mjs +324 -0
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/env.mjs +2 -1
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +50 -0
- package/runtime/flight-tap.mjs +28 -0
- package/runtime/html-only.mjs +27 -0
- package/runtime/http.mjs +2 -0
- package/runtime/incremental-cache.mjs +25 -4
- package/runtime/instrumentation.mjs +49 -0
- package/runtime/navigation-recovery.mjs +36 -0
- package/runtime/navigation.mjs +7 -0
- package/runtime/node-html-stream.mjs +47 -0
- package/runtime/pages-client.mjs +24 -8
- package/runtime/pages-navigation.mjs +54 -64
- package/runtime/persistent/client.mjs +6 -5
- package/runtime/persistent/daemon.mjs +66 -56
- package/runtime/persistent/native-proxy.mjs +63 -0
- package/runtime/prefetch-scheduler.mjs +110 -0
- package/runtime/preload.mjs +12 -0
- package/runtime/profile.mjs +14 -0
- package/runtime/profiles.json +2 -2
- package/runtime/render.mjs +3 -0
- package/runtime/route-pattern.mjs +44 -0
- package/runtime/rsc-worker.mjs +41 -12
- package/runtime/trace.mjs +78 -0
- package/runtime/typed-route.mjs +19 -0
- package/runtime/watch-project.mjs +29 -18
- package/runtime/worker.mjs +102 -24
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import {setTimeout as delay} from 'node:timers/promises';
|
|
3
|
+
import {createCloudClient} from './cloud/client.mjs';
|
|
4
|
+
import {cloudOrigin,credentialPath,linkPath,readState} from './cloud/state.mjs';
|
|
5
|
+
|
|
6
|
+
// Explicit --host-ai only. The cloud never receives a localhost capability or
|
|
7
|
+
// an address to fetch: this process pulls a bounded, allowlisted command queue.
|
|
8
|
+
const allowed = new Set(['dev_snapshot','dev_connection','dev_logs','dev_routes','dev_route_ensure','dev_cache_invalidate','dev_verify','dev_source','dev_ai_context']);
|
|
9
|
+
export async function startDevHostRelay({root,tools,env=process.env,fetcher=fetch,interval=1000,signal:parentSignal}) {
|
|
10
|
+
const controller=new AbortController(),signal=AbortSignal.any([controller.signal,...(parentSignal?[parentSignal]:[])]);
|
|
11
|
+
const link=await readState(linkPath(root));
|
|
12
|
+
const origin=cloudOrigin(env.PRNEXT_CLOUD_URL||link?.origin||'https://app.prnext.dev');
|
|
13
|
+
const credential=await readState(credentialPath(origin,env));
|
|
14
|
+
const log=(level,message)=>tools.addServerLog(level,message,{process:'host-relay'});
|
|
15
|
+
const status=value=>tools.setHostRelayState?.({enabled:true,linked:false,origin,...value});
|
|
16
|
+
status({});
|
|
17
|
+
if(!credential||credential.origin!==origin||!/^prn_[A-Za-z0-9_-]{43}$/.test(credential.accessToken||'')||credential.expiresAt<=Date.now()) {
|
|
18
|
+
log('warn',`Host AI relay is off. Run prnext login --server ${origin}, then restart with --host-ai.`);
|
|
19
|
+
status({error:'Run prnext login to connect this terminal.'});
|
|
20
|
+
return {close:async()=>{}};
|
|
21
|
+
}
|
|
22
|
+
const api=createCloudClient(origin,{token:credential.accessToken,signal,fetcher});
|
|
23
|
+
let relayId,stopped=false;
|
|
24
|
+
const register=async()=>{
|
|
25
|
+
const reply=await api('/ai/dev/register',{method:'POST',body:{session:tools.session,title:path.basename(root).slice(0,80),...(link?.origin===origin&&link.projectId?{projectId:link.projectId}:{})}});
|
|
26
|
+
if(typeof reply.id!=='string'||reply.id.length>100)throw Error('Host returned an invalid development relay.');
|
|
27
|
+
relayId=reply.id;status({linked:true,lastSeen:Date.now()});log('info','Host AI relay ready. Your existing Host MCP can discover this local development session.');
|
|
28
|
+
};
|
|
29
|
+
try{await register();}catch(error){const message=error.status===404?'This Host server needs the local development relay update. Local MCP remains available.':`Host AI relay unavailable: ${error.message}`;status({error:message});log('warn',message);return {close:async()=>{}};}
|
|
30
|
+
async function work(command) {
|
|
31
|
+
let result;
|
|
32
|
+
if(!command||typeof command.id!=='string'||!allowed.has(command.tool)||!command.args||typeof command.args!=='object'||Array.isArray(command.args))
|
|
33
|
+
result={error:'Unsupported development command.'};
|
|
34
|
+
else if(!Number.isFinite(command.expiresAt)||command.expiresAt<=Date.now())result={error:'Development command expired before execution.'};
|
|
35
|
+
else {
|
|
36
|
+
try{result={result:await tools.executeTool(command.tool,command.args,{client:`Host MCP: ${String(command.client||'assistant').slice(0,70)}`})};}
|
|
37
|
+
catch(error){result={error:String(error.message).slice(0,1000)};}
|
|
38
|
+
}
|
|
39
|
+
if(!stopped)await api('/ai/dev/result',{method:'POST',body:{id:relayId,commandId:command.id,...result}});
|
|
40
|
+
}
|
|
41
|
+
const running=(async()=>{
|
|
42
|
+
let failures=0;
|
|
43
|
+
while(!stopped){
|
|
44
|
+
try{
|
|
45
|
+
const reply=await api('/ai/dev/poll',{method:'POST',body:{id:relayId}});
|
|
46
|
+
status({linked:true,lastSeen:Date.now()});
|
|
47
|
+
for(const command of (Array.isArray(reply.commands)?reply.commands:[]).slice(0,1)) {if(stopped||signal.aborted)break;await work(command);}
|
|
48
|
+
failures=0;
|
|
49
|
+
}catch(error){
|
|
50
|
+
if(stopped)break;
|
|
51
|
+
status({error:error.message});
|
|
52
|
+
if(error.status===401||error.status===403){log('warn','Host AI relay disconnected: terminal access expired or was revoked.');break;}
|
|
53
|
+
if(error.status===404){try{await register();failures=0;}catch{failures++;}}
|
|
54
|
+
else failures++;
|
|
55
|
+
if(failures===1)log('warn','Host AI relay reconnecting; no command is replayed.');
|
|
56
|
+
}
|
|
57
|
+
try{await delay(Math.min(10000,interval*2**Math.min(failures,3)),undefined,{signal});}catch{break;}
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
return {id:relayId,async close(){
|
|
61
|
+
if(stopped)return;stopped=true;controller.abort();status({enabled:false});
|
|
62
|
+
// Abort long local verification without blocking development shutdown.
|
|
63
|
+
await Promise.race([running,delay(100)]);
|
|
64
|
+
try{await createCloudClient(origin,{token:credential.accessToken,signal:AbortSignal.timeout(1500),fetcher})('/ai/dev/close',{method:'POST',body:{id:relayId}});}catch{}
|
|
65
|
+
}};
|
|
66
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import logo from './dev-logo.mjs';
|
|
2
|
+
import { createLazyDevTool, registerDevToolLoaders } from './dev-lazy-tool.mjs';
|
|
3
|
+
import { popoutIcon } from './dev-popout.mjs';
|
|
4
|
+
import { deviceIcons } from './dev-ui-icons.mjs';
|
|
5
|
+
import { createDevPosition } from './dev-position.mjs';
|
|
6
|
+
import { createDevIssueMotion } from './dev-issue-motion.mjs';
|
|
7
|
+
|
|
8
|
+
const loadDevTool = registerDevToolLoaders({
|
|
9
|
+
workbench: () => import('./dev-workbench.mjs'),
|
|
10
|
+
inspector: () => import('./dev-tools-panel.mjs'),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// This module is imported only by the development browser entry. No framework
|
|
14
|
+
// contexts, application CSS, network requests or additional SSE streams.
|
|
15
|
+
export function createDevIndicator({ position = 'bottom-left', snapshot, showIssues, capture, tools }) {
|
|
16
|
+
const host = document.createElement('prnext-dev-tools');
|
|
17
|
+
host.id = '__prnext_dev_tools__';
|
|
18
|
+
const shadow = host.attachShadow({ mode: 'open' });
|
|
19
|
+
const style = document.createElement('style');
|
|
20
|
+
style.textContent = `
|
|
21
|
+
:host{all:initial;position:fixed;bottom:max(18px,env(safe-area-inset-bottom));left:max(18px,env(safe-area-inset-left));width:44px;height:44px;--issue-rgb:240,92,102;--issue-alpha:.5;--origin-x:22px;--origin-y:calc(100% - 22px);--flip-x:1;--flip-y:1;--panel-from:8px;z-index:2147483646;color-scheme:dark;font:13px/1.5 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#ededf0;text-align:left;direction:ltr}
|
|
22
|
+
:host([data-position^=top]){top:max(18px,env(safe-area-inset-top));bottom:auto;--origin-y:22px;--flip-y:-1;--panel-from:-8px}:host([data-position$=right]){right:max(18px,env(safe-area-inset-right));left:auto;--origin-x:calc(100% - 22px);--flip-x:-1}
|
|
23
|
+
*,*::before,*::after{box-sizing:border-box}button{font:inherit;color:inherit;-webkit-tap-highlight-color:transparent;cursor:pointer}button:focus-visible{outline:1px solid #858585;outline-offset:-2px}button:disabled{cursor:default}svg{width:16px;height:16px;flex:none}
|
|
24
|
+
.trigger{position:relative;z-index:3;display:grid;place-items:center;width:44px;height:44px;padding:0;border:1px solid #303030;border-radius:999px;background:#080808;box-shadow:0 2px 5px #0006,0 8px 22px #0004,inset 0 1px 0 #ffffff06;user-select:none;cursor:grab}:host([data-dragging=true]) .trigger{cursor:grabbing;scale:1.06;border-color:#777;box-shadow:0 10px 24px #0008}
|
|
25
|
+
.trigger:hover{border-color:#555;background:#101010}.trigger[aria-expanded=true]{border-color:transparent;background:transparent;box-shadow:none}.trigger img{width:28px;height:28px;border-radius:50%;pointer-events:none;filter:grayscale(1) contrast(1.25)}
|
|
26
|
+
.bubble{position:absolute;bottom:0;left:0;z-index:1;pointer-events:none;filter:drop-shadow(0 12px 22px #0008) drop-shadow(0 2px 5px #0006)}.bubble-mask{width:100%;height:100%;clip-path:circle(22px at var(--origin-x) var(--origin-y))}.bubble-mask[data-open=true]{clip-path:circle(var(--bubble-radius) at var(--origin-x) var(--origin-y))}.bubble svg{display:block;width:100%;height:100%;transform:scale(var(--flip-x),var(--flip-y));transform-origin:center}.bubble path{fill:#080808;stroke:#292929;stroke-width:1}
|
|
27
|
+
.ring{position:absolute;inset:3px;pointer-events:none}.ring svg{display:block;width:100%;height:100%;overflow:visible}.ring circle{fill:none;stroke:transparent;stroke-width:1.5;stroke-linecap:round;stroke-dasharray:0 50 0 50;transform:rotate(-113.4deg);transform-origin:center}
|
|
28
|
+
.trigger[data-busy=true] .ring circle{stroke:#b3c1ff;stroke-dasharray:25 0 25 50}.trigger[data-issue-severity=error] .ring circle{stroke:rgba(var(--issue-rgb),var(--issue-alpha));stroke-dasharray:13 37 13 37}.trigger[data-issue-severity=critical] .ring circle{stroke:rgba(var(--issue-rgb),var(--issue-alpha));stroke-dasharray:50 0 50 0}
|
|
29
|
+
.issue-motion{position:absolute;top:0;left:0;width:1px;height:1px;overflow:visible;z-index:4;pointer-events:none;visibility:hidden;opacity:var(--issue-alpha)}.issue-motion[data-running=true]{visibility:visible}.issue-motion-path,.issue-motion-ring,.issue-motion-streak{stroke:rgb(var(--issue-rgb))}
|
|
30
|
+
:host([data-issue-transfer]) .trigger[data-issue-severity=error] .ring circle,:host([data-issue-transfer]) .trigger[data-issue-severity=critical] .ring circle{transition:none}
|
|
31
|
+
:host([data-issue-transfer=moving]) .trigger[data-issue-severity=error] .ring circle,:host([data-issue-transfer=moving]) .trigger[data-issue-severity=critical] .ring circle,:host([data-issue-transfer=open]) .trigger[data-issue-severity=error] .ring circle,:host([data-issue-transfer=open]) .trigger[data-issue-severity=critical] .ring circle{stroke:transparent}
|
|
32
|
+
:host([data-issue-transfer=moving]) .action-issues{border-color:transparent}
|
|
33
|
+
.panel{position:absolute;bottom:36px;left:0;width:264px;max-width:calc(100vw - 36px);max-height:calc(100dvh - 100px);overflow:auto;overscroll-behavior:contain;padding:8px 8px 12px;border:0;border-radius:20px;background:transparent;scrollbar-color:#333 #080808;transform-origin:var(--origin-x) bottom}
|
|
34
|
+
:host([data-position^=top]) .bubble{top:0;bottom:auto}:host([data-position^=top]) .panel{top:36px;bottom:auto;transform-origin:var(--origin-x) top}:host([data-position$=right]) .bubble,:host([data-position$=right]) .panel{right:0;left:auto}
|
|
35
|
+
.corner-hints{position:fixed;inset:0;pointer-events:none;z-index:0}.corner-hint{position:absolute;width:44px;height:44px;bottom:max(18px,env(safe-area-inset-bottom));left:max(18px,env(safe-area-inset-left));border:1px dashed #ffffff35;border-radius:50%;background:#08080888}.corner-hint[data-corner^=top]{top:max(18px,env(safe-area-inset-top));bottom:auto}.corner-hint[data-corner$=right]{right:max(18px,env(safe-area-inset-right));left:auto}.corner-hint[data-active=true]{border-style:solid;border-color:#aaa;background:#ffffff16;scale:1.1}
|
|
36
|
+
:host([hidden]),[hidden]{display:none!important}.panel{z-index:2}.panel:not([data-open=true]){opacity:0;pointer-events:none}
|
|
37
|
+
.panel:focus{outline:none}.trigger:focus-visible{outline-offset:-4px}.action:focus-visible{background:#171717}
|
|
38
|
+
.action{display:flex;align-items:center;gap:10px;width:100%;min-height:40px;border:0;padding:9px 10px;border-radius:11px;background:transparent;text-decoration:none;text-align:left;font-size:12px}.action:hover:not(:disabled){background:#161616}.action:disabled{color:#a1a1a1}.action .hint{margin-left:auto;color:#a1a1a1;font-size:11px}.divider{height:1px;margin:6px 8px;background:#202020}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
|
39
|
+
.action-issues{border:1px solid rgba(var(--issue-rgb),var(--issue-alpha));padding:8px 9px}.action-issues .hint{color:rgb(var(--issue-rgb))}
|
|
40
|
+
.action-split{display:flex;align-items:center}.action-split .action{flex:1;min-width:0}.detach{display:grid;place-items:center;flex:none;width:32px;min-height:36px;border:0;border-radius:8px;background:transparent;color:#aaa;padding:4px}.detach:hover{background:#161616;color:#fff}.detach svg{width:14px;height:14px}
|
|
41
|
+
.devices{display:flex;gap:3px;padding:4px;background:#111;border-radius:11px;margin:6px 4px}.device-choice{position:relative;display:grid;place-items:center;flex:1;min-width:0;min-height:34px;padding:6px 0;border:0;border-radius:8px;background:transparent;color:#aaa}.device-choice:hover{background:#1c1c1c;color:#fff}.device-choice[aria-pressed=true]{background:#222;color:#fff}.devices .detach{width:32px;min-height:34px}.device-choice::after{content:attr(data-tooltip);position:absolute;bottom:calc(100% + 8px);left:0;background:#1b1b1b;color:#eee;padding:6px 9px;border-radius:7px;font-size:11px;white-space:nowrap;box-shadow:0 3px 14px #0005;opacity:0;visibility:hidden;pointer-events:none;z-index:4}.device-choice:hover::after,.device-choice:focus-visible::after{opacity:1;visibility:visible}
|
|
42
|
+
@media(prefers-reduced-motion:no-preference){.trigger{transition:background-color 150ms,border-color 150ms,box-shadow 150ms,transform 150ms,scale 180ms}.trigger:active{transform:scale(.96)}.ring circle{transition:stroke 160ms ease-out,stroke-dasharray 160ms ease-out}.corner-hint{transition:background-color 150ms,border-color 150ms,scale 180ms}.bubble-mask{transition:clip-path 260ms cubic-bezier(.3,0,.3,1)}.bubble-mask[data-open=true]{transition-duration:360ms;transition-timing-function:cubic-bezier(.16,1,.3,1)}.panel{transition:opacity 110ms ease-out,transform 230ms cubic-bezier(.16,1,.3,1)}.panel:not([data-open=true]){transform:translateY(var(--panel-from)) scale(.97)}.panel[data-open=true]{transition:opacity 120ms ease-out 60ms,transform 180ms cubic-bezier(.16,1,.3,1) 60ms}:host([data-position^=top]) .bubble-mask{transition-duration:180ms}:host([data-position^=top]) .bubble-mask[data-open=true]{transition-duration:220ms}:host([data-position^=top]) .panel{transition:opacity 90ms ease-out,transform 180ms cubic-bezier(.16,1,.3,1)}:host([data-position^=top]) .panel[data-open=true]{transition:opacity 90ms ease-out,transform 140ms cubic-bezier(.16,1,.3,1)}.trigger[data-busy=true] .ring{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}}
|
|
43
|
+
@media(pointer:coarse){.action,.device-choice,.devices .detach{min-height:44px}.devices .detach{width:44px}}
|
|
44
|
+
@media(forced-colors:active){.trigger,.action-issues{border-color:ButtonText}.bubble path{fill:Canvas;stroke:ButtonText}.trigger[data-busy=true] .ring circle,.trigger[data-issue-severity=error] .ring circle,.trigger[data-issue-severity=critical] .ring circle{stroke:CanvasText}.issue-motion{opacity:1}.issue-motion-path,.issue-motion-ring,.issue-motion-streak{stroke:CanvasText}button:focus-visible{outline:2px solid Highlight;outline-offset:-3px}}
|
|
45
|
+
`;
|
|
46
|
+
const trigger = document.createElement('button');
|
|
47
|
+
trigger.type = 'button'; trigger.className = 'trigger';
|
|
48
|
+
trigger.setAttribute('aria-label', 'Open PRNext developer tools');
|
|
49
|
+
trigger.setAttribute('aria-haspopup', 'dialog');
|
|
50
|
+
trigger.setAttribute('aria-expanded', 'false'); trigger.setAttribute('aria-controls', 'tools');
|
|
51
|
+
const mark = document.createElement('img'); mark.src = logo; mark.alt = ''; mark.draggable = false;
|
|
52
|
+
const ring = document.createElement('span'); ring.className = 'ring'; ring.setAttribute('aria-hidden', 'true');
|
|
53
|
+
const ringSvg=document.createElementNS('http://www.w3.org/2000/svg','svg');ringSvg.setAttribute('viewBox','0 0 36 36');
|
|
54
|
+
const ringCircle=document.createElementNS('http://www.w3.org/2000/svg','circle');
|
|
55
|
+
for(const[name,value]of Object.entries({cx:'18',cy:'18',r:'17.25',pathLength:'100'}))ringCircle.setAttribute(name,value);
|
|
56
|
+
ringSvg.append(ringCircle);ring.append(ringSvg);
|
|
57
|
+
trigger.append(mark, ring);
|
|
58
|
+
const panel = document.createElement('section');
|
|
59
|
+
panel.id = 'tools'; panel.className = 'panel'; panel.hidden = true; panel.inert = true; panel.tabIndex = -1;
|
|
60
|
+
panel.setAttribute('role', 'dialog'); panel.setAttribute('aria-label', 'PRNext developer tools');
|
|
61
|
+
const bubble=document.createElement('div');bubble.className='bubble';bubble.hidden=true;bubble.setAttribute('aria-hidden','true');
|
|
62
|
+
const mask=document.createElement('div');mask.className='bubble-mask';
|
|
63
|
+
const surface=document.createElementNS('http://www.w3.org/2000/svg','svg');surface.setAttribute('preserveAspectRatio','none');
|
|
64
|
+
const shape=document.createElementNS('http://www.w3.org/2000/svg','path');surface.append(shape);mask.append(surface);bubble.append(mask);
|
|
65
|
+
function measureBubble(){
|
|
66
|
+
if(panel.hidden)return;
|
|
67
|
+
const width=panel.offsetWidth,height=panel.offsetHeight;
|
|
68
|
+
bubble.style.width=width+'px';bubble.style.height=(height+36)+'px';bubble.style.setProperty('--bubble-radius',Math.ceil(Math.hypot(width-22,height+14)+2)+'px');surface.setAttribute('viewBox',`0 0 ${width} ${height+36}`);
|
|
69
|
+
// One contour: rounded menu, curved shoulder, and the logo's circular foot.
|
|
70
|
+
shape.setAttribute('d',`M20.5 .5H${width-20.5}Q${width-.5} .5 ${width-.5} 20.5V${height-20}Q${width-.5} ${height} ${width-20.5} ${height}H64Q44 ${height} 44 ${height+14}A21.75 21.75 0 0 1 .5 ${height+14}V20.5Q.5 .5 20.5 .5Z`);
|
|
71
|
+
issueMotion?.refresh();
|
|
72
|
+
}
|
|
73
|
+
// Only constant icon markup goes through innerHTML. Labels use textContent.
|
|
74
|
+
const icon = paths => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
|
|
75
|
+
function action(label, drawing, handler) {
|
|
76
|
+
const button = document.createElement('button'); button.type = 'button'; button.className = 'action';
|
|
77
|
+
button.innerHTML = icon(drawing); const text = document.createElement('span'); text.textContent = label;
|
|
78
|
+
button.append(text); button.onclick = handler; panel.append(button); return button;
|
|
79
|
+
}
|
|
80
|
+
const issues = action('View issues', '<circle cx="12" cy="12" r="8.5"/><path d="M12 7.5v5m0 3.5h.01"/>', () => { close(false); showIssues(); });
|
|
81
|
+
issues.classList.add('action-issues');
|
|
82
|
+
const issueCount = document.createElement('span'); issueCount.className = 'hint'; issues.append(issueCount);
|
|
83
|
+
action('Reload page', '<path d="M20 7v5h-5M4 17v-5h5"/><path d="M6 7a7 7 0 0 1 12-1l2 6M4 12l2 6a7 7 0 0 0 12-1"/>', () => location.reload());
|
|
84
|
+
let workbench;
|
|
85
|
+
const toolError=error=>{feedback.textContent='Could not open development tool. Try again.';console.error(error);};
|
|
86
|
+
const getWorkbench = () => workbench ||= createLazyDevTool(async()=>{
|
|
87
|
+
const {createDevWorkbench}=await loadDevTool('workbench');
|
|
88
|
+
return createDevWorkbench({shadow,capture,tools,restoreFocus:()=>trigger.focus()});
|
|
89
|
+
},{onError:toolError,reserve(method,args){
|
|
90
|
+
if(!['popConsole','popPreview'].includes(method))return;
|
|
91
|
+
try{return window.open('about:blank','_blank',`popup=yes,width=${method==='popConsole'?860:args[0]==='tablet'?1000:560},height=${method==='popConsole'?520:960},resizable=yes,scrollbars=yes`);}catch{return null;}
|
|
92
|
+
}});
|
|
93
|
+
function detachButton(label,handler) {
|
|
94
|
+
const button=document.createElement('button');button.type='button';button.className='detach';button.innerHTML=popoutIcon;
|
|
95
|
+
button.setAttribute('aria-label',label);button.title=label;button.onclick=handler;return button;
|
|
96
|
+
}
|
|
97
|
+
const consoleAction=action('Client console', '<path d="m5 7 5 5-5 5m8 0h6"/>', () => { close(false); getWorkbench().showConsole(); });
|
|
98
|
+
const consoleRow=document.createElement('div');consoleRow.className='action-split';consoleAction.replaceWith(consoleRow);
|
|
99
|
+
consoleRow.append(consoleAction,detachButton('Open console in new window',()=>{close(false);getWorkbench().popConsole();}));
|
|
100
|
+
let inspector;
|
|
101
|
+
const getInspector = () => inspector ||= createLazyDevTool(async()=>{
|
|
102
|
+
const {createDevToolsPanel}=await loadDevTool('inspector');
|
|
103
|
+
return createDevToolsPanel({shadow,tools,restoreFocus:()=>trigger.focus()});
|
|
104
|
+
},{onError:toolError});
|
|
105
|
+
if (tools) {
|
|
106
|
+
action('Inspect','<path d="M3 12h4l3-8 4 16 3-8h4"/>',()=>{close(false);getInspector().open('requests');});
|
|
107
|
+
action('Select element for AI','<path d="m4 3 7 17 2-7 7-2L4 3Z"/>',()=>{close(false);getInspector().pick();});
|
|
108
|
+
}
|
|
109
|
+
const devices = document.createElement('div'); devices.className = 'devices'; devices.setAttribute('role','group'); devices.setAttribute('aria-label','Preview device');
|
|
110
|
+
let selectedDevice='desktop',previewSelected=false;
|
|
111
|
+
const deviceChoices=new Map();
|
|
112
|
+
const currentDevice=()=>previewSelected?workbench?.previewState().device||selectedDevice:selectedDevice;
|
|
113
|
+
function updateDevices(){
|
|
114
|
+
const current=currentDevice();
|
|
115
|
+
for(const [value,button] of deviceChoices)button.setAttribute('aria-pressed',String(value===current));
|
|
116
|
+
devicePopout.title=`Open ${current} in new window`;
|
|
117
|
+
}
|
|
118
|
+
for (const [value,label,width] of [
|
|
119
|
+
['desktop','Desktop','Responsive'],['phone','Phone','390px'],['tablet','Tablet','768px'],
|
|
120
|
+
]) {
|
|
121
|
+
const button = document.createElement('button'); button.type = 'button'; button.className='device-choice'; button.innerHTML = deviceIcons[value];
|
|
122
|
+
button.setAttribute('aria-label',label);button.setAttribute('aria-description',width);button.dataset.tooltip=`${label} · ${width}`;
|
|
123
|
+
button.onclick = () => { selectedDevice=value;previewSelected=true;close(false);getWorkbench().openPreview(value);updateDevices(); };
|
|
124
|
+
deviceChoices.set(value,button);devices.append(button);
|
|
125
|
+
}
|
|
126
|
+
const devicePopout=detachButton('Open device preview in new window',()=>{const value=currentDevice();close(false);getWorkbench().popPreview(value);});
|
|
127
|
+
devices.append(devicePopout);updateDevices();
|
|
128
|
+
panel.append(devices);
|
|
129
|
+
const divider = document.createElement('div'); divider.className = 'divider'; panel.append(divider);
|
|
130
|
+
action('Hide until reload', '<path d="m3 3 18 18M10.6 10.6a2 2 0 0 0 2.8 2.8M9.5 5.2A10 10 0 0 1 12 5c6 0 10 7 10 7a17 17 0 0 1-3 3.5M6.3 6.3A22 22 0 0 0 2 12s4 7 10 7a10 10 0 0 0 4.5-1.1"/>', () => {
|
|
131
|
+
close(false);issueMotion.closeImmediately(); workbench?.hide(); inspector?.hide(); host.hidden = true; restorePageFocus();
|
|
132
|
+
});
|
|
133
|
+
const live = document.createElement('span'); live.className = 'sr-only'; live.setAttribute('role', 'status');
|
|
134
|
+
const feedback = document.createElement('span'); feedback.className = 'sr-only'; feedback.setAttribute('role', 'status');
|
|
135
|
+
shadow.append(style,bubble,panel,trigger,live,feedback); document.body.append(host);
|
|
136
|
+
const issueMotion = createDevIssueMotion({ host, shadow, ringSvg, issues, panel });
|
|
137
|
+
const geometry=new ResizeObserver(measureBubble);geometry.observe(panel);
|
|
138
|
+
let opened = false, timer, previousSummary, previousPageFocus = document.activeElement;
|
|
139
|
+
function restorePageFocus() {
|
|
140
|
+
if (previousPageFocus?.isConnected && previousPageFocus !== document.body && previousPageFocus !== host) previousPageFocus.focus();
|
|
141
|
+
else { const body = document.body, old = body.getAttribute('tabindex'); body.tabIndex = -1; body.focus(); if (old === null) body.removeAttribute('tabindex'); else body.setAttribute('tabindex', old); }
|
|
142
|
+
}
|
|
143
|
+
function close(restore) {
|
|
144
|
+
opened = false; clearTimeout(timer); trigger.setAttribute('aria-expanded', 'false');
|
|
145
|
+
issueMotion.update({severity:trigger.dataset.issueSeverity,open:false});
|
|
146
|
+
panel.dataset.open = 'false';mask.dataset.open='false';panel.inert = true; panel.setAttribute('aria-hidden', 'true');
|
|
147
|
+
if (restore) trigger.focus();
|
|
148
|
+
timer = setTimeout(() => { if (!opened) {panel.hidden=true;bubble.hidden=true;} }, matchMedia('(prefers-reduced-motion: reduce)').matches ? 0 : 280);
|
|
149
|
+
}
|
|
150
|
+
function update() {
|
|
151
|
+
// App Router's global error boundary can replace the document body.
|
|
152
|
+
if (!host.isConnected && document.body) document.body.append(host);
|
|
153
|
+
const info = snapshot();
|
|
154
|
+
const count = info.issues || 0;
|
|
155
|
+
const severity=count?(info.criticalIssues>0?'critical':'error'):'none';
|
|
156
|
+
const status = count ? 'error' : info.connection !== 'connected' ? 'reconnecting' : info.building ? 'building' : 'ready';
|
|
157
|
+
const label = count ? `${count} ${count === 1 ? 'issue' : 'issues'}` : status === 'building' ? 'Compiling…' : status === 'reconnecting' ? 'Connecting…' : 'Ready';
|
|
158
|
+
if (count === 0 && shadow.activeElement === issues) panel.focus();
|
|
159
|
+
issues.hidden = count === 0; issueCount.textContent = String(count);
|
|
160
|
+
trigger.dataset.busy = String(info.building && !count); trigger.title = `PRNext · ${label}`;
|
|
161
|
+
trigger.dataset.issueSeverity=severity;
|
|
162
|
+
issueMotion.update({severity,open:opened&&!panel.hidden});
|
|
163
|
+
if(count)trigger.setAttribute('aria-description',severity==='critical'?'Critical error. Open issues to view details.':'Non-blocking issue. Open issues to view details.');else trigger.removeAttribute('aria-description');
|
|
164
|
+
trigger.setAttribute('aria-label', 'Open PRNext developer tools' + (count ? ` (${label})` : ''));
|
|
165
|
+
const summary=count?`${label}. ${severity==='critical'?'Critical error':'Non-blocking issue'}`:label;
|
|
166
|
+
if (previousSummary !== summary) { live.textContent = `PRNext: ${summary}`; previousSummary = summary; }
|
|
167
|
+
}
|
|
168
|
+
trigger.onclick = () => {
|
|
169
|
+
if (opened) { close(true); return; }
|
|
170
|
+
opened = true; clearTimeout(timer); feedback.textContent = '';
|
|
171
|
+
update();updateDevices(); panel.hidden = false;bubble.hidden=false;panel.inert = false;measureBubble();
|
|
172
|
+
panel.removeAttribute('aria-hidden'); trigger.setAttribute('aria-expanded', 'true');
|
|
173
|
+
mask.getBoundingClientRect();panel.dataset.open='true';mask.dataset.open='true';panel.focus();
|
|
174
|
+
issueMotion.update({severity:trigger.dataset.issueSeverity,open:true});
|
|
175
|
+
};
|
|
176
|
+
const placement = createDevPosition({ host, trigger, position, announce: text => { feedback.textContent = text; }, onDrag() {
|
|
177
|
+
close(false);issueMotion.closeImmediately(); panel.hidden = true; bubble.hidden = true;
|
|
178
|
+
} });
|
|
179
|
+
const controller = new AbortController(), options = { capture: true, signal: controller.signal };
|
|
180
|
+
document.addEventListener('pointerdown', event => { if (opened && !event.composedPath().some(node => node === panel || node === trigger)) close(false); }, options);
|
|
181
|
+
document.addEventListener('focusin', event => {
|
|
182
|
+
if (!event.composedPath().includes(host)) { previousPageFocus = event.target; if (opened) close(false); }
|
|
183
|
+
}, options);
|
|
184
|
+
shadow.addEventListener('focusin', event => { if (opened && !panel.contains(event.target) && event.target !== trigger) close(false); }, {signal:controller.signal});
|
|
185
|
+
shadow.addEventListener('keydown', event => { if (event.key === 'Escape' && opened) { event.preventDefault(); event.stopPropagation(); close(true); } });
|
|
186
|
+
globalThis.addEventListener('popstate', update, { signal: controller.signal });
|
|
187
|
+
update();
|
|
188
|
+
const contextListener = event => { close(false); getInspector().open('ai', event.detail); };
|
|
189
|
+
if (tools) globalThis.addEventListener('prnext:devtools:context', contextListener, {signal:controller.signal});
|
|
190
|
+
return { update, dispose() { clearTimeout(timer);issueMotion.dispose();placement.dispose();geometry.disconnect();controller.abort();workbench?.dispose();inspector?.dispose();host.remove(); } };
|
|
191
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// A bounded, reversible transfer between the existing issue ring and menu border.
|
|
2
|
+
const NS = 'http://www.w3.org/2000/svg';
|
|
3
|
+
const circleStart = -113.4 * Math.PI / 180, arcControl = 4 / 3 * Math.tan(Math.PI / 16), cornerControl = 4 / 3 * Math.tan(Math.PI / 8);
|
|
4
|
+
const lerp = (a, b, t) => a + (b - a) * t;
|
|
5
|
+
const number = value => Number(value.toFixed(4));
|
|
6
|
+
let revealSequence = 0;
|
|
7
|
+
|
|
8
|
+
function circle(cx, cy, radius) {
|
|
9
|
+
const result = [cx + radius * Math.cos(circleStart), cy + radius * Math.sin(circleStart)];
|
|
10
|
+
for (let index = 0; index < 8; index++) {
|
|
11
|
+
const a = circleStart + index * Math.PI / 4, b = a + Math.PI / 4;
|
|
12
|
+
result.push(cx + radius * (Math.cos(a) - arcControl * Math.sin(a)), cy + radius * (Math.sin(a) + arcControl * Math.cos(a)),
|
|
13
|
+
cx + radius * (Math.cos(b) + arcControl * Math.sin(b)), cy + radius * (Math.sin(b) - arcControl * Math.cos(b)), cx + radius * Math.cos(b), cy + radius * Math.sin(b));
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
function rectangle(x, y, width, height, radius) {
|
|
18
|
+
const r = Math.max(0, Math.min(radius, width / 2, height / 2)), k = r * cornerControl, right = x + width, bottom = y + height;
|
|
19
|
+
const points = [x + r, y];
|
|
20
|
+
const line = (ax, ay, bx, by) => points.push(lerp(ax, bx, 1 / 3), lerp(ay, by, 1 / 3), lerp(ax, bx, 2 / 3), lerp(ay, by, 2 / 3), bx, by);
|
|
21
|
+
line(x + r, y, right - r, y); points.push(right - r + k, y, right, y + r - k, right, y + r);
|
|
22
|
+
line(right, y + r, right, bottom - r); points.push(right, bottom - r + k, right - r + k, bottom, right - r, bottom);
|
|
23
|
+
line(right - r, bottom, x + r, bottom); points.push(x + r - k, bottom, x, bottom - r + k, x, bottom - r);
|
|
24
|
+
line(x, bottom - r, x, y + r); points.push(x, y + r - k, x + r - k, y, x + r, y);
|
|
25
|
+
return points;
|
|
26
|
+
}
|
|
27
|
+
function path(points) {
|
|
28
|
+
let result = `M${number(points[0])} ${number(points[1])}`;
|
|
29
|
+
for (let index = 2; index < points.length; index += 6) result += 'C' + points.slice(index, index + 6).map(number).join(' ');
|
|
30
|
+
return result + 'Z';
|
|
31
|
+
}
|
|
32
|
+
const clamp = value => Math.max(0, Math.min(1, value));
|
|
33
|
+
const easing = t => t * t * (3 - 2 * t);
|
|
34
|
+
// One continuous ease-in-out: gentle endpoints, a quicker middle, no overshoot.
|
|
35
|
+
const transferEasing = t => t * t * t * (t * (6 * t - 15) + 10);
|
|
36
|
+
const gatherEnd = .12, travelStart = .08, arrival = .82;
|
|
37
|
+
const leftDepartureOffset = 100 - (Math.PI - circleStart) / (2 * Math.PI) * 100;
|
|
38
|
+
const rightDepartureOffset = circleStart / (2 * Math.PI) * 100;
|
|
39
|
+
|
|
40
|
+
function entryOnSide(points, right) {
|
|
41
|
+
const segments = Array.from({ length: 8 }, (_, index) => points.slice(2 + index * 6, 8 + index * 6));
|
|
42
|
+
const next = right ? 3 : 6;
|
|
43
|
+
return [...segments[next - 1].slice(4), ...segments.slice(next).flat(), ...segments.slice(0, next).flat()];
|
|
44
|
+
}
|
|
45
|
+
function roundedRoute(points, maximumRadius = 4) {
|
|
46
|
+
let result = `M${points[0].map(number).join(' ')}`;
|
|
47
|
+
for (let index = 1; index < points.length - 1; index++) {
|
|
48
|
+
const [x, y] = points[index], previous = points[index - 1], next = points[index + 1];
|
|
49
|
+
const before = Math.hypot(x - previous[0], y - previous[1]), after = Math.hypot(next[0] - x, next[1] - y);
|
|
50
|
+
const radius = Math.min(maximumRadius, before / 2, after / 2);
|
|
51
|
+
if (!radius) { result += `L${number(x)} ${number(y)}`; continue; }
|
|
52
|
+
const a = [x - (x - previous[0]) / before * radius, y - (y - previous[1]) / before * radius];
|
|
53
|
+
const b = [x + (next[0] - x) / after * radius, y + (next[1] - y) / after * radius];
|
|
54
|
+
result += `L${a.map(number).join(' ')}Q${number(x)} ${number(y)} ${b.map(number).join(' ')}`;
|
|
55
|
+
}
|
|
56
|
+
return result + `L${points.at(-1).map(number).join(' ')}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function createDevIssueMotion({ host, shadow, ringSvg, issues, panel }) {
|
|
60
|
+
const view = host.ownerDocument.defaultView, noop = { update() {}, refresh() {}, closeImmediately() {}, dispose() {} };
|
|
61
|
+
if (!view.requestAnimationFrame || !view.matchMedia || !host.ownerDocument.createElementNS) return noop;
|
|
62
|
+
const svg = host.ownerDocument.createElementNS(NS, 'svg'), outline = host.ownerDocument.createElementNS(NS, 'path');
|
|
63
|
+
const ring = host.ownerDocument.createElementNS(NS, 'path'), streak = host.ownerDocument.createElementNS(NS, 'path');
|
|
64
|
+
const defs = host.ownerDocument.createElementNS(NS, 'defs'), clip = host.ownerDocument.createElementNS(NS, 'clipPath'), reveal = host.ownerDocument.createElementNS(NS, 'rect');
|
|
65
|
+
clip.id = `prnext-issue-border-reveal-${++revealSequence}`; clip.setAttribute('clipPathUnits', 'userSpaceOnUse');
|
|
66
|
+
reveal.classList.add('issue-motion-reveal'); clip.append(reveal); defs.append(clip);
|
|
67
|
+
svg.classList.add('issue-motion'); svg.setAttribute('viewBox', '0 0 1 1'); svg.setAttribute('aria-hidden', 'true');
|
|
68
|
+
outline.classList.add('issue-motion-path'); ring.classList.add('issue-motion-ring'); streak.classList.add('issue-motion-streak');
|
|
69
|
+
for (const stroke of [ring, outline]) { stroke.setAttribute('pathLength', '100'); stroke.setAttribute('fill', 'none'); stroke.setAttribute('stroke-linecap', 'round'); }
|
|
70
|
+
outline.setAttribute('stroke-width', '1'); streak.setAttribute('fill', 'none'); streak.setAttribute('stroke-width', '1.5'); streak.setAttribute('stroke-linecap', 'round');
|
|
71
|
+
outline.setAttribute('clip-path', `url(#${clip.id})`);
|
|
72
|
+
svg.append(defs, ring, outline, streak); shadow.append(svg);
|
|
73
|
+
const controller = new view.AbortController(), events = { signal: controller.signal }, reduced = view.matchMedia('(prefers-reduced-motion: reduce)');
|
|
74
|
+
let severity = 'none', opened = false, disposed = false, frame = 0, animation, current, source, target;
|
|
75
|
+
function measureSource(rest = false) {
|
|
76
|
+
const origin = host.getBoundingClientRect(), rect = ringSvg.getBoundingClientRect(), width = rest ? ringSvg.clientWidth : rect.width;
|
|
77
|
+
if (!width) return;
|
|
78
|
+
return { x: rect.left - origin.left + rect.width / 2, y: rect.top - origin.top + rect.height / 2,
|
|
79
|
+
radius: 17.25 * width / 36, width: 1.5 * width / 36 };
|
|
80
|
+
}
|
|
81
|
+
function measureTarget() {
|
|
82
|
+
if (issues.hidden || panel.hidden) return;
|
|
83
|
+
const origin = host.getBoundingClientRect(), rect = issues.getBoundingClientRect(), parent = panel.getBoundingClientRect(), style = view.getComputedStyle(panel);
|
|
84
|
+
const values = /^matrix\(([^)]+)\)/.exec(style.transform)?.[1].split(',').map(Number), sx = values?.[0] || 1, sy = values?.[3] || 1;
|
|
85
|
+
const [ox, oy] = style.transformOrigin.split(' ').map(parseFloat);
|
|
86
|
+
// Undo the bubble content's temporary scale/translation, keeping its final border as target.
|
|
87
|
+
const panelX = parent.left - origin.left - (values?.[4] || 0) - (ox || 0) * (1 - sx);
|
|
88
|
+
const panelY = parent.top - origin.top - (values?.[5] || 0) - (oy || 0) * (1 - sy);
|
|
89
|
+
const x = panelX + (rect.left - parent.left) / sx + .5, y = panelY + (rect.top - parent.top) / sy + .5;
|
|
90
|
+
const width = rect.width / sx - 1, height = rect.height / sy - 1;
|
|
91
|
+
const right = host.dataset.position?.endsWith('right') || false;
|
|
92
|
+
const points = entryOnSide(rectangle(x, y, width, height, Math.max(0, parseFloat(view.getComputedStyle(issues).borderTopLeftRadius) - .5)), right);
|
|
93
|
+
const below = source.y > panelY + panel.offsetHeight / 2;
|
|
94
|
+
const railX = right ? panelX + parent.width / sx - parseFloat(style.paddingRight) / 2 : panelX + parseFloat(style.paddingLeft) / 2;
|
|
95
|
+
const marginY = below ? panelY + panel.offsetHeight - parseFloat(style.paddingBottom) / 2 : panelY + parseFloat(style.paddingTop) / 2;
|
|
96
|
+
// Leave the ring on the menu's outside edge, tangent to its circumference. The
|
|
97
|
+
// segment must never start at the center or cut across the logo artwork.
|
|
98
|
+
const departureX = source.x + (right ? source.radius : -source.radius);
|
|
99
|
+
const route = [[departureX, source.y], [departureX, marginY], [railX, marginY], [railX, points[1]], [points[0], points[1]]];
|
|
100
|
+
return { points, route, right, routeRadius: below ? 6 : 4, reveal: { x: x - 1, y: y - 1, width: width + 2, height: height + 2 } };
|
|
101
|
+
}
|
|
102
|
+
function measure(destination = opened) {
|
|
103
|
+
source = measureSource(true) || source;
|
|
104
|
+
if (destination && source) {
|
|
105
|
+
target = measureTarget() || target;
|
|
106
|
+
if (target) {
|
|
107
|
+
outline.setAttribute('d', path(target.points)); streak.setAttribute('d', roundedRoute(target.route, target.routeRadius));
|
|
108
|
+
for (const key of ['x', 'y', 'height']) reveal.setAttribute(key, String(number(target.reveal[key])));
|
|
109
|
+
// Geometry is measured once per placement, never in the animation loop.
|
|
110
|
+
target.routeLength = Math.max(14, streak.getTotalLength());
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function sample(progress) {
|
|
115
|
+
// Flight and border tracing share one eased clock. Re-easing each of these
|
|
116
|
+
// phases made the short flight stop and accelerate at every handoff.
|
|
117
|
+
const gather = easing(clamp(progress / gatherEnd)), travel = clamp((progress - travelStart) / (arrival - travelStart));
|
|
118
|
+
const fill = clamp((progress - arrival) / (1 - arrival)), formation = easing(clamp((gather - .45) / .55));
|
|
119
|
+
const length = (14 + 24 * travel * (1 - travel)) * formation, arcLength = severity === 'critical' ? 50 : 13;
|
|
120
|
+
return { progress, ringX: source.x, ringY: source.y, ringRadius: source.radius, ringWidth: source.width,
|
|
121
|
+
ringOpacity: 1 - easing(clamp((gather - .55) / .45)), ringLength: arcLength * (1 - gather),
|
|
122
|
+
ringGap: lerp(50 - arcLength, 100, gather), ringOffset: (target?.right ? rightDepartureOffset : leftDepartureOffset) * gather,
|
|
123
|
+
streakOffset: Math.max(0, ((target?.routeLength || 14) - 14) * travel + (14 * formation - length)),
|
|
124
|
+
streakLength: length, streakWidth: lerp(source.width, 1, travel),
|
|
125
|
+
streakOpacity: formation * (1 - easing(clamp(fill / .18))), fill };
|
|
126
|
+
}
|
|
127
|
+
function paint(pose) {
|
|
128
|
+
current = pose;
|
|
129
|
+
const length = Math.max(0, Math.min(50, pose.ringLength)), gap = Math.max(0, Math.min(100 - 2 * length, pose.ringGap)), fill = clamp(pose.fill);
|
|
130
|
+
ring.setAttribute('d', path(circle(pose.ringX, pose.ringY, Math.max(0, pose.ringRadius))));
|
|
131
|
+
ring.setAttribute('stroke-dasharray', [length, gap, length, Math.max(0, 100 - 2 * length - gap)].map(number).join(' '));
|
|
132
|
+
ring.setAttribute('stroke-dashoffset', String(number(pose.ringOffset)));
|
|
133
|
+
ring.setAttribute('stroke-width', String(number(pose.ringWidth))); ring.setAttribute('opacity', String(number(clamp(pose.ringOpacity))));
|
|
134
|
+
// Reveal from the arriving thread's side, keeping the contour fixed.
|
|
135
|
+
if (target) reveal.setAttribute('x', String(number(target.reveal.x + (target.right ? target.reveal.width * (1 - fill) : 0))));
|
|
136
|
+
reveal.setAttribute('width', String(number((target?.reveal.width || 0) * fill)));
|
|
137
|
+
outline.setAttribute('opacity', fill > 0 ? '1' : '0');
|
|
138
|
+
streak.setAttribute('stroke-dasharray', `${number(Math.max(0, pose.streakLength))} ${number((target?.routeLength || 14) + 14)}`);
|
|
139
|
+
streak.setAttribute('stroke-width', String(number(pose.streakWidth)));
|
|
140
|
+
streak.setAttribute('stroke-dashoffset', String(number(-pose.streakOffset))); streak.setAttribute('opacity', String(number(clamp(pose.streakOpacity))));
|
|
141
|
+
svg.dataset.progress = String(number(pose.progress)); svg.dataset.fill = String(number(fill));
|
|
142
|
+
svg.dataset.stage = pose.progress <= 0 ? 'ring' : pose.progress >= 1 ? 'border' : pose.progress < gatherEnd ? 'gather' : pose.progress < arrival ? 'travel' : 'fill';
|
|
143
|
+
}
|
|
144
|
+
function cancel() { if (frame) view.cancelAnimationFrame(frame); frame = 0; animation = undefined; }
|
|
145
|
+
function phase(name, running = false) { svg.dataset.phase = name; svg.dataset.running = String(running); host.dataset.issueTransfer = running ? 'moving' : name; }
|
|
146
|
+
function finish() {
|
|
147
|
+
cancel(); if (source) paint(sample(opened ? 1 : 0)); phase(opened ? 'open' : 'closed');
|
|
148
|
+
}
|
|
149
|
+
function disable() {
|
|
150
|
+
cancel(); severity = 'none'; opened = false; current = source = target = undefined;
|
|
151
|
+
svg.dataset.phase = 'disabled'; svg.dataset.running = 'false'; svg.dataset.progress = '0'; svg.dataset.fill = '0'; svg.dataset.stage = 'ring';
|
|
152
|
+
ring.setAttribute('opacity', '0'); ring.removeAttribute('d'); outline.setAttribute('opacity', '0'); outline.removeAttribute('d'); streak.setAttribute('opacity', '0'); streak.removeAttribute('d'); delete host.dataset.issueTransfer;
|
|
153
|
+
}
|
|
154
|
+
function tick(now) {
|
|
155
|
+
frame = 0; if (!animation || disposed) return;
|
|
156
|
+
const amount = clamp((now - animation.start) / animation.duration), t = transferEasing(amount), from = animation.from;
|
|
157
|
+
const progress = lerp(animation.origin, animation.to, t), pose = sample(progress);
|
|
158
|
+
const correction = 1 - clamp((t - animation.correctionAt) / Math.max(.000001, 1 - animation.correctionAt));
|
|
159
|
+
// Correct from the last rendered pose, not from a restarted phase. This also
|
|
160
|
+
// preserves the short traveling stroke when the severity changes mid-transfer.
|
|
161
|
+
for (const key of Object.keys(pose)) if (key !== 'progress') pose[key] += (from[key] - animation.baseline[key]) * correction;
|
|
162
|
+
paint(pose);
|
|
163
|
+
if (amount >= 1) finish(); else frame = view.requestAnimationFrame(tick);
|
|
164
|
+
}
|
|
165
|
+
function start() {
|
|
166
|
+
const interrupted = Boolean(animation); cancel();
|
|
167
|
+
source = measureSource(true) || source;
|
|
168
|
+
// Keep both endpoints fixed during reversals. Only the short stroke travels;
|
|
169
|
+
// the destination contour never morphs or follows it through the menu.
|
|
170
|
+
if (opened && (!interrupted || !target)) measure();
|
|
171
|
+
if (!source) return;
|
|
172
|
+
if (!current || reduced.matches) { finish(); return; }
|
|
173
|
+
// The nearby top-corner target uses a quicker clock, synchronized with its
|
|
174
|
+
// 140ms panel entrance. Keep the longer bottom-corner transfer unchanged.
|
|
175
|
+
const duration = host.dataset.position?.startsWith('top') ? (opened ? 240 : 200)
|
|
176
|
+
: opened ? Math.min(480, Math.max(360, (target?.routeLength || 300) * 1.6)) : 360;
|
|
177
|
+
animation = { from: current, baseline: sample(current.progress), origin: current.progress, correctionAt: 0,
|
|
178
|
+
to: opened ? 1 : 0, duration, start: view.performance.now() };
|
|
179
|
+
phase(opened ? 'opening' : 'closing', true); frame = view.requestAnimationFrame(tick);
|
|
180
|
+
}
|
|
181
|
+
function refresh({ immediate = false } = {}) {
|
|
182
|
+
if (disposed || severity === 'none') return;
|
|
183
|
+
if (immediate || reduced.matches || !animation) { measure(); finish(); }
|
|
184
|
+
}
|
|
185
|
+
reduced.addEventListener('change', () => refresh({ immediate: true }), events);
|
|
186
|
+
view.addEventListener('resize', () => refresh({ immediate: true }), events);
|
|
187
|
+
view.addEventListener('blur', () => refresh({ immediate: true }), events);
|
|
188
|
+
view.visualViewport?.addEventListener('resize', () => refresh({ immediate: true }), events);
|
|
189
|
+
panel.addEventListener('scroll', () => refresh({ immediate: true }), events);
|
|
190
|
+
disable();
|
|
191
|
+
return {
|
|
192
|
+
update({ severity: next = 'none', open = false } = {}) {
|
|
193
|
+
if (disposed) return;
|
|
194
|
+
if (next === 'none') { disable(); return; }
|
|
195
|
+
const changed = opened !== open, first = severity === 'none', changedSeverity = severity !== next;
|
|
196
|
+
severity = next; opened = open;
|
|
197
|
+
if (first) { source = measureSource(); if (source) paint(sample(0)); }
|
|
198
|
+
if (!current) return;
|
|
199
|
+
if (!changed && !first && !changedSeverity) return;
|
|
200
|
+
if (!changed && !animation) { finish(); return; }
|
|
201
|
+
if (!changed && animation) {
|
|
202
|
+
// A severity-only update must not restart the travel/fill clock: the
|
|
203
|
+
// closing contour still has to disappear before the menu is hidden.
|
|
204
|
+
animation.from = current; animation.baseline = sample(current.progress);
|
|
205
|
+
animation.correctionAt = transferEasing(clamp((view.performance.now() - animation.start) / animation.duration));
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
start();
|
|
209
|
+
},
|
|
210
|
+
refresh,
|
|
211
|
+
closeImmediately() { if (disposed) return; opened = false; if (severity !== 'none') { measure(false); finish(); } else cancel(); },
|
|
212
|
+
dispose() { if (disposed) return; disposed = true; cancel(); controller.abort(); svg.remove(); delete host.dataset.issueTransfer; },
|
|
213
|
+
};
|
|
214
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Capture user activation synchronously for detached windows, even on a slow
|
|
2
|
+
// connection. Import failures/disposal close reservations instead of leaking tabs.
|
|
3
|
+
const loaderRegistry = Symbol.for('prnext.dev.tool.loaders');
|
|
4
|
+
|
|
5
|
+
// The indicator survives Fast Refresh, while hashed import URLs belong to a
|
|
6
|
+
// build generation. Publish the current factories on module evaluation so an
|
|
7
|
+
// older, unopened indicator never requests a retired generation's lazy chunk.
|
|
8
|
+
// Existing tool instances remain owned by createLazyDevTool and keep their DOM.
|
|
9
|
+
export function registerDevToolLoaders(loaders, target = globalThis) {
|
|
10
|
+
const registry = target[loaderRegistry] ||= Object.create(null);
|
|
11
|
+
Object.assign(registry, loaders);
|
|
12
|
+
return name => {
|
|
13
|
+
const load = registry[name];
|
|
14
|
+
if (typeof load !== 'function') throw Error('Development tool loader is unavailable.');
|
|
15
|
+
return load();
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createLazyDevTool(load,{onError=console.error,reserve}={}) {
|
|
20
|
+
let instance,pending,disposed=false,epoch=0;
|
|
21
|
+
const get=()=>pending||=(async()=>{const value=await load();if(disposed){value.dispose();throw Error('Development tool was closed');}return instance=value;})().catch(error=>{pending=undefined;throw error;});
|
|
22
|
+
return new Proxy({}, {get(_target,key){
|
|
23
|
+
if(key==='dispose')return()=>{disposed=true;instance?.dispose();};
|
|
24
|
+
if(key==='hide')return()=>{epoch++;instance?.hide();};
|
|
25
|
+
if(key==='previewState')return()=>instance?.previewState()||{};
|
|
26
|
+
return(...args)=>{
|
|
27
|
+
if(disposed)return;
|
|
28
|
+
if(instance)return instance[key](...args);
|
|
29
|
+
const popup=reserve?.(key,args);
|
|
30
|
+
const expected=epoch;
|
|
31
|
+
void get().then(tool=>{if(disposed||expected!==epoch){popup?.close();return;}return tool[key](...args,...(popup!==undefined?[popup]:[]));}).catch(error=>{popup?.close();if(!disposed)onError(error);});
|
|
32
|
+
};
|
|
33
|
+
}});
|
|
34
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
// PRNext brand asset from .github/assets/prnext-logo.svg; embedded for offline development.
|
|
2
|
+
export default "data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%22250%20250%20756%20756%22%20fill%3D%22none%22%3E%3Ctitle%3EPRNext%3C%2Ftitle%3E%3Crect%20x%3D%22250%22%20y%3D%22250%22%20width%3D%22756%22%20height%3D%22756%22%20rx%3D%22156%22%20fill%3D%22%2309070d%22%2F%3E%3Cimage%20href%3D%22data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAABOYAAATmCAMAAACyIl4YAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAwBQTFRFAAAA%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5%2F%2Fn5mcfEEAAAAQB0Uk5TAB8sGSAiHhoYISUnKS0vMC4qKCQmIysxHRsWCwQFE2ack5aXlZKZmp2em5iUkYyGgoGAf357dm1jVkc3Dgim%2F%2Fv39vX08%2FLx7uXZybahiGhKMhAGBxyj%2Fv38%2Bvjw7N%2FQz8i8rI5rQhei5NfHtIR4aVQ06ebarYdgQD44DODYw%2B3Mr3VQCvnnzad0WEY6D9KxbAIB6NO6rl5LoMSPAxXin9WDYg3ecRLKqm5wUuPcPah6XeHRPBFXu0RO1sJIpQk%2Fzlo7asXbZ6RP1HKNW7Jlwanvy3wzfXmFb3O5ZLXrTOqLObd33UMUq7BZvjU2ir9fwElTs5BFuEFVvYlRXGHGTaE6%2BPQAAFpWSURBVHic7N0FWJXXHwfw995L2wp6g4sdf2ubORUUC3sooM7uWUOdWLMDW1SurdgzZqDO7sKcTmfr7BYQG1EE%2FpfQOYM4573ved%2FD9%2FM8KgLvOb97Hd%2B9cUIlAABwTcW6AAAAy0LMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMQcAnEPMAQDnEHMAwDnEHABwDjEHAJxDzAEA5xBzAMA5xBwAcA4xBwCcQ8wBAOcQcwDAOcQcAHAOMUdBlST%2B47g4jfmjyAyqj97ROPXzLNE2b%2BLszR8%2FtxIcXjsIr23%2F%2FbI6%2FrdI%2B48%2B8fK%2F7b%2FLLJgP%2BXDM2w9fsBGEN%2FaJh5qPMX9bgijb999i%2Fnr8NyR6nfRpm38bfvNRpwlFZPi354yxCS%2Ftte1bwfpZ%2FIcZNELMq3%2B%2FN0t0fEvqxKLMDTu81sTFf2iXVIT5VcfYR8bFZXgZlSv%2BvYiLEKI11iqVbXwRDuGC4BiZWIzaXL%2F5XUl8oa8Tv57YZWIX71%2F0%2B08lHWVu0fymxL%2F2xLfz7fujPpT373vw75sWmVmwibTRaNQq1V0B0hvEHDFjUsjFJPyovtPEaQTVG8Hu45iLEhxihDjBWoj%2F8TcHQfR%2F3vCEdEj4YpKoT7swR0f8T%2B2X%2FpHi3h8Z9SFholWffkeizw%2BP%2B%2BTv1h91ndhYdPxRmsikz3xcmPkVJbTwvlVNpN1%2Fe4gTohxi4qyF14LaLrG%2BWEFll1hcfIuJDZi%2Fz%2FrfV2wXndRGQpfvv0P4wqfiX3d00mv%2F8HYmFRr%2FfcJ%2Fjv3gjZ2gjhHeqezf%2F4%2Fp%2Ff%2Bi%2Fiod8sUDgCeIOWJuRyvg3VMe1WcOR8XGvYn1VK1lXRpYCn5QifmoVI9Y1wBppxUS%2F7M3B9wDTa6P4m4O68rAUhBzxLwPe15kXQNQU2lUmpwZzSm3pV6IW%2FztVaQdfxBzxBBzfLEtrlK9dkg4r5v5bOBo1uWAiBBzxBBzfNHGX8c%2BMDwuo1KFxsyPZV0OiAgxRwz35rikf6ipFGOlWlLVRTVq6GDW1YAoEHPEfEIqI%2BZ45XGgSuJzifED%2FFjXAtQQc8QQc9xz%2BU6lGmst9FX1YF0JUEHMEcNFK%2Fc89jdQqabbOJyuizF1ioaYI%2Ba9vu5z1jWAxXlcylR8fkfVZecpmCWmWIg5YsaYYctZ1wCSMDxppFp0PX9tbUfWlQARxBwx4%2FB3iLn0QWulqnb71eU6vu9YVwJEEHPEjMPVi1jXANJxz7umseqvoFmtWBcCaYaYI4aYS3dsW0dmeHCs0ayDrAuBtEHMEUPMpTt6dWgze5WqB56wKwxijhhiLl1S14z6fZSqTWTK3wmygZgjhphLrzTd9mmWRGE9TuVAzBFDzKVX2kdWXTcfH%2BnFug5ILcQcMQwoScc83v7ZSaXyiU75O0EGEHPEMDw4fdP0Wv%2F46PPs91jXASlDzBFDzKVvWpU63%2BlHF16l%2FJ3AGmKOGGIu3VN75QmfPURVj3UdkALEHDHEHAiCe%2FiB8U1Xdsa0fllDzBHz3lwTK5SAoDdsb%2FA%2FzACTNcQcMcQcxNM%2B7urQb2Jt1mVAMhBzxLzXd8KWNxBP5bpiZrRnDOsy4GsQc8Swsxd8YOfnP6j995V7sq4DvggxRwwxBx%2BorKqV%2Ff3HWjihkyfEHDHsBQEfs%2FquzqFIHc7n5AgxRww7e8EnyhTvtERbjXUV8BnEHDHEHHzCI0T396SqrKuAzyDmiOGiFT6lDxvctun11azLgE8g5ojhEQR8gdGqXc2%2BebEFmKwg5ogh5uALdKGlts0ciCU3ZQUxRwwxB19k0NexafCUdRXwEcQcMcQcfIVz195Xi2H7L%2FlAzBFDzMFXaEu6d7z2lnUV8AFijhietMJXWY1sU3%2B6FsszyQRijhjGzcHXqUo96%2BA60Y91GZAAMUcMMQfJ0Kqts6zCfyDygJgjhpiDZGmK7PNYe4d1FSAg5ij4%2FLF%2FAOsaQNbcMrpXecO6CEDMUUDMQUps%2FHecrtKDdRWAmCPmduQgYg6SpbI2dqlcfw3rMtI9xBwxtxObRrKuAeTObsgvAS1vs64ivUPMEXM7WgHvHqRI53fHdwSm8jOFH1RiexoXw7sHKdJmr1LANYp1FekbflCJ7WnSZyvrGkAJinZUVX7Juoh0DTFHzH%2Fy%2FKmsawBF8Ki7f4ATRtCxg5gj5j8uBPubQGpoIwodfviAdRXpGGKOmH%2FAXsQcpI5JpVIVZ11E%2BoWYI%2BZvMs1iXQMohUfUj%2B52uG5lBDFHDDEHqafVZN365j7rKtIrxBwxxBykhXXdpXnWsS4inULMEUPMQZqo7SdVHodxwiwg5ogh5iBtdOXq1LjHuoh0CTFHDE9aIY3srPKvDGVdRHqEmCOGcXOQVrZVG5V%2BxbqIdAgxR2yPD2ZBQFpVjQjuhwXopIaYI4aYg7QztnGp%2BJh1EekOYo4Ypu4DAfv8m14i5ySGmCOGhZiAyDzVnesYVyIp%2FKASc7v0P7x7kHburc43wUY4ksIPKjG3I9uxSDoQKNP8ekvknJQQc8R8%2FmiIfVqBhPPJIo99MBpJOog5YtiOGghpxzQofBzzIaSDmCPmfdjzIusaQKHynHnwkHUN6Qhijpj35prPWdcACmUzyMUd2xpKBjFHzBgzbDnrGkChdBG%2FnO3Huoj0AzFHzDj8HWIOCOnH9B93qBXrKtILxBwxo2rEItY1gHJ1erXEy491EekEYo6Y8y%2FzcrGuAZRL712mxSHWRaQTiDliQYvu5mZdAyiY%2BtfBZ3PcZV1FuoCYIxZkOwQxBzTmHb2cC6sySQAxRyzoxjon1jWAso2uPgGTISSAmCOGmANaZTbuxBWBBBBzxBBzQMs585EbI3E%2BZ3GIOWKIOaDXbkr9nMg5S0PMEUPMAT2bCm8C3rEugnuIOWJBN5bhvgrQWqTx34JRJRaGmCOGcXMgip%2FaY41NC0PMEUPMgSjyjjKyLoF3iDlimOwForBulLNVFOsi%2BIaYI%2BY8p09O1jUAD3w8Tyxfw7oIriHmiDmrMyDmQAQquydnXrMugmuIOWJYiAlEondVd2VdA9cQc8QQcyAWK4dDvQezLoJjiDlixuHqRaxrAD5oA2refsW6CI4h5ohhkXQQjXXDMk2wBY7FIOaIYcsbEI%2FdHBVGYVoMYo4YYg7Eo%2Bp9sT%2FrGviFmCPm7Xgf%2B7SCSLRhpWucww44FoKYI%2Ba9vtNF1jUAPwat2vyAdQ28QswR8z7siZgD0dj3bD6xI%2BsiOIWYI%2BajUj1iXQPww%2FDk5i4D6yI4hZgj5vPH%2FgGsawCOGH7MO%2FIIlp6zBMQcMbcj20eyrgG40rSU9z3WNXAJMUcMZ3Mgsk7DDupZ18AlxBwx7%2FV1MaAExGTVMUv7h6yL4BFijpj3pnZ40gqiWuDwd23WNfAIMUfMdZltU9Y1AF80dguxIrUFIOaI%2BfzRdWus7TuN%2BcOYOGshKk5tl7DIRIaoOI1K0ETaC%2F2SvvOtsKm%2BYDPB%2FFFcwvsdFyXYvW8lyiHxz0hBcHj%2FuUg7IX7RbAdzu%2Ba2hbikfyZzT9Yfeo82%2F1KZPxOjiRTeHxojaGLjj7RP7OP952MSDk3qxSHhV0zCZ8y%2Fx%2F8lvr%2F4LvoJ8SW%2Bdnhfz0ccEpsREsuIef%2Bn%2BeXEmQ8095p4fEIDcfEvyVy1KqHT%2BPrMzav6vU18A9TxR8clvJD4Cs2vNO59Jyo782fsopNeV1z8i1ebvx6rilNZVS3ps%2FvgjdgcnN%2Bh1z%2B%2B0j0QD1tFh5ijYGMfY2W72%2FxB9dfmH91XDjYJOzTZxj3LaF9xVK6GVToLczqvjv9UOUE43niO%2BYP9QhXzbxviPrzvcWM6mH8fIoyaLwgdkj43XxgkjBbi%2F149SoiLeyU4hid8PmN8qLwX34Cdue8h8UcmHjpEEPIMVI0WBk4x%2F%2BWXMULS54fEfzDqfdMd4n8NSfhE%2FO%2FxnxDmDzS35hlfrvlbXndI%2Bs6PfWgnoa0hSX8cjorPYzvza%2F3oeGF%2FOfN3x39td0JRo%2BJfjvmLt3LPiX%2FpD80HH45KeCHmV5bB%2FJJeOSY2G55BE%2FMqgyZWFd%2BiMGRPrDmto6MFa%2Btwx9c1ijhUzzr5eU5P693hXSbEcDxboOyOqdVY18AfxBwF5%2FLHyyW8g4npc%2Fz9582p9uGN%2FTCCmORiJC6Fr3%2Fyrxf35X%2FNuM%2F%2FmeOSyk7LP%2F%2F7YlSJH6r%2B85mvdvqhpke5%2Fv1KSj68p%2FFvafy7KYTWyr3O5%2FXxhXb%2FWK3MNTMmuYOVbUGv8lhfU3SIOVCQwBfTm91o12PLlfV59rGuxTLUuS9cjWBdBHcQc6A8nb1%2BnTHPzukyjxevNnVuO%2BJ8TmSIOVAe08Q8tb5zKuDL4006rXpgtTDWRfAGMQfKFDjqb4c1h2%2Fm5G847YazbqxL4A1iDhTL50G3agNi3%2FIWdO4XDyzH01ZRIeZAyeaXOL0vhrec66L%2FkcOrcZYQc6BkPvvXrm28cx%2FrMsRVpq%2BVI%2Bsa%2BIKYA4XT7Lt662DO%2B6zLEJHabnkxnl4Pe4g5UDz10Ti7sTxd5unOTavKugauIOZA8QxhZVuvj0z5%2BxRD67bkeMrfBamGmAMePKvk8jaadRHisbn7xHUN6yI4gpgDLuRymjw2lnUR4llnVQR358SDmAMu%2BI8o%2Bu1N1kWIp5avAUtTiwcxB3wIfFT42OV3rKsQi3Odl3dGsy6CH4g54EW43fAH3CxJaTu%2F5BPWNfADMQe8cLuUofIt1kWIxVDoSSDrGviBmANu%2BHRvXyQbJ8uo60Y3Ocm6Bn4g5oAjr45O4iTntCXnvSh6kHUVvEDMAUe8t1%2BYv491ESLxfLiEtzUJmEHMAU8G9Vv2O%2BsaROIyx%2BkV6xp4gZgDnpj2O%2Bc%2BycfjVkP%2BLk1DWBfBCcQc8MVxRrm2rGsQR%2BmQExzN62AKMQd8GXThqtt51kWIwtpv5BHWNXACMQecCej2Xd4sPCzLZKgx5xjrGjiBmAPuVGiy0YmDh5T6J5OHVOnBugouIOaAO3ZP1xXlIB60mjmz%2FsBDCDEg5oA%2F2gbTf4hiXYQIyq71mMO6Bi4g5oBHl4PPxCr%2FutXWeYM9H6NjGEPMAY9sjyz7S%2FmjMfTtKufl4WEKc4g54FJWp70tWNdAzydwQWXWNfAAMQdc8srdv88d1kVQszIW7ce6Bh4g5oBLQfnurnrBughqzhrdeNY18AAxB58wjum01v6UIBQRnrkLQpkGi6O9Dj6ye5c19rUg2KsF4alVFpUQIQiJC5LnFFwPxv8tWhP%2FtxhrQcie1I75k0KsdcKHCd%2BaNeHDWHML5iaEaLWQ3S0k%2FnuyC64b4o9XCe9s573pKNrrcFi45cEb0VpjQ%2F94k7MNHkJQQ8zBJ3w2vlw%2FpY4g9BSGHhOGlM6x%2BX6GCjUaRp4RFp8R4iY3O996zc%2F5BgoDhJXx3%2FzjOKFYBeFFI2FFwlnHaUFYlympHfMnhW%2FzJDa5xPxbsRUfuijpINwclqlVt%2FPmZsYJwv6Z8cf3ETTlbp8u5PZiydq1oryQzG0Kr1X8Y4ilM2pVY12D8iHm4BOm3luXDB%2Be9Jd88b%2FFTzn66Nqpv%2FlX%2Bfd%2FSWE60lcvufon%2FF7%2BowbGC%2F3Lt96%2FdnSHPdlj0ljylxnv5x45T5SWGFpWdWgr1jUoH2IOPrXUc9SJnIwGnekjOtkWbrgmXwYxks4YsqPGIIUvJrzg5mk%2F1jUoH2IOPrehQQ1xTqjIWNsU%2Bymb%2FgcRtp1%2FtXHzbfpWWLLa8mIxco4WYg4%2B17ja1RMs%2B9eqnwTfKyJC0vo33LGBvhWm%2FAbVmMK6BsVDzMHn9tRo77qQaQUq70tTDlWlbiZIVemOvwjlMGS0Lt%2BFdQ2Kh5iDL3B13dDtENtpRqqib1tvp9543ntbYW2lHWLUw4qztnpd1jUoHmIOvuhE5hGsx2s56NdNoB9GN%2BnSoAFKnsSvCrwZJt5gwnQKMQdfZLvq3QyWjyHMnKuHHl1P3crmNyvtbolQDStazdkGWg5Wz2MKMQdfZn980k3GJahKbZiYlXpwrNd31k1%2FVfI6H%2BtG5EfM0UHMwdcU10azLsEtf7%2FK9FMiNtSu56jgnHO406kn6xoUDjEHX3N9gIr1TS21ffFo%2BuEUnWts6aXg8yG7vP9DzNFBzMHXNK72tC7rdLA21GszjvqH3Pbcjg3KncVvyD53G%2Ba1UkHMwVdl%2BN%2BkeawftwqT4y5G0J%2FMXB4TxfrMlFzGIZUYPw1SOsQcfJXPJg8vtqOEzYyzDCXpt7dSl8oYJ0IxbLgFF17HugZlQ8xBMm7pJp1nfT7nO2y%2FI3UjSw%2BOGHlBhGKYMHZcPpd1DcqGmIPkHDhcYO19tiXowxfkfUfdSmevadFK3dPQ49KIfKxrUDbEHCTH7cjELW8Z16DzHf8HdSNBDwud2y9CMSwY%2BpfEzykVvH2QLKMxx4tcjO%2Fea3L3L0E%2FhC%2FbL9FHmA8EJKLTDHR9wroIRUPMQQqif7rHejCGUePVhL6GLLl772R9o5FQ06VHWJegaIg5SEHn2TupFwqhpFXnmGpF3UrQlku5I0WohoEJDxqyLkHREHOQAp%2F1Z8ffYF1EGddz%2Bg60jQRdXZ9LjGKkV8Y3D%2BsSFA0xBylx6z%2Bg%2BQ7Wo87c6j14Rbv3i%2Bl5iTvrFXl7zvp2ZD%2FWE1KUDDEHKTtazIf5YAxrk3sodSPZCgb7KXI2RB3Drwq9rSgLiDlImd2GW%2BVZn0zo252a3pe2iKXds4e0EKUcia1bZU19zZ6OIeYgFea%2FaNsthvVpUJMOhai36fKvsahvB9YX4AQaz7%2F9mHUNCoaYg1S5sP4s49kQgoch36CD1K30GtosK%2BvATruiGec%2BY12DgiHmIFWyFZzH%2BrJV92Te5KnUrehDfN%2BynteRdvZb3A6xrkHBEHOQKm6zpjvSn0rR0f3615HN92hbuT9Mp7xZXzb%2BbyqzrkHBEHOQOgGlHs3PzvpqL3dVv2K0YWuauM1ukNLWTFcvPdfpDusilAsxB6kTZFvf6ifWt%2Becn0ysSH2PytRs5BOlRYbOqnNlBT45kQvEHKSWw3abIaxnt1p3eDTOn3bb0s3Vfq4zTZRypKNuXhhXrcQQc5BqGfcee8Z8B%2Ftq%2FUflpF00fX79UVeysb4ATxtD2VWYvU8MMQep5n0kuxvzJXhtbI8Upb09F7RobMYJrC%2FA00bf%2BXQv1jUoF2IO0mDT%2BjtvWN8iKrO5Yx%2FqRjLOD%2FuD9QV4mujqlyrKugblQsxBWmzSHV7DuARtydV3h1KP4cvQve5IJW2XpSu55i%2FWNSgXYg7Swn%2FKP41Y1yB02vc97VMIIejGsFXzxChGIog5Gog5SIvAElmPrGJdhMY%2BoBL9DM9GN9esZP5AJdW0Yfdu%2FMD6RFqxEHOQJia%2Fc%2BFDWF%2FtmaY2FGHR9Fsjs54QoRipbDo0N5h1DUqFmIM0Olfot8Wsa9BmnGFH3chy50vLRKhFKhmbt1fUQxM5QcxBGnnN%2FfsW85xzuPGkYAhlG97TGh3wEKUaSYxp%2FTyMdQ1KhZiDtPIv9XxhdtajzgbtmuWxlrINk%2F%2B%2BBafod7qWiE9b3UvWNSgVYg7S7l3HO46sJxFodhrpozasYtOrSpnFb7c1%2B1PWNSgVYg7STrNy0%2B1YxjXoC2elnxYQEJKrboAIxUhBY1yPlTUJIeaAwHyPLswvoDxaZX2pp23EzePs8yjW8zpSR1UmJi%2FrlU2VCjEHBIK2eO58x%2Fpqz66ttiX1IpvC82OLmb%2BS1Clacxnt3cj0CjEHBEwTlvb4hvWabfpCjR7Vom7FZa6HhzL2bi095KfVrGtQKMQcENGXWzqc9eBaVeuxR2YPpm3FZU2ZqmJUY3Hum%2B4%2BYl2DQiHmgIz%2Bzuy1Tqwft%2Bat2qcy7YWc8VGvzoNZj49JDdtm4X1Z16BQiDkg4zxnRNE3rNNB3cDel3bgm2nipVJlWV%2BAp4Zm5cUqrGtQKMQckMpc2DH7XcY12P7y%2FTd%2BtIsJa4Znv3ZSlHIsyiPLzg2sa1AoxBwQy1Sn0D%2BsL1uFOn%2F4V6Ntw25xoUnvmL%2BSlKh%2BdinHugaFQswBMeP426eZj8UwrZ9DfekcdGPr8OXMX0lKdE8nYwFhMog5oDChdVPWJeib%2BTUeQ91KlsPz%2F5L7KGFdREAJ1jUoFGIOKGhtD7VhPffdoKnRdxDt7Tkh62wrue9pqH0cWIx1DQqFmAMKpqGn7w1kXYS2wfistHt9CUt9d%2FcWoxgL0oY%2FYz1SUakQc0AjYET2ltQRQ8uqhmc52im2znkmPBsnSjWWoz%2B%2FjXoSb%2FqEmAMqpq4nhjPf09DuV4Mb9YPSnH6uw2Q%2B6yvsHtYoIYKYA0rblz9gv3h305e2KtrlOzLlmW2S96iSAlMysy5BmRBzQKlz47hxrB9DCLWOHCxOu2j60h45y98SpRpLaVK9COsSlAkxB7T2WBdjv3drhpnfPaFu5HrdPNnpl3aynBZu%2F2NdgjIh5oCaUZ%2BV%2Bd4QgqF3L9rTOUGoWEN1ScbXrVV%2Fu8q6BGVCzAE10409%2BkjWRWjtu9FPhVp%2B%2F8JtGT%2BGaHyA%2BVbgyoSYA3rL7ab2Yb6lgqFVXEfqc0q3MTK4AP%2BqMSumsy5BmRBzIIKlM1utdWI9KdRU4nVwK8o2AnvffNuB9fiYr2pzcyTrEpQJMQciCNJlW3YxhnER%2BifDcuWjbcQ4y32YbOca1KrozroEZULMgSgyzn%2B9lPWwEn298Xu%2FoX1Sql9wfSPzG41fseCaB%2BsSlAkxB%2BJQFRi0g%2FlgjLyn%2FylFO%2FcsoOrQKEfWF%2BBfZt%2FJm3UJyoSYA3E4H3e89yvrdHB%2BsjHja8o2Alfn%2BGUE6822vwwxRwgxB2Kp%2BETH%2BvacYL0m01Qr2lWZMmz7c4ssh5UY1UtYl6BMiDkQS2f7pw%2BiWBchdDXVpd7TUNfq0S05Pm616%2BTDugRlQsyBWAIHXLk2jHURglXurdSXzoEldG%2FHsr4A%2FwJbzVbWJSgTYg5EY3hWbdBk1nOldM9uj69P3cq9qi4yvGy19vRlXYIyIeZARD%2FtbP0P68etBm3072M6Ujaiv3Vxgvz2brUKysu6BGVCzIGYgufH0D7ppNe4eg3qrLX6pvhNEUoRl9Xc%2FKxLUCbEHIjJpUWHLsyv9my%2FH0Y9rMR%2F7FT7jawvwD%2BlMSxlXYIyIeZAXJ4L2c99XxB3ZcMc2kYqhpe7K7PHrWrbbaxLUCbEHIjr%2FrDrOZkvPieEts8WSbto%2BqYXi9lfgP%2BHeuo3rEtQJsQciMvo0vYW872%2BBIeb4Ta09%2Be85rwewPp5yn8h5ggh5kBk9y8%2F%2BpP9Gh%2BVN16l3dNQHhfgH3NW%2Fca6BGVCzIHYvAcV6RjD%2Bu69of9hl3q0jdiO3yarvSGcw7ezLkGZEHMgNtM4o%2B981nPfternAdTbYBldnn8np72%2BPPbtZF2CMiHmQHzqn7NcY34WZFtoSm7aZyH3L8feWixKNaIwhO5iXYIyIebAAuZP7Mc%2BHcoM97tAu9mXcfrpEEf2D46TqNR7WJegTIg5sACf4fVzs65BUFVZVm0uZRum6Coz7r0RpRwR6NXLWJegTIg5sIDA1QMvb2O%2BKJM%2BW8Z5a6tRNmLf49wb5vM6kujUy1mXoEyIObAIq7WRC9mfBeWr0dGRdga%2B140m23OxfnCcSPdwH%2BsSlAkxBxYRNHjn2r2sixAM3V92uk3ZhulRl4hx8rg9h5gjhJgDy9jjeTYkiPmcUOsiVRpTNzKwkWNb%2BlJEoFfhopUIYg4sZeKMve1Y55w2%2FJ5XDdrbc16F22nai1IOJTyCIISYA0sx7c16n%2FljCME4NwN11rrszkJ%2FTigClYr9fQBFQsyBxXgt%2B%2Fkm6y2qBUOWuCEGyja8N5y5dmaHKOVQwSwIQog5sKD%2B7kuY37xXGXuMOUH7uHXgu4d3WM9fw9R9Yog5sKDNGcdkY79DVmWXeqtp927N2PVlBPNRJViIiRBiDizI%2BKxJ1j9ZFyFYd3CuFUnZhuHlhOCsrHMOMUcIMQeWtDzzJtUF1kUI9vo2V1%2FRLiZcYV1v1ssRYJF0Qog5sKx3f287y%2F66NexkwX6UObfZv2J2xo8hNG4j2BagVIg5sKz5b%2FNPfMu6CMFnxtD%2BlI8hnJ8WbrdanGpIYZ9WQog5sLAcBcpcZj%2F3PcPDG08om3B1Ot%2BE7S4XNiUDmPavWIg5sDSjYwfGZ0Fm%2BtCMP3SgbaRCcJ8YlhfgxiY%2FMOxdwRBzYGmmg8O7sa5B0KqWr25K20jA%2F6a%2FZXliaj%2BkEsPeFQwxBxYX1OLw5pOsixDsV8flpJ17ZlVmbg%2BG03QXXPNg17mSIebA8jqf3fsD%2B9mtDt%2BXP%2B9H2UbQ3w3msxs9V7pqA2Z9KxpiDiwvaGTHwVVZFyHoIx5Mr0zbSEC9EbHMcq5Mm5KsulY2xBxIwPjGpU4I8zmhutpTz1WgfVZ6LuMZZo873XpnZ9W1siHmQBIHSm2fzjznhCoFDs2hbGLpz4tCTohSTNpV1rF%2FlqNIiDmQRJBv2WLsZ31ZGZ%2Bto2zCVLzq77NEKSbtRvx0hVHPCoeYA2n4R65pdoX1qkz6J5GL8lG2Edi7T4eRtAs7kZnaidV5pMIh5kAqzwdeZL7Ips76mI%2FDYMpGMmyL8WcyfG7TutYsulU%2BxBxIRXN%2Fwl3WSxkJgm%2B22pNoF5%2Bzc148UJRi0iiuQV8W3SofYg6k4jN87VHaZd%2FoqUvv3O1I2YZPcFF9dgazvu49C5O%2BUx4g5kAy87%2FJvJr9jgoalwvnctDeW1u9%2F5Lkl63akqv6tJK6Uz4g5kBC31a7z37tOWFHk6aRHemasL29eYfUr0T3dHJRibvkBWIOJLQl1zb2p3OCKbD5asrhc94j9h%2B9LU41qaYf0%2FS4xF3yAjEHEsqw99gR1iuNmy26XqZof7rFhDu32bZfpGpSS9U7qonEXfICMQdSqpR1DO2eDKJ4cm4K3eNW5zn9m0l8YuphOBUobY%2FcQMyBlBxPTDjPuoZ4ZX9uRTe51eTgGBwt7Whn9e5n2STtkB%2BIOZCSV3vd5LusizDT2J%2B7T%2FmotKH2srSTdK3HX28saYf8QMyBtOYdkPrW%2FRcZ6%2BkL6qlaaPzr5GhJRzvbXZ5dW8r%2BOIKYA2mdX3mAdQnxdBo777pULTjX1TyT9HGK%2FRRHJyn74whiDqQVNUQmoyL85vVuQ3Vi%2BUsm1T6RakmVRVNnst8hTZkQcyCtnC4Zc7Gf2RqvR599FWlOx7JnXSrlzFZ9swZb6knYH08QcyCtoAeXY2UwdM5MF24q95LieM2bBctEKyZlVsatg2QxGEeBEHMgsV23f4thXUMird3FcWfJh8%2Ft8WhzVcRqUmI92fW5hN1xBTEHEtu75tpr1jW8l79pCZrHrXkqSDhyzljZ74V0vfEFMQcSa3RpPLM9Yz5lNaZ3U4oLwXx5JVwndEGMa6h0vfEFMQcSy7H1%2BGrWNbynH2CqTTHk9ubGNeLVkpLcT3azX61PoRBzILHOjwznWNfwgc76xOhXxIu4lXH%2FU8xikueZo9Nb6XrjC2IOvsaU9OenV3Umuud9hicTD8hjREmCNqHjM5Iusnnp1GxRa0nW1JJG1jsGKRZiDr7E5DBcVU71yPxRrrjjccLwyB6CKU6Y2FeYqConHI%2FrR5F0QcN8d8lomKt66Qsn0kXTN8yT8KFAgcwOo6XrjS%2BIOTkJ0vlkXnl58ZY6D1aMF%2FL4LDF%2FqtjHX3%2B%2FuEfSJ1ck%2FtHso6989NXErzcTzrcWhCXvP5W0M9SST5pIaCO%2BkdaJX1zhlyfLeuHNJvPf6tsKDYUTYeeL6aMLZnkmmD8tCA1PlG7TTLjRVFjoX3CYX1pfpSnTbjb7%2F32Z2iN6lJasoNmnn0h2Xup89GB5Ob1tioKYk4XAgw9XGb02ZL6zenJUgTFnTnXrPn5iWFxsrEqIE%2BIShjzcT%2FiXSvznikv4TS8UTQi3%2BF2e76s%2FaixO%2BPfr5i8%2BVAtxce%2B%2FpkpoIS7uv02Yvy2hfVX8181fjKtx5r8%2FvrpQVWzOjz6lVatUD6xVglPQghFp%2FkE3zVopr2Gu83bvdCeqaFKWXVLFnLbIj%2F%2BTqCsOIeZkwfXK6p6F%2FH%2F735sao%2Fs998sVd9%2BcLO%2Fz6uuSvv7Vf8O4xC%2BqPm5I9dGBH3%2Bk%2BvBn%2FKfiPv3pNVfzn60PtPGR%2BEBYtc%2FVd20KVX5GdjFnnOtSJc2vIp7624JSxZzOsO%2BkRF1xCDHHWJBt976Zli%2F23DXU%2FvrbOJnM9kwlTfvASwQ3p0wz88hmfHACH6eWs4m2wMmRP69U%2F2D6WhUKS9QVhxBzDDm3mf9O81fE23urZDLLM21ULgcMhwiOM01us0%2FsWihorRb8%2FDiY6FCr80OlijnVqoDxEnXFIcQcQ8srzWg6zbnDT06xn10kKoHLBJft1QiOk1nMGfLZlH5Atp9hhmUrpPqHcz71ZwaJuuIQYo6VHEtLZHux9vqpnEpMuARNPH4lmgQgr5jTPQuo%2BJTw2PCX%2FaX619PUmhguUVccQsyx4HY40z%2FL6i86IuGMSNF5ROy%2FGEhyEhQ0bM3wKNHLIebQxjeM9NiJ87%2BRKOb0VjPqhkjTFY8QcwyYes4MbTTreWysYk%2FkzEqX%2BLEg0eY1g%2BaMkNMsiKGN%2Bg0mPbZC7T1ilpIM65aZ78rr8bSiIOYkl3FoiF2NdbGVZbD9PIXS3VqXINu5XrNl5XWRiyGnnvTNijdkN%2BbMPH%2F2F7OYZLgbvEinagBiTmrzO73qP3DBeTns4UdD93hDs8eEF1GOp3YvFLcacna6HrWIL1kF4cJxqV6Jb4HKWFOTHGJOWtvuWO%2BSdts7i3DOt6Q36bK75w7pZLPeXJ8R58dTXAv%2BEC3V0kgTutx%2BLFFXPELMSSjboiFHT414m9LkBgVwcF5G%2FAO%2Bt8qIfSKWQkFbJ3jPAOIbc2YF80n1LGW17Su6XWXTN8ScVLz2RpSu6BGo5IerHzgsKOZAfN1dsdtCebwJhohrZ%2BwpjjeOX5FJomHdhn61Cx6UpisuIeakEbhm3YNZl2Sy1QstVUD0BLJpA2bO868clcetSXtN1%2Fo0x7uuG3FWrFpSYHdNys11%2BIOYk4TP%2BrM3d5924mNZRI%2F7vtXUxEm1yT3w7IOUv00Cq48%2Fb05zvOrY7m1i1ZKCMgNzSNQTnxBzEggatGpN6SWcnMoJ1hVKd35EfnjP%2BlINwkjBtI4%2FRVINRcs622qaWMWkoLYX5u3TQMxZnPPY1j97D2NdhWj0P7cIe0V8tGli8M7tIlZDTGXTrSPd7KlA4%2BG%2FpPpfV%2BA%2FFBvzAGLO8hrPPbH%2FqIxWBaejdd7%2BYDXJhP1EhteN8sliXHSbceOykr%2BMeJurd4qW6up7Vhls6kUDMWdh9%2BMaLlstk5tRYlA127qe4nD7Ts9uilUKjQVP90yhvFO6t31eiU7mtJqmx7ENBA3EnGXdX%2BZ4jaeRAOpf3gRSTCH3%2FjnHBBlsaKBVFyz%2BO9F6wf8yfb%2FoiUSPlAwRW%2FFzSgVvn2X9Nd1oI4uLNJG0M10ZTTr9wUxr2%2FaKDB436yNMmdbQzYQ37c%2F2%2Bp1E01nsfi5lkKYnXiHmLEm7%2F%2Fryt8qf2vUvvx7fuaZ5J6%2BPRD2dL4fQL3potxNlE%2Ffvl%2FAQpZZUGGPIFytVX3xCzFlM4ITQ0J4x8hgJKw59%2BJKl%2FSiO9wmxL5xFBqnv3kefmXL6gveuu4uI1hQloCv06%2FIOEvXFKcScxeypEbZvbYwMfqpFY6ebN55mCqjueMgGOTyMmZ%2FHvQnl4m2d7TNek%2Brq29muxVmKGwWAmLMcbffl%2FXdKdfdGEnZ%2FuDuvpjg%2B4PK4nrdFq4aY2nvJX1rak%2BznoV0kGyNUelN3pBwdxJxlOIfe8bWRwTNFMXVxrUj1iirleCuDjQs9ck%2FYmJuyDTfHC3rp9vAY8fQ2Fg6mg5izjIAKQdeVtelqyl6cm0T141Yms5MM3pHS%2FfKZiJcLTtLYtEaqG3Nm4%2F5%2Bi5ijg5iziFzGeUs52yS9lk5nQzNtQL9z9UX2Kad9bN%2B5M%2B3qSWETPWdK9mjJULBYM16mQzODmLOAoL5nBt%2BVx5pqolnkaypI89Omiep4WwajIjQbhkylbCKo6xAXCddhsLJth%2FmstBBz4gtcHTWJn6n6iXQz%2B%2BekmW8U2GDDSRncqtT5nLhKeblpelQv6Jo41aSuv7WVa0jYHZ8Qc%2BLLdmhitAx%2BpMWk9n41wEBxnWZ8WrXmEYlW2k1Ovgu3fqV8ajm%2F9thf2otTTaos87zyQsLu%2BISYE5366IhsnKWcVf%2F%2BS4vTNDCo1fL9YhVDTtfYvRDF6iqJbnnkk3K1EE1AkSfYBYIWYk5kzm26DJXPNqQi8Xuwi2bAnOCz5ceb7G%2Bj60IfHM1C24ir73pJdxEvsyUHxVIJkAgxJzKng8uPSbXdk2R%2BX5%2BhFc3x25yW3Wc%2F%2FcFON2811aroZkEVJkRJ%2BrzYM8cVrMFEDTEnLs39GTdkMNJfVB419Hn0FJfhgd%2BVutaT%2Fa6NuhcFZr2lbMM27NgYaZ8XN23ZXsIherxCzInJ5JDn51ysixCbyuN6Q5odsLwdY%2B6Tr6ounjI7z9A2sWdwkUFSPn4QtCW9SsymHcwMiDlRGVU2FXg7lxPKOJfeTnPd5LDown4ZjJgTZpd%2FTttEePsoaTcTd9b0Gl0FUyCoIebEZF2yxQbWNYhMa63zvk5zR2u515JgGUxlXbSlZVHKES1u2us%2BUu1YmMTu92lDpO2RT4g5EWmmX30gg8FholL5t7hF1cCBDnIYD6HZ9Vt72nkptodKVRWlmNSr1WPMGIm75BJiTkS%2FO6zgan25eFY7us2hauDmCak2M01OhrtjO9NOQw0%2BdVjqGXztMt%2BgWawZkiDmRLP5f7NOZ%2Bct5Xr0Dc9MEw9Ofz3ZyH5ZdJXd%2Ffq5aNdsuz4gXOoVVmw26cfjAYQIEHNicX7qvKsF6yLEtqBdP1U9iuO1E%2FIPY79FrdbVJ3AcZRv%2Bpuy%2Fj5H6f2LLarnSnUpDIsScWAI6%2Br5lPwRWZJmWluhH86Cv55SWMriMN0x1zkU7%2Fc7BZZef5K9k3u5IXLOKATEnlu2rbtMOPpUbY8c2oTQPSTdfKHD6EvuUE37Peph2Jqv6YuAVqU9LdWHjKjtyNjuaEcScOHy2Tbkrhx1IxaSp%2FG0LmnG9Pruye%2F0pWjXENK3zP79Kd2MuaMv4LE0lH%2FtnsCrVS%2Bo%2BOYWYE4c%2B5zc3WdcgNt9ff6ZKB%2F2pl5JOGfiKot0q0Y4L9n%2FcZIAotaSJ1ZbddaXvlUuIOVEEjhx5hLvLi7CT86gG4P%2FTqyL7h6xC6RZrD9Cu8aEt2ylAlGLSpJZbw8fS98olxJwoHLZvP8i6BpHZVi1qRfOQ1XX4oYvsH8loNUG2jYMpG8lQvOZV6V%2BKhyF8A2%2F%2FUbGCmBOBaeLooxflMG1TRPp2Rye8pDjee%2BfaiW9Eq4aYyr4l7SgfU%2FEwTTCDyS0uc5zksOIBFxBzIvApsLKAI%2FszF1GVXf8gK83P9rZbb9eyX3xJKH1tC%2B0D0qUHYt6w2Fa8Sm0fzp5psYOYE8HSUqv3sq5BXIYK0xx1a8mPN0bmHr5SBtN7O0X7TaCcRtD5bCkbBntRaq3%2BeriWdhgMJEHM0fPZcLGbDK7PRKQbHafOR9PA0uazV4lVDDltmG%2FWIZSPH7wL%2FtFyuzjlpInuWbgMBuPwAjFHL%2BfcuVn4urwwFdn8G9V9e7tDVjJYJc3QbtPJtnQnc4HD87o%2FZHFaql4ehCWYRIOYo1dxSKAMFlQT0%2BDzBWkesgqZC9b5h%2F3sB3Ulm%2BG0D4a8y90%2BJ0oxaWTQNfQKY9ExnxBztEyTamXKIYPxYeLR%2B8%2BZRjPJKLD3sFaD2N%2BY09k4zKVNuYCQbvOZPFuyH5yVasdI%2BA%2FEHK3A7n%2F75WJ%2F6iIi48lsd%2FpSXHO6zembWdJN%2Fr5M7bShAe1uMROdN7MZ9e259Bz7lV34gZij5Rxy0sS6BlEVHXazCs0TFdvBTXvI4SK%2BrbEGbRMTg5xzMrnrap%2FZ8Ce2ZxUPYo7SnuqTN7LfaVk82qczcrymWdbcuVWNLSdEK4eYbZ%2BZGooRMfH2VM8zap441aSR1q5ofyYd8woxR8lpUOwx9hdo4jE0eFTyLM2M%2Fc1zX%2BSUwVDp2v2sAikf9jr9NeQNmyfouqzfdmHSMa8Qc5Q2zS7EYPCoxdi0OLScborR4QKNRaqFRmWXHoMG0zVh84t9IzajYjS9xtySwf8pOIKYoxNQ4PZR2q1U5KTsthtUKefS4uxrqfeF%2BZz%2B8Z3C6%2BmaME2YUnA8o9GQpbsswn5eokLM0cnV5hjrEkS1aSjdre%2B9R%2BvKYFywdcs7mSl3uHHO02E7qzOqys0rRTDqmlOIOTqeiz1ZlyAi7bRBDan2%2BbtXa9l49iPm1M1KUj0rjueYZclAUYpJuwU3mwW0YtQ3pxBzdB4PvsC6BPFYdy1X4inF8W5Hzh7bKoPnMRln%2F0M76T3DwmPMdhbPOLkwo565hZijYZrokJufSfu6xv0e06ScoOnTYJBYxZDTllzf5SXdJatp4tpFFxgtI6VVnaoyl03X%2FELM0ehco2ZDGSyqJhLfwRF0F0tZ%2FrRuK1ItFEpv3dWDcsTcckOpRjkYnZZqKhYafJtN1%2FxCzNHIZpjD6gaO6AwRty7Q%2FdcQtqVyO%2Fah72Kd15%2FyDNuldre%2FFotTTdo5ZJqYm1Xf3ELM0bg1vJcMniuKwzT31GGa453Vp0yX2Y%2F20r2YeLo5XROBq3JMYbYlmb7w09%2FZv4u8QczRCN4l%2BR7FFtNu3WGqlX9sZpTsz%2F5cTvfkYaEjdAMZA9%2B9Kspi74dEpe8erEh5yQ2fQcxRsNmnk8GCQ%2BKo%2FNtTE82NOaf8gwLZR77KfvW%2B%2BpRtuP7kNE6UYogMyDUaKSc6xByFXb7fcZJy%2BojL3U00S8y9sPHq%2Bla8ekiVbuFBu7VpgM%2F2lczWYtBpBrsNoRzYDJ9DzFE4OC4zJ4uj21st3%2FCEZom54O%2FzZ5fBLaWmp91oB9Ze3xrK7hajppJ%2BN%2B0aefA5xByF8ysY7FJsCfbLz7pT3Veb3%2FBqP7GKIedxOMAjlu7GnPHSd%2BXvsLvFmLvAlCfMOucYYo5Y0LDh%2B6NlMOZfBBknDqba4abz7J0L2Ae%2Bx90jO53omthTqzDLIUJWW7Lm4GkhCNlAzBEzvNo3jv3PtijKrxxKtQHWrJzTaXddEEEZ51adKW%2FfO4zJH8DwcXGxtyNpVjSFr0HMEbM7fGIZ6xpEYfReXJXmtrfb3mmRZ9if1lq79dlAOWIuYE2pZ2z2foine3xdq8OdOUtAzBHLka0hFxsGG5f0phqQ6jVnZktmo2n%2FpZt9vRRtG3s768QohZCVS%2FcyDLvnGWKOmPrlo7asaxBDl2%2B%2Bm0gzlyP8%2B4Hb2Z%2FLCYZGK6kHnC3cze5cznzR%2FdP3eP5gGYg5Ylm87nGwOokh%2F9OF3x2kaEAdHDNDBnv%2BFDt0h3Ypypx3941heGPO5fsSA7Gbl2Ug5ki5VTqTTflPxTwidpiq0jRgCqh%2BTaxiKCy42agkZUbY7%2FjnN3Z5rQ3PXTJTB2bdcw4xR0rz5pYM7kjRcu%2BZ5znN8YEH896TwUwQ7ZxhgXQtmOJWDGM4x0tQdSxUjmH3nEPMkQoLH83yRo5Ith4OpXo6GebdVgY35jya%2F9mU8nJz85upC1n%2Bb8t29P4%2BDLvnHGKO1OyCY9lPVaekc6128g3NiLlGDZe9Zb8sidBpwq1ndC0YXzjXZvnc3FafX49LVotBzJF699xbBkNiqegjbB8fojg%2BaJjj5haiVUPOqlQd2r0f5jvOYPk8SVdw3QPaNQfg6xBzpDw7ntnBugZKbUJ71qS5b994jJOXDB6yCkOnnaB9GPTXty0Z3mLUjvF4sIk2qeHrEHOkLp2fJoPLNRqlNRO6zKE43nXw9FoyGLRva6g6YADdIs6bra6ty87wFqOqbPRUdr2nA4g5UuPzr1X0Kky6EqW63KJqIWuBzDI4lzO0HXwzlK4J%2F8gzhU6KUw0Rj8zdbGTwTnIMMUeqh9UDRcecVbdqwXQ3vcvWksM6VLmPh6%2BivN47sOTea5Zn5suqFTvBwVN7GUPMEXJ2zOHIfiQFhbJ7r1NNLercx6kr%2B5zXhRbKX5ou5by3nNx2kuVAb2PBfoWxZaFFIeYIedX5jXUJVNy7lLCluem%2BfNW3h2Uw1816xyTK9TxNjs9iV4lTDBGtxj1LU4b9pwuIOUK5sudkXQKVhm6%2F%2BNCsvjQq9p937C9ZtS%2B%2BKUq5Krrxpe%2Bhd%2BJUQ8SjedYOVGuaQsoQc4S2r7rKugQKNoaLZ8bT7P1wqHilizJ40NzlyBraC%2BfgAdvbilEKqU4NDZRDmyFFiDlCnivqsC6BnEfU0yU0P1umcVP1g0Srhpi%2B4vd13OmGtDhHnPwlG8tbjMZZp%2B5SLgYKKULMETp%2F3591CeR8Vv%2Bjobnpbny6cbgMzuWsvxuzOJJuxJxr9kGTmT5JKu6yqTHdS4CUIeYI3RxxnXUJ5DL3nkF1EnSvS6l9IpVCwXq0Zvxquibse%2BRfw%2FJBirrS9oujsS%2BrxSHmCOWfP4x1CaQ0LuVHU53LPcvTaL8MTuY8b0ynq8Lkkz26G8uhHB7uLfU0k4ohlRBzhJr4yWBjUjJ%2B80Oopolv7jl8nli1UNCdO0fZQuDDYK0opZBacOY17stJATFHqK%2FdAdYlEKq8IjwDzYi5jHNu72I5AiOJ8%2FkHj%2BhaMKoyl6Kb7kZHtXB0ptvU%2B1dAKiDmCL04w3DbYgoq%2B4v6qqPJjzf55DwbIIOZST512rSj2ltWEOwP3ZskTjGE%2Fbc1HWbZfzqCmCN09W8T6xJI6J7Udm5M00CgcQDLTf7eM1RatoZy52afBWsY7v1g%2FtEbP7YBVtKUBmKOUP3ZclhQMs1cpmUxUA0TMx68J4fT2NGHZsRRnlNOyLg6F8t5HCN%2BOeuJS1ZpIOYIRQbL4TZ8GmmtnzY5RLPEnGCtH7eJ%2FX5mmpYzFhena8LrUcfjF8SphojBe2tOh8EMC0hXEHOErhxXYMzpS9r3omrAq%2BbVh%2BxTTuexKNiRrgnjC5%2F7r8Sphoje6kT7vgz7T2cQc4S2rmD5kI6QpvhSun3dPef1imU%2FYd%2B2UmBXiqco8VQurQ6x3MrD%2FZdXK%2F0Y9p%2FOIOYIKTHmimasu4smHlyaf7tZDgujFciUl3bmwITqfgwHOGvDFoX%2BjccP0kHMEVJgzNnMP7aL5sacsczfB2Tw3MU5YmMeygtntxN5DQz3ftCWVFPePIC0QcwRejPqIOsS0qp4j%2BO%2F0uSDXU%2Fn5zLYzKxTwDV%2FysnujhWabmJ47a0qVeMpZj9ICTFHKNSksJjTtPy%2B4lOqFk4Mz8R%2BVXTBz6nPwHp0TWRZ7DSE4Yg5Qzf1FMolByBtEHOEIkICWJeQJirfIYXX0TQw6EL5Hey3n9KFLnc8QLf3g3O96jFMB8V43lTkyHIlQ8wROnVqMesS0sTYLqoL1XjaLGsCsrDf48fZSxd1ju6S1WvWi6ExLF9J1lmU8zcgrRBzhHbeUVbMbbKfSZUOwY9rtGO%2F%2BJL%2B51XzIumaWK67uJGyCSrt%2Flqagf3Qw3QGMUdoaMlprEtIA7VxkGsYxfHeG0pqI9mnnNCj303aC%2BewGtVZbj1decOV%2BnTrukPaIeYIKSvmfPvpmtKczN0f33Yc%2BytWQbj7oA7tNNCpnkzPSqeXDMSa6JJDzBGam11BMVer3TI9zQiG%2B8uKF2ovWjXErLycu%2FWjGxjs2rXoljPsArvTsb9G0T1AARKIOUIhy1hO%2FE4Tfbjrt5Oohr%2Fs%2Fdlnn0jFULAefbML3Vw1obEh81GGez9kuBxeIoRd9%2BkXYo6QgmJOMyB7GZrjTasjM4pVCwVtk4HXo%2BmacL7Q8x67lNNazTFkwtMHFhBzhBQUc8t0GV%2FTHO9yauNCsWohp424c2Uk3cpFzmUur%2BzJ7sZcmZ11iz3BjTkWEHOElHNvrtPgV3Y0I%2BaswpYdptk7QiTOd%2FZT%2Fse6tE3ulgw38MhwbWwvOSx8kB4h5ggp5UmrNmzQkFsUKWUqfuLWPyy3Mk3irGlSn66FwBHbFp0XpxgC%2BgGNjnkpbH4gPxBzhJQyC8L6j8UuDyj2hjFeKuucQwZTWbs8oh3Rsrni5oXsdiRb5BN8IAIbTzOCmCP09IAi5rQa%2B738IZzieJ%2BNl%2FfJ4MackPvUg16US4pXcD0uTi0EDNm61OiHZTSZQcwRUsTUfW3Jf3IuoToJsh3sNkysasjpH3nkp9qPTBD2VF8RRPmclpgudEDugVXw8IEdxBwhRaw3p29WsLieasL%2B9sC3rMLhI0ZNkInyXKhR%2BaLLWS0xZ93Sa%2FUrpBxDiDlC7d8pYPVgq9ARXlQNHO2nzsV%2Bkpchm0dnyipy7bk4g9XeD%2FbT501iv4RVuoaYI6SERdLt3FsvpLmhZXi7ciLViDuRtAs8RdeAUbWmTHNG53Iel4aoCrLpGt5DzBFSQsyNvt7%2FV5prpUxaz7vs9%2FEShG4ZKZcLvj%2B%2FDdXy8DRq%2BR46SLkNGdBCzBGSf8xpe2cuuZ1mnrhT88xH34pWDjHf%2FseW093Y8rdr2Y7VEnOeC9pkxx5erCHmCMn%2B3pyh4NrtBpoGAgv%2BeYH9fTnBpdeD55T7w2Qqko3RAGcb%2F1zWVP8IIArEHCHZP2l1yVv1GsWwYCHwwMk8MlhIU1h9eX9myqeUI69GMxrg3O7UsnE0%2FwggDsQcIbnHnPWCC%2FeoLpZyXDkpg1tKmkrbr42nCwp9vel1nFiclurCfm6Qq4sM3kRAzBGSe8zVfkC3fVSGBZvvsBqB8S%2BVy5RYJ7om7DZ858dkxrxz3gM184djuJwcIOYIyTvmtKoZRR9RHG9ymNt%2FjQxmstrNK0h5V820p83sKHGKSRudoW4rGbyDEA8xR0jeMeejUb%2BimTZwf8Pm144yeP4wdKcH3ZriPrtOd33N4hajdRPXqq60%2B1aASBBzhNrrZRxz9j1LfUMTUl6FQ%2BvIYJ0pjf0f7o3prvo2P809SKRq0kBb0nA0ZuUL6TuGL0PMEZLxuDmt6sXEzlQhnPlQxrYiFUNB36xQxQi6Jnx23%2FCWfvElXWjFoXp3bFMoH4g5QjK%2BaDWMfl1sKtXSZodf%2FrlDrGrI%2BRawWk%2B5%2BNI27cHD0t8gW6QPnNgzExaXkw%2FEHCEZx5yDVyNHmuOXek65HMP8xpz2sVtRB7pJXsaWa9qdk3p9d32Y7aXJW%2BZI3CskCzFHSL4xZ9OwL9WezUvrWDWWwRwvg02WQLoW3I7k3t9CnGJSz1Chu%2F%2BpdVL3CslDzBGS77KaSzdHWtPct5997pfBMhgJ4d5tE%2BVc0CydayyWfMK%2B78DwyMGUl9ogNsQcIbnGnLrNX7O0FCtpGkNyhLWTwSQvP6eaL%2BlayPS%2FISapz0oXOH9%2F2F7iPiFliDlCMo05g9eIsTQ7YPmENH5xQ7RqyGmKTnugp2nAe9rznI3EqiZ1dCVe56hSSQ4L9MEnEHOEZHpvblFMX1%2BaAbVW0yqNl8HG8NoOutojqKayNi5b00%2FiJXudM%2BvGjZpKtSY9WAZijlCoSYYxpw3%2FbcNvIRQNuA3%2BrrUMTkd0oREPaOaqCYLd025D2opTTCppO9wY89oN8x5kCTFHSJYXrc5Zy7emOd7UvGp2sWqhUcs3imrCfuDBGraLRKoldaxa3Dzt9ocM%2F88H8RBzhGQZcy6LnKkWNQ%2FM%2FLsMzuUEoenym1RXzj6HgqdLe%2Bmd4fb1bOq%2BWI5EphBzhOS4HfWgPje%2BpTmhcGye508ZDCURVmbITNdAhqnrXolTSqrYNm9wruhZujUGwJIQc4R23lnMuoRPaJ03b29Bc2NOE75wO5M1i%2F7LdlShYnRhm2HB2UOSrZWnsvG9M%2B%2BaL1bPlDPEHKGhJWWwhMd%2FqDwL16VqYJT1VpFKoVJMO57yVKxs1x3SbUhm0%2FdJB8oRfmBpiDlCsos59%2FAmP9L8cPuP0Y2Xw0sa0yq0J9WpkW53xFCpFkVX2S7%2BYV2eBa2k6Q1IIeYIyS3m1LtXt6Bacsi%2B07ObItVCQafJ9K0%2F1dMDq7WRm6UZu6Yr0eZst64TwiXpDGgg5gjJLebaeOagOt5n%2FpK1Mpjjpe5QMxfN8ab9v868JckKc1rN47LBC76XoiughZgjNDe7vGJu8%2BPcVMf3KrQ6J%2FPFlwS97UJ7qqcggwb%2Bdv%2BKFE%2BL9RG%2FD3mzehJ2mlYExByh2O4XWJfwEZfAGQ40ez%2B4DvjZ%2B550t%2B2%2Fql3znVRLzBme5658Xqxivk4Xavf7Y2Of1TKYFgepgZgj1O5hJOsS%2FuVsk3MCzRWn98FMjf4UrRpiutC7Pldo1hY3jXAtYfmZCCqrapcP9B8l9XqdQA4xR0hWMTei16X6NPGQobjbSfabsgo2%2Fa5mjKBYW9w0cUPO%2FhY%2Fw9KVrHX1zoANV7EIunIg5gjJKOY8IgYG5KL4oQt88cLvR4lX8%2FgSzdgjg6hGzO15HPa7WMV8hc7jdbnuh7KWwuxVRUHMEZJPzOkqndbMpWlgT87pF8UqhoK2R%2BtIqtuDS3%2Fu3bK9WNV8gVYTkVP9eEnwcKy1pDSIOULyiTnNyvyZqe4T2Sy5vl2sYshpHz2%2BoKcKkAHfLs5qyYeszhFtVmxxrFA3EjP0lQYxR0g%2BMbehb06qWQPnTu5%2BJ4Pb6dY1snahOT6wYcbBzy12Y05lNyBgTP2fxz22VAdgSYg5QnKJOcOT0Nu2FD%2FdQd26VAmWwcAIG339njSnYm7aq022iVbNf%2BjDPDO9bVC%2FZslwnMYpFGKOkFxibsEPDX6jSaml9ysNzsV%2BXLDgeWP2JJpnl1YXX4%2BzzDmpLuKHA%2F2qrozY4IZnq0qFmCMkk5irdb9ha5qTIM0%2F2s63RauGmNZ12qOMNCmlORblb4EVQdU1629ZFVRi449OMrisB2KIOUKyiDlt%2BKyaY5tTNOBdcH1rGTx%2BUDlPHD%2BVqoVnLZ%2BLVEsS7SOrefeXFwiqXq9hGYweUTrEHCFZxJzKpf5zmv2vTJM6XoyVwRWrjec9Ld0VYYECIp%2FLqWx%2FKhHkNNhp%2BCsV7sgpHmKOkCzmtC446htBc7xR62Wh2%2FZp4rclfwuqTVmz%2F7XpsHhXldqIZvY5tfNG7PbRVqSZWwKygZgjFLKMfcw5FF88huZkzq52n5nM7znp%2FY%2BPDqJbz8ihROG7osziUGnmHnGucLFJkaaOzRxk8PgZRIKYIySDmNOX%2B6lVMMXxzovW%2FcT6gkz7KDB32P%2BoFogzTeizMzPNYxit%2BcdA9UBfbJd67qJfXIc3mEmz1gvIEWKOEPuYM8zsO6IczSnHq5LB4xmfzHncDdhouracalMsr5%2B0k6IpbjDqHmtzVjzrVGRF1Qq5%2FA8cUetjcR7HG8QcIeYxZzXSpcRTiuO9igw830%2B0atLOfB539ohX3%2BP20XTtaCIqzBtI0HvNvVb1cv9Z%2Fdtr42v%2Bs3NZu7ZdDnrbYaNVTiHmCLGOOd2kzV2p1k7K7tp7bNz7RepUSb9%2FvmhdGv8D%2BbeBLx4Y9%2F531cTFpa6sctsQmrbmv2BTrgUvhLD%2FdP2ZpFqcBKFv%2F%2FLXheHqTRcqXa4evSF0wS7V0YkN8ZyBd4g5Qqxjzt76iC3VFWfGc%2F7HYmN%2B2WS45zVeHWP12kF4JQgZomLjz65UGiEmTrCOilPbfVgZycruSIWkjfoyHllxTCj%2FbdIX5pRvFv9HhaiEO2wZzL%2Fih9o4vDIf8fJsr86CkLSQeLnyzb6Li9OYG4%2Ft%2F3bC%2Bq1Fm9u8GDKY5iUkuBUyYaVgHzbn2Etz11Hxya8WYlV2Ca%2FinfklxNfypt%2Bxzh2EP%2FQ%2BuY%2BsO3Lo2K%2Fjtu12G7P8jc2rtznUfut9MLuBd4g5Qqxjrte5IXQN6MsVKVcntuZIQTjV7WCJ7CNqCTsEwUPYHj9CZZj51whBCEz8ZJKyNm%2F%2FfP9R5uPlhONJny8nJIzMTfqih%2FlX%2FIDjWjuEssKflTKbv6txwhdWm7%2FvkMf3Pc2NZ525bneuZlvEWc%2FIpqywXohcXc7ceGLPQq3twrCeCa9ihPklxH%2FiiblgcxVzeo9eYPvqrUo18%2FoSLKaUniDmCLGNObXd7yb6EyGA9AExR4htzE3IWmkILrUAUgcxR4hpzGW4%2BNSEnd4BUgkxR4hhzOnDm795i6EPAKmFmCPEMObsJi74gWpALUD6gpgjxC7mrIfH1HzLqG8AJULMEWIXc7V%2F8GA%2B3x5ASRBzhNjF3IuH%2FfGQFSANEHOEWMXcoqoej9cy6RlAqRBzhOZmn8aiW5ch%2FbZFsegYQLkQc4SGlmQRc9rCxTLWY9AvgJIh5ggxiTkP96xlHDEbEyBtEHOEWFy0quxH7u8jea8ASoeYI8TiEYT9Bh3VDjcA6RNijhCLmJv85G9sUwCQZog5QtLHnKGES9bO2KYAIM0Qc4QkjzlVqb3uU6TtEoAPiDlCkj9pLVqieDNM8gIggJgjJHXMmdZ%2FX4JqZ3qAdAsxR0jamNOVKDd9g4T9AfAEMUdI2phzLmalwUNWADKIOULSxlzjK8seS9gdAFcQc4QkjbkyW4s9PChddwB8QcwRkjTmxn1vUmHzBwBCiDlCEsacqcQz7RupOgPgD2KOkHQxpx52bOQrifoC4BFijpB0MedwvdVgiboC4BJijpBUMafT2G8c3VGSrgA4hZgjJFXM2U%2B%2FWUOSjgC4hZgjJFHMqWr1eIY5XgBUEHOEnh4IkKKbaZrhsdjIC4AKYo5QRIgEMWf4UZe3yxrL9wPANcQcISliTjf6Rd4sFu8FgHeIOUKnTi22eB8m44QrwRbvBYB3iDlCO%2B9YOua0ofPybMemrADUEHOELP%2Bk1ZD15DELdwGQLiDmCFl%2BLwhTp3%2BeWLgLgHQBMUeoe6yFY87h%2BxKvWlm2C4D0ATFHqN3DSIu2b%2BXVfMAci%2FYAkF4g5gitOHHCks3rsv%2FpmamnJXsASDcQc4TObNpuwdZVdhPdnlqwfYD0BDFHaGycJWOuzaK%2Fn1mweYB0BTFHaN9ZS07CKuDV5Y4FmwdIVxBzhCwac%2FNa3%2B2HvR8ARIKYI9S2tMVizqrdvVEvLdU4QPqDmCPUJsdJC7WsLRi8qriF2gZIjxBzhCwXc2V8ag%2F1s1DbAOkRYo6QpWJOH1aj2yrs%2FQAgIsQcIUvFnE2P2tYWaRgg3ULMEbLUk9Y%2BY2%2Fet0jDAOkWYo6QhWKuWMiAcAwlARAVYo6QRWJOF%2FZgxgzs%2FQAgLsQcoeE2FpjsZW2cPCkXTuYAxIWYI2SJmLPqlbX2K9FbBUjvEHOELBBzul%2FUblFiNwoAiDlC4secNrz0t5EYMQcgOsQcoTOLxV5W0%2B6G%2B1yRmwQAATFHTPTVg43WLUKbi9skAMRDzBESPeamZevthoesABaAmCMk8iLpOhvfqn9UE7NFAEiCmCMk7iMIw%2Bh6phoitgcA%2F0LMERI35tzjfJ1EbA4APoKYIyRqzGmWX5kaLF5zAPAxxBwhUWNur6mXeI0BwH8h5giJGHP6Rz3aR4jVGAB8CjFHSMSYK%2BpRO2CwWI0BwKcQc4TEiznj3IN1Y0RqCwA%2Bh5gjJFbMaR%2BZrHZhWDCABSHmCIkVc85aox%2FO5QAsCTFHaGycODE3JlulJ6I0BABfgZgjJNIi6S7FXMJ6itEQAHwNYo6QSDG38tYk7P0AYFmIOUKixJxm7M01rjiZA7AsxBwhMWLOYHU60yERagGA5CDmCIkRc%2B6PVzjcEaEWAEgOYo4Q%2FZNWXbi9zgWzHwAsDjFHiH7cnPXyE3VFKQUAkoWYI0Qfc74Bp7BdIYAEEHOEqGOu2MG7xQ%2BKUwsAJAcxR4hyLwidvnD9piFiFQMAyUDMEaLb2UsbmneulWi1AEByEHOE6sVGUhyt9rctjwn7ANJAzBFq95Ai5vS%2FBk2LE68WAEgOYo4QTcxpxxlsRCwFAJKFmCNEE3P6oz%2BOFbEUAEgWYo4QRcy5h1uX6CBmLQCQHMQcIfKY81C3%2FPaxqLUAQHIQc4TIY853%2BA7ftaLWAgDJQcwRiu1%2Bgeg4lc0NH9yYA5ASYo5QyDKimNPru96pKnYtAJAcxBwhwpizWeobh1XRASSFmCNEGHPVynbGQpoA0kLMESKKOU2pZ%2Bpi2HsaQFqIOUJEMddG028QUg5AYog5Qt2vp31AiZ9TwZ%2BCLVALACQHMUco7ePmdGGlQm7fs0gxAJAMxByhtMecR5vQUhYpBQCShZgjlPaYa9PG9p1FSgGAZCHmCKV5Wc1Ogx%2BN62mZWgAgOYg5QmlcJF0bOniXerSligGAZCDmCKVxZy%2F1tiqNs3S0VDEAkAzEHKG0xZyVbdvOTyxWCwAkBzFHKE0xp2vs9zLccrUAQHIQc4TSEnPax%2Fev%2FoAJ%2BwCMIOYIpSXmDJm%2F7WLBUgAgWYg5QmmIOesKI39rZclaACA5iDlCaYi5PEUnDcaIOQBmEHOEUh9zuUOedMOIOQB2EHOEzmxKXczpHtXK39jCtQBAchBzhFI7C0JdxJQP6wUDsISYI5TKmFPlnZX3gaVrAYDkIOYIpTLmGgeF37V0KQCQLMQcodTFnG5YmbFYFR2ALcQcoVTF3CK7DXfwkBWAMcQcodTEnMkYbXwrQS0AkBzEHKGUY06rLrmmF2Y%2FADCHmCOU8vBgD13ZctOwxBwAc4g5QinGnEeberucpKkFAJKDmCOUYsw5VDm5WppSACBZiDlCKd6by3VvVDVpSgGAZCHmCNUrkmzMadZkGZoTy5IAyAFijlD368ltYOg8%2FlDXCMlqAYDkIOYIhSy7kMxX3a7sqL5WsloAIDmIOUJzs0%2F76te0Nm%2BmrPSTsBgASAZijtDQkl%2BNOV347HtVpKwFAJKDmCN06tTir31J3eHBHwelrAUAkoOYIxQREvC1L%2BXJMu%2B1lKUAQLIQc4Ta6792wlZr8b0s9yStBQCSg5gjtHXFra985fcl%2FSStBACSh5gjNCPLvC9%2B3qpd%2BYGY5AUgJ4g5QleOfzHmVOMvdX8pdS0AkBzEHKGvxJz74psSFwIAKUDMEfpKzJWfFCt1JQCQPMQcoS%2Fem7Mf2%2F1H7HADIDOIOUJfetLqoTZ0xog5ALlBzBF6M%2BrzcXONm%2BnKY%2FoDgNwg5gh9HnMG3YgdPe8wKQYAkoGYI%2FRZzOkran47zKYWAEgOYo7QZ3Na7YOvr8DW0wAyhJgj9PTAf2JOFz5pYxRSDkCOEHOEPlmIST1s63hWpQBAshBzhD5ZVtNn6c1QVqUAQLIQc4T%2Bu0h60YwN%2FsSq6ADyhJgjFNv93y1vtOELZ%2FTUMywGAJKBmCPU7uG%2FGxiqfM%2FP7YdJXgAyhZgj9FHMeRQuXvoVy1oAIDmIOUIfxdyY1g%2BRcgDyhZgj9G%2FM6Tu2KIiZrADyhZgj9OERhG5mu55ne7ItBgCSgZgjFLIsMeas2h1f9JxxLQCQHMQcoaSY09k8OfCMdS0AkBzEHKGkWRALft3VFVNZAWQNMUcoYeq%2BPtzuD7yDADKHH1JC7fUHBcHu13c1WBcCAClAzBFK2PLGs6ftO9aFAEAKEHOE4jcwdAu%2Bme0e60IAIAWIOUIzjkWH5S48DXs%2FAMgeYo5Q6FGTwet4LkzYB5A9xByhdtV3VesQzboKAEgZYo7QsbyPr9djXQQApAJijlDXdjsy44oVQAkQc4QWX9Ih5QAUATFHaPaQMNYlAECqIOYAgHOIOQDgHGIOADiHmAMAziHmAIBziDkA4BxiDgA4h5gDAM79v506kAEAAAAY5G99j68g0hwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAOc0Bc5oD5jQHzGkOmNMcMKc5YE5zwJzmgDnNAXOaA%2BY0B8xpDpjTHDCnOWBOc8Cc5oA5zQFzmgPmNAfMaQ6Y0xwwpzlgTnPAnOaAuQBnvTcx2S%2F5QAAAAABJRU5ErkJggg%3D%3D%22%20width%3D%221254%22%20height%3D%221254%22%2F%3E%3C%2Fsvg%3E%0A";
|