diff --git a/assets/allocation_dark.svg b/assets/allocation_dark.svg index 601ef49..5d9084a 100644 --- a/assets/allocation_dark.svg +++ b/assets/allocation_dark.svg @@ -6,7 +6,7 @@ - 2026-07-06T14:54:56.315364 + 2026-07-08T00:13:38.234142 image/svg+xml @@ -21,2487 +21,2487 @@ - - - +" clip-path="url(#p5385e92166)" style="fill: #9881f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7688f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4f91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9b80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a47ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4c91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a37ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8186f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9c80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a67ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a37ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8685f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4693f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #598ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4c91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4693f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9681f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9681f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #638cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7688f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a87ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a87ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #598ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #598ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4e91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #578ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #598ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8d83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7f86f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #ab7df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9f80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7489f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a17ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9681f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8186f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5c8ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8186f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8186f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5c8ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4992f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #578ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4f91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8d83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9681f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9f80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9881f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5490f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4792f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9881f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #558ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4892f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4f91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4c91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8e83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7688f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a97df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a57ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5f8df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4792f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9482f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #568ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4593f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4593f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a77ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4892f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9482f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a37ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8485f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5f8df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #668cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5c8ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9482f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8685f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7489f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8e83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a57ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #598ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4792f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8884f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7f86f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9e80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5490f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8685f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8e83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a77ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #638cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8884f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #698bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #578ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #568ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #668cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a77ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5190f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #638cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8e83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8d83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #648cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4892f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7f86f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a37ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8186f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8186f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #608df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #668cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5190f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8186f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7489f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5c8ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #598ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #648cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #648cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5c8ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a67ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9c80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9c80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5f8df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4e91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #598ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4f91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4c91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8e83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9881f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8c84f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9681f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #638cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9681f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7688f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4493f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7489f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #608df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5f8df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9c80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8c84f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7688f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8e83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7d87f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #578ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7d87f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9e80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5c8ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #578ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9c80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4c91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4693f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7d87f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4e91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4593f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7f86f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #558ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4e91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4c91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8685f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5c8ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4493f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9b80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7688f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a57ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a17ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #668cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #698bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5c8ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5390f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4992f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8485f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #698bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8685f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9b80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a57ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5c8ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5490f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5390f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4992f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #598ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8485f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9c80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4992f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9f80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7f86f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a17ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a17ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8e83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7f86f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9f80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7489f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4f91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8485f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #668cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #578ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9c80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8e83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a77ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a97df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9183f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7688f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4493f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5190f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4c91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #648cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4f91f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9881f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a97df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8685f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4992f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7d87f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7d87f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7489f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5490f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6f8af8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4992f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9881f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #608df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8186f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #698bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #ab7df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a97df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #558ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #558ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7f86f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4892f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9f80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9482f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8186f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a87ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #608df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5190f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #648cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #668cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #578ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #608df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8d83f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a87ef8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9482f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9f80f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9681f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #648cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9881f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #a17ff8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7988f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #608df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8485f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #698bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #668cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #638cf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8485f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #5390f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #6a8bf8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #9881f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8685f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #7d87f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #608df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #4992f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #ab7df8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p5385e92166)" style="fill: #8984f8; opacity: 0.85; stroke: #e6edf3; stroke-width: 0.2; stroke-linejoin: miter"/> @@ -2510,41 +2510,41 @@ z - @@ -2558,73 +2558,73 @@ z - - @@ -2641,29 +2641,29 @@ z - @@ -2680,48 +2680,48 @@ z - @@ -2738,67 +2738,67 @@ z - @@ -2815,52 +2815,52 @@ z - - @@ -2873,224 +2873,224 @@ z - - - - - - @@ -3109,9 +3109,9 @@ z - + @@ -3123,72 +3123,72 @@ L 528.088766 175.788 - + - - @@ -3201,9 +3201,9 @@ z - + @@ -3217,31 +3217,31 @@ L 528.088766 119.009053 - + - @@ -3254,9 +3254,9 @@ z - + @@ -3270,151 +3270,151 @@ L 528.088766 62.230105 - - - - - @@ -3433,272 +3433,272 @@ z - + - - - - - - - - - - @@ -3752,347 +3752,347 @@ z - - - - - - - - - - - @@ -4118,260 +4118,260 @@ z - - - - - - @@ -4446,7 +4446,7 @@ z - + diff --git a/assets/allocation_light.svg b/assets/allocation_light.svg index f813bd9..dff3803 100644 --- a/assets/allocation_light.svg +++ b/assets/allocation_light.svg @@ -6,7 +6,7 @@ - 2026-07-06T14:54:55.829702 + 2026-07-08T00:13:37.196181 image/svg+xml @@ -21,2487 +21,2487 @@ - - - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6c55de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #445ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1666db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6f54de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7a52df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1267da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7952df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #505add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7054de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7c51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7952df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5659dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0b69da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2264db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1367da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0b69da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6955de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6955de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2d62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #445ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7e51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7e51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2264db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2264db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1567da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1f64db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2264db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5e57de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4e5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #8250df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7453de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #415ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7652df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6955de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #505add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2563db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #505add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #505add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2563db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0f68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1f64db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1666db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5e57de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6955de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7453de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6c55de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1c65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0c68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6c55de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1d65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0e68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1666db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1367da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6057de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #445ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #8050df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7a52df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2962db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0c68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6755de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1e65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0a69da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0a69da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7d51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0e68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6755de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7952df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #545add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2962db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3161dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2563db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6755de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5659dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #415ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6057de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7a52df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2264db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0c68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5958dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4e5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7353de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1c65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5659dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6057de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7e51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2d62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5958dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3460dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1f64db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1e65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3161dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7d51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1966db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2d62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6057de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5e57de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2f61dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0e68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4e5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7952df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #505add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #505add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2a62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3161dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1966db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #505add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #415ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2563db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2264db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2f61dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2f61dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2563db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7c51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7054de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7054de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2962db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1567da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2264db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1666db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1267da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6057de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6c55de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5e57de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6955de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2d62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6955de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #445ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0969da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #415ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2a62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2962db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7054de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5e57de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #445ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6057de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4b5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1f64db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4b5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7353de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2563db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1f64db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7054de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1367da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0b69da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4b5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1567da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0a69da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4e5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1d65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1567da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1367da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5659dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2563db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0969da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6f54de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #445ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7a52df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7652df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3161dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3460dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2563db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1a65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0f68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #545add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3460dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5659dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6f54de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7a52df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2563db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1c65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1a65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0f68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2264db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #545add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7054de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0f68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7453de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4e5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7652df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7652df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6057de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4e5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7453de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #415ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1666db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #545add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3161dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1f64db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7054de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6057de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7d51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #8050df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6356de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #445ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0969da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1966db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1267da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2f61dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1666db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6c55de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #8050df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5659dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0f68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4b5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4b5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #415ddc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1c65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3c5fdc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0f68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6c55de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2a62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #505add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3460dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #8250df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #8050df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1d65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1d65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4e5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0e68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7453de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6755de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #505add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7e51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2a62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1966db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2f61dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3161dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1f64db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2a62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5e57de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7e51df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6755de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7453de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6955de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2f61dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6c55de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #7652df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #475cdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2a62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #545add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3460dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3161dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2d62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #545add; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #1a65db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #3660dc; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #6c55de; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5659dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #4b5bdd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #2a62db; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #0f68da; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #8250df; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> - +" clip-path="url(#p6d6e7d66c6)" style="fill: #5a58dd; opacity: 0.85; stroke: #24292f; stroke-width: 0.2; stroke-linejoin: miter"/> @@ -2510,41 +2510,41 @@ z - @@ -2558,73 +2558,73 @@ z - - @@ -2641,29 +2641,29 @@ z - @@ -2680,48 +2680,48 @@ z - @@ -2738,67 +2738,67 @@ z - @@ -2815,52 +2815,52 @@ z - - @@ -2873,224 +2873,224 @@ z - - - - - - @@ -3109,9 +3109,9 @@ z - + @@ -3123,72 +3123,72 @@ L 528.088766 175.788 - + - - @@ -3201,9 +3201,9 @@ z - + @@ -3217,31 +3217,31 @@ L 528.088766 119.009053 - + - @@ -3254,9 +3254,9 @@ z - + @@ -3270,151 +3270,151 @@ L 528.088766 62.230105 - - - - - @@ -3433,272 +3433,272 @@ z - + - - - - - - - - - - @@ -3752,347 +3752,347 @@ z - - - - - - - - - - - @@ -4118,260 +4118,260 @@ z - - - - - - @@ -4446,7 +4446,7 @@ z - + diff --git a/assets/hero_dark.svg b/assets/hero_dark.svg index bb61313..8ba2f59 100644 --- a/assets/hero_dark.svg +++ b/assets/hero_dark.svg @@ -6,7 +6,7 @@ - 2026-07-06T14:54:55.951662 + 2026-07-08T00:13:37.505539 image/svg+xml @@ -21,148 +21,148 @@ - - - + - + - - - @@ -175,50 +175,50 @@ z - + - @@ -232,9 +232,9 @@ z - + @@ -251,9 +251,9 @@ L 259.379603 49.32 - + @@ -267,9 +267,9 @@ L 371.033404 49.32 - + @@ -282,398 +282,497 @@ L 482.687206 49.32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - @@ -709,58 +808,58 @@ z - - + + - + - @@ -770,32 +869,32 @@ z - - + + - + - @@ -805,77 +904,77 @@ z - - + + - + - @@ -885,60 +984,60 @@ z - - + + - + - @@ -948,12 +1047,12 @@ z - - + + - + @@ -967,195 +1066,195 @@ L 520.92 62.307692 - - - - - @@ -1189,11 +1288,12 @@ z - - + + @@ -1208,193 +1308,232 @@ L 409.081872 65.116653 - + - + - + - + - + - + - + + + + + + + + + + - - - + + - + - - - @@ -1414,42 +1553,24 @@ z - - - - - - - - - - - - - - - - - - - + - @@ -1468,9 +1589,9 @@ z - + - + @@ -1485,43 +1606,63 @@ z - - - + + + - + + + + + + + + + + + + + + @@ -1539,7 +1680,7 @@ z - + @@ -1550,8 +1691,87 @@ z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -1564,343 +1784,343 @@ z - + - + - - - - - - - - - - @@ -1918,236 +2138,236 @@ z - + - - - - - - - - @@ -2184,241 +2404,212 @@ z - + - - - - - - - - @@ -2534,7 +2725,7 @@ z - + diff --git a/assets/hero_light.svg b/assets/hero_light.svg index e879ef4..868d2f7 100644 --- a/assets/hero_light.svg +++ b/assets/hero_light.svg @@ -6,7 +6,7 @@ - 2026-07-06T14:54:55.441941 + 2026-07-08T00:13:36.446970 image/svg+xml @@ -21,148 +21,148 @@ - - - + - + - - - @@ -175,50 +175,50 @@ z - + - @@ -232,9 +232,9 @@ z - + @@ -251,9 +251,9 @@ L 259.379603 49.32 - + @@ -267,9 +267,9 @@ L 371.033404 49.32 - + @@ -282,398 +282,497 @@ L 482.687206 49.32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - @@ -709,58 +808,58 @@ z - - + + - + - @@ -770,32 +869,32 @@ z - - + + - + - @@ -805,77 +904,77 @@ z - - + + - + - @@ -885,60 +984,60 @@ z - - + + - + - @@ -948,12 +1047,12 @@ z - - + + - + @@ -967,195 +1066,195 @@ L 520.92 62.307692 - - - - - @@ -1189,11 +1288,12 @@ z - - + + @@ -1208,193 +1308,232 @@ L 409.081872 65.116653 - + - + - + - + - + - + - + + + + + + + + + + - - - + + - + - - - @@ -1414,42 +1553,24 @@ z - - - - - - - - - - - - - - - - - - - + - @@ -1468,9 +1589,9 @@ z - + - + @@ -1485,43 +1606,63 @@ z - - - + + + - + + + + + + + + + + + + + + @@ -1539,7 +1680,7 @@ z - + @@ -1550,8 +1691,87 @@ z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -1564,343 +1784,343 @@ z - + - + - - - - - - - - - - @@ -1918,236 +2138,236 @@ z - + - - - - - - - - @@ -2184,241 +2404,212 @@ z - + - - - - - - - - @@ -2534,7 +2725,7 @@ z - + diff --git a/assets/quality_dark.svg b/assets/quality_dark.svg index 75f4962..573626d 100644 --- a/assets/quality_dark.svg +++ b/assets/quality_dark.svg @@ -1,12 +1,12 @@ - + - 2026-07-06T14:54:56.061886 + 2026-07-08T00:13:37.721097 image/svg+xml @@ -21,120 +21,120 @@ - - - + - + - - @@ -145,31 +145,31 @@ z - + - @@ -180,76 +180,76 @@ z - + - @@ -260,59 +260,59 @@ z - + - @@ -323,32 +323,32 @@ z - + - @@ -362,500 +362,500 @@ z - - - - - - - - - - - - - @@ -891,102 +891,102 @@ z - - - - @@ -1011,41 +1011,41 @@ z - @@ -1070,19 +1070,19 @@ z - @@ -1107,34 +1107,34 @@ z - @@ -1159,62 +1159,62 @@ z - - @@ -1235,34 +1235,34 @@ z - @@ -1281,112 +1281,112 @@ z - - - @@ -1408,359 +1408,541 @@ z - + - + - + - + - + - + - + - + - + - + + + + + + + - - - + + - - - - - - - + - - - + - + - + - + - - + + - + - + - + - - - + - + - + + + - + - - - + - + - + - + - - + + - + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1768,409 +1950,409 @@ z - - - - - - - - - - - - - @@ -2199,89 +2381,89 @@ z - - - - @@ -2314,42 +2496,42 @@ z - + - - @@ -2371,27 +2553,67 @@ z - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -2406,9 +2628,9 @@ z - + - + @@ -2426,7 +2648,7 @@ z - + diff --git a/assets/quality_light.svg b/assets/quality_light.svg index 0b95b57..d480882 100644 --- a/assets/quality_light.svg +++ b/assets/quality_light.svg @@ -1,12 +1,12 @@ - + - 2026-07-06T14:54:55.564089 + 2026-07-08T00:13:36.680861 image/svg+xml @@ -21,120 +21,120 @@ - - - + - + - - @@ -145,31 +145,31 @@ z - + - @@ -180,76 +180,76 @@ z - + - @@ -260,59 +260,59 @@ z - + - @@ -323,32 +323,32 @@ z - + - @@ -362,500 +362,500 @@ z - - - - - - - - - - - - - @@ -891,102 +891,102 @@ z - - - - @@ -1011,41 +1011,41 @@ z - @@ -1070,19 +1070,19 @@ z - @@ -1107,34 +1107,34 @@ z - @@ -1159,62 +1159,62 @@ z - - @@ -1235,34 +1235,34 @@ z - @@ -1281,112 +1281,112 @@ z - - - @@ -1408,359 +1408,541 @@ z - + - + - + - + - + - + - + - + - + - + + + + + + + - - - + + - - - - - - - + - - - + - + - + - + - - + + - + - + - + - - - + - + - + + + - + - - - + - + - + - + - - + + - + - + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1768,409 +1950,409 @@ z - - - - - - - - - - - - - @@ -2199,89 +2381,89 @@ z - - - - @@ -2314,42 +2496,42 @@ z - + - - @@ -2371,27 +2553,67 @@ z - - + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -2406,9 +2628,9 @@ z - + - + @@ -2426,7 +2648,7 @@ z - + diff --git a/assets/scaling_dark.svg b/assets/scaling_dark.svg index f127dbd..525650a 100644 --- a/assets/scaling_dark.svg +++ b/assets/scaling_dark.svg @@ -6,7 +6,7 @@ - 2026-07-06T14:54:56.142872 + 2026-07-08T00:13:37.965945 image/svg+xml @@ -21,90 +21,90 @@ - - - + - - @@ -115,9 +115,9 @@ z - + @@ -131,40 +131,40 @@ L 99.560726 36.414 - + - @@ -175,9 +175,9 @@ z - + @@ -189,449 +189,551 @@ L 203.289223 36.414 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - @@ -661,47 +763,47 @@ z - - + + - + - @@ -714,12 +816,12 @@ z - - + + - + @@ -733,12 +835,12 @@ L 264.286312 177.527697 - - + + - + @@ -750,12 +852,12 @@ L 264.286312 156.153505 - - + + - + @@ -768,12 +870,12 @@ L 264.286312 134.779314 - - + + - + @@ -787,12 +889,12 @@ L 264.286312 113.405122 - - + + - + @@ -803,12 +905,12 @@ L 264.286312 92.03093 - - + + - + @@ -820,12 +922,12 @@ L 264.286312 70.656738 - - + + - + @@ -837,26 +939,224 @@ L 264.286312 49.282546 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -875,7 +1175,7 @@ z - + @@ -885,201 +1185,201 @@ z - + - - - - - - @@ -1100,27 +1400,27 @@ z - + - @@ -1140,8 +1440,23 @@ z + + + + + + + + + + + + + + + - + @@ -1154,343 +1469,343 @@ z - + - + - - - - - - - - - - @@ -1507,346 +1822,380 @@ z - - + + - - - - - - - - - + + + + + + + + - - + + - - - - - - - - - + + + + + + + + - - + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - @@ -1859,117 +2208,117 @@ z - - + + - - - @@ -1992,32 +2341,27 @@ z - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + diff --git a/assets/scaling_light.svg b/assets/scaling_light.svg index 22f51b0..444142c 100644 --- a/assets/scaling_light.svg +++ b/assets/scaling_light.svg @@ -6,7 +6,7 @@ - 2026-07-06T14:54:55.660859 + 2026-07-08T00:13:36.929885 image/svg+xml @@ -21,90 +21,90 @@ - - - + - - @@ -115,9 +115,9 @@ z - + @@ -131,40 +131,40 @@ L 99.560726 36.414 - + - @@ -175,9 +175,9 @@ z - + @@ -189,449 +189,551 @@ L 203.289223 36.414 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - @@ -661,47 +763,47 @@ z - - + + - + - @@ -714,12 +816,12 @@ z - - + + - + @@ -733,12 +835,12 @@ L 264.286312 177.527697 - - + + - + @@ -750,12 +852,12 @@ L 264.286312 156.153505 - - + + - + @@ -768,12 +870,12 @@ L 264.286312 134.779314 - - + + - + @@ -787,12 +889,12 @@ L 264.286312 113.405122 - - + + - + @@ -803,12 +905,12 @@ L 264.286312 92.03093 - - + + - + @@ -820,12 +922,12 @@ L 264.286312 70.656738 - - + + - + @@ -837,26 +939,224 @@ L 264.286312 49.282546 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - @@ -875,7 +1175,7 @@ z - + @@ -885,201 +1185,201 @@ z - + - - - - - - @@ -1100,27 +1400,27 @@ z - + - @@ -1140,8 +1440,23 @@ z + + + + + + + + + + + + + + + - + @@ -1154,343 +1469,343 @@ z - + - + - - - - - - - - - - @@ -1507,346 +1822,380 @@ z - - + + - - - - - - - - - + + + + + + + + - - + + - - - - - - - - - + + + + + + + + - - + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - + - - - - @@ -1859,117 +2208,117 @@ z - - + + - - - @@ -1992,32 +2341,27 @@ z - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + diff --git a/examples/05_benchmark.py b/examples/05_benchmark.py index e81b6b1..36daa0d 100644 --- a/examples/05_benchmark.py +++ b/examples/05_benchmark.py @@ -20,6 +20,8 @@ def main() -> None: "greedy_by_size_allocator", "greedy_by_size_allocator_cpp", "greedy_by_all_allocator_cpp", + "best_fit_allocator", + "telamalloc_allocator", ) # minimalloc is an optional dependency that only builds on some platforms if HAS_MINIMALLOC: diff --git a/scripts/generate_readme_assets.py b/scripts/generate_readme_assets.py index 8825ee4..4751c56 100644 --- a/scripts/generate_readme_assets.py +++ b/scripts/generate_readme_assets.py @@ -16,9 +16,9 @@ uv run python scripts/generate_readme_assets.py -The benchmark portion takes a few minutes (genetic search and branch-and-bound -timeouts dominate). Use ``--dump data.json`` once and ``--data data.json`` to -iterate on rendering without re-running it. ``--preview DIR`` additionally +The benchmark portion takes a few minutes (every search allocator runs at the +library-wide 3 s budget). Use ``--dump data.json`` once and ``--data data.json`` +to iterate on rendering without re-running it. ``--preview DIR`` additionally writes PNG previews. """ @@ -41,9 +41,7 @@ from matplotlib.patches import Rectangle from matplotlib.ticker import FuncFormatter, MultipleLocator from omnimalloc import run_allocation, validate_allocation -from omnimalloc.allocators import BaseAllocator -from omnimalloc.allocators.minimalloc import MinimallocAllocator -from omnimalloc.allocators.supermalloc import SupermallocAllocator, SupermallocConfig +from omnimalloc.allocators import DEFAULT_MAX_SECONDS, BaseAllocator from omnimalloc.benchmark.sources import BaseSource from omnimalloc.benchmark.timer import Timer from omnimalloc.common.units import MB @@ -54,11 +52,9 @@ from omnimalloc.primitives import Pool SEED = 0 -SUPERMALLOC_TIMEOUT = 10.0 -SCALING_TIMEOUT = 3.0 MINIMALLOC_URL = "git+https://github.com/google/minimalloc.git" SCALING_SIZES = (10, 32, 100, 316, 1000, 3162, 10000) -SCALING_SIZES_SLOW = SCALING_SIZES[:-1] # hill climbing needs minutes at 10k +SCALING_SIZES_SLOW = SCALING_SIZES[:-1] # minimalloc cannot solve 10k in budget ALLOCATION_PROBLEM = "mm-G" ASSETS_DIR = Path(__file__).resolve().parent.parent / "assets" @@ -67,42 +63,49 @@ ALLOCATORS: dict[str, tuple[str, str]] = { "naive_allocator": ("naive", "baseline"), "random_allocator": ("random search", "baseline"), - "greedy_by_area_allocator_cpp": ("greedy (area)", "greedy"), "greedy_by_size_allocator_cpp": ("greedy (size)", "greedy"), "greedy_by_all_allocator_cpp": ("greedy (all)", "greedy"), - "hill_climb_allocator": ("hill climbing", "search"), - "genetic_allocator": ("genetic", "search"), + "best_fit_allocator": ("best-fit", "greedy_alt"), + "hill_climb_allocator": ("hill climbing", "search_alt"), + "genetic_allocator": ("genetic", "search_alt"), + "simulated_annealing_allocator": ("simulated annealing", "search"), + "tabu_search_allocator": ("tabu search", "search"), + "telamalloc_allocator": ("telamalloc", "telamalloc"), "minimalloc_allocator": ("minimalloc", "minimalloc"), "supermalloc_allocator": ("supermalloc", "exact"), } HERO_ALLOCATORS = ( "random_allocator", - "greedy_by_area_allocator_cpp", "greedy_by_size_allocator_cpp", "greedy_by_all_allocator_cpp", + "best_fit_allocator", "hill_climb_allocator", "genetic_allocator", + "simulated_annealing_allocator", + "tabu_search_allocator", + "telamalloc_allocator", "minimalloc_allocator", "supermalloc_allocator", ) QUALITY_ALLOCATORS = ( "greedy_by_size_allocator_cpp", - "greedy_by_all_allocator_cpp", + "best_fit_allocator", + "tabu_search_allocator", + "telamalloc_allocator", "minimalloc_allocator", "supermalloc_allocator", ) +# best-fit is omitted: its curve coincides with greedy (size) at every size. SCALING_ALLOCATORS = ( "naive_allocator", "greedy_by_size_allocator_cpp", "hill_climb_allocator", + "telamalloc_allocator", "minimalloc_allocator", "supermalloc_allocator", ) -# Quality re-colors greedy (size) so the two greedy variants stay distinguishable. -QUALITY_ROLES = {"greedy_by_size_allocator_cpp": "greedy_alt"} - QUALITY_PROBLEMS = ("mm-A", "mm-C", "mm-H", "mm-K", "pinwheel", "tiling", "random") PROBLEM_LABELS = { "mm-A": "minimalloc A", @@ -118,23 +121,28 @@ # Direct-label offsets in points, tuned per hero point: (dx, dy, ha). HERO_LABEL_OFFSETS: dict[str, tuple[float, float, str]] = { "random_allocator": (0, -11, "center"), - "greedy_by_area_allocator_cpp": (0, 8, "center"), "greedy_by_size_allocator_cpp": (-8, 0, "right"), "greedy_by_all_allocator_cpp": (0, -11, "center"), - "hill_climb_allocator": (0, 8, "center"), - "genetic_allocator": (0, -11, "center"), + "best_fit_allocator": (0, 8, "center"), + "hill_climb_allocator": (0, -11, "center"), + "genetic_allocator": (8, -6, "left"), + "simulated_annealing_allocator": (-8, 3, "right"), + "tabu_search_allocator": (0, 10, "center"), + "telamalloc_allocator": (-8, 0, "right"), "minimalloc_allocator": (0, 8, "center"), "supermalloc_allocator": (-10, 0, "right"), } # Direct-label offsets in points: (dx, dy, ha). minimalloc's line ends early -# (no 10k point), so its label anchors left, away from the 10k label cluster. +# (no 10k point), so its label anchors left, away from the 10k label cluster; +# the four lines that converge on the 3 s budget at 10k get staggered dy. SCALING_LABEL_OFFSETS = { "naive_allocator": (4, -2, "left"), "greedy_by_size_allocator_cpp": (4, -4, "left"), - "hill_climb_allocator": (4, 2, "left"), + "hill_climb_allocator": (4, 3, "left"), + "telamalloc_allocator": (4, -11, "left"), "minimalloc_allocator": (0, 9, "center"), - "supermalloc_allocator": (4, 4, "left"), + "supermalloc_allocator": (4, 10, "left"), } @@ -161,6 +169,8 @@ class Theme: "greedy": "#0969da", "greedy_alt": "#54aeff", "search": "#bc4c00", + "search_alt": "#9a6700", + "telamalloc": "#1b7c83", "minimalloc": "#bf3989", "exact": "#8250df", }, @@ -177,6 +187,8 @@ class Theme: "greedy": "#4493f8", "greedy_alt": "#79c0ff", "search": "#f0883e", + "search_alt": "#d29922", + "telamalloc": "#39c5cf", "minimalloc": "#db61a2", "exact": "#ab7df8", }, @@ -203,14 +215,6 @@ def _ensure_minimalloc() -> None: _pip_install(MINIMALLOC_URL) -def _allocator(name: str, timeout: float = SUPERMALLOC_TIMEOUT) -> BaseAllocator: - if name == "supermalloc_allocator": - return SupermallocAllocator(config=SupermallocConfig(timeout=timeout)) - if name == "minimalloc_allocator": - return MinimallocAllocator(timeout=int(timeout)) - return BaseAllocator.resolve(name) - - def _solve( pool: Pool, allocator: BaseAllocator, *, validate: bool = True ) -> tuple[float, float, Pool]: @@ -245,7 +249,7 @@ def collect_data() -> dict[str, Any]: supermalloc_pools: dict[str, Pool] = {} names = set(HERO_ALLOCATORS) | set(QUALITY_ALLOCATORS) for name in sorted(names): - allocator = _allocator(name) + allocator = BaseAllocator.resolve(name) problems = tuple(suite) if name in QUALITY_ALLOCATORS else hard runs[name] = {} for problem in problems: @@ -272,9 +276,9 @@ def collect_data() -> dict[str, Any]: source = BaseSource.get("random_source")() scaling: dict[str, list[list[float]]] = {} for name in SCALING_ALLOCATORS: - allocator = _allocator(name, SCALING_TIMEOUT) - # Hill climbing needs minutes at 10k; minimalloc can't solve 10k in budget. - capped = name in ("hill_climb_allocator", "minimalloc_allocator") + allocator = BaseAllocator.resolve(name) + # minimalloc can't solve 10k within the budget and would error out. + capped = name == "minimalloc_allocator" sizes = SCALING_SIZES_SLOW if capped else SCALING_SIZES scaling[name] = [] for size in sizes: @@ -329,10 +333,6 @@ def _style(theme: Theme) -> dict[str, Any]: } -def _quality_role(name: str) -> str: - return QUALITY_ROLES.get(name, ALLOCATORS[name][1]) - - def _title(fig: Figure, theme: Theme, title: str, subtitle: str) -> None: fig.text( 0.01, @@ -519,7 +519,7 @@ def render_quality(data: dict[str, Any], theme: Theme, preview: Path | None) -> value, y, s=52 if emphasis else 34, - color=theme.role[_quality_role(name)], + color=theme.role[ALLOCATORS[name][1]], zorder=4, linewidths=1.2 if emphasis else 0, edgecolors=theme.ink if emphasis else "none", @@ -535,7 +535,7 @@ def render_quality(data: dict[str, Any], theme: Theme, preview: Path | None) -> _title(fig, theme, "Quality per problem", "100% = proven lower bound") series = [ - (ALLOCATORS[name][0], theme.role[_quality_role(name)]) + (ALLOCATORS[name][0], theme.role[ALLOCATORS[name][1]]) for name in QUALITY_ALLOCATORS ] _series_line(fig, series, y=0.842) @@ -591,7 +591,7 @@ def render_scaling(data: dict[str, Any], theme: Theme, preview: Path | None) -> fig, theme, "Scaling", - f"random problems · supermalloc budget {SCALING_TIMEOUT:.0f} s", + f"random problems · search budget {DEFAULT_MAX_SECONDS:.0f} s", ) fig.subplots_adjust(top=0.845, bottom=0.125, left=0.16, right=0.97) _save(fig, "scaling", theme, preview) diff --git a/src/cpp/allocators/defaults.hpp b/src/cpp/allocators/defaults.hpp new file mode 100644 index 0000000..1e80b0b --- /dev/null +++ b/src/cpp/allocators/defaults.hpp @@ -0,0 +1,13 @@ +// +// SPDX-License-Identifier: Apache-2.0 +// + +#pragma once + +namespace omnimalloc { + +// Shared wall-clock budget for every time-bounded allocator (seconds). +// Mirrors DEFAULT_MAX_SECONDS in the Python package. +inline constexpr double kDefaultMaxSeconds = 3.0; + +} // namespace omnimalloc diff --git a/src/cpp/allocators/simulated_annealing.hpp b/src/cpp/allocators/simulated_annealing.hpp index 33d343e..0c7302b 100644 --- a/src/cpp/allocators/simulated_annealing.hpp +++ b/src/cpp/allocators/simulated_annealing.hpp @@ -7,6 +7,7 @@ #include #include +#include "allocators/defaults.hpp" #include "primitives/allocation.hpp" namespace omnimalloc { @@ -22,7 +23,7 @@ struct SimulatedAnnealingConfig { // Wall-clock budget checked once per iteration; 0 disables it. Each // iteration re-evaluates a full O(n) placement, so `max_iterations` alone // does not bound runtime as `allocations` grows - this does. - double max_seconds = 2.0; + double max_seconds = kDefaultMaxSeconds; }; // Simulated annealing over first-fit placement orders. Each iteration swaps a diff --git a/src/cpp/allocators/tabu_search.hpp b/src/cpp/allocators/tabu_search.hpp index 1ee897d..3302122 100644 --- a/src/cpp/allocators/tabu_search.hpp +++ b/src/cpp/allocators/tabu_search.hpp @@ -7,6 +7,7 @@ #include #include +#include "allocators/defaults.hpp" #include "primitives/allocation.hpp" namespace omnimalloc { @@ -22,7 +23,7 @@ struct TabuSearchConfig { // iteration evaluates `neighborhood_size` full O(n) placements, so // `max_iterations` alone does not bound runtime as `allocations` grows - // this does. - double max_seconds = 2.0; + double max_seconds = kDefaultMaxSeconds; }; // Tabu search over first-fit placement orders. Each iteration samples diff --git a/src/cpp/allocators/telamalloc.hpp b/src/cpp/allocators/telamalloc.hpp index 8eabc3e..74b6e9e 100644 --- a/src/cpp/allocators/telamalloc.hpp +++ b/src/cpp/allocators/telamalloc.hpp @@ -7,6 +7,7 @@ #include #include +#include "allocators/defaults.hpp" #include "primitives/allocation.hpp" namespace omnimalloc { @@ -21,7 +22,7 @@ struct TelamallocConfig { int max_backtracks = 10000; // Wall-clock budget for the whole allocate(); 0 disables it, leaving the // per-attempt `max_backtracks` as the only bound. - double max_seconds = 2.0; + double max_seconds = kDefaultMaxSeconds; }; // TelaMalloc-style allocator after Maas et al., ASPLOS 2023 ("TelaMalloc: diff --git a/src/python/omnimalloc/_cpp.pyi b/src/python/omnimalloc/_cpp.pyi index c7ec2e6..e8c8fb3 100644 --- a/src/python/omnimalloc/_cpp.pyi +++ b/src/python/omnimalloc/_cpp.pyi @@ -116,7 +116,7 @@ class BestFitAllocatorCpp: def __hash__(self) -> int: ... class SimulatedAnnealingConfig: - def __init__(self, seed: int = 42, max_iterations: int = 3000, initial_temperature: float = 3.0, cooling_rate: float = 0.998, max_seconds: float = 2.0) -> None: ... + def __init__(self, seed: int = 42, max_iterations: int = 3000, initial_temperature: float = 3.0, cooling_rate: float = 0.998, max_seconds: float = 3.0) -> None: ... @property def seed(self) -> int: ... @@ -154,7 +154,7 @@ class SimulatedAnnealingAllocatorCpp: def allocate(self, allocations: Sequence[Allocation]) -> list[Allocation]: ... class TabuSearchConfig: - def __init__(self, seed: int = 42, max_iterations: int = 500, neighborhood_size: int = 20, tabu_tenure: int = 15, max_seconds: float = 2.0) -> None: ... + def __init__(self, seed: int = 42, max_iterations: int = 500, neighborhood_size: int = 20, tabu_tenure: int = 15, max_seconds: float = 3.0) -> None: ... @property def seed(self) -> int: ... @@ -192,7 +192,7 @@ class TabuSearchAllocatorCpp: def allocate(self, allocations: Sequence[Allocation]) -> list[Allocation]: ... class TelamallocConfig: - def __init__(self, seed: int = 42, max_backtracks: int = 10000, max_seconds: float = 2.0) -> None: ... + def __init__(self, seed: int = 42, max_backtracks: int = 10000, max_seconds: float = 3.0) -> None: ... @property def seed(self) -> int: ... diff --git a/src/python/omnimalloc/allocators/__init__.py b/src/python/omnimalloc/allocators/__init__.py index a1dea65..336f32e 100644 --- a/src/python/omnimalloc/allocators/__init__.py +++ b/src/python/omnimalloc/allocators/__init__.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # +from .base import DEFAULT_MAX_SECONDS as DEFAULT_MAX_SECONDS from .base import BaseAllocator as BaseAllocator from .best_fit import BestFitAllocator as BestFitAllocator from .genetic import GeneticAllocator as GeneticAllocator diff --git a/src/python/omnimalloc/allocators/base.py b/src/python/omnimalloc/allocators/base.py index cda9ef4..0d12ec9 100644 --- a/src/python/omnimalloc/allocators/base.py +++ b/src/python/omnimalloc/allocators/base.py @@ -3,13 +3,16 @@ # from abc import abstractmethod -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Final from omnimalloc.common.registry import Registered if TYPE_CHECKING: from omnimalloc.primitives import Allocation +# Shared wall-clock budget for every time-bounded allocator (seconds). +DEFAULT_MAX_SECONDS: Final[float] = 3.0 + def require_unique_ids(allocations: tuple["Allocation", ...]) -> None: """Raise if any allocation id repeats; id-keyed placement assumes uniqueness.""" diff --git a/src/python/omnimalloc/allocators/genetic.py b/src/python/omnimalloc/allocators/genetic.py index 17fb1dc..60239c9 100644 --- a/src/python/omnimalloc/allocators/genetic.py +++ b/src/python/omnimalloc/allocators/genetic.py @@ -3,13 +3,14 @@ # import random +import time from typing import Any, cast from omnimalloc._cpp import FirstFitPlacer from omnimalloc.common.optional import require_optional from omnimalloc.primitives import Allocation -from .base import require_unique_ids +from .base import DEFAULT_MAX_SECONDS, require_unique_ids from .greedy import GreedyAllocator from .greedy_base import ( order_by_area, @@ -30,7 +31,11 @@ class GeneticAllocator(GreedyAllocator): - """Genetic algorithm allocator that evolves greedy placement orders.""" + """Genetic algorithm allocator that evolves greedy placement orders. + + `max_seconds` (default 3s) bounds wall-clock time between generations, + independent of `num_generations`; set it to 0 to disable the deadline. + """ def __init__( self, @@ -40,6 +45,7 @@ def __init__( crossover_prob: float = 0.7, mutation_prob: float = 0.2, tournament_size: int = 3, + max_seconds: float = DEFAULT_MAX_SECONDS, ) -> None: if not HAS_DEAP: require_optional("deap", "GeneticAllocator") @@ -56,6 +62,8 @@ def __init__( ) if tournament_size <= 0: raise ValueError(f"tournament_size must be positive, got {tournament_size}") + if max_seconds < 0: + raise ValueError(f"max_seconds must be non-negative, got {max_seconds}") self.seed = seed self.population_size = population_size @@ -63,6 +71,7 @@ def __init__( self.crossover_prob = crossover_prob self.mutation_prob = mutation_prob self.tournament_size = tournament_size + self.max_seconds = max_seconds # Setup DEAP creators (only once per process, they live in a global namespace) if not hasattr(creator, "FitnessMin"): @@ -135,16 +144,27 @@ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ... hall_of_fame = tools.HallOfFame(maxsize=1) + def evaluate_invalid(individuals: list[Any]) -> None: + for individual in individuals: + if not individual.fitness.valid: + individual.fitness.values = toolbox.evaluate(individual) # type: ignore[unresolved-attribute] + + # DEAP's eaSimple, unrolled so a wall-clock deadline can stop between + # generations; varAnd keeps the RNG stream identical to eaSimple. # TODO(fpedd): Try eaMuPlusLambda and eaMuCommaLambda - algorithms.eaSimple( - population=population, - toolbox=toolbox, - cxpb=self.crossover_prob, - mutpb=self.mutation_prob, - ngen=self.num_generations, - halloffame=hall_of_fame, - verbose=False, - ) + deadline = time.monotonic() + self.max_seconds if self.max_seconds else None + evaluate_invalid(population) + hall_of_fame.update(population) + for _ in range(self.num_generations): + if deadline is not None and time.monotonic() >= deadline: + break + offspring = toolbox.select(population, len(population)) # type: ignore[unresolved-attribute] + offspring = algorithms.varAnd( + offspring, toolbox, self.crossover_prob, self.mutation_prob + ) + evaluate_invalid(offspring) + hall_of_fame.update(offspring) + population[:] = offspring best_permutation = list(hall_of_fame[0]) return tuple(placer.place(best_permutation)) diff --git a/src/python/omnimalloc/allocators/hillclimb.py b/src/python/omnimalloc/allocators/hillclimb.py index 709b6a8..eeafa05 100644 --- a/src/python/omnimalloc/allocators/hillclimb.py +++ b/src/python/omnimalloc/allocators/hillclimb.py @@ -4,10 +4,12 @@ import math import random +import time from omnimalloc._cpp import FirstFitPlacer from omnimalloc.primitives import Allocation +from .base import DEFAULT_MAX_SECONDS from .greedy import GreedyAllocator from .greedy_base import compute_conflicts, peak_memory @@ -20,6 +22,8 @@ class HillClimbAllocator(GreedyAllocator): peak improves (or occasionally when it worsens, per the annealing schedule). `acceptance_temperature` is the percent worsening accepted with probability 1/e at the start; it cools linearly to zero. + `max_seconds` (default 3s) bounds wall-clock time as the input grows, + independent of `max_iterations`; set it to 0 to disable the deadline. """ def __init__( @@ -27,6 +31,7 @@ def __init__( max_iterations: int = 100, seed: int = 42, acceptance_temperature: float = 2.0, + max_seconds: float = DEFAULT_MAX_SECONDS, ) -> None: if max_iterations <= 0: raise ValueError(f"max_iterations must be positive, got {max_iterations}") @@ -35,10 +40,13 @@ def __init__( f"acceptance_temperature must be non-negative, " f"got {acceptance_temperature}" ) + if max_seconds < 0: + raise ValueError(f"max_seconds must be non-negative, got {max_seconds}") self._max_iterations = max_iterations self._seed = seed self._acceptance_temperature = acceptance_temperature + self._max_seconds = max_seconds def _collect_neighbors( self, @@ -114,6 +122,7 @@ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ... if not allocations: return allocations + deadline = time.monotonic() + self._max_seconds if self._max_seconds else None rng = random.Random(self._seed) conflicts = compute_conflicts(allocations) placer = FirstFitPlacer(list(allocations)) @@ -136,6 +145,8 @@ def allocate(self, allocations: tuple[Allocation, ...]) -> tuple[Allocation, ... best, best_peak = current, current_peak for iteration in range(self._max_iterations): + if deadline is not None and time.monotonic() >= deadline: + break swap = self._propose_swap( order, current, current_peak, rng, allocations, adjacency ) diff --git a/src/python/omnimalloc/allocators/minimalloc.py b/src/python/omnimalloc/allocators/minimalloc.py index f69a38f..93868cc 100644 --- a/src/python/omnimalloc/allocators/minimalloc.py +++ b/src/python/omnimalloc/allocators/minimalloc.py @@ -8,7 +8,7 @@ from omnimalloc.common.units import TB from omnimalloc.primitives import Allocation -from .base import BaseAllocator, require_unique_ids +from .base import DEFAULT_MAX_SECONDS, BaseAllocator, require_unique_ids try: import minimalloc as mm # type: ignore @@ -45,7 +45,9 @@ def _to_buffer(allocation: Allocation) -> "mm.Buffer": class MinimallocAllocator(BaseAllocator): """Wrapper for Google's minimalloc constraint-based allocator.""" - def __init__(self, timeout: int = 10, max_capacity: int = 1 * TB) -> None: + def __init__( + self, timeout: int = int(DEFAULT_MAX_SECONDS), max_capacity: int = 1 * TB + ) -> None: _require_minimalloc() self._timeout = timeout self._max_capacity = max_capacity diff --git a/src/python/omnimalloc/allocators/simulated_annealing.py b/src/python/omnimalloc/allocators/simulated_annealing.py index 609fee6..4d595c6 100644 --- a/src/python/omnimalloc/allocators/simulated_annealing.py +++ b/src/python/omnimalloc/allocators/simulated_annealing.py @@ -10,7 +10,7 @@ from omnimalloc._cpp import SimulatedAnnealingConfig as _SimulatedAnnealingConfig from omnimalloc.primitives import Allocation -from .base import BaseAllocator +from .base import DEFAULT_MAX_SECONDS, BaseAllocator @dataclass(frozen=True) @@ -21,7 +21,7 @@ class SimulatedAnnealingConfig: max_iterations: int = 3000 initial_temperature: float = 3.0 cooling_rate: float = 0.998 - max_seconds: float = 2.0 + max_seconds: float = DEFAULT_MAX_SECONDS def __post_init__(self) -> None: if self.max_iterations <= 0: @@ -59,7 +59,7 @@ class SimulatedAnnealingAllocator(BaseAllocator): (including every candidate placement) runs natively, it evaluates far more candidates per second than an equivalent Python-orchestrated local search such as `HillClimbAllocator`. Each iteration re-evaluates a full placement - of every allocation, so `max_seconds` (default 2s) bounds wall-clock time + of every allocation, so `max_seconds` (default 3s) bounds wall-clock time as the input grows, independent of `max_iterations`. """ diff --git a/src/python/omnimalloc/allocators/supermalloc.py b/src/python/omnimalloc/allocators/supermalloc.py index bf3e476..e6d6565 100644 --- a/src/python/omnimalloc/allocators/supermalloc.py +++ b/src/python/omnimalloc/allocators/supermalloc.py @@ -16,7 +16,7 @@ greedy_many, solve_many, ) -from omnimalloc.allocators.base import BaseAllocator +from omnimalloc.allocators.base import DEFAULT_MAX_SECONDS, BaseAllocator from omnimalloc.primitives.allocation import Allocation logger = logging.getLogger(__name__) @@ -60,7 +60,7 @@ class SortKey(str, Enum): class SupermallocConfig: """Configuration for the SupermallocAllocator.""" - timeout: float = 10.0 + timeout: float = DEFAULT_MAX_SECONDS heuristics: tuple[Heuristic, ...] = DEFAULT_HEURISTICS cores: int | None = None canonical: bool = True diff --git a/src/python/omnimalloc/allocators/tabu_search.py b/src/python/omnimalloc/allocators/tabu_search.py index 9a92c06..51bd50a 100644 --- a/src/python/omnimalloc/allocators/tabu_search.py +++ b/src/python/omnimalloc/allocators/tabu_search.py @@ -8,7 +8,7 @@ from omnimalloc._cpp import TabuSearchConfig as _TabuSearchConfig from omnimalloc.primitives import Allocation -from .base import BaseAllocator +from .base import DEFAULT_MAX_SECONDS, BaseAllocator @dataclass(frozen=True) @@ -19,7 +19,7 @@ class TabuSearchConfig: max_iterations: int = 500 neighborhood_size: int = 20 tabu_tenure: int = 15 - max_seconds: float = 2.0 + max_seconds: float = DEFAULT_MAX_SECONDS def __post_init__(self) -> None: if self.max_iterations <= 0: @@ -57,7 +57,7 @@ class TabuSearchAllocator(BaseAllocator): swap just made is then forbidden from being immediately reversed for `tabu_tenure` iterations, which helps the search escape local optima without cycling between the same two orders. Each iteration evaluates - `neighborhood_size` full placements, so `max_seconds` (default 2s) bounds + `neighborhood_size` full placements, so `max_seconds` (default 3s) bounds wall-clock time as the input grows, independent of `max_iterations`. """ diff --git a/src/python/omnimalloc/allocators/telamalloc.py b/src/python/omnimalloc/allocators/telamalloc.py index 026b304..1fc8f9e 100644 --- a/src/python/omnimalloc/allocators/telamalloc.py +++ b/src/python/omnimalloc/allocators/telamalloc.py @@ -8,7 +8,7 @@ from omnimalloc._cpp import TelamallocConfig as _TelamallocConfig from omnimalloc.primitives import Allocation -from .base import BaseAllocator +from .base import DEFAULT_MAX_SECONDS, BaseAllocator @dataclass(frozen=True) @@ -17,7 +17,7 @@ class TelamallocConfig: seed: int = 42 max_backtracks: int = 10000 - max_seconds: float = 2.0 + max_seconds: float = DEFAULT_MAX_SECONDS def __post_init__(self) -> None: if self.max_backtracks < 0: @@ -52,7 +52,7 @@ class TelamallocAllocator(BaseAllocator): capacity from a first-fit incumbent down toward its load lower bound. An occasional seeded random-walk repair breaks min-conflict cycles; results are deterministic for a fixed `seed`, and setting `max_seconds` (default - 2s) to 0 makes them reproducible across machines via `max_backtracks` + 3s) to 0 makes them reproducible across machines via `max_backtracks` alone. """ diff --git a/tests/unit/allocators/test_genetic.py b/tests/unit/allocators/test_genetic.py index d2bb228..2cab0a2 100644 --- a/tests/unit/allocators/test_genetic.py +++ b/tests/unit/allocators/test_genetic.py @@ -57,6 +57,11 @@ def test_genetic_rejects_invalid_tournament_size() -> None: GeneticAllocator(tournament_size=0) +def test_genetic_rejects_negative_max_seconds() -> None: + with pytest.raises(ValueError, match="max_seconds must be non-negative"): + GeneticAllocator(max_seconds=-1.0) + + def test_genetic_rejects_duplicate_ids() -> None: alloc = Allocation(id=1, size=100, start=0, end=10) with pytest.raises(ValueError, match="allocation ids must be unique"): diff --git a/tests/unit/allocators/test_hillclimb.py b/tests/unit/allocators/test_hillclimb.py index d40fdc7..3f199d3 100644 --- a/tests/unit/allocators/test_hillclimb.py +++ b/tests/unit/allocators/test_hillclimb.py @@ -38,6 +38,11 @@ def test_hillclimb_rejects_negative_temperature() -> None: HillClimbAllocator(acceptance_temperature=-1.0) +def test_hillclimb_rejects_negative_max_seconds() -> None: + with pytest.raises(ValueError, match="max_seconds must be non-negative"): + HillClimbAllocator(max_seconds=-1.0) + + def test_hillclimb_preserves_allocations() -> None: allocator = HillClimbAllocator() allocs = (