From ea1f81aa56f0167f4821cd933e9df5563e3cc65d Mon Sep 17 00:00:00 2001 From: Romgi Productions Date: Fri, 10 Jul 2026 22:35:35 -0400 Subject: [PATCH 1/2] Made favicon show up --- app/layout.tsx | 7 +++++-- public/favicon.ico | Bin 0 -> 14284 bytes 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 public/favicon.ico diff --git a/app/layout.tsx b/app/layout.tsx index 6f0b5df..1f6b5ef 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -39,8 +39,11 @@ export async function generateMetadata(): Promise { authors: [{ name: "RouteLab" }], creator: "RouteLab", icons: { - icon: "/favicon.svg", - shortcut: "/favicon.svg", + icon: [ + { url: "/favicon.svg", type: "image/svg+xml" }, + { url: "/favicon.ico", sizes: "any" }, + ], + shortcut: "/favicon.ico", }, openGraph: { type: "website", diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..00c1b149029c5d020748e8e2fa74ccab03d75a11 GIT binary patch literal 14284 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 F{{hYZr}h8< literal 0 HcmV?d00001 From 398e4a8adf0820be7ca33ee5462657aef09f6450 Mon Sep 17 00:00:00 2001 From: Romgi Productions Date: Sat, 11 Jul 2026 16:19:28 -0400 Subject: [PATCH 2/2] fix: repair compare visuals and playback timing --- README.md | 2 +- components/CompareWorkspace.module.css | 25 +- components/CompareWorkspace.tsx | 225 +++++++++++++++--- docs/animation-system.md | 8 +- lib/algorithms/index.ts | 16 +- lib/compare-playback.ts | 74 ++++++ tests/compare-playback.test.ts | 68 ++++++ tests/e2e/routelab.spec.ts | 69 ++++++ tests/e2e/visual.spec.ts | 11 + .../compare-graph-chromium-win32.png | Bin 0 -> 21383 bytes .../compare-graph-mobile-chromium-win32.png | Bin 0 -> 14820 bytes .../compare-initial-mobile-chromium-win32.png | Bin 58620 -> 59273 bytes 12 files changed, 450 insertions(+), 48 deletions(-) create mode 100644 lib/compare-playback.ts create mode 100644 tests/compare-playback.test.ts create mode 100644 tests/e2e/visual.spec.ts-snapshots/compare-graph-chromium-win32.png create mode 100644 tests/e2e/visual.spec.ts-snapshots/compare-graph-mobile-chromium-win32.png diff --git a/README.md b/README.md index b81ba15..04ed84e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The application is an educational simulator, not a navigation service. Its built - **Story Mode (`/`)** introduces graphs, route objectives, search strategies, source concepts, scenarios, and trace architecture through responsive interactive demonstrations. - **Algorithm Lab (`/lab`)** runs one of seven algorithms with scenario, cost-model, heuristic, language, closure, speed, and timeline controls. It includes metrics, frontier/current-event inspection, node details, and an accessible graph table. -- **Compare Mode (`/compare`)** synchronizes two to four compatible algorithms on the same graph and playback clock (two lanes by default). +- **Compare Mode (`/compare`)** synchronizes two to four compatible algorithms on the same graph, with playback from 0.5× to 100× and a measured real-time option (two lanes by default). - **Code Explorer (`/code`)** presents curated read-only Dijkstra excerpts in six languages with conceptual line highlighting, copy, and download actions. - **Documentation (`/docs`)** provides an in-product guide; the [`docs/`](docs/) directory contains the detailed engineering reference. diff --git a/components/CompareWorkspace.module.css b/components/CompareWorkspace.module.css index ccc5c31..4e19070 100644 --- a/components/CompareWorkspace.module.css +++ b/components/CompareWorkspace.module.css @@ -453,7 +453,9 @@ .graphFrame { position: relative; + height: 266px; min-height: 266px; + overflow: hidden; border-block: 1px solid var(--line); background: linear-gradient(var(--bg-elevated) 1px, transparent 1px), @@ -471,15 +473,19 @@ .graphFrame :global(.graph-canvas), .graphFrame :global(.graph-visual) { - height: 266px; + height: 100%; + min-height: 0; } .graphFrame :global(.graph-visual) { position: relative; } -.graphFrame svg { +.graphFrame :global(.graph-visual) > svg { + position: absolute; display: block; + inset: 0; + box-sizing: border-box; width: 100%; height: 100%; padding: 18px; @@ -721,7 +727,7 @@ .timeline { display: grid; - grid-template-columns: auto minmax(220px, 1fr) 90px; + grid-template-columns: auto minmax(220px, 1fr) minmax(128px, auto); gap: 22px; align-items: center; width: min(1540px, calc(100% - 48px)); @@ -809,6 +815,15 @@ font-size: 11px; } +.speedField small { + margin-top: 2px; + color: var(--text-dim); + font-family: var(--font-geist-mono), monospace; + font-size: 7px; + line-height: 1.2; + white-space: nowrap; +} + @media (max-width: 1280px) { .intro, .configurationInner, @@ -1001,9 +1016,7 @@ text-overflow: ellipsis; } - .graphFrame, - .graphFrame :global(.graph-canvas), - .graphFrame :global(.graph-visual) { + .graphFrame { min-height: 235px; height: 235px; } diff --git a/components/CompareWorkspace.tsx b/components/CompareWorkspace.tsx index 9c00341..3e23254 100644 --- a/components/CompareWorkspace.tsx +++ b/components/CompareWorkspace.tsx @@ -1,6 +1,12 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; +import { + useEffect, + useMemo, + useRef, + useState, + useSyncExternalStore, +} from "react"; import { GraphCanvas } from "@/components/GraphCanvas"; import { @@ -15,6 +21,15 @@ import { type HeuristicId, type RunOptions, } from "@/lib/algorithms"; +import { + formatExecutionTime, + getPlaybackDurationMs, + getRealtimeLaneStep, + getTimelineStepAtElapsed, + parsePlaybackMode, + PLAYBACK_SPEEDS, + type PlaybackMode, +} from "@/lib/compare-playback"; import { BUILT_IN_SCENARIOS, SCENARIO_LIMITS, @@ -25,7 +40,6 @@ import { import styles from "./CompareWorkspace.module.css"; const DEFAULT_ALGORITHMS: readonly AlgorithmId[] = ["dijkstra", "astar"]; -const PLAYBACK_SPEEDS = [0.5, 1, 2, 4] as const; const COST_LABELS: Readonly> = { weight: "Edge weight", @@ -48,6 +62,10 @@ const OPTIMALITY_LABELS: Readonly> = { unreachable: "Destination unreachable", }; +const subscribeToHydration = () => () => undefined; +const getHydratedSnapshot = () => true; +const getServerHydratedSnapshot = () => false; + interface ComparisonRun { readonly algorithmId: AlgorithmId; readonly result: AlgorithmResult | null; @@ -216,8 +234,18 @@ export function CompareWorkspace({ resolvedInitialHeuristic, ); const [step, setStep] = useState(0); + const stepRef = useRef(0); const [isPlaying, setIsPlaying] = useState(false); - const [playbackSpeed, setPlaybackSpeed] = useState(1); + const [playbackMode, setPlaybackMode] = useState(1); + const [realtimeElapsedMs, setRealtimeElapsedMs] = useState( + null, + ); + const realtimeElapsedRef = useRef(0); + const hydrated = useSyncExternalStore( + subscribeToHydration, + getHydratedSnapshot, + getServerHydratedSnapshot, + ); const [copyState, setCopyState] = useState<"idle" | "copied" | "address-bar">( "idle", ); @@ -257,8 +285,20 @@ export function CompareWorkspace({ (run): run is ComparisonRun & { result: AlgorithmResult } => run.result !== null, ); + const maxExecutionTimeMs = Math.max( + 0, + ...validRuns.map((run) => run.result.metrics.executionTimeMs), + ); + const resolveLaneStep = (result: AlgorithmResult) => + playbackMode === "realtime" && realtimeElapsedMs !== null + ? getRealtimeLaneStep({ + elapsedMs: realtimeElapsedMs, + executionTimeMs: result.metrics.executionTimeMs, + traceLength: result.trace.length, + }) + : Math.min(step, Math.max(0, result.trace.length - 1)); const completedRunCount = validRuns.filter( - (run) => step >= run.result.trace.length - 1, + (run) => resolveLaneStep(run.result) >= run.result.trace.length - 1, ).length; const sharedComplete = validRuns.length > 0 && step >= maxStep; const successfulRuns = validRuns.filter((run) => run.result.found); @@ -274,28 +314,73 @@ export function CompareWorkspace({ useEffect(() => { if (!isPlaying) return undefined; - const interval = window.setInterval( - () => { - setStep((currentStep) => { - const nextStep = Math.min(maxStep, currentStep + 1); - if (nextStep >= maxStep) { - setIsPlaying(false); - setAnnouncement( - `Comparison finished. ${validRuns.length} traces are complete.`, - ); - } - return nextStep; - }); - }, - Math.max(70, Math.round(680 / playbackSpeed)), - ); + const startStep = Math.min(maxStep, stepRef.current); + const realtimeStartElapsedMs = + playbackMode === "realtime" + ? Math.min(maxExecutionTimeMs, realtimeElapsedRef.current) + : 0; + const durationMs = getPlaybackDurationMs({ + startStep, + maxStep, + mode: playbackMode, + realtimeDurationMs: maxExecutionTimeMs, + realtimeStartElapsedMs, + }); + let frameId = 0; + const startedAt = window.performance.now(); + let renderedStep = startStep; - return () => window.clearInterval(interval); - }, [isPlaying, maxStep, playbackSpeed, validRuns.length]); + const tick = (timestamp: number) => { + const elapsedMs = Math.max(0, timestamp - startedAt); + + if (playbackMode === "realtime") { + const totalElapsedMs = + maxExecutionTimeMs <= 0 + ? 0 + : Math.min(maxExecutionTimeMs, realtimeStartElapsedMs + elapsedMs); + realtimeElapsedRef.current = totalElapsedMs; + setRealtimeElapsedMs(totalElapsedMs); + } + + const nextStep = getTimelineStepAtElapsed({ + startStep, + maxStep, + elapsedMs, + durationMs, + }); + if (nextStep !== renderedStep) { + renderedStep = nextStep; + stepRef.current = nextStep; + setStep(nextStep); + } + + if (elapsedMs >= durationMs) { + stepRef.current = maxStep; + setStep(maxStep); + if (playbackMode === "realtime") { + realtimeElapsedRef.current = maxExecutionTimeMs; + setRealtimeElapsedMs(maxExecutionTimeMs); + } + setIsPlaying(false); + setAnnouncement( + `Comparison finished. ${validRuns.length} traces are complete.`, + ); + return; + } + + frameId = window.requestAnimationFrame(tick); + }; + + frameId = window.requestAnimationFrame(tick); + return () => window.cancelAnimationFrame(frameId); + }, [isPlaying, maxExecutionTimeMs, maxStep, playbackMode, validRuns.length]); function resetTimeline(message: string) { setIsPlaying(false); + stepRef.current = 0; setStep(0); + realtimeElapsedRef.current = 0; + setRealtimeElapsedMs(null); setCopyState("idle"); setAnnouncement(message); } @@ -361,19 +446,66 @@ export function CompareWorkspace({ setAnnouncement(`Playback paused at shared step ${step + 1}.`); return; } - if (sharedComplete) setStep(0); + const startStep = sharedComplete ? 0 : stepRef.current; + if (sharedComplete) { + stepRef.current = 0; + setStep(0); + } + if (playbackMode === "realtime") { + const elapsedMs = sharedComplete + ? 0 + : (realtimeElapsedMs ?? + (maxStep > 0 ? (startStep / maxStep) * maxExecutionTimeMs : 0)); + realtimeElapsedRef.current = elapsedMs; + setRealtimeElapsedMs(elapsedMs); + } setIsPlaying(true); setAnnouncement( - sharedComplete ? "Comparison replay started." : "Playback started.", + playbackMode === "realtime" + ? `Real-time playback started. Slowest measured compute time: ${formatExecutionTime(maxExecutionTimeMs)}.` + : sharedComplete + ? "Comparison replay started." + : `Playback started at ${playbackMode} times speed.`, ); } function moveToStep(nextStep: number, message: string) { + const resolvedStep = Math.min(maxStep, Math.max(0, nextStep)); setIsPlaying(false); - setStep(Math.min(maxStep, Math.max(0, nextStep))); + stepRef.current = resolvedStep; + setStep(resolvedStep); + if (playbackMode === "realtime") { + const elapsedMs = + maxStep > 0 ? (resolvedStep / maxStep) * maxExecutionTimeMs : 0; + realtimeElapsedRef.current = elapsedMs; + setRealtimeElapsedMs(elapsedMs); + } else { + realtimeElapsedRef.current = 0; + setRealtimeElapsedMs(null); + } setAnnouncement(message); } + function handlePlaybackModeChange(value: string) { + const nextMode = parsePlaybackMode(value); + setPlaybackMode(nextMode); + if (nextMode === "realtime") { + const elapsedMs = + maxStep > 0 ? (stepRef.current / maxStep) * maxExecutionTimeMs : 0; + realtimeElapsedRef.current = elapsedMs; + setRealtimeElapsedMs(elapsedMs); + setAnnouncement( + validRuns.length === 0 + ? "Real-time mode selected, but no compatible runs are available." + : `Real-time mode selected. Slowest measured compute time: ${formatExecutionTime(maxExecutionTimeMs)}.`, + ); + } else { + realtimeElapsedRef.current = 0; + setRealtimeElapsedMs(null); + setAnnouncement(`Playback speed changed to ${nextMode} times.`); + } + } + async function copyConfiguration() { const url = new URL(window.location.href); url.searchParams.set("scenario", scenario.id); @@ -579,12 +711,10 @@ export function CompareWorkspace({ {runs.map((run, index) => { const info = ALGORITHM_INFO[run.algorithmId]; const result = run.result; - const laneStep = result - ? Math.min(step, Math.max(0, result.trace.length - 1)) - : 0; + const laneStep = result ? resolveLaneStep(result) : 0; const event = result?.trace[laneStep] ?? null; const laneComplete = result - ? step >= result.trace.length - 1 + ? laneStep >= result.trace.length - 1 : false; return ( @@ -682,7 +812,10 @@ export function CompareWorkspace({ {result ? ( <> -
+
+
+
Max frontier
+
+ {result.metrics.maximumFrontierSize.toLocaleString( + "en-US", + )} +
+
Trace events
@@ -738,6 +879,19 @@ export function CompareWorkspace({ )}
+
+
Compute
+
+ {hydrated + ? formatExecutionTime( + result.metrics.executionTimeMs, + ) + : "—"} +
+
@@ -861,15 +1015,24 @@ export function CompareWorkspace({
diff --git a/docs/animation-system.md b/docs/animation-system.md index 0e9fab8..bbd3050 100644 --- a/docs/animation-system.md +++ b/docs/animation-system.md @@ -76,9 +76,9 @@ No algorithm is rerun during timeline navigation. A new trace is generated only ## Playback controller -Autoplay owns one cancelable browser timer. Each tick advances by one semantic event until the final cursor, then stops. Changing route, scenario, algorithm, cost model, heuristic, closure state, or trace cancels the prior timer. Starting playback at the end restarts from cursor zero only through an explicit replay action. +Autoplay owns one cancelable animation-frame loop. Elapsed wall-clock time determines the semantic cursor, so faster rates can advance several correctness events in one paint without distorting total playback time. Changing route, scenario, algorithm, cost model, heuristic, closure state, or trace cancels the prior loop. Starting playback at the end restarts from cursor zero only through an explicit replay action. -The speed control maps human-readable rates to timer cadence. It does not batch correctness events together. The browser may throttle timers in a background tab; on return, RouteLab continues from the current cursor rather than racing through wall-clock “missed” ticks. +The speed control maps human-readable rates from 0.5× through 100× to elapsed playback time. Real-time mode instead maps each lane's event stream across that algorithm's measured computation duration, while retaining the shared normalized progress controller. The browser may throttle animation frames in a background tab; on return, RouteLab derives the cursor from elapsed time and safely catches up to the correct position. Global keyboard shortcuts are scoped away from editable controls: @@ -92,9 +92,9 @@ Native buttons and the range input remain the authoritative accessible controls ## Compare synchronization -Compare Mode runs two to four distinct compatible algorithms independently against the same immutable graph, start, goal, closures, and cost metric. Each pane owns a trace/result; the controller owns playback, speed, and a shared step. +Compare Mode runs two to four distinct compatible algorithms independently against the same immutable graph, start, goal, closures, and cost metric. Each pane owns a trace/result; the controller owns playback mode and normalized progress. Fixed-speed playback uses one shared semantic step. Real-time playback resolves a lane-specific step from its measured computation duration, so quicker algorithms can visibly finish before slower ones. -At zero-based shared event index `s`, a pane selects `min(s, trace.length - 1)` and replays through that selected event. A shorter trace therefore stays at its completed state while a longer trace continues. Metrics remain the pane's real counters; RouteLab does not stretch or synthesize events to make animations look simultaneous. Reset and scrub apply to every pane atomically. +In fixed-speed playback, at zero-based shared event index `s`, a pane selects `min(s, trace.length - 1)` and replays through that selected event. A shorter trace therefore stays at its completed state while a longer trace continues. In real-time playback, each pane maps elapsed time across its own trace using that algorithm's measured total computation duration. Metrics remain the pane's real counters; RouteLab does not synthesize correctness events to make animations look simultaneous. Reset and scrub apply to every pane atomically. For comparisons with substantially different event counts, normalized-progress visualization can be added later, but it must be labeled because equal percentages do not represent equivalent algorithm decisions. diff --git a/lib/algorithms/index.ts b/lib/algorithms/index.ts index 86ffe95..311673d 100644 --- a/lib/algorithms/index.ts +++ b/lib/algorithms/index.ts @@ -1258,6 +1258,10 @@ interface ResultParts { readonly allPairs?: AllPairsShortestPaths; } +function monotonicNow(): number { + return typeof performance === "undefined" ? Date.now() : performance.now(); +} + function makeResult( options: ResolvedRunOptions, recorder: TraceRecorder, @@ -1268,7 +1272,7 @@ function makeResult( ): AlgorithmResult { const resultMetrics: AlgorithmMetrics = { ...metrics, - executionTimeMs: Math.max(0, Date.now() - startedAt), + executionTimeMs: Math.max(0, monotonicNow() - startedAt), traceEventCount: recorder.trace.length, }; return { @@ -1337,7 +1341,7 @@ function executeBreadthFirstSearch( options: ResolvedRunOptions, warnings: readonly CompatibilityIssue[], ): AlgorithmResult { - const startedAt = Date.now(); + const startedAt = monotonicNow(); const traversal = buildTraversalGraph(graph, options); const recorder = new TraceRecorder( options.algorithmId, @@ -1487,7 +1491,7 @@ function executeBestFirst( warnings: readonly CompatibilityIssue[], mode: BestFirstMode, ): AlgorithmResult { - const startedAt = Date.now(); + const startedAt = monotonicNow(); const traversal = buildTraversalGraph(graph, options); const goal = traversal.nodesById.get(options.goalId); if (!goal) throw new Error("Validated goal node is missing."); @@ -1749,7 +1753,7 @@ function executeBidirectionalDijkstra( options: ResolvedRunOptions, warnings: readonly CompatibilityIssue[], ): AlgorithmResult { - const startedAt = Date.now(); + const startedAt = monotonicNow(); const traversal = buildTraversalGraph(graph, options); const recorder = new TraceRecorder( options.algorithmId, @@ -2077,7 +2081,7 @@ function executeBellmanFord( options: ResolvedRunOptions, warnings: readonly CompatibilityIssue[], ): AlgorithmResult { - const startedAt = Date.now(); + const startedAt = monotonicNow(); const traversal = buildTraversalGraph(graph, options); const recorder = new TraceRecorder( options.algorithmId, @@ -2319,7 +2323,7 @@ function executeFloydWarshall( options: ResolvedRunOptions, warnings: readonly CompatibilityIssue[], ): AlgorithmResult { - const startedAt = Date.now(); + const startedAt = monotonicNow(); const traversal = buildTraversalGraph(graph, options); const recorder = new TraceRecorder( options.algorithmId, diff --git a/lib/compare-playback.ts b/lib/compare-playback.ts new file mode 100644 index 0000000..c30f886 --- /dev/null +++ b/lib/compare-playback.ts @@ -0,0 +1,74 @@ +export const BASE_EVENT_DURATION_MS = 680; +export const PLAYBACK_SPEEDS = [0.5, 1, 2, 4, 10, 25, 50, 100] as const; + +export type PlaybackSpeed = (typeof PLAYBACK_SPEEDS)[number]; +export type PlaybackMode = PlaybackSpeed | "realtime"; + +export function parsePlaybackMode(value: string): PlaybackMode { + if (value === "realtime") return value; + const speed = Number(value) as PlaybackSpeed; + return PLAYBACK_SPEEDS.includes(speed) ? speed : 1; +} + +export function getPlaybackDurationMs({ + startStep, + maxStep, + mode, + realtimeDurationMs, + realtimeStartElapsedMs = 0, +}: { + startStep: number; + maxStep: number; + mode: PlaybackMode; + realtimeDurationMs: number; + realtimeStartElapsedMs?: number; +}): number { + const remainingSteps = Math.max(0, maxStep - startStep); + if (remainingSteps === 0) return 0; + if (mode === "realtime") { + return Math.max(0, realtimeDurationMs - realtimeStartElapsedMs); + } + return (remainingSteps * BASE_EVENT_DURATION_MS) / mode; +} + +export function getTimelineStepAtElapsed({ + startStep, + maxStep, + elapsedMs, + durationMs, +}: { + startStep: number; + maxStep: number; + elapsedMs: number; + durationMs: number; +}): number { + const firstStep = Math.min(maxStep, Math.max(0, startStep)); + const remainingSteps = Math.max(0, maxStep - firstStep); + if (remainingSteps === 0 || durationMs <= 0) return maxStep; + const progress = Math.min(1, Math.max(0, elapsedMs / durationMs)); + return Math.min(maxStep, firstStep + Math.floor(progress * remainingSteps)); +} + +export function getRealtimeLaneStep({ + elapsedMs, + executionTimeMs, + traceLength, +}: { + elapsedMs: number; + executionTimeMs: number; + traceLength: number; +}): number { + const finalStep = Math.max(0, traceLength - 1); + if (finalStep === 0 || executionTimeMs <= 0) return finalStep; + const progress = Math.min(1, Math.max(0, elapsedMs / executionTimeMs)); + return Math.min(finalStep, Math.floor(progress * finalStep)); +} + +export function formatExecutionTime(milliseconds: number): string { + if (!Number.isFinite(milliseconds) || milliseconds < 0) return "—"; + if (milliseconds < 0.01) return "<0.01 ms"; + if (milliseconds < 1) return `${milliseconds.toFixed(2)} ms`; + if (milliseconds < 10) return `${milliseconds.toFixed(2)} ms`; + if (milliseconds < 1000) return `${milliseconds.toFixed(1)} ms`; + return `${(milliseconds / 1000).toFixed(2)} s`; +} diff --git a/tests/compare-playback.test.ts b/tests/compare-playback.test.ts new file mode 100644 index 0000000..515787e --- /dev/null +++ b/tests/compare-playback.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "vitest"; +import { + formatExecutionTime, + getPlaybackDurationMs, + getRealtimeLaneStep, + getTimelineStepAtElapsed, + parsePlaybackMode, + PLAYBACK_SPEEDS, +} from "@/lib/compare-playback"; + +describe("Compare Mode playback timing", () => { + it("offers multipliers through 100x and parses real-time mode", () => { + expect(PLAYBACK_SPEEDS).toEqual([0.5, 1, 2, 4, 10, 25, 50, 100]); + expect(parsePlaybackMode("100")).toBe(100); + expect(parsePlaybackMode("realtime")).toBe("realtime"); + expect(parsePlaybackMode("invalid")).toBe(1); + }); + + it("lets high multipliers advance multiple events per rendered frame", () => { + const durationMs = getPlaybackDurationMs({ + startStep: 0, + maxStep: 100, + mode: 100, + realtimeDurationMs: 0, + }); + expect(durationMs).toBe(680); + expect( + getTimelineStepAtElapsed({ + startStep: 0, + maxStep: 100, + elapsedMs: 16, + durationMs, + }), + ).toBe(2); + expect( + getTimelineStepAtElapsed({ + startStep: 0, + maxStep: 100, + elapsedMs: durationMs, + durationMs, + }), + ).toBe(100); + }); + + it("maps each real-time lane to its own measured completion time", () => { + expect( + getRealtimeLaneStep({ + elapsedMs: 3, + executionTimeMs: 6, + traceLength: 101, + }), + ).toBe(50); + expect( + getRealtimeLaneStep({ + elapsedMs: 16, + executionTimeMs: 0, + traceLength: 101, + }), + ).toBe(100); + }); + + it("formats sub-millisecond and visible execution durations honestly", () => { + expect(formatExecutionTime(0)).toBe("<0.01 ms"); + expect(formatExecutionTime(0.125)).toBe("0.13 ms"); + expect(formatExecutionTime(12.34)).toBe("12.3 ms"); + expect(formatExecutionTime(1234)).toBe("1.23 s"); + }); +}); diff --git a/tests/e2e/routelab.spec.ts b/tests/e2e/routelab.spec.ts index 9a78b8f..9c49fe3 100644 --- a/tests/e2e/routelab.spec.ts +++ b/tests/e2e/routelab.spec.ts @@ -131,6 +131,75 @@ test("Compare Mode runs real synchronized traces", async ({ page }) => { await expect(page.getByLabel("Algorithm in lane 2")).toHaveValue("astar"); const sliders = page.getByRole("slider"); await expect(sliders.first()).toBeEnabled(); + + const graphFrames = page.getByTestId("compare-graph-frame"); + await expect(graphFrames).toHaveCount(2); + for (const frame of await graphFrames.all()) { + const geometry = await frame.evaluate((element) => { + const frameRect = element.getBoundingClientRect(); + const visualRect = element + .querySelector(".graph-visual")! + .getBoundingClientRect(); + const svgRect = element.querySelector("svg")!.getBoundingClientRect(); + return { + visualContained: + visualRect.top >= frameRect.top - 1 && + visualRect.bottom <= frameRect.bottom + 1, + svgContained: + svgRect.top >= frameRect.top - 1 && + svgRect.bottom <= frameRect.bottom + 1, + }; + }); + expect(geometry.visualContained).toBe(true); + expect(geometry.svgContained).toBe(true); + } +}); + +test("Compare Mode supports 100x and measured real-time playback", async ({ + page, +}) => { + await page.goto("/compare"); + const timeline = page + .getByRole("region", { name: "Synchronized playback controls" }) + .getByRole("slider"); + const speed = page.getByRole("combobox", { name: "Playback speed" }); + const computeTimes = page.getByTestId("compare-compute-time"); + + await expect(computeTimes.first()).not.toHaveText("—"); + await expect(speed.locator("option")).toHaveText([ + "0.5×", + "1×", + "2×", + "4×", + "10×", + "25×", + "50×", + "100×", + "Real time", + ]); + + const finalStep = await timeline.getAttribute("max"); + expect(finalStep).not.toBeNull(); + await speed.selectOption("100"); + await page.getByRole("button", { name: "Play comparison" }).click(); + await expect(timeline).toHaveValue(finalStep!, { timeout: 6_000 }); + + await speed.selectOption("realtime"); + await expect(speed).toHaveValue("realtime"); + await page.getByRole("button", { name: "Play comparison" }).click(); + await expect(timeline).toHaveValue(finalStep!, { timeout: 2_000 }); + await expect(page.getByText(/measured$/)).toBeVisible(); + + await page.goto("/compare?scenario=all-pairs-matrix&algorithms=bfs,dijkstra"); + await page.waitForLoadState("networkidle"); + const unavailableSpeed = page.getByRole("combobox", { + name: "Playback speed", + }); + await unavailableSpeed.selectOption("realtime"); + await expect(unavailableSpeed).toHaveValue("realtime"); + await expect( + page.getByText("No compatible runs", { exact: true }), + ).toBeVisible(); }); test("malformed imports fail as data without executing labels", async ({ diff --git a/tests/e2e/visual.spec.ts b/tests/e2e/visual.spec.ts index 9929c4e..7fbda13 100644 --- a/tests/e2e/visual.spec.ts +++ b/tests/e2e/visual.spec.ts @@ -61,3 +61,14 @@ test("Compare Mode visual baseline", async ({ page }) => { maxDiffPixelRatio: 0.005, }); }); + +test("Compare graph visual baseline", async ({ page }) => { + await page.goto("/compare"); + const graphFrame = page.getByTestId("compare-graph-frame").first(); + await graphFrame.scrollIntoViewIfNeeded(); + await expect(graphFrame).toHaveScreenshot("compare-graph.png", { + animations: "disabled", + caret: "hide", + maxDiffPixelRatio: 0.005, + }); +}); diff --git a/tests/e2e/visual.spec.ts-snapshots/compare-graph-chromium-win32.png b/tests/e2e/visual.spec.ts-snapshots/compare-graph-chromium-win32.png new file mode 100644 index 0000000000000000000000000000000000000000..fdb8b97e88db3dee8c80caaf13e48a5f4cc65cfe GIT binary patch literal 21383 zcmb5Wby!s2`z}mLmw+H4E!{C7Eg;<>-5o%Q;j-bSmb$l_pZquuxAMkj&l}*~j>1^U)(yX>9AyAFaI)Nbf9vSI-NZwO) z`(Dc$?vZ|_0;)Z!Wr{sj6AOkkTd8r&uhJhft3S&~X;8!?yarVZkl_T|@{PwDJ5>6@ z&lkL?#%Ae$S#&%b+usz`hVx8z-Cg3u_4}<|gaa({Rq&wwfInvIJD|>6^C~Ru?TtQu+ z@@E$Iq6!q7y~egdU(~<6O**la{$`${AD%E27l8~jwIkj;@*sbncGzKG*zQ&^5^+cN zuIbiA@GB%2igAXh+Hyp*p!v* z*s|EMDbtk;)u!Ugh=C4o9LC=mw-=`95P7o3lZOsNsBYtUtjJ$6e^~@+lJH2w%xX#O zt9kA;88TkDJt6vy!Fy20_a;ES(ejhK6(%>m(2IIc=!uX1&o=RBguO+qA(Gjz%Xnqa z37%;DzVJiAy$ei52+85PGmb>a)n&P8ew-Od795s@7=VrlcV4)_{dsAeisBRL*!3Dk zRb^3B?ev8#hpLPjzarlAXMW$GV-9~@i<08Zdp-4uyU(*E!aBO=S1(5YCTUdP7G1mq z$WjV&WuC`#(-03n)c=J3i#E6W#rXA4%6qch(jUb77fJ!$(nOFgbTb+g4ABjLty+G#kP+VSI5jM)j zQ|GCFrO{@Dva0oS-ci1Qy1PyyKs=I2k*b~kE5Q&0Qiue7@URY=@mFrYhP4LLD{NfR zEJBD|QwbEH*B(rzq3RnVco6a+b{GP~&8sdfHE|C=@ObyQngC z2pD)2ZruzGcxi28jP7r3Fi!2+##K6A89>48lVOQrm_pBT94B5T&azD~#!3|mIWEpG zRjILCXY^L!Q)0)*zON}R#C@E|$W!tvJVYyt+KH^Hl)Ii_>ZRY$L3N%4Yt7d7h;|az65`0oG5~hjoG}@j14cj`{a=!Kp1h*B=qs9hjpZaW|j6FVyNmUg2xU|Al_8z7# zMh`}9wXG>ZVg=!1*@8r&q9-}lVVbTgpVC>LdEdk-^8^%H(xIfDfBsdeMlV+WOQiI% z9KDeck4%vc<+9q=c*8o%$7uEfR;(r%y$oij`9x9JPGQCHqj>ggEYnz-HchLsLoBz} zZ@FiL20;l8tKUs)Yg>>G*kiCfQ{+#Wrfn}3O<^Cusfnvk7djS~_P^Q5F87e2&g`bi z?8Po$?`tpX4HRAts4S~(63Ah#AXW<#abLy7z%k-Qu{74ib^s!g*9?pmAdP~cU@Hv} z0UMjy*WFZy_cPx-y0gy5r*s6P$4!M`Gx#cR9wZV}hBaU@nw)JtJeOb9mgB@mq_;Ht z^BIZ0IMP--K0Yf3;fx)bX%qzg{%^Zsw#89JPJ+6tal(i1y0hl9Ala`AUuN>zmdspQ zaa)vVSC<-T47k@llV`{%KEP5Cb-1BYXW&2yi1+hPKuA6G@wvr4Ay53(waby-#6OF@o|$aqyR&l4#x>n5B{)!aKL=Bh9E|$$ zqiz|BEiSOnn%0e}_r{mhRm}5RGj}-M4IzyUA#8a04GI#sbMU#`*Fz1W-Y1@eQKG1H zxkzdtVYB^Fca0GKEv@pP#^Pm-(keC2@B}4(T(40fX{aJJ@0o-R7dn$EqYgSXu)281 zrCC!^f->s}_x9nrPGX){g&dU`dU=zVh^19Th8B)aSoDdvu2Zo!M$guf8`=U1C$kQfKnAs@;ZZ`jw){ zXXwp6wZ(in>G}-6lXZM(jvHO9*zj{r@cjfLA0tiex5q7&aRz2QlJeJ|b+mY$Y^3aX z)~w4_8+_U0t4^n~Tu3#m_smWCdyaH?l9G9b7MvnU%~#%9pi1w(`;W!F;b&kj z>`#LO(0}_^#>QfT{G8H@^Iu{W=gTk6%Nv=~6RX$+jP05j5L#?THy9IE7LuaVPa3aC z+3r{sb0>7Co^4mhxOR zppcj3iSNwYfz7D&g{d3bB|B4?SP8uN?J|?jJ0JabXZamgGk&$n)ysPoT#G0MuLicU z(j5=6UV_{Z^KeV@GDtJr!p-Nnab*JT@MZjRhDZhno8V4saCwfFZ0#L6r& zK}~f@bNAVN8&yA%ze3Ue78Ox)WVl?X*9PXS^+^6w2KcQAh|G6Tgg47w+oA4CN8NF` zZH%kh?l5$P@heEPD0s+#Cry?-kNIW6Jnyb0=J`G{_H4BzfJi`da}tal!E-+sqwTJJ zSUxd%u&DBLozZ{a!DMjEayJh!pTuT{A9i-J^%)fU^4j>Ze;#O!YK3dSdUsIHx{*ZJ zUmx{bbwZxXQU8-LN_+dEMMfEztL0hJrgXIVUrd*ZE-3xh62>%b@Bx*)%=Bf zK1X<1Q$w?PX#3&#MqHSzHlPk#3y>CRE%qO%ZP@m>`|r7g(qT0#m1TwKo=$6 ziy<*&?EaQD;)y0HQf0={fYz)q17W%TM6-V`HF1; zeH8>t-7=ph>3+PLM1wEva1bv_?F-yaIXKPO@8=9*e~3RAEfhOAVN|HO3v(5m=t$hn6&W>YjgxO1fWWX{rlO6va ztoMfbt^J~>n+VzGZm>EHFL{|P?ZjM>vzf%%Gi*&^si%U@xZm2nP`ORYiA31@9X6vo zqvY_nc|@i3@O+PuW$$i2^b;c|$qh5yO~ZfwnP2;3-vQmsIfaFH-9?UcArriyhP>U2 z8FC=cTXhkkG-^E8Iz2u%y%3ZC=nZ)`G@TsF-pdeb?Cjyz0&@~=^~%T`=N{RNaWJsq zP4W~j->e@A>}YYjm;tZ7d;#N```vqf1?K33`?*@hmNE!f^HBC!gw`u#LK4sA*cN64 zD_|4v2`V$3ih@F(^O*;H`e>@5qh=ZS2Zt{jVw(8o^Dz3AqWz6cjZBO!bI1+afQ;E3 z`t#{L(^AgLB-Z-#lO@LNyZ&0Ldx?FQN_~Se4z{c)$4z@jGavZMYN&5{bA5e4qg!00 zwqhwchTZyH3M)~g!i&R$B+RQ*5>CFj^SQ7Sf_DUce2F8OP2QWf4`vLC97av^`+Qe# zN7Jx$)~a7N9Q@vNDDQnrj>}=vBt&|iA&a)nVG=Wf>X1PMCYtGZ^RT_|8{Kag2|frT z*3x`UvgV`1D+e!8HM*8InzmkIKKvv-S-YAP?K|`Acb=D(Y!`N#-5`?kaWRSz4J|i$ za$32DkuAmHL(s$OgU^xQ@(@$2yPa6bja{n9_^j;hvea2RlgiJ zN9fQ?SbfgaF)=JQghS9&4Wix`(<83=a@Qm6H5`>0Pd_&xaAOwUf?G-c8>OK(W424z*Q14jbYX%N!R_` zXP~=1Sp?Pl>68>XBqBvC%6h}b+pu$2JF9v~RVSgh4Tmv#TqHfB`D`!0>GokPD|9J0 zVB=M*8wuwND*jQU)t7v$*)sL9Skp_Ri;Aj-0*V}l?bp2yl};v&JUbWPYF^f{o3K$H z9JOz6i`%ATcDQf)Enj)&>*J{@M|WUVffJS_OApq-T)imH%2(LWN$eq~QPfh=(_q!Z z<4G*cS`#c3lyotXc}wJsVO-@_CQTbl#OM)Qn-@Ezar^uhA=|j8;Xg;SG6b9!8r^pZ zIGa57Jxdpvr*+IN2^Ev=1?Fl6bOgp+T;~HWSAJh%SzZ%u2A0n0!t`gG{1RXK%`0HT zE{9#()%;zKa7mnb?u2T(?(WR<1FLR=9pp(4ne)uR>vKzzxpi(whdo0&^$BfRFF5jL z(ONii6#|Kn<}Rg$1h!m)#@Obv$TWYWE-5fbwS8dyj}~y^g_xpz4o~M0`;2T8NZuwF zN~c21vM>dUdO36zEc)_K$mzHfdv@UxVoBmpyzGx?@!TKd)SF(kcZn% z{!s@PX)F{1acaNn%@P)97&G>Fxv|EEgh6o7LU~Cl+fVjXn>3tqgoBPo9q+SMzn_M= zMg(d8Ohf_yd4nH2x9bK6rf+?RCe*MhE^pW2bGoya#E+tF}-t6W^S)wfI zvYY%o_E*v+x|5oZ`;OawFoIT;PmBIhI9kjF-NIk&5qv$Jtjp1fbb zYg@eWT7xGnfUo(10LF{S#hZqh+i%upcIyki>!wFPvJ9kt93d1~!Y1y!oLUAd?IEKkVq&xFub_dP zf}P2?EfOl?eB3rajOWF~hst=!_SVd01lLhMFD2G>bIX^8nUW|%YEdmJn%UA9o!X`p z1Ge_^RG|v3#)R78nJlzH)wj`DSa7-CaM1>a64@X);)EdF-lTV$Py_r!$9o}L zYQHpMxUJ`@`-clY|Hj=CVxZy=Q<4ZTV;0!-FmQRb?R;SPVqjfcyE+9q_)ZgovE;o` zDDueo$9HqZSEI{|-$f+3xW^iRNt_^0WAAstr6u+NB#>3f7k6opkbk$j%7Zk6}em%JEw znft}HNoq&Tzvm#Mj%!gwhbva5sv5%%wD{7M%&}`YftbYNEI$b`D0A=2-F_Qa<)js;$**`hRLes1PoK+g~1!2 zw`-s0hw*j8#N7GRcz*B`UX~@K;UwTaGS3YgWMX%TRD=PnG z!%D>3Dp@a<@B^aJ)Z2kMeVu^4o-#)ACtRbxovDj^#FFTc&R>SX3f9O!J-^0$!y#>9 z5r*zB_v65g+%!ltMfMC$N;= zRSKpMkoGpL+-bSmkB4AMES-kFgIbN)oObYER zicH!^nXVt5`BXwpiySh)J)$azMwyaf`m}d}I(%WDQa)a4xe~#fUw7EylUeES*JYrf zCr!c1Q4x{yV-B{SA|hQt%6lz3Q-)M?aFW6(#zE<6_cCu;e}sN8lruM|HXL{sbH2ss ztyKG=rmFQfFAp=US5%wzEOpcMibKLQcTt4F^viuS3lL`)Bk)-tJ3$?I_r0!#b%P*$ zC@Q9ayz%S)ShV}p#Cx8$*?}&jCmvp#c2d*}9=a~iet2elM^ZHlt{D(%17zqe?5ZV= z0^xUyPYnb|%*|NBi+K$)F(z_JQn_9z*eqSDH&)R6gj0~jB?!|zuW}?Xag-ZD#S+2n zPoaF&@T?UZ3sqJ#!zfb*+!CCnHX)7Ci=cjo6N2mlNE%-oP;A#o(`_na~TlJKNV43}6uC>yNx&(x1 z?yF9Zh>9FnysIV0y0Dmf#3%cpBKVJ-i2&!%Oc+A4KFY%hwD?h%f^9y^gu_Xs`_91% z>Z>r903Sy)(l|=<_dZG;y_R$YQhU)dt%j+H-bC^2q7cn2sCzXc6f11VcgF@f93`tQ zM+aTkjy5=6Z(@7ENn(IJ4Y*f?`RBXs*K+ehKM-;9kRK*x1wY1)5{$ zEJYEYk7KAG1|nn;=QJ!6uN3apWp_-G$W-#pd($b0R3tMF+x=$pNDM2Z0nML=3(ZG4 zbjVWCCCijUEV??6l*WjHuuP|_h~RSG&WV`W)LIVraoK0HT$Q`($*6pmhzB-^nNVzg z46?ODy%&wfyp%3I8Q7h9!C>Z^EC91mYB9RbmBsfT^Co6>i-kR@fJ#I7LjKMsV;i2% zOc!DQLBgm~qO8%FFhqh{($wisI>xN+j-&~}3aS3qQ?UMNRnq0dT6Acv9JXPl59XZd z>V zCX9^ujwGVFm%S43QU2fa*Rbq@rx*Viba+qk+OU!|;}g(ukv23nKK)!X=kR0^hv+0B zG-T9d-FKw1bI5}<`0tXtw#VOBKRM}ZjhuOXvqM4RLL7pC1a-)k1R-HOZg~cBHkAWk zD?6xhIbIGY%R$PZbrllE9w6QusMNTTszGlLD&Y z;@;!22xj}-E22ER&XKd_S9$4ed!-3OsscqKlOw%8xi3^p?%4`izG($iQf+-y7rRqc z2Bi;eM;mK$7J#b1^>T5CrZxJh-{>5vtyNx0x?ZEaw)flnJomx3dbfoKrkJZn&nM*w zzTV;LQ|A{f9tXvg>;5{QB4e)qFyHVxk1BoT&u;cFr77**+GmQ-jCIN5tg6Zli&>fs z9nJJ2T!*%=uHKfgvkkZ?@shq7Ao;hv)?fenr@TfKy86d+o^D<8{VaNejWg)ev;L5BvlP59cc_nx-#io0lt9VSI?(xwvrM8D}^uWGE%XBhU9P z>TsCY^5bIQC(QO%GQR8i)^>$KIa&5?Dm69W@#v7rdtFKjY3bW+K?r#wLG?4sU+baw z)-uF>)t^(g1Lnzu?y)-qei$CA63M-5*Q{lm&g)J|;Aj2{br`iRla9Zv(AjATe~RAi zk9g#(9E)>1CsE~2;DB*l@pJ7&P~OKgcmhKng$v;MV1~q(@lnp?n*~gmL>X0)a>?}~ zxcCMxUiTSvlm$Mn594^I2#b?_`;_z4!F}oR!YE~FJo^;KJ_xr@XoJ<}Sn9*Ce_@iK zioH^2N{++zY$?|z6vPJvhvc4Q{%qegB?cHt1>_SRnJgOpEAzQto9NUT(1)HsE`lO% zyV0Jtu5}HB8SJ@^A`>lj&FUpnsBKGgLq{u`o{=qhD1@%Xv%Nj<>;h^v%1_Hiu1ZxQ zZ!H)mzk5@FbYYgJiC23Pf2iqLz6O~jN+4azOA&~Zqo0q02RJK-40%XW#-fJaG%Q=^(L4x~ zoi&V_Ft|SQ?vU6lZ}js<+CSN&n#P1PptPKB8UcHFPw&ZUpV(D#VYZLJ!{s_v`{kmu zf4RQNA>_y5EoPiTK=HJQ@7Rs_*?pWy#(?Eg;Cg+>{pmwzc!>}8Qzsez0!XLDA?s1& zeYD9HA6_@!#l`(bjLEEs+r1i=z~q}I{e2G@xq(5)b=<>{fBU#S=wXrS9+7jSwtoF2 z>U)e~Za+h!B+bRxeb0jyOS13cXwdDX_*RH@yBfurA4-VN$R+smE>|Tf$rtFya3z^l z-}v0K>z3?^_U)tFri;LvqYnKNEp5nXd=wLrLbyUWJwnb2tjXp3ipdYTBw{CAEIKeB9=ZUWhho9b$OPyS5jb*+zJrSkA)xTxmMYqP!mbU_)o zVL`Q>Fd(k&Q177HwqFi4F%b81U1{Csx>85CWF;Cp=$*I`JEq}CD{J)6&re+ddv;Pl;o zs3Z}c*+@KWvvf!t+_aCd#y!6nEGeYKzAm@6DA#bNJUZoLln;ImdG@CV3~IPI{xRRn zpe`Huu&R=4=+N-Vt;rwH<(W1*>~dOJd*DE9p~?TCS-Ym8HgDtf&Z%Yj;8Btx&*@&9 zgtiE4505OL*6o|lUf6WmAP*pTHgEa;cuPp60m$r2Y)HvoEc!5iNIj@$9t zoPNt1lZTed{hLSS5`)p0e=8FXxsQ^d&@uz&Qx zkofkx&FPeB_veT=aY?jilpl#IxmX*;L$xS1G(F%#UmuGGW@-*nINq zqCS3f!1+iHq&ZoQuDN#5L76+N9-HQ72!g*JRT zIz+7+xHCcs?_NsCWLfbYxpYUtbxVeVDGn>0vP8v%?rVcC82yP9H|^`&kB^re-x1ip zpmNM?>I@~%9^LS%(a481wn8pea9?4#r^klpPaAvPE~IwwQQ%(I_Y7X2|5!qPN3e4t zbX&FoC0f3kcQd&eGyzIU{m8+3sOUR_=W!8jDj8H6RPD!$+a2eIAtr`wFm#8AUC`?(jJ+(M}e=GQc;wH*0B zeLBAo$v#eqaP(&a^p7S;W3xxWqHP^{+eP;e3c(Nw8%C9s&ECqx&u%U zQ1*9$E7ogPeMhE(eg0mM7=d$|UR(`)(+HrS(^_M~N76?5BxAS}z+wPS=>2(HrVd1s z0wDu1=Hv29i_tR(q^3=?-iauTq^F`^GHNntF!#kw_+zD#=IF>n2b4u+fdQTj>#Vy z7p{AKjycnG;NPue?X|no&F#s%-Iimf=@q`4bZ-17{G0X~SHEekUJ>t3 zcSS(;tU_{nta4Bx-0@36I(|VWOuJmxDOt>*T6Zga{aOwcrm$(RJ@9s4k>{g6W4)B%6u2K`c9m|jBqmcLX9)k=gCXF3P;9T)c7Pc^gZ=m?u>_oA8CDnj`@#4GUF zwA53W9SJ3!=EX@Ra<4gE*(B!aX&}vy`4I~wltV{@SURLI*}j4eH!zR9B(5#Fxahm9 zT~&PoYWnJO-I9$DZ~RUPMGExZ6mxRkd{i2Q#L9$f^MbwNhP)7wu!68+%AUHp;y^l)H=ZMyM-J2)NesA05SOILQ`vc5 z0DqBmwqVA*657O|f83)wzy%kdOhG0Vw6$*#fw=(lNTUU^ zm#x1;255>wQ?&<^$#S#@Nj~&N-h>*bN~1+NHu;;G`PA#1WGXR`+i>v3;R%&9Ja(t7 zo>{s2l>dw+jT0C%0`q?|=!@J4`(($EVWg{HK1JNSell}4bg<>R?jjB{kc3?pYnWt{ z@&1NGIE}N&+^WWA!@A9Z-V`cu<4H+AUnFs`RjT&V1Zv0S=K9zqeJeoRtb|~{&4z+wW?A?l7#7L)u{X;~oy<^ z6HEcH8U+ZXYC&*5=c7!xdG#{YR7%Rt;^9}Ae9%Ip0((W-$!AjBn`DLhNvBhXnAaW4 zN1iGhUpOkAL7(uvkKjE3x^%5??+zV3KbeTmbjDI-Z8MHE8E0eTprm?nqF7bzeH zSTcq{esA)^UV+DlJ$`d}kt{&`v-goE_#O1KtfM@yzWcdPUy(uV=yJJvt?eT#ux^F; z#mR8sl<@iaz`0vUb2ypbcb5SQTr|%oW;36j`>h9CbS!-RE>6g&i@YK7{v6ME$5S5# z-QQB!G5EQp4PDuyJwEj@x1&HDI==Vplzsn@cDlmWlC0Rt5P(RWjQ`}3blj|+E^q13 zw4k-n(n@WGU_}vq)aWuT6&z4bU%Luv-y3NRkmq#9;W+*-O zqLc?V_lH=Ml<%(|j$!#4#n6M>j)x1XcO*0W^|GO_wE$K^Z$@Vs(zlF)4OfwVq-?n} z;b(2%p^m~tVa@IikSC|#1__0Bg!zXwM3p7V&2d&P#^2Y-`$JhT&jwqAf0-BmvZFK# z+Mb+0X}dpb>3}cj0LTlq>$O&md2!~*rX11~Nh;Mslk45CP2JRDvIDf^Tp*|?;Oy5m zLk`_b4E!$0{Lf#jhokjg?Jqy4(zmGmxfF^6oFfdN9+F} z?$gxD-(voa5RVxTtIS&_l$zwhWaAc(gMq?EOM4Fvao0PW%G){(Bf(FB)gCN}`|Ky5 z45_61OCu3O+gLvaBYnXo7;kveADDm}nfEzY-Y}Ekx9D*_ma3ylFCn!yLdtGgy~i9@ zR26vrWZYjoT_tyt9=y6TP(ViQO%=yZykUiWPSXkgy6*YQkHw=5gM%R+fnRygHPk9Y z>GC&bPF%pMfyu+TtoW!~=2+0(D@vi`sx5EvcjH_YI0ggO5Z*L!vEkiPL;L;jN`Il8 zp|;Bd=TChWV!K|K$B6O~&mkuZ_Qe`^kw<}%Xlcpz@Z{8>Zbmz3!{|~6T)Q4J`#z%! z&w^a_@K4%a&h1Vl35R%fUCq1o5$IJ%EHBAnD?ELlGIK+gCcb2YC}|v)9Fz!eU+nSZJe_C=&v?C)?I?E0&b>LO?=_` zR3#0a8znJ{eiuja&*i}6I{WqIZVgULx8e^Y59wo@eTf4<2j@@jr4lG6932G6;hWQ; zp?Bifp$~0Z9C)Z*oA41jM^qlT#r%Gq_DqBKyM3USjJ2td58Y=6`a;8LE0AefXPC?Q zHt)1T`{g#!3?AH6D4gWb*pJsy~kiXmVtxNaOEFp4j}KxWBGq#<6^|eC@Ojp|Fdqog&wv8}hrfUX z^e?m+`1u!*Z1GzpX4PN|Acc5;1Z05!xrBeh0DvSogmnpkL|d^q#Er(2XpeIy{b%g} zEdZ8nuU0cx?D9ts9JrWwZ*~16(;oXlM3g9LM|PGgRT`qQXYcUC&Fu+U+q&5VK#hBDZguue{?&%Ls2y)L z*Z-JKdRakI{d0>B!n>%=s~XwAOTxh5e1%wDuuP==GHPs~?fao7$7$b z&a$dptHL_ju)dwXt8w%6N&VF~Y`{RGet9u(W2pgAR{!(geoaJS^;NuNuI*_`fv}el zxpfnBSGAwd_irON+)(W77O5h7Mb=*Kj0Ce*Wy`M?OI6JOWISQ#l9wulkHmVT%xM=?9N5pGF8h-(^R-_ zJ5)+DOAJuTq7s=cd32(Mz!vQUFz_#F_UmrcB`5!*1(2a&mjS&NiIpYDv>>g7w>wxt ztdAj)ej^w%f&Bm?){m7!3ZsWSQ%p9jwsj7C-^W+V1mCpE`7A#NnXnzVk2pB|+ps|# zk91+An(p8h*P}KPTppS@g1o?#G-~FDiJ$VYsoHiNO|dD$nX2&t|E~*$E@hfzZQ7V?%Zs2q-h*0?a{8tCcl|u;*~rF zI*R!yKtdb;t4;kMG!#Z5x+$@Sh7*!=^0l__?;aP;7>>Wz5&b50$haqe$4BK;i{7cp z|Ia@G&7sd%?W2jP|91%d6#$z5x-=0V_coA3)c^?p%OHsVqD>$N@cuS1GF5;yfJbor|4GFbxXqfw zq-cg@Uy*h4h;Rf#7MZsi+>H?CsQ0pvH1TcHCcJ#+WcBMZ-N4f!h!TT=kuIQ`vo!r2 z?DcH%S=g_$sC`)%8_Ah`O-n@~CJlJM3`S%crlUqu79a`%xf(_*p5@LtM|GbJVl5Ja z*>UNuADb04QWC)Q{}CHF(g3{l+yd`#VYbOFMwRVRV8N2driaAt|LC2=N&|ad+-La- zEK|hEIAqvh<@3mY+JU$x&x?4etG|o%6TWb2=cd4J{E*68?+qSs1O(S=T^4~j+`~oh zJm>)%TRB@rS&y}%Eo^WLlsc?Z>Uwbiy;^?fVdim)9X$kCx3B>+@<^r%sDM92y1K>= zlrN0iJrCNbZqYN89_<}Yi{3GjzBj@ba(hMWY#y+3I2?Wvbmo?Cew9zvdi+QH*2UE- zV0#oNL1?f({B?&(`CD=Leoi?jd)w9gSo>9j$?8*^!bSSxJhhTzQ>H|kocq2U1K4b9 zzA+wt;7V~i-0Ed&+$%-by}w)b z-wMrP(4;RcG<(;{>EEiKA)`)pf63W)otO%kEe<_kPmATM4VAs4jSLLu+k!3vsIEC@ z1R$4vc%nBkoDW@inBlaXIZ6zVMk+{E>o37s01)LY3VCG*`ERv}KLB8ODD)lW>IsHH zrz!16$La7Mr7$%##WUzFBIb7gUhm!JJiKAa zKhy!Wf$;b{mPN)lqTamTWQQXzz`oaZe@BwNJqa!0o|q+=@5O?}t^=O1hL1jL*9Mgx z*&~*BbHGE#{*PkyeyVhE)?0*(QXnqq`WIe~|Mg<)NY7iEN0k$>xxogy`s_L-V?paj;`gjM+C)}I ztD|@w7b2?HOTO#V8-R2Q5__pu&Ka=o-0`QhfRqOc#Uz@$nNI)@<;g48Ly;Z#qK_{!OMaeJ z@s#%q{@jZpYv&b>es$M%rt@-a5<4JPmQ%o(A2(JRHaOLok3H|(D(47Gb<ZiBs8wFgF?ILtN?};&Z|>%O~vsdE1ujw}<|29C@5ONs11hm)E6EO|zOa z1?6n=yqqYe4>9@)6g4H>Mgr8hTz z$qMC=D*wiYQ{5mE^BTLQUk6ej?u^G)0oMcw+ONdubjpYHpuNKESsnrq)MwHIQWm1I!y(y!^)?l^b>81-Bi6X6i$5o@tYG7-GHOGpv8O*!NYk@el;G4-{ z?N!F@sL1R{zbxy_A1KwakxenI`?mzKj69?nd~vv!;t9`B!0Su)b{>gi?{RNJl|Ykr zuAk>=7~Pc$6^PUu@CmD0~)DtO7y?tThmH0|D+GtA5LuR{2oBi(c0AP#;a@A4e1}8wo|WeTT+2MEY}HC!dL0^Rcxh$q5%w7 z@(`bn0C`Gq2Zlr$mzY%8r(1KQh3J@}gy{Et4c7bYePK}UtaMZ_$diGGu;vL(F#B5D zNa2u~AM$J@LT$(=nEX-kR5ioz=+4VCata!YcrZ#*tg;Om6&mQCVeS*qP{qav#L3vF z4cve^MQ_|pVHtwnE6%a}1VKpm0o6WZ;i;NdusC^CH0FW4zMpF|&>)32HBqNz;1EAL z0p5USr(eW{4st^c(o^QR-%h$Vrpfx44+EV$z?!gze43uiRjPgLGZOVtW1IhFyMOvv zYk@zw>e216TWw}c_>sMT_M@u7b1Z`D0akZ{44LO(G4H_H|9UlovyTFP+r%AYNdl8l zU}JDQ{Z6wokbGrE7ylT;4luo>c@A!+0$>1KbCqTXsO~kuuNGIoUX~&k3{n;qN1y$! z@`gad)L0W2r2CH(V~efQj!vonMSv=e)}*8O0>XelWw7LyM}=GWKTa50@-z5)v1>b# zf0``C^jVX7%80Tt;a}gt-w;G|Th46^2y5)u{oXZc?&`g&TQ|%AEkFtpEbnY(z&40l zCP54W;lr64L@A>%KeXw;Hg{q;J$`5OuNP#CxzcWuH~sR51Y;t!&Pzl8`e$q3DY<0ix3(R zX$eVYpk;r=2LD{hT=VF|pnispX1~xv)>VUFB2R~ZW2nJ(xlZ=Z-5 zgDJGDcuIbQf1u2DD%3IX&)*4AoqmRDMce+YXOLf28`%JLqKqMd(1%AXDUB@Pn;QR;au`~R~gMmY=0r*n#6jS@v9uLZB!P*}hsS_<=TT(+uRjHp#yCmo5teN_w%(ta@RrEp)Sblu=XyuF_T%1~%JldL z`!`!b0m;!z3j1%=|9t zv}k5`(ZH{Sxaf8`L0I|AqgR8gO4!$2FlgJm(G<6_rEd9%!1>GI8kyCJjsC6&37wgv7)ySzUhPW$ps1-2m3`gSl?#d)w25A`m)3iCnS^Q6@L zFgq9iTkNCJ{ANhrdJ&(}EGGKbVV>pch)3UB;f0QZ^?LzE@>B3L!vXclyiu?#LyeN3 zM)(V|NUrNr`b1qt^G^ms@jFRDWwZa9qCx`^kax0?=B+2y$g*`?1-M5ZyK!8eha+s) z#*GHLl9<1rS^-2%Gf)zNTO5o67%xRDz?~P21iIZ2M;X$zWU-GUU#b+WedSk4=V6*3 ze~$2elQ$aak{8KZRCx>1%|#YnXQn6r9kAqG&GF5M_IbZe6y!SnDBL1^>r z#**f6RN9?MYBrN`kpnt8m!G3Ip`)g1JDm76St9?8B3dxZ=8{e*UhXEda!Ntbj`e@e zGu-G5?RmPULiyT;?c3!ObVlUBmE{ElPAEXNwqkIu3prFky~im^1dYdH)RO#;?^<~f zV_Ie}NuY8n#X_QLssmETc`7TRZqK*wS~~vg9j|VvxzhKp{Zp%OJ2@#Y)4${9ZF-*!y{y|*)B(nmoB09GJ10{^x-az0_@qZxDtM-*+|?}^zkG*p zo*iU8$!1vlswp7~`}Aa4KzdVj6BrAySBaU{g1z`++4)~@)up~#en@7q{K{;<=g+w0 zVL$gxkOAY16h*O1D8jWiOEztG`EG;MaD|_erKE&IP-m8IyWXxY;O0-Lwc<%raIDrh z*(hB07O~@xo1;3f%fR{L@}Yg$!IpXlO!+6O11!3hTP^PU z7Jpt1_n2f*AKSa%v^a2JbG zP^)t=oeh2_L-~wY12{##41@Jvws_ntbil+G{QAep4pRY4e6$?s0KD@RCFf+o&=uPx zLUESShn9E}<+lmE5h2!i2jZj|z#oH@W9cyDsTWQQip_5RX$n{!99<+gqy)M<70x+h z*2zvSS1YzT{ZovU^xPe-^?4u>IEIyc;gmQgJnsG%qxhd{6PAr+;C(kN#^_HQkZIyn z-$l=e8a)4vyqrtfoC>JD>}h3Ef=HwU0Z^L{Q~9(UNU zqAdnPjb5fY2;k-TZf%Rf=Yrmy3~okVI8~N8_gVn2H8ErWuR8GKc|Obq!3|6U@(WZ3DFX2tc+}NC%${+tY#nN5LM+U2tQn6b@X+uDg)o>=Dy0bjzt4l6yKbeZfmCltwfd9*}o4O>yPD_u=c zrTu7eM^o;kp$lSRHvpxQpQG7~8jsT_TG&~6_*f+?PW`!Nh`EHxUl~^GGJi%EhB@Q_W8)o6W5Pym6+s_WAHiQKv5Mmf&m;&baJW+u2v7jZk)uV@pj#~|m94x_C zFYh@zYkn-*{xC19Ql;)gsf{<&_w$nOAyueOxJF60Jl~=dur5%0_L|Rb-+;)8N3xYx zu0Umazf>*rh2PZ0B2|t9e#`ra7f!_O_O_Bt45&lB->ov1V3Gq}SvQsU8-38_D?ZMv zfw;g~FyA15v)eXmGP*4EG29xkgpP-*}#TV z8&&6IefXH7Z>N}ULF`^0L3ltRD?Rty`;xaeo~>I|Mw`+wu{b?Z<; zNor-OG05vOM^pT0bL-vSqbQ5~g^8RyWNtcLcHk%aUVZR#!tim}j>^g5Smv>0>YzVi z$r>p=f7Qut#t|!>*B!cucMae0csnkb=@Pz^{(Wr6?stvP6N`VGc z+fl#Pu;plpy>CRlql)mOlD2iuI5Qvoo2I3IfQ`uXV^1IGxs32bABYkuHBY_T;H*H6 zUO-Pa(nrJYH{X>Lf7|BkI2$q>;|kB?I3Ind0VQ%q@GT8Soo&X0<@|4`!6L?Nf{4d( zgN`#AHr_By_3Gc>t~85w)?2r=J=xm* zw?|XId~2b(`Pr^p!`Lh-9G};v=APO=*Wl_pqnFu_@LOo<;D1csm~Gn;z;**!&}KhU zk0rls=E;CcSd)$OXHM293kMGHwpZbTD?5?~s1JcGjZJ-W+>#fe-pPpA#zc1 zYqw_dO%#w|K8bc?kI@$qG}G&}jF>EROF_Hx^3}*_-Rw&srtFnK2Zf2=s`-kh?lFv*2a!m?iqv0ylz(lE(P-HeOJrQMXNFU z)~9L34+%?}ud$}c-?90Ne#&vFW)mZJO)qg`Bf+=UG45L8?GL%dwo#hWcZmRVUhsb2 z%Xqe-ywIkq!HHJopjnAVwKwJHjqBLg7H+!BiSZ6#eb%)HiI0&V$n~~PcMP38*f+t2 zTEf4x3LyOd!tU5A*vADe;k`EPj&$9m-F7eB4E&O zarh>HMjSq`oQjweIVYmAb`N8{J2%Sw*VRJ;9by_Y?O`%^ds1wt`0G!WLc*W+hb=lw z#mMuUQ$sB^XhCBy8nzP1yWbE{rEm1Iw7YLKPYL|Gj;$hQKZ@+Pm z+!@SUKM~)v z0V^2jd$XM~h#s~=Anx14>;nRXT|UFtVumVHl?ccWq`kYMLW7~2GQx!vn@!XJ6NQkQ zwSt723Z$yg#r)k{H7XHh#qw*?aO3mbROUp1yFEW)=Nz;Rl}VtNLP5zdW#i`=zF)v0 zdAnOYG7ijhv||F1c^OSo7B^%gox4hMU69(VKDg$5Edu zM>r?a`3-tsX&fK3RFPNW@758l*dY?$~@iP<$C!;63ltPg#foIu1V!{d2RoT-%^>`%u%UomY*IaL2DqmCx+R zU0D93QS}~qm|#2CMrPT;QjdYI|Sw!e`r{S zE(*|wC>fG_;4%U?{*n{Hzr!V?D zD<&9OI{8X%KRaJnxLl$QK)JIuhD$N_r9%oOo=Q6hlqG*b*;JU@AlVVi^hoGpPK>7^ zG3+rcR;K>4=B5mKf=O7`Jqo-1))DR=XaoqIj5u=+Ya?5DzSVl@hPG46bru9GGrRcR zz<<=sB6|1KsqL&^wtT!lme1!KMaQO20;Ui9&JgH>x1JuQC8H$kK0S6}!b4d58xN!N z+zCtt?MfFTCx5rc?=$OnIA39odd0R~($t~URs6=jf~zh)!bTE37sF%W#-)whOs!zS zeX)?ru)HSa zT*`oPDJ=QgMcEep8V;B?FLJ?I00F+sQg6_5NmZRwArZY2#YV_7@beVRKh@R+hfD!# zpT-+!zPQ7AjpObb=>Co49!5O*0ja8Al1sXrDw`9-G%s5GaaxJaq@E z%SVrK#RY@GE_#W9Qkfs5sC49lDtM;lS*^#h=y$CDUqI=z@iF{vCoZ_{;XvpYdEMM= z^kK1?uq8nsaFH5xt2KyCo(I<{>SW|44>}_s#`2O`pz-oYXQRk>YIkRD-(mpK0zhgu z$7Fo_a~W1C5QKdXrF2%%72H87J?bSe2H%s6Bxi;nw&GK;ddDr`J%*=&UDt^)isJ7pZdMZ*=q z1sB+B)JLo!&4_}<3Z9uyE=;7^4bkfGn>Fy~#e_v^qpQh53rblS+4~B))^b=yoaimN z)G+~Hh%p0c{0NpT?@b(C`=a_W$8!jges4%BTf^R&&3oq>rWuXv_ZeBNEL<`D5{iS7{O;1-GVnc`$v}GWNH7kKg~>RhY`Ea;i%As=Oj*gqnEcs<}~RU zXw%+5d_Bn>P#A42H0nrnQAxqhkdM_@OFGf6w>yU;gzR3EmS0^Fjs0>lxxXboBaYwV z&yiu*n_MA+=oxrhjOv2+j74UR>iug%bU8B2^UdT516lwG)~0!jDCoHFfn$$`TMusU zFa}*NT5Y}nkx_sD?Oxa?`npCVc6rI6YZpv-ZB|0N= zFN}$Wl5pw=qM%@_1B`$3Lexguf3OPib`X5=Ahq&+i@8c4yQh#U(z~#2h#}2U$-u+# zzV-br+Et`MOnW#B9o@+ZiTu?8&}p*JJerJjHVHnU-gHU>aEH$HnuW<5BY4z*0c;f) Aga7~l literal 0 HcmV?d00001 diff --git a/tests/e2e/visual.spec.ts-snapshots/compare-graph-mobile-chromium-win32.png b/tests/e2e/visual.spec.ts-snapshots/compare-graph-mobile-chromium-win32.png new file mode 100644 index 0000000000000000000000000000000000000000..b3b1a6c30081d612e4435a7af0d72b1d73b940d6 GIT binary patch literal 14820 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

e*<#wa@sg<6$c<~p5c7UmGF`OSbCY3m2 zL0f&x=ZZ%h`hW3y^_xbO&U*a9e+p3fN;bm?N5Fn#Yu<-T>-t-M??)$o|F%?QD6(H^ z4sa<`|1wGZ=BBr;u4w}T*PC0<)Ei&=M`?fxba|`Z)kcL#n{~gO#85%-m9fe!l@1P@ zJ)U=&n4^io-e~gyi3G>wr%oT+m~SrAS2_au{_hZ}Wc+k~eDW1W3pyCMI(q7~@iIH| z*~yB#?)eqd@AQ7%x!R~pij@W4P>L{8gv6z;?#Il5_+??j>ktcvsrPfcqKdK_KbmTl zX%`$Q{4|o#LDyNdySY7+1=lB6t&UPFngL`|xng$^E2~k}`)-*Zj&0Mru(`Gxgt4>f zwUx5d@MgFc`BjoCjl-z@=8t1r;NbE3GJV`B<^xE_o2?(VdAqryIdk1P@WL{`Zi>(f zD!tkB;svI*JY>CI*UGQE%+aPwpp<$McyR~m&h?cMp@Q?Nlc#X5R3Q}X4$a?qmzTKty^VWq|(ocph)FCDLy_5@{sf9l4APk6GW5-9bWN>%;NQknU6!)nd zgUVxzPMI6J6kggMR+^G(%Qj~ju-{6t5zM9l&)lpZ3?!%(cW>&|W==P2Ujj}|b8L0L z0qps``^<2D61YS-EZHLt@NWxDolb{}Kom*3lqB@?3>Ow)F1hZBS95efFRGVilhUciFgf`m)6YF@C=^VGd zWIynyQi?3vQC4s|Y+X2SDzZk28l8H zH76%IhynOg;oVNY&L_}WXf8`uRwtfk+-AlR3;e4yS3Bgf)mUC$mbTpTHyh?w`O?8p z8yW2{kd-Wnb^qw{F+XMa(EGrz6f!0os_~`LRV&{#M5OgCAWt~TI#lE3&%VEIB%$yp}#Hx=BTS{EI3|u-|y0xM-QMwiF^SYX-bokIb-$0c*3rwI|PZ6#yDZ~yY zcPI&iU_=X3!_j{?kGy`8i}M#_J^NQL0u`uu*LO!t7ahF8?n(J{D`QYf)-z-_9i9`E zzvA|+sXUc&9(EX3A?tO>f(~ME4_Rz)My%>^>VR>~c0463ISUGFzE z>2-3&9*`lHb#H`kD(B5FE=6o5&#%b+Zof$ReZUz-WMsFME5|KaL=TlVz zE*&q$xA|6O{hu?L4R9}Llm3?0%T}4M`Hd7jUDI>Ku*Emccbjs-R5)UgSxPfs{fle9h4@|B?CV724%q(#__um2bpLsms!6K7);Vbqv=klh$UN*wCNyhFL0rgn-2W(m0l%2n?PEP;rfv-Ez==w>Egq24Q2_-1OHsv^m5?E? z25Li)+~7V^Ty;TBG5uesx{?)7BZS~fCs?$MsKJ(fS0z_l9_g|JB%#nIqqDmXPY&$0 z2%4%d-xx51zp|T*?V~V6A`;Y!ZQAFZIyxylpp4QUu%}GbRGAL19(oExbY%NHa+W8P zx;S9CmbI(3zhKkS$-q)}XZFLc77Wf81FdZ13_~NQ+&qYBcW}SSez?%9QcoV2Hjum= zIQA(w5))cA?DZ>JalBZ~&!n>h(^_j+blds}zT2E&j_ zPrT!I>)`7hptg@SbboJ|sjWJ{aGILpnB-vXdD+i~L~cMgi&Q!(g=q|i^eLH!27kb9 zUQcW2f~z7I-JMM!J7?bop-jv@g^^4L_i(oov~mC4<;EA^&d8S+cU0vUk#)=?g(HI! z1F`_bx9O!5fs23-f5wcd19gmhS4weq?y0UDvNer+BaT)lN?&%Oy)AUWOtYVuN5cUI z!kc`K7{Od#2=m}%p7I=>G_Sz%>-BNL`ukJht@<|SXYkvkbKup=vAgmlrhZVww1=nnw)L^wOHM`>ig)WP_5(k;VGnhvsJlAw zk>!IoeIm5{rY5*Ta zj4qL=hYC^ss)ZwX+id*o;ItKv-iPyb5yNZo(#*l+fv$!e8(DRbEFIRR~wt!4n7Kp3sv zs79PH1W4lL)L9rlMwR9WA%P}{ka$C_*$XUtw%3;O6g?)=?;4|yH79-*o`sY9m_4P| zCDGM&xla;W{FlHRmXoQJT}aTysntBrzpZh}KH{ZsPpNtAf{=&m7z;m!OgG(1I80J$ z93)znE9i6Bxa;Ef(8}+Auj_P7!~+}ZtJs-k)DpT00ZLe;WxR1d!UA%#(~1Tb5-D(B zBBjjNs>SWv^&{Y>FNJC!J*J?9EitKXp$=@Pu+T>SYz=U93_C%`9~$I5@2Z)8H8qnhl<|FUW(KgU_AQN zzTd~k`BR9Bt;uV{ZOBkwV-)4?hIXqd_sa+UgAV@vc7`rEfn1f*gH>X7B&_zS zPs6N25a163fRJoJHU8~ByfV2MHY1@Bp88Ud5mp81Kn=e#5x?x)edaO}Y z%A?g_gyPm&W97hpgz>M7=wA(;BW^W>+V0Z zo-lRt$fOCdIC0XVuLoE1alfV213opXs!HR;zA^4ME7-!T%Q2w)vjn1nIeUqeW@yi( z%7JLLsS)BDk!2~G5Sw2bR6s_rYXU&0$=#*iSP#!JGS~hG0R-M`q}RG{#M=E;Oa{qc z&7!^8a{9cH0Bl;1+z`wF|2@R9Rj8t#7^nX=KK!Gjhbk<5FlA0ra)wMS07xoKs`sPS zx?}*}$4KddLCNA70&Z7zk}StE&-fKUr2GYC3-YiUslPk#G#j`kT zvM1Xft4so7GwyLtaUQ;=h*y{WCsAgwDJs=I;xIe*CmzR*Z`jrW6({wifFmW12lg$5 z{G{eKzY(n0e=F)3L&?LntR!bY4!Nm))sq1lLt0LP!tPAgqM$C06@WL1zSL936-j_c z<_k+%fU8#iZ=|e-adcNEYnWA7$l&6wG2Z!Uo;rJTCUKgN)6TpVA5!q5i8cnwZu!GE zy$pTMy&V$Q!OXDEPm_KxNXnmE_?Ipm%5|`(($dhG!fL6oZwgPDp-DI}`dHY*>zQgS|#6y!S0(arv|ec?FQ~6F?#HfgcIGe#Wr8M)&msT z#0sr!&4Dxpeg=Pvnnj-lA+0R7bPkAYDRS1COjA9)LS2N`b03~3xjZ}bpQj7&Ke0ca z+L2APGG{Ju(NNh9IyXG!@ddk|vE_lhQ|0tMb5Gmb^gu1ED5_}b?vva0Jr1L4FedT< zj=`GFelIk_%rria1%)bf@ap0z2LXUHOk!TOza(GS4QsQV`P5U3a}}A_&FYp##r~r6 zDy16eN#l6H5Av8jX|u>* zWd+5fW6;7fT1B!XK&>aO(s`|GF=D_K6}Fvu1J2MEXiG3Rjxh-L~Z*MUV z6L^eO@UR@#tUsL63(u*?mcp^+#1E^>Nr=i_$!fHJ%D((Zpf_?m4YU5+J(b3q5}Kd% z#B9JVOrP!;Zjh8K!t$Gm{Jgj8bC^zShBg{1BbPH-!uJdf{xtlxsT}8)Bw1`paV$DA z3##!IZ|JB|jAi2dKQx@fcrt@*b5BT*Ob_hIueV%Pv2zb!%*}`oFq<^P?-R?6OA5U> zuV-AlB~?B~NNo$pMht)>F*qXZxXtXH5&cZlWmM2-#M5LXn~vt^ygxyy)7C;FTsHE= z8Dr%6eO)-`Hn(K~-jK-T`uJ+X8V^EOk3}s4Z(Y zV-R-F!$_aY{@SJqDSktI7d_6xl_5ht0)d;{`Ky}_?pHA!lFM1=>pXP+QW*0&4Q8EL z_!5?R5S@py`09%|jvJV?h9p&UkzY{)1x-in|-6b1eYBmSzW6h&$q0!HC?%A+%saO?gsyl-Yflz{ev)Qsr-g+ZXS`%Bo4 zA?Z5pZZ)d6E)EZ9be`5p7o+HV%>NherOi>~Zqcy@gQ%W@Xe#H*+$>aRm6iHv!{+fW z26y}S%eHAh{^1Ddg(92l06esIYrDJG1MFEe*yaIE--9#@TkKkz>i+&j3oezFB}G^C zEsAEuBu~Mn!n-rg;M|s!<&bghEP?5a5|(r_kA8t#;`-%FJLI;+87EaQ+LJkq*^CQC z9A4Vk?zjBpcgLH=HRV^27FZWliprBcvA=DlizYpE;-1mS#k=VCj#3iiJFWR|4F}o~ z=*!wE6~(h`@e;9(gPL>_MpR0<(oGpWZQm3?yd{f;0>{k_xF3L|D15zfvGE0%k4*vu zdIHxQBA~yL(>DswR+lU(pY0zVUG4lmmbI+{>)az%>9s%t07{;v3&6hX&xT~ z#q;k3KjNtd_d3o|Q1J9wVjloddyW>CVPPS?#5aJ5pHazC%k;Sb4! zX$+iiGF@(fVU}E_5$I{Lm_x`6&h~TegR7*7aIjp~ZZSErY$w|6(hS1w)YKd^#Fdo^ z0aAg04{zK=f#?UcY3?YYoNa zQSz~qO$Fo44_Dr(0ayJb3$%D1GF)!5D{1hEG(0~;~azv%nS&?A@4>H^z7 zhe8!VK_Pqow@2V(`J~pk$LH)!plAK;9!W@eV^a!;QP)9nE3eq~soZAE`{r(9rDo;0 zaqD$>qQq^_CGeNGUziRg+!Usgw#?r19IsoEN?bJn$;bh3wyj)vYD1D&cr}@IKdw`2 zyD!HGFz-QI-Y4;Hj{DOh&OebUnMg9O%v*0tIQ)-p#NQT7pVlCWOT*czYiNuT#oG$f zVREuqy(98piZ}6Nkz+v-0=r87PO}&Fgh+v(tgR&rZ^E@&7l@boqF#2a3vKT1gP&Wn ze%)fsHa07-7*3^tDxr17Oj#W_+99c;w83u8e{!}r*HLa`_R|ffN5qUCm98+xS6U@Lb&Za_HzLsEsXmR^ z93aktevO(7zY|y z<>m?`GM>?YlZR|dx!e!#1XYA_-L$#r_{dQm;-EeI`+PFmU)Y6>i>P8@2qW&RFw< zE6eP{{B!&c)~<^vTv6F*k|e?Rdxodo8bVl-16m?y*D6#GmapC92$UxC;;ZiI6KW4Y z`$Nz5%SdwgzKU#g_|1vpAPvFCKT$8}jIa;OlU&TWd^7N)5IHJY3xpvh*{(v z84|4Zps+}b80z30jY4NsS|C2!!6BFi8?)P`K)-_j!oS*9Rg&nv6uAIA-@wJ8A96w% zhRsMq+A6cYO@xQD7 z_vnO|gE-S3|E?A7R0h~QaQlTpfaF~8q1v*_k}u3yROObW_XzIpG# z7$ahkgc2Dn>sAm5kh08N}u|QS# zo82tHbf&P|^?nF(=aUne@j#yaV|%7CQI6k?ljUy~>73Kd-*bP^RQ_Rc&&xd&>YX0^ zCbORRyd{aztC7{yAqw$-Qq@?P>%yOaI+9LsD(MWrH>i=IR3H-;QII(C<@{|=M$ZeE za1(bj+TM&4B7|j~#12WO0{^Q4>PRzAp~bBw6;Sn>)o2`gb`)I$)dlW#(F{5~Q^;_m zTPCB2UJT{Mq${woFCHi3JEAnbcN@9QsJb_nn`R-K9)9}iS)gXeY8ow1=3 zxjYnXW2BI?nuA=q*_j3rNEta$Kpi>@-Mb~S0ty+^{#mKTm#r=hYpbG1ey72O16 z36R(DO3{L+xN8QsF^{T`@MBhyW0NW72@TV?cF~&TEF5W1eY@UF<(bCMET;_Z(BJis5Ki$S=FK7_W4DGT?F65?&=VUM8Hn{;6pKi+9vc@w!LcE)ZLnsXv7#ruvEYS6d#+@yp&$rJu^RPhjzWb^J{!Y% zJAY|(fv{^KhDOT*-|(k0A?(zBBDh#Zffs|+j$mE8ut3ur_-Pfdl(_lhX6u0zvw8ld z#krAbLEaL@5>S>+g2c9(&>Sc*8x z(fHO?b4iFiuHCUqc8_nCtGltU^gc_g=^Y0D_U+J3!HSo$+}M20w4D>ycuQi2Guo31 zmydHYk@8=$zFHjf)RUH?y~;l#zV%E?cQW*yOzkqEh+k5ZgKKM&JjQDaBbLU;N|Rhn&aO~vbDii)Zn{!I=$ z(M56j#o!$_&EuG`$FtDvAn{T;jv%C4=C_;P=&p>EVRF2~r9f!}Kzo$loou(3X@%Az z-rk7Q=_l)I1Vx8g_h$>Y^(%|;`sMJDTseE2egu^ zk?`F2?TUW2aIp%9hacQ!!9{A8H^0stUJlm)>I^z8#?sFf)!G(DRYloIw5(9)XaS*R zUOZGTNbBgjeh2g-D5nC>O69)7fRoqoq(*Vcl-y3P4qgMurc+#Bs53Qzz@S zeX8t&q#{DLOF0bZF}r=nByiTOp)Dk*#BUByUd40kO4Eo-@)Y_xyBwaumDcy6g%APv zJvv1pSU;(o$~9PK7KoXyI#7UY$<*@A2rZE*=vTjGB-TO!5l62wcIJf?SFSWK$W&px zPtNnOB}tF>2bA8?nhpnJ*em$Qc% z4*!0i60UJVC6XSrJ(4wjw(jnT!#Q#7KnC*V5z6_ z%k^C1he>Cfg#K;({v##Ipnl zR2&Lj3(M(e>8KH-dB~j1`g<2~NYTDT(zq0bI*o>%EMv8jx6+Aud3;}O3q1{@!j}AD zK)k!!aR4JZ%?Z`A3AZf9#-hEOxbPKl0=zV!G3*`oRJar*1{7E(a?kAT5K YMN$_1a1RoKyE_Dj;O=(#{=?~u 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 NqNI{UrI>;L{{V2X?yLX+ delta 8062 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 h+FE=p;Qk{AXn_&)+rbg2LU