From 98b1c46a247cb472355468274ec920e4feb392b7 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Mon, 31 Aug 2026 09:00:26 +0000 Subject: [PATCH 01/11] docs: prototype interactive sandbox diagram --- assets/css/interactive-diagram.css | 374 ++++++++++++++++++ assets/css/style.css | 1 + assets/js/interactive-diagram.js | 300 ++++++++++++++ content/manuals/ai/sandboxes/architecture.md | 8 + .../diagrams/credential-injection.yaml | 149 +++++++ .../_partials/interactive-diagram-script.html | 4 + layouts/_shortcodes/interactive-diagram.html | 77 ++++ .../interactive-diagram.markdown.md | 13 + layouts/baseof.html | 4 + 9 files changed, 930 insertions(+) create mode 100644 assets/css/interactive-diagram.css create mode 100644 assets/js/interactive-diagram.js create mode 100644 content/manuals/ai/sandboxes/diagrams/credential-injection.yaml create mode 100644 layouts/_partials/interactive-diagram-script.html create mode 100644 layouts/_shortcodes/interactive-diagram.html create mode 100644 layouts/_shortcodes/interactive-diagram.markdown.md diff --git a/assets/css/interactive-diagram.css b/assets/css/interactive-diagram.css new file mode 100644 index 000000000000..4cfcc1bb85f1 --- /dev/null +++ b/assets/css/interactive-diagram.css @@ -0,0 +1,374 @@ +.interactive-diagram { + --diagram-accent: var(--color-blue-500); + --diagram-accent-soft: var(--color-blue-50); + --diagram-border: var(--color-gray-200); + --diagram-ink: var(--color-gray-900); + --diagram-muted: var(--color-gray-600); + overflow: hidden; + border: 1px solid var(--diagram-border); + border-radius: 0.5rem; + background: white; + color: var(--diagram-ink); +} + +.dark .interactive-diagram { + --diagram-accent: var(--color-blue-300); + --diagram-accent-soft: var(--color-blue-950); + --diagram-border: var(--color-gray-700); + --diagram-ink: var(--color-gray-100); + --diagram-muted: var(--color-gray-400); + background: var(--color-gray-950); +} + +.interactive-diagram__header { + padding: 1.25rem 1.5rem 1rem; + border-bottom: 1px solid var(--diagram-border); +} + +.interactive-diagram__title, +.interactive-diagram__description, +.interactive-diagram__step-title, +.interactive-diagram__step-body, +.interactive-diagram__state { + margin: 0; +} + +.interactive-diagram__title { + color: var(--diagram-ink); + font-size: 1rem; + font-weight: 650; +} + +.interactive-diagram__description { + margin-top: 0.25rem; + color: var(--diagram-muted); + font-size: 0.875rem; + line-height: 1.45; +} + +.interactive-diagram__stage { + overflow-x: auto; + background-color: var(--diagram-accent-soft); + background-image: + linear-gradient( + to right, + color-mix(in srgb, var(--diagram-border) 28%, transparent) 1px, + transparent 1px + ), + linear-gradient( + to bottom, + color-mix(in srgb, var(--diagram-border) 28%, transparent) 1px, + transparent 1px + ); + background-size: 24px 24px; +} + +.interactive-diagram__svg { + display: block; + width: 100%; + min-width: 760px; + height: auto; + font-family: "Roboto Flex", sans-serif; +} + +.interactive-diagram__boundary rect { + fill: color-mix(in srgb, white 72%, transparent); + stroke: var(--diagram-border); + stroke-width: 1.5; + stroke-dasharray: 5 5; +} + +.dark .interactive-diagram__boundary rect { + fill: color-mix(in srgb, var(--color-gray-900) 82%, transparent); +} + +.interactive-diagram__boundary--sandbox rect { + stroke: color-mix(in srgb, var(--diagram-accent) 65%, var(--diagram-border)); + stroke-width: 2; +} + +.interactive-diagram__boundary-label { + fill: var(--diagram-muted); + font-family: "Roboto Mono", monospace; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.045em; + text-transform: uppercase; +} + +.interactive-diagram__edge { + stroke: var(--color-gray-400); + stroke-width: 2; + stroke-dasharray: 5 7; + opacity: 0.5; + transition: + opacity 180ms ease, + stroke 180ms ease, + stroke-width 180ms ease; +} + +.interactive-diagram__svg marker path { + fill: context-stroke; +} + +.interactive-diagram__edge.is-active { + stroke: var(--diagram-accent); + stroke-width: 3; + stroke-dasharray: none; + opacity: 1; +} + +.interactive-diagram__node rect { + fill: white; + stroke: var(--diagram-border); + stroke-width: 1.5; + transition: + fill 180ms ease, + stroke 180ms ease, + stroke-width 180ms ease; +} + +.dark .interactive-diagram__node rect { + fill: var(--color-gray-900); +} + +.interactive-diagram__node--context { + opacity: 0.64; +} + +.interactive-diagram__node--secret rect { + fill: var(--color-green-50); + stroke: var(--color-green-400); +} + +.dark .interactive-diagram__node--secret rect { + fill: var(--color-green-950); + stroke: var(--color-green-700); +} + +.interactive-diagram__node.is-active rect { + fill: var(--diagram-accent-soft); + stroke: var(--diagram-accent); + stroke-width: 3; +} + +.interactive-diagram__node-label { + fill: var(--diagram-ink); + font-size: 14px; + font-weight: 650; +} + +.interactive-diagram__node-description { + fill: var(--diagram-muted); + font-size: 11px; +} + +.interactive-diagram__token rect { + fill: var(--diagram-ink); + stroke: white; + stroke-width: 2; + filter: drop-shadow(0 2px 3px rgb(18 21 31 / 0.18)); +} + +.interactive-diagram__token-label { + fill: white; + font-family: "Roboto Mono", monospace; + font-size: 11px; + font-weight: 600; + text-anchor: middle; +} + +.interactive-diagram__status { + min-height: 7.5rem; + padding: 1rem 1.5rem; + border-top: 1px solid var(--diagram-border); + border-bottom: 1px solid var(--diagram-border); + background: white; +} + +.dark .interactive-diagram__status { + background: var(--color-gray-950); +} + +.interactive-diagram__step-copy { + display: grid; + grid-template-columns: 3.5rem 1fr; + gap: 0.75rem; +} + +.interactive-diagram__step-number { + color: var(--diagram-accent); + font-family: "Roboto Mono", monospace; + font-size: 0.75rem; + font-weight: 600; + letter-spacing: 0.04em; +} + +.interactive-diagram__step-title { + color: var(--diagram-ink); + font-size: 0.95rem; + font-weight: 650; +} + +.interactive-diagram__step-body { + margin-top: 0.25rem; + color: var(--diagram-muted); + font-size: 0.85rem; + line-height: 1.45; +} + +.interactive-diagram__state { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.75rem; + margin-left: 4.25rem; + color: var(--diagram-muted); + font-size: 0.78rem; +} + +.interactive-diagram__state code { + padding: 0.05rem 0.35rem; + border-radius: 0.2rem; + background: var(--diagram-accent-soft); + color: var(--diagram-ink); + font-size: 0.75rem; +} + +.interactive-diagram__controls { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.8rem 1rem; +} + +.interactive-diagram__button { + min-height: 2.25rem; + padding: 0.4rem 0.7rem; + border: 1px solid var(--diagram-border); + border-radius: 0.25rem; + background: transparent; + color: var(--diagram-ink); + font-size: 0.78rem; + font-weight: 600; +} + +.interactive-diagram__button:hover:not(:disabled) { + border-color: var(--diagram-accent); + background: var(--diagram-accent-soft); +} + +.interactive-diagram__button:focus-visible, +.interactive-diagram__progress-step:focus-visible { + outline: 3px solid var(--color-blue-focusvisible); + outline-offset: 2px; +} + +.interactive-diagram__button:disabled { + cursor: not-allowed; + opacity: 0.45; +} + +.interactive-diagram__button--play { + min-width: 3.8rem; + border-color: var(--diagram-accent); + color: var(--diagram-accent); +} + +.interactive-diagram__progress { + display: flex; + flex: 1; + justify-content: center; + gap: 0.45rem; +} + +.interactive-diagram__progress-step { + position: relative; + width: 1.75rem; + height: 1.75rem; + padding: 0; + border: 0; + background: transparent; +} + +.interactive-diagram__progress-step::before { + position: absolute; + top: 50%; + left: 50%; + width: 0.6rem; + height: 0.6rem; + border: 1px solid var(--color-gray-400); + border-radius: 50%; + background: transparent; + content: ""; + transform: translate(-50%, -50%); +} + +.interactive-diagram__progress-step.is-active::before { + border-color: var(--diagram-accent); + background: var(--diagram-accent); + box-shadow: 0 0 0 3px var(--color-blue-focus); +} + +.interactive-diagram__fallback { + padding: 1rem 1.5rem; +} + +.interactive-diagram.is-enhanced .interactive-diagram__fallback { + display: none; +} + +.interactive-diagram:not(.is-enhanced) .interactive-diagram__stage, +.interactive-diagram:not(.is-enhanced) .interactive-diagram__status, +.interactive-diagram:not(.is-enhanced) .interactive-diagram__controls { + display: none; +} + +@media (prefers-reduced-motion: no-preference) { + .interactive-diagram__token.is-entering { + animation: interactive-diagram-token 280ms ease-out; + } + + @keyframes interactive-diagram-token { + from { + opacity: 0; + } + } +} + +@media (prefers-reduced-motion: reduce) { + .interactive-diagram__edge, + .interactive-diagram__node rect { + transition: none; + } +} + +@media (max-width: 640px) { + .interactive-diagram__header, + .interactive-diagram__status { + padding-inline: 1rem; + } + + .interactive-diagram__step-copy { + grid-template-columns: 1fr; + gap: 0.35rem; + } + + .interactive-diagram__state { + margin-left: 0; + } + + .interactive-diagram__controls { + flex-wrap: wrap; + } + + .interactive-diagram__progress { + order: 4; + flex-basis: 100%; + padding-top: 0.35rem; + } + + .interactive-diagram__button:last-child { + margin-left: auto; + } +} diff --git a/assets/css/style.css b/assets/css/style.css index f63fc6f5aec2..d1a2620a8fef 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -42,6 +42,7 @@ @import "syntax-dark.css"; @import "syntax-light.css"; @import "components.css"; +@import "interactive-diagram.css"; @import "highlight-github-dark.css"; @variant dark (&:where(.dark, .dark *)); diff --git a/assets/js/interactive-diagram.js b/assets/js/interactive-diagram.js new file mode 100644 index 000000000000..3d429f6739e3 --- /dev/null +++ b/assets/js/interactive-diagram.js @@ -0,0 +1,300 @@ +const svgNamespace = "http://www.w3.org/2000/svg"; + +document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { + const configElement = root.querySelector("[data-interactive-diagram-config]"); + if (!configElement) return; + + let config; + try { + config = JSON.parse(configElement.textContent); + } catch { + return; + } + + const stage = root.querySelector("[data-diagram-stage]"); + if (!stage || !config.canvas || !config.steps?.length) return; + + const state = buildDiagram(stage, config); + const previousButton = root.querySelector("[data-step-previous]"); + const nextButton = root.querySelector("[data-step-next]"); + const playButton = root.querySelector("[data-step-play]"); + const progress = root.querySelector("[data-step-progress]"); + let currentStep = 0; + let playTimer; + + const stepButtons = config.steps.map((step, index) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = "interactive-diagram__progress-step"; + button.setAttribute("aria-label", `Show step ${index + 1}: ${step.label}`); + button.addEventListener("click", () => { + stopPlaying(); + showStep(index); + }); + progress?.append(button); + return button; + }); + + function showStep(index) { + currentStep = Math.max(0, Math.min(index, config.steps.length - 1)); + const step = config.steps[currentStep]; + const activeNodes = new Set(step.activeNodes ?? []); + const activeEdges = new Set(step.activeEdges ?? []); + + state.nodes.forEach((element, id) => { + element.classList.toggle("is-active", activeNodes.has(id)); + }); + state.edges.forEach((element, id) => { + element.classList.toggle("is-active", activeEdges.has(id)); + }); + positionToken(state, config, step.token); + + setText( + root, + "[data-step-number]", + `${String(currentStep + 1).padStart(2, "0")} / ${String(config.steps.length).padStart(2, "0")}`, + ); + setText(root, "[data-step-title]", step.label); + setText(root, "[data-step-body]", step.body); + setText(root, "[data-state-label]", step.stateLabel); + setText(root, "[data-state-value]", step.stateValue); + + if (previousButton) previousButton.disabled = currentStep === 0; + if (nextButton) + nextButton.disabled = currentStep === config.steps.length - 1; + stepButtons.forEach((button, buttonIndex) => { + const selected = buttonIndex === currentStep; + button.classList.toggle("is-active", selected); + button.setAttribute("aria-current", selected ? "step" : "false"); + }); + } + + function stopPlaying() { + window.clearInterval(playTimer); + playTimer = undefined; + playButton?.setAttribute("aria-pressed", "false"); + if (playButton) playButton.textContent = "Play"; + } + + previousButton?.addEventListener("click", () => { + stopPlaying(); + showStep(currentStep - 1); + }); + nextButton?.addEventListener("click", () => { + stopPlaying(); + showStep(currentStep + 1); + }); + playButton?.addEventListener("click", () => { + if (playTimer) { + stopPlaying(); + return; + } + if (currentStep === config.steps.length - 1) showStep(0); + playButton.setAttribute("aria-pressed", "true"); + playButton.textContent = "Pause"; + playTimer = window.setInterval(() => { + if (currentStep === config.steps.length - 1) { + stopPlaying(); + } else { + showStep(currentStep + 1); + } + }, 2600); + }); + + root.classList.add("is-enhanced"); + showStep(0); +}); + +function buildDiagram(stage, config) { + const svg = createSvgElement("svg", { + viewBox: `0 0 ${config.canvas.width} ${config.canvas.height}`, + role: "img", + "aria-label": config.description, + }); + svg.classList.add("interactive-diagram__svg"); + + const definitions = createSvgElement("defs"); + const marker = createSvgElement("marker", { + id: `arrow-${Math.random().toString(36).slice(2)}`, + viewBox: "0 0 10 10", + refX: "8", + refY: "5", + markerWidth: "7", + markerHeight: "7", + orient: "auto-start-reverse", + }); + marker.append(createSvgElement("path", { d: "M 0 0 L 10 5 L 0 10 z" })); + definitions.append(marker); + svg.append(definitions); + + config.boundaries?.forEach((boundary) => { + const group = createSvgElement("g"); + group.classList.add( + "interactive-diagram__boundary", + `interactive-diagram__boundary--${boundary.kind}`, + ); + group.append( + createSvgElement("rect", { + x: boundary.x, + y: boundary.y, + width: boundary.width, + height: boundary.height, + rx: "8", + }), + createSvgText( + boundary.label, + boundary.x + 16, + boundary.y + 25, + "interactive-diagram__boundary-label", + ), + ); + svg.append(group); + }); + + const nodesById = new Map(config.nodes.map((node) => [node.id, node])); + const edges = new Map(); + config.edges.forEach((edge) => { + const from = nodesById.get(edge.from); + const to = nodesById.get(edge.to); + if (!from || !to) return; + const points = edgePoints(from, to, edge.offset); + const line = createSvgElement("line", { + x1: points.x1, + y1: points.y1, + x2: points.x2, + y2: points.y2, + "marker-end": `url(#${marker.id})`, + }); + line.classList.add("interactive-diagram__edge"); + svg.append(line); + edges.set(edge.id, line); + }); + + const nodes = new Map(); + config.nodes.forEach((node) => { + const group = createSvgElement("g"); + group.classList.add( + "interactive-diagram__node", + `interactive-diagram__node--${node.kind}`, + ); + group.append( + createSvgElement("rect", { + x: node.x, + y: node.y, + width: node.width, + height: node.height, + rx: "7", + }), + createSvgText( + node.label, + node.x + 14, + node.y + 29, + "interactive-diagram__node-label", + ), + createSvgText( + node.description, + node.x + 14, + node.y + 51, + "interactive-diagram__node-description", + ), + ); + svg.append(group); + nodes.set(node.id, group); + }); + + const token = createSvgElement("g"); + token.classList.add("interactive-diagram__token"); + const tokenRect = createSvgElement("rect", { height: "28", rx: "14" }); + const tokenText = createSvgText("", 0, 0, "interactive-diagram__token-label"); + token.append(tokenRect, tokenText); + svg.append(token); + stage.append(svg); + + return { nodes, edges, nodesById, token, tokenRect, tokenText }; +} + +function positionToken(state, config, tokenConfig) { + if (!tokenConfig) { + state.token.hidden = true; + return; + } + + let x; + let y; + if (tokenConfig.node) { + const node = state.nodesById.get(tokenConfig.node); + if (node) { + x = node.x + node.width / 2; + y = node.y + node.height / 2; + } + } else if (tokenConfig.edge) { + const edge = config.edges.find( + (candidate) => candidate.id === tokenConfig.edge, + ); + const from = state.nodesById.get(edge?.from); + const to = state.nodesById.get(edge?.to); + if (from && to) { + const points = edgePoints(from, to, edge.offset); + x = (points.x1 + points.x2) / 2; + y = (points.y1 + points.y2) / 2; + } + } + if (x === undefined || y === undefined) return; + + const width = Math.max(70, tokenConfig.label.length * 7.2 + 24); + state.token.hidden = false; + state.token.setAttribute( + "transform", + `translate(${x - width / 2} ${y - 14})`, + ); + state.tokenRect.setAttribute("width", width); + state.tokenText.setAttribute("x", width / 2); + state.tokenText.setAttribute("y", "18"); + state.tokenText.textContent = tokenConfig.label; + state.token.classList.remove("is-entering"); + window.requestAnimationFrame(() => state.token.classList.add("is-entering")); +} + +function edgePoints(from, to, offset = 0) { + const fromCenter = { + x: from.x + from.width / 2, + y: from.y + from.height / 2, + }; + const toCenter = { x: to.x + to.width / 2, y: to.y + to.height / 2 }; + const dx = toCenter.x - fromCenter.x; + const dy = toCenter.y - fromCenter.y; + if (Math.abs(dx) >= Math.abs(dy)) { + return { + x1: dx >= 0 ? from.x + from.width : from.x, + y1: fromCenter.y + offset, + x2: dx >= 0 ? to.x : to.x + to.width, + y2: toCenter.y + offset, + }; + } + return { + x1: fromCenter.x + offset, + y1: dy >= 0 ? from.y + from.height : from.y, + x2: toCenter.x + offset, + y2: dy >= 0 ? to.y : to.y + to.height, + }; +} + +function createSvgElement(name, attributes = {}) { + const element = document.createElementNS(svgNamespace, name); + Object.entries(attributes).forEach(([key, value]) => + element.setAttribute(key, value), + ); + return element; +} + +function createSvgText(value, x, y, className) { + const text = createSvgElement("text", { x, y }); + text.classList.add(className); + text.textContent = value; + return text; +} + +function setText(root, selector, value) { + const element = root.querySelector(selector); + if (element) element.textContent = value; +} diff --git a/content/manuals/ai/sandboxes/architecture.md b/content/manuals/ai/sandboxes/architecture.md index 88c8da4bdebe..fa7462e4b74e 100644 --- a/content/manuals/ai/sandboxes/architecture.md +++ b/content/manuals/ai/sandboxes/architecture.md @@ -61,6 +61,14 @@ proxy also handles [credential injection](configuration/credentials.md). See works and [Default security posture](security/defaults.md) for what is allowed out of the box. +### Follow an authenticated request + +The following diagram shows where Docker Sandboxes checks network policy and +replaces a sentinel credential with the real value. The real credential stays +outside the sandbox throughout the request. + +{{< interactive-diagram src="diagrams/credential-injection.yaml" >}} + ### Upstream proxy The host-side proxy makes its outbound connections using your host's network diff --git a/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml b/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml new file mode 100644 index 000000000000..d93bfbc96847 --- /dev/null +++ b/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml @@ -0,0 +1,149 @@ +title: Follow an authenticated request +description: Step through a request as Docker Sandboxes applies network policy and injects a credential outside the microVM. +canvas: + width: 920 + height: 430 +boundaries: + - id: host + label: Host machine + x: 18 + y: 24 + width: 682 + height: 382 + kind: host + - id: sandbox + label: Sandbox microVM + x: 42 + y: 68 + width: 348 + height: 310 + kind: sandbox +nodes: + - id: agent + label: AI agent + description: Sends the API request + x: 78 + y: 122 + width: 168 + height: 76 + kind: process + - id: docker-engine + label: Docker Engine + description: Private to this sandbox + x: 78 + y: 267 + width: 128 + height: 64 + kind: context + - id: workspace + label: Workspace + description: Shared with the host + x: 226 + y: 267 + width: 128 + height: 64 + kind: context + - id: policy + label: Network policy + description: Checks the destination + x: 446 + y: 106 + width: 176 + height: 70 + kind: control + - id: proxy + label: Host-side proxy + description: Rewrites the auth header + x: 446 + y: 218 + width: 176 + height: 70 + kind: control + - id: credentials + label: Credential store + description: Real token stays here + x: 446 + y: 326 + width: 176 + height: 56 + kind: secret + - id: provider + label: Model provider + description: Receives the request + x: 746 + y: 192 + width: 156 + height: 84 + kind: external +edges: + - id: agent-policy + from: agent + to: policy + - id: policy-proxy + from: policy + to: proxy + - id: credentials-proxy + from: credentials + to: proxy + - id: proxy-provider + from: proxy + to: provider + offset: -7 + - id: provider-proxy + from: provider + to: proxy + offset: 7 +steps: + - label: The agent prepares the request + body: The agent sees a sentinel value instead of the real API credential. + activeNodes: [agent] + token: + node: agent + label: proxy-managed + stateLabel: Auth header inside the sandbox + stateValue: "Bearer proxy-managed" + - label: The request leaves the microVM + body: Outbound HTTP and HTTPS traffic crosses the sandbox boundary through the host network path. + activeNodes: [agent, policy] + activeEdges: [agent-policy] + token: + edge: agent-policy + label: proxy-managed + stateLabel: Credential exposed to the microVM + stateValue: No + - label: Network policy checks the destination + body: The request continues only when an active policy permits the provider domain. + activeNodes: [policy] + activeEdges: [agent-policy] + token: + node: policy + label: allowed + stateLabel: Policy decision + stateValue: Allow destination + - label: The proxy retrieves the credential + body: The host-side proxy resolves the matching credential without copying it into the sandbox. + activeNodes: [proxy, credentials] + activeEdges: [policy-proxy, credentials-proxy] + token: + edge: credentials-proxy + label: host lookup + stateLabel: Credential location + stateValue: Host credential store + - label: The proxy rewrites the header + body: The proxy replaces the sentinel with the real credential after the request has left the microVM. + activeNodes: [proxy, provider] + activeEdges: [proxy-provider] + token: + edge: proxy-provider + label: Bearer •••••• + stateLabel: Auth header sent to the provider + stateValue: Real credential + - label: The response returns + body: The provider response returns through the host proxy to the agent. The credential remains on the host. + activeNodes: [provider, proxy, agent] + activeEdges: [provider-proxy] + token: + edge: provider-proxy + label: 200 OK + stateLabel: Credential after the request + stateValue: Still on the host diff --git a/layouts/_partials/interactive-diagram-script.html b/layouts/_partials/interactive-diagram-script.html new file mode 100644 index 000000000000..84c0ec2fa443 --- /dev/null +++ b/layouts/_partials/interactive-diagram-script.html @@ -0,0 +1,4 @@ +{{ $diagram := resources.Get "js/interactive-diagram.js" + | js.Build (dict "minify" true "targetPath" "interactive-diagram.js") +}} + diff --git a/layouts/_shortcodes/interactive-diagram.html b/layouts/_shortcodes/interactive-diagram.html new file mode 100644 index 000000000000..ce64bde4fd47 --- /dev/null +++ b/layouts/_shortcodes/interactive-diagram.html @@ -0,0 +1,77 @@ +{{- $src := .Get "src" -}} +{{- if not $src -}} + {{- errorf "interactive-diagram shortcode: missing src: %s" .Position -}} +{{- end -}} +{{- $filePath := path.Join .Page.File.Dir $src -}} +{{- if not (fileExists $filePath) -}} + {{- errorf "interactive-diagram shortcode: file %q not found: %s" $filePath .Position -}} +{{- end -}} +{{- $diagram := readFile $filePath | transform.Unmarshal -}} +{{- $id := printf "interactive-diagram-%s-%d" .Page.File.UniqueID .Ordinal -}} +{{- .Page.Store.Set "interactive-diagram" true -}} +
+
+

+ {{ $diagram.title }} +

+

{{ $diagram.description }}

+
+
+
+
+ +
+

+

+
+
+

+ + +

+
+
+ + +
+ +
+
+
    + {{- range $diagram.steps }} +
  1. {{ .label }}: {{ .body }}
  2. + {{- end }} +
+
+ +
diff --git a/layouts/_shortcodes/interactive-diagram.markdown.md b/layouts/_shortcodes/interactive-diagram.markdown.md new file mode 100644 index 000000000000..c50b4317a993 --- /dev/null +++ b/layouts/_shortcodes/interactive-diagram.markdown.md @@ -0,0 +1,13 @@ +{{- $src := .Get "src" -}} +{{- $filePath := path.Join .Page.File.Dir $src -}} +{{- if not (fileExists $filePath) -}} + {{- errorf "interactive-diagram shortcode: file %q not found: %s" $filePath .Position -}} +{{- end -}} +{{- $diagram := readFile $filePath | transform.Unmarshal -}} +{{ $diagram.title }} + +{{ $diagram.description }} + +{{ range $index, $step := $diagram.steps -}} +{{ add $index 1 }}. {{ $step.label }}: {{ $step.body }} +{{ end -}} diff --git a/layouts/baseof.html b/layouts/baseof.html index 823d4a087912..b862091a975e 100644 --- a/layouts/baseof.html +++ b/layouts/baseof.html @@ -95,5 +95,9 @@ {{ with .Store.Get "mermaid" }} {{ partialCached "mermaid-script.html" "-" "-" }} {{ end }} + {{/* Load interactive diagrams only on pages that contain the shortcode */}} + {{ with .Store.Get "interactive-diagram" }} + {{ partialCached "interactive-diagram-script.html" "-" "-" }} + {{ end }} From dcc046b8c6ccb7ba5ffc4bd5c8d83605dbe0a971 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Mon, 31 Aug 2026 09:24:20 +0000 Subject: [PATCH 02/11] docs: improve diagram contrast and motion --- assets/css/interactive-diagram.css | 37 ++++++++++++++----- assets/js/interactive-diagram.js | 59 ++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/assets/css/interactive-diagram.css b/assets/css/interactive-diagram.css index 4cfcc1bb85f1..358cb345f0d0 100644 --- a/assets/css/interactive-diagram.css +++ b/assets/css/interactive-diagram.css @@ -2,8 +2,11 @@ --diagram-accent: var(--color-blue-500); --diagram-accent-soft: var(--color-blue-50); --diagram-border: var(--color-gray-200); + --diagram-structure: var(--color-gray-500); --diagram-ink: var(--color-gray-900); --diagram-muted: var(--color-gray-600); + --diagram-token-bg: var(--color-gray-900); + --diagram-token-fg: white; overflow: hidden; border: 1px solid var(--diagram-border); border-radius: 0.5rem; @@ -15,8 +18,11 @@ --diagram-accent: var(--color-blue-300); --diagram-accent-soft: var(--color-blue-950); --diagram-border: var(--color-gray-700); + --diagram-structure: var(--color-gray-600); --diagram-ink: var(--color-gray-100); --diagram-muted: var(--color-gray-400); + --diagram-token-bg: var(--color-blue-300); + --diagram-token-fg: var(--color-gray-950); background: var(--color-gray-950); } @@ -73,7 +79,7 @@ .interactive-diagram__boundary rect { fill: color-mix(in srgb, white 72%, transparent); - stroke: var(--diagram-border); + stroke: var(--diagram-structure); stroke-width: 1.5; stroke-dasharray: 5 5; } @@ -97,7 +103,7 @@ } .interactive-diagram__edge { - stroke: var(--color-gray-400); + stroke: var(--diagram-structure); stroke-width: 2; stroke-dasharray: 5 7; opacity: 0.5; @@ -120,7 +126,7 @@ .interactive-diagram__node rect { fill: white; - stroke: var(--diagram-border); + stroke: var(--diagram-structure); stroke-width: 1.5; transition: fill 180ms ease, @@ -133,7 +139,20 @@ } .interactive-diagram__node--context { - opacity: 0.64; + color: var(--diagram-muted); +} + +.interactive-diagram__node--context rect { + fill: var(--color-gray-50); + stroke-dasharray: 3 3; +} + +.dark .interactive-diagram__node--context rect { + fill: var(--color-gray-900); +} + +.interactive-diagram__node--context .interactive-diagram__node-label { + fill: var(--diagram-muted); } .interactive-diagram__node--secret rect { @@ -164,14 +183,14 @@ } .interactive-diagram__token rect { - fill: var(--diagram-ink); - stroke: white; + fill: var(--diagram-token-bg); + stroke: var(--diagram-token-fg); stroke-width: 2; filter: drop-shadow(0 2px 3px rgb(18 21 31 / 0.18)); } .interactive-diagram__token-label { - fill: white; + fill: var(--diagram-token-fg); font-family: "Roboto Mono", monospace; font-size: 11px; font-weight: 600; @@ -245,7 +264,7 @@ .interactive-diagram__button { min-height: 2.25rem; padding: 0.4rem 0.7rem; - border: 1px solid var(--diagram-border); + border: 1px solid var(--diagram-structure); border-radius: 0.25rem; background: transparent; color: var(--diagram-ink); @@ -297,7 +316,7 @@ left: 50%; width: 0.6rem; height: 0.6rem; - border: 1px solid var(--color-gray-400); + border: 1px solid var(--diagram-structure); border-radius: 50%; background: transparent; content: ""; diff --git a/assets/js/interactive-diagram.js b/assets/js/interactive-diagram.js index 3d429f6739e3..779122e61cb2 100644 --- a/assets/js/interactive-diagram.js +++ b/assets/js/interactive-diagram.js @@ -210,10 +210,21 @@ function buildDiagram(stage, config) { svg.append(token); stage.append(svg); - return { nodes, edges, nodesById, token, tokenRect, tokenText }; + return { + nodes, + edges, + nodesById, + token, + tokenRect, + tokenText, + tokenAnimation: undefined, + }; } function positionToken(state, config, tokenConfig) { + state.tokenAnimation?.remove(); + state.tokenAnimation = undefined; + if (!tokenConfig) { state.token.hidden = true; return; @@ -221,6 +232,7 @@ function positionToken(state, config, tokenConfig) { let x; let y; + let travelStart; if (tokenConfig.node) { const node = state.nodesById.get(tokenConfig.node); if (node) { @@ -235,8 +247,10 @@ function positionToken(state, config, tokenConfig) { const to = state.nodesById.get(edge?.to); if (from && to) { const points = edgePoints(from, to, edge.offset); - x = (points.x1 + points.x2) / 2; - y = (points.y1 + points.y2) / 2; + const progress = 0.62; + x = points.x1 + (points.x2 - points.x1) * progress; + y = points.y1 + (points.y2 - points.y1) * progress; + travelStart = { x: points.x1, y: points.y1 }; } } if (x === undefined || y === undefined) return; @@ -252,7 +266,44 @@ function positionToken(state, config, tokenConfig) { state.tokenText.setAttribute("y", "18"); state.tokenText.textContent = tokenConfig.label; state.token.classList.remove("is-entering"); - window.requestAnimationFrame(() => state.token.classList.add("is-entering")); + if (travelStart && !prefersReducedMotion()) { + animateTokenTravel(state, travelStart, { x, y }, width); + } else { + window.requestAnimationFrame(() => + state.token.classList.add("is-entering"), + ); + } +} + +function animateTokenTravel(state, from, to, width) { + const animation = createSvgElement("animateTransform", { + attributeName: "transform", + type: "translate", + from: `${from.x - width / 2} ${from.y - 14}`, + to: `${to.x - width / 2} ${to.y - 14}`, + dur: "650ms", + calcMode: "spline", + keyTimes: "0;1", + keySplines: "0.2 0 0 1", + }); + state.token.prepend(animation); + state.tokenAnimation = animation; + if (typeof animation.beginElement === "function") { + animation.beginElement(); + window.setTimeout(() => { + animation.remove(); + if (state.tokenAnimation === animation) state.tokenAnimation = undefined; + }, 700); + } else { + animation.remove(); + state.tokenAnimation = undefined; + } +} + +function prefersReducedMotion() { + return ( + window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false + ); } function edgePoints(from, to, offset = 0) { From 803d0e08941017c635b514879390602a73867b5e Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Mon, 31 Aug 2026 10:20:43 +0000 Subject: [PATCH 03/11] docs: refine diagram layout and controls --- assets/css/interactive-diagram.css | 44 +++++-- assets/js/interactive-diagram.js | 111 ++++++++++++++---- .../diagrams/credential-injection.yaml | 3 + layouts/_shortcodes/interactive-diagram.html | 44 ++++--- 4 files changed, 154 insertions(+), 48 deletions(-) diff --git a/assets/css/interactive-diagram.css b/assets/css/interactive-diagram.css index 358cb345f0d0..1fb9ebf264f6 100644 --- a/assets/css/interactive-diagram.css +++ b/assets/css/interactive-diagram.css @@ -255,10 +255,20 @@ } .interactive-diagram__controls { + display: grid; + grid-template-areas: "transport progress"; + grid-template-columns: auto auto; + align-items: center; + justify-content: center; + gap: 1.25rem; + padding: 0.8rem 1rem; +} + +.interactive-diagram__transport { display: flex; + grid-area: transport; align-items: center; gap: 0.5rem; - padding: 0.8rem 1rem; } .interactive-diagram__button { @@ -289,14 +299,30 @@ } .interactive-diagram__button--play { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.45rem; min-width: 3.8rem; border-color: var(--diagram-accent); color: var(--diagram-accent); } +.interactive-diagram__timer { + min-width: 2.3rem; + padding-left: 0.45rem; + border-left: 1px solid + color-mix(in srgb, var(--diagram-accent) 55%, transparent); + font-family: "Roboto Mono", monospace; + font-size: 0.65rem; + font-variant-numeric: tabular-nums; + font-weight: 600; + text-align: right; +} + .interactive-diagram__progress { display: flex; - flex: 1; + grid-area: progress; justify-content: center; gap: 0.45rem; } @@ -378,16 +404,18 @@ } .interactive-diagram__controls { - flex-wrap: wrap; + grid-template-areas: + "progress" + "transport"; + grid-template-columns: minmax(0, 1fr); + gap: 0.35rem; } .interactive-diagram__progress { - order: 4; - flex-basis: 100%; - padding-top: 0.35rem; + padding-bottom: 0.2rem; } - .interactive-diagram__button:last-child { - margin-left: auto; + .interactive-diagram__transport { + justify-content: center; } } diff --git a/assets/js/interactive-diagram.js b/assets/js/interactive-diagram.js index 779122e61cb2..50b62c212b56 100644 --- a/assets/js/interactive-diagram.js +++ b/assets/js/interactive-diagram.js @@ -18,9 +18,14 @@ document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { const previousButton = root.querySelector("[data-step-previous]"); const nextButton = root.querySelector("[data-step-next]"); const playButton = root.querySelector("[data-step-play]"); + const playLabel = root.querySelector("[data-step-play-label]"); + const playTimerLabel = root.querySelector("[data-step-play-timer]"); const progress = root.querySelector("[data-step-progress]"); + const autoplayDuration = config.autoplayDuration ?? 4000; let currentStep = 0; let playTimer; + let countdownFrame; + let stepDeadline; const stepButtons = config.steps.map((step, index) => { const button = document.createElement("button"); @@ -70,10 +75,41 @@ document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { } function stopPlaying() { - window.clearInterval(playTimer); + window.clearTimeout(playTimer); + window.cancelAnimationFrame(countdownFrame); playTimer = undefined; + countdownFrame = undefined; + stepDeadline = undefined; playButton?.setAttribute("aria-pressed", "false"); - if (playButton) playButton.textContent = "Play"; + if (playLabel) playLabel.textContent = "Play"; + if (playTimerLabel) { + playTimerLabel.hidden = true; + playTimerLabel.textContent = ""; + } + } + + function scheduleNextStep() { + window.clearTimeout(playTimer); + window.cancelAnimationFrame(countdownFrame); + stepDeadline = now() + autoplayDuration; + playTimer = window.setTimeout(() => { + if (currentStep === config.steps.length - 1) { + stopPlaying(); + } else { + showStep(currentStep + 1); + scheduleNextStep(); + } + }, autoplayDuration); + updateCountdown(); + } + + function updateCountdown() { + if (!playTimer || !stepDeadline || !playTimerLabel) return; + const remaining = Math.max(0, stepDeadline - now()); + playTimerLabel.textContent = `${(Math.ceil(remaining / 100) / 10).toFixed(1)}s`; + if (remaining > 0) { + countdownFrame = window.requestAnimationFrame(updateCountdown); + } } previousButton?.addEventListener("click", () => { @@ -91,14 +127,9 @@ document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { } if (currentStep === config.steps.length - 1) showStep(0); playButton.setAttribute("aria-pressed", "true"); - playButton.textContent = "Pause"; - playTimer = window.setInterval(() => { - if (currentStep === config.steps.length - 1) { - stopPlaying(); - } else { - showStep(currentStep + 1); - } - }, 2600); + if (playLabel) playLabel.textContent = "Pause"; + if (playTimerLabel) playTimerLabel.hidden = false; + scheduleNextStep(); }); root.classList.add("is-enhanced"); @@ -230,14 +261,13 @@ function positionToken(state, config, tokenConfig) { return; } - let x; - let y; + const width = Math.max(70, tokenConfig.label.length * 7.2 + 24); + let point; let travelStart; if (tokenConfig.node) { const node = state.nodesById.get(tokenConfig.node); if (node) { - x = node.x + node.width / 2; - y = node.y + node.height / 2; + point = nodeTokenPoint(node, width, tokenConfig.placement); } } else if (tokenConfig.edge) { const edge = config.edges.find( @@ -247,19 +277,18 @@ function positionToken(state, config, tokenConfig) { const to = state.nodesById.get(edge?.to); if (from && to) { const points = edgePoints(from, to, edge.offset); - const progress = 0.62; - x = points.x1 + (points.x2 - points.x1) * progress; - y = points.y1 + (points.y2 - points.y1) * progress; - travelStart = { x: points.x1, y: points.y1 }; + const progress = tokenConfig.progress ?? 0.5; + const labelOffset = tokenConfig.labelOffset ?? 0; + point = edgeTokenPoint(points, progress, labelOffset); + travelStart = edgeTokenPoint(points, 0, labelOffset); } } - if (x === undefined || y === undefined) return; + if (!point) return; - const width = Math.max(70, tokenConfig.label.length * 7.2 + 24); state.token.hidden = false; state.token.setAttribute( "transform", - `translate(${x - width / 2} ${y - 14})`, + `translate(${point.x - width / 2} ${point.y - 14})`, ); state.tokenRect.setAttribute("width", width); state.tokenText.setAttribute("x", width / 2); @@ -267,7 +296,7 @@ function positionToken(state, config, tokenConfig) { state.tokenText.textContent = tokenConfig.label; state.token.classList.remove("is-entering"); if (travelStart && !prefersReducedMotion()) { - animateTokenTravel(state, travelStart, { x, y }, width); + animateTokenTravel(state, travelStart, point, width); } else { window.requestAnimationFrame(() => state.token.classList.add("is-entering"), @@ -275,6 +304,40 @@ function positionToken(state, config, tokenConfig) { } } +function nodeTokenPoint(node, width, placement = "bottom") { + const gap = 8; + const halfHeight = 14; + const positions = { + top: { + x: node.x + node.width / 2, + y: node.y - gap - halfHeight, + }, + right: { + x: node.x + node.width + gap + width / 2, + y: node.y + node.height / 2, + }, + bottom: { + x: node.x + node.width / 2, + y: node.y + node.height + gap + halfHeight, + }, + left: { + x: node.x - gap - width / 2, + y: node.y + node.height / 2, + }, + }; + return positions[placement] ?? positions.bottom; +} + +function edgeTokenPoint(points, progress, labelOffset) { + const dx = points.x2 - points.x1; + const dy = points.y2 - points.y1; + const length = Math.hypot(dx, dy) || 1; + return { + x: points.x1 + dx * progress - (dy / length) * labelOffset, + y: points.y1 + dy * progress + (dx / length) * labelOffset, + }; +} + function animateTokenTravel(state, from, to, width) { const animation = createSvgElement("animateTransform", { attributeName: "transform", @@ -306,6 +369,10 @@ function prefersReducedMotion() { ); } +function now() { + return window.performance?.now?.() ?? Date.now(); +} + function edgePoints(from, to, offset = 0) { const fromCenter = { x: from.x + from.width / 2, diff --git a/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml b/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml index d93bfbc96847..2d2fcf847906 100644 --- a/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml +++ b/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml @@ -1,5 +1,6 @@ title: Follow an authenticated request description: Step through a request as Docker Sandboxes applies network policy and injects a credential outside the microVM. +autoplayDuration: 4000 canvas: width: 920 height: 430 @@ -100,6 +101,7 @@ steps: token: node: agent label: proxy-managed + placement: bottom stateLabel: Auth header inside the sandbox stateValue: "Bearer proxy-managed" - label: The request leaves the microVM @@ -118,6 +120,7 @@ steps: token: node: policy label: allowed + placement: top stateLabel: Policy decision stateValue: Allow destination - label: The proxy retrieves the credential diff --git a/layouts/_shortcodes/interactive-diagram.html b/layouts/_shortcodes/interactive-diagram.html index ce64bde4fd47..3cd67435cb6c 100644 --- a/layouts/_shortcodes/interactive-diagram.html +++ b/layouts/_shortcodes/interactive-diagram.html @@ -40,29 +40,37 @@

- - +
+ + + +
-
    From cba82a21e247fb8949bac9542e2474ca9f0001a3 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Mon, 31 Aug 2026 10:44:36 +0000 Subject: [PATCH 04/11] docs: simplify diagram playback and state --- assets/css/interactive-diagram.css | 92 +++++----- assets/js/interactive-diagram.js | 157 +++++++++++++----- .../diagrams/credential-injection.yaml | 18 +- layouts/_shortcodes/interactive-diagram.html | 73 ++++---- 4 files changed, 204 insertions(+), 136 deletions(-) diff --git a/assets/css/interactive-diagram.css b/assets/css/interactive-diagram.css index 1fb9ebf264f6..c2bcfa38f439 100644 --- a/assets/css/interactive-diagram.css +++ b/assets/css/interactive-diagram.css @@ -27,10 +27,18 @@ } .interactive-diagram__header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: start; + gap: 1rem; padding: 1.25rem 1.5rem 1rem; border-bottom: 1px solid var(--diagram-border); } +.interactive-diagram__header-copy { + min-width: 0; +} + .interactive-diagram__title, .interactive-diagram__description, .interactive-diagram__step-title, @@ -237,38 +245,44 @@ } .interactive-diagram__state { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; margin-top: 0.75rem; margin-left: 4.25rem; - color: var(--diagram-muted); + padding-left: 0.65rem; + border-left: 2px solid var(--diagram-accent); + color: var(--diagram-ink); font-size: 0.78rem; + line-height: 1.45; } -.interactive-diagram__state code { - padding: 0.05rem 0.35rem; - border-radius: 0.2rem; - background: var(--diagram-accent-soft); - color: var(--diagram-ink); - font-size: 0.75rem; +.interactive-diagram__autoplay-progress { + height: 3px; + overflow: hidden; + background: color-mix(in srgb, var(--diagram-structure) 28%, transparent); +} + +.interactive-diagram__autoplay-progress-bar { + display: block; + width: 100%; + height: 100%; + background: var(--diagram-accent); + transform: scaleX(0); + transform-origin: left center; } .interactive-diagram__controls { display: grid; - grid-template-areas: "transport progress"; - grid-template-columns: auto auto; + grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; - justify-content: center; - gap: 1.25rem; + gap: 0.75rem; padding: 0.8rem 1rem; } -.interactive-diagram__transport { - display: flex; - grid-area: transport; - align-items: center; - gap: 0.5rem; +.interactive-diagram__controls > .interactive-diagram__button:first-child { + justify-self: end; +} + +.interactive-diagram__controls > .interactive-diagram__button:last-child { + justify-self: start; } .interactive-diagram__button { @@ -299,30 +313,14 @@ } .interactive-diagram__button--play { - display: inline-flex; - align-items: center; - justify-content: center; - gap: 0.45rem; min-width: 3.8rem; border-color: var(--diagram-accent); color: var(--diagram-accent); } -.interactive-diagram__timer { - min-width: 2.3rem; - padding-left: 0.45rem; - border-left: 1px solid - color-mix(in srgb, var(--diagram-accent) 55%, transparent); - font-family: "Roboto Mono", monospace; - font-size: 0.65rem; - font-variant-numeric: tabular-nums; - font-weight: 600; - text-align: right; -} - .interactive-diagram__progress { display: flex; - grid-area: progress; + min-width: 0; justify-content: center; gap: 0.45rem; } @@ -365,10 +363,15 @@ .interactive-diagram:not(.is-enhanced) .interactive-diagram__stage, .interactive-diagram:not(.is-enhanced) .interactive-diagram__status, +.interactive-diagram:not(.is-enhanced) .interactive-diagram__autoplay-progress, .interactive-diagram:not(.is-enhanced) .interactive-diagram__controls { display: none; } +.interactive-diagram:not(.is-enhanced) .interactive-diagram__button--play { + display: none; +} + @media (prefers-reduced-motion: no-preference) { .interactive-diagram__token.is-entering { animation: interactive-diagram-token 280ms ease-out; @@ -404,18 +407,19 @@ } .interactive-diagram__controls { - grid-template-areas: - "progress" - "transport"; - grid-template-columns: minmax(0, 1fr); - gap: 0.35rem; + gap: 0.25rem; + padding-inline: 0.75rem; } .interactive-diagram__progress { - padding-bottom: 0.2rem; + gap: 0.05rem; + } + + .interactive-diagram__progress-step { + width: 1.5rem; } - .interactive-diagram__transport { - justify-content: center; + .interactive-diagram__button { + padding-inline: 0.55rem; } } diff --git a/assets/js/interactive-diagram.js b/assets/js/interactive-diagram.js index 50b62c212b56..3ad545440284 100644 --- a/assets/js/interactive-diagram.js +++ b/assets/js/interactive-diagram.js @@ -19,13 +19,21 @@ document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { const nextButton = root.querySelector("[data-step-next]"); const playButton = root.querySelector("[data-step-play]"); const playLabel = root.querySelector("[data-step-play-label]"); - const playTimerLabel = root.querySelector("[data-step-play-timer]"); const progress = root.querySelector("[data-step-progress]"); - const autoplayDuration = config.autoplayDuration ?? 4000; + const autoplayProgress = root.querySelector("[data-step-autoplay-progress]"); + const autoplayProgressBar = root.querySelector( + "[data-step-autoplay-progress-bar]", + ); + const autoplayDuration = Math.max( + 100, + Number(config.autoplayDuration) || 4000, + ); let currentStep = 0; let playTimer; - let countdownFrame; - let stepDeadline; + let progressFrame; + let stepStartedAt; + let elapsedInStep = 0; + let isPlaying = false; const stepButtons = config.steps.map((step, index) => { const button = document.createElement("button"); @@ -33,7 +41,7 @@ document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { button.className = "interactive-diagram__progress-step"; button.setAttribute("aria-label", `Show step ${index + 1}: ${step.label}`); button.addEventListener("click", () => { - stopPlaying(); + resetPlayback(); showStep(index); }); progress?.append(button); @@ -61,8 +69,10 @@ document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { ); setText(root, "[data-step-title]", step.label); setText(root, "[data-step-body]", step.body); - setText(root, "[data-state-label]", step.stateLabel); - setText(root, "[data-state-value]", step.stateValue); + setText(root, "[data-step-state]", step.state); + + elapsedInStep = 0; + setAutoplayProgress(0); if (previousButton) previousButton.disabled = currentStep === 0; if (nextButton) @@ -72,64 +82,119 @@ document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { button.classList.toggle("is-active", selected); button.setAttribute("aria-current", selected ? "step" : "false"); }); + updatePlayButton(); } - function stopPlaying() { + function clearPlaybackTimers() { window.clearTimeout(playTimer); - window.cancelAnimationFrame(countdownFrame); + window.cancelAnimationFrame(progressFrame); playTimer = undefined; - countdownFrame = undefined; - stepDeadline = undefined; - playButton?.setAttribute("aria-pressed", "false"); - if (playLabel) playLabel.textContent = "Play"; - if (playTimerLabel) { - playTimerLabel.hidden = true; - playTimerLabel.textContent = ""; + progressFrame = undefined; + } + + function resetPlayback() { + clearPlaybackTimers(); + isPlaying = false; + elapsedInStep = 0; + setAutoplayProgress(0); + updatePlayButton(); + } + + function pausePlaying() { + if (!isPlaying) return; + elapsedInStep = Math.min( + autoplayDuration, + elapsedInStep + now() - stepStartedAt, + ); + clearPlaybackTimers(); + isPlaying = false; + setAutoplayProgress(elapsedInStep / autoplayDuration); + updatePlayButton(); + } + + function startPlaying() { + if ( + currentStep === config.steps.length - 1 && + elapsedInStep >= autoplayDuration + ) { + showStep(0); } + isPlaying = true; + stepStartedAt = now(); + updatePlayButton(); + scheduleCurrentStep(); } - function scheduleNextStep() { - window.clearTimeout(playTimer); - window.cancelAnimationFrame(countdownFrame); - stepDeadline = now() + autoplayDuration; - playTimer = window.setTimeout(() => { - if (currentStep === config.steps.length - 1) { - stopPlaying(); - } else { - showStep(currentStep + 1); - scheduleNextStep(); - } - }, autoplayDuration); - updateCountdown(); + function scheduleCurrentStep() { + clearPlaybackTimers(); + const remaining = Math.max(0, autoplayDuration - elapsedInStep); + playTimer = window.setTimeout(completeCurrentStep, remaining); + updateAutoplayProgress(); + } + + function completeCurrentStep() { + elapsedInStep = autoplayDuration; + setAutoplayProgress(1); + if (currentStep === config.steps.length - 1) { + clearPlaybackTimers(); + isPlaying = false; + updatePlayButton(); + return; + } + showStep(currentStep + 1); + stepStartedAt = now(); + scheduleCurrentStep(); } - function updateCountdown() { - if (!playTimer || !stepDeadline || !playTimerLabel) return; - const remaining = Math.max(0, stepDeadline - now()); - playTimerLabel.textContent = `${(Math.ceil(remaining / 100) / 10).toFixed(1)}s`; - if (remaining > 0) { - countdownFrame = window.requestAnimationFrame(updateCountdown); + function updateAutoplayProgress() { + if (!isPlaying) return; + const elapsed = Math.min( + autoplayDuration, + elapsedInStep + now() - stepStartedAt, + ); + setAutoplayProgress(elapsed / autoplayDuration); + if (elapsed < autoplayDuration) { + progressFrame = window.requestAnimationFrame(updateAutoplayProgress); + } + } + + function setAutoplayProgress(value) { + const progressValue = Math.max(0, Math.min(1, value)); + if (autoplayProgressBar) { + autoplayProgressBar.style.transform = `scaleX(${progressValue})`; + } + autoplayProgress?.setAttribute( + "aria-valuenow", + String(Math.round(progressValue * 100)), + ); + } + + function updatePlayButton() { + playButton?.setAttribute("aria-pressed", String(isPlaying)); + if (!playLabel) return; + if (isPlaying) { + playLabel.textContent = "Pause"; + } else if ( + currentStep === config.steps.length - 1 && + elapsedInStep >= autoplayDuration + ) { + playLabel.textContent = "Restart"; + } else { + playLabel.textContent = "Play"; } } previousButton?.addEventListener("click", () => { - stopPlaying(); + resetPlayback(); showStep(currentStep - 1); }); nextButton?.addEventListener("click", () => { - stopPlaying(); + resetPlayback(); showStep(currentStep + 1); }); playButton?.addEventListener("click", () => { - if (playTimer) { - stopPlaying(); - return; - } - if (currentStep === config.steps.length - 1) showStep(0); - playButton.setAttribute("aria-pressed", "true"); - if (playLabel) playLabel.textContent = "Pause"; - if (playTimerLabel) playTimerLabel.hidden = false; - scheduleNextStep(); + if (isPlaying) pausePlaying(); + else startPlaying(); }); root.classList.add("is-enhanced"); diff --git a/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml b/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml index 2d2fcf847906..297794004c4a 100644 --- a/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml +++ b/content/manuals/ai/sandboxes/diagrams/credential-injection.yaml @@ -102,8 +102,7 @@ steps: node: agent label: proxy-managed placement: bottom - stateLabel: Auth header inside the sandbox - stateValue: "Bearer proxy-managed" + state: The sandbox sees only the proxy-managed sentinel. - label: The request leaves the microVM body: Outbound HTTP and HTTPS traffic crosses the sandbox boundary through the host network path. activeNodes: [agent, policy] @@ -111,8 +110,7 @@ steps: token: edge: agent-policy label: proxy-managed - stateLabel: Credential exposed to the microVM - stateValue: No + state: The real credential remains outside the microVM. - label: Network policy checks the destination body: The request continues only when an active policy permits the provider domain. activeNodes: [policy] @@ -121,8 +119,7 @@ steps: node: policy label: allowed placement: top - stateLabel: Policy decision - stateValue: Allow destination + state: Network policy allows the provider destination. - label: The proxy retrieves the credential body: The host-side proxy resolves the matching credential without copying it into the sandbox. activeNodes: [proxy, credentials] @@ -130,8 +127,7 @@ steps: token: edge: credentials-proxy label: host lookup - stateLabel: Credential location - stateValue: Host credential store + state: The credential stays in the host credential store. - label: The proxy rewrites the header body: The proxy replaces the sentinel with the real credential after the request has left the microVM. activeNodes: [proxy, provider] @@ -139,8 +135,7 @@ steps: token: edge: proxy-provider label: Bearer •••••• - stateLabel: Auth header sent to the provider - stateValue: Real credential + state: The provider receives the rewritten authorization header. - label: The response returns body: The provider response returns through the host proxy to the agent. The credential remains on the host. activeNodes: [provider, proxy, agent] @@ -148,5 +143,4 @@ steps: token: edge: provider-proxy label: 200 OK - stateLabel: Credential after the request - stateValue: Still on the host + state: The credential remains on the host after the response. diff --git a/layouts/_shortcodes/interactive-diagram.html b/layouts/_shortcodes/interactive-diagram.html index 3cd67435cb6c..f95670145168 100644 --- a/layouts/_shortcodes/interactive-diagram.html +++ b/layouts/_shortcodes/interactive-diagram.html @@ -16,10 +16,20 @@ aria-labelledby="{{ $id }}-title" >
    -

    - {{ $diagram.title }} -

    -

    {{ $diagram.description }}

    +
    +

    + {{ $diagram.title }} +

    +

    {{ $diagram.description }}

    +
    +

-

- - -

+

+ +
+
-
- - - -
+
+
    From 9218d525afec96047aa93483187506e20709b8b7 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:19:13 +0000 Subject: [PATCH 05/11] docs: split sandbox security model into focused diagrams The existing security illustration combines every trust boundary and integration into one dense view. Replace it with three small, code-defined sequences covering Docker execution, direct workspace edits, and remote MCP calls. --- .../diagrams/direct-workspace-edit.yaml | 98 ++++++++++++++ .../ai/sandboxes/diagrams/mcp-tool-call.yaml | 124 ++++++++++++++++++ .../diagrams/private-docker-engine.yaml | 88 +++++++++++++ .../manuals/ai/sandboxes/security/_index.md | 24 +++- 4 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 content/manuals/ai/sandboxes/diagrams/direct-workspace-edit.yaml create mode 100644 content/manuals/ai/sandboxes/diagrams/mcp-tool-call.yaml create mode 100644 content/manuals/ai/sandboxes/diagrams/private-docker-engine.yaml diff --git a/content/manuals/ai/sandboxes/diagrams/direct-workspace-edit.yaml b/content/manuals/ai/sandboxes/diagrams/direct-workspace-edit.yaml new file mode 100644 index 000000000000..e20d3b69ab5e --- /dev/null +++ b/content/manuals/ai/sandboxes/diagrams/direct-workspace-edit.yaml @@ -0,0 +1,98 @@ +title: Follow a workspace edit in direct mode +description: Step through a file edit as it becomes visible in the working tree on the host. +autoplayDuration: 4000 +canvas: + width: 820 + height: 330 +boundaries: + - id: host + label: Host machine + x: 16 + y: 20 + width: 788 + height: 290 + kind: host + - id: sandbox + label: Sandbox microVM + x: 38 + y: 58 + width: 500 + height: 230 + kind: sandbox +nodes: + - id: agent + label: AI agent + description: Edits a file + x: 68 + y: 126 + width: 150 + height: 72 + kind: process + - id: mounted-workspace + label: Mounted workspace + description: View inside the sandbox + x: 330 + y: 126 + width: 174 + height: 72 + kind: context + - id: host-worktree + label: Host working tree + description: Same files, not a copy + x: 612 + y: 86 + width: 166 + height: 72 + kind: context + - id: host-tools + label: Host tools + description: Editor, Git, and builds + x: 612 + y: 218 + width: 166 + height: 72 + kind: process +edges: + - id: agent-workspace + from: agent + to: mounted-workspace + - id: workspace-worktree + from: mounted-workspace + to: host-worktree + offset: -10 + - id: worktree-tools + from: host-worktree + to: host-tools +steps: + - label: The agent edits a file + body: In direct mode, the agent writes to the workspace mounted in the microVM. + activeNodes: [agent] + token: + node: agent + label: app.py + placement: bottom + state: The write begins inside the sandbox. + - label: The write reaches the mounted workspace + body: The mounted path gives the sandbox a read-write view of the project directory. + activeNodes: [agent, mounted-workspace] + activeEdges: [agent-workspace] + token: + edge: agent-workspace + label: write app.py + state: Direct mode does not create a private workspace copy. + - label: The host working tree changes + body: The mounted workspace and the host working tree refer to the same files. + activeNodes: [mounted-workspace, host-worktree] + activeEdges: [workspace-worktree] + token: + edge: workspace-worktree + label: same write + state: The edit is visible on the host as soon as it is written. + - label: Host tools see the update + body: Editors, Git, and build tools on the host observe the changed working tree. + activeNodes: [host-worktree, host-tools] + activeEdges: [worktree-tools] + token: + edge: worktree-tools + label: changed + state: Review workspace changes before running modified code on the host. diff --git a/content/manuals/ai/sandboxes/diagrams/mcp-tool-call.yaml b/content/manuals/ai/sandboxes/diagrams/mcp-tool-call.yaml new file mode 100644 index 000000000000..d7ccb2c7559e --- /dev/null +++ b/content/manuals/ai/sandboxes/diagrams/mcp-tool-call.yaml @@ -0,0 +1,124 @@ +title: Follow a remote MCP tool call +description: Step through a tool call as the host-side gateway applies policy and credentials. +autoplayDuration: 4000 +canvas: + width: 860 + height: 340 +boundaries: + - id: host + label: Host machine + x: 16 + y: 20 + width: 642 + height: 300 + kind: host + - id: sandbox + label: Sandbox microVM + x: 38 + y: 60 + width: 210 + height: 230 + kind: sandbox +nodes: + - id: agent + label: AI agent + description: Calls an MCP tool + x: 68 + y: 135 + width: 150 + height: 72 + kind: process + - id: gateway + label: MCP gateway + description: Runs on the host + x: 286 + y: 135 + width: 150 + height: 72 + kind: control + - id: policy + label: MCP policy + description: Checks tool access + x: 478 + y: 66 + width: 150 + height: 72 + kind: control + - id: credentials + label: Credential store + description: OAuth token stays here + x: 478 + y: 232 + width: 150 + height: 64 + kind: secret + - id: server + label: Remote MCP server + description: Runs outside the host + x: 694 + y: 135 + width: 150 + height: 72 + kind: external +edges: + - id: agent-gateway + from: agent + to: gateway + - id: gateway-policy + from: gateway + to: policy + - id: credentials-gateway + from: credentials + to: gateway + - id: gateway-server + from: gateway + to: server + offset: -8 + - id: server-gateway + from: server + to: gateway + offset: 8 +steps: + - label: The agent requests an MCP tool + body: The supported agent sends a tool call to the gateway endpoint exposed inside the sandbox. + activeNodes: [agent, gateway] + activeEdges: [agent-gateway] + token: + edge: agent-gateway + label: tools/call + labelOffset: -54 + state: The agent communicates with the host-side gateway, not the remote server directly. + - label: The gateway checks MCP policy + body: When organization governance is active, policy determines whether the tool is available. + activeNodes: [gateway, policy] + activeEdges: [gateway-policy] + token: + edge: gateway-policy + label: allowed? + labelOffset: -30 + state: Only tools permitted by active MCP policy can run. + - label: The gateway uses the host credential + body: The gateway retrieves the server credential without copying its value into the microVM. + activeNodes: [gateway, credentials] + activeEdges: [credentials-gateway] + token: + edge: credentials-gateway + label: OAuth + labelOffset: -18 + state: The OAuth credential remains in the host credential store. + - label: The gateway invokes the remote server + body: The approved tool call leaves the host through the gateway with the required authentication. + activeNodes: [gateway, server] + activeEdges: [gateway-server] + token: + edge: gateway-server + label: authenticated call + state: The remote server receives the call from the gateway. + - label: The result returns to the agent + body: The tool result travels back through the gateway to the agent inside the sandbox. + activeNodes: [server, gateway, agent] + activeEdges: [server-gateway, agent-gateway] + token: + edge: server-gateway + label: tool result + state: The result crosses into the microVM; the host credential does not. diff --git a/content/manuals/ai/sandboxes/diagrams/private-docker-engine.yaml b/content/manuals/ai/sandboxes/diagrams/private-docker-engine.yaml new file mode 100644 index 000000000000..73f0a6a9fa96 --- /dev/null +++ b/content/manuals/ai/sandboxes/diagrams/private-docker-engine.yaml @@ -0,0 +1,88 @@ +title: Run Docker inside the microVM +description: Step through a Docker command and see why it never reaches the host Docker daemon. +autoplayDuration: 4000 +canvas: + width: 820 + height: 310 +boundaries: + - id: host + label: Host machine + x: 16 + y: 20 + width: 788 + height: 270 + kind: host + - id: sandbox + label: Sandbox microVM + x: 38 + y: 58 + width: 590 + height: 210 + kind: sandbox +nodes: + - id: agent + label: AI agent + description: Runs a Docker command + x: 70 + y: 126 + width: 150 + height: 72 + kind: process + - id: sandbox-engine + label: Docker Engine + description: Private to the sandbox + x: 264 + y: 126 + width: 150 + height: 72 + kind: control + - id: container + label: Container + description: Runs inside the microVM + x: 458 + y: 126 + width: 150 + height: 72 + kind: process + - id: host-engine + label: Host Docker Engine + description: Not reachable + x: 636 + y: 126 + width: 150 + height: 72 + kind: context +edges: + - id: agent-engine + from: agent + to: sandbox-engine + - id: engine-container + from: sandbox-engine + to: container +steps: + - label: The agent runs a Docker command + body: The command starts inside the sandbox microVM. + activeNodes: [agent] + token: + node: agent + label: docker run + placement: bottom + state: The command originates inside the microVM. + - label: The sandbox engine receives the command + body: The agent connects to the Docker Engine dedicated to this sandbox. + activeNodes: [agent, sandbox-engine] + activeEdges: [agent-engine] + token: + edge: agent-engine + label: Docker API + labelOffset: -54 + state: No command reaches the host Docker daemon. + - label: The engine creates the container + body: The private engine creates and runs the container inside the same microVM. + activeNodes: [sandbox-engine, container] + activeEdges: [engine-container] + token: + edge: engine-container + label: create + labelOffset: 54 + state: The container and its engine remain inside the sandbox boundary. diff --git a/content/manuals/ai/sandboxes/security/_index.md b/content/manuals/ai/sandboxes/security/_index.md index 5af246133cf2..32ac133bfeb3 100644 --- a/content/manuals/ai/sandboxes/security/_index.md +++ b/content/manuals/ai/sandboxes/security/_index.md @@ -53,7 +53,29 @@ sandbox VM. An agent can invoke the tools those servers expose through the MCP gateway, subject to MCP policies when organization governance is active. Treat local MCP servers as trusted host integrations. -![Sandbox security model showing the hypervisor boundary between the sandbox VM and the host system. The workspace directory is shared read-write. The agent process, Docker Engine, packages, and VM filesystem are inside the VM. Host filesystem, processes, Docker Engine, and network are outside the VM and not accessible. A proxy enforces allow/deny policies and injects credentials into outbound requests.](../images/sbx-security.png) +The following sequences focus on three distinct interactions across these trust +boundaries. To follow an outbound request through network policy and credential +injection, see [Architecture](../architecture.md#follow-an-authenticated-request). + +### Run Docker inside the microVM + +The sandbox Docker Engine handles the agent's commands without exposing the +Docker daemon on the host. + +{{< interactive-diagram src="../diagrams/private-docker-engine.yaml" >}} + +### Follow a workspace edit + +Direct mode mounts the workspace read-write, so the agent and host tools see +the same working tree. + +{{< interactive-diagram src="../diagrams/direct-workspace-edit.yaml" >}} + +### Follow an MCP tool call + +The host-side MCP gateway brokers access from the agent to a remote MCP server. + +{{< interactive-diagram src="../diagrams/mcp-tool-call.yaml" >}} ## Isolation layers From 5f9a63a9f16cb48f70f3f3f048718edababe12c6 Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:20:48 +0000 Subject: [PATCH 06/11] docs: remove superseded sandbox security image The security model page now uses focused interactive sequences, leaving the previous composite image unreferenced. Remove the obsolete image so unused-media validation passes. --- .../ai/sandboxes/images/sbx-security.png | Bin 121269 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 content/manuals/ai/sandboxes/images/sbx-security.png diff --git a/content/manuals/ai/sandboxes/images/sbx-security.png b/content/manuals/ai/sandboxes/images/sbx-security.png deleted file mode 100644 index 0f86c436c507b31068307a68caa5f03d1b40ab0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121269 zcmdqIXEa=4+cvy0dheYfdI>?aXhZZ)M2jFq?=?E3_aQ`y-bD$bcOrW4Eg?G5qId5| za^KIhzO}x;?~m8A_RQM*y3TeSXSwzwTunt58-oG^003-xx#t=HfB^vjltDBk_?^^u znj-*!c&n!LLYkb60e**!jDei&(Vr{)3j;anzw7<|{oURD?d{#o&CS*I&E?hg#l_Xx z*~Q7}#mUL(@1wKdzmE_V$169dGUY-rCw(-#S=X-&POXegu8faQ4vsC4jEwgU z{}>z``PRSC*EiVNJJ;Ra*Y<7ZYiD;;=Tv)pS3}!GUF&#rbNiRZ(T4iwiu&QQn!)Po z`cG8@RaLcx6@8z}zvP$ol$3nV{`9S|ur#CaYieFcZf;?Ac0p2hTVhsgMn>-Y^yajW z*|DjONy+I^N%g=z5Rq6HmQb7UJ|!f+2Dm_g!9E1Rz63;9Mn%OV?jrg|d=3wb@d_&o z4vBdeTpAP<;TH4>X%X24`pMrv)Wbi;$-l_nr@-4g5P2BY#xu{u1M2b4@147^rF)K< zYqqP4H%bS(sdJ{2lZUZGy1ktnY8~cl+mF`P&S;fb`qn8rmdWU)*cKKJ+U7}SZ*8^S zen2n4HZ`%(gfNRJi^+70+0f9Y;J)U{7)sg z_|%dx@OXK}ICxc}u?Y!fso1&Yxw(Y{u?bnZWFK=1y5bPCa7Zz8K$zJj+1Q1Mg=yH? zo?7CPGO~)XF!LJVlQS?0Gci7)XB5;Rpd@(=W_a{ijPwzh{wWAFhnlq>sqSn8?W($w(O#R7bGj>|b@!P|>^x@Od?D zV;jac;H3Y5^5 z7fS?LOq>jf=(R`8p$aXFnv&zti-qQ2KJoyt80d4*)G)?4X06Ne<=Qun=y0!)o0RD;tt*SF5rmhdSNe7$_3}ITd0C4wZcVQlCvjYH!=~9uZ zef}W82u}a)yqnYn1i(nzN0E6GS*WEC5om8b$`D&Sp%bUkfC{`6ggcC>|ojVP~99?t=t`B|bYaFxX#_GzkVl#L<0riHZTuU1kDNRhi0X zEl+1ID`Po1*@n_xV{ifh9Lin}QUy=?4mM-o zcS(yM3onws_cgOn0D%1paOjh6*>wlQlQ#vn4>#(;xljhB1z#0YL<2A#8F0 z@JtRF9WX*I1|tAOdDIL*yA)?{*pW)G3Gz@l4xTxrp|e$WHxQCKOT2<|FlBY zrOGSsv>lRdd1-r_Yvx{!R=jPrZqj?nuX9PH@x}f7##Mq|i9jn>@o%GCV%{mp8!{)e zT$M77cN*oiIGe@;D>GUow^rF|;r3Qy_TE-OrbqqLimhdmc1@M+`m1Q)ZmP)2AM zpV{l7jP9Bf7eG377?GIKA2}CAGi-ZnUSG>{FYqG&x+VpM^ zZ{l-&E0XC(b^gPPOZDP|z8pl%-lTiq{|%{Hl(qu9RI&QtkcrIt9xn167x>#5FR};L zUXb3Xk|ByJ8eI_Hz-w48hFR$=_5P31QPDpTF{JIqgwfMWTuB7zNQVbiw z?99l&2x!8}YPNctT_sBTm(k&y-}GTRdHNeRq#vDq+RsK+`1Gh+OWc*JQ1?mu5$*kJ z7zCN_x@cW+&kTK##aAPbWZy(V-dwHpLi#!h9W0 zfi4h|=r2EKIU$vfTd~)!+}cck?KFTT8I>r<5?Z@xBM!7^?visXVQRYs;?J+)Y4A21j6c05Ra*yO*R}R zd4OGE1>=zOsihTSx%Os9Q*Gva&m4p&8J7RJw=(hW*2D>GLv5ETLMW@(ls4k@>yO)z z>bMO@;QqU#m-|h3TbLX;ln9?xVVNZ|42@3hIdmB+_v0O!Bx~zo@F)q06)ltqC)R~{ z;O+UAq@u2_f>-ssp{Z|@`Bhwx>zrtU*t2trYLp`@%Tk3__jtp*Q_PuShfE5DudbWf zt!UE+2aksiE{EQ)OCF$Z%NBT=p`Hy+IVE;XS(&CnHc70y=>{SPH=SWmS7i!V+rE3t zs7;Lc2;fi>nu7QYt_{)YCM3*h;2=q=xImkuqIeK>tzVn6X!=8dw11I!Q^MLcy6G+{c^h({q}a&k@KgB%twknz8}Y~aIXcg>;4n=8ReHX^7e zof8LyP0RhDBYCU?403c_+D=qI1p=d-&^7jXEoETENG&yNrrllW1S}8&_|~;9=3drG zrH}6uW4#LUHVM5S*HWffgI&_Cy=;E^>Z&Qi(5ZPKXY{Lef~h_U;0Fpj%@eK(^7ON9 zoziLI>X!Asklp(X;io_TMjyt}6lWP~ForQfaII_FY)yb3f3MfR0?D*ffWf1RCbWmn z6QqPp_DZ@S=32lrB-d>Q+@wCiO@b8g*z^+#*aWtK6>ljUEl~X~88r7nJ`%-{enK8S z{vm#aUTHn`T52z@-z!_W>~Q(mIl5;pwh!O<10w3w|e1B zx_Y7Sp0Za=MTX;6Y$j!Pv>N)4-Uqz@B`Y4-OoWvCsSPEa(?|i5RmX}5+>_koARqO3 zDFTP8+*e=a5Wya(G{D5kGr`_v@gIooZD3psZZ@KJdtdhZ9GVqoOEqCRW+(Wi=QK4N zGqF5n)f0ACht#y<=jVu$W_>5aZE|v5cyBNkejs62d=b&UVeEE{p3C&+@tg;4`Of9; zX9$!w#cdf>9gv!ZlHw?sOqzmuISJ;XpI|c#6q)UgkPty`*lX6sp9KPjW^y1}cshpo z7XYICSk~wFIz zo2Orp5Ie2SG_Ovg3wMR&7CNnEkORty1J1{11_$y^vceNAM%~BVzpO>#?|UO|eFIQpWhy#oNtj6WrqZQoyJfM30@o1v; zT^!^N+^2et?p1JGFUpAWyZYWq-*aBo-(`D7k_hMqFexhJ8w9&_~hv$ zL)0o(1cuelEmp&(MIpCHtnk?0&EvX2%cTG*QOttxA4fJN`&QEu}McPG1GZ}XUwO@Fe0A&`kl&Gej$)a9Xk}EuG_1@Dt$(Z*?o>dPGS528o8_?WFcqwHOTSC032VSeYalSa z0bACBQb-aFIeYL9-#i{XMBs`_b<@;*^rKFt)F-E~-3x5}i6X^0Sm&FHnj&2d5zXd@ zPJtCmL*EBeb~srE>5*!PHpVnq95EC3Id#O9#Om7Wz=U-6rGbQ^I99C&1dE-fihW1u z1j1^oJBGlJDZ7=z*8(oJ+{s0V9g-7irveEXY^v%{3M`QjJejvARo(eF>GN?SN z8P;YI&6{hg-3T|f=>4$d4?|VkK0u)bc~XDb|AS;P8of;lxHJ_9fya6%}>Fdm@FjA`I#qBxvO5w=z6VLA3Gg_km3a9r@t0 z(Qi1}8XVYX(@Y4ONS2Yr@9|>v#vk3rFrWKwiXHqI4_Ul!InUS>VJ6P2ATP=cB+6lz z)CTDT8bt=$rX%FOmXqsw+CEEykBRz@BA*=xt7IgL^J;Fb1q;3JmvpSY?%vy2TkKzM zGxsp7!XH+ZcotBlG1E`P<0Y9V5Q;2dk}lE} z$Q8)o;H`*J;_#BH?_)Qmw$*P-CWlb(^^+I84|Z;1xGo4D z)wfYDCfZ)>dsj7GU1{kas$%XRkAIe8dq>nrKa2Puv52)`PJ_7bup{|xnN-eaXGlKg zZ69Bmj`xF!pVumLifE8oy#4vY7t?wmu2Wc!?qTcS&BK_d&cz>NW%Q>NzwcfgsJJg} zS~KB#c+K^Ld5Qiby#jeF!U&URfMhqRQCDET>Z}Pe?On@N-{Y%7vF5`bIEjBgYd_G_ zx`mx`uP)d4*VBhBMwp|-b0`f3^^nFMUjwl-YkByFI{lX@#o!1#qDUEyH?zvuv$cIoi;QEe!1uY!FOn4e%dqYNWqbDrtp;kX_d>7-Cl~ z4i%iCcmNt<8LeapO9bj*Y@}pqHGdV`|%vh~R%C4gHkXTFx?z zvfWlSs$>Dfpfr6JTuHmnlE-~ud%jN(*+IwF zFb))nkG-V@2S4!G2s32hPx!`5*&}KoSKQ7!g@MXB+V>Ua6*HG8|-}Rb-Cx*U42&~T^ zY|`g`RSV?d8N`{@f~VmTxRDHe5hs&svh+Hg?k`BoeSZvkzzffxGYoE2n?k{Ze?t=z zbrmnVTb=9u;kf6(W2E*KzO=iDJ)#jp!%Sn6iBenvp$Yi&$e&ZtJPrY_2){80uz{pu zR}pWB4kDy;=3x#E{`>~An(k`45={+x0~m{s<$TddmK?dI7zk_Jev>gr3)?mIWfJcn4K ze&R}Gn~rEHlsvU^79&gyJ6K`e6B9pc6a9JXz`susG+4B|NR%p&i3NIN)pt5OYX`Bb zZikzDdhW~yXAM!i@7_2|XGT|Mj*x(Zouq9L^c_}Vkn*jz>&wq4l%`(0b>b|)VH4NZ ztFAV*q)+dvM8{%&MwX|N+TJOu{*I*4^P_0m8ki!Pqzkr&TM@=-^k8~UP(#klE~V+U z`jN+IIeuF(^#{#XS&Sq(jP;0FsO$nFe5o9Kao8+$jdCrm^8T6FnLG8;E0pM44e8K} zSwk-K-Sf?e$Br6g^CLa;56d4UuSI-FV()K-$Ygx&f_8~G1@Yp=J=}3E} z9)Qh5%o>%}tvpDa{IHI%t~$Xt*7PjXfpSm)g;DO|>b$!lKAIqtOxFME(arqB`Wa{Br-IR=)~EfIIpIa~827 z=iMK<(c)9oE|}NzE>S)WIZ<$<{~L!KXhTjarRn|b0omi+7knCFe|QmF^Pm)8znz3H zgMV@kJn;T6-~Kr*QEq|Ikke1OwqZT`ThV*h{#lQ2|0{JIegFT>Do|@z!Yr6n1w-|p ztP146R-5XNX0o%@L713=KiWt8{9!We4={GEXAjN>y;3j3cBBS2IO`#`e(9~`rz{@*s?SylFC6n=oUn(8LEA{B;lQp%94$b{Dc2rUKg97L!R~z0 z*CJ%*p>-#eYXe}frg`$~4!GX`<@E~@?UMwrz^HuftDd`rBhh~(x@rCoU%+oLINS{W1I@$cuue5*W@K5%65@>%L>+J}X?0SV1-U9|F+ntId;`E`be`+*Km}#u&0J z_K@Ly@EnJG&Z?c@7s%m<`TjrbhP#UpiuDjo>V};72Y{D`-*L>;TWFVW5wbr2lW2VZ zh|q6@sbB+>qx%!hMN2q!|M&}I-+5MQ-W zE-abkG(N_1AQLe zpFn?b>wSo2AF-(Tz41Ym!2(JSx9{hQY@y2D?bY;u_&O~@N-Kq0R{>#3L$s$3&xk(A+zWOk6kIWq(O$A#ha1kOCKd+ zLIkDfV*9>VeYGGVEqh}VWn4T&)}V5JPn4 z*CTsa<4*){J_h)+(EO(e8fXS_2nKPS=scGb+dBdgH_xn5HImaJP&AUMSVgua;8Fk^ zgulv?jWJ7AV5AZl+3ZzGoMd)p7PoL3uS=mk=nLQk^s^k2u?*CcN6M67jrz#}Woz#Q z0?K!xwk}Mv3$NIV+w(nEZqdG*+8p zZkJ{HLI*r5nuqiC`;r3k@*;=VBKrvg^YVP0#p1k=K3?U#*P;)O4YPXm#ij#=^HZ_w zE@nNITUxk$kAgjNk|@j`sS(uA=>AWwSuI(pJJZtg#IR(o?CKD)A)95GF3`cm4AD; zWH(OSX|n5FldyEhU~3%slz!yEd$GR(`mLXx)XC0=>~u(MQ$#QJ_yg^o#-NV<$WY7a zn@l@xjLUxfkF8**oNpBRx^*76_Zaq*X9wbk4l@mnf~6UF(c+wgtIgO3_^*SzeE(#K zO^1&$=6D8JHJ?*^g5>M0EYx(D)XV(vrwrA%-=cm@@`U)rl3fuWh@Ha1h-He&i20wd zX-J0k(}fxbS2&ecIFT9SIQJQ=2(7&o3V!wB)h7Phe_M^K4x9V$tMfz0!NXyhayPE;VBk+_ezj`f!?#mpVvMS*ysXP{6f|h&YFCRfh!#`0&oWp^ z@tSz11R593DAo^qb$D=1r1W0&82!MCq)B!JGVIEZ=Ft$kP-bF(zFMp-M~IvFg*E#r zC|%;%Knp3%E%6kEV46I=@ergehYI*}??#c+yb4S2PF)g7#zR6C!wZwZQZw0wX zB-NF9a#P+gsDY;E#G1270cFirE}BxCbmt?*JhCNXWM$9%g!VIuZ9J%|Xxzw)b~tMm zlC605mV(xnLi*;nNU*F0MMxr>G2<4g-&HYJR?hq#6Jq3&?RAHiTt{P$Q7!n7&9}L8 z(CZzr`#rCpAJUs;@OihG48|smrC?~TlMP^Q?FtUn=#0re(0Yxk5vxJnvrX!sLBUED zob|@AlnTk>Q^Hg3oGS+_*LeS5jBHZ+@gyCGk0=xEEtc&{`j{~L&bnNCM*LI9UHo5- zRO!iw^~crVOu-!YWAq)vO8pEv8`YxLP&Av8P?!{6qec|IcjUzhpYb)N-cfKV?$}}X zGRGUAt_}Vf)n<_+2b0JO17XsKg@vIJpwZK1fji_S@bUy5z&a3^1QR_GD#QUMk3uel zCWr^Y(qHbPbj~$r!GP0u*Yg<~-P`Uj&*Vgmph`iIVp5OG< zd0YRvr8rLRk2rU)21aq%%O6RfJD#Ns+9=n?1k~kud$tRFROx9kK182Bl&Ti3O*F`~ z3V3{~2QoWN8hy=^3&YAERl|sW759^*8Osi$N#DD}H#^S^zH@tvw>D7y(KEOL`w*L$ z^XLt2bA0+YtbTbUSY?bsz!?P7EHT7bF1SKdd|!)r-embc>qa)qxzfh?5f?{)Ogj3r zp!_|8yjlGENa5knkoNLkX*}YdG}4v)UrmC3qX^EA-+o_Ss(xvi7ki@>Dd{!Y*tiNP zu^&WHp2tH6X>{U-pAOW%XB;yLuE4u6voCw39~W~?e^KSW2tAs5gW60o1-@x$I)B$P zi;m=m@)+nyt9b<+OoT5M0-&p63^0Qej5NI~v$U z=|_T2<}LjuA44MY>C`$xQ%0xyca^*$WlYACIL7K|3(ws2z>lSjK}f>Nk8=+KU0TtL zwa{|TczIDYJ)g(!CHNx{3j}hnXz(+r)IoGKEZZj0>84`OzJ}&da?_v zVt`>5F(z9>!k6VF<%KRAS?tS3a3Om*QQlg2?T;uPE)(Phh3UPI!6d8@3>WHRxofG^ zC%Ua{h9$wYwf4ahy+wNKfTaa0-S54qjZm$mDr5-EoMyI0YL@T0%jW!9YH(QM8JB2< zZIi0Rpx&<3N2PfhytHQQyLcFj?kJ`24eeAF+U|Eh-q7X8v-|kTCDP?`r7{gUC@kfo z{1|=zNJeG{L8?bqjs%NO1*l~mHbY=s6TV))^M1{4bEuSh#7%}}yJa0!q0h@2Pa+Sp zMufRPXXVwmX;_LQi8Z*R8kgBjtuVu76v))N8Fe^M6#O;UbOo%s`vxAgD)bj!nZgLa z&)sI7>AzsczWfpXHLr%Hwmq9#nFLN|oGW*Zf$~dZmGl?6!zE4tkJTI|4Xrsh2pYvlAa=DS$57CnoX@v2g?U8P z&+(@i{GH;7zs@~J;V@}|tPu=jMy_39tzPHRml||7 z2%QwJZi~XJ-pKaO_}x|(Jd=8%an*+K7nIRS?4CSgpT+;ymWK`iW0E4I@4aGQ()JU^ zj`AvITNzQkCRW{K=z3ax^h(E4{F7^QUB0^yafr}$UXR2W2I8KBPTX0Evd*Q8O^xgE zYdxoV>DoL&Nu4?2Ojdv73`DpylkH!|X+jCl!gbFXR*-q-&J%57aBW+Wb;1GSI zdgLi73(ZHp;?@^6hP3--ziga6>z|E^;^~Sy-I~<4IhtGEo@d;Aw`f|duM5rW>bM;^ z*llvQ{#vDcJ3>m?&!c*$tb0AJvMq~Qjq&Myh%2@eR@}*ca1AAD2)o2D*`WwGB^}2M zKESIyIRRWVWVMt1mBhFU!&rLx1TyD>361^Hib!(K4$i6&ZNZ?@<`%&1@UbhrMc+2q z%sy>7ddc;pNGKdq#@~0eiCMeq{kc4KxFd*nZxdn#YogI9Vb4EoJgYXmW?_JPRF6C& z6%e9fu@*rA)}F*dJCbG>CNh79Z%w`AdMj0n#+`MaflB7q0(c=;EIZBfZIv~DXL;rj zZYxyC8TozkQIODTGi?>IaQO6|T1HSzv*hzVZn^~p9e4U1k$3j40>e*?zoneX(z(v> zb3pn(5LM>dxM@F<>%PaUIpR&K!q=J9Icn1T@J!0cHQeD zh7b91!y+B3nN`Fk?#ubnY)k&O1V(<2)zOT`qtJHE0j_R8w*xWVdX<*2tNV2JV{r^i z&60N#ovPWOqnL`a8C9a0z9pjj@#u0Z`Let^6U0BoGb5?^L*cOy%XU!0lv=+}#fJxGuXk z-W_k$i*#D)g2=z_xK`9vw<1(iD^+3HdVxX4j#5|P~XrGl=xojc6Wg8 zU)q`r`!i?!nVCGZ7oi)^VNZ8eJ|BRdYjZK=OIS z^Lfl4ktH8#;hEl!w@NWr67rlOdCuC;Br5K~*cbFfR?}Nk))p+qt8MO!A;%>RYYH5| z{&QWAbEzY1OL5eIeuA}wz}xd_%oE*`AT4B@fU~WYe)u0#U_bf(rjY&aY)A+5BxCNa zkhtlme&X<8I;9zJrzhh$ty0f|zWHZd94(@8zZj2_^W4;4!a|_C7AFsRIwZ8Ef%?HP z@L6?&H?IU5%9McfBF>NPwSF?GFQe0>RqE!yu0!c;YLNVm+>m8_OJ2Vzl6nsr_eGMS z6Q#8NUo+#W#mfQSiAl7hjp9cKJK_jqu4C8|+ACryezPrrO>(FqzNC+q+1eZO_i}!o!>2c;sWg zy+!xwBmF`RxN!Nwsx#dP-&J!k)3114()oWlY*o1~oUjE>$`f7D*t;hqk$$bb22}Q{ z4dq)FJ&{K*3y7e|&-RvvYmjU;rIXl+gOZhVv9wl6qB@sq zByh~Ki6|O!XnQ$X?sw6uJkhT0krEEx+5_XtlZpJbezu^7VJZ5x6jY-o0tn775&|&- zZfWGXcnsKD_1ZA<_?zUt}TIqVV-lM0) zJk5U`W4_@doH8psy-$0v$hb$UayRmWJgB;I&-kY8jzF0Kgbsp+y~{?EW`Lg#S?CEW z)ViY8@cO?6V`g)r(VJ=ptw@4M=S<_YNDxS+@jwCy0*rXQ^oM8(A@iF)dyKKy`!(3L zBD_yhQg~MvO#XYjp#6x2yCn$q7`yU4d?fIHic)$pc#L>wZPsO-sb? zNBxE)BF$P4cNO;;Wc*PX+T|-B&#ypEb(EXbGK2prg$rR`p9k5h@zVGL-MZA0W7Bhy zo%V_Y2ZOlVABdZww>v~I2k%)@)TDBo;7RANcloE@chQNGPJD>aKNO}iJo|^{DX^A;9fT^p)k=9K{LiK zc=Fcx-tWAGXusCCua5;!q~1$3;-5Kgn07j*%Z)h;izX{qpvzFLUn`wg%SVBB9g4PV zLUXNel0a3M>eXA6UXBV@ZZw8Z6rX!(it(YV?8V_Qt3HZ&#{Sxh(3JhVDZ#A;L0TV- z=G#=C(XSWXNwIR$9!I~4S9|jEco=Vg(vj!b?voeADQO9t$|QnsqmR*+xn64lSE>P@U1@P9?@Pjgxb=E*u5;5Q-Hxnt_Et~KWD zHz8*z!OoVx`02kJiO5?cGVdDWO8{vP*GzoKb!i1sis{2kv9rFK#=ZW<*h?<#w;>fe zhY|iFNeZ^DN)oI&7E;fc{PLNiPYzlNzFj#via#6U*nF%fi9}4y38CpsLg$QFHJPVD z(mA5{6rnzyk7LnT$J+9nCC+z2BzMn$On>LtC8^Npb(Mal-DMYmWEXHwkax=(^7-Xs zV|q&owr~7qT%dry2o>ln|M;IK%5Ssd$BqhKLzUDB6uK4j3b+xTR#LGRjbzD3m{w^} zP!-3Mchix_qNR=3@B+68DnWYwRr;8vIS zDx#0y36NeN=*9^G&trQ1B|*yckqZsEfhRj;46iK-)(F9^t}i$@wR8OigJpZ@_|Ta& z8**Rmgwx)(!BLxgVr0YVoV?^m6Wm3@D)nQQ!O}jn6L?`R>cDYAU=MzP3!w?w>hbO* zu?G`mp53X+FI|9y4u1$1R)5v}BA-dz?`{!GPPI4!orlUIv_%0?nVxv4q)W9(nm?lc zH8d*MuSJERW?EBbhv7>w)6YpE`Mgm5O&J92tZa6C&Y$OgO-Bq-_ZyIW0n<9 z<;!4&rQz?9Gxr`$hu+c-mv$SR{3uIbY8r=^pP!teKIQCh>ioYWma{v_8pH_@=G`_; zaQdlLPj-W!q_f3~Z3Y5|N?!<9%3)xn**t#p_ZCMX!Y{sRz~1w&s|$jeaU$3Uc z0upPm`2-pS2|3sOZ!7@T!z;sKhGgb1T~l9*tbxNIcz~2#c2H%Uf%xau@V_y=H_acl zGZ?e|uLb`~a4bWwo!(ftX`sbVTE7gTN1O+vc@jLE8lIkP401|rnQNpp@sRh`FsWb= z>&9m*5b)uf_8eWxS6@JfnbFIlsz|U#i)Sc8FGvY!S9}7Y!5;(pH~jiCKc)(E3-uWY zj11JLYw(*juk(MXLVtXhB_VG#)Lz>LT@Gc_7anxac#Int(o%PRDaJrgvooxeO0~5v z=mIq;V-x^JP~lQUi728MUmMY#GKoZx{p<05(L9ywND~y2bEyOXA6NS*Vs<^tp4&k$m8-`*Nduj z){yz>M`2yPFq!@HCvriUlHi48@*oKTf;?JaT)YOIM-{OFD4(OZP77YE_e2Av@-o3A z&e~}X&=ZnubJ;(|_5nbL89Rg-Vj+F)wLXxgN?;8a5nt^a|2`0FC26qCrM1m-;6w$Q zr_Ug#2Z;pfzLgPMR|e#E7<+Njz%4R|2r@>^v2`kN&BMCuyYGB?Mbw2d;c&+KRV|hf0ox*P*56`apps$N}CFZL>7Wl94 zjofKus9u$tV;?JIPw3NCKJ+MfP1esEse3#{>>&i&=G{7VN`A&^ ziMxrVNXOHk9oCYfBB-p7O%CFjxMLPFVKsj@g|LArxR(!$$ZkRLlMhKvWMO{-Er4w{zdo$2&H>ev9f;$-a;Csp{3&YBG6> z0c2yb&hFd_^-2SW1J_eJG2`reMi)_pYk@0#+erQad;9FchGne5ZvVu=nO4_M=_>74 zF9Nx{nCL?NHtc%mLIm85h{tQiAhf&7aJr2$xI&&kP7PE#M`$1L-CpdY|2+S`QWe1{ zlyD8{(S!z6z)O#{x&Bgk1RhBJWv9ROsxem@C5c&XwP|8|zx79A4*-rY(2p}{p=WFb zr6weJ8TIVAsG{@OC4;}t3-!{cQoWCGQt~X5(RdtG-5zkVD?IiH@L^Pe;YMp90$1*0 zhR2)vr=`X|vvtnTTt--fyCHTwon^{eyXq3a)iI=*u*#`YBF7~)&^y|g$z)=Qd6$G`ruH-9vf&{V`z z9tP(O|4WeWAwoNJMUe0coyUcx8I8Y4iWLM*Y)%@Fp|&Tr;T;@z; z5snzQP&8tg76G$5ItmkJqZ zZ*!XNv*5MhdBov@OI)P<7U#(^dVIl@%p1;d;}(!m)RM|p>MIa{qQLMah$-CZS;%AS z^Hwn&MdnwQ;-*vpq7YAH^G7Z(rhYZM!qH4&Mfq>mA_fL}7MAzs!vDp9 zz{~@+OBx7pH;rJF!}{E2jW>17j?ag_7ED9)oWEo;?}iOUqy`2)2gmiM;j7yoJb_K_ z1yaeFmazmctb}6m=8^Jxsjd=UU3wYYwWLAfsH=CO)f+={HdT?1QxV{UJYp83dr2qvYw_oOKvW@CBzs@Obp`2M1(+CO4{W&mIWNc+Y5+GIjY}Btx!zb0<%ht_BvSs>IbS zmd)PZz@?>?KCf5<}wKs1>cAr&qpOs7r{uy5%%F9uZ z{_?~0`ZBWK@%oem_YBKVZybGc9zug)>89as$p?XlhCy6$WmyD?GhU=9X#`_t_0H(<9xtW3b`M;^*SA=I5 z=b;OfFXK(cly@5{xLZo}t6)2uf64-Da1u`OYPUkRRFm}0w}%%Ue`Zauod}UsaQgkm z5lZ_bnjP|Pgx5&PySn*@pPR_f9$vvfXw3ncD2?q$e3)&>w-c^ZU~J2;XL z0a3fbKR_Z{ODDPlv4)`HiVMm&&R|*UkV8H+yx+4lfeB)9YE9xDX&&HQCRwXTR#b)g z)G#VRK?U-#@RIZdNmuDbM7w~-oXZB}NL5&g@YlCnjB-XfAfcv8zaOFp@9n0D9Whyg zcOp6)kh2NbbevH2p`dxj-mA^L1WASLMtVC4P?u$3puPsWge{c{oe?mMf*~?m7!UjE zkrO&~T%{p|rk8ssuc07#%RHH-rL-A%D`sS9F!lxh{J{WdLp|~n_!B&7QxzE z-?3i>Dj1994~pxN7ok{nU>yVXQBcZVPN{}sG6|4oy_52^E-a0O-@A)5Ja9LWq6)!2>snjB|gv5%{-AH%G(xEQhAuc5$B`syJNJ%Il zB?=-f-SE5k`nRJNPT0$`=cJ~(@A*DkWFwl4Dli4-A^5WtHZREA2E|W*# zs_@*2tvIm*8$O{S^BQP{U(jr`-cR?e!yJmKUDq@l`9WnUO3=nSJ^Tdu09dp zr(_5Jom@AAEZZoJspFYmBt)%id3lFp)P2|degOgqwmh#W$Z*GT`#t7o9HIL+Zm`l1>x zo9oqbbRC+jp}W}$cdOW+E-iEt3LT2BC5)YPyfaqd*3-QuHI=ozwXti!b)5VMcdKm? z8x&udJv*WZj^dlO;?nOMw+?#%8WIDFA@Anrs=PK+tV<7_G6;EgZ27<6^+?V>BI1t5 zSxW%nbCzN*00s&tyy?!_oWWvV<73lN9`hHBGyn5EM+;su)skb6`?N2pOe8d5(*Dif zgOW1kD(?S60@p_w9Pn6wH#;4OfDwxPoze%LR0OUk;TLU`7Fv>{PY{$K0RML9Y(_m$ z??#DGU5+jYn+njldhW!mcd-pKDS`>0x$=SQIj>zdv(l*AmvB`?i5$rios* z4$GH6d{g->T1_itJD(Ey%2SmPglEF8c@Sv_)E| zCYmliW*`s^M7$;xaod<&V9|JS@v?vFjTd9{_iwsHlK({+|J;Vt41jMonCi0f#3InmC-!IY*X#*G|B1EyT+5-RwK<4ni^)VJXxgE7HC&#k zTe}O{xgWY7prB57u`$+Ha$y_h%iE@h|D#K2bCG#4>Q=i`*CD8SC`$7V=QXcfv*lWX zoxZ+z#|AXJBj3#Z9E_iFc5M5J>857VMVY3Uk222xfv~4JmV{Uv{l0W;r2QqRG&lH0 zy3e0a>WcxFQ}VC=57dk;c;agbv|w2#<+digp%ip=%0E}P;0^eJoF1gF-*^mdeO;eE z`|KTfF%8%QDK9@HYdn_7@D;e`QO8-CIoyBII%+bD`a-zN8AiwyNJyE*1DDVtx^vCW z*RWx>4Jobpb2UK|t%cU-$b9)PGMsCuOD9kALg=pv-mT?2s)=;;sRiJr@(pzH*T56g zLa+KvnzO!;-4xnU+#8^sc##8p}_R0}Qy8$@`&9Zr{Q2&l8mI@Zb5_{=QG@`^S~{w}a)r zf3*CmEo#0m^dT|hYIRZT+@3P*`|pv-)s#RynrGg z-IssoU!i*^X_Ub$+63Be99@A{D3c2-aU947#T{89?&DWzFuU#Ff7DocHUT}EHv+rF z+%>!NIFK3{1cvRX@1ICc?AXYLFCy3uoK)TrY%O#4apr_4`eXF`Ki%t$@>j;GmU3@s zfL&#}jzxX?eu{m^9cY2McHV8HaUWMN?XD@{J%eXtO`m6{MwTN^Asq5xIj zd5&y}^--?0y^B4cSzVoxlqTGb^npTZ-EQS2rPn-ovvSQ|f%{ouAe%SqxS!Ak9gTd~ zqXxUV43;T!An;?8v;?$Vk>uR0-n8u#kBRO{5tMjQC~^;XLd?OYJMAqu?UIdiauqal z3e6hxV4lW>Ks3r1)<(_pQ|C9>X zIH)X|^i$c$`Pu3j_ZLG?BHIZdfULQ*J`sbynC%bsLSQ|j@ImMhc&zTX#R>`E4=zX* z|B$XYwDN_5Cz_W@e}eo4Z^9(MP(IL}(=by)xXbHD0g931??O%`>l4N1h0_O^B!ds> zL?*o5>?zsrjDcN#-knxIAyP%DzudAF`IW#~T)zl4G>Pl#*=pydWXJzEg0HJLyF`6a z$BcX9!^vr>c*+sI_^*T{c0~UZ&mtc<*!jmom?6Da?-Nb7Ci>LxEZU15Z2d!<=6ECf zQmoY-SL%`8^%Cm*97W+x`BwUwP(nz!9Nc#~y;nC+{bi~?QP8fs<@mQ2)^S2lU_qT( zni6cZlsHgMT`E+A^+Y*y&2QdU1;r@j1BrVGRpc8Ca_Vy*NfPgvA8`^V_OCe8J)GqK zCcVtqMjwC^4|K^MG&-)&|@U3TS$h{>>xoZnp7*&5_ga+FXQl{y0Yr2}~zMCZ!X%WfU z;wPl+#=Osb!HduLp!>%R$%oXd83nfb0&rd3V<7rK78bTv;;~^ zz1b|;am0>A^KV~7nto&9_u+}Ic*e^UfI1gXI9yUhLG3oWrh+^D@~ZL^j_jzGMq@8* zIk9!IN-mV_>5A`vC42^n{{}DHf;uN0SJbRJXnV4t#Aqw%=ar!-Lz1s#$D2-5C)ECt z*f89h&9cq2_eA*-PeUTOMC$SDuvmc)oI`IZxW`y+l^BD|ql4~y z>$H$0&k;!@`3VX~wy3tmr1>^&NvhQ#ClW6F*sWVi@nMxWvWnwx$8nf?n~aVpE9y7#*0%v~hROs4h8uxR3499~ z3>PlLkEoxwm5hl+>VOL;+SHGp9m*eoVqak4L^`uFWTeCNv{E7Tso={1z|!oJ-nrO_6wv@Qgf{{V2TmcgR5iIqyd08@|CL%Ql^P;^S^h z4o^+yOt0?a$~84-xCF1?>4JM(U0QAwb~yd$!QoflWX|c=N?o3o@80*FZ;rVgZq;(6 z^F9A{ytg3rJORO_9!H>VLXk`^%$`i3E2LD+p%aJJC<3Zb1Sk{<_HS7H5#B~>!A3N3 zdsTrb^KkasmdVVNj{FOr>>Y2)vwHRM|smIL8exi7ihYe--3;ncvmVffVnLS%f4Ijn-#; z!3isU%lfrAnG~16)|gY8lvnLWxV-7@)c5sGt=#B!@@J+rOE$94V>hYe$lTOR7=`l> z=yH|D)irORPM%v2M+vBX=itOw5F}I|dr_%&r%0K|O?I#i((!vIUEEcY`*LVwC(TD@Pz9!D5EjL%OY1Bm9->ce?0r z3=$$AvL6ewC)ZGNDrDxExi6}wRISMo%-5^chf<=xn^?J;kN8(4fS{#}Gu&e<_Qx^VjBr`Yoz-+L^j@8yH) zM0Mvb?yunmt}-iR#ZXKYKS&YBog#XxCOq`ui@HXNY?Q?S|J@oQPXV(V+4sVUtthR& z=tDdwcHa9+=>OJi;Z!5MIm}?3;qCnKj9sdkJmj9-_ALYW<-xhI2GP~Ac?NY`%WE zw2L!6o7EtRp3#slT9Q0Tl`;?ilburUvzvzgh~C$b>@xV)1k0Zn`dtR-Cq$_2_@>{E z*x=i&Jv{Javld>g>Q8M(Eu+JQen;VHRFv zrCtEsytsa-p;NS5dkt_ymp*8)W8!vhh*tpkW(0N1XJBhPqWGf>Gb*{Q6)%6h6YC?6 z=#j!ewMCEi;t)RU(Us3>9Y7Yd{PvL`v7RdA_o2M^8IOqWV{dAkaR{{f$OtZ=sBUKV zGupTWFqUhL{oR-z`M^rq*oE}JaY@mx+$BaQr8F01 zlBc+Q=N?f#Nk5&{>yDqdPk-HCO_TPQI@tg0ZBq`qU- zD%pMlH2m8?*m@n#n;Pi z$1P61$7{VfbZ@klR!r^WxodwueRz8_kqG1OO);s*5?{8ghRn=_9^!!Ap`^y&*fk1k zT5Q5ewJ+y80;)OYXlxjSrug*28!sHTaI)f1N=6!gC$^wC{+u>Uhv2(0z}=8iyzW25 zqV1 z?dap18YA%xF@UWKmB~xD(Z;Qn5If4upOO!!ZrcR#Bv(T?uY{U8tGSlEYFMXQtlpay z?4^F`T91sM9qtcC;HbmRR-}Jbqb6ijTok_*5pZe^4tD3v|4vQQ6Yw$&o;tM+bA0X; z>g;b=FtMu{rEL?XP(B*yEg*wD&NXmZ}2!6>7L>L@g*vrsGx9Q7cZllyj@Klgx@QB{}e* zb?U+=_~*=_c6#%ttAoB_Cjw+M^{HPgt32`UnM{zI<6|(ByY>Gqm6hwAv?IKf4dtPu z4kJS4^ZaV1h;p_d`fNzVuZwIyj|8esAyv`1=*Q`mEd@_{N+f-?ncXaYeEw-cwc&l+ zE9&n%nN4eM@i>Oq`zCgrxmm>!jFe|q3W`1c9S1^)@z@m?mzM)6v!N<|!FMk6VEB!X zl|`9IfOw(I)U-!|y$W?qJiu}uo_{-Y>VePC`dgp#4bgv-^WKwkj^TytypZDCXYKK2 zr;LBTZ-8SEpV{79yA{!s2nIW+Z9_k1}XVAEC- zk>*2q8yI|FB-ua+klgdtSU>3O(K)KVsg*1%*^#} z!^&tf@6s;mHWQ?C+QS)l>nFL5Xcxb%B&$0%3yaN!wSFrTa^sM@2()G5A3?cSz>Cp% zUs^GOn~i|J3A*rIGqpgu?@6wr#>?FergtXE_vO3X-~zrZcli-_O1a3r@W>bW?o;Mq zi*Su-!gz5pb-?B?wdxUmgeZ~k=cM|IPY$(!c6KTwoMuRsucvrj*q>sI9t)FiRJEqt z$g6CEA977h-~~LaFiwqSQ~8QB6ey2LeHCjG)ZdRXdG$u{!4{|1Nk=SkGu}&$2^xmx z-z-@1M84NI@<-n88?_A@&;YHb+(;4%3{)jPZH=TSX?%k$K+ zO!dFkr0q!;@)auqX14C~y!BJm@YYGT;*fCipm<2VLj?tTuoXzvNk%FddO{uYHrqh+p#25w4v{yKMf?-si-sV2$ z&fc#d2^gAN!fUMGvaDcK1RYhs@z~SZ4Dw@>%{2Cxb<};Hel12s5Tq34%5o9yQd=0y zsHbk;X#DU}mL0nvjK{Dnm~O_{Y=o}N>w3;Kd0>)p_BBXjF*BfLQUAqrC?V`eN5TNyEF{yB#Io0__y{Ra3;dsGS)ALX4{(-U&Aj*BT0Q(u3kt z-XIu@;FUK*+f&>Z9B$1Jefd4be2)g>bC9RXjbK!-s3Z#)X$J;^QS=(${nD0EZ*C|} zucO2Y3x6MMpfIDT36u#!W6|nc*~#w`@a05(r6A(@b|CDD|NV}~K5u~#+IvJsKV+>G zDNW^K{6*ga+)oj`oUOtzu}`fjK(c;aJS(oqj%xTn?HI1498I8LCk`A##@uocDkj7 zzB~r95}N?KMj#|l2$E;;6rztx9lC$BD1d0J#Zqd{)V-vN6>I$kl~)NEn%!EzMJtlY ziQvWamGGsMx&Il3@hHq}3)#n!Twp1j4r>aAmqLol<$az!K)02+r)Ti)#sXj87#`dv ze+yjD@l<(_oU|oEOujkqk*;tf5C$ZDuWx_Ev9FfYLi-Pj>pARe-jA+;G7Npu%jClP z$CNff;Xhl&7<|conwMbj21&mIw3)jTj-tOY7b z8TVy&+SX_;DjKX%7{GY&RP1VJ9(mk8nW6a3grd0Y1>VVv>`Tv4)V|M62oOLFk?pB| zL1)GAXBw~7wED>aOT1zZyxnZLq}fK;DG5lf#Ut}FbMSp)DgZAG%9=h>z zM_W_&?AQfKXA}P}#}W#;$uFS~-@>(V;Wi_f%jSc9F{uR~C7r?7wDZ5xEYWR)#_s@) zKY1Sz@NH!^C*G}$oi{%5b%3AB^g(KUNXhFzH2*fFfKVFPd6Ik=312AX!oD9xIATW3 zwC_GU9QP%zkA_Z*Lc-P`!GC`uNFj?4{mX>~54k$_Nmw|bA z?1(=)u$6V%_=h0;wZ2c6c2<53-s%yxvuqkpQ{J1o#-Tc~<+#+&iG()bj%43!w25KZjH*nEA31$P3PUFPh->HiD~Zkr@UJ- zi#ugQOuh8{bAeoO)%{CEeVZhqrEk4x+>%NE2_HlG3n-o9UDo z-SV~6lOf4sRyh*>2ulW4?|Sc!lG!!YKa^CS`yD9t9tj3McFPYli))DhK3)+_=w= zNFw_apI)bt9luX*D80gbxqn1^72|4~w|M!JdIsxW!SYEd@Od$1`9v8XV*SRHnvth> z)|Og_wiDX_VyA}|X_fl?TT|puiCLl6Z+5JYJ0vX9#~QL~$C0x_x1N)G+R@o$u>KX> z$y;IzEiiszGa;kB7V0f|I}m_}=5Z4ttKP_kq&EpZ9si8#NY5?bGbpjDn_@Cmvm7{3 zKl?)yjZg4B0kwgvFyvuHWzhvi=flza(a_jInj;CxIjcGYD-nP5{-nz#-37Pt#YV8dURt>dnY+bh`9E*-xcpGKa&M}i zn}G7S1K!!=+&nuu2;=?9ZXO`G-ia4#i73S_ftKD&WpkZ zze{-kDtQzL<73ad555U4a1cA}MCf^#(OU=PzVvNP1OM3Y4;!F4F+_I_P6Sort*+>7 zD&q*4B?K^)zg%ZjY7$lL2N3yOL@6X)Di4wNgXe|s?mInXB6=?sdZqM~N|8c28R%6q zN}#52dk@bb)Ah@OiGSK82jV??KF>3{;(w>)UorEmM@d{nIt zcP=q}vqZg0ry##pJHwl%L5O#c&J-t6WS*8imX7@MWX^$hOq_-1mvWngkD5^5JEiBC z7?ueiOd2XbPzS3z{Tl&3fQJmO?tYyfgi6E5q#-50jf`%%cTZaN#VpC){?Gy@EVj8G zTM+?Xg!+v;v*VQyq@jVl6ia&e=uTs)j&i>^+1q+_0(ZymMCK0D678<6sbas$mk3SO zIHcnBzcIX`BEp5cs-==|eBb(P)+yv0NTBU8&DCC{Z=N3(Z>R8VillaWBSb>Blx4_5H?zvcK>!a_5)G<{U^i>Sw;c+{gV z4uy|Nk=8YEw~~4jC_j`o+A}|?tEYq`2j0I+`{-ZsfGc>D#lY$%P{EA)BHT}$@_BG3 z^mSd>^E|+@2vs)moB3g6Nwx{PIPajK5#Vh)zq_|HN8e8{)?K{iSdb2>)OO7z@^>nf zd1eLi=51Ma&IbxZ`Q`bJR!o_yt<@KguC_?yMxK7Z_QlEm%(nQzLO4BvXOOy=7^kpC z*(9^Nh3%FDAzTaD>sNmPjcVj1L^&J5X4Sn2j0`(y??@=a#~$eR$UG(=6}s`{uwbcv z%zZ%n2|HMYEmchPtJ(aBemkY?Y*rDD3?kd}by`wI7Yg%=a27rshopJQqu52`1B@cW zL=OAEKIb6vL`E57LU{SH8T{VA=ibv0ro>c4qcBgkmOg~b7{XA7=$VO03-v`6H~q#4 zRGMl2rOWEmLk$4?koSdQP%s(O1EEjJMD6Z}r-DI8<2fqiF*qG7{daPIvJechG^dTB zA0($}AP8QPwAZq-65`-d^mCx_h~ea{Snz12P%tyFLCgq&5hJZj({-MR+j{;gzF}lL ziyT)l$p2Phz+{Q)4#yk9lzQhMr%5iZhj=BgL`XTY(8q`7eqov3tI5_X27TY>IY>DX zEJwm0+P;x#h;_@(8(G~RxX-;iczGT}Xt{;Xgsui6&^Ed6&M6WKMghqa*K8jbgU5k9 zx1UwXJw)q?2TnVObSZUGV)~=BmyBPE=9EX#E4*dHFl+I*tf)hYKfv!nW3vk{r)FAlCceK>6Y9=B>l z&MMCSc=D}ubi*XiYp(^tmNw40Qs*2U>`_lEDJoInVJ$0HKT=EvMatIPNY`)i34|L_o8~L_2OgF51-tl z>v4a^=}zdGVBOlJmEId;ww}6e zcur;0te^F_GrWkiENqs{t0iNX+f<+<5p{B_w*N6hb-ARm&z||ml4o8VmnI)jwW$#}#MWcSoG}z8>4t5>ldpw<&hLoNASf zObE0YrH#YgL}BRQ*9JWzCa)@LE7MVQ?Dyik*o+T0GMzXS*W(W;ZBg)XF2*T$DYa85 z9`?scX%Vvg@bFinHX3Yb`9AEk?oj zVi<07NUnjF!jI0<=>~qYQ&H_+4}Y8anA`cYKG7Q*J)*A#rt@7Ne3Xd`fiOg{=gvM% z>H0hpGU8ND4RbV22BDzpqMLkjJW>$f=y6&D=r*{fV=(dtf!Yqifk@GI(wZ39fjFq)+ovZevHp^Z%rB zEn@?ZL?K_~4~G|&pYPrL$MRN~+3KrgaId&wgI1l-PPW7W5LLOOQkdk+SFZwL^YGQj*zXXe%; z>Aw2>Lck{Qx$gJ&{Sn$M39KU6!yuO7dejdWG)J;xs*f`GL;0Y@>y;x&Tp76s4-kt1 z69^U`HrQg4Ia z(lBEKF(rNTQ9Sk9jOPT6X+JHyE4t6J(`2`VigcIZ4Fv@9hw(^{g_n|H{b$&jol{i} zS3u+~n{8Wz(~Y&)FzNR9iJ%7eK3TIy&%NdqaY09~hz5NHTAzVmymIIFd(JzHct%~* zt3;$BevOiJ(npmr>2bHR%;7#@;H(3zZz*~t!-1TtXk&d3TPEONc7 z`GZW~WL%nh%;L;J3|lQgM?)du0<}Txw}#jJWWXUj>X=#r@8!El(`3WQss(jgBwY%; zEwjDjF$B_m?>>H?5>fjjeVb{(YoQ@z4tTR`ZWIDu5xZ}G$8=kzCZWzXg4wS&JN!>R zf2||>GE9V6-vx~uOSbL7%LIWq*EgK`*Y1E~G%>XkY|)~!$gG)v_Vfzi4KQe9jGUnF z^gktl%=gL5AODD4kUPb}e=O66x_q~Yu8==gik6Zx-s>z&XCcPOZI zKMRslLL`RQ%#DqWyE=9zLv`17%7yT)=@iXme}5fLuO<7n5XYA^f$*M||C)KBn`J9M z`A>Wc2^F`=pJc@g53bm0n&L^Cp_0BHd1H%dV2@b8$KWb)mk>rs9tV1p@X=pkzGd4k z0-K1_pwJ4zGtrRgkpo-#FUiXlog0EO85cqqVqPKBf<|=Qd@|=IFQ}g|bdSkj!`(45 z50=nVU{BAkAPQ@p$h=Tn&$v)DJiM@HoiabXxadfe)U-dnIsGsrgf+VQ*>xCB1KLk4 zo9Jhy-UB!}!iWp8VP(TZA7Yc$flC>%XIbY4U2;Z~_OXPjx=AedoV4epz2 z8xl&O7EZ}MJ279<`B7($sUxt-LJb@fQTyPVKL*{#+5ltNJ+>_XX+wbx);gAX@hD{a zPORafm*w=)$S-H##0cjen%_!9>yHhO_8O22LLA2Ag2wK7O>me?XCHvm%Vcv=Z%)b0 zj^q+J+iAeVo5rojvs+Jq#WUHMB~bN(aczZ$kkYeX(*~ASEQkL(=0{`J5-26H0WE@b zB$P#jk^A3S#xpNudg}|pa%I1!@N2dMAK`*cXF<~T!e4sN~=`qW;g>9_$csAfH-z%;meAy=mIztI$^H^y^=yxmn zfxA<>V1*`5jqj%c+J95E6*t)s^D!-n9JO9&10^AZWpE}nx6IxY%l}VtaLE;yZ{@F- zgn%)~BU?zCHXdCo8svFRG7ROsBKF{yX2!vFNx>MxrtjRBeNZZRCh(;vit1FM^=TwhM!*gf`9<037w|9$*_Oaan&@-Iv%a*M|!Q4E~p=% z(9~T7nol(ZOiEE=i0l6+^8>~Trn+GD@gWJARs-LTfVvFP+tdi2tq}5^Iext!@y}XPgtWb6N&%lZ1Q@%E&OKd1?nMIxHQ+rYwk zEbYW?5gSJ)n{K`RK~U%-DbmGcPu}LAO4leU=N)Und*4V}e<}NBM7|fmVHCnXQ6ZvZ zGC8c^Wi=)TR|iI8@$oe318Kc0ui;f93XUsgg_aw*1o>n{#&rSO_8r`NBnl<>=3QIg zh8RoRv5jW&i5%S5HyE0fgAsm7g45O{i2l!900D!Ka>RNdc@|iH9 zZ@n7bL2P=#)UK)H8norS>aT0Cjqu?RA~_Ie|8QiUgu8(*F#rwuFOF@9d6@AVVA4h> zvsxfw7+Ii|jaD#p*J4?X6`G17oZ;tftmCi>Hx7H^Sa8)((R^pu9*@iW=h9>yeVfm~ zdf_yf*+JpNB%URGuQ(%ae%eQottI@6J=5guYmD9s3~9AMLmo1LWb>aD98Oqn_$X%z z(!K%%_x|ljXq*MQ_hz;~mVCwO?)BOvQvS)%-FJ?79J$|h9Zer3u%{vPtkDOe6nUYU z|7Qa%t=@T|Fqp&s1t&=)`~UbM7fTCP7dKto;I~$1q^#FJ73)blHrC5LPUSRHuBFUB zfL|L7G$I4$>K{FTZTtHKFF`DU8P;ODtS0W-oU8wWnoDO=<>Wfgi~;fghy`SHA7wC` z8;WCDzpTv3fV_cXwAt{d9tizdU+DBdE92f0nG(Y~7TGspK~L1e4C^+CU#a7#lxWzN?%Kkw1Ff%JkcOS@nuc-s#Mv<;vpf#DjP(0pt@0?$G(K+6v%)5tqqk z0`W4GQJ=t=wfYV3+VoeVgw=zbLPvHG@=b9TCg%Di`OJ)uv*SW*YdV(F9K`D;Wze^0LHG67*OlHz=iohDncyDj|<}e15n8pojAc7 zP1D#?;}EZa+n8e<-%L#Jqnh|>wlfXstNVW$QH+*ezcu)q-cYKZwj?M$efxt zSBJHRVuZjw)9HLkfwgma4WcllVdOnAde&#dF-=CRAX@_4uR<*69U0sUWA}b`)|UV` zilZiyXKb&Dd*B?twrJ&bPQZ-X zaD6@D_&XjVvRyJazLr1&XS5N%hIhv`&!rbwU5VE6IxFdd7o?w71qB$pmV^AL@a*ZM z5YFB}ALTxoW{6|0>HH6uL!MbpXfAB^>aF}|2}d-(%%3k0FGa~o#uP9*$D`jSXx=T= z;US{x+%tu`p-R>;!?61yEY8B^u#+^(JAZhNx@f>#Kx3^J)WfV=3Q6Yr-q=UPXoAS^ z_oqNnibImsU;-rYtF6Sv1HZOG!He43eQSXRZ?QgG)?X0Dd4Zh9KB$lt|3e;UXIGV! z5pxyP>FIGY@EvC8*+9`YD2alXGICECe_;Dc1BMj0=wJ7I+pRq!anLehA~Z>JWs+hD z&M2JVzWhgDfdho)OI4y3@J23u_WH&QaD*u*(eAJ|{Ier(R@(uor+;qjt$RZeAlH#k zE^z{%-tdAMXRK5(Y-ZM!U>Bk=_9lDzXaZ}eO%Py^lsN8C&$4lzgt28ZKf7n6d`QNWFghcZeQ&^^paKmSvgktVj{8%{C9{G@ic90K|SlkWMrnfbJq{;XczQd$F9#0G@eR;|UZWx{93)cK<1@r9C zW!`XH8IwMMP5IguwIrfA9yAp-1d&igbmw0QVhJ2!Ff+c!Y7}N$7Q=T%V1MS!`v86= zq65OXoqtQjkcyCbXSzT3>SG6Q?zpy3Kz8E2%e3^Mz&(7^iZl|uAvtAv5W2_{Cxjzx zW=a7gM1vGh=gDkes;yvh?*@#a#c&U#XgIC|92*#EvLeVsMK0Ol2mS-2)K#I^#Ot(` zWgvkKUOgz7B;6IzmQvV`cS7&5MPENZ$b0|9Oh;lf4t#a1cWl1c~2EFM|v8?Ghep)*QEe0q>GR4u>kt z^f9rrcMZSEMxLK-%bh9Nj+Z|Zr-Px2EytNV$2V67Tx5-hUjD= z5w);ZDK&ni0etjRTk%cCrR}i#w+FAxk08j>um*=g)>+AF+sMHK`D`z`89@GQ=NnU~ zZ{;LG*j(Vy3ixvf*i0KK1(J^a?qn#qW0(WnLiTg~%z!&xmwjRT#d{wM-m{Wq3b0rwi5;n@U>q`P4o`MM6QGYox4C-TBn98g_WxMDXzNEt)U z4rgVFQ<~z4`bcg7XOF_eBPNUkGywvU;51?l?ht}~&og)KeL0#_?**a|2kypNV*}jf z`eljslFd1Rnf7MyTe7KMv)*%gx^J{r@FRXmiauw%6q>&XnGKr>kA+?>GMt*nf9ct zr<{C*fsYye5bu^HBQeQbl#I+N{nCNoZcuIhzLI+-{IfoVz4q@jNi21^Sop2}MFvbC zJ0pe|NyNeDPV$-NSv5QcBE!nyODz;gT3=E8(4&L2o`h=m?{O{sQX{Z_Cm4)PXPflW;&az9HUGHvRntO|Fu=i?9gnY<~2Z?~)>JID%%2$xDB4l(v< z=F~w^dm4@!+ZY{*oYt?v@_J>1`PnZEG$~#j0}fz;zEuDZ)U1Mk)*k2=6cOSqzfrvB zsVpFY{mlpA=uF?zxtr`HV9fqJux%jcdbVCsyNLQ%*}?cqbc{4UasoCSss5?qiT=N{Z^ZegDzptX|<)%$viT~xaAPM{)(+R z#(_@WjLLj5^h-qPz*`B&?k!s+y=pEkqh?BO65E<~kBoro#FUG=G&*{u%Hp#!pp-}rB+sb#w7ts|3KL4qIwPH zp7p_YIG3>BDr02YLjL9Jk=`i+()w>F7qfXVJEcYGf{3Bs3J3HM)ZaIdr2cg;=PZ2^ z{7I>0!_jMs6zGktKBs@{dDfA5pUI}dM2hF@AKHDSTA9RwRgbZk zhp0X!?VpYt#kr)rfry5-ZL@du>2Q%>8a_lE#rqv1fM z=@1WmXD6}x^KFrM_3W(uo32x~^S3IxpZ(r}ULWyON$b@NI$NzirR-D;hXf`y!!I9% zo-CJ~vqlE1TV4@9vO{yqhkRNQR@cr7JzHH~US1hdju1OR9Vnrvc14vUt4dfMsJ?SO zB1<14zZcas66Zm=1-9Uzvvbe_za!OYx%V7qXp#%howkV%Fb zD=OH9G3c_!X|0#DcdMk^SN?T^NHxYjIg7rzT6bLe9BKG5C2a(lNxD#jAX(*gDy0 zCcT;FgqCN@Ww6$pG4xw*=s;Wvb@jniwQx2A@=sN@qwThJN~UWb?ok_)%^pk)TOzts z<%W@0v?=Yih-6qIhoDQ}Ta$hozBFdVu1j~q`O1C?tYxX{vv0sNph&Fkj4<=0`&O4x z2iZLEj3AuaN1E&gVi&fW`&1`(j5@J@>!TfPl~{ZV?;O&E7pZ5x@hyR9^6KSF+%E=y za5QgjwjbUwusM3E3Q;oH80_1w4wi9I^UdT7KgkG5Czqey!ZoP14kTB|kohzLaAcb2 zHE#!s=iQ9EI^QaAK+_hUuRZ(454Pva1-eNw~!Kq!gr`m80q|K^UgRb-WjSKD8l@rT0UR91XDFhdx*6dkM=lDL_e_?
    gdE{=p%A#3bZ+S|0x9G~2i}1>2VlrvB2A+2tsvCHcuA}*WZh;w2S?{O3DS|^D<_RL1XdvtZ|=Rz&@mR>&p9x} zT-89nlapcF+#8 zknvcX(eIR{w8o*}=J2ZC@6$_{JblQ>!ZzQ4NG}0lt|md`pgXw=fu&8-Ga;2J3$`c^ zAuOONgiH9rE|Q#pT?qBdKT3gq#v;w-b``ne6*m)Un(&{gJG@CW->KK+8Si~>WE*O0 zbhPrdU%wx+?ftEP;c-3R!uFfE^;Pfaj`Pm#ciXa@wK*`sotLwwswxG7#T8E_ZhsY3 zr-W1)6<{nAt+qq=G+Z6!t4`8I29hN?bHb#?E_D=R*R9caGsUSEUo$Py^-}fn9}1k= zqV)os4!&-RkXp+t=gYXE35V0Nf?d5}tCG&P-!>T8hq%-G{RF-oS8=bi7f`SpXByz8 z^BnzDbFL#NflY@o6>WZfalFEx+^0*cHS~Sai5~NerRIhtPO@tjBPr#!oD{8_z7kE! zOhn^kO}Qj2;NI_&7<;aq-D8?V$28|BTPa*33NUW%TE6F(H=F(8zWT|@OHbXLT%UBm z5=^!~&b%$pI@vo%Z!`VzY<<+GGEnWCGZLPjb%;-kZ205uLb99Qf)~mrhqF^T5<3EX z)GizUU~ol>WWWzkV75_QiIE)}x~r~8V-iPOOAgpwQG#8}1LvqO_ z1PKW#rMtUfX#^IeyA?z_mCmJGjnE``JD4D>Pg{&gqWZG z$q4qQWxZYspkJtrVq+kJ_#MU^P;6XO;BrUc2%GGGeEt&Y%?c@ua2m!@qDd_cZ7s5? zQeF7@*34@R2>8<8O(9B5PEV7$^>y;)7cI@>`JH}1cvn)Uv_S&cckv|Xtn%9>eTB_D zkTxTqU1<|J_FrQ#q8^0d-Lvu3>|s=8c`vT?6kU=wH(xwb`U~gLkeQ=;>@89hProrZ zLJ1bnC1BdjBpy7;8c6r4O_4-39lXe4qxcdZI%Lj~Fw1;c}YP ztt+gGlXSM_rM@ADuwa ze+ZD$S&e6a&ofQVd2aFGSD@Q9x7E5Z^+W7LYD=j9!-%8X+9PH0!PJG_?MG3hW`v31 z;PM0O02z0M#>Dljk67=Tv%6gffalRB{_j5NK}Qb)42&0snM8@*QadatFa5H{I7SzA5fxZ+_${F11cG&{2r+2W2BCfNv@T zoI3YumN54B=9HsmE(q-q91A#hZRpf*ub25r0v(=M*;oI*i|_Sxqi6kE1ucoK*!xEz zqBEx~gRRQlFR?XQHOpdue-uNh#H80T>W?n>`rAn);fg)6N>7EQ+Hbqw;dxds>j3+8 zuyMux3+Q>z0Nyev`bp{pLrA;AB~4eB^V~a{MtF~Vtav^t6l^z+x*ZZZ?t+M48ydN!Q78e#coVWC4>}FOW46s?z z^&MyV@3;Q5ACjg3HR-?QlQ|9`pC?4k33+XRwr!ADv))MK2`34lHJ>K649Mpd{sL;z zDH4D=YcCk*;Q8fq4ky}H-uG1(EBxM!a#>2r&g{kS&_UfgJT<(sp zU_tmCp@G)niI4>nEE`+{b%ITOqpF%i?LEc;`CPBoI9K7k!n%)CkCO4M8agOI41#1+ z7-!;h1U^=SwQ+!&)gt?I;$Xd?Hxrym=aleC_7f?{=Q)lD$R_~j%&&ld=874>Z2(J> zEd41VKX|0vNcw0NXr-SpoYTkKaC}&M1G6Qd4sC65(Guxi1I9DcM@Z~ z-@;3^UGzM6!Ja@qo#7iiQT1mE{e#-O$`3i^T61p*Y5|d(*TnD-iAb~wH)M{MtaCw5 zK>A*F8ff1IzHcJzhJ1I$unesf#*fW2N~p5YaCEqG2!`exzP;27eVvlj@u6sCMTs|^Sah~L7s={g$pAlH3B zll4WHPuoD_ZEMO#uqUxe-_c2(7jp`~D=f-HEQWOcoD?)@*>NNS+70&R;Q96T23si3 z5;2pwIZ6!eQPzIly@v@7zBx`~%<{9raPL1K0WS4cmHC$`%+_oiU>qjm`ZBHQV9*DB zaGU~_c5NzX5G7yiTfPFF^fuoJa&Y||$DTf@J?DGjgPcjh?FTgNiNEU$q7oHugYDY_v&TF)pxcxi{As6NW}tnq3so*v)scLoO-gF@t$sdR77>KR@x__c!Z*^V z=8Hi9QI&O($K++2TZ>^=wN#7uE@HEjLqsa?7PBWtWg2Uvvpf0*C9_|tf}Ei!e@WMI zAKTFZ=7Oa6Ul-{a8DCkJKk>(6MD^Ii)?9LP)r8ObhV!mR02`!pwpPt}STV$xIMe$I zHwvs1kWaX@z`ZF{3hp!7TLPw~GhX|LL9S4&)T(%)X*IXIkr2yvzi>f$W18iEp5OV>Nv#LdTej(%< zJP>OFK`d*nAm6l6=!^!T4^vBtsT8)WdMrLp-N>LG3HhZ#U0t1pV_YUruDi|oFX^o7 z(eaV!HNhirs&t9vyFZd;K+%`SJ;F?4?Ro+~Fq?)e{<+ zwv+{i2WgiyAvI_g_$E7p6|z2t^4Y0Z1#?vQ{#psSc+`gAcppWH?(xLkYP+OheSh4rB}=~&zFEagJKiZB_l95CXK6vVE^{OS7a$@? zA>5O%H1%bI@NVLL5VY(h#q zOTay-oSO*Ij`%_cfW_aYe7z28Q*% z-+{sj<8lit0^CPrFs~OK7ZTH<+^Ez=t#A1H+ejpmYPQ1*kzX%U7FVJ~^dK71;t)%K5WxZKFb(w3<;L+mJw^V#nAmo{wj41J*fv(PgZ(rq;C+UX5J4=tE; zbzAPcWyxL|8LJ7F`>e7{dmL^EV^hjtDzU)&8^6eT3jy$;)s$w|kGJ-UT$cP<$cq4uC_yaa?!pggzwh}_9WuW{mEsc{qnsJ3Y*QDxE3 zae+6X9>wN}6|~vf5~i1dLgSPh+d?EAU4-0+$21DWu1o97#SkLSL|J@8tZEOki(7L2 z{{~(Cq%DpUiCGtxdXi-=)EiozcymB3Cqq{sB@20uL5+#aMNiMLGWHAuRSi7^pJt6| zk?MQO+S<`?&M{6cVU`*%Z@P6X-v{KG)_o*4 z1Lpp~0d&AFv(wm9PSoykPQdl_qSVEOYG zJMT4__{S2q8F^9BH7i&%HPj$t%b_J!BLBCDCH>ehoBIV|(CU5S{#?O@TIn#V{b7~(}AKx!f8Ej4TY`iwI4aR~n4Fk;GkR`5UoXP3|lDCO?^K9B< zX_{TN75*5q>XJ>l?D^b%trq&>XC|?m?q~tCrw`egL!O=i!wMgQ%XmlR0NSUEABYmC z(>{IQ@Yf9n?@8bS;Tb)VgDznHf{E&GiE(v`UQ{Ycq7ptH%U(WoKGcbg z#4*T+{ui`6rm}-~huX{jaso6vhH--!w>6R28(qUQOrS`z;?6D&JdhIShtCgu$h^N@ z2F|dS7t#x$;Yt!= zC%x&8ns>cO?aqq{^jMpJ6qTs_Qb45302-8cK|$iH#YL$D=L)r0(z9^!DD(iV>_zFp zU#wt}5QW9(8Tzx`evI?Cgsy|?Z)y#g5&uBH!+gMSFia$4psYfE@2@xoUJ-r_q-7*_^mHU zYSH6X3T+n93&8lOMi^^MZPh{r*~Js8cB}t?AMHw0DBo&pGzHq_ zzBzf~^Mz8?VPGt7Enr~tSr=C)Dq@;=5aDxL(tGdyyP`KI_rW8l_@#>4huWKsQ_Gj` z&dv)FHcymI6Q#n!=9`5SbiV7E`Ue#3@@a0m*!s{|ZPp6GUldh6n0N`nFL}RGy&6d& zqTG0g0CN^+j6Aj?l`cA9fi2r?{MX1 z&j7kw%codG_#Pheh`axZkLLUY7ex4V0+4$f0u-pbiT>8u+ju4v=m?$Wm#4X=|M3S~ z<4g(b;!RF`f{=$p_g~ZPaA47DO4R)mH1QLKxr5ttzons~^)=Gd!BR8@t^e#$qicqK z%L`8b3NCm0?ALx34r-CKD3E<2+U$h_C8lB8e;ID)Q+&eM@*f)34{9k#Wu}~K8@53( zp{Mq|ER~)yI1`+|^mfN2aF4P(frdnxB2pLi;x`NQG++Ls1@dH01o*Rq^$?(GgHc^+ zH?TPGlhA-my>7jM3M%st(^tg2j2x_)CXuLiMMe+^`so?yWt1D5kHg*+)8e4r%_~?R zZqAzf8UtuImblRoO3V!O8~=9+FGKK4`0zWMmM{c?g!XY42|aAoGCTtt#p$LWmGs(^qWqyrF{`>OOq0HP=TM6dy^nT@1?5Djsv7`}rSf zK!*vAZALsSj-&b1@YMF3VuR_^*SeQF5*x$dr?oDh6S86sO%W|Q%)W7#!IZqH|BXr( zPb|p%s2m(IdYdMPU1PyRWXmQv-0eu_{kMJ7Sx%IdO#E;-f|clRnAwj}XiY^X{7^eQ zdC<3$b)~4kWe-z9lwjUI`;5qNph4K9QGBx9Ypkn%@(u)nF3mYiI7*oF_=bJ=$@mK1 zbNY;cIzwx4sW+?VR!!$Ii%l1FA#W5jGe2fn)L7G_76Nl(iHxF%XG4v zjT&~Evn>?GfkF(3kIw)P;Y$Pxm=Zw(ci#(ByLp3&ci6RNAD!Nr#L1^3XvV3(8mWli z^M+<4W>wFQ){*AE=8aj?tP$#EwcI(PzDC$HspJ zCYz$Thxk;Wztv;CIaLt2NGn)!@V{?2Cn;hYbd6NA?8m72NIaAK*>b3g`U|z!5Rs$O zAm#T-J@y6jTkhhU&bdp0IDv+Rvh(C>>vi~(LTwHSp)vN!~ zLtf~`pRDq~{ag&vt=Ro`KeRnJGya?8x#y3ec-VxUbAUtw&mo=c4Wh{1ZxRx#))M5k zVz0-8Yy;JAmh_M!-t~GM&GxVRdBHOxh{qRzX9}1`!5C2l(slC9b(?OCUytGw2j7lP9_JDDKPM#wLu?eEmob9zq~8bZnQ!% z-2;_1=Nv` zc(X$q0qKWI{Db5B$yqC=&Nkm)2z(u_MboF;`1*Ia#N~fet>Y?9sL5$=$dr}|G1*Jz zO!2J{Z}4G8+k>}eL;nj^$5bGUlbZh_$Nxy{18xKfp6Ftv@e`3$bFhbi$&s-&Pv%~< zg?t50S^hm?Urmp|b2Wx$Lk$tJ4jsXgc9R!B*ew$vvK0P5ci_w(jCehzYBmD_>~;?P zBZmLI3FEwp)!mC(`^G*r3v8d6Z1o|!lMeACb0)8!QGIMkIc`ulN@V*7YyV@T6V0&@ z!RSfHb^kvOAC(sl+b+JNu9b&DKFAlt{$*m+q9H18*YZOiPKR4P8WDRVlia+VKooe~ zWZn7K$c7<+DCqEhUE@FAS@1syoW4ugK?r2ik@ENJ96-x`*Y|nRep@isLODl7jI*y z5v0J47ZE~^f-a4(tkL;50=iIbSD-Syiv9bo6gwFp z6YoU-jh*7NBkZkNYK#u~#@WZWc+?xQoq%9sHxs0|TBunmKRY_VeVtd#;u1w}Xxjym zUMRl?(x(($<{s9xfN`pu?J#ZspfFEO?0!A*g`KVW@;N^D17G*QtT9bD#HyM-P0pY= zGxjz@jH0;QH$1gl;tvVQli4PU+=vS~LV#cobZcJMSDl-z3~e=Sk=5N@JhFr}7{U>; zyyV}WiYKn4awnP^65PDYE#-|;cubJl3RbH)+f#;8xnNPg8g`28Qv+qZE#~=y5tXOc zgp_zLTCM>Rr7T{1fAEc~0W^E1zd!at?b;mCNVAYbIT}{tzZ(09WL~`&xUZ22*Mtf+ zAc#;9sO$Bv9Oo=41y_9MH%Pz3r*X6RW!Yw;%s5Z?&3+71WQBh4j;JvfTe~?C{tKGR zky&8A#Zstv4#@MNl5G+tAS(_iej{b9E0px=@p~bi3MqhGNuLo7YXDc~3N%e#A`Tfn zaH+QyKEUqiDnlJaJ&LQ75Gdr(e$13#i^|N|C>9Lmfm+R!?MDW>K1K0knoYSY9%z=P z=4Z(BoCV;4E}QISxIt7~Z4|9v96{HLElMG4*uuo)k%exV_#pws z)tH2=_6wx+73_s=q`+mPkq_ICad8>M3izk~x6$EmN``f8WG+;YP2k@N?=aO|v)>Et z7zi}C+ZPQI3SP-TKj*W9`oH~!JxR%sK_A@S34Dqp_7ze!t75k;t3m+9Mbt6Iug^sE zDa}jTq2Gkz1hG{4!USaIosbXTtO>3I4hIn1lxjodiTtK10p%TL;J>|$+hfauEi4&~ za?t;64EP*3{=t=zt55%NaEZEJhyV?lYWW=1Aep|fKI%d{ELS}01-E8Q@rq_@EnRiE zrZRzRT_ipW8A}NM)-&&&0`E(r8*0?kq}Ub+g8+^Fw?+n2Asthh4>N=F?KuN~+of$- zRy*c>&ZB7A_CUS-0XuJ%E310NaXpw(f^-|F9mI5DpuDSFv@)$-<=4KySRjcn!!ITS zrx3fFi4L8TFl*lSQPsuZ9ar#``i+yvyaS@_YH`rI@~W5)IB@&9K6j32f8fA|%<;uE z$pifqi4B;IQ7<-gV0AfK8u6Mnc;fS~&rHQsj@5j}0W|Xpd$`VrA?d-_t*(11kX)MYBvnI^+UWq7@ zF)A{a0#@XY8O!n@FRU}PUQ&%$fnb_*Ma&?}BT(;>H=3o{e@!$UN1MI8vBJ0&1{y71 z6z@*%zFhXr{Sz9t;HLWioC!(X7J$ZuhGpQBXLG{u_>e~0^oogxsBam!*v-4AuYbv_!JzddKFZB zGWdkrI{>R>kHO+2o!<%ldqq6fW=g|PK(7N^%FB{u0wVNaZ0R6t4RSL=(`#yH12iO>)&yzvQL3@%Q@^qF;9r3>=lWnGF}9(n zHdKz=sH{MY$aX|P!DsbIjrO!?6m8+zIz&xw*r7t-{gHw*6~x?lWX3OrMD$*oqU{D0 zpZo!^R)W%yRi&{Xz85wFW;LT+a1&%hvo0S*a)%>WllFc&;{?kUiRmp1d>j|wCp?sj z>!+yw`2cnfl2R~BS%n^~MuRb8@ANvqFvo!BZe{wuj-O(O)58`dlGvKziR|!!_guaYcg*Q8!#f^ySMZ(? zf%_?KKV~iTW1rBd>P0T)|42`6U@S&$O8A?uVCd1BGxSk^eq?s%-oDo1xOu^5g7? z_S?hXYriv6V$1a7*afW=;#(mZgoN~R{!i_XXxDp7OIc!E>YFahk>5AC)TTJ{yb?;5 znSXix{=A%bEEl@0ps_zn6~rqVk^YIL3H)kr5AAN6Hu5uOFLoGUh+~==u7 zGKTN_WFQWU5m-%)4+sBK=|e=q-!w}6VSAWK&<3?B%MzfZq)kU|e&0*PUOC7}1`>w& zp1cO4taUweaT}e6KJR1m9~QTCD_AMTl65*bLaC%jECBqkaeeO=+Fv;)sVCYeP!><3 z1V6liU)n_5M;IY}(H{Y>iC^BX&@rl_-Ooa4K$z;ji z;S-pzahkGtDxIlMNWUFPEBn^8uP{zM!h#omv708P#z$OT%)_`FD_;494<0kxEqt*r z{|9HdV%d__i+*E9MNbE+DrNU_A7AzqbgTU=xZNC<@2eWmN4CL#Hx>5!jxCuUarmr_ zZ}huL8(Q3uEIi_dcScb>FCAk3EGZxch4}M%R5#t9PVA&tC6Ds>jVSr)#Nqcy&Ry7q z?lGk4k8Jz9V7D0Yl!~lZC`(!b&uo)yBY(MK%;FF#RHW4SCpK59^u1nmPrYdq0;RSHHG| zc|_Wrv_KRUaW+}6Hiam-_HW~}kM!^#P2#|fGBNb8X?!JBHZ+yTiNayaz;InL&;3!> zS_1nJ0$m|ke~ce+x#3__js2f>(CH3?me0ZJg_c(+w*z5BPCs^YT zQ^~e3P;q=Kl=&7a78Wmrk@1rZ+#j`*Ft*)|_n7fn@2t2twereX z9t78B86^#Jb06b<@yH~10kvT8(L(v{yG~_p$3Pgz!<3IM0s`~l`gLFB#Jr)}pNW@B zOi>CSJ>|WjR6P#S9MyS(V^lvX{lM5Y1>J>s6oUrI7D+D1kCoN?{ z@GP;BFy})MYpo;-NR;^B;d;QH>1d>Uq{8`~+)wz~ZUhgUzMd}WeEMxMcq`nD%QfxV z7`?K=VctP9Iy49GeS=xwbRj)^OvemeMUB@dgJPllddYGf0@ zY8$89hoAhuyWPXgvKPU^kO$UqVG1U|)=t+Pdjcj6rE;uZgP_?6LwZ$FY5PdaXGS$` zVK;jhGvph4m5R=w!97mW{*VFfRng&aT|2xmLAbbwCi8nY6ws}vDJ%|=Dr#_ErkOT> z((C@uBsDkjqC)A(kAj(UUiaJrn?&0 z;GX!zrcEwI1YW9ylNEQ7nRnxyVe%3o*Myh;23#iTf4_}Y5{4rSHpr%m2nXY!@>_8G zGvXX1JsZz>K3n=marjCeD$YRf4Hajkw}l$!_%psvcssH49Kisqv1)IB4Z8CeB$@V< z|K$WTYuXe@h>+|4GHL&t;y!2G9OulvNpp6eh;_-O$S~#6&HK0eyQ6e?v-NF)>vx(& z=SGtzyF=te9+8=^(-fnX*J*8aSg)DH)^8p%ZV-wK$q+V#?=T1%q8K1@a-lME3@pgu z02npgkR=NE2t4DXN`2w0;d?667p##<);(`aV7ebU+UMu`B)@Hb-i6;#P4s0n=zMkIR)(*RZ|63EZ_8p zwqqKQbIQgYy1u;0;x+avF)XNTdj;O(T<1#8G<&6Gd&s71yW?I!p&cia#gF+%tZ6gr zF8HTThp9llgqf=CZH(>GgKhio&!jd@C5G+(>Q-NMe{NTasY}E7969W9c`7-<%6D_s zvE+?ss7ggzwS%U3^Dk*YThZv6x_Y~anyS3zKV!shO&H-(C!s%+{a?ei6y@H-{!+sQ65N<3+r}4Dl4!!Ctr}+VwtNbK}Kl477 z{@tdm0p3kX8$G2f*U0U8Wt=Z=g6b0Hh0X*ri!HlJGAa&)OzIPw zrKy($rK8CTRmS1Ky^>VMhPdq3*-X8aZMVd(pJmh%E-J4sW$&piQT}~~l+fL0uI`Or zwxm>2^Yp69EW*C&-~0;kOQi!pmKsoc1x|GnmXKU^rHqez=c*3yJ4X*?w@%JQe&X{| z=H|bIbKJgAg3W{t4ahmuAfNW6FvDLE<_bELl1&8zu?Ml0QYT6?VSi?c5Hl@X99%wT zPGvA;9&y$WxxG} zMA4firx%-*JJ(JUPrH61!(zYkI&PUoz2t-RFv0PrFcbm>8=h#E?R7K)^TEs7k%ecW ztqcIcmlt$0H3UM#&*iMoGi4Sd-?!7D?R7G&aTeN(ZJhFDA@gux;4q>Q1Zq<+mlTH{ zKVyQA)2z-p7FHe;R-WXaV4~`uMeT`;vV*VWuVv|F4xpX2Hs@`d$$%L0I2hjLJ0&l7 zeN-{~PO{shSSGZ>k*GB8o!?vzK0~Yuj&S}!OVk>=ts@0}&dc02qXQ)0P zGi9q2Ouw{CPo&)Kl?oBgbigK#EM^@)xD1VmAzuD48=ySVJ-C6v@fqrao9tv@R|c50 z)3LWr#E*WWxNTC8wyC1t>&!t0!9NPU^7whfh>->_ih>|5?@U18J!XzSM8hQK7^%`S zFI%n2;W`H2bOhI*HBV!!Zir?Vd=1M9tcl?{8hQTiTuL>m~hEfti}A5xpk&gu)I z2_EIz)3Z3Pa`jk;vRMAGrCDYu=eXjaRK%N`eZh0Y<-A>Ex;Fmv?wciJ*WKFgdn-~u z-6jssqwd3lJH5$?Z;4AC@%U6%ofYfV?>Fp{%`o|wpY6$!6XE=-I)2xn3d^Da!xZw= z*>VE;+mxy;I(U8r8rujdGlwxy6Pdr|)eRTRoEm&CQh^n!%0hYiu1Qz@yT(&`o3?Lo z8paIsGKTsk8Q2)#t8l_TmB_L{6uWLnqtV-=#>{h@#zF;Oy>@ATN{u8nJ+dIU?$u!} z$C1hva%i>E!u@8uq^*&6^$XEHj>$B~3o&8luqe@wf!_4ol$ku!<1!d$Ch9~)AO2vR zRh>H})QUpegi!VQo@llsMFHnad@X%iHEwU(6~*~KCEbr8XxF_i6Fij?#8*+rCvP@! zVlt0fpt;PLt0D)hh^%g76gXC-RN$92!wB5~u~ukRQWlK_FrzBB(2CsUJY+BTj#8IE=gn(Rd1y%RCH?9x)D)Zp^ZAoixJT*iF`*v>!uub3d5Ad;UkB z01T3qu-%I>3QzM%>TeOul0sQy79zmc%>|ygTwX11S#OsUF)WxCVmG3ne*7{f4er&N z_r7tC@RpNf$a%PR#turnz0hTZGZ|2w;ofYDAnDpuL~&gLhCbs(a_*=^#ozlXI`Q>8 z8N!Kzp4Ym;a&WAh6n{{y*<+mjl*jgoCp>M#S$oYL3ViJk*WA<%kfN-6%Lo@`ac&{N zm&y!MS>0ZKkK6u4Y2zh-+}=k(h_WHZcNCK<2YY^iR^BzxBeiuq=@T0)fq*1>qSUvZFJ!MH!P!40UXo`r^SS2h;dChut^2^JW)zJ_#ll zLaLTwqvGKy$2-<{Jj&ExLg)BoJlC?N107gl8nON1Uwg8Wn1xWIv6oYasz4R(>|oeQ z`Nu|zUT3rqV|;$0+7w^Lgvbm{UR35LHo`%|Lsp&NM%`2-H9I3Ys~buFtmf#vf&oU? z)|#aq;-Se2(SVc8bOz9Ir?)lzxQAanmorqa_1Dx|R3c|}ER<9}`5>LoCM`FLo<DR60ZTcLguO@?qpkm{S3Q4nw%!9D@(vzYIqjCi}uz z%$eUh47}i>p*joujvhL)`gRPL4@VeY?Cr5aP?0cYY4}nPg7xIB1~qa1akFz0fjK0F zQo6XaG(3bw_Ta1Vj--@Y>v!sF>WZLV`FGQB-6C9V^%70_82CA=aWmR?+Mc~%W8eRJ z53_>%MI$hy#3D%S<8D*hdzgY&IW_OB`x`dX0lZ!bQ}{b1vdpfgV?xXM=aU?vhiBYw zKDW-Sr|UTIQWg>&D%(I*A31$-p5!eBI|uMh;crw7G^#EtCXn@5aHkdUzP z%U-MuFT7Db56WdXR}To=pdmD$e%-ucRye@)D4MieSA_HFNIMcWnq*pj?Iw!Hfi~yB z2R+Y_Xfj?kWJX`yzR|}j3Z1a1)^(sNq98gbKGt1aCOzH*BNWvyFN!-=dBBS~C;umU zlFm)4KSGj8q$*J#bB2h)!Eb~v_A~byG7Y))BKp~SBysa!^e8?9Oo!1fN~u7TZX*I2 zL^vY+=X^pe{EcrGE-3xKXh&Xwm{eftT<)}XSlvV#A0o*#(K4u!?CLN&o6Mur@=(KI zYf={A*!`8>1u^Ma_f1hSoIFU7~94u0uVgIp1Csh%~h$WvBvTcUo?@Ac)H)htfNI5rS z>gfpLjXsfT;UnL=dUGWoKdHnDrMpx(XrKvfOOkKVeBW1fRBUZYS zUKeHhZihbdgv4W?YLR8x;No;k)TPw`nIUoyosG|Xc~&_j?3embKcM3h{>zkGoZ+(Lkd%DmCGe`W^H`E9eOa{ee8z}C0p27ct*pw+ z5f1PMgoyqSkz;gfZ?X<`*J@3y41PdB*1J*LLKd@r*|KH6{U|57=H6L82EOD{pTpN)+*1V z5#g7w{IC5!b?q@2ek5^XIQ_~gk zP^T`ZS&#Xw4Ar;y^x4pjVov|EgJ+Pr=*929@VfBsNZ*Edr~Bra+=!=ZZE{;s^bZ}L z$13S>ViBVUpVl_iq%UVo317Bl2*w}UgedNTlDqC2MTr~t=vVu@;9^` z9{~w93_{fUgwt6rh^P?ipc216smx^#fz~5LT+23|MbkgC@x?Dg;fCi{FD5Mt0IF~n z-iC%exN(!Tq~Gr4h7yCghBH^FJI$Xrj1kI(Skv(c`O|2@mDbsz7IZ$Fg*hyduHgG7 zhiAE-sDte-VipC-DS*K;3Rro-Ed?ot_f*kfY1@Uj_=a-O&FeJ3e zy(U?h-WjURTjYZ@(zz(&{M9`tBD!f~4Mo&wG{u5_=q+v~HD&$}YLt5a(B9gYkdIRu zHIG>tP@pAM8K$SM0?QjYtUeh|Fh|Sz2}Crw%8 z@neZ!P;<}|#uTHfpfj?ttfEKAgpbjo62ekt@G&vqV063}k&R#nja_nnEd#UBi4 zbr=u)zcrJ`KtyYJOUL00$7jYrRk)7p1~bHtTN(0xcpcxj{o>9N@_`ahxAajTnOjHv zAP_ZZG$L3x@FugXb0-0j;{(`9>RST9AL~q<3h>>tbkQ{IGulLYuEl6+0l;?-P~Y|U zcqMN*4w&%8l~P)cEZ@Ciuc4EpG|!t6s~GAh36XxsZk`jx?m7)1yWMMW2<%7J?-lSv zpc7@P^uARA+dOkEBT=CFVbfM{=@*aEIEXW$qfb&g5hv#eUuQEWR3HP%0mhSMLGZ97 zO<}(6;@k!78uGFSmXCB@WrE9psSP~gdO-b01t>x`{IkRkTZb9WC7(BLo9glm2x7&v zlWN=XahC|c`#R+0p68MA@$~=+0SX2tE0OL-I~ZlN8zm~F37Zi9`qVOFP_5e}0y}AA z5H*aEz+Ogh4Ie>R)jCzIL{^t{@xwPd$%M9fn5X9w-B<70RBRt;q}d0{5H;2Gvl_LvRrtGYC*4bY^T}k*Y;CJa89i z9Eu_#NC~IouPAvVNNyasUYfQK_?f4Pa$9qXL##BGQ< z4?bL0Fqm^YYGQT_sO4ogA!RilCMY9>f6xdj-sDa_tA=`)IrSB!JUK%$a1%Q- zvi4;jZJ7m&oswZf;QZBJ2$?xma?IEBpDx@#=;}ei4riNJ^ai63!=6f@panzF{tLy* zo*j|*Nu$#aSUQ;&46&g=&SZO#Drf}`umlyXzYnfAZASK~2b_P4i{XaklPn<$0M?GszV!=c#c*040uX{cP zBv;FijiB#-d203Agj7r?%Aj=Y8espVWFXe6DzKX9KSPK?e$c{iuhYm|RyYQc%Lr|- zzX4%T&)QtHE|2o{zFI3>N9j-@UrJYyytjUD<+xwAP~uqA$Fdj))YKF$(2}vpUiaT zB6WT{?vmE)+;S%CHJNFzx*tXH*TFF|Q7*yfVXWmCX7RQ8n)#tMbhTA05ZB#mzp+|X zdOO(b?%Pz08*`jm4;P!u&`#hK+olFPx6&Slg_Y5B{Wbna)jwTVjb%njwauJ zidCXjICoYxnE+m@bU9d!IY!LQ1kzo zI?Jf2-UnJ!4&l%Z0tXoB1}Ui_N0Azk?rxBh792oI7!U~o5hO&Ur8^zE8$ki-7NzkX z{oVgv_Y-Te&YZX3Q+w}+)4oFWmXThSsMPF*jObl4+s6AbZbR=4rU!)eZ%=<!b0qm>gUztMA# z{4EF_;9k>>d?m(?%D>ln$XQhkSgjRRKW8`c9x_d!P>o^J?2LF1fT>1yiq`f|WJ^1E zmcUI=qm!Vpicr2|rSDE0`M7s)^7^h<4o<43>-zY)qQF)+h0+>{a@>xKOT(k|Z}c;R zlC{=f#x);ZrsH(wd-w0p+X#@WPCrM#_7tyMJGo8HXz}8qHe0-Mu5n+H*hyg)z7_0AMp_>&1$gobQpC`MiV$Ho8j&-xiz88X* z{-x;0jaetT6H+Rw5E8QfaQYr}d_K%* z#~I?{EWp>ZM5k*yb3nMwE3_+O>lJTxgRL@X%f3;y)1PU9-|T8A1}?g5T9|hMW-^cT z)w%U@no^#*9DUz%XdCot$gA^+{CJfJ$o3b&jlnpVU-t7u;$ENJM44gXA1d#?)4lWf z7e?mN++vjXS{;8`Ftay3DjyEvVwz5t`+djXJL36o;G9=F$CY6Wi?DZyDNa-L!xrnt z`IAVG*{t0e^ zsSC_HPV+9B($;*-l`n*#@3F%9n<{&#F_oD+jt0~0gHH_P3qL!35&yu4?;`wiYx5zl z*=dz5MnFt}X~Ih&Ac-v@et;|da_$+6`Quj390He{9y_13Tz5~N?Tw}yC3>m-#5>PS zB;s#vY}E&leg)HO(iRQF!^ukf8-)w=oI#@57xJ0bhAl|Ix_d0V6$gxHmmu6ak`LPW zIo~@K`TbHY&4ZS}_Avn~0qQ&&*F4?wkS*O6+Dk@2w+8;n9P6BqcHx`zaW$xlDEy2T zc(XEGGdwnSEj&`~@q)p3U-Y!~RQAw|qRS$5qj?y zx;3gJH?4@8WNF)Ek5oqyM)&>mr)B7rdSWUnGQ$k3;0bA(PlXC45E1|p;n%f4N=4$B zL#ry#-t$V0h*q4EYy-?~7eZk1>kxa%yk2{9LP-`Uj+rmcy!Q*TC7_MiWLv=H6vBY% z-y^Ra|7Nzn<0yn{uJNzv4lR6gL}fXb`IU_TW<79mpF~mvcP2=Kc0A8xf{a-lht^2g zKgSca*xVme0UcD<_OYXNLVrIk1RK{2e4P?#^V_5gS}KMjEnd<|+C~dw3->hUbwZE# zGTZz~wPmIlaK?S#f2ib|2X<5^>TAtWK56AEdw((a)3Y*m%$7tQPJa~}tVv*1jp*Pr@100p7l_`=-%R@W?DMq5dwq=5<%zx}dIA15&V~YC?3u&i-#w8R^CxOI~CU^f1_-$qFW_$p$3Cab`i) zd-M+Ii5bl%jIII0W3=V>Fufh} z7lE)2X?U2ACX9yos?hQcU}XqVJq{jvxnxh;HAsAHTv;!+cZ&rymDoeQI6ykjT9RUq zuVaVOL@95htq}yX02JvQOH$@Ez~v+}%U=HYTWxVGbOC4uH3(hvRT0(rHxyvV;@FGodU0xBJe&nSjeCy#>CMJ_z-$V@Bc4qQMUslAW9N z{wDhzWZYDSE{*?=RfQS^XMUX_>H)e*KsZt2nwqquZS)`uE zhmuVF->w8286)!JFz@8_F;E6U*-)fd)5d}16>Z6;rtNrz_;*u zuphoODua<;z7SDzJYSV2z9B67!w07tC14J~XZTW_N0DIf-#)WKZ>bTz4ocgaq0X)n z!?kUXlzzRpCQ4qo^tT!1A>io};Jt}df4zAc^-^4*3^hsv5$0M8FzWD_$W!Km8&%Me zMd9CW17%pLiTSMtt^+Ys)ijS39#xiYR(;uqq9siX1Tm|0U+Q+2%!i#Q--!QgwMDsd=nuUCjc~b39(2U-4qpKIa!b z;6#EeNYkWOTN081DQ?MT4*d(*SclLjP|5MABEO`E`R|w@D#{#{OC=Th?}&AH7{6zT zvtLmc2fe%}8}uWfNy`#Ms33CL&L^luuyrg9#H1}>LJ+T)Xq^&to?Z4{<`E+s>m&Zz z1~Y{*8KxLKnh$LxE#N+?^S<9o{!f3fYna&FK0Yb^{;hItFvXe60|ostb)aVg1*_Zu zPo?IDHgCKt<7K$xd6yB84SXR1za1}3*)igAERHxYbN9z#gAi8GqdtLqnnGN?`cV z+%@9>jU-QkE9#nlO0byBIemZsOP=9R?p2cihhHOBPY(`v6if^y+r%#mhZcdBLlVFL zmCVPFoK@uTRbfAnox@ROb^BOnX+2@$+2qOJ4Z1a1<`-w1hnVk)l2=J!a;JEq|CXzC zfc(qgxu@a-H^_?Jg=uQD+iBN%Rky038KH9)h}4j+gjd&G`N+WA$?;SPkJCc`WcIpq zDCU9&Q}U-S5}4(W6R%d;!^S0V=2->@pSR2ZaC1BHPJd%}yI%-l))si?V&Q={Fm;b`H>FKs$nU6KTpWmew9jyS zdUepJLgYa`hr<>rf0dD*lfoA|8kt zRqmsa;Yj2Ekj)Fmkd+I8Nk#?m8^1v`{w9gZ7tz=7UW?xw$98Q$a{t=p&SKsmaz&7m zEU);Z)A_-xE=%@`Y@0snlOLLBld7r9mwv7E*9yfS=c3TLHg+V}f20+KT7?U7UeL{K zarx4u1RiRGOVQXneZKvlzwj^80>ghiW&)sBxE^w>*^PV8Inh14gjE&^0U6Fa;fAo<5P)Wrz z)YNY1Q`O>43mBv!QAOd!IhzpX5nE(3vF_ip>HdQlQRS9>;6vTXbi1n#J-+iIOkkn} z4?;hTuH1d$z=lb?&QN^)AClzVt}A>Dynl5Kt)8f)rL$FfCFzqX8xD`kTU92Kpys|~ z_V#-4^v3D-bv4aJkz2653;bsEpvdjyCG)&Q;D>LC`Oh5XJC(F91Fr9Aj_m$HT>mT) z+m4L|+q&m7l@`|xXbBT+O461vt_9!uZuuz#b1HBP-v-=KUuwz;6Ji5Qwes1}_YSuo zieq*fbO<&&2B2PPEV4dXTD)}m^?@oNwv8v6%GhErB}ZqAJrafc5Qc@c+3_wIp_x_I z2wsBFKT?@_Qy!85*T4FfyK|x{C;@VYK(FzX#i3dlUO=6%*u!cNDc1zo+$&Te0OQe? z@B6%c^%`154qm8VxwE8)osBf+#a@XkeB9N4Fimaej<)Ful6&_SeAsPIWNI94YN3WA z$+=xWu|=xhVu^cF<*Tx>#`4GZVAm{*pi{^_TWOLbGBPsSl2S6k(&1I-aKGZNt1BhJ zwju#uG6z{I9EV)Fex<(mVHvqA$rXMC`_R2YDb?hx`tKN8#wUWzYCe>l0T$Ee-_C0W zepCrT+lJ(RHe=DnEju$B8rU|#-W2k!^vvN?ks?Y<6P{a!$|7g!SZeEqtDa8+PaXL46t58hGJcrA1&37ETF+$Ao@?1V8aOI#0!$~36-v7`Xw+Gq99x@R1BmjRRKic-|?*?c?&YuZJR z@Sh@Y%g{piRap)Zj%>1RqLv@wAL}Z}Ws$QI?l>!GbA#?p!d}=jEWS0zm(>wq|HSf7 zs-V`i;?i%8!%>4OIX>W(8YMOZXe3fKf>aRMR9-B7&x(C@2pBn2xsoyc7`2w8`2yYv zgxjO^)QFn;l!-<*lRAYvaB8NhS(}K%UN$e;?b}~v5C|~ZA`;bz^Cz9qQ@dGvdfk40 zni^!5jB8!Hw`Nw}z8*05q=pbCiy~AAUfkh^ifJf_wUjwVyqd+Dg}an?cCx3jYus*bQXNH(-1G^mF{)E)lQ+gsUZXQ!r!{eXUnC6 zhokTU!I!N05`}6+xJ;B}xc51L+Dc>WiU^Wwm8>zAtfF-@>r4%80+Lbhrfh2Y8!0VR=&a~7dL9=F!7Bs-=Mja|FBqhJ3VF+Ebr z3nDA|pz_RsQvImpt-;4m;ExkJgg;5KvslIYS^iBU8%WC(pU?qYb61p-byuC1IJse@ zWL0M(?E(}~_u0NB`g62by1DWzyKSoU$d)r(+F@rJ^}HUInc*$kh#odQ28I4X8jP_k zXO}aor3h~BRN(z6l8T%=_j3e-i&fc>oSRn>AF0ay>6_9may)zr0@i2CzpK(@_lkq& zCVIn;gEdt)lymBF{$zpRdCE+50twWcuP%#~B zHvK3_(Qw<(CxGCe7!(k|AKU6DofeS^9)84vLNAJi+vgor)Mr(uDb5Jg2Wohdl5^<~ZwmDS=bJRMj0S{Ol$ zeRsrRv4DKVQ!)om5=UUg-w6!iN%{p`Z$CK)QyUHF*BFH;e9a%8w!eZF8k6Of@p^(0Gq4PEqgx$77c*Hb&YGM+n1?DAqzi- z*E$D>hg+%#&1xGpW)hW)Y95yREnh5Y&Ts91w0~3Hq?L{8UBu>?Q{~vml#G7JXghND z)0{SAvRM)X#JRM0_p-aFuu3Pw_b;3qg-@)o>t`bE?l^t9t|VZ_<#@q2FV{8sk==VC zChaji)OKA?keo5Fzw4;OJB<4Xd69MDPf6gFwv8ini-{gOaV5tt8Ee?~DZn%3F{F3tt4bGE^Y?dL2o1HYy-=EO1RFM?4YkyrG}ol~gn$ZZyd1$5HG1 zJFT^mIvcl8+{u=hlw>@b%r;v`{@b9mwQ}kU@s=>B%8le7H(yQIs>2#4<~|QF*`ed% z{roR(_}(JV+~}Fe+??G$*?^T~1uKa_<(ILyrfL`B=5-N!IWxb%8Xx{8f*F!a`iMpH zQ~z@!N>^EOXyd0rHJz;h!^>$zK0d}B2KA!F8s3!alKf;iMp6`iFZ~N}ikST+!ddX; zh;Dy#@(Ily5zZJ(E;FxAOjHy#Xy z9USucfI$R3>>eUFMe%$*n#Eug`-$uTH8)C+E6WY7B`!-I!6N7;Z6mb^$VVGYUk%w4 z|9(Sn`#g$!n(#FGf>58TDC%*F7^V#Jjs8$g&USBy+2};}C!K9%udo1Nio+|ZVsRrvo;tZ> zT85y{SNu-LywQ-!-+-I9N_@;Bu64O`dhROPh6cPfqo!!YIRUF zGahcPXn>#ZQIVd{sgZj)9`|78(vkr*{DqlK=kTZeDpfpJY!H6;q zc=-KkW{M)Vi`lZ5N!WX4RD=wTk{1a(fa4%EB!BwhGj12W{d=E_?cbQDk6_%$v3lfJsFs{Wb#$y%`BTXJR zN`ND6UdjV;-((;e0#++n@4m7{iC9u(HyDhM${>iIF{zRo{8$Wi#1)#a6hZU?w}=AE z!P1_Np~@T@*#7p%dtxvEaj3s=_EYPVB6xp!$IV4=668UHI(DI}4SL$5j(N0`SR~54 zLKzj$*ek6J-palFWE0OmxT{D??|omf87?vv%oaTw;)GTa!@V}f!ovBOCuxJ;M{s8i zkGL3tOKL4A2G0GuReRzaggK)r1H<5YP7>{Y4{<)Vr3rp3%v*2o1yd&5RSbSsieh^7 zf6_Z+dHzN@%PrJGVm`Ldq`Bg*(}1@TE?Bc!NF5pVkLR;qx6=zn5W~O^`8G@u)s2Bn z!QlXrG|BBD%HJK8cS(G&9moQuuY{DzhXV<09la`QY;yb;r-9#Y@4pk zwlf4}Nh+x=dbhl9cHh~QbEEPh=PwGVLaDX5uW}Udm8;eq_y?%_81vtL@o_Zm{*G%* zyw|K!Y|N4yIXYrrC35&gKzje3C3eP?79Dog%0)$g|N z-<>F4l-TQVh41h;YvGl=Od7T$u0tBRAF0G{#qsGDR|+n%DJ8!aO@q5H8e5G;2V!hL_tfAW zL>i)$o!pB@OQX8BVW`RFmG-9|d$ys1X$j^hw*nWL0B<-)XaxwA&t`}%2k4Y{%sQKZ}d zYAsKA8g;c3$Y@J}FPEo8J#@kSokS?+cYZm;7P-h4NBEXA=1=}h4jr6FwJ3tgmwBZd zNy^ZLZSNS#m?yJZj^MTF_^TgNMpj^ac7@Um&vf@*AnFr*i7}M6S(PI#^bTGirKrq7 zi)8H1pn=zj6oW3Qn@1%i=8cKMVO_ntFH!oSVbU*AmY}z%KAY*9gXy$2G(oZOGH@T6 z02f`^C)1=L;_-!Uv9kUkOKnA*`e{I??MZnRz(jlxQ*^iV?v?!n_Sj2EL&VgUOwP^m z7dqQ79@aelsmu8iaquPQLYp50Q`-;R(@O9k6h!Zf#`i+~uqb)V?|HavY0PT2iDTec zQ1NRUO){mC5w{Yda)OS`kLJ@4gBUzn`hOiY{BTaIF~wRMemqn}V4UX_U>=WS{~o?* zi2JBHc%r!&CjJfR{NcwO4*#TGy!PJGQ`+vOHhH>t0}kqXVqx9)yal9MJD@+%v*84Y zu0g%1@CU`2mz`LlbjwVqcv#^+mByvaMZsY@zW^hYsnNsSV7SN8(=-+X-7?G&WUKo5 z4pu_58|;Bv0SV7^;+NMa%I)~R1@Pnyqw$Fw!9Q2>K@KNx#L2!KomSWSrii3abK6yj zh(An1_CC~p9?hlqJerLw0#J(nri4R5hD3)&9Q>(!u}8YOz+C%+m?rH_6RhZ@QM2eQ z;C5QmWzvgPrQ?BvIvx~ExHnN4fr#kbd4RHt)lN7Hdjo^kB*w#!J-EXQUIEy8R9{Pa zERv&Mz<@Do5yVgGTq<&H4(MgzEbw)}a748guBqi9AhRMnO#40>-dX(iAm}IuBfhT) zA>>suOeV&oQ-clem}xOg0}_%2+Ak_RIrac88+nJx$(bMg<&V;fHH`2&Hfi%!TI#4GJUbRXEa7p5j3{fqL|c z5Gj%83KSyMg(jNgL&opGfYyTAdd;M_wpG$!gP#-SL!8l63tm{t#1y}8<=W~AL8~I} zI@maXx47=kAoB1-U|E~#*l$pgq3~pvt#cT7>MCcUqFL>*6IMS2X;{Q*o8V+$!hYfl zks9tMkXfB?`oOAiBv1~gf9S2UIFR}!TJ*8kT>J<<&Zp*bpT0O|Ajsyf@{<%^kGv5# zS3cT!ZUP|>b2-TrSJwDFk95hIuq=s^o6V=_ih{`;?d6bI@$XVFQ6$TwZ!LB9w9vwo z^NK9)FQM(>3^0|J!}kwbi=_yJOWS1AU7R#;`9bI;1!8F6w@!paytCv!$wKx-<1T=vWd^oF#=X5E~_z;L@^5Iu`(a{phy*$ET^E=%a<5 z((;2<@P!CvMP7YRu&^K0BSPxXvksdzWu>iM5Znh-Z=dn+#T*(K%`Ld1PyYzWZpT0x z0(Zj;Kel|wv!?rM-z485YU4OmD-OYbI_mV%hTz-ajncS?55nrm8Eqn-2D=jzcHLre6bwdB1| zg3F)Hh&es($^N0zjj;F(erfykQ2B9Q-0VtS%)GTP18g+MIPfCWFcnx>q}oZhgvcl! z+ZJPNEQNIyb8;mxqBC06w@C-&U%YomxA_~8bMnp#5RbQgk%A+MUnJpR;raZb2#~;p zW_rp9h-B3#=HK)c#QI=sfp{cGOt4#2}n-D-n_f@~rB?5a!N9IW9|tskj0l*ObpTkvf^wOJf|TX ze5c^$k7y@xBF0}-Lyz?ym)f&W(iZcW;1QRkxQUn(Bk$S$-al3?Mw5s439D#`Tzdpm z8~1VIK8ogXL92voFbcU0mT^Z1-nA3^I$?xL!D}!y$XRn33>dADhjBk1{1_c*6eaL8 z9zLu=RP04L)huOa{feGcaf@Gt+kwOKtuQ9vRXCsl-XDI{^N{lOC)?rMPNuZ+?nDgM z)pT_UCKu3h_>He|?{m*>JA>aJ7}riiM*>dg&<>nTr;u}S_FM|BKr$X=^7T`Ws+^?x zm8}o8L(UgM;2!uUL=F5LIrpVPCR9-dIWoI8uphAriGIeV(@nVgK+|rcAoxl0MH4(3 zJ(eE@xroKWa{3%Ie(+hWOA9vaP-%o3R7kt~l|kT^S=FbI0Y&n zv1rWB*5PmsaW1rJs~i>vA~f zC*vB^FVdFP{qsqPk$HB{P%?KypG9X#VvLM_rs{BvA4qupFfi9zwLk^y=01u5-%MXr z60u?@S)*spnTuAy!oGJa5hgrP5C?^oWRQt7d68Ec%>D3bX&%r)3trKINS;{^_dM0wD=RF=H#K$R zm5UDX^2A;*&@l=l-aS(i9iSi~e<8={pSu8`$0FSk8?fIy?i1A5@zvApPMl|Z0eygy z0o8e2uGxxe>NFB6BE!3uGApiUZMS&g>YfwNf=QWH$$A@es2i^4h($c!(yf3{$fsM| z2u^Gwqe4M@E&~V_UaR4uV4qd$Bv_1Vj4v03IPK0^`?pGS-4k|Ka z|3n+PrDsF4zLW1@QSO$@RY80lc0ssB^-vfiu41X9Z1em49qY`YF@pt~wPhkOCydS# zm0zUtjXljBZE+;K&$RBCD)pUFb=lh~_0Iy;H>ok;w#3PF4sx@zb~vRogqWh?w_dqf z(nmBGdd*MehbRWSMwL?&Q6pQ;JTsg!13V#UG5O%DdLPJL9@DYnx%?8Ma}Qy%?<`2T zmfk|@l!J0a_xD+JzA52#OHf%DO78hJ54V-nGedQKeHh^LjtaGlEEQuLL8P~jtSf;J zUbG_Sc5JTX%(>}*tEw!o&k6&OAm9sUdF|0ub-juR;`H8Zv;zvtBfsk?)&ti=Dbu*~ z@6_@tMs^dyTFjDK;i`E-caovUpR=iWdZ5g&UTRJf5Y*y`HZ-@NzIaQVe>h$@xNTfZB^vZHqOKBNRAx9kSk*Y9u7DUpE@7_i!@4fp&T=gW17%3Dgg4wI7a$u|1+r)83 zyBkEI!=fR5Osw>U{x_~vZs^MGiJcL1ry6kZg6q{?4D?u0%7yp|bt>%~V(USuh%!>B z2!jFr?{*+_m4}wc5v{hu)12H(YkM+7QMNTH0Dh}oU^f5Dije!!lvV6blR5`{H4M(5 zD%(lyhNGCC@So4@Vzf*wwe0{D@XA*eglnnaAYFPzb+w{)=b zmfyVeUR{dU=5v|6;o^wX;WxMRi<%f=v|x2p6d1%2v^4I|9EJ>*b8C46n^QK1<<&iE zTHVe%fTVLuwB4c&Djui3f)*lGGeaUGvU4BR{quhDiSMKYVt;AXM5O-Cn>R5EpuF%i z+Tmcf&B%#hJg@>Yopa*j`1f(A4?a^X$6D^6P6I=M zeZ7IZ2@A-lKSLkX&6^QZ=(Yyk`PzmrBaYGE`9uiG&?BAMii8`_B%&%p9z-pwn95@d zV<20t(k;;uo*^h@xz)?8V(WcI#P^!n0g7@Ffo5Yr?6$2VgBp^+iA4ut@rM7ebT7=2 zvnN#4l&sjoX50kT%vXN3={qUEioEu0KMH#LQuV!AEZq95h!30#J{!W!XxmwZd=4kU zRTx@(dpf;M51aOss+mf&C9-969TO!dAdC(d#?-A1zd2YeX>0@ItkyY$--|vT_*I$= zO%}RZd4<=VL&O0rQIXIb6SL0LVp_yoP5X~}iawxE{8W}0VLYae#c&dZpS41UN(dEzKVYS3&M-=84FeinGSflCe zA<0lL89wzb9|EkX(StS2zK@~QnU5^iNeLvT=OKbeO7sQC-n4zDYh4Ka;?XKE+138}S<5(*YkB9h5|8ipqaw8)i_wUzb z&Dgd*YJdwNs6-Sy`$woBDDasI`7*54c&wq2odh#(;a7IF)XCb`T<_u5@KQg$wG78( zb-z`#9i4AEgf}5sw^svH4~rY)x8?1Y9aUjOY4r6D(|>7w*%j^o!LPaJfL`~&erInw zoB2e2etq|#=DpPzu?Q1(SVXCt_mf3+Y*bcEn2M6R3%i0_TOoYK&_9v;*wm@27H<_l zl~79*6i|Z0p)qbn4!eF~s z-*bMG*iLQ}B8-Rii(4JS`Ko4mWWrEZ1`q3A(WjBYN}6^R2@({Q!8R6F7oi%U&R2w4 zKi5}~XQzkhU%b2bG<#jmI?URv9q93A-%UbZz~3?kYE6!-1aaV~HDqK8Mp}Ar0Tcqh6Xx+|CD; zdmSf*ld3YPKEp|K#>6x)8S&6c@YPXd^OFsW^cV%lLme6vRTpc8W3+hq#3rO8k7Is% zY1;HQ%{7buD*9Cqeg4BbZnPX`jA|%T-%%MTVpaP&mKt?MD*6$gw_NNnMoja9GF$EE zeK(03;_*jeDq>?}p#NboP6#2K5hnA?VKNj=u7E!8gh}Ujs}4q1%4n`w@@MQ_l&GaMq`BgC96;4VMK*Dx_NX-orzW4T2YD zxY9#k;=~YTj1hO@1@SyJBTv>gmrRbUu-)7fXsrYwQESf%XjXkgzATvfCa zi}IR7je`;aVRA^=`@$Bdw@x^jQIpN@7-80h+S!?hRa9aKw)gWRFxm9hPu)Pf$;14zVWAUBnR9_D_>aeK(xx>o{e zI6HUon5S-O4#(l~HU=sGJB36x{zgcm6gNQJ>qedXj=p*Rao^ z#&{xSyG|?(K2_EpA!gnM64}f>69*gz-|o2%?~5+Uij*6=`=z0h7wsjq8t80;wd`LJ zSQEu1?P_%)sV5r!745fu*o;}^7UxBKZy|CR*Ep`73WYT5=WoC(N=4u}F-1T~ zB2#iPN!ik}7y|NuC3H@xIY-*u|KWE{!dnxW(|8yI=9760%ZbO+{a`n)-B$mzp5vTT4_kIi>z{Wt(mC3_MM5_F`uN++H25&Gi9o6%a*QdoH=3I113g zO1r`?Lr+ZW3P%v42UDF(Q8$A zLeSxI@Hfd}oBvLMZF6i(fW_w2N)c|?kOGeq;Q%0$z%PR$_GGvS7+>Iy_UNZtu6aMU z5|1{;o)A_!L%m+6#JmG<(;ZQZ`A^B0#b*Kf!sn2!8vv!V!n)4df#q?=z$Q`= zJpg+w=WjouoWPLJR%W<;#+w*z#l2(B({ssiag1+vU5Gd$syGY|v3!_&yVcd88$#c% zv+Fo;lpD}`=dm?<$C&BvCSi2PJ;W)mt|WF6akABwC3+v#cfuVRb=$a7;N-be{L1S za8O=8wFUlr6&D3=IE&}spY3K3IWs<>QoYP|SQj1y@Hk-Me2?fbiV?0j9b`gy$*szd zT`Co>cSQ_PpAG(F=Tw0TEResEorCzUpA+`M#8RUAGcSncNEXNR!24EbIRTIa5=i{} zBj}3^6M|$|=4+3XyzuzPD{@0KYL(0>jX0-P!-r*=gs)=YI9dGwqZ#G|VO63=6v?pL zz6ajMheb5(HP{+%i;_nWM$ggNdJE|l-JiVzb0)KTfu_Q8vM9pnUOyF3z23hlZuY%> z=21jQQUN@0zvhV}4@he%ffq7zQ!z7uo_+`+TKsA94p=QPUB7yGqU=rL7pO?F8N0^bDB+_#9AT!ExK-a_L{^ZRtLg5UwfDAUJhm%%5>02PNju zP$2am{r~SGSyAPHM4xDjLm}FwbzXM4Mv{8GOR(-4B=Wx+e>GCh{!NyS1+iE5$ zeol4>Os$UGp--{5Ka&!JnmY<3Y1+wfoeWQ;DtkS!=Krr-oOFn=omeQzyu~r@1v%ULpmvZ9 z{JM$AZMhRztVYzcoq4zy+gins3+qH;RtZGOFE{{u5Dd0`Vl+aI3m)7Kbb;iX$8j}s z*pvL)K(+|YeJfodDu3DkdG_^Fi7{pCBYfP?%es|}7`3~Ol@#sU)JjMU%coHcf+wx zg*YA5|271t0Z4c95wud5Mb;lua)0_o7@g@jPpvWc27AT9XQS?Ycb=!gvaK8X!lLj4 zFIaXwypZYd%(rUFdp4Veowrfi5a(|LA~s6k^Cc6Y!$$!^FN3Tn5y7scWZrf&^p~=K z3W?5gxc&Z?pXdNyHy`o%(vzXpcT z3dh+{nG@zuh2kq;YNpFMlE*r>z4AKV%Ip9lY3f;koh#W5Rh zH)gIU+pF)}GaT#Jph|iF_vLgz6I`5S_|N(Yc~tw)#8P|o#jiMULX#qP@C%P>c^lh; zdxM~4OoDPog7m=}3R!pE(fe(KOFrF}5*R`7&8(GBbA2jQqz#da!YojKwP-0UCsk0+ z%5H1e5Bw=vYKm&9n4|!CY+m4Y^RC?;EjoYF`HoJ*xC5Y!ry`n?==%(oadYN={HjiX zLtqJ1Fvv`PQ3_z{ls&ng+SAmqyUd9}H^P?;NL&lD{ZP%{u_&T?2bzi)^M72ZwAqe= z{0!Qo#`k7PFZ@*A@PMqRb}rcW~O zadAgk`e!Z6%mLMLR5wI>pne}FOtkgxS+^_B3BUG6G>BPgM9Xf|d}wZM1$R~l^!}7` zX4s}U!d?dJDyRDBBpNd3&+}rbr+RN;fx7ghdVpuuPvv{vb)FR!@=w5@#fiBB6WL#_ zw*vDkQb#W9-VO_-fg|Xa^a{Q`wY4uu`xPi6fI(j!l@0;d7V-R=GT2FeD#D+6FQ`>n zllbZUYTu2gjXpN8bw>Z*K3Smy!7;q9bUY(GW58aL;-#DEj`_x2iM+qo1hz)5GZ7F~W2rY)qQ-hOxf!%L9s*SJd4W zION1U~>&0}8sKqp7w*=Z?p^y|5p)cK7a=k;0wWPoU#$>*V@ffo%+2 zz$-63P@)fOGTW2@6<>eN7EPtPOh>ufyH(EHKVtI@=UqbN^J&+PnQq3lq+l}j`HX922SK9!GdEKVYh2{_9Ok-|W21R+(m0#}FOKt+Q|^afVf}{% z83^6i*k!pnri~2`X#?o&sG&YSKFyZjtaITvHyojn*U`l;>{0i-K7){pPV_AW(TQ7b zzA7;in3noj{qk9WlOe|ODoAN_2C%se+}&ZaGm=__Y)IQ#g5V$G4wxuP!ql0mB@JY-U@C zl5xrW@&DwvI|er%jdArr1!F5nyDBT|F&j9b`m`Fw326#=qGfk-ONx_h^9)&)c6SmS zn0+28rD|UMI8A#rj*rBK5RQex7nK1prPXq5ydWQ4@ItlFub|g8Fe=a6a!-j8+Yl|9 zzNUaL=QL~~c*)RTY#|UlV{B4I6GS*0D&Ym2se+QCLT(i9Bd8z-Elt=Xu4CdxzLsbc zg|lB267KPIa00&SfZ=9U+=ux?IO}y}N>0pOuhxcqWq6%W@}YwkNK`LoP_Z$}nGYfg z@vQq9$O>|uaYj^;JG!SC1~|ru&(31dfPV`II}@nFxO|2 zrG8cjjZp?sR=xo7rh34Z#Km5J;&sx@E`q+r_#N3;4>kBr;BvzndbaljKbx@fyMw}t z^2_MZAscGFDk*z~=r=GQdFSDeh=OMzsmfJQK|dFq567N7z7Vq}$`)T62NZKhgM>hs zg*B9UKIm`ezL^LWWS`0UIQw+|`a$)0q#Nw!@bLA=x8PJJS)Fytphusf+hV2_DsFh6 zbiEhg>yOIa6cFA_uvlUM1A4Eu}k<`+14<5X;f<| zwP1Yw8C2V&nZ>5db*Tt5`sMBVG**N;#5>U-isJ|pUt7{UA)Be6Lojw5-+4OD8d|!} zkL7Dbn!VDcU+6D2Fuz~E zEgar{;r5^`0^AH;Z<`F2fXaCQSK@9OgsJeeGd|mUN|QE^EGsoY1DoUxj!IW;U};Ye zp(|iePXd0Wny*6g>z2#IiJu6WS7B`Kk75U{(B|(%O-rNwtsY@G*?azGu^iE0=R+?` zvPp*-p`?!QTYXPqr$Qr+y$inkf@`zAeJ*-$mm}r=jJ&UKGS<7ja*~ZZ8V8*4D-v*H z;KN@(5e3yUxCWphqI1$0nLJAg311x5!5=wz4E2?EKszv{F-(X7mtjM!<;t#_Aak zY6Xh#?}n{|_9L`9pue8tmo>vlJ>QlK{7jg+tj`-Ug7gc%0cOZPt}5v`QNpc+-MsFX z+4Z$Y(SnY2GlvX-qsX*G1umRs_L-eQ=y-h`W!Frk@oCm;)G$pD{O2D!E|5Mek^xje zPggT~%8t=3x}}XbEfSibt89WpGP9BPDLES?Sn|}V;weM>o6+AWH}xzGFReXB4h+*i zbCmP5{YzO3hJpyKcBt{Mjr7R-B-f03$FFW`griyfl8JaJyo$u0RNa-J_uPWJYjWu< z`|T~@FKHB}-xyvh4Yv1|eA7~&fj(0QB=QC=MxM}(Y!xJy`v;JMR3~o@*^n3YCta^V z@c53>fG3W=6~iTc>Yt-(Erli}dLI{b{=yKj;4wvUCsW|FmwGelb=#r{^BI|BKlKSc zghIxls~>3sn*Pi~Ysw99y|GQxzCaqDRAY7bJ*2O!Vx+UgR0tzXBL&SCkk{C}WG9d4 z&iz5~#IB%<$h1-Aq1miBy+eh+$!8{H2E<7LQsWHTe}_9Hq1IjLPEmW`}3qgbJ&h-Y6@O*|3R9 znh=po_KZ$6(gIY~{dW?&`V_wb%Us_1u8@;2%mhBPw#>2}9_0;(Nd0vreI$Zr2$CA7 zt#g2+a^ztjr-%sqR;&^p(zH{g?&$aMTF-k3a1FA!@?qzw=^o7m0_S&FA3Tq#UYQ|v zM(=31MTe)vMx{{1;eGC zFAi7GV~u7p^ciR=HelX)JLWM{b>I1qj~{|-m?+&+g^n9;HE(T_?R7TKMY+WNjl<(nm{l)%Ja zwVTQ$4>2UhZl;WU+p{*@c6-7KK}L898~P()vG1O-PeFf(!l!{a@KRZ<@lwe}n^*c7 z#jCx309605e?kS?RCTNfCYCooIiOfDnArKw8U~r^SCX}G&*|D@1Bu2|q6v$&^i?L& ziWUQZr6(B%M|85nLceeOWJ5NCh_sE7_9xQ`Y0^+)Xz7Ohm7Nt$4=^xoyMx5%d^d3m zGEMhg;>h1}3gT@~q4+pR^O-WTU|cMn;IRXK>KKth)Ult7tQeT!;355rh9#<%m#djl z!>Fm%>GxTEUf@yLdozkfBU6FJ7+($SRO0YRdZ1WvyIiDz)13=kA#4mqy#P5Mmcbr- z>`pD{aINT4=jESO=ka6V-Y0Jn>4HA1UPkWBDzSwS4_?!)?38AD+N1F4Rtk;o=U?s7 zpGZRksN#4Un@y3HG%o<$V}+M2y^8M0W~Y53uEhGaU|8!OmEs!4my0Q=&4zQ@>YQlC z0m}6k1-g!(2txm4nhTU0*{+^O{Lg#7DrQeZ6BH9?HK=9(%KUTUged&nCxa)|=H)8U z;j#jzoZPQi;Pk>9M9_HicNChrDZ4k(37X6de5JH)8I`gL<0c2W_|sU%5((2lxa30m zd4+L9mcnZbPTh@20e5kBHw5~yU=Ko9JwK*v@5R7$G1T5r;5^=z6GO9tNVnpWT)Wg* z33K#8e-e!JG0Ko*&3WzPeRvxY^W|OiTW0}$oRrdc$C;~sXc?mQhz{Js$ z_ZFGYe7bp%1vEj2DsTy2c{)p?_9@zv9`PXW%1Zp-!-k;r6-6qA%6mv&%%}RP@JKaKE-n-9Pcxw@;F6oY1dTBQ%_4D~oI$dU`KW$N!r?0t z;&<709!R5&)Q;%hpoqp{?Ai4mqiyn1EU|09%#ajn&e#O#?hy zLix95|Cfcc&|ha{#HAdHz<9lsx57>1vZ82X(A%FN3O`d+nOeLnd`}JOMyC}u`vUYE zdEKh#Bk|&FNH7`n`@^0-2*tvqa-^Ff?V2n)_W2ozvm91zgHjxKS$Yuqa}Gl*yU7v= z2w+5E$zR4D(ujsZ;10+8p)aK$A>L$|*3`iVB1|+ZkRVNUR_Rrepa<7C-x5HNc_w<% z-hNsfnSQtmt)6DHT4x~^oIbKTf--f z<$wFJEz%zE$1?g%3AY;77J?yulr{1S8PvnyW)i(Gj_4$(@i>bocQ{^$`_o zxri>C^maLl4N2Y?p%O!+4MS3VNG3P2{F8^Z^q%Q|Dl30m)$U%EhC*hbhIM2ZO!M?B zat|3UI-y+B9&xa-X<=U8%mkHP@E0}v#6-+J0;EiA*MMc5*PGAZ{UItfQXfKe)0?Rc z9gs-^T+O5(k`rI4IpR%}_|pG-5~#Y&>uH1tEcxOwZQh-exMiD@aK_P9!X}?cul)dy zY#sC=D(`>LxGB<85oP@FcXZ2D5Kl`N5%fAOQk4BN&jgu9_nMNJeAO1%Px+{Z6t!Jj z4l8AUnz5E&1dB|j@DcRnDz`h(^~<2%kNEz?JEW zI|B9jKW1OIlszneHFf@Y9cfgqa6&IvZFJo03kewaP3hopUhD}>-~FC_}S>r z83AUL!RMd9A?GwT3Ew)M2rIS6 zBC22jq<0{0oOPS(zckZ2&0JkISF zun}3KA#La!O&q23yS)kcpl>TY<;!f5W}rUb0HbY+dtGDNkrcI$lmRFUGF}!Is)tIE z!kS*BT#Dd(R7xZiY|z8SmB?zTo*c z+1g(D7C1`73rCHN=LUYTT7m`k(OI<(qMX! zpVFB48`##{%&gPf>Yrf(=-KmKqR*e9@;SCw+;73IG?U#=et@OCRdcgKO;RebmY6Jd zgZ|=urlqoEeS_0M_51}klO0m-Nzx* zJn_X!1`f9G`iHC+ur*?Z>RU`ovAbtQ_R<1olGk5-YU1&Nx}cRS^w+97*F>Am)L{zG zMfAiDeq}XL5?_=sB?o%d}ULWv^?=hoIhVXN0a2pXxao8!SV zfLJ+&kzxwM~D~*41+3(j#q&j zZqCU?oai}rSsO+xI>#K|cw+4g`jwsY7yRgjS8GSvr|TiIq=Y6Y`L)Ak#eHJ{1$v({ zO>KMcBy$Uq;brKEN8lqr*S7E?u)>Jn8b|3qfuG;5!%!h~P#jf-*3R3cc;3z*_DkK4 z9h?dQ^_A*#!3tbD9V<$^TKIlDtQI1!09e-MB2p!c`t>?z3o1M^EyYc)q!6-XGHlWm zP1R4P>gKpQBzQ2*Kfe*U2oowx8kfx)#y4@nCcd_}p$Y_iqQ_*Mg~zCQe7@C~s>8_} z^rVo3=%sN6rOl+XLaXc>{RvVhkR74AQeB(dMT&YbL)+*L0QjQh!2I+)KVmm9U5Mcz%ZY$l-z)R94E9-wMr_ zuvG+BFp8mxWShr(tJ}nKtz-2z$Ua<8e^c(rh=j?J7!M@Td3Z}N5i`Aak$tgu{PLbe z&-#o}=O53P^@dxfR67ofTdhQg#NGJB>*_EyxgjC5n0fhf`ko1vZR7N~B*87G|l`ll^P@l|Z0Na%Da6 zxqq$v?^`t1eNaLAoJadp(blgnYXs zG0bW4j~CxG5K}+qYxEKjuN_6zB!VvWd9i}*`;@HSw^-TQB9sX8^A&7#A8YA$}RJO!#% z@S~IQv;ZREgblP@clmp*md3E_?SUvtvq`t9Wn=Ysx#Bjy6+g%eMmVy^m!7RpK}R@d zGu7WA=`5X!ycF7xzmb20oZown|HE^^@A6Qu>KbExXEX{Jds*UVG}=FYQ}z69Ps;U8 z;!pfON-jD}06M68XUcwWtbK6p1MJ3h|3)!=;7}5nP%&2!sL1&smziF* zgM|-jROe0`DYoUS_E^aSL zQT(c7t%Q4`h)SwR>60}Vx^PX3P{bj*!d5Gm5Dy(>>+SigFe^-g%x_kV3y9}n3S zQrCathJS5WrV2%kzQcMH0VcZbl49F^7T5mt3^(CMN4)zq4fjv#WDlYsa7Kb)EInqo zC(6e$5j=fCHHjBTQHxJ@c(B`I3ydwcq2@RGdzdW@U059n@laNREkR+)mPs#=MfT$tBuPUhm47h_@4X{`{Eq? zK3kWZZA~f;arq~T){)KZW*(C}GkI{em;l_Myuk@GAn*8r@VovXSd++$)|)q?6Tp$q z{)>xnv;P}9uY46+j9CF?PGQ^VY#8*(fQTp(7BO0oKEUzC5<+2pfU2@q# zbYFfEWJ`(WWelJhc$bV=xsHA&+U#4OCpPz9usc3seQQHg(@+tCExc50piu%Pz|i8Y zj`xZ4J{kOLR(|9iW|}suE<5D#f9`Z&QHrx)J(J zY)-8HM9ht8&1vM+yh4AG(`8rqGdG%dhWP!9pVLh`tCzw-BLSbbRAFNa!|e}Gf4ce; zI)8=M!K=pm5g|q^Cd~|UMhB$WpKHVIaJ~eEW86Xd%FNs3r9wB`)-qn?;7*F8I9YiK30OM73nV-s`S8^@S!==l<* z>B_*cpsw=_#X&>CXtrI|-eQzL!yXyN3P)ACyaXFyn^?funFEA~OEbpYzP!u5(nWmP zV?!sTK?y#?sUPLyR|bJ?8MVs73|NgvSvM2Gzn&oq$|4jDFh+scw!J;ztQWzCE%|xI z`W`IBn3dpZU-Nw#*Hvg{TS$F+77ko>Vs0Z1DyZR^350ceI)_m)P!}+W!T7~nX)IoX z2MjhZXl&)*zCuZY`-3P;8DwXNp_#ns0h&k7Mkqh6Myq(*U-uDgz)joBHKFbnMDVU%s#C96XY7_iz0FXIY{jKyE6?eBgx}x9 zQ)S!!VuznhZ;nvn^lktlvy90A|i@AP!4F34#5>x(J#4w=S@TlKGyNLw`*9m4*JD(1OE$QGNJ^zhGHF}@Q zGAun0iZ=ToCx;VOoIMKhW3Hn{h&FP+@pdGgWA`hMUSsggf{siM8$C)a$)I3UcQ{cK zCkV0Mp8P4YVExX$tm)+~v{h z1+aJ0KE?fjB)*n+2nHM+b#dU}NYQmGV8h%(h}zHqMbC^nD)PDQQ;7GhhCVm%@3^L7 z3*dAaAVRQ-@>Uy>cWjA#O$V!F1KugfkY;ibFd^2y@fumj(+1B-%L%AG(TX5~RKKB`HV$izWXoNOq; zwkCpV;5q*k4mwGtbdAuvR##{By61ZdLD6_#+h8;bs znx_)@+N<9*<#unIPjF@tA#*1_QNB`=yocv9Fz%H^=>iXB;Jk!Nu~jz>x!Vl=+0!|} zQX?nYn5w~^sGn$i{Ei_ebPOCp%{|%>e*$c;zg89h{IQ2wXmu3iKK~T_Y6`nFIZBuGy{`PI3=4!n=%ntjw>otL29{tSeg_zcOdmuxm`02++ zDEO(Xus1NYJl!vaE@e_Rt`Ua1Y+%PM{obIDs*L-5|L00EzcLEdlrrKfU^16=q=#af zO^@=|Sm{8%Yg*}OF~&3`6L;Usq4s_%*Q{cvhaIF8!dA`WqKK;ho3un@6fDf0bFwLH zC(OVR#s;Idd;kr0Gq(FIdAh?p3ALcbC>OTK<+Vs0`@T!7!jCk1fvPv}jV*~ae^)#! zNKlMVMm%cw-D7wm3p(zia__4I1+Qzosyx5zH_Sr4!lS*fj&bI3OCISP)mnuY9}=KK zUo9ZyQN|l)2%_T=*YyR}mriw6E+eV2WEy7^%|TSxjI8t z5I|$v()qQD)1b=bli>@LvizL4b4~iAmR-C<3X7#QXK~P67p1c_xzqYnqXNR(`})*n zRE^*F{a?Q9b^hAc^a4fuXI)zw!TTF;zK~{)>Hx8=FkA3VS=C?oC%6m?e1=PA_^y>5 zapkzC3)8mwXgJcEOF(w`r-}Y&naO!)6t(B-z>jT-xAKQDTtu}Wf$vd1ADGOV@1>mf z-^aF-c?PJIJ%g4J`Kp}CQTSDB1WGmTiIf1`D9yazFH|q(xp7XknC(o2d<%Zy*2$8N z@gw1dH5xw$;c+zP3si~Im1!;E@6;IcHuJge*r;X7@0>O?k%@}YSbyK7Tsd{SZhKz=GE>?yUBpodQ+Ab;PftYXsGkhF$3lNlZd~Dq$2~Meg?vkU!vn>K z`5Y|uuwcW;!d@}xW5oY~Gn>$syg)JC{#x>%H-8o)fs49*6vCEFvnL6Kw@&nIjnXJ_ zTV;;Yu#v=u^xPYE=JLtNEx6T; zF&_&(AQhhD&eI&M-q?68)2=_#TnUY8Ata0L@u4Ni)TCf*b!K_pT_?`_>XM|9&qr}C z&Pk$V9eZm7dy`Qun(%dW&qqUWKL_o%5Cn9P)QvlDDWdm+**_i^?*EQ>&o+!~+X;4) zUY^C1>v3LC1y)JT+`IHXt25F-nKbXjL9<(GI0s2_5;yr;SFQ?tG&Yx7n?Nt^08J2&$6$lYI9Wk>fUteWaI~Ygsbl(W*Wj3EC5&CTejpyI zWu5PUpP=_Krsc7QGuO6?yvSI}j|M59+66G8`bO1aW2 zRg1eJwOUN5rg~Djkm{p$vQ?pQl-2zE;Y-h*ARMEi#@iPA75~Y=SBqK$1myV<@e${~ z)EhHZ+TV!v9WIfDf0iBfDt%^e$v3B)wwq%pZ%aa>%Es47Rcl8s-iP>md-%P6ZU3;X z-@E5lEB0MdR|(-~w>f+hKD9WGu<@t#8D;0$tKM&QBBi8rro^YNtl3jDG6Wnv8pf84 z-mnR!wbpb>Pob@Q$-4UK*Y7Qwc}-`^a9QpKl^0n{U8o@zC})|BogK$it*8cG%)@FD zGF1$d->Z#KXB|1}=pA|}m^?yK;LvuHlgJKa>3Sk%NTT->nneO>2+4x%gy97USnyfTpj6ma9S?E8+rQbi8_?^BkJ;SH_{XZ+6Wo9kc&0N zWXX*Q?S~lHX?a`%-=?4|ko=&g#+AgR{lntN(xf-$%6e-J&qaMp%dDk?-}Lmkn-k}3 zDJ}#rNLNx87+AYpttN;us*j{#62djtxuZZ+04)^66f4wxxPRzb79YcR^Jd>pewI=! zJ+s(IS~AH|4;zWCAdtbRTh(>|%;=qg#;w?u8xjH}fsuX!X2(#ZWL$jfY~ay+Ank;Q zGC=cK9Np+geeirUrh4$hXNXD}FfbTX5CrjN!DZi;!_e|?x|?q6L(i7GKvpS4Sw;*<6#*NI zTiqZh`Q`PEpB_2&PtRD%Za;<#vNx5m0O`UZLL>|`XA0UQHZ{Z%wv}!HCMS7c!vox{ z%c+A6c#?X`DrQXTASo7TH8eBtD1L=ShDI}5YRdnglrQNEX0@w6icLa$ery7`5rBF+ zGzEKHp=3?|HUINW^)`honZQkOs!pXLZ4g zfaFRNa3nkzhLv~z-->3@{$(9+ddm%u3+*d2`(*(6K`;syd-z13CyqiKLKMzb%Ja~g z7t7KS#gnccGDM{9DeaFl4|u|r#?mKV5gB8b{10iz=o5A(?xD;#ai=>R1&vGN0ABgs zvl>7`Ya#R%ODN)rW9Pqx_Mfmds$OT}HqftVH!C+2G@G|WG1i38k8jSE8M%&~Swdq} zu$FT0`T@v_bIswO5#_{gEEK?v;jlmVy?~?`vO)oxUa%rzSmtT}C^i#SzW-P$&`nKq zIYViSx_Eo}XcureJ+c~5;SjzUE1$OU&wxC|xS6Eicet(=178Pa60A4hN)G{VL<_9U8_z=oC-Xabq5Nudo8U(OC+!Hn%VN zYXx2aecwa%Kya*Skl?&AZ!dO|vc={O(WIj$bY9#$Bag_1M}GG|u3#nnTaE0i#=H?i>mXjq z=0v%lPrW%5msQ2!{)X6qV6RaZBLX(Sb?CY`3e~Uljx#$5QQ#+AGT*vix)$*P_%L-q zD+=flFdVPLgePx#sZjE%6;|GLBTHMKB?KMhnO{oFGZ#byx1x4HWj@b11r`$^dAYUg zad&3muEwL(k3|$9ek~rGdcPkQH-)U10_}h3Sr$6L6Et)Ma@zWXtf4V7+EeU5gCyr} z21fAeDlo=u-g+ZHPIG^c$N>xlbR4CN)qMjj)C-{v2uW4K-&QEl3|#FCFdk*@qJ?_5 za_yOJ)8An~mFn*30T?vAMA}ST$Cv~Yz%mar^`#r(E{}qA&j0!zq_iB_IB?^4d98g^MI|u* zX1Q!Ga6kT+u^&mcJ*?42k^C>{gCMepdi*vLNsg*vJPIYM5^>-Wjv({wS|IGUyUY)vx&1bB)oLzmpoWZt@NyYtTu7^7GzbRJi@TDRCa1!#@2Hd{< z37Ghct#s|7oGF`SNuKaT+~i~Jubl}qi9`*BD}x>Uu>%huh)rRj4#-}38}DF*EGR5p z+p9Q_q6ndUv{3r#a+A^yu6WqnJMX#8{0PN2A$M8yo3h|&*XwNZ zRbhEq2}R=wA&+)b-rKh(-`}yZ$;$*zZzU759ylh40VrXA@GB+US~4Bwj|{M!Qp_~M zJ?TKjzZkefBb2!)lCr>W^p00I%EeX%6LqP^+C;GpCfbz?qYj0EQUevr=ZM5cpymw>G-lE$&LQg>T|#StHqiubASO( z`QH}|m$o0Km~8r5O|@!y@(iJ$Mv$R~SN;TqhZlYy2IM~!4=JJAx+QBW!t)RmyGJ%v z4rbDVq94veyb6uMe>BVAK4cO`Tgb=QVwwlW0*zN;`KS%5?|dXF1Mxbjgr7~R98i8N z{fmxnj_xxKbf|Ujz4xljvy`KJIfjBbs<~5rwAdH(@|9`|(X|M-w#_iZ9n_5mzVo?gkA;0Yt7#0qibtsZ!ZuP={ zE#5Z1h^>T4Zze_c3rZ>;o_=OK`#o)N6LUF9b<77Dyyv{j0cP_k}yTo~eO zK?PAqCrb=;YO3I=JBsBQehGylJ|4FA8WV{&HXDhgAf2AsL0{%OPs@e}!A_&jZNCIu zk5;`etq$f=&wfqY%#*T)s@kPEpcJ#?Qs3-X1cs=7u2?ZD zntpuX&3FerbGNM2D=+e=%3{?9GC#rB zaMMM{!tg)ymqx*n%|8#J2AF3WhgT;cLTR=}`bpM4z#_I`UUWcJ`LSw{0!gHq-B8Vkk=JTKM`0bgmXVaAnZ4r>~E=Imov* zvP@zAPjQ(z(quPxXfkNAKBkZOYHd21-9x{HT6}t0GogZ2{&wUP$`42;kD?uDoEb zfr@a7fVHA;eatc#f=~)w_a^1^T8PQJPvhL}ZwvD4SNoXIrUVLr>Igp0PO9!Ks3G&b zk;k@1(c=g#T*}d3=QVMdvjQja82{&sPdwViJdv_6lZ~OH%n4~tVz~(k-IUuro1eA~ z8VDXNBf8;d)S0f&=_H75S^LWfZ&OOYwE0~=U?~ucpS_v>E}*4{ZHT(IQNRZZvy+rP zSL z-nZo-3IyO@Y(tWqQqDc>bFA4B$6~8Xd@N;u{6X@98;YVpiiCC}w>l5Re+xBb4MEy8 zHpcOr*waa~0mAUwGDAJK>`tZHvJt8kys@BNf+9hO0?o~%sJYYU_h&VVr)vH=`VOTi zc^Y#ZPx$@5x&Tbs;?i60r*})!aV7W~ukvaes8wpl?{-n$g&VdZ$j~&u6m5c-0HSKO zc7aUYEYe*WpP;D2L0$t@4koJw6_n=*Jpv*K7GjTaM;p<|5`}kYBo|uvaKxSrn@i`S z@_W37*)xX%K@C(`e5Yvm?me^1av(gB+7OH`oy7^rW z_QxLvq(D$~QngbVEs9J~QhwqQPKD0Cdsv%|c4*_OyB2p>4uz;@L6KEpXy^pxoh$y? z1#Yb6RNL-`u7D7w&xpjg8jb28TAze^@lf=~Z)0}80e1-rBSL^jln0D|BH zaTSyTt#4C^z(Fcay#gaWOA%P|LMA^GXO{PT(5{{4=tFy07WBvTe}VtMV7nOEid5r3 z7tNiLOKIy{?~VlmI8M!2h4Qdu8-jBGSVcN}z)=4ScuZ15=*W5zx#h(*hp%V(j5vuh zhR$u<)?2sHz=5Lb`hiXr`UmX%g@hDZ!R%QZ%316A{v_Bc`!8MHKJsXL`HWBL>FErw z4`TOI?Kl#k6y25WF${w!ii&v#QlND=5BF9M+l>^po zWT}O=Q?aR{a)-}ODsE-3Qlc93w&ZP7p;+6IQ<#R#%%NM66O9xnU5|&$$#c}0U2%sepV7L`Ig*Z`$6lfZ2A^G6%p%(83r{qpQa%$Hx?cIpaoLiE1;j9iE^~n>s*S6*BKcWDAHW=Ma4iVzLn+?= zGY6d6ki?WAZ!5A<1LX;@$!h?t{(qv|ci_xm7W`S;iqz&nXE#M(yMAZojii7WRb3`@ zCP3r=&wqE4cbY$m#Et-zNSj3@KqbBczp@Xl9%jM%CyXgU!UW~&Q`AAhA066DjM3-D zqskU-Z1De#{LjI8G=?ZZJlWH1bKT`I*DSRDS(DVX-KV(io`;OFAl z>$^DeS-{8pFEFk;G!C2g;puWp=q^8HH-!PC{tLOFe~iFW2sb_Vr@Mk=H z`3(Qm>|-~Ga1CWcrCEV64*LltPQNbvzi8j+p;G!m`O7^E+P5&gW&VbVf1P<#=mbET zDr7_du6WDC1|(rTAew2_T^5Zu=6xeyc}{}7{vj7E@&Mh({5y@P2F!zZ=zbSi72yBK zPIXfqXF=O~n$2mrrJ-xGMc=j~RS|?`Je8CQUs3Z+`+*+___D#8G zFVsQFjNx`oK;NB{87fK9p$y>V6lxk3Bq`!>8HXr#Fh8m*dd}sx%?-v$Ayy|wAhARC z_?1Z>)?=&QpLi2VOh)*OOCaksA?germA&8EcPr0`VDUV=-k;wO&~Qh(T|G4ZEywf{ z=@xj;)rqC zHi;GsOok|X)M+WRg7R#i90pfNDem2J;wVA|{9K;H5vq_W=xVkRLPJ)_NQXI83#k)J zG4oR;mj}}jI!Fb&TW^msg*iy@{F-QNQSM%pm+IC{Z$Qk2DZ$#I)}F|brkrYSg3NuL ztw`YMz6UoWhcmk=n-a8BN%9IS=&^m_!3Se6Q7GzR8*6zH9WLoRnekptkTMrou%g?<44|pLHfJc4cHy63Q<&x8I~xIpC`XfM zBdfEkgsCGB#({#6j7D+Nswmfk)W`U7?DedJ$rWY5do8#zPulwbu?e=RZEq{(oP*f* znfCMsXb>fEc*0@Wy{)2#<(<2!C+2Q{5EM6%|7U_JOlatL%7%LqfSr#Hqon-D=vTTe zlSW~!Y6l!#YNzp?ZB+~-zajrKfn_isNE;V$eI95EfugruR_gV8RrO)tfygDLw6acT zMb`w?S--G;D_IU!w+OTgxA0DN@!uM!EL2J_h2J*>{V!@v33BfIbHQ|Z3ZX5m_#pBQ zQU^CoYw<5yi{R7$eM@`pAp#T*o#ONIg6|yk&wPLnk~SlcCE%AOmIsCa7V%^69`+Fx z7=WEkJi)eN03zXVP906A4#Rl_nN_Sj@xX zvR6B>a^dN_bJKtEarafcN-)kA{YvWi)P5k2(npAaS)vAoRhwnyGo^Hb@$<#@H7X@tW*hv%V=16^4VfV zcFb24AW#4Q(&8?Sn9IQ&$4@D=iDl zII0AHYxa)JF68FyRpxbQ3#kvw%mm)bIm2kVER9L4z(Z7m+UQ0WYXW;~0`6Z>iJ`J- zy+SnwQE{}918mn1B8AKJ)nN3znd;qZ6Ox$K$DC=RUo#MWg&7ny@1DH$i&oG|sj1m{ zlEhb!pCf07imcVqvvi)eG1k%n;CAs1^t$aas=!}(ioK9VbpU2*U;Yr03oT<}05YQ# zOy^TZRP@jewI2D}atp;VIyRk!eQ1P^>}8X=u*$Y4iwt`(ZBvsAkI-y7Ri$z?8?_`b z8NuAB3{)Gw8CCyB{Z8_MY(CQhl)JLk0F|UEN<8`cjCrs%6}D9=aoJ&m;u4K%BZ(`d zkSV5G4YJ|;^Sbp~i6ZLJ$sf3i)40z|d?O-zyL$?wNLgQ71@ld+~s;eYx#5>u_Hnl2b20OF5^?FZQ2TsOj929 zN(jmGesMUY3mfI3qxDjQK2f+$kyra8RwVzZ$w%7A#rAvt-r21d^?tnmT7I^#f*nD8@mI}$qx!ea z*H}na&-I`GJi{9Lg~!}j>+>|G9^sgu#gHH7(%9>_SZ-OTe^Hxl=FAH*Z_xcMTPzq*XwvWZl1;tajH%@M*zlWiNo+-&!1e&73Ex5g2>}%BRaA1ij$N=h9t;4;_nUgR;popAL=N=`|V|%T}FdA6!TxgMqpy)ARuO;X{}KZ6HlYM!drOtPr`DdoHx~0b)W(I^l~dLK zB{BWVmyL->Lc1buc1!t`kp;>C%9912%Q?%O{r zYaRZ|6h%ueZ`X!g@+q0TqWKcIb9QoTl=}iD((~?KN#ZX&&Nq%g4@0=S@(>kgfYn_V zK-`sMX)XVX8E#>B7xHty(ayr6qqAK8Q=BmbZHl6?v>6rpnfelh$N5tC0h)$lJxevq z{yv3>4!+CjhwU;%-lfX)cBCYJ#GSz_O3|D4!6gt{M&vJkXUru<9*Rs`QW4}ATa{;D zB133b-qFT!a7e&uA%{A{k@t-0o69Lfm!|A>0afT+H&ZFs1mh90_xF6l-@28p3l zLO{AZq-?yz}IC| z!QenN9Ure8=QI~@d=2Hcd-IcW2E{X*;2huu`ZF&Y_21_5Eos28AzS6HOe3>;P`D^9 zO#-&m&!50GB9e$bwIX3OZ_DWms<*yRdLbFy@G1TlZBSm$xHwS_NR3VOzkIhgN%J?H z?{|i61B?chH6$xI! zo-v@`9MS%wX*VS^TF>N#H2>g5_Hadvd=eE0@=*qKOT(t}vk35epdF;ww|xq%3uH%~ zd*=O!(#$6+0C{dDUGc!j>khTCd%?*7p>rd2Fj?+vvbj&7@_uZY6bIa~#G0OyEyGEGP@)l^Rn(NOiS>6ba4NJPiUS@4 zg2uK;(0dv7Lu&l{w_}zlo5TrNOX_Jy7EM}C@$12IT?xmRrB~Zu1_VAR&9-x6`F_O$ zR4Xes(kymfj&$HLxYC(8Io}!Y{dd0qHJEjlp!WbQ^|#*R!d)6ETx%gYWL6FCnZyqg zbO-~Ar?V_F)0lVaUKWmLznxx83h1xhizTA|bNb(d5FoMEGuf_$=bhZaZCp-PG!#-o zX4x=RDhM)|eDV=TCD-L;g9O_Cs`azS8Pql2df4~czjZ!rdE@=_Ur8?oY}=#$rj@r+ z=9>lZc&nCpDssjsg~U0S!)KDGnsNhS=68t2Zf6l^3Fi13HKUv<8 zu(}NV-(fsKqYQ`=3t!oYFvAvMs)xV3{ld>QBa%g(TBXkG`c*l`$<4s`#aC@Y;1QpR zl_I>j4Cun;lg1TV@V-p}Dc&^qiHZ*6HsjXMu!becyExVc5L1>@)9fu%##7S_JsQ=} zj5bBf@$AG0Iw67mb{4mFmaM0yq_O-1VhE*(t@eN{NAV1Y$PZIV?M}UiCoj~9r0r<= z!ln^y9B~sgrWhI-d(_T})|KQ>l`vc6vLs*jiRpM%q3ATjsy4GNo#nJ`U~- zJ-ln@JE;RgBr`%u=d@-R3C3E)i8pm>(gzC)9sZcKINZM~0}ku|3}{v@;5z}E{G+#B zZ8hoy+IdT%Y6ira4!A!k_8YIJIK@Qq(2LRX_J1|rK@!t64M5S&)t=Dpg*wthSChm# zgM;(`J5l@Hu?~`y4wBX+5a+r=HMYOe@=>Z7wd3AogChl_AvO3!iuH; zCKg;@)Rfx4(r|$9Z*}G0+<;L*wtCC0kG2C=3;b{S)>ns-BBmB~%tt9r4bJ8h4|D%J zk$Ab6v7JcfHOm(c6;&8IR8vMZ>+mbLS}2lj#2y9|ZeJOaObm?$4bc!8s7$ z&OBi4SUP)L0Pl{`j@sDd;$$@P*Ut3l*>|6%o4LlcIAMnAjg*^rePQRXPo#j&&St(D z3qSUZe*P&OwM0g018@Q~X|nzwaRyv<>(Ya$3$B0DO+wW~{k71CFOzeYnT?1`Bg z1wtM!gU#l`73_ZkLp0B}X`cS<@Ic9)1A`C2=+htk<{cOIrirgQRyB zCPtsYF5e1)8mZjnt2e6?i5MWv!2+(brdov7e$wx^_^4s-ACa59XkSk;qb~Sws4ubj z$@emNhTRLKt??91XJW4=Qg?f}hw0#m<=x0KtS|+-AK0?=o?0KK&KdR{=m@_98d=}K zot_1^gf154@Qkev(h^A?r(`(qBb|mAKHil@ehlpOQNcijo6VfZl~!y4FMnCXt{M^N ztI1za7OArjmMd_;E?@M|ie_M2!<$i`4QJ_CXI@aV+Tk#`B+rL?VklB-X?uC7hgYJW zF%2a9iPQpDeKtiOQUG%X#jlN0Ux+6_8LFKM4w9r^L~~wV=ar-08JTQ1!+Wz0Y?~x7 zGT#VT5~;6KBb?W0Sl`UNfC{YP>9ujHc@y?07?GPfb1MD*P`!LYMkOkRz-NzB75x0I zR^aA&z=Ol{r;)KLM6yJCxKC6(8MY%emm^ByBgur@om3piL%bP5rwLdEb5d3fQFdoC+ewFkxL)Xdt&%btE8FYj6(iK@%2qO-;*K<%nKj9YOn}7E7GC{ zPb7E;a%O`+2(2{}`p5Ywq-aBUP#QA;o9YtC$F-u>i{aql=Bq)i zPS!VBGK}^qz%gqJc;XIFbj5I14Iy8s5N+i6?@A{C(;`(QXNBJ`lS8t!kSWY$Sm+Qe z`3xAd0`lVgt{Zrn)LOAX1XH5JHRa7}&zU}Ww4|*VMyp~udy`|t=*u70=?Ucvvt!{4 z^KiocTzQQ<%zWNeRsdv{9nqzudw}KAL_u~#Anwn$D0<#G2}B@dt_@*xsutvxw5NLk$Zfg?sP>}6zf#MM^FkNKXFh4=Fd{M9crXh=GoSt>kli&XlUuC zH?#`rn}A_U&{J04g{KQCmFf+bB1;CNnL3za{K*GOHNmt)`u?ag?@fbUV{$qBy}2mZ z8gmHpsSYh;{&w3JN?R?zPvMnHT>M#QTqngp?njDk9g+w;~N6PiT4(AL4 zUcqw*JDP~%V8db@x{e@97LH{3Osz-YEWIc4RHw=08-mS_&2BaZuA=J7_u zZtPQ%uNhw4$&lk`P`SvyKeK<$Y>Mi8EZGRyIe!tWu4a_LQ`Qh=K-8s|{@5K`R>Y<5A9IaLDhgZkZ@-vpbkcP?v(#*ybH9 zOABu_On{K?&g*&_ds*;$ECQ@_r{BUb;vl9q(vj{L-ETzcweWWUPPhjqD_t05hd%xI zyE;)b8B(Wq<^j*!crO_&S4fLCnXHw3O$PgdfVWo-VAA&q6cdXkVFd2;E!gBd#vZu+ zETS3{c!pZ=$9;x=g0l2jOYN~`P^`gkbaIvU;mmgwDJ6BdBk++-t5$$Ltq$({V}TGrA~pl zeFy71_92vx;>8oV!jjc$RpjzEo~+gxod`dbZ&BA#<3p9UKUQOmX3->_No1z8EgO ztgf~WvYW$W4#uMJ-52VPJNJQdWXj!f!|3frLk$mtMXw1Lu& zp;$rf_CF|(gc9)kE!^^g>mKO&Z)Rl~A;}ZI@*9m$hhV!yd>Is{Zz(9=OM$V108Y0N ziuGCwX5kyQraG)?9`gi>Q0zAY4m2I5ORWB zW#lfMy&!gU>6g!AJ_^*<)WMQ`Vk-l%G%zT3u~yl?c^8MgMr_isNV!zm|4EM&LG;lv z13F)W=x%Ry<|wrfa@%x5^FWj6Xd}w`ci!S8IN0TOBJApRbbw^e_)O4`0jo)KhvcjT zzh7Cs=aE1xNZvWg$%hP!Bja42YcnrlFPW{)qwOU z|1h&xuPW7X`|t2sv3a{L!k&V()KAXa$ojiTk#$#C+g@3tEDZt;3^8BP^6@2j;Ju=Z z!GMCh4GD~#qpHXSA--)!^0%BwO_I4asL#;_u{0t-7liVXU69C?#_+n88G`0AuH*7u z_T3>e>}3W&cTYK^cttJnURZo{tAH2%kZ#qpeMJ8V*Di-kIEuuzS-7j7y)hs52Hc5( zVr|MMGQ|W7XDfh$Gr_?Gm(WSji}yCD)kBxME9jrec2G+CaIjhbBcz0tL6FC->&9?Y zn(hxz9Cm5Mv+q5vW$2mDoH%avG${hUPvNeSac`u>-H?pvNu%(nK2>ZU7MM3+#g{Ar zcV;r~sAxu2aW5*+WZ3RmK4v%=LTBbAl;Q?_wME#VRGQw%&^ve$;}*U03d((?#cxr! zmaw!Zj^J)rqe}X^1|rvw)5+0d*p`Zl7Pu6ml7sl_KO`2ZZ2HO+CGyJD1GT~x$6zBn zq~zF-Jvl0LaG4_T89est18jp)S8M^e4WgBm=kN4Gd;O}TO!~(vY*ONZ%cxK%Jc3V3 zm3|SyBMZuGw)&#YxXp&^>Q4K;>COWc_M|~WOB!bHH`)%(pfH|cZ%{P9dWrzV7r&o( z%$=8n7&~}zmqq-?MUHx&9(~P$6b2_FK|m=(_u;w>A;={Y+(`aA~sQ190A>VstE- zI&;mS$;32q7gRSW#K_P$rrE-ivhbRsw1Do9=4*#3yCX5>P;!~0p7-8{QX`j;`dYMa zp6uW}EUIe3I-^r1l!Y_@;bT20ox-mFGpfS9Ef zp5&�xR?BhV}u}6c@4|b6w&;gRi;Fn5|9O!?WJl(%ZizpR+nBgU^{hsFq@Q_nvHn zem0@&)s<+}=JGluLuDSe{H(zCWssPvr#OP90{&R*0eqwU0WoAwn=5Wb*!52byC;fi zHE3@lV~`QZ4DW?qXFJLO#J@k{VQq~R8av=U0x7H%772RXrg?2-*urT!o;A{$iFYpB zO0uJh1q;RQ1~klO3RLMGo(=y3u(GmPiLhmpf#$IKC-W_?xchX%d}txA5s4A%TxX-N zzy^H}YaDytiFdpbwX1=YV157VTetKYtZ;zI=#Kw9Ok6R6mlT?qq4=CMx+pKCS2ih7 z60-@>d^7~hg(p2p+!!Gw- zW-lz9P~+%n z@x@;)21OSxjJrN0YvY5GsL9Gc)0MhBGEUT$gaxNPMus`|!|31USBB6jeyjl3XiJjz zo3!-9CZ9ul#SILcBY0xo;wZG*<*-Wp#`Z&QFO3UqRd8Esa9aTZO}7zs^>Lzu{WY*u zqT3pb`2kJ;Ozki%$^%GZplI=cE}d9qq1*nJ9=cOGY$XZ&JJnBFZ#2<6ZLTBRv4b!_ z&`1h{J%!?lI-CJq^8uE&a1L|8Dln-3pq?v%kpz{4A!3BE@^jQJ=!}LAO{_sAX@2(z zzz`+L*s@062D!l1OYxA81?fz-A0co3J$77+8PR?S&we|Kxm&RE%Ru&yW!U}S zAC$$R(g+?3$EU)c2=FlP;+)g)R{pc;Tn8TW$oeZbSu%IYVp&$3RYy|!!oZ4x>nwq~ znr(eL`}d>A2{qZ{U-CSW7Wg9fu!GVr7&$Xp8t~c7y(tuR%_3F@+K2d*N@`tD`)Y?R zuiieDgNVIMvNVo8_g$3Rw->wC;yN}1;z$eF=cyS=0>3+!HdboZ)%yX1DIz6`?XR5W z$45bC$$+T4a2Oto8icC-uI7&2=^ z(IZhQ5+6-(pb+Fk1{*An1%CS9aR-kjjkgdrC zC>^x0S4=$8Drt^wQQyh^o{!^z#2CPQCnt{E79qvwhwF_>Am9J_mC{ZJK&2HHl442@ zasb@=hLQZQTNRKJ8-;*$`gOH-&mPqKbKAM~TJB@8L4R0kFb}MIYfQh0f=csjZ?{iH zu%A$xVpH}Lj9J8Cp(Bs8!>*raT;PH1vx;`Iv1@cyTo$Ad0R{S(o?v=+7h6dp*dY2@ z=xcQ}Iq0h6}h9~B5ioD@r%T39c`H{U!to2o=x;T9=_c1GHtm0yY~K{MFV?zkP@Dsj`1|X#Y;D?hRH@W*l+t zNyRVF?N8=QlwFWdo4T3*;c&>JVZ*E7+g&~&rrgNv?u@|J%Lh4?6GXs-)aMS zy;Ly{nKPmw{dsSiWRAW`GA!TY&2n+Rtios*7iS6Qd*3>8hKnDi16r2)GrWv56q`12 zp6wniT=^ZUUJ_D^_?)&{+<~vju!}w|IqQY3<9HzLTRtLY)*-%({od|9tgn@I)osU< z%|pOp&P_V}8f?6L8f=RJi-_$`6ZyJN30LSLhtBgFs_9(6MQNH)@_xZr#EW*(*8cv= zJ(I6jEs_3a>Yu7kK303*cCep=uhW28*VnX6&qyE3R-&8kGLj1){H>RY;SpU@er@F9IS*H_q~e<@Y$&KZE$gwVX(Y-abIY>X;(YYanI$ z)O=!pTBJps;_qi*Gi^oo)@hj_R{Jo41y#1)&T{x%2rD#&DO~|sS9MQM4>&SyM~{YKkrhQe$>yQv>R#hIoWt2h4G5~@$xaK?`o3u&vneu>}GcK`Zv#| zsOm!d83vPWM`Rr%q7MVqC5X{ixYn_B>_)^uqh}(BUtqINL@*oWU0XxRw;kIa zuari45Ld^}D5EUxd#Hx=N_hJcL;AhIF9p9gV^H)0dO80qoI)+q&5*Z0y+UU{=R}6s zh;KxEe{a$kInb#K4qmSa*zL0XFM>&t66WBS;cb0`By-&D0PG@-s61^%+XMcFq~$Lu zh=!wf%e{7hRjRBfy?`Y@p~}x`xG9sOTH+Tshc}#(s@SR_@I@lYQ#D8F=|rhm{N}lH zIjXdHcDuA*zVwWgs6pOh!uvas>|Eay+_P^;nw48~^5`IG(?@!YKXsk#$}?LuRc`2O zJKMCVz8&p|7wReWUfIhrurO)XJ@iCy=j_EQy=(NX8T&LdvwgcrUn8@sp{twn?3vD# zeL!NDJIc+HK-Bs1{G&%KUv_;|97)a^`{HAY?)Nn1+{f7sAMg=ryz|1m#LOVbWgM(5 z9{k9Jar?<@?grrXY^|B>C|6ASqGD`IXb7Os1c{@{*wS?5=nf1f}Rj z8Wl<<3jJyX4S4qXrV_588Luf^pB%oeC|p8|GT-g(Zs1VO-b;e81@z@gJTiSbtwmL{RUmUt_NIbyw|n{Hb|WteibxWA zfvSDFs>?~pDaRJ2^h|ilVlw;;0Dy!djl57dAG%JfynY*h0xS2z8~SaZo%P_(X#e*Z zx_WL>9@ivDXiiF^4}@kx!P85u*0Zf61?_*stx>8 z4Xh|GtA$lv{*E2ICoabDh=`sB@Q9Mb_ zQbJIVWSNF{Bne^20uVX((+bCA4}M2U(FNM$WUkk{a&BYJkuqXe3ff$EstjsW*dH;q zA2a2xRQf(2#pn3cd-e3Q)f`{GC`gj;X?Dgc<+p+8=;+;`Jlt@5|8{7IF2(&-r?C0j zB!OCAq<`>q=tuhYK+gm?QC=?i^wt7mfP}lc@bxS-Vd)22P;9^~-!1MXBr<)Uo%AIO zGfx)iGBg^#YpyBHj+vE#b4@(;Q5o_cV8i#J;@B4XV8^cOl!R+(f2(ir{aW$6kIF+& z)Xz}<6>E?0P$bm6N+jilhh|E@d{l1v1BQgwRoN4; zwTX?inpyBBc0F_9o^1nc8Y(G^C5i`gmL$FsQS1{PbJ3V5|+u`yb!AdUxH+oJcS7Ry${tatz{pID{D&@ zQUMp!a{?p-!2ro@%qcI)+^>7WjO5hA)S`1?T_6denN&0q^vE$F!P%}uD)fzZ@N;C- zu6Dz*`-IBT!I%LOg16;TR(Ks@8e72VgacYJ>^2Kcb!hAk7<1%zoF{{k`@N`Ox~mP^ zS2aHTIl_Roa3{TF3^q*%tn040JxYB&2fJ?jg)>BsgvP4b|23g%KYnOyW*?S*zXn*G|k~xE1PMPOVEZ6B;{mj=U$^<+^THK1zl`^v^>TG;_46=T* z+qw=eF=j{)`g7iQHPM6}4>^dm&=;lM4tvq9@*+keB|`wX{6v()v1}m2`j+qWd7M@M z3*%43V!NCPZGG(raS(OAym! z6|51zI4E7t2w1Ah*Hq#`d|1Rh4S=7-AI2AK_IIHwD}%tTit zy1A$EI2Rf!KrHDGGBeke=BlulM!vsX!ahxC>>t>Pg&2MQ@*dCS8}j?+MGw(t-9iXf z7S}m1W_+OV`Is{MHzNkddRSkh@~aAwi7zv=Ygn&d5Q{O}whPDdA%=MCW9ky!sNQ|& znkwS;chdSiNSM`948NX**+L3#uuLb_~_LZh#@E?MPrOIJUkr70u@bv zZV7S_+Y*U-F8Offxf;A3#4C=U=rs`oxo>Yu@XDv*lx;{Of$c%UR#H+MY-nTn{a-=- z;gug~wv`m{I;-XUsJFN86V2Up!(QVr!cMj=kXi!Nz@M)`k8E|S`h|Xjla%J{(k*gm zq$kEdtbM%^(N-@RauS;S+)8|kP;B>iC})wVCi2xcnS8y5J(+IdFRYhSl@+PHL_-Z5 zM&)n|PP&*_r3Z-!Iy6ENx~KH0x`Q`6MhPWEL?kF#fs0LufW@AR?~GaC$Dr#IX{l>R13IP+1lk;0r{kL6to4*{GFu|z-Re>iZ#0h>QNr)hONN)ml1dt2ftpEuBFZAE2 zZ{2T<`1I2^{mn{EBu?4y4))BO0Ub!c(R(R`G&XpTi;xUX(kzFMpLs1NnkRB{ruKy- zq2X!#3hdWwm9^%7jHasZVZyk_)&c;_j@rQcDXeD#Vl~20c2E&X8QkEB3lpj1W#TMS zmX{;WLz0nv^wGP|NrSGav)A*>FnUL25(8_+u5n>NIyF}**rU&k)Q0B&v`Oc*Up1ITZqyM4!?)BVw|Qe zm_2#|IxW#k%=|w_K(Gu2s~w{?_}=`#*>YJKpfawib}o%6#7XN* z=6gO{DPPP$^WQ<>Wd!z%muvFESg;?F5B)p8P8RMN{4j@r2yI*ct^)m2PPFjz;V5-D zV*v6NSR!)eFO2eAiR>>vxAKvz*o8Vr&{?6fazaCu;rP8KO*y$G8$HwPfy00y4gc&h zR3Z0WPt257`&g`^Dq;LE{^KCnG9o3BEOt0HmX`7(LMv!9j;EO%k9$`HK+m%GJo)ll z0Poux%hZ<;1+U6CZ#-DH=QoN^%*erfof~(}qkMj&w)<8F_M&_|Uxpd`+d2 z8Lgfq^9O*4*-ED}dJtIr}*z@~vU1{L|a;2tZOn!=xJbOCOccby%_J zl8#9)y$uR`VAU1%a>1d~$wi81y^=mvO^{+gW{&|lJ@ZT@5aRcWkCE?wkDDn4KKwC` z6L}}pis=#84#=(&XA-Kg_I%z4mjj0UdPPToSEid>iF=Xv{mxFKhj{LH zE_Mg&o$Yi^*i}u1m)@&9e}8Rw!Yc3odE))*PQh6+t?=V@&#SYQ+{~w>EDFE&Ow0>q z{l1?k6RGMSUa#_0=`!7qxV;Np6(~St(N^6J=H25`iZZA9^58-PzAGQRo8My|~P!`!VGuAO7VDKocEI4h^49bAunTSQjXqWLMAg_PITC&C__zha1(`FRh+q!jK=wV0TaW-_ zM;i|o5!okX7V6W8h^X?Oay*Esj5|_^Kj=1$iJc%TAel4ch?85eiE8?Ad4cxsH>)5d>2+-}Q@OJXw5G$#EW^a!EQ!IG-l$7Ze{2l;OAgliKu2zzhh zT;3s8k8!#?Mx~#q-;76v+WYE`Ivn>+hQ2HW=x+7@By$7LFT~FR#@Y9OHPRJCVPqOG zv*1p5hr5<^=z2QdnDpI(aFS+HMS^ArV&L0n=KC*M9)CKY?VnveXub=UU4Qg&R%R+RGyVX<7M>Vg zChEBH&)h8Fu}HM{bYax47P9rX+n-T98sgJeX*A8A-@i5nn%3>inM36W55F~q^%!wFyF>iTROi}Xwq-_+i$TU$6C z`sj)@$}6whA4Jdq1UWd@3)u=#QBLvtqz(}#tbI<3g%e#dZ2R4;Vc(=Hg%De8w2z%8 znOUS8lFZ!%bcrB-`VcCnIH$mE0q7L)7BonfGwxA8)pR}XSX42B>BL&lR4-1_neW89g`lmboss^Xds6 z=rinU4d=S*_zV$6De=|!9=<1Cyi~d36?p zMovyuJG`SHG$ND@8df-rv0qCiNqXcsA(W-pNCuidC{p9&>7HBterCQ<9 zTq7UMw=Be%NbYzt;7pr5>a46`$({w$Od{tX`?CYR<4Ec&wi>Ki1!s^8U#O?4;Delj)?!kVvPkZ4#eU4rU_7j z`8KUlJ!(W&GmT?{20(zp8Zd<5G3&eQ6OC5H$Zb^#ljgQDm>2x*g_fD9|GUvd9XV2x z6YtDr7(P-&Pd3*9H92#NKC?#o`OpzQYm&y)XgK4t9528cyKPFiv>}|t)-Z`Yd}M)F zOmvyibGQy}2}O~kHHt}aWcuC>VtTW%$Uvr8 zX1i#QQkU+L)J~Dv$o_0yhg$~3(^ofajZduZCLknScoRwO4X!5S&_IdiWNV*<1E_-<5gKIDXJ8&r}J0x&ve9 zX&V$0m9S=>UTlJ&XbemRR-2bP5F=Bd++W4o+M>xhHr^h89E81DcZ)xdc;-%B2Azs1e(1OFM21r>nXONJOc|FQ0%}N?UVf{y zErQ_(m5M&(xd&QFIhj0Q)TYYq76Oq>K+a@=8cpIU75NeUKe0z)^=WxMXfH8u8LV_O zi`hRvdf~r^E3W=LT#(Ie#h?LEHw1(r&XwT>a`?k4BS zV~v+JqAJqOfz^C5CuNfFBCsH1R;zPY6c8#wsIenXb4VoX6!zCmTv zYpzQ(A!#OPy~SEKe7+W`Y(Qc`Ano>8y~5rlW$ObTH|8m*NlOsJ+@pit|HvF(*3T23 z58Rh8o-OIGL(jWd!gad)G!|~+zjkuP<%`KIf1p7FBPG9?{z&;)P!MNO86>^tBKD;deZYgk2^JLn5C?M#e^hj7A&k|ZaH?X6qUtT)PWT!uH2yaKXNN)! z58_vM_^;#qal=%@UmSth?0^Whu;~R&BDZ~DDCcN~h)hGFfp2rKpjIuMWcqf4SZ7gM3#)J?!b-&`0r(n6che-V;unjlz>X z{z*)a7v(gk>?4g2c)`?w*I9!oA6SDVZ*hvF%SYu7LMMoi@;NlaLyWxbwi@X1QURlt zM&SitJFVfWI;d+c?v`h6iGg!32F!u`fT`+lF?eRV>ewUJ@R9i!GOu8xp5)uuHLyux zzJ)MpagTr2v`zBoirXoOLI5SSEBv=X?`k7Jn<%!sU4?eXy@QbDI;{wW-%yoP+Y6Bb z{5J1i>7c+_(8c$fL}>t0&jh2%MTX7Lo_>4H4voaZSDIF@Ov9kkj5L8}B@}0ilqqUE zF$orJgc1<#(2<{gx6Vs=_Rbhy6j_dC#u$=Om>6~9xRWNZ;<)4;lJ#LfAM#h-ZXLze zN2n*W+$PLjUZyB10$-SbQ;jB^yT#WxHfC)PKWg~1+96uh1Z8I?Yd**gkADmRE%bK{ zlD4OBC4Cfem$~=Od`p9fP)8BXY-0Zs_Pu-F3}s9p#ulc(iypugMBK7`GYPuq9>GI0 zA_m{jffl{Yh8h$l&KNb?8L^t6Z&ZqhmN_1P6Pm$iKk>3h4+o4^A7ZOc|sK=vubXnW9X z<*DK(DcYAW_Ad5Y>yY-4;T5;Eig!nO_gkoPp0un^TfROIrEG5L7H7N4&-Lm}M8=?T-V2X|46P+#va)CU=y(yV}8s!?OjLC8I$HzN+bxR)9|z zyHA#?x76*`mj^^YqJfn@m0CIR!$MYI_g9vI_oF?bc=B(Rs zPhpC4SUsPn2dZbLqC>4?9#)cF_Un)D zyPkBxm898qTsheoQiWXGT$pO@d-)hSf4bAS0y*Eu4?I@8`a&85ITOntrUIMk70P}p zfj4KW=LLwr>o97Qs_(TaNVDl7e3WM=2(nMd*JKK>}wNd1Bfm>87c*UAUR$#(YA9^}^BxZig{QL?wcWs*X4l=p^0-_=_nnLp%R!F#+Jg*gxmC*Bc`#Q%YHakp;SF7_fq7N9gop(#W1w z3f!3OrbZPM6DSc0dWw-7DRO?aKdpqX6d&ObApP0Y#wV+1?zZ(b(-D>^=}Fn4$W`O| z3$s)GZEuNulaao({Qm3a>srDZz3o`KwVo&0Yk2kc>euYn zeJ@ZwJmd~qkM&2D0Doe*JC-Eb>vehqjGdc~rU}+8aEHsu>7fbT_H}g?GXlwnT-$r_ zj8wZD%}UQ9EsMN^o4$2c8H|ioUznM+z47mOt3y=NTK4YVM?lh);CY-8zttkRQZott zlY3TV3*WWD&kTfJ>72Hz9zfm=n_o4u0YtwI#C&VwqZcZ-UP^@ zXClfzynf2P(~7&7B=3Rxsf>lkdP0Qs&!}dl|6pJ}1+z&O&pL3i&Z042#+UsFW8*1} zFCua7hq28ql)!1g>7uxD;Ae6`xX>&c5(!dAt6d_A zapoBJEyHHF=_)%?8}z~T5Bsy*R)l3r)BHnd-5#cHvh9Eja|-7RCQBI)Bt-f@Z!;G( zt+Zn&Qw5Jv5ij=mb>TAU-kj1!^5Q=M6P_UtBnb<$Nt&;(bQm}}!K>N~+xia^gzR!P z=ldZUc4l1&7nIt~k6o;yHPddgG#BPDZ27pFsoMLO!Ut=OGR*FxZm7dx>Zcc$eOB16 zz|;!jlT!>=ykAB`^+mFb4o8XYr@i*%2SoJt)Z~O^&G&3j_?4<726)OgsArkU>-5ae zw74BM5y01GCOTA6Co_?XqoH@kcjq7(`4%h?{TCs|{%>J&lnjXz_Zr>GEM9!5CUzT5 z*UnadkQ=5!w%FV%M$f|VCLh(wvbV9&(RW)RZ>GnGhHB(J^9|e%oZ_l>a&lmomzQIi z4}bc;TFlXoObT?t=UD4CIHLPhK5#lW@zvNh#Cv;bsrC>JO|l2O$ggfx5c0`;qp{ib zh2%Rw`Kv@K(2(+#SbzE^ATYWuuZNXxojG6#zGi{Qgt_s2h!;T&d{9;BPf`zIwnIuh zw)GA~uuXSrm|T3A=VD8b5gs3$n>QhO3XN#e&scdtO&;iQS_fZQjS!vcX#b;1hu%Ek zNFh=O+8s(n8~MdYhKtJ91+Rfs`@+7x#F|dgQ{ZvAnIQkBcc8r&dh_{%MSHp~DjyCA zfsjQ99UX2dfoGD4zbJua$5@#FEgr1_Id~1aMyNy+15HBOxfqDFuSjr;`IC!H?0SS{ ze%S5}o+?I#ZQT1Bdg3?D>>?dR<_EA7#hI@~b$D6M3O!MAS^dh7jc8B2g{P4Az-ILmKPNtV z<`+NbU4s`55wE<&jF_e9!w!;sby3t+v=wL1aTDVu;VhQC5UZHO`vx5@YD62WUW)Dj z#+-C})Jv2#@tGrWvo4D9a!7A)`Yt6W!yK+z&t;j2JE0iJ;Kk#wf3XYcEC9T?nSdMa z!s%ZJN$i{myW}7a5-18bi`pA|fA3e=`BeFEH-!|uOS<9O>L8;C^~KO63u15C1OVZ| z;tHpJ+7Qdx5-V{cX!-MS6KBY|Fw0ZvgL(6&q-YU)OZ}D0P9nl~7*0EkBAbH?@sn5f zUUxzE1$ns&FIpqxm{h`GNo+MDw6F>QkhJcrh5#6m={8w~!7=_ag}+%R)%k5&3E~#Q z)%!+Ue?4b3aNbC5nYQ^4c99O+4)c$xTYhcT>y7g{2E8HjC{}5|0d3%fu%Z4x47~lM z&zy+J%K)=Ek`$WKra&15k2K{8KRWlmuYWz$gttIV0y8I<20nBKuaYFVmCr#`ll2gm zStH%;zdX8+J{+}M(e;EI{D(dn(}Jf@g$}RH|LrTw#ysRqK~z)b&+z}R1$m-UN@?#l z{>c-gnPKexC>DGF75e2*fT4xd?)9%qiu(3TyY8&3=?m*A&1g1GrIbgrj+-` z=Hf$y$-!;7`hA;r;NS7o$$FYqLkjT$U_yd+P&=%+s|-arr=W3s3my&}t#~lrA*eLa zD#5NT%NExiu=={He{p10IPw3mIhj7@!U&u5!os@nh>kVnp02Y)HB(zMc8=_1yN@C@ z^8VUjwA@vp-3sp?^kEsu9lnj*X=n9yBT&rt>L3;^3!yF5%ggrPXZ{QCxIjHX1K<|< z>?aL!BZfZo+GJTUq>n6(s`6$$A4TIN3$>qo*LGtb|BJ;4pyZfu_R|;vp;2F76xN9@ zUZz(!6#ixrJ6IiNG3QWzbRxGDkrT+ils?Gt_u+p%|6DshwFf{)R9SZZ?p*uxtOLFG zp+iUs-Wp;&H}>#9hu=&Cz>qs*G}}cmlB#)ay2H5_XjbEI0HoLM7g_bKajS6#u-`X@ z6_=56{srpqng3uNdGDB`LSw@0o_h0dTn64@dhWj?JC&>lx_6h$rPY2l^?a|&1rUV! zza^}RDPQJ}OM6@r`X2q%r}e|<(tdvWIJWr%vJ5?RJ`@FAB>HQLegGwMTh9av1A1Vq z+C!95_xbl`9!|}}vR=Q-A=6;!-tb%XTJh*Rw)=Syu-f>2`jYuS%v9hSw>7btE{e2# zyRqTCqKs2}>USa@M^^pg4So#+9||=2|LtEYb8=a@F@wA0p^K6p@_)4929nNw5yl75((yo8INXU-*pxus zE(v2{DZrKdYL=n08aB4~QyCGKYZK-b08$NUW$!qQ9))#BbdtM5aED*XaEx}Iv>)>y zHFS#F%Nk()3mwSCu!N||A05lzu`R8sb${{mrr4-I?1-0LZWEvJ)4;WiuSIx&ea(dt6r$mWBCPA166tRRSf%(O^rFEth8|*4s zEE!}`7AqHRe7S}Pd{pSl#Ly3r~%d<9)2Ub{$?B)J$&|g1$W9_+jype5eQr z0CPTZe^S&+!lNdGXVU5V?`{;|C=N<_QG$$4COtOdv$ z&8h15Lp73ZgPjk#KE~w~JQE^d)QljQ2HDl8?D?gUK?D#b><>1 z4;>uz7J(XIz-v%Ri({*x;i-{4yF0`+62H$JB+|!#sUckn3v()X@KI>_d~#HPX-QET z{US?*K~~mah^TC%q09CDXFRwX$%*`K4j$$+=KSFsvMl|Nj6Lm_BKVBpbT~Guw(zNg#%Frt|BdT%v%$B8+8Sb} z1%i26tsm11I@`)nVD-d0uk9!R7fH{ti?~QrIx!6C=5an` zM#uGx!-(900g_ZJLY!aykN&|yNkD`afgpQl*ou)XmII3~C#MLRAeoBjSiN+9Y_gK* z3Epf`v(PhYe-B> z&N-T6jOEcPV#kV-rbZs3MFl|hT&2YAOaFV!KaQQ+`j0|YvN1xz0iQJ|1Gw*btN7W(u}{!)u%1Df zEHDtsXrUf>5QBJk5s^0{{Y!OSJyQKa_%Qv@W-r}CmNnL@c1ui;hCH;RI-1SgnJ^ZQ z_K$wTHNb``G2=w>%Fr`{CM~ythYz8hyP_sYy|?*z#7fegtu>?@(Sqa-?pZ08 zrt14kBEuBI?C6R?1&XF?5LLc)E%pg-ORlnN4ly%~tw;|r0AR1R&{81z^~vALbN;3w znDh8sE*xq_384g*I5pNfF*CqGi3tK^5?=W0qyPlK%*3TE@}I%t$YNrmgWivJ?DEh7 zn5W1sg~kII!&K|9v=%77y;X*OMNyfVW5fJB|6$~6AwWbKonOa|!01urSw5nm_mL4s z6S|z$-!U7tbvKX{u;h=e#san~YXmbIRX}jGf7AtLfZKBwWY2tfxP3bcIxXu}&|*rp7P@SLnT3DtPlcH=FiRwN4e;7C~JPir_0 zstl`tyhIfe;C;B!YKWPpc@Vn@Gn@Hf0?TcxpoIW0jk*fiLm)20gsNF7 z0n6Df1yI>#Kco)?jAZcdydTfSE;Ss~%&!H{#UKxV(eMajf<)E)V*-EU#h?wO9MR(1 z#RCY2N%GK`kI5jWBXf*{SwLO|MJyYD@AxM+e;a|F50&XL129OWAksjJlJ8Qpb zKwGJT0F4IxUjqKtz^@B-UG*nE-wz=vEfsqSczKmDgV|%IrIY;|CWikALe*946%Qop z@iUhV*V3Te`iwNX&oV5u!Pq*q7R&@Qmjam99sruzZv&X@yw%a+z9I~PNLZ|U35zfP z1inALSG43zpgGp^wD)MU1!Y9(#e*}9I;+@{DVe;A5d~eIiFCR^6|HH z(g}d=-$^_}J9}(D<^2$ZNT4515sLnm>OZ3wtu+B-lHeS)8V07LIyPdMU*nDbXbs^3 z;)P)GAF0Ba54IlyF_pd3c=H@^g)SaR)txE0Oj7|lFbi+CfzSiBQ?mcs3fo_%T0>~G z>=T3`mo!1|EnvQIy(MQP>utxMh}QL1_AlXR){tT#qgFG1UNk|4sF4pLebDA1Ao-C$ z9MGU*0&xBt)s^f1@QS_$=?ngrX_EfY(~f4T!4AB<&Kn?~?l+jV&UeD1`Gr(35s;74 z=rhdV(6yi<5M88!j_eyTAPKcUZ=3{Rg?}`=yEz2&Uzbd-t91~rTnw{WVqm}LcO!-Q z_QSARe`3=3TUmJ^dofHM!OUQau)dt-wo1j&F2wrq_rvJ}3$J!m zQN%TW5+V@iCg%2_i$t_AXUHT=^}h@Uecr#Yz3ob}AphA{ae8r*h*jSvie{Wm2B0Fe z2YB%T)1Qhz1_X2YK-{U^Msq4Bj`q3YP?F2`>qdUAxCyBFgvezz(n%>4n_=kB*xVYf zA*&n^y{@Cq`1feYQ9sn~chzI>DFemb;dMaGIkaaa9tPZ0O#uI|LD%R3I~GF3!vsd`LFp^A=%hK0R33K5s22Y zYD}NFO9%!K=QkXrRqqA~`w`uX{;BF`>*pYi<=uRYDLDrjniyEy`1<=iApO8~DE7YQ z)njHP8u#y}oJ&^>dOfC3XlnUmP1MW&7^`frn0PCSq?i4dYl_Y#GNY0rrFu+9 zg)HlRi0&1hm%NZSqgh%|$$a~dP;f2qh3Z?`7Ni!SHK|6tPosXN29cx|RXz(<@yKs` zQ;*A?i_-N~A(|_iPh)6I*pV{NecD5!B~k56YxMGzWnIi|8+&&dr~)sZx>U}ZnVw$= z?Kd&Fv@2W2u&+>0i`P8+Jj_Rnw2JsfRQcN5K z))Ae2Lj-|Rd1{-W?*5ogI6DQ_w4fJ~1v{rBK}-0?O%2Yj-ZQ4~Al|i^```WhV|X%J z?bEXyH=Sm0tt#$@CF%wyEoBsh(O-D5l(v`CklxmWo#x#bQ!G$*5HAXWKzOuxv>-5q z8Up{HFJ+-XsV`y}A{s~*DhvI;UxFd>(Er!QKhlEsop=T6cW9zSV!%lB zgD4u}a)s*0k~#Rlm^VmE#RFQ)t8K&-M8ZMIS+q81>85jezcnUeYCZaZJ0Nq_WwOe8D?qxD>!=MWJ4VzSaX)O8hWD=F<90d*N(TrT_V0L)jdSIB`A0qBpLm(W18U`Sux+-Mi!d*qJfmr-Jc> zhjc{EOhX|!LltNiYGrPpONOcU)S&Z=OXCqyt(c>;E4!9x!92PX4~;U|LdfM$dA8@5pta zkM&*0pN^u?EYP{X5tTIDnrlI~2OQqm#CXlel#2Z}+Wn+M9+)j~)Okac6lF`%N=_i; zdcfS~iwY4UklK4<4jH0GLQ&|>Ut1A{6O5aAy^kh=Gn{bc_k;S&Y5y@z4wmAcYC!xP zxb@6${hjH!v^=-0KN!{DjXwng#F9_1C_1VSdPE3l8_kY({orjpULWMxntF?3xM|m+t;ICB(WolpOWHNg#T#_`ZmLIlXI^l# zA-yykKVbJ)V5RoER!UoFD%Dr3-4p_@R6{7d1IsUwNYdv+7RV_%1}qbj#w9$B3 z^`6T<<@v5yJgV0oFp{@SgkpZ;WI`~r^m>_#rm?0Q=MR4(3j|WS|5n zT4?QgKJa_)p5}Dm44mDkX|^~Y&72WI>>V5c(}p18qCn8G{euFuq^>a&0<_RiPM8Ai z;&g9m)lXkH{~Em7yz5^!u+F4yYm^KBXsAdm(iprAp{C!) zyfQk+SfZcO#NqC4zd=xsjm1FrVB7limOP;yWk&oO5&4U9jky^#{=>I7ELKKPE4r}H zA6Qlv4N1~e!+*=VXB$C{KPgXpX+Gaoq{;J|HCk|R+H45fX92dJ1%aAev4n>-S&##~ zeO}90>L~h{Lj^hHmXy;L5OOPBDPNE3eQQq0IoaFhtu`yCbJ5`Pg?RT)jR=hn@3cfh z4x%RN!mE$7o#NPOv&lbm4jHNwmsk`iPE3?>MZ7D|dFF%x1TzrGHZocJeJ?#NjThl9 zPry{Y{byXbglfrA;c4EE+qZEt=^=F*J3+YFQMqPtrE;15;8jziMGQ>GZCgK0FC!?g z*%t<$p?n?3sEp8!*B5N5$jxFCT`D%IeZyq0qaBJuMEZbTPeGuyhEVqCZ@X`Zz35sA zYT7Axz#%g9b1aLhzqyKP;+;c9cRskZ=`^ zyuvJvb;&8K)-Vy4tcARkI#+phR|nbo9vh&o!nVRO1hLhzN~`A_I%3pos^7Sb=dgZ( z>^{bK)H_b{%QS7Lw>%gIb6HlPl8tSHuDGcP{+Wwj#}DsrFz^O^zA)&d4hQ?Oh4R)JK_QYYL#D4Z_|?_f>;zHdXj8F6A@dAGx{_1d`04r+94X zls}RDtff5-4nmi8{wSpSe7-bzRaKl=zUAm6Hc0gRJC zpzBA@y0TJHeBFtWpeF-A0J?N8%wNyz54o2J;S}i^9dGQf1mtO9t_(vSM;C$7gS5=pR%rEXXl`z#3zr!cKnVEb2+C_l!IaaLO1Uc>Efb)3+ z3pDo#*9mH;C_7;4yqNtm7V;hpjQ2nw5ll7ZrXB@NPecy6zDVU?T>N8F+vE~g5mSL- zw7)*hSpaDZ<*3r&nJV2-v7ykmA_0LawrTzl;VFrPGh2yM($ZIg>$Pcq0 zodO6fNvUXBJl+yiGj+p z%&W-S|#1Xt433pThHMG8%We+vWygL)9iRiY{ailECqltO>jo@D`TQQ%nK zCLG25_&_P_dUU{uIS426Xi7vZ(-_4bB$33Pp&f)uO|TD^?aJ1N>`^0yBq9)Jw>DDH zJ{hI8@hO-UUWwfPE%syW#`}iOk6ka;jNxffcX95nQ!NN*$!IiGjesvkf&}#Ey-x3aOf)rjL#bS9u_1ng?i=;!77xj^zDi80d3#KNOPPXfaUpmy)+*wXL z5qI45GDqbi<)2*%1Q3*bonUR}5j-kIt1)FQO)VHsnUOPud_#Eh*b=BTk;8nYxES5% z|K437UXZsL4R4fG;ir8K4@GLr@Eq;FKCx*+UQt?$?7J{DJgUGaw*;0lX7@yRdBTYH z+`BVTeRx(vjRa%%fqwq4ZG01gp`$nHpc4pH(*oiR7UOT4DJjMaN?_|lE_-n0?YWMB zd|}|o7C$_}l)f@Qq^F-xs#|YDMB>!q`F8Xt(R@*Q*}x|fd#@S$KgwFmz@gzWJ`+Hj zSf5%G;0o4kzbrob3W9<3`5-Wa=gb+!a z*tT^mx9AM*eW6Dx5!-M{58yEn=r~w;>61~CLfP~tR1gAH} zleyJlo<>z3d)-w$@5{EU$lELhI~`gq?W{airgm+yBA8pY7I1gqKpKA$Z132QbISIX zP}<>!?z^<%NrvaonSAyL;UOEf%&&@rgTJ$rw))>F9Jf3146hz7i_69vfCkTqEj*a1 zu_L;qvAgNao3fR(sW-Z+rJrc`tOslPclGzvkxQ7H$Jm?K6bDn2Nfq}~g<4lZTJz-^ z*FNr3o$!wOxVvh1A1?3mczCqk+um&Sw%bjdcbpL^=^dQjoV`-3;IG)aiI_!Mo{ul@ zv~K&^kVqT(_3{j^s~I}uWM|}JB>WslSw(iT?ia^t-eTHf^{=mCk)`trDxMBbMsN8W z2#l;nJ_;=^FO-;=-Ou=)&wa05uyt%b zOM4M^o(Ow!@c;1``~0C~`XH6mxXOVx-FCY_<+r`DCEU>%Z^WeHD`eDy|5@Onv6m0L| zK=$ESpz~@B0N5W00)VT875Tol{!_B7m71Jf2M@{upDB_7?Q#J3k0d9?8Wp)*3oC^K zy#k^VBQP3)Js|vhMvt`>p`hf@g57P(6##v-?gB7c^MP*3da1Lo@*@B(E%bQd{s#aI ztnu=j>M)R*atHuW0CX1kOUU+@FDQRX|3^Y^46BWi04z76HUK6_0qzhz*6UsIn`fFZ zOq{_3=7_-E|BClt{G(FY2ws-KC|FV@_Z54t4|r065Tb~?U`bTT{*Q#U=fV6=P8dM< z56$gb>rGETkHCYv^!oE7)=8q(kU?vTuDz=KK68j5-`X-li8woB1kl)mv%pal1Ozf) zU$i%Gxg7y z!vr)|p_@v;0_vr8LZth3?jNC+_7?m?-jq#pc+U#$nT@*B5^ zN(k1F&-cV!6e!MfufCyju{$O6IBDN(zw;h2yBEC|c_tT2`~UbC3AwvFaBQYXX<_qu zU%Zr3p6S}$e122NB^w*Ba*WsY-njUJNdK$-McD#oS1|-ARR{qQP%D^#b~ZadD#Ydy z1uV+N<~^Itofxqok&%nN53Syt!fA+;Y4>Dq2hDw|D5uRQbe~dG`@l*pH-WeaT2nH% zK=M^P7cPFoT)+2=Od!H3qi+yjo)%`Meo^SX-#s`339&^RpAC`p)?qI zcp=RgnR~uqdRd`^=w0jd2)2}TAn)q@rEx1GA0yGd&ZzShYp`60Ht+?uR1NSn*-zE$ zr(ut)-NQzKy&lYt&EF2O$3yIvw#Pul*3>4zUP{kM<#FAb_84<(Zy5Y7rUmBU=tSYT zm-X&UW}&@^F~(O({XX{UvK0u;XJ$>b(XOs5O|mCYV}$R6X0dNYl9eWUa(~I1ntC?K z$Tj-`wW!XQ`Uf~_&>k_1G~Vj2-w1%+{ZNGIHT!eF#_?WRMav_pbS|yMS-S$U=&@Cf z=5wu;Sk;?s!4v16T&61X1lHVFl#=|a)z4xz>sUUFic!Gty?iY-z&sOTT0k8_i*cZg z5(Ad5!BI?$G7wrpOa+UnU@&pFjPTvRq#}0}lcw%Ap|eX4x~*7~(dELd@)8W7$E=UI z2r}|MgJTsPWW4ynKcc%-5x1NCGFz~_p&Q5<>FK0TR-S6>s9V{IjQP!H zGdhp=Oy>d5T~m#xIBvXXE^#*UdSAI)9#|w5K_ZeL^P75nSH7H}u?|Ju@ zOCs>CYJR>tdv>7c7PPl!*=Wxs;lLo_nd7{qvS8q|lK2UQFE)&V=dlXC6I$Oz*^D0A z>=rqj>yoaV&~Z{+cT7y%R(AP`;9S8iuajPM2*=67mAF0y1t~PYN2pTXqKNcenyj-} z|LT${z0Icvnv>jzi&9^#{jnWw3~$f$1@pk-he?Ai9rx(L9WFA_s}wra8*Q2oLY1N= z%orYGnhaG-Ewion7V>!^&x{^i30HU8n}*fHt@M4~z%#BYbfUfzN$R2>i#2fTJBVz1 zgV6Ey=`(D3#G?Qir&DgsUQRLSE}rb?47i5Nfok4m>KYyfw>&nCrczvs$of3-{2mTf z=kfhsLBw>6prK5u1&`JXzOIZ3T(wS3s)?9D(__qPMHPZL|1eoX`k1j<*NjL!EdEZWiq_!kb3`Kw#f|PQUGE)mMeW^sp~n# zulY$sraI@o5@U-Vk3`e=PDNsw4j#*frJr<+ZNmz?=6WX6-;#9icQtB)ZiWe(K;kU> zFym5KvnOe=0STq)!I}b`o;Q3&hxaU6#GXn$mSIT5M;T$UwTbG)a}DYl;(lzqedg?) zNE@)7*0r*<)@jP*w|0y=^6mQx8aZQinO}0GM}3gm@((go_O2A*`k+=V;v#Wi zXEDDsbdYpmIzkq2%-D;JJUO)MBe}g0M@?jlwf9U@+!qv2t5{|<9#KAS(_M3xDoW}& zPx5lcY6O1;?Z?)p!ev>e+5iH2s0B7+jYL!>@xDmvlOl=*S+ixYugddb_m=Yof?T(c28cm&IqO*EUe16fw=?hB?I;5QRSQBWvnwiA9U( z8^x#l)s-F=RV3-6gEbDL?J%sZL1r5~@8E{g>y6z!Ic2bikSaK9Hk&pWFrd@h)@5Av zG;hamwnb$!S~pnIUdWr;3O%yIjV)v{EXzq33tD#_wC=cJaFU-D_xlC)2A@~V zFv9<$G3hfB)U|dw>#r)1sDqQTo|sO=%uv&I_p4j;(OYq}gEwtvqi%nVpGD@~DFHh= zLZABfjMcNy`q3zXk}>!kNVnk*C`Y`?rG3jQG?EpH3LE#w3)k*QE8x{zwimuD;fK0R z$$d{F{jx-)Uy!=-vk9J6Ob)~j_nfjX-wZ*!RI}~~t8ZJV>T!;ka+f5f-u0|CxkBr+aH~Y{FaN;W`S!N zM`Ej$d}ms5c6dMH&MNKyUQEnM)2f+?$@u!ExqnOjoY@Q2oG@^QIpLWEKA)Ei5L;!GkC+t;U2 zZ4{*l^dy)YfHUV|YPvwjI>%4o8QyY5>9P!y;oH;vxXA}zCXBvp`&pTp$QzTUk=K>T z!OQW+NJcDjTtuI@0N4vPOcdk#q>&M>?{?$N7|t^lqZql^j#PlC{PE5Cr=**PP?2O` zQM~AkEf(Y|=y`GIXkulnDVDhZM|UzQLSPVNHv>RnrwkK5_N2k zbHcz*D2bftDx#`Js1SHKO^F}fG*4(lL&0$Ffgw*IeJ`INYBp3=;UeNTXcF~F1hP%! z$(m5TWAz8mAxqIWY64mXL~bvmb(33)bu~T?X6hpYv$+Q&3qK+sXQV@ z_(*{6sN<(lqP{LAoa)+)kXzgZv46geOb?766oW&O_n#lfhLX|FAq_*Ge1dw}D8{{P z5A@mH6+?>MqIQIP@o~7~pdNQ)vhP;JwR!uMO#k3obTY?QvAB3dD+#$IcKg#GcBh?6)d+u>4xa{A*H_g(+mv=I8Y zL&lQuFU`iR;j0hiKelIPthH7Zf9tjt#h^Ja`+mP8QabT-2vu>D8a8~Q?M6}{OiP?Z zpc>4e5YOooJdq&K0L&S1?q|e~(6RQI6aW0NesBvObI zC5p#^3Tq!&Z9ZnD?qm{}uRn?zF5-=t7@;#)5n`PM<;OtR=LhtyyfUm`IiDrM_ole! z2TnFgHfV*R574n!D_^v=fCW`Q2Zz|Cr#YY62)%YicP?}$z>+>U`?0mDcmE;AsQ=o$ zuf`~kU2}!HnB;2^l#j>gr+}1`x^|F^;S&!rJzI?TaTI*t8g*LEXy*x}z*w6hG zUKc{mMrY%5^;GrO(@4zk%*ZUS_BPj{-`LeBy=-Ug-zF#c*uw`L)9f_cT_zF;G4B>m zC(pbdFmWl*mBk`-IE<}+mpJ}eUGXs9fGu;q2E~=9+x-?k8>8ghNvW1$?Tut~;U~D9 zO{aUog>6LQZ;u{ta6RX}p)tbAFou&pP(QWCf51wisq1Jm3}%YnDqYkS2|%)(2#Yzl zN5q^%UN%YNv6?Z8zP|VwrIS;L2PS`dq}-z7um)~z z1KZ*Y z?P^Hk@xZ5U2EJ!m1uE|>IJz3?INV;qT_#RjybA6nLi02&M!$A_X0if(X!Fh$IbWH> zA#XCaM&UE(&^It+$s!jBtQFTB@2_5KV;gGR+o!kkXyUt$CqW!{z#5XtBGsHd7;YX}_D9b1V`b9;=xpnacJbkRn@QI&_HF!6=Ja99ByLpUyYhZ>>n7 zrXTo9%<{P6G}*-!d)g-Vh8P+$gwIdGnOtTheOVc>b~HaA!FMOubsOAjT|883{%k^2 zr&L4l$_aUbXuz#K@m@rSnKI62*AW!m7F&7$`{A4AmW1X}cO}(ad9}^hbDzn~nA>Xn zwxzdnNOs>p^yGZ~POT~%D}uvzH%)$2R6Q=0pL#Ap>V4!_-LjR@|2up~apUVNuE9`E z&#e>DMLX5|%fc)+?%kNulflLCp|#C!to{$?%mIE61La*&7X{av!LwzCKV6#rU=w@z z8yHuPsy9s*8XU`C#XS8_UDavCX@*^XSS`UJR3w^~^>}4WMQ5y;s3aiOKCMv1r{!xX z&O?_IlYQ~^BnY7>tNN9by2^aocBE$(yI{%EPu5g3j8oPM+w>QHWV^haqi@qHln>skP#l;kFRhYSF?S)jj!LcTMD6NT& zhS>^N({v2KNCj1+;%ZDTo!*t;gBQkA=)0_h!ev{mLkjzl&(s%^HsM>vLIi$xB%w@( zDwGF%QbIW(zqU}gH)8?<4sTS}M;x;(oDy&d$_1i3Nsy3L?7P zg`3TaJNUM^IJI8^PjDE5-46)SauQX^py;~}oz+$CHzaY)-?41fNRTIiXx+9bhr*0e zZ8mqGV!fnqef#y4HO~c?H|?zeyWQ&X3QpZ8w+T;2HRwK~L^j-MV!Y#v-_js^e`!a+ z^(aAYhAB`|X!Fyw8(oJ`Q@+dXimk4we+GFGCGj!eYBW=5Kyp9=(IuJ7LKXzn7mw_NY1hD0f>{hmpD75i@an{@lKj*PN&Ycytp zepMao3xa2gX@4ywjD(+Gpd$dnbnU0kCa^dwf5Y4BG4#&dz-Lr!h{pv!yYn6|y|X#W zQR?AlKUnfLNg3&(&W8k=%7KyA%YccFrE0)22Z{$Y?X!p#%Gs4m90$>z1gXYW7Dbze z>>xXP%$gP2e6U&yLnhdvxagvrS{i?QGxE?LZSl@8KCQXASuy(HDE>+Sd-Yy$Y5@sZ z!P4uSq(!Y%uSGibaUmrIT)M$btz9@|w1oaO9dn|#i6K3Y3}@~b^F(xC~-W+RyD`yh(`9wxL=`=}Idl<|v=aQm}Y zdj%^SMTgW}=q-sFmslic$nS)X1NP=c=dy?Xoc;YgS-J;aj8L3Zb>ovl)iS;&R*nj_ zXC$4jBmL`ylAU|Vt;JXC3mlEf+n-6S0ZWanMjR eVP4Tnq#Lc_3$yl*sID%z2 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 From af00db30a04116226c3809ea61ac07d279f554ce Mon Sep 17 00:00:00 2001 From: David Karlsson <35727626+dvdksn@users.noreply.github.com> Date: Mon, 31 Aug 2026 12:12:47 +0000 Subject: [PATCH 07/11] docs: add topology mode to interactive diagrams The sequence presentation forces stable security boundaries into an artificial timeline. Add a topology mode with persistent context and details on demand, then replace the three security sequences with one trust-boundary overview. --- assets/css/interactive-diagram.css | 164 ++++++++++++- assets/js/interactive-diagram.js | 223 ++++++++++++++++-- .../diagrams/direct-workspace-edit.yaml | 98 -------- .../ai/sandboxes/diagrams/mcp-tool-call.yaml | 124 ---------- .../diagrams/private-docker-engine.yaml | 88 ------- .../diagrams/trust-boundary-topology.yaml | 150 ++++++++++++ .../manuals/ai/sandboxes/security/_index.md | 26 +- layouts/_shortcodes/interactive-diagram.html | 146 +++++++----- 8 files changed, 618 insertions(+), 401 deletions(-) delete mode 100644 content/manuals/ai/sandboxes/diagrams/direct-workspace-edit.yaml delete mode 100644 content/manuals/ai/sandboxes/diagrams/mcp-tool-call.yaml delete mode 100644 content/manuals/ai/sandboxes/diagrams/private-docker-engine.yaml create mode 100644 content/manuals/ai/sandboxes/diagrams/trust-boundary-topology.yaml diff --git a/assets/css/interactive-diagram.css b/assets/css/interactive-diagram.css index c2bcfa38f439..35838e2b5b7f 100644 --- a/assets/css/interactive-diagram.css +++ b/assets/css/interactive-diagram.css @@ -5,6 +5,8 @@ --diagram-structure: var(--color-gray-500); --diagram-ink: var(--color-gray-900); --diagram-muted: var(--color-gray-600); + --diagram-shared: var(--color-green-600); + --diagram-brokered: var(--color-violet-600); --diagram-token-bg: var(--color-gray-900); --diagram-token-fg: white; overflow: hidden; @@ -21,6 +23,8 @@ --diagram-structure: var(--color-gray-600); --diagram-ink: var(--color-gray-100); --diagram-muted: var(--color-gray-400); + --diagram-shared: var(--color-green-400); + --diagram-brokered: var(--color-violet-300); --diagram-token-bg: var(--color-blue-300); --diagram-token-fg: var(--color-gray-950); background: var(--color-gray-950); @@ -39,6 +43,10 @@ min-width: 0; } +.interactive-diagram--topology .interactive-diagram__header { + grid-template-columns: minmax(0, 1fr); +} + .interactive-diagram__title, .interactive-diagram__description, .interactive-diagram__step-title, @@ -132,6 +140,64 @@ opacity: 1; } +.interactive-diagram__edge--interactive { + cursor: pointer; + opacity: 0.82; +} + +.interactive-diagram__edge--shared { + stroke: var(--diagram-shared); +} + +.interactive-diagram__edge--brokered { + stroke: var(--diagram-brokered); +} + +.interactive-diagram__edge-hit { + stroke: transparent; + stroke-width: 22; + stroke-dasharray: none; + pointer-events: stroke; +} + +.interactive-diagram__edge-label rect { + fill: white; + stroke: var(--diagram-border); + stroke-width: 1; + stroke-dasharray: none; +} + +.dark .interactive-diagram__edge-label rect { + fill: var(--color-gray-900); +} + +.interactive-diagram__edge-label-text { + fill: var(--diagram-ink); + stroke: none; + font-family: "Roboto Mono", monospace; + font-size: 10.5px; + font-weight: 600; + text-anchor: middle; +} + +.interactive-diagram__edge--interactive.is-active + .interactive-diagram__edge-label + rect { + stroke: var(--diagram-accent); + stroke-width: 2; +} + +.interactive-diagram__edge--interactive:focus-visible { + outline: none; +} + +.interactive-diagram__edge--interactive:focus-visible + .interactive-diagram__edge-label + rect { + stroke: var(--color-blue-focusvisible); + stroke-width: 3; +} + .interactive-diagram__node rect { fill: white; stroke: var(--diagram-structure); @@ -142,6 +208,20 @@ stroke-width 180ms ease; } +.interactive-diagram--topology .interactive-diagram__node { + cursor: pointer; + transition: opacity 180ms ease; +} + +.interactive-diagram--topology .interactive-diagram__node:focus-visible { + outline: none; +} + +.interactive-diagram--topology .interactive-diagram__node:focus-visible rect { + stroke: var(--color-blue-focusvisible); + stroke-width: 3; +} + .dark .interactive-diagram__node rect { fill: var(--color-gray-900); } @@ -168,6 +248,24 @@ stroke: var(--color-green-400); } +.interactive-diagram__node--shared rect { + fill: var(--color-green-50); + stroke: var(--diagram-shared); +} + +.dark .interactive-diagram__node--shared rect { + fill: var(--color-green-950); +} + +.interactive-diagram__node--restricted rect { + fill: var(--color-gray-50); + stroke-dasharray: 3 3; +} + +.dark .interactive-diagram__node--restricted rect { + fill: var(--color-gray-900); +} + .dark .interactive-diagram__node--secret rect { fill: var(--color-green-950); stroke: var(--color-green-700); @@ -179,6 +277,18 @@ stroke-width: 3; } +.interactive-diagram__node.is-related rect { + stroke: var(--diagram-accent); + stroke-width: 2; +} + +.interactive-diagram--topology.has-topology-focus + .interactive-diagram__node:not(.is-active, .is-related), +.interactive-diagram--topology.has-topology-focus + .interactive-diagram__edge:not(.is-active) { + opacity: 0.22; +} + .interactive-diagram__node-label { fill: var(--diagram-ink); font-size: 14px; @@ -254,6 +364,49 @@ line-height: 1.45; } +.interactive-diagram__topology-detail { + display: grid; + min-height: 7rem; + grid-template-columns: 8.5rem minmax(0, 1fr); + gap: 1rem; + padding: 1rem 1.5rem 1.15rem; + border-top: 1px solid var(--diagram-border); + background: white; +} + +.dark .interactive-diagram__topology-detail { + background: var(--color-gray-950); +} + +.interactive-diagram__topology-category, +.interactive-diagram__topology-title, +.interactive-diagram__topology-body { + margin: 0; +} + +.interactive-diagram__topology-category { + padding-top: 0.15rem; + color: var(--diagram-accent); + font-family: "Roboto Mono", monospace; + font-size: 0.7rem; + font-weight: 650; + letter-spacing: 0.045em; + text-transform: uppercase; +} + +.interactive-diagram__topology-title { + color: var(--diagram-ink); + font-size: 0.95rem; + font-weight: 650; +} + +.interactive-diagram__topology-body { + margin-top: 0.3rem; + color: var(--diagram-muted); + font-size: 0.85rem; + line-height: 1.5; +} + .interactive-diagram__autoplay-progress { height: 3px; overflow: hidden; @@ -363,6 +516,7 @@ .interactive-diagram:not(.is-enhanced) .interactive-diagram__stage, .interactive-diagram:not(.is-enhanced) .interactive-diagram__status, +.interactive-diagram:not(.is-enhanced) .interactive-diagram__topology-detail, .interactive-diagram:not(.is-enhanced) .interactive-diagram__autoplay-progress, .interactive-diagram:not(.is-enhanced) .interactive-diagram__controls { display: none; @@ -386,6 +540,7 @@ @media (prefers-reduced-motion: reduce) { .interactive-diagram__edge, + .interactive-diagram__node, .interactive-diagram__node rect { transition: none; } @@ -393,7 +548,8 @@ @media (max-width: 640px) { .interactive-diagram__header, - .interactive-diagram__status { + .interactive-diagram__status, + .interactive-diagram__topology-detail { padding-inline: 1rem; } @@ -402,6 +558,12 @@ gap: 0.35rem; } + .interactive-diagram__topology-detail { + min-height: 8.5rem; + grid-template-columns: 1fr; + gap: 0.35rem; + } + .interactive-diagram__state { margin-left: 0; } diff --git a/assets/js/interactive-diagram.js b/assets/js/interactive-diagram.js index 3ad545440284..59535a59f3e3 100644 --- a/assets/js/interactive-diagram.js +++ b/assets/js/interactive-diagram.js @@ -12,9 +12,17 @@ document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { } const stage = root.querySelector("[data-diagram-stage]"); - if (!stage || !config.canvas || !config.steps?.length) return; + if (!stage || !config.canvas || !config.nodes?.length) return; const state = buildDiagram(stage, config); + if (config.type === "topology") { + initializeTopology(root, stage, state, config); + } else if (config.steps?.length) { + initializeSequence(root, state, config); + } +}); + +function initializeSequence(root, state, config) { const previousButton = root.querySelector("[data-step-previous]"); const nextButton = root.querySelector("[data-step-next]"); const playButton = root.querySelector("[data-step-play]"); @@ -199,12 +207,141 @@ document.querySelectorAll("[data-interactive-diagram]").forEach((root) => { root.classList.add("is-enhanced"); showStep(0); -}); +} + +function initializeTopology(root, stage, state, config) { + const overview = config.overview ?? { + category: "Overview", + label: config.title, + body: config.description, + }; + let pinnedItem; + + const items = [ + ...config.nodes.map((node) => ({ + ...node, + itemType: "node", + element: state.nodes.get(node.id), + })), + ...config.edges.map((edge) => ({ + ...edge, + itemType: "edge", + element: state.edges.get(edge.id), + })), + ].filter((item) => item.element); + + items.forEach((item) => { + const { element } = item; + element.setAttribute("role", "button"); + element.setAttribute("tabindex", "0"); + element.setAttribute("aria-label", `${item.label}. ${item.details}`); + element.setAttribute("aria-pressed", "false"); + + element.addEventListener("pointerenter", () => { + if (!pinnedItem) showItem(item); + }); + element.addEventListener("pointerleave", () => { + if (!pinnedItem && element !== document.activeElement) showOverview(); + }); + element.addEventListener("focus", () => { + if (!pinnedItem) showItem(item); + }); + element.addEventListener("blur", () => { + if (!pinnedItem) showOverview(); + }); + element.addEventListener("click", (event) => { + event.stopPropagation(); + if (pinnedItem?.itemType === item.itemType && pinnedItem.id === item.id) { + pinnedItem = undefined; + showOverview(); + } else { + pinnedItem = item; + showItem(item); + } + updatePressedState(); + }); + element.addEventListener("keydown", (event) => { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + element.dispatchEvent(new MouseEvent("click", { bubbles: true })); + } + }); + }); + + stage.addEventListener("click", () => { + pinnedItem = undefined; + updatePressedState(); + showOverview(); + }); + root.addEventListener("keydown", (event) => { + if (event.key !== "Escape") return; + pinnedItem = undefined; + updatePressedState(); + showOverview(); + }); + + function showItem(item) { + const activeNodes = new Set(); + const relatedNodes = new Set(); + const activeEdges = new Set(); + + if (item.itemType === "node") { + activeNodes.add(item.id); + config.edges.forEach((edge) => { + if (edge.from !== item.id && edge.to !== item.id) return; + activeEdges.add(edge.id); + relatedNodes.add(edge.from === item.id ? edge.to : edge.from); + }); + } else { + activeEdges.add(item.id); + activeNodes.add(item.from); + activeNodes.add(item.to); + } + + root.classList.add("has-topology-focus"); + state.nodes.forEach((element, id) => { + element.classList.toggle("is-active", activeNodes.has(id)); + element.classList.toggle("is-related", relatedNodes.has(id)); + }); + state.edges.forEach((element, id) => { + element.classList.toggle("is-active", activeEdges.has(id)); + }); + setTopologyDetail(root, item.category, item.label, item.details); + } + + function showOverview() { + root.classList.remove("has-topology-focus"); + state.nodes.forEach((element) => { + element.classList.remove("is-active", "is-related"); + }); + state.edges.forEach((element) => element.classList.remove("is-active")); + setTopologyDetail(root, overview.category, overview.label, overview.body); + } + + function updatePressedState() { + items.forEach((item) => { + const pressed = + pinnedItem?.itemType === item.itemType && pinnedItem.id === item.id; + item.element.setAttribute("aria-pressed", String(pressed)); + item.element.classList.toggle("is-pinned", pressed); + }); + } + + root.classList.add("is-enhanced"); + showOverview(); +} + +function setTopologyDetail(root, category, label, body) { + setText(root, "[data-topology-category]", category); + setText(root, "[data-topology-title]", label); + setText(root, "[data-topology-body]", body); +} function buildDiagram(stage, config) { + const isTopology = config.type === "topology"; const svg = createSvgElement("svg", { viewBox: `0 0 ${config.canvas.width} ${config.canvas.height}`, - role: "img", + role: isTopology ? "group" : "img", "aria-label": config.description, }); svg.classList.add("interactive-diagram__svg"); @@ -254,16 +391,69 @@ function buildDiagram(stage, config) { const to = nodesById.get(edge.to); if (!from || !to) return; const points = edgePoints(from, to, edge.offset); - const line = createSvgElement("line", { + const lineAttributes = { x1: points.x1, y1: points.y1, x2: points.x2, y2: points.y2, "marker-end": `url(#${marker.id})`, - }); - line.classList.add("interactive-diagram__edge"); - svg.append(line); - edges.set(edge.id, line); + }; + if (edge.bidirectional) { + lineAttributes["marker-start"] = `url(#${marker.id})`; + } + + if (isTopology) { + const group = createSvgElement("g"); + group.classList.add( + "interactive-diagram__edge", + "interactive-diagram__edge--interactive", + `interactive-diagram__edge--${edge.kind}`, + ); + const line = createSvgElement("line", lineAttributes); + line.classList.add("interactive-diagram__edge-line"); + const hitTarget = createSvgElement("line", { + x1: points.x1, + y1: points.y1, + x2: points.x2, + y2: points.y2, + }); + hitTarget.classList.add("interactive-diagram__edge-hit"); + group.append(line, hitTarget); + + if (edge.label) { + const labelPoint = edgeTokenPoint( + points, + edge.labelProgress ?? 0.5, + edge.labelOffset ?? 0, + ); + const labelWidth = Math.max(68, edge.label.length * 6.6 + 20); + const labelGroup = createSvgElement("g"); + labelGroup.classList.add("interactive-diagram__edge-label"); + labelGroup.append( + createSvgElement("rect", { + x: labelPoint.x - labelWidth / 2, + y: labelPoint.y - 13, + width: labelWidth, + height: 26, + rx: "13", + }), + createSvgText( + edge.label, + labelPoint.x, + labelPoint.y + 4, + "interactive-diagram__edge-label-text", + ), + ); + group.append(labelGroup); + } + svg.append(group); + edges.set(edge.id, group); + } else { + const line = createSvgElement("line", lineAttributes); + line.classList.add("interactive-diagram__edge"); + svg.append(line); + edges.set(edge.id, line); + } }); const nodes = new Map(); @@ -298,12 +488,17 @@ function buildDiagram(stage, config) { nodes.set(node.id, group); }); - const token = createSvgElement("g"); - token.classList.add("interactive-diagram__token"); - const tokenRect = createSvgElement("rect", { height: "28", rx: "14" }); - const tokenText = createSvgText("", 0, 0, "interactive-diagram__token-label"); - token.append(tokenRect, tokenText); - svg.append(token); + let token; + let tokenRect; + let tokenText; + if (!isTopology) { + token = createSvgElement("g"); + token.classList.add("interactive-diagram__token"); + tokenRect = createSvgElement("rect", { height: "28", rx: "14" }); + tokenText = createSvgText("", 0, 0, "interactive-diagram__token-label"); + token.append(tokenRect, tokenText); + svg.append(token); + } stage.append(svg); return { diff --git a/content/manuals/ai/sandboxes/diagrams/direct-workspace-edit.yaml b/content/manuals/ai/sandboxes/diagrams/direct-workspace-edit.yaml deleted file mode 100644 index e20d3b69ab5e..000000000000 --- a/content/manuals/ai/sandboxes/diagrams/direct-workspace-edit.yaml +++ /dev/null @@ -1,98 +0,0 @@ -title: Follow a workspace edit in direct mode -description: Step through a file edit as it becomes visible in the working tree on the host. -autoplayDuration: 4000 -canvas: - width: 820 - height: 330 -boundaries: - - id: host - label: Host machine - x: 16 - y: 20 - width: 788 - height: 290 - kind: host - - id: sandbox - label: Sandbox microVM - x: 38 - y: 58 - width: 500 - height: 230 - kind: sandbox -nodes: - - id: agent - label: AI agent - description: Edits a file - x: 68 - y: 126 - width: 150 - height: 72 - kind: process - - id: mounted-workspace - label: Mounted workspace - description: View inside the sandbox - x: 330 - y: 126 - width: 174 - height: 72 - kind: context - - id: host-worktree - label: Host working tree - description: Same files, not a copy - x: 612 - y: 86 - width: 166 - height: 72 - kind: context - - id: host-tools - label: Host tools - description: Editor, Git, and builds - x: 612 - y: 218 - width: 166 - height: 72 - kind: process -edges: - - id: agent-workspace - from: agent - to: mounted-workspace - - id: workspace-worktree - from: mounted-workspace - to: host-worktree - offset: -10 - - id: worktree-tools - from: host-worktree - to: host-tools -steps: - - label: The agent edits a file - body: In direct mode, the agent writes to the workspace mounted in the microVM. - activeNodes: [agent] - token: - node: agent - label: app.py - placement: bottom - state: The write begins inside the sandbox. - - label: The write reaches the mounted workspace - body: The mounted path gives the sandbox a read-write view of the project directory. - activeNodes: [agent, mounted-workspace] - activeEdges: [agent-workspace] - token: - edge: agent-workspace - label: write app.py - state: Direct mode does not create a private workspace copy. - - label: The host working tree changes - body: The mounted workspace and the host working tree refer to the same files. - activeNodes: [mounted-workspace, host-worktree] - activeEdges: [workspace-worktree] - token: - edge: workspace-worktree - label: same write - state: The edit is visible on the host as soon as it is written. - - label: Host tools see the update - body: Editors, Git, and build tools on the host observe the changed working tree. - activeNodes: [host-worktree, host-tools] - activeEdges: [worktree-tools] - token: - edge: worktree-tools - label: changed - state: Review workspace changes before running modified code on the host. diff --git a/content/manuals/ai/sandboxes/diagrams/mcp-tool-call.yaml b/content/manuals/ai/sandboxes/diagrams/mcp-tool-call.yaml deleted file mode 100644 index d7ccb2c7559e..000000000000 --- a/content/manuals/ai/sandboxes/diagrams/mcp-tool-call.yaml +++ /dev/null @@ -1,124 +0,0 @@ -title: Follow a remote MCP tool call -description: Step through a tool call as the host-side gateway applies policy and credentials. -autoplayDuration: 4000 -canvas: - width: 860 - height: 340 -boundaries: - - id: host - label: Host machine - x: 16 - y: 20 - width: 642 - height: 300 - kind: host - - id: sandbox - label: Sandbox microVM - x: 38 - y: 60 - width: 210 - height: 230 - kind: sandbox -nodes: - - id: agent - label: AI agent - description: Calls an MCP tool - x: 68 - y: 135 - width: 150 - height: 72 - kind: process - - id: gateway - label: MCP gateway - description: Runs on the host - x: 286 - y: 135 - width: 150 - height: 72 - kind: control - - id: policy - label: MCP policy - description: Checks tool access - x: 478 - y: 66 - width: 150 - height: 72 - kind: control - - id: credentials - label: Credential store - description: OAuth token stays here - x: 478 - y: 232 - width: 150 - height: 64 - kind: secret - - id: server - label: Remote MCP server - description: Runs outside the host - x: 694 - y: 135 - width: 150 - height: 72 - kind: external -edges: - - id: agent-gateway - from: agent - to: gateway - - id: gateway-policy - from: gateway - to: policy - - id: credentials-gateway - from: credentials - to: gateway - - id: gateway-server - from: gateway - to: server - offset: -8 - - id: server-gateway - from: server - to: gateway - offset: 8 -steps: - - label: The agent requests an MCP tool - body: The supported agent sends a tool call to the gateway endpoint exposed inside the sandbox. - activeNodes: [agent, gateway] - activeEdges: [agent-gateway] - token: - edge: agent-gateway - label: tools/call - labelOffset: -54 - state: The agent communicates with the host-side gateway, not the remote server directly. - - label: The gateway checks MCP policy - body: When organization governance is active, policy determines whether the tool is available. - activeNodes: [gateway, policy] - activeEdges: [gateway-policy] - token: - edge: gateway-policy - label: allowed? - labelOffset: -30 - state: Only tools permitted by active MCP policy can run. - - label: The gateway uses the host credential - body: The gateway retrieves the server credential without copying its value into the microVM. - activeNodes: [gateway, credentials] - activeEdges: [credentials-gateway] - token: - edge: credentials-gateway - label: OAuth - labelOffset: -18 - state: The OAuth credential remains in the host credential store. - - label: The gateway invokes the remote server - body: The approved tool call leaves the host through the gateway with the required authentication. - activeNodes: [gateway, server] - activeEdges: [gateway-server] - token: - edge: gateway-server - label: authenticated call - state: The remote server receives the call from the gateway. - - label: The result returns to the agent - body: The tool result travels back through the gateway to the agent inside the sandbox. - activeNodes: [server, gateway, agent] - activeEdges: [server-gateway, agent-gateway] - token: - edge: server-gateway - label: tool result - state: The result crosses into the microVM; the host credential does not. diff --git a/content/manuals/ai/sandboxes/diagrams/private-docker-engine.yaml b/content/manuals/ai/sandboxes/diagrams/private-docker-engine.yaml deleted file mode 100644 index 73f0a6a9fa96..000000000000 --- a/content/manuals/ai/sandboxes/diagrams/private-docker-engine.yaml +++ /dev/null @@ -1,88 +0,0 @@ -title: Run Docker inside the microVM -description: Step through a Docker command and see why it never reaches the host Docker daemon. -autoplayDuration: 4000 -canvas: - width: 820 - height: 310 -boundaries: - - id: host - label: Host machine - x: 16 - y: 20 - width: 788 - height: 270 - kind: host - - id: sandbox - label: Sandbox microVM - x: 38 - y: 58 - width: 590 - height: 210 - kind: sandbox -nodes: - - id: agent - label: AI agent - description: Runs a Docker command - x: 70 - y: 126 - width: 150 - height: 72 - kind: process - - id: sandbox-engine - label: Docker Engine - description: Private to the sandbox - x: 264 - y: 126 - width: 150 - height: 72 - kind: control - - id: container - label: Container - description: Runs inside the microVM - x: 458 - y: 126 - width: 150 - height: 72 - kind: process - - id: host-engine - label: Host Docker Engine - description: Not reachable - x: 636 - y: 126 - width: 150 - height: 72 - kind: context -edges: - - id: agent-engine - from: agent - to: sandbox-engine - - id: engine-container - from: sandbox-engine - to: container -steps: - - label: The agent runs a Docker command - body: The command starts inside the sandbox microVM. - activeNodes: [agent] - token: - node: agent - label: docker run - placement: bottom - state: The command originates inside the microVM. - - label: The sandbox engine receives the command - body: The agent connects to the Docker Engine dedicated to this sandbox. - activeNodes: [agent, sandbox-engine] - activeEdges: [agent-engine] - token: - edge: agent-engine - label: Docker API - labelOffset: -54 - state: No command reaches the host Docker daemon. - - label: The engine creates the container - body: The private engine creates and runs the container inside the same microVM. - activeNodes: [sandbox-engine, container] - activeEdges: [engine-container] - token: - edge: engine-container - label: create - labelOffset: 54 - state: The container and its engine remain inside the sandbox boundary. diff --git a/content/manuals/ai/sandboxes/diagrams/trust-boundary-topology.yaml b/content/manuals/ai/sandboxes/diagrams/trust-boundary-topology.yaml new file mode 100644 index 000000000000..30af26e89185 --- /dev/null +++ b/content/manuals/ai/sandboxes/diagrams/trust-boundary-topology.yaml @@ -0,0 +1,150 @@ +type: topology +title: Explore the sandbox trust boundary +description: Select a component or labeled connection to inspect what is shared, brokered, or isolated. +canvas: + width: 940 + height: 460 +overview: + category: Overview + label: The microVM is the primary trust boundary + body: The agent controls resources inside the microVM. It can cross the boundary only through explicit workspace and skills mounts, or through host gateways. Other host resources have no direct access path. +boundaries: + - id: host + label: Host machine + x: 18 + y: 20 + width: 700 + height: 420 + kind: host + - id: sandbox + label: Sandbox microVM + x: 42 + y: 60 + width: 390 + height: 350 + kind: sandbox + - id: external + label: Outside the host + x: 752 + y: 60 + width: 170 + height: 350 + kind: external +nodes: + - id: agent + label: AI agent + description: Full control in the microVM + details: The agent has sudo access inside the microVM. It can install packages, run code, and modify resources that are inside the sandbox boundary. + category: Inside the microVM + x: 76 + y: 112 + width: 180 + height: 72 + kind: process + - id: vm-filesystem + label: VM filesystem + description: Private to this sandbox + details: The microVM has its own persistent filesystem. The agent cannot use it to browse files elsewhere on the host. + category: Inside the microVM + x: 70 + y: 298 + width: 165 + height: 72 + kind: context + - id: sandbox-engine + label: Docker Engine + description: No host connection + details: Containers created by the agent run on the Docker Engine inside the microVM. This engine has no connection to the Docker daemon on the host. + category: Inside the microVM + x: 250 + y: 298 + width: 150 + height: 72 + kind: control + - id: workspace + label: Workspace + description: Mounted from the host + details: Direct mode mounts the host working tree read-write, so edits are visible on both sides. Clone mode mounts the host repository read-only and gives the agent a private clone. + category: Explicitly shared + x: 492 + y: 72 + width: 190 + height: 72 + kind: shared + - id: skills + label: Shared skills store + description: Optional shared mount + details: Supported agents mount the host-side skills store read-write unless you opt out. Changes can become visible to agents in other participating sandboxes. + category: Explicitly shared + x: 492 + y: 172 + width: 190 + height: 72 + kind: shared + - id: gateways + label: Host gateways + description: Network proxy and MCP + details: The host proxy and MCP gateway broker approved connections. They apply policy and use host-side credentials without copying credential values into the microVM. + category: Brokered access + x: 492 + y: 272 + width: 190 + height: 72 + kind: control + - id: host-resources + label: Other host resources + description: No direct access + details: The agent has no direct path to the rest of the host filesystem, host processes, or the host Docker daemon. Explicit MCP tools do not expose these resources by default. + category: Outside the microVM + x: 492 + y: 372 + width: 190 + height: 54 + kind: restricted + - id: external-services + label: Allowed services + description: Remote services + details: Remote services run outside the host. The sandbox reaches them through the corresponding host gateway, subject to any active network or MCP policy. + category: Outside the host + x: 762 + y: 272 + width: 150 + height: 72 + kind: external +edges: + - id: workspace-mount + from: agent + to: workspace + label: Workspace mount + details: This explicit mount is the agent's path to project files on the host. Its permissions depend on whether the sandbox uses direct mode or clone mode. + category: Shared relationship + kind: shared + bidirectional: true + labelOffset: -18 + - id: skills-mount + from: agent + to: skills + label: Skills mount + details: This optional read-write mount connects supported agents to a persistent skills store shared through the host. + category: Shared relationship + kind: shared + bidirectional: true + labelOffset: 2 + - id: gateway-path + from: agent + to: gateways + label: Brokered path + details: Outbound network and MCP traffic crosses the microVM boundary only through host-side gateways that enforce the applicable policies. + category: Brokered relationship + kind: brokered + bidirectional: true + labelOffset: 18 + - id: allowed-destinations + from: gateways + to: external-services + label: Policy-controlled + details: Host gateways apply the relevant network or MCP policy before connecting to remote services. + category: Brokered relationship + kind: brokered + bidirectional: true + labelOffset: -50 diff --git a/content/manuals/ai/sandboxes/security/_index.md b/content/manuals/ai/sandboxes/security/_index.md index 32ac133bfeb3..dfeff70e6d9c 100644 --- a/content/manuals/ai/sandboxes/security/_index.md +++ b/content/manuals/ai/sandboxes/security/_index.md @@ -53,29 +53,13 @@ sandbox VM. An agent can invoke the tools those servers expose through the MCP gateway, subject to MCP policies when organization governance is active. Treat local MCP servers as trusted host integrations. -The following sequences focus on three distinct interactions across these trust -boundaries. To follow an outbound request through network policy and credential -injection, see [Architecture](../architecture.md#follow-an-authenticated-request). +The topology below shows the resources inside each boundary and the explicit +paths that cross them. -### Run Docker inside the microVM +{{< interactive-diagram src="../diagrams/trust-boundary-topology.yaml" >}} -The sandbox Docker Engine handles the agent's commands without exposing the -Docker daemon on the host. - -{{< interactive-diagram src="../diagrams/private-docker-engine.yaml" >}} - -### Follow a workspace edit - -Direct mode mounts the workspace read-write, so the agent and host tools see -the same working tree. - -{{< interactive-diagram src="../diagrams/direct-workspace-edit.yaml" >}} - -### Follow an MCP tool call - -The host-side MCP gateway brokers access from the agent to a remote MCP server. - -{{< interactive-diagram src="../diagrams/mcp-tool-call.yaml" >}} +To follow an outbound request through network policy and credential injection, +see [Architecture](../architecture.md#follow-an-authenticated-request). ## Isolation layers diff --git a/layouts/_shortcodes/interactive-diagram.html b/layouts/_shortcodes/interactive-diagram.html index f95670145168..eb77a8bd8006 100644 --- a/layouts/_shortcodes/interactive-diagram.html +++ b/layouts/_shortcodes/interactive-diagram.html @@ -7,12 +7,14 @@ {{- errorf "interactive-diagram shortcode: file %q not found: %s" $filePath .Position -}} {{- end -}} {{- $diagram := readFile $filePath | transform.Unmarshal -}} +{{- $type := $diagram.type | default "sequence" -}} {{- $id := printf "interactive-diagram-%s-%d" .Page.File.UniqueID .Ordinal -}} {{- .Page.Store.Set "interactive-diagram" true -}}
    @@ -22,67 +24,101 @@

    {{ $diagram.description }}

- + {{- if eq $type "sequence" }} + + {{- end }}
-
-
- -
-

-

+ {{- if eq $type "topology" }} +
+

+ {{ $diagram.overview.category }} +

+
+

+ {{ $diagram.overview.label }} +

+

+ {{ $diagram.overview.body }} +

-

-
-
- -
-
- +
+ +
+

+

+
+
+

+
- -
+ class="interactive-diagram__autoplay-progress" + data-step-autoplay-progress + role="progressbar" + aria-label="Time until the next step" + aria-valuemin="0" + aria-valuemax="100" + aria-valuenow="0" + > + +
+
+ +
+ +
+ {{- end }}
-
    - {{- range $diagram.steps }} -
  1. {{ .label }}: {{ .body }}
  2. - {{- end }} -
+ {{- if eq $type "topology" }} +
    + {{- range $diagram.nodes }} +
  • {{ .label }}: {{ .details }}
  • + {{- end }} + {{- range $diagram.edges }} +
  • {{ .label }}: {{ .details }}
  • + {{- end }} +
+ {{- else }} +
    + {{- range $diagram.steps }} +
  1. {{ .label }}: {{ .body }}
  2. + {{- end }} +
+ {{- end }}