From 6cd9d818e0adfdf907ac242b2ab874e51389770a Mon Sep 17 00:00:00 2001 From: ben-fornefeld Date: Wed, 22 Jul 2026 18:31:26 +0200 Subject: [PATCH 1/8] remove marketing rewrites, redirects, sitemap and all analytics (PostHog, Vercel, GTM) --- bun.lock | 174 +---------- next.config.ts | 75 +---- package.json | 6 - src/app/(rewrites)/[[...slug]]/not-found.tsx | 13 - src/app/(rewrites)/[[...slug]]/route.ts | 135 -------- src/app/(rewrites)/not-found/page.tsx | 13 - src/app/body.tsx | 14 +- src/app/dashboard/[teamSlug]/layout.tsx | 6 +- src/app/global-error.tsx | 24 -- src/app/layout.tsx | 26 +- src/app/robots.ts | 11 +- src/app/sbx/new/route.ts | 76 ----- src/app/sitemap.ts | 295 ------------------ src/configs/documentation.ts | 1 - src/configs/redirects.ts | 61 ---- src/configs/rewrites.ts | 69 ---- src/configs/sitemap.ts | 11 - src/core/server/proxy/classifier.ts | 13 +- src/core/server/proxy/handlers.ts | 54 ---- src/core/server/proxy/runtime.ts | 18 +- src/features/client-providers.tsx | 35 +-- src/features/dashboard/context.tsx | 2 - .../navbar/dashboard-survey-popover.tsx | 118 ------- .../dashboard/posthog-error-boundary.tsx | 36 --- .../dashboard/sandboxes/list/tracking.ts | 14 +- src/features/dashboard/sidebar/footer.tsx | 137 ++------ src/features/dashboard/sidebar/menu.tsx | 6 - .../dashboard/templates/detail/tabs.tsx | 19 -- .../templates/detail/visibility-dropdown.tsx | 6 - .../dashboard/templates/list/table-body.tsx | 7 - .../dashboard/templates/list/table-config.tsx | 13 +- .../dashboard/templates/tags/table-config.tsx | 13 +- src/features/ory-posthog-identity-bridge.tsx | 49 --- src/features/posthog-provider.tsx | 123 -------- src/lib/hooks/use-analytics.ts | 13 +- src/lib/utils/redirects.ts | 20 -- src/lib/utils/rewrites.ts | 193 ------------ src/ui/error.tsx | 13 +- src/ui/survey.tsx | 205 ------------ tests/unit/proxy-handlers.test.ts | 95 ------ tests/unit/proxy-plan.test.ts | 39 --- tests/unit/rewrites-strip-analytics.test.ts | 62 ---- tests/unit/sitemap.test.ts | 80 ----- vercel.json | 81 +---- 44 files changed, 71 insertions(+), 2403 deletions(-) delete mode 100644 src/app/(rewrites)/[[...slug]]/not-found.tsx delete mode 100644 src/app/(rewrites)/[[...slug]]/route.ts delete mode 100644 src/app/(rewrites)/not-found/page.tsx delete mode 100644 src/app/sbx/new/route.ts delete mode 100644 src/app/sitemap.ts delete mode 100644 src/configs/documentation.ts delete mode 100644 src/configs/redirects.ts delete mode 100644 src/configs/rewrites.ts delete mode 100644 src/configs/sitemap.ts delete mode 100644 src/features/dashboard/navbar/dashboard-survey-popover.tsx delete mode 100644 src/features/dashboard/posthog-error-boundary.tsx delete mode 100644 src/features/ory-posthog-identity-bridge.tsx delete mode 100644 src/features/posthog-provider.tsx delete mode 100644 src/lib/utils/redirects.ts delete mode 100644 src/lib/utils/rewrites.ts delete mode 100644 src/ui/survey.tsx delete mode 100644 tests/unit/proxy-handlers.test.ts delete mode 100644 tests/unit/proxy-plan.test.ts delete mode 100644 tests/unit/rewrites-strip-analytics.test.ts delete mode 100644 tests/unit/sitemap.test.ts diff --git a/bun.lock b/bun.lock index cc39b7eee..47c6ad31a 100644 --- a/bun.lock +++ b/bun.lock @@ -9,7 +9,6 @@ "@launchdarkly/node-server-sdk": "^9.11.2", "@launchdarkly/openfeature-node-server": "^1.2.0", "@next/env": "^16.2.7", - "@next/third-parties": "^16.2.10", "@openfeature/server-sdk": "^1.22.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/auto-instrumentations-node": "^0.77.0", @@ -52,13 +51,10 @@ "@trpc/server": "^11.7.1", "@trpc/tanstack-react-query": "^11.7.1", "@types/micromatch": "^4.0.9", - "@vercel/analytics": "^1.5.0", "@vercel/otel": "^2.1.2", - "@vercel/speed-insights": "^1.2.0", "@xterm/addon-canvas": "^0.7.0", "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^6.0.0", - "cheerio": "^1.0.0", "chrono-node": "^2.8.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -68,7 +64,6 @@ "e2b": "^2.28.0", "echarts": "^6.0.0", "echarts-for-react": "^3.0.2", - "fast-xml-parser": "^5.3.5", "immer": "^10.1.1", "input-otp": "^1.4.2", "jose": "^6.2.3", @@ -108,7 +103,6 @@ "devDependencies": { "@biomejs/biome": "^2.4.4", "@playwright/test": "^1.59.1", - "@posthog/nextjs-config": "^1.9.55", "@tailwindcss/postcss": "^4.0.15", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.2", @@ -437,8 +431,6 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.7", "", { "os": "win32", "cpu": "x64" }, "sha512-J4WlM72NMk076Qsg0jTdK3SNXatlSdnjW7L7oNGLst1tAGjHrJh/FYi+pw9wyIjEtGRKDNzD0zuiY16oWYWVaw=="], - "@next/third-parties": ["@next/third-parties@16.2.10", "", { "dependencies": { "third-party-capital": "1.0.20" }, "peerDependencies": { "next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0-beta.0", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0" } }, "sha512-H3yxCMLziM1JKXnjQv83WBH2cp1fB1vMxpC1/bBTpvvaesBEuRuprpzq5RI32atis0VhUZflgdpJdGNZIGpQaQ=="], - "@openfeature/core": ["@openfeature/core@1.11.0", "", {}, "sha512-P0u3/ht/oZCQT89fOed+laLk0kZR529a825cS02uPDglxXbE97irWYpDAeRGGVETIzKfuy+H2g8c3Ccv/tXJNQ=="], "@openfeature/server-sdk": ["@openfeature/server-sdk@1.22.0", "", { "peerDependencies": { "@openfeature/core": "^1.11.0" } }, "sha512-YBrf6SQkn0FNB/dRAtLEs41dvFMUE8CrQTwI+iLaMFUIqWlqGNJfGnulKSneEKS+2OgKTAC6DdmKcZ6tK7kBcg=="], @@ -699,18 +691,8 @@ "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], - "@posthog/cli": ["@posthog/cli@0.7.22", "", { "dependencies": { "detect-libc": "^2.1.2" }, "bin": { "posthog-cli": "run-posthog-cli.js" } }, "sha512-hqFQv7d6gstI/nW3kgefWWxoiQUgl2Tt4hrNLE0LOPKOvTQ0fZc5P85NZKWbpOk4NEiEDZFwP/5EN6zEw7Rihg=="], - "@posthog/core": ["@posthog/core@1.5.1", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-8fdEzfvdStr45iIncTD+gnqp45UBTUpRK/bwB4shP5usCKytnPIeilU8rIpNBOVjJPwfW+2N8yWhQ0l14x191Q=="], - "@posthog/nextjs-config": ["@posthog/nextjs-config@1.9.66", "", { "dependencies": { "@posthog/cli": "~0.7.21", "@posthog/plugin-utils": "1.1.1", "@posthog/webpack-plugin": "1.5.22", "semver": "^7.7.2" }, "peerDependencies": { "next": ">12.1.0" } }, "sha512-iUzaUvl1wgL+sE24ao3QSZGD/aHoIyK8bSa7q81BqJemSfoezEMBR0wXmnFWHqg2jfFrwrHjphsGqY2MQhQjAA=="], - - "@posthog/plugin-utils": ["@posthog/plugin-utils@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-vCbaFeuwf9Pc0gI5bkCGvkOn2Bxru2KbZJtOa6loTJjanCNoMsjECEPijr7X5oln1IIg+VKnGiwV4tKY2b7NuQ=="], - - "@posthog/types": ["@posthog/types@1.386.3", "", {}, "sha512-LqJoiQi2eyWn7rCUgnn+D+F3Efp6+04o72bjSX6kWHx0nFaYNC/nJuAIRliDTY/X7GPIUAaHAcSjbMI/9wfX1Q=="], - - "@posthog/webpack-plugin": ["@posthog/webpack-plugin@1.5.22", "", { "dependencies": { "@posthog/cli": "~0.7.21", "@posthog/core": "1.32.3", "@posthog/plugin-utils": "1.1.1" }, "peerDependencies": { "webpack": "^5" } }, "sha512-vXlu73m90sAOkWzcdnIhJY9PnI5YXR+TfXe2Ssk4tUMpO/sMp1kOLc8S64dwUgEUISnE1hSJWQmmciLn3BKTXQ=="], - "@preact/signals": ["@preact/signals@1.3.2", "", { "dependencies": { "@preact/signals-core": "^1.7.0" }, "peerDependencies": { "preact": "10.x" } }, "sha512-naxcJgUJ6BTOROJ7C3QML7KvwKwCXQJYTc5L/b0eEsdYgPB6SxwoQ1vDGcS0Q7GVjAenVq/tXrybVdFShHYZWg=="], "@preact/signals-core": ["@preact/signals-core@1.12.1", "", {}, "sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA=="], @@ -1013,8 +995,6 @@ "@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="], - "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], - "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/memcached": ["@types/memcached@2.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg=="], @@ -1053,12 +1033,8 @@ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - "@vercel/analytics": ["@vercel/analytics@1.5.0", "", { "peerDependencies": { "@remix-run/react": "^2", "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@remix-run/react", "@sveltejs/kit", "next", "react", "svelte", "vue", "vue-router"] }, "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g=="], - "@vercel/otel": ["@vercel/otel@2.1.2", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "@opentelemetry/api-logs": ">=0.200.0 <0.300.0", "@opentelemetry/instrumentation": ">=0.200.0 <0.300.0", "@opentelemetry/resources": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-logs": ">=0.200.0 <0.300.0", "@opentelemetry/sdk-metrics": ">=2.0.0 <3.0.0", "@opentelemetry/sdk-trace-base": ">=2.0.0 <3.0.0" } }, "sha512-PbGyq1lLwWbnftylNcQ6KFxc7DLc8LJdnaU3snM43bgXiWkWsHrgaU/LdUD8sHaSgG8UKuydX/aymNt3J+hzuA=="], - "@vercel/speed-insights": ["@vercel/speed-insights@1.2.0", "", { "peerDependencies": { "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@sveltejs/kit", "next", "react", "svelte", "vue", "vue-router"] }, "sha512-y9GVzrUJ2xmgtQlzFP2KhVRoCglwfRQgjyfY607aU0hh0Un6d0OUyrJkjuAlsV18qR4zfoFPs/BiIj9YDS6Wzw=="], - "@vitest/coverage-v8": ["@vitest/coverage-v8@3.2.4", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@bcoe/v8-coverage": "^1.0.2", "ast-v8-to-istanbul": "^0.3.3", "debug": "^4.4.1", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", "istanbul-lib-source-maps": "^5.0.6", "istanbul-reports": "^3.1.7", "magic-string": "^0.30.17", "magicast": "^0.3.5", "std-env": "^3.9.0", "test-exclude": "^7.0.1", "tinyrainbow": "^2.0.0" }, "peerDependencies": { "@vitest/browser": "3.2.4", "vitest": "3.2.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ=="], "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], @@ -1077,60 +1053,22 @@ "@vitest/utils": ["@vitest/utils@3.0.7", "", { "dependencies": { "@vitest/pretty-format": "3.0.7", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" } }, "sha512-xePVpCRfooFX3rANQjwoditoXgWb1MaFbzmGuPP59MK6i13mrnDw/yEIyJudLeW6/38mCNcwCiJIGmpDPibAIg=="], - "@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="], - - "@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="], - - "@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="], - - "@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="], - - "@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="], - - "@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="], - - "@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="], - - "@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="], - - "@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="], - - "@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="], - - "@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="], - - "@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="], - - "@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="], - - "@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="], - - "@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="], - "@xterm/addon-canvas": ["@xterm/addon-canvas@0.7.0", "", { "peerDependencies": { "@xterm/xterm": "^5.0.0" } }, "sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw=="], "@xterm/addon-webgl": ["@xterm/addon-webgl@0.19.0", "", {}, "sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A=="], "@xterm/xterm": ["@xterm/xterm@6.0.0", "", {}, "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg=="], - "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="], - - "@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="], - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], - "acorn-import-phases": ["acorn-import-phases@1.0.4", "", { "peerDependencies": { "acorn": "^8.14.0" } }, "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ=="], - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], - "ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="], - "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -1161,13 +1099,11 @@ "bippy": ["bippy@0.3.34", "", { "dependencies": { "@types/react-reconciler": "^0.28.9" }, "peerDependencies": { "react": ">=17.0.1" } }, "sha512-vmptmU/20UdIWHHhq7qCSHhHzK7Ro3YJ1utU0fBG7ujUc58LEfTtilKxcF0IOgSjT5XLcm7CBzDjbv4lcKApGQ=="], - "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + "browserslist": ["browserslist@4.27.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", "electron-to-chromium": "^1.5.238", "node-releases": "^2.0.26", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw=="], "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], @@ -1191,14 +1127,8 @@ "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], - "cheerio": ["cheerio@1.1.2", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg=="], - - "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], - "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], - "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], - "chrono-node": ["chrono-node@2.9.0", "", {}, "sha512-glI4YY2Jy6JII5l3d5FN6rcrIbKSQqKPhWsIRYPK2IK8Mm4Q1ZZFdYIaDqglUNf7gNwG+kWIzTn0omzzE0VkvQ=="], "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], @@ -1237,12 +1167,8 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], - "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], - "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], @@ -1319,14 +1245,12 @@ "echarts-for-react": ["echarts-for-react@3.0.2", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "size-sensor": "^1.0.1" }, "peerDependencies": { "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0", "react": "^15.0.0 || >=16.0.0" } }, "sha512-DRwIiTzx8JfwPOVgGttDytBqdp5VzCSyMRIxubgU/g2n9y3VLUmF2FK7Icmg/sNVkv4+rktmrLN9w22U2yy3fA=="], - "electron-to-chromium": ["electron-to-chromium@1.5.372", "", {}, "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA=="], + "electron-to-chromium": ["electron-to-chromium@1.5.245", "", {}, "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ=="], "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], - "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], - "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], @@ -1341,18 +1265,10 @@ "escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], - "eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], - - "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], - - "estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="], "expect-type": ["expect-type@1.2.2", "", {}, "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA=="], @@ -1369,8 +1285,6 @@ "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], - "fast-xml-parser": ["fast-xml-parser@5.3.5", "", { "dependencies": { "strnum": "^2.1.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-JeaA2Vm9ffQKp9VjvfzObuMCjUYAp5WDYhRYL5LrBPY/jUDlUtOvDfot0vKSkB9tuX885BDHjtw4fZadD95wnA=="], - "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -1409,8 +1323,6 @@ "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], - "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], - "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -1441,8 +1353,6 @@ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - "immer": ["immer@10.2.0", "", {}, "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw=="], "import-in-the-middle": ["import-in-the-middle@3.0.2", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-LGLYRl0A2gtyUJb2WDliBHmk6TtlHwdDjxonacZ8QrEs/ZW+YDgNv2QAfjRQWpS8HqvNcq6GGnN6jrOa5FysDQ=="], @@ -1487,8 +1397,6 @@ "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="], - "jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="], - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], @@ -1541,8 +1449,6 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], - "loader-runner": ["loader-runner@4.3.2", "", {}, "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w=="], - "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], @@ -1571,8 +1477,6 @@ "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], - "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], - "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], @@ -1585,8 +1489,6 @@ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -1611,8 +1513,6 @@ "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], - "neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="], - "next": ["next@16.2.7", "", { "dependencies": { "@next/env": "16.2.7", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.7", "@next/swc-darwin-x64": "16.2.7", "@next/swc-linux-arm64-gnu": "16.2.7", "@next/swc-linux-arm64-musl": "16.2.7", "@next/swc-linux-x64-gnu": "16.2.7", "@next/swc-linux-x64-musl": "16.2.7", "@next/swc-win32-arm64-msvc": "16.2.7", "@next/swc-win32-x64-msvc": "16.2.7", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-eMJxgjRzBaj3olkP4cBamHDXL79A8FC6u1GcsO1D1Tsx8bw/LLXUJCaoajVxtnhD3A1IJqIT8IcRJjgBIPJq4w=="], "next-safe-action": ["next-safe-action@8.0.11", "", { "peerDependencies": { "next": ">= 14.0.0", "react": ">= 18.2.0", "react-dom": ">= 18.2.0" } }, "sha512-gqJLmnQLAoFCq1kRBopN46New+vx1n9J9Y/qDQLXpv/VqU40AWxDakvshwwnWAt8R0kLvlakNYNLX5PqlXWSMg=="], @@ -1623,9 +1523,7 @@ "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], - "node-releases": ["node-releases@2.0.47", "", {}, "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og=="], - - "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "nuqs": ["nuqs@2.7.2", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^6 || ^7", "react-router-dom": "^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-wOPJoz5om7jMJQick9zU1S/Q+joL+B2DZTZxfCleHEcUzjUnPoujGod4+nAmUWb+G9TwZnyv+mfNqlyfEi8Zag=="], @@ -1655,10 +1553,6 @@ "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], - "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], - - "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], @@ -1785,14 +1679,10 @@ "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - "schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="], - "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="], "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -1861,8 +1751,6 @@ "strip-literal": ["strip-literal@3.1.0", "", { "dependencies": { "js-tokens": "^9.0.1" } }, "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg=="], - "strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], - "style-to-js": ["style-to-js@1.1.19", "", { "dependencies": { "style-to-object": "1.0.12" } }, "sha512-Ev+SgeqiNGT1ufsXyVC5RrJRXdrkRJ1Gol9Qw7Pb72YCKJXrBvP0ckZhBeVSrw2m06DJpei2528uIpjMb4TsoQ=="], "style-to-object": ["style-to-object@1.0.12", "", { "dependencies": { "inline-style-parser": "0.2.6" } }, "sha512-ddJqYnoT4t97QvN2C95bCgt+m7AAgXjVnkk/jxAfmp7EAB8nnqqZYEbMd3em7/vEomDb2LAQKAy1RFfv41mdNw=="], @@ -1887,12 +1775,8 @@ "terser": ["terser@5.44.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw=="], - "terser-webpack-plugin": ["terser-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ=="], - "test-exclude": ["test-exclude@7.0.1", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^9.0.4" } }, "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg=="], - "third-party-capital": ["third-party-capital@1.0.20", "", {}, "sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA=="], - "thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="], "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], @@ -1951,7 +1835,7 @@ "unplugin": ["unplugin@2.1.0", "", { "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" } }, "sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], "uri-js-replace": ["uri-js-replace@1.0.1", "", {}, "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g=="], @@ -1985,22 +1869,14 @@ "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], - "watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="], - "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="], - "webpack": ["webpack@5.107.2", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.22.0", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "loader-runner": "^4.3.2", "mime-db": "^1.54.0", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.5.0", "watchpack": "^2.5.1", "webpack-sources": "^3.5.0" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ=="], - - "webpack-sources": ["webpack-sources@3.5.0", "", {}, "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ=="], - "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], - "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], - "whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="], "whatwg-url": ["whatwg-url@13.0.0", "", { "dependencies": { "tr46": "^4.1.1", "webidl-conversions": "^7.0.0" } }, "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig=="], @@ -2043,8 +1919,6 @@ "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/helper-compilation-targets/browserslist": ["browserslist@4.27.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", "electron-to-chromium": "^1.5.238", "node-releases": "^2.0.26", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw=="], - "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -2275,8 +2149,6 @@ "@ory/nextjs/@ory/client-fetch": ["@ory/client-fetch@1.22.22", "", {}, "sha512-MEoPKIRWETj6mTsJnWVCMJTFpZ/40885YZJUEc1vM7v7AwjLBgJPE7xsm2QvSYCSAiLBzbROETxEtvZiRCzYJw=="], - "@posthog/webpack-plugin/@posthog/core": ["@posthog/core@1.32.3", "", { "dependencies": { "@posthog/types": "1.386.3" } }, "sha512-vwOEMfZvGv5XxNWV7p9I52NSmvFNMhyW2IHpIoUHW5jLkgUrknzJW1H/qxVGSIrNNVQkfsoaDFzDhJdg10pgrA=="], - "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], "@radix-ui/react-checkbox/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], @@ -2405,13 +2277,7 @@ "ast-v8-to-istanbul/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="], - "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001799", "", {}, "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw=="], - - "cheerio/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - - "cheerio/undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], - - "cheerio/whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], + "browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="], "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -2421,8 +2287,6 @@ "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - "esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], - "framer-motion/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], @@ -2453,8 +2317,6 @@ "jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="], - "knip/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], @@ -2463,10 +2325,6 @@ "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - "parse5-htmlparser2-tree-adapter/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - - "parse5-parser-stream/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - "path-scurry/lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="], "postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], @@ -2523,12 +2381,6 @@ "vitest/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "webpack/acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], - - "webpack/enhanced-resolve": ["enhanced-resolve@5.24.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ=="], - - "webpack/es-module-lexer": ["es-module-lexer@2.1.0", "", {}, "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ=="], - "whatwg-url/webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -2541,14 +2393,6 @@ "yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - "@babel/helper-compilation-targets/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.25", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA=="], - - "@babel/helper-compilation-targets/browserslist/electron-to-chromium": ["electron-to-chromium@1.5.245", "", {}, "sha512-rdmGfW47ZhL/oWEJAY4qxRtdly2B98ooTJ0pdEI4jhVLZ6tNf8fPtov2wS1IRKwFJT92le3x4Knxiwzl7cPPpQ=="], - - "@babel/helper-compilation-targets/browserslist/node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], - - "@babel/helper-compilation-targets/browserslist/update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], - "@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], "@bramus/specificity/css-tree/mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], @@ -2717,8 +2561,6 @@ "@tailwindcss/postcss/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "cheerio/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -2741,10 +2583,6 @@ "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "parse5-htmlparser2-tree-adapter/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - - "parse5-parser-stream/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "react-scan/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], "react-scan/playwright/playwright-core": ["playwright-core@1.56.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ=="], @@ -2759,8 +2597,6 @@ "vite/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "webpack/enhanced-resolve/tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], diff --git a/next.config.ts b/next.config.ts index 56ed374fa..e537e129b 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,8 +1,5 @@ -// NOTE: related to src/configs/rewrites.ts import path from 'node:path' -import { withPostHogConfig } from '@posthog/nextjs-config' import type { NextConfig } from 'next' -import { DOCUMENTATION_DOMAIN } from './src/configs/documentation' const browserStub = (file: string) => path.resolve(process.cwd(), 'stubs', file) @@ -78,77 +75,7 @@ const config: NextConfig = { ], }, ], - rewrites: async () => ({ - beforeFiles: [ - { - source: '/ph-proxy/static/:path*', - destination: 'https://us-assets.i.posthog.com/static/:path*', - }, - { - source: '/ph-proxy/:path*', - destination: 'https://us.i.posthog.com/:path*', - }, - - // Asset rewrites for Mintlify - { - source: '/mintlify-assets/:path*', - destination: `https://${DOCUMENTATION_DOMAIN}/mintlify-assets/:path*`, - }, - { - source: '/_mintlify/:path*', - destination: `https://${DOCUMENTATION_DOMAIN}/_mintlify/:path*`, - }, - // LLMs.txt - { - source: '/llms.txt', - destination: `https://${DOCUMENTATION_DOMAIN}/llms.txt`, - }, - { - source: '/llms-full.txt', - destination: `https://${DOCUMENTATION_DOMAIN}/llms-full.txt`, - }, - ], - }), - redirects: async () => [ - { - source: '/docs/api/cli', - destination: '/auth/cli', - permanent: true, - }, - { - source: '/auth/sign-in', - destination: '/sign-in', - permanent: true, - }, - { - source: '/auth/sign-up', - destination: '/sign-up', - permanent: true, - }, - // SEO Redirects - { - source: '/ai-agents/:path*', - destination: '/', - permanent: true, - }, - { - source: - '/blog/how-perplexity-implemented-advanced-data-analysis-for-pro-users-in-1-week', - destination: '/blog/category/case-studies', - permanent: true, - }, - ], skipTrailingSlashRedirect: true, } -export default withPostHogConfig(config, { - personalApiKey: process.env.POSTHOG_API_KEY ?? '', - projectId: process.env.POSTHOG_PROJECT_ID, - host: 'https://us.posthog.com', - sourcemaps: { - enabled: Boolean(process.env.POSTHOG_API_KEY), - releaseName: 'dashboard', - releaseVersion: process.env.VERCEL_GIT_COMMIT_SHA, - deleteAfterUpload: true, - }, -}) +export default config diff --git a/package.json b/package.json index e80afd4d3..db7376ad0 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "@launchdarkly/node-server-sdk": "^9.11.2", "@launchdarkly/openfeature-node-server": "^1.2.0", "@next/env": "^16.2.7", - "@next/third-parties": "^16.2.10", "@openfeature/server-sdk": "^1.22.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/auto-instrumentations-node": "^0.77.0", @@ -92,13 +91,10 @@ "@trpc/server": "^11.7.1", "@trpc/tanstack-react-query": "^11.7.1", "@types/micromatch": "^4.0.9", - "@vercel/analytics": "^1.5.0", "@vercel/otel": "^2.1.2", - "@vercel/speed-insights": "^1.2.0", "@xterm/addon-canvas": "^0.7.0", "@xterm/addon-webgl": "^0.19.0", "@xterm/xterm": "^6.0.0", - "cheerio": "^1.0.0", "chrono-node": "^2.8.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -108,7 +104,6 @@ "e2b": "^2.28.0", "echarts": "^6.0.0", "echarts-for-react": "^3.0.2", - "fast-xml-parser": "^5.3.5", "immer": "^10.1.1", "input-otp": "^1.4.2", "jose": "^6.2.3", @@ -148,7 +143,6 @@ "devDependencies": { "@biomejs/biome": "^2.4.4", "@playwright/test": "^1.59.1", - "@posthog/nextjs-config": "^1.9.55", "@tailwindcss/postcss": "^4.0.15", "@testing-library/dom": "^10.4.1", "@testing-library/react": "^16.3.2", diff --git a/src/app/(rewrites)/[[...slug]]/not-found.tsx b/src/app/(rewrites)/[[...slug]]/not-found.tsx deleted file mode 100644 index cb158ef70..000000000 --- a/src/app/(rewrites)/[[...slug]]/not-found.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Metadata } from 'next' -import NotFound from '@/ui/not-found' - -export const metadata: Metadata = { - title: '404 - Page Not Found', - description: - 'The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.', - robots: 'noindex, nofollow', -} - -export default function RootNotFound() { - return -} diff --git a/src/app/(rewrites)/[[...slug]]/route.ts b/src/app/(rewrites)/[[...slug]]/route.ts deleted file mode 100644 index 6882a62dd..000000000 --- a/src/app/(rewrites)/[[...slug]]/route.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { NextRequest } from 'next/server' -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' -import { BASE_URL } from '@/configs/urls' -import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger' -import { - getRewriteForPath, - rewriteContentPagesHtml, -} from '@/lib/utils/rewrites' - -export const dynamic = 'force-dynamic' - -const REVALIDATE_TIME = 900 // 15 minutes ttl -const CDN_CACHE_CONTROL = `public, s-maxage=${REVALIDATE_TIME}, stale-while-revalidate=${REVALIDATE_TIME}` - -function getRewriteRequestHeaders(): Headers { - return new Headers({ - Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - }) -} - -function setRewriteCacheHeaders(headers: Headers): void { - headers.delete('set-cookie') - headers.delete('set-cookie2') - headers.set('Cache-Control', 'public, max-age=0, must-revalidate') - headers.set('CDN-Cache-Control', CDN_CACHE_CONTROL) - headers.set('Vercel-CDN-Cache-Control', CDN_CACHE_CONTROL) -} - -export async function GET(request: NextRequest): Promise { - const url = new URL(request.url) - - // Next.js (versions > 15.3.0) can alias the root path ("/") in `request.url` to - // the corresponding file path (e.g., "/index") during internal processing. - // This is a hotfix to normalize the pathname back to "/". We previously tried - // using `request.nextUrl.pathname` to preserve the original client-requested URL, - // but that approach did not work as well in practice. - // This issue does only seem to occur in Vercel Production builds, not in local development or preview deployments. - - // NOTE - Not sure if this is a bug or intended from the Next.js team, but it is what it is. - // We need to handle this case because @rewrites.ts has a config for rewriting "/" (index), - // and without this normalization, we would get a 404 in production when Next.js aliases "/" to "/index". - if (url.pathname === '/index') { - url.pathname = '/' - } - - const updateUrlHostname = (newHostname: string) => { - url.hostname = newHostname - url.port = '' - url.protocol = 'https' - } - - const { config, rule } = getRewriteForPath(url.pathname, 'route') - - if (config) { - if (rule?.pathPreprocessor) { - url.pathname = rule.pathPreprocessor(url.pathname) - } - updateUrlHostname(config.domain) - } - - try { - const notFound = !config - const fetchUrl = notFound ? `${BASE_URL}/not-found` : url.toString() - - const res = await fetch(fetchUrl, { - headers: notFound - ? new Headers(request.headers) - : getRewriteRequestHeaders(), - redirect: 'follow', - ...(notFound - ? { cache: 'no-store' } - : { - next: { - revalidate: REVALIDATE_TIME, - }, - }), - }) - - const contentType = res.headers.get('Content-Type') - const newHeaders = new Headers(res.headers) - - if (!notFound) { - setRewriteCacheHeaders(newHeaders) - } - - if (contentType?.startsWith('text/html')) { - let html = await res.text() - - // remove content-encoding header to ensure proper rendering - newHeaders.delete('content-encoding') - newHeaders.delete('content-length') - - // rewrite absolute URLs pointing to the rewritten domain to relative paths and with correct SEO tags - if (config) { - const rewrittenPrefix = `https://${config.domain}` - - html = rewriteContentPagesHtml(html, { - seo: { - pathname: url.pathname, - allowIndexing: ALLOW_SEO_INDEXING, - }, - hrefPrefixes: [rewrittenPrefix, 'https://e2b.dev'], - stripAnalytics: process.env.VERCEL_ENV !== 'production', - }) - } - - // create a new response with the modified HTML - const modifiedResponse = new Response(html, { - status: notFound ? 404 : res.status, - headers: newHeaders, - }) - - return modifiedResponse - } - - return new Response(res.body, { - status: res.status, - statusText: res.statusText, - headers: newHeaders, - }) - } catch (error) { - l.error({ - key: 'url_rewrite:unexpected_error', - error: serializeErrorForLog(error), - }) - - return new Response( - `Proxy Error: ${error instanceof Error ? error.message : 'Unknown error'}`, - { - status: 502, - headers: { 'Content-Type': 'text/plain' }, - } - ) - } -} diff --git a/src/app/(rewrites)/not-found/page.tsx b/src/app/(rewrites)/not-found/page.tsx deleted file mode 100644 index 461d6b490..000000000 --- a/src/app/(rewrites)/not-found/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { Metadata } from 'next' -import NotFound from '@/ui/not-found' - -export const metadata: Metadata = { - title: '404 - Page Not Found', - description: - 'The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.', - robots: 'noindex, nofollow', -} - -export default function NotFoundShell() { - return -} diff --git a/src/app/body.tsx b/src/app/body.tsx index 911994f0b..6f60f84c6 100644 --- a/src/app/body.tsx +++ b/src/app/body.tsx @@ -1,25 +1,13 @@ 'use client' -import { useParams } from 'next/navigation' import type { ReactNode } from 'react' -import { cn } from '@/lib/utils' export function Body({ children, }: { children: ReactNode }): React.ReactElement { - const mode = useMode() - return ( - - {children} - + {children} ) } - -export function useMode(): string | undefined { - const { slug } = useParams() - - return Array.isArray(slug) && slug.length > 0 ? slug[0] : undefined -} diff --git a/src/app/dashboard/[teamSlug]/layout.tsx b/src/app/dashboard/[teamSlug]/layout.tsx index c76a2f988..d508b6a21 100644 --- a/src/app/dashboard/[teamSlug]/layout.tsx +++ b/src/app/dashboard/[teamSlug]/layout.tsx @@ -11,7 +11,6 @@ import { FeatureFlagsProvider } from '@/core/modules/feature-flags/feature-flags import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server' import { getAuthContext } from '@/core/server/auth' import DashboardLayoutView from '@/features/dashboard/layouts/layout' -import { DashboardPostHogErrorBoundary } from '@/features/dashboard/posthog-error-boundary' import { ProjectsAnnouncementBanner } from '@/features/dashboard/projects-announcement/banner' import Sidebar from '@/features/dashboard/sidebar/sidebar' import { @@ -20,6 +19,7 @@ import { prefetchAsync, trpc, } from '@/trpc/server' +import { CatchErrorBoundary } from '@/ui/error' import { SidebarInset, SidebarProvider } from '@/ui/primitives/sidebar' export const metadata: Metadata = { @@ -102,11 +102,11 @@ export default async function DashboardLayout({ announcement banner above stays fully visible. */} - + {children} - + diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index 4b40d9237..0f7cb255c 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -1,7 +1,5 @@ 'use client' -import posthog from 'posthog-js' -import { useEffect } from 'react' import ErrorBoundary from '@/ui/error' export default function GlobalError({ @@ -9,32 +7,10 @@ export default function GlobalError({ }: { error: Error & { digest?: string } }) { - useEffect(() => { - const key = process.env.NEXT_PUBLIC_POSTHOG_KEY - if (!key) return - - // global-error renders outside RootLayout/ClientProviders, so PostHogProvider - // never ran posthog.init. Initialize a minimal client here so root-layout and - // provider failures are still reported instead of silently dropped. - if (!posthog.__loaded) { - posthog.init(key, { - api_host: '/ph-proxy', - ui_host: 'https://us.posthog.com', - capture_exceptions: false, - }) - posthog.register({ - environment: process.env.NEXT_PUBLIC_VERCEL_ENV ?? 'development', - }) - } - - posthog.captureException(error) - }, [error]) - return ( ) } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1715483f7..9610e85e0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,9 +1,6 @@ import '@/app/fonts' import '@/styles/globals.css' -import { GoogleTagManager } from '@next/third-parties/google' -import { Analytics } from '@vercel/analytics/next' -import { SpeedInsights } from '@vercel/speed-insights/next' import type { Metadata } from 'next/types' import { Suspense } from 'react' import { getFaviconIcons } from '@/configs/favicon' @@ -11,17 +8,6 @@ import ClientProviders from '@/features/client-providers' import { Toaster } from '@/ui/primitives/toaster' import { Body } from './body' -function getPostHogEnvironment() { - switch (process.env.VERCEL_ENV) { - case 'production': - case 'preview': - case 'development': - return process.env.VERCEL_ENV - default: - return 'development' - } -} - export const metadata: Metadata = { icons: { icon: getFaviconIcons(process.env.VERCEL_ENV), @@ -33,25 +19,15 @@ export default function RootLayout({ }: { children: React.ReactNode }) { - const postHogEnabled = !!process.env.NEXT_PUBLIC_POSTHOG_KEY - const postHogEnvironment = getPostHogEnvironment() - const gtmId = process.env.NEXT_PUBLIC_GTM_ID - return ( - {gtmId && } - + {children} - - ) diff --git a/src/app/robots.ts b/src/app/robots.ts index 85bbf22e1..d1293f262 100644 --- a/src/app/robots.ts +++ b/src/app/robots.ts @@ -1,19 +1,10 @@ import type { MetadataRoute } from 'next' -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' export default function robots(): MetadataRoute.Robots { - if (!ALLOW_SEO_INDEXING) { - // We serve an empty robots.txt for a 200 status code - return { - rules: {}, - } - } - return { rules: { userAgent: '*', - allow: '/', + disallow: '/', }, - sitemap: `https://e2b.dev/sitemap.xml`, } } diff --git a/src/app/sbx/new/route.ts b/src/app/sbx/new/route.ts deleted file mode 100644 index 26710beb1..000000000 --- a/src/app/sbx/new/route.ts +++ /dev/null @@ -1,76 +0,0 @@ -import Sandbox from 'e2b' -import { type NextRequest, NextResponse } from 'next/server' -import { authHeaders } from '@/configs/api' -import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls' -import { getAuthContext } from '@/core/server/auth' -import { resolveUserTeam } from '@/core/server/functions/team/resolve-user-team' -import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger' -import { normalizeTerminalTemplate } from '@/features/dashboard/terminal/template' - -export const GET = async (req: NextRequest) => { - try { - const requestUrl = new URL(req.url) - const template = normalizeTerminalTemplate( - requestUrl.searchParams.get('template') ?? undefined - ) - - if (!template) { - return NextResponse.redirect(new URL(req.url).origin) - } - - const authContext = await getAuthContext() - - if (!authContext) { - const params = new URLSearchParams({ - returnTo: `${requestUrl.pathname}${requestUrl.search}`, - }) - - return NextResponse.redirect( - new URL(`${AUTH_URLS.SIGN_IN}?${params.toString()}`, req.url) - ) - } - - const team = await resolveUserTeam( - authContext.user.id, - authContext.accessToken - ) - - if (!team) { - return NextResponse.redirect(new URL(req.url).origin) - } - - const sbx = await Sandbox.create(template, { - apiUrl: process.env.NEXT_PUBLIC_INFRA_API_URL, - domain: process.env.NEXT_PUBLIC_E2B_DOMAIN, - apiHeaders: { - ...authHeaders(authContext.accessToken, team.id), - }, - }) - - const terminalParams = new URLSearchParams({ template }) - const command = requestUrl.searchParams.get('command')?.trim() - - if (command) { - terminalParams.set('command', command) - } - - const terminalUrl = PROTECTED_URLS.SANDBOX_TERMINAL( - team.slug, - sbx.sandboxId - ) - - return NextResponse.redirect( - new URL(`${terminalUrl}?${terminalParams.toString()}`, req.url) - ) - } catch (error) { - l.warn( - { - key: 'sbx_new:unexpected_error', - error: serializeErrorForLog(error), - }, - `sbx_new: unexpected error` - ) - - return NextResponse.redirect(new URL(req.url).origin) - } -} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts deleted file mode 100644 index d8e8e9aae..000000000 --- a/src/app/sitemap.ts +++ /dev/null @@ -1,295 +0,0 @@ -/** - * Sitemap Generator for E2B Website - * - * This module generates a unified sitemap for the E2B website by aggregating sitemaps - * from multiple sources including the main landing page, blog, documentation, and Framer sites. - * It handles fetching, parsing, deduplication, and proper URL formatting to ensure all content - * is discoverable by search engines. - */ - -import { XMLParser } from 'fast-xml-parser' -import type { MetadataRoute } from 'next' -import { DOCUMENTATION_DOMAIN } from '@/configs/documentation' -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' -import { LANDING_PAGE_DOMAIN, ROUTE_REWRITE_CONFIG } from '@/configs/rewrites' -import { SITEMAP_EXCLUDE_CONFIG } from '@/configs/sitemap' -import type { DomainConfig } from '@/types/rewrites.types' - -// Cache the sitemap for 15 minutes (in seconds) -const SITEMAP_CACHE_TIME = 15 * 60 - -/** - * Valid change frequency values for sitemap entries - * @see https://www.sitemaps.org/protocol.html - */ -type ChangeFrequency = - | 'always' - | 'hourly' - | 'daily' - | 'weekly' - | 'monthly' - | 'yearly' - | 'never' - -/** - * Configuration for a site whose sitemap should be included - */ -type Site = { - sitemapUrl: string // URL to the site's sitemap.xml - lastModified?: string | Date // Default last modified date for entries - changeFrequency?: ChangeFrequency // Default change frequency for entries - priority?: number // Default priority for entries (0.0 to 1.0) - baseUrl?: string // Base URL to use for final sitemap entries -} - -/** - * List of sites to include in the unified sitemap - * Each site has its own sitemap.xml that will be fetched and processed - */ -const sites: Site[] = [ - { - sitemapUrl: `https://${LANDING_PAGE_DOMAIN}/sitemap.xml`, - priority: 1.0, - changeFrequency: 'weekly', - baseUrl: 'https://e2b.dev', - }, - { - sitemapUrl: `https://${DOCUMENTATION_DOMAIN}/sitemap.xml`, - priority: 0.9, - changeFrequency: 'weekly', - baseUrl: 'https://e2b.dev', - }, -] - -/** - * Structure of a single URL entry in a sitemap - */ -type SitemapData = { - loc: string // URL location - lastmod?: string | Date // Last modified date - changefreq?: ChangeFrequency // Change frequency - priority?: number // Priority (0.0 to 1.0) -} - -/** - * Structure of a sitemap XML document - */ -type Sitemap = { - urlset: { - url: SitemapData | SitemapData[] // Single URL or array of URLs - } -} - -/** - * Fetches and parses a sitemap XML file from a given URL - * - * @param url The URL of the sitemap.xml file to fetch - * @returns Parsed sitemap data or empty sitemap on error - */ -async function getXmlData(url: string): Promise { - const parser = new XMLParser() - - try { - const response = await fetch(url, { - next: { revalidate: SITEMAP_CACHE_TIME }, - headers: { - Accept: 'application/xml', - }, - }) - - if (!response.ok) { - console.warn(`Failed to fetch sitemap from ${url}:`, response.statusText) - return { urlset: { url: [] } } - } - - const text = await response.text() - return parser.parse(text) as Sitemap - } catch (error) { - console.error(`Error fetching sitemap from ${url}:`, error) - return { urlset: { url: [] } } - } -} - -function matchesPathPrefix(path: string, prefix: string): boolean { - return path === prefix || path.startsWith(`${prefix}/`) -} - -/** - * Finds the corresponding rewrite configuration for a given site based on its sitemap URL domain. - * - * @param site The site configuration. - * @returns The matching DomainConfig or undefined if not found. - */ -function findRewriteConfig(site: Site): DomainConfig | undefined { - try { - const siteDomain = new URL(site.sitemapUrl).hostname - return ROUTE_REWRITE_CONFIG.find((config) => config.domain === siteDomain) - } catch (error) { - console.error( - `Error parsing sitemapUrl ${site.sitemapUrl} to find rewrite config:`, - error - ) - return undefined - } -} - -function findExcludedPathPrefixes(site: Site): string[] { - try { - const siteDomain = new URL(site.sitemapUrl).hostname - return ( - SITEMAP_EXCLUDE_CONFIG.find((config) => config.domain === siteDomain) - ?.excludedPathPrefixes ?? [] - ) - } catch (error) { - console.error( - `Error parsing sitemapUrl ${site.sitemapUrl} to find sitemap exclusions:`, - error - ) - return [] - } -} - -/** - * Processes a site's sitemap and converts it to Next.js sitemap format - * Applies path preprocessing based on ROUTE_REWRITE_CONFIG. - * - * @param site The site configuration to process - * @returns Array of sitemap entries in Next.js format - */ -async function getSitemap(site: Site): Promise { - const data = await getXmlData(site.sitemapUrl) - const rewriteConfig = findRewriteConfig(site) // Find the rewrite config for this site - const excludedPathPrefixes = findExcludedPathPrefixes(site) - - if (!data || !site.baseUrl) { - // Ensure baseUrl is defined, as it's crucial for constructing final URLs - if (!site.baseUrl) { - console.warn( - `Site ${site.sitemapUrl} is missing baseUrl, skipping sitemap generation for this site.` - ) - } - return [] - } - - /** - * Processes a single URL entry from the sitemap - */ - const processUrl = ( - line: SitemapData - ): MetadataRoute.Sitemap[number] | null => { - try { - const originalUrl = new URL(line.loc) - const rewrittenPathname = originalUrl.pathname - let finalPathname = rewrittenPathname // Default to the path from the sitemap - - if ( - excludedPathPrefixes.some((prefix) => - matchesPathPrefix(rewrittenPathname, prefix) - ) - ) { - return null - } - - // Find the corresponding original path if a preprocessor was involved - if (rewriteConfig) { - for (const rule of rewriteConfig.rules) { - // Use sitemapMatchPath if available for matching - if (rule.sitemapMatchPath) { - if (rewrittenPathname.startsWith(rule.sitemapMatchPath)) { - // Reconstruct the original path using the explicit match - const suffix = rewrittenPathname.substring( - rule.sitemapMatchPath.length - ) - finalPathname = rule.path + suffix // Use the rule's original path + suffix - break // Found the matching rule - } - } - } - } - - // Construct the final URL using the site's base URL and the determined pathname - const finalUrl = new URL(finalPathname, site.baseUrl).toString() - - return { - url: finalUrl, - priority: line?.priority ?? site.priority, // Use nullish coalescing for defaults - changeFrequency: line?.changefreq ?? site.changeFrequency, // Use nullish coalescing - lastModified: line?.lastmod ?? site.lastModified, // Use nullish coalescing - } - } catch (error) { - console.error(`Error processing sitemap URL ${line.loc}:`, error) - return null // Return null if URL processing fails - } - } - - // Handle both array and single-item sitemaps - if (!data.urlset?.url) { - console.warn( - `Sitemap from ${site.sitemapUrl} has no urlset or url property.` - ) - return [] - } - - if (Array.isArray(data.urlset.url)) { - // Filter out any potential null results from processUrl if errors occurred - return data.urlset.url - .map(processUrl) - .filter((entry) => entry !== null) as MetadataRoute.Sitemap - } else if (typeof data.urlset.url === 'object' && data.urlset.url !== null) { - const entry = processUrl(data.urlset.url) - return entry ? [entry] : [] // Return array with the entry or empty array if null - } else { - console.warn( - `Sitemap from ${site.sitemapUrl} has unexpected urlset.url structure:`, - data.urlset.url - ) - return [] - } -} - -export const constructSitemap = async (): Promise => { - // Return empty sitemap if SEO indexing is not allowed - if (!ALLOW_SEO_INDEXING) { - return [] - } - - let mergedSitemap: MetadataRoute.Sitemap = [] - - // Fetch sitemaps from all configured sites (Webflow & Framer sites + docs) - for (const site of sites) { - const urls = await getSitemap(site) - mergedSitemap = mergedSitemap.concat(urls) - } - - // Deduplicate URLs, keeping the one with the highest priority - const urlMap = new Map() - mergedSitemap.forEach((entry) => { - const existingEntry = urlMap.get(entry.url) - // Keep the entry with the highest priority (lower number means higher priority in sitemaps typically, but the code uses higher number = higher priority) - // Ensure priority is treated as a number, defaulting to 0 if undefined - const currentPriority = entry.priority ?? 0 - const existingPriority = existingEntry?.priority ?? 0 - - if (!existingEntry || currentPriority > existingPriority) { - urlMap.set(entry.url, entry) - } - }) - - // Convert the map values back to an array - const uniqueSitemap = Array.from(urlMap.values()) - - // Sort all unique URLs alphabetically - return uniqueSitemap.sort((a, b) => a.url.localeCompare(b.url)) -} - -/** - * Main sitemap generation function that Next.js calls - * - * Fetches and merges sitemaps from all configured sites, - * deduplicates entries, and returns a sorted list of URLs - * - * @returns Complete sitemap for the E2B website - */ -export default async function sitemap(): Promise { - return await constructSitemap() -} diff --git a/src/configs/documentation.ts b/src/configs/documentation.ts deleted file mode 100644 index d97959d73..000000000 --- a/src/configs/documentation.ts +++ /dev/null @@ -1 +0,0 @@ -export const DOCUMENTATION_DOMAIN = 'e2b.mintlify.app' diff --git a/src/configs/redirects.ts b/src/configs/redirects.ts deleted file mode 100644 index c4ce9a957..000000000 --- a/src/configs/redirects.ts +++ /dev/null @@ -1,61 +0,0 @@ -// For certain redirects, we want to ship custom headers as well -// These redirects need to be handled in middleware - -export interface MiddlewareRedirect { - source: string - destination: string - statusCode: number - headers?: Record -} - -export const MIDDLEWARE_REDIRECTS: MiddlewareRedirect[] = [ - { - source: '/change', - destination: - '/careers?utm_source=billboard&utm_medium=outdoor&utm_campaign=prague_ooh_2025&utm_content=change', - statusCode: 302, - headers: { - 'X-Robots-Tag': 'noindex', - }, - }, - { - source: '/humans', - destination: - '/careers?utm_source=billboard&utm_medium=outdoor&utm_campaign=prague_ooh_2025&utm_content=humans', - statusCode: 302, - headers: { - 'X-Robots-Tag': 'noindex', - }, - }, - { - source: '/machines', - destination: - '/careers?utm_source=billboard&utm_medium=outdoor&utm_campaign=prague_ooh_2025&utm_content=machines', - statusCode: 302, - headers: { - 'X-Robots-Tag': 'noindex', - }, - }, - - // Docker MCP Campaign - { - source: '/start', - destination: - '/docs/mcp?utm_source=billboard&utm_medium=outdoor&utm_campaign=docker_2025&utm_content=ooh', - statusCode: 302, - headers: { - 'X-Robots-Tag': 'noindex', - }, - }, - - // SF 2026 Billboard Campaign - { - source: '/computers', - destination: - '/blog/how-manus-uses-e2b-to-provide-agents-with-virtual-computers?utm_source=billboard&utm_medium=outdoor&utm_campaign=sf_ooh_2026&utm_content=computers', - statusCode: 302, - headers: { - 'X-Robots-Tag': 'noindex', - }, - }, -] diff --git a/src/configs/rewrites.ts b/src/configs/rewrites.ts deleted file mode 100644 index ed3c27584..000000000 --- a/src/configs/rewrites.ts +++ /dev/null @@ -1,69 +0,0 @@ -import type { DomainConfig } from '@/types/rewrites.types' -import { DOCUMENTATION_DOMAIN } from './documentation' - -export const LANDING_PAGE_DOMAIN = 'www.e2b-landing-page.com' - -// Currently we have two locations for rewrites to happen. - -// 1. Route handler catch-all rewrite config (cached on build time with revalidation) -// 2. Middleware native rewrite config (dynamic) -export type RewriteConfigType = 'route' | 'middleware' - -// Route handler catch-all rewrite config -// IMPORTANT: The order of the rules is important, as the first matching rule will be used -export const ROUTE_REWRITE_CONFIG: DomainConfig[] = [ - { - domain: LANDING_PAGE_DOMAIN, - rules: [ - { path: '/' }, - { path: '/terms' }, - { path: '/privacy' }, - { path: '/pricing' }, - { path: '/thank-you' }, - { path: '/contact' }, - { path: '/research' }, - { path: '/startups' }, - { path: '/enterprise' }, - { path: '/careers' }, - { - path: '/blog/category', - pathPreprocessor: (path) => path.replace('/blog', ''), - sitemapMatchPath: '/category', - }, - { path: '/blog' }, - { path: '/cookbook' }, - ], - }, -] - -/** - * Middleware native rewrite config - * - * We implement rewrites directly in middleware rather than using Next.js's built-in - * `rewrites` configuration in next.config.ts because we need to set custom request - * and response headers for these rewritten requests. - * - * Specifically, we need to: - * - Add custom headers to the request (e.g., x-e2b-should-index for SEO control) - * - Set custom response headers (e.g., X-Robots-Tag for search engine indexing) - * - Have fine-grained control over the rewrite behavior based on environment variables - * - * Next.js's native rewrite configuration doesn't provide this level of header manipulation - * capability, so we handle these rewrites in our middleware layer where we have full - * control over the request/response cycle. - */ -export const MIDDLEWARE_REWRITE_CONFIG: DomainConfig[] = [ - { - domain: DOCUMENTATION_DOMAIN, - rules: [ - { path: '/docs.md' }, - { path: '/docs/llms.txt', pathPreprocessor: () => '/llms.txt' }, - { - path: '/docs/llms-full.txt', - pathPreprocessor: () => '/llms-full.txt', - }, - { path: '/docs' }, - { path: '/mcp' }, - ], - }, -] diff --git a/src/configs/sitemap.ts b/src/configs/sitemap.ts deleted file mode 100644 index 4c1c84b91..000000000 --- a/src/configs/sitemap.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { DOCUMENTATION_DOMAIN } from './documentation' - -export const SITEMAP_EXCLUDE_CONFIG: Array<{ - domain: string - excludedPathPrefixes: string[] -}> = [ - { - domain: DOCUMENTATION_DOMAIN, - excludedPathPrefixes: ['/docs/sdk-reference'], - }, -] diff --git a/src/core/server/proxy/classifier.ts b/src/core/server/proxy/classifier.ts index 15305ec35..25dd0cff1 100644 --- a/src/core/server/proxy/classifier.ts +++ b/src/core/server/proxy/classifier.ts @@ -1,15 +1,12 @@ import 'server-cli-only' import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls' -import { getMiddlewareRedirectFromPath } from '@/lib/utils/redirects' -import { getRewriteForPath } from '@/lib/utils/rewrites' export type ProxyPlan = | { kind: 'bypass' } | { kind: 'trpc' } | { kind: 'auth-page' } | { kind: 'dashboard-page' } - | { kind: 'rewrite' } | { kind: 'public' } const TRPC_API_PREFIXES = ['/api/trpc'] as const @@ -36,15 +33,7 @@ export function classifyProxyRequest(pathname: string): ProxyPlan { return { kind: 'dashboard-page' } } - const hasRedirect = Boolean(getMiddlewareRedirectFromPath(pathname)) - const hasRouteRewrite = Boolean(getRewriteForPath(pathname, 'route').config) - const hasMiddlewareRewrite = Boolean( - getRewriteForPath(pathname, 'middleware').config - ) - - return hasRedirect || hasRouteRewrite || hasMiddlewareRewrite - ? { kind: 'rewrite' } - : { kind: 'public' } + return { kind: 'public' } } export function planNeedsAuthGate(plan: ProxyPlan): boolean { diff --git a/src/core/server/proxy/handlers.ts b/src/core/server/proxy/handlers.ts index 15f187763..83b3ae530 100644 --- a/src/core/server/proxy/handlers.ts +++ b/src/core/server/proxy/handlers.ts @@ -1,10 +1,7 @@ import 'server-cli-only' import { type NextRequest, NextResponse } from 'next/server' -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls' -import { getMiddlewareRedirectFromPath } from '@/lib/utils/redirects' -import { getRewriteForPath } from '@/lib/utils/rewrites' import { isProxyAuthRoute, isProxyDashboardRoute } from './classifier' export function getAuthRedirect( @@ -28,57 +25,6 @@ export function getAuthRedirect( return null } -export function handleMiddlewareRedirect( - request: NextRequest -): NextResponse | null { - const redirect = getMiddlewareRedirectFromPath(request.nextUrl.pathname) - if (!redirect) return null - - return NextResponse.redirect(new URL(redirect.destination, request.url), { - status: redirect.statusCode, - headers: new Headers(redirect.headers), - }) -} - -export function handleRouteRewritePassthrough( - request: NextRequest -): NextResponse | null { - const { config } = getRewriteForPath(request.nextUrl.pathname, 'route') - return config ? NextResponse.next({ request }) : null -} - -export function handleMiddlewareRewrite( - request: NextRequest -): NextResponse | null { - const { config, rule } = getRewriteForPath( - request.nextUrl.pathname, - 'middleware' - ) - if (!config) return null - - const rewriteUrl = new URL(request.url) - rewriteUrl.hostname = config.domain - rewriteUrl.protocol = 'https' - rewriteUrl.port = '' - if (rule?.pathPreprocessor) { - rewriteUrl.pathname = rule.pathPreprocessor(rewriteUrl.pathname) - } - - const requestHeaders = new Headers(request.headers) - if (ALLOW_SEO_INDEXING) { - requestHeaders.set('x-e2b-should-index', '1') - } - - const response = NextResponse.rewrite(rewriteUrl, { - request: { headers: requestHeaders }, - }) - response.headers.set( - 'X-Robots-Tag', - ALLOW_SEO_INDEXING ? 'index, follow' : 'noindex, nofollow' - ) - return response -} - export function handleAuthGate( request: NextRequest, isAuthenticated: boolean diff --git a/src/core/server/proxy/runtime.ts b/src/core/server/proxy/runtime.ts index 878747f81..e5b48c891 100644 --- a/src/core/server/proxy/runtime.ts +++ b/src/core/server/proxy/runtime.ts @@ -29,12 +29,7 @@ import { type ProxyPlan, planNeedsAuthGate, } from './classifier' -import { - handleAuthGate, - handleMiddlewareRedirect, - handleMiddlewareRewrite, - handleRouteRewritePassthrough, -} from './handlers' +import { handleAuthGate } from './handlers' type RunProxyOptions = { isAuthenticated?: boolean @@ -191,17 +186,6 @@ async function runProxyConcerns( options: RunProxyOptions = {} ): Promise { try { - if (plan.kind !== 'bypass' && plan.kind !== 'trpc') { - const redirect = handleMiddlewareRedirect(request) - if (redirect) return redirect - - const routeRewrite = handleRouteRewritePassthrough(request) - if (routeRewrite) return routeRewrite - - const middlewareRewrite = handleMiddlewareRewrite(request) - if (middlewareRewrite) return middlewareRewrite - } - if (plan.kind === 'auth-page' || plan.kind === 'dashboard-page') { return handleAuthGate(request, options.isAuthenticated ?? false) } diff --git a/src/features/client-providers.tsx b/src/features/client-providers.tsx index 0177abfa2..dbead0917 100644 --- a/src/features/client-providers.tsx +++ b/src/features/client-providers.tsx @@ -6,39 +6,26 @@ import { TRPCReactProvider } from '@/trpc/client' import { Toaster } from '@/ui/primitives/sonner' import { ToastProvider } from '@/ui/primitives/toast' import { TooltipProvider } from '@/ui/primitives/tooltip' -import type { PostHogEnvironment } from './posthog-provider' -import { PostHogProvider } from './posthog-provider' interface ClientProvidersProps { children: React.ReactNode - postHogEnabled: boolean - postHogEnvironment: PostHogEnvironment } -export default function ClientProviders({ - children, - postHogEnabled, - postHogEnvironment, -}: ClientProvidersProps) { +export default function ClientProviders({ children }: ClientProvidersProps) { return ( - - - - {children} - - - - + + {children} + + + ) diff --git a/src/features/dashboard/context.tsx b/src/features/dashboard/context.tsx index 56ebdc776..464cd51be 100644 --- a/src/features/dashboard/context.tsx +++ b/src/features/dashboard/context.tsx @@ -5,7 +5,6 @@ import { useDebounceCallback } from 'usehooks-ts' import { COOKIE_KEYS } from '@/configs/cookies' import type { AuthUser } from '@/core/modules/auth/models' import type { TeamModel } from '@/core/modules/teams/models' -import { OryPostHogIdentityBridge } from '@/features/ory-posthog-identity-bridge' import { setBrowserCookie } from '@/lib/utils/browser-cookies' interface DashboardContextValue { @@ -48,7 +47,6 @@ export function DashboardContextProvider({ return ( - {children} ) diff --git a/src/features/dashboard/navbar/dashboard-survey-popover.tsx b/src/features/dashboard/navbar/dashboard-survey-popover.tsx deleted file mode 100644 index 5a93d97c2..000000000 --- a/src/features/dashboard/navbar/dashboard-survey-popover.tsx +++ /dev/null @@ -1,118 +0,0 @@ -'use client' - -import { PopoverTrigger } from '@radix-ui/react-popover' -import { usePostHog } from 'posthog-js/react' -import { useCallback, useState } from 'react' -import { toast } from 'sonner' -import { INCLUDE_DASHBOARD_FEEDBACK_SURVEY } from '@/configs/env-flags' -import { l } from '@/core/shared/clients/logger/logger' -import { useAppPostHogProvider } from '@/features/posthog-provider' -import { Popover, PopoverContent } from '@/ui/primitives/popover' -import { SurveyContent } from '@/ui/survey' - -interface DashboardSurveyPopoverProps { - trigger: React.ReactNode -} - -function DashboardSurveyPopover({ trigger }: DashboardSurveyPopoverProps) { - const posthog = usePostHog() - const [isOpen, setIsOpen] = useState(false) - const [wasSubmitted, setWasSubmitted] = useState(false) - - const { dashboardFeedbackSurvey, isInitialized } = useAppPostHogProvider() - - const handleSubmit = useCallback( - (responses: Record) => { - if (!dashboardFeedbackSurvey) return - - const responseData = Object.entries(responses).reduce( - (acc, [index, response]) => ({ - ...acc, - [`$survey_response${index === '0' ? '' : '_' + index}`]: response, - }), - {} - ) - - posthog.capture('survey sent', { - $survey_id: dashboardFeedbackSurvey.id, - ...responseData, - }) - - setWasSubmitted(true) - - toast.success('Thank you! Your feedback has been recorded.') - - // reset states - setIsOpen(false) - setTimeout(() => { - setWasSubmitted(false) - }, 100) - }, - [dashboardFeedbackSurvey, posthog] - ) - - // we will optimistically render the button on first render. - // if we can't resolve the survey on the client side, we hide the button. - if (!dashboardFeedbackSurvey && isInitialized) { - return null - } - - return ( - { - if (!dashboardFeedbackSurvey) { - l.error( - { - key: 'dashboard_survey_popover:survey_not_found', - context: { - survey_id: - process.env.NEXT_PUBLIC_POSTHOG_DASHBOARD_FEEDBACK_SURVEY_ID, - }, - }, - 'Tried to open survey popover but survey was not found.' - ) - return - } - - if (!open && !wasSubmitted && dashboardFeedbackSurvey) { - posthog.capture('survey dismissed', { - $survey_id: dashboardFeedbackSurvey.id, - }) - } - if (open && dashboardFeedbackSurvey) { - posthog.capture('survey shown', { - $survey_id: dashboardFeedbackSurvey.id, - }) - } - setIsOpen(open) - }} - > - {trigger} - - - {dashboardFeedbackSurvey && ( - - )} - - - ) -} - -export default function DashboardSurveyPopoverResolver( - props: DashboardSurveyPopoverProps -) { - if (!INCLUDE_DASHBOARD_FEEDBACK_SURVEY) { - return null - } - - return -} diff --git a/src/features/dashboard/posthog-error-boundary.tsx b/src/features/dashboard/posthog-error-boundary.tsx deleted file mode 100644 index 4672914cb..000000000 --- a/src/features/dashboard/posthog-error-boundary.tsx +++ /dev/null @@ -1,36 +0,0 @@ -'use client' - -import { - PostHogErrorBoundary, - type PostHogErrorBoundaryFallbackProps, -} from 'posthog-js/react' -import { ErrorIndicator } from '@/ui/error-indicator' -import Frame from '@/ui/frame' - -// Not the shared ErrorBoundary: it calls captureException, which would -// double-report since PostHogErrorBoundary already reports the caught error. -function Fallback({ error }: PostHogErrorBoundaryFallbackProps) { - const message = error instanceof Error ? error.message : String(error) - - return ( -
- - - -
- ) -} - -export function DashboardPostHogErrorBoundary({ - children, -}: { - children: React.ReactNode -}) { - return ( - {children} - ) -} diff --git a/src/features/dashboard/sandboxes/list/tracking.ts b/src/features/dashboard/sandboxes/list/tracking.ts index 2b4186ad6..244366150 100644 --- a/src/features/dashboard/sandboxes/list/tracking.ts +++ b/src/features/dashboard/sandboxes/list/tracking.ts @@ -1,13 +1,7 @@ 'use client' -import posthog from 'posthog-js' - +// OSS: analytics removed; no-op kept so call sites stay identical to console. export const trackSandboxListInteraction = ( - action: string, - properties: Record = {} -) => { - posthog.capture('sandboxes list interacted', { - action, - ...properties, - }) -} + _action: string, + _properties: Record = {} +) => {} diff --git a/src/features/dashboard/sidebar/footer.tsx b/src/features/dashboard/sidebar/footer.tsx index 1e9fb0244..dbad77469 100644 --- a/src/features/dashboard/sidebar/footer.tsx +++ b/src/features/dashboard/sidebar/footer.tsx @@ -1,125 +1,46 @@ 'use client' import Link from 'next/link' -import { - INCLUDE_DASHBOARD_FEEDBACK_SURVEY, - INCLUDE_REPORT_ISSUE, -} from '@/configs/env-flags' import { GITHUB_URL } from '@/configs/urls' -import { cn } from '@/lib/utils' -import { - BugIcon, - DocsIcon, - ExternalLinkIcon, - FeedbackIcon, - GithubIcon, -} from '@/ui/primitives/icons' +import { DocsIcon, ExternalLinkIcon, GithubIcon } from '@/ui/primitives/icons' import { - SIDEBAR_TRANSITION_CLASSNAMES, SidebarFooter, SidebarGroup, SidebarMenu, SidebarMenuButton, SidebarMenuItem, } from '@/ui/primitives/sidebar' -import DashboardSurveyPopover from '../navbar/dashboard-survey-popover' -import ContactSupportDialog from '../navbar/report-issue-dialog' export default function DashboardSidebarFooter() { return ( - <> - - - - - - - - GitHub - - - - - - - - - Documentation - - - - - - - - - {(INCLUDE_DASHBOARD_FEEDBACK_SURVEY || INCLUDE_REPORT_ISSUE) && ( - - {INCLUDE_DASHBOARD_FEEDBACK_SURVEY && ( - - - - Feedback - - } - /> - - )} - {INCLUDE_REPORT_ISSUE && ( - - - - Support - - } - /> - - )} + + + + + + + + GitHub + + + + + + + + + Documentation + + + + - )} - + + ) } diff --git a/src/features/dashboard/sidebar/menu.tsx b/src/features/dashboard/sidebar/menu.tsx index 1f3120935..269b31fe2 100644 --- a/src/features/dashboard/sidebar/menu.tsx +++ b/src/features/dashboard/sidebar/menu.tsx @@ -2,12 +2,9 @@ import { Portal } from '@radix-ui/react-portal' import Link from 'next/link' -import { usePostHog } from 'posthog-js/react' import { useState } from 'react' import { PROTECTED_URLS } from '@/configs/urls' import { getTeamDisplayName } from '@/core/modules/teams/utils' -import { resetOryPostHogIdentity } from '@/features/ory-posthog-identity-bridge' -import { useAppPostHogProvider } from '@/features/posthog-provider' import { cn } from '@/lib/utils' import { DropdownMenu, @@ -32,8 +29,6 @@ import { TeamAvatar } from './team-avatar' export default function DashboardSidebarMenu() { const { team, user } = useDashboard() - const { enabled: postHogEnabled } = useAppPostHogProvider() - const posthog = usePostHog() const [createTeamOpen, setCreateTeamOpen] = useState(false) // Stays true until the hard navigation unloads the page; the overlay should // never tear down before then. @@ -41,7 +36,6 @@ export default function DashboardSidebarMenu() { const handleLogout = () => { setIsLoggingOut(true) - if (postHogEnabled) resetOryPostHogIdentity(posthog) // Hard navigation (not the Next router) to a plain route handler that clears // the session cookie server-side: a soft RSC redirect would re-render the // signed-out dashboard and tear down this overlay before the browser leaves diff --git a/src/features/dashboard/templates/detail/tabs.tsx b/src/features/dashboard/templates/detail/tabs.tsx index 4545593d6..e22ac977f 100644 --- a/src/features/dashboard/templates/detail/tabs.tsx +++ b/src/features/dashboard/templates/detail/tabs.tsx @@ -1,8 +1,6 @@ 'use client' import { usePathname } from 'next/navigation' -import posthog from 'posthog-js' -import { useEffect, useRef } from 'react' import { PROTECTED_URLS } from '@/configs/urls' import { DashboardTabsList } from '@/ui/dashboard-tabs' import { BuildIcon, TagIcon, TrendIcon } from '@/ui/primitives/icons' @@ -30,23 +28,6 @@ export default function TemplateDetailTabs({ }: TemplateDetailTabsProps) { const pathname = usePathname() const activeTab = tabFromPath(pathname, templateId) - const prevTabRef = useRef(null) - - useEffect(() => { - if (prevTabRef.current === null) { - posthog.capture('template detail opened', { - templateId, - tab: activeTab, - }) - } else if (prevTabRef.current !== activeTab) { - posthog.capture('template detail tab switched', { - templateId, - fromTab: prevTabRef.current, - toTab: activeTab, - }) - } - prevTabRef.current = activeTab - }, [activeTab, templateId]) return ( - posthog.capture('template detail opened', { - templateId: template.templateID, - fromTab: 'list', - }) - } /> )} {row.getVisibleCells().map((cell) => ( diff --git a/src/features/dashboard/templates/list/table-config.tsx b/src/features/dashboard/templates/list/table-config.tsx index e51658fcf..3db00e9cb 100644 --- a/src/features/dashboard/templates/list/table-config.tsx +++ b/src/features/dashboard/templates/list/table-config.tsx @@ -5,7 +5,6 @@ import { getCoreRowModel, type TableOptions, } from '@tanstack/react-table' -import posthog from 'posthog-js' import { useMemo } from 'react' import type { DefaultTemplate, Template } from '@/core/modules/templates/models' import { getTimestampColumnSize } from '@/features/dashboard/common/timestamp-column' @@ -22,15 +21,11 @@ import { // TABLE CONFIG export const fallbackData: (Template | DefaultTemplate)[] = [] +// OSS: analytics removed; no-op kept so call sites stay identical to console. export const trackTemplateTableInteraction = ( - action: string, - properties: Record = {} -) => { - posthog.capture('template table interacted', { - action, - ...properties, - }) -} + _action: string, + _properties: Record = {} +) => {} const TIMESTAMP_COLUMN_BASE_SIZE = 172 diff --git a/src/features/dashboard/templates/tags/table-config.tsx b/src/features/dashboard/templates/tags/table-config.tsx index df89faee7..781dfca6e 100644 --- a/src/features/dashboard/templates/tags/table-config.tsx +++ b/src/features/dashboard/templates/tags/table-config.tsx @@ -7,7 +7,6 @@ import { type SortingState, type TableOptions, } from '@tanstack/react-table' -import posthog from 'posthog-js' import { useMemo } from 'react' import { ActionsCell, BuildLinkCell, TagPillCell } from './table-cells' import type { TagGroup } from './types' @@ -68,15 +67,11 @@ export function serverSortToSorting(sort: TagGroupsServerSort): SortingState { export const fallbackData: TagGroup[] = [] +// OSS: analytics removed; no-op kept so call sites stay identical to console. export const trackTagTableInteraction = ( - action: string, - properties: Record = {} -) => { - posthog.capture('tag table interacted', { - action, - ...properties, - }) -} + _action: string, + _properties: Record = {} +) => {} export const useTagColumns = () => useMemo[]>( diff --git a/src/features/ory-posthog-identity-bridge.tsx b/src/features/ory-posthog-identity-bridge.tsx deleted file mode 100644 index c0dc571c5..000000000 --- a/src/features/ory-posthog-identity-bridge.tsx +++ /dev/null @@ -1,49 +0,0 @@ -'use client' - -import { usePostHog } from 'posthog-js/react' -import { useEffect } from 'react' -import type { AuthUser } from '@/core/modules/auth/models' -import type { TeamModel } from '@/core/modules/teams/models' -import { useAppPostHogProvider } from '@/features/posthog-provider' - -export function resetOryPostHogIdentity( - posthog: ReturnType -) { - posthog.reset() -} - -export function OryPostHogIdentityBridge({ - user, - team, -}: { - user: AuthUser - team: Pick -}) { - const posthog = usePostHog() - const { enabled, environment, isLoaded } = useAppPostHogProvider() - - useEffect(() => { - if (!enabled || !isLoaded) { - return - } - - posthog.identify(user.id, { email: user.email, environment }) - posthog.group('team', team.id, { - environment, - name: team.name, - slug: team.slug, - }) - }, [ - enabled, - environment, - isLoaded, - posthog, - team.id, - team.name, - team.slug, - user.email, - user.id, - ]) - - return null -} diff --git a/src/features/posthog-provider.tsx b/src/features/posthog-provider.tsx deleted file mode 100644 index f3204426b..000000000 --- a/src/features/posthog-provider.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import { usePathname } from 'next/navigation' -import posthog, { type Survey } from 'posthog-js' -import { PostHogProvider as PHProvider } from 'posthog-js/react' -import { createContext, useContext, useEffect, useState } from 'react' - -export type PostHogEnvironment = 'production' | 'preview' | 'development' - -interface AppPostHogContextValue { - enabled: boolean - environment: PostHogEnvironment - isLoaded: boolean - isInitialized: boolean - dashboardFeedbackSurvey: Survey | null -} - -const AppPostHogContext = createContext( - undefined -) - -export function useAppPostHogProvider() { - const ctx = useContext(AppPostHogContext) - - if (!ctx) { - throw new Error( - 'useAppPostHogProvider must be used within a PostHogProvider' - ) - } - - return ctx -} - -export function PostHogProvider({ - children, - enabled, - environment, -}: { - children: React.ReactNode - enabled: boolean - environment: PostHogEnvironment -}) { - const pathname = usePathname() - const [dashboardFeedbackSurvey, setDashboardFeedbackSurvey] = - useState(null) - const [isLoaded, setIsLoaded] = useState(() => posthog.__loaded) - const [isInitialized, setIsInitialized] = useState(false) - - // Only track the dashboard app — not auth, marketing, or proxied (docs/blog) - // paths. PostHog initializes lazily once the user reaches a /dashboard route. - const shouldInit = enabled && !!pathname?.startsWith('/dashboard') - - useEffect(() => { - if (!shouldInit || !process.env.NEXT_PUBLIC_POSTHOG_KEY) { - return - } - - const registerEnvironment = () => { - posthog.register({ - environment, - }) - } - - const loadDashboardFeedbackSurvey = () => { - posthog.getSurveys((surveys) => { - for (const survey of surveys) { - switch (survey.id) { - case process.env.NEXT_PUBLIC_POSTHOG_DASHBOARD_FEEDBACK_SURVEY_ID: - setDashboardFeedbackSurvey(survey) - break - } - } - setIsInitialized(true) - }) - } - - const finishLoading = () => { - registerEnvironment() - loadDashboardFeedbackSurvey() - setIsLoaded(true) - } - - if (posthog.__loaded) { - finishLoading() - return - } - - posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { - // Note that PostHog will automatically capture page views and common events - // - // This setup utilizes Next.js rewrites to act as a reverse proxy, to improve - // reliability of client-side tracking & make requests less likely to be intercepted by tracking blockers. - // https://posthog.com/docs/libraries/next-js#configuring-a-reverse-proxy-to-posthog - api_host: '/ph-proxy', - ui_host: 'https://us.posthog.com', - capture_exceptions: { - capture_unhandled_errors: true, - capture_unhandled_rejections: true, - capture_console_errors: false, - }, - advanced_enable_surveys: true, - disable_session_recording: process.env.NODE_ENV !== 'production', - advanced_disable_toolbar_metrics: true, - opt_in_site_apps: true, - loaded: (posthog) => { - if (process.env.NODE_ENV === 'development') posthog.debug() - finishLoading() - }, - }) - }, [environment, shouldInit]) - - return ( - - {children} - - ) -} diff --git a/src/lib/hooks/use-analytics.ts b/src/lib/hooks/use-analytics.ts index a2a28be0e..53a0a8a90 100644 --- a/src/lib/hooks/use-analytics.ts +++ b/src/lib/hooks/use-analytics.ts @@ -1,19 +1,12 @@ 'use client' -import { usePostHog } from 'posthog-js/react' import { useCallback } from 'react' +// OSS: analytics removed; no-op kept so call sites stay identical to console. export const useSandboxInspectAnalytics = () => { - const posthog = usePostHog() - const handler = useCallback( - (action: string, properties: Record = {}) => { - posthog.capture('sandbox inspect interacted', { - action, - ...properties, - }) - }, - [posthog] + (_action: string, _properties: Record = {}) => {}, + [] ) return { trackInteraction: handler } diff --git a/src/lib/utils/redirects.ts b/src/lib/utils/redirects.ts deleted file mode 100644 index e808238cc..000000000 --- a/src/lib/utils/redirects.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - MIDDLEWARE_REDIRECTS, - type MiddlewareRedirect, -} from '@/configs/redirects' - -/** - * Finds a middleware redirect configuration for the given pathname. - * - * These redirects are primarily used for marketing campaigns that include - * custom headers like noindex to prevent search engine indexing of - * temporary campaign URLs. - * - * @param pathname - The request pathname to match against redirect sources - * @returns The matching redirect configuration, or undefined if no match found - */ -export function getMiddlewareRedirectFromPath( - pathname: string -): MiddlewareRedirect | undefined { - return MIDDLEWARE_REDIRECTS.find((redirect) => redirect.source === pathname) -} diff --git a/src/lib/utils/rewrites.ts b/src/lib/utils/rewrites.ts deleted file mode 100644 index 27988d056..000000000 --- a/src/lib/utils/rewrites.ts +++ /dev/null @@ -1,193 +0,0 @@ -import * as cheerio from 'cheerio' -import { - MIDDLEWARE_REWRITE_CONFIG, - type RewriteConfigType, - ROUTE_REWRITE_CONFIG, -} from '@/configs/rewrites' -import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger' -import type { RewriteConfig } from '@/types/rewrites.types' - -function getRewriteForPath( - path: string, - configType: RewriteConfigType -): RewriteConfig { - const config = - configType === 'route' ? ROUTE_REWRITE_CONFIG : MIDDLEWARE_REWRITE_CONFIG - - for (const domainConfig of config) { - const isIndex = path === '/' || path === '' - - const matchingRule = domainConfig.rules.find((rule) => { - if (isIndex && rule.path === '/') { - return true - } - - if ( - rule.path !== '/' && - (path === rule.path || path.startsWith(`${rule.path}/`)) - ) { - return true - } - - return false - }) - - if (matchingRule) { - return { - config: domainConfig, - rule: matchingRule, - } - } - } - - return { config: null, rule: null } -} - -// --- SEO Tag Rewriter (Cheerio) --- - -interface SeoTagOptions { - pathname: string - allowIndexing: boolean -} - -/** - * Rewrites SEO meta tags in an HTML document using Cheerio. - * It removes existing robots meta tags and canonical links, - * then prepends new ones based on the provided options. - * - * @param $ The Cheerio API instance. - * @param options Configuration options including the pathname and NO_INDEX flag. - */ -function rewriteSeoTags($: cheerio.CheerioAPI, options: SeoTagOptions): void { - const { pathname, allowIndexing } = options - - $('meta[name="robots"]').remove() - $('link[rel="canonical"]').remove() - - const robotsContent = allowIndexing ? 'index,follow' : 'noindex,nofollow' - - const formattedPathname = pathname.startsWith('/') ? pathname : `/${pathname}` - const canonicalUrl = `https://e2b.dev${formattedPathname}` - - const $head = $('head') - if ($head.length > 0) { - // Add newline characters for readability in the final HTML source - $head.prepend(` -`) - $head.prepend(` -`) - return - } - - l.warn( - { - key: 'cheerio_seo_rewriter:head_tag_not_found', - context: { pathname, allowIndexing }, - }, - `Head tag not found for pathname ${pathname}` - ) -} - -/** - * Rewrites absolute href attributes in an HTML document using Cheerio. - * It finds and tags whose href starts with the given prefix - * and replaces the href with a relative path (pathname + search + hash). - * - * @param $ The Cheerio API instance. - * @param rewrittenPrefix The absolute URL prefix (e.g., "https://example.com") to replace. - */ -function rewriteAbsoluteHrefsInDoc( - $: cheerio.CheerioAPI, - rewrittenPrefixes: string[] -): void { - $('a[href]').each((_, element) => { - const $element = $(element) - const href = $element.attr('href') - - if (!href || !rewrittenPrefixes.some((prefix) => href.startsWith(prefix))) { - return - } - - try { - const url = new URL(href) - const relativePath = url.pathname + url.search + url.hash - $element.attr('href', relativePath) - } catch (e) { - l.warn( - { - key: 'cheerio_href_rewriter:failed', - error: serializeErrorForLog(e), - context: { href }, - }, - `Failed to parse or set href="${href}"` - ) - } - }) -} - -// Substrings identifying analytics tags baked into the proxied Webflow HTML. -// GTM is the single loader — GA4 and the LinkedIn Insight Tag are injected by it -// at runtime, so removing the GTM loader stops all three. PostHog and the -// remaining GA/LinkedIn entries are matched directly as defense-in-depth. -const ANALYTICS_TAG_MATCHERS = [ - 'googletagmanager.com', - 'gtm.start', - 'GTM-', - 'google-analytics.com', - 'gtag(', - 'snap.licdn.com', - 'px.ads.linkedin.com', - '_linkedin_partner_id', - 'posthog', -] - -/** - * Removes third-party analytics tags from an HTML document using Cheerio. - * Scans only - - - - - - - - - - Pricing - -` - -const seo = { pathname: '/', allowIndexing: false } - -describe('rewriteContentPagesHtml — stripAnalytics', () => { - it('removes every analytics vendor when stripAnalytics is true', () => { - const out = rewriteContentPagesHtml(FIXTURE_HTML, { - seo, - stripAnalytics: true, - }) - - expect(out).not.toContain('gtm.start') - expect(out).not.toContain('GTM-K2W3LVD2') - expect(out).not.toContain('googletagmanager.com') - expect(out).not.toContain('posthog') - expect(out).not.toContain('gtag(') - expect(out).not.toContain('snap.licdn.com') - }) - - it('keeps non-analytics scripts and page markup when stripping', () => { - const out = rewriteContentPagesHtml(FIXTURE_HTML, { - seo, - hrefPrefixes: ['https://e2b.dev'], - stripAnalytics: true, - }) - - expect(out).toContain('cdn.prod.website-files.com/site/js/app.js') - expect(out).toContain('page bootstrap, not analytics') - expect(out).toContain('href="/pricing"') - }) - - it('leaves analytics tags intact when stripAnalytics is falsy (production)', () => { - const out = rewriteContentPagesHtml(FIXTURE_HTML, { - seo, - stripAnalytics: false, - }) - - expect(out).toContain('GTM-K2W3LVD2') - expect(out).toContain('posthog') - expect(out).toContain('snap.licdn.com') - expect(out).toContain('www.googletagmanager.com/ns.html') - }) -}) diff --git a/tests/unit/sitemap.test.ts b/tests/unit/sitemap.test.ts deleted file mode 100644 index 79d948fe0..000000000 --- a/tests/unit/sitemap.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { DOCUMENTATION_DOMAIN } from '@/configs/documentation' -import { LANDING_PAGE_DOMAIN } from '@/configs/rewrites' - -vi.mock('@/configs/env-flags', () => ({ - ALLOW_SEO_INDEXING: true, -})) - -import { constructSitemap } from '@/app/sitemap' - -describe('constructSitemap', () => { - const fetchMock = vi.fn() - - beforeEach(() => { - vi.stubGlobal('fetch', fetchMock) - }) - - afterEach(() => { - vi.unstubAllGlobals() - vi.resetAllMocks() - }) - - it('excludes sdk reference paths from the merged sitemap', async () => { - fetchMock.mockImplementation(async (input) => { - const url = input instanceof URL ? input.toString() : String(input) - - if (url === `https://${LANDING_PAGE_DOMAIN}/sitemap.xml`) { - return new Response( - ` - - - https://${LANDING_PAGE_DOMAIN}/pricing - - - `, - { - status: 200, - headers: { - 'Content-Type': 'application/xml', - }, - } - ) - } - - if (url === `https://${DOCUMENTATION_DOMAIN}/sitemap.xml`) { - return new Response( - ` - - - https://${DOCUMENTATION_DOMAIN}/docs/quickstart - - - https://${DOCUMENTATION_DOMAIN}/docs/sdk-reference - - - https://${DOCUMENTATION_DOMAIN}/docs/sdk-reference/python - - - `, - { - status: 200, - headers: { - 'Content-Type': 'application/xml', - }, - } - ) - } - - throw new Error(`Unexpected sitemap fetch: ${url}`) - }) - - const sitemap = await constructSitemap() - const urls = sitemap.map((entry) => entry.url) - - expect(urls).toContain('https://e2b.dev/pricing') - expect(urls).toContain('https://e2b.dev/docs/quickstart') - expect(urls).not.toContain('https://e2b.dev/docs/sdk-reference') - expect(urls).not.toContain('https://e2b.dev/docs/sdk-reference/python') - }) -}) diff --git a/vercel.json b/vercel.json index f8577bf41..03d263efb 100644 --- a/vercel.json +++ b/vercel.json @@ -2,84 +2,5 @@ "github": { "silent": true }, - "trailingSlash": false, - "redirects": [ - { - "source": "/docs", - "has": [ - { - "type": "query", - "key": "reason", - "value": "sdk-missing-api-key" - } - ], - "destination": "/docs?ref=sdk-missing-api-key&reason=", - "permanent": true - }, - { - "source": "/", - "has": [ - { - "type": "host", - "value": "blog.e2b.dev" - } - ], - "destination": "https://e2b.dev/blog", - "permanent": true - }, - { - "source": "/:path*", - "has": [ - { - "type": "host", - "value": "blog.e2b.dev" - } - ], - "destination": "https://e2b.dev/blog/:path*", - "permanent": true - }, - { - "source": "/log/:path*", - "destination": "https://e2b.dev/blog/:path*", - "permanent": true - }, - { - "source": "/blog/log/:path*", - "destination": "https://e2b.dev/blog/:path*", - "permanent": true - }, - { - "source": "/", - "has": [ - { - "type": "host", - "value": "changelog.e2b.dev" - } - ], - "destination": "https://e2b.dev/changelog", - "permanent": true - }, - { - "source": "/", - "has": [ - { - "type": "host", - "value": "docs.e2b.dev" - } - ], - "destination": "https://e2b.dev/docs", - "permanent": true - }, - { - "source": "/:path*", - "has": [ - { - "type": "host", - "value": "docs.e2b.dev" - } - ], - "destination": "https://e2b.dev/docs/:path*", - "permanent": true - } - ] + "trailingSlash": false } From 98144989fd6cfc10d9c7892896467fbdac443817 Mon Sep 17 00:00:00 2001 From: ben-fornefeld Date: Wed, 22 Jul 2026 18:47:48 +0200 Subject: [PATCH 2/8] remove enterprise verticals: billing, webhooks/argus, events, team management, keys, account, support, feature flags, team metrics, byoc/connections, standalone terminal --- bun.lock | 57 - package.json | 8 - spec/openapi.argus.yaml | 834 ------------ src/app/(auth)/auth/cli/page.tsx | 273 ---- src/app/(auth)/layout.tsx | 3 +- .../dashboard/[teamSlug]/account/error.tsx | 13 - src/app/dashboard/[teamSlug]/account/page.tsx | 29 - src/app/dashboard/[teamSlug]/agents/page.tsx | 64 - .../dashboard/[teamSlug]/billing/error.tsx | 13 - src/app/dashboard/[teamSlug]/billing/page.tsx | 26 - .../[teamSlug]/billing/plan/error.tsx | 13 - .../[teamSlug]/billing/plan/page.tsx | 22 - .../[teamSlug]/billing/plan/select/error.tsx | 13 - .../[teamSlug]/billing/plan/select/page.tsx | 20 - src/app/dashboard/[teamSlug]/byoc/page.tsx | 51 - .../dashboard/[teamSlug]/connections/page.tsx | 122 -- .../dashboard/[teamSlug]/general/error.tsx | 13 - src/app/dashboard/[teamSlug]/general/page.tsx | 17 - src/app/dashboard/[teamSlug]/keys/page.tsx | 23 - src/app/dashboard/[teamSlug]/layout.tsx | 81 +- src/app/dashboard/[teamSlug]/limits/error.tsx | 13 - .../dashboard/[teamSlug]/limits/loading.tsx | 1 - src/app/dashboard/[teamSlug]/limits/page.tsx | 21 - .../dashboard/[teamSlug]/members/error.tsx | 13 - src/app/dashboard/[teamSlug]/members/page.tsx | 23 - .../[teamSlug]/sandboxes/(tabs)/error.tsx | 13 - .../[teamSlug]/sandboxes/(tabs)/layout.tsx | 37 - .../sandboxes/(tabs)/list/error.tsx | 13 - .../[teamSlug]/sandboxes/(tabs)/list/page.tsx | 31 - .../sandboxes/(tabs)/list2/error.tsx | 13 - .../sandboxes/(tabs)/monitoring/error.tsx | 13 - .../sandboxes/(tabs)/monitoring/page.tsx | 28 - .../[teamSlug]/sandboxes/(tabs)/page.tsx | 10 - .../sandboxes/[sandboxId]/events/page.tsx | 5 - .../[teamSlug]/{keys => sandboxes}/error.tsx | 2 +- .../sandboxes/{(tabs)/list2 => }/page.tsx | 9 +- .../dashboard/[teamSlug]/terminal/page.tsx | 213 --- src/app/dashboard/[teamSlug]/usage/error.tsx | 13 - .../dashboard/[teamSlug]/usage/loading.tsx | 1 - src/app/dashboard/[teamSlug]/usage/page.tsx | 115 -- .../[webhookId]/(tabs)/deliveries/page.tsx | 25 - .../webhooks/[webhookId]/(tabs)/layout.tsx | 36 - .../[webhookId]/(tabs)/overview/page.tsx | 46 - .../[teamSlug]/webhooks/[webhookId]/page.tsx | 17 - .../dashboard/[teamSlug]/webhooks/error.tsx | 13 - .../dashboard/[teamSlug]/webhooks/page.tsx | 29 - src/app/dashboard/account/route.ts | 46 - src/app/dashboard/terminal/route.ts | 10 - src/app/login/layout.tsx | 3 +- src/app/recovery/layout.tsx | 3 +- src/app/registration/layout.tsx | 3 +- src/app/settings/layout.tsx | 3 +- src/app/verification/layout.tsx | 3 +- src/configs/dashboard-tab-url-map.ts | 20 +- src/configs/env-flags.ts | 12 - src/configs/intervals.ts | 1 - src/configs/mock-data.ts | 255 +--- src/configs/sidebar.ts | 131 +- src/core/modules/billing/models.ts | 111 -- src/core/modules/billing/repository.server.ts | 340 ----- .../feature-flags/boolean-definitions.ts | 50 - src/core/modules/feature-flags/context.ts | 11 - src/core/modules/feature-flags/definitions.ts | 84 -- .../feature-flags/feature-flags.client.tsx | 82 -- .../feature-flags/feature-flags.server.ts | 147 -- ...aunchdarkly-openfeature-provider.server.ts | 141 -- .../openfeature-client.server.ts | 83 -- .../modules/feature-flags/provider.server.ts | 21 - src/core/modules/feature-flags/types.ts | 34 - src/core/modules/keys/models.ts | 4 - src/core/modules/keys/repository.server.ts | 124 -- src/core/modules/keys/schemas.ts | 15 - .../sandboxes/lifecycle-event-types.ts | 19 - src/core/modules/sandboxes/models.client.ts | 16 - src/core/modules/sandboxes/models.ts | 89 +- .../modules/sandboxes/repository.server.ts | 202 --- src/core/modules/sandboxes/schemas.ts | 54 - src/core/modules/support/repository.server.ts | 269 ---- .../modules/teams/teams-repository.server.ts | 177 --- .../modules/webhooks/repository.server.ts | 309 ----- src/core/server/api/routers/billing.ts | 147 -- src/core/server/api/routers/index.ts | 6 - src/core/server/api/routers/sandbox.ts | 20 +- src/core/server/api/routers/sandboxes.ts | 135 +- src/core/server/api/routers/support.ts | 65 - src/core/server/api/routers/teams.ts | 135 +- src/core/server/api/routers/user.ts | 121 +- src/core/server/api/routers/webhooks.ts | 235 ---- src/core/server/functions/sandboxes/utils.ts | 243 +--- src/core/server/functions/usage/get-usage.ts | 41 - src/core/server/functions/webhooks/schema.ts | 97 -- src/core/shared/clients/api.ts | 3 +- src/core/shared/contracts/argus-api.types.ts | 705 ---------- src/core/shared/errors.ts | 34 - .../account/access-token-settings.tsx | 88 -- .../dashboard/account/email-settings.tsx | 212 --- .../dashboard/account/name-settings.tsx | 136 -- .../account/password-settings-server.tsx | 28 - .../dashboard/account/password-settings.tsx | 229 ---- .../dashboard/account/reauth-dialog.tsx | 38 - .../dashboard/account/timezone-settings.tsx | 194 --- .../dashboard/account/user-access-token.tsx | 80 -- src/features/dashboard/billing/addons.tsx | 268 ---- .../concurrent-sandboxes-addon-dialog.tsx | 405 ------ src/features/dashboard/billing/constants.ts | 31 - src/features/dashboard/billing/credits.tsx | 33 - src/features/dashboard/billing/hooks.ts | 335 ----- src/features/dashboard/billing/invoices.tsx | 128 -- .../dashboard/billing/select-plan.tsx | 314 ----- .../dashboard/billing/selected-plan.tsx | 250 ---- .../dashboard/billing/tier-avatar-border.tsx | 38 - src/features/dashboard/billing/types.ts | 19 - src/features/dashboard/billing/utils.ts | 85 -- src/features/dashboard/byoc/byoc-setup.tsx | 174 --- src/features/dashboard/byoc/terraform.ts | 22 - src/features/dashboard/connections/catalog.ts | 24 - src/features/dashboard/layouts/footer.tsx | 3 - src/features/dashboard/layouts/layout.tsx | 18 +- .../layouts/status-indicator.server.tsx | 116 -- .../dashboard/layouts/status-indicator.ts | 82 -- .../layouts/team-blocked-indicator.tsx | 156 --- .../dashboard/limits/alert-ascii-icon.tsx | 72 - src/features/dashboard/limits/ascii-icon.tsx | 52 - .../dashboard/limits/currency-input.ts | 14 - .../dashboard/limits/focus-block-input.ts | 22 - .../dashboard/limits/limit-ascii-icon.tsx | 70 - .../limits/remove-usage-limit-dialog.tsx | 129 -- .../limits/set-usage-limit-dialog.tsx | 86 -- .../dashboard/limits/usage-alert-form.tsx | 282 ---- .../dashboard/limits/usage-alert-section.tsx | 62 - .../dashboard/limits/usage-limit-form.tsx | 286 ---- .../dashboard/limits/usage-limit-section.tsx | 79 -- .../dashboard/limits/usage-limits.tsx | 65 - .../dashboard/members/add-member-dialog.tsx | 34 - .../dashboard/members/add-member-form.tsx | 116 -- .../dashboard/members/member-table-row.tsx | 283 ---- .../dashboard/members/member-table.tsx | 38 - .../dashboard/members/member-table.utils.ts | 21 - .../members/members-page-content.tsx | 123 -- .../members/remove-member-dialog.tsx | 78 -- .../dashboard/navbar/file-drop-zone.tsx | 129 -- .../dashboard/navbar/report-issue-dialog.tsx | 282 ---- .../projects-announcement/banner.tsx | 50 - .../sandbox/events/event-type-badge.tsx | 24 - .../sandbox/events/event-type-filter.tsx | 79 -- .../sandbox/events/event-type-map.ts | 22 - .../dashboard/sandbox/events/filter-params.ts | 29 - .../dashboard/sandbox/events/index.ts | 1 - .../dashboard/sandbox/events/table.tsx | 224 ---- .../events/use-sandbox-event-filters.ts | 52 - .../dashboard/sandbox/events/view.tsx | 49 - .../monitoring-sandbox-metrics-chart.tsx | 2 +- .../sandbox/monitoring/utils/step.ts | 21 + .../sandbox/monitoring/utils/timeframe.ts | 2 +- .../sandboxes/list/feature-flag.server.ts | 27 - .../dashboard/sandboxes/list/table-cells.tsx | 26 - .../dashboard/sandboxes/list/table-config.tsx | 96 -- .../dashboard/sandboxes/list/table.tsx | 35 +- .../sandboxes/live-counter.client.tsx | 30 - .../sandboxes/live-counter.server.tsx | 33 - .../dashboard/sandboxes/live-counter.tsx | 34 - .../sandboxes/monitoring/charts-context.tsx | 117 -- .../charts/animated-metric-display.tsx | 57 - .../sandboxes/monitoring/charts/charts.tsx | 43 - .../components/range-label.tsx | 30 - .../components/time-range-selector.tsx | 104 -- .../charts/concurrent-chart/hooks.ts | 116 -- .../charts/concurrent-chart/index.tsx | 107 -- .../charts/concurrent-chart/utils.ts | 35 - .../sandboxes/monitoring/charts/content.tsx | 48 - .../sandboxes/monitoring/charts/fallback.tsx | 41 - .../charts/startrate-chart/hooks.ts | 46 - .../charts/startrate-chart/index.tsx | 60 - .../charts/team-metrics-chart/constants.ts | 24 - .../charts/team-metrics-chart/index.tsx | 434 ------ .../charts/team-metrics-chart/types.ts | 43 - .../charts/team-metrics-chart/utils.ts | 263 ---- .../sandboxes/monitoring/header.client.tsx | 116 -- .../dashboard/sandboxes/monitoring/header.tsx | 82 -- .../monitoring/hooks/use-recent-metrics.ts | 93 -- .../monitoring/hooks/use-timeframe.ts | 184 --- .../monitoring/time-picker/constants.ts | 80 -- .../sandboxes/monitoring/time-picker/hooks.ts | 129 -- .../monitoring/time-picker/index.tsx | 346 ----- .../monitoring/time-picker/time-panel.tsx | 278 ---- .../sandboxes/monitoring/time-picker/utils.ts | 122 -- .../monitoring/time-picker/validation.ts | 136 -- .../dashboard/sandboxes/monitoring/utils.ts | 62 - .../settings/general/team-avatar.tsx | 15 - .../dashboard/settings/general/team-info.tsx | 36 - .../dashboard/settings/general/team-name.tsx | 165 --- .../settings/keys/api-keys-page-content.tsx | 129 -- .../settings/keys/api-keys-table-row.tsx | 137 -- .../settings/keys/api-keys-table.tsx | 98 -- .../dashboard/settings/keys/api-keys-utils.ts | 43 - .../settings/keys/create-api-key-dialog.tsx | 249 ---- .../settings/keys/delete-api-key-dialog.tsx | 122 -- src/features/dashboard/settings/keys/index.ts | 5 - .../dashboard/settings/webhooks/constants.ts | 38 - .../webhooks/delete-webhook-dialog.tsx | 114 -- .../settings/webhooks/detail/chart-utils.ts | 98 -- .../webhooks/detail/deliveries-content.tsx | 583 -------- .../webhooks/detail/delivery-filter-params.ts | 27 - .../settings/webhooks/detail/fallbacks.tsx | 49 - .../settings/webhooks/detail/header.tsx | 101 -- .../settings/webhooks/detail/index.ts | 3 - .../settings/webhooks/detail/layout.tsx | 48 - .../webhooks/detail/overview-content.tsx | 402 ------ .../settings/webhooks/detail/stats-chart.tsx | 805 ----------- .../webhooks/detail/stats-interval-select.tsx | 45 - .../settings/webhooks/detail/stats-range.ts | 144 -- .../discard-webhook-changes-dialog.tsx | 51 - .../settings/webhooks/event-badges.tsx | 54 - .../webhooks/finish-webhook-setup-dialog.tsx | 157 --- .../dashboard/settings/webhooks/table-row.tsx | 295 ---- .../dashboard/settings/webhooks/table.tsx | 98 -- .../dashboard/settings/webhooks/types.ts | 3 - .../webhooks/update-webhook-secret-dialog.tsx | 182 --- .../webhooks/upsert-webhook-dialog-steps.tsx | 396 ------ .../webhooks/upsert-webhook-dialog.tsx | 375 ------ .../webhooks/webhooks-page-content.tsx | 174 --- .../dashboard/shared/event-type-badge.tsx | 24 - .../shared/event-type-filter-params.ts | 24 - .../dashboard/shared/event-type-filter.tsx | 79 -- .../dashboard/shared/event-type-map.ts | 22 - src/features/dashboard/shared/index.ts | 3 - src/features/dashboard/sidebar/command.tsx | 3 +- src/features/dashboard/sidebar/content.tsx | 3 +- .../dashboard/sidebar/use-visible-links.ts | 16 - src/features/dashboard/team-blocked/index.ts | 2 - .../missing-payment-method-dialog.tsx | 238 ---- .../team-blocked-dialog-storage.ts | 11 - .../team-blocked/team-blocked-message.ts | 62 - .../team-blocked/team-blocked-recovery.tsx | 451 ------- .../verification-required-dialog.tsx | 275 ---- .../usage/billing-timezone-banner.tsx | 45 - .../usage/compute-usage-chart/index.tsx | 308 ----- .../usage/compute-usage-chart/types.ts | 30 - src/features/dashboard/usage/constants.ts | 229 ---- src/features/dashboard/usage/display-utils.ts | 333 ----- .../dashboard/usage/redesign/chart-utils.ts | 8 - .../dashboard/usage/redesign/metrics.ts | 43 - .../usage/redesign/single-metric-chart.tsx | 120 -- .../usage/redesign/usage-area-chart.tsx | 346 ----- .../usage/redesign/usage-fallback.ts | 8 - .../usage/redesign/usage-metric-detail.tsx | 244 ---- .../dashboard/usage/sampling-utils.ts | 200 --- src/features/dashboard/usage/types.ts | 44 - .../dashboard/usage/usage-charts-context.tsx | 305 ----- .../dashboard/usage/usage-metric-chart.tsx | 163 --- .../usage/usage-time-range-controls.tsx | 193 --- .../dashboard/usage/usage-timezone-utils.ts | 61 - .../dashboard/usage/usage-timezone.tsx | 106 -- .../usage/usage-top-time-range-controls.tsx | 22 - src/lib/utils/server.ts | 83 -- tests/development/sandbox-events.test.ts | 333 ----- tests/unit/auth-user-emails.test.ts | 62 - tests/unit/billing-repository.test.ts | 125 -- tests/unit/byoc-page.test.tsx | 91 -- tests/unit/byoc-terraform.test.ts | 29 - tests/unit/chart-utils.test.ts | 270 ---- tests/unit/connections-catalog.test.ts | 89 -- tests/unit/connections-page.test.tsx | 106 -- tests/unit/currency.test.ts | 33 - tests/unit/feature-flags.test.ts | 229 ---- tests/unit/fill-metrics-with-zeros.test.ts | 1192 ----------------- tests/unit/keys-repository.test.ts | 181 --- tests/unit/member-table-utils.test.ts | 105 -- tests/unit/openfeature-client.test.ts | 78 -- .../projects-announcement-banner.test.tsx | 80 -- tests/unit/sandbox-lifecycle.test.ts | 235 ---- tests/unit/sidebar-feature-flags.test.ts | 61 - tests/unit/sidebar.test.ts | 39 - tests/unit/status-indicator.test.ts | 146 -- tests/unit/team-blocked-dialog-copy.test.tsx | 150 --- .../unit/team-blocked-error-handling.test.ts | 204 --- tests/unit/team-blocked-message.test.ts | 70 - tests/unit/team-metrics-step.test.ts | 219 --- .../unit/team-monitoring-time-picker.test.ts | 68 - tests/unit/team-name-schema.test.ts | 17 - tests/unit/teams-repository.test.ts | 31 - tests/unit/usage-display-utils.test.ts | 62 - tests/unit/usage-sampling-utils.test.ts | 86 -- tests/unit/usage-time-range-presets.test.ts | 51 - tests/unit/usage-timezone-utils.test.ts | 143 -- tests/unit/user-router.test.ts | 102 -- tsconfig.json | 3 - 287 files changed, 84 insertions(+), 31119 deletions(-) delete mode 100644 spec/openapi.argus.yaml delete mode 100644 src/app/(auth)/auth/cli/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/account/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/account/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/agents/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/billing/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/billing/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/billing/plan/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/billing/plan/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/billing/plan/select/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/billing/plan/select/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/byoc/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/connections/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/general/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/general/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/keys/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/limits/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/limits/loading.tsx delete mode 100644 src/app/dashboard/[teamSlug]/limits/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/members/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/members/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/sandboxes/(tabs)/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/sandboxes/(tabs)/layout.tsx delete mode 100644 src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list2/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/sandboxes/(tabs)/monitoring/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/sandboxes/(tabs)/monitoring/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/sandboxes/(tabs)/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/sandboxes/[sandboxId]/events/page.tsx rename src/app/dashboard/[teamSlug]/{keys => sandboxes}/error.tsx (85%) rename src/app/dashboard/[teamSlug]/sandboxes/{(tabs)/list2 => }/page.tsx (63%) delete mode 100644 src/app/dashboard/[teamSlug]/terminal/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/usage/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/usage/loading.tsx delete mode 100644 src/app/dashboard/[teamSlug]/usage/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/deliveries/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/layout.tsx delete mode 100644 src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/overview/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/webhooks/[webhookId]/page.tsx delete mode 100644 src/app/dashboard/[teamSlug]/webhooks/error.tsx delete mode 100644 src/app/dashboard/[teamSlug]/webhooks/page.tsx delete mode 100644 src/app/dashboard/account/route.ts delete mode 100644 src/app/dashboard/terminal/route.ts delete mode 100644 src/core/modules/billing/models.ts delete mode 100644 src/core/modules/billing/repository.server.ts delete mode 100644 src/core/modules/feature-flags/boolean-definitions.ts delete mode 100644 src/core/modules/feature-flags/context.ts delete mode 100644 src/core/modules/feature-flags/definitions.ts delete mode 100644 src/core/modules/feature-flags/feature-flags.client.tsx delete mode 100644 src/core/modules/feature-flags/feature-flags.server.ts delete mode 100644 src/core/modules/feature-flags/launchdarkly-openfeature-provider.server.ts delete mode 100644 src/core/modules/feature-flags/openfeature-client.server.ts delete mode 100644 src/core/modules/feature-flags/provider.server.ts delete mode 100644 src/core/modules/feature-flags/types.ts delete mode 100644 src/core/modules/keys/models.ts delete mode 100644 src/core/modules/keys/repository.server.ts delete mode 100644 src/core/modules/keys/schemas.ts delete mode 100644 src/core/modules/sandboxes/lifecycle-event-types.ts delete mode 100644 src/core/modules/sandboxes/schemas.ts delete mode 100644 src/core/modules/support/repository.server.ts delete mode 100644 src/core/modules/teams/teams-repository.server.ts delete mode 100644 src/core/modules/webhooks/repository.server.ts delete mode 100644 src/core/server/api/routers/billing.ts delete mode 100644 src/core/server/api/routers/support.ts delete mode 100644 src/core/server/api/routers/webhooks.ts delete mode 100644 src/core/server/functions/usage/get-usage.ts delete mode 100644 src/core/server/functions/webhooks/schema.ts delete mode 100644 src/core/shared/contracts/argus-api.types.ts delete mode 100644 src/features/dashboard/account/access-token-settings.tsx delete mode 100644 src/features/dashboard/account/email-settings.tsx delete mode 100644 src/features/dashboard/account/name-settings.tsx delete mode 100644 src/features/dashboard/account/password-settings-server.tsx delete mode 100644 src/features/dashboard/account/password-settings.tsx delete mode 100644 src/features/dashboard/account/reauth-dialog.tsx delete mode 100644 src/features/dashboard/account/timezone-settings.tsx delete mode 100644 src/features/dashboard/account/user-access-token.tsx delete mode 100644 src/features/dashboard/billing/addons.tsx delete mode 100644 src/features/dashboard/billing/concurrent-sandboxes-addon-dialog.tsx delete mode 100644 src/features/dashboard/billing/constants.ts delete mode 100644 src/features/dashboard/billing/credits.tsx delete mode 100644 src/features/dashboard/billing/hooks.ts delete mode 100644 src/features/dashboard/billing/invoices.tsx delete mode 100644 src/features/dashboard/billing/select-plan.tsx delete mode 100644 src/features/dashboard/billing/selected-plan.tsx delete mode 100644 src/features/dashboard/billing/tier-avatar-border.tsx delete mode 100644 src/features/dashboard/billing/types.ts delete mode 100644 src/features/dashboard/billing/utils.ts delete mode 100644 src/features/dashboard/byoc/byoc-setup.tsx delete mode 100644 src/features/dashboard/byoc/terraform.ts delete mode 100644 src/features/dashboard/connections/catalog.ts delete mode 100644 src/features/dashboard/layouts/status-indicator.server.tsx delete mode 100644 src/features/dashboard/layouts/status-indicator.ts delete mode 100644 src/features/dashboard/layouts/team-blocked-indicator.tsx delete mode 100644 src/features/dashboard/limits/alert-ascii-icon.tsx delete mode 100644 src/features/dashboard/limits/ascii-icon.tsx delete mode 100644 src/features/dashboard/limits/currency-input.ts delete mode 100644 src/features/dashboard/limits/focus-block-input.ts delete mode 100644 src/features/dashboard/limits/limit-ascii-icon.tsx delete mode 100644 src/features/dashboard/limits/remove-usage-limit-dialog.tsx delete mode 100644 src/features/dashboard/limits/set-usage-limit-dialog.tsx delete mode 100644 src/features/dashboard/limits/usage-alert-form.tsx delete mode 100644 src/features/dashboard/limits/usage-alert-section.tsx delete mode 100644 src/features/dashboard/limits/usage-limit-form.tsx delete mode 100644 src/features/dashboard/limits/usage-limit-section.tsx delete mode 100644 src/features/dashboard/limits/usage-limits.tsx delete mode 100644 src/features/dashboard/members/add-member-dialog.tsx delete mode 100644 src/features/dashboard/members/add-member-form.tsx delete mode 100644 src/features/dashboard/members/member-table-row.tsx delete mode 100644 src/features/dashboard/members/member-table.tsx delete mode 100644 src/features/dashboard/members/member-table.utils.ts delete mode 100644 src/features/dashboard/members/members-page-content.tsx delete mode 100644 src/features/dashboard/members/remove-member-dialog.tsx delete mode 100644 src/features/dashboard/navbar/file-drop-zone.tsx delete mode 100644 src/features/dashboard/navbar/report-issue-dialog.tsx delete mode 100644 src/features/dashboard/projects-announcement/banner.tsx delete mode 100644 src/features/dashboard/sandbox/events/event-type-badge.tsx delete mode 100644 src/features/dashboard/sandbox/events/event-type-filter.tsx delete mode 100644 src/features/dashboard/sandbox/events/event-type-map.ts delete mode 100644 src/features/dashboard/sandbox/events/filter-params.ts delete mode 100644 src/features/dashboard/sandbox/events/index.ts delete mode 100644 src/features/dashboard/sandbox/events/table.tsx delete mode 100644 src/features/dashboard/sandbox/events/use-sandbox-event-filters.ts delete mode 100644 src/features/dashboard/sandbox/events/view.tsx create mode 100644 src/features/dashboard/sandbox/monitoring/utils/step.ts delete mode 100644 src/features/dashboard/sandboxes/list/feature-flag.server.ts delete mode 100644 src/features/dashboard/sandboxes/live-counter.client.tsx delete mode 100644 src/features/dashboard/sandboxes/live-counter.server.tsx delete mode 100644 src/features/dashboard/sandboxes/live-counter.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts-context.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/animated-metric-display.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/charts.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/concurrent-chart/components/range-label.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/concurrent-chart/components/time-range-selector.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/concurrent-chart/hooks.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/concurrent-chart/index.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/concurrent-chart/utils.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/content.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/fallback.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/startrate-chart/hooks.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/startrate-chart/index.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/team-metrics-chart/constants.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/team-metrics-chart/index.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/team-metrics-chart/types.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/charts/team-metrics-chart/utils.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/header.client.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/header.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/hooks/use-recent-metrics.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/hooks/use-timeframe.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/time-picker/constants.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/time-picker/hooks.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/time-picker/index.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/time-picker/time-panel.tsx delete mode 100644 src/features/dashboard/sandboxes/monitoring/time-picker/utils.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/time-picker/validation.ts delete mode 100644 src/features/dashboard/sandboxes/monitoring/utils.ts delete mode 100644 src/features/dashboard/settings/general/team-avatar.tsx delete mode 100644 src/features/dashboard/settings/general/team-info.tsx delete mode 100644 src/features/dashboard/settings/general/team-name.tsx delete mode 100644 src/features/dashboard/settings/keys/api-keys-page-content.tsx delete mode 100644 src/features/dashboard/settings/keys/api-keys-table-row.tsx delete mode 100644 src/features/dashboard/settings/keys/api-keys-table.tsx delete mode 100644 src/features/dashboard/settings/keys/api-keys-utils.ts delete mode 100644 src/features/dashboard/settings/keys/create-api-key-dialog.tsx delete mode 100644 src/features/dashboard/settings/keys/delete-api-key-dialog.tsx delete mode 100644 src/features/dashboard/settings/keys/index.ts delete mode 100644 src/features/dashboard/settings/webhooks/constants.ts delete mode 100644 src/features/dashboard/settings/webhooks/delete-webhook-dialog.tsx delete mode 100644 src/features/dashboard/settings/webhooks/detail/chart-utils.ts delete mode 100644 src/features/dashboard/settings/webhooks/detail/deliveries-content.tsx delete mode 100644 src/features/dashboard/settings/webhooks/detail/delivery-filter-params.ts delete mode 100644 src/features/dashboard/settings/webhooks/detail/fallbacks.tsx delete mode 100644 src/features/dashboard/settings/webhooks/detail/header.tsx delete mode 100644 src/features/dashboard/settings/webhooks/detail/index.ts delete mode 100644 src/features/dashboard/settings/webhooks/detail/layout.tsx delete mode 100644 src/features/dashboard/settings/webhooks/detail/overview-content.tsx delete mode 100644 src/features/dashboard/settings/webhooks/detail/stats-chart.tsx delete mode 100644 src/features/dashboard/settings/webhooks/detail/stats-interval-select.tsx delete mode 100644 src/features/dashboard/settings/webhooks/detail/stats-range.ts delete mode 100644 src/features/dashboard/settings/webhooks/discard-webhook-changes-dialog.tsx delete mode 100644 src/features/dashboard/settings/webhooks/event-badges.tsx delete mode 100644 src/features/dashboard/settings/webhooks/finish-webhook-setup-dialog.tsx delete mode 100644 src/features/dashboard/settings/webhooks/table-row.tsx delete mode 100644 src/features/dashboard/settings/webhooks/table.tsx delete mode 100644 src/features/dashboard/settings/webhooks/types.ts delete mode 100644 src/features/dashboard/settings/webhooks/update-webhook-secret-dialog.tsx delete mode 100644 src/features/dashboard/settings/webhooks/upsert-webhook-dialog-steps.tsx delete mode 100644 src/features/dashboard/settings/webhooks/upsert-webhook-dialog.tsx delete mode 100644 src/features/dashboard/settings/webhooks/webhooks-page-content.tsx delete mode 100644 src/features/dashboard/shared/event-type-badge.tsx delete mode 100644 src/features/dashboard/shared/event-type-filter-params.ts delete mode 100644 src/features/dashboard/shared/event-type-filter.tsx delete mode 100644 src/features/dashboard/shared/event-type-map.ts delete mode 100644 src/features/dashboard/sidebar/use-visible-links.ts delete mode 100644 src/features/dashboard/team-blocked/index.ts delete mode 100644 src/features/dashboard/team-blocked/missing-payment-method-dialog.tsx delete mode 100644 src/features/dashboard/team-blocked/team-blocked-dialog-storage.ts delete mode 100644 src/features/dashboard/team-blocked/team-blocked-message.ts delete mode 100644 src/features/dashboard/team-blocked/team-blocked-recovery.tsx delete mode 100644 src/features/dashboard/team-blocked/verification-required-dialog.tsx delete mode 100644 src/features/dashboard/usage/billing-timezone-banner.tsx delete mode 100644 src/features/dashboard/usage/compute-usage-chart/index.tsx delete mode 100644 src/features/dashboard/usage/compute-usage-chart/types.ts delete mode 100644 src/features/dashboard/usage/constants.ts delete mode 100644 src/features/dashboard/usage/display-utils.ts delete mode 100644 src/features/dashboard/usage/redesign/chart-utils.ts delete mode 100644 src/features/dashboard/usage/redesign/metrics.ts delete mode 100644 src/features/dashboard/usage/redesign/single-metric-chart.tsx delete mode 100644 src/features/dashboard/usage/redesign/usage-area-chart.tsx delete mode 100644 src/features/dashboard/usage/redesign/usage-fallback.ts delete mode 100644 src/features/dashboard/usage/redesign/usage-metric-detail.tsx delete mode 100644 src/features/dashboard/usage/sampling-utils.ts delete mode 100644 src/features/dashboard/usage/types.ts delete mode 100644 src/features/dashboard/usage/usage-charts-context.tsx delete mode 100644 src/features/dashboard/usage/usage-metric-chart.tsx delete mode 100644 src/features/dashboard/usage/usage-time-range-controls.tsx delete mode 100644 src/features/dashboard/usage/usage-timezone-utils.ts delete mode 100644 src/features/dashboard/usage/usage-timezone.tsx delete mode 100644 src/features/dashboard/usage/usage-top-time-range-controls.tsx delete mode 100644 src/lib/utils/server.ts delete mode 100644 tests/development/sandbox-events.test.ts delete mode 100644 tests/unit/auth-user-emails.test.ts delete mode 100644 tests/unit/billing-repository.test.ts delete mode 100644 tests/unit/byoc-page.test.tsx delete mode 100644 tests/unit/byoc-terraform.test.ts delete mode 100644 tests/unit/chart-utils.test.ts delete mode 100644 tests/unit/connections-catalog.test.ts delete mode 100644 tests/unit/connections-page.test.tsx delete mode 100644 tests/unit/currency.test.ts delete mode 100644 tests/unit/feature-flags.test.ts delete mode 100644 tests/unit/fill-metrics-with-zeros.test.ts delete mode 100644 tests/unit/keys-repository.test.ts delete mode 100644 tests/unit/member-table-utils.test.ts delete mode 100644 tests/unit/openfeature-client.test.ts delete mode 100644 tests/unit/projects-announcement-banner.test.tsx delete mode 100644 tests/unit/sandbox-lifecycle.test.ts delete mode 100644 tests/unit/sidebar-feature-flags.test.ts delete mode 100644 tests/unit/sidebar.test.ts delete mode 100644 tests/unit/status-indicator.test.ts delete mode 100644 tests/unit/team-blocked-dialog-copy.test.tsx delete mode 100644 tests/unit/team-blocked-error-handling.test.ts delete mode 100644 tests/unit/team-blocked-message.test.ts delete mode 100644 tests/unit/team-metrics-step.test.ts delete mode 100644 tests/unit/team-monitoring-time-picker.test.ts delete mode 100644 tests/unit/team-name-schema.test.ts delete mode 100644 tests/unit/teams-repository.test.ts delete mode 100644 tests/unit/usage-display-utils.test.ts delete mode 100644 tests/unit/usage-sampling-utils.test.ts delete mode 100644 tests/unit/usage-time-range-presets.test.ts delete mode 100644 tests/unit/usage-timezone-utils.test.ts delete mode 100644 tests/unit/user-router.test.ts diff --git a/bun.lock b/bun.lock index 47c6ad31a..c4e4d0e89 100644 --- a/bun.lock +++ b/bun.lock @@ -6,10 +6,7 @@ "name": "@e2b/dashboard", "dependencies": { "@hookform/resolvers": "^5.2.2", - "@launchdarkly/node-server-sdk": "^9.11.2", - "@launchdarkly/openfeature-node-server": "^1.2.0", "@next/env": "^16.2.7", - "@openfeature/server-sdk": "^1.22.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/auto-instrumentations-node": "^0.77.0", "@opentelemetry/exporter-logs-otlp-http": "^0.203.0", @@ -39,14 +36,11 @@ "@radix-ui/react-toast": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.0", "@shikijs/themes": "3.2.1", - "@stripe/react-stripe-js": "^5.3.0", - "@stripe/stripe-js": "^8.2.0", "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-query": "^5.90.7", "@tanstack/react-query-devtools": "^5.91.1", "@tanstack/react-table": "^8.20.6", "@tanstack/react-virtual": "^3.13.12", - "@team-plain/typescript-sdk": "^5.11.0", "@trpc/client": "^11.7.1", "@trpc/server": "^11.7.1", "@trpc/tanstack-react-query": "^11.7.1", @@ -78,7 +72,6 @@ "openapi-fetch": "^0.14.0", "pathe": "^2.0.3", "pino": "^9.7.0", - "posthog-js": "^1.268.1", "react": "19.2.4", "react-day-picker": "^9.9.0", "react-dom": "19.2.4", @@ -313,8 +306,6 @@ "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.2", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA=="], - "@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="], - "@grpc/grpc-js": ["@grpc/grpc-js@1.14.4", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ=="], "@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="], @@ -401,14 +392,6 @@ "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], - "@launchdarkly/js-sdk-common": ["@launchdarkly/js-sdk-common@2.25.1", "", {}, "sha512-erG2RbA8QQMKW+D9Y7Uahez1dU+TzmyTzv9qlB7b0Pr/DS2SmUg5H8pJbkLcRrSxoshczZdB1UqsuE3RAk7UYQ=="], - - "@launchdarkly/js-server-sdk-common": ["@launchdarkly/js-server-sdk-common@2.19.1", "", { "dependencies": { "@launchdarkly/js-sdk-common": "2.25.1", "semver": "7.5.4" } }, "sha512-3N1R62FF5qeBJdZbZk62ixGFG73kbwwqoHD00e0OygMB3hIRuV+spYbB3S8BXZ8VpnzKBame0OChC8lYrb/Zow=="], - - "@launchdarkly/node-server-sdk": ["@launchdarkly/node-server-sdk@9.11.2", "", { "dependencies": { "@launchdarkly/js-server-sdk-common": "2.19.1", "https-proxy-agent": "^7.0.6", "launchdarkly-eventsource": "2.2.0" } }, "sha512-+S0V8bkxSvD3THaLTw/AKG6OVFFUqcoKXDrV7JhuCk9xhvI/LsvgpdzHZdzJjlicW/U+DRn3rPvhzGdYPLyasg=="], - - "@launchdarkly/openfeature-node-server": ["@launchdarkly/openfeature-node-server@1.2.0", "", { "peerDependencies": { "@launchdarkly/node-server-sdk": "9.x", "@openfeature/server-sdk": "^1.16.0" } }, "sha512-VXnfQJfHsofhmHV4HvURLdMiX3bSwLumyeSPzuFn+5FdNKH2l44ijwXeSsotXpANMp+Gmz1LiQLz7ftgTSHKXg=="], - "@marsidev/react-turnstile": ["@marsidev/react-turnstile@1.5.3", "", { "peerDependencies": { "react": "^17.0.2 || ^18.0.0 || ^19.0", "react-dom": "^17.0.2 || ^18.0.0 || ^19.0" } }, "sha512-8Dij2jiNGNczq1U4EKpO4do2XepcTPxSMc2ZzvHndO+gcp68tvMULm27z2P99rGkdB89hc3452NZeu2Rti4g6A=="], "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], @@ -431,10 +414,6 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.7", "", { "os": "win32", "cpu": "x64" }, "sha512-J4WlM72NMk076Qsg0jTdK3SNXatlSdnjW7L7oNGLst1tAGjHrJh/FYi+pw9wyIjEtGRKDNzD0zuiY16oWYWVaw=="], - "@openfeature/core": ["@openfeature/core@1.11.0", "", {}, "sha512-P0u3/ht/oZCQT89fOed+laLk0kZR529a825cS02uPDglxXbE97irWYpDAeRGGVETIzKfuy+H2g8c3Ccv/tXJNQ=="], - - "@openfeature/server-sdk": ["@openfeature/server-sdk@1.22.0", "", { "peerDependencies": { "@openfeature/core": "^1.11.0" } }, "sha512-YBrf6SQkn0FNB/dRAtLEs41dvFMUE8CrQTwI+iLaMFUIqWlqGNJfGnulKSneEKS+2OgKTAC6DdmKcZ6tK7kBcg=="], - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.203.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ=="], @@ -691,8 +670,6 @@ "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], - "@posthog/core": ["@posthog/core@1.5.1", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-8fdEzfvdStr45iIncTD+gnqp45UBTUpRK/bwB4shP5usCKytnPIeilU8rIpNBOVjJPwfW+2N8yWhQ0l14x191Q=="], - "@preact/signals": ["@preact/signals@1.3.2", "", { "dependencies": { "@preact/signals-core": "^1.7.0" }, "peerDependencies": { "preact": "10.x" } }, "sha512-naxcJgUJ6BTOROJ7C3QML7KvwKwCXQJYTc5L/b0eEsdYgPB6SxwoQ1vDGcS0Q7GVjAenVq/tXrybVdFShHYZWg=="], "@preact/signals-core": ["@preact/signals-core@1.12.1", "", {}, "sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA=="], @@ -877,10 +854,6 @@ "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], - "@stripe/react-stripe-js": ["@stripe/react-stripe-js@5.3.0", "", { "dependencies": { "prop-types": "^15.7.2" }, "peerDependencies": { "@stripe/stripe-js": ">=8.0.0 <9.0.0", "react": ">=16.8.0 <20.0.0", "react-dom": ">=16.8.0 <20.0.0" } }, "sha512-q9Wl6JKg9JUU9zRcQyx4rkKauiM4Il+/ohJemQkLa4SsFEvcXVzX9X89cRLiaAEiNOaJ/2EQML+aWNSHc0EXBw=="], - - "@stripe/stripe-js": ["@stripe/stripe-js@8.3.0", "", {}, "sha512-hmC3u0anySPI+As+wFGrKG38pXnXAUqyiitnYeYknEOslcDM96AkBdMNxKYOopecuP/v1HMbclUEq8/DKKn+6w=="], - "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], "@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="], @@ -931,8 +904,6 @@ "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.12", "", {}, "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA=="], - "@team-plain/typescript-sdk": ["@team-plain/typescript-sdk@5.11.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "ajv": "^8.12.0", "ajv-formats": "^2.1.1", "graphql": "^16.6.0", "lodash.get": "^4.4.2", "zod": "3.22.4" } }, "sha512-EEyfQjD1d1tFMLFtqV7StWtiwAesFD1B5zdTYvcjpoJLlftr4NMS0BVllSKnr7n7jjWivTsVuuG7yZcZD6ZonA=="], - "@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="], "@testing-library/react": ["@testing-library/react@16.3.2", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g=="], @@ -1065,10 +1036,6 @@ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], - - "ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="], - "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -1163,8 +1130,6 @@ "copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="], - "core-js": ["core-js@3.46.0", "", {}, "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA=="], - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], @@ -1283,8 +1248,6 @@ "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], - "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], - "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -1327,8 +1290,6 @@ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - "graphql": ["graphql@16.12.0", "", {}, "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ=="], - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], @@ -1423,8 +1384,6 @@ "knip": ["knip@6.11.0", "", { "dependencies": { "fdir": "^6.5.0", "formatly": "^0.3.0", "get-tsconfig": "4.14.0", "jiti": "^2.6.0", "minimist": "^1.2.8", "oxc-parser": "^0.128.0", "oxc-resolver": "^11.19.1", "picomatch": "^4.0.4", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.16", "unbash": "^3.0.0", "yaml": "^2.8.2", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-84PTlN8Q5smLpTbzs8smTVh8PMbTDXtw0tFksXq/m6auGFC/KSzJykKFmnYh3As38kiWDkoDBvdTTyKk5M1TAQ=="], - "launchdarkly-eventsource": ["launchdarkly-eventsource@2.2.0", "", {}, "sha512-u38fYlLSq/m6oFz0MS1/76Sj2xzlYhTKZ+sf/vju6PA86PMc6fPlY5k8CdU79edLXjNwsvIQTDvDNy3llDqB8A=="], - "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], @@ -1455,8 +1414,6 @@ "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="], - "lodash.get": ["lodash.get@4.4.2", "", {}, "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="], - "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], @@ -1597,8 +1554,6 @@ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - "posthog-js": ["posthog-js@1.288.0", "", { "dependencies": { "@posthog/core": "1.5.1", "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", "web-vitals": "^4.2.4" } }, "sha512-KOeF8PK/zxBuFB4b3FVkj5JxSWAfSOrfDVvWj5VrJNBGYqr8igDbAl10huFv9NB4/K9XeIWQ7AzPPGV4D3lbEA=="], - "preact": ["preact@10.27.2", "", {}, "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg=="], "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], @@ -1871,8 +1826,6 @@ "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], - "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], - "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="], "webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="], @@ -1945,8 +1898,6 @@ "@jest/types/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "@launchdarkly/js-server-sdk-common/semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="], - "@opentelemetry/auto-instrumentations-node/@opentelemetry/resources": ["@opentelemetry/resources@2.7.1", "", { "dependencies": { "@opentelemetry/core": "2.7.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ=="], "@opentelemetry/auto-instrumentations-node/@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/configuration": "0.219.0", "@opentelemetry/context-async-hooks": "2.8.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/exporter-logs-otlp-grpc": "0.219.0", "@opentelemetry/exporter-logs-otlp-http": "0.219.0", "@opentelemetry/exporter-logs-otlp-proto": "0.219.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.219.0", "@opentelemetry/exporter-metrics-otlp-http": "0.219.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.219.0", "@opentelemetry/exporter-prometheus": "0.219.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.219.0", "@opentelemetry/exporter-trace-otlp-http": "0.219.0", "@opentelemetry/exporter-trace-otlp-proto": "0.219.0", "@opentelemetry/exporter-zipkin": "2.8.0", "@opentelemetry/instrumentation": "0.219.0", "@opentelemetry/otlp-exporter-base": "0.219.0", "@opentelemetry/otlp-grpc-exporter-base": "0.219.0", "@opentelemetry/propagator-b3": "2.8.0", "@opentelemetry/propagator-jaeger": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0", "@opentelemetry/sdk-trace-node": "2.8.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-NWLpWLEb8gV3+JBHYoIrktbM385wyHpRJoh3J/4Q52d4PR+AlPMNGJT3DzBUrDSUEVbKAXoHR+EDAPxtiNcj8g=="], @@ -2261,8 +2212,6 @@ "@tailwindcss/postcss/postcss": ["postcss@8.5.1", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ=="], - "@team-plain/typescript-sdk/zod": ["zod@3.22.4", "", {}, "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg=="], - "@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@types/jest/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], @@ -2329,8 +2278,6 @@ "postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], - "posthog-js/fflate": ["fflate@0.4.8", "", {}, "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="], - "pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], @@ -2401,8 +2348,6 @@ "@jest/types/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "@launchdarkly/js-server-sdk-common/semver/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "@opentelemetry/auto-instrumentations-node/@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@2.7.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw=="], "@opentelemetry/auto-instrumentations-node/@opentelemetry/sdk-node/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg=="], @@ -2603,8 +2548,6 @@ "yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@launchdarkly/js-server-sdk-common/semver/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - "@opentelemetry/auto-instrumentations-node/@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], "@opentelemetry/auto-instrumentations-node/@opentelemetry/sdk-node/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.219.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.219.0", "@opentelemetry/core": "2.8.0", "@opentelemetry/resources": "2.8.0", "@opentelemetry/sdk-logs": "0.219.0", "@opentelemetry/sdk-metrics": "2.8.0", "@opentelemetry/sdk-trace-base": "2.8.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-aaYKAyXhw9VchKZVGOopD3Gw/kPsyrX2c6IQ0AW32mTjqmZOh5Y6Gf5OYqTNqVktAeBjmFinhyFaCwW6GYK9YQ=="], diff --git a/package.json b/package.json index db7376ad0..6422bf84a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,6 @@ "<<<<<<< Gen": "", "generate:infra": "bunx openapi-typescript ./spec/openapi.infra.yaml -o ./src/core/shared/contracts/infra-api.types.ts", "generate:dashboard-api": "bunx openapi-typescript ./spec/openapi.dashboard-api.yaml -o ./src/core/shared/contracts/dashboard-api.types.ts", - "generate:argus": "bunx openapi-typescript ./spec/openapi.argus.yaml -o ./src/core/shared/contracts/argus-api.types.ts", "<<<<<<< Scripts": "", "scripts:check-app-env": "bun scripts/check-app-env.ts", "scripts:check-playwright-env": "bun scripts/check-playwright-env.ts", @@ -46,10 +45,7 @@ }, "dependencies": { "@hookform/resolvers": "^5.2.2", - "@launchdarkly/node-server-sdk": "^9.11.2", - "@launchdarkly/openfeature-node-server": "^1.2.0", "@next/env": "^16.2.7", - "@openfeature/server-sdk": "^1.22.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/auto-instrumentations-node": "^0.77.0", "@opentelemetry/exporter-logs-otlp-http": "^0.203.0", @@ -79,14 +75,11 @@ "@radix-ui/react-toast": "^1.2.7", "@radix-ui/react-tooltip": "^1.2.0", "@shikijs/themes": "3.2.1", - "@stripe/react-stripe-js": "^5.3.0", - "@stripe/stripe-js": "^8.2.0", "@tanstack/match-sorter-utils": "^8.19.4", "@tanstack/react-query": "^5.90.7", "@tanstack/react-query-devtools": "^5.91.1", "@tanstack/react-table": "^8.20.6", "@tanstack/react-virtual": "^3.13.12", - "@team-plain/typescript-sdk": "^5.11.0", "@trpc/client": "^11.7.1", "@trpc/server": "^11.7.1", "@trpc/tanstack-react-query": "^11.7.1", @@ -118,7 +111,6 @@ "openapi-fetch": "^0.14.0", "pathe": "^2.0.3", "pino": "^9.7.0", - "posthog-js": "^1.268.1", "react": "19.2.4", "react-day-picker": "^9.9.0", "react-dom": "19.2.4", diff --git a/spec/openapi.argus.yaml b/spec/openapi.argus.yaml deleted file mode 100644 index c8e17631b..000000000 --- a/spec/openapi.argus.yaml +++ /dev/null @@ -1,834 +0,0 @@ -openapi: 3.0.0 -info: - version: 0.1.0 - title: E2B Argus API - -servers: - - url: https://api.e2b.app - -components: - securitySchemes: - ApiKeyAuth: - type: apiKey - in: header - name: X-API-Key - AccessTokenAuth: - type: http - scheme: bearer - bearerFormat: access_token - # Generated code uses security schemas in the alphabetical order. - # In order to check first the token, and then the team (so we can already use the user), - # there is a 1 and 2 present in the names of the security schemas. - Supabase1TokenAuth: - type: apiKey - in: header - name: X-Supabase-Token - Supabase2TeamAuth: - type: apiKey - in: header - name: X-Supabase-Team - AdminTokenAuth: - type: apiKey - in: header - name: X-Admin-Token - - parameters: - sandboxID: - name: sandboxID - in: path - required: true - schema: - type: string - webhookID: - name: webhookID - in: path - required: true - schema: - type: string - format: uuid - - responses: - "400": - description: Bad request - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "401": - description: Authentication error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "404": - description: Not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "409": - description: Conflict - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - "500": - description: Server error - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - - schemas: - Error: - required: - - code - - message - properties: - code: - type: integer - format: int32 - description: Error code - message: - type: string - description: Error - - SandboxEvent: - description: Sandbox event - required: - - id - - version - - type - - timestamp - - sandboxId - - sandboxExecutionId - - sandboxTemplateId - - sandboxBuildId - - sandboxTeamId - properties: - id: - type: string - format: uuid - description: Event unique identifier - version: - type: string - description: Event structure version - type: - type: string - description: Event name - eventCategory: - type: string - deprecated: true - description: Category of the event (e.g., 'lifecycle', 'process', etc.) - eventLabel: - type: string - deprecated: true - description: Label for the specific event type (e.g., 'sandbox_started', 'process_oom', etc.) - eventData: - type: object - nullable: true - description: Optional JSON data associated with the event - - timestamp: - type: string - format: date-time - description: Timestamp of the event - sandboxId: - type: string - format: string - description: Unique identifier for the sandbox - sandboxExecutionId: - type: string - format: string - description: Unique identifier for the sandbox execution - sandboxTemplateId: - type: string - format: string - description: Unique identifier for the sandbox template - sandboxBuildId: - type: string - format: string - description: Unique identifier for the sandbox build - sandboxTeamId: - type: string - format: uuid - description: Team identifier associated with the sandbox - - WebhookCreate: - description: Configuration for registering new webhooks - required: - - name - - url - - events - - signatureSecret - properties: - name: - type: string - url: - type: string - format: uri - events: - type: array - items: - type: string - enabled: - type: boolean - default: true - signatureSecret: - type: string - description: Secret used to sign the webhook payloads - - WebhookCreation: - description: Webhook creation response - required: - - id - - name - - createdAt - - teamId - - url - - enabled - - events - properties: - id: - type: string - description: Webhook unique identifier - name: - type: string - description: Webhook user friendly name - createdAt: - type: string - format: date-time - description: Time when the template was created - teamId: - type: string - description: Unique identifier for the team - url: - type: string - format: uri - enabled: - type: boolean - events: - type: array - items: - type: string - - WebhookDetail: - description: Webhook detail response - required: - - id - - teamId - - name - - createdAt - - url - - enabled - - events - properties: - id: - type: string - description: Webhook unique identifier - teamId: - type: string - description: Unique identifier for the team - name: - type: string - description: Webhook user friendly name - createdAt: - type: string - format: date-time - description: Time when the template was created - url: - type: string - format: uri - enabled: - type: boolean - events: - type: array - items: - type: string - - WebhookConfiguration: - description: Configuration for updating existing webhooks - properties: - enabled: - type: boolean - name: - type: string - description: Webhook user friendly name - url: - type: string - format: uri - events: - type: array - items: - type: string - signatureSecret: - type: string - description: Secret used to sign the webhook payloads - - WebhookDelivery: - description: Webhook delivery attempt - required: - - id - - teamId - - webhookId - - eventId - - sandboxId - - eventType - - status - - durationMs - - requestBody - - requestHeaders - - requestUrl - - errorClass - - timestamp - properties: - id: - type: string - format: uuid - description: Delivery attempt identifier - teamId: - type: string - format: uuid - description: Team identifier - webhookId: - type: string - format: uuid - description: Webhook configuration identifier - eventId: - type: string - format: uuid - description: Sandbox event identifier - sandboxId: - type: string - description: Sandbox identifier - eventType: - type: string - description: Sandbox event type - status: - type: string - enum: [success, failed] - description: Delivery attempt status - durationMs: - type: integer - format: int32 - description: Delivery request duration in milliseconds - requestBody: - type: string - description: Serialized webhook request body - requestHeaders: - type: string - description: JSON-encoded request headers with sensitive values redacted - requestUrl: - type: string - format: uri - description: URL attempted for this delivery - responseBody: - type: string - nullable: true - description: Truncated response body, if a response was received - responseHeaders: - type: string - nullable: true - description: JSON-encoded response headers, if a response was received - responseHttpStatusCode: - type: integer - format: int32 - nullable: true - description: HTTP response status code, if a response was received - errorClass: - type: string - nullable: true - enum: - - http_error - - dns_error - - timeout - - transport_error - - request_error - - signature_error - - canceled - description: Machine-readable non-HTTP or HTTP failure class - errorMessage: - type: string - nullable: true - description: Error message for failures without a useful response body - timestamp: - type: string - format: date-time - description: Time when the delivery attempt started - - WebhookDeliveryStats: - description: Webhook delivery aggregate stats - required: - - buckets - - total - - failed - - durationMs - properties: - buckets: - type: array - items: - $ref: "#/components/schemas/WebhookDeliveryStatsBucket" - total: - type: integer - format: int64 - failed: - type: integer - format: int64 - durationMs: - $ref: "#/components/schemas/WebhookDeliveryDurationStats" - - WebhookDeliveryDurationStats: - description: Webhook delivery duration statistics in milliseconds - required: - - minimum - - average - - maximum - properties: - minimum: - type: number - format: double - average: - type: number - format: double - maximum: - type: number - format: double - - WebhookDeliveryStatsBucket: - description: Webhook delivery stats for a time bucket - required: - - timestamp - - total - - failed - - durationMs - properties: - timestamp: - type: string - format: date-time - total: - type: integer - format: int64 - failed: - type: integer - format: int64 - durationMs: - $ref: "#/components/schemas/WebhookDeliveryDurationStats" - - WebhookDeliveryGroup: - description: Webhook delivery attempts grouped by sandbox event - required: - - eventId - - eventType - - sandboxId - - attempts - properties: - eventId: - type: string - format: uuid - eventType: - type: string - sandboxId: - type: string - attempts: - type: array - items: - $ref: "#/components/schemas/WebhookDelivery" - - WebhookDeliveriesListPayload: - description: Paginated webhook delivery attempts grouped by event - required: - - data - - nextCursor - properties: - data: - type: array - items: - $ref: "#/components/schemas/WebhookDeliveryGroup" - nextCursor: - type: string - nullable: true - description: Cursor to pass to the next list request, or null when there is no next page. - -paths: - /health: - get: - description: Health check - responses: - 200: - description: Request was successful - 401: - $ref: "#/components/responses/401" - - /events/sandboxes/{sandboxID}: - get: - description: Get sandbox events - tags: [events] - security: - - ApiKeyAuth: [] - - Supabase1TokenAuth: [] - Supabase2TeamAuth: [] - parameters: - - $ref: "#/components/parameters/sandboxID" - - name: offset - in: query - required: false - schema: - type: integer - format: int32 - minimum: 0 - default: 0 - - name: limit - in: query - required: false - schema: - type: integer - format: int32 - minimum: 1 - maximum: 100 - default: 10 - - name: orderAsc - in: query - required: false - schema: - type: boolean - default: false - - name: types - in: query - required: false - style: form - explode: true - schema: - type: array - items: - type: string - description: Filter events to the provided event types - responses: - "200": - description: Successfully returned the sandbox events - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/SandboxEvent" - "400": - $ref: "#/components/responses/400" - "404": - $ref: "#/components/responses/404" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - /events/sandboxes: - get: - description: Get all sandbox events for the team associated with the API key - tags: [events] - security: - - ApiKeyAuth: [] - - Supabase1TokenAuth: [] - Supabase2TeamAuth: [] - parameters: - - name: offset - in: query - required: false - schema: - type: integer - format: int32 - minimum: 0 - default: 0 - - name: limit - in: query - required: false - schema: - type: integer - format: int32 - minimum: 1 - maximum: 100 - default: 10 - - name: orderAsc - in: query - required: false - schema: - type: boolean - default: false - - name: types - in: query - required: false - style: form - explode: true - schema: - type: array - items: - type: string - description: Filter events to the provided event types - responses: - "200": - description: Successfully returned the sandbox events - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/SandboxEvent" - "400": - $ref: "#/components/responses/400" - "404": - $ref: "#/components/responses/404" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - /events/webhooks: - post: - operationId: webhookCreate - description: Register events webhook. - tags: [webhooks] - security: - - ApiKeyAuth: [] - - Supabase1TokenAuth: [] - Supabase2TeamAuth: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookCreate" - responses: - "201": - description: Successfully created webhook. - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookCreation" - "400": - $ref: "#/components/responses/400" - "404": - $ref: "#/components/responses/404" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - get: - operationId: webhooksList - description: List registered webhooks. - tags: [webhooks] - security: - - ApiKeyAuth: [] - - Supabase1TokenAuth: [] - Supabase2TeamAuth: [] - responses: - "200": - description: List of registered webhooks. - content: - application/json: - schema: - type: array - items: - $ref: "#/components/schemas/WebhookDetail" - "404": - $ref: "#/components/responses/404" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - /events/webhooks/{webhookID}: - get: - operationId: webhookGet - description: Get a registered webhook. - tags: [webhooks] - security: - - ApiKeyAuth: [] - - Supabase1TokenAuth: [] - Supabase2TeamAuth: [] - parameters: - - $ref: "#/components/parameters/webhookID" - responses: - "200": - description: Successfully returned the webhook configuration. - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookDetail" - "404": - $ref: "#/components/responses/404" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - patch: - operationId: webhookUpdate - description: Update a registered webhook configuration. - tags: [webhooks] - security: - - ApiKeyAuth: [] - - Supabase1TokenAuth: [] - Supabase2TeamAuth: [] - parameters: - - $ref: "#/components/parameters/webhookID" - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookConfiguration" - responses: - "200": - description: Successfully updated webhook. - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookDetail" - "400": - $ref: "#/components/responses/400" - "404": - $ref: "#/components/responses/404" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - delete: - operationId: webhookDelete - description: Delete a registered webhook. - tags: [webhooks] - security: - - ApiKeyAuth: [] - - Supabase1TokenAuth: [] - Supabase2TeamAuth: [] - parameters: - - $ref: "#/components/parameters/webhookID" - responses: - "200": - description: Successfully deleted webhook. - "404": - $ref: "#/components/responses/404" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - /events/webhooks/{webhookID}/deliveries: - get: - operationId: webhookDeliveriesList - description: List webhook delivery attempts. - tags: [webhooks] - security: - - ApiKeyAuth: [] - - Supabase1TokenAuth: [] - Supabase2TeamAuth: [] - parameters: - - $ref: "#/components/parameters/webhookID" - - name: cursor - in: query - required: false - schema: - type: string - description: Opaque cursor from the previous response's nextCursor field. - - name: limit - in: query - required: false - schema: - type: integer - format: int32 - minimum: 1 - maximum: 100 - default: 25 - - name: orderAsc - in: query - required: false - schema: - type: boolean - default: false - - name: start - in: query - required: false - schema: - type: string - format: date-time - description: Include deliveries at or after this timestamp. - - name: end - in: query - required: false - schema: - type: string - format: date-time - description: Include deliveries before this timestamp. - - name: deliveryStatus - in: query - required: false - style: form - explode: false - schema: - type: array - items: - type: string - enum: [success, failed] - description: Filter deliveries by delivery status - - name: eventType - in: query - required: false - style: form - explode: false - schema: - type: array - items: - type: string - description: Filter deliveries by event type - responses: - "200": - description: List of webhook delivery attempts grouped by event. - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookDeliveriesListPayload" - "400": - $ref: "#/components/responses/400" - "404": - $ref: "#/components/responses/404" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" - - /events/webhooks/{webhookID}/stats: - get: - operationId: webhookDeliveryStats - description: Get webhook delivery aggregate stats. - tags: [webhooks] - security: - - ApiKeyAuth: [] - - Supabase1TokenAuth: [] - Supabase2TeamAuth: [] - parameters: - - $ref: "#/components/parameters/webhookID" - - name: start - in: query - required: false - schema: - type: string - format: date-time - description: Inclusive stats range start. Defaults to 24 hours ago. - - name: end - in: query - required: false - schema: - type: string - format: date-time - description: Exclusive stats range end. Defaults to now. - responses: - "200": - description: Webhook delivery stats. - content: - application/json: - schema: - $ref: "#/components/schemas/WebhookDeliveryStats" - "404": - $ref: "#/components/responses/404" - "401": - $ref: "#/components/responses/401" - "500": - $ref: "#/components/responses/500" diff --git a/src/app/(auth)/auth/cli/page.tsx b/src/app/(auth)/auth/cli/page.tsx deleted file mode 100644 index 88deb13cd..000000000 --- a/src/app/(auth)/auth/cli/page.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import { redirect } from 'next/navigation' -import { AUTH_URLS, HELP_URLS, PROTECTED_URLS } from '@/configs/urls' -import type { FeatureFlagContext } from '@/core/modules/feature-flags/context' -import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server' -import { createUserTeamsRepository } from '@/core/modules/teams/user-teams-repository.server' -import { getAuthContext } from '@/core/server/auth' -import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger' -import { isLoopbackUrl } from '@/core/shared/schemas/url' -import { encodedRedirect } from '@/lib/utils/auth' -import { generateE2BUserAccessToken } from '@/lib/utils/server' -import { isVersionGreaterOrEqual } from '@/lib/utils/version' -import { CodeBlock } from '@/ui/code-block' -import { Alert, AlertDescription, AlertTitle } from '@/ui/primitives/alert' -import { CloudIcon, LaptopIcon, LinkIcon } from '@/ui/primitives/icons' - -// Minimum CLI version that supports the OAuth JWT auth flow. -// CLI versions >= this use the new public-client OAuth flow; -// older versions fall back to the legacy e2b access token flow. -// Old binaries (pre-2.13.0) don't send cliVersion at all (it's a new param), -// so they correctly fall to 'legacy'. -const MIN_CLI_VERSION_FOR_HYDRA_FLOW = '2.13.0' - -const CLI_UPDATE_COMMAND = 'npm install -g @e2b/cli@latest' - -// Shown to legacy (<2.13) CLIs when access-token provisioning is disabled. -// The dashboard sends this string to the CLI's loopback server, which echoes -// it back via `state=error`; keeping it in one place lets the error page -// recognize the update-required case and surface the deprecation help link. -const CLI_UPDATE_REQUIRED_MESSAGE = `CLI update required. Run: ${CLI_UPDATE_COMMAND}` - -type CLISearchParams = Promise<{ - next?: string - cliVersion?: string - state?: string - error?: string -}> - -type CLIFlow = 'hydra' | 'legacy' - -function resolveCLIFlow(cliVersion: string | undefined): CLIFlow { - if (!cliVersion) return 'legacy' - return isVersionGreaterOrEqual(cliVersion, MIN_CLI_VERSION_FOR_HYDRA_FLOW) - ? 'hydra' - : 'legacy' -} - -function handleHydraCLIAuth(next: string) { - if (!isLoopbackUrl(next)) { - throw new Error('Invalid redirect URL') - } - return redirect(`/api/auth/oauth/cli-start?next=${encodeURIComponent(next)}`) -} - -async function handleLegacyCLIAuth( - next: string, - userEmail: string, - authProviderAccessToken: string -) { - if (!isLoopbackUrl(next)) { - throw new Error('Invalid redirect URL') - } - - const teamsResult = await createUserTeamsRepository({ - accessToken: authProviderAccessToken, - }).listUserTeams() - - if (!teamsResult.ok) { - throw new Error('Failed to resolve default project') - } - - const defaultTeam = - teamsResult.data.find((team) => team.isDefault && team.slug) ?? - teamsResult.data.find((team) => team.slug) - - if (!defaultTeam) { - throw new Error('Failed to resolve default project') - } - - const e2bAccessToken = await generateE2BUserAccessToken( - authProviderAccessToken - ) - - const searchParams = new URLSearchParams({ - email: userEmail, - accessToken: e2bAccessToken.token, - defaultTeamId: defaultTeam.id, - }) - - return redirect(`${next}?${searchParams.toString()}`) -} - -function CLIIcons() { - return ( -

- - - - - - - - - -

- ) -} - -function ErrorState({ - message, - showDeprecationHelp = false, -}: { - message?: string - showDeprecationHelp?: boolean -}) { - return ( -
- -

Unable to link the CLI

-
- {showDeprecationHelp ? ( -
- - CLI update required - - Your CLI is out of date. Update to the latest version to - continue: - - - {CLI_UPDATE_COMMAND} -

- Access tokens are deprecated.{' '} - - Learn more - - . -

-
- ) : ( - - Error - - {message ?? - 'Something went wrong while linking the CLI. Please try again.'} - - - )} -
-
- ) -} - -function SuccessState() { - return ( - <> -

Successfully linked

-
You can close this page and start using CLI.
- - ) -} - -export default async function CLIAuthPage({ - searchParams, -}: { - searchParams: CLISearchParams -}) { - const { next, cliVersion, state, error } = await searchParams - const authContext = await getAuthContext() - - if (state === 'success') { - return - } - - if (state === 'error') { - return ( - - ) - } - - if (!next || !isLoopbackUrl(next)) { - l.error( - { - key: 'cli_auth:invalid_redirect_url', - user_id: authContext?.user.id, - context: { next }, - }, - `Invalid redirect URL: ${next}` - ) - redirect(PROTECTED_URLS.DASHBOARD) - } - - if (!authContext) { - const returnToParams = new URLSearchParams({ next }) - if (cliVersion) returnToParams.set('cliVersion', cliVersion) - return redirect( - `${AUTH_URLS.SIGN_IN}?returnTo=${encodeURIComponent( - `${AUTH_URLS.CLI}?${returnToParams.toString()}` - )}` - ) - } - - const flow = resolveCLIFlow(cliVersion) - - if (flow === 'legacy') { - const flagContext: FeatureFlagContext = { - user: { - id: authContext.user.id, - email: authContext.user.email ?? undefined, - }, - } - const tokenProvisioningDisabled = await featureFlags.isEnabled( - 'disableE2BAccessTokenProvisioning', - flagContext - ) - - if (tokenProvisioningDisabled) { - const errorUrl = new URL(next) - errorUrl.searchParams.set('error', CLI_UPDATE_REQUIRED_MESSAGE) - return redirect(errorUrl.toString()) - } - } - - if (error) { - return - } - - try { - if (flow === 'hydra') { - return handleHydraCLIAuth(next) - } - - if (!authContext.user.email) { - throw new Error('No user email found') - } - - return await handleLegacyCLIAuth( - next, - authContext.user.email, - authContext.accessToken - ) - } catch (err) { - if (err instanceof Error && err.message.includes('NEXT_REDIRECT')) { - throw err - } - - l.error( - { - key: 'cli_auth:unexpected_error', - error: serializeErrorForLog(err), - user_id: authContext.user.id, - context: { next, flow }, - }, - `Unexpected error during CLI authentication: ${err instanceof Error ? err.message : String(err)}` - ) - - const redirectParams: Record = { next } - if (cliVersion) redirectParams.cliVersion = cliVersion - - return encodedRedirect( - 'error', - '/auth/cli', - (err as Error).message, - redirectParams - ) - } -} diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index 79849e560..bac1ec15f 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,9 +1,8 @@ -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' import { METADATA } from '@/configs/metadata' import { cn } from '@/lib/utils' import { GridPattern } from '@/ui/grid-pattern' -const robots = ALLOW_SEO_INDEXING ? 'index, follow' : 'noindex, nofollow' +const robots = 'noindex, nofollow' export const metadata = { title: METADATA.title, diff --git a/src/app/dashboard/[teamSlug]/account/error.tsx b/src/app/dashboard/[teamSlug]/account/error.tsx deleted file mode 100644 index 4d4ecd0c8..000000000 --- a/src/app/dashboard/[teamSlug]/account/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function AccountPageError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/account/page.tsx b/src/app/dashboard/[teamSlug]/account/page.tsx deleted file mode 100644 index 3cefacfb7..000000000 --- a/src/app/dashboard/[teamSlug]/account/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { AccessTokenSettings } from '@/features/dashboard/account/access-token-settings' -import { EmailSettings } from '@/features/dashboard/account/email-settings' -import { NameSettings } from '@/features/dashboard/account/name-settings' -import { PasswordSettingsServer } from '@/features/dashboard/account/password-settings-server' -import { TimezoneSettings } from '@/features/dashboard/account/timezone-settings' - -export interface AccountPageSearchParams { - reauth?: '1' -} - -export default async function AccountPage({ - searchParams, -}: { - searchParams: Promise -}) { - return ( -
- - - - - - - - - -
- ) -} diff --git a/src/app/dashboard/[teamSlug]/agents/page.tsx b/src/app/dashboard/[teamSlug]/agents/page.tsx deleted file mode 100644 index 89f14af89..000000000 --- a/src/app/dashboard/[teamSlug]/agents/page.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import type { Metadata } from 'next' -import { notFound, redirect } from 'next/navigation' -import { AUTH_URLS } from '@/configs/urls' -import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server' -import { getAuthContext } from '@/core/server/auth' -import { getTeamIdFromSlug } from '@/core/server/functions/team/get-team-id-from-slug' -import { AgentsList } from '@/features/dashboard/agents/agents-list' -import { AGENT_TEMPLATES } from '@/features/dashboard/agents/config' -import { Page } from '@/features/dashboard/layouts/page' - -export const metadata: Metadata = { - title: 'Agents - E2B', -} - -type AgentsPageProps = { - params: Promise<{ - teamSlug: string - }> -} - -export default async function AgentsPage({ params }: AgentsPageProps) { - const [{ teamSlug }, authContext] = await Promise.all([ - params, - getAuthContext(), - ]) - - if (!authContext) { - redirect(AUTH_URLS.SIGN_IN) - } - - const teamId = await getTeamIdFromSlug(teamSlug, authContext.accessToken) - - if (!teamId.ok || !teamId.data) { - notFound() - } - - const agentsEnabled = await featureFlags.isEnabled('agentsEnabled', { - user: { - id: authContext.user.id, - email: authContext.user.email ?? undefined, - }, - team: { - id: teamId.data, - slug: teamSlug, - }, - }) - - if (!agentsEnabled) { - notFound() - } - - return ( - -
-

Agents

-

- Start a coding-agent sandbox and open it in the dashboard terminal. -

-
- - -
- ) -} diff --git a/src/app/dashboard/[teamSlug]/billing/error.tsx b/src/app/dashboard/[teamSlug]/billing/error.tsx deleted file mode 100644 index ed22d82be..000000000 --- a/src/app/dashboard/[teamSlug]/billing/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function BillingPageError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/billing/page.tsx b/src/app/dashboard/[teamSlug]/billing/page.tsx deleted file mode 100644 index 4c7111e69..000000000 --- a/src/app/dashboard/[teamSlug]/billing/page.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import Credits from '@/features/dashboard/billing/credits' -import Invoices from '@/features/dashboard/billing/invoices' -import SelectedPlan from '@/features/dashboard/billing/selected-plan' -import { HydrateClient, prefetch, trpc } from '@/trpc/server' - -export default async function BillingPage({ - params, -}: { - params: Promise<{ teamSlug: string }> -}) { - const { teamSlug } = await params - - prefetch(trpc.billing.getItems.queryOptions({ teamSlug })) - prefetch(trpc.billing.getUsage.queryOptions({ teamSlug })) - prefetch(trpc.billing.getInvoices.queryOptions({ teamSlug })) - - return ( - -
- - - -
-
- ) -} diff --git a/src/app/dashboard/[teamSlug]/billing/plan/error.tsx b/src/app/dashboard/[teamSlug]/billing/plan/error.tsx deleted file mode 100644 index 34f271d2c..000000000 --- a/src/app/dashboard/[teamSlug]/billing/plan/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function BillingPlanPageError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/billing/plan/page.tsx b/src/app/dashboard/[teamSlug]/billing/plan/page.tsx deleted file mode 100644 index ee3f5f824..000000000 --- a/src/app/dashboard/[teamSlug]/billing/plan/page.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import Addons from '@/features/dashboard/billing/addons' -import SelectedPlan from '@/features/dashboard/billing/selected-plan' -import { HydrateClient, prefetch, trpc } from '@/trpc/server' - -export default async function BillingPlanPage({ - params, -}: { - params: Promise<{ teamSlug: string }> -}) { - const { teamSlug } = await params - - prefetch(trpc.billing.getItems.queryOptions({ teamSlug })) - - return ( - -
- - -
-
- ) -} diff --git a/src/app/dashboard/[teamSlug]/billing/plan/select/error.tsx b/src/app/dashboard/[teamSlug]/billing/plan/select/error.tsx deleted file mode 100644 index 1459303c4..000000000 --- a/src/app/dashboard/[teamSlug]/billing/plan/select/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function BillingPlanSelectPageError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/billing/plan/select/page.tsx b/src/app/dashboard/[teamSlug]/billing/plan/select/page.tsx deleted file mode 100644 index 18a0014bb..000000000 --- a/src/app/dashboard/[teamSlug]/billing/plan/select/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import SelectPlan from '@/features/dashboard/billing/select-plan' -import { HydrateClient, prefetch, trpc } from '@/trpc/server' - -export default async function BillingPlanSelectPage({ - params, -}: { - params: Promise<{ teamSlug: string }> -}) { - const { teamSlug } = await params - - prefetch(trpc.billing.getItems.queryOptions({ teamSlug })) - - return ( - -
- -
-
- ) -} diff --git a/src/app/dashboard/[teamSlug]/byoc/page.tsx b/src/app/dashboard/[teamSlug]/byoc/page.tsx deleted file mode 100644 index d8e77603b..000000000 --- a/src/app/dashboard/[teamSlug]/byoc/page.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import type { Metadata } from 'next' -import { notFound, redirect } from 'next/navigation' -import { AUTH_URLS } from '@/configs/urls' -import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server' -import { getAuthContext } from '@/core/server/auth' -import { getTeamIdFromSlug } from '@/core/server/functions/team/get-team-id-from-slug' -import { ByocSetup } from '@/features/dashboard/byoc/byoc-setup' - -export const metadata: Metadata = { - title: 'BYOC - E2B', -} - -type ByocPageProps = { - params: Promise<{ - teamSlug: string - }> -} - -export default async function ByocPage({ params }: ByocPageProps) { - const [{ teamSlug }, authContext] = await Promise.all([ - params, - getAuthContext(), - ]) - - if (!authContext) { - redirect(AUTH_URLS.SIGN_IN) - } - - const teamId = await getTeamIdFromSlug(teamSlug, authContext.accessToken) - - if (!teamId.ok || !teamId.data) { - notFound() - } - - const byocSetup = await featureFlags.getPayload('byocSetup', { - user: { - id: authContext.user.id, - email: authContext.user.email ?? undefined, - }, - team: { - id: teamId.data, - slug: teamSlug, - }, - }) - - if (!byocSetup.enabled) { - notFound() - } - - return -} diff --git a/src/app/dashboard/[teamSlug]/connections/page.tsx b/src/app/dashboard/[teamSlug]/connections/page.tsx deleted file mode 100644 index fd43e17de..000000000 --- a/src/app/dashboard/[teamSlug]/connections/page.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import type { Metadata } from 'next' -import Link from 'next/link' -import { notFound, redirect } from 'next/navigation' -import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls' -import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server' -import { getAuthContext } from '@/core/server/auth' -import { getTeamIdFromSlug } from '@/core/server/functions/team/get-team-id-from-slug' -import { - DECLARED_CONNECTIONS, - mergeConnectionCatalog, -} from '@/features/dashboard/connections/catalog' -import { Page } from '@/features/dashboard/layouts/page' -import { Button } from '@/ui/primitives/button' -import { IntegrationsIcon } from '@/ui/primitives/icons' - -export const metadata: Metadata = { - title: 'Connections - E2B', -} - -type ConnectionsPageProps = { - params: Promise<{ - teamSlug: string - }> -} - -export default async function ConnectionsPage({ - params, -}: ConnectionsPageProps) { - const [{ teamSlug }, authContext] = await Promise.all([ - params, - getAuthContext(), - ]) - - if (!authContext) { - redirect(AUTH_URLS.SIGN_IN) - } - - const teamId = await getTeamIdFromSlug(teamSlug, authContext.accessToken) - - if (!teamId.ok || !teamId.data) { - notFound() - } - - const featureFlagContext = { - user: { - id: authContext.user.id, - email: authContext.user.email ?? undefined, - }, - team: { - id: teamId.data, - }, - } - const connectionsEnabled = await featureFlags.isEnabled( - 'connectionsEnabled', - featureFlagContext - ) - - if (!connectionsEnabled) { - notFound() - } - - const additionalConnections = await featureFlags.getPayload( - 'developmentConnections', - featureFlagContext - ) - const connections = mergeConnectionCatalog( - DECLARED_CONNECTIONS, - additionalConnections - ) - - return ( - -
-
-

Connections

-

- Connect services that run workloads in your E2B project. -

-
- - {connections.length > 0 ? ( -
- {connections.map((connection) => ( -
-
-
- -
-
-
-

- {connection.name} -

- - {connection.template} - -
-

- {connection.description} -

-
-
- -
- ))} -
- ) : null} -
-
- ) -} diff --git a/src/app/dashboard/[teamSlug]/general/error.tsx b/src/app/dashboard/[teamSlug]/general/error.tsx deleted file mode 100644 index d05558725..000000000 --- a/src/app/dashboard/[teamSlug]/general/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function GeneralPageError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/general/page.tsx b/src/app/dashboard/[teamSlug]/general/page.tsx deleted file mode 100644 index 0707ca4a1..000000000 --- a/src/app/dashboard/[teamSlug]/general/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Page } from '@/features/dashboard/layouts/page' -import { TeamAvatar } from '@/features/dashboard/settings/general/team-avatar' -import { TeamInfo } from '@/features/dashboard/settings/general/team-info' -import { TeamName } from '@/features/dashboard/settings/general/team-name' - -export default async function GeneralPage() { - return ( - - -
- -
- -
- - ) -} diff --git a/src/app/dashboard/[teamSlug]/keys/page.tsx b/src/app/dashboard/[teamSlug]/keys/page.tsx deleted file mode 100644 index e244b80e8..000000000 --- a/src/app/dashboard/[teamSlug]/keys/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Page } from '@/features/dashboard/layouts/page' -import { ApiKeysPageContent } from '@/features/dashboard/settings/keys' -import { HydrateClient, prefetch, trpc } from '@/trpc/server' - -interface KeysPageProps { - params: Promise<{ - teamSlug: string - }> -} - -export default async function KeysPage({ params }: KeysPageProps) { - const { teamSlug } = await params - - prefetch(trpc.teams.listApiKeys.queryOptions({ teamSlug })) - - return ( - - - - - - ) -} diff --git a/src/app/dashboard/[teamSlug]/layout.tsx b/src/app/dashboard/[teamSlug]/layout.tsx index d508b6a21..041662d51 100644 --- a/src/app/dashboard/[teamSlug]/layout.tsx +++ b/src/app/dashboard/[teamSlug]/layout.tsx @@ -5,20 +5,11 @@ import { DashboardTeamGate } from '@/app/dashboard/[teamSlug]/team-gate' import { COOKIE_KEYS } from '@/configs/cookies' import { METADATA } from '@/configs/metadata' import { AUTH_URLS } from '@/configs/urls' -import { DASHBOARD_TEAMS_LIST_QUERY_OPTIONS } from '@/core/application/teams/queries' import { DASHBOARD_USER_PROFILE_QUERY_OPTIONS } from '@/core/application/user/queries' -import { FeatureFlagsProvider } from '@/core/modules/feature-flags/feature-flags.client' -import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server' import { getAuthContext } from '@/core/server/auth' import DashboardLayoutView from '@/features/dashboard/layouts/layout' -import { ProjectsAnnouncementBanner } from '@/features/dashboard/projects-announcement/banner' import Sidebar from '@/features/dashboard/sidebar/sidebar' -import { - getQueryClient, - HydrateClient, - prefetchAsync, - trpc, -} from '@/trpc/server' +import { HydrateClient, prefetchAsync, trpc } from '@/trpc/server' import { CatchErrorBoundary } from '@/ui/error' import { SidebarInset, SidebarProvider } from '@/ui/primitives/sidebar' @@ -52,67 +43,33 @@ export default async function DashboardLayout({ throw redirect(AUTH_URLS.SIGN_IN) } - const teamsQueryOptions = trpc.teams.list.queryOptions( - undefined, - DASHBOARD_TEAMS_LIST_QUERY_OPTIONS - ) const userProfileQueryOptions = trpc.user.profile.queryOptions( undefined, DASHBOARD_USER_PROFILE_QUERY_OPTIONS ) - const queryClient = getQueryClient() - const teams = await queryClient - .fetchQuery(teamsQueryOptions) - .catch(() => null) - const team = teams?.find((candidate) => candidate.slug === teamSlug) - const dashboardTeam = team - ? { - id: team.id, - name: team.name, - slug: teamSlug, - } - : undefined - - const featureFlagContext = { - user: { - id: authContext.user.id, - email: authContext.user.email ?? undefined, - }, - team: dashboardTeam, - } - const [evaluatedFeatureFlags] = await Promise.all([ - featureFlags.evaluateAll(featureFlagContext), - prefetchAsync(userProfileQueryOptions), - ]) + await prefetchAsync(userProfileQueryOptions) return ( - - - -
- -
- {/* Anchored to this row instead of the viewport so the - announcement banner above stays fully visible. */} - - - - - {children} - - - -
+ + +
+
+ + + + + {children} + + +
- - - +
+
+
) } diff --git a/src/app/dashboard/[teamSlug]/limits/error.tsx b/src/app/dashboard/[teamSlug]/limits/error.tsx deleted file mode 100644 index 7dccbca76..000000000 --- a/src/app/dashboard/[teamSlug]/limits/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function LimitsPageError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/limits/loading.tsx b/src/app/dashboard/[teamSlug]/limits/loading.tsx deleted file mode 100644 index 249f11404..000000000 --- a/src/app/dashboard/[teamSlug]/limits/loading.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@/features/dashboard/loading-layout' diff --git a/src/app/dashboard/[teamSlug]/limits/page.tsx b/src/app/dashboard/[teamSlug]/limits/page.tsx deleted file mode 100644 index 334109859..000000000 --- a/src/app/dashboard/[teamSlug]/limits/page.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Page } from '@/features/dashboard/layouts/page' -import { UsageLimits } from '@/features/dashboard/limits/usage-limits' -import { HydrateClient, prefetch, trpc } from '@/trpc/server' - -interface LimitsPageProps { - params: Promise<{ teamSlug: string }> -} - -export default async function LimitsPage({ params }: LimitsPageProps) { - const { teamSlug } = await params - - prefetch(trpc.billing.getLimits.queryOptions({ teamSlug })) - - return ( - - - - - - ) -} diff --git a/src/app/dashboard/[teamSlug]/members/error.tsx b/src/app/dashboard/[teamSlug]/members/error.tsx deleted file mode 100644 index e2df67c76..000000000 --- a/src/app/dashboard/[teamSlug]/members/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function MembersPageError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/members/page.tsx b/src/app/dashboard/[teamSlug]/members/page.tsx deleted file mode 100644 index cd907cfda..000000000 --- a/src/app/dashboard/[teamSlug]/members/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Page } from '@/features/dashboard/layouts/page' -import { MembersPageContent } from '@/features/dashboard/members/members-page-content' -import { HydrateClient, prefetch, trpc } from '@/trpc/server' - -interface MembersPageProps { - params: Promise<{ - teamSlug: string - }> -} - -export default async function MembersPage({ params }: MembersPageProps) { - const { teamSlug } = await params - - prefetch(trpc.teams.members.queryOptions({ teamSlug })) - - return ( - - - - - - ) -} diff --git a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/error.tsx b/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/error.tsx deleted file mode 100644 index 45ac9f606..000000000 --- a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function SandboxesTabsError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/layout.tsx b/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/layout.tsx deleted file mode 100644 index 074de5997..000000000 --- a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/layout.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { PROTECTED_URLS } from '@/configs/urls' -import { isNewSandboxListEnabled } from '@/features/dashboard/sandboxes/list/feature-flag.server' -import { DashboardTabsList } from '@/ui/dashboard-tabs' -import { ListIcon, TrendIcon } from '@/ui/primitives/icons' - -export default async function SandboxesTabsLayout({ - children, - params, -}: LayoutProps<'/dashboard/[teamSlug]/sandboxes'>) { - const { teamSlug } = await params - const listHref = (await isNewSandboxListEnabled(teamSlug)) - ? PROTECTED_URLS.SANDBOXES_LIST2(teamSlug) - : PROTECTED_URLS.SANDBOXES_LIST(teamSlug) - - return ( -
- , - }, - { - id: 'list', - label: 'List', - href: listHref, - icon: , - }, - ]} - /> - {children} -
- ) -} diff --git a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list/error.tsx b/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list/error.tsx deleted file mode 100644 index 29b53e454..000000000 --- a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function SandboxesListError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list/page.tsx b/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list/page.tsx deleted file mode 100644 index fae1f1970..000000000 --- a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list/page.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { redirect } from 'next/navigation' -import { Suspense } from 'react' -import { PROTECTED_URLS } from '@/configs/urls' -import LoadingLayout from '@/features/dashboard/loading-layout' -import { isNewSandboxListEnabled } from '@/features/dashboard/sandboxes/list/feature-flag.server' -import SandboxesTable from '@/features/dashboard/sandboxes/list/table' -import { HydrateClient, prefetch, trpc } from '@/trpc/server' - -export default async function SandboxesListPage({ - params, -}: PageProps<'/dashboard/[teamSlug]/sandboxes/list'>) { - const { teamSlug } = await params - - if (await isNewSandboxListEnabled(teamSlug)) { - redirect(PROTECTED_URLS.SANDBOXES_LIST2(teamSlug)) - } - - prefetch( - trpc.sandboxes.getSandboxes.queryOptions({ - teamSlug, - }) - ) - - return ( - - }> - - - - ) -} diff --git a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list2/error.tsx b/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list2/error.tsx deleted file mode 100644 index 8346f7ae0..000000000 --- a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list2/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function NewSandboxesListError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/monitoring/error.tsx b/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/monitoring/error.tsx deleted file mode 100644 index e4b69bc39..000000000 --- a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/monitoring/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function SandboxesMonitoringError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/monitoring/page.tsx b/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/monitoring/page.tsx deleted file mode 100644 index 0273918be..000000000 --- a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/monitoring/page.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { TeamMetricsCharts } from '@/features/dashboard/sandboxes/monitoring/charts/charts' -import SandboxesMonitoringHeader from '@/features/dashboard/sandboxes/monitoring/header' - -type MonitoringSearchParams = { - start?: string - end?: string -} - -export default async function SandboxesMonitoringPage({ - params, - searchParams, -}: PageProps<'/dashboard/[teamSlug]/sandboxes/monitoring'> & { - searchParams: Promise -}) { - const { start, end } = await searchParams - - return ( -
- -
- -
-
- ) -} diff --git a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/page.tsx b/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/page.tsx deleted file mode 100644 index 8e3a22e7d..000000000 --- a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { redirect } from 'next/navigation' -import { PROTECTED_URLS } from '@/configs/urls' - -export default async function SandboxesTabsPage({ - params, -}: PageProps<'/dashboard/[teamSlug]/sandboxes'>) { - const { teamSlug } = await params - - redirect(PROTECTED_URLS.SANDBOXES_MONITORING(teamSlug)) -} diff --git a/src/app/dashboard/[teamSlug]/sandboxes/[sandboxId]/events/page.tsx b/src/app/dashboard/[teamSlug]/sandboxes/[sandboxId]/events/page.tsx deleted file mode 100644 index 64cce3cbf..000000000 --- a/src/app/dashboard/[teamSlug]/sandboxes/[sandboxId]/events/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { SandboxEventsView } from '@/features/dashboard/sandbox/events' - -export default function SandboxEventsPage() { - return -} diff --git a/src/app/dashboard/[teamSlug]/keys/error.tsx b/src/app/dashboard/[teamSlug]/sandboxes/error.tsx similarity index 85% rename from src/app/dashboard/[teamSlug]/keys/error.tsx rename to src/app/dashboard/[teamSlug]/sandboxes/error.tsx index 553282320..6d41cb2da 100644 --- a/src/app/dashboard/[teamSlug]/keys/error.tsx +++ b/src/app/dashboard/[teamSlug]/sandboxes/error.tsx @@ -2,7 +2,7 @@ import { DashboardRouteError } from '@/features/dashboard/shared/route-error' -export default function KeysPageError({ +export default function SandboxesError({ error, reset, }: { diff --git a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list2/page.tsx b/src/app/dashboard/[teamSlug]/sandboxes/page.tsx similarity index 63% rename from src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list2/page.tsx rename to src/app/dashboard/[teamSlug]/sandboxes/page.tsx index d9cafecff..7e091f10c 100644 --- a/src/app/dashboard/[teamSlug]/sandboxes/(tabs)/list2/page.tsx +++ b/src/app/dashboard/[teamSlug]/sandboxes/page.tsx @@ -1,22 +1,15 @@ -import { redirect } from 'next/navigation' import { Suspense } from 'react' -import { PROTECTED_URLS } from '@/configs/urls' import LoadingLayout from '@/features/dashboard/loading-layout' -import { isNewSandboxListEnabled } from '@/features/dashboard/sandboxes/list/feature-flag.server' import { NewSandboxesTable } from '@/features/dashboard/sandboxes/list/table' import { HydrateClient, prefetch, trpc } from '@/trpc/server' -export default async function NewSandboxesListPage({ +export default async function SandboxesListPage({ params, }: { params: Promise<{ teamSlug: string }> }) { const { teamSlug } = await params - if (!(await isNewSandboxListEnabled(teamSlug))) { - redirect(PROTECTED_URLS.SANDBOXES_LIST(teamSlug)) - } - prefetch( trpc.sandboxes.listSandboxesPaginated.infiniteQueryOptions({ teamSlug, diff --git a/src/app/dashboard/[teamSlug]/terminal/page.tsx b/src/app/dashboard/[teamSlug]/terminal/page.tsx deleted file mode 100644 index a65357029..000000000 --- a/src/app/dashboard/[teamSlug]/terminal/page.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import Link from 'next/link' -import type { Metadata } from 'next/types' -import { authHeaders } from '@/configs/api' -import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls' -import { createUserTeamsRepository } from '@/core/modules/teams/user-teams-repository.server' -import { getAuthContext } from '@/core/server/auth' -import { infra } from '@/core/shared/clients/api' -import type { components as InfraComponents } from '@/core/shared/contracts/infra-api.types' -import { SandboxIdSchema } from '@/core/shared/schemas/api' -import DashboardTerminal from '@/features/dashboard/terminal/dashboard-terminal' -import { normalizeTerminalTemplate } from '@/features/dashboard/terminal/template' -import { Button } from '@/ui/primitives/button' - -export const metadata: Metadata = { - title: 'Terminal - E2B', - robots: 'noindex, nofollow', -} - -interface TeamTerminalPageProps { - params: Promise<{ - teamSlug: string - }> - searchParams: Promise<{ - command?: string - sandboxId?: string - template?: string - }> -} - -export default async function TeamTerminalPage({ - params, - searchParams, -}: TeamTerminalPageProps) { - const [{ teamSlug }, { command = '', sandboxId, template }] = - await Promise.all([params, searchParams]) - const hasTemplateOverride = template !== undefined - const requestedTemplate = hasTemplateOverride - ? normalizeTerminalTemplate(template) - : undefined - const terminalSandboxId = normalizeTerminalSandboxId(sandboxId) - - if (!terminalSandboxId && hasTemplateOverride && !requestedTemplate) { - return - } - - if (terminalSandboxId === null) { - return - } - - const authContext = await getAuthContext() - - if (!authContext) { - return ( - - ) - } - - const teamsRepository = createUserTeamsRepository({ - accessToken: authContext.accessToken, - }) - const teamsResult = await teamsRepository.listUserTeams() - - if (!teamsResult.ok) { - return - } - - const team = teamsResult.data.find((candidate) => candidate.slug === teamSlug) - - if (!team) { - return - } - - const terminalSandbox = terminalSandboxId - ? await getSandboxInTeam({ - accessToken: authContext.accessToken, - sandboxId: terminalSandboxId, - teamId: team.id, - }) - : undefined - - if (terminalSandboxId && !terminalSandbox) { - return ( - - ) - } - - const terminalTemplate = terminalSandbox - ? (terminalSandbox.alias ?? terminalSandbox.templateID) - : (requestedTemplate ?? 'base') - - if (!terminalTemplate) { - return - } - - return ( -
- -
- ) -} - -function normalizeTerminalSandboxId(sandboxId?: string) { - const value = sandboxId?.trim() - if (!value) return undefined - - const parsedSandboxId = SandboxIdSchema.safeParse(value) - return parsedSandboxId.success ? parsedSandboxId.data : null -} - -async function getSandboxInTeam({ - accessToken, - sandboxId, - teamId, -}: { - accessToken: string - sandboxId: string - teamId: string -}): Promise { - try { - const result = await infra.GET('/sandboxes/{sandboxID}', { - params: { - path: { - sandboxID: sandboxId, - }, - }, - headers: { - ...authHeaders(accessToken, teamId), - }, - cache: 'no-store', - }) - - if (!result.response.ok || !result.data) return null - return result.data - } catch { - return null - } -} - -function TerminalSignIn({ - command, - sandboxId, - teamSlug, - template, -}: { - command?: string - sandboxId?: string - teamSlug: string - template?: string -}) { - const returnToQuery = new URLSearchParams({ - ...(command ? { command } : {}), - ...(sandboxId ? { sandboxId } : {}), - ...(template ? { template } : {}), - }).toString() - const returnTo = `${PROTECTED_URLS.TERMINAL(teamSlug)}${ - returnToQuery ? `?${returnToQuery}` : '' - }` - const signInHref = `${AUTH_URLS.SIGN_IN}?${new URLSearchParams({ - returnTo, - }).toString()}` - - return ( -
-
-
-

Sign in to open a terminal

-

- The terminal runs in your E2B dashboard account. -

-
- -
-
- ) -} - -function TerminalUnavailable({ - message = 'We could not resolve a dashboard project for this account.', -}: { - message?: string -}) { - return ( -
-
-

Terminal unavailable

-

{message}

-
-
- ) -} diff --git a/src/app/dashboard/[teamSlug]/usage/error.tsx b/src/app/dashboard/[teamSlug]/usage/error.tsx deleted file mode 100644 index 9bbb24a17..000000000 --- a/src/app/dashboard/[teamSlug]/usage/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function UsagePageError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/usage/loading.tsx b/src/app/dashboard/[teamSlug]/usage/loading.tsx deleted file mode 100644 index 249f11404..000000000 --- a/src/app/dashboard/[teamSlug]/usage/loading.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@/features/dashboard/loading-layout' diff --git a/src/app/dashboard/[teamSlug]/usage/page.tsx b/src/app/dashboard/[teamSlug]/usage/page.tsx deleted file mode 100644 index 2ae97b742..000000000 --- a/src/app/dashboard/[teamSlug]/usage/page.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { redirect } from 'next/navigation' -import { AUTH_URLS } from '@/configs/urls' -import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server' -import { getAuthContext } from '@/core/server/auth' -import { getTeamIdFromSlug } from '@/core/server/functions/team/get-team-id-from-slug' -import { getUsage } from '@/core/server/functions/usage/get-usage' -import { BillingTimezoneBanner } from '@/features/dashboard/usage/billing-timezone-banner' -import { EMPTY_USAGE } from '@/features/dashboard/usage/redesign/usage-fallback' -import { UsageMetricDetail } from '@/features/dashboard/usage/redesign/usage-metric-detail' -import { UsageChartsProvider } from '@/features/dashboard/usage/usage-charts-context' -import { UsageMetricChart } from '@/features/dashboard/usage/usage-metric-chart' -import { UsageTimezoneProvider } from '@/features/dashboard/usage/usage-timezone' -import { UsageTopTimeRangeControls } from '@/features/dashboard/usage/usage-top-time-range-controls' -import ErrorBoundary from '@/ui/error' -import Frame from '@/ui/frame' - -export default async function UsagePage({ - params, -}: { - params: Promise<{ teamSlug: string }> -}) { - const { teamSlug } = await params - - const authContext = await getAuthContext() - - if (!authContext) { - redirect(AUTH_URLS.SIGN_IN) - } - - const teamId = await getTeamIdFromSlug(teamSlug, authContext.accessToken) - - const newUsagePage = await featureFlags.isEnabled('newUsagePage', { - user: { - id: authContext.user.id, - email: authContext.user.email ?? undefined, - }, - team: - teamId.ok && teamId.data - ? { id: teamId.data, slug: teamSlug } - : undefined, - }) - - const result = await getUsage({ teamSlug }) - - // The redesign opens on the cost breakdown; it degrades to an empty state - // when usage can't be loaded instead of erroring the whole screen. - if (newUsagePage) { - return ( - - - - - - ) - } - - if (!result?.data || result.serverError || result.validationErrors) { - return ( - - ) - } - - return ( - - -
-
- - -
- - -
-
- - - - -
- -
-
-
-
- ) -} diff --git a/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/deliveries/page.tsx b/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/deliveries/page.tsx deleted file mode 100644 index 0ae08599a..000000000 --- a/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/deliveries/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { WebhookDeliveriesContent } from '@/features/dashboard/settings/webhooks/detail' -import { prefetch, trpc } from '@/trpc/server' - -type WebhookDeliveriesPageProps = { - params: Promise<{ - teamSlug: string - webhookId: string - }> -} - -export default async function WebhookDeliveriesPage({ - params, -}: WebhookDeliveriesPageProps) { - const { teamSlug, webhookId } = await params - - prefetch( - trpc.webhooks.listDeliveries.infiniteQueryOptions({ - teamSlug, - webhookId, - limit: 25, - }) - ) - - return -} diff --git a/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/layout.tsx b/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/layout.tsx deleted file mode 100644 index d0eaf8cff..000000000 --- a/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/layout.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { notFound } from 'next/navigation' -import { INCLUDE_ARGUS } from '@/configs/env-flags' -import { WebhookDetailLayout } from '@/features/dashboard/settings/webhooks/detail' -import { HydrateClient, prefetch, trpc } from '@/trpc/server' - -type WebhookTabsLayoutProps = { - children: React.ReactNode - params: Promise<{ - teamSlug: string - webhookId: string - }> -} - -export default async function WebhookTabsLayout({ - children, - params, -}: WebhookTabsLayoutProps) { - if (!INCLUDE_ARGUS) { - return notFound() - } - - const { teamSlug, webhookId } = await params - - prefetch(trpc.webhooks.get.queryOptions({ teamSlug, webhookId })) - prefetch( - trpc.webhooks.listDeliveries.queryOptions({ teamSlug, webhookId, limit: 1 }) - ) - - return ( - - - {children} - - - ) -} diff --git a/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/overview/page.tsx b/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/overview/page.tsx deleted file mode 100644 index 2be576401..000000000 --- a/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/(tabs)/overview/page.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { WebhookOverviewContent } from '@/features/dashboard/settings/webhooks/detail' -import { - getValidWebhookStatsBounds, - getWebhookStatsApiBounds, - getWebhookStatsRange, - loadWebhookStatsTimeframeParams, -} from '@/features/dashboard/settings/webhooks/detail/stats-range' -import { prefetch, trpc } from '@/trpc/server' - -type WebhookOverviewPageProps = { - params: Promise<{ - teamSlug: string - webhookId: string - }> - searchParams: Promise> -} - -export default async function WebhookOverviewPage({ - params, - searchParams, -}: WebhookOverviewPageProps) { - const { teamSlug, webhookId } = await params - const timeframeParams = await loadWebhookStatsTimeframeParams(searchParams) - const fallbackRangeBounds = getWebhookStatsRange('this-week') - const rangeBounds = getValidWebhookStatsBounds({ - start: timeframeParams.start ?? fallbackRangeBounds.start, - end: timeframeParams.end ?? fallbackRangeBounds.end, - }) - const apiRangeBounds = getWebhookStatsApiBounds(rangeBounds) - - prefetch( - trpc.webhooks.getDeliveryStats.queryOptions({ - teamSlug, - webhookId, - ...apiRangeBounds, - }) - ) - - return ( - - ) -} diff --git a/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/page.tsx b/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/page.tsx deleted file mode 100644 index e2af1c389..000000000 --- a/src/app/dashboard/[teamSlug]/webhooks/[webhookId]/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { redirect } from 'next/navigation' -import { PROTECTED_URLS } from '@/configs/urls' - -type WebhookDetailPageProps = { - params: Promise<{ - teamSlug: string - webhookId: string - }> -} - -export default async function WebhookDetailPage({ - params, -}: WebhookDetailPageProps) { - const { teamSlug, webhookId } = await params - - redirect(PROTECTED_URLS.WEBHOOK_OVERVIEW(teamSlug, webhookId)) -} diff --git a/src/app/dashboard/[teamSlug]/webhooks/error.tsx b/src/app/dashboard/[teamSlug]/webhooks/error.tsx deleted file mode 100644 index 780f8e741..000000000 --- a/src/app/dashboard/[teamSlug]/webhooks/error.tsx +++ /dev/null @@ -1,13 +0,0 @@ -'use client' - -import { DashboardRouteError } from '@/features/dashboard/shared/route-error' - -export default function WebhooksPageError({ - error, - reset, -}: { - error: Error & { digest?: string } - reset: () => void -}) { - return -} diff --git a/src/app/dashboard/[teamSlug]/webhooks/page.tsx b/src/app/dashboard/[teamSlug]/webhooks/page.tsx deleted file mode 100644 index 3cef4b11a..000000000 --- a/src/app/dashboard/[teamSlug]/webhooks/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { notFound } from 'next/navigation' -import { INCLUDE_ARGUS } from '@/configs/env-flags' -import { Page } from '@/features/dashboard/layouts/page' -import { WebhooksPageContent } from '@/features/dashboard/settings/webhooks/webhooks-page-content' -import { HydrateClient, prefetch, trpc } from '@/trpc/server' - -interface WebhooksPageProps { - params: Promise<{ - teamSlug: string - }> -} - -export default async function WebhooksPage({ params }: WebhooksPageProps) { - if (!INCLUDE_ARGUS) { - return notFound() - } - - const { teamSlug } = await params - - prefetch(trpc.webhooks.list.queryOptions({ teamSlug })) - - return ( - - - - - - ) -} diff --git a/src/app/dashboard/account/route.ts b/src/app/dashboard/account/route.ts deleted file mode 100644 index 914a84f45..000000000 --- a/src/app/dashboard/account/route.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { type NextRequest, NextResponse } from 'next/server' -import { AUTH_URLS, PROTECTED_URLS } from '@/configs/urls' -import { getAuthContext, signOut } from '@/core/server/auth' -import { resolveUserTeam } from '@/core/server/functions/team/resolve-user-team' -import { l } from '@/core/shared/clients/logger/logger' -import { setTeamCookies } from '@/lib/utils/cookies' - -export async function GET(request: NextRequest) { - const authContext = await getAuthContext() - - if (!authContext) { - return NextResponse.redirect(new URL(AUTH_URLS.SIGN_IN, request.url)) - } - - const team = await resolveUserTeam( - authContext.user.id, - authContext.accessToken - ) - - if (!team) { - l.warn( - { - key: 'dashboard_account:no_personal_team', - user_id: authContext.user.id, - }, - 'no personal team for user, signing out' - ) - - const { redirectTo } = await signOut({ - origin: request.nextUrl.origin, - }) - - return NextResponse.redirect(new URL(redirectTo, request.url)) - } - - await setTeamCookies(team.id, team.slug) - - const redirectPath = PROTECTED_URLS.RESOLVED_ACCOUNT_SETTINGS(team.slug) - const redirectUrl = new URL(redirectPath, request.url) - - request.nextUrl.searchParams.forEach((value, key) => { - redirectUrl.searchParams.set(key, value) - }) - - return NextResponse.redirect(redirectUrl) -} diff --git a/src/app/dashboard/terminal/route.ts b/src/app/dashboard/terminal/route.ts deleted file mode 100644 index e0926c9af..000000000 --- a/src/app/dashboard/terminal/route.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { type NextRequest, NextResponse } from 'next/server' -import { PROTECTED_URLS } from '@/configs/urls' - -export function GET(request: NextRequest) { - const redirectUrl = new URL(PROTECTED_URLS.DASHBOARD, request.url) - redirectUrl.search = request.nextUrl.search - redirectUrl.searchParams.set('tab', 'terminal') - - return NextResponse.redirect(redirectUrl) -} diff --git a/src/app/login/layout.tsx b/src/app/login/layout.tsx index cf074c68a..97f7a1aa7 100644 --- a/src/app/login/layout.tsx +++ b/src/app/login/layout.tsx @@ -1,4 +1,3 @@ -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' import { METADATA } from '@/configs/metadata' import { cn } from '@/lib/utils' import { GridPattern } from '@/ui/grid-pattern' @@ -10,7 +9,7 @@ export const metadata = { description: METADATA.description, openGraph: METADATA.openGraph, twitter: METADATA.twitter, - robots: ALLOW_SEO_INDEXING ? 'index, follow' : 'noindex, nofollow', + robots: 'noindex, nofollow', } export default function OryLoginLayout({ diff --git a/src/app/recovery/layout.tsx b/src/app/recovery/layout.tsx index 9c2fac242..c50825f67 100644 --- a/src/app/recovery/layout.tsx +++ b/src/app/recovery/layout.tsx @@ -1,4 +1,3 @@ -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' import { METADATA } from '@/configs/metadata' import { cn } from '@/lib/utils' import { GridPattern } from '@/ui/grid-pattern' @@ -10,7 +9,7 @@ export const metadata = { description: METADATA.description, openGraph: METADATA.openGraph, twitter: METADATA.twitter, - robots: ALLOW_SEO_INDEXING ? 'index, follow' : 'noindex, nofollow', + robots: 'noindex, nofollow', } export default function OryRecoveryLayout({ diff --git a/src/app/registration/layout.tsx b/src/app/registration/layout.tsx index fc8c86207..0f33ac457 100644 --- a/src/app/registration/layout.tsx +++ b/src/app/registration/layout.tsx @@ -1,4 +1,3 @@ -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' import { METADATA } from '@/configs/metadata' import { cn } from '@/lib/utils' import { GridPattern } from '@/ui/grid-pattern' @@ -10,7 +9,7 @@ export const metadata = { description: METADATA.description, openGraph: METADATA.openGraph, twitter: METADATA.twitter, - robots: ALLOW_SEO_INDEXING ? 'index, follow' : 'noindex, nofollow', + robots: 'noindex, nofollow', } export default function OryRegistrationLayout({ diff --git a/src/app/settings/layout.tsx b/src/app/settings/layout.tsx index 2c2cf6e73..8c6bd157e 100644 --- a/src/app/settings/layout.tsx +++ b/src/app/settings/layout.tsx @@ -1,4 +1,3 @@ -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' import { METADATA } from '@/configs/metadata' import { AUTH_URLS } from '@/configs/urls' import { buttonVariants } from '@/ui/primitives/button' @@ -6,7 +5,7 @@ import { buttonVariants } from '@/ui/primitives/button' export const metadata = { title: METADATA.title, description: METADATA.description, - robots: ALLOW_SEO_INDEXING ? 'index, follow' : 'noindex, nofollow', + robots: 'noindex, nofollow', } // Shell-less: no sidebar/team chrome, so the page renders with only a Kratos diff --git a/src/app/verification/layout.tsx b/src/app/verification/layout.tsx index 7024f4c21..44ce48ec5 100644 --- a/src/app/verification/layout.tsx +++ b/src/app/verification/layout.tsx @@ -1,4 +1,3 @@ -import { ALLOW_SEO_INDEXING } from '@/configs/env-flags' import { METADATA } from '@/configs/metadata' import { cn } from '@/lib/utils' import { GridPattern } from '@/ui/grid-pattern' @@ -10,7 +9,7 @@ export const metadata = { description: METADATA.description, openGraph: METADATA.openGraph, twitter: METADATA.twitter, - robots: ALLOW_SEO_INDEXING ? 'index, follow' : 'noindex, nofollow', + robots: 'noindex, nofollow', } export default function OryVerificationLayout({ diff --git a/src/configs/dashboard-tab-url-map.ts b/src/configs/dashboard-tab-url-map.ts index 8a8bde780..135ece57b 100644 --- a/src/configs/dashboard-tab-url-map.ts +++ b/src/configs/dashboard-tab-url-map.ts @@ -1,28 +1,12 @@ import { PROTECTED_URLS } from './urls' /** - * Maps `/dashboard?tab=` query values to team-scoped dashboard URLs. + * Maps `?tab=` query values to dashboard URLs. * * These `?tab=` links are NOT legacy: they are stable public entrypoints used - * to crosslink into the dashboard from outside (emails, docs, the CLI, etc.). - * They must keep working forever — do not remove tabs or change this contract - * without coordinating with the places that link here. + * to crosslink into the dashboard from outside (docs, the CLI, etc.). */ export const TAB_URL_MAP: Record string> = { sandboxes: (teamSlug) => PROTECTED_URLS.SANDBOXES(teamSlug), templates: (teamSlug) => PROTECTED_URLS.TEMPLATES(teamSlug), - usage: (teamSlug) => PROTECTED_URLS.USAGE(teamSlug), - billing: (teamSlug) => PROTECTED_URLS.BILLING(teamSlug), - limits: (teamSlug) => PROTECTED_URLS.LIMITS(teamSlug), - keys: (teamSlug) => PROTECTED_URLS.KEYS(teamSlug), - settings: (teamSlug) => PROTECTED_URLS.GENERAL(teamSlug), - team: (teamSlug) => PROTECTED_URLS.GENERAL(teamSlug), - general: (teamSlug) => PROTECTED_URLS.GENERAL(teamSlug), - members: (teamSlug) => PROTECTED_URLS.MEMBERS(teamSlug), - byoc: (teamSlug) => PROTECTED_URLS.BYOC(teamSlug), - connections: (teamSlug) => PROTECTED_URLS.CONNECTIONS(teamSlug), - account: (_) => PROTECTED_URLS.ACCOUNT_SETTINGS, - personal: (_) => PROTECTED_URLS.ACCOUNT_SETTINGS, - terminal: (teamSlug) => PROTECTED_URLS.TERMINAL(teamSlug), - budget: (teamSlug) => PROTECTED_URLS.LIMITS(teamSlug), } diff --git a/src/configs/env-flags.ts b/src/configs/env-flags.ts index 8756d04c0..c4ec4963a 100644 --- a/src/configs/env-flags.ts +++ b/src/configs/env-flags.ts @@ -1,16 +1,4 @@ -export const ALLOW_SEO_INDEXING = process.env.ALLOW_SEO_INDEXING === '1' export const VERBOSE = process.env.NEXT_PUBLIC_VERBOSE === '1' -export const INCLUDE_BILLING = process.env.NEXT_PUBLIC_INCLUDE_BILLING === '1' -export const INCLUDE_ARGUS = process.env.NEXT_PUBLIC_INCLUDE_ARGUS === '1' -export const INCLUDE_STATUS_INDICATOR = - process.env.NEXT_PUBLIC_INCLUDE_STATUS_INDICATOR === '1' export const USE_MOCK_DATA = process.env.VERCEL_ENV !== 'production' && process.env.NEXT_PUBLIC_MOCK_DATA === '1' - -export const INCLUDE_DASHBOARD_FEEDBACK_SURVEY = - process.env.NEXT_PUBLIC_POSTHOG_DASHBOARD_FEEDBACK_SURVEY_ID && - process.env.NEXT_PUBLIC_POSTHOG_KEY - -export const INCLUDE_REPORT_ISSUE = - process.env.NEXT_PUBLIC_INCLUDE_REPORT_ISSUE === '1' diff --git a/src/configs/intervals.ts b/src/configs/intervals.ts index 55ed8971c..8b48f899a 100644 --- a/src/configs/intervals.ts +++ b/src/configs/intervals.ts @@ -9,4 +9,3 @@ export const TEAM_METRICS_POLLING_INTERVAL_MS = 10_000 export const TEAM_METRICS_TIMEFRAME_UPDATE_MS = 10_000 // backend metrics collection interval -export const TEAM_METRICS_BACKEND_COLLECTION_INTERVAL_MS = 30_000 diff --git a/src/configs/mock-data.ts b/src/configs/mock-data.ts index 4d886d6b3..39c45d441 100644 --- a/src/configs/mock-data.ts +++ b/src/configs/mock-data.ts @@ -1,10 +1,7 @@ import { addHours, subHours } from 'date-fns' import { nanoid } from 'nanoid' import type { Sandbox, Sandboxes } from '@/core/modules/sandboxes/models' -import type { - ClientSandboxesMetrics, - ClientTeamMetrics, -} from '@/core/modules/sandboxes/models.client' +import type { ClientSandboxesMetrics } from '@/core/modules/sandboxes/models.client' import type { DefaultTemplate, Template } from '@/core/modules/templates/models' const DEFAULT_TEMPLATES: DefaultTemplate[] = [ @@ -952,258 +949,8 @@ function generateMockMetrics(sandboxes: Sandbox[]): { * This function replicates the back-end step calculation logic from e2b-dev/infra. * https://github.com/e2b-dev/infra/blob/19778a715e8df3adea83858c798582d289bd7159/packages/api/internal/handlers/sandbox_metrics.go#L90 */ -export function calculateTeamMetricsStep( - startMs: number, - endMs: number -): number { - const duration = endMs - startMs - const hour = 60 * 60 * 1000 - const minute = 60 * 1000 - const second = 1000 - - switch (true) { - case duration < hour: - return 5 * second - case duration < 6 * hour: - return 30 * second - case duration < 12 * hour: - return minute - case duration < 24 * hour: - return 2 * minute - case duration < 7 * 24 * hour: - return 5 * minute - default: - return 15 * minute - } -} - -/** - * Generate mock team metrics for monitoring charts - * Supports small, medium, and large teams with realistic patterns - * Can generate data for the past 30 days from now - */ -export function generateMockTeamMetrics( - startMs: number, - endMs: number -): { metrics: ClientTeamMetrics; step: number } { - const now = Date.now() - const thirtyDaysAgo = now - 30 * 24 * 60 * 60 * 1000 - - // Clamp start time to no earlier than 30 days ago - if (startMs < thirtyDaysAgo) { - startMs = thirtyDaysAgo - } - - // Don't generate data beyond current time - if (endMs > now) { - endMs = now - } - const profiles = { - small: { - baseConcurrent: 50, - peakConcurrent: 200, - baseRate: 0.1, - peakRate: 2, - volatility: 0.15, - }, - medium: { - baseConcurrent: 500, - peakConcurrent: 5000, - baseRate: 1, - peakRate: 10, - volatility: 0.2, - }, - large: { - baseConcurrent: 20000, - peakConcurrent: 80000, - baseRate: 5, - peakRate: 25, - volatility: 0.25, - }, - } - - const profile = profiles.large - const metrics: ClientTeamMetrics = [] - - // Use the backend's step calculation logic - const step = calculateTeamMetricsStep(startMs, endMs) - - // Normalize start time based on step size - let normalizedStartMs: number - const second = 1000 - const minute = 60 * second - - if (step < minute) { - // For steps less than a minute, normalize to nearest step boundary - if (step === 5 * second) { - // Normalize to nearest 5 seconds - normalizedStartMs = Math.floor(startMs / (5 * second)) * (5 * second) - } else if (step === 30 * second) { - // Normalize to 00 or 30 seconds - normalizedStartMs = Math.floor(startMs / (30 * second)) * (30 * second) - } else { - normalizedStartMs = startMs - } - } else { - // For minute-based steps, normalize to the minute - normalizedStartMs = Math.floor(startMs / minute) * minute - } - - // Calculate normalized end time to ensure we generate data up to "now" or requested end - let normalizedEndMs: number - if (step < minute) { - if (step === 5 * second) { - normalizedEndMs = Math.floor(endMs / (5 * second)) * (5 * second) - } else if (step === 30 * second) { - normalizedEndMs = Math.floor(endMs / (30 * second)) * (30 * second) - } else { - normalizedEndMs = endMs - } - } else { - normalizedEndMs = Math.floor(endMs / minute) * minute - } - - const durationMs = normalizedEndMs - normalizedStartMs - let numPoints = Math.floor(durationMs / step) + 1 // +1 to include the end point - - // Cap number of points for performance - numPoints = Math.min(numPoints, 2000) - - // Generate realistic patterns - for (let i = 0; i < numPoints; i++) { - const timestamp = normalizedStartMs + i * step - - // Don't generate points beyond the normalized end time or current time - if (timestamp > normalizedEndMs || timestamp > now) break - - const date = new Date(timestamp) - - // Time-based patterns - const hourOfDay = date.getHours() - const dayOfWeek = date.getDay() - const minuteOfHour = date.getMinutes() - - // Business hours factor (9-17 in user's timezone) - const isBusinessHours = hourOfDay >= 9 && hourOfDay <= 17 - const isWeekday = dayOfWeek >= 1 && dayOfWeek <= 5 - - // Calculate load factors - let loadFactor = 0.3 // base load - - if (isWeekday) { - if (isBusinessHours) { - // Peak during business hours - loadFactor = 0.7 + Math.sin(((hourOfDay - 9) * Math.PI) / 8) * 0.3 - - // Add lunch dip - if (hourOfDay === 12 || hourOfDay === 13) { - loadFactor *= 0.85 - } - } else if (hourOfDay >= 6 && hourOfDay < 9) { - // Morning ramp-up - loadFactor = 0.3 + (hourOfDay - 6) * 0.15 - } else if (hourOfDay > 17 && hourOfDay <= 20) { - // Evening wind-down - loadFactor = 0.7 - (hourOfDay - 17) * 0.1 - } else { - // Night time - loadFactor = 0.2 + Math.random() * 0.1 - } - } else { - // Weekend - lower but some activity - loadFactor = 0.15 + Math.sin((hourOfDay * Math.PI) / 12) * 0.1 - } - - // Add some micro-patterns (spikes and dips) - const microPattern = Math.sin((minuteOfHour * Math.PI) / 30) * 0.05 - loadFactor += microPattern - - // Add random volatility - const randomVolatility = (Math.random() - 0.5) * profile.volatility - loadFactor = Math.max(0.1, Math.min(1, loadFactor + randomVolatility)) - - // Calculate metrics based on load - const concurrentSandboxes = Math.round( - profile.baseConcurrent + - (profile.peakConcurrent - profile.baseConcurrent) * loadFactor - ) - - // Start rate has higher volatility and spikiness - const rateVolatility = (Math.random() - 0.5) * profile.volatility * 2 - const startRateFactor = Math.max(0, loadFactor + rateVolatility) - - // Add occasional spikes in start rate (deployments, CI/CD runs) - const hasSpike = Math.random() < 0.02 // 2% chance of spike - const spikeFactor = hasSpike ? 1.5 + Math.random() : 1 - - const sandboxStartRate = Math.max( - 0, - (profile.baseRate + - (profile.peakRate - profile.baseRate) * startRateFactor) * - spikeFactor - ) - - metrics.push({ - timestamp, - concurrentSandboxes, - sandboxStartRate: Math.round(sandboxStartRate * 100) / 100, // Round to 2 decimals - }) - } - - // Add some smoothing to make it more realistic - for (let i = 1; i < metrics.length - 1; i++) { - const prev = metrics[i - 1]! - const curr = metrics[i]! - const next = metrics[i + 1]! - - // Smooth concurrent sandboxes (they change more gradually) - curr.concurrentSandboxes = Math.round( - prev.concurrentSandboxes * 0.3 + - curr.concurrentSandboxes * 0.4 + - next.concurrentSandboxes * 0.3 - ) - } - - return { metrics, step } -} - export const MOCK_METRICS_DATA = (sandboxes: Sandbox[]) => generateMockMetrics(sandboxes) export const MOCK_SANDBOXES_DATA = () => generateMockSandboxes(120) export const MOCK_TEMPLATES_DATA = TEMPLATES export const MOCK_DEFAULT_TEMPLATES_DATA = DEFAULT_TEMPLATES -export const MOCK_TEAM_METRICS_DATA = generateMockTeamMetrics - -/** - * Generate mock max team metrics data for a given date range and metric - */ -export const MOCK_TEAM_METRICS_MAX_DATA = ( - startDateMs: number, - endDateMs: number, - metric: 'concurrent_sandboxes' | 'sandbox_start_rate' -) => { - const metrics = generateMockTeamMetrics(startDateMs, endDateMs) - - // find the maximum value for the requested metric - let maxValue = 0 - let maxTimestamp = startDateMs - - for (const m of metrics.metrics) { - const value = - metric === 'concurrent_sandboxes' - ? m.concurrentSandboxes - : m.sandboxStartRate - - if (value > maxValue) { - maxValue = value - maxTimestamp = m.timestamp - } - } - - return { - timestamp: maxTimestamp, - timestampUnix: maxTimestamp, // already in milliseconds - value: maxValue, - metric, - } -} diff --git a/src/configs/sidebar.ts b/src/configs/sidebar.ts index ea7061ac7..d2bd02fa0 100644 --- a/src/configs/sidebar.ts +++ b/src/configs/sidebar.ts @@ -1,40 +1,19 @@ -import type { BooleanFeatureFlagId } from '@/core/modules/feature-flags/boolean-definitions' -import { - AccountSettingsIcon, - CardIcon, - CloudIcon, - GaugeIcon, - type Icon, - IntegrationsIcon, - KeyIcon, - PersonsIcon, - SandboxIcon, - SettingsIcon, - TemplateIcon, - TerminalIcon, - UsageIcon, - WebhookIcon, -} from '@/ui/primitives/icons' -import { INCLUDE_ARGUS, INCLUDE_BILLING } from './env-flags' +import { type Icon, SandboxIcon, TemplateIcon } from '@/ui/primitives/icons' import { PROTECTED_URLS } from './urls' type SidebarNavArgs = { teamSlug?: string } -export type SidebarFeatureFlagId = BooleanFeatureFlagId | 'byocSetup' - export type SidebarNavItem = { label: string href: (args: SidebarNavArgs) => string icon: Icon group?: string activeMatch?: string - featureFlag?: SidebarFeatureFlagId } export const SIDEBAR_MAIN_LINKS: SidebarNavItem[] = [ - // Base { label: 'Sandboxes', href: (args) => PROTECTED_URLS.SANDBOXES(args.teamSlug!), @@ -47,112 +26,6 @@ export const SIDEBAR_MAIN_LINKS: SidebarNavItem[] = [ icon: TemplateIcon, activeMatch: `/dashboard/*/templates/**`, }, - - // Integration - { - label: 'Agents', - group: 'integration', - href: (args) => PROTECTED_URLS.AGENTS(args.teamSlug!), - icon: TerminalIcon, - activeMatch: `/dashboard/*/agents`, - featureFlag: 'agentsEnabled', - }, - { - label: 'Connections', - group: 'integration', - href: (args) => PROTECTED_URLS.CONNECTIONS(args.teamSlug!), - icon: IntegrationsIcon, - activeMatch: `/dashboard/*/connections`, - featureFlag: 'connectionsEnabled', - }, - { - label: 'BYOC', - group: 'integration', - href: (args) => PROTECTED_URLS.BYOC(args.teamSlug!), - icon: CloudIcon, - activeMatch: `/dashboard/*/byoc`, - featureFlag: 'byocSetup', - }, - ...(INCLUDE_ARGUS - ? [ - { - label: 'Webhooks', - group: 'integration', - href: (args: SidebarNavArgs) => - PROTECTED_URLS.WEBHOOKS(args.teamSlug!), - icon: WebhookIcon, - activeMatch: `/dashboard/*/webhooks/**`, - }, - ] - : []), - - // Project - { - label: 'General', - href: (args) => PROTECTED_URLS.GENERAL(args.teamSlug!), - icon: SettingsIcon, - group: 'project', - activeMatch: `/dashboard/*/general`, - }, - { - label: 'API Keys', - href: (args) => PROTECTED_URLS.KEYS(args.teamSlug!), - icon: KeyIcon, - group: 'project', - activeMatch: `/dashboard/*/keys`, - }, - { - label: 'Members', - href: (args) => PROTECTED_URLS.MEMBERS(args.teamSlug!), - icon: PersonsIcon, - group: 'project', - activeMatch: `/dashboard/*/members`, - }, - - // Billing - ...(INCLUDE_BILLING - ? [ - { - label: 'Usage', - href: (args: SidebarNavArgs) => PROTECTED_URLS.USAGE(args.teamSlug!), - icon: UsageIcon, - group: 'billing', - activeMatch: `/dashboard/*/usage/**`, - }, - { - label: 'Limits', - href: (args: SidebarNavArgs) => PROTECTED_URLS.LIMITS(args.teamSlug!), - group: 'billing', - icon: GaugeIcon, - activeMatch: `/dashboard/*/limits/**`, - }, - { - label: 'Billing', - href: (args: SidebarNavArgs) => - PROTECTED_URLS.BILLING(args.teamSlug!), - icon: CardIcon, - group: 'billing', - activeMatch: `/dashboard/*/billing/**`, - }, - ] - : []), ] -export const SIDEBAR_EXTRA_LINKS: SidebarNavItem[] = [ - { - label: 'Account Settings', - href: () => PROTECTED_URLS.ACCOUNT_SETTINGS, - icon: AccountSettingsIcon, - }, -] - -export const SIDEBAR_ALL_LINKS = [...SIDEBAR_MAIN_LINKS, ...SIDEBAR_EXTRA_LINKS] - -export function filterSidebarLinks( - links: SidebarNavItem[], - isEnabled: (flagId: SidebarFeatureFlagId) => boolean -) { - return links.filter( - (link) => !link.featureFlag || isEnabled(link.featureFlag) - ) -} +export const SIDEBAR_ALL_LINKS = [...SIDEBAR_MAIN_LINKS] diff --git a/src/core/modules/billing/models.ts b/src/core/modules/billing/models.ts deleted file mode 100644 index 51f4f6547..000000000 --- a/src/core/modules/billing/models.ts +++ /dev/null @@ -1,111 +0,0 @@ -export interface Invoice { - cost: number - paid: boolean - url: string - date_created: string - credits_used: number -} - -export interface BillingLimit { - limit_amount_gte: number | null - alert_amount_gte: number | null -} - -export interface CustomerPortalResponse { - url: string -} - -export interface UsageResponse { - credits: number - day_usages: { - date: string - sandbox_count: number - cpu_hours: number - ram_gib_hours: number - price_for_ram: number - price_for_cpu: number - }[] - hour_usages: { - timestamp: number - sandbox_count: number - cpu_hours: number - ram_gib_hours: number - price_for_ram: number - price_for_cpu: number - }[] -} - -export interface CreateTeamsResponse { - id: string - slug: string -} - -export interface AddOnOrderItem { - name: string - quantity: number -} - -export interface AddOnOrderCreateResponse { - id: string - amount_due: number - items: AddOnOrderItem[] -} - -export interface AddOnOrderConfirmResponse { - client_secret: string -} - -export interface VerificationPaymentResponse { - client_secret: string - amount_due_cents: number -} - -export interface PaymentMethodsCustomerSession { - client_secret: string -} - -export interface PaymentMethodsSession { - client_secret: string - setup_intent_client_secret?: string -} - -export interface PaymentMethodsSetupSession extends PaymentMethodsSession { - setup_intent_client_secret: string -} - -export interface TierLimits { - sandbox_concurrency: number - max_cpu: number - max_ram_mib: number - max_sandbox_duration_hours: number - disk_size_mib: number -} - -export interface TierInfo { - id: string - name: string - price_cents: number - limits?: TierLimits -} - -export interface AddonInfo { - id: string - name: string - price_cents: number - quantity?: number -} - -export interface TeamAddons { - current: AddonInfo[] - available: AddonInfo[] -} - -export interface TeamTiers { - current: string - available: TierInfo[] -} - -export interface TeamItems { - tiers: TeamTiers - addons: TeamAddons -} diff --git a/src/core/modules/billing/repository.server.ts b/src/core/modules/billing/repository.server.ts deleted file mode 100644 index 8d2561bb1..000000000 --- a/src/core/modules/billing/repository.server.ts +++ /dev/null @@ -1,340 +0,0 @@ -import 'server-only' - -import { z } from 'zod' -import { authHeaders } from '@/configs/api' -import type { - AddOnOrderConfirmResponse, - AddOnOrderCreateResponse, - BillingLimit, - CustomerPortalResponse, - Invoice, - PaymentMethodsCustomerSession, - PaymentMethodsSetupSession, - TeamItems, - UsageResponse, - VerificationPaymentResponse, -} from '@/core/modules/billing/models' -import { repoErrorFromHttp } from '@/core/shared/errors' -import type { TeamRequestScope } from '@/core/shared/repository-scope' -import { err, ok, type RepoResult } from '@/core/shared/result' - -type BillingRepositoryDeps = { - billingApiUrl: string -} - -export type BillingScope = TeamRequestScope - -export interface BillingRepository { - createCheckout(tierId: string): Promise> - createCustomerPortalSession( - origin?: string | null - ): Promise> - getItems(): Promise> - getUsage(): Promise> - getInvoices(): Promise> - getLimits(): Promise> - setLimit(key: string, value: number): Promise> - clearLimit(key: string): Promise> - createOrder(itemId: string): Promise> - confirmOrder(orderId: string): Promise> - getCustomerSession(): Promise> - createPaymentMethodsSession(): Promise> - createVerificationPayment(): Promise> -} - -async function parseText(response: Response): Promise { - return (await response.text()) || 'Request failed' -} - -const PaymentMethodsSessionResponseSchema = z.object({ - client_secret: z.string().min(1), - setup_intent_client_secret: z.string().min(1), -}) - -const VerificationPaymentResponseSchema = z.object({ - client_secret: z.string().min(1), - amount_due_cents: z.number().int().positive(), -}) - -export function createBillingRepository( - scope: BillingScope, - deps: BillingRepositoryDeps = { - billingApiUrl: process.env.BILLING_API_URL ?? '', - } -): BillingRepository { - return { - async createCheckout(tierId) { - const res = await fetch(`${deps.billingApiUrl}/checkouts`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - body: JSON.stringify({ - teamID: scope.teamId, - tierID: tierId, - }), - }) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - const data = (await res.json()) as { url: string; error?: string } - if (data.error) { - return err(repoErrorFromHttp(500, data.error)) - } - - return ok({ url: data.url }) - }, - async createCustomerPortalSession(origin) { - const res = await fetch(`${deps.billingApiUrl}/stripe/portal`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...(origin ? { Origin: origin } : {}), - ...authHeaders(scope.accessToken, scope.teamId), - }, - }) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - return ok((await res.json()) as CustomerPortalResponse) - }, - async getItems() { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/items`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - return ok((await res.json()) as TeamItems) - }, - async getUsage() { - const res = await fetch( - `${deps.billingApiUrl}/v2/teams/${scope.teamId}/usage`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - const responseData = (await res.json()) as UsageResponse - return ok({ - ...responseData, - hour_usages: responseData.hour_usages.map((hour) => ({ - ...hour, - timestamp: hour.timestamp * 1000, - })), - }) - }, - async getInvoices() { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/invoices`, - { - headers: { - ...authHeaders(scope.accessToken, scope.teamId), - }, - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - return ok((await res.json()) as Invoice[]) - }, - async getLimits() { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/billing-limits`, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - const data = (await res.json()) as Partial - return ok({ - limit_amount_gte: data.limit_amount_gte ?? null, - alert_amount_gte: data.alert_amount_gte ?? null, - }) - }, - async setLimit(key, value) { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/billing-limits`, - { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - body: JSON.stringify({ - [key]: value, - }), - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - return ok(undefined) - }, - async clearLimit(key) { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/billing-limits/${key}`, - { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - return ok(undefined) - }, - async createOrder(itemId) { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/orders`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - body: JSON.stringify({ - items: [{ name: itemId, quantity: 1 }], - }), - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - return ok((await res.json()) as AddOnOrderCreateResponse) - }, - async confirmOrder(orderId) { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/orders/${orderId}`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - return ok((await res.json()) as AddOnOrderConfirmResponse) - }, - async getCustomerSession() { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/payment-methods-session`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - return ok((await res.json()) as PaymentMethodsCustomerSession) - }, - async createPaymentMethodsSession() { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/payment-methods-session?include_setup_intent=true`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - const parseResult = PaymentMethodsSessionResponseSchema.safeParse( - await res.json() - ) - - if (!parseResult.success) { - return err( - repoErrorFromHttp(500, 'Invalid payment methods session response') - ) - } - - return ok(parseResult.data) - }, - async createVerificationPayment() { - const res = await fetch( - `${deps.billingApiUrl}/teams/${scope.teamId}/verification-payment`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders(scope.accessToken, scope.teamId), - }, - } - ) - - if (!res.ok) { - return err(repoErrorFromHttp(res.status, await parseText(res))) - } - - const parseResult = VerificationPaymentResponseSchema.safeParse( - await res.json() - ) - - if (!parseResult.success) { - return err( - repoErrorFromHttp(500, 'Invalid verification payment response') - ) - } - - return ok(parseResult.data) - }, - } -} diff --git a/src/core/modules/feature-flags/boolean-definitions.ts b/src/core/modules/feature-flags/boolean-definitions.ts deleted file mode 100644 index 17a284ce1..000000000 --- a/src/core/modules/feature-flags/boolean-definitions.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { BooleanFeatureFlagDefinition } from '@/core/modules/feature-flags/types' - -export const BOOLEAN_FEATURE_FLAGS = { - agentsEnabled: { - kind: 'boolean', - key: 'agents_enabled', - defaultValue: false, - description: 'Enables the dashboard agents launcher.', - exposure: 'both', - }, - connectionsEnabled: { - kind: 'boolean', - key: 'connections_enabled', - defaultValue: false, - description: 'Enables the dashboard connections page.', - exposure: 'both', - }, - isAdmin: { - kind: 'boolean', - key: 'is_admin', - defaultValue: false, - description: 'Enables dashboard admin-only surfaces.', - exposure: 'server', - }, - newSandboxList: { - kind: 'boolean', - key: 'new_sandbox_list', - defaultValue: false, - description: - 'Enables the new sandbox list with pagination and paused sandbox coverage.', - exposure: 'both', - }, - newUsagePage: { - kind: 'boolean', - key: 'new-usage-page', - defaultValue: false, - description: 'Enables the redesigned (Dashboard 2.0) usage page skeleton.', - exposure: 'server', - }, - disableE2BAccessTokenProvisioning: { - kind: 'boolean', - key: 'disable_e2b_access_token_provisioning', - defaultValue: false, - description: - 'Disables provisioning of e2b access tokens via generateE2BUserAccessToken. When enabled, the legacy CLI flow shows an upgrade prompt and the createAccessToken tRPC mutation returns an error.', - exposure: 'both', - }, -} as const satisfies Record - -export type BooleanFeatureFlagId = keyof typeof BOOLEAN_FEATURE_FLAGS diff --git a/src/core/modules/feature-flags/context.ts b/src/core/modules/feature-flags/context.ts deleted file mode 100644 index 8881bb6dc..000000000 --- a/src/core/modules/feature-flags/context.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type FeatureFlagContext = { - user: { - id: string - email?: string - } - team?: { - id: string - slug?: string - name?: string - } -} diff --git a/src/core/modules/feature-flags/definitions.ts b/src/core/modules/feature-flags/definitions.ts deleted file mode 100644 index 918e1a8b3..000000000 --- a/src/core/modules/feature-flags/definitions.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { z } from 'zod' -import { - BOOLEAN_FEATURE_FLAGS, - type BooleanFeatureFlagId, -} from '@/core/modules/feature-flags/boolean-definitions' -import type { FeatureFlagDefinition } from '@/core/modules/feature-flags/types' - -export const developmentConnectionSchema = z.object({ - name: z.string().trim().min(1).max(100), - template: z - .string() - .trim() - .regex(/^[a-zA-Z0-9][a-zA-Z0-9_.:/-]{0,127}$/), - description: z.string().trim().min(1).max(500), -}) - -export type DevelopmentConnection = z.infer - -const gcpRegionSchema = z - .string() - .trim() - .regex(/^[a-z][a-z0-9-]{2,62}$/) - -const byocSetupTemplateSchema = z.string().trim().min(1).max(50_000) - -export const byocSetupSchema = z.object({ - enabled: z.literal(true), - principal: z - .string() - .trim() - .regex( - /^serviceAccount:[a-z][a-z0-9-]{4,28}[a-z0-9]@[a-z][a-z0-9-]{4,28}[a-z0-9]\.iam\.gserviceaccount\.com$/ - ), - regions: z - .array(gcpRegionSchema) - .min(1) - .max(20) - .refine((regions) => new Set(regions).size === regions.length), - templates: z.object({ - gcloud: byocSetupTemplateSchema, - terraform: byocSetupTemplateSchema, - }), -}) - -export type ByocSetupConfig = z.infer -export type ByocSetupValue = ByocSetupConfig | { enabled: false } - -export const FEATURE_FLAGS = { - ...BOOLEAN_FEATURE_FLAGS, - byocSetup: { - kind: 'payload', - key: 'byoc_setup', - defaultValue: { enabled: false } as ByocSetupValue, - schema: z.discriminatedUnion('enabled', [ - z.object({ enabled: z.literal(false) }), - byocSetupSchema, - ]), - description: 'Configures team-targeted BYOC onboarding.', - exposure: 'both', - }, - developmentConnections: { - kind: 'payload', - key: 'dev_connections', - defaultValue: [] as DevelopmentConnection[], - schema: z.array(developmentConnectionSchema).max(50), - description: - 'Adds team-targeted development services to the Connections page.', - exposure: 'server', - }, -} as const satisfies Record - -export type FeatureFlagId = keyof typeof FEATURE_FLAGS - -export type FeatureFlagIdByKind = { - [Id in FeatureFlagId]: (typeof FEATURE_FLAGS)[Id]['kind'] extends Kind - ? Id - : never -}[FeatureFlagId] - -export type { BooleanFeatureFlagId } -export type PayloadFeatureFlagId = FeatureFlagIdByKind<'payload'> - -export type PayloadFeatureFlagValue = - (typeof FEATURE_FLAGS)[Id]['defaultValue'] diff --git a/src/core/modules/feature-flags/feature-flags.client.tsx b/src/core/modules/feature-flags/feature-flags.client.tsx deleted file mode 100644 index a0a1b3843..000000000 --- a/src/core/modules/feature-flags/feature-flags.client.tsx +++ /dev/null @@ -1,82 +0,0 @@ -'use client' - -import { - createContext, - type ReactNode, - useCallback, - useContext, - useMemo, -} from 'react' -import { - BOOLEAN_FEATURE_FLAGS, - type BooleanFeatureFlagId, -} from '@/core/modules/feature-flags/boolean-definitions' -import type { EvaluatedFeatureFlag } from '@/core/modules/feature-flags/types' - -type FeatureFlagsContextValue = { - flags: EvaluatedFeatureFlag[] - isEnabled(flagId: BooleanFeatureFlagId): boolean - hasPayload(flagId: 'byocSetup'): boolean -} - -const FeatureFlagsContext = createContext( - undefined -) - -export function FeatureFlagsProvider({ - children, - initialFlags, -}: { - children: ReactNode - initialFlags: EvaluatedFeatureFlag[] -}) { - const flagsById = useMemo( - () => new Map(initialFlags.map((flag) => [flag.id, flag])), - [initialFlags] - ) - - const isEnabled = useCallback( - (flagId: BooleanFeatureFlagId) => { - const evaluatedFlag = flagsById.get(flagId) - - if (typeof evaluatedFlag?.value === 'boolean') { - return evaluatedFlag.value - } - - return BOOLEAN_FEATURE_FLAGS[flagId].defaultValue - }, - [flagsById] - ) - - const hasPayload = (flagId: 'byocSetup') => { - const value = flagsById.get(flagId)?.value - return ( - typeof value === 'object' && - value !== null && - 'enabled' in value && - value.enabled === true - ) - } - - const value = { flags: initialFlags, hasPayload, isEnabled } - - return ( - - {children} - - ) -} - -export function useFeatureFlags() { - const context = useContext(FeatureFlagsContext) - - if (!context) { - throw new Error('useFeatureFlags must be used within FeatureFlagsProvider') - } - - return context -} - -export function useFeatureFlag(flagId: BooleanFeatureFlagId) { - return useFeatureFlags().isEnabled(flagId) -} diff --git a/src/core/modules/feature-flags/feature-flags.server.ts b/src/core/modules/feature-flags/feature-flags.server.ts deleted file mode 100644 index e331a0d93..000000000 --- a/src/core/modules/feature-flags/feature-flags.server.ts +++ /dev/null @@ -1,147 +0,0 @@ -import 'server-only' - -import type { FeatureFlagContext } from '@/core/modules/feature-flags/context' -import { - type BooleanFeatureFlagId, - FEATURE_FLAGS, - type FeatureFlagId, - type PayloadFeatureFlagId, - type PayloadFeatureFlagValue, -} from '@/core/modules/feature-flags/definitions' -import type { - EvaluatedFeatureFlag, - FeatureFlagDefinition, - PayloadFeatureFlagDefinition, -} from '@/core/modules/feature-flags/types' -import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger' -import { launchDarklyOpenFeatureProvider } from './launchdarkly-openfeature-provider.server' -import type { - FeatureFlagProvider, - FeatureFlagSnapshot, -} from './provider.server' - -export type FeatureFlagService = { - isEnabled( - flagId: BooleanFeatureFlagId, - context: FeatureFlagContext - ): Promise - getPayload( - flagId: Id, - context: FeatureFlagContext - ): Promise> - evaluateAll(context: FeatureFlagContext): Promise -} - -function logUnexpectedFlagValue(flag: FeatureFlagDefinition, value: unknown) { - if (value === undefined) { - return - } - - l.warn( - { - key: 'feature_flags:unexpected_value', - context: { - flagKey: flag.key, - expectedKind: flag.kind, - actualType: typeof value, - }, - }, - 'Feature flag returned an unexpected value' - ) -} - -function parsePayload( - flag: PayloadFeatureFlagDefinition, - payload: unknown -): T { - if (payload === undefined) { - return flag.defaultValue - } - - const parsed = flag.schema.safeParse(payload) - - if (!parsed.success) { - l.warn( - { - key: 'feature_flags:invalid_payload', - context: { flagKey: flag.key }, - error: serializeErrorForLog(parsed.error), - }, - 'Feature flag payload has invalid shape' - ) - - return flag.defaultValue - } - - return parsed.data -} - -function getEvaluatedValue( - flag: FeatureFlagDefinition, - snapshot: FeatureFlagSnapshot -) { - if (flag.kind === 'boolean') { - const value = snapshot.getFlagValue(flag.key) - - if (typeof value === 'boolean') { - return value - } - - logUnexpectedFlagValue(flag, value) - return flag.defaultValue - } - - return parsePayload(flag, snapshot.getPayload(flag.key)) -} - -export function createFeatureFlagService( - provider: FeatureFlagProvider = launchDarklyOpenFeatureProvider -): FeatureFlagService { - return { - async isEnabled(flagId, context) { - const flag = FEATURE_FLAGS[flagId] - const snapshot = await provider.evaluate(context, [flag]) - const value = snapshot.getFlagValue(flag.key) - - if (typeof value === 'boolean') { - return value - } - - logUnexpectedFlagValue(flag, value) - return flag.defaultValue - }, - - async getPayload(flagId, context) { - const flag = FEATURE_FLAGS[flagId] as PayloadFeatureFlagDefinition< - PayloadFeatureFlagValue - > - const snapshot = await provider.evaluate(context, [flag]) - - return parsePayload(flag, snapshot.getPayload(flag.key)) - }, - - async evaluateAll(context) { - const flags = ( - Object.entries(FEATURE_FLAGS) as [ - FeatureFlagId, - FeatureFlagDefinition, - ][] - ).filter(([, flag]) => flag.exposure !== 'server') - const snapshot = await provider.evaluate( - context, - flags.map(([, flag]) => flag) - ) - - return flags.map(([id, flag]) => ({ - id, - key: flag.key, - kind: flag.kind, - description: flag.description, - defaultValue: flag.defaultValue, - value: getEvaluatedValue(flag, snapshot), - })) - }, - } -} - -export const featureFlags = createFeatureFlagService() diff --git a/src/core/modules/feature-flags/launchdarkly-openfeature-provider.server.ts b/src/core/modules/feature-flags/launchdarkly-openfeature-provider.server.ts deleted file mode 100644 index 57d40d648..000000000 --- a/src/core/modules/feature-flags/launchdarkly-openfeature-provider.server.ts +++ /dev/null @@ -1,141 +0,0 @@ -import 'server-only' - -import type { - EvaluationContext, - EvaluationDetails, - JsonValue, -} from '@openfeature/server-sdk' -import type { FeatureFlagContext } from '@/core/modules/feature-flags/context' -import type { FeatureFlagDefinition } from '@/core/modules/feature-flags/types' -import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger' -import { getOpenFeatureServerClient } from './openfeature-client.server' -import { - type FeatureFlagProvider, - type FeatureFlagSnapshot, - unavailableSnapshot, -} from './provider.server' - -function definedStringAttributes( - attributes: Record -) { - return Object.fromEntries( - Object.entries(attributes).filter((entry): entry is [string, string] => { - const [, value] = entry - return typeof value === 'string' && value.length > 0 - }) - ) -} - -export function createOpenFeatureEvaluationContext( - context: FeatureFlagContext -): EvaluationContext { - const user = { - targetingKey: context.user.id, - ...definedStringAttributes({ - email: context.user.email, - }), - } - - if (!context.team) { - return { - kind: 'user', - ...user, - } - } - - return { - kind: 'multi', - user, - team: { - targetingKey: context.team.id, - ...definedStringAttributes({ - name: context.team.name, - slug: context.team.slug, - }), - }, - } -} - -function logEvaluationError( - flag: FeatureFlagDefinition, - details: EvaluationDetails -) { - if (details.reason !== 'ERROR') { - return - } - - l.warn( - { - key: 'feature_flags:launchdarkly_evaluation_error', - context: { - flagKey: flag.key, - errorCode: details.errorCode, - errorMessage: details.errorMessage, - }, - }, - 'LaunchDarkly feature flag evaluation returned an error result' - ) -} - -function toJsonValue(value: unknown): JsonValue { - return value as JsonValue -} - -export const launchDarklyOpenFeatureProvider: FeatureFlagProvider = { - async evaluate(context, flags) { - const client = await getOpenFeatureServerClient() - - if (!client) { - return unavailableSnapshot - } - - const evaluationContext = createOpenFeatureEvaluationContext(context) - const flagValues = new Map() - const payloads = new Map() - - try { - await Promise.all( - flags.map(async (flag) => { - if (flag.kind === 'boolean') { - const details = await client.getBooleanDetails( - flag.key, - flag.defaultValue, - evaluationContext - ) - logEvaluationError(flag, details) - flagValues.set(flag.key, details.value) - return - } - - const details = await client.getObjectDetails( - flag.key, - toJsonValue(flag.defaultValue), - evaluationContext - ) - logEvaluationError(flag, details) - payloads.set(flag.key, details.value) - }) - ) - - return { - getFlagValue(key) { - return flagValues.get(key) - }, - getPayload(key) { - return payloads.get(key) - }, - } satisfies FeatureFlagSnapshot - } catch (error) { - l.warn( - { - key: 'feature_flags:launchdarkly_evaluation_failed', - context: { flagKeys: flags.map((flag) => flag.key) }, - error: serializeErrorForLog(error), - }, - 'LaunchDarkly feature flag evaluation failed' - ) - - return unavailableSnapshot - } - }, -} diff --git a/src/core/modules/feature-flags/openfeature-client.server.ts b/src/core/modules/feature-flags/openfeature-client.server.ts deleted file mode 100644 index 66e757f7a..000000000 --- a/src/core/modules/feature-flags/openfeature-client.server.ts +++ /dev/null @@ -1,83 +0,0 @@ -import 'server-only' - -import { LaunchDarklyProvider } from '@launchdarkly/openfeature-node-server' -import { type Client, OpenFeature } from '@openfeature/server-sdk' -import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger' - -const OPENFEATURE_DOMAIN = 'dashboard-feature-flags' -const LAUNCHDARKLY_INIT_TIMEOUT_SECONDS = 3 -const LAUNCHDARKLY_INIT_RETRY_INTERVAL_MS = 60_000 - -let openFeatureClientPromise: Promise | undefined -let launchDarklyInitializationFailedAt: number | undefined -let loggedMissingLaunchDarklyKey = false - -function getLaunchDarklySdkKey() { - const key = process.env.LAUNCHDARKLY_SDK_KEY?.trim() - return key || null -} - -async function initializeOpenFeatureClient(sdkKey: string) { - try { - const provider = new LaunchDarklyProvider( - sdkKey, - { - sendEvents: false, - }, - LAUNCHDARKLY_INIT_TIMEOUT_SECONDS - ) - - await OpenFeature.setProviderAndWait(OPENFEATURE_DOMAIN, provider) - launchDarklyInitializationFailedAt = undefined - - return OpenFeature.getClient(OPENFEATURE_DOMAIN) - } catch (error) { - launchDarklyInitializationFailedAt = Date.now() - l.warn( - { - key: 'feature_flags:launchdarkly_initialization_failed', - error: serializeErrorForLog(error), - }, - 'LaunchDarkly OpenFeature provider initialization failed' - ) - - return null - } -} - -function shouldRetryFailedInitialization() { - return ( - launchDarklyInitializationFailedAt !== undefined && - Date.now() - launchDarklyInitializationFailedAt >= - LAUNCHDARKLY_INIT_RETRY_INTERVAL_MS - ) -} - -export function getOpenFeatureServerClient() { - if (openFeatureClientPromise !== undefined) { - if (shouldRetryFailedInitialization()) { - openFeatureClientPromise = undefined - launchDarklyInitializationFailedAt = undefined - } else { - return openFeatureClientPromise - } - } - - const sdkKey = getLaunchDarklySdkKey() - - if (!sdkKey) { - if (!loggedMissingLaunchDarklyKey) { - loggedMissingLaunchDarklyKey = true - l.warn( - { key: 'feature_flags:launchdarkly_unconfigured' }, - 'LaunchDarkly feature flags are disabled because LAUNCHDARKLY_SDK_KEY is missing' - ) - } - - openFeatureClientPromise = Promise.resolve(null) - return openFeatureClientPromise - } - - openFeatureClientPromise = initializeOpenFeatureClient(sdkKey) - return openFeatureClientPromise -} diff --git a/src/core/modules/feature-flags/provider.server.ts b/src/core/modules/feature-flags/provider.server.ts deleted file mode 100644 index 5197f7a15..000000000 --- a/src/core/modules/feature-flags/provider.server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import 'server-only' - -import type { FeatureFlagContext } from '@/core/modules/feature-flags/context' -import type { FeatureFlagDefinition } from '@/core/modules/feature-flags/types' - -export type FeatureFlagSnapshot = { - getFlagValue(key: string): unknown - getPayload(key: string): unknown -} - -export type FeatureFlagProvider = { - evaluate( - context: FeatureFlagContext, - flags: readonly FeatureFlagDefinition[] - ): Promise -} - -export const unavailableSnapshot: FeatureFlagSnapshot = { - getFlagValue: () => undefined, - getPayload: () => undefined, -} diff --git a/src/core/modules/feature-flags/types.ts b/src/core/modules/feature-flags/types.ts deleted file mode 100644 index e2286cb18..000000000 --- a/src/core/modules/feature-flags/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { z } from 'zod' - -export type FeatureFlagExposure = 'server' | 'client' | 'both' - -type BaseFeatureFlagDefinition = { - key: string - defaultValue: unknown - description?: string - exposure?: FeatureFlagExposure -} - -export type BooleanFeatureFlagDefinition = BaseFeatureFlagDefinition & { - kind: 'boolean' - defaultValue: boolean -} - -export type PayloadFeatureFlagDefinition = BaseFeatureFlagDefinition & { - kind: 'payload' - defaultValue: T - schema: z.ZodType -} - -export type FeatureFlagDefinition = - | BooleanFeatureFlagDefinition - | PayloadFeatureFlagDefinition - -export type EvaluatedFeatureFlag = { - id: string - key: string - kind: FeatureFlagDefinition['kind'] - value: unknown - defaultValue: unknown - description?: string -} diff --git a/src/core/modules/keys/models.ts b/src/core/modules/keys/models.ts deleted file mode 100644 index ce1e5094e..000000000 --- a/src/core/modules/keys/models.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { components as InfraComponents } from '@/contracts/infra-api' - -export type CreatedTeamAPIKey = InfraComponents['schemas']['CreatedTeamAPIKey'] -export type TeamAPIKey = InfraComponents['schemas']['TeamAPIKey'] diff --git a/src/core/modules/keys/repository.server.ts b/src/core/modules/keys/repository.server.ts deleted file mode 100644 index 0d408e4f4..000000000 --- a/src/core/modules/keys/repository.server.ts +++ /dev/null @@ -1,124 +0,0 @@ -import 'server-only' - -import { authHeaders } from '@/configs/api' -import type { CreatedTeamAPIKey, TeamAPIKey } from '@/core/modules/keys/models' -import { - type AuthUserEmailResolver, - getAuthUserEmailsById, - resolveCreatorEmails, -} from '@/core/modules/users/auth-user-emails.server' -import { infra } from '@/core/shared/clients/api' -import { createRepoError, repoErrorFromHttp } from '@/core/shared/errors' -import type { TeamRequestScope } from '@/core/shared/repository-scope' -import { err, ok, type RepoResult } from '@/core/shared/result' - -type KeysRepositoryDeps = { - infraClient: typeof infra - authHeaders: typeof authHeaders - resolveAuthUserEmailsById: AuthUserEmailResolver -} - -export type KeysScope = TeamRequestScope - -export interface KeysRepository { - listTeamApiKeys(): Promise> - createApiKey(name: string): Promise> - deleteApiKey(apiKeyId: string): Promise> -} - -export function createKeysRepository( - scope: KeysScope, - deps: KeysRepositoryDeps = { - infraClient: infra, - authHeaders: authHeaders, - resolveAuthUserEmailsById: getAuthUserEmailsById, - } -): KeysRepository { - return { - async listTeamApiKeys() { - const res = await deps.infraClient.GET('/api-keys', { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - }) - - if (!res.response.ok || res.error) { - return err( - repoErrorFromHttp( - res.response.status, - res.error?.message ?? 'Failed to get API keys', - res.error - ) - ) - } - - return ok( - await resolveCreatorEmails( - res.data ?? [], - deps.resolveAuthUserEmailsById - ) - ) - }, - async createApiKey(name) { - const res = await deps.infraClient.POST('/api-keys', { - body: { - name, - }, - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - }) - - if (!res.response.ok || res.error) { - return err( - repoErrorFromHttp( - res.response.status, - res.error?.message ?? 'Failed to create API key', - res.error - ) - ) - } - - if (!res.data) { - return err( - createRepoError({ - code: 'internal', - status: 500, - message: 'Failed to create API key', - }) - ) - } - - const [apiKey] = await resolveCreatorEmails( - [res.data], - deps.resolveAuthUserEmailsById - ) - - return ok(apiKey ?? res.data) - }, - async deleteApiKey(apiKeyId) { - const res = await deps.infraClient.DELETE('/api-keys/{apiKeyID}', { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - params: { - path: { - apiKeyID: apiKeyId, - }, - }, - }) - - if (!res.response.ok || res.error) { - return err( - repoErrorFromHttp( - res.response.status, - res.error?.message ?? 'Failed to delete API key', - res.error - ) - ) - } - - return ok(undefined) - }, - } -} diff --git a/src/core/modules/keys/schemas.ts b/src/core/modules/keys/schemas.ts deleted file mode 100644 index bb6005c48..000000000 --- a/src/core/modules/keys/schemas.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { z } from 'zod' - -const API_KEY_NAME_MAX_LENGTH = 50 - -const CreateApiKeySchema = z.object({ - name: z - .string({ error: 'Name is required' }) - .min(1, 'Name cannot be empty') - .max(API_KEY_NAME_MAX_LENGTH, { - message: `Name cannot be longer than ${API_KEY_NAME_MAX_LENGTH} characters`, - }) - .trim(), -}) - -export { CreateApiKeySchema } diff --git a/src/core/modules/sandboxes/lifecycle-event-types.ts b/src/core/modules/sandboxes/lifecycle-event-types.ts deleted file mode 100644 index 220284bce..000000000 --- a/src/core/modules/sandboxes/lifecycle-event-types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { z } from 'zod' - -const SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX = 'sandbox.lifecycle.' - -const SandboxLifecycleEventTypeSchema = z.enum([ - `${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}created`, - `${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}updated`, - `${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}paused`, - `${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}resumed`, - `${SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX}killed`, -]) - -type SandboxLifecycleEventType = z.infer - -export { - SANDBOX_LIFECYCLE_EVENT_TYPE_PREFIX, - SandboxLifecycleEventTypeSchema, - type SandboxLifecycleEventType, -} diff --git a/src/core/modules/sandboxes/models.client.ts b/src/core/modules/sandboxes/models.client.ts index 37049db03..06d6cd5c7 100644 --- a/src/core/modules/sandboxes/models.client.ts +++ b/src/core/modules/sandboxes/models.client.ts @@ -1,5 +1,3 @@ -import type { TeamMetric } from './models' - export type ClientSandboxMetric = { cpuCount: number cpuUsedPct: number @@ -11,17 +9,3 @@ export type ClientSandboxMetric = { } export type ClientSandboxesMetrics = Record - -export type ClientTeamMetric = Pick< - TeamMetric, - 'concurrentSandboxes' | 'sandboxStartRate' -> & { - timestamp: number -} - -export type ClientTeamMetrics = Array - -export type TeamMetricsResponse = { - metrics: ClientTeamMetrics - step: number -} diff --git a/src/core/modules/sandboxes/models.ts b/src/core/modules/sandboxes/models.ts index 253eb1c94..ef7d84dc6 100644 --- a/src/core/modules/sandboxes/models.ts +++ b/src/core/modules/sandboxes/models.ts @@ -1,4 +1,3 @@ -import type { components as ArgusComponents } from '@/contracts/argus-api' import type { components as DashboardComponents } from '@/contracts/dashboard-api' import type { components as InfraComponents } from '@/contracts/infra-api' @@ -8,7 +7,6 @@ export type Sandboxes = InfraComponents['schemas']['ListedSandbox'][] export type SandboxState = InfraComponents['schemas']['SandboxState'] export type SandboxesMetricsRecord = InfraComponents['schemas']['SandboxesWithMetrics']['sandboxes'] -export type TeamMetric = InfraComponents['schemas']['TeamMetric'] export type SandboxInfo = InfraComponents['schemas']['SandboxDetail'] interface SandboxDetailsBaseModel { @@ -53,7 +51,14 @@ export interface SandboxLogsModel { } export type SandboxMetric = InfraComponents['schemas']['SandboxMetric'] -export type SandboxEventModel = ArgusComponents['schemas']['SandboxEvent'] + +// OSS: sandbox lifecycle events (argus) are not available; the shape is kept +// for parity with console. `lifecycle.events` is always empty here. +export interface SandboxEventModel { + id: string + timestamp: string + type: string +} export interface SandboxLifecycleModel { createdAt: string | null @@ -62,84 +67,6 @@ export interface SandboxLifecycleModel { events: SandboxEventModel[] } -const SANDBOX_LIFECYCLE_EVENT_PREFIX = 'sandbox.lifecycle.' -const SANDBOX_LIFECYCLE_CREATED_EVENT = 'sandbox.lifecycle.created' -const SANDBOX_LIFECYCLE_PAUSED_EVENT = 'sandbox.lifecycle.paused' -const SANDBOX_LIFECYCLE_RESUMED_EVENT = 'sandbox.lifecycle.resumed' -const SANDBOX_LIFECYCLE_KILLED_EVENT = 'sandbox.lifecycle.killed' - -function parseEventTimestampMs(value: string): number | null { - if (!value) { - return null - } - - const timestampMs = new Date(value).getTime() - if (!Number.isFinite(timestampMs)) { - return null - } - - return timestampMs -} - -function sortEventsByTimestamp( - events: SandboxEventModel[] -): SandboxEventModel[] { - return [...events].sort((a, b) => { - const timestampA = - parseEventTimestampMs(a.timestamp) ?? Number.MAX_SAFE_INTEGER - const timestampB = - parseEventTimestampMs(b.timestamp) ?? Number.MAX_SAFE_INTEGER - if (timestampA !== timestampB) { - return timestampA - timestampB - } - - return a.id.localeCompare(b.id) - }) -} - -export function deriveSandboxLifecycleFromEvents( - events: SandboxEventModel[] -): SandboxLifecycleModel { - const lifecycleEvents = sortEventsByTimestamp( - events.filter((event) => - event.type.startsWith(SANDBOX_LIFECYCLE_EVENT_PREFIX) - ) - ) - - let createdAt: string | null = null - let pausedAt: string | null = null - let endedAt: string | null = null - let lastEvent: SandboxEventModel | null = null - - for (const event of lifecycleEvents) { - const timestampMs = parseEventTimestampMs(event.timestamp) - if (timestampMs === null) { - continue - } - - if (event.type === SANDBOX_LIFECYCLE_CREATED_EVENT && createdAt === null) { - createdAt = event.timestamp - } - - lastEvent = event - } - - if (lastEvent?.type === SANDBOX_LIFECYCLE_PAUSED_EVENT) { - pausedAt = lastEvent.timestamp - } - - if (lastEvent?.type === SANDBOX_LIFECYCLE_KILLED_EVENT) { - endedAt = lastEvent.timestamp - } - - return { - createdAt, - pausedAt, - endedAt, - events: lifecycleEvents, - } -} - // mappings export function mapInfraSandboxLogToModel( diff --git a/src/core/modules/sandboxes/repository.server.ts b/src/core/modules/sandboxes/repository.server.ts index b98d6afa6..cced1b58d 100644 --- a/src/core/modules/sandboxes/repository.server.ts +++ b/src/core/modules/sandboxes/repository.server.ts @@ -4,11 +4,9 @@ import { authHeaders } from '@/configs/api' import type { components as DashboardComponents } from '@/contracts/dashboard-api' import type { components as InfraComponents } from '@/contracts/infra-api' import type { - SandboxEventModel, Sandboxes, SandboxesMetricsRecord, SandboxState, - TeamMetric, } from '@/core/modules/sandboxes/models' import { api, infra } from '@/core/shared/clients/api' import { l } from '@/core/shared/clients/logger/logger' @@ -67,36 +65,20 @@ export interface SandboxesRepository { } > > - getSandboxLifecycleEvents( - sandboxId: string - ): Promise> getSandboxMetrics( sandboxId: string, options: GetSandboxMetricsOptions ): Promise> - listSandboxes(): Promise> listSandboxesPaginated( options: ListSandboxesOptions ): Promise> getSandboxesMetrics( sandboxIds: string[] ): Promise> - getTeamMetricsRange( - startUnixSeconds: number, - endUnixSeconds: number - ): Promise> - getTeamMetricsMax( - startUnixSeconds: number, - endUnixSeconds: number, - metric: 'concurrent_sandboxes' | 'sandbox_start_rate' - ): Promise> } const SANDBOX_NOT_FOUND_MESSAGE = "Sandbox not found or you don't have access to it" -const SANDBOX_EVENTS_PAGE_SIZE = 100 -const SANDBOX_EVENTS_MAX_PAGES = 50 -const SANDBOX_LIFECYCLE_EVENT_PREFIX = 'sandbox.lifecycle.' export function createSandboxesRepository( scope: SandboxesRequestScope, @@ -248,79 +230,6 @@ export function createSandboxesRepository( ) ) }, - async getSandboxLifecycleEvents(sandboxId) { - const lifecycleEvents: SandboxEventModel[] = [] - - for ( - let pageIndex = 0, offset = 0; - pageIndex < SANDBOX_EVENTS_MAX_PAGES; - pageIndex += 1, offset += SANDBOX_EVENTS_PAGE_SIZE - ) { - try { - const result = await deps.infraClient.GET( - '/events/sandboxes/{sandboxID}', - { - params: { - path: { - sandboxID: sandboxId, - }, - query: { - offset, - limit: SANDBOX_EVENTS_PAGE_SIZE, - orderAsc: true, - }, - }, - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - cache: 'no-store', - } - ) - - if (!result.response.ok || result.error) { - l.warn({ - key: 'repositories:sandboxes:get_sandbox_lifecycle_events:infra_error', - error: result.error, - team_id: scope.teamId, - context: { - status: result.response.status, - path: '/events/sandboxes/{sandboxID}', - sandbox_id: sandboxId, - offset, - limit: SANDBOX_EVENTS_PAGE_SIZE, - }, - }) - break - } - - const page = result.data ?? [] - lifecycleEvents.push( - ...page.filter((event) => - event.type.startsWith(SANDBOX_LIFECYCLE_EVENT_PREFIX) - ) - ) - - if (page.length < SANDBOX_EVENTS_PAGE_SIZE) { - break - } - } catch (error) { - l.warn({ - key: 'repositories:sandboxes:get_sandbox_lifecycle_events:infra_exception', - error, - team_id: scope.teamId, - context: { - path: '/events/sandboxes/{sandboxID}', - sandbox_id: sandboxId, - offset, - limit: SANDBOX_EVENTS_PAGE_SIZE, - }, - }) - break - } - } - - return ok(lifecycleEvents) - }, async getSandboxMetrics(sandboxId, options) { const startUnixSeconds = Math.floor(options.startUnixMs / 1000) const endUnixSeconds = Math.floor(options.endUnixMs / 1000) @@ -373,35 +282,6 @@ export function createSandboxesRepository( return ok(result.data) }, - async listSandboxes() { - const result = await deps.infraClient.GET('/sandboxes', { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - cache: 'no-store', - }) - - if (!result.response.ok || result.error) { - l.error({ - key: 'repositories:sandboxes:list_sandboxes:infra_error', - error: result.error, - team_id: scope.teamId, - context: { - status: result.response.status, - path: '/sandboxes', - }, - }) - return err( - repoErrorFromHttp( - result.response.status, - result.error?.message ?? 'Failed to list sandboxes', - result.error - ) - ) - } - - return ok(result.data ?? []) - }, async listSandboxesPaginated(options) { const result = await deps.infraClient.GET('/v2/sandboxes', { params: { @@ -476,87 +356,5 @@ export function createSandboxesRepository( return ok(result.data.sandboxes) }, - async getTeamMetricsRange(startUnixSeconds, endUnixSeconds) { - const result = await deps.infraClient.GET('/teams/{teamID}/metrics', { - params: { - path: { - teamID: scope.teamId, - }, - query: { - start: startUnixSeconds, - end: endUnixSeconds, - }, - }, - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - cache: 'no-store', - }) - - if (!result.response.ok || result.error) { - l.error({ - key: 'repositories:sandboxes:get_team_metrics:infra_error', - error: result.error, - team_id: scope.teamId, - context: { - status: result.response.status, - path: '/teams/{teamID}/metrics', - start_unix_seconds: startUnixSeconds, - end_unix_seconds: endUnixSeconds, - }, - }) - return err( - repoErrorFromHttp( - result.response.status, - result.error?.message ?? 'Failed to fetch project metrics', - result.error - ) - ) - } - - return ok(result.data) - }, - async getTeamMetricsMax(startUnixSeconds, endUnixSeconds, metric) { - const result = await deps.infraClient.GET('/teams/{teamID}/metrics/max', { - params: { - path: { - teamID: scope.teamId, - }, - query: { - start: startUnixSeconds, - end: endUnixSeconds, - metric, - }, - }, - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - cache: 'no-store', - }) - - if (!result.response.ok || result.error) { - l.error({ - key: 'repositories:sandboxes:get_team_metrics_max:infra_error', - error: result.error, - team_id: scope.teamId, - context: { - status: result.response.status, - path: '/teams/{teamID}/metrics/max', - start_unix_seconds: startUnixSeconds, - end_unix_seconds: endUnixSeconds, - metric, - }, - }) - return err( - repoErrorFromHttp( - result.response.status, - result.error?.message ?? 'Failed to fetch project metrics max', - result.error - ) - ) - } - - return ok(result.data) - }, } } diff --git a/src/core/modules/sandboxes/schemas.ts b/src/core/modules/sandboxes/schemas.ts deleted file mode 100644 index 18b4d280c..000000000 --- a/src/core/modules/sandboxes/schemas.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { z } from 'zod' -import { MAX_DAYS_AGO } from '@/features/dashboard/sandboxes/monitoring/time-picker/constants' - -// PRIVATE - -const _startDateSchema = z - .number() - .int() - .positive() - .describe('Unix timestamp in milliseconds') - .refine( - (start) => { - const now = Date.now() - - return start >= now - MAX_DAYS_AGO - }, - { - message: `Start date cannot be more than ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days ago`, - } - ) - -const _endDateSchema = z - .number() - .int() - .positive() - .describe('Unix timestamp in milliseconds') - .refine((end) => end <= Date.now(), { - message: 'End date cannot be in the future', - }) - -const _dateRangeRefine = (data: { startDate: number; endDate: number }) => { - return data.endDate - data.startDate <= MAX_DAYS_AGO -} - -const _dateRangeRefineMessage = { - message: `Date range cannot exceed ${MAX_DAYS_AGO / (1000 * 60 * 60 * 24)} days`, -} - -// PUBLIC - -export const GetTeamMetricsSchema = z - .object({ - startDate: _startDateSchema, - endDate: _endDateSchema, - }) - .refine(_dateRangeRefine, _dateRangeRefineMessage) - -export const GetTeamMetricsMaxSchema = z - .object({ - startDate: _startDateSchema, - endDate: _endDateSchema, - metric: z.enum(['concurrent_sandboxes', 'sandbox_start_rate']), - }) - .refine(_dateRangeRefine, _dateRangeRefineMessage) diff --git a/src/core/modules/support/repository.server.ts b/src/core/modules/support/repository.server.ts deleted file mode 100644 index bcd0e61fa..000000000 --- a/src/core/modules/support/repository.server.ts +++ /dev/null @@ -1,269 +0,0 @@ -import 'server-only' - -import { AttachmentType, PlainClient } from '@team-plain/typescript-sdk' -import { createUserTeamsRepository } from '@/core/modules/teams/user-teams-repository.server' -import { getTeamDisplayName } from '@/core/modules/teams/utils' -import { l, serializeErrorForLog } from '@/core/shared/clients/logger/logger' -import { repoErrorFromHttp } from '@/core/shared/errors' -import type { TeamRequestScope } from '@/core/shared/repository-scope' -import { err, ok, type RepoResult } from '@/core/shared/result' - -const MAX_FILE_SIZE = 10 * 1024 * 1024 -const MAX_FILES = 5 - -interface FileInput { - name: string - type: string - base64: string -} - -type SupportRepositoryDeps = { - createPlainClient: () => PlainClient -} - -export type SupportScope = TeamRequestScope - -export interface SupportRepository { - getTeamSupportData(): Promise< - RepoResult<{ - name: string - email: string - tier: string - }> - > - createSupportThread(input: { - description: string - files?: FileInput[] - teamId: string - teamName: string - customerEmail: string - accountOwnerEmail: string - customerTier: string - }): Promise> -} - -function formatThreadText(input: { - description: string - teamId: string - customerEmail: string - accountOwnerEmail: string - customerTier: string -}): string { - const { - description, - teamId, - customerEmail, - accountOwnerEmail, - customerTier, - } = input - - const header = [ - '########', - `Customer: ${customerEmail}`, - `Account Owner: ${accountOwnerEmail}`, - `Tier: ${customerTier}`, - `TeamID: ${teamId}`, - `Orbit: https://orbit.e2b.dev/teams/${teamId}/users`, - '########', - ].join('\n') - - const truncatedDescription = description.slice(0, 10000) - - return `${header}\n\n${truncatedDescription}` -} - -async function uploadAttachmentToPlain( - client: PlainClient, - customerId: string, - file: FileInput -): Promise { - const buffer = Buffer.from(file.base64, 'base64') - - if (buffer.byteLength > MAX_FILE_SIZE) { - throw new Error(`File ${file.name} exceeds 10MB limit`) - } - - const uploadUrlResult = await client.createAttachmentUploadUrl({ - customerId, - fileName: file.name, - fileSizeBytes: buffer.byteLength, - attachmentType: AttachmentType.CustomTimelineEntry, - }) - - if (uploadUrlResult.error) { - throw new Error( - `Failed to create upload URL for ${file.name}: ${uploadUrlResult.error.message}` - ) - } - - const { uploadFormUrl, uploadFormData, attachment } = uploadUrlResult.data - const formData = new FormData() - for (const { key, value } of uploadFormData) { - formData.append(key, value) - } - formData.append('file', new Blob([buffer], { type: file.type }), file.name) - - const uploadResponse = await fetch(uploadFormUrl, { - method: 'POST', - body: formData, - }) - - if (!uploadResponse.ok) { - throw new Error( - `Failed to upload ${file.name}: ${uploadResponse.status} ${uploadResponse.statusText}` - ) - } - - return attachment.id -} - -export function createSupportRepository( - scope: SupportScope, - deps: SupportRepositoryDeps = { - createPlainClient: () => - new PlainClient({ - apiKey: process.env.PLAIN_API_KEY ?? '', - }), - } -): SupportRepository { - return { - async getTeamSupportData() { - const teamsResult = await createUserTeamsRepository({ - accessToken: scope.accessToken, - }).listUserTeams() - - if (!teamsResult.ok) { - l.error( - { - key: 'repositories:support:fetch_team_error', - error: teamsResult.error, - team_id: scope.teamId, - }, - 'failed to fetch team data' - ) - return err(teamsResult.error) - } - - const team = teamsResult.data.find( - (candidate) => candidate.id === scope.teamId - ) - - if (!team) { - return err( - repoErrorFromHttp(403, 'Project not found or access denied', { - teamId: scope.teamId, - }) - ) - } - - return ok({ - name: getTeamDisplayName(team), - email: team.email, - tier: team.tier, - }) - }, - async createSupportThread(input) { - if (!process.env.PLAIN_API_KEY) { - return err(repoErrorFromHttp(500, 'Support API not configured')) - } - - const { - description, - files, - teamId, - teamName, - customerEmail, - accountOwnerEmail, - customerTier, - } = input - - const client = deps.createPlainClient() - const customerResult = await client.upsertCustomer({ - identifier: { - emailAddress: customerEmail, - }, - onCreate: { - email: { - email: customerEmail, - isVerified: true, - }, - fullName: customerEmail, - }, - onUpdate: {}, - }) - - if (customerResult.error) { - return err( - repoErrorFromHttp( - 500, - 'Failed to create support ticket', - customerResult.error - ) - ) - } - - const customerId = customerResult.data.customer.id - const attachmentIds: string[] = [] - const validFiles = (files ?? []).slice(0, MAX_FILES) - - for (const file of validFiles) { - try { - const attachmentId = await uploadAttachmentToPlain( - client, - customerId, - file - ) - attachmentIds.push(attachmentId) - } catch (error) { - l.error( - { - key: 'repositories:support:attachment_upload_error', - error: serializeErrorForLog(error), - team_id: teamId, - context: { - file_name: file.name, - }, - }, - 'failed to upload support ticket attachment' - ) - } - } - - const title = `Support Request [${teamName}]` - const threadText = formatThreadText({ - description, - teamId, - customerEmail, - accountOwnerEmail, - customerTier, - }) - - const result = await client.createThread({ - title, - customerIdentifier: { - customerId, - }, - components: [ - { - componentText: { - text: threadText, - }, - }, - ], - ...(attachmentIds.length > 0 ? { attachmentIds } : {}), - }) - - if (result.error) { - return err( - repoErrorFromHttp( - 500, - 'Failed to create support ticket', - result.error - ) - ) - } - - return ok({ threadId: result.data.id }) - }, - } -} diff --git a/src/core/modules/teams/teams-repository.server.ts b/src/core/modules/teams/teams-repository.server.ts deleted file mode 100644 index b49e65f36..000000000 --- a/src/core/modules/teams/teams-repository.server.ts +++ /dev/null @@ -1,177 +0,0 @@ -import 'server-only' - -import { authHeaders } from '@/configs/api' -import type { components as DashboardComponents } from '@/contracts/dashboard-api' -import { api } from '@/core/shared/clients/api' -import { createRepoError, repoErrorFromHttp } from '@/core/shared/errors' -import type { RequestScope } from '@/core/shared/repository-scope' -import { err, ok, type RepoResult } from '@/core/shared/result' -import type { TeamMember } from './models' - -type TeamsRepositoryDeps = { - apiClient: typeof api - authHeaders: typeof authHeaders -} - -export type TeamsRequestScope = RequestScope & { - teamId?: string -} - -export interface TeamsRepository { - listTeamMembers(): Promise> - updateTeamName( - name: string - ): Promise> - addTeamMember(email: string): Promise> - removeTeamMember(userId: string): Promise> -} - -function requireTeamId(scope: TeamsRequestScope): RepoResult { - if (!scope.teamId) { - return err( - createRepoError({ - code: 'internal', - status: 500, - message: 'teamId is required for team-scoped repository operation', - }) - ) - } - - return ok(scope.teamId) -} - -export function createTeamsRepository( - scope: TeamsRequestScope, - deps: TeamsRepositoryDeps = { - apiClient: api, - authHeaders: authHeaders, - } -): TeamsRepository { - return { - async listTeamMembers(): Promise> { - const teamId = requireTeamId(scope) - if (!teamId.ok) { - return teamId - } - - const { data, error, response } = await deps.apiClient.GET( - '/teams/{teamID}/members', - { - params: { path: { teamID: teamId.data } }, - headers: deps.authHeaders(scope.accessToken, teamId.data), - } - ) - - if (!response.ok || error) { - return err( - repoErrorFromHttp( - response.status, - error?.message ?? 'Failed to fetch project members', - error - ) - ) - } - - const mapped: TeamMember[] = (data?.members ?? []).map((member) => ({ - info: { - id: member.id, - email: member.email, - name: member.name ?? undefined, - avatar_url: member.profilePictureUrl ?? undefined, - providers: member.providers ?? [], - createdAt: member.createdAt, - }, - relation: { - added_by: member.addedBy ?? null, - is_default: member.isDefault, - }, - })) - - return ok(mapped) - }, - async updateTeamName( - name - ): Promise< - RepoResult - > { - const teamId = requireTeamId(scope) - if (!teamId.ok) { - return teamId - } - - const { data, error, response } = await deps.apiClient.PATCH( - '/teams/{teamID}', - { - params: { path: { teamID: teamId.data } }, - headers: deps.authHeaders(scope.accessToken, teamId.data), - body: { name }, - } - ) - - if (!response.ok || error || !data) { - return err( - repoErrorFromHttp( - response.status, - error?.message ?? 'Failed to update project name', - error - ) - ) - } - - return ok(data) - }, - async addTeamMember(email): Promise> { - const teamId = requireTeamId(scope) - if (!teamId.ok) { - return teamId - } - - const { error, response } = await deps.apiClient.POST( - '/teams/{teamID}/members', - { - params: { path: { teamID: teamId.data } }, - headers: deps.authHeaders(scope.accessToken, teamId.data), - body: { email }, - } - ) - - if (!response.ok || error) { - return err( - repoErrorFromHttp( - response.status, - error?.message ?? 'Failed to add project member', - error - ) - ) - } - - return ok(undefined) - }, - async removeTeamMember(userId): Promise> { - const teamId = requireTeamId(scope) - if (!teamId.ok) { - return teamId - } - - const { error, response } = await deps.apiClient.DELETE( - '/teams/{teamID}/members/{userId}', - { - params: { path: { teamID: teamId.data, userId } }, - headers: deps.authHeaders(scope.accessToken, teamId.data), - } - ) - - if (!response.ok || error) { - return err( - repoErrorFromHttp( - response.status, - error?.message ?? 'Failed to remove project member', - error - ) - ) - } - - return ok(undefined) - }, - } -} diff --git a/src/core/modules/webhooks/repository.server.ts b/src/core/modules/webhooks/repository.server.ts deleted file mode 100644 index 62a145256..000000000 --- a/src/core/modules/webhooks/repository.server.ts +++ /dev/null @@ -1,309 +0,0 @@ -import 'server-only' - -import { authHeaders } from '@/configs/api' -import type { UpsertWebhookInput } from '@/core/server/functions/webhooks/schema' -import { infra } from '@/core/shared/clients/api' -import type { - components as ArgusComponents, - operations as ArgusOperations, -} from '@/core/shared/contracts/argus-api.types' -import { repoErrorFromHttp } from '@/core/shared/errors' -import type { TeamRequestScope } from '@/core/shared/repository-scope' -import { err, ok, type RepoResult } from '@/core/shared/result' - -type WebhooksRepositoryDeps = { - infraClient: typeof infra - authHeaders: typeof authHeaders -} - -export type WebhooksScope = TeamRequestScope - -type ListWebhookDeliveriesInput = NonNullable< - ArgusOperations['webhookDeliveriesList']['parameters']['query'] -> & { - webhookId: string -} - -type ListWebhookDeliveriesResult = - ArgusComponents['schemas']['WebhookDeliveriesListPayload'] - -type GetWebhookDeliveryStatsInput = NonNullable< - ArgusOperations['webhookDeliveryStats']['parameters']['query'] -> & { - webhookId: string -} - -export interface WebhooksRepository { - listWebhooks(): Promise< - RepoResult - > - getWebhook( - webhookId: string - ): Promise> - listWebhookDeliveries( - input: ListWebhookDeliveriesInput - ): Promise> - getWebhookDeliveryStats( - input: GetWebhookDeliveryStatsInput - ): Promise> - upsertWebhook(input: UpsertWebhookInput): Promise> - deleteWebhook(webhookId: string): Promise> - updateWebhookSecret( - webhookId: string, - signatureSecret: string - ): Promise> -} - -export function createWebhooksRepository( - scope: WebhooksScope, - deps: WebhooksRepositoryDeps = { - infraClient: infra, - authHeaders: authHeaders, - } -): WebhooksRepository { - return { - async listWebhooks() { - const response = await deps.infraClient.GET('/events/webhooks', { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - }) - - if (!response.response.ok || response.error) { - if (response.response.status === 404) { - return ok([]) - } - - return err( - repoErrorFromHttp( - response.response.status, - response.error?.message ?? 'Failed to list webhooks', - response.error - ) - ) - } - - return ok(response.data ?? []) - }, - async getWebhook(webhookId) { - const response = await deps.infraClient.GET( - '/events/webhooks/{webhookID}', - { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - params: { - path: { webhookID: webhookId }, - }, - } - ) - - if (!response.response.ok || response.error) { - return err( - repoErrorFromHttp( - response.response.status, - response.error?.message ?? 'Failed to get webhook', - response.error - ) - ) - } - - return ok(response.data) - }, - async listWebhookDeliveries(input) { - const response = await deps.infraClient.GET( - '/events/webhooks/{webhookID}/deliveries', - { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - params: { - path: { webhookID: input.webhookId }, - query: { - limit: input.limit, - cursor: input.cursor, - orderAsc: input.orderAsc, - start: input.start, - end: input.end, - deliveryStatus: input.deliveryStatus, - eventType: input.eventType, - }, - }, - } - ) - - if (!response.response.ok || response.error) { - return err( - repoErrorFromHttp( - response.response.status, - response.error?.message ?? 'Failed to list webhook deliveries', - response.error - ) - ) - } - - return ok({ - data: response.data?.data ?? [], - nextCursor: response.data?.nextCursor ?? null, - }) - }, - async getWebhookDeliveryStats(input) { - const response = await deps.infraClient.GET( - '/events/webhooks/{webhookID}/stats', - { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - params: { - path: { webhookID: input.webhookId }, - query: { - start: input.start, - end: input.end, - }, - }, - } - ) - - if (!response.response.ok || response.error) { - return err( - repoErrorFromHttp( - response.response.status, - response.error?.message ?? 'Failed to get webhook delivery stats', - response.error - ) - ) - } - - return ok(response.data) - }, - async upsertWebhook(input) { - if (input.mode === 'update') { - if (!input.webhookId) { - return err( - repoErrorFromHttp( - 400, - 'webhookId is required when updating a webhook' - ) - ) - } - - const response = await deps.infraClient.PATCH( - '/events/webhooks/{webhookID}', - { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - params: { - path: { webhookID: input.webhookId }, - }, - body: { - name: input.name, - url: input.url, - events: input.events, - enabled: input.enabled, - }, - } - ) - - if (!response.response.ok || response.error) { - return err( - repoErrorFromHttp( - response.response.status, - response.error?.message ?? 'Failed to upsert webhook', - response.error - ) - ) - } - - return ok(undefined) - } - - if (!input.signatureSecret) { - return err( - repoErrorFromHttp( - 400, - 'signatureSecret is required when creating a webhook' - ) - ) - } - - const response = await deps.infraClient.POST('/events/webhooks', { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - body: { - name: input.name, - url: input.url, - events: input.events, - enabled: input.enabled, - signatureSecret: input.signatureSecret, - }, - }) - - if (!response.response.ok || response.error) { - return err( - repoErrorFromHttp( - response.response.status, - response.error?.message ?? 'Failed to upsert webhook', - response.error - ) - ) - } - - return ok(undefined) - }, - async deleteWebhook(webhookId) { - const response = await deps.infraClient.DELETE( - '/events/webhooks/{webhookID}', - { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - params: { - path: { webhookID: webhookId }, - }, - } - ) - - if (!response.response.ok || response.error) { - return err( - repoErrorFromHttp( - response.response.status, - response.error?.message ?? 'Failed to delete webhook', - response.error - ) - ) - } - - return ok(undefined) - }, - async updateWebhookSecret(webhookId, signatureSecret) { - const response = await deps.infraClient.PATCH( - '/events/webhooks/{webhookID}', - { - headers: { - ...deps.authHeaders(scope.accessToken, scope.teamId), - }, - params: { - path: { webhookID: webhookId }, - }, - body: { - signatureSecret, - }, - } - ) - - if (!response.response.ok || response.error) { - return err( - repoErrorFromHttp( - response.response.status, - response.error?.message ?? 'Failed to update webhook secret', - response.error - ) - ) - } - - return ok(undefined) - }, - } -} diff --git a/src/core/server/api/routers/billing.ts b/src/core/server/api/routers/billing.ts deleted file mode 100644 index c7bdf0154..000000000 --- a/src/core/server/api/routers/billing.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { TRPCError } from '@trpc/server' -import { headers } from 'next/headers' -import { z } from 'zod' -import { createBillingRepository } from '@/core/modules/billing/repository.server' -import { throwTRPCErrorFromRepoError } from '@/core/server/adapters/errors' -import { withTeamAuthedRequestRepository } from '@/core/server/api/middlewares/repository' -import { createTRPCRouter } from '@/core/server/trpc/init' -import { protectedTeamProcedure } from '@/core/server/trpc/procedures' -import { - ADDON_500_SANDBOXES_ID, - ADDON_PURCHASE_ACTION_ERRORS, -} from '@/features/dashboard/billing/constants' - -function limitTypeToKey(type: 'limit' | 'alert') { - return type === 'limit' ? 'limit_amount_gte' : 'alert_amount_gte' -} - -const billingRepositoryProcedure = protectedTeamProcedure.use( - withTeamAuthedRequestRepository( - createBillingRepository, - (billingRepository) => ({ - billingRepository, - }) - ) -) - -export const billingRouter = createTRPCRouter({ - createCheckout: billingRepositoryProcedure - .input(z.object({ tierId: z.string() })) - .mutation(async ({ ctx, input }) => { - const result = await ctx.billingRepository.createCheckout(input.tierId) - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return result.data - }), - - createCustomerPortalSession: billingRepositoryProcedure.mutation( - async ({ ctx }) => { - const origin = (await headers()).get('origin') - const result = - await ctx.billingRepository.createCustomerPortalSession(origin) - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return { url: result.data.url } - } - ), - - getItems: billingRepositoryProcedure.query(async ({ ctx }) => { - const result = await ctx.billingRepository.getItems() - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return result.data - }), - - getUsage: billingRepositoryProcedure.query(async ({ ctx }) => { - const result = await ctx.billingRepository.getUsage() - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return result.data - }), - - getInvoices: billingRepositoryProcedure.query(async ({ ctx }) => { - const result = await ctx.billingRepository.getInvoices() - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return result.data - }), - - getLimits: billingRepositoryProcedure.query(async ({ ctx }) => { - const result = await ctx.billingRepository.getLimits() - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return result.data - }), - - setLimit: billingRepositoryProcedure - .input( - z.object({ - type: z.enum(['limit', 'alert']), - value: z.number().min(1), - }) - ) - .mutation(async ({ ctx, input }) => { - const { type, value } = input - const result = await ctx.billingRepository.setLimit( - limitTypeToKey(type), - value - ) - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - }), - - clearLimit: billingRepositoryProcedure - .input(z.object({ type: z.enum(['limit', 'alert']) })) - .mutation(async ({ ctx, input }) => { - const { type } = input - const result = await ctx.billingRepository.clearLimit( - limitTypeToKey(type) - ) - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - }), - - createOrder: billingRepositoryProcedure - .input(z.object({ itemId: z.literal(ADDON_500_SANDBOXES_ID) })) - .mutation(async ({ ctx, input }) => { - const { itemId } = input - const result = await ctx.billingRepository.createOrder(itemId) - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return result.data - }), - - confirmOrder: billingRepositoryProcedure - .input(z.object({ orderId: z.string().uuid() })) - .mutation(async ({ ctx, input }) => { - const { orderId } = input - const result = await ctx.billingRepository.confirmOrder(orderId) - if (!result.ok) { - if ( - result.error.message.includes( - 'Missing payment method, please update your payment information' - ) - ) { - throw new TRPCError({ - code: 'BAD_REQUEST', - message: ADDON_PURCHASE_ACTION_ERRORS.missingPaymentMethod, - }) - } - throwTRPCErrorFromRepoError(result.error) - } - - return result.data - }), - - getCustomerSession: billingRepositoryProcedure.mutation(async ({ ctx }) => { - const result = await ctx.billingRepository.getCustomerSession() - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return result.data - }), - - createPaymentMethodsSession: billingRepositoryProcedure.mutation( - async ({ ctx }) => { - const result = await ctx.billingRepository.createPaymentMethodsSession() - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return result.data - } - ), - createVerificationPayment: billingRepositoryProcedure.mutation( - async ({ ctx }) => { - const result = await ctx.billingRepository.createVerificationPayment() - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - return result.data - } - ), -}) diff --git a/src/core/server/api/routers/index.ts b/src/core/server/api/routers/index.ts index e38e546d2..8d5db9280 100644 --- a/src/core/server/api/routers/index.ts +++ b/src/core/server/api/routers/index.ts @@ -1,24 +1,18 @@ import { createCallerFactory, createTRPCRouter } from '@/core/server/trpc/init' -import { billingRouter } from './billing' import { buildsRouter } from './builds' import { sandboxRouter } from './sandbox' import { sandboxesRouter } from './sandboxes' -import { supportRouter } from './support' import { teamsRouter } from './teams' import { templatesRouter } from './templates' import { userRouter } from './user' -import { webhooksRouter } from './webhooks' export const trpcAppRouter = createTRPCRouter({ sandbox: sandboxRouter, sandboxes: sandboxesRouter, templates: templatesRouter, builds: buildsRouter, - billing: billingRouter, - support: supportRouter, teams: teamsRouter, user: userRouter, - webhooks: webhooksRouter, }) export type TRPCAppRouter = typeof trpcAppRouter diff --git a/src/core/server/api/routers/sandbox.ts b/src/core/server/api/routers/sandbox.ts index 8e672b928..42e177ab4 100644 --- a/src/core/server/api/routers/sandbox.ts +++ b/src/core/server/api/routers/sandbox.ts @@ -4,7 +4,6 @@ import { Sandbox, TimeoutError } from 'e2b' import { z } from 'zod' import { authHeaders } from '@/configs/api' import { - deriveSandboxLifecycleFromEvents, mapApiSandboxRecordToModel, mapInfraSandboxDetailsToModel, mapInfraSandboxLogToModel, @@ -55,14 +54,9 @@ export const sandboxRouter = createTRPCRouter({ ? mapInfraSandboxDetailsToModel(detailsResult.data.details) : mapApiSandboxRecordToModel(detailsResult.data.details) - const lifecycleEventsResult = - await ctx.sandboxesRepository.getSandboxLifecycleEvents(sandboxId) - if (!lifecycleEventsResult.ok) { - throwTRPCErrorFromRepoError(lifecycleEventsResult.error) - } - const derivedLifecycle = deriveSandboxLifecycleFromEvents( - lifecycleEventsResult.data - ) + // OSS: lifecycle events (argus) are unavailable; derive the lifecycle + // timeline from the sandbox details alone. Kept shape-identical to + // console so downstream chart code stays unchanged. const fallbackPausedAt = mappedDetails.state === 'paused' ? mappedDetails.endAt : null const fallbackEndedAt = @@ -73,10 +67,10 @@ export const sandboxRouter = createTRPCRouter({ return { ...mappedDetails, lifecycle: { - createdAt: derivedLifecycle.createdAt ?? mappedDetails.startedAt, - pausedAt: derivedLifecycle.pausedAt ?? fallbackPausedAt, - endedAt: derivedLifecycle.endedAt ?? fallbackEndedAt, - events: derivedLifecycle.events, + createdAt: mappedDetails.startedAt, + pausedAt: fallbackPausedAt, + endedAt: fallbackEndedAt, + events: [], }, } }), diff --git a/src/core/server/api/routers/sandboxes.ts b/src/core/server/api/routers/sandboxes.ts index c529088ac..3abf4bd1b 100644 --- a/src/core/server/api/routers/sandboxes.ts +++ b/src/core/server/api/routers/sandboxes.ts @@ -1,22 +1,10 @@ import { z } from 'zod' import { USE_MOCK_DATA } from '@/configs/env-flags' -import { - calculateTeamMetricsStep, - MOCK_SANDBOXES_DATA, - MOCK_TEAM_METRICS_DATA, - MOCK_TEAM_METRICS_MAX_DATA, -} from '@/configs/mock-data' +import { MOCK_SANDBOXES_DATA } from '@/configs/mock-data' import { createSandboxesRepository } from '@/core/modules/sandboxes/repository.server' -import { - GetTeamMetricsMaxSchema, - GetTeamMetricsSchema, -} from '@/core/modules/sandboxes/schemas' import { throwTRPCErrorFromRepoError } from '@/core/server/adapters/errors' import { withTeamAuthedRequestRepository } from '@/core/server/api/middlewares/repository' -import { - fillTeamMetricsWithZeros, - transformMetricsToClientMetrics, -} from '@/core/server/functions/sandboxes/utils' +import { transformMetricsToClientMetrics } from '@/core/server/functions/sandboxes/utils' import { createTRPCRouter } from '@/core/server/trpc/init' import { protectedTeamProcedure } from '@/core/server/trpc/procedures' @@ -31,25 +19,6 @@ const sandboxesRepositoryProcedure = protectedTeamProcedure.use( export const sandboxesRouter = createTRPCRouter({ // QUERIES - getSandboxes: sandboxesRepositoryProcedure.query(async ({ ctx }) => { - if (USE_MOCK_DATA) { - await new Promise((resolve) => setTimeout(resolve, 200)) - - return { - sandboxes: MOCK_SANDBOXES_DATA(), - } - } - - const sandboxesResult = await ctx.sandboxesRepository.listSandboxes() - if (!sandboxesResult.ok) { - throwTRPCErrorFromRepoError(sandboxesResult.error) - } - - return { - sandboxes: sandboxesResult.data, - } - }), - listSandboxesPaginated: sandboxesRepositoryProcedure .input( z.object({ @@ -116,105 +85,5 @@ export const sandboxesRouter = createTRPCRouter({ } }), - getTeamMetrics: sandboxesRepositoryProcedure - .input(GetTeamMetricsSchema) - .query(async ({ ctx, input }) => { - const { startDate: startDateMs, endDate: endDateMs } = input - - // use mock data if enabled - if (USE_MOCK_DATA) { - const mockData = MOCK_TEAM_METRICS_DATA(startDateMs, endDateMs) - const filledMetrics = fillTeamMetricsWithZeros( - mockData.metrics, - startDateMs, - endDateMs, - mockData.step - ) - return { - metrics: filledMetrics, - step: mockData.step, - } - } - - const startS = Math.floor(startDateMs / 1000) - const endS = Math.floor(endDateMs / 1000) - - // calculate step to determine overfetch amount - const stepMs = calculateTeamMetricsStep(startDateMs, endDateMs) - - // overfetch by one step - // the overfetch is accounted for when post-processing the data using fillTeamMetricsWithZeros - const overfetchS = Math.ceil(stepMs / 1000) - - const metricDataResult = - await ctx.sandboxesRepository.getTeamMetricsRange( - startS, - endS + overfetchS - ) - if (!metricDataResult.ok) { - throwTRPCErrorFromRepoError(metricDataResult.error) - } - const metricData = metricDataResult.data - - // transform timestamps from seconds to milliseconds - const metrics = metricData.map( - (d: { - concurrentSandboxes: number - sandboxStartRate: number - timestampUnix: number - }) => ({ - concurrentSandboxes: d.concurrentSandboxes, - sandboxStartRate: d.sandboxStartRate, - timestamp: d.timestampUnix * 1000, - }) - ) - - // fill gaps with zeros for smooth visualization - const filledMetrics = fillTeamMetricsWithZeros( - metrics, - startDateMs, - endDateMs, - stepMs - ) - - return { - metrics: filledMetrics, - step: stepMs, - } - }), - - getTeamMetricsMax: sandboxesRepositoryProcedure - .input(GetTeamMetricsMaxSchema) - .query(async ({ ctx, input }) => { - const { startDate: startDateMs, endDate: endDateMs, metric } = input - - if (USE_MOCK_DATA) { - return MOCK_TEAM_METRICS_MAX_DATA(startDateMs, endDateMs, metric) - } - - // convert milliseconds to seconds for the API - const startS = Math.floor(startDateMs / 1000) - const endS = Math.floor(endDateMs / 1000) - - const maxMetricResult = await ctx.sandboxesRepository.getTeamMetricsMax( - startS, - endS, - metric - ) - if (!maxMetricResult.ok) { - throwTRPCErrorFromRepoError(maxMetricResult.error) - } - const maxMetric = maxMetricResult.data - - // since javascript timestamps are in milliseconds, we want to convert the timestamp back to milliseconds - const timestampMs = maxMetric.timestampUnix * 1000 - - return { - timestamp: timestampMs, - value: maxMetric.value, - metric, - } - }), - // MUTATIONS }) diff --git a/src/core/server/api/routers/support.ts b/src/core/server/api/routers/support.ts deleted file mode 100644 index 5184f814e..000000000 --- a/src/core/server/api/routers/support.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { TRPCError } from '@trpc/server' -import { z } from 'zod' -import { createSupportRepository } from '@/core/modules/support/repository.server' -import { throwTRPCErrorFromRepoError } from '@/core/server/adapters/errors' -import { withTeamAuthedRequestRepository } from '@/core/server/api/middlewares/repository' -import { createTRPCRouter } from '@/core/server/trpc/init' -import { protectedTeamProcedure } from '@/core/server/trpc/procedures' -import { FileSchema } from '@/core/shared/schemas/file' - -const E2B_API_KEY_REGEX = /e2b_[a-f0-9]{40}/i - -const supportRepositoryProcedure = protectedTeamProcedure.use( - withTeamAuthedRequestRepository( - createSupportRepository, - (supportRepository) => ({ - supportRepository, - }) - ) -) - -export const supportRouter = createTRPCRouter({ - contactSupport: supportRepositoryProcedure - .input( - z.object({ - description: z.string().min(1), - files: z.array(FileSchema).max(5).optional(), - }) - ) - .mutation(async ({ ctx, input }) => { - const { teamId, user } = ctx - const email = user.email - - if (!email) { - throw new Error('Email not found') - } - - if (E2B_API_KEY_REGEX.test(input.description)) { - throw new TRPCError({ - code: 'BAD_REQUEST', - message: - 'Your message contains an API key. Please remove it before sending.', - }) - } - - const teamResult = await ctx.supportRepository.getTeamSupportData() - if (!teamResult.ok) { - throwTRPCErrorFromRepoError(teamResult.error) - } - - const createResult = await ctx.supportRepository.createSupportThread({ - description: input.description, - files: input.files, - teamId, - teamName: teamResult.data.name, - customerEmail: email, - accountOwnerEmail: teamResult.data.email, - customerTier: teamResult.data.tier, - }) - if (!createResult.ok) { - throwTRPCErrorFromRepoError(createResult.error) - } - - return createResult.data - }), -}) diff --git a/src/core/server/api/routers/teams.ts b/src/core/server/api/routers/teams.ts index 2ce934137..bd3415c80 100644 --- a/src/core/server/api/routers/teams.ts +++ b/src/core/server/api/routers/teams.ts @@ -1,26 +1,9 @@ -import { revalidatePath } from 'next/cache' -import { z } from 'zod' -import { createKeysRepository } from '@/core/modules/keys/repository.server' -import { CreateApiKeySchema } from '@/core/modules/keys/schemas' -import { - AddTeamMemberSchema, - CreateTeamSchema, - RemoveTeamMemberSchema, - TeamNameSchema, -} from '@/core/modules/teams/schemas' -import { createTeamsRepository } from '@/core/modules/teams/teams-repository.server' +import { CreateTeamSchema } from '@/core/modules/teams/schemas' import { createUserTeamsRepository } from '@/core/modules/teams/user-teams-repository.server' import { throwTRPCErrorFromRepoError } from '@/core/server/adapters/errors' -import { - withAuthedRequestRepository, - withTeamAuthedRequestRepository, -} from '@/core/server/api/middlewares/repository' +import { withAuthedRequestRepository } from '@/core/server/api/middlewares/repository' import { createTRPCRouter } from '@/core/server/trpc/init' -import { - protectedProcedure, - protectedTeamProcedure, -} from '@/core/server/trpc/procedures' -import { l } from '@/core/shared/clients/logger/logger' +import { protectedProcedure } from '@/core/server/trpc/procedures' const userTeamsRepositoryProcedure = protectedProcedure.use( withAuthedRequestRepository( @@ -31,18 +14,6 @@ const userTeamsRepositoryProcedure = protectedProcedure.use( ) ) -const teamsRepositoryProcedure = protectedTeamProcedure.use( - withTeamAuthedRequestRepository(createTeamsRepository, (teamsRepository) => ({ - teamsRepository, - })) -) - -const keysRepositoryProcedure = protectedTeamProcedure.use( - withTeamAuthedRequestRepository(createKeysRepository, (keysRepository) => ({ - keysRepository, - })) -) - export const teamsRouter = createTRPCRouter({ list: userTeamsRepositoryProcedure.query(async ({ ctx }) => { const teamsResult = await ctx.userTeamsRepository.listUserTeams() @@ -54,66 +25,6 @@ export const teamsRouter = createTRPCRouter({ return teamsResult.data }), - listApiKeys: keysRepositoryProcedure.query(async ({ ctx }) => { - const result = await ctx.keysRepository.listTeamApiKeys() - - if (!result.ok) { - throwTRPCErrorFromRepoError(result.error) - } - - return { apiKeys: result.data } - }), - - createApiKey: keysRepositoryProcedure - .input(CreateApiKeySchema) - .mutation(async ({ ctx, input }) => { - const { name } = input - - const result = await ctx.keysRepository.createApiKey(name) - - if (!result.ok) { - l.error({ - key: 'create_api_key_trpc:error', - message: result.error.message, - error: result.error, - team_id: ctx.teamId, - user_id: ctx.session.user.id, - context: { name }, - }) - - throwTRPCErrorFromRepoError(result.error) - } - - return { createdApiKey: result.data } - }), - - deleteApiKey: keysRepositoryProcedure - .input( - z.object({ - apiKeyId: z.uuid(), - }) - ) - .mutation(async ({ ctx, input }) => { - const { apiKeyId } = input - - const result = await ctx.keysRepository.deleteApiKey(apiKeyId) - - if (!result.ok) { - l.error({ - key: 'delete_api_key_trpc:error', - message: result.error.message, - error: result.error, - team_id: ctx.teamId, - user_id: ctx.session.user.id, - context: { apiKeyId }, - }) - - throwTRPCErrorFromRepoError(result.error) - } - - return undefined - }), - create: userTeamsRepositoryProcedure .input(CreateTeamSchema) .mutation(async ({ ctx, input }) => { @@ -123,44 +34,4 @@ export const teamsRouter = createTRPCRouter({ return result.data }), - members: teamsRepositoryProcedure.query(async ({ ctx }) => { - const result = await ctx.teamsRepository.listTeamMembers() - - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - - return result.data - }), - updateName: teamsRepositoryProcedure - .input( - z.object({ - name: TeamNameSchema, - }) - ) - .mutation(async ({ ctx, input }) => { - const result = await ctx.teamsRepository.updateTeamName(input.name) - - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - - revalidatePath(`/dashboard/${input.teamSlug}/general`, 'page') - - return result.data - }), - addMember: teamsRepositoryProcedure - .input(AddTeamMemberSchema) - .mutation(async ({ ctx, input }) => { - const result = await ctx.teamsRepository.addTeamMember(input.email) - - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - - revalidatePath(`/dashboard/${input.teamSlug}/members`, 'page') - }), - removeMember: teamsRepositoryProcedure - .input(RemoveTeamMemberSchema) - .mutation(async ({ ctx, input }) => { - const result = await ctx.teamsRepository.removeTeamMember(input.userId) - - if (!result.ok) throwTRPCErrorFromRepoError(result.error) - - revalidatePath(`/dashboard/${input.teamSlug}/members`, 'page') - }), }) diff --git a/src/core/server/api/routers/user.ts b/src/core/server/api/routers/user.ts index 39352f6e1..2ed002870 100644 --- a/src/core/server/api/routers/user.ts +++ b/src/core/server/api/routers/user.ts @@ -1,33 +1,13 @@ -import { TRPCError } from '@trpc/server' -import { z } from 'zod' -import { featureFlags } from '@/core/modules/feature-flags/feature-flags.server' -import { - type AuthUser, - getUserProfile, - handleInSessionPasswordChange, - updateUser, -} from '@/core/server/auth' +import { type AuthUser, getUserProfile } from '@/core/server/auth' import { createTRPCRouter } from '@/core/server/trpc/init' import { protectedProcedure } from '@/core/server/trpc/procedures' import { l } from '@/core/shared/clients/logger/logger' -import { generateE2BUserAccessToken } from '@/lib/utils/server' // How long the live identity-provider profile lookup is allowed to take before // we fall back to the cheap session user. Keeps a slow Ory admin API out of the // critical render path for every dashboard page. const PROFILE_LOOKUP_TIMEOUT_MS = 3000 -const UpdateUserSchema = z - .object({ - email: z.email().optional(), - password: z.string().min(8).optional(), - name: z.string().min(1).max(100).optional(), - }) - .refine((data) => Boolean(data.email || data.password || data.name), { - message: 'At least one field must be provided (email, password, name)', - path: [], - }) - const TIMEOUT = Symbol('profile-lookup-timeout') async function withTimeout( @@ -75,103 +55,4 @@ export const userRouter = createTRPCRouter({ return ctx.user }), - - update: protectedProcedure - .input(UpdateUserSchema) - .mutation(async ({ ctx, input }) => { - // Basic security check: a password must not equal the account email - // (current or the new one being set in the same request). - if (input.password) { - const password = input.password.toLowerCase() - const matchesCurrentEmail = password === ctx.user.email?.toLowerCase() - const matchesNewEmail = - input.email !== undefined && password === input.email.toLowerCase() - - if (matchesCurrentEmail || matchesNewEmail) { - return { status: 'error' as const, code: 'weak_password' as const } - } - } - - if (input.email !== undefined || input.password !== undefined) { - const profile = await withTimeout( - getUserProfile().catch(() => null), - PROFILE_LOOKUP_TIMEOUT_MS - ) - const credentialProfile = - profile && profile !== TIMEOUT ? profile : ctx.user - - if (!profile || profile === TIMEOUT) { - l.warn( - { - key: 'trpc_user_update:profile_fallback', - user_id: ctx.user.id, - context: { timed_out: profile === TIMEOUT }, - }, - 'user profile lookup failed during credential update; falling back to session user capabilities' - ) - } - - if ( - (input.email !== undefined && !credentialProfile.canChangeEmail) || - (input.password !== undefined && !credentialProfile.canChangePassword) - ) { - return { - status: 'error' as const, - code: 'account_credentials_not_changeable' as const, - } - } - } - - const result = await updateUser({ - email: input.email, - password: input.password, - name: input.name, - }) - - if (result.ok) { - // A password change signs out every OTHER device but keeps the current - // one signed in (recovery's sign-out-everywhere path is separate). - if (input.password) { - await handleInSessionPasswordChange() - } - - return { status: 'ok' as const, user: result.user } - } - - if (result.code === 'reauthentication_needed') { - return { status: 'reauth' as const } - } - - return { status: 'error' as const, code: result.code } - }), - - // Creates (POSTs) a fresh E2B access token — non-idempotent, fired on demand. - createAccessToken: protectedProcedure.mutation(async ({ ctx }) => { - const provisioningDisabled = await featureFlags.isEnabled( - 'disableE2BAccessTokenProvisioning', - { - user: { - id: ctx.user.id, - email: ctx.user.email ?? undefined, - }, - } - ) - - if (provisioningDisabled) { - throw new TRPCError({ - code: 'PRECONDITION_FAILED', - message: 'E2B access token provisioning is disabled', - }) - } - - try { - return await generateE2BUserAccessToken(ctx.session.access_token) - } catch (error) { - throw new TRPCError({ - code: 'INTERNAL_SERVER_ERROR', - message: 'Failed to generate access token', - cause: error, - }) - } - }), }) diff --git a/src/core/server/api/routers/webhooks.ts b/src/core/server/api/routers/webhooks.ts deleted file mode 100644 index 85cd59da5..000000000 --- a/src/core/server/api/routers/webhooks.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { createWebhooksRepository } from '@/core/modules/webhooks/repository.server' -import { throwTRPCErrorFromRepoError } from '@/core/server/adapters/errors' -import { withTeamAuthedRequestRepository } from '@/core/server/api/middlewares/repository' -import { - DeleteWebhookInputSchema, - GetWebhookDeliveryStatsInputSchema, - GetWebhookInputSchema, - ListWebhookDeliveriesInputSchema, - UpdateWebhookSecretInputSchema, - UpsertWebhookInputSchema, -} from '@/core/server/functions/webhooks/schema' -import { createTRPCRouter } from '@/core/server/trpc/init' -import { protectedTeamProcedure } from '@/core/server/trpc/procedures' -import { l } from '@/core/shared/clients/logger/logger' -import type { components as ArgusComponents } from '@/core/shared/contracts/argus-api.types' - -type WebhookDeliveryGroup = ArgusComponents['schemas']['WebhookDeliveryGroup'] - -const toDeliveryGroup = (group: WebhookDeliveryGroup) => { - const attempts = [...group.attempts].sort( - (left, right) => - new Date(right.timestamp).getTime() - new Date(left.timestamp).getTime() - ) - - return { - eventId: group.eventId, - eventType: group.eventType, - sandboxId: group.sandboxId, - attempts, - attemptCount: attempts.length, - latestAttempt: attempts[0] ?? null, - } -} - -const webhooksRepositoryProcedure = protectedTeamProcedure.use( - withTeamAuthedRequestRepository( - createWebhooksRepository, - (webhooksRepository) => ({ webhooksRepository }) - ) -) - -export const webhooksRouter = createTRPCRouter({ - list: webhooksRepositoryProcedure.query(async ({ ctx }) => { - const result = await ctx.webhooksRepository.listWebhooks() - - if (!result.ok) { - l.error( - { - key: 'list_webhooks_trpc:error', - status: result.error.status, - error: result.error, - team_id: ctx.teamId, - user_id: ctx.session.user.id, - }, - `Failed to list webhooks: ${result.error.status}: ${result.error.message}` - ) - - throwTRPCErrorFromRepoError(result.error) - } - - return { webhooks: result.data } - }), - - get: webhooksRepositoryProcedure - .input(GetWebhookInputSchema) - .query(async ({ ctx, input }) => { - const result = await ctx.webhooksRepository.getWebhook(input.webhookId) - - if (!result.ok) { - l.error( - { - key: 'get_webhook_trpc:error', - status: result.error.status, - error: result.error, - team_id: ctx.teamId, - user_id: ctx.session.user.id, - context: { webhookId: input.webhookId }, - }, - `Failed to get webhook: ${result.error.status}: ${result.error.message}` - ) - - throwTRPCErrorFromRepoError(result.error) - } - - return { webhook: result.data } - }), - - listDeliveries: webhooksRepositoryProcedure - .input(ListWebhookDeliveriesInputSchema) - .query(async ({ ctx, input }) => { - const result = await ctx.webhooksRepository.listWebhookDeliveries({ - webhookId: input.webhookId, - limit: input.limit, - cursor: input.cursor, - orderAsc: input.orderAsc, - start: input.start, - end: input.end, - deliveryStatus: input.deliveryStatus, - eventType: input.eventType, - }) - - if (!result.ok) { - l.error( - { - key: 'list_webhook_deliveries_trpc:error', - status: result.error.status, - error: result.error, - team_id: ctx.teamId, - user_id: ctx.session.user.id, - context: { - webhookId: input.webhookId, - deliveryStatus: input.deliveryStatus, - eventType: input.eventType, - }, - }, - `Failed to list webhook deliveries: ${result.error.status}: ${result.error.message}` - ) - - throwTRPCErrorFromRepoError(result.error) - } - - return { - groups: result.data.data.map(toDeliveryGroup), - nextCursor: result.data.nextCursor, - } - }), - - getDeliveryStats: webhooksRepositoryProcedure - .input(GetWebhookDeliveryStatsInputSchema) - .query(async ({ ctx, input }) => { - const result = await ctx.webhooksRepository.getWebhookDeliveryStats({ - webhookId: input.webhookId, - start: input.start, - end: input.end, - }) - - if (!result.ok) { - l.error( - { - key: 'get_webhook_delivery_stats_trpc:error', - status: result.error.status, - error: result.error, - team_id: ctx.teamId, - user_id: ctx.session.user.id, - context: { - webhookId: input.webhookId, - start: input.start, - end: input.end, - }, - }, - `Failed to get webhook delivery stats: ${result.error.status}: ${result.error.message}` - ) - - throwTRPCErrorFromRepoError(result.error) - } - - return { stats: result.data } - }), - - upsert: webhooksRepositoryProcedure - .input(UpsertWebhookInputSchema) - .mutation(async ({ ctx, input }) => { - const result = await ctx.webhooksRepository.upsertWebhook(input) - - if (!result.ok) { - l.error( - { - key: - input.mode === 'update' - ? 'update_webhook_trpc:error' - : 'create_webhook_trpc:error', - status: result.error.status, - error: result.error, - team_id: ctx.teamId, - user_id: ctx.session.user.id, - context: { - mode: input.mode, - name: input.name, - events: input.events, - }, - }, - `Failed to ${input.mode} webhook: ${result.error.status}: ${result.error.message}` - ) - - throwTRPCErrorFromRepoError(result.error) - } - }), - - delete: webhooksRepositoryProcedure - .input(DeleteWebhookInputSchema) - .mutation(async ({ ctx, input }) => { - const result = await ctx.webhooksRepository.deleteWebhook(input.webhookId) - - if (!result.ok) { - l.error( - { - key: 'delete_webhook_trpc:error', - status: result.error.status, - error: result.error, - team_id: ctx.teamId, - user_id: ctx.session.user.id, - context: { webhookId: input.webhookId }, - }, - `Failed to delete webhook: ${result.error.status}: ${result.error.message}` - ) - - throwTRPCErrorFromRepoError(result.error) - } - }), - - updateSecret: webhooksRepositoryProcedure - .input(UpdateWebhookSecretInputSchema) - .mutation(async ({ ctx, input }) => { - const result = await ctx.webhooksRepository.updateWebhookSecret( - input.webhookId, - input.signatureSecret - ) - - if (!result.ok) { - l.error( - { - key: 'update_webhook_secret_trpc:error', - status: result.error.status, - error: result.error, - team_id: ctx.teamId, - user_id: ctx.session.user.id, - context: { webhookId: input.webhookId }, - }, - `Failed to update webhook secret: ${result.error.status}: ${result.error.message}` - ) - - throwTRPCErrorFromRepoError(result.error) - } - }), -}) diff --git a/src/core/server/functions/sandboxes/utils.ts b/src/core/server/functions/sandboxes/utils.ts index 4d0d2fda0..64c91ada7 100644 --- a/src/core/server/functions/sandboxes/utils.ts +++ b/src/core/server/functions/sandboxes/utils.ts @@ -1,9 +1,5 @@ -import { TEAM_METRICS_BACKEND_COLLECTION_INTERVAL_MS } from '@/configs/intervals' import type { SandboxesMetricsRecord } from '@/core/modules/sandboxes/models' -import type { - ClientSandboxesMetrics, - ClientTeamMetrics, -} from '@/core/modules/sandboxes/models.client' +import type { ClientSandboxesMetrics } from '@/core/modules/sandboxes/models.client' export function transformMetricsToClientMetrics( metrics: SandboxesMetricsRecord @@ -23,240 +19,3 @@ export function transformMetricsToClientMetrics( ]) ) } - -export function calculateStepForRange(startMs: number, endMs: number): number { - const duration = endMs - startMs - return calculateStepForDuration(duration) -} - -// this function comes from e2b-dev/infra and is used to calculate the step for a given duration for /teams/$teamId/metrics -export function calculateStepForDuration(durationMs: number): number { - const hour = 60 * 60 * 1000 - const minute = 60 * 1000 - const second = 1000 - - switch (true) { - case durationMs < hour: - return 5 * second - case durationMs < 6 * hour: - return 30 * second - case durationMs < 12 * hour: - return minute - case durationMs < 24 * hour: - return 2 * minute - case durationMs < 7 * 24 * hour: - return 5 * minute - default: - return 15 * minute - } -} - -function _createZeroMetricPoint(timestamp: number): ClientTeamMetrics[0] { - return { - timestamp, - concurrentSandboxes: 0, - sandboxStartRate: 0, - } -} - -function _generateEmptyTimeSeriesWithZeros( - start: number, - end: number, - step: number -): ClientTeamMetrics { - const calculatedStep = step > 0 ? step : calculateStepForRange(start, end) - const result: ClientTeamMetrics = [] - - for (let timestamp = start; timestamp < end; timestamp += calculatedStep) { - result.push(_createZeroMetricPoint(timestamp)) - } - - return result -} - -function _isGapAnomalous( - gapDuration: number, - expectedStep: number, - anomalousGapTolerance: number -): boolean { - return gapDuration > expectedStep * (1 + anomalousGapTolerance) -} - -// adds zero-valued data points at the beginning of the time series if there's an anomalous gap -function _addStartPaddingZeros( - result: ClientTeamMetrics, - sortedData: ClientTeamMetrics, - start: number, - step: number, - anomalousGapTolerance: number -): void { - const firstDataPoint = sortedData[0]! - const gapFromStart = firstDataPoint.timestamp - start - const isStartAnomalous = _isGapAnomalous( - gapFromStart, - step, - anomalousGapTolerance - ) - - if (isStartAnomalous) { - result.push(_createZeroMetricPoint(start)) - - const prefixZeroTimestamp = firstDataPoint.timestamp - step - if ( - prefixZeroTimestamp > start && - prefixZeroTimestamp < firstDataPoint.timestamp - ) { - result.push(_createZeroMetricPoint(prefixZeroTimestamp)) - } - } -} - -// fills gaps between consecutive data points with zero values when gaps are anomalously large -function _fillIntermediateGapsWithZeros( - result: ClientTeamMetrics, - sortedData: ClientTeamMetrics, - currentIndex: number, - step: number, - anomalousGapTolerance: number -): void { - const currentPoint = sortedData[currentIndex]! - const nextPoint = sortedData[currentIndex + 1]! - const actualGap = nextPoint.timestamp - currentPoint.timestamp - const isAnomalousGap = _isGapAnomalous(actualGap, step, anomalousGapTolerance) - - if (isAnomalousGap) { - const suffixZeroTimestamp = currentPoint.timestamp + step - if ( - suffixZeroTimestamp < nextPoint.timestamp && - suffixZeroTimestamp > currentPoint.timestamp - ) { - result.push(_createZeroMetricPoint(suffixZeroTimestamp)) - } - - const prefixZeroTimestamp = nextPoint.timestamp - step - if ( - prefixZeroTimestamp > currentPoint.timestamp && - prefixZeroTimestamp < nextPoint.timestamp && - prefixZeroTimestamp > suffixZeroTimestamp - ) { - result.push(_createZeroMetricPoint(prefixZeroTimestamp)) - } - } -} - -function _shouldAddEndZeros( - gapToEnd: number, - step: number, - anomalousGapTolerance: number -): boolean { - const isEndAnomalous = _isGapAnomalous(gapToEnd, step, anomalousGapTolerance) - - return ( - (isEndAnomalous && - gapToEnd > TEAM_METRICS_BACKEND_COLLECTION_INTERVAL_MS) || - (step > 0 && - gapToEnd >= step * 3 && - gapToEnd > TEAM_METRICS_BACKEND_COLLECTION_INTERVAL_MS) || - gapToEnd >= 5 * 60 * 1000 - ) -} - -function _addEndPaddingZeros( - result: ClientTeamMetrics, - lastDataPoint: ClientTeamMetrics[0], - end: number, - step: number, - anomalousGapTolerance: number -): void { - const gapToEnd = end - lastDataPoint.timestamp - - if (_shouldAddEndZeros(gapToEnd, step, anomalousGapTolerance)) { - const suffixZeroTimestamp = lastDataPoint.timestamp + step - if (suffixZeroTimestamp < end) { - result.push(_createZeroMetricPoint(suffixZeroTimestamp)) - } - - result.push(_createZeroMetricPoint(end - 1000)) - } -} - -function _stripPotentialOverfetching( - data: ClientTeamMetrics, - start: number, - end: number -): ClientTeamMetrics { - return data.filter((d) => d.timestamp >= start && d.timestamp <= end) -} - -/** - * Fills team metrics data with zero values to create smooth, continuous time series for visualization. - * - * This function is critical for creating meaningful charts of team sandbox activity over time. - * It handles several scenarios where raw metrics data would create poor user experiences: - * - * 1. **Empty Data**: When no metrics exist, generates a complete zero-filled time series - * 2. **Sparse Data**: When data points are too far apart, adds zeros to show periods of inactivity - * 3. **Boundary Gaps**: When requested time range extends beyond available data, pads with zeros - * 4. **Activity Waves**: Creates clear visual separation between periods of activity and inactivity - * - * @param data - Array of team metrics with concurrentSandboxes and sandboxStartRate - * @param start - Start timestamp (Unix milliseconds) for the desired time range - * @param end - End timestamp (Unix milliseconds) for the desired time range - * @param step - Expected interval (milliseconds) between data points - * @param anomalousGapTolerance - Multiplier for detecting unusually large gaps (default 0.25 = 25%) - * @returns Sorted array of metrics with zero-padding for smooth visualization - */ -export function fillTeamMetricsWithZeros( - data: ClientTeamMetrics, - start: number, - end: number, - step: number, - anomalousGapTolerance: number = 0.25 -): ClientTeamMetrics { - if (!data.length) { - return _generateEmptyTimeSeriesWithZeros(start, end, step) - } - - let sortedData = [...data].sort((a, b) => a.timestamp - b.timestamp) - const result: ClientTeamMetrics = [] - - _addStartPaddingZeros(result, sortedData, start, step, anomalousGapTolerance) - - // handle single data point case: add the point and check for end padding - if (data.length === 1) { - const singlePoint = sortedData[0]! - result.push(singlePoint) - _addEndPaddingZeros(result, singlePoint, end, step, anomalousGapTolerance) - const sorted = result.sort((a, b) => a.timestamp - b.timestamp) - - return _stripPotentialOverfetching(sorted, start, end) - } - - for (let i = 0; i < sortedData.length; i++) { - const currentPoint = sortedData[i]! - result.push(currentPoint) - - if (i === sortedData.length - 1) { - break - } - - _fillIntermediateGapsWithZeros( - result, - sortedData, - i, - step, - anomalousGapTolerance - ) - } - - const lastDataPoint = sortedData[sortedData.length - 1]! - _addEndPaddingZeros(result, lastDataPoint, end, step, anomalousGapTolerance) - - // re-sort the data - sortedData = result.sort((a, b) => a.timestamp - b.timestamp) - - // strip potential overfetching coming from the server functions - const strippedData = _stripPotentialOverfetching(sortedData, start, end) - - return strippedData -} diff --git a/src/core/server/functions/usage/get-usage.ts b/src/core/server/functions/usage/get-usage.ts deleted file mode 100644 index e01c4aeaa..000000000 --- a/src/core/server/functions/usage/get-usage.ts +++ /dev/null @@ -1,41 +0,0 @@ -import 'server-only' - -import { cacheLife, cacheTag } from 'next/cache' -import { z } from 'zod' -import { CACHE_TAGS } from '@/configs/cache' -import { createBillingRepository } from '@/core/modules/billing/repository.server' -import { - authActionClient, - withTeamSlugResolution, -} from '@/core/server/actions/client' -import { returnServerError } from '@/core/server/actions/utils' -import { getPublicRepoErrorMessage } from '@/core/shared/errors' -import { TeamSlugSchema } from '@/core/shared/schemas/team' - -const GetUsageAuthActionSchema = z.object({ - teamSlug: TeamSlugSchema, -}) - -export const getUsage = authActionClient - .schema(GetUsageAuthActionSchema) - .metadata({ serverFunctionName: 'getUsage' }) - .use(withTeamSlugResolution) - .action(async ({ ctx }) => { - 'use cache' - - const { teamId } = ctx - - cacheLife('hours') - cacheTag(CACHE_TAGS.TEAM_USAGE(teamId)) - - const result = await createBillingRepository({ - accessToken: ctx.session.access_token, - teamId, - }).getUsage() - - if (!result.ok) { - return returnServerError(getPublicRepoErrorMessage(result.error)) - } - - return result.data - }) diff --git a/src/core/server/functions/webhooks/schema.ts b/src/core/server/functions/webhooks/schema.ts deleted file mode 100644 index 949b7b8a5..000000000 --- a/src/core/server/functions/webhooks/schema.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { z } from 'zod' -import { SandboxLifecycleEventTypeSchema } from '@/core/modules/sandboxes/lifecycle-event-types' - -const WebhookUrlSchema = z.httpUrl('Must be a valid URL').trim() -const WebhookSecretSchema = z - .string() - .trim() - .min(32, 'Secret must be at least 32 characters') - -export const UpsertWebhookInputSchema = z - .object({ - mode: z.enum(['create', 'update']), - webhookId: z.uuid().optional(), - name: z.string().min(1, 'Name is required').trim(), - url: WebhookUrlSchema, - events: z - .array(SandboxLifecycleEventTypeSchema) - .min(1, 'At least one event is required'), - signatureSecret: WebhookSecretSchema.optional(), - enabled: z.boolean().optional().default(true), - }) - .superRefine((data, ctx) => { - if (data.mode === 'create' && !data.signatureSecret) { - ctx.addIssue({ - code: 'custom', - message: 'Secret is required when creating a webhook', - path: ['signatureSecret'], - }) - } - if (data.mode === 'update' && !data.webhookId) { - ctx.addIssue({ - code: 'custom', - message: 'webhookId is required when updating a webhook', - path: ['webhookId'], - }) - } - }) - -export const DeleteWebhookInputSchema = z.object({ - webhookId: z.uuid(), -}) - -export const UpdateWebhookSecretInputSchema = z.object({ - webhookId: z.uuid(), - signatureSecret: WebhookSecretSchema, -}) - -const DeliveryStatusSchema = z.enum(['success', 'failed']) - -export const GetWebhookInputSchema = z.object({ - webhookId: z.uuid(), -}) - -export const ListWebhookDeliveriesInputSchema = z.object({ - webhookId: z.uuid(), - limit: z.number().int().min(1).max(100).optional().default(25), - cursor: z.string().optional(), - orderAsc: z.boolean().optional().default(false), - start: z.iso.datetime().optional(), - end: z.iso.datetime().optional(), - deliveryStatus: z.array(DeliveryStatusSchema).optional(), - eventType: z.array(SandboxLifecycleEventTypeSchema).optional(), -}) - -export const GetWebhookDeliveryStatsInputSchema = z - .object({ - webhookId: z.uuid(), - start: z.iso.datetime().optional(), - end: z.iso.datetime().optional(), - }) - .superRefine((data, ctx) => { - if (!data.start || !data.end) return - - const start = new Date(data.start) - const end = new Date(data.end) - if (end.getTime() - start.getTime() <= 7 * 24 * 60 * 60 * 1000) return - - ctx.addIssue({ - code: 'custom', - message: 'Webhook delivery stats range must be 7 days or less', - path: ['start'], - }) - }) - -export type UpsertWebhookFormInput = z.input -export type UpsertWebhookInput = z.output -export type DeleteWebhookInput = z.input -export type UpdateWebhookSecretInput = z.input< - typeof UpdateWebhookSecretInputSchema -> -export type GetWebhookInput = z.input -export type ListWebhookDeliveriesInput = z.input< - typeof ListWebhookDeliveriesInputSchema -> -export type GetWebhookDeliveryStatsInput = z.input< - typeof GetWebhookDeliveryStatsInputSchema -> diff --git a/src/core/shared/clients/api.ts b/src/core/shared/clients/api.ts index 181446a68..d6c23276f 100644 --- a/src/core/shared/clients/api.ts +++ b/src/core/shared/clients/api.ts @@ -1,9 +1,8 @@ import createClient from 'openapi-fetch' -import type { paths as ArgusPaths } from '@/core/shared/contracts/argus-api.types' import type { paths as DashboardPaths } from '@/core/shared/contracts/dashboard-api.types' import type { paths as InfraPaths } from '@/core/shared/contracts/infra-api.types' -type CombinedPaths = InfraPaths & ArgusPaths +type CombinedPaths = InfraPaths const INFRA_API_URL = process.env.NEXT_PUBLIC_INFRA_API_URL || diff --git a/src/core/shared/contracts/argus-api.types.ts b/src/core/shared/contracts/argus-api.types.ts deleted file mode 100644 index 21d74c1ce..000000000 --- a/src/core/shared/contracts/argus-api.types.ts +++ /dev/null @@ -1,705 +0,0 @@ -/** - * This file was auto-generated by openapi-typescript. - * Do not make direct changes to the file. - */ - -export interface paths { - '/health': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** @description Health check */ - get: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description Request was successful */ - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - 401: components['responses']['401'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/events/sandboxes/{sandboxID}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** @description Get sandbox events */ - get: { - parameters: { - query?: { - offset?: number - limit?: number - orderAsc?: boolean - /** @description Filter events to the provided event types */ - types?: string[] - } - header?: never - path: { - sandboxID: components['parameters']['sandboxID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned the sandbox events */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['SandboxEvent'][] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/events/sandboxes': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** @description Get all sandbox events for the team associated with the API key */ - get: { - parameters: { - query?: { - offset?: number - limit?: number - orderAsc?: boolean - /** @description Filter events to the provided event types */ - types?: string[] - } - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned the sandbox events */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['SandboxEvent'][] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/events/webhooks': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** @description List registered webhooks. */ - get: operations['webhooksList'] - put?: never - /** @description Register events webhook. */ - post: operations['webhookCreate'] - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/events/webhooks/{webhookID}': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** @description Get a registered webhook. */ - get: operations['webhookGet'] - put?: never - post?: never - /** @description Delete a registered webhook. */ - delete: operations['webhookDelete'] - options?: never - head?: never - /** @description Update a registered webhook configuration. */ - patch: operations['webhookUpdate'] - trace?: never - } - '/events/webhooks/{webhookID}/deliveries': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** @description List webhook delivery attempts. */ - get: operations['webhookDeliveriesList'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/events/webhooks/{webhookID}/stats': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** @description Get webhook delivery aggregate stats. */ - get: operations['webhookDeliveryStats'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } -} -export type webhooks = Record -export interface components { - schemas: { - Error: { - /** - * Format: int32 - * @description Error code - */ - code: number - /** @description Error */ - message: string - } - /** @description Sandbox event */ - SandboxEvent: { - /** - * Format: uuid - * @description Event unique identifier - */ - id: string - /** @description Event structure version */ - version: string - /** @description Event name */ - type: string - /** - * @deprecated - * @description Category of the event (e.g., 'lifecycle', 'process', etc.) - */ - eventCategory?: string - /** - * @deprecated - * @description Label for the specific event type (e.g., 'sandbox_started', 'process_oom', etc.) - */ - eventLabel?: string - /** @description Optional JSON data associated with the event */ - eventData?: Record | null - /** - * Format: date-time - * @description Timestamp of the event - */ - timestamp: string - /** - * Format: string - * @description Unique identifier for the sandbox - */ - sandboxId: string - /** - * Format: string - * @description Unique identifier for the sandbox execution - */ - sandboxExecutionId: string - /** - * Format: string - * @description Unique identifier for the sandbox template - */ - sandboxTemplateId: string - /** - * Format: string - * @description Unique identifier for the sandbox build - */ - sandboxBuildId: string - /** - * Format: uuid - * @description Team identifier associated with the sandbox - */ - sandboxTeamId: string - } - /** @description Configuration for registering new webhooks */ - WebhookCreate: { - name: string - /** Format: uri */ - url: string - events: string[] - /** @default true */ - enabled: boolean - /** @description Secret used to sign the webhook payloads */ - signatureSecret: string - } - /** @description Webhook creation response */ - WebhookCreation: { - /** @description Webhook unique identifier */ - id: string - /** @description Webhook user friendly name */ - name: string - /** - * Format: date-time - * @description Time when the template was created - */ - createdAt: string - /** @description Unique identifier for the team */ - teamId: string - /** Format: uri */ - url: string - enabled: boolean - events: string[] - } - /** @description Webhook detail response */ - WebhookDetail: { - /** @description Webhook unique identifier */ - id: string - /** @description Unique identifier for the team */ - teamId: string - /** @description Webhook user friendly name */ - name: string - /** - * Format: date-time - * @description Time when the template was created - */ - createdAt: string - /** Format: uri */ - url: string - enabled: boolean - events: string[] - } - /** @description Configuration for updating existing webhooks */ - WebhookConfiguration: { - enabled?: boolean - /** @description Webhook user friendly name */ - name?: string - /** Format: uri */ - url?: string - events?: string[] - /** @description Secret used to sign the webhook payloads */ - signatureSecret?: string - } - /** @description Webhook delivery attempt */ - WebhookDelivery: { - /** - * Format: uuid - * @description Delivery attempt identifier - */ - id: string - /** - * Format: uuid - * @description Team identifier - */ - teamId: string - /** - * Format: uuid - * @description Webhook configuration identifier - */ - webhookId: string - /** - * Format: uuid - * @description Sandbox event identifier - */ - eventId: string - /** @description Sandbox identifier */ - sandboxId: string - /** @description Sandbox event type */ - eventType: string - /** - * @description Delivery attempt status - * @enum {string} - */ - status: 'success' | 'failed' - /** - * Format: int32 - * @description Delivery request duration in milliseconds - */ - durationMs: number - /** @description Serialized webhook request body */ - requestBody: string - /** @description JSON-encoded request headers with sensitive values redacted */ - requestHeaders: string - /** - * Format: uri - * @description URL attempted for this delivery - */ - requestUrl: string - /** @description Truncated response body, if a response was received */ - responseBody?: string | null - /** @description JSON-encoded response headers, if a response was received */ - responseHeaders?: string | null - /** - * Format: int32 - * @description HTTP response status code, if a response was received - */ - responseHttpStatusCode?: number | null - /** - * @description Machine-readable non-HTTP or HTTP failure class - * @enum {string|null} - */ - errorClass: - | 'http_error' - | 'dns_error' - | 'timeout' - | 'transport_error' - | 'request_error' - | 'signature_error' - | 'canceled' - | null - /** @description Error message for failures without a useful response body */ - errorMessage?: string | null - /** - * Format: date-time - * @description Time when the delivery attempt started - */ - timestamp: string - } - /** @description Webhook delivery aggregate stats */ - WebhookDeliveryStats: { - buckets: components['schemas']['WebhookDeliveryStatsBucket'][] - /** Format: int64 */ - total: number - /** Format: int64 */ - failed: number - durationMs: components['schemas']['WebhookDeliveryDurationStats'] - } - /** @description Webhook delivery duration statistics in milliseconds */ - WebhookDeliveryDurationStats: { - /** Format: double */ - minimum: number - /** Format: double */ - average: number - /** Format: double */ - maximum: number - } - /** @description Webhook delivery stats for a time bucket */ - WebhookDeliveryStatsBucket: { - /** Format: date-time */ - timestamp: string - /** Format: int64 */ - total: number - /** Format: int64 */ - failed: number - durationMs: components['schemas']['WebhookDeliveryDurationStats'] - } - /** @description Webhook delivery attempts grouped by sandbox event */ - WebhookDeliveryGroup: { - /** Format: uuid */ - eventId: string - eventType: string - sandboxId: string - attempts: components['schemas']['WebhookDelivery'][] - } - /** @description Paginated webhook delivery attempts grouped by event */ - WebhookDeliveriesListPayload: { - data: components['schemas']['WebhookDeliveryGroup'][] - /** @description Cursor to pass to the next list request, or null when there is no next page. */ - nextCursor: string | null - } - } - responses: { - /** @description Bad request */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Authentication error */ - 401: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Not found */ - 404: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Conflict */ - 409: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - /** @description Server error */ - 500: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['Error'] - } - } - } - parameters: { - sandboxID: string - webhookID: string - } - requestBodies: never - headers: never - pathItems: never -} -export type $defs = Record -export interface operations { - webhooksList: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description List of registered webhooks. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['WebhookDetail'][] - } - } - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - webhookCreate: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['WebhookCreate'] - } - } - responses: { - /** @description Successfully created webhook. */ - 201: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['WebhookCreation'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - webhookGet: { - parameters: { - query?: never - header?: never - path: { - webhookID: components['parameters']['webhookID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully returned the webhook configuration. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['WebhookDetail'] - } - } - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - webhookDelete: { - parameters: { - query?: never - header?: never - path: { - webhookID: components['parameters']['webhookID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Successfully deleted webhook. */ - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - webhookUpdate: { - parameters: { - query?: never - header?: never - path: { - webhookID: components['parameters']['webhookID'] - } - cookie?: never - } - requestBody: { - content: { - 'application/json': components['schemas']['WebhookConfiguration'] - } - } - responses: { - /** @description Successfully updated webhook. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['WebhookDetail'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - webhookDeliveriesList: { - parameters: { - query?: { - /** @description Opaque cursor from the previous response's nextCursor field. */ - cursor?: string - limit?: number - orderAsc?: boolean - /** @description Include deliveries at or after this timestamp. */ - start?: string - /** @description Include deliveries before this timestamp. */ - end?: string - /** @description Filter deliveries by delivery status */ - deliveryStatus?: ('success' | 'failed')[] - /** @description Filter deliveries by event type */ - eventType?: string[] - } - header?: never - path: { - webhookID: components['parameters']['webhookID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description List of webhook delivery attempts grouped by event. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['WebhookDeliveriesListPayload'] - } - } - 400: components['responses']['400'] - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } - webhookDeliveryStats: { - parameters: { - query?: { - /** @description Inclusive stats range start. Defaults to 24 hours ago. */ - start?: string - /** @description Exclusive stats range end. Defaults to now. */ - end?: string - } - header?: never - path: { - webhookID: components['parameters']['webhookID'] - } - cookie?: never - } - requestBody?: never - responses: { - /** @description Webhook delivery stats. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['WebhookDeliveryStats'] - } - } - 401: components['responses']['401'] - 404: components['responses']['404'] - 500: components['responses']['500'] - } - } -} diff --git a/src/core/shared/errors.ts b/src/core/shared/errors.ts index dd76c876a..e97de21a8 100644 --- a/src/core/shared/errors.ts +++ b/src/core/shared/errors.ts @@ -1,14 +1,5 @@ -import { getBlockedReasonText } from '@/features/dashboard/team-blocked/team-blocked-message' import type { RepoError, RepoErrorCode } from './result' -/** - * Wire-format prefix used by infra-api for 403 responses caused by a - * blocked team. The dashboard pattern-matches this prefix to translate - * the response into a friendly, user-facing message. - * - */ -export const TEAM_BLOCKED_MESSAGE_PREFIX = 'team is blocked' - export const PUBLIC_ERROR_MESSAGE_UNAUTHORIZED = 'Unauthorized' export const PUBLIC_ERROR_MESSAGE_FORBIDDEN = 'You are not authorized to access this resource' @@ -54,27 +45,6 @@ export const ApiError = (message: string) => new E2BError('API_ERROR', message) export const UnknownError = (message?: string) => new E2BError('UNKNOWN', message ?? PUBLIC_ERROR_MESSAGE_INTERNAL) -export function isTeamBlockedError(input: { - status?: number - message?: string | null -}): boolean { - if (input.status !== 403 || !input.message) return false - return input.message.toLowerCase().startsWith(TEAM_BLOCKED_MESSAGE_PREFIX) -} - -export function extractBlockedReason( - message: string | null | undefined -): string | null { - if (!message) return null - const lower = message.toLowerCase() - if (!lower.startsWith(TEAM_BLOCKED_MESSAGE_PREFIX)) return null - const rest = message - .slice(TEAM_BLOCKED_MESSAGE_PREFIX.length) - .replace(/^[:\s]+/, '') - .trim() - return rest || null -} - export function createRepoError(input: { code: RepoErrorCode status: number @@ -96,10 +66,6 @@ export function getPublicErrorMessage(input: { }): string { const { code, status, message } = input - if (isTeamBlockedError({ status, message })) { - return getBlockedReasonText(extractBlockedReason(message)) - } - if (code === 'unauthorized' || status === 401) return PUBLIC_ERROR_MESSAGE_UNAUTHORIZED if (code === 'forbidden' || status === 403) diff --git a/src/features/dashboard/account/access-token-settings.tsx b/src/features/dashboard/account/access-token-settings.tsx deleted file mode 100644 index 9ab0d7ef6..000000000 --- a/src/features/dashboard/account/access-token-settings.tsx +++ /dev/null @@ -1,88 +0,0 @@ -'use client' - -import Link from 'next/link' -import { HELP_URLS, PROTECTED_URLS } from '@/configs/urls' -import { useFeatureFlag } from '@/core/modules/feature-flags/feature-flags.client' -import { cn } from '@/lib/utils' -import { Badge } from '@/ui/primitives/badge' -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from '@/ui/primitives/card' -import { ChevronRightIcon, KeyIcon } from '@/ui/primitives/icons' -import { useDashboard } from '../context' -import UserAccessToken from './user-access-token' - -interface AccessTokenSettingsProps { - className?: string -} - -export function AccessTokenSettings({ className }: AccessTokenSettingsProps) { - const { user, team } = useDashboard() - const provisioningDisabled = useFeatureFlag( - 'disableE2BAccessTokenProvisioning' - ) - - if (!user) return null - - return ( - - - - Access Token - {provisioningDisabled && Deprecated} - - Manage your personal access token. - - - - - - {provisioningDisabled && ( -

- Access tokens are deprecated and will stop working on August 1, - 2026. -
- Use an API key instead.{' '} - - Learn more - - . -

- )} -
- - {provisioningDisabled ? ( - - - - - - Use API keys instead - - - - - - ) : ( - -

- Keep it safe, as it can be used to authenticate with E2B services. -

-
- )} -
- ) -} diff --git a/src/features/dashboard/account/email-settings.tsx b/src/features/dashboard/account/email-settings.tsx deleted file mode 100644 index f0cfb276e..000000000 --- a/src/features/dashboard/account/email-settings.tsx +++ /dev/null @@ -1,212 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { useSearchParams } from 'next/navigation' -import { useEffect, useState } from 'react' -import { useForm } from 'react-hook-form' -import { z } from 'zod' -import { USER_MESSAGES } from '@/configs/user-messages' -import { - defaultErrorToast, - defaultSuccessToast, - useToast, -} from '@/lib/hooks/use-toast' -import { cn } from '@/lib/utils' -import { useTRPC } from '@/trpc/client' -import { Button } from '@/ui/primitives/button' -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from '@/ui/primitives/card' -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from '@/ui/primitives/form' -import { Input } from '@/ui/primitives/input' -import { useDashboard } from '../context' -import { ReauthDialog } from './reauth-dialog' - -const formSchema = z.object({ - email: z.email('Invalid e-mail address'), -}) - -type FormValues = z.infer - -interface EmailSettingsProps { - className?: string -} - -export function EmailSettings({ className }: EmailSettingsProps) { - 'use no memo' - - const { user } = useDashboard() - const searchParams = useSearchParams() - const { toast } = useToast() - const trpc = useTRPC() - const queryClient = useQueryClient() - const [reauthDialogOpen, setReauthDialogOpen] = useState(false) - const showEmailSettings = - Boolean(user?.canChangeEmail) || Boolean(user?.providers.includes('email')) - const canChangeEmail = Boolean(user?.canChangeEmail) - - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: { - email: searchParams.get('new_email') || user?.email || '', - }, - values: { - email: searchParams.get('new_email') || user?.email || '', - }, - }) - - const { mutate: updateEmail, isPending } = useMutation( - trpc.user.update.mutationOptions({ - onSuccess: (data) => { - if (data.status === 'reauth') { - setReauthDialogOpen(true) - return - } - - if (data.status === 'ok') { - queryClient.setQueryData(trpc.user.profile.queryKey(), data.user) - toast( - defaultSuccessToast(USER_MESSAGES.emailUpdateVerification.message, { - duration: USER_MESSAGES.emailUpdateVerification.timeoutMs, - }) - ) - return - } - - if (data.status === 'error' && data.code === 'email_exists') { - form.setError('email', { message: 'E-mail already in use.' }) - return - } - - if (data.status === 'error' && data.code === 'email_invalid') { - form.setError('email', { message: 'Invalid e-mail address.' }) - return - } - - if ( - data.status === 'error' && - data.code === 'account_credentials_not_changeable' - ) { - toast(defaultErrorToast('E-mail changes are currently unavailable.')) - return - } - - toast(defaultErrorToast('Failed to update e-mail.')) - }, - onError: () => { - toast(defaultErrorToast('Failed to update e-mail.')) - }, - }) - ) - - useEffect(() => { - if ( - !searchParams.has('success') && - !searchParams.has('error') && - !searchParams.has('type') - ) - return - - if (searchParams.get('type') === 'update_email') { - const success = searchParams.get('success') - if (success !== null) { - toast(defaultSuccessToast(success)) - return - } - - const message = searchParams.get('message') - if (message !== null) { - toast(defaultSuccessToast(message)) - return - } - - const error = searchParams.get('error') - if (error !== null) { - toast(defaultErrorToast(error)) - } - } - }, [searchParams, toast]) - - if (!user || !showEmailSettings) return null - - function submitEmailChange(values: FormValues) { - if (!canChangeEmail) return - updateEmail({ email: values.email }) - } - - return ( - <> -
- - - - E-Mail - - {canChangeEmail - ? 'Update your e-mail address.' - : 'E-mail changes are currently unavailable.'} - - - - - ( - - - - - - - )} - /> - - - -

- Has to be a valid e-mail address. -

- -
-
-
- - - - - ) -} diff --git a/src/features/dashboard/account/name-settings.tsx b/src/features/dashboard/account/name-settings.tsx deleted file mode 100644 index 2a9c8f7e8..000000000 --- a/src/features/dashboard/account/name-settings.tsx +++ /dev/null @@ -1,136 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { useForm } from 'react-hook-form' -import { z } from 'zod' -import { USER_MESSAGES } from '@/configs/user-messages' -import { - defaultErrorToast, - defaultSuccessToast, - useToast, -} from '@/lib/hooks/use-toast' -import { cn } from '@/lib/utils' -import { useTRPC } from '@/trpc/client' -import { Button } from '@/ui/primitives/button' -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from '@/ui/primitives/card' -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from '@/ui/primitives/form' -import { Input } from '@/ui/primitives/input' -import { useDashboard } from '../context' - -const formSchema = z.object({ - name: z - .string() - .min(1, 'Name cannot be empty') - .max(100, 'Max 100 characters'), -}) - -type FormValues = z.infer - -interface NameSettingsProps { - className?: string -} - -export function NameSettings({ className }: NameSettingsProps) { - 'use no memo' - - const { user } = useDashboard() - const { toast } = useToast() - const trpc = useTRPC() - const queryClient = useQueryClient() - - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: { - name: user?.name || '', - }, - values: { - name: user?.name || '', - }, - }) - - const { mutate: updateName, isPending } = useMutation( - trpc.user.update.mutationOptions({ - onSuccess: (data) => { - if (data.status === 'ok') { - queryClient.setQueryData(trpc.user.profile.queryKey(), data.user) - toast(defaultSuccessToast(USER_MESSAGES.nameUpdated.message)) - return - } - - toast(defaultErrorToast(USER_MESSAGES.failedUpdateName.message)) - }, - onError: () => { - toast(defaultErrorToast(USER_MESSAGES.failedUpdateName.message)) - }, - }) - ) - - if (!user) return null - - return ( -
- - updateName({ name: values.name }) - )} - className="w-full" - > - - - Name - - Update your account name, which will be visible to your project - members. - - - - - ( - - - - - - - )} - /> - - - -

Max 100 characters.

- -
-
-
- - ) -} diff --git a/src/features/dashboard/account/password-settings-server.tsx b/src/features/dashboard/account/password-settings-server.tsx deleted file mode 100644 index c7ce086ee..000000000 --- a/src/features/dashboard/account/password-settings-server.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { AccountPageSearchParams } from '@/app/dashboard/[teamSlug]/account/page' -import { isCurrentSessionFresh } from '@/core/server/auth' -import { PasswordSettings } from './password-settings' - -interface PasswordSettingsServerProps { - className?: string - searchParams: Promise -} - -export async function PasswordSettingsServer({ - className, - searchParams, -}: PasswordSettingsServerProps) { - const [{ reauth = '0' }, isSessionFresh] = await Promise.all([ - searchParams, - isCurrentSessionFresh(), - ]) - - const showPasswordChangeForm = reauth === '1' - - return ( - - ) -} diff --git a/src/features/dashboard/account/password-settings.tsx b/src/features/dashboard/account/password-settings.tsx deleted file mode 100644 index c23c38706..000000000 --- a/src/features/dashboard/account/password-settings.tsx +++ /dev/null @@ -1,229 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import { useMutation } from '@tanstack/react-query' -import { useEffect, useState } from 'react' -import { useForm } from 'react-hook-form' -import { z } from 'zod' -import { USER_MESSAGES } from '@/configs/user-messages' -import { - defaultErrorToast, - defaultSuccessToast, - useToast, -} from '@/lib/hooks/use-toast' -import { cn } from '@/lib/utils' -import { useTRPC } from '@/trpc/client' -import { Button } from '@/ui/primitives/button' -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from '@/ui/primitives/card' -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from '@/ui/primitives/form' -import { Input } from '@/ui/primitives/input' -import { useDashboard } from '../context' -import { ReauthDialog } from './reauth-dialog' - -const formSchema = z - .object({ - password: z.string().min(8, 'Password must be at least 8 characters'), - confirmPassword: z - .string() - .min(8, 'Password must be at least 8 characters'), - nonce: z.string().optional(), - }) - .refine((data) => data.password === data.confirmPassword, { - message: "Passwords don't match", - path: ['confirmPassword'], - }) - -type FormValues = z.infer - -interface PasswordSettingsProps { - className?: string - showPasswordChangeForm: boolean - isSessionFresh: boolean -} - -export function PasswordSettings({ - className, - showPasswordChangeForm, - isSessionFresh, -}: PasswordSettingsProps) { - 'use no memo' - - const { user } = useDashboard() - const { toast } = useToast() - const trpc = useTRPC() - const [reauthDialogOpen, setReauthDialogOpen] = useState(false) - const [clientShowPasswordForm, setClientShowPasswordForm] = useState( - showPasswordChangeForm || isSessionFresh - ) - - useEffect(() => { - setClientShowPasswordForm(showPasswordChangeForm || isSessionFresh) - }, [showPasswordChangeForm, isSessionFresh]) - - const form = useForm({ - resolver: zodResolver(formSchema), - defaultValues: { - password: '', - confirmPassword: '', - }, - }) - - const { mutate: updatePassword, isPending } = useMutation( - trpc.user.update.mutationOptions({ - onSuccess: (data) => { - if (data.status === 'reauth') { - setReauthDialogOpen(true) - return - } - - if (data.status === 'error') { - if (data.code === 'account_credentials_not_changeable') { - toast(defaultErrorToast(USER_MESSAGES.failedUpdatePassword.message)) - return - } - - const message = - data.code === 'same_password' - ? 'New password cannot be the same as the old password.' - : 'Password is too weak.' - form.setError('confirmPassword', { message }) - return - } - - toast(defaultSuccessToast(USER_MESSAGES.passwordUpdated.message)) - - form.reset() - setClientShowPasswordForm(false) - window.history.replaceState({}, '', window.location.pathname) - }, - onError: () => { - toast(defaultErrorToast(USER_MESSAGES.failedUpdatePassword.message)) - }, - }) - ) - - function onSubmit(values: FormValues) { - updatePassword({ password: values.password }) - } - - function handleChangePassword() { - if (isSessionFresh) { - setClientShowPasswordForm(true) - return - } - - setReauthDialogOpen(true) - } - - if (!user || !user.canChangePassword) return null - - return ( - <> - - - Password - Change your account password. - - - - {clientShowPasswordForm ? ( -
- -
- ( - - - - - - - )} - /> - - ( - - - { - if (e.key === 'Enter') { - form.handleSubmit(onSubmit)() - } - }} - {...field} - /> - - - - )} - /> -
-
- - ) : ( - <> - {!isSessionFresh && ( -

- To change your password, you'll need to re-authenticate for - security. -

- )} - - - )} -
- - {clientShowPasswordForm && ( - -

- Your password must be at least 8 characters long. -

- -
- )} -
- - - - ) -} diff --git a/src/features/dashboard/account/reauth-dialog.tsx b/src/features/dashboard/account/reauth-dialog.tsx deleted file mode 100644 index b772af131..000000000 --- a/src/features/dashboard/account/reauth-dialog.tsx +++ /dev/null @@ -1,38 +0,0 @@ -'use client' - -import { reauthForAccountSettingsAction } from '@/core/server/actions/auth-actions' -import { AlertDialog } from '@/ui/alert-dialog' - -interface ReauthDialogProps { - open?: boolean - onOpenChange?: (open: boolean) => void -} - -export function ReauthDialog({ open, onOpenChange }: ReauthDialogProps) { - const handleReauth = async () => { - // Hard navigation (not the Next router): the OAuth start route is a side-effecting GET - // that must run exactly once, so a soft RSC navigation would corrupt the - // OAuth flow. See reauthForAccountSettingsAction. - const { url } = await reauthForAccountSettingsAction() - window.location.href = url - } - - return ( - - To make this change, you'll need to re-authenticate{' '} - for security. -

- } - confirm="Sign in again" - confirmProps={{ - variant: 'primary', - }} - onConfirm={handleReauth} - /> - ) -} diff --git a/src/features/dashboard/account/timezone-settings.tsx b/src/features/dashboard/account/timezone-settings.tsx deleted file mode 100644 index 3d3e92077..000000000 --- a/src/features/dashboard/account/timezone-settings.tsx +++ /dev/null @@ -1,194 +0,0 @@ -'use client' - -import { useEffect, useMemo, useState } from 'react' -import { type Timezone, useTimezone } from '@/features/dashboard/timezone' -import { - formatTimezoneLabel, - getBrowserTimezone, - getTimezones, -} from '@/features/dashboard/timezone/utils' -import { - defaultErrorToast, - defaultSuccessToast, - useToast, -} from '@/lib/hooks/use-toast' -import { cn } from '@/lib/utils' -import { Button } from '@/ui/primitives/button' -import { - Card, - CardContent, - CardDescription, - CardFooter, - CardHeader, - CardTitle, -} from '@/ui/primitives/card' -import { - Command, - CommandEmpty, - CommandInput, - CommandItem, - CommandList, -} from '@/ui/primitives/command' -import { - Popover, - PopoverContent, - PopoverTrigger, -} from '@/ui/primitives/popover' - -interface TimezoneSettingsProps { - className?: string -} - -const TIMEZONE_SAVE_SETTLE_DELAY_MS = 300 - -export const TimezoneSettings = ({ className }: TimezoneSettingsProps) => { - const { timezone, setTimezone } = useTimezone() - const { toast } = useToast() - const [open, setOpen] = useState(false) - const [isSaving, setIsSaving] = useState(false) - - const [browserTimezone, setBrowserTimezone] = useState(null) - const [availableTimezones, setAvailableTimezones] = useState([ - timezone, - ]) - - useEffect(() => { - const detectedBrowserTimezone = getBrowserTimezone() - setBrowserTimezone(detectedBrowserTimezone) - setAvailableTimezones( - Array.from( - new Set([timezone, detectedBrowserTimezone, ...getTimezones()]) - ) - ) - }, [timezone]) - - const timezoneOptions = useMemo( - () => - availableTimezones.map((option) => ({ - value: option, - label: formatTimezoneLabel(option), - })), - [availableTimezones] - ) - const timezoneLabel = useMemo(() => formatTimezoneLabel(timezone), [timezone]) - const browserTimezoneLabel = useMemo( - () => - browserTimezone ? formatTimezoneLabel(browserTimezone) : 'Detecting...', - [browserTimezone] - ) - const isBrowserTimezoneSelected = timezone === browserTimezone - const showUseBrowserTimezoneButton = - Boolean(browserTimezone) && !isBrowserTimezoneSelected - - const handleTimezoneSelect = async (nextTimezone: Timezone) => { - if (isSaving) return - - if (nextTimezone === timezone) { - setOpen(false) - return - } - - setIsSaving(true) - setOpen(false) - const didSave = await setTimezone(nextTimezone) - - await new Promise((resolve) => - window.setTimeout(resolve, TIMEZONE_SAVE_SETTLE_DELAY_MS) - ) - setIsSaving(false) - - if (!didSave) { - toast(defaultErrorToast('Failed to update timezone preference.')) - return - } - - toast(defaultSuccessToast('Timezone updated.')) - } - - return ( - - - Timezone - - Choose how dashboard time ranges, charts, and timestamp labels should - be displayed. - - - - - { - if (!isSaving) setOpen(nextOpen) - }} - > - - - - - - - - No timezones found. - {timezoneOptions.map((option) => ( - void handleTimezoneSelect(option.value)} - className="justify-between" - > - {option.label} - {option.value === timezone ? ( - - Selected - - ) : null} - - ))} - - - - - - - -

- Browser timezone:{' '} - {browserTimezoneLabel} -

-
- -
-
-
- ) -} diff --git a/src/features/dashboard/account/user-access-token.tsx b/src/features/dashboard/account/user-access-token.tsx deleted file mode 100644 index c7b1d2c45..000000000 --- a/src/features/dashboard/account/user-access-token.tsx +++ /dev/null @@ -1,80 +0,0 @@ -'use client' - -import { useMutation } from '@tanstack/react-query' -import { useState } from 'react' -import { defaultErrorToast, useToast } from '@/lib/hooks/use-toast' -import { useTRPC } from '@/trpc/client' -import CopyButton from '@/ui/copy-button' -import { IconButton } from '@/ui/primitives/icon-button' -import { EyeClosedIcon, EyeOpenIcon } from '@/ui/primitives/icons' -import { Input } from '@/ui/primitives/input' -import { Loader } from '@/ui/primitives/loader' - -interface UserAccessTokenProps { - className?: string - disabled?: boolean -} - -export default function UserAccessToken({ - className, - disabled, -}: UserAccessTokenProps) { - const { toast } = useToast() - const trpc = useTRPC() - const [token, setToken] = useState() - const [isVisible, setIsVisible] = useState(false) - - const { mutate: fetchToken, isPending } = useMutation( - trpc.user.createAccessToken.mutationOptions({ - onSuccess: (data) => { - if (data?.token) { - setToken(data.token) - setIsVisible(true) - } - }, - onError: () => { - toast(defaultErrorToast('Failed to fetch access token')) - }, - }) - ) - - return ( -
-
- - { - if (token) { - setIsVisible(!isVisible) - setToken(undefined) - } else { - fetchToken() - } - }} - disabled={isPending || disabled} - > - {isPending ? ( - - ) : token ? ( - isVisible ? ( - - ) : ( - - ) - ) : ( - - )} - - -
-
- ) -} diff --git a/src/features/dashboard/billing/addons.tsx b/src/features/dashboard/billing/addons.tsx deleted file mode 100644 index 70e2e123c..000000000 --- a/src/features/dashboard/billing/addons.tsx +++ /dev/null @@ -1,268 +0,0 @@ -'use client' - -import { useMutation } from '@tanstack/react-query' -import Link from 'next/link' -import { useState } from 'react' -import { PROTECTED_URLS } from '@/configs/urls' -import type { AddonInfo } from '@/core/modules/billing/models' -import { useRouteParams } from '@/lib/hooks/use-route-params' -import { defaultErrorToast, useToast } from '@/lib/hooks/use-toast' -import { formatCurrency } from '@/lib/utils/formatting' -import { useTRPC } from '@/trpc/client' -import HelpTooltip from '@/ui/help-tooltip' -import { Badge } from '@/ui/primitives/badge' -import { Button } from '@/ui/primitives/button' -import { InfoIcon, SandboxIcon, UpgradeIcon } from '@/ui/primitives/icons' -import { Label } from '@/ui/primitives/label' -import { Loader } from '@/ui/primitives/loader' -import { Skeleton } from '@/ui/primitives/skeleton' -import { useDashboard } from '../context' -import { ConcurrentSandboxAddOnPurchaseDialog } from './concurrent-sandboxes-addon-dialog' -import { ADDON_500_SANDBOXES_ID, TIER_PRO_ID } from './constants' -import { useBillingItems } from './hooks' -import { formatAddonQuantity } from './utils' - -interface AddonItemProps { - name: string - priceCents: number - description: string - actions?: React.ReactNode -} - -function AddonItem({ name, priceCents, description, actions }: AddonItemProps) { - return ( -
-
-
- -
- -
-
- {name} - - {formatCurrency(priceCents / 100)}/MO - -
-

{description}

-
-
- - {actions && ( -
- {actions} -
- )} -
- ) -} - -interface ActiveAddonsProps { - addons: Array<{ label: string; price_cents: number }> -} - -function ActiveAddons({ addons }: ActiveAddonsProps) { - if (addons.length === 0) return null - - return ( -
- -
- {addons.map((addon, index) => ( - - ))} -
-
- ) -} - -interface AvailableAddonsProps { - addon: AddonInfo - onAdd: () => void - isLoading: boolean - disabled: boolean -} - -function AvailableAddons({ - addon, - onAdd, - isLoading, - disabled, -}: AvailableAddonsProps) { - return ( -
- -
- - - } - > - Add-on cost is added on top of all your current costs. - Stackable. - - {isLoading ? ( -
- -
- ) : ( - - )} - - } - /> -
-
- ) -} - -function AddonItemSkeleton() { - return ( -
-
- -
-
- - -
- -
-
- -
- ) -} - -function AddonsLoading() { - return ( -
- -
- -
-
- ) -} - -function AddonsUpgradePlaceholder() { - const { teamSlug } = useRouteParams<'/dashboard/[teamSlug]/billing/plan'>() - - return ( -
- -
-

- Upgrade to Pro to purchase add-ons for higher concurrency limits. -

- -
-
- ) -} - -export default function Addons() { - const { team } = useDashboard() - const { toast } = useToast() - const { teamSlug } = useRouteParams<'/dashboard/[teamSlug]/billing/plan'>() - const trpc = useTRPC() - const [isDialogOpen, setIsDialogOpen] = useState(false) - const { tierData, addonData, isLoading } = useBillingItems() - const currentConcurrentSandboxesLimit = team.limits.concurrentSandboxes - - const selectedTierId = tierData?.selected?.id - const currentAddon = addonData?.current - const availableAddon = addonData?.available - - const isOnProTier = selectedTierId === TIER_PRO_ID - - const createOrderMutation = useMutation( - trpc.billing.createOrder.mutationOptions({ - onSuccess: () => { - setIsDialogOpen(true) - }, - onError: (error) => { - toast(defaultErrorToast(error.message ?? 'Failed to create order')) - }, - }) - ) - - const handleAddAddon = () => { - if (!team) return - - createOrderMutation.mutate({ - teamSlug, - itemId: ADDON_500_SANDBOXES_ID, - }) - } - - if (isLoading) { - return ( -
- -
- ) - } - - if (!isOnProTier) { - return ( -
- -
- ) - } - - const addonPriceCents = - availableAddon?.price_cents ?? currentAddon?.price_cents - const activeAddons = - currentAddon && addonPriceCents - ? formatAddonQuantity(currentAddon.quantity ?? 0, addonPriceCents) - : [] - - const hasActiveAddons = activeAddons.length > 0 - const orderData = createOrderMutation.data - - return ( -
- {hasActiveAddons && } - - {availableAddon && ( - - )} - - {orderData && availableAddon && ( - - )} -
- ) -} diff --git a/src/features/dashboard/billing/concurrent-sandboxes-addon-dialog.tsx b/src/features/dashboard/billing/concurrent-sandboxes-addon-dialog.tsx deleted file mode 100644 index cb4e0459b..000000000 --- a/src/features/dashboard/billing/concurrent-sandboxes-addon-dialog.tsx +++ /dev/null @@ -1,405 +0,0 @@ -'use client' - -import { - Elements, - PaymentElement, - useElements, - useStripe, -} from '@stripe/react-stripe-js' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { useRouter } from 'next/navigation' -import { useState } from 'react' -import { DASHBOARD_TEAMS_LIST_QUERY_OPTIONS } from '@/core/application/teams/queries' -import { useRouteParams } from '@/lib/hooks/use-route-params' -import { defaultErrorToast, useToast } from '@/lib/hooks/use-toast' -import { useTRPC } from '@/trpc/client' -import { AsciiSandbox } from '@/ui/patterns' -import { Alert, AlertDescription } from '@/ui/primitives/alert' -import { Button } from '@/ui/primitives/button' -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from '@/ui/primitives/dialog' -import { - AlertIcon, - ArrowRightIcon, - CardIcon, - CreditsIcon, - SandboxIcon, -} from '@/ui/primitives/icons' -import { Loader } from '@/ui/primitives/loader' -import { useDashboard } from '../context' -import { - ADDON_PURCHASE_ACTION_ERRORS, - ADDON_PURCHASE_MESSAGES, - SANDBOXES_PER_ADDON, -} from './constants' -import { - stripePromise, - usePaymentConfirmation, - usePaymentElementAppearance, -} from './hooks' - -interface ConcurrentSandboxAddOnPurchaseDialogProps { - open: boolean - onOpenChange: (open: boolean) => void - orderId: string - monthlyPriceCents: number - amountDueCents: number - currentConcurrentSandboxesLimit?: number -} - -function DialogContent_Inner({ - onOpenChange, - orderId, - monthlyPriceCents, - amountDueCents, - currentConcurrentSandboxesLimit, -}: Omit) { - const { team } = useDashboard() - const { toast } = useToast() - const { teamSlug } = useRouteParams<'/dashboard/[teamSlug]/billing/plan'>() - const trpc = useTRPC() - const queryClient = useQueryClient() - const [showPaymentForm, setShowPaymentForm] = useState(false) - const [clientSecret, setClientSecret] = useState(null) - const [customerSessionClientSecret, setCustomerSessionClientSecret] = - useState(null) - - const customerSessionMutation = useMutation( - trpc.billing.getCustomerSession.mutationOptions({ - onSuccess: (data) => { - if (data?.client_secret) { - setCustomerSessionClientSecret(data.client_secret) - } - }, - onError: (error) => { - console.error( - '[Payment] Failed to get customer session:', - error.message - ) - toast(defaultErrorToast(ADDON_PURCHASE_MESSAGES.error.generic)) - }, - }) - ) - - const handleSwitchToPaymentElement = (clientSecret: string) => { - if (!team) return - setClientSecret(clientSecret) - setShowPaymentForm(true) - customerSessionMutation.mutate({ teamSlug }) - } - - const itemsQueryKey = trpc.billing.getItems.queryOptions({ - teamSlug, - }).queryKey - const teamListQueryKey = trpc.teams.list.queryOptions( - undefined, - DASHBOARD_TEAMS_LIST_QUERY_OPTIONS - ).queryKey - - const { confirmPayment, isConfirming } = usePaymentConfirmation({ - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: itemsQueryKey }) - queryClient.invalidateQueries({ queryKey: teamListQueryKey }) - onOpenChange(false) - }, - onFallbackToPaymentElement: handleSwitchToPaymentElement, - }) - - const confirmOrderMutation = useMutation( - trpc.billing.confirmOrder.mutationOptions({ - onSuccess: async (data) => { - if (data?.client_secret && !isConfirming) { - await confirmPayment(data.client_secret) - } - }, - onError: (error) => { - console.error('[Payment] Failed to confirm order:', error.message) - if ( - error.message === ADDON_PURCHASE_ACTION_ERRORS.missingPaymentMethod - ) { - toast( - defaultErrorToast( - ADDON_PURCHASE_MESSAGES.error.missingPaymentMethod - ) - ) - } else { - toast(defaultErrorToast(ADDON_PURCHASE_MESSAGES.error.generic)) - } - }, - }) - ) - - const handlePurchase = () => { - if (!team) return - - confirmOrderMutation.mutate({ teamSlug, orderId }) - } - - const limitIncreaseText = currentConcurrentSandboxesLimit ? ( - <> - Increases total concurrent sandbox limit from{' '} - {currentConcurrentSandboxesLimit.toLocaleString('en-US')} to{' '} - - {(currentConcurrentSandboxesLimit + SANDBOXES_PER_ADDON).toLocaleString( - 'en-US' - )} - - - ) : ( - <> - Increases total concurrent sandbox limit by {SANDBOXES_PER_ADDON} - - ) - - const isProcessing = confirmOrderMutation.isPending || isConfirming - - return ( - <> - -
- - +500 Sandboxes Add-on - - - {showPaymentForm && } - - {showPaymentForm && customerSessionClientSecret && clientSecret ? ( -
- -
- ) : showPaymentForm ? ( - - ) : null} - - - - {!showPaymentForm && !isProcessing ? ( - - ) : !showPaymentForm ? ( - - ) : null} -
- - ) -} - -// decorative sidebar with sandbox icon -function DialogSidebar() { - return ( -
-
- -
-
- -
-
- ) -} - -function PaymentAuthFailedAlert() { - return ( - - - - Payment authentication failed in the last attempt. Please select a new - payment method or enter the same card details again to retry. - - - ) -} - -function LoadingState({ message }: { message: string }) { - return ( -
- - {message} -
- ) -} - -interface AddonFeaturesListProps { - limitIncreaseText: React.ReactNode - monthlyPriceCents: number - amountDueCents: number -} - -function AddonFeaturesList({ - limitIncreaseText, - monthlyPriceCents, - amountDueCents, -}: AddonFeaturesListProps) { - return ( -
    -
  • - -

    {limitIncreaseText}

    -
  • - -
  • - -

    - Raises current subscription by ${monthlyPriceCents / 100}/month -

    -
  • - -
  • - -

    - Pay ${(amountDueCents / 100).toFixed(2)} now for the remaining - time of the month -

    -
  • -
- ) -} - -// wrapper for stripe elements with saved payment methods -function PaymentElementWrapper({ - clientSecret, - customerSessionClientSecret, - onOpenChange, - isLoading, -}: { - clientSecret: string - customerSessionClientSecret: string - onOpenChange: (open: boolean) => void - isLoading: boolean -}) { - const appearance = usePaymentElementAppearance() - - return ( -
- - - -
- ) -} - -// payment element form component (fallback for manual payment entry) -function PaymentElementForm({ - onOpenChange, - isLoading, -}: { - onOpenChange: (open: boolean) => void - isLoading: boolean -}) { - const stripe = useStripe() - const elements = useElements() - const { toast } = useToast() - const [isConfirmingPayment, setIsConfirmingPayment] = useState(false) - const router = useRouter() - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - - if (!stripe || !elements) { - console.error('[Payment] Stripe or elements not loaded') - toast(defaultErrorToast(ADDON_PURCHASE_MESSAGES.error.generic)) - return - } - - setIsConfirmingPayment(true) - - const { error } = await stripe.confirmPayment({ - elements, - redirect: 'if_required', - }) - - if (error) { - console.error('[Payment] Manual payment confirmation failed:', error) - toast( - defaultErrorToast( - error.message ?? ADDON_PURCHASE_MESSAGES.error.generic - ) - ) - setIsConfirmingPayment(false) - return - } - - toast({ - title: ADDON_PURCHASE_MESSAGES.success.title, - description: ADDON_PURCHASE_MESSAGES.success.description, - }) - onOpenChange(false) - setIsConfirmingPayment(false) - router.refresh() - } - - const isProcessing = isLoading || isConfirmingPayment - - return ( -
- - {!isProcessing ? ( - - ) : ( - - )} - - ) -} - -export function ConcurrentSandboxAddOnPurchaseDialog({ - open, - onOpenChange, - ...props -}: ConcurrentSandboxAddOnPurchaseDialogProps) { - return ( - - - - - - ) -} diff --git a/src/features/dashboard/billing/constants.ts b/src/features/dashboard/billing/constants.ts deleted file mode 100644 index 11d921e7e..000000000 --- a/src/features/dashboard/billing/constants.ts +++ /dev/null @@ -1,31 +0,0 @@ -export const ADDON_500_SANDBOXES_ID = 'addon_concurrency_500_sandboxes' as const -export const SANDBOXES_PER_ADDON = 500 - -export const TIER_PRO_ID = 'pro_v1' as const -export const TIER_BASE_ID = 'base_v1' as const - -export const ADDON_PURCHASE_ACTION_ERRORS = { - missingPaymentMethod: 'missing_payment_method', -} - -export const ADDON_PURCHASE_MESSAGES = { - success: { - title: 'Add-on activated', - description: - '500 additional concurrent sandboxes have been added to your subscription', - }, - loading: { - processing: 'Processing...', - loadingPaymentMethods: 'Loading your saved payment methods...', - }, - error: { - // user-actionable: card authentication (3D Secure) failed - cardAuthFailed: - 'Card authentication failed. Please try a different payment method.', - missingPaymentMethod: - 'You have no attached payment method. Please add a payment method via "Manage subscription" first.', - // generic error for all other failures (logged on our side) - generic: - 'Something went wrong. Please try again or contact support if the issue persists.', - }, -} as const diff --git a/src/features/dashboard/billing/credits.tsx b/src/features/dashboard/billing/credits.tsx deleted file mode 100644 index cd162f8b5..000000000 --- a/src/features/dashboard/billing/credits.tsx +++ /dev/null @@ -1,33 +0,0 @@ -'use client' - -import { formatCurrency } from '@/lib/utils/formatting' -import { Label } from '@/ui/primitives/label' -import { Separator } from '@/ui/primitives/separator' -import { Skeleton } from '@/ui/primitives/skeleton' -import { useUsage } from './hooks' - -export default function Credits() { - const { credits, isLoading } = useUsage() - - return ( -
- -
- -
- {isLoading ? ( - - ) : ( - - {formatCurrency(credits ?? 0)} - - )} -
-

- Automatically applied to invoices. Subscription costs excluded. -

-
- -
- ) -} diff --git a/src/features/dashboard/billing/hooks.ts b/src/features/dashboard/billing/hooks.ts deleted file mode 100644 index 8fb42f41d..000000000 --- a/src/features/dashboard/billing/hooks.ts +++ /dev/null @@ -1,335 +0,0 @@ -'use client' - -import { loadStripe } from '@stripe/stripe-js' -import { useQuery } from '@tanstack/react-query' -import { useTheme } from 'next-themes' -import { useState } from 'react' -import { useRouteParams } from '@/lib/hooks/use-route-params' -import { defaultErrorToast, useToast } from '@/lib/hooks/use-toast' -import { useTRPC } from '@/trpc/client' -import { ADDON_PURCHASE_MESSAGES } from './constants' -import { extractAddonData, extractTierData } from './utils' - -const stripePromise = loadStripe( - process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY! -) - -/** - * Provides themed appearance configuration for Stripe Payment Element - * Matches design system: bg-highlight, stroke, stroke-active, fg, fg-tertiary - */ -export function usePaymentElementAppearance() { - const { resolvedTheme } = useTheme() - const isDark = resolvedTheme === 'dark' - - return { - theme: 'flat' as const, - variables: { - // accent colors - colorPrimary: isDark ? '#ff8800' : '#e56f00', - colorDanger: isDark ? '#f54545' : '#ff4400', - colorSuccess: isDark ? '#00d992' : '#00a670', - - // backgrounds - colorBackground: isDark ? '#141414' : '#f5f5f5', // bg-1 - - // text - colorText: isDark ? '#ffffff' : '#0a0a0a', // fg - colorTextSecondary: isDark ? '#e6e6e6' : '#333333', // fg-secondary - colorTextPlaceholder: isDark ? '#848484' : '#707070', // fg-tertiary - - // icons - colorIconCardError: isDark ? '#f54545' : '#ff4400', - - // typography - fontFamily: - '"IBM Plex Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', - fontSizeBase: '14px', - fontLineHeight: '20px', - fontWeightNormal: '400', - fontWeightLight: '400', - - // borders - borderRadius: '6px', - spacingUnit: '4px', - }, - rules: { - '.Input': { - backgroundColor: isDark ? '#1f1f1f' : '#f2f2f2', // bg-highlight - border: isDark ? '1px solid #292929' : '1px solid #d6d6d6', // stroke - padding: '10px 12px', - boxShadow: 'none', - transition: 'border-color 150ms ease', - }, - '.Input:hover': { - backgroundColor: isDark ? '#1f1f1f' : '#f2f2f2', - border: isDark ? '1px solid #383838' : '1px solid #c2c2c2', - }, - '.Input:focus': { - backgroundColor: isDark ? '#1f1f1f' : '#f2f2f2', - border: isDark ? '1px solid #424242' : '1px solid #c2c2c2', // stroke-active - boxShadow: 'none', - outline: 'none', - }, - '.Input::placeholder': { - color: isDark ? '#848484' : '#707070', // fg-tertiary - }, - '.Label': { - fontSize: '12px', - fontWeight: '400', - color: isDark ? '#e6e6e6' : '#333333', // fg-secondary - marginBottom: '6px', - textTransform: 'none', - }, - '.Tab': { - backgroundColor: isDark ? '#141414' : '#f5f5f5', // bg-1 - border: isDark ? '1px solid #292929' : '1px solid #d6d6d6', // stroke - boxShadow: 'none', - }, - '.Tab:hover': { - backgroundColor: isDark ? '#1f1f1f' : '#ebebeb', - border: isDark ? '1px solid #383838' : '1px solid #c2c2c2', - }, - '.Tab--selected': { - backgroundColor: isDark ? '#1f1f1f' : '#f2f2f2', // bg-highlight - border: isDark ? '1px solid #424242' : '1px solid #c2c2c2', // stroke-active - boxShadow: 'none', - }, - '.TabLabel': { - fontWeight: '400', - color: isDark ? '#ffffff' : '#0a0a0a', - }, - '.TabIcon': { - fill: isDark ? '#848484' : '#707070', - }, - '.TabIcon--selected': { - fill: isDark ? '#ffffff' : '#0a0a0a', - }, - '.Block': { - backgroundColor: isDark ? '#141414' : '#f5f5f5', // bg-1 - border: isDark ? '1px solid #292929' : '1px solid #d6d6d6', // stroke - }, - '.PickerItem': { - backgroundColor: isDark ? '#141414' : '#f5f5f5', - border: isDark ? '1px solid #292929' : '1px solid #d6d6d6', - padding: '10px 12px', - }, - '.PickerItem:hover': { - backgroundColor: isDark ? '#1f1f1f' : '#ebebeb', - border: isDark ? '1px solid #383838' : '1px solid #c2c2c2', - }, - '.PickerItem--selected': { - backgroundColor: isDark ? '#1f1f1f' : '#f2f2f2', - border: isDark ? '1px solid #424242' : '1px solid #c2c2c2', - }, - '.PickerItem--selected:hover': { - backgroundColor: isDark ? '#1f1f1f' : '#f2f2f2', - }, - '.AccordionButton': { - backgroundColor: isDark ? '#141414' : '#f5f5f5', - border: isDark ? '1px solid #292929' : '1px solid #d6d6d6', - }, - '.AccordionButton:hover': { - backgroundColor: isDark ? '#1f1f1f' : '#ebebeb', - border: isDark ? '1px solid #383838' : '1px solid #c2c2c2', - }, - '.AccordionButton--selected': { - backgroundColor: isDark ? '#1f1f1f' : '#f2f2f2', - border: isDark ? '1px solid #424242' : '1px solid #c2c2c2', - }, - '.AccordionButton--selected:hover': { - backgroundColor: isDark ? '#1f1f1f' : '#f2f2f2', - }, - '.CheckboxInput': { - backgroundColor: isDark ? '#1f1f1f' : '#ffffff', - border: isDark ? '1px solid #424242' : '1px solid #707070', - boxShadow: 'none', - }, - '.CheckboxInput:hover': { - border: isDark ? '1px solid #848484' : '1px solid #333333', - }, - '.CheckboxInput--checked': { - backgroundColor: isDark ? '#ff8800' : '#e56f00', - border: isDark ? '1px solid #ff8800' : '1px solid #e56f00', - }, - '.CheckboxLabel': { - color: isDark ? '#e6e6e6' : '#333333', - }, - '.Spinner': { - color: isDark ? '#ff8800' : '#e56f00', // accent-main-highlight - borderColor: isDark - ? 'rgba(255, 136, 0, 0.3)' - : 'rgba(229, 111, 0, 0.3)', - }, - '.RedirectText': { - color: isDark ? '#e6e6e6' : '#333333', // fg-secondary - }, - }, - } -} - -interface UsePaymentConfirmationOptions { - onSuccess: () => void - onFallbackToPaymentElement: (clientSecret: string) => void -} - -/** - * Handles the payment confirmation flow with fallback to manual payment entry - */ -export function usePaymentConfirmation({ - onSuccess, - onFallbackToPaymentElement, -}: UsePaymentConfirmationOptions) { - const { toast } = useToast() - const [isConfirming, setIsConfirming] = useState(false) - - const confirmPayment = async (clientSecret: string) => { - setIsConfirming(true) - - try { - const stripe = await stripePromise - - if (!stripe) { - console.error('[Payment] Stripe failed to load') - toast(defaultErrorToast(ADDON_PURCHASE_MESSAGES.error.generic)) - setIsConfirming(false) - return - } - - // retrieve payment intent to check status - const { paymentIntent, error: retrieveError } = - await stripe.retrievePaymentIntent(clientSecret) - - if (retrieveError) { - console.error( - '[Payment] Failed to retrieve payment intent:', - retrieveError - ) - toast(defaultErrorToast(ADDON_PURCHASE_MESSAGES.error.generic)) - onFallbackToPaymentElement(clientSecret) - setIsConfirming(false) - return - } - - // fallback if no payment method attached - if (paymentIntent.status === 'requires_payment_method') { - onFallbackToPaymentElement(clientSecret) - setIsConfirming(false) - return - } - - // handle 3D Secure or other authentication - if (paymentIntent.status === 'requires_action') { - const { error: actionError } = await stripe.handleNextAction({ - clientSecret, - }) - - if (actionError) { - console.error('[Payment] Card authentication failed:', actionError) - toast(defaultErrorToast(ADDON_PURCHASE_MESSAGES.error.cardAuthFailed)) - onFallbackToPaymentElement(clientSecret) - setIsConfirming(false) - return - } - - // verify final status after authentication - const { paymentIntent: finalIntent, error: finalError } = - await stripe.retrievePaymentIntent(clientSecret) - - if (finalError || finalIntent.status !== 'succeeded') { - console.error( - '[Payment] Final payment check failed:', - finalError?.message, - finalIntent?.status - ) - toast(defaultErrorToast(ADDON_PURCHASE_MESSAGES.error.generic)) - setIsConfirming(false) - return - } - } else if (paymentIntent.status !== 'succeeded') { - console.error( - '[Payment] Unexpected payment status:', - paymentIntent?.status - ) - toast(defaultErrorToast(ADDON_PURCHASE_MESSAGES.error.generic)) - setIsConfirming(false) - return - } - - // success - toast({ - title: ADDON_PURCHASE_MESSAGES.success.title, - description: ADDON_PURCHASE_MESSAGES.success.description, - }) - onSuccess() - setIsConfirming(false) - } catch (err) { - console.error( - '[Payment] Unexpected error during payment confirmation:', - err - ) - toast(defaultErrorToast(ADDON_PURCHASE_MESSAGES.error.generic)) - setIsConfirming(false) - } - } - - return { confirmPayment, isConfirming } -} - -export { stripePromise } - -export function useBillingItems() { - const { teamSlug } = useRouteParams<'/dashboard/[teamSlug]/billing'>() - const trpc = useTRPC() - - const { data: items, isLoading } = useQuery({ - ...trpc.billing.getItems.queryOptions({ teamSlug }), - throwOnError: true, - }) - - const tierData = items ? extractTierData(items) : undefined - const addonData = - items && tierData - ? extractAddonData(items, tierData.selected?.id) - : undefined - - return { - items, - tierData, - addonData, - isLoading, - } -} - -export function useUsage() { - const { teamSlug } = useRouteParams<'/dashboard/[teamSlug]/billing'>() - const trpc = useTRPC() - - const { data: usage, isLoading } = useQuery({ - ...trpc.billing.getUsage.queryOptions({ teamSlug }), - throwOnError: true, - }) - - return { - usage, - credits: usage?.credits, - isLoading, - } -} - -export function useInvoices() { - const { teamSlug } = useRouteParams<'/dashboard/[teamSlug]/billing'>() - const trpc = useTRPC() - - const { - data: invoices, - isLoading, - error, - } = useQuery(trpc.billing.getInvoices.queryOptions({ teamSlug })) - - return { - invoices, - isLoading, - error, - } -} diff --git a/src/features/dashboard/billing/invoices.tsx b/src/features/dashboard/billing/invoices.tsx deleted file mode 100644 index 7c3b9ae5c..000000000 --- a/src/features/dashboard/billing/invoices.tsx +++ /dev/null @@ -1,128 +0,0 @@ -'use client' - -import Link from 'next/link' -import { useTimezone } from '@/features/dashboard/timezone' -import { cn } from '@/lib/utils' -import { formatCurrency, formatDate } from '@/lib/utils/formatting' -import { Badge } from '@/ui/primitives/badge' -import { Button } from '@/ui/primitives/button' -import { ArrowDownIcon, ExternalLinkIcon } from '@/ui/primitives/icons' -import { InvoiceIcon } from '@/ui/primitives/icons-legacy' -import { Label } from '@/ui/primitives/label' -import { - Table, - TableBody, - TableCell, - TableEmptyState, - TableHead, - TableHeader, - TableLoadingState, - TableRow, -} from '@/ui/primitives/table' -import { useInvoices } from './hooks' - -const COLUMN_WIDTHS = { - date: 120, - status: 96, - actions: 80, -} as const - -function colStyle(width: number) { - return { width, minWidth: width, maxWidth: width } -} - -interface InvoicesEmptyProps { - error?: string -} - -function InvoicesEmpty({ error }: InvoicesEmptyProps) { - return ( - - - - {error ? error : 'No invoices yet'} - - - ) -} - -export default function BillingInvoicesTable() { - const { timezone } = useTimezone() - const { invoices, isLoading, error } = useInvoices() - - const hasData = invoices && invoices.length > 0 - const showLoader = isLoading && !hasData - const showEmpty = !isLoading && !hasData - - return ( -
- - - - - - - - - - - - - - - Date - - - - Status - Amount - Actions - - - - {showLoader && ( - - )} - {showEmpty && } - - {hasData && - invoices.map((invoice) => ( - - - {formatDate(invoice.date_created, { timezone }) ?? '—'} - - - - {invoice.paid ? '✓ Paid' : 'Pending'} - - - - {invoice.credits_used > 0 && ( - - {formatCurrency(invoice.cost + invoice.credits_used)} - - )} - {formatCurrency(invoice.cost)} - - - - - - ))} - -
-
- ) -} diff --git a/src/features/dashboard/billing/select-plan.tsx b/src/features/dashboard/billing/select-plan.tsx deleted file mode 100644 index 5fd02aafa..000000000 --- a/src/features/dashboard/billing/select-plan.tsx +++ /dev/null @@ -1,314 +0,0 @@ -'use client' - -import { useMutation } from '@tanstack/react-query' -import { useRouter } from 'next/navigation' -import type { TierInfo } from '@/core/modules/billing/models' -import { getTeamDisplayName } from '@/core/modules/teams/utils' -import { useRouteParams } from '@/lib/hooks/use-route-params' -import { defaultErrorToast, useToast } from '@/lib/hooks/use-toast' -import { formatCurrency } from '@/lib/utils/formatting' -import { useTRPC } from '@/trpc/client' -import { Badge } from '@/ui/primitives/badge' -import { Button } from '@/ui/primitives/button' -import { - BuildIcon, - GaugeIcon, - SandboxIcon, - SupportIcon, - TimeIcon, - UpgradeIcon, -} from '@/ui/primitives/icons' -import { Separator } from '@/ui/primitives/separator' -import { Skeleton } from '@/ui/primitives/skeleton' -import { useDashboard } from '../context' -import { TIER_BASE_ID, TIER_PRO_ID } from './constants' -import { useBillingItems } from './hooks' -import { TierAvatarBorder } from './tier-avatar-border' -import { formatHours, formatMibToGb, formatTierDisplayName } from './utils' - -interface PlanFeature { - icon: React.ReactNode - label: React.ReactNode -} - -function getHobbyFeatures(tier?: TierInfo): PlanFeature[] { - const limits = tier?.limits - if (!limits) return [] - - return [ - { - icon: , - label: ( - <> - {limits.sandbox_concurrency}{' '} - concurrent sandboxes - - ), - }, - { - icon: , - label: ( - <> - - {formatHours(limits.max_sandbox_duration_hours)} - {' '} - session length - - ), - }, - { - icon: , - label: ( - <> - {formatMibToGb(limits.disk_size_mib)}{' '} - disk per sandbox - - ), - }, - { - icon: , - label: 'Community support', - }, - ] -} - -function getProFeatures(tier?: TierInfo): PlanFeature[] { - const limits = tier?.limits - if (!limits) return [] - - return [ - { - icon: , - label: ( - <> - {limits.sandbox_concurrency}{' '} - concurrent sandboxes - - ), - }, - { - icon: , - label: ( - <> - - {formatHours(limits.max_sandbox_duration_hours)} - {' '} - session length - - ), - }, - { - icon: , - label: 'Add-on support', - }, - { - icon: , - label: ( - <> - {formatMibToGb(limits.disk_size_mib)}{' '} - disk per sandbox - - ), - }, - ] -} - -interface PlanCardProps { - tier?: TierInfo - isCurrentPlan: boolean - isBaseTier: boolean - features: PlanFeature[] - isLoading?: boolean - onSelectPlan: () => void - isSelectingPlan: boolean -} - -function PlanCardSkeleton() { - return ( -
- -
-
- - -
- -
- {Array.from({ length: 4 }).map((_, i) => ( -
- - -
- ))} -
-
-
- ) -} - -function PlanCard({ - tier, - isCurrentPlan, - isBaseTier, - features, - isLoading, - onSelectPlan, - isSelectingPlan, -}: PlanCardProps) { - const { team } = useDashboard() - - if (isLoading) { - return - } - - const icon = isBaseTier ? ( - - ) : ( - - ) - - const displayName = tier - ? formatTierDisplayName(tier.name) - : isBaseTier - ? 'Hobby' - : 'Professional' - const priceDisplay = tier?.price_cents - ? formatCurrency(tier.price_cents / 100) - : 'FREE' - - const teamDisplayName = getTeamDisplayName(team) - - const buttonVariant = isBaseTier ? 'secondary' : 'primary' - const buttonText = isBaseTier ? 'Downgrade' : 'Upgrade' - - return ( -
-
- {icon} - -
- -
-
-
- - {displayName} - - {priceDisplay} -
- - {isCurrentPlan ? ( - - ) : ( -
- - Changes apply to:{' '} - {teamDisplayName} - - -
- )} -
- - - -
- {features.map((feature, index) => ( -
- {feature.icon} - {feature.label} -
- ))} -
-
-
- ) -} - -export default function SelectPlan() { - const { toast } = useToast() - const { tierData, isLoading } = useBillingItems() - const { teamSlug } = - useRouteParams<'/dashboard/[teamSlug]/billing/plan/select'>() - const router = useRouter() - const trpc = useTRPC() - - const { mutate: createCheckout, isPending: isCheckoutLoading } = useMutation( - trpc.billing.createCheckout.mutationOptions({ - onSuccess: (data) => { - router.push(data.url) - }, - onError: (error) => { - toast( - defaultErrorToast(error.message ?? 'Failed to redirect to checkout') - ) - }, - }) - ) - - const { mutate: openCustomerPortal, isPending: isPortalLoading } = - useMutation( - trpc.billing.createCustomerPortalSession.mutationOptions({ - onSuccess: (data) => { - router.push(data.url) - }, - onError: (error) => { - toast( - defaultErrorToast(error.message ?? 'Failed to open customer portal') - ) - }, - }) - ) - - const handleUpgrade = (tierId: string) => { - createCheckout({ teamSlug, tierId }) - } - - const handleDowngrade = () => { - openCustomerPortal({ teamSlug }) - } - - const baseTier = tierData?.base - const proTier = tierData?.pro - const selectedTierId = tierData?.selected?.id - - const isOnBaseTier = selectedTierId === TIER_BASE_ID - const isOnProTier = selectedTierId === TIER_PRO_ID - - const hobbyFeatures = getHobbyFeatures(baseTier) - const proFeatures = getProFeatures(proTier) - - return ( -
- - proTier?.id && handleUpgrade(proTier.id)} - isSelectingPlan={isCheckoutLoading} - /> -
- ) -} diff --git a/src/features/dashboard/billing/selected-plan.tsx b/src/features/dashboard/billing/selected-plan.tsx deleted file mode 100644 index fad5fc171..000000000 --- a/src/features/dashboard/billing/selected-plan.tsx +++ /dev/null @@ -1,250 +0,0 @@ -'use client' - -import { useMutation } from '@tanstack/react-query' -import Link from 'next/link' -import { usePathname, useRouter } from 'next/navigation' -import { PROTECTED_URLS } from '@/configs/urls' -import type { TeamLimits } from '@/core/modules/teams/models' -import { useRouteParams } from '@/lib/hooks/use-route-params' -import { defaultErrorToast, useToast } from '@/lib/hooks/use-toast' -import { formatCurrency } from '@/lib/utils/formatting' -import { useTRPC } from '@/trpc/client' -import { Badge } from '@/ui/primitives/badge' -import { Button } from '@/ui/primitives/button' -import { - BuildIcon, - CpuIcon, - GaugeIcon, - MemoryIcon, - SandboxIcon, - TimeIcon, - UpgradeIcon, -} from '@/ui/primitives/icons' -import { Label } from '@/ui/primitives/label' -import { Separator } from '@/ui/primitives/separator' -import { Skeleton } from '@/ui/primitives/skeleton' -import { useDashboard } from '../context' -import { useBillingItems } from './hooks' -import { TierAvatarBorder } from './tier-avatar-border' -import type { BillingTierData } from './types' -import { formatHours, formatMibToGb, formatTierDisplayName } from './utils' - -function formatCpu(vcpu: number): string { - return `${vcpu} vCPU` -} - -export default function SelectedPlan() { - const { tierData, isLoading: isBillingLoading } = useBillingItems() - const { team } = useDashboard() - const isLoading = isBillingLoading - - return ( -
- - -
- ) -} - -interface PlanAvatarProps { - selectedTier: BillingTierData['selected'] - isLoading: boolean -} - -function PlanAvatar({ selectedTier, isLoading }: PlanAvatarProps) { - const isBaseTier = !selectedTier || selectedTier.id.includes('base') - - const icon = isBaseTier ? ( - - ) : ( - - ) - - if (isLoading) { - return - } - - return ( -
- {icon} - -
- ) -} - -interface PlanDetailsProps { - selectedTier: BillingTierData['selected'] - teamLimits: TeamLimits | null | undefined - isLoading: boolean -} - -function PlanDetails({ - selectedTier, - teamLimits, - isLoading, -}: PlanDetailsProps) { - const isBaseTier = !selectedTier || selectedTier.id.includes('base') - const { teamSlug } = useRouteParams<'/dashboard/[teamSlug]/billing'>() - const pathname = usePathname() - const router = useRouter() - const { toast } = useToast() - const trpc = useTRPC() - const isOnPlanPage = pathname.endsWith('/plan') - - const { mutate: openCustomerPortal, isPending: isPortalLoading } = - useMutation( - trpc.billing.createCustomerPortalSession.mutationOptions({ - onSuccess: (data) => { - router.push(data.url) - }, - onError: (error) => { - toast( - defaultErrorToast(error.message ?? 'Failed to open customer portal') - ) - }, - }) - ) - - const handleManagePayment = () => { - openCustomerPortal({ teamSlug }) - } - - return ( -
-
- - -
- {isOnPlanPage ? ( - - ) : isLoading ? ( - - ) : ( - <> - {isBaseTier ? ( - - ) : ( - - )} - - - )} -
-
- - - - -
- ) -} - -interface PlanTitleProps { - selectedTier: BillingTierData['selected'] - isLoading: boolean -} - -function PlanTitle({ selectedTier, isLoading }: PlanTitleProps) { - return ( -
- - {isLoading ? ( - - ) : ( -
- - {selectedTier ? formatTierDisplayName(selectedTier.name) : null} - - - {selectedTier?.price_cents - ? `${formatCurrency(selectedTier.price_cents / 100)}/mo` - : 'FREE'} - -
- )} -
- ) -} - -interface PlanFeaturesProps { - teamLimits: TeamLimits | null | undefined - isLoading: boolean -} - -function PlanFeatures({ teamLimits, isLoading }: PlanFeaturesProps) { - if (isLoading) { - return ( -
- {Array.from({ length: 4 }).map((_, i) => ( -
- - -
- ))} -
- ) - } - - if (!teamLimits) return null - - const features = [ - { - icon: , - label: `${teamLimits.concurrentSandboxes} concurrent sandboxes`, - }, - { - icon: , - label: `${formatHours(teamLimits.maxLengthHours)} session length`, - }, - { - icon: , - label: `${formatMibToGb(teamLimits.diskMb)} disk per sandbox`, - }, - { - icon: , - label: `${formatCpu(teamLimits.maxVcpu)} max`, - }, - { - icon: , - label: `${formatMibToGb(teamLimits.maxRamMb)} RAM max`, - }, - ] - - return ( -
- {features.map((feature) => ( -
- {feature.icon} - {feature.label} -
- ))} -
- ) -} diff --git a/src/features/dashboard/billing/tier-avatar-border.tsx b/src/features/dashboard/billing/tier-avatar-border.tsx deleted file mode 100644 index d09a1d052..000000000 --- a/src/features/dashboard/billing/tier-avatar-border.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { cn } from '@/lib/utils' - -interface TierAvatarBorderProps { - className?: string -} - -// decorative dot grid pattern path data -const PATTERN_PATH = `M133.944 -0.800001C133.944 -1.344 133.76 -1.8 133.392 -2.168C133.008 -2.552 132.544 -2.744 132 -2.744C131.456 -2.744 131 -2.552 130.632 -2.168C130.248 -1.8 130.056 -1.344 130.056 -0.800001C130.056 -0.256001 130.248 0.207999 130.632 0.592C131 0.96 131.456 1.144 132 1.144C132.544 1.144 133.008 0.96 133.392 0.592C133.76 0.207999 133.944 -0.256001 133.944 -0.800001ZM138.744 -0.800001C138.744 -1.344 138.56 -1.8 138.192 -2.168C137.808 -2.552 137.344 -2.744 136.8 -2.744C136.256 -2.744 135.8 -2.552 135.432 -2.168C135.048 -1.8 134.856 -1.344 134.856 -0.800001C134.856 -0.256001 135.048 0.207999 135.432 0.592C135.8 0.96 136.256 1.144 136.8 1.144C137.344 1.144 137.808 0.96 138.192 0.592C138.56 0.207999 138.744 -0.256001 138.744 -0.800001ZM143.544 -0.800001C143.544 -1.344 143.36 -1.8 142.992 -2.168C142.608 -2.552 142.144 -2.744 141.6 -2.744C141.056 -2.744 140.6 -2.552 140.232 -2.168C139.848 -1.8 139.656 -1.344 139.656 -0.800001C139.656 -0.256001 139.848 0.207999 140.232 0.592C140.6 0.96 141.056 1.144 141.6 1.144C142.144 1.144 142.608 0.96 142.992 0.592C143.36 0.207999 143.544 -0.256001 143.544 -0.800001ZM135.696 1.432C135.696 1.064 135.576 0.759999 135.336 0.519999C135.08 0.263999 134.768 0.135998 134.4 0.135998C134.032 0.135998 133.728 0.263999 133.488 0.519999C133.232 0.759999 133.104 1.064 133.104 1.432C133.104 1.8 133.232 2.112 133.488 2.368C133.728 2.608 134.032 2.728 134.4 2.728C134.768 2.728 135.08 2.608 135.336 2.368C135.576 2.112 135.696 1.8 135.696 1.432ZM140.496 1.432C140.496 1.064 140.376 0.759999 140.136 0.519999C139.88 0.263999 139.568 0.135998 139.2 0.135998C138.832 0.135998 138.528 0.263999 138.288 0.519999C138.032 0.759999 137.904 1.064 137.904 1.432C137.904 1.8 138.032 2.112 138.288 2.368C138.528 2.608 138.832 2.728 139.2 2.728C139.568 2.728 139.88 2.608 140.136 2.368C140.376 2.112 140.496 1.8 140.496 1.432ZM145.296 1.432C145.296 1.064 145.168 0.759999 144.912 0.519999C144.672 0.263999 144.368 0.135998 144 0.135998C143.632 0.135998 143.328 0.263999 143.088 0.519999C142.832 0.759999 142.704 1.064 142.704 1.432C142.704 1.8 142.832 2.112 143.088 2.368C143.328 2.608 143.632 2.728 144 2.728C144.368 2.728 144.672 2.608 144.912 2.368C145.168 2.112 145.296 1.8 145.296 1.432ZM133.944 3.664C133.944 3.12 133.76 2.664 133.392 2.296C133.008 1.912 132.544 1.72 132 1.72C131.456 1.72 131 1.912 130.632 2.296C130.248 2.664 130.056 3.12 130.056 3.664C130.056 4.208 130.248 4.672 130.632 5.056C131 5.424 131.456 5.608 132 5.608C132.544 5.608 133.008 5.424 133.392 5.056C133.76 4.672 133.944 4.208 133.944 3.664ZM143.544 3.664C143.544 3.12 143.36 2.664 142.992 2.296C142.608 1.912 142.144 1.72 141.6 1.72C141.056 1.72 140.6 1.912 140.232 2.296C139.848 2.664 139.656 3.12 139.656 3.664C139.656 4.208 139.848 4.672 140.232 5.056C140.6 5.424 141.056 5.608 141.6 5.608C142.144 5.608 142.608 5.424 142.992 5.056C143.36 4.672 143.544 4.208 143.544 3.664ZM138.744 3.664C138.744 3.12 138.56 2.664 138.192 2.296C137.808 1.912 137.344 1.72 136.8 1.72C136.256 1.72 135.8 1.912 135.432 2.296C135.048 2.664 134.856 3.12 134.856 3.664C134.856 4.208 135.048 4.672 135.432 5.056C135.8 5.424 136.256 5.608 136.8 5.608C137.344 5.608 137.808 5.424 138.192 5.056C138.56 4.672 138.744 4.208 138.744 3.664ZM135.696 5.896C135.696 5.528 135.576 5.224 135.336 4.984C135.08 4.728 134.768 4.6 134.4 4.6C134.032 4.6 133.728 4.728 133.488 4.984C133.232 5.224 133.104 5.528 133.104 5.896C133.104 6.264 133.232 6.576 133.488 6.832C133.728 7.072 134.032 7.192 134.4 7.192C134.768 7.192 135.08 7.072 135.336 6.832C135.576 6.576 135.696 6.264 135.696 5.896ZM145.296 5.896C145.296 5.528 145.168 5.224 144.912 4.984C144.672 4.728 144.368 4.6 144 4.6C143.632 4.6 143.328 4.728 143.088 4.984C142.832 5.224 142.704 5.528 142.704 5.896C142.704 6.264 142.832 6.576 143.088 6.832C143.328 7.072 143.632 7.192 144 7.192C144.368 7.192 144.672 7.072 144.912 6.832C145.168 6.576 145.296 6.264 145.296 5.896ZM140.496 5.896C140.496 5.528 140.376 5.224 140.136 4.984C139.88 4.728 139.568 4.6 139.2 4.6C138.832 4.6 138.528 4.728 138.288 4.984C138.032 5.224 137.904 5.528 137.904 5.896C137.904 6.264 138.032 6.576 138.288 6.832C138.528 7.072 138.832 7.192 139.2 7.192C139.568 7.192 139.88 7.072 140.136 6.832C140.376 6.576 140.496 6.264 140.496 5.896ZM133.944 8.128C133.944 7.584 133.76 7.128 133.392 6.76C133.008 6.376 132.544 6.184 132 6.184C131.456 6.184 131 6.376 130.632 6.76C130.248 7.128 130.056 7.584 130.056 8.128C130.056 8.672 130.248 9.136 130.632 9.52C131 9.888 131.456 10.072 132 10.072C132.544 10.072 133.008 9.888 133.392 9.52C133.76 9.136 133.944 8.672 133.944 8.128ZM143.544 8.128C143.544 7.584 143.36 7.128 142.992 6.76C142.608 6.376 142.144 6.184 141.6 6.184C141.056 6.184 140.6 6.376 140.232 6.76C139.848 7.128 139.656 7.584 139.656 8.128C139.656 8.672 139.848 9.136 140.232 9.52C140.6 9.888 141.056 10.072 141.6 10.072C142.144 10.072 142.608 9.888 142.992 9.52C143.36 9.136 143.544 8.672 143.544 8.128ZM138.744 8.128C138.744 7.584 138.56 7.128 138.192 6.76C137.808 6.376 137.344 6.184 136.8 6.184C136.256 6.184 135.8 6.376 135.432 6.76C135.048 7.128 134.856 7.584 134.856 8.128C134.856 8.672 135.048 9.136 135.432 9.52C135.8 9.888 136.256 10.072 136.8 10.072C137.344 10.072 137.808 9.888 138.192 9.52C138.56 9.136 138.744 8.672 138.744 8.128ZM135.696 10.36C135.696 9.992 135.576 9.688 135.336 9.448C135.08 9.192 134.768 9.064 134.4 9.064C134.032 9.064 133.728 9.192 133.488 9.448C133.232 9.688 133.104 9.992 133.104 10.36C133.104 10.728 133.232 11.04 133.488 11.296C133.728 11.536 134.032 11.656 134.4 11.656C134.768 11.656 135.08 11.536 135.336 11.296C135.576 11.04 135.696 10.728 135.696 10.36ZM145.296 10.36C145.296 9.992 145.168 9.688 144.912 9.448C144.672 9.192 144.368 9.064 144 9.064C143.632 9.064 143.328 9.192 143.088 9.448C142.832 9.688 142.704 9.992 142.704 10.36C142.704 10.728 142.832 11.04 143.088 11.296C143.328 11.536 143.632 11.656 144 11.656C144.368 11.656 144.672 11.536 144.912 11.296C145.168 11.04 145.296 10.728 145.296 10.36ZM140.496 10.36C140.496 9.992 140.376 9.688 140.136 9.448C139.88 9.192 139.568 9.064 139.2 9.064C138.832 9.064 138.528 9.192 138.288 9.448C138.032 9.688 137.904 9.992 137.904 10.36C137.904 10.728 138.032 11.04 138.288 11.296C138.528 11.536 138.832 11.656 139.2 11.656C139.568 11.656 139.88 11.536 140.136 11.296C140.376 11.04 140.496 10.728 140.496 10.36ZM138.744 12.592C138.744 12.048 138.56 11.592 138.192 11.224C137.808 10.84 137.344 10.648 136.8 10.648C136.256 10.648 135.8 10.84 135.432 11.224C135.048 11.592 134.856 12.048 134.856 12.592C134.856 13.136 135.048 13.6 135.432 13.984C135.8 14.352 136.256 14.536 136.8 14.536C137.344 14.536 137.808 14.352 138.192 13.984C138.56 13.6 138.744 13.136 138.744 12.592ZM143.544 12.592C143.544 12.048 143.36 11.592 142.992 11.224C142.608 10.84 142.144 10.648 141.6 10.648C141.056 10.648 140.6 10.84 140.232 11.224C139.848 11.592 139.656 12.048 139.656 12.592C139.656 13.136 139.848 13.6 140.232 13.984C140.6 14.352 141.056 14.536 141.6 14.536C142.144 14.536 142.608 14.352 142.992 13.984C143.36 13.6 143.544 13.136 143.544 12.592ZM133.944 12.592C133.944 12.048 133.76 11.592 133.392 11.224C133.008 10.84 132.544 10.648 132 10.648C131.456 10.648 131 10.84 130.632 11.224C130.248 11.592 130.056 12.048 130.056 12.592C130.056 13.136 130.248 13.6 130.632 13.984C131 14.352 131.456 14.536 132 14.536C132.544 14.536 133.008 14.352 133.392 13.984C133.76 13.6 133.944 13.136 133.944 12.592ZM140.496 14.824C140.496 14.456 140.376 14.152 140.136 13.912C139.88 13.656 139.568 13.528 139.2 13.528C138.832 13.528 138.528 13.656 138.288 13.912C138.032 14.152 137.904 14.456 137.904 14.824C137.904 15.192 138.032 15.504 138.288 15.76C138.528 16 138.832 16.12 139.2 16.12C139.568 16.12 139.88 16 140.136 15.76C140.376 15.504 140.496 15.192 140.496 14.824ZM145.296 14.824C145.296 14.456 145.168 14.152 144.912 13.912C144.672 13.656 144.368 13.528 144 13.528C143.632 13.528 143.328 13.656 143.088 13.912C142.832 14.152 142.704 14.456 142.704 14.824C142.704 15.192 142.832 15.504 143.088 15.76C143.328 16 143.632 16.12 144 16.12C144.368 16.12 144.672 16 144.912 15.76C145.168 15.504 145.296 15.192 145.296 14.824ZM135.696 14.824C135.696 14.456 135.576 14.152 135.336 13.912C135.08 13.656 134.768 13.528 134.4 13.528C134.032 13.528 133.728 13.656 133.488 13.912C133.232 14.152 133.104 14.456 133.104 14.824C133.104 15.192 133.232 15.504 133.488 15.76C133.728 16 134.032 16.12 134.4 16.12C134.768 16.12 135.08 16 135.336 15.76C135.576 15.504 135.696 15.192 135.696 14.824ZM138.744 17.056C138.744 16.512 138.56 16.056 138.192 15.688C137.808 15.304 137.344 15.112 136.8 15.112C136.256 15.112 135.8 15.304 135.432 15.688C135.048 16.056 134.856 16.512 134.856 17.056C134.856 17.6 135.048 18.064 135.432 18.448C135.8 18.816 136.256 19 136.8 19C137.344 19 137.808 18.816 138.192 18.448C138.56 18.064 138.744 17.6 138.744 17.056ZM133.944 17.056C133.944 16.512 133.76 16.056 133.392 15.688C133.008 15.304 132.544 15.112 132 15.112C131.456 15.112 131 15.304 130.632 15.688C130.248 16.056 130.056 16.512 130.056 17.056C130.056 17.6 130.248 18.064 130.632 18.448C131 18.816 131.456 19 132 19C132.544 19 133.008 18.816 133.392 18.448C133.76 18.064 133.944 17.6 133.944 17.056ZM143.544 17.056C143.544 16.512 143.36 16.056 142.992 15.688C142.608 15.304 142.144 15.112 141.6 15.112C141.056 15.112 140.6 15.304 140.232 15.688C139.848 16.056 139.656 16.512 139.656 17.056C139.656 17.6 139.848 18.064 140.232 18.448C140.6 18.816 141.056 19 141.6 19C142.144 19 142.608 18.816 142.992 18.448C143.36 18.064 143.544 17.6 143.544 17.056ZM140.496 19.288C140.496 18.92 140.376 18.616 140.136 18.376C139.88 18.12 139.568 17.992 139.2 17.992C138.832 17.992 138.528 18.12 138.288 18.376C138.032 18.616 137.904 18.92 137.904 19.288C137.904 19.656 138.032 19.968 138.288 20.224C138.528 20.464 138.832 20.584 139.2 20.584C139.568 20.584 139.88 20.464 140.136 20.224C140.376 19.968 140.496 19.656 140.496 19.288ZM135.696 19.288C135.696 18.92 135.576 18.616 135.336 18.376C135.08 18.12 134.768 17.992 134.4 17.992C134.032 17.992 133.728 18.12 133.488 18.376C133.232 18.616 133.104 18.92 133.104 19.288C133.104 19.656 133.232 19.968 133.488 20.224C133.728 20.464 134.032 20.584 134.4 20.584C134.768 20.584 135.08 20.464 135.336 20.224C135.576 19.968 135.696 19.656 135.696 19.288ZM145.296 19.288C145.296 18.92 145.168 18.616 144.912 18.376C144.672 18.12 144.368 17.992 144 17.992C143.632 17.992 143.328 18.12 143.088 18.376C142.832 18.616 142.704 18.92 142.704 19.288C142.704 19.656 142.832 19.968 143.088 20.224C143.328 20.464 143.632 20.584 144 20.584C144.368 20.584 144.672 20.464 144.912 20.224C145.168 19.968 145.296 19.656 145.296 19.288ZM138.744 21.52C138.744 20.976 138.56 20.52 138.192 20.152C137.808 19.768 137.344 19.576 136.8 19.576C136.256 19.576 135.8 19.768 135.432 20.152C135.048 20.52 134.856 20.976 134.856 21.52C134.856 22.064 135.048 22.52 135.432 22.888C135.8 23.272 136.256 23.464 136.8 23.464C137.344 23.464 137.808 23.272 138.192 22.888C138.56 22.52 138.744 22.064 138.744 21.52ZM133.944 21.52C133.944 20.976 133.76 20.52 133.392 20.152C133.008 19.768 132.544 19.576 132 19.576C131.456 19.576 131 19.768 130.632 20.152C130.248 20.52 130.056 20.976 130.056 21.52C130.056 22.064 130.248 22.52 130.632 22.888C131 23.272 131.456 23.464 132 23.464C132.544 23.464 133.008 23.272 133.392 22.888C133.76 22.52 133.944 22.064 133.944 21.52ZM143.544 21.52C143.544 20.976 143.36 20.52 142.992 20.152C142.608 19.768 142.144 19.576 141.6 19.576C141.056 19.576 140.6 19.768 140.232 20.152C139.848 20.52 139.656 20.976 139.656 21.52C139.656 22.064 139.848 22.52 140.232 22.888C140.6 23.272 141.056 23.464 141.6 23.464C142.144 23.464 142.608 23.272 142.992 22.888C143.36 22.52 143.544 22.064 143.544 21.52ZM140.496 23.752C140.496 23.384 140.376 23.072 140.136 22.816C139.88 22.576 139.568 22.456 139.2 22.456C138.832 22.456 138.528 22.576 138.288 22.816C138.032 23.072 137.904 23.384 137.904 23.752C137.904 24.12 138.032 24.424 138.288 24.664C138.528 24.92 138.832 25.048 139.2 25.048C139.568 25.048 139.88 24.92 140.136 24.664C140.376 24.424 140.496 24.12 140.496 23.752ZM135.696 23.752C135.696 23.384 135.576 23.072 135.336 22.816C135.08 22.576 134.768 22.456 134.4 22.456C134.032 22.456 133.728 22.576 133.488 22.816C133.232 23.072 133.104 23.384 133.104 23.752C133.104 24.12 133.232 24.424 133.488 24.664C133.728 24.92 134.032 25.048 134.4 25.048C134.768 25.048 135.08 24.92 135.336 24.664C135.576 24.424 135.696 24.12 135.696 23.752ZM145.296 23.752C145.296 23.384 145.168 23.072 144.912 22.816C144.672 22.576 144.368 22.456 144 22.456C143.632 22.456 143.328 22.576 143.088 22.816C142.832 23.072 142.704 23.384 142.704 23.752C142.704 24.12 142.832 24.424 143.088 24.664C143.328 24.92 143.632 25.048 144 25.048C144.368 25.048 144.672 24.92 144.912 24.664C145.168 24.424 145.296 24.12 145.296 23.752ZM138.744 25.984C138.744 25.44 138.56 24.976 138.192 24.592C137.808 24.224 137.344 24.04 136.8 24.04C136.256 24.04 135.8 24.224 135.432 24.592C135.048 24.976 134.856 25.44 134.856 25.984C134.856 26.528 135.048 26.984 135.432 27.352C135.8 27.736 136.256 27.928 136.8 27.928C137.344 27.928 137.808 27.736 138.192 27.352C138.56 26.984 138.744 26.528 138.744 25.984ZM133.944 25.984C133.944 25.44 133.76 24.976 133.392 24.592C133.008 24.224 132.544 24.04 132 24.04C131.456 24.04 131 24.224 130.632 24.592C130.248 24.976 130.056 25.44 130.056 25.984C130.056 26.528 130.248 26.984 130.632 27.352C131 27.736 131.456 27.928 132 27.928C132.544 27.928 133.008 27.736 133.392 27.352C133.76 26.984 133.944 26.528 133.944 25.984ZM143.544 25.984C143.544 25.44 143.36 24.976 142.992 24.592C142.608 24.224 142.144 24.04 141.6 24.04C141.056 24.04 140.6 24.224 140.232 24.592C139.848 24.976 139.656 25.44 139.656 25.984C139.656 26.528 139.848 26.984 140.232 27.352C140.6 27.736 141.056 27.928 141.6 27.928C142.144 27.928 142.608 27.736 142.992 27.352C143.36 26.984 143.544 26.528 143.544 25.984ZM140.496 28.216C140.496 27.848 140.376 27.536 140.136 27.28C139.88 27.04 139.568 26.92 139.2 26.92C138.832 26.92 138.528 27.04 138.288 27.28C138.032 27.536 137.904 27.848 137.904 28.216C137.904 28.584 138.032 28.888 138.288 29.128C138.528 29.384 138.832 29.512 139.2 29.512C139.568 29.512 139.88 29.384 140.136 29.128C140.376 28.888 140.496 28.584 140.496 28.216ZM135.696 28.216C135.696 27.848 135.576 27.536 135.336 27.28C135.08 27.04 134.768 26.92 134.4 26.92C134.032 26.92 133.728 27.04 133.488 27.28C133.232 27.536 133.104 27.848 133.104 28.216C133.104 28.584 133.232 28.888 133.488 29.128C133.728 29.384 134.032 29.512 134.4 29.512C134.768 29.512 135.08 29.384 135.336 29.128C135.576 28.888 135.696 28.584 135.696 28.216ZM145.296 28.216C145.296 27.848 145.168 27.536 144.912 27.28C144.672 27.04 144.368 26.92 144 26.92C143.632 26.92 143.328 27.04 143.088 27.28C142.832 27.536 142.704 27.848 142.704 28.216C142.704 28.584 142.832 28.888 143.088 29.128C143.328 29.384 143.632 29.512 144 29.512C144.368 29.512 144.672 29.384 144.912 29.128C145.168 28.888 145.296 28.584 145.296 28.216ZM133.944 30.2C133.944 29.656 133.76 29.2 133.392 28.832C133.008 28.448 132.544 28.256 132 28.256C131.456 28.256 131 28.448 130.632 28.832C130.248 29.2 130.056 29.656 130.056 30.2C130.056 30.744 130.248 31.208 130.632 31.592C131 31.96 131.456 32.144 132 32.144C132.544 32.144 133.008 31.96 133.392 31.592C133.76 31.208 133.944 30.744 133.944 30.2ZM138.744 30.2C138.744 29.656 138.56 29.2 138.192 28.832C137.808 28.448 137.344 28.256 136.8 28.256C136.256 28.256 135.8 28.448 135.432 28.832C135.048 29.2 134.856 29.656 134.856 30.2C134.856 30.744 135.048 31.208 135.432 31.592C135.8 31.96 136.256 32.144 136.8 32.144C137.344 32.144 137.808 31.96 138.192 31.592C138.56 31.208 138.744 30.744 138.744 30.2ZM143.544 30.2C143.544 29.656 143.36 29.2 142.992 28.832C142.608 28.448 142.144 28.256 141.6 28.256C141.056 28.256 140.6 28.448 140.232 28.832C139.848 29.2 139.656 29.656 139.656 30.2C139.656 30.744 139.848 31.208 140.232 31.592C140.6 31.96 141.056 32.144 141.6 32.144C142.144 32.144 142.608 31.96 142.992 31.592C143.36 31.208 143.544 30.744 143.544 30.2ZM135.696 32.432C135.696 32.064 135.576 31.76 135.336 31.52C135.08 31.264 134.768 31.136 134.4 31.136C134.032 31.136 133.728 31.264 133.488 31.52C133.232 31.76 133.104 32.064 133.104 32.432C133.104 32.8 133.232 33.112 133.488 33.368C133.728 33.608 134.032 33.728 134.4 33.728C134.768 33.728 135.08 33.608 135.336 33.368C135.576 33.112 135.696 32.8 135.696 32.432ZM140.496 32.432C140.496 32.064 140.376 31.76 140.136 31.52C139.88 31.264 139.568 31.136 139.2 31.136C138.832 31.136 138.528 31.264 138.288 31.52C138.032 31.76 137.904 32.064 137.904 32.432C137.904 32.8 138.032 33.112 138.288 33.368C138.528 33.608 138.832 33.728 139.2 33.728C139.568 33.728 139.88 33.608 140.136 33.368C140.376 33.112 140.496 32.8 140.496 32.432ZM145.296 32.432C145.296 32.064 145.168 31.76 144.912 31.52C144.672 31.264 144.368 31.136 144 31.136C143.632 31.136 143.328 31.264 143.088 31.52C142.832 31.76 142.704 32.064 142.704 32.432C142.704 32.8 142.832 33.112 143.088 33.368C143.328 33.608 143.632 33.728 144 33.728C144.368 33.728 144.672 33.608 144.912 33.368C145.168 33.112 145.296 32.8 145.296 32.432ZM133.944 34.664C133.944 34.12 133.76 33.664 133.392 33.296C133.008 32.912 132.544 32.72 132 32.72C131.456 32.72 131 32.912 130.632 33.296C130.248 33.664 130.056 34.12 130.056 34.664C130.056 35.208 130.248 35.672 130.632 36.056C131 36.424 131.456 36.608 132 36.608C132.544 36.608 133.008 36.424 133.392 36.056C133.76 35.672 133.944 35.208 133.944 34.664ZM143.544 34.664C143.544 34.12 143.36 33.664 142.992 33.296C142.608 32.912 142.144 32.72 141.6 32.72C141.056 32.72 140.6 32.912 140.232 33.296C139.848 33.664 139.656 34.12 139.656 34.664C139.656 35.208 139.848 35.672 140.232 36.056C140.6 36.424 141.056 36.608 141.6 36.608C142.144 36.608 142.608 36.424 142.992 36.056C143.36 35.672 143.544 35.208 143.544 34.664ZM138.744 34.664C138.744 34.12 138.56 33.664 138.192 33.296C137.808 32.912 137.344 32.72 136.8 32.72C136.256 32.72 135.8 32.912 135.432 33.296C135.048 33.664 134.856 34.12 134.856 34.664C134.856 35.208 135.048 35.672 135.432 36.056C135.8 36.424 136.256 36.608 136.8 36.608C137.344 36.608 137.808 36.424 138.192 36.056C138.56 35.672 138.744 35.208 138.744 34.664ZM135.696 36.896C135.696 36.528 135.576 36.224 135.336 35.984C135.08 35.728 134.768 35.6 134.4 35.6C134.032 35.6 133.728 35.728 133.488 35.984C133.232 36.224 133.104 36.528 133.104 36.896C133.104 37.264 133.232 37.576 133.488 37.832C133.728 38.072 134.032 38.192 134.4 38.192C134.768 38.192 135.08 38.072 135.336 37.832C135.576 37.576 135.696 37.264 135.696 36.896ZM145.296 36.896C145.296 36.528 145.168 36.224 144.912 35.984C144.672 35.728 144.368 35.6 144 35.6C143.632 35.6 143.328 35.728 143.088 35.984C142.832 36.224 142.704 36.528 142.704 36.896C142.704 37.264 142.832 37.576 143.088 37.832C143.328 38.072 143.632 38.192 144 38.192C144.368 38.192 144.672 38.072 144.912 37.832C145.168 37.576 145.296 37.264 145.296 36.896ZM140.496 36.896C140.496 36.528 140.376 36.224 140.136 35.984C139.88 35.728 139.568 35.6 139.2 35.6C138.832 35.6 138.528 35.728 138.288 35.984C138.032 36.224 137.904 36.528 137.904 36.896C137.904 37.264 138.032 37.576 138.288 37.832C138.528 38.072 138.832 38.192 139.2 38.192C139.568 38.192 139.88 38.072 140.136 37.832C140.376 37.576 140.496 37.264 140.496 36.896ZM133.944 39.128C133.944 38.584 133.76 38.128 133.392 37.76C133.008 37.376 132.544 37.184 132 37.184C131.456 37.184 131 37.376 130.632 37.76C130.248 38.128 130.056 38.584 130.056 39.128C130.056 39.672 130.248 40.136 130.632 40.52C131 40.888 131.456 41.072 132 41.072C132.544 41.072 133.008 40.888 133.392 40.52C133.76 40.136 133.944 39.672 133.944 39.128ZM143.544 39.128C143.544 38.584 143.36 38.128 142.992 37.76C142.608 37.376 142.144 37.184 141.6 37.184C141.056 37.184 140.6 37.376 140.232 37.76C139.848 38.128 139.656 38.584 139.656 39.128C139.656 39.672 139.848 40.136 140.232 40.52C140.6 40.888 141.056 41.072 141.6 41.072C142.144 41.072 142.608 40.888 142.992 40.52C143.36 40.136 143.544 39.672 143.544 39.128ZM138.744 39.128C138.744 38.584 138.56 38.128 138.192 37.76C137.808 37.376 137.344 37.184 136.8 37.184C136.256 37.184 135.8 37.376 135.432 37.76C135.048 38.128 134.856 38.584 134.856 39.128C134.856 39.672 135.048 40.136 135.432 40.52C135.8 40.888 136.256 41.072 136.8 41.072C137.344 41.072 137.808 40.888 138.192 40.52C138.56 40.136 138.744 39.672 138.744 39.128ZM135.696 41.36C135.696 40.992 135.576 40.688 135.336 40.448C135.08 40.192 134.768 40.064 134.4 40.064C134.032 40.064 133.728 40.192 133.488 40.448C133.232 40.688 133.104 40.992 133.104 41.36C133.104 41.728 133.232 42.04 133.488 42.296C133.728 42.536 134.032 42.656 134.4 42.656C134.768 42.656 135.08 42.536 135.336 42.296C135.576 42.04 135.696 41.728 135.696 41.36ZM145.296 41.36C145.296 40.992 145.168 40.688 144.912 40.448C144.672 40.192 144.368 40.064 144 40.064C143.632 40.064 143.328 40.192 143.088 40.448C142.832 40.688 142.704 40.992 142.704 41.36C142.704 41.728 142.832 42.04 143.088 42.296C143.328 42.536 143.632 42.656 144 42.656C144.368 42.656 144.672 42.536 144.912 42.296C145.168 42.04 145.296 41.728 145.296 41.36ZM140.496 41.36C140.496 40.992 140.376 40.688 140.136 40.448C139.88 40.192 139.568 40.064 139.2 40.064C138.832 40.064 138.528 40.192 138.288 40.448C138.032 40.688 137.904 40.992 137.904 41.36C137.904 41.728 138.032 42.04 138.288 42.296C138.528 42.536 138.832 42.656 139.2 42.656C139.568 42.656 139.88 42.536 140.136 42.296C140.376 42.04 140.496 41.728 140.496 41.36ZM138.744 43.592C138.744 43.048 138.56 42.592 138.192 42.224C137.808 41.84 137.344 41.648 136.8 41.648C136.256 41.648 135.8 41.84 135.432 42.224C135.048 42.592 134.856 43.048 134.856 43.592C134.856 44.136 135.048 44.6 135.432 44.984C135.8 45.352 136.256 45.536 136.8 45.536C137.344 45.536 137.808 45.352 138.192 44.984C138.56 44.6 138.744 44.136 138.744 43.592ZM143.544 43.592C143.544 43.048 143.36 42.592 142.992 42.224C142.608 41.84 142.144 41.648 141.6 41.648C141.056 41.648 140.6 41.84 140.232 42.224C139.848 42.592 139.656 43.048 139.656 43.592C139.656 44.136 139.848 44.6 140.232 44.984C140.6 45.352 141.056 45.536 141.6 45.536C142.144 45.536 142.608 45.352 142.992 44.984C143.36 44.6 143.544 44.136 143.544 43.592ZM133.944 43.592C133.944 43.048 133.76 42.592 133.392 42.224C133.008 41.84 132.544 41.648 132 41.648C131.456 41.648 131 41.84 130.632 42.224C130.248 42.592 130.056 43.048 130.056 43.592C130.056 44.136 130.248 44.6 130.632 44.984C131 45.352 131.456 45.536 132 45.536C132.544 45.536 133.008 45.352 133.392 44.984C133.76 44.6 133.944 44.136 133.944 43.592ZM140.496 45.824C140.496 45.456 140.376 45.152 140.136 44.912C139.88 44.656 139.568 44.528 139.2 44.528C138.832 44.528 138.528 44.656 138.288 44.912C138.032 45.152 137.904 45.456 137.904 45.824C137.904 46.192 138.032 46.504 138.288 46.76C138.528 47 138.832 47.12 139.2 47.12C139.568 47.12 139.88 47 140.136 46.76C140.376 46.504 140.496 46.192 140.496 45.824ZM145.296 45.824C145.296 45.456 145.168 45.152 144.912 44.912C144.672 44.656 144.368 44.528 144 44.528C143.632 44.528 143.328 44.656 143.088 44.912C142.832 45.152 142.704 45.456 142.704 45.824C142.704 46.192 142.832 46.504 143.088 46.76C143.328 47 143.632 47.12 144 47.12C144.368 47.12 144.672 47 144.912 46.76C145.168 46.504 145.296 46.192 145.296 45.824ZM135.696 45.824C135.696 45.456 135.576 45.152 135.336 44.912C135.08 44.656 134.768 44.528 134.4 44.528C134.032 44.528 133.728 44.656 133.488 44.912C133.232 45.152 133.104 45.456 133.104 45.824C133.104 46.192 133.232 46.504 133.488 46.76C133.728 47 134.032 47.12 134.4 47.12C134.768 47.12 135.08 47 135.336 46.76C135.576 46.504 135.696 46.192 135.696 45.824ZM138.744 48.056C138.744 47.512 138.56 47.056 138.192 46.688C137.808 46.304 137.344 46.112 136.8 46.112C136.256 46.112 135.8 46.304 135.432 46.688C135.048 47.056 134.856 47.512 134.856 48.056C134.856 48.6 135.048 49.064 135.432 49.448C135.8 49.816 136.256 50 136.8 50C137.344 50 137.808 49.816 138.192 49.448C138.56 49.064 138.744 48.6 138.744 48.056ZM133.944 48.056C133.944 47.512 133.76 47.056 133.392 46.688C133.008 46.304 132.544 46.112 132 46.112C131.456 46.112 131 46.304 130.632 46.688C130.248 47.056 130.056 47.512 130.056 48.056C130.056 48.6 130.248 49.064 130.632 49.448C131 49.816 131.456 50 132 50C132.544 50 133.008 49.816 133.392 49.448C133.76 49.064 133.944 48.6 133.944 48.056ZM143.544 48.056C143.544 47.512 143.36 47.056 142.992 46.688C142.608 46.304 142.144 46.112 141.6 46.112C141.056 46.112 140.6 46.304 140.232 46.688C139.848 47.056 139.656 47.512 139.656 48.056C139.656 48.6 139.848 49.064 140.232 49.448C140.6 49.816 141.056 50 141.6 50C142.144 50 142.608 49.816 142.992 49.448C143.36 49.064 143.544 48.6 143.544 48.056ZM140.496 50.288C140.496 49.92 140.376 49.616 140.136 49.376C139.88 49.12 139.568 48.992 139.2 48.992C138.832 48.992 138.528 49.12 138.288 49.376C138.032 49.616 137.904 49.92 137.904 50.288C137.904 50.656 138.032 50.968 138.288 51.224C138.528 51.464 138.832 51.584 139.2 51.584C139.568 51.584 139.88 51.464 140.136 51.224C140.376 50.968 140.496 50.656 140.496 50.288ZM135.696 50.288C135.696 49.92 135.576 49.616 135.336 49.376C135.08 49.12 134.768 48.992 134.4 48.992C134.032 48.992 133.728 49.12 133.488 49.376C133.232 49.616 133.104 49.92 133.104 50.288C133.104 50.656 133.232 50.968 133.488 51.224C133.728 51.464 134.032 51.584 134.4 51.584C134.768 51.584 135.08 51.464 135.336 51.224C135.576 50.968 135.696 50.656 135.696 50.288ZM145.296 50.288C145.296 49.92 145.168 49.616 144.912 49.376C144.672 49.12 144.368 48.992 144 48.992C143.632 48.992 143.328 49.12 143.088 49.376C142.832 49.616 142.704 49.92 142.704 50.288C142.704 50.656 142.832 50.968 143.088 51.224C143.328 51.464 143.632 51.584 144 51.584C144.368 51.584 144.672 51.464 144.912 51.224C145.168 50.968 145.296 50.656 145.296 50.288ZM138.744 52.52C138.744 51.976 138.56 51.52 138.192 51.152C137.808 50.768 137.344 50.576 136.8 50.576C136.256 50.576 135.8 50.768 135.432 51.152C135.048 51.52 134.856 51.976 134.856 52.52C134.856 53.064 135.048 53.52 135.432 53.888C135.8 54.272 136.256 54.464 136.8 54.464C137.344 54.464 137.808 54.272 138.192 53.888C138.56 53.52 138.744 53.064 138.744 52.52ZM133.944 52.52C133.944 51.976 133.76 51.52 133.392 51.152C133.008 50.768 132.544 50.576 132 50.576C131.456 50.576 131 50.768 130.632 51.152C130.248 51.52 130.056 51.976 130.056 52.52C130.056 53.064 130.248 53.52 130.632 53.888C131 54.272 131.456 54.464 132 54.464C132.544 54.464 133.008 54.272 133.392 53.888C133.76 53.52 133.944 53.064 133.944 52.52ZM143.544 52.52C143.544 51.976 143.36 51.52 142.992 51.152C142.608 50.768 142.144 50.576 141.6 50.576C141.056 50.576 140.6 50.768 140.232 51.152C139.848 51.52 139.656 51.976 139.656 52.52C139.656 53.064 139.848 53.52 140.232 53.888C140.6 54.272 141.056 54.464 141.6 54.464C142.144 54.464 142.608 54.272 142.992 53.888C143.36 53.52 143.544 53.064 143.544 52.52ZM140.496 54.752C140.496 54.384 140.376 54.072 140.136 53.816C139.88 53.576 139.568 53.456 139.2 53.456C138.832 53.456 138.528 53.576 138.288 53.816C138.032 54.072 137.904 54.384 137.904 54.752C137.904 55.12 138.032 55.424 138.288 55.664C138.528 55.92 138.832 56.048 139.2 56.048C139.568 56.048 139.88 55.92 140.136 55.664C140.376 55.424 140.496 55.12 140.496 54.752ZM135.696 54.752C135.696 54.384 135.576 54.072 135.336 53.816C135.08 53.576 134.768 53.456 134.4 53.456C134.032 53.456 133.728 53.576 133.488 53.816C133.232 54.072 133.104 54.384 133.104 54.752C133.104 55.12 133.232 55.424 133.488 55.664C133.728 55.92 134.032 56.048 134.4 56.048C134.768 56.048 135.08 55.92 135.336 55.664C135.576 55.424 135.696 55.12 135.696 54.752ZM145.296 54.752C145.296 54.384 145.168 54.072 144.912 53.816C144.672 53.576 144.368 53.456 144 53.456C143.632 53.456 143.328 53.576 143.088 53.816C142.832 54.072 142.704 54.384 142.704 54.752C142.704 55.12 142.832 55.424 143.088 55.664C143.328 55.92 143.632 56.048 144 56.048C144.368 56.048 144.672 55.92 144.912 55.664C145.168 55.424 145.296 55.12 145.296 54.752ZM138.744 56.984C138.744 56.44 138.56 55.976 138.192 55.592C137.808 55.224 137.344 55.04 136.8 55.04C136.256 55.04 135.8 55.224 135.432 55.592C135.048 55.976 134.856 56.44 134.856 56.984C134.856 57.528 135.048 57.984 135.432 58.352C135.8 58.736 136.256 58.928 136.8 58.928C137.344 58.928 137.808 58.736 138.192 58.352C138.56 57.984 138.744 57.528 138.744 56.984ZM133.944 56.984C133.944 56.44 133.76 55.976 133.392 55.592C133.008 55.224 132.544 55.04 132 55.04C131.456 55.04 131 55.224 130.632 55.592C130.248 55.976 130.056 56.44 130.056 56.984C130.056 57.528 130.248 57.984 130.632 58.352C131 58.736 131.456 58.928 132 58.928C132.544 58.928 133.008 58.736 133.392 58.352C133.76 57.984 133.944 57.528 133.944 56.984ZM143.544 56.984C143.544 56.44 143.36 55.976 142.992 55.592C142.608 55.224 142.144 55.04 141.6 55.04C141.056 55.04 140.6 55.224 140.232 55.592C139.848 55.976 139.656 56.44 139.656 56.984C139.656 57.528 139.848 57.984 140.232 58.352C140.6 58.736 141.056 58.928 141.6 58.928C142.144 58.928 142.608 58.736 142.992 58.352C143.36 57.984 143.544 57.528 143.544 56.984ZM140.496 59.216C140.496 58.848 140.376 58.536 140.136 58.28C139.88 58.04 139.568 57.92 139.2 57.92C138.832 57.92 138.528 58.04 138.288 58.28C138.032 58.536 137.904 58.848 137.904 59.216C137.904 59.584 138.032 59.888 138.288 60.128C138.528 60.384 138.832 60.512 139.2 60.512C139.568 60.512 139.88 60.384 140.136 60.128C140.376 59.888 140.496 59.584 140.496 59.216ZM135.696 59.216C135.696 58.848 135.576 58.536 135.336 58.28C135.08 58.04 134.768 57.92 134.4 57.92C134.032 57.92 133.728 58.04 133.488 58.28C133.232 58.536 133.104 58.848 133.104 59.216C133.104 59.584 133.232 59.888 133.488 60.128C133.728 60.384 134.032 60.512 134.4 60.512C134.768 60.512 135.08 60.384 135.336 60.128C135.576 59.888 135.696 59.584 135.696 59.216ZM145.296 59.216C145.296 58.848 145.168 58.536 144.912 58.28C144.672 58.04 144.368 57.92 144 57.92C143.632 57.92 143.328 58.04 143.088 58.28C142.832 58.536 142.704 58.848 142.704 59.216C142.704 59.584 142.832 59.888 143.088 60.128C143.328 60.384 143.632 60.512 144 60.512C144.368 60.512 144.672 60.384 144.912 60.128C145.168 59.888 145.296 59.584 145.296 59.216ZM119.385 30.2C119.385 29.832 119.265 29.528 119.025 29.288C118.769 29.032 118.457 28.904 118.089 28.904C117.721 28.904 117.417 29.032 117.177 29.288C116.921 29.528 116.793 29.832 116.793 30.2C116.793 30.568 116.921 30.88 117.177 31.136C117.417 31.376 117.721 31.496 118.089 31.496C118.457 31.496 118.769 31.376 119.025 31.136C119.265 30.88 119.385 30.568 119.385 30.2ZM124.185 30.2C124.185 29.832 124.065 29.528 123.825 29.288C123.569 29.032 123.257 28.904 122.889 28.904C122.521 28.904 122.217 29.032 121.977 29.288C121.721 29.528 121.593 29.832 121.593 30.2C121.593 30.568 121.721 30.88 121.977 31.136C122.217 31.376 122.521 31.496 122.889 31.496C123.257 31.496 123.569 31.376 123.825 31.136C124.065 30.88 124.185 30.568 124.185 30.2ZM128.985 30.2C128.985 29.832 128.865 29.528 128.625 29.288C128.369 29.032 128.057 28.904 127.689 28.904C127.321 28.904 127.017 29.032 126.777 29.288C126.521 29.528 126.393 29.832 126.393 30.2C126.393 30.568 126.521 30.88 126.777 31.136C127.017 31.376 127.321 31.496 127.689 31.496C128.057 31.496 128.369 31.376 128.625 31.136C128.865 30.88 128.985 30.568 128.985 30.2ZM121.353 32.432C121.353 32.192 121.273 31.992 121.113 31.832C120.937 31.656 120.729 31.568 120.489 31.568C120.249 31.568 120.049 31.656 119.889 31.832C119.713 31.992 119.625 32.192 119.625 32.432C119.625 32.672 119.713 32.88 119.889 33.056C120.049 33.216 120.249 33.296 120.489 33.296C120.729 33.296 120.937 33.216 121.113 33.056C121.273 32.88 121.353 32.672 121.353 32.432ZM126.153 32.432C126.153 32.192 126.073 31.992 125.913 31.832C125.737 31.656 125.529 31.568 125.289 31.568C125.049 31.568 124.849 31.656 124.689 31.832C124.513 31.992 124.425 32.192 124.425 32.432C124.425 32.672 124.513 32.88 124.689 33.056C124.849 33.216 125.049 33.296 125.289 33.296C125.529 33.296 125.737 33.216 125.913 33.056C126.073 32.88 126.153 32.672 126.153 32.432ZM130.953 32.432C130.953 32.192 130.865 31.992 130.689 31.832C130.529 31.656 130.329 31.568 130.089 31.568C129.849 31.568 129.649 31.656 129.489 31.832C129.313 31.992 129.225 32.192 129.225 32.432C129.225 32.672 129.313 32.88 129.489 33.056C129.649 33.216 129.849 33.296 130.089 33.296C130.329 33.296 130.529 33.216 130.689 33.056C130.865 32.88 130.953 32.672 130.953 32.432ZM119.385 34.664C119.385 34.296 119.265 33.992 119.025 33.752C118.769 33.496 118.457 33.368 118.089 33.368C117.721 33.368 117.417 33.496 117.177 33.752C116.921 33.992 116.793 34.296 116.793 34.664C116.793 35.032 116.921 35.344 117.177 35.6C117.417 35.84 117.721 35.96 118.089 35.96C118.457 35.96 118.769 35.84 119.025 35.6C119.265 35.344 119.385 35.032 119.385 34.664ZM128.985 34.664C128.985 34.296 128.865 33.992 128.625 33.752C128.369 33.496 128.057 33.368 127.689 33.368C127.321 33.368 127.017 33.496 126.777 33.752C126.521 33.992 126.393 34.296 126.393 34.664C126.393 35.032 126.521 35.344 126.777 35.6C127.017 35.84 127.321 35.96 127.689 35.96C128.057 35.96 128.369 35.84 128.625 35.6C128.865 35.344 128.985 35.032 128.985 34.664ZM124.185 34.664C124.185 34.296 124.065 33.992 123.825 33.752C123.569 33.496 123.257 33.368 122.889 33.368C122.521 33.368 122.217 33.496 121.977 33.752C121.721 33.992 121.593 34.296 121.593 34.664C121.593 35.032 121.721 35.344 121.977 35.6C122.217 35.84 122.521 35.96 122.889 35.96C123.257 35.96 123.569 35.84 123.825 35.6C124.065 35.344 124.185 35.032 124.185 34.664ZM121.353 36.896C121.353 36.656 121.273 36.456 121.113 36.296C120.937 36.12 120.729 36.032 120.489 36.032C120.249 36.032 120.049 36.12 119.889 36.296C119.713 36.456 119.625 36.656 119.625 36.896C119.625 37.136 119.713 37.344 119.889 37.52C120.049 37.68 120.249 37.76 120.489 37.76C120.729 37.76 120.937 37.68 121.113 37.52C121.273 37.344 121.353 37.136 121.353 36.896ZM130.953 36.896C130.953 36.656 130.865 36.456 130.689 36.296C130.529 36.12 130.329 36.032 130.089 36.032C129.849 36.032 129.649 36.12 129.489 36.296C129.313 36.456 129.225 36.656 129.225 36.896C129.225 37.136 129.313 37.344 129.489 37.52C129.649 37.68 129.849 37.76 130.089 37.76C130.329 37.76 130.529 37.68 130.689 37.52C130.865 37.344 130.953 37.136 130.953 36.896ZM126.153 36.896C126.153 36.656 126.073 36.456 125.913 36.296C125.737 36.12 125.529 36.032 125.289 36.032C125.049 36.032 124.849 36.12 124.689 36.296C124.513 36.456 124.425 36.656 124.425 36.896C124.425 37.136 124.513 37.344 124.689 37.52C124.849 37.68 125.049 37.76 125.289 37.76C125.529 37.76 125.737 37.68 125.913 37.52C126.073 37.344 126.153 37.136 126.153 36.896ZM119.385 39.128C119.385 38.76 119.265 38.456 119.025 38.216C118.769 37.96 118.457 37.832 118.089 37.832C117.721 37.832 117.417 37.96 117.177 38.216C116.921 38.456 116.793 38.76 116.793 39.128C116.793 39.496 116.921 39.808 117.177 40.064C117.417 40.304 117.721 40.424 118.089 40.424C118.457 40.424 118.769 40.304 119.025 40.064C119.265 39.808 119.385 39.496 119.385 39.128ZM128.985 39.128C128.985 38.76 128.865 38.456 128.625 38.216C128.369 37.96 128.057 37.832 127.689 37.832C127.321 37.832 127.017 37.96 126.777 38.216C126.521 38.456 126.393 38.76 126.393 39.128C126.393 39.496 126.521 39.808 126.777 40.064C127.017 40.304 127.321 40.424 127.689 40.424C128.057 40.424 128.369 40.304 128.625 40.064C128.865 39.808 128.985 39.496 128.985 39.128ZM124.185 39.128C124.185 38.76 124.065 38.456 123.825 38.216C123.569 37.96 123.257 37.832 122.889 37.832C122.521 37.832 122.217 37.96 121.977 38.216C121.721 38.456 121.593 38.76 121.593 39.128C121.593 39.496 121.721 39.808 121.977 40.064C122.217 40.304 122.521 40.424 122.889 40.424C123.257 40.424 123.569 40.304 123.825 40.064C124.065 39.808 124.185 39.496 124.185 39.128ZM121.353 41.36C121.353 41.12 121.273 40.92 121.113 40.76C120.937 40.584 120.729 40.496 120.489 40.496C120.249 40.496 120.049 40.584 119.889 40.76C119.713 40.92 119.625 41.12 119.625 41.36C119.625 41.6 119.713 41.808 119.889 41.984C120.049 42.144 120.249 42.224 120.489 42.224C120.729 42.224 120.937 42.144 121.113 41.984C121.273 41.808 121.353 41.6 121.353 41.36ZM130.953 41.36C130.953 41.12 130.865 40.92 130.689 40.76C130.529 40.584 130.329 40.496 130.089 40.496C129.849 40.496 129.649 40.584 129.489 40.76C129.313 40.92 129.225 41.12 129.225 41.36C129.225 41.6 129.313 41.808 129.489 41.984C129.649 42.144 129.849 42.224 130.089 42.224C130.329 42.224 130.529 42.144 130.689 41.984C130.865 41.808 130.953 41.6 130.953 41.36ZM126.153 41.36C126.153 41.12 126.073 40.92 125.913 40.76C125.737 40.584 125.529 40.496 125.289 40.496C125.049 40.496 124.849 40.584 124.689 40.76C124.513 40.92 124.425 41.12 124.425 41.36C124.425 41.6 124.513 41.808 124.689 41.984C124.849 42.144 125.049 42.224 125.289 42.224C125.529 42.224 125.737 42.144 125.913 41.984C126.073 41.808 126.153 41.6 126.153 41.36ZM124.185 43.592C124.185 43.224 124.065 42.92 123.825 42.68C123.569 42.424 123.257 42.296 122.889 42.296C122.521 42.296 122.217 42.424 121.977 42.68C121.721 42.92 121.593 43.224 121.593 43.592C121.593 43.96 121.721 44.272 121.977 44.528C122.217 44.768 122.521 44.888 122.889 44.888C123.257 44.888 123.569 44.768 123.825 44.528C124.065 44.272 124.185 43.96 124.185 43.592ZM128.985 43.592C128.985 43.224 128.865 42.92 128.625 42.68C128.369 42.424 128.057 42.296 127.689 42.296C127.321 42.296 127.017 42.424 126.777 42.68C126.521 42.92 126.393 43.224 126.393 43.592C126.393 43.96 126.521 44.272 126.777 44.528C127.017 44.768 127.321 44.888 127.689 44.888C128.057 44.888 128.369 44.768 128.625 44.528C128.865 44.272 128.985 43.96 128.985 43.592ZM119.385 43.592C119.385 43.224 119.265 42.92 119.025 42.68C118.769 42.424 118.457 42.296 118.089 42.296C117.721 42.296 117.417 42.424 117.177 42.68C116.921 42.92 116.793 43.224 116.793 43.592C116.793 43.96 116.921 44.272 117.177 44.528C117.417 44.768 117.721 44.888 118.089 44.888C118.457 44.888 118.769 44.768 119.025 44.528C119.265 44.272 119.385 43.96 119.385 43.592ZM126.153 45.824C126.153 45.584 126.073 45.384 125.913 45.224C125.737 45.048 125.529 44.96 125.289 44.96C125.049 44.96 124.849 45.048 124.689 45.224C124.513 45.384 124.425 45.584 124.425 45.824C124.425 46.064 124.513 46.272 124.689 46.448C124.849 46.608 125.049 46.688 125.289 46.688C125.529 46.688 125.737 46.608 125.913 46.448C126.073 46.272 126.153 46.064 126.153 45.824ZM130.953 45.824C130.953 45.584 130.865 45.384 130.689 45.224C130.529 45.048 130.329 44.96 130.089 44.96C129.849 44.96 129.649 45.048 129.489 45.224C129.313 45.384 129.225 45.584 129.225 45.824C129.225 46.064 129.313 46.272 129.489 46.448C129.649 46.608 129.849 46.688 130.089 46.688C130.329 46.688 130.529 46.608 130.689 46.448C130.865 46.272 130.953 46.064 130.953 45.824ZM121.353 45.824C121.353 45.584 121.273 45.384 121.113 45.224C120.937 45.048 120.729 44.96 120.489 44.96C120.249 44.96 120.049 45.048 119.889 45.224C119.713 45.384 119.625 45.584 119.625 45.824C119.625 46.064 119.713 46.272 119.889 46.448C120.049 46.608 120.249 46.688 120.489 46.688C120.729 46.688 120.937 46.608 121.113 46.448C121.273 46.272 121.353 46.064 121.353 45.824ZM124.185 48.056C124.185 47.688 124.065 47.384 123.825 47.144C123.569 46.888 123.257 46.76 122.889 46.76C122.521 46.76 122.217 46.888 121.977 47.144C121.721 47.384 121.593 47.688 121.593 48.056C121.593 48.424 121.721 48.736 121.977 48.992C122.217 49.232 122.521 49.352 122.889 49.352C123.257 49.352 123.569 49.232 123.825 48.992C124.065 48.736 124.185 48.424 124.185 48.056ZM119.385 48.056C119.385 47.688 119.265 47.384 119.025 47.144C118.769 46.888 118.457 46.76 118.089 46.76C117.721 46.76 117.417 46.888 117.177 47.144C116.921 47.384 116.793 47.688 116.793 48.056C116.793 48.424 116.921 48.736 117.177 48.992C117.417 49.232 117.721 49.352 118.089 49.352C118.457 49.352 118.769 49.232 119.025 48.992C119.265 48.736 119.385 48.424 119.385 48.056ZM128.985 48.056C128.985 47.688 128.865 47.384 128.625 47.144C128.369 46.888 128.057 46.76 127.689 46.76C127.321 46.76 127.017 46.888 126.777 47.144C126.521 47.384 126.393 47.688 126.393 48.056C126.393 48.424 126.521 48.736 126.777 48.992C127.017 49.232 127.321 49.352 127.689 49.352C128.057 49.352 128.369 49.232 128.625 48.992C128.865 48.736 128.985 48.424 128.985 48.056ZM126.153 50.288C126.153 50.048 126.073 49.848 125.913 49.688C125.737 49.512 125.529 49.424 125.289 49.424C125.049 49.424 124.849 49.512 124.689 49.688C124.513 49.848 124.425 50.048 124.425 50.288C124.425 50.528 124.513 50.736 124.689 50.912C124.849 51.072 125.049 51.152 125.289 51.152C125.529 51.152 125.737 51.072 125.913 50.912C126.073 50.736 126.153 50.528 126.153 50.288ZM121.353 50.288C121.353 50.048 121.273 49.848 121.113 49.688C120.937 49.512 120.729 49.424 120.489 49.424C120.249 49.424 120.049 49.512 119.889 49.688C119.713 49.848 119.625 50.048 119.625 50.288C119.625 50.528 119.713 50.736 119.889 50.912C120.049 51.072 120.249 51.152 120.489 51.152C120.729 51.152 120.937 51.072 121.113 50.912C121.273 50.736 121.353 50.528 121.353 50.288ZM130.953 50.288C130.953 50.048 130.865 49.848 130.689 49.688C130.529 49.512 130.329 49.424 130.089 49.424C129.849 49.424 129.649 49.512 129.489 49.688C129.313 49.848 129.225 50.048 129.225 50.288C129.225 50.528 129.313 50.736 129.489 50.912C129.649 51.072 129.849 51.152 130.089 51.152C130.329 51.152 130.529 51.072 130.689 50.912C130.865 50.736 130.953 50.528 130.953 50.288ZM124.185 52.52C124.185 52.152 124.065 51.848 123.825 51.608C123.569 51.352 123.257 51.224 122.889 51.224C122.521 51.224 122.217 51.352 121.977 51.608C121.721 51.848 121.593 52.152 121.593 52.52C121.593 52.888 121.721 53.192 121.977 53.432C122.217 53.688 122.521 53.816 122.889 53.816C123.257 53.816 123.569 53.688 123.825 53.432C124.065 53.192 124.185 52.888 124.185 52.52ZM119.385 52.52C119.385 52.152 119.265 51.848 119.025 51.608C118.769 51.352 118.457 51.224 118.089 51.224C117.721 51.224 117.417 51.352 117.177 51.608C116.921 51.848 116.793 52.152 116.793 52.52C116.793 52.888 116.921 53.192 117.177 53.432C117.417 53.688 117.721 53.816 118.089 53.816C118.457 53.816 118.769 53.688 119.025 53.432C119.265 53.192 119.385 52.888 119.385 52.52ZM128.985 52.52C128.985 52.152 128.865 51.848 128.625 51.608C128.369 51.352 128.057 51.224 127.689 51.224C127.321 51.224 127.017 51.352 126.777 51.608C126.521 51.848 126.393 52.152 126.393 52.52C126.393 52.888 126.521 53.192 126.777 53.432C127.017 53.688 127.321 53.816 127.689 53.816C128.057 53.816 128.369 53.688 128.625 53.432C128.865 53.192 128.985 52.888 128.985 52.52ZM126.153 54.752C126.153 54.512 126.073 54.304 125.913 54.128C125.737 53.968 125.529 53.888 125.289 53.888C125.049 53.888 124.849 53.968 124.689 54.128C124.513 54.304 124.425 54.512 124.425 54.752C124.425 54.992 124.513 55.192 124.689 55.352C124.849 55.528 125.049 55.616 125.289 55.616C125.529 55.616 125.737 55.528 125.913 55.352C126.073 55.192 126.153 54.992 126.153 54.752ZM121.353 54.752C121.353 54.512 121.273 54.304 121.113 54.128C120.937 53.968 120.729 53.888 120.489 53.888C120.249 53.888 120.049 53.968 119.889 54.128C119.713 54.304 119.625 54.512 119.625 54.752C119.625 54.992 119.713 55.192 119.889 55.352C120.049 55.528 120.249 55.616 120.489 55.616C120.729 55.616 120.937 55.528 121.113 55.352C121.273 55.192 121.353 54.992 121.353 54.752ZM130.953 54.752C130.953 54.512 130.865 54.304 130.689 54.128C130.529 53.968 130.329 53.888 130.089 53.888C129.849 53.888 129.649 53.968 129.489 54.128C129.313 54.304 129.225 54.512 129.225 54.752C129.225 54.992 129.313 55.192 129.489 55.352C129.649 55.528 129.849 55.616 130.089 55.616C130.329 55.616 130.529 55.528 130.689 55.352C130.865 55.192 130.953 54.992 130.953 54.752ZM124.185 56.984C124.185 56.616 124.065 56.304 123.825 56.048C123.569 55.808 123.257 55.688 122.889 55.688C122.521 55.688 122.217 55.808 121.977 56.048C121.721 56.304 121.593 56.616 121.593 56.984C121.593 57.352 121.721 57.656 121.977 57.896C122.217 58.152 122.521 58.28 122.889 58.28C123.257 58.28 123.569 58.152 123.825 57.896C124.065 57.656 124.185 57.352 124.185 56.984ZM119.385 56.984C119.385 56.616 119.265 56.304 119.025 56.048C118.769 55.808 118.457 55.688 118.089 55.688C117.721 55.688 117.417 55.808 117.177 56.048C116.921 56.304 116.793 56.616 116.793 56.984C116.793 57.352 116.921 57.656 117.177 57.896C117.417 58.152 117.721 58.28 118.089 58.28C118.457 58.28 118.769 58.152 119.025 57.896C119.265 57.656 119.385 57.352 119.385 56.984ZM128.985 56.984C128.985 56.616 128.865 56.304 128.625 56.048C128.369 55.808 128.057 55.688 127.689 55.688C127.321 55.688 127.017 55.808 126.777 56.048C126.521 56.304 126.393 56.616 126.393 56.984C126.393 57.352 126.521 57.656 126.777 57.896C127.017 58.152 127.321 58.28 127.689 58.28C128.057 58.28 128.369 58.152 128.625 57.896C128.865 57.656 128.985 57.352 128.985 56.984ZM126.153 59.216C126.153 58.976 126.073 58.768 125.913 58.592C125.737 58.432 125.529 58.352 125.289 58.352C125.049 58.352 124.849 58.432 124.689 58.592C124.513 58.768 124.425 58.976 124.425 59.216C124.425 59.456 124.513 59.656 124.689 59.816C124.849 59.992 125.049 60.08 125.289 60.08C125.529 60.08 125.737 59.992 125.913 59.816C126.073 59.656 126.153 59.456 126.153 59.216ZM121.353 59.216C121.353 58.976 121.273 58.768 121.113 58.592C120.937 58.432 120.729 58.352 120.489 58.352C120.249 58.352 120.049 58.432 119.889 58.592C119.713 58.768 119.625 58.976 119.625 59.216C119.625 59.456 119.713 59.656 119.889 59.816C120.049 59.992 120.249 60.08 120.489 60.08C120.729 60.08 120.937 59.992 121.113 59.816C121.273 59.656 121.353 59.456 121.353 59.216ZM130.953 59.216C130.953 58.976 130.865 58.768 130.689 58.592C130.529 58.432 130.329 58.352 130.089 58.352C129.849 58.352 129.649 58.432 129.489 58.592C129.313 58.768 129.225 58.976 129.225 59.216C129.225 59.456 129.313 59.656 129.489 59.816C129.649 59.992 129.849 60.08 130.089 60.08C130.329 60.08 130.529 59.992 130.689 59.816C130.865 59.656 130.953 59.456 130.953 59.216ZM133.944 61.2C133.944 60.656 133.76 60.2 133.392 59.832C133.008 59.448 132.544 59.256 132 59.256C131.456 59.256 131 59.448 130.632 59.832C130.248 60.2 130.056 60.656 130.056 61.2C130.056 61.744 130.248 62.208 130.632 62.592C131 62.96 131.456 63.144 132 63.144C132.544 63.144 133.008 62.96 133.392 62.592C133.76 62.208 133.944 61.744 133.944 61.2ZM138.744 61.2C138.744 60.656 138.56 60.2 138.192 59.832C137.808 59.448 137.344 59.256 136.8 59.256C136.256 59.256 135.8 59.448 135.432 59.832C135.048 60.2 134.856 60.656 134.856 61.2C134.856 61.744 135.048 62.208 135.432 62.592C135.8 62.96 136.256 63.144 136.8 63.144C137.344 63.144 137.808 62.96 138.192 62.592C138.56 62.208 138.744 61.744 138.744 61.2ZM143.544 61.2C143.544 60.656 143.36 60.2 142.992 59.832C142.608 59.448 142.144 59.256 141.6 59.256C141.056 59.256 140.6 59.448 140.232 59.832C139.848 60.2 139.656 60.656 139.656 61.2C139.656 61.744 139.848 62.208 140.232 62.592C140.6 62.96 141.056 63.144 141.6 63.144C142.144 63.144 142.608 62.96 142.992 62.592C143.36 62.208 143.544 61.744 143.544 61.2ZM135.696 63.432C135.696 63.064 135.576 62.76 135.336 62.52C135.08 62.264 134.768 62.136 134.4 62.136C134.032 62.136 133.728 62.264 133.488 62.52C133.232 62.76 133.104 63.064 133.104 63.432C133.104 63.8 133.232 64.112 133.488 64.368C133.728 64.608 134.032 64.728 134.4 64.728C134.768 64.728 135.08 64.608 135.336 64.368C135.576 64.112 135.696 63.8 135.696 63.432ZM140.496 63.432C140.496 63.064 140.376 62.76 140.136 62.52C139.88 62.264 139.568 62.136 139.2 62.136C138.832 62.136 138.528 62.264 138.288 62.52C138.032 62.76 137.904 63.064 137.904 63.432C137.904 63.8 138.032 64.112 138.288 64.368C138.528 64.608 138.832 64.728 139.2 64.728C139.568 64.728 139.88 64.608 140.136 64.368C140.376 64.112 140.496 63.8 140.496 63.432ZM145.296 63.432C145.296 63.064 145.168 62.76 144.912 62.52C144.672 62.264 144.368 62.136 144 62.136C143.632 62.136 143.328 62.264 143.088 62.52C142.832 62.76 142.704 63.064 142.704 63.432C142.704 63.8 142.832 64.112 143.088 64.368C143.328 64.608 143.632 64.728 144 64.728C144.368 64.728 144.672 64.608 144.912 64.368C145.168 64.112 145.296 63.8 145.296 63.432ZM133.944 65.664C133.944 65.12 133.76 64.664 133.392 64.296C133.008 63.912 132.544 63.72 132 63.72C131.456 63.72 131 63.912 130.632 64.296C130.248 64.664 130.056 65.12 130.056 65.664C130.056 66.208 130.248 66.672 130.632 67.056C131 67.424 131.456 67.608 132 67.608C132.544 67.608 133.008 67.424 133.392 67.056C133.76 66.672 133.944 66.208 133.944 65.664ZM143.544 65.664C143.544 65.12 143.36 64.664 142.992 64.296C142.608 63.912 142.144 63.72 141.6 63.72C141.056 63.72 140.6 63.912 140.232 64.296C139.848 64.664 139.656 65.12 139.656 65.664C139.656 66.208 139.848 66.672 140.232 67.056C140.6 67.424 141.056 67.608 141.6 67.608C142.144 67.608 142.608 67.424 142.992 67.056C143.36 66.672 143.544 66.208 143.544 65.664ZM138.744 65.664C138.744 65.12 138.56 64.664 138.192 64.296C137.808 63.912 137.344 63.72 136.8 63.72C136.256 63.72 135.8 63.912 135.432 64.296C135.048 64.664 134.856 65.12 134.856 65.664C134.856 66.208 135.048 66.672 135.432 67.056C135.8 67.424 136.256 67.608 136.8 67.608C137.344 67.608 137.808 67.424 138.192 67.056C138.56 66.672 138.744 66.208 138.744 65.664ZM135.696 67.896C135.696 67.528 135.576 67.224 135.336 66.984C135.08 66.728 134.768 66.6 134.4 66.6C134.032 66.6 133.728 66.728 133.488 66.984C133.232 67.224 133.104 67.528 133.104 67.896C133.104 68.264 133.232 68.576 133.488 68.832C133.728 69.072 134.032 69.192 134.4 69.192C134.768 69.192 135.08 69.072 135.336 68.832C135.576 68.576 135.696 68.264 135.696 67.896ZM145.296 67.896C145.296 67.528 145.168 67.224 144.912 66.984C144.672 66.728 144.368 66.6 144 66.6C143.632 66.6 143.328 66.728 143.088 66.984C142.832 67.224 142.704 67.528 142.704 67.896C142.704 68.264 142.832 68.576 143.088 68.832C143.328 69.072 143.632 69.192 144 69.192C144.368 69.192 144.672 69.072 144.912 68.832C145.168 68.576 145.296 68.264 145.296 67.896ZM140.496 67.896C140.496 67.528 140.376 67.224 140.136 66.984C139.88 66.728 139.568 66.6 139.2 66.6C138.832 66.6 138.528 66.728 138.288 66.984C138.032 67.224 137.904 67.528 137.904 67.896C137.904 68.264 138.032 68.576 138.288 68.832C138.528 69.072 138.832 69.192 139.2 69.192C139.568 69.192 139.88 69.072 140.136 68.832C140.376 68.576 140.496 68.264 140.496 67.896ZM133.944 70.128C133.944 69.584 133.76 69.128 133.392 68.76C133.008 68.376 132.544 68.184 132 68.184C131.456 68.184 131 68.376 130.632 68.76C130.248 69.128 130.056 69.584 130.056 70.128C130.056 70.672 130.248 71.136 130.632 71.52C131 71.888 131.456 72.072 132 72.072C132.544 72.072 133.008 71.888 133.392 71.52C133.76 71.136 133.944 70.672 133.944 70.128ZM143.544 70.128C143.544 69.584 143.36 69.128 142.992 68.76C142.608 68.376 142.144 68.184 141.6 68.184C141.056 68.184 140.6 68.376 140.232 68.76C139.848 69.128 139.656 69.584 139.656 70.128C139.656 70.672 139.848 71.136 140.232 71.52C140.6 71.888 141.056 72.072 141.6 72.072C142.144 72.072 142.608 71.888 142.992 71.52C143.36 71.136 143.544 70.672 143.544 70.128ZM138.744 70.128C138.744 69.584 138.56 69.128 138.192 68.76C137.808 68.376 137.344 68.184 136.8 68.184C136.256 68.184 135.8 68.376 135.432 68.76C135.048 69.128 134.856 69.584 134.856 70.128C134.856 70.672 135.048 71.136 135.432 71.52C135.8 71.888 136.256 72.072 136.8 72.072C137.344 72.072 137.808 71.888 138.192 71.52C138.56 71.136 138.744 70.672 138.744 70.128ZM135.696 72.36C135.696 71.992 135.576 71.688 135.336 71.448C135.08 71.192 134.768 71.064 134.4 71.064C134.032 71.064 133.728 71.192 133.488 71.448C133.232 71.688 133.104 71.992 133.104 72.36C133.104 72.728 133.232 73.04 133.488 73.296C133.728 73.536 134.032 73.656 134.4 73.656C134.768 73.656 135.08 73.536 135.336 73.296C135.576 73.04 135.696 72.728 135.696 72.36ZM145.296 72.36C145.296 71.992 145.168 71.688 144.912 71.448C144.672 71.192 144.368 71.064 144 71.064C143.632 71.064 143.328 71.192 143.088 71.448C142.832 71.688 142.704 71.992 142.704 72.36C142.704 72.728 142.832 73.04 143.088 73.296C143.328 73.536 143.632 73.656 144 73.656C144.368 73.656 144.672 73.536 144.912 73.296C145.168 73.04 145.296 72.728 145.296 72.36ZM140.496 72.36C140.496 71.992 140.376 71.688 140.136 71.448C139.88 71.192 139.568 71.064 139.2 71.064C138.832 71.064 138.528 71.192 138.288 71.448C138.032 71.688 137.904 71.992 137.904 72.36C137.904 72.728 138.032 73.04 138.288 73.296C138.528 73.536 138.832 73.656 139.2 73.656C139.568 73.656 139.88 73.536 140.136 73.296C140.376 73.04 140.496 72.728 140.496 72.36ZM138.744 74.592C138.744 74.048 138.56 73.592 138.192 73.224C137.808 72.84 137.344 72.648 136.8 72.648C136.256 72.648 135.8 72.84 135.432 73.224C135.048 73.592 134.856 74.048 134.856 74.592C134.856 75.136 135.048 75.6 135.432 75.984C135.8 76.352 136.256 76.536 136.8 76.536C137.344 76.536 137.808 76.352 138.192 75.984C138.56 75.6 138.744 75.136 138.744 74.592ZM143.544 74.592C143.544 74.048 143.36 73.592 142.992 73.224C142.608 72.84 142.144 72.648 141.6 72.648C141.056 72.648 140.6 72.84 140.232 73.224C139.848 73.592 139.656 74.048 139.656 74.592C139.656 75.136 139.848 75.6 140.232 75.984C140.6 76.352 141.056 76.536 141.6 76.536C142.144 76.536 142.608 76.352 142.992 75.984C143.36 75.6 143.544 75.136 143.544 74.592ZM133.944 74.592C133.944 74.048 133.76 73.592 133.392 73.224C133.008 72.84 132.544 72.648 132 72.648C131.456 72.648 131 72.84 130.632 73.224C130.248 73.592 130.056 74.048 130.056 74.592C130.056 75.136 130.248 75.6 130.632 75.984C131 76.352 131.456 76.536 132 76.536C132.544 76.536 133.008 76.352 133.392 75.984C133.76 75.6 133.944 75.136 133.944 74.592ZM140.496 76.824C140.496 76.456 140.376 76.152 140.136 75.912C139.88 75.656 139.568 75.528 139.2 75.528C138.832 75.528 138.528 75.656 138.288 75.912C138.032 76.152 137.904 76.456 137.904 76.824C137.904 77.192 138.032 77.504 138.288 77.76C138.528 78 138.832 78.12 139.2 78.12C139.568 78.12 139.88 78 140.136 77.76C140.376 77.504 140.496 77.192 140.496 76.824ZM145.296 76.824C145.296 76.456 145.168 76.152 144.912 75.912C144.672 75.656 144.368 75.528 144 75.528C143.632 75.528 143.328 75.656 143.088 75.912C142.832 76.152 142.704 76.456 142.704 76.824C142.704 77.192 142.832 77.504 143.088 77.76C143.328 78 143.632 78.12 144 78.12C144.368 78.12 144.672 78 144.912 77.76C145.168 77.504 145.296 77.192 145.296 76.824ZM135.696 76.824C135.696 76.456 135.576 76.152 135.336 75.912C135.08 75.656 134.768 75.528 134.4 75.528C134.032 75.528 133.728 75.656 133.488 75.912C133.232 76.152 133.104 76.456 133.104 76.824C133.104 77.192 133.232 77.504 133.488 77.76C133.728 78 134.032 78.12 134.4 78.12C134.768 78.12 135.08 78 135.336 77.76C135.576 77.504 135.696 77.192 135.696 76.824ZM138.744 79.056C138.744 78.512 138.56 78.056 138.192 77.688C137.808 77.304 137.344 77.112 136.8 77.112C136.256 77.112 135.8 77.304 135.432 77.688C135.048 78.056 134.856 78.512 134.856 79.056C134.856 79.6 135.048 80.064 135.432 80.448C135.8 80.816 136.256 81 136.8 81C137.344 81 137.808 80.816 138.192 80.448C138.56 80.064 138.744 79.6 138.744 79.056ZM133.944 79.056C133.944 78.512 133.76 78.056 133.392 77.688C133.008 77.304 132.544 77.112 132 77.112C131.456 77.112 131 77.304 130.632 77.688C130.248 78.056 130.056 78.512 130.056 79.056C130.056 79.6 130.248 80.064 130.632 80.448C131 80.816 131.456 81 132 81C132.544 81 133.008 80.816 133.392 80.448C133.76 80.064 133.944 79.6 133.944 79.056ZM143.544 79.056C143.544 78.512 143.36 78.056 142.992 77.688C142.608 77.304 142.144 77.112 141.6 77.112C141.056 77.112 140.6 77.304 140.232 77.688C139.848 78.056 139.656 78.512 139.656 79.056C139.656 79.6 139.848 80.064 140.232 80.448C140.6 80.816 141.056 81 141.6 81C142.144 81 142.608 80.816 142.992 80.448C143.36 80.064 143.544 79.6 143.544 79.056ZM140.496 81.288C140.496 80.92 140.376 80.616 140.136 80.376C139.88 80.12 139.568 79.992 139.2 79.992C138.832 79.992 138.528 80.12 138.288 80.376C138.032 80.616 137.904 80.92 137.904 81.288C137.904 81.656 138.032 81.968 138.288 82.224C138.528 82.464 138.832 82.584 139.2 82.584C139.568 82.584 139.88 82.464 140.136 82.224C140.376 81.968 140.496 81.656 140.496 81.288ZM135.696 81.288C135.696 80.92 135.576 80.616 135.336 80.376C135.08 80.12 134.768 79.992 134.4 79.992C134.032 79.992 133.728 80.12 133.488 80.376C133.232 80.616 133.104 80.92 133.104 81.288C133.104 81.656 133.232 81.968 133.488 82.224C133.728 82.464 134.032 82.584 134.4 82.584C134.768 82.584 135.08 82.464 135.336 82.224C135.576 81.968 135.696 81.656 135.696 81.288ZM145.296 81.288C145.296 80.92 145.168 80.616 144.912 80.376C144.672 80.12 144.368 79.992 144 79.992C143.632 79.992 143.328 80.12 143.088 80.376C142.832 80.616 142.704 80.92 142.704 81.288C142.704 81.656 142.832 81.968 143.088 82.224C143.328 82.464 143.632 82.584 144 82.584C144.368 82.584 144.672 82.464 144.912 82.224C145.168 81.968 145.296 81.656 145.296 81.288ZM138.744 83.52C138.744 82.976 138.56 82.52 138.192 82.152C137.808 81.768 137.344 81.576 136.8 81.576C136.256 81.576 135.8 81.768 135.432 82.152C135.048 82.52 134.856 82.976 134.856 83.52C134.856 84.064 135.048 84.52 135.432 84.888C135.8 85.272 136.256 85.464 136.8 85.464C137.344 85.464 137.808 85.272 138.192 84.888C138.56 84.52 138.744 84.064 138.744 83.52ZM133.944 83.52C133.944 82.976 133.76 82.52 133.392 82.152C133.008 81.768 132.544 81.576 132 81.576C131.456 81.576 131 81.768 130.632 82.152C130.248 82.52 130.056 82.976 130.056 83.52C130.056 84.064 130.248 84.52 130.632 84.888C131 85.272 131.456 85.464 132 85.464C132.544 85.464 133.008 85.272 133.392 84.888C133.76 84.52 133.944 84.064 133.944 83.52ZM143.544 83.52C143.544 82.976 143.36 82.52 142.992 82.152C142.608 81.768 142.144 81.576 141.6 81.576C141.056 81.576 140.6 81.768 140.232 82.152C139.848 82.52 139.656 82.976 139.656 83.52C139.656 84.064 139.848 84.52 140.232 84.888C140.6 85.272 141.056 85.464 141.6 85.464C142.144 85.464 142.608 85.272 142.992 84.888C143.36 84.52 143.544 84.064 143.544 83.52ZM140.496 85.752C140.496 85.384 140.376 85.072 140.136 84.816C139.88 84.576 139.568 84.456 139.2 84.456C138.832 84.456 138.528 84.576 138.288 84.816C138.032 85.072 137.904 85.384 137.904 85.752C137.904 86.12 138.032 86.424 138.288 86.664C138.528 86.92 138.832 87.048 139.2 87.048C139.568 87.048 139.88 86.92 140.136 86.664C140.376 86.424 140.496 86.12 140.496 85.752ZM135.696 85.752C135.696 85.384 135.576 85.072 135.336 84.816C135.08 84.576 134.768 84.456 134.4 84.456C134.032 84.456 133.728 84.576 133.488 84.816C133.232 85.072 133.104 85.384 133.104 85.752C133.104 86.12 133.232 86.424 133.488 86.664C133.728 86.92 134.032 87.048 134.4 87.048C134.768 87.048 135.08 86.92 135.336 86.664C135.576 86.424 135.696 86.12 135.696 85.752ZM145.296 85.752C145.296 85.384 145.168 85.072 144.912 84.816C144.672 84.576 144.368 84.456 144 84.456C143.632 84.456 143.328 84.576 143.088 84.816C142.832 85.072 142.704 85.384 142.704 85.752C142.704 86.12 142.832 86.424 143.088 86.664C143.328 86.92 143.632 87.048 144 87.048C144.368 87.048 144.672 86.92 144.912 86.664C145.168 86.424 145.296 86.12 145.296 85.752ZM138.744 87.984C138.744 87.44 138.56 86.976 138.192 86.592C137.808 86.224 137.344 86.04 136.8 86.04C136.256 86.04 135.8 86.224 135.432 86.592C135.048 86.976 134.856 87.44 134.856 87.984C134.856 88.528 135.048 88.984 135.432 89.352C135.8 89.736 136.256 89.928 136.8 89.928C137.344 89.928 137.808 89.736 138.192 89.352C138.56 88.984 138.744 88.528 138.744 87.984ZM133.944 87.984C133.944 87.44 133.76 86.976 133.392 86.592C133.008 86.224 132.544 86.04 132 86.04C131.456 86.04 131 86.224 130.632 86.592C130.248 86.976 130.056 87.44 130.056 87.984C130.056 88.528 130.248 88.984 130.632 89.352C131 89.736 131.456 89.928 132 89.928C132.544 89.928 133.008 89.736 133.392 89.352C133.76 88.984 133.944 88.528 133.944 87.984ZM143.544 87.984C143.544 87.44 143.36 86.976 142.992 86.592C142.608 86.224 142.144 86.04 141.6 86.04C141.056 86.04 140.6 86.224 140.232 86.592C139.848 86.976 139.656 87.44 139.656 87.984C139.656 88.528 139.848 88.984 140.232 89.352C140.6 89.736 141.056 89.928 141.6 89.928C142.144 89.928 142.608 89.736 142.992 89.352C143.36 88.984 143.544 88.528 143.544 87.984ZM140.496 90.216C140.496 89.848 140.376 89.536 140.136 89.28C139.88 89.04 139.568 88.92 139.2 88.92C138.832 88.92 138.528 89.04 138.288 89.28C138.032 89.536 137.904 89.848 137.904 90.216C137.904 90.584 138.032 90.888 138.288 91.128C138.528 91.384 138.832 91.512 139.2 91.512C139.568 91.512 139.88 91.384 140.136 91.128C140.376 90.888 140.496 90.584 140.496 90.216ZM135.696 90.216C135.696 89.848 135.576 89.536 135.336 89.28C135.08 89.04 134.768 88.92 134.4 88.92C134.032 88.92 133.728 89.04 133.488 89.28C133.232 89.536 133.104 89.848 133.104 90.216C133.104 90.584 133.232 90.888 133.488 91.128C133.728 91.384 134.032 91.512 134.4 91.512C134.768 91.512 135.08 91.384 135.336 91.128C135.576 90.888 135.696 90.584 135.696 90.216ZM145.296 90.216C145.296 89.848 145.168 89.536 144.912 89.28C144.672 89.04 144.368 88.92 144 88.92C143.632 88.92 143.328 89.04 143.088 89.28C142.832 89.536 142.704 89.848 142.704 90.216C142.704 90.584 142.832 90.888 143.088 91.128C143.328 91.384 143.632 91.512 144 91.512C144.368 91.512 144.672 91.384 144.912 91.128C145.168 90.888 145.296 90.584 145.296 90.216ZM119.385 61.2C119.385 60.832 119.265 60.528 119.025 60.288C118.769 60.032 118.457 59.904 118.089 59.904C117.721 59.904 117.417 60.032 117.177 60.288C116.921 60.528 116.793 60.832 116.793 61.2C116.793 61.568 116.921 61.88 117.177 62.136C117.417 62.376 117.721 62.496 118.089 62.496C118.457 62.496 118.769 62.376 119.025 62.136C119.265 61.88 119.385 61.568 119.385 61.2ZM124.185 61.2C124.185 60.832 124.065 60.528 123.825 60.288C123.569 60.032 123.257 59.904 122.889 59.904C122.521 59.904 122.217 60.032 121.977 60.288C121.721 60.528 121.593 60.832 121.593 61.2C121.593 61.568 121.721 61.88 121.977 62.136C122.217 62.376 122.521 62.496 122.889 62.496C123.257 62.496 123.569 62.376 123.825 62.136C124.065 61.88 124.185 61.568 124.185 61.2ZM128.985 61.2C128.985 60.832 128.865 60.528 128.625 60.288C128.369 60.032 128.057 59.904 127.689 59.904C127.321 59.904 127.017 60.032 126.777 60.288C126.521 60.528 126.393 60.832 126.393 61.2C126.393 61.568 126.521 61.88 126.777 62.136C127.017 62.376 127.321 62.496 127.689 62.496C128.057 62.496 128.369 62.376 128.625 62.136C128.865 61.88 128.985 61.568 128.985 61.2ZM121.353 63.432C121.353 63.192 121.273 62.992 121.113 62.832C120.937 62.656 120.729 62.568 120.489 62.568C120.249 62.568 120.049 62.656 119.889 62.832C119.713 62.992 119.625 63.192 119.625 63.432C119.625 63.672 119.713 63.88 119.889 64.056C120.049 64.216 120.249 64.296 120.489 64.296C120.729 64.296 120.937 64.216 121.113 64.056C121.273 63.88 121.353 63.672 121.353 63.432ZM126.153 63.432C126.153 63.192 126.073 62.992 125.913 62.832C125.737 62.656 125.529 62.568 125.289 62.568C125.049 62.568 124.849 62.656 124.689 62.832C124.513 62.992 124.425 63.192 124.425 63.432C124.425 63.672 124.513 63.88 124.689 64.056C124.849 64.216 125.049 64.296 125.289 64.296C125.529 64.296 125.737 64.216 125.913 64.056C126.073 63.88 126.153 63.672 126.153 63.432ZM130.953 63.432C130.953 63.192 130.865 62.992 130.689 62.832C130.529 62.656 130.329 62.568 130.089 62.568C129.849 62.568 129.649 62.656 129.489 62.832C129.313 62.992 129.225 63.192 129.225 63.432C129.225 63.672 129.313 63.88 129.489 64.056C129.649 64.216 129.849 64.296 130.089 64.296C130.329 64.296 130.529 64.216 130.689 64.056C130.865 63.88 130.953 63.672 130.953 63.432ZM119.385 65.664C119.385 65.296 119.265 64.992 119.025 64.752C118.769 64.496 118.457 64.368 118.089 64.368C117.721 64.368 117.417 64.496 117.177 64.752C116.921 64.992 116.793 65.296 116.793 65.664C116.793 66.032 116.921 66.344 117.177 66.6C117.417 66.84 117.721 66.96 118.089 66.96C118.457 66.96 118.769 66.84 119.025 66.6C119.265 66.344 119.385 66.032 119.385 65.664ZM128.985 65.664C128.985 65.296 128.865 64.992 128.625 64.752C128.369 64.496 128.057 64.368 127.689 64.368C127.321 64.368 127.017 64.496 126.777 64.752C126.521 64.992 126.393 65.296 126.393 65.664C126.393 66.032 126.521 66.344 126.777 66.6C127.017 66.84 127.321 66.96 127.689 66.96C128.057 66.96 128.369 66.84 128.625 66.6C128.865 66.344 128.985 66.032 128.985 65.664ZM124.185 65.664C124.185 65.296 124.065 64.992 123.825 64.752C123.569 64.496 123.257 64.368 122.889 64.368C122.521 64.368 122.217 64.496 121.977 64.752C121.721 64.992 121.593 65.296 121.593 65.664C121.593 66.032 121.721 66.344 121.977 66.6C122.217 66.84 122.521 66.96 122.889 66.96C123.257 66.96 123.569 66.84 123.825 66.6C124.065 66.344 124.185 66.032 124.185 65.664ZM121.353 67.896C121.353 67.656 121.273 67.456 121.113 67.296C120.937 67.12 120.729 67.032 120.489 67.032C120.249 67.032 120.049 67.12 119.889 67.296C119.713 67.456 119.625 67.656 119.625 67.896C119.625 68.136 119.713 68.344 119.889 68.52C120.049 68.68 120.249 68.76 120.489 68.76C120.729 68.76 120.937 68.68 121.113 68.52C121.273 68.344 121.353 68.136 121.353 67.896ZM130.953 67.896C130.953 67.656 130.865 67.456 130.689 67.296C130.529 67.12 130.329 67.032 130.089 67.032C129.849 67.032 129.649 67.12 129.489 67.296C129.313 67.456 129.225 67.656 129.225 67.896C129.225 68.136 129.313 68.344 129.489 68.52C129.649 68.68 129.849 68.76 130.089 68.76C130.329 68.76 130.529 68.68 130.689 68.52C130.865 68.344 130.953 68.136 130.953 67.896ZM126.153 67.896C126.153 67.656 126.073 67.456 125.913 67.296C125.737 67.12 125.529 67.032 125.289 67.032C125.049 67.032 124.849 67.12 124.689 67.296C124.513 67.456 124.425 67.656 124.425 67.896C124.425 68.136 124.513 68.344 124.689 68.52C124.849 68.68 125.049 68.76 125.289 68.76C125.529 68.76 125.737 68.68 125.913 68.52C126.073 68.344 126.153 68.136 126.153 67.896ZM119.385 70.128C119.385 69.76 119.265 69.456 119.025 69.216C118.769 68.96 118.457 68.832 118.089 68.832C117.721 68.832 117.417 68.96 117.177 69.216C116.921 69.456 116.793 69.76 116.793 70.128C116.793 70.496 116.921 70.808 117.177 71.064C117.417 71.304 117.721 71.424 118.089 71.424C118.457 71.424 118.769 71.304 119.025 71.064C119.265 70.808 119.385 70.496 119.385 70.128ZM128.985 70.128C128.985 69.76 128.865 69.456 128.625 69.216C128.369 68.96 128.057 68.832 127.689 68.832C127.321 68.832 127.017 68.96 126.777 69.216C126.521 69.456 126.393 69.76 126.393 70.128C126.393 70.496 126.521 70.808 126.777 71.064C127.017 71.304 127.321 71.424 127.689 71.424C128.057 71.424 128.369 71.304 128.625 71.064C128.865 70.808 128.985 70.496 128.985 70.128ZM124.185 70.128C124.185 69.76 124.065 69.456 123.825 69.216C123.569 68.96 123.257 68.832 122.889 68.832C122.521 68.832 122.217 68.96 121.977 69.216C121.721 69.456 121.593 69.76 121.593 70.128C121.593 70.496 121.721 70.808 121.977 71.064C122.217 71.304 122.521 71.424 122.889 71.424C123.257 71.424 123.569 71.304 123.825 71.064C124.065 70.808 124.185 70.496 124.185 70.128ZM121.353 72.36C121.353 72.12 121.273 71.92 121.113 71.76C120.937 71.584 120.729 71.496 120.489 71.496C120.249 71.496 120.049 71.584 119.889 71.76C119.713 71.92 119.625 72.12 119.625 72.36C119.625 72.6 119.713 72.808 119.889 72.984C120.049 73.144 120.249 73.224 120.489 73.224C120.729 73.224 120.937 73.144 121.113 72.984C121.273 72.808 121.353 72.6 121.353 72.36ZM130.953 72.36C130.953 72.12 130.865 71.92 130.689 71.76C130.529 71.584 130.329 71.496 130.089 71.496C129.849 71.496 129.649 71.584 129.489 71.76C129.313 71.92 129.225 72.12 129.225 72.36C129.225 72.6 129.313 72.808 129.489 72.984C129.649 73.144 129.849 73.224 130.089 73.224C130.329 73.224 130.529 73.144 130.689 72.984C130.865 72.808 130.953 72.6 130.953 72.36ZM126.153 72.36C126.153 72.12 126.073 71.92 125.913 71.76C125.737 71.584 125.529 71.496 125.289 71.496C125.049 71.496 124.849 71.584 124.689 71.76C124.513 71.92 124.425 72.12 124.425 72.36C124.425 72.6 124.513 72.808 124.689 72.984C124.849 73.144 125.049 73.224 125.289 73.224C125.529 73.224 125.737 73.144 125.913 72.984C126.073 72.808 126.153 72.6 126.153 72.36ZM124.185 74.592C124.185 74.224 124.065 73.92 123.825 73.68C123.569 73.424 123.257 73.296 122.889 73.296C122.521 73.296 122.217 73.424 121.977 73.68C121.721 73.92 121.593 74.224 121.593 74.592C121.593 74.96 121.721 75.272 121.977 75.528C122.217 75.768 122.521 75.888 122.889 75.888C123.257 75.888 123.569 75.768 123.825 75.528C124.065 75.272 124.185 74.96 124.185 74.592ZM128.985 74.592C128.985 74.224 128.865 73.92 128.625 73.68C128.369 73.424 128.057 73.296 127.689 73.296C127.321 73.296 127.017 73.424 126.777 73.68C126.521 73.92 126.393 74.224 126.393 74.592C126.393 74.96 126.521 75.272 126.777 75.528C127.017 75.768 127.321 75.888 127.689 75.888C128.057 75.888 128.369 75.768 128.625 75.528C128.865 75.272 128.985 74.96 128.985 74.592ZM119.385 74.592C119.385 74.224 119.265 73.92 119.025 73.68C118.769 73.424 118.457 73.296 118.089 73.296C117.721 73.296 117.417 73.424 117.177 73.68C116.921 73.92 116.793 74.224 116.793 74.592C116.793 74.96 116.921 75.272 117.177 75.528C117.417 75.768 117.721 75.888 118.089 75.888C118.457 75.888 118.769 75.768 119.025 75.528C119.265 75.272 119.385 74.96 119.385 74.592ZM126.153 76.824C126.153 76.584 126.073 76.384 125.913 76.224C125.737 76.048 125.529 75.96 125.289 75.96C125.049 75.96 124.849 76.048 124.689 76.224C124.513 76.384 124.425 76.584 124.425 76.824C124.425 77.064 124.513 77.272 124.689 77.448C124.849 77.608 125.049 77.688 125.289 77.688C125.529 77.688 125.737 77.608 125.913 77.448C126.073 77.272 126.153 77.064 126.153 76.824ZM130.953 76.824C130.953 76.584 130.865 76.384 130.689 76.224C130.529 76.048 130.329 75.96 130.089 75.96C129.849 75.96 129.649 76.048 129.489 76.224C129.313 76.384 129.225 76.584 129.225 76.824C129.225 77.064 129.313 77.272 129.489 77.448C129.649 77.608 129.849 77.688 130.089 77.688C130.329 77.688 130.529 77.608 130.689 77.448C130.865 77.272 130.953 77.064 130.953 76.824ZM121.353 76.824C121.353 76.584 121.273 76.384 121.113 76.224C120.937 76.048 120.729 75.96 120.489 75.96C120.249 75.96 120.049 76.048 119.889 76.224C119.713 76.384 119.625 76.584 119.625 76.824C119.625 77.064 119.713 77.272 119.889 77.448C120.049 77.608 120.249 77.688 120.489 77.688C120.729 77.688 120.937 77.608 121.113 77.448C121.273 77.272 121.353 77.064 121.353 76.824ZM124.185 79.056C124.185 78.688 124.065 78.384 123.825 78.144C123.569 77.888 123.257 77.76 122.889 77.76C122.521 77.76 122.217 77.888 121.977 78.144C121.721 78.384 121.593 78.688 121.593 79.056C121.593 79.424 121.721 79.736 121.977 79.992C122.217 80.232 122.521 80.352 122.889 80.352C123.257 80.352 123.569 80.232 123.825 79.992C124.065 79.736 124.185 79.424 124.185 79.056ZM119.385 79.056C119.385 78.688 119.265 78.384 119.025 78.144C118.769 77.888 118.457 77.76 118.089 77.76C117.721 77.76 117.417 77.888 117.177 78.144C116.921 78.384 116.793 78.688 116.793 79.056C116.793 79.424 116.921 79.736 117.177 79.992C117.417 80.232 117.721 80.352 118.089 80.352C118.457 80.352 118.769 80.232 119.025 79.992C119.265 79.736 119.385 79.424 119.385 79.056ZM128.985 79.056C128.985 78.688 128.865 78.384 128.625 78.144C128.369 77.888 128.057 77.76 127.689 77.76C127.321 77.76 127.017 77.888 126.777 78.144C126.521 78.384 126.393 78.688 126.393 79.056C126.393 79.424 126.521 79.736 126.777 79.992C127.017 80.232 127.321 80.352 127.689 80.352C128.057 80.352 128.369 80.232 128.625 79.992C128.865 79.736 128.985 79.424 128.985 79.056ZM126.153 81.288C126.153 81.048 126.073 80.848 125.913 80.688C125.737 80.512 125.529 80.424 125.289 80.424C125.049 80.424 124.849 80.512 124.689 80.688C124.513 80.848 124.425 81.048 124.425 81.288C124.425 81.528 124.513 81.736 124.689 81.912C124.849 82.072 125.049 82.152 125.289 82.152C125.529 82.152 125.737 82.072 125.913 81.912C126.073 81.736 126.153 81.528 126.153 81.288ZM121.353 81.288C121.353 81.048 121.273 80.848 121.113 80.688C120.937 80.512 120.729 80.424 120.489 80.424C120.249 80.424 120.049 80.512 119.889 80.688C119.713 80.848 119.625 81.048 119.625 81.288C119.625 81.528 119.713 81.736 119.889 81.912C120.049 82.072 120.249 82.152 120.489 82.152C120.729 82.152 120.937 82.072 121.113 81.912C121.273 81.736 121.353 81.528 121.353 81.288ZM130.953 81.288C130.953 81.048 130.865 80.848 130.689 80.688C130.529 80.512 130.329 80.424 130.089 80.424C129.849 80.424 129.649 80.512 129.489 80.688C129.313 80.848 129.225 81.048 129.225 81.288C129.225 81.528 129.313 81.736 129.489 81.912C129.649 82.072 129.849 82.152 130.089 82.152C130.329 82.152 130.529 82.072 130.689 81.912C130.865 81.736 130.953 81.528 130.953 81.288ZM124.185 83.52C124.185 83.152 124.065 82.848 123.825 82.608C123.569 82.352 123.257 82.224 122.889 82.224C122.521 82.224 122.217 82.352 121.977 82.608C121.721 82.848 121.593 83.152 121.593 83.52C121.593 83.888 121.721 84.192 121.977 84.432C122.217 84.688 122.521 84.816 122.889 84.816C123.257 84.816 123.569 84.688 123.825 84.432C124.065 84.192 124.185 83.888 124.185 83.52ZM119.385 83.52C119.385 83.152 119.265 82.848 119.025 82.608C118.769 82.352 118.457 82.224 118.089 82.224C117.721 82.224 117.417 82.352 117.177 82.608C116.921 82.848 116.793 83.152 116.793 83.52C116.793 83.888 116.921 84.192 117.177 84.432C117.417 84.688 117.721 84.816 118.089 84.816C118.457 84.816 118.769 84.688 119.025 84.432C119.265 84.192 119.385 83.888 119.385 83.52ZM128.985 83.52C128.985 83.152 128.865 82.848 128.625 82.608C128.369 82.352 128.057 82.224 127.689 82.224C127.321 82.224 127.017 82.352 126.777 82.608C126.521 82.848 126.393 83.152 126.393 83.52C126.393 83.888 126.521 84.192 126.777 84.432C127.017 84.688 127.321 84.816 127.689 84.816C128.057 84.816 128.369 84.688 128.625 84.432C128.865 84.192 128.985 83.888 128.985 83.52ZM126.153 85.752C126.153 85.512 126.073 85.304 125.913 85.128C125.737 84.968 125.529 84.888 125.289 84.888C125.049 84.888 124.849 84.968 124.689 85.128C124.513 85.304 124.425 85.512 124.425 85.752C124.425 85.992 124.513 86.192 124.689 86.352C124.849 86.528 125.049 86.616 125.289 86.616C125.529 86.616 125.737 86.528 125.913 86.352C126.073 86.192 126.153 85.992 126.153 85.752ZM121.353 85.752C121.353 85.512 121.273 85.304 121.113 85.128C120.937 84.968 120.729 84.888 120.489 84.888C120.249 84.888 120.049 84.968 119.889 85.128C119.713 85.304 119.625 85.512 119.625 85.752C119.625 85.992 119.713 86.192 119.889 86.352C120.049 86.528 120.249 86.616 120.489 86.616C120.729 86.616 120.937 86.528 121.113 86.352C121.273 86.192 121.353 85.992 121.353 85.752ZM130.953 85.752C130.953 85.512 130.865 85.304 130.689 85.128C130.529 84.968 130.329 84.888 130.089 84.888C129.849 84.888 129.649 84.968 129.489 85.128C129.313 85.304 129.225 85.512 129.225 85.752C129.225 85.992 129.313 86.192 129.489 86.352C129.649 86.528 129.849 86.616 130.089 86.616C130.329 86.616 130.529 86.528 130.689 86.352C130.865 86.192 130.953 85.992 130.953 85.752ZM124.185 87.984C124.185 87.616 124.065 87.304 123.825 87.048C123.569 86.808 123.257 86.688 122.889 86.688C122.521 86.688 122.217 86.808 121.977 87.048C121.721 87.304 121.593 87.616 121.593 87.984C121.593 88.352 121.721 88.656 121.977 88.896C122.217 89.152 122.521 89.28 122.889 89.28C123.257 89.28 123.569 89.152 123.825 88.896C124.065 88.656 124.185 88.352 124.185 87.984ZM119.385 87.984C119.385 87.616 119.265 87.304 119.025 87.048C118.769 86.808 118.457 86.688 118.089 86.688C117.721 86.688 117.417 86.808 117.177 87.048C116.921 87.304 116.793 87.616 116.793 87.984C116.793 88.352 116.921 88.656 117.177 88.896C117.417 89.152 117.721 89.28 118.089 89.28C118.457 89.28 118.769 89.152 119.025 88.896C119.265 88.656 119.385 88.352 119.385 87.984ZM128.985 87.984C128.985 87.616 128.865 87.304 128.625 87.048C128.369 86.808 128.057 86.688 127.689 86.688C127.321 86.688 127.017 86.808 126.777 87.048C126.521 87.304 126.393 87.616 126.393 87.984C126.393 88.352 126.521 88.656 126.777 88.896C127.017 89.152 127.321 89.28 127.689 89.28C128.057 89.28 128.369 89.152 128.625 88.896C128.865 88.656 128.985 88.352 128.985 87.984ZM126.153 90.216C126.153 89.976 126.073 89.768 125.913 89.592C125.737 89.432 125.529 89.352 125.289 89.352C125.049 89.352 124.849 89.432 124.689 89.592C124.513 89.768 124.425 89.976 124.425 90.216C124.425 90.456 124.513 90.656 124.689 90.816C124.849 90.992 125.049 91.08 125.289 91.08C125.529 91.08 125.737 90.992 125.913 90.816C126.073 90.656 126.153 90.456 126.153 90.216ZM121.353 90.216C121.353 89.976 121.273 89.768 121.113 89.592C120.937 89.432 120.729 89.352 120.489 89.352C120.249 89.352 120.049 89.432 119.889 89.592C119.713 89.768 119.625 89.976 119.625 90.216C119.625 90.456 119.713 90.656 119.889 90.816C120.049 90.992 120.249 91.08 120.489 91.08C120.729 91.08 120.937 90.992 121.113 90.816C121.273 90.656 121.353 90.456 121.353 90.216ZM130.953 90.216C130.953 89.976 130.865 89.768 130.689 89.592C130.529 89.432 130.329 89.352 130.089 89.352C129.849 89.352 129.649 89.432 129.489 89.592C129.313 89.768 129.225 89.976 129.225 90.216C129.225 90.456 129.313 90.656 129.489 90.816C129.649 90.992 129.849 91.08 130.089 91.08C130.329 91.08 130.529 90.992 130.689 90.816C130.865 90.656 130.953 90.456 130.953 90.216ZM105.043 61.2C105.043 60.96 104.963 60.76 104.803 60.6C104.627 60.424 104.419 60.336 104.179 60.336C103.939 60.336 103.739 60.424 103.579 60.6C103.403 60.76 103.315 60.96 103.315 61.2C103.315 61.44 103.403 61.648 103.579 61.824C103.739 61.984 103.939 62.064 104.179 62.064C104.419 62.064 104.627 61.984 104.803 61.824C104.963 61.648 105.043 61.44 105.043 61.2ZM109.843 61.2C109.843 60.96 109.763 60.76 109.603 60.6C109.427 60.424 109.219 60.336 108.979 60.336C108.739 60.336 108.539 60.424 108.379 60.6C108.203 60.76 108.115 60.96 108.115 61.2C108.115 61.44 108.203 61.648 108.379 61.824C108.539 61.984 108.739 62.064 108.979 62.064C109.219 62.064 109.427 61.984 109.603 61.824C109.763 61.648 109.843 61.44 109.843 61.2ZM114.643 61.2C114.643 60.96 114.563 60.76 114.403 60.6C114.227 60.424 114.019 60.336 113.779 60.336C113.539 60.336 113.339 60.424 113.179 60.6C113.003 60.76 112.915 60.96 112.915 61.2C112.915 61.44 113.003 61.648 113.179 61.824C113.339 61.984 113.539 62.064 113.779 62.064C114.019 62.064 114.227 61.984 114.403 61.824C114.563 61.648 114.643 61.44 114.643 61.2ZM107.155 63.432C107.155 63.272 107.099 63.136 106.987 63.024C106.875 62.912 106.739 62.856 106.579 62.856C106.419 62.856 106.283 62.912 106.171 63.024C106.059 63.136 106.003 63.272 106.003 63.432C106.003 63.592 106.059 63.728 106.171 63.84C106.283 63.952 106.419 64.008 106.579 64.008C106.739 64.008 106.875 63.952 106.987 63.84C107.099 63.728 107.155 63.592 107.155 63.432ZM111.955 63.432C111.955 63.272 111.899 63.136 111.787 63.024C111.675 62.912 111.539 62.856 111.379 62.856C111.219 62.856 111.083 62.912 110.971 63.024C110.859 63.136 110.803 63.272 110.803 63.432C110.803 63.592 110.859 63.728 110.971 63.84C111.083 63.952 111.219 64.008 111.379 64.008C111.539 64.008 111.675 63.952 111.787 63.84C111.899 63.728 111.955 63.592 111.955 63.432ZM116.755 63.432C116.755 63.272 116.699 63.136 116.587 63.024C116.475 62.912 116.339 62.856 116.179 62.856C116.019 62.856 115.883 62.912 115.771 63.024C115.659 63.136 115.603 63.272 115.603 63.432C115.603 63.592 115.659 63.728 115.771 63.84C115.883 63.952 116.019 64.008 116.179 64.008C116.339 64.008 116.475 63.952 116.587 63.84C116.699 63.728 116.755 63.592 116.755 63.432ZM105.043 65.664C105.043 65.424 104.963 65.224 104.803 65.064C104.627 64.888 104.419 64.8 104.179 64.8C103.939 64.8 103.739 64.888 103.579 65.064C103.403 65.224 103.315 65.424 103.315 65.664C103.315 65.904 103.403 66.112 103.579 66.288C103.739 66.448 103.939 66.528 104.179 66.528C104.419 66.528 104.627 66.448 104.803 66.288C104.963 66.112 105.043 65.904 105.043 65.664ZM114.643 65.664C114.643 65.424 114.563 65.224 114.403 65.064C114.227 64.888 114.019 64.8 113.779 64.8C113.539 64.8 113.339 64.888 113.179 65.064C113.003 65.224 112.915 65.424 112.915 65.664C112.915 65.904 113.003 66.112 113.179 66.288C113.339 66.448 113.539 66.528 113.779 66.528C114.019 66.528 114.227 66.448 114.403 66.288C114.563 66.112 114.643 65.904 114.643 65.664ZM109.843 65.664C109.843 65.424 109.763 65.224 109.603 65.064C109.427 64.888 109.219 64.8 108.979 64.8C108.739 64.8 108.539 64.888 108.379 65.064C108.203 65.224 108.115 65.424 108.115 65.664C108.115 65.904 108.203 66.112 108.379 66.288C108.539 66.448 108.739 66.528 108.979 66.528C109.219 66.528 109.427 66.448 109.603 66.288C109.763 66.112 109.843 65.904 109.843 65.664ZM107.155 67.896C107.155 67.736 107.099 67.6 106.987 67.488C106.875 67.376 106.739 67.32 106.579 67.32C106.419 67.32 106.283 67.376 106.171 67.488C106.059 67.6 106.003 67.736 106.003 67.896C106.003 68.056 106.059 68.192 106.171 68.304C106.283 68.416 106.419 68.472 106.579 68.472C106.739 68.472 106.875 68.416 106.987 68.304C107.099 68.192 107.155 68.056 107.155 67.896ZM116.755 67.896C116.755 67.736 116.699 67.6 116.587 67.488C116.475 67.376 116.339 67.32 116.179 67.32C116.019 67.32 115.883 67.376 115.771 67.488C115.659 67.6 115.603 67.736 115.603 67.896C115.603 68.056 115.659 68.192 115.771 68.304C115.883 68.416 116.019 68.472 116.179 68.472C116.339 68.472 116.475 68.416 116.587 68.304C116.699 68.192 116.755 68.056 116.755 67.896ZM111.955 67.896C111.955 67.736 111.899 67.6 111.787 67.488C111.675 67.376 111.539 67.32 111.379 67.32C111.219 67.32 111.083 67.376 110.971 67.488C110.859 67.6 110.803 67.736 110.803 67.896C110.803 68.056 110.859 68.192 110.971 68.304C111.083 68.416 111.219 68.472 111.379 68.472C111.539 68.472 111.675 68.416 111.787 68.304C111.899 68.192 111.955 68.056 111.955 67.896ZM105.043 70.128C105.043 69.888 104.963 69.688 104.803 69.528C104.627 69.352 104.419 69.264 104.179 69.264C103.939 69.264 103.739 69.352 103.579 69.528C103.403 69.688 103.315 69.888 103.315 70.128C103.315 70.368 103.403 70.576 103.579 70.752C103.739 70.912 103.939 70.992 104.179 70.992C104.419 70.992 104.627 70.912 104.803 70.752C104.963 70.576 105.043 70.368 105.043 70.128ZM114.643 70.128C114.643 69.888 114.563 69.688 114.403 69.528C114.227 69.352 114.019 69.264 113.779 69.264C113.539 69.264 113.339 69.352 113.179 69.528C113.003 69.688 112.915 69.888 112.915 70.128C112.915 70.368 113.003 70.576 113.179 70.752C113.339 70.912 113.539 70.992 113.779 70.992C114.019 70.992 114.227 70.912 114.403 70.752C114.563 70.576 114.643 70.368 114.643 70.128ZM109.843 70.128C109.843 69.888 109.763 69.688 109.603 69.528C109.427 69.352 109.219 69.264 108.979 69.264C108.739 69.264 108.539 69.352 108.379 69.528C108.203 69.688 108.115 69.888 108.115 70.128C108.115 70.368 108.203 70.576 108.379 70.752C108.539 70.912 108.739 70.992 108.979 70.992C109.219 70.992 109.427 70.912 109.603 70.752C109.763 70.576 109.843 70.368 109.843 70.128ZM107.155 72.264C107.155 72.104 107.099 71.968 106.987 71.856C106.875 71.744 106.739 71.688 106.579 71.688C106.419 71.688 106.283 71.744 106.171 71.856C106.059 71.968 106.003 72.104 106.003 72.264C106.003 72.424 106.059 72.56 106.171 72.672C106.283 72.784 106.419 72.84 106.579 72.84C106.739 72.84 106.875 72.784 106.987 72.672C107.099 72.56 107.155 72.424 107.155 72.264ZM116.755 72.264C116.755 72.104 116.699 71.968 116.587 71.856C116.475 71.744 116.339 71.688 116.179 71.688C116.019 71.688 115.883 71.744 115.771 71.856C115.659 71.968 115.603 72.104 115.603 72.264C115.603 72.424 115.659 72.56 115.771 72.672C115.883 72.784 116.019 72.84 116.179 72.84C116.339 72.84 116.475 72.784 116.587 72.672C116.699 72.56 116.755 72.424 116.755 72.264ZM111.955 72.264C111.955 72.104 111.899 71.968 111.787 71.856C111.675 71.744 111.539 71.688 111.379 71.688C111.219 71.688 111.083 71.744 110.971 71.856C110.859 71.968 110.803 72.104 110.803 72.264C110.803 72.424 110.859 72.56 110.971 72.672C111.083 72.784 111.219 72.84 111.379 72.84C111.539 72.84 111.675 72.784 111.787 72.672C111.899 72.56 111.955 72.424 111.955 72.264ZM109.843 74.496C109.843 74.256 109.763 74.056 109.603 73.896C109.427 73.72 109.219 73.632 108.979 73.632C108.739 73.632 108.539 73.72 108.379 73.896C108.203 74.056 108.115 74.256 108.115 74.496C108.115 74.736 108.203 74.944 108.379 75.12C108.539 75.28 108.739 75.36 108.979 75.36C109.219 75.36 109.427 75.28 109.603 75.12C109.763 74.944 109.843 74.736 109.843 74.496ZM114.643 74.496C114.643 74.256 114.563 74.056 114.403 73.896C114.227 73.72 114.019 73.632 113.779 73.632C113.539 73.632 113.339 73.72 113.179 73.896C113.003 74.056 112.915 74.256 112.915 74.496C112.915 74.736 113.003 74.944 113.179 75.12C113.339 75.28 113.539 75.36 113.779 75.36C114.019 75.36 114.227 75.28 114.403 75.12C114.563 74.944 114.643 74.736 114.643 74.496ZM105.043 74.496C105.043 74.256 104.963 74.056 104.803 73.896C104.627 73.72 104.419 73.632 104.179 73.632C103.939 73.632 103.739 73.72 103.579 73.896C103.403 74.056 103.315 74.256 103.315 74.496C103.315 74.736 103.403 74.944 103.579 75.12C103.739 75.28 103.939 75.36 104.179 75.36C104.419 75.36 104.627 75.28 104.803 75.12C104.963 74.944 105.043 74.736 105.043 74.496ZM111.955 76.728C111.955 76.568 111.899 76.432 111.787 76.32C111.675 76.208 111.539 76.152 111.379 76.152C111.219 76.152 111.083 76.208 110.971 76.32C110.859 76.432 110.803 76.568 110.803 76.728C110.803 76.888 110.859 77.024 110.971 77.136C111.083 77.248 111.219 77.304 111.379 77.304C111.539 77.304 111.675 77.248 111.787 77.136C111.899 77.024 111.955 76.888 111.955 76.728ZM116.755 76.728C116.755 76.568 116.699 76.432 116.587 76.32C116.475 76.208 116.339 76.152 116.179 76.152C116.019 76.152 115.883 76.208 115.771 76.32C115.659 76.432 115.603 76.568 115.603 76.728C115.603 76.888 115.659 77.024 115.771 77.136C115.883 77.248 116.019 77.304 116.179 77.304C116.339 77.304 116.475 77.248 116.587 77.136C116.699 77.024 116.755 76.888 116.755 76.728ZM107.155 76.728C107.155 76.568 107.099 76.432 106.987 76.32C106.875 76.208 106.739 76.152 106.579 76.152C106.419 76.152 106.283 76.208 106.171 76.32C106.059 76.432 106.003 76.568 106.003 76.728C106.003 76.888 106.059 77.024 106.171 77.136C106.283 77.248 106.419 77.304 106.579 77.304C106.739 77.304 106.875 77.248 106.987 77.136C107.099 77.024 107.155 76.888 107.155 76.728ZM109.843 78.96C109.843 78.72 109.763 78.52 109.603 78.36C109.427 78.184 109.219 78.096 108.979 78.096C108.739 78.096 108.539 78.184 108.379 78.36C108.203 78.52 108.115 78.72 108.115 78.96C108.115 79.2 108.203 79.408 108.379 79.584C108.539 79.744 108.739 79.824 108.979 79.824C109.219 79.824 109.427 79.744 109.603 79.584C109.763 79.408 109.843 79.2 109.843 78.96ZM105.043 78.96C105.043 78.72 104.963 78.52 104.803 78.36C104.627 78.184 104.419 78.096 104.179 78.096C103.939 78.096 103.739 78.184 103.579 78.36C103.403 78.52 103.315 78.72 103.315 78.96C103.315 79.2 103.403 79.408 103.579 79.584C103.739 79.744 103.939 79.824 104.179 79.824C104.419 79.824 104.627 79.744 104.803 79.584C104.963 79.408 105.043 79.2 105.043 78.96ZM114.643 78.96C114.643 78.72 114.563 78.52 114.403 78.36C114.227 78.184 114.019 78.096 113.779 78.096C113.539 78.096 113.339 78.184 113.179 78.36C113.003 78.52 112.915 78.72 112.915 78.96C112.915 79.2 113.003 79.408 113.179 79.584C113.339 79.744 113.539 79.824 113.779 79.824C114.019 79.824 114.227 79.744 114.403 79.584C114.563 79.408 114.643 79.2 114.643 78.96ZM111.955 81.192C111.955 81.032 111.899 80.896 111.787 80.784C111.675 80.672 111.539 80.616 111.379 80.616C111.219 80.616 111.083 80.672 110.971 80.784C110.859 80.896 110.803 81.032 110.803 81.192C110.803 81.352 110.859 81.488 110.971 81.6C111.083 81.712 111.219 81.768 111.379 81.768C111.539 81.768 111.675 81.712 111.787 81.6C111.899 81.488 111.955 81.352 111.955 81.192ZM107.155 81.192C107.155 81.032 107.099 80.896 106.987 80.784C106.875 80.672 106.739 80.616 106.579 80.616C106.419 80.616 106.283 80.672 106.171 80.784C106.059 80.896 106.003 81.032 106.003 81.192C106.003 81.352 106.059 81.488 106.171 81.6C106.283 81.712 106.419 81.768 106.579 81.768C106.739 81.768 106.875 81.712 106.987 81.6C107.099 81.488 107.155 81.352 107.155 81.192ZM116.755 81.192C116.755 81.032 116.699 80.896 116.587 80.784C116.475 80.672 116.339 80.616 116.179 80.616C116.019 80.616 115.883 80.672 115.771 80.784C115.659 80.896 115.603 81.032 115.603 81.192C115.603 81.352 115.659 81.488 115.771 81.6C115.883 81.712 116.019 81.768 116.179 81.768C116.339 81.768 116.475 81.712 116.587 81.6C116.699 81.488 116.755 81.352 116.755 81.192ZM109.843 83.424C109.843 83.184 109.763 82.984 109.603 82.824C109.427 82.648 109.219 82.56 108.979 82.56C108.739 82.56 108.539 82.648 108.379 82.824C108.203 82.984 108.115 83.184 108.115 83.424C108.115 83.664 108.203 83.864 108.379 84.024C108.539 84.2 108.739 84.288 108.979 84.288C109.219 84.288 109.427 84.2 109.603 84.024C109.763 83.864 109.843 83.664 109.843 83.424ZM105.043 83.424C105.043 83.184 104.963 82.984 104.803 82.824C104.627 82.648 104.419 82.56 104.179 82.56C103.939 82.56 103.739 82.648 103.579 82.824C103.403 82.984 103.315 83.184 103.315 83.424C103.315 83.664 103.403 83.864 103.579 84.024C103.739 84.2 103.939 84.288 104.179 84.288C104.419 84.288 104.627 84.2 104.803 84.024C104.963 83.864 105.043 83.664 105.043 83.424ZM114.643 83.424C114.643 83.184 114.563 82.984 114.403 82.824C114.227 82.648 114.019 82.56 113.779 82.56C113.539 82.56 113.339 82.648 113.179 82.824C113.003 82.984 112.915 83.184 112.915 83.424C112.915 83.664 113.003 83.864 113.179 84.024C113.339 84.2 113.539 84.288 113.779 84.288C114.019 84.288 114.227 84.2 114.403 84.024C114.563 83.864 114.643 83.664 114.643 83.424ZM111.955 85.656C111.955 85.496 111.899 85.36 111.787 85.248C111.675 85.136 111.539 85.08 111.379 85.08C111.219 85.08 111.083 85.136 110.971 85.248C110.859 85.36 110.803 85.496 110.803 85.656C110.803 85.816 110.859 85.952 110.971 86.064C111.083 86.176 111.219 86.232 111.379 86.232C111.539 86.232 111.675 86.176 111.787 86.064C111.899 85.952 111.955 85.816 111.955 85.656ZM107.155 85.656C107.155 85.496 107.099 85.36 106.987 85.248C106.875 85.136 106.739 85.08 106.579 85.08C106.419 85.08 106.283 85.136 106.171 85.248C106.059 85.36 106.003 85.496 106.003 85.656C106.003 85.816 106.059 85.952 106.171 86.064C106.283 86.176 106.419 86.232 106.579 86.232C106.739 86.232 106.875 86.176 106.987 86.064C107.099 85.952 107.155 85.816 107.155 85.656ZM116.755 85.656C116.755 85.496 116.699 85.36 116.587 85.248C116.475 85.136 116.339 85.08 116.179 85.08C116.019 85.08 115.883 85.136 115.771 85.248C115.659 85.36 115.603 85.496 115.603 85.656C115.603 85.816 115.659 85.952 115.771 86.064C115.883 86.176 116.019 86.232 116.179 86.232C116.339 86.232 116.475 86.176 116.587 86.064C116.699 85.952 116.755 85.816 116.755 85.656ZM109.843 87.888C109.843 87.648 109.763 87.44 109.603 87.264C109.427 87.104 109.219 87.024 108.979 87.024C108.739 87.024 108.539 87.104 108.379 87.264C108.203 87.44 108.115 87.648 108.115 87.888C108.115 88.128 108.203 88.328 108.379 88.488C108.539 88.664 108.739 88.752 108.979 88.752C109.219 88.752 109.427 88.664 109.603 88.488C109.763 88.328 109.843 88.128 109.843 87.888ZM105.043 87.888C105.043 87.648 104.963 87.44 104.803 87.264C104.627 87.104 104.419 87.024 104.179 87.024C103.939 87.024 103.739 87.104 103.579 87.264C103.403 87.44 103.315 87.648 103.315 87.888C103.315 88.128 103.403 88.328 103.579 88.488C103.739 88.664 103.939 88.752 104.179 88.752C104.419 88.752 104.627 88.664 104.803 88.488C104.963 88.328 105.043 88.128 105.043 87.888ZM114.643 87.888C114.643 87.648 114.563 87.44 114.403 87.264C114.227 87.104 114.019 87.024 113.779 87.024C113.539 87.024 113.339 87.104 113.179 87.264C113.003 87.44 112.915 87.648 112.915 87.888C112.915 88.128 113.003 88.328 113.179 88.488C113.339 88.664 113.539 88.752 113.779 88.752C114.019 88.752 114.227 88.664 114.403 88.488C114.563 88.328 114.643 88.128 114.643 87.888ZM111.955 90.12C111.955 89.96 111.899 89.824 111.787 89.712C111.675 89.6 111.539 89.544 111.379 89.544C111.219 89.544 111.083 89.6 110.971 89.712C110.859 89.824 110.803 89.96 110.803 90.12C110.803 90.28 110.859 90.416 110.971 90.528C111.083 90.64 111.219 90.696 111.379 90.696C111.539 90.696 111.675 90.64 111.787 90.528C111.899 90.416 111.955 90.28 111.955 90.12ZM107.155 90.12C107.155 89.96 107.099 89.824 106.987 89.712C106.875 89.6 106.739 89.544 106.579 89.544C106.419 89.544 106.283 89.6 106.171 89.712C106.059 89.824 106.003 89.96 106.003 90.12C106.003 90.28 106.059 90.416 106.171 90.528C106.283 90.64 106.419 90.696 106.579 90.696C106.739 90.696 106.875 90.64 106.987 90.528C107.099 90.416 107.155 90.28 107.155 90.12ZM116.755 90.12C116.755 89.96 116.699 89.824 116.587 89.712C116.475 89.6 116.339 89.544 116.179 89.544C116.019 89.544 115.883 89.6 115.771 89.712C115.659 89.824 115.603 89.96 115.603 90.12C115.603 90.28 115.659 90.416 115.771 90.528C115.883 90.64 116.019 90.696 116.179 90.696C116.339 90.696 116.475 90.64 116.587 90.528C116.699 90.416 116.755 90.28 116.755 90.12ZM133.944 92.2C133.944 91.656 133.76 91.2 133.392 90.832C133.008 90.448 132.544 90.256 132 90.256C131.456 90.256 131 90.448 130.632 90.832C130.248 91.2 130.056 91.656 130.056 92.2C130.056 92.744 130.248 93.208 130.632 93.592C131 93.96 131.456 94.144 132 94.144C132.544 94.144 133.008 93.96 133.392 93.592C133.76 93.208 133.944 92.744 133.944 92.2ZM138.744 92.2C138.744 91.656 138.56 91.2 138.192 90.832C137.808 90.448 137.344 90.256 136.8 90.256C136.256 90.256 135.8 90.448 135.432 90.832C135.048 91.2 134.856 91.656 134.856 92.2C134.856 92.744 135.048 93.208 135.432 93.592C135.8 93.96 136.256 94.144 136.8 94.144C137.344 94.144 137.808 93.96 138.192 93.592C138.56 93.208 138.744 92.744 138.744 92.2ZM143.544 92.2C143.544 91.656 143.36 91.2 142.992 90.832C142.608 90.448 142.144 90.256 141.6 90.256C141.056 90.256 140.6 90.448 140.232 90.832C139.848 91.2 139.656 91.656 139.656 92.2C139.656 92.744 139.848 93.208 140.232 93.592C140.6 93.96 141.056 94.144 141.6 94.144C142.144 94.144 142.608 93.96 142.992 93.592C143.36 93.208 143.544 92.744 143.544 92.2ZM135.696 94.432C135.696 94.064 135.576 93.76 135.336 93.52C135.08 93.264 134.768 93.136 134.4 93.136C134.032 93.136 133.728 93.264 133.488 93.52C133.232 93.76 133.104 94.064 133.104 94.432C133.104 94.8 133.232 95.112 133.488 95.368C133.728 95.608 134.032 95.728 134.4 95.728C134.768 95.728 135.08 95.608 135.336 95.368C135.576 95.112 135.696 94.8 135.696 94.432ZM140.496 94.432C140.496 94.064 140.376 93.76 140.136 93.52C139.88 93.264 139.568 93.136 139.2 93.136C138.832 93.136 138.528 93.264 138.288 93.52C138.032 93.76 137.904 94.064 137.904 94.432C137.904 94.8 138.032 95.112 138.288 95.368C138.528 95.608 138.832 95.728 139.2 95.728C139.568 95.728 139.88 95.608 140.136 95.368C140.376 95.112 140.496 94.8 140.496 94.432ZM145.296 94.432C145.296 94.064 145.168 93.76 144.912 93.52C144.672 93.264 144.368 93.136 144 93.136C143.632 93.136 143.328 93.264 143.088 93.52C142.832 93.76 142.704 94.064 142.704 94.432C142.704 94.8 142.832 95.112 143.088 95.368C143.328 95.608 143.632 95.728 144 95.728C144.368 95.728 144.672 95.608 144.912 95.368C145.168 95.112 145.296 94.8 145.296 94.432ZM133.944 96.664C133.944 96.12 133.76 95.664 133.392 95.296C133.008 94.912 132.544 94.72 132 94.72C131.456 94.72 131 94.912 130.632 95.296C130.248 95.664 130.056 96.12 130.056 96.664C130.056 97.208 130.248 97.672 130.632 98.056C131 98.424 131.456 98.608 132 98.608C132.544 98.608 133.008 98.424 133.392 98.056C133.76 97.672 133.944 97.208 133.944 96.664ZM143.544 96.664C143.544 96.12 143.36 95.664 142.992 95.296C142.608 94.912 142.144 94.72 141.6 94.72C141.056 94.72 140.6 94.912 140.232 95.296C139.848 95.664 139.656 96.12 139.656 96.664C139.656 97.208 139.848 97.672 140.232 98.056C140.6 98.424 141.056 98.608 141.6 98.608C142.144 98.608 142.608 98.424 142.992 98.056C143.36 97.672 143.544 97.208 143.544 96.664ZM138.744 96.664C138.744 96.12 138.56 95.664 138.192 95.296C137.808 94.912 137.344 94.72 136.8 94.72C136.256 94.72 135.8 94.912 135.432 95.296C135.048 95.664 134.856 96.12 134.856 96.664C134.856 97.208 135.048 97.672 135.432 98.056C135.8 98.424 136.256 98.608 136.8 98.608C137.344 98.608 137.808 98.424 138.192 98.056C138.56 97.672 138.744 97.208 138.744 96.664ZM135.696 98.896C135.696 98.528 135.576 98.224 135.336 97.984C135.08 97.728 134.768 97.6 134.4 97.6C134.032 97.6 133.728 97.728 133.488 97.984C133.232 98.224 133.104 98.528 133.104 98.896C133.104 99.264 133.232 99.576 133.488 99.832C133.728 100.072 134.032 100.192 134.4 100.192C134.768 100.192 135.08 100.072 135.336 99.832C135.576 99.576 135.696 99.264 135.696 98.896ZM145.296 98.896C145.296 98.528 145.168 98.224 144.912 97.984C144.672 97.728 144.368 97.6 144 97.6C143.632 97.6 143.328 97.728 143.088 97.984C142.832 98.224 142.704 98.528 142.704 98.896C142.704 99.264 142.832 99.576 143.088 99.832C143.328 100.072 143.632 100.192 144 100.192C144.368 100.192 144.672 100.072 144.912 99.832C145.168 99.576 145.296 99.264 145.296 98.896ZM140.496 98.896C140.496 98.528 140.376 98.224 140.136 97.984C139.88 97.728 139.568 97.6 139.2 97.6C138.832 97.6 138.528 97.728 138.288 97.984C138.032 98.224 137.904 98.528 137.904 98.896C137.904 99.264 138.032 99.576 138.288 99.832C138.528 100.072 138.832 100.192 139.2 100.192C139.568 100.192 139.88 100.072 140.136 99.832C140.376 99.576 140.496 99.264 140.496 98.896ZM133.944 101.128C133.944 100.584 133.76 100.128 133.392 99.76C133.008 99.376 132.544 99.184 132 99.184C131.456 99.184 131 99.376 130.632 99.76C130.248 100.128 130.056 100.584 130.056 101.128C130.056 101.672 130.248 102.136 130.632 102.52C131 102.888 131.456 103.072 132 103.072C132.544 103.072 133.008 102.888 133.392 102.52C133.76 102.136 133.944 101.672 133.944 101.128ZM143.544 101.128C143.544 100.584 143.36 100.128 142.992 99.76C142.608 99.376 142.144 99.184 141.6 99.184C141.056 99.184 140.6 99.376 140.232 99.76C139.848 100.128 139.656 100.584 139.656 101.128C139.656 101.672 139.848 102.136 140.232 102.52C140.6 102.888 141.056 103.072 141.6 103.072C142.144 103.072 142.608 102.888 142.992 102.52C143.36 102.136 143.544 101.672 143.544 101.128ZM138.744 101.128C138.744 100.584 138.56 100.128 138.192 99.76C137.808 99.376 137.344 99.184 136.8 99.184C136.256 99.184 135.8 99.376 135.432 99.76C135.048 100.128 134.856 100.584 134.856 101.128C134.856 101.672 135.048 102.136 135.432 102.52C135.8 102.888 136.256 103.072 136.8 103.072C137.344 103.072 137.808 102.888 138.192 102.52C138.56 102.136 138.744 101.672 138.744 101.128ZM135.696 103.36C135.696 102.992 135.576 102.688 135.336 102.448C135.08 102.192 134.768 102.064 134.4 102.064C134.032 102.064 133.728 102.192 133.488 102.448C133.232 102.688 133.104 102.992 133.104 103.36C133.104 103.728 133.232 104.04 133.488 104.296C133.728 104.536 134.032 104.656 134.4 104.656C134.768 104.656 135.08 104.536 135.336 104.296C135.576 104.04 135.696 103.728 135.696 103.36ZM145.296 103.36C145.296 102.992 145.168 102.688 144.912 102.448C144.672 102.192 144.368 102.064 144 102.064C143.632 102.064 143.328 102.192 143.088 102.448C142.832 102.688 142.704 102.992 142.704 103.36C142.704 103.728 142.832 104.04 143.088 104.296C143.328 104.536 143.632 104.656 144 104.656C144.368 104.656 144.672 104.536 144.912 104.296C145.168 104.04 145.296 103.728 145.296 103.36ZM140.496 103.36C140.496 102.992 140.376 102.688 140.136 102.448C139.88 102.192 139.568 102.064 139.2 102.064C138.832 102.064 138.528 102.192 138.288 102.448C138.032 102.688 137.904 102.992 137.904 103.36C137.904 103.728 138.032 104.04 138.288 104.296C138.528 104.536 138.832 104.656 139.2 104.656C139.568 104.656 139.88 104.536 140.136 104.296C140.376 104.04 140.496 103.728 140.496 103.36ZM138.744 105.592C138.744 105.048 138.56 104.592 138.192 104.224C137.808 103.84 137.344 103.648 136.8 103.648C136.256 103.648 135.8 103.84 135.432 104.224C135.048 104.592 134.856 105.048 134.856 105.592C134.856 106.136 135.048 106.6 135.432 106.984C135.8 107.352 136.256 107.536 136.8 107.536C137.344 107.536 137.808 107.352 138.192 106.984C138.56 106.6 138.744 106.136 138.744 105.592ZM143.544 105.592C143.544 105.048 143.36 104.592 142.992 104.224C142.608 103.84 142.144 103.648 141.6 103.648C141.056 103.648 140.6 103.84 140.232 104.224C139.848 104.592 139.656 105.048 139.656 105.592C139.656 106.136 139.848 106.6 140.232 106.984C140.6 107.352 141.056 107.536 141.6 107.536C142.144 107.536 142.608 107.352 142.992 106.984C143.36 106.6 143.544 106.136 143.544 105.592ZM133.944 105.592C133.944 105.048 133.76 104.592 133.392 104.224C133.008 103.84 132.544 103.648 132 103.648C131.456 103.648 131 103.84 130.632 104.224C130.248 104.592 130.056 105.048 130.056 105.592C130.056 106.136 130.248 106.6 130.632 106.984C131 107.352 131.456 107.536 132 107.536C132.544 107.536 133.008 107.352 133.392 106.984C133.76 106.6 133.944 106.136 133.944 105.592ZM140.496 107.824C140.496 107.456 140.376 107.152 140.136 106.912C139.88 106.656 139.568 106.528 139.2 106.528C138.832 106.528 138.528 106.656 138.288 106.912C138.032 107.152 137.904 107.456 137.904 107.824C137.904 108.192 138.032 108.504 138.288 108.76C138.528 109 138.832 109.12 139.2 109.12C139.568 109.12 139.88 109 140.136 108.76C140.376 108.504 140.496 108.192 140.496 107.824ZM145.296 107.824C145.296 107.456 145.168 107.152 144.912 106.912C144.672 106.656 144.368 106.528 144 106.528C143.632 106.528 143.328 106.656 143.088 106.912C142.832 107.152 142.704 107.456 142.704 107.824C142.704 108.192 142.832 108.504 143.088 108.76C143.328 109 143.632 109.12 144 109.12C144.368 109.12 144.672 109 144.912 108.76C145.168 108.504 145.296 108.192 145.296 107.824ZM135.696 107.824C135.696 107.456 135.576 107.152 135.336 106.912C135.08 106.656 134.768 106.528 134.4 106.528C134.032 106.528 133.728 106.656 133.488 106.912C133.232 107.152 133.104 107.456 133.104 107.824C133.104 108.192 133.232 108.504 133.488 108.76C133.728 109 134.032 109.12 134.4 109.12C134.768 109.12 135.08 109 135.336 108.76C135.576 108.504 135.696 108.192 135.696 107.824ZM138.744 110.056C138.744 109.512 138.56 109.056 138.192 108.688C137.808 108.304 137.344 108.112 136.8 108.112C136.256 108.112 135.8 108.304 135.432 108.688C135.048 109.056 134.856 109.512 134.856 110.056C134.856 110.6 135.048 111.064 135.432 111.448C135.8 111.816 136.256 112 136.8 112C137.344 112 137.808 111.816 138.192 111.448C138.56 111.064 138.744 110.6 138.744 110.056ZM133.944 110.056C133.944 109.512 133.76 109.056 133.392 108.688C133.008 108.304 132.544 108.112 132 108.112C131.456 108.112 131 108.304 130.632 108.688C130.248 109.056 130.056 109.512 130.056 110.056C130.056 110.6 130.248 111.064 130.632 111.448C131 111.816 131.456 112 132 112C132.544 112 133.008 111.816 133.392 111.448C133.76 111.064 133.944 110.6 133.944 110.056ZM143.544 110.056C143.544 109.512 143.36 109.056 142.992 108.688C142.608 108.304 142.144 108.112 141.6 108.112C141.056 108.112 140.6 108.304 140.232 108.688C139.848 109.056 139.656 109.512 139.656 110.056C139.656 110.6 139.848 111.064 140.232 111.448C140.6 111.816 141.056 112 141.6 112C142.144 112 142.608 111.816 142.992 111.448C143.36 111.064 143.544 110.6 143.544 110.056ZM140.496 112.288C140.496 111.92 140.376 111.616 140.136 111.376C139.88 111.12 139.568 110.992 139.2 110.992C138.832 110.992 138.528 111.12 138.288 111.376C138.032 111.616 137.904 111.92 137.904 112.288C137.904 112.656 138.032 112.968 138.288 113.224C138.528 113.464 138.832 113.584 139.2 113.584C139.568 113.584 139.88 113.464 140.136 113.224C140.376 112.968 140.496 112.656 140.496 112.288ZM135.696 112.288C135.696 111.92 135.576 111.616 135.336 111.376C135.08 111.12 134.768 110.992 134.4 110.992C134.032 110.992 133.728 111.12 133.488 111.376C133.232 111.616 133.104 111.92 133.104 112.288C133.104 112.656 133.232 112.968 133.488 113.224C133.728 113.464 134.032 113.584 134.4 113.584C134.768 113.584 135.08 113.464 135.336 113.224C135.576 112.968 135.696 112.656 135.696 112.288ZM145.296 112.288C145.296 111.92 145.168 111.616 144.912 111.376C144.672 111.12 144.368 110.992 144 110.992C143.632 110.992 143.328 111.12 143.088 111.376C142.832 111.616 142.704 111.92 142.704 112.288C142.704 112.656 142.832 112.968 143.088 113.224C143.328 113.464 143.632 113.584 144 113.584C144.368 113.584 144.672 113.464 144.912 113.224C145.168 112.968 145.296 112.656 145.296 112.288ZM138.744 114.52C138.744 113.976 138.56 113.52 138.192 113.152C137.808 112.768 137.344 112.576 136.8 112.576C136.256 112.576 135.8 112.768 135.432 113.152C135.048 113.52 134.856 113.976 134.856 114.52C134.856 115.064 135.048 115.52 135.432 115.888C135.8 116.272 136.256 116.464 136.8 116.464C137.344 116.464 137.808 116.272 138.192 115.888C138.56 115.52 138.744 115.064 138.744 114.52ZM133.944 114.52C133.944 113.976 133.76 113.52 133.392 113.152C133.008 112.768 132.544 112.576 132 112.576C131.456 112.576 131 112.768 130.632 113.152C130.248 113.52 130.056 113.976 130.056 114.52C130.056 115.064 130.248 115.52 130.632 115.888C131 116.272 131.456 116.464 132 116.464C132.544 116.464 133.008 116.272 133.392 115.888C133.76 115.52 133.944 115.064 133.944 114.52ZM143.544 114.52C143.544 113.976 143.36 113.52 142.992 113.152C142.608 112.768 142.144 112.576 141.6 112.576C141.056 112.576 140.6 112.768 140.232 113.152C139.848 113.52 139.656 113.976 139.656 114.52C139.656 115.064 139.848 115.52 140.232 115.888C140.6 116.272 141.056 116.464 141.6 116.464C142.144 116.464 142.608 116.272 142.992 115.888C143.36 115.52 143.544 115.064 143.544 114.52ZM140.496 116.752C140.496 116.384 140.376 116.072 140.136 115.816C139.88 115.576 139.568 115.456 139.2 115.456C138.832 115.456 138.528 115.576 138.288 115.816C138.032 116.072 137.904 116.384 137.904 116.752C137.904 117.12 138.032 117.424 138.288 117.664C138.528 117.92 138.832 118.048 139.2 118.048C139.568 118.048 139.88 117.92 140.136 117.664C140.376 117.424 140.496 117.12 140.496 116.752ZM135.696 116.752C135.696 116.384 135.576 116.072 135.336 115.816C135.08 115.576 134.768 115.456 134.4 115.456C134.032 115.456 133.728 115.576 133.488 115.816C133.232 116.072 133.104 116.384 133.104 116.752C133.104 117.12 133.232 117.424 133.488 117.664C133.728 117.92 134.032 118.048 134.4 118.048C134.768 118.048 135.08 117.92 135.336 117.664C135.576 117.424 135.696 117.12 135.696 116.752ZM145.296 116.752C145.296 116.384 145.168 116.072 144.912 115.816C144.672 115.576 144.368 115.456 144 115.456C143.632 115.456 143.328 115.576 143.088 115.816C142.832 116.072 142.704 116.384 142.704 116.752C142.704 117.12 142.832 117.424 143.088 117.664C143.328 117.92 143.632 118.048 144 118.048C144.368 118.048 144.672 117.92 144.912 117.664C145.168 117.424 145.296 117.12 145.296 116.752ZM138.744 118.984C138.744 118.44 138.56 117.976 138.192 117.592C137.808 117.224 137.344 117.04 136.8 117.04C136.256 117.04 135.8 117.224 135.432 117.592C135.048 117.976 134.856 118.44 134.856 118.984C134.856 119.528 135.048 119.984 135.432 120.352C135.8 120.736 136.256 120.928 136.8 120.928C137.344 120.928 137.808 120.736 138.192 120.352C138.56 119.984 138.744 119.528 138.744 118.984ZM133.944 118.984C133.944 118.44 133.76 117.976 133.392 117.592C133.008 117.224 132.544 117.04 132 117.04C131.456 117.04 131 117.224 130.632 117.592C130.248 117.976 130.056 118.44 130.056 118.984C130.056 119.528 130.248 119.984 130.632 120.352C131 120.736 131.456 120.928 132 120.928C132.544 120.928 133.008 120.736 133.392 120.352C133.76 119.984 133.944 119.528 133.944 118.984ZM143.544 118.984C143.544 118.44 143.36 117.976 142.992 117.592C142.608 117.224 142.144 117.04 141.6 117.04C141.056 117.04 140.6 117.224 140.232 117.592C139.848 117.976 139.656 118.44 139.656 118.984C139.656 119.528 139.848 119.984 140.232 120.352C140.6 120.736 141.056 120.928 141.6 120.928C142.144 120.928 142.608 120.736 142.992 120.352C143.36 119.984 143.544 119.528 143.544 118.984ZM140.496 121.216C140.496 120.848 140.376 120.536 140.136 120.28C139.88 120.04 139.568 119.92 139.2 119.92C138.832 119.92 138.528 120.04 138.288 120.28C138.032 120.536 137.904 120.848 137.904 121.216C137.904 121.584 138.032 121.888 138.288 122.128C138.528 122.384 138.832 122.512 139.2 122.512C139.568 122.512 139.88 122.384 140.136 122.128C140.376 121.888 140.496 121.584 140.496 121.216ZM135.696 121.216C135.696 120.848 135.576 120.536 135.336 120.28C135.08 120.04 134.768 119.92 134.4 119.92C134.032 119.92 133.728 120.04 133.488 120.28C133.232 120.536 133.104 120.848 133.104 121.216C133.104 121.584 133.232 121.888 133.488 122.128C133.728 122.384 134.032 122.512 134.4 122.512C134.768 122.512 135.08 122.384 135.336 122.128C135.576 121.888 135.696 121.584 135.696 121.216ZM145.296 121.216C145.296 120.848 145.168 120.536 144.912 120.28C144.672 120.04 144.368 119.92 144 119.92C143.632 119.92 143.328 120.04 143.088 120.28C142.832 120.536 142.704 120.848 142.704 121.216C142.704 121.584 142.832 121.888 143.088 122.128C143.328 122.384 143.632 122.512 144 122.512C144.368 122.512 144.672 122.384 144.912 122.128C145.168 121.888 145.296 121.584 145.296 121.216ZM119.385 92.2C119.385 91.832 119.265 91.528 119.025 91.288C118.769 91.032 118.457 90.904 118.089 90.904C117.721 90.904 117.417 91.032 117.177 91.288C116.921 91.528 116.793 91.832 116.793 92.2C116.793 92.568 116.921 92.88 117.177 93.136C117.417 93.376 117.721 93.496 118.089 93.496C118.457 93.496 118.769 93.376 119.025 93.136C119.265 92.88 119.385 92.568 119.385 92.2ZM124.185 92.2C124.185 91.832 124.065 91.528 123.825 91.288C123.569 91.032 123.257 90.904 122.889 90.904C122.521 90.904 122.217 91.032 121.977 91.288C121.721 91.528 121.593 91.832 121.593 92.2C121.593 92.568 121.721 92.88 121.977 93.136C122.217 93.376 122.521 93.496 122.889 93.496C123.257 93.496 123.569 93.376 123.825 93.136C124.065 92.88 124.185 92.568 124.185 92.2ZM128.985 92.2C128.985 91.832 128.865 91.528 128.625 91.288C128.369 91.032 128.057 90.904 127.689 90.904C127.321 90.904 127.017 91.032 126.777 91.288C126.521 91.528 126.393 91.832 126.393 92.2C126.393 92.568 126.521 92.88 126.777 93.136C127.017 93.376 127.321 93.496 127.689 93.496C128.057 93.496 128.369 93.376 128.625 93.136C128.865 92.88 128.985 92.568 128.985 92.2ZM121.353 94.432C121.353 94.192 121.273 93.992 121.113 93.832C120.937 93.656 120.729 93.568 120.489 93.568C120.249 93.568 120.049 93.656 119.889 93.832C119.713 93.992 119.625 94.192 119.625 94.432C119.625 94.672 119.713 94.88 119.889 95.056C120.049 95.216 120.249 95.296 120.489 95.296C120.729 95.296 120.937 95.216 121.113 95.056C121.273 94.88 121.353 94.672 121.353 94.432ZM126.153 94.432C126.153 94.192 126.073 93.992 125.913 93.832C125.737 93.656 125.529 93.568 125.289 93.568C125.049 93.568 124.849 93.656 124.689 93.832C124.513 93.992 124.425 94.192 124.425 94.432C124.425 94.672 124.513 94.88 124.689 95.056C124.849 95.216 125.049 95.296 125.289 95.296C125.529 95.296 125.737 95.216 125.913 95.056C126.073 94.88 126.153 94.672 126.153 94.432ZM130.953 94.432C130.953 94.192 130.865 93.992 130.689 93.832C130.529 93.656 130.329 93.568 130.089 93.568C129.849 93.568 129.649 93.656 129.489 93.832C129.313 93.992 129.225 94.192 129.225 94.432C129.225 94.672 129.313 94.88 129.489 95.056C129.649 95.216 129.849 95.296 130.089 95.296C130.329 95.296 130.529 95.216 130.689 95.056C130.865 94.88 130.953 94.672 130.953 94.432ZM119.385 96.664C119.385 96.296 119.265 95.992 119.025 95.752C118.769 95.496 118.457 95.368 118.089 95.368C117.721 95.368 117.417 95.496 117.177 95.752C116.921 95.992 116.793 96.296 116.793 96.664C116.793 97.032 116.921 97.344 117.177 97.6C117.417 97.84 117.721 97.96 118.089 97.96C118.457 97.96 118.769 97.84 119.025 97.6C119.265 97.344 119.385 97.032 119.385 96.664ZM128.985 96.664C128.985 96.296 128.865 95.992 128.625 95.752C128.369 95.496 128.057 95.368 127.689 95.368C127.321 95.368 127.017 95.496 126.777 95.752C126.521 95.992 126.393 96.296 126.393 96.664C126.393 97.032 126.521 97.344 126.777 97.6C127.017 97.84 127.321 97.96 127.689 97.96C128.057 97.96 128.369 97.84 128.625 97.6C128.865 97.344 128.985 97.032 128.985 96.664ZM124.185 96.664C124.185 96.296 124.065 95.992 123.825 95.752C123.569 95.496 123.257 95.368 122.889 95.368C122.521 95.368 122.217 95.496 121.977 95.752C121.721 95.992 121.593 96.296 121.593 96.664C121.593 97.032 121.721 97.344 121.977 97.6C122.217 97.84 122.521 97.96 122.889 97.96C123.257 97.96 123.569 97.84 123.825 97.6C124.065 97.344 124.185 97.032 124.185 96.664ZM121.353 98.896C121.353 98.656 121.273 98.456 121.113 98.296C120.937 98.12 120.729 98.032 120.489 98.032C120.249 98.032 120.049 98.12 119.889 98.296C119.713 98.456 119.625 98.656 119.625 98.896C119.625 99.136 119.713 99.344 119.889 99.52C120.049 99.68 120.249 99.76 120.489 99.76C120.729 99.76 120.937 99.68 121.113 99.52C121.273 99.344 121.353 99.136 121.353 98.896ZM130.953 98.896C130.953 98.656 130.865 98.456 130.689 98.296C130.529 98.12 130.329 98.032 130.089 98.032C129.849 98.032 129.649 98.12 129.489 98.296C129.313 98.456 129.225 98.656 129.225 98.896C129.225 99.136 129.313 99.344 129.489 99.52C129.649 99.68 129.849 99.76 130.089 99.76C130.329 99.76 130.529 99.68 130.689 99.52C130.865 99.344 130.953 99.136 130.953 98.896ZM126.153 98.896C126.153 98.656 126.073 98.456 125.913 98.296C125.737 98.12 125.529 98.032 125.289 98.032C125.049 98.032 124.849 98.12 124.689 98.296C124.513 98.456 124.425 98.656 124.425 98.896C124.425 99.136 124.513 99.344 124.689 99.52C124.849 99.68 125.049 99.76 125.289 99.76C125.529 99.76 125.737 99.68 125.913 99.52C126.073 99.344 126.153 99.136 126.153 98.896ZM119.385 101.128C119.385 100.76 119.265 100.456 119.025 100.216C118.769 99.96 118.457 99.832 118.089 99.832C117.721 99.832 117.417 99.96 117.177 100.216C116.921 100.456 116.793 100.76 116.793 101.128C116.793 101.496 116.921 101.808 117.177 102.064C117.417 102.304 117.721 102.424 118.089 102.424C118.457 102.424 118.769 102.304 119.025 102.064C119.265 101.808 119.385 101.496 119.385 101.128ZM128.985 101.128C128.985 100.76 128.865 100.456 128.625 100.216C128.369 99.96 128.057 99.832 127.689 99.832C127.321 99.832 127.017 99.96 126.777 100.216C126.521 100.456 126.393 100.76 126.393 101.128C126.393 101.496 126.521 101.808 126.777 102.064C127.017 102.304 127.321 102.424 127.689 102.424C128.057 102.424 128.369 102.304 128.625 102.064C128.865 101.808 128.985 101.496 128.985 101.128ZM124.185 101.128C124.185 100.76 124.065 100.456 123.825 100.216C123.569 99.96 123.257 99.832 122.889 99.832C122.521 99.832 122.217 99.96 121.977 100.216C121.721 100.456 121.593 100.76 121.593 101.128C121.593 101.496 121.721 101.808 121.977 102.064C122.217 102.304 122.521 102.424 122.889 102.424C123.257 102.424 123.569 102.304 123.825 102.064C124.065 101.808 124.185 101.496 124.185 101.128ZM121.353 103.36C121.353 103.12 121.273 102.92 121.113 102.76C120.937 102.584 120.729 102.496 120.489 102.496C120.249 102.496 120.049 102.584 119.889 102.76C119.713 102.92 119.625 103.12 119.625 103.36C119.625 103.6 119.713 103.808 119.889 103.984C120.049 104.144 120.249 104.224 120.489 104.224C120.729 104.224 120.937 104.144 121.113 103.984C121.273 103.808 121.353 103.6 121.353 103.36ZM130.953 103.36C130.953 103.12 130.865 102.92 130.689 102.76C130.529 102.584 130.329 102.496 130.089 102.496C129.849 102.496 129.649 102.584 129.489 102.76C129.313 102.92 129.225 103.12 129.225 103.36C129.225 103.6 129.313 103.808 129.489 103.984C129.649 104.144 129.849 104.224 130.089 104.224C130.329 104.224 130.529 104.144 130.689 103.984C130.865 103.808 130.953 103.6 130.953 103.36ZM126.153 103.36C126.153 103.12 126.073 102.92 125.913 102.76C125.737 102.584 125.529 102.496 125.289 102.496C125.049 102.496 124.849 102.584 124.689 102.76C124.513 102.92 124.425 103.12 124.425 103.36C124.425 103.6 124.513 103.808 124.689 103.984C124.849 104.144 125.049 104.224 125.289 104.224C125.529 104.224 125.737 104.144 125.913 103.984C126.073 103.808 126.153 103.6 126.153 103.36ZM124.185 105.592C124.185 105.224 124.065 104.92 123.825 104.68C123.569 104.424 123.257 104.296 122.889 104.296C122.521 104.296 122.217 104.424 121.977 104.68C121.721 104.92 121.593 105.224 121.593 105.592C121.593 105.96 121.721 106.272 121.977 106.528C122.217 106.768 122.521 106.888 122.889 106.888C123.257 106.888 123.569 106.768 123.825 106.528C124.065 106.272 124.185 105.96 124.185 105.592ZM128.985 105.592C128.985 105.224 128.865 104.92 128.625 104.68C128.369 104.424 128.057 104.296 127.689 104.296C127.321 104.296 127.017 104.424 126.777 104.68C126.521 104.92 126.393 105.224 126.393 105.592C126.393 105.96 126.521 106.272 126.777 106.528C127.017 106.768 127.321 106.888 127.689 106.888C128.057 106.888 128.369 106.768 128.625 106.528C128.865 106.272 128.985 105.96 128.985 105.592ZM119.385 105.592C119.385 105.224 119.265 104.92 119.025 104.68C118.769 104.424 118.457 104.296 118.089 104.296C117.721 104.296 117.417 104.424 117.177 104.68C116.921 104.92 116.793 105.224 116.793 105.592C116.793 105.96 116.921 106.272 117.177 106.528C117.417 106.768 117.721 106.888 118.089 106.888C118.457 106.888 118.769 106.768 119.025 106.528C119.265 106.272 119.385 105.96 119.385 105.592ZM126.153 107.824C126.153 107.584 126.073 107.384 125.913 107.224C125.737 107.048 125.529 106.96 125.289 106.96C125.049 106.96 124.849 107.048 124.689 107.224C124.513 107.384 124.425 107.584 124.425 107.824C124.425 108.064 124.513 108.272 124.689 108.448C124.849 108.608 125.049 108.688 125.289 108.688C125.529 108.688 125.737 108.608 125.913 108.448C126.073 108.272 126.153 108.064 126.153 107.824ZM130.953 107.824C130.953 107.584 130.865 107.384 130.689 107.224C130.529 107.048 130.329 106.96 130.089 106.96C129.849 106.96 129.649 107.048 129.489 107.224C129.313 107.384 129.225 107.584 129.225 107.824C129.225 108.064 129.313 108.272 129.489 108.448C129.649 108.608 129.849 108.688 130.089 108.688C130.329 108.688 130.529 108.608 130.689 108.448C130.865 108.272 130.953 108.064 130.953 107.824ZM121.353 107.824C121.353 107.584 121.273 107.384 121.113 107.224C120.937 107.048 120.729 106.96 120.489 106.96C120.249 106.96 120.049 107.048 119.889 107.224C119.713 107.384 119.625 107.584 119.625 107.824C119.625 108.064 119.713 108.272 119.889 108.448C120.049 108.608 120.249 108.688 120.489 108.688C120.729 108.688 120.937 108.608 121.113 108.448C121.273 108.272 121.353 108.064 121.353 107.824ZM124.185 110.056C124.185 109.688 124.065 109.384 123.825 109.144C123.569 108.888 123.257 108.76 122.889 108.76C122.521 108.76 122.217 108.888 121.977 109.144C121.721 109.384 121.593 109.688 121.593 110.056C121.593 110.424 121.721 110.736 121.977 110.992C122.217 111.232 122.521 111.352 122.889 111.352C123.257 111.352 123.569 111.232 123.825 110.992C124.065 110.736 124.185 110.424 124.185 110.056ZM119.385 110.056C119.385 109.688 119.265 109.384 119.025 109.144C118.769 108.888 118.457 108.76 118.089 108.76C117.721 108.76 117.417 108.888 117.177 109.144C116.921 109.384 116.793 109.688 116.793 110.056C116.793 110.424 116.921 110.736 117.177 110.992C117.417 111.232 117.721 111.352 118.089 111.352C118.457 111.352 118.769 111.232 119.025 110.992C119.265 110.736 119.385 110.424 119.385 110.056ZM128.985 110.056C128.985 109.688 128.865 109.384 128.625 109.144C128.369 108.888 128.057 108.76 127.689 108.76C127.321 108.76 127.017 108.888 126.777 109.144C126.521 109.384 126.393 109.688 126.393 110.056C126.393 110.424 126.521 110.736 126.777 110.992C127.017 111.232 127.321 111.352 127.689 111.352C128.057 111.352 128.369 111.232 128.625 110.992C128.865 110.736 128.985 110.424 128.985 110.056ZM126.153 112.288C126.153 112.048 126.073 111.848 125.913 111.688C125.737 111.512 125.529 111.424 125.289 111.424C125.049 111.424 124.849 111.512 124.689 111.688C124.513 111.848 124.425 112.048 124.425 112.288C124.425 112.528 124.513 112.736 124.689 112.912C124.849 113.072 125.049 113.152 125.289 113.152C125.529 113.152 125.737 113.072 125.913 112.912C126.073 112.736 126.153 112.528 126.153 112.288ZM121.353 112.288C121.353 112.048 121.273 111.848 121.113 111.688C120.937 111.512 120.729 111.424 120.489 111.424C120.249 111.424 120.049 111.512 119.889 111.688C119.713 111.848 119.625 112.048 119.625 112.288C119.625 112.528 119.713 112.736 119.889 112.912C120.049 113.072 120.249 113.152 120.489 113.152C120.729 113.152 120.937 113.072 121.113 112.912C121.273 112.736 121.353 112.528 121.353 112.288ZM130.953 112.288C130.953 112.048 130.865 111.848 130.689 111.688C130.529 111.512 130.329 111.424 130.089 111.424C129.849 111.424 129.649 111.512 129.489 111.688C129.313 111.848 129.225 112.048 129.225 112.288C129.225 112.528 129.313 112.736 129.489 112.912C129.649 113.072 129.849 113.152 130.089 113.152C130.329 113.152 130.529 113.072 130.689 112.912C130.865 112.736 130.953 112.528 130.953 112.288ZM124.185 114.52C124.185 114.152 124.065 113.848 123.825 113.608C123.569 113.352 123.257 113.224 122.889 113.224C122.521 113.224 122.217 113.352 121.977 113.608C121.721 113.848 121.593 114.152 121.593 114.52C121.593 114.888 121.721 115.192 121.977 115.432C122.217 115.688 122.521 115.816 122.889 115.816C123.257 115.816 123.569 115.688 123.825 115.432C124.065 115.192 124.185 114.888 124.185 114.52ZM119.385 114.52C119.385 114.152 119.265 113.848 119.025 113.608C118.769 113.352 118.457 113.224 118.089 113.224C117.721 113.224 117.417 113.352 117.177 113.608C116.921 113.848 116.793 114.152 116.793 114.52C116.793 114.888 116.921 115.192 117.177 115.432C117.417 115.688 117.721 115.816 118.089 115.816C118.457 115.816 118.769 115.688 119.025 115.432C119.265 115.192 119.385 114.888 119.385 114.52ZM128.985 114.52C128.985 114.152 128.865 113.848 128.625 113.608C128.369 113.352 128.057 113.224 127.689 113.224C127.321 113.224 127.017 113.352 126.777 113.608C126.521 113.848 126.393 114.152 126.393 114.52C126.393 114.888 126.521 115.192 126.777 115.432C127.017 115.688 127.321 115.816 127.689 115.816C128.057 115.816 128.369 115.688 128.625 115.432C128.865 115.192 128.985 114.888 128.985 114.52ZM126.153 116.752C126.153 116.512 126.073 116.304 125.913 116.128C125.737 115.968 125.529 115.888 125.289 115.888C125.049 115.888 124.849 115.968 124.689 116.128C124.513 116.304 124.425 116.512 124.425 116.752C124.425 116.992 124.513 117.192 124.689 117.352C124.849 117.528 125.049 117.616 125.289 117.616C125.529 117.616 125.737 117.528 125.913 117.352C126.073 117.192 126.153 116.992 126.153 116.752ZM121.353 116.752C121.353 116.512 121.273 116.304 121.113 116.128C120.937 115.968 120.729 115.888 120.489 115.888C120.249 115.888 120.049 115.968 119.889 116.128C119.713 116.304 119.625 116.512 119.625 116.752C119.625 116.992 119.713 117.192 119.889 117.352C120.049 117.528 120.249 117.616 120.489 117.616C120.729 117.616 120.937 117.528 121.113 117.352C121.273 117.192 121.353 116.992 121.353 116.752ZM130.953 116.752C130.953 116.512 130.865 116.304 130.689 116.128C130.529 115.968 130.329 115.888 130.089 115.888C129.849 115.888 129.649 115.968 129.489 116.128C129.313 116.304 129.225 116.512 129.225 116.752C129.225 116.992 129.313 117.192 129.489 117.352C129.649 117.528 129.849 117.616 130.089 117.616C130.329 117.616 130.529 117.528 130.689 117.352C130.865 117.192 130.953 116.992 130.953 116.752ZM124.185 118.984C124.185 118.616 124.065 118.304 123.825 118.048C123.569 117.808 123.257 117.688 122.889 117.688C122.521 117.688 122.217 117.808 121.977 118.048C121.721 118.304 121.593 118.616 121.593 118.984C121.593 119.352 121.721 119.656 121.977 119.896C122.217 120.152 122.521 120.28 122.889 120.28C123.257 120.28 123.569 120.152 123.825 119.896C124.065 119.656 124.185 119.352 124.185 118.984ZM119.385 118.984C119.385 118.616 119.265 118.304 119.025 118.048C118.769 117.808 118.457 117.688 118.089 117.688C117.721 117.688 117.417 117.808 117.177 118.048C116.921 118.304 116.793 118.616 116.793 118.984C116.793 119.352 116.921 119.656 117.177 119.896C117.417 120.152 117.721 120.28 118.089 120.28C118.457 120.28 118.769 120.152 119.025 119.896C119.265 119.656 119.385 119.352 119.385 118.984ZM128.985 118.984C128.985 118.616 128.865 118.304 128.625 118.048C128.369 117.808 128.057 117.688 127.689 117.688C127.321 117.688 127.017 117.808 126.777 118.048C126.521 118.304 126.393 118.616 126.393 118.984C126.393 119.352 126.521 119.656 126.777 119.896C127.017 120.152 127.321 120.28 127.689 120.28C128.057 120.28 128.369 120.152 128.625 119.896C128.865 119.656 128.985 119.352 128.985 118.984ZM126.153 121.216C126.153 120.976 126.073 120.768 125.913 120.592C125.737 120.432 125.529 120.352 125.289 120.352C125.049 120.352 124.849 120.432 124.689 120.592C124.513 120.768 124.425 120.976 124.425 121.216C124.425 121.456 124.513 121.656 124.689 121.816C124.849 121.992 125.049 122.08 125.289 122.08C125.529 122.08 125.737 121.992 125.913 121.816C126.073 121.656 126.153 121.456 126.153 121.216ZM121.353 121.216C121.353 120.976 121.273 120.768 121.113 120.592C120.937 120.432 120.729 120.352 120.489 120.352C120.249 120.352 120.049 120.432 119.889 120.592C119.713 120.768 119.625 120.976 119.625 121.216C119.625 121.456 119.713 121.656 119.889 121.816C120.049 121.992 120.249 122.08 120.489 122.08C120.729 122.08 120.937 121.992 121.113 121.816C121.273 121.656 121.353 121.456 121.353 121.216ZM130.953 121.216C130.953 120.976 130.865 120.768 130.689 120.592C130.529 120.432 130.329 120.352 130.089 120.352C129.849 120.352 129.649 120.432 129.489 120.592C129.313 120.768 129.225 120.976 129.225 121.216C129.225 121.456 129.313 121.656 129.489 121.816C129.649 121.992 129.849 122.08 130.089 122.08C130.329 122.08 130.529 121.992 130.689 121.816C130.865 121.656 130.953 121.456 130.953 121.216ZM133.944 123.2C133.944 122.656 133.76 122.2 133.392 121.832C133.008 121.448 132.544 121.256 132 121.256C131.456 121.256 131 121.448 130.632 121.832C130.248 122.2 130.056 122.656 130.056 123.2C130.056 123.744 130.248 124.208 130.632 124.592C131 124.96 131.456 125.144 132 125.144C132.544 125.144 133.008 124.96 133.392 124.592C133.76 124.208 133.944 123.744 133.944 123.2ZM138.744 123.2C138.744 122.656 138.56 122.2 138.192 121.832C137.808 121.448 137.344 121.256 136.8 121.256C136.256 121.256 135.8 121.448 135.432 121.832C135.048 122.2 134.856 122.656 134.856 123.2C134.856 123.744 135.048 124.208 135.432 124.592C135.8 124.96 136.256 125.144 136.8 125.144C137.344 125.144 137.808 124.96 138.192 124.592C138.56 124.208 138.744 123.744 138.744 123.2ZM143.544 123.2C143.544 122.656 143.36 122.2 142.992 121.832C142.608 121.448 142.144 121.256 141.6 121.256C141.056 121.256 140.6 121.448 140.232 121.832C139.848 122.2 139.656 122.656 139.656 123.2C139.656 123.744 139.848 124.208 140.232 124.592C140.6 124.96 141.056 125.144 141.6 125.144C142.144 125.144 142.608 124.96 142.992 124.592C143.36 124.208 143.544 123.744 143.544 123.2ZM135.696 125.432C135.696 125.064 135.576 124.76 135.336 124.52C135.08 124.264 134.768 124.136 134.4 124.136C134.032 124.136 133.728 124.264 133.488 124.52C133.232 124.76 133.104 125.064 133.104 125.432C133.104 125.8 133.232 126.112 133.488 126.368C133.728 126.608 134.032 126.728 134.4 126.728C134.768 126.728 135.08 126.608 135.336 126.368C135.576 126.112 135.696 125.8 135.696 125.432ZM140.496 125.432C140.496 125.064 140.376 124.76 140.136 124.52C139.88 124.264 139.568 124.136 139.2 124.136C138.832 124.136 138.528 124.264 138.288 124.52C138.032 124.76 137.904 125.064 137.904 125.432C137.904 125.8 138.032 126.112 138.288 126.368C138.528 126.608 138.832 126.728 139.2 126.728C139.568 126.728 139.88 126.608 140.136 126.368C140.376 126.112 140.496 125.8 140.496 125.432ZM145.296 125.432C145.296 125.064 145.168 124.76 144.912 124.52C144.672 124.264 144.368 124.136 144 124.136C143.632 124.136 143.328 124.264 143.088 124.52C142.832 124.76 142.704 125.064 142.704 125.432C142.704 125.8 142.832 126.112 143.088 126.368C143.328 126.608 143.632 126.728 144 126.728C144.368 126.728 144.672 126.608 144.912 126.368C145.168 126.112 145.296 125.8 145.296 125.432ZM133.944 127.664C133.944 127.12 133.76 126.664 133.392 126.296C133.008 125.912 132.544 125.72 132 125.72C131.456 125.72 131 125.912 130.632 126.296C130.248 126.664 130.056 127.12 130.056 127.664C130.056 128.208 130.248 128.672 130.632 129.056C131 129.424 131.456 129.608 132 129.608C132.544 129.608 133.008 129.424 133.392 129.056C133.76 128.672 133.944 128.208 133.944 127.664ZM143.544 127.664C143.544 127.12 143.36 126.664 142.992 126.296C142.608 125.912 142.144 125.72 141.6 125.72C141.056 125.72 140.6 125.912 140.232 126.296C139.848 126.664 139.656 127.12 139.656 127.664C139.656 128.208 139.848 128.672 140.232 129.056C140.6 129.424 141.056 129.608 141.6 129.608C142.144 129.608 142.608 129.424 142.992 129.056C143.36 128.672 143.544 128.208 143.544 127.664ZM138.744 127.664C138.744 127.12 138.56 126.664 138.192 126.296C137.808 125.912 137.344 125.72 136.8 125.72C136.256 125.72 135.8 125.912 135.432 126.296C135.048 126.664 134.856 127.12 134.856 127.664C134.856 128.208 135.048 128.672 135.432 129.056C135.8 129.424 136.256 129.608 136.8 129.608C137.344 129.608 137.808 129.424 138.192 129.056C138.56 128.672 138.744 128.208 138.744 127.664ZM135.696 129.896C135.696 129.528 135.576 129.224 135.336 128.984C135.08 128.728 134.768 128.6 134.4 128.6C134.032 128.6 133.728 128.728 133.488 128.984C133.232 129.224 133.104 129.528 133.104 129.896C133.104 130.264 133.232 130.576 133.488 130.832C133.728 131.072 134.032 131.192 134.4 131.192C134.768 131.192 135.08 131.072 135.336 130.832C135.576 130.576 135.696 130.264 135.696 129.896ZM145.296 129.896C145.296 129.528 145.168 129.224 144.912 128.984C144.672 128.728 144.368 128.6 144 128.6C143.632 128.6 143.328 128.728 143.088 128.984C142.832 129.224 142.704 129.528 142.704 129.896C142.704 130.264 142.832 130.576 143.088 130.832C143.328 131.072 143.632 131.192 144 131.192C144.368 131.192 144.672 131.072 144.912 130.832C145.168 130.576 145.296 130.264 145.296 129.896ZM140.496 129.896C140.496 129.528 140.376 129.224 140.136 128.984C139.88 128.728 139.568 128.6 139.2 128.6C138.832 128.6 138.528 128.728 138.288 128.984C138.032 129.224 137.904 129.528 137.904 129.896C137.904 130.264 138.032 130.576 138.288 130.832C138.528 131.072 138.832 131.192 139.2 131.192C139.568 131.192 139.88 131.072 140.136 130.832C140.376 130.576 140.496 130.264 140.496 129.896ZM133.944 132.128C133.944 131.584 133.76 131.128 133.392 130.76C133.008 130.376 132.544 130.184 132 130.184C131.456 130.184 131 130.376 130.632 130.76C130.248 131.128 130.056 131.584 130.056 132.128C130.056 132.672 130.248 133.136 130.632 133.52C131 133.888 131.456 134.072 132 134.072C132.544 134.072 133.008 133.888 133.392 133.52C133.76 133.136 133.944 132.672 133.944 132.128ZM143.544 132.128C143.544 131.584 143.36 131.128 142.992 130.76C142.608 130.376 142.144 130.184 141.6 130.184C141.056 130.184 140.6 130.376 140.232 130.76C139.848 131.128 139.656 131.584 139.656 132.128C139.656 132.672 139.848 133.136 140.232 133.52C140.6 133.888 141.056 134.072 141.6 134.072C142.144 134.072 142.608 133.888 142.992 133.52C143.36 133.136 143.544 132.672 143.544 132.128ZM138.744 132.128C138.744 131.584 138.56 131.128 138.192 130.76C137.808 130.376 137.344 130.184 136.8 130.184C136.256 130.184 135.8 130.376 135.432 130.76C135.048 131.128 134.856 131.584 134.856 132.128C134.856 132.672 135.048 133.136 135.432 133.52C135.8 133.888 136.256 134.072 136.8 134.072C137.344 134.072 137.808 133.888 138.192 133.52C138.56 133.136 138.744 132.672 138.744 132.128ZM135.696 134.36C135.696 133.992 135.576 133.688 135.336 133.448C135.08 133.192 134.768 133.064 134.4 133.064C134.032 133.064 133.728 133.192 133.488 133.448C133.232 133.688 133.104 133.992 133.104 134.36C133.104 134.728 133.232 135.04 133.488 135.296C133.728 135.536 134.032 135.656 134.4 135.656C134.768 135.656 135.08 135.536 135.336 135.296C135.576 135.04 135.696 134.728 135.696 134.36ZM145.296 134.36C145.296 133.992 145.168 133.688 144.912 133.448C144.672 133.192 144.368 133.064 144 133.064C143.632 133.064 143.328 133.192 143.088 133.448C142.832 133.688 142.704 133.992 142.704 134.36C142.704 134.728 142.832 135.04 143.088 135.296C143.328 135.536 143.632 135.656 144 135.656C144.368 135.656 144.672 135.536 144.912 135.296C145.168 135.04 145.296 134.728 145.296 134.36ZM140.496 134.36C140.496 133.992 140.376 133.688 140.136 133.448C139.88 133.192 139.568 133.064 139.2 133.064C138.832 133.064 138.528 133.192 138.288 133.448C138.032 133.688 137.904 133.992 137.904 134.36C137.904 134.728 138.032 135.04 138.288 135.296C138.528 135.536 138.832 135.656 139.2 135.656C139.568 135.656 139.88 135.536 140.136 135.296C140.376 135.04 140.496 134.728 140.496 134.36ZM138.744 136.592C138.744 136.048 138.56 135.592 138.192 135.224C137.808 134.84 137.344 134.648 136.8 134.648C136.256 134.648 135.8 134.84 135.432 135.224C135.048 135.592 134.856 136.048 134.856 136.592C134.856 137.136 135.048 137.6 135.432 137.984C135.8 138.352 136.256 138.536 136.8 138.536C137.344 138.536 137.808 138.352 138.192 137.984C138.56 137.6 138.744 137.136 138.744 136.592ZM143.544 136.592C143.544 136.048 143.36 135.592 142.992 135.224C142.608 134.84 142.144 134.648 141.6 134.648C141.056 134.648 140.6 134.84 140.232 135.224C139.848 135.592 139.656 136.048 139.656 136.592C139.656 137.136 139.848 137.6 140.232 137.984C140.6 138.352 141.056 138.536 141.6 138.536C142.144 138.536 142.608 138.352 142.992 137.984C143.36 137.6 143.544 137.136 143.544 136.592ZM133.944 136.592C133.944 136.048 133.76 135.592 133.392 135.224C133.008 134.84 132.544 134.648 132 134.648C131.456 134.648 131 134.84 130.632 135.224C130.248 135.592 130.056 136.048 130.056 136.592C130.056 137.136 130.248 137.6 130.632 137.984C131 138.352 131.456 138.536 132 138.536C132.544 138.536 133.008 138.352 133.392 137.984C133.76 137.6 133.944 137.136 133.944 136.592ZM140.496 138.824C140.496 138.456 140.376 138.152 140.136 137.912C139.88 137.656 139.568 137.528 139.2 137.528C138.832 137.528 138.528 137.656 138.288 137.912C138.032 138.152 137.904 138.456 137.904 138.824C137.904 139.192 138.032 139.504 138.288 139.76C138.528 140 138.832 140.12 139.2 140.12C139.568 140.12 139.88 140 140.136 139.76C140.376 139.504 140.496 139.192 140.496 138.824ZM145.296 138.824C145.296 138.456 145.168 138.152 144.912 137.912C144.672 137.656 144.368 137.528 144 137.528C143.632 137.528 143.328 137.656 143.088 137.912C142.832 138.152 142.704 138.456 142.704 138.824C142.704 139.192 142.832 139.504 143.088 139.76C143.328 140 143.632 140.12 144 140.12C144.368 140.12 144.672 140 144.912 139.76C145.168 139.504 145.296 139.192 145.296 138.824ZM135.696 138.824C135.696 138.456 135.576 138.152 135.336 137.912C135.08 137.656 134.768 137.528 134.4 137.528C134.032 137.528 133.728 137.656 133.488 137.912C133.232 138.152 133.104 138.456 133.104 138.824C133.104 139.192 133.232 139.504 133.488 139.76C133.728 140 134.032 140.12 134.4 140.12C134.768 140.12 135.08 140 135.336 139.76C135.576 139.504 135.696 139.192 135.696 138.824ZM138.744 141.056C138.744 140.512 138.56 140.056 138.192 139.688C137.808 139.304 137.344 139.112 136.8 139.112C136.256 139.112 135.8 139.304 135.432 139.688C135.048 140.056 134.856 140.512 134.856 141.056C134.856 141.6 135.048 142.064 135.432 142.448C135.8 142.816 136.256 143 136.8 143C137.344 143 137.808 142.816 138.192 142.448C138.56 142.064 138.744 141.6 138.744 141.056ZM133.944 141.056C133.944 140.512 133.76 140.056 133.392 139.688C133.008 139.304 132.544 139.112 132 139.112C131.456 139.112 131 139.304 130.632 139.688C130.248 140.056 130.056 140.512 130.056 141.056C130.056 141.6 130.248 142.064 130.632 142.448C131 142.816 131.456 143 132 143C132.544 143 133.008 142.816 133.392 142.448C133.76 142.064 133.944 141.6 133.944 141.056ZM143.544 141.056C143.544 140.512 143.36 140.056 142.992 139.688C142.608 139.304 142.144 139.112 141.6 139.112C141.056 139.112 140.6 139.304 140.232 139.688C139.848 140.056 139.656 140.512 139.656 141.056C139.656 141.6 139.848 142.064 140.232 142.448C140.6 142.816 141.056 143 141.6 143C142.144 143 142.608 142.816 142.992 142.448C143.36 142.064 143.544 141.6 143.544 141.056ZM140.496 143.288C140.496 142.92 140.376 142.616 140.136 142.376C139.88 142.12 139.568 141.992 139.2 141.992C138.832 141.992 138.528 142.12 138.288 142.376C138.032 142.616 137.904 142.92 137.904 143.288C137.904 143.656 138.032 143.968 138.288 144.224C138.528 144.464 138.832 144.584 139.2 144.584C139.568 144.584 139.88 144.464 140.136 144.224C140.376 143.968 140.496 143.656 140.496 143.288ZM135.696 143.288C135.696 142.92 135.576 142.616 135.336 142.376C135.08 142.12 134.768 141.992 134.4 141.992C134.032 141.992 133.728 142.12 133.488 142.376C133.232 142.616 133.104 142.92 133.104 143.288C133.104 143.656 133.232 143.968 133.488 144.224C133.728 144.464 134.032 144.584 134.4 144.584C134.768 144.584 135.08 144.464 135.336 144.224C135.576 143.968 135.696 143.656 135.696 143.288ZM145.296 143.288C145.296 142.92 145.168 142.616 144.912 142.376C144.672 142.12 144.368 141.992 144 141.992C143.632 141.992 143.328 142.12 143.088 142.376C142.832 142.616 142.704 142.92 142.704 143.288C142.704 143.656 142.832 143.968 143.088 144.224C143.328 144.464 143.632 144.584 144 144.584C144.368 144.584 144.672 144.464 144.912 144.224C145.168 143.968 145.296 143.656 145.296 143.288ZM138.744 145.52C138.744 144.976 138.56 144.52 138.192 144.152C137.808 143.768 137.344 143.576 136.8 143.576C136.256 143.576 135.8 143.768 135.432 144.152C135.048 144.52 134.856 144.976 134.856 145.52C134.856 146.064 135.048 146.52 135.432 146.888C135.8 147.272 136.256 147.464 136.8 147.464C137.344 147.464 137.808 147.272 138.192 146.888C138.56 146.52 138.744 146.064 138.744 145.52ZM133.944 145.52C133.944 144.976 133.76 144.52 133.392 144.152C133.008 143.768 132.544 143.576 132 143.576C131.456 143.576 131 143.768 130.632 144.152C130.248 144.52 130.056 144.976 130.056 145.52C130.056 146.064 130.248 146.52 130.632 146.888C131 147.272 131.456 147.464 132 147.464C132.544 147.464 133.008 147.272 133.392 146.888C133.76 146.52 133.944 146.064 133.944 145.52ZM143.544 145.52C143.544 144.976 143.36 144.52 142.992 144.152C142.608 143.768 142.144 143.576 141.6 143.576C141.056 143.576 140.6 143.768 140.232 144.152C139.848 144.52 139.656 144.976 139.656 145.52C139.656 146.064 139.848 146.52 140.232 146.888C140.6 147.272 141.056 147.464 141.6 147.464C142.144 147.464 142.608 147.272 142.992 146.888C143.36 146.52 143.544 146.064 143.544 145.52ZM140.496 147.752C140.496 147.384 140.376 147.072 140.136 146.816C139.88 146.576 139.568 146.456 139.2 146.456C138.832 146.456 138.528 146.576 138.288 146.816C138.032 147.072 137.904 147.384 137.904 147.752C137.904 148.12 138.032 148.424 138.288 148.664C138.528 148.92 138.832 149.048 139.2 149.048C139.568 149.048 139.88 148.92 140.136 148.664C140.376 148.424 140.496 148.12 140.496 147.752ZM135.696 147.752C135.696 147.384 135.576 147.072 135.336 146.816C135.08 146.576 134.768 146.456 134.4 146.456C134.032 146.456 133.728 146.576 133.488 146.816C133.232 147.072 133.104 147.384 133.104 147.752C133.104 148.12 133.232 148.424 133.488 148.664C133.728 148.92 134.032 149.048 134.4 149.048C134.768 149.048 135.08 148.92 135.336 148.664C135.576 148.424 135.696 148.12 135.696 147.752ZM145.296 147.752C145.296 147.384 145.168 147.072 144.912 146.816C144.672 146.576 144.368 146.456 144 146.456C143.632 146.456 143.328 146.576 143.088 146.816C142.832 147.072 142.704 147.384 142.704 147.752C142.704 148.12 142.832 148.424 143.088 148.664C143.328 148.92 143.632 149.048 144 149.048C144.368 149.048 144.672 148.92 144.912 148.664C145.168 148.424 145.296 148.12 145.296 147.752ZM138.744 149.984C138.744 149.44 138.56 148.976 138.192 148.592C137.808 148.224 137.344 148.04 136.8 148.04C136.256 148.04 135.8 148.224 135.432 148.592C135.048 148.976 134.856 149.44 134.856 149.984C134.856 150.528 135.048 150.984 135.432 151.352C135.8 151.736 136.256 151.928 136.8 151.928C137.344 151.928 137.808 151.736 138.192 151.352C138.56 150.984 138.744 150.528 138.744 149.984ZM133.944 149.984C133.944 149.44 133.76 148.976 133.392 148.592C133.008 148.224 132.544 148.04 132 148.04C131.456 148.04 131 148.224 130.632 148.592C130.248 148.976 130.056 149.44 130.056 149.984C130.056 150.528 130.248 150.984 130.632 151.352C131 151.736 131.456 151.928 132 151.928C132.544 151.928 133.008 151.736 133.392 151.352C133.76 150.984 133.944 150.528 133.944 149.984ZM143.544 149.984C143.544 149.44 143.36 148.976 142.992 148.592C142.608 148.224 142.144 148.04 141.6 148.04C141.056 148.04 140.6 148.224 140.232 148.592C139.848 148.976 139.656 149.44 139.656 149.984C139.656 150.528 139.848 150.984 140.232 151.352C140.6 151.736 141.056 151.928 141.6 151.928C142.144 151.928 142.608 151.736 142.992 151.352C143.36 150.984 143.544 150.528 143.544 149.984ZM140.496 152.216C140.496 151.848 140.376 151.536 140.136 151.28C139.88 151.04 139.568 150.92 139.2 150.92C138.832 150.92 138.528 151.04 138.288 151.28C138.032 151.536 137.904 151.848 137.904 152.216C137.904 152.584 138.032 152.888 138.288 153.128C138.528 153.384 138.832 153.512 139.2 153.512C139.568 153.512 139.88 153.384 140.136 153.128C140.376 152.888 140.496 152.584 140.496 152.216ZM135.696 152.216C135.696 151.848 135.576 151.536 135.336 151.28C135.08 151.04 134.768 150.92 134.4 150.92C134.032 150.92 133.728 151.04 133.488 151.28C133.232 151.536 133.104 151.848 133.104 152.216C133.104 152.584 133.232 152.888 133.488 153.128C133.728 153.384 134.032 153.512 134.4 153.512C134.768 153.512 135.08 153.384 135.336 153.128C135.576 152.888 135.696 152.584 135.696 152.216ZM145.296 152.216C145.296 151.848 145.168 151.536 144.912 151.28C144.672 151.04 144.368 150.92 144 150.92C143.632 150.92 143.328 151.04 143.088 151.28C142.832 151.536 142.704 151.848 142.704 152.216C142.704 152.584 142.832 152.888 143.088 153.128C143.328 153.384 143.632 153.512 144 153.512C144.368 153.512 144.672 153.384 144.912 153.128C145.168 152.888 145.296 152.584 145.296 152.216ZM133.944 154.2C133.944 153.656 133.76 153.2 133.392 152.832C133.008 152.448 132.544 152.256 132 152.256C131.456 152.256 131 152.448 130.632 152.832C130.248 153.2 130.056 153.656 130.056 154.2C130.056 154.744 130.248 155.208 130.632 155.592C131 155.96 131.456 156.144 132 156.144C132.544 156.144 133.008 155.96 133.392 155.592C133.76 155.208 133.944 154.744 133.944 154.2ZM138.744 154.2C138.744 153.656 138.56 153.2 138.192 152.832C137.808 152.448 137.344 152.256 136.8 152.256C136.256 152.256 135.8 152.448 135.432 152.832C135.048 153.2 134.856 153.656 134.856 154.2C134.856 154.744 135.048 155.208 135.432 155.592C135.8 155.96 136.256 156.144 136.8 156.144C137.344 156.144 137.808 155.96 138.192 155.592C138.56 155.208 138.744 154.744 138.744 154.2ZM143.544 154.2C143.544 153.656 143.36 153.2 142.992 152.832C142.608 152.448 142.144 152.256 141.6 152.256C141.056 152.256 140.6 152.448 140.232 152.832C139.848 153.2 139.656 153.656 139.656 154.2C139.656 154.744 139.848 155.208 140.232 155.592C140.6 155.96 141.056 156.144 141.6 156.144C142.144 156.144 142.608 155.96 142.992 155.592C143.36 155.208 143.544 154.744 143.544 154.2ZM135.696 156.432C135.696 156.064 135.576 155.76 135.336 155.52C135.08 155.264 134.768 155.136 134.4 155.136C134.032 155.136 133.728 155.264 133.488 155.52C133.232 155.76 133.104 156.064 133.104 156.432C133.104 156.8 133.232 157.112 133.488 157.368C133.728 157.608 134.032 157.728 134.4 157.728C134.768 157.728 135.08 157.608 135.336 157.368C135.576 157.112 135.696 156.8 135.696 156.432ZM140.496 156.432C140.496 156.064 140.376 155.76 140.136 155.52C139.88 155.264 139.568 155.136 139.2 155.136C138.832 155.136 138.528 155.264 138.288 155.52C138.032 155.76 137.904 156.064 137.904 156.432C137.904 156.8 138.032 157.112 138.288 157.368C138.528 157.608 138.832 157.728 139.2 157.728C139.568 157.728 139.88 157.608 140.136 157.368C140.376 157.112 140.496 156.8 140.496 156.432ZM145.296 156.432C145.296 156.064 145.168 155.76 144.912 155.52C144.672 155.264 144.368 155.136 144 155.136C143.632 155.136 143.328 155.264 143.088 155.52C142.832 155.76 142.704 156.064 142.704 156.432C142.704 156.8 142.832 157.112 143.088 157.368C143.328 157.608 143.632 157.728 144 157.728C144.368 157.728 144.672 157.608 144.912 157.368C145.168 157.112 145.296 156.8 145.296 156.432ZM133.944 158.664C133.944 158.12 133.76 157.664 133.392 157.296C133.008 156.912 132.544 156.72 132 156.72C131.456 156.72 131 156.912 130.632 157.296C130.248 157.664 130.056 158.12 130.056 158.664C130.056 159.208 130.248 159.672 130.632 160.056C131 160.424 131.456 160.608 132 160.608C132.544 160.608 133.008 160.424 133.392 160.056C133.76 159.672 133.944 159.208 133.944 158.664ZM143.544 158.664C143.544 158.12 143.36 157.664 142.992 157.296C142.608 156.912 142.144 156.72 141.6 156.72C141.056 156.72 140.6 156.912 140.232 157.296C139.848 157.664 139.656 158.12 139.656 158.664C139.656 159.208 139.848 159.672 140.232 160.056C140.6 160.424 141.056 160.608 141.6 160.608C142.144 160.608 142.608 160.424 142.992 160.056C143.36 159.672 143.544 159.208 143.544 158.664ZM138.744 158.664C138.744 158.12 138.56 157.664 138.192 157.296C137.808 156.912 137.344 156.72 136.8 156.72C136.256 156.72 135.8 156.912 135.432 157.296C135.048 157.664 134.856 158.12 134.856 158.664C134.856 159.208 135.048 159.672 135.432 160.056C135.8 160.424 136.256 160.608 136.8 160.608C137.344 160.608 137.808 160.424 138.192 160.056C138.56 159.672 138.744 159.208 138.744 158.664ZM135.696 160.896C135.696 160.528 135.576 160.224 135.336 159.984C135.08 159.728 134.768 159.6 134.4 159.6C134.032 159.6 133.728 159.728 133.488 159.984C133.232 160.224 133.104 160.528 133.104 160.896C133.104 161.264 133.232 161.576 133.488 161.832C133.728 162.072 134.032 162.192 134.4 162.192C134.768 162.192 135.08 162.072 135.336 161.832C135.576 161.576 135.696 161.264 135.696 160.896ZM145.296 160.896C145.296 160.528 145.168 160.224 144.912 159.984C144.672 159.728 144.368 159.6 144 159.6C143.632 159.6 143.328 159.728 143.088 159.984C142.832 160.224 142.704 160.528 142.704 160.896C142.704 161.264 142.832 161.576 143.088 161.832C143.328 162.072 143.632 162.192 144 162.192C144.368 162.192 144.672 162.072 144.912 161.832C145.168 161.576 145.296 161.264 145.296 160.896ZM140.496 160.896C140.496 160.528 140.376 160.224 140.136 159.984C139.88 159.728 139.568 159.6 139.2 159.6C138.832 159.6 138.528 159.728 138.288 159.984C138.032 160.224 137.904 160.528 137.904 160.896C137.904 161.264 138.032 161.576 138.288 161.832C138.528 162.072 138.832 162.192 139.2 162.192C139.568 162.192 139.88 162.072 140.136 161.832C140.376 161.576 140.496 161.264 140.496 160.896ZM133.944 163.128C133.944 162.584 133.76 162.128 133.392 161.76C133.008 161.376 132.544 161.184 132 161.184C131.456 161.184 131 161.376 130.632 161.76C130.248 162.128 130.056 162.584 130.056 163.128C130.056 163.672 130.248 164.136 130.632 164.52C131 164.888 131.456 165.072 132 165.072C132.544 165.072 133.008 164.888 133.392 164.52C133.76 164.136 133.944 163.672 133.944 163.128ZM143.544 163.128C143.544 162.584 143.36 162.128 142.992 161.76C142.608 161.376 142.144 161.184 141.6 161.184C141.056 161.184 140.6 161.376 140.232 161.76C139.848 162.128 139.656 162.584 139.656 163.128C139.656 163.672 139.848 164.136 140.232 164.52C140.6 164.888 141.056 165.072 141.6 165.072C142.144 165.072 142.608 164.888 142.992 164.52C143.36 164.136 143.544 163.672 143.544 163.128ZM138.744 163.128C138.744 162.584 138.56 162.128 138.192 161.76C137.808 161.376 137.344 161.184 136.8 161.184C136.256 161.184 135.8 161.376 135.432 161.76C135.048 162.128 134.856 162.584 134.856 163.128C134.856 163.672 135.048 164.136 135.432 164.52C135.8 164.888 136.256 165.072 136.8 165.072C137.344 165.072 137.808 164.888 138.192 164.52C138.56 164.136 138.744 163.672 138.744 163.128ZM135.696 165.36C135.696 164.992 135.576 164.688 135.336 164.448C135.08 164.192 134.768 164.064 134.4 164.064C134.032 164.064 133.728 164.192 133.488 164.448C133.232 164.688 133.104 164.992 133.104 165.36C133.104 165.728 133.232 166.04 133.488 166.296C133.728 166.536 134.032 166.656 134.4 166.656C134.768 166.656 135.08 166.536 135.336 166.296C135.576 166.04 135.696 165.728 135.696 165.36ZM145.296 165.36C145.296 164.992 145.168 164.688 144.912 164.448C144.672 164.192 144.368 164.064 144 164.064C143.632 164.064 143.328 164.192 143.088 164.448C142.832 164.688 142.704 164.992 142.704 165.36C142.704 165.728 142.832 166.04 143.088 166.296C143.328 166.536 143.632 166.656 144 166.656C144.368 166.656 144.672 166.536 144.912 166.296C145.168 166.04 145.296 165.728 145.296 165.36ZM140.496 165.36C140.496 164.992 140.376 164.688 140.136 164.448C139.88 164.192 139.568 164.064 139.2 164.064C138.832 164.064 138.528 164.192 138.288 164.448C138.032 164.688 137.904 164.992 137.904 165.36C137.904 165.728 138.032 166.04 138.288 166.296C138.528 166.536 138.832 166.656 139.2 166.656C139.568 166.656 139.88 166.536 140.136 166.296C140.376 166.04 140.496 165.728 140.496 165.36ZM138.744 167.592C138.744 167.048 138.56 166.592 138.192 166.224C137.808 165.84 137.344 165.648 136.8 165.648C136.256 165.648 135.8 165.84 135.432 166.224C135.048 166.592 134.856 167.048 134.856 167.592C134.856 168.136 135.048 168.6 135.432 168.984C135.8 169.352 136.256 169.536 136.8 169.536C137.344 169.536 137.808 169.352 138.192 168.984C138.56 168.6 138.744 168.136 138.744 167.592ZM143.544 167.592C143.544 167.048 143.36 166.592 142.992 166.224C142.608 165.84 142.144 165.648 141.6 165.648C141.056 165.648 140.6 165.84 140.232 166.224C139.848 166.592 139.656 167.048 139.656 167.592C139.656 168.136 139.848 168.6 140.232 168.984C140.6 169.352 141.056 169.536 141.6 169.536C142.144 169.536 142.608 169.352 142.992 168.984C143.36 168.6 143.544 168.136 143.544 167.592ZM133.944 167.592C133.944 167.048 133.76 166.592 133.392 166.224C133.008 165.84 132.544 165.648 132 165.648C131.456 165.648 131 165.84 130.632 166.224C130.248 166.592 130.056 167.048 130.056 167.592C130.056 168.136 130.248 168.6 130.632 168.984C131 169.352 131.456 169.536 132 169.536C132.544 169.536 133.008 169.352 133.392 168.984C133.76 168.6 133.944 168.136 133.944 167.592ZM140.496 169.824C140.496 169.456 140.376 169.152 140.136 168.912C139.88 168.656 139.568 168.528 139.2 168.528C138.832 168.528 138.528 168.656 138.288 168.912C138.032 169.152 137.904 169.456 137.904 169.824C137.904 170.192 138.032 170.504 138.288 170.76C138.528 171 138.832 171.12 139.2 171.12C139.568 171.12 139.88 171 140.136 170.76C140.376 170.504 140.496 170.192 140.496 169.824ZM145.296 169.824C145.296 169.456 145.168 169.152 144.912 168.912C144.672 168.656 144.368 168.528 144 168.528C143.632 168.528 143.328 168.656 143.088 168.912C142.832 169.152 142.704 169.456 142.704 169.824C142.704 170.192 142.832 170.504 143.088 170.76C143.328 171 143.632 171.12 144 171.12C144.368 171.12 144.672 171 144.912 170.76C145.168 170.504 145.296 170.192 145.296 169.824ZM135.696 169.824C135.696 169.456 135.576 169.152 135.336 168.912C135.08 168.656 134.768 168.528 134.4 168.528C134.032 168.528 133.728 168.656 133.488 168.912C133.232 169.152 133.104 169.456 133.104 169.824C133.104 170.192 133.232 170.504 133.488 170.76C133.728 171 134.032 171.12 134.4 171.12C134.768 171.12 135.08 171 135.336 170.76C135.576 170.504 135.696 170.192 135.696 169.824ZM138.744 172.056C138.744 171.512 138.56 171.056 138.192 170.688C137.808 170.304 137.344 170.112 136.8 170.112C136.256 170.112 135.8 170.304 135.432 170.688C135.048 171.056 134.856 171.512 134.856 172.056C134.856 172.6 135.048 173.064 135.432 173.448C135.8 173.816 136.256 174 136.8 174C137.344 174 137.808 173.816 138.192 173.448C138.56 173.064 138.744 172.6 138.744 172.056ZM133.944 172.056C133.944 171.512 133.76 171.056 133.392 170.688C133.008 170.304 132.544 170.112 132 170.112C131.456 170.112 131 170.304 130.632 170.688C130.248 171.056 130.056 171.512 130.056 172.056C130.056 172.6 130.248 173.064 130.632 173.448C131 173.816 131.456 174 132 174C132.544 174 133.008 173.816 133.392 173.448C133.76 173.064 133.944 172.6 133.944 172.056ZM143.544 172.056C143.544 171.512 143.36 171.056 142.992 170.688C142.608 170.304 142.144 170.112 141.6 170.112C141.056 170.112 140.6 170.304 140.232 170.688C139.848 171.056 139.656 171.512 139.656 172.056C139.656 172.6 139.848 173.064 140.232 173.448C140.6 173.816 141.056 174 141.6 174C142.144 174 142.608 173.816 142.992 173.448C143.36 173.064 143.544 172.6 143.544 172.056ZM140.496 174.288C140.496 173.92 140.376 173.616 140.136 173.376C139.88 173.12 139.568 172.992 139.2 172.992C138.832 172.992 138.528 173.12 138.288 173.376C138.032 173.616 137.904 173.92 137.904 174.288C137.904 174.656 138.032 174.968 138.288 175.224C138.528 175.464 138.832 175.584 139.2 175.584C139.568 175.584 139.88 175.464 140.136 175.224C140.376 174.968 140.496 174.656 140.496 174.288ZM135.696 174.288C135.696 173.92 135.576 173.616 135.336 173.376C135.08 173.12 134.768 172.992 134.4 172.992C134.032 172.992 133.728 173.12 133.488 173.376C133.232 173.616 133.104 173.92 133.104 174.288C133.104 174.656 133.232 174.968 133.488 175.224C133.728 175.464 134.032 175.584 134.4 175.584C134.768 175.584 135.08 175.464 135.336 175.224C135.576 174.968 135.696 174.656 135.696 174.288ZM145.296 174.288C145.296 173.92 145.168 173.616 144.912 173.376C144.672 173.12 144.368 172.992 144 172.992C143.632 172.992 143.328 173.12 143.088 173.376C142.832 173.616 142.704 173.92 142.704 174.288C142.704 174.656 142.832 174.968 143.088 175.224C143.328 175.464 143.632 175.584 144 175.584C144.368 175.584 144.672 175.464 144.912 175.224C145.168 174.968 145.296 174.656 145.296 174.288ZM138.744 176.52C138.744 175.976 138.56 175.52 138.192 175.152C137.808 174.768 137.344 174.576 136.8 174.576C136.256 174.576 135.8 174.768 135.432 175.152C135.048 175.52 134.856 175.976 134.856 176.52C134.856 177.064 135.048 177.52 135.432 177.888C135.8 178.272 136.256 178.464 136.8 178.464C137.344 178.464 137.808 178.272 138.192 177.888C138.56 177.52 138.744 177.064 138.744 176.52ZM133.944 176.52C133.944 175.976 133.76 175.52 133.392 175.152C133.008 174.768 132.544 174.576 132 174.576C131.456 174.576 131 174.768 130.632 175.152C130.248 175.52 130.056 175.976 130.056 176.52C130.056 177.064 130.248 177.52 130.632 177.888C131 178.272 131.456 178.464 132 178.464C132.544 178.464 133.008 178.272 133.392 177.888C133.76 177.52 133.944 177.064 133.944 176.52ZM143.544 176.52C143.544 175.976 143.36 175.52 142.992 175.152C142.608 174.768 142.144 174.576 141.6 174.576C141.056 174.576 140.6 174.768 140.232 175.152C139.848 175.52 139.656 175.976 139.656 176.52C139.656 177.064 139.848 177.52 140.232 177.888C140.6 178.272 141.056 178.464 141.6 178.464C142.144 178.464 142.608 178.272 142.992 177.888C143.36 177.52 143.544 177.064 143.544 176.52ZM140.496 178.752C140.496 178.384 140.376 178.072 140.136 177.816C139.88 177.576 139.568 177.456 139.2 177.456C138.832 177.456 138.528 177.576 138.288 177.816C138.032 178.072 137.904 178.384 137.904 178.752C137.904 179.12 138.032 179.424 138.288 179.664C138.528 179.92 138.832 180.048 139.2 180.048C139.568 180.048 139.88 179.92 140.136 179.664C140.376 179.424 140.496 179.12 140.496 178.752ZM135.696 178.752C135.696 178.384 135.576 178.072 135.336 177.816C135.08 177.576 134.768 177.456 134.4 177.456C134.032 177.456 133.728 177.576 133.488 177.816C133.232 178.072 133.104 178.384 133.104 178.752C133.104 179.12 133.232 179.424 133.488 179.664C133.728 179.92 134.032 180.048 134.4 180.048C134.768 180.048 135.08 179.92 135.336 179.664C135.576 179.424 135.696 179.12 135.696 178.752ZM145.296 178.752C145.296 178.384 145.168 178.072 144.912 177.816C144.672 177.576 144.368 177.456 144 177.456C143.632 177.456 143.328 177.576 143.088 177.816C142.832 178.072 142.704 178.384 142.704 178.752C142.704 179.12 142.832 179.424 143.088 179.664C143.328 179.92 143.632 180.048 144 180.048C144.368 180.048 144.672 179.92 144.912 179.664C145.168 179.424 145.296 179.12 145.296 178.752ZM138.744 180.984C138.744 180.44 138.56 179.976 138.192 179.592C137.808 179.224 137.344 179.04 136.8 179.04C136.256 179.04 135.8 179.224 135.432 179.592C135.048 179.976 134.856 180.44 134.856 180.984C134.856 181.528 135.048 181.984 135.432 182.352C135.8 182.736 136.256 182.928 136.8 182.928C137.344 182.928 137.808 182.736 138.192 182.352C138.56 181.984 138.744 181.528 138.744 180.984ZM133.944 180.984C133.944 180.44 133.76 179.976 133.392 179.592C133.008 179.224 132.544 179.04 132 179.04C131.456 179.04 131 179.224 130.632 179.592C130.248 179.976 130.056 180.44 130.056 180.984C130.056 181.528 130.248 181.984 130.632 182.352C131 182.736 131.456 182.928 132 182.928C132.544 182.928 133.008 182.736 133.392 182.352C133.76 181.984 133.944 181.528 133.944 180.984ZM143.544 180.984C143.544 180.44 143.36 179.976 142.992 179.592C142.608 179.224 142.144 179.04 141.6 179.04C141.056 179.04 140.6 179.224 140.232 179.592C139.848 179.976 139.656 180.44 139.656 180.984C139.656 181.528 139.848 181.984 140.232 182.352C140.6 182.736 141.056 182.928 141.6 182.928C142.144 182.928 142.608 182.736 142.992 182.352C143.36 181.984 143.544 181.528 143.544 180.984ZM140.496 183.216C140.496 182.848 140.376 182.536 140.136 182.28C139.88 182.04 139.568 181.92 139.2 181.92C138.832 181.92 138.528 182.04 138.288 182.28C138.032 182.536 137.904 182.848 137.904 183.216C137.904 183.584 138.032 183.888 138.288 184.128C138.528 184.384 138.832 184.512 139.2 184.512C139.568 184.512 139.88 184.384 140.136 184.128C140.376 183.888 140.496 183.584 140.496 183.216ZM135.696 183.216C135.696 182.848 135.576 182.536 135.336 182.28C135.08 182.04 134.768 181.92 134.4 181.92C134.032 181.92 133.728 182.04 133.488 182.28C133.232 182.536 133.104 182.848 133.104 183.216C133.104 183.584 133.232 183.888 133.488 184.128C133.728 184.384 134.032 184.512 134.4 184.512C134.768 184.512 135.08 184.384 135.336 184.128C135.576 183.888 135.696 183.584 135.696 183.216ZM145.296 183.216C145.296 182.848 145.168 182.536 144.912 182.28C144.672 182.04 144.368 181.92 144 181.92C143.632 181.92 143.328 182.04 143.088 182.28C142.832 182.536 142.704 182.848 142.704 183.216C142.704 183.584 142.832 183.888 143.088 184.128C143.328 184.384 143.632 184.512 144 184.512C144.368 184.512 144.672 184.384 144.912 184.128C145.168 183.888 145.296 183.584 145.296 183.216ZM119.385 154.2C119.385 153.832 119.265 153.528 119.025 153.288C118.769 153.032 118.457 152.904 118.089 152.904C117.721 152.904 117.417 153.032 117.177 153.288C116.921 153.528 116.793 153.832 116.793 154.2C116.793 154.568 116.921 154.88 117.177 155.136C117.417 155.376 117.721 155.496 118.089 155.496C118.457 155.496 118.769 155.376 119.025 155.136C119.265 154.88 119.385 154.568 119.385 154.2ZM124.185 154.2C124.185 153.832 124.065 153.528 123.825 153.288C123.569 153.032 123.257 152.904 122.889 152.904C122.521 152.904 122.217 153.032 121.977 153.288C121.721 153.528 121.593 153.832 121.593 154.2C121.593 154.568 121.721 154.88 121.977 155.136C122.217 155.376 122.521 155.496 122.889 155.496C123.257 155.496 123.569 155.376 123.825 155.136C124.065 154.88 124.185 154.568 124.185 154.2ZM128.985 154.2C128.985 153.832 128.865 153.528 128.625 153.288C128.369 153.032 128.057 152.904 127.689 152.904C127.321 152.904 127.017 153.032 126.777 153.288C126.521 153.528 126.393 153.832 126.393 154.2C126.393 154.568 126.521 154.88 126.777 155.136C127.017 155.376 127.321 155.496 127.689 155.496C128.057 155.496 128.369 155.376 128.625 155.136C128.865 154.88 128.985 154.568 128.985 154.2ZM121.353 156.432C121.353 156.192 121.273 155.992 121.113 155.832C120.937 155.656 120.729 155.568 120.489 155.568C120.249 155.568 120.049 155.656 119.889 155.832C119.713 155.992 119.625 156.192 119.625 156.432C119.625 156.672 119.713 156.88 119.889 157.056C120.049 157.216 120.249 157.296 120.489 157.296C120.729 157.296 120.937 157.216 121.113 157.056C121.273 156.88 121.353 156.672 121.353 156.432ZM126.153 156.432C126.153 156.192 126.073 155.992 125.913 155.832C125.737 155.656 125.529 155.568 125.289 155.568C125.049 155.568 124.849 155.656 124.689 155.832C124.513 155.992 124.425 156.192 124.425 156.432C124.425 156.672 124.513 156.88 124.689 157.056C124.849 157.216 125.049 157.296 125.289 157.296C125.529 157.296 125.737 157.216 125.913 157.056C126.073 156.88 126.153 156.672 126.153 156.432ZM130.953 156.432C130.953 156.192 130.865 155.992 130.689 155.832C130.529 155.656 130.329 155.568 130.089 155.568C129.849 155.568 129.649 155.656 129.489 155.832C129.313 155.992 129.225 156.192 129.225 156.432C129.225 156.672 129.313 156.88 129.489 157.056C129.649 157.216 129.849 157.296 130.089 157.296C130.329 157.296 130.529 157.216 130.689 157.056C130.865 156.88 130.953 156.672 130.953 156.432ZM119.385 158.664C119.385 158.296 119.265 157.992 119.025 157.752C118.769 157.496 118.457 157.368 118.089 157.368C117.721 157.368 117.417 157.496 117.177 157.752C116.921 157.992 116.793 158.296 116.793 158.664C116.793 159.032 116.921 159.344 117.177 159.6C117.417 159.84 117.721 159.96 118.089 159.96C118.457 159.96 118.769 159.84 119.025 159.6C119.265 159.344 119.385 159.032 119.385 158.664ZM128.985 158.664C128.985 158.296 128.865 157.992 128.625 157.752C128.369 157.496 128.057 157.368 127.689 157.368C127.321 157.368 127.017 157.496 126.777 157.752C126.521 157.992 126.393 158.296 126.393 158.664C126.393 159.032 126.521 159.344 126.777 159.6C127.017 159.84 127.321 159.96 127.689 159.96C128.057 159.96 128.369 159.84 128.625 159.6C128.865 159.344 128.985 159.032 128.985 158.664ZM124.185 158.664C124.185 158.296 124.065 157.992 123.825 157.752C123.569 157.496 123.257 157.368 122.889 157.368C122.521 157.368 122.217 157.496 121.977 157.752C121.721 157.992 121.593 158.296 121.593 158.664C121.593 159.032 121.721 159.344 121.977 159.6C122.217 159.84 122.521 159.96 122.889 159.96C123.257 159.96 123.569 159.84 123.825 159.6C124.065 159.344 124.185 159.032 124.185 158.664ZM121.353 160.896C121.353 160.656 121.273 160.456 121.113 160.296C120.937 160.12 120.729 160.032 120.489 160.032C120.249 160.032 120.049 160.12 119.889 160.296C119.713 160.456 119.625 160.656 119.625 160.896C119.625 161.136 119.713 161.344 119.889 161.52C120.049 161.68 120.249 161.76 120.489 161.76C120.729 161.76 120.937 161.68 121.113 161.52C121.273 161.344 121.353 161.136 121.353 160.896ZM130.953 160.896C130.953 160.656 130.865 160.456 130.689 160.296C130.529 160.12 130.329 160.032 130.089 160.032C129.849 160.032 129.649 160.12 129.489 160.296C129.313 160.456 129.225 160.656 129.225 160.896C129.225 161.136 129.313 161.344 129.489 161.52C129.649 161.68 129.849 161.76 130.089 161.76C130.329 161.76 130.529 161.68 130.689 161.52C130.865 161.344 130.953 161.136 130.953 160.896ZM126.153 160.896C126.153 160.656 126.073 160.456 125.913 160.296C125.737 160.12 125.529 160.032 125.289 160.032C125.049 160.032 124.849 160.12 124.689 160.296C124.513 160.456 124.425 160.656 124.425 160.896C124.425 161.136 124.513 161.344 124.689 161.52C124.849 161.68 125.049 161.76 125.289 161.76C125.529 161.76 125.737 161.68 125.913 161.52C126.073 161.344 126.153 161.136 126.153 160.896ZM119.385 163.128C119.385 162.76 119.265 162.456 119.025 162.216C118.769 161.96 118.457 161.832 118.089 161.832C117.721 161.832 117.417 161.96 117.177 162.216C116.921 162.456 116.793 162.76 116.793 163.128C116.793 163.496 116.921 163.808 117.177 164.064C117.417 164.304 117.721 164.424 118.089 164.424C118.457 164.424 118.769 164.304 119.025 164.064C119.265 163.808 119.385 163.496 119.385 163.128ZM128.985 163.128C128.985 162.76 128.865 162.456 128.625 162.216C128.369 161.96 128.057 161.832 127.689 161.832C127.321 161.832 127.017 161.96 126.777 162.216C126.521 162.456 126.393 162.76 126.393 163.128C126.393 163.496 126.521 163.808 126.777 164.064C127.017 164.304 127.321 164.424 127.689 164.424C128.057 164.424 128.369 164.304 128.625 164.064C128.865 163.808 128.985 163.496 128.985 163.128ZM124.185 163.128C124.185 162.76 124.065 162.456 123.825 162.216C123.569 161.96 123.257 161.832 122.889 161.832C122.521 161.832 122.217 161.96 121.977 162.216C121.721 162.456 121.593 162.76 121.593 163.128C121.593 163.496 121.721 163.808 121.977 164.064C122.217 164.304 122.521 164.424 122.889 164.424C123.257 164.424 123.569 164.304 123.825 164.064C124.065 163.808 124.185 163.496 124.185 163.128ZM121.353 165.36C121.353 165.12 121.273 164.92 121.113 164.76C120.937 164.584 120.729 164.496 120.489 164.496C120.249 164.496 120.049 164.584 119.889 164.76C119.713 164.92 119.625 165.12 119.625 165.36C119.625 165.6 119.713 165.808 119.889 165.984C120.049 166.144 120.249 166.224 120.489 166.224C120.729 166.224 120.937 166.144 121.113 165.984C121.273 165.808 121.353 165.6 121.353 165.36ZM130.953 165.36C130.953 165.12 130.865 164.92 130.689 164.76C130.529 164.584 130.329 164.496 130.089 164.496C129.849 164.496 129.649 164.584 129.489 164.76C129.313 164.92 129.225 165.12 129.225 165.36C129.225 165.6 129.313 165.808 129.489 165.984C129.649 166.144 129.849 166.224 130.089 166.224C130.329 166.224 130.529 166.144 130.689 165.984C130.865 165.808 130.953 165.6 130.953 165.36ZM126.153 165.36C126.153 165.12 126.073 164.92 125.913 164.76C125.737 164.584 125.529 164.496 125.289 164.496C125.049 164.496 124.849 164.584 124.689 164.76C124.513 164.92 124.425 165.12 124.425 165.36C124.425 165.6 124.513 165.808 124.689 165.984C124.849 166.144 125.049 166.224 125.289 166.224C125.529 166.224 125.737 166.144 125.913 165.984C126.073 165.808 126.153 165.6 126.153 165.36ZM124.185 167.592C124.185 167.224 124.065 166.92 123.825 166.68C123.569 166.424 123.257 166.296 122.889 166.296C122.521 166.296 122.217 166.424 121.977 166.68C121.721 166.92 121.593 167.224 121.593 167.592C121.593 167.96 121.721 168.272 121.977 168.528C122.217 168.768 122.521 168.888 122.889 168.888C123.257 168.888 123.569 168.768 123.825 168.528C124.065 168.272 124.185 167.96 124.185 167.592ZM128.985 167.592C128.985 167.224 128.865 166.92 128.625 166.68C128.369 166.424 128.057 166.296 127.689 166.296C127.321 166.296 127.017 166.424 126.777 166.68C126.521 166.92 126.393 167.224 126.393 167.592C126.393 167.96 126.521 168.272 126.777 168.528C127.017 168.768 127.321 168.888 127.689 168.888C128.057 168.888 128.369 168.768 128.625 168.528C128.865 168.272 128.985 167.96 128.985 167.592ZM119.385 167.592C119.385 167.224 119.265 166.92 119.025 166.68C118.769 166.424 118.457 166.296 118.089 166.296C117.721 166.296 117.417 166.424 117.177 166.68C116.921 166.92 116.793 167.224 116.793 167.592C116.793 167.96 116.921 168.272 117.177 168.528C117.417 168.768 117.721 168.888 118.089 168.888C118.457 168.888 118.769 168.768 119.025 168.528C119.265 168.272 119.385 167.96 119.385 167.592ZM126.153 169.824C126.153 169.584 126.073 169.384 125.913 169.224C125.737 169.048 125.529 168.96 125.289 168.96C125.049 168.96 124.849 169.048 124.689 169.224C124.513 169.384 124.425 169.584 124.425 169.824C124.425 170.064 124.513 170.272 124.689 170.448C124.849 170.608 125.049 170.688 125.289 170.688C125.529 170.688 125.737 170.608 125.913 170.448C126.073 170.272 126.153 170.064 126.153 169.824ZM130.953 169.824C130.953 169.584 130.865 169.384 130.689 169.224C130.529 169.048 130.329 168.96 130.089 168.96C129.849 168.96 129.649 169.048 129.489 169.224C129.313 169.384 129.225 169.584 129.225 169.824C129.225 170.064 129.313 170.272 129.489 170.448C129.649 170.608 129.849 170.688 130.089 170.688C130.329 170.688 130.529 170.608 130.689 170.448C130.865 170.272 130.953 170.064 130.953 169.824ZM121.353 169.824C121.353 169.584 121.273 169.384 121.113 169.224C120.937 169.048 120.729 168.96 120.489 168.96C120.249 168.96 120.049 169.048 119.889 169.224C119.713 169.384 119.625 169.584 119.625 169.824C119.625 170.064 119.713 170.272 119.889 170.448C120.049 170.608 120.249 170.688 120.489 170.688C120.729 170.688 120.937 170.608 121.113 170.448C121.273 170.272 121.353 170.064 121.353 169.824ZM124.185 172.056C124.185 171.688 124.065 171.384 123.825 171.144C123.569 170.888 123.257 170.76 122.889 170.76C122.521 170.76 122.217 170.888 121.977 171.144C121.721 171.384 121.593 171.688 121.593 172.056C121.593 172.424 121.721 172.736 121.977 172.992C122.217 173.232 122.521 173.352 122.889 173.352C123.257 173.352 123.569 173.232 123.825 172.992C124.065 172.736 124.185 172.424 124.185 172.056ZM119.385 172.056C119.385 171.688 119.265 171.384 119.025 171.144C118.769 170.888 118.457 170.76 118.089 170.76C117.721 170.76 117.417 170.888 117.177 171.144C116.921 171.384 116.793 171.688 116.793 172.056C116.793 172.424 116.921 172.736 117.177 172.992C117.417 173.232 117.721 173.352 118.089 173.352C118.457 173.352 118.769 173.232 119.025 172.992C119.265 172.736 119.385 172.424 119.385 172.056ZM128.985 172.056C128.985 171.688 128.865 171.384 128.625 171.144C128.369 170.888 128.057 170.76 127.689 170.76C127.321 170.76 127.017 170.888 126.777 171.144C126.521 171.384 126.393 171.688 126.393 172.056C126.393 172.424 126.521 172.736 126.777 172.992C127.017 173.232 127.321 173.352 127.689 173.352C128.057 173.352 128.369 173.232 128.625 172.992C128.865 172.736 128.985 172.424 128.985 172.056ZM126.153 174.288C126.153 174.048 126.073 173.848 125.913 173.688C125.737 173.512 125.529 173.424 125.289 173.424C125.049 173.424 124.849 173.512 124.689 173.688C124.513 173.848 124.425 174.048 124.425 174.288C124.425 174.528 124.513 174.736 124.689 174.912C124.849 175.072 125.049 175.152 125.289 175.152C125.529 175.152 125.737 175.072 125.913 174.912C126.073 174.736 126.153 174.528 126.153 174.288ZM121.353 174.288C121.353 174.048 121.273 173.848 121.113 173.688C120.937 173.512 120.729 173.424 120.489 173.424C120.249 173.424 120.049 173.512 119.889 173.688C119.713 173.848 119.625 174.048 119.625 174.288C119.625 174.528 119.713 174.736 119.889 174.912C120.049 175.072 120.249 175.152 120.489 175.152C120.729 175.152 120.937 175.072 121.113 174.912C121.273 174.736 121.353 174.528 121.353 174.288ZM130.953 174.288C130.953 174.048 130.865 173.848 130.689 173.688C130.529 173.512 130.329 173.424 130.089 173.424C129.849 173.424 129.649 173.512 129.489 173.688C129.313 173.848 129.225 174.048 129.225 174.288C129.225 174.528 129.313 174.736 129.489 174.912C129.649 175.072 129.849 175.152 130.089 175.152C130.329 175.152 130.529 175.072 130.689 174.912C130.865 174.736 130.953 174.528 130.953 174.288ZM124.185 176.52C124.185 176.152 124.065 175.848 123.825 175.608C123.569 175.352 123.257 175.224 122.889 175.224C122.521 175.224 122.217 175.352 121.977 175.608C121.721 175.848 121.593 176.152 121.593 176.52C121.593 176.888 121.721 177.192 121.977 177.432C122.217 177.688 122.521 177.816 122.889 177.816C123.257 177.816 123.569 177.688 123.825 177.432C124.065 177.192 124.185 176.888 124.185 176.52ZM119.385 176.52C119.385 176.152 119.265 175.848 119.025 175.608C118.769 175.352 118.457 175.224 118.089 175.224C117.721 175.224 117.417 175.352 117.177 175.608C116.921 175.848 116.793 176.152 116.793 176.52C116.793 176.888 116.921 177.192 117.177 177.432C117.417 177.688 117.721 177.816 118.089 177.816C118.457 177.816 118.769 177.688 119.025 177.432C119.265 177.192 119.385 176.888 119.385 176.52ZM128.985 176.52C128.985 176.152 128.865 175.848 128.625 175.608C128.369 175.352 128.057 175.224 127.689 175.224C127.321 175.224 127.017 175.352 126.777 175.608C126.521 175.848 126.393 176.152 126.393 176.52C126.393 176.888 126.521 177.192 126.777 177.432C127.017 177.688 127.321 177.816 127.689 177.816C128.057 177.816 128.369 177.688 128.625 177.432C128.865 177.192 128.985 176.888 128.985 176.52ZM126.153 178.752C126.153 178.512 126.073 178.304 125.913 178.128C125.737 177.968 125.529 177.888 125.289 177.888C125.049 177.888 124.849 177.968 124.689 178.128C124.513 178.304 124.425 178.512 124.425 178.752C124.425 178.992 124.513 179.192 124.689 179.352C124.849 179.528 125.049 179.616 125.289 179.616C125.529 179.616 125.737 179.528 125.913 179.352C126.073 179.192 126.153 178.992 126.153 178.752ZM121.353 178.752C121.353 178.512 121.273 178.304 121.113 178.128C120.937 177.968 120.729 177.888 120.489 177.888C120.249 177.888 120.049 177.968 119.889 178.128C119.713 178.304 119.625 178.512 119.625 178.752C119.625 178.992 119.713 179.192 119.889 179.352C120.049 179.528 120.249 179.616 120.489 179.616C120.729 179.616 120.937 179.528 121.113 179.352C121.273 179.192 121.353 178.992 121.353 178.752ZM130.953 178.752C130.953 178.512 130.865 178.304 130.689 178.128C130.529 177.968 130.329 177.888 130.089 177.888C129.849 177.888 129.649 177.968 129.489 178.128C129.313 178.304 129.225 178.512 129.225 178.752C129.225 178.992 129.313 179.192 129.489 179.352C129.649 179.528 129.849 179.616 130.089 179.616C130.329 179.616 130.529 179.528 130.689 179.352C130.865 179.192 130.953 178.992 130.953 178.752ZM124.185 180.984C124.185 180.616 124.065 180.304 123.825 180.048C123.569 179.808 123.257 179.688 122.889 179.688C122.521 179.688 122.217 179.808 121.977 180.048C121.721 180.304 121.593 180.616 121.593 180.984C121.593 181.352 121.721 181.656 121.977 181.896C122.217 182.152 122.521 182.28 122.889 182.28C123.257 182.28 123.569 182.152 123.825 181.896C124.065 181.656 124.185 181.352 124.185 180.984ZM119.385 180.984C119.385 180.616 119.265 180.304 119.025 180.048C118.769 179.808 118.457 179.688 118.089 179.688C117.721 179.688 117.417 179.808 117.177 180.048C116.921 180.304 116.793 180.616 116.793 180.984C116.793 181.352 116.921 181.656 117.177 181.896C117.417 182.152 117.721 182.28 118.089 182.28C118.457 182.28 118.769 182.152 119.025 181.896C119.265 181.656 119.385 181.352 119.385 180.984ZM128.985 180.984C128.985 180.616 128.865 180.304 128.625 180.048C128.369 179.808 128.057 179.688 127.689 179.688C127.321 179.688 127.017 179.808 126.777 180.048C126.521 180.304 126.393 180.616 126.393 180.984C126.393 181.352 126.521 181.656 126.777 181.896C127.017 182.152 127.321 182.28 127.689 182.28C128.057 182.28 128.369 182.152 128.625 181.896C128.865 181.656 128.985 181.352 128.985 180.984ZM126.153 183.216C126.153 182.976 126.073 182.768 125.913 182.592C125.737 182.432 125.529 182.352 125.289 182.352C125.049 182.352 124.849 182.432 124.689 182.592C124.513 182.768 124.425 182.976 124.425 183.216C124.425 183.456 124.513 183.656 124.689 183.816C124.849 183.992 125.049 184.08 125.289 184.08C125.529 184.08 125.737 183.992 125.913 183.816C126.073 183.656 126.153 183.456 126.153 183.216ZM121.353 183.216C121.353 182.976 121.273 182.768 121.113 182.592C120.937 182.432 120.729 182.352 120.489 182.352C120.249 182.352 120.049 182.432 119.889 182.592C119.713 182.768 119.625 182.976 119.625 183.216C119.625 183.456 119.713 183.656 119.889 183.816C120.049 183.992 120.249 184.08 120.489 184.08C120.729 184.08 120.937 183.992 121.113 183.816C121.273 183.656 121.353 183.456 121.353 183.216ZM130.953 183.216C130.953 182.976 130.865 182.768 130.689 182.592C130.529 182.432 130.329 182.352 130.089 182.352C129.849 182.352 129.649 182.432 129.489 182.592C129.313 182.768 129.225 182.976 129.225 183.216C129.225 183.456 129.313 183.656 129.489 183.816C129.649 183.992 129.849 184.08 130.089 184.08C130.329 184.08 130.529 183.992 130.689 183.816C130.865 183.656 130.953 183.456 130.953 183.216ZM105.043 154.2C105.043 153.96 104.963 153.76 104.803 153.6C104.627 153.424 104.419 153.336 104.179 153.336C103.939 153.336 103.739 153.424 103.579 153.6C103.403 153.76 103.315 153.96 103.315 154.2C103.315 154.44 103.403 154.648 103.579 154.824C103.739 154.984 103.939 155.064 104.179 155.064C104.419 155.064 104.627 154.984 104.803 154.824C104.963 154.648 105.043 154.44 105.043 154.2ZM109.843 154.2C109.843 153.96 109.763 153.76 109.603 153.6C109.427 153.424 109.219 153.336 108.979 153.336C108.739 153.336 108.539 153.424 108.379 153.6C108.203 153.76 108.115 153.96 108.115 154.2C108.115 154.44 108.203 154.648 108.379 154.824C108.539 154.984 108.739 155.064 108.979 155.064C109.219 155.064 109.427 154.984 109.603 154.824C109.763 154.648 109.843 154.44 109.843 154.2ZM114.643 154.2C114.643 153.96 114.563 153.76 114.403 153.6C114.227 153.424 114.019 153.336 113.779 153.336C113.539 153.336 113.339 153.424 113.179 153.6C113.003 153.76 112.915 153.96 112.915 154.2C112.915 154.44 113.003 154.648 113.179 154.824C113.339 154.984 113.539 155.064 113.779 155.064C114.019 155.064 114.227 154.984 114.403 154.824C114.563 154.648 114.643 154.44 114.643 154.2ZM107.155 156.432C107.155 156.272 107.099 156.136 106.987 156.024C106.875 155.912 106.739 155.856 106.579 155.856C106.419 155.856 106.283 155.912 106.171 156.024C106.059 156.136 106.003 156.272 106.003 156.432C106.003 156.592 106.059 156.728 106.171 156.84C106.283 156.952 106.419 157.008 106.579 157.008C106.739 157.008 106.875 156.952 106.987 156.84C107.099 156.728 107.155 156.592 107.155 156.432ZM111.955 156.432C111.955 156.272 111.899 156.136 111.787 156.024C111.675 155.912 111.539 155.856 111.379 155.856C111.219 155.856 111.083 155.912 110.971 156.024C110.859 156.136 110.803 156.272 110.803 156.432C110.803 156.592 110.859 156.728 110.971 156.84C111.083 156.952 111.219 157.008 111.379 157.008C111.539 157.008 111.675 156.952 111.787 156.84C111.899 156.728 111.955 156.592 111.955 156.432ZM116.755 156.432C116.755 156.272 116.699 156.136 116.587 156.024C116.475 155.912 116.339 155.856 116.179 155.856C116.019 155.856 115.883 155.912 115.771 156.024C115.659 156.136 115.603 156.272 115.603 156.432C115.603 156.592 115.659 156.728 115.771 156.84C115.883 156.952 116.019 157.008 116.179 157.008C116.339 157.008 116.475 156.952 116.587 156.84C116.699 156.728 116.755 156.592 116.755 156.432ZM105.043 158.664C105.043 158.424 104.963 158.224 104.803 158.064C104.627 157.888 104.419 157.8 104.179 157.8C103.939 157.8 103.739 157.888 103.579 158.064C103.403 158.224 103.315 158.424 103.315 158.664C103.315 158.904 103.403 159.112 103.579 159.288C103.739 159.448 103.939 159.528 104.179 159.528C104.419 159.528 104.627 159.448 104.803 159.288C104.963 159.112 105.043 158.904 105.043 158.664ZM114.643 158.664C114.643 158.424 114.563 158.224 114.403 158.064C114.227 157.888 114.019 157.8 113.779 157.8C113.539 157.8 113.339 157.888 113.179 158.064C113.003 158.224 112.915 158.424 112.915 158.664C112.915 158.904 113.003 159.112 113.179 159.288C113.339 159.448 113.539 159.528 113.779 159.528C114.019 159.528 114.227 159.448 114.403 159.288C114.563 159.112 114.643 158.904 114.643 158.664ZM109.843 158.664C109.843 158.424 109.763 158.224 109.603 158.064C109.427 157.888 109.219 157.8 108.979 157.8C108.739 157.8 108.539 157.888 108.379 158.064C108.203 158.224 108.115 158.424 108.115 158.664C108.115 158.904 108.203 159.112 108.379 159.288C108.539 159.448 108.739 159.528 108.979 159.528C109.219 159.528 109.427 159.448 109.603 159.288C109.763 159.112 109.843 158.904 109.843 158.664ZM107.155 160.896C107.155 160.736 107.099 160.6 106.987 160.488C106.875 160.376 106.739 160.32 106.579 160.32C106.419 160.32 106.283 160.376 106.171 160.488C106.059 160.6 106.003 160.736 106.003 160.896C106.003 161.056 106.059 161.192 106.171 161.304C106.283 161.416 106.419 161.472 106.579 161.472C106.739 161.472 106.875 161.416 106.987 161.304C107.099 161.192 107.155 161.056 107.155 160.896ZM116.755 160.896C116.755 160.736 116.699 160.6 116.587 160.488C116.475 160.376 116.339 160.32 116.179 160.32C116.019 160.32 115.883 160.376 115.771 160.488C115.659 160.6 115.603 160.736 115.603 160.896C115.603 161.056 115.659 161.192 115.771 161.304C115.883 161.416 116.019 161.472 116.179 161.472C116.339 161.472 116.475 161.416 116.587 161.304C116.699 161.192 116.755 161.056 116.755 160.896ZM111.955 160.896C111.955 160.736 111.899 160.6 111.787 160.488C111.675 160.376 111.539 160.32 111.379 160.32C111.219 160.32 111.083 160.376 110.971 160.488C110.859 160.6 110.803 160.736 110.803 160.896C110.803 161.056 110.859 161.192 110.971 161.304C111.083 161.416 111.219 161.472 111.379 161.472C111.539 161.472 111.675 161.416 111.787 161.304C111.899 161.192 111.955 161.056 111.955 160.896ZM105.043 163.128C105.043 162.888 104.963 162.688 104.803 162.528C104.627 162.352 104.419 162.264 104.179 162.264C103.939 162.264 103.739 162.352 103.579 162.528C103.403 162.688 103.315 162.888 103.315 163.128C103.315 163.368 103.403 163.576 103.579 163.752C103.739 163.912 103.939 163.992 104.179 163.992C104.419 163.992 104.627 163.912 104.803 163.752C104.963 163.576 105.043 163.368 105.043 163.128ZM114.643 163.128C114.643 162.888 114.563 162.688 114.403 162.528C114.227 162.352 114.019 162.264 113.779 162.264C113.539 162.264 113.339 162.352 113.179 162.528C113.003 162.688 112.915 162.888 112.915 163.128C112.915 163.368 113.003 163.576 113.179 163.752C113.339 163.912 113.539 163.992 113.779 163.992C114.019 163.992 114.227 163.912 114.403 163.752C114.563 163.576 114.643 163.368 114.643 163.128ZM109.843 163.128C109.843 162.888 109.763 162.688 109.603 162.528C109.427 162.352 109.219 162.264 108.979 162.264C108.739 162.264 108.539 162.352 108.379 162.528C108.203 162.688 108.115 162.888 108.115 163.128C108.115 163.368 108.203 163.576 108.379 163.752C108.539 163.912 108.739 163.992 108.979 163.992C109.219 163.992 109.427 163.912 109.603 163.752C109.763 163.576 109.843 163.368 109.843 163.128ZM107.155 165.264C107.155 165.104 107.099 164.968 106.987 164.856C106.875 164.744 106.739 164.688 106.579 164.688C106.419 164.688 106.283 164.744 106.171 164.856C106.059 164.968 106.003 165.104 106.003 165.264C106.003 165.424 106.059 165.56 106.171 165.672C106.283 165.784 106.419 165.84 106.579 165.84C106.739 165.84 106.875 165.784 106.987 165.672C107.099 165.56 107.155 165.424 107.155 165.264ZM116.755 165.264C116.755 165.104 116.699 164.968 116.587 164.856C116.475 164.744 116.339 164.688 116.179 164.688C116.019 164.688 115.883 164.744 115.771 164.856C115.659 164.968 115.603 165.104 115.603 165.264C115.603 165.424 115.659 165.56 115.771 165.672C115.883 165.784 116.019 165.84 116.179 165.84C116.339 165.84 116.475 165.784 116.587 165.672C116.699 165.56 116.755 165.424 116.755 165.264ZM111.955 165.264C111.955 165.104 111.899 164.968 111.787 164.856C111.675 164.744 111.539 164.688 111.379 164.688C111.219 164.688 111.083 164.744 110.971 164.856C110.859 164.968 110.803 165.104 110.803 165.264C110.803 165.424 110.859 165.56 110.971 165.672C111.083 165.784 111.219 165.84 111.379 165.84C111.539 165.84 111.675 165.784 111.787 165.672C111.899 165.56 111.955 165.424 111.955 165.264ZM109.843 167.496C109.843 167.256 109.763 167.056 109.603 166.896C109.427 166.72 109.219 166.632 108.979 166.632C108.739 166.632 108.539 166.72 108.379 166.896C108.203 167.056 108.115 167.256 108.115 167.496C108.115 167.736 108.203 167.944 108.379 168.12C108.539 168.28 108.739 168.36 108.979 168.36C109.219 168.36 109.427 168.28 109.603 168.12C109.763 167.944 109.843 167.736 109.843 167.496ZM114.643 167.496C114.643 167.256 114.563 167.056 114.403 166.896C114.227 166.72 114.019 166.632 113.779 166.632C113.539 166.632 113.339 166.72 113.179 166.896C113.003 167.056 112.915 167.256 112.915 167.496C112.915 167.736 113.003 167.944 113.179 168.12C113.339 168.28 113.539 168.36 113.779 168.36C114.019 168.36 114.227 168.28 114.403 168.12C114.563 167.944 114.643 167.736 114.643 167.496ZM105.043 167.496C105.043 167.256 104.963 167.056 104.803 166.896C104.627 166.72 104.419 166.632 104.179 166.632C103.939 166.632 103.739 166.72 103.579 166.896C103.403 167.056 103.315 167.256 103.315 167.496C103.315 167.736 103.403 167.944 103.579 168.12C103.739 168.28 103.939 168.36 104.179 168.36C104.419 168.36 104.627 168.28 104.803 168.12C104.963 167.944 105.043 167.736 105.043 167.496ZM111.955 169.728C111.955 169.568 111.899 169.432 111.787 169.32C111.675 169.208 111.539 169.152 111.379 169.152C111.219 169.152 111.083 169.208 110.971 169.32C110.859 169.432 110.803 169.568 110.803 169.728C110.803 169.888 110.859 170.024 110.971 170.136C111.083 170.248 111.219 170.304 111.379 170.304C111.539 170.304 111.675 170.248 111.787 170.136C111.899 170.024 111.955 169.888 111.955 169.728ZM116.755 169.728C116.755 169.568 116.699 169.432 116.587 169.32C116.475 169.208 116.339 169.152 116.179 169.152C116.019 169.152 115.883 169.208 115.771 169.32C115.659 169.432 115.603 169.568 115.603 169.728C115.603 169.888 115.659 170.024 115.771 170.136C115.883 170.248 116.019 170.304 116.179 170.304C116.339 170.304 116.475 170.248 116.587 170.136C116.699 170.024 116.755 169.888 116.755 169.728ZM107.155 169.728C107.155 169.568 107.099 169.432 106.987 169.32C106.875 169.208 106.739 169.152 106.579 169.152C106.419 169.152 106.283 169.208 106.171 169.32C106.059 169.432 106.003 169.568 106.003 169.728C106.003 169.888 106.059 170.024 106.171 170.136C106.283 170.248 106.419 170.304 106.579 170.304C106.739 170.304 106.875 170.248 106.987 170.136C107.099 170.024 107.155 169.888 107.155 169.728ZM109.843 171.96C109.843 171.72 109.763 171.52 109.603 171.36C109.427 171.184 109.219 171.096 108.979 171.096C108.739 171.096 108.539 171.184 108.379 171.36C108.203 171.52 108.115 171.72 108.115 171.96C108.115 172.2 108.203 172.408 108.379 172.584C108.539 172.744 108.739 172.824 108.979 172.824C109.219 172.824 109.427 172.744 109.603 172.584C109.763 172.408 109.843 172.2 109.843 171.96ZM105.043 171.96C105.043 171.72 104.963 171.52 104.803 171.36C104.627 171.184 104.419 171.096 104.179 171.096C103.939 171.096 103.739 171.184 103.579 171.36C103.403 171.52 103.315 171.72 103.315 171.96C103.315 172.2 103.403 172.408 103.579 172.584C103.739 172.744 103.939 172.824 104.179 172.824C104.419 172.824 104.627 172.744 104.803 172.584C104.963 172.408 105.043 172.2 105.043 171.96ZM114.643 171.96C114.643 171.72 114.563 171.52 114.403 171.36C114.227 171.184 114.019 171.096 113.779 171.096C113.539 171.096 113.339 171.184 113.179 171.36C113.003 171.52 112.915 171.72 112.915 171.96C112.915 172.2 113.003 172.408 113.179 172.584C113.339 172.744 113.539 172.824 113.779 172.824C114.019 172.824 114.227 172.744 114.403 172.584C114.563 172.408 114.643 172.2 114.643 171.96ZM111.955 174.192C111.955 174.032 111.899 173.896 111.787 173.784C111.675 173.672 111.539 173.616 111.379 173.616C111.219 173.616 111.083 173.672 110.971 173.784C110.859 173.896 110.803 174.032 110.803 174.192C110.803 174.352 110.859 174.488 110.971 174.6C111.083 174.712 111.219 174.768 111.379 174.768C111.539 174.768 111.675 174.712 111.787 174.6C111.899 174.488 111.955 174.352 111.955 174.192ZM107.155 174.192C107.155 174.032 107.099 173.896 106.987 173.784C106.875 173.672 106.739 173.616 106.579 173.616C106.419 173.616 106.283 173.672 106.171 173.784C106.059 173.896 106.003 174.032 106.003 174.192C106.003 174.352 106.059 174.488 106.171 174.6C106.283 174.712 106.419 174.768 106.579 174.768C106.739 174.768 106.875 174.712 106.987 174.6C107.099 174.488 107.155 174.352 107.155 174.192ZM116.755 174.192C116.755 174.032 116.699 173.896 116.587 173.784C116.475 173.672 116.339 173.616 116.179 173.616C116.019 173.616 115.883 173.672 115.771 173.784C115.659 173.896 115.603 174.032 115.603 174.192C115.603 174.352 115.659 174.488 115.771 174.6C115.883 174.712 116.019 174.768 116.179 174.768C116.339 174.768 116.475 174.712 116.587 174.6C116.699 174.488 116.755 174.352 116.755 174.192ZM109.843 176.424C109.843 176.184 109.763 175.984 109.603 175.824C109.427 175.648 109.219 175.56 108.979 175.56C108.739 175.56 108.539 175.648 108.379 175.824C108.203 175.984 108.115 176.184 108.115 176.424C108.115 176.664 108.203 176.864 108.379 177.024C108.539 177.2 108.739 177.288 108.979 177.288C109.219 177.288 109.427 177.2 109.603 177.024C109.763 176.864 109.843 176.664 109.843 176.424ZM105.043 176.424C105.043 176.184 104.963 175.984 104.803 175.824C104.627 175.648 104.419 175.56 104.179 175.56C103.939 175.56 103.739 175.648 103.579 175.824C103.403 175.984 103.315 176.184 103.315 176.424C103.315 176.664 103.403 176.864 103.579 177.024C103.739 177.2 103.939 177.288 104.179 177.288C104.419 177.288 104.627 177.2 104.803 177.024C104.963 176.864 105.043 176.664 105.043 176.424ZM114.643 176.424C114.643 176.184 114.563 175.984 114.403 175.824C114.227 175.648 114.019 175.56 113.779 175.56C113.539 175.56 113.339 175.648 113.179 175.824C113.003 175.984 112.915 176.184 112.915 176.424C112.915 176.664 113.003 176.864 113.179 177.024C113.339 177.2 113.539 177.288 113.779 177.288C114.019 177.288 114.227 177.2 114.403 177.024C114.563 176.864 114.643 176.664 114.643 176.424ZM111.955 178.656C111.955 178.496 111.899 178.36 111.787 178.248C111.675 178.136 111.539 178.08 111.379 178.08C111.219 178.08 111.083 178.136 110.971 178.248C110.859 178.36 110.803 178.496 110.803 178.656C110.803 178.816 110.859 178.952 110.971 179.064C111.083 179.176 111.219 179.232 111.379 179.232C111.539 179.232 111.675 179.176 111.787 179.064C111.899 178.952 111.955 178.816 111.955 178.656ZM107.155 178.656C107.155 178.496 107.099 178.36 106.987 178.248C106.875 178.136 106.739 178.08 106.579 178.08C106.419 178.08 106.283 178.136 106.171 178.248C106.059 178.36 106.003 178.496 106.003 178.656C106.003 178.816 106.059 178.952 106.171 179.064C106.283 179.176 106.419 179.232 106.579 179.232C106.739 179.232 106.875 179.176 106.987 179.064C107.099 178.952 107.155 178.816 107.155 178.656ZM116.755 178.656C116.755 178.496 116.699 178.36 116.587 178.248C116.475 178.136 116.339 178.08 116.179 178.08C116.019 178.08 115.883 178.136 115.771 178.248C115.659 178.36 115.603 178.496 115.603 178.656C115.603 178.816 115.659 178.952 115.771 179.064C115.883 179.176 116.019 179.232 116.179 179.232C116.339 179.232 116.475 179.176 116.587 179.064C116.699 178.952 116.755 178.816 116.755 178.656ZM109.843 180.888C109.843 180.648 109.763 180.44 109.603 180.264C109.427 180.104 109.219 180.024 108.979 180.024C108.739 180.024 108.539 180.104 108.379 180.264C108.203 180.44 108.115 180.648 108.115 180.888C108.115 181.128 108.203 181.328 108.379 181.488C108.539 181.664 108.739 181.752 108.979 181.752C109.219 181.752 109.427 181.664 109.603 181.488C109.763 181.328 109.843 181.128 109.843 180.888ZM105.043 180.888C105.043 180.648 104.963 180.44 104.803 180.264C104.627 180.104 104.419 180.024 104.179 180.024C103.939 180.024 103.739 180.104 103.579 180.264C103.403 180.44 103.315 180.648 103.315 180.888C103.315 181.128 103.403 181.328 103.579 181.488C103.739 181.664 103.939 181.752 104.179 181.752C104.419 181.752 104.627 181.664 104.803 181.488C104.963 181.328 105.043 181.128 105.043 180.888ZM114.643 180.888C114.643 180.648 114.563 180.44 114.403 180.264C114.227 180.104 114.019 180.024 113.779 180.024C113.539 180.024 113.339 180.104 113.179 180.264C113.003 180.44 112.915 180.648 112.915 180.888C112.915 181.128 113.003 181.328 113.179 181.488C113.339 181.664 113.539 181.752 113.779 181.752C114.019 181.752 114.227 181.664 114.403 181.488C114.563 181.328 114.643 181.128 114.643 180.888ZM111.955 183.12C111.955 182.96 111.899 182.824 111.787 182.712C111.675 182.6 111.539 182.544 111.379 182.544C111.219 182.544 111.083 182.6 110.971 182.712C110.859 182.824 110.803 182.96 110.803 183.12C110.803 183.28 110.859 183.416 110.971 183.528C111.083 183.64 111.219 183.696 111.379 183.696C111.539 183.696 111.675 183.64 111.787 183.528C111.899 183.416 111.955 183.28 111.955 183.12ZM107.155 183.12C107.155 182.96 107.099 182.824 106.987 182.712C106.875 182.6 106.739 182.544 106.579 182.544C106.419 182.544 106.283 182.6 106.171 182.712C106.059 182.824 106.003 182.96 106.003 183.12C106.003 183.28 106.059 183.416 106.171 183.528C106.283 183.64 106.419 183.696 106.579 183.696C106.739 183.696 106.875 183.64 106.987 183.528C107.099 183.416 107.155 183.28 107.155 183.12ZM116.755 183.12C116.755 182.96 116.699 182.824 116.587 182.712C116.475 182.6 116.339 182.544 116.179 182.544C116.019 182.544 115.883 182.6 115.771 182.712C115.659 182.824 115.603 182.96 115.603 183.12C115.603 183.28 115.659 183.416 115.771 183.528C115.883 183.64 116.019 183.696 116.179 183.696C116.339 183.696 116.475 183.64 116.587 183.528C116.699 183.416 116.755 183.28 116.755 183.12ZM133.944 185.2C133.944 184.656 133.76 184.2 133.392 183.832C133.008 183.448 132.544 183.256 132 183.256C131.456 183.256 131 183.448 130.632 183.832C130.248 184.2 130.056 184.656 130.056 185.2C130.056 185.744 130.248 186.208 130.632 186.592C131 186.96 131.456 187.144 132 187.144C132.544 187.144 133.008 186.96 133.392 186.592C133.76 186.208 133.944 185.744 133.944 185.2ZM138.744 185.2C138.744 184.656 138.56 184.2 138.192 183.832C137.808 183.448 137.344 183.256 136.8 183.256C136.256 183.256 135.8 183.448 135.432 183.832C135.048 184.2 134.856 184.656 134.856 185.2C134.856 185.744 135.048 186.208 135.432 186.592C135.8 186.96 136.256 187.144 136.8 187.144C137.344 187.144 137.808 186.96 138.192 186.592C138.56 186.208 138.744 185.744 138.744 185.2ZM143.544 185.2C143.544 184.656 143.36 184.2 142.992 183.832C142.608 183.448 142.144 183.256 141.6 183.256C141.056 183.256 140.6 183.448 140.232 183.832C139.848 184.2 139.656 184.656 139.656 185.2C139.656 185.744 139.848 186.208 140.232 186.592C140.6 186.96 141.056 187.144 141.6 187.144C142.144 187.144 142.608 186.96 142.992 186.592C143.36 186.208 143.544 185.744 143.544 185.2ZM135.696 187.432C135.696 187.064 135.576 186.76 135.336 186.52C135.08 186.264 134.768 186.136 134.4 186.136C134.032 186.136 133.728 186.264 133.488 186.52C133.232 186.76 133.104 187.064 133.104 187.432C133.104 187.8 133.232 188.112 133.488 188.368C133.728 188.608 134.032 188.728 134.4 188.728C134.768 188.728 135.08 188.608 135.336 188.368C135.576 188.112 135.696 187.8 135.696 187.432ZM140.496 187.432C140.496 187.064 140.376 186.76 140.136 186.52C139.88 186.264 139.568 186.136 139.2 186.136C138.832 186.136 138.528 186.264 138.288 186.52C138.032 186.76 137.904 187.064 137.904 187.432C137.904 187.8 138.032 188.112 138.288 188.368C138.528 188.608 138.832 188.728 139.2 188.728C139.568 188.728 139.88 188.608 140.136 188.368C140.376 188.112 140.496 187.8 140.496 187.432ZM145.296 187.432C145.296 187.064 145.168 186.76 144.912 186.52C144.672 186.264 144.368 186.136 144 186.136C143.632 186.136 143.328 186.264 143.088 186.52C142.832 186.76 142.704 187.064 142.704 187.432C142.704 187.8 142.832 188.112 143.088 188.368C143.328 188.608 143.632 188.728 144 188.728C144.368 188.728 144.672 188.608 144.912 188.368C145.168 188.112 145.296 187.8 145.296 187.432ZM133.944 189.664C133.944 189.12 133.76 188.664 133.392 188.296C133.008 187.912 132.544 187.72 132 187.72C131.456 187.72 131 187.912 130.632 188.296C130.248 188.664 130.056 189.12 130.056 189.664C130.056 190.208 130.248 190.672 130.632 191.056C131 191.424 131.456 191.608 132 191.608C132.544 191.608 133.008 191.424 133.392 191.056C133.76 190.672 133.944 190.208 133.944 189.664ZM143.544 189.664C143.544 189.12 143.36 188.664 142.992 188.296C142.608 187.912 142.144 187.72 141.6 187.72C141.056 187.72 140.6 187.912 140.232 188.296C139.848 188.664 139.656 189.12 139.656 189.664C139.656 190.208 139.848 190.672 140.232 191.056C140.6 191.424 141.056 191.608 141.6 191.608C142.144 191.608 142.608 191.424 142.992 191.056C143.36 190.672 143.544 190.208 143.544 189.664ZM138.744 189.664C138.744 189.12 138.56 188.664 138.192 188.296C137.808 187.912 137.344 187.72 136.8 187.72C136.256 187.72 135.8 187.912 135.432 188.296C135.048 188.664 134.856 189.12 134.856 189.664C134.856 190.208 135.048 190.672 135.432 191.056C135.8 191.424 136.256 191.608 136.8 191.608C137.344 191.608 137.808 191.424 138.192 191.056C138.56 190.672 138.744 190.208 138.744 189.664ZM135.696 191.896C135.696 191.528 135.576 191.224 135.336 190.984C135.08 190.728 134.768 190.6 134.4 190.6C134.032 190.6 133.728 190.728 133.488 190.984C133.232 191.224 133.104 191.528 133.104 191.896C133.104 192.264 133.232 192.576 133.488 192.832C133.728 193.072 134.032 193.192 134.4 193.192C134.768 193.192 135.08 193.072 135.336 192.832C135.576 192.576 135.696 192.264 135.696 191.896ZM145.296 191.896C145.296 191.528 145.168 191.224 144.912 190.984C144.672 190.728 144.368 190.6 144 190.6C143.632 190.6 143.328 190.728 143.088 190.984C142.832 191.224 142.704 191.528 142.704 191.896C142.704 192.264 142.832 192.576 143.088 192.832C143.328 193.072 143.632 193.192 144 193.192C144.368 193.192 144.672 193.072 144.912 192.832C145.168 192.576 145.296 192.264 145.296 191.896ZM140.496 191.896C140.496 191.528 140.376 191.224 140.136 190.984C139.88 190.728 139.568 190.6 139.2 190.6C138.832 190.6 138.528 190.728 138.288 190.984C138.032 191.224 137.904 191.528 137.904 191.896C137.904 192.264 138.032 192.576 138.288 192.832C138.528 193.072 138.832 193.192 139.2 193.192C139.568 193.192 139.88 193.072 140.136 192.832C140.376 192.576 140.496 192.264 140.496 191.896ZM133.944 194.128C133.944 193.584 133.76 193.128 133.392 192.76C133.008 192.376 132.544 192.184 132 192.184C131.456 192.184 131 192.376 130.632 192.76C130.248 193.128 130.056 193.584 130.056 194.128C130.056 194.672 130.248 195.136 130.632 195.52C131 195.888 131.456 196.072 132 196.072C132.544 196.072 133.008 195.888 133.392 195.52C133.76 195.136 133.944 194.672 133.944 194.128ZM143.544 194.128C143.544 193.584 143.36 193.128 142.992 192.76C142.608 192.376 142.144 192.184 141.6 192.184C141.056 192.184 140.6 192.376 140.232 192.76C139.848 193.128 139.656 193.584 139.656 194.128C139.656 194.672 139.848 195.136 140.232 195.52C140.6 195.888 141.056 196.072 141.6 196.072C142.144 196.072 142.608 195.888 142.992 195.52C143.36 195.136 143.544 194.672 143.544 194.128ZM138.744 194.128C138.744 193.584 138.56 193.128 138.192 192.76C137.808 192.376 137.344 192.184 136.8 192.184C136.256 192.184 135.8 192.376 135.432 192.76C135.048 193.128 134.856 193.584 134.856 194.128C134.856 194.672 135.048 195.136 135.432 195.52C135.8 195.888 136.256 196.072 136.8 196.072C137.344 196.072 137.808 195.888 138.192 195.52C138.56 195.136 138.744 194.672 138.744 194.128ZM135.696 196.36C135.696 195.992 135.576 195.688 135.336 195.448C135.08 195.192 134.768 195.064 134.4 195.064C134.032 195.064 133.728 195.192 133.488 195.448C133.232 195.688 133.104 195.992 133.104 196.36C133.104 196.728 133.232 197.04 133.488 197.296C133.728 197.536 134.032 197.656 134.4 197.656C134.768 197.656 135.08 197.536 135.336 197.296C135.576 197.04 135.696 196.728 135.696 196.36ZM145.296 196.36C145.296 195.992 145.168 195.688 144.912 195.448C144.672 195.192 144.368 195.064 144 195.064C143.632 195.064 143.328 195.192 143.088 195.448C142.832 195.688 142.704 195.992 142.704 196.36C142.704 196.728 142.832 197.04 143.088 197.296C143.328 197.536 143.632 197.656 144 197.656C144.368 197.656 144.672 197.536 144.912 197.296C145.168 197.04 145.296 196.728 145.296 196.36ZM140.496 196.36C140.496 195.992 140.376 195.688 140.136 195.448C139.88 195.192 139.568 195.064 139.2 195.064C138.832 195.064 138.528 195.192 138.288 195.448C138.032 195.688 137.904 195.992 137.904 196.36C137.904 196.728 138.032 197.04 138.288 197.296C138.528 197.536 138.832 197.656 139.2 197.656C139.568 197.656 139.88 197.536 140.136 197.296C140.376 197.04 140.496 196.728 140.496 196.36ZM138.744 198.592C138.744 198.048 138.56 197.592 138.192 197.224C137.808 196.84 137.344 196.648 136.8 196.648C136.256 196.648 135.8 196.84 135.432 197.224C135.048 197.592 134.856 198.048 134.856 198.592C134.856 199.136 135.048 199.6 135.432 199.984C135.8 200.352 136.256 200.536 136.8 200.536C137.344 200.536 137.808 200.352 138.192 199.984C138.56 199.6 138.744 199.136 138.744 198.592ZM143.544 198.592C143.544 198.048 143.36 197.592 142.992 197.224C142.608 196.84 142.144 196.648 141.6 196.648C141.056 196.648 140.6 196.84 140.232 197.224C139.848 197.592 139.656 198.048 139.656 198.592C139.656 199.136 139.848 199.6 140.232 199.984C140.6 200.352 141.056 200.536 141.6 200.536C142.144 200.536 142.608 200.352 142.992 199.984C143.36 199.6 143.544 199.136 143.544 198.592ZM133.944 198.592C133.944 198.048 133.76 197.592 133.392 197.224C133.008 196.84 132.544 196.648 132 196.648C131.456 196.648 131 196.84 130.632 197.224C130.248 197.592 130.056 198.048 130.056 198.592C130.056 199.136 130.248 199.6 130.632 199.984C131 200.352 131.456 200.536 132 200.536C132.544 200.536 133.008 200.352 133.392 199.984C133.76 199.6 133.944 199.136 133.944 198.592ZM140.496 200.824C140.496 200.456 140.376 200.152 140.136 199.912C139.88 199.656 139.568 199.528 139.2 199.528C138.832 199.528 138.528 199.656 138.288 199.912C138.032 200.152 137.904 200.456 137.904 200.824C137.904 201.192 138.032 201.504 138.288 201.76C138.528 202 138.832 202.12 139.2 202.12C139.568 202.12 139.88 202 140.136 201.76C140.376 201.504 140.496 201.192 140.496 200.824ZM145.296 200.824C145.296 200.456 145.168 200.152 144.912 199.912C144.672 199.656 144.368 199.528 144 199.528C143.632 199.528 143.328 199.656 143.088 199.912C142.832 200.152 142.704 200.456 142.704 200.824C142.704 201.192 142.832 201.504 143.088 201.76C143.328 202 143.632 202.12 144 202.12C144.368 202.12 144.672 202 144.912 201.76C145.168 201.504 145.296 201.192 145.296 200.824ZM135.696 200.824C135.696 200.456 135.576 200.152 135.336 199.912C135.08 199.656 134.768 199.528 134.4 199.528C134.032 199.528 133.728 199.656 133.488 199.912C133.232 200.152 133.104 200.456 133.104 200.824C133.104 201.192 133.232 201.504 133.488 201.76C133.728 202 134.032 202.12 134.4 202.12C134.768 202.12 135.08 202 135.336 201.76C135.576 201.504 135.696 201.192 135.696 200.824ZM138.744 203.056C138.744 202.512 138.56 202.056 138.192 201.688C137.808 201.304 137.344 201.112 136.8 201.112C136.256 201.112 135.8 201.304 135.432 201.688C135.048 202.056 134.856 202.512 134.856 203.056C134.856 203.6 135.048 204.064 135.432 204.448C135.8 204.816 136.256 205 136.8 205C137.344 205 137.808 204.816 138.192 204.448C138.56 204.064 138.744 203.6 138.744 203.056ZM133.944 203.056C133.944 202.512 133.76 202.056 133.392 201.688C133.008 201.304 132.544 201.112 132 201.112C131.456 201.112 131 201.304 130.632 201.688C130.248 202.056 130.056 202.512 130.056 203.056C130.056 203.6 130.248 204.064 130.632 204.448C131 204.816 131.456 205 132 205C132.544 205 133.008 204.816 133.392 204.448C133.76 204.064 133.944 203.6 133.944 203.056ZM143.544 203.056C143.544 202.512 143.36 202.056 142.992 201.688C142.608 201.304 142.144 201.112 141.6 201.112C141.056 201.112 140.6 201.304 140.232 201.688C139.848 202.056 139.656 202.512 139.656 203.056C139.656 203.6 139.848 204.064 140.232 204.448C140.6 204.816 141.056 205 141.6 205C142.144 205 142.608 204.816 142.992 204.448C143.36 204.064 143.544 203.6 143.544 203.056ZM140.496 205.288C140.496 204.92 140.376 204.616 140.136 204.376C139.88 204.12 139.568 203.992 139.2 203.992C138.832 203.992 138.528 204.12 138.288 204.376C138.032 204.616 137.904 204.92 137.904 205.288C137.904 205.656 138.032 205.968 138.288 206.224C138.528 206.464 138.832 206.584 139.2 206.584C139.568 206.584 139.88 206.464 140.136 206.224C140.376 205.968 140.496 205.656 140.496 205.288ZM135.696 205.288C135.696 204.92 135.576 204.616 135.336 204.376C135.08 204.12 134.768 203.992 134.4 203.992C134.032 203.992 133.728 204.12 133.488 204.376C133.232 204.616 133.104 204.92 133.104 205.288C133.104 205.656 133.232 205.968 133.488 206.224C133.728 206.464 134.032 206.584 134.4 206.584C134.768 206.584 135.08 206.464 135.336 206.224C135.576 205.968 135.696 205.656 135.696 205.288ZM145.296 205.288C145.296 204.92 145.168 204.616 144.912 204.376C144.672 204.12 144.368 203.992 144 203.992C143.632 203.992 143.328 204.12 143.088 204.376C142.832 204.616 142.704 204.92 142.704 205.288C142.704 205.656 142.832 205.968 143.088 206.224C143.328 206.464 143.632 206.584 144 206.584C144.368 206.584 144.672 206.464 144.912 206.224C145.168 205.968 145.296 205.656 145.296 205.288ZM138.744 207.52C138.744 206.976 138.56 206.52 138.192 206.152C137.808 205.768 137.344 205.576 136.8 205.576C136.256 205.576 135.8 205.768 135.432 206.152C135.048 206.52 134.856 206.976 134.856 207.52C134.856 208.064 135.048 208.52 135.432 208.888C135.8 209.272 136.256 209.464 136.8 209.464C137.344 209.464 137.808 209.272 138.192 208.888C138.56 208.52 138.744 208.064 138.744 207.52ZM133.944 207.52C133.944 206.976 133.76 206.52 133.392 206.152C133.008 205.768 132.544 205.576 132 205.576C131.456 205.576 131 205.768 130.632 206.152C130.248 206.52 130.056 206.976 130.056 207.52C130.056 208.064 130.248 208.52 130.632 208.888C131 209.272 131.456 209.464 132 209.464C132.544 209.464 133.008 209.272 133.392 208.888C133.76 208.52 133.944 208.064 133.944 207.52ZM143.544 207.52C143.544 206.976 143.36 206.52 142.992 206.152C142.608 205.768 142.144 205.576 141.6 205.576C141.056 205.576 140.6 205.768 140.232 206.152C139.848 206.52 139.656 206.976 139.656 207.52C139.656 208.064 139.848 208.52 140.232 208.888C140.6 209.272 141.056 209.464 141.6 209.464C142.144 209.464 142.608 209.272 142.992 208.888C143.36 208.52 143.544 208.064 143.544 207.52ZM140.496 209.752C140.496 209.384 140.376 209.072 140.136 208.816C139.88 208.576 139.568 208.456 139.2 208.456C138.832 208.456 138.528 208.576 138.288 208.816C138.032 209.072 137.904 209.384 137.904 209.752C137.904 210.12 138.032 210.424 138.288 210.664C138.528 210.92 138.832 211.048 139.2 211.048C139.568 211.048 139.88 210.92 140.136 210.664C140.376 210.424 140.496 210.12 140.496 209.752ZM135.696 209.752C135.696 209.384 135.576 209.072 135.336 208.816C135.08 208.576 134.768 208.456 134.4 208.456C134.032 208.456 133.728 208.576 133.488 208.816C133.232 209.072 133.104 209.384 133.104 209.752C133.104 210.12 133.232 210.424 133.488 210.664C133.728 210.92 134.032 211.048 134.4 211.048C134.768 211.048 135.08 210.92 135.336 210.664C135.576 210.424 135.696 210.12 135.696 209.752ZM145.296 209.752C145.296 209.384 145.168 209.072 144.912 208.816C144.672 208.576 144.368 208.456 144 208.456C143.632 208.456 143.328 208.576 143.088 208.816C142.832 209.072 142.704 209.384 142.704 209.752C142.704 210.12 142.832 210.424 143.088 210.664C143.328 210.92 143.632 211.048 144 211.048C144.368 211.048 144.672 210.92 144.912 210.664C145.168 210.424 145.296 210.12 145.296 209.752ZM138.744 211.984C138.744 211.44 138.56 210.976 138.192 210.592C137.808 210.224 137.344 210.04 136.8 210.04C136.256 210.04 135.8 210.224 135.432 210.592C135.048 210.976 134.856 211.44 134.856 211.984C134.856 212.528 135.048 212.984 135.432 213.352C135.8 213.736 136.256 213.928 136.8 213.928C137.344 213.928 137.808 213.736 138.192 213.352C138.56 212.984 138.744 212.528 138.744 211.984ZM133.944 211.984C133.944 211.44 133.76 210.976 133.392 210.592C133.008 210.224 132.544 210.04 132 210.04C131.456 210.04 131 210.224 130.632 210.592C130.248 210.976 130.056 211.44 130.056 211.984C130.056 212.528 130.248 212.984 130.632 213.352C131 213.736 131.456 213.928 132 213.928C132.544 213.928 133.008 213.736 133.392 213.352C133.76 212.984 133.944 212.528 133.944 211.984ZM143.544 211.984C143.544 211.44 143.36 210.976 142.992 210.592C142.608 210.224 142.144 210.04 141.6 210.04C141.056 210.04 140.6 210.224 140.232 210.592C139.848 210.976 139.656 211.44 139.656 211.984C139.656 212.528 139.848 212.984 140.232 213.352C140.6 213.736 141.056 213.928 141.6 213.928C142.144 213.928 142.608 213.736 142.992 213.352C143.36 212.984 143.544 212.528 143.544 211.984ZM140.496 214.216C140.496 213.848 140.376 213.536 140.136 213.28C139.88 213.04 139.568 212.92 139.2 212.92C138.832 212.92 138.528 213.04 138.288 213.28C138.032 213.536 137.904 213.848 137.904 214.216C137.904 214.584 138.032 214.888 138.288 215.128C138.528 215.384 138.832 215.512 139.2 215.512C139.568 215.512 139.88 215.384 140.136 215.128C140.376 214.888 140.496 214.584 140.496 214.216ZM135.696 214.216C135.696 213.848 135.576 213.536 135.336 213.28C135.08 213.04 134.768 212.92 134.4 212.92C134.032 212.92 133.728 213.04 133.488 213.28C133.232 213.536 133.104 213.848 133.104 214.216C133.104 214.584 133.232 214.888 133.488 215.128C133.728 215.384 134.032 215.512 134.4 215.512C134.768 215.512 135.08 215.384 135.336 215.128C135.576 214.888 135.696 214.584 135.696 214.216ZM145.296 214.216C145.296 213.848 145.168 213.536 144.912 213.28C144.672 213.04 144.368 212.92 144 212.92C143.632 212.92 143.328 213.04 143.088 213.28C142.832 213.536 142.704 213.848 142.704 214.216C142.704 214.584 142.832 214.888 143.088 215.128C143.328 215.384 143.632 215.512 144 215.512C144.368 215.512 144.672 215.384 144.912 215.128C145.168 214.888 145.296 214.584 145.296 214.216ZM119.385 185.2C119.385 184.832 119.265 184.528 119.025 184.288C118.769 184.032 118.457 183.904 118.089 183.904C117.721 183.904 117.417 184.032 117.177 184.288C116.921 184.528 116.793 184.832 116.793 185.2C116.793 185.568 116.921 185.88 117.177 186.136C117.417 186.376 117.721 186.496 118.089 186.496C118.457 186.496 118.769 186.376 119.025 186.136C119.265 185.88 119.385 185.568 119.385 185.2ZM124.185 185.2C124.185 184.832 124.065 184.528 123.825 184.288C123.569 184.032 123.257 183.904 122.889 183.904C122.521 183.904 122.217 184.032 121.977 184.288C121.721 184.528 121.593 184.832 121.593 185.2C121.593 185.568 121.721 185.88 121.977 186.136C122.217 186.376 122.521 186.496 122.889 186.496C123.257 186.496 123.569 186.376 123.825 186.136C124.065 185.88 124.185 185.568 124.185 185.2ZM128.985 185.2C128.985 184.832 128.865 184.528 128.625 184.288C128.369 184.032 128.057 183.904 127.689 183.904C127.321 183.904 127.017 184.032 126.777 184.288C126.521 184.528 126.393 184.832 126.393 185.2C126.393 185.568 126.521 185.88 126.777 186.136C127.017 186.376 127.321 186.496 127.689 186.496C128.057 186.496 128.369 186.376 128.625 186.136C128.865 185.88 128.985 185.568 128.985 185.2ZM121.353 187.432C121.353 187.192 121.273 186.992 121.113 186.832C120.937 186.656 120.729 186.568 120.489 186.568C120.249 186.568 120.049 186.656 119.889 186.832C119.713 186.992 119.625 187.192 119.625 187.432C119.625 187.672 119.713 187.88 119.889 188.056C120.049 188.216 120.249 188.296 120.489 188.296C120.729 188.296 120.937 188.216 121.113 188.056C121.273 187.88 121.353 187.672 121.353 187.432ZM126.153 187.432C126.153 187.192 126.073 186.992 125.913 186.832C125.737 186.656 125.529 186.568 125.289 186.568C125.049 186.568 124.849 186.656 124.689 186.832C124.513 186.992 124.425 187.192 124.425 187.432C124.425 187.672 124.513 187.88 124.689 188.056C124.849 188.216 125.049 188.296 125.289 188.296C125.529 188.296 125.737 188.216 125.913 188.056C126.073 187.88 126.153 187.672 126.153 187.432ZM130.953 187.432C130.953 187.192 130.865 186.992 130.689 186.832C130.529 186.656 130.329 186.568 130.089 186.568C129.849 186.568 129.649 186.656 129.489 186.832C129.313 186.992 129.225 187.192 129.225 187.432C129.225 187.672 129.313 187.88 129.489 188.056C129.649 188.216 129.849 188.296 130.089 188.296C130.329 188.296 130.529 188.216 130.689 188.056C130.865 187.88 130.953 187.672 130.953 187.432ZM119.385 189.664C119.385 189.296 119.265 188.992 119.025 188.752C118.769 188.496 118.457 188.368 118.089 188.368C117.721 188.368 117.417 188.496 117.177 188.752C116.921 188.992 116.793 189.296 116.793 189.664C116.793 190.032 116.921 190.344 117.177 190.6C117.417 190.84 117.721 190.96 118.089 190.96C118.457 190.96 118.769 190.84 119.025 190.6C119.265 190.344 119.385 190.032 119.385 189.664ZM128.985 189.664C128.985 189.296 128.865 188.992 128.625 188.752C128.369 188.496 128.057 188.368 127.689 188.368C127.321 188.368 127.017 188.496 126.777 188.752C126.521 188.992 126.393 189.296 126.393 189.664C126.393 190.032 126.521 190.344 126.777 190.6C127.017 190.84 127.321 190.96 127.689 190.96C128.057 190.96 128.369 190.84 128.625 190.6C128.865 190.344 128.985 190.032 128.985 189.664ZM124.185 189.664C124.185 189.296 124.065 188.992 123.825 188.752C123.569 188.496 123.257 188.368 122.889 188.368C122.521 188.368 122.217 188.496 121.977 188.752C121.721 188.992 121.593 189.296 121.593 189.664C121.593 190.032 121.721 190.344 121.977 190.6C122.217 190.84 122.521 190.96 122.889 190.96C123.257 190.96 123.569 190.84 123.825 190.6C124.065 190.344 124.185 190.032 124.185 189.664ZM121.353 191.896C121.353 191.656 121.273 191.456 121.113 191.296C120.937 191.12 120.729 191.032 120.489 191.032C120.249 191.032 120.049 191.12 119.889 191.296C119.713 191.456 119.625 191.656 119.625 191.896C119.625 192.136 119.713 192.344 119.889 192.52C120.049 192.68 120.249 192.76 120.489 192.76C120.729 192.76 120.937 192.68 121.113 192.52C121.273 192.344 121.353 192.136 121.353 191.896ZM130.953 191.896C130.953 191.656 130.865 191.456 130.689 191.296C130.529 191.12 130.329 191.032 130.089 191.032C129.849 191.032 129.649 191.12 129.489 191.296C129.313 191.456 129.225 191.656 129.225 191.896C129.225 192.136 129.313 192.344 129.489 192.52C129.649 192.68 129.849 192.76 130.089 192.76C130.329 192.76 130.529 192.68 130.689 192.52C130.865 192.344 130.953 192.136 130.953 191.896ZM126.153 191.896C126.153 191.656 126.073 191.456 125.913 191.296C125.737 191.12 125.529 191.032 125.289 191.032C125.049 191.032 124.849 191.12 124.689 191.296C124.513 191.456 124.425 191.656 124.425 191.896C124.425 192.136 124.513 192.344 124.689 192.52C124.849 192.68 125.049 192.76 125.289 192.76C125.529 192.76 125.737 192.68 125.913 192.52C126.073 192.344 126.153 192.136 126.153 191.896ZM119.385 194.128C119.385 193.76 119.265 193.456 119.025 193.216C118.769 192.96 118.457 192.832 118.089 192.832C117.721 192.832 117.417 192.96 117.177 193.216C116.921 193.456 116.793 193.76 116.793 194.128C116.793 194.496 116.921 194.808 117.177 195.064C117.417 195.304 117.721 195.424 118.089 195.424C118.457 195.424 118.769 195.304 119.025 195.064C119.265 194.808 119.385 194.496 119.385 194.128ZM128.985 194.128C128.985 193.76 128.865 193.456 128.625 193.216C128.369 192.96 128.057 192.832 127.689 192.832C127.321 192.832 127.017 192.96 126.777 193.216C126.521 193.456 126.393 193.76 126.393 194.128C126.393 194.496 126.521 194.808 126.777 195.064C127.017 195.304 127.321 195.424 127.689 195.424C128.057 195.424 128.369 195.304 128.625 195.064C128.865 194.808 128.985 194.496 128.985 194.128ZM124.185 194.128C124.185 193.76 124.065 193.456 123.825 193.216C123.569 192.96 123.257 192.832 122.889 192.832C122.521 192.832 122.217 192.96 121.977 193.216C121.721 193.456 121.593 193.76 121.593 194.128C121.593 194.496 121.721 194.808 121.977 195.064C122.217 195.304 122.521 195.424 122.889 195.424C123.257 195.424 123.569 195.304 123.825 195.064C124.065 194.808 124.185 194.496 124.185 194.128ZM121.353 196.36C121.353 196.12 121.273 195.92 121.113 195.76C120.937 195.584 120.729 195.496 120.489 195.496C120.249 195.496 120.049 195.584 119.889 195.76C119.713 195.92 119.625 196.12 119.625 196.36C119.625 196.6 119.713 196.808 119.889 196.984C120.049 197.144 120.249 197.224 120.489 197.224C120.729 197.224 120.937 197.144 121.113 196.984C121.273 196.808 121.353 196.6 121.353 196.36ZM130.953 196.36C130.953 196.12 130.865 195.92 130.689 195.76C130.529 195.584 130.329 195.496 130.089 195.496C129.849 195.496 129.649 195.584 129.489 195.76C129.313 195.92 129.225 196.12 129.225 196.36C129.225 196.6 129.313 196.808 129.489 196.984C129.649 197.144 129.849 197.224 130.089 197.224C130.329 197.224 130.529 197.144 130.689 196.984C130.865 196.808 130.953 196.6 130.953 196.36ZM126.153 196.36C126.153 196.12 126.073 195.92 125.913 195.76C125.737 195.584 125.529 195.496 125.289 195.496C125.049 195.496 124.849 195.584 124.689 195.76C124.513 195.92 124.425 196.12 124.425 196.36C124.425 196.6 124.513 196.808 124.689 196.984C124.849 197.144 125.049 197.224 125.289 197.224C125.529 197.224 125.737 197.144 125.913 196.984C126.073 196.808 126.153 196.6 126.153 196.36ZM124.185 198.592C124.185 198.224 124.065 197.92 123.825 197.68C123.569 197.424 123.257 197.296 122.889 197.296C122.521 197.296 122.217 197.424 121.977 197.68C121.721 197.92 121.593 198.224 121.593 198.592C121.593 198.96 121.721 199.272 121.977 199.528C122.217 199.768 122.521 199.888 122.889 199.888C123.257 199.888 123.569 199.768 123.825 199.528C124.065 199.272 124.185 198.96 124.185 198.592ZM128.985 198.592C128.985 198.224 128.865 197.92 128.625 197.68C128.369 197.424 128.057 197.296 127.689 197.296C127.321 197.296 127.017 197.424 126.777 197.68C126.521 197.92 126.393 198.224 126.393 198.592C126.393 198.96 126.521 199.272 126.777 199.528C127.017 199.768 127.321 199.888 127.689 199.888C128.057 199.888 128.369 199.768 128.625 199.528C128.865 199.272 128.985 198.96 128.985 198.592ZM119.385 198.592C119.385 198.224 119.265 197.92 119.025 197.68C118.769 197.424 118.457 197.296 118.089 197.296C117.721 197.296 117.417 197.424 117.177 197.68C116.921 197.92 116.793 198.224 116.793 198.592C116.793 198.96 116.921 199.272 117.177 199.528C117.417 199.768 117.721 199.888 118.089 199.888C118.457 199.888 118.769 199.768 119.025 199.528C119.265 199.272 119.385 198.96 119.385 198.592ZM126.153 200.824C126.153 200.584 126.073 200.384 125.913 200.224C125.737 200.048 125.529 199.96 125.289 199.96C125.049 199.96 124.849 200.048 124.689 200.224C124.513 200.384 124.425 200.584 124.425 200.824C124.425 201.064 124.513 201.272 124.689 201.448C124.849 201.608 125.049 201.688 125.289 201.688C125.529 201.688 125.737 201.608 125.913 201.448C126.073 201.272 126.153 201.064 126.153 200.824ZM130.953 200.824C130.953 200.584 130.865 200.384 130.689 200.224C130.529 200.048 130.329 199.96 130.089 199.96C129.849 199.96 129.649 200.048 129.489 200.224C129.313 200.384 129.225 200.584 129.225 200.824C129.225 201.064 129.313 201.272 129.489 201.448C129.649 201.608 129.849 201.688 130.089 201.688C130.329 201.688 130.529 201.608 130.689 201.448C130.865 201.272 130.953 201.064 130.953 200.824ZM121.353 200.824C121.353 200.584 121.273 200.384 121.113 200.224C120.937 200.048 120.729 199.96 120.489 199.96C120.249 199.96 120.049 200.048 119.889 200.224C119.713 200.384 119.625 200.584 119.625 200.824C119.625 201.064 119.713 201.272 119.889 201.448C120.049 201.608 120.249 201.688 120.489 201.688C120.729 201.688 120.937 201.608 121.113 201.448C121.273 201.272 121.353 201.064 121.353 200.824ZM124.185 203.056C124.185 202.688 124.065 202.384 123.825 202.144C123.569 201.888 123.257 201.76 122.889 201.76C122.521 201.76 122.217 201.888 121.977 202.144C121.721 202.384 121.593 202.688 121.593 203.056C121.593 203.424 121.721 203.736 121.977 203.992C122.217 204.232 122.521 204.352 122.889 204.352C123.257 204.352 123.569 204.232 123.825 203.992C124.065 203.736 124.185 203.424 124.185 203.056ZM119.385 203.056C119.385 202.688 119.265 202.384 119.025 202.144C118.769 201.888 118.457 201.76 118.089 201.76C117.721 201.76 117.417 201.888 117.177 202.144C116.921 202.384 116.793 202.688 116.793 203.056C116.793 203.424 116.921 203.736 117.177 203.992C117.417 204.232 117.721 204.352 118.089 204.352C118.457 204.352 118.769 204.232 119.025 203.992C119.265 203.736 119.385 203.424 119.385 203.056ZM128.985 203.056C128.985 202.688 128.865 202.384 128.625 202.144C128.369 201.888 128.057 201.76 127.689 201.76C127.321 201.76 127.017 201.888 126.777 202.144C126.521 202.384 126.393 202.688 126.393 203.056C126.393 203.424 126.521 203.736 126.777 203.992C127.017 204.232 127.321 204.352 127.689 204.352C128.057 204.352 128.369 204.232 128.625 203.992C128.865 203.736 128.985 203.424 128.985 203.056ZM126.153 205.288C126.153 205.048 126.073 204.848 125.913 204.688C125.737 204.512 125.529 204.424 125.289 204.424C125.049 204.424 124.849 204.512 124.689 204.688C124.513 204.848 124.425 205.048 124.425 205.288C124.425 205.528 124.513 205.736 124.689 205.912C124.849 206.072 125.049 206.152 125.289 206.152C125.529 206.152 125.737 206.072 125.913 205.912C126.073 205.736 126.153 205.528 126.153 205.288ZM121.353 205.288C121.353 205.048 121.273 204.848 121.113 204.688C120.937 204.512 120.729 204.424 120.489 204.424C120.249 204.424 120.049 204.512 119.889 204.688C119.713 204.848 119.625 205.048 119.625 205.288C119.625 205.528 119.713 205.736 119.889 205.912C120.049 206.072 120.249 206.152 120.489 206.152C120.729 206.152 120.937 206.072 121.113 205.912C121.273 205.736 121.353 205.528 121.353 205.288ZM130.953 205.288C130.953 205.048 130.865 204.848 130.689 204.688C130.529 204.512 130.329 204.424 130.089 204.424C129.849 204.424 129.649 204.512 129.489 204.688C129.313 204.848 129.225 205.048 129.225 205.288C129.225 205.528 129.313 205.736 129.489 205.912C129.649 206.072 129.849 206.152 130.089 206.152C130.329 206.152 130.529 206.072 130.689 205.912C130.865 205.736 130.953 205.528 130.953 205.288ZM124.185 207.52C124.185 207.152 124.065 206.848 123.825 206.608C123.569 206.352 123.257 206.224 122.889 206.224C122.521 206.224 122.217 206.352 121.977 206.608C121.721 206.848 121.593 207.152 121.593 207.52C121.593 207.888 121.721 208.192 121.977 208.432C122.217 208.688 122.521 208.816 122.889 208.816C123.257 208.816 123.569 208.688 123.825 208.432C124.065 208.192 124.185 207.888 124.185 207.52ZM119.385 207.52C119.385 207.152 119.265 206.848 119.025 206.608C118.769 206.352 118.457 206.224 118.089 206.224C117.721 206.224 117.417 206.352 117.177 206.608C116.921 206.848 116.793 207.152 116.793 207.52C116.793 207.888 116.921 208.192 117.177 208.432C117.417 208.688 117.721 208.816 118.089 208.816C118.457 208.816 118.769 208.688 119.025 208.432C119.265 208.192 119.385 207.888 119.385 207.52ZM128.985 207.52C128.985 207.152 128.865 206.848 128.625 206.608C128.369 206.352 128.057 206.224 127.689 206.224C127.321 206.224 127.017 206.352 126.777 206.608C126.521 206.848 126.393 207.152 126.393 207.52C126.393 207.888 126.521 208.192 126.777 208.432C127.017 208.688 127.321 208.816 127.689 208.816C128.057 208.816 128.369 208.688 128.625 208.432C128.865 208.192 128.985 207.888 128.985 207.52ZM126.153 209.752C126.153 209.512 126.073 209.304 125.913 209.128C125.737 208.968 125.529 208.888 125.289 208.888C125.049 208.888 124.849 208.968 124.689 209.128C124.513 209.304 124.425 209.512 124.425 209.752C124.425 209.992 124.513 210.192 124.689 210.352C124.849 210.528 125.049 210.616 125.289 210.616C125.529 210.616 125.737 210.528 125.913 210.352C126.073 210.192 126.153 209.992 126.153 209.752ZM121.353 209.752C121.353 209.512 121.273 209.304 121.113 209.128C120.937 208.968 120.729 208.888 120.489 208.888C120.249 208.888 120.049 208.968 119.889 209.128C119.713 209.304 119.625 209.512 119.625 209.752C119.625 209.992 119.713 210.192 119.889 210.352C120.049 210.528 120.249 210.616 120.489 210.616C120.729 210.616 120.937 210.528 121.113 210.352C121.273 210.192 121.353 209.992 121.353 209.752ZM130.953 209.752C130.953 209.512 130.865 209.304 130.689 209.128C130.529 208.968 130.329 208.888 130.089 208.888C129.849 208.888 129.649 208.968 129.489 209.128C129.313 209.304 129.225 209.512 129.225 209.752C129.225 209.992 129.313 210.192 129.489 210.352C129.649 210.528 129.849 210.616 130.089 210.616C130.329 210.616 130.529 210.528 130.689 210.352C130.865 210.192 130.953 209.992 130.953 209.752ZM124.185 211.984C124.185 211.616 124.065 211.304 123.825 211.048C123.569 210.808 123.257 210.688 122.889 210.688C122.521 210.688 122.217 210.808 121.977 211.048C121.721 211.304 121.593 211.616 121.593 211.984C121.593 212.352 121.721 212.656 121.977 212.896C122.217 213.152 122.521 213.28 122.889 213.28C123.257 213.28 123.569 213.152 123.825 212.896C124.065 212.656 124.185 212.352 124.185 211.984ZM119.385 211.984C119.385 211.616 119.265 211.304 119.025 211.048C118.769 210.808 118.457 210.688 118.089 210.688C117.721 210.688 117.417 210.808 117.177 211.048C116.921 211.304 116.793 211.616 116.793 211.984C116.793 212.352 116.921 212.656 117.177 212.896C117.417 213.152 117.721 213.28 118.089 213.28C118.457 213.28 118.769 213.152 119.025 212.896C119.265 212.656 119.385 212.352 119.385 211.984ZM128.985 211.984C128.985 211.616 128.865 211.304 128.625 211.048C128.369 210.808 128.057 210.688 127.689 210.688C127.321 210.688 127.017 210.808 126.777 211.048C126.521 211.304 126.393 211.616 126.393 211.984C126.393 212.352 126.521 212.656 126.777 212.896C127.017 213.152 127.321 213.28 127.689 213.28C128.057 213.28 128.369 213.152 128.625 212.896C128.865 212.656 128.985 212.352 128.985 211.984ZM126.153 214.216C126.153 213.976 126.073 213.768 125.913 213.592C125.737 213.432 125.529 213.352 125.289 213.352C125.049 213.352 124.849 213.432 124.689 213.592C124.513 213.768 124.425 213.976 124.425 214.216C124.425 214.456 124.513 214.656 124.689 214.816C124.849 214.992 125.049 215.08 125.289 215.08C125.529 215.08 125.737 214.992 125.913 214.816C126.073 214.656 126.153 214.456 126.153 214.216ZM121.353 214.216C121.353 213.976 121.273 213.768 121.113 213.592C120.937 213.432 120.729 213.352 120.489 213.352C120.249 213.352 120.049 213.432 119.889 213.592C119.713 213.768 119.625 213.976 119.625 214.216C119.625 214.456 119.713 214.656 119.889 214.816C120.049 214.992 120.249 215.08 120.489 215.08C120.729 215.08 120.937 214.992 121.113 214.816C121.273 214.656 121.353 214.456 121.353 214.216ZM130.953 214.216C130.953 213.976 130.865 213.768 130.689 213.592C130.529 213.432 130.329 213.352 130.089 213.352C129.849 213.352 129.649 213.432 129.489 213.592C129.313 213.768 129.225 213.976 129.225 214.216C129.225 214.456 129.313 214.656 129.489 214.816C129.649 214.992 129.849 215.08 130.089 215.08C130.329 215.08 130.529 214.992 130.689 214.816C130.865 214.656 130.953 214.456 130.953 214.216ZM105.043 185.2C105.043 184.96 104.963 184.76 104.803 184.6C104.627 184.424 104.419 184.336 104.179 184.336C103.939 184.336 103.739 184.424 103.579 184.6C103.403 184.76 103.315 184.96 103.315 185.2C103.315 185.44 103.403 185.648 103.579 185.824C103.739 185.984 103.939 186.064 104.179 186.064C104.419 186.064 104.627 185.984 104.803 185.824C104.963 185.648 105.043 185.44 105.043 185.2ZM109.843 185.2C109.843 184.96 109.763 184.76 109.603 184.6C109.427 184.424 109.219 184.336 108.979 184.336C108.739 184.336 108.539 184.424 108.379 184.6C108.203 184.76 108.115 184.96 108.115 185.2C108.115 185.44 108.203 185.648 108.379 185.824C108.539 185.984 108.739 186.064 108.979 186.064C109.219 186.064 109.427 185.984 109.603 185.824C109.763 185.648 109.843 185.44 109.843 185.2ZM114.643 185.2C114.643 184.96 114.563 184.76 114.403 184.6C114.227 184.424 114.019 184.336 113.779 184.336C113.539 184.336 113.339 184.424 113.179 184.6C113.003 184.76 112.915 184.96 112.915 185.2C112.915 185.44 113.003 185.648 113.179 185.824C113.339 185.984 113.539 186.064 113.779 186.064C114.019 186.064 114.227 185.984 114.403 185.824C114.563 185.648 114.643 185.44 114.643 185.2ZM107.155 187.432C107.155 187.272 107.099 187.136 106.987 187.024C106.875 186.912 106.739 186.856 106.579 186.856C106.419 186.856 106.283 186.912 106.171 187.024C106.059 187.136 106.003 187.272 106.003 187.432C106.003 187.592 106.059 187.728 106.171 187.84C106.283 187.952 106.419 188.008 106.579 188.008C106.739 188.008 106.875 187.952 106.987 187.84C107.099 187.728 107.155 187.592 107.155 187.432ZM111.955 187.432C111.955 187.272 111.899 187.136 111.787 187.024C111.675 186.912 111.539 186.856 111.379 186.856C111.219 186.856 111.083 186.912 110.971 187.024C110.859 187.136 110.803 187.272 110.803 187.432C110.803 187.592 110.859 187.728 110.971 187.84C111.083 187.952 111.219 188.008 111.379 188.008C111.539 188.008 111.675 187.952 111.787 187.84C111.899 187.728 111.955 187.592 111.955 187.432ZM116.755 187.432C116.755 187.272 116.699 187.136 116.587 187.024C116.475 186.912 116.339 186.856 116.179 186.856C116.019 186.856 115.883 186.912 115.771 187.024C115.659 187.136 115.603 187.272 115.603 187.432C115.603 187.592 115.659 187.728 115.771 187.84C115.883 187.952 116.019 188.008 116.179 188.008C116.339 188.008 116.475 187.952 116.587 187.84C116.699 187.728 116.755 187.592 116.755 187.432ZM105.043 189.664C105.043 189.424 104.963 189.224 104.803 189.064C104.627 188.888 104.419 188.8 104.179 188.8C103.939 188.8 103.739 188.888 103.579 189.064C103.403 189.224 103.315 189.424 103.315 189.664C103.315 189.904 103.403 190.112 103.579 190.288C103.739 190.448 103.939 190.528 104.179 190.528C104.419 190.528 104.627 190.448 104.803 190.288C104.963 190.112 105.043 189.904 105.043 189.664ZM114.643 189.664C114.643 189.424 114.563 189.224 114.403 189.064C114.227 188.888 114.019 188.8 113.779 188.8C113.539 188.8 113.339 188.888 113.179 189.064C113.003 189.224 112.915 189.424 112.915 189.664C112.915 189.904 113.003 190.112 113.179 190.288C113.339 190.448 113.539 190.528 113.779 190.528C114.019 190.528 114.227 190.448 114.403 190.288C114.563 190.112 114.643 189.904 114.643 189.664ZM109.843 189.664C109.843 189.424 109.763 189.224 109.603 189.064C109.427 188.888 109.219 188.8 108.979 188.8C108.739 188.8 108.539 188.888 108.379 189.064C108.203 189.224 108.115 189.424 108.115 189.664C108.115 189.904 108.203 190.112 108.379 190.288C108.539 190.448 108.739 190.528 108.979 190.528C109.219 190.528 109.427 190.448 109.603 190.288C109.763 190.112 109.843 189.904 109.843 189.664ZM107.155 191.896C107.155 191.736 107.099 191.6 106.987 191.488C106.875 191.376 106.739 191.32 106.579 191.32C106.419 191.32 106.283 191.376 106.171 191.488C106.059 191.6 106.003 191.736 106.003 191.896C106.003 192.056 106.059 192.192 106.171 192.304C106.283 192.416 106.419 192.472 106.579 192.472C106.739 192.472 106.875 192.416 106.987 192.304C107.099 192.192 107.155 192.056 107.155 191.896ZM116.755 191.896C116.755 191.736 116.699 191.6 116.587 191.488C116.475 191.376 116.339 191.32 116.179 191.32C116.019 191.32 115.883 191.376 115.771 191.488C115.659 191.6 115.603 191.736 115.603 191.896C115.603 192.056 115.659 192.192 115.771 192.304C115.883 192.416 116.019 192.472 116.179 192.472C116.339 192.472 116.475 192.416 116.587 192.304C116.699 192.192 116.755 192.056 116.755 191.896ZM111.955 191.896C111.955 191.736 111.899 191.6 111.787 191.488C111.675 191.376 111.539 191.32 111.379 191.32C111.219 191.32 111.083 191.376 110.971 191.488C110.859 191.6 110.803 191.736 110.803 191.896C110.803 192.056 110.859 192.192 110.971 192.304C111.083 192.416 111.219 192.472 111.379 192.472C111.539 192.472 111.675 192.416 111.787 192.304C111.899 192.192 111.955 192.056 111.955 191.896ZM105.043 194.128C105.043 193.888 104.963 193.688 104.803 193.528C104.627 193.352 104.419 193.264 104.179 193.264C103.939 193.264 103.739 193.352 103.579 193.528C103.403 193.688 103.315 193.888 103.315 194.128C103.315 194.368 103.403 194.576 103.579 194.752C103.739 194.912 103.939 194.992 104.179 194.992C104.419 194.992 104.627 194.912 104.803 194.752C104.963 194.576 105.043 194.368 105.043 194.128ZM114.643 194.128C114.643 193.888 114.563 193.688 114.403 193.528C114.227 193.352 114.019 193.264 113.779 193.264C113.539 193.264 113.339 193.352 113.179 193.528C113.003 193.688 112.915 193.888 112.915 194.128C112.915 194.368 113.003 194.576 113.179 194.752C113.339 194.912 113.539 194.992 113.779 194.992C114.019 194.992 114.227 194.912 114.403 194.752C114.563 194.576 114.643 194.368 114.643 194.128ZM109.843 194.128C109.843 193.888 109.763 193.688 109.603 193.528C109.427 193.352 109.219 193.264 108.979 193.264C108.739 193.264 108.539 193.352 108.379 193.528C108.203 193.688 108.115 193.888 108.115 194.128C108.115 194.368 108.203 194.576 108.379 194.752C108.539 194.912 108.739 194.992 108.979 194.992C109.219 194.992 109.427 194.912 109.603 194.752C109.763 194.576 109.843 194.368 109.843 194.128ZM107.155 196.264C107.155 196.104 107.099 195.968 106.987 195.856C106.875 195.744 106.739 195.688 106.579 195.688C106.419 195.688 106.283 195.744 106.171 195.856C106.059 195.968 106.003 196.104 106.003 196.264C106.003 196.424 106.059 196.56 106.171 196.672C106.283 196.784 106.419 196.84 106.579 196.84C106.739 196.84 106.875 196.784 106.987 196.672C107.099 196.56 107.155 196.424 107.155 196.264ZM116.755 196.264C116.755 196.104 116.699 195.968 116.587 195.856C116.475 195.744 116.339 195.688 116.179 195.688C116.019 195.688 115.883 195.744 115.771 195.856C115.659 195.968 115.603 196.104 115.603 196.264C115.603 196.424 115.659 196.56 115.771 196.672C115.883 196.784 116.019 196.84 116.179 196.84C116.339 196.84 116.475 196.784 116.587 196.672C116.699 196.56 116.755 196.424 116.755 196.264ZM111.955 196.264C111.955 196.104 111.899 195.968 111.787 195.856C111.675 195.744 111.539 195.688 111.379 195.688C111.219 195.688 111.083 195.744 110.971 195.856C110.859 195.968 110.803 196.104 110.803 196.264C110.803 196.424 110.859 196.56 110.971 196.672C111.083 196.784 111.219 196.84 111.379 196.84C111.539 196.84 111.675 196.784 111.787 196.672C111.899 196.56 111.955 196.424 111.955 196.264ZM109.843 198.496C109.843 198.256 109.763 198.056 109.603 197.896C109.427 197.72 109.219 197.632 108.979 197.632C108.739 197.632 108.539 197.72 108.379 197.896C108.203 198.056 108.115 198.256 108.115 198.496C108.115 198.736 108.203 198.944 108.379 199.12C108.539 199.28 108.739 199.36 108.979 199.36C109.219 199.36 109.427 199.28 109.603 199.12C109.763 198.944 109.843 198.736 109.843 198.496ZM114.643 198.496C114.643 198.256 114.563 198.056 114.403 197.896C114.227 197.72 114.019 197.632 113.779 197.632C113.539 197.632 113.339 197.72 113.179 197.896C113.003 198.056 112.915 198.256 112.915 198.496C112.915 198.736 113.003 198.944 113.179 199.12C113.339 199.28 113.539 199.36 113.779 199.36C114.019 199.36 114.227 199.28 114.403 199.12C114.563 198.944 114.643 198.736 114.643 198.496ZM105.043 198.496C105.043 198.256 104.963 198.056 104.803 197.896C104.627 197.72 104.419 197.632 104.179 197.632C103.939 197.632 103.739 197.72 103.579 197.896C103.403 198.056 103.315 198.256 103.315 198.496C103.315 198.736 103.403 198.944 103.579 199.12C103.739 199.28 103.939 199.36 104.179 199.36C104.419 199.36 104.627 199.28 104.803 199.12C104.963 198.944 105.043 198.736 105.043 198.496ZM111.955 200.728C111.955 200.568 111.899 200.432 111.787 200.32C111.675 200.208 111.539 200.152 111.379 200.152C111.219 200.152 111.083 200.208 110.971 200.32C110.859 200.432 110.803 200.568 110.803 200.728C110.803 200.888 110.859 201.024 110.971 201.136C111.083 201.248 111.219 201.304 111.379 201.304C111.539 201.304 111.675 201.248 111.787 201.136C111.899 201.024 111.955 200.888 111.955 200.728ZM116.755 200.728C116.755 200.568 116.699 200.432 116.587 200.32C116.475 200.208 116.339 200.152 116.179 200.152C116.019 200.152 115.883 200.208 115.771 200.32C115.659 200.432 115.603 200.568 115.603 200.728C115.603 200.888 115.659 201.024 115.771 201.136C115.883 201.248 116.019 201.304 116.179 201.304C116.339 201.304 116.475 201.248 116.587 201.136C116.699 201.024 116.755 200.888 116.755 200.728ZM107.155 200.728C107.155 200.568 107.099 200.432 106.987 200.32C106.875 200.208 106.739 200.152 106.579 200.152C106.419 200.152 106.283 200.208 106.171 200.32C106.059 200.432 106.003 200.568 106.003 200.728C106.003 200.888 106.059 201.024 106.171 201.136C106.283 201.248 106.419 201.304 106.579 201.304C106.739 201.304 106.875 201.248 106.987 201.136C107.099 201.024 107.155 200.888 107.155 200.728ZM109.843 202.96C109.843 202.72 109.763 202.52 109.603 202.36C109.427 202.184 109.219 202.096 108.979 202.096C108.739 202.096 108.539 202.184 108.379 202.36C108.203 202.52 108.115 202.72 108.115 202.96C108.115 203.2 108.203 203.408 108.379 203.584C108.539 203.744 108.739 203.824 108.979 203.824C109.219 203.824 109.427 203.744 109.603 203.584C109.763 203.408 109.843 203.2 109.843 202.96ZM105.043 202.96C105.043 202.72 104.963 202.52 104.803 202.36C104.627 202.184 104.419 202.096 104.179 202.096C103.939 202.096 103.739 202.184 103.579 202.36C103.403 202.52 103.315 202.72 103.315 202.96C103.315 203.2 103.403 203.408 103.579 203.584C103.739 203.744 103.939 203.824 104.179 203.824C104.419 203.824 104.627 203.744 104.803 203.584C104.963 203.408 105.043 203.2 105.043 202.96ZM114.643 202.96C114.643 202.72 114.563 202.52 114.403 202.36C114.227 202.184 114.019 202.096 113.779 202.096C113.539 202.096 113.339 202.184 113.179 202.36C113.003 202.52 112.915 202.72 112.915 202.96C112.915 203.2 113.003 203.408 113.179 203.584C113.339 203.744 113.539 203.824 113.779 203.824C114.019 203.824 114.227 203.744 114.403 203.584C114.563 203.408 114.643 203.2 114.643 202.96ZM111.955 205.192C111.955 205.032 111.899 204.896 111.787 204.784C111.675 204.672 111.539 204.616 111.379 204.616C111.219 204.616 111.083 204.672 110.971 204.784C110.859 204.896 110.803 205.032 110.803 205.192C110.803 205.352 110.859 205.488 110.971 205.6C111.083 205.712 111.219 205.768 111.379 205.768C111.539 205.768 111.675 205.712 111.787 205.6C111.899 205.488 111.955 205.352 111.955 205.192ZM107.155 205.192C107.155 205.032 107.099 204.896 106.987 204.784C106.875 204.672 106.739 204.616 106.579 204.616C106.419 204.616 106.283 204.672 106.171 204.784C106.059 204.896 106.003 205.032 106.003 205.192C106.003 205.352 106.059 205.488 106.171 205.6C106.283 205.712 106.419 205.768 106.579 205.768C106.739 205.768 106.875 205.712 106.987 205.6C107.099 205.488 107.155 205.352 107.155 205.192ZM116.755 205.192C116.755 205.032 116.699 204.896 116.587 204.784C116.475 204.672 116.339 204.616 116.179 204.616C116.019 204.616 115.883 204.672 115.771 204.784C115.659 204.896 115.603 205.032 115.603 205.192C115.603 205.352 115.659 205.488 115.771 205.6C115.883 205.712 116.019 205.768 116.179 205.768C116.339 205.768 116.475 205.712 116.587 205.6C116.699 205.488 116.755 205.352 116.755 205.192ZM109.843 207.424C109.843 207.184 109.763 206.984 109.603 206.824C109.427 206.648 109.219 206.56 108.979 206.56C108.739 206.56 108.539 206.648 108.379 206.824C108.203 206.984 108.115 207.184 108.115 207.424C108.115 207.664 108.203 207.864 108.379 208.024C108.539 208.2 108.739 208.288 108.979 208.288C109.219 208.288 109.427 208.2 109.603 208.024C109.763 207.864 109.843 207.664 109.843 207.424ZM105.043 207.424C105.043 207.184 104.963 206.984 104.803 206.824C104.627 206.648 104.419 206.56 104.179 206.56C103.939 206.56 103.739 206.648 103.579 206.824C103.403 206.984 103.315 207.184 103.315 207.424C103.315 207.664 103.403 207.864 103.579 208.024C103.739 208.2 103.939 208.288 104.179 208.288C104.419 208.288 104.627 208.2 104.803 208.024C104.963 207.864 105.043 207.664 105.043 207.424ZM114.643 207.424C114.643 207.184 114.563 206.984 114.403 206.824C114.227 206.648 114.019 206.56 113.779 206.56C113.539 206.56 113.339 206.648 113.179 206.824C113.003 206.984 112.915 207.184 112.915 207.424C112.915 207.664 113.003 207.864 113.179 208.024C113.339 208.2 113.539 208.288 113.779 208.288C114.019 208.288 114.227 208.2 114.403 208.024C114.563 207.864 114.643 207.664 114.643 207.424ZM111.955 209.656C111.955 209.496 111.899 209.36 111.787 209.248C111.675 209.136 111.539 209.08 111.379 209.08C111.219 209.08 111.083 209.136 110.971 209.248C110.859 209.36 110.803 209.496 110.803 209.656C110.803 209.816 110.859 209.952 110.971 210.064C111.083 210.176 111.219 210.232 111.379 210.232C111.539 210.232 111.675 210.176 111.787 210.064C111.899 209.952 111.955 209.816 111.955 209.656ZM107.155 209.656C107.155 209.496 107.099 209.36 106.987 209.248C106.875 209.136 106.739 209.08 106.579 209.08C106.419 209.08 106.283 209.136 106.171 209.248C106.059 209.36 106.003 209.496 106.003 209.656C106.003 209.816 106.059 209.952 106.171 210.064C106.283 210.176 106.419 210.232 106.579 210.232C106.739 210.232 106.875 210.176 106.987 210.064C107.099 209.952 107.155 209.816 107.155 209.656ZM116.755 209.656C116.755 209.496 116.699 209.36 116.587 209.248C116.475 209.136 116.339 209.08 116.179 209.08C116.019 209.08 115.883 209.136 115.771 209.248C115.659 209.36 115.603 209.496 115.603 209.656C115.603 209.816 115.659 209.952 115.771 210.064C115.883 210.176 116.019 210.232 116.179 210.232C116.339 210.232 116.475 210.176 116.587 210.064C116.699 209.952 116.755 209.816 116.755 209.656ZM109.843 211.888C109.843 211.648 109.763 211.44 109.603 211.264C109.427 211.104 109.219 211.024 108.979 211.024C108.739 211.024 108.539 211.104 108.379 211.264C108.203 211.44 108.115 211.648 108.115 211.888C108.115 212.128 108.203 212.328 108.379 212.488C108.539 212.664 108.739 212.752 108.979 212.752C109.219 212.752 109.427 212.664 109.603 212.488C109.763 212.328 109.843 212.128 109.843 211.888ZM105.043 211.888C105.043 211.648 104.963 211.44 104.803 211.264C104.627 211.104 104.419 211.024 104.179 211.024C103.939 211.024 103.739 211.104 103.579 211.264C103.403 211.44 103.315 211.648 103.315 211.888C103.315 212.128 103.403 212.328 103.579 212.488C103.739 212.664 103.939 212.752 104.179 212.752C104.419 212.752 104.627 212.664 104.803 212.488C104.963 212.328 105.043 212.128 105.043 211.888ZM114.643 211.888C114.643 211.648 114.563 211.44 114.403 211.264C114.227 211.104 114.019 211.024 113.779 211.024C113.539 211.024 113.339 211.104 113.179 211.264C113.003 211.44 112.915 211.648 112.915 211.888C112.915 212.128 113.003 212.328 113.179 212.488C113.339 212.664 113.539 212.752 113.779 212.752C114.019 212.752 114.227 212.664 114.403 212.488C114.563 212.328 114.643 212.128 114.643 211.888ZM111.955 214.12C111.955 213.96 111.899 213.824 111.787 213.712C111.675 213.6 111.539 213.544 111.379 213.544C111.219 213.544 111.083 213.6 110.971 213.712C110.859 213.824 110.803 213.96 110.803 214.12C110.803 214.28 110.859 214.416 110.971 214.528C111.083 214.64 111.219 214.696 111.379 214.696C111.539 214.696 111.675 214.64 111.787 214.528C111.899 214.416 111.955 214.28 111.955 214.12ZM107.155 214.12C107.155 213.96 107.099 213.824 106.987 213.712C106.875 213.6 106.739 213.544 106.579 213.544C106.419 213.544 106.283 213.6 106.171 213.712C106.059 213.824 106.003 213.96 106.003 214.12C106.003 214.28 106.059 214.416 106.171 214.528C106.283 214.64 106.419 214.696 106.579 214.696C106.739 214.696 106.875 214.64 106.987 214.528C107.099 214.416 107.155 214.28 107.155 214.12ZM116.755 214.12C116.755 213.96 116.699 213.824 116.587 213.712C116.475 213.6 116.339 213.544 116.179 213.544C116.019 213.544 115.883 213.6 115.771 213.712C115.659 213.824 115.603 213.96 115.603 214.12C115.603 214.28 115.659 214.416 115.771 214.528C115.883 214.64 116.019 214.696 116.179 214.696C116.339 214.696 116.475 214.64 116.587 214.528C116.699 214.416 116.755 214.28 116.755 214.12ZM133.944 216.2C133.944 215.656 133.76 215.2 133.392 214.832C133.008 214.448 132.544 214.256 132 214.256C131.456 214.256 131 214.448 130.632 214.832C130.248 215.2 130.056 215.656 130.056 216.2C130.056 216.744 130.248 217.208 130.632 217.592C131 217.96 131.456 218.144 132 218.144C132.544 218.144 133.008 217.96 133.392 217.592C133.76 217.208 133.944 216.744 133.944 216.2ZM138.744 216.2C138.744 215.656 138.56 215.2 138.192 214.832C137.808 214.448 137.344 214.256 136.8 214.256C136.256 214.256 135.8 214.448 135.432 214.832C135.048 215.2 134.856 215.656 134.856 216.2C134.856 216.744 135.048 217.208 135.432 217.592C135.8 217.96 136.256 218.144 136.8 218.144C137.344 218.144 137.808 217.96 138.192 217.592C138.56 217.208 138.744 216.744 138.744 216.2ZM143.544 216.2C143.544 215.656 143.36 215.2 142.992 214.832C142.608 214.448 142.144 214.256 141.6 214.256C141.056 214.256 140.6 214.448 140.232 214.832C139.848 215.2 139.656 215.656 139.656 216.2C139.656 216.744 139.848 217.208 140.232 217.592C140.6 217.96 141.056 218.144 141.6 218.144C142.144 218.144 142.608 217.96 142.992 217.592C143.36 217.208 143.544 216.744 143.544 216.2ZM135.696 218.432C135.696 218.064 135.576 217.76 135.336 217.52C135.08 217.264 134.768 217.136 134.4 217.136C134.032 217.136 133.728 217.264 133.488 217.52C133.232 217.76 133.104 218.064 133.104 218.432C133.104 218.8 133.232 219.112 133.488 219.368C133.728 219.608 134.032 219.728 134.4 219.728C134.768 219.728 135.08 219.608 135.336 219.368C135.576 219.112 135.696 218.8 135.696 218.432ZM140.496 218.432C140.496 218.064 140.376 217.76 140.136 217.52C139.88 217.264 139.568 217.136 139.2 217.136C138.832 217.136 138.528 217.264 138.288 217.52C138.032 217.76 137.904 218.064 137.904 218.432C137.904 218.8 138.032 219.112 138.288 219.368C138.528 219.608 138.832 219.728 139.2 219.728C139.568 219.728 139.88 219.608 140.136 219.368C140.376 219.112 140.496 218.8 140.496 218.432ZM145.296 218.432C145.296 218.064 145.168 217.76 144.912 217.52C144.672 217.264 144.368 217.136 144 217.136C143.632 217.136 143.328 217.264 143.088 217.52C142.832 217.76 142.704 218.064 142.704 218.432C142.704 218.8 142.832 219.112 143.088 219.368C143.328 219.608 143.632 219.728 144 219.728C144.368 219.728 144.672 219.608 144.912 219.368C145.168 219.112 145.296 218.8 145.296 218.432ZM133.944 220.664C133.944 220.12 133.76 219.664 133.392 219.296C133.008 218.912 132.544 218.72 132 218.72C131.456 218.72 131 218.912 130.632 219.296C130.248 219.664 130.056 220.12 130.056 220.664C130.056 221.208 130.248 221.672 130.632 222.056C131 222.424 131.456 222.608 132 222.608C132.544 222.608 133.008 222.424 133.392 222.056C133.76 221.672 133.944 221.208 133.944 220.664ZM143.544 220.664C143.544 220.12 143.36 219.664 142.992 219.296C142.608 218.912 142.144 218.72 141.6 218.72C141.056 218.72 140.6 218.912 140.232 219.296C139.848 219.664 139.656 220.12 139.656 220.664C139.656 221.208 139.848 221.672 140.232 222.056C140.6 222.424 141.056 222.608 141.6 222.608C142.144 222.608 142.608 222.424 142.992 222.056C143.36 221.672 143.544 221.208 143.544 220.664ZM138.744 220.664C138.744 220.12 138.56 219.664 138.192 219.296C137.808 218.912 137.344 218.72 136.8 218.72C136.256 218.72 135.8 218.912 135.432 219.296C135.048 219.664 134.856 220.12 134.856 220.664C134.856 221.208 135.048 221.672 135.432 222.056C135.8 222.424 136.256 222.608 136.8 222.608C137.344 222.608 137.808 222.424 138.192 222.056C138.56 221.672 138.744 221.208 138.744 220.664ZM135.696 222.896C135.696 222.528 135.576 222.224 135.336 221.984C135.08 221.728 134.768 221.6 134.4 221.6C134.032 221.6 133.728 221.728 133.488 221.984C133.232 222.224 133.104 222.528 133.104 222.896C133.104 223.264 133.232 223.576 133.488 223.832C133.728 224.072 134.032 224.192 134.4 224.192C134.768 224.192 135.08 224.072 135.336 223.832C135.576 223.576 135.696 223.264 135.696 222.896ZM145.296 222.896C145.296 222.528 145.168 222.224 144.912 221.984C144.672 221.728 144.368 221.6 144 221.6C143.632 221.6 143.328 221.728 143.088 221.984C142.832 222.224 142.704 222.528 142.704 222.896C142.704 223.264 142.832 223.576 143.088 223.832C143.328 224.072 143.632 224.192 144 224.192C144.368 224.192 144.672 224.072 144.912 223.832C145.168 223.576 145.296 223.264 145.296 222.896ZM140.496 222.896C140.496 222.528 140.376 222.224 140.136 221.984C139.88 221.728 139.568 221.6 139.2 221.6C138.832 221.6 138.528 221.728 138.288 221.984C138.032 222.224 137.904 222.528 137.904 222.896C137.904 223.264 138.032 223.576 138.288 223.832C138.528 224.072 138.832 224.192 139.2 224.192C139.568 224.192 139.88 224.072 140.136 223.832C140.376 223.576 140.496 223.264 140.496 222.896ZM133.944 225.128C133.944 224.584 133.76 224.128 133.392 223.76C133.008 223.376 132.544 223.184 132 223.184C131.456 223.184 131 223.376 130.632 223.76C130.248 224.128 130.056 224.584 130.056 225.128C130.056 225.672 130.248 226.136 130.632 226.52C131 226.888 131.456 227.072 132 227.072C132.544 227.072 133.008 226.888 133.392 226.52C133.76 226.136 133.944 225.672 133.944 225.128ZM143.544 225.128C143.544 224.584 143.36 224.128 142.992 223.76C142.608 223.376 142.144 223.184 141.6 223.184C141.056 223.184 140.6 223.376 140.232 223.76C139.848 224.128 139.656 224.584 139.656 225.128C139.656 225.672 139.848 226.136 140.232 226.52C140.6 226.888 141.056 227.072 141.6 227.072C142.144 227.072 142.608 226.888 142.992 226.52C143.36 226.136 143.544 225.672 143.544 225.128ZM138.744 225.128C138.744 224.584 138.56 224.128 138.192 223.76C137.808 223.376 137.344 223.184 136.8 223.184C136.256 223.184 135.8 223.376 135.432 223.76C135.048 224.128 134.856 224.584 134.856 225.128C134.856 225.672 135.048 226.136 135.432 226.52C135.8 226.888 136.256 227.072 136.8 227.072C137.344 227.072 137.808 226.888 138.192 226.52C138.56 226.136 138.744 225.672 138.744 225.128ZM135.696 227.36C135.696 226.992 135.576 226.688 135.336 226.448C135.08 226.192 134.768 226.064 134.4 226.064C134.032 226.064 133.728 226.192 133.488 226.448C133.232 226.688 133.104 226.992 133.104 227.36C133.104 227.728 133.232 228.04 133.488 228.296C133.728 228.536 134.032 228.656 134.4 228.656C134.768 228.656 135.08 228.536 135.336 228.296C135.576 228.04 135.696 227.728 135.696 227.36ZM145.296 227.36C145.296 226.992 145.168 226.688 144.912 226.448C144.672 226.192 144.368 226.064 144 226.064C143.632 226.064 143.328 226.192 143.088 226.448C142.832 226.688 142.704 226.992 142.704 227.36C142.704 227.728 142.832 228.04 143.088 228.296C143.328 228.536 143.632 228.656 144 228.656C144.368 228.656 144.672 228.536 144.912 228.296C145.168 228.04 145.296 227.728 145.296 227.36ZM140.496 227.36C140.496 226.992 140.376 226.688 140.136 226.448C139.88 226.192 139.568 226.064 139.2 226.064C138.832 226.064 138.528 226.192 138.288 226.448C138.032 226.688 137.904 226.992 137.904 227.36C137.904 227.728 138.032 228.04 138.288 228.296C138.528 228.536 138.832 228.656 139.2 228.656C139.568 228.656 139.88 228.536 140.136 228.296C140.376 228.04 140.496 227.728 140.496 227.36ZM138.744 229.592C138.744 229.048 138.56 228.592 138.192 228.224C137.808 227.84 137.344 227.648 136.8 227.648C136.256 227.648 135.8 227.84 135.432 228.224C135.048 228.592 134.856 229.048 134.856 229.592C134.856 230.136 135.048 230.6 135.432 230.984C135.8 231.352 136.256 231.536 136.8 231.536C137.344 231.536 137.808 231.352 138.192 230.984C138.56 230.6 138.744 230.136 138.744 229.592ZM143.544 229.592C143.544 229.048 143.36 228.592 142.992 228.224C142.608 227.84 142.144 227.648 141.6 227.648C141.056 227.648 140.6 227.84 140.232 228.224C139.848 228.592 139.656 229.048 139.656 229.592C139.656 230.136 139.848 230.6 140.232 230.984C140.6 231.352 141.056 231.536 141.6 231.536C142.144 231.536 142.608 231.352 142.992 230.984C143.36 230.6 143.544 230.136 143.544 229.592ZM133.944 229.592C133.944 229.048 133.76 228.592 133.392 228.224C133.008 227.84 132.544 227.648 132 227.648C131.456 227.648 131 227.84 130.632 228.224C130.248 228.592 130.056 229.048 130.056 229.592C130.056 230.136 130.248 230.6 130.632 230.984C131 231.352 131.456 231.536 132 231.536C132.544 231.536 133.008 231.352 133.392 230.984C133.76 230.6 133.944 230.136 133.944 229.592ZM140.496 231.824C140.496 231.456 140.376 231.152 140.136 230.912C139.88 230.656 139.568 230.528 139.2 230.528C138.832 230.528 138.528 230.656 138.288 230.912C138.032 231.152 137.904 231.456 137.904 231.824C137.904 232.192 138.032 232.504 138.288 232.76C138.528 233 138.832 233.12 139.2 233.12C139.568 233.12 139.88 233 140.136 232.76C140.376 232.504 140.496 232.192 140.496 231.824ZM145.296 231.824C145.296 231.456 145.168 231.152 144.912 230.912C144.672 230.656 144.368 230.528 144 230.528C143.632 230.528 143.328 230.656 143.088 230.912C142.832 231.152 142.704 231.456 142.704 231.824C142.704 232.192 142.832 232.504 143.088 232.76C143.328 233 143.632 233.12 144 233.12C144.368 233.12 144.672 233 144.912 232.76C145.168 232.504 145.296 232.192 145.296 231.824ZM135.696 231.824C135.696 231.456 135.576 231.152 135.336 230.912C135.08 230.656 134.768 230.528 134.4 230.528C134.032 230.528 133.728 230.656 133.488 230.912C133.232 231.152 133.104 231.456 133.104 231.824C133.104 232.192 133.232 232.504 133.488 232.76C133.728 233 134.032 233.12 134.4 233.12C134.768 233.12 135.08 233 135.336 232.76C135.576 232.504 135.696 232.192 135.696 231.824ZM138.744 234.056C138.744 233.512 138.56 233.056 138.192 232.688C137.808 232.304 137.344 232.112 136.8 232.112C136.256 232.112 135.8 232.304 135.432 232.688C135.048 233.056 134.856 233.512 134.856 234.056C134.856 234.6 135.048 235.064 135.432 235.448C135.8 235.816 136.256 236 136.8 236C137.344 236 137.808 235.816 138.192 235.448C138.56 235.064 138.744 234.6 138.744 234.056ZM133.944 234.056C133.944 233.512 133.76 233.056 133.392 232.688C133.008 232.304 132.544 232.112 132 232.112C131.456 232.112 131 232.304 130.632 232.688C130.248 233.056 130.056 233.512 130.056 234.056C130.056 234.6 130.248 235.064 130.632 235.448C131 235.816 131.456 236 132 236C132.544 236 133.008 235.816 133.392 235.448C133.76 235.064 133.944 234.6 133.944 234.056ZM143.544 234.056C143.544 233.512 143.36 233.056 142.992 232.688C142.608 232.304 142.144 232.112 141.6 232.112C141.056 232.112 140.6 232.304 140.232 232.688C139.848 233.056 139.656 233.512 139.656 234.056C139.656 234.6 139.848 235.064 140.232 235.448C140.6 235.816 141.056 236 141.6 236C142.144 236 142.608 235.816 142.992 235.448C143.36 235.064 143.544 234.6 143.544 234.056ZM140.496 236.288C140.496 235.92 140.376 235.616 140.136 235.376C139.88 235.12 139.568 234.992 139.2 234.992C138.832 234.992 138.528 235.12 138.288 235.376C138.032 235.616 137.904 235.92 137.904 236.288C137.904 236.656 138.032 236.968 138.288 237.224C138.528 237.464 138.832 237.584 139.2 237.584C139.568 237.584 139.88 237.464 140.136 237.224C140.376 236.968 140.496 236.656 140.496 236.288ZM135.696 236.288C135.696 235.92 135.576 235.616 135.336 235.376C135.08 235.12 134.768 234.992 134.4 234.992C134.032 234.992 133.728 235.12 133.488 235.376C133.232 235.616 133.104 235.92 133.104 236.288C133.104 236.656 133.232 236.968 133.488 237.224C133.728 237.464 134.032 237.584 134.4 237.584C134.768 237.584 135.08 237.464 135.336 237.224C135.576 236.968 135.696 236.656 135.696 236.288ZM145.296 236.288C145.296 235.92 145.168 235.616 144.912 235.376C144.672 235.12 144.368 234.992 144 234.992C143.632 234.992 143.328 235.12 143.088 235.376C142.832 235.616 142.704 235.92 142.704 236.288C142.704 236.656 142.832 236.968 143.088 237.224C143.328 237.464 143.632 237.584 144 237.584C144.368 237.584 144.672 237.464 144.912 237.224C145.168 236.968 145.296 236.656 145.296 236.288ZM138.744 238.52C138.744 237.976 138.56 237.52 138.192 237.152C137.808 236.768 137.344 236.576 136.8 236.576C136.256 236.576 135.8 236.768 135.432 237.152C135.048 237.52 134.856 237.976 134.856 238.52C134.856 239.064 135.048 239.52 135.432 239.888C135.8 240.272 136.256 240.464 136.8 240.464C137.344 240.464 137.808 240.272 138.192 239.888C138.56 239.52 138.744 239.064 138.744 238.52ZM133.944 238.52C133.944 237.976 133.76 237.52 133.392 237.152C133.008 236.768 132.544 236.576 132 236.576C131.456 236.576 131 236.768 130.632 237.152C130.248 237.52 130.056 237.976 130.056 238.52C130.056 239.064 130.248 239.52 130.632 239.888C131 240.272 131.456 240.464 132 240.464C132.544 240.464 133.008 240.272 133.392 239.888C133.76 239.52 133.944 239.064 133.944 238.52ZM143.544 238.52C143.544 237.976 143.36 237.52 142.992 237.152C142.608 236.768 142.144 236.576 141.6 236.576C141.056 236.576 140.6 236.768 140.232 237.152C139.848 237.52 139.656 237.976 139.656 238.52C139.656 239.064 139.848 239.52 140.232 239.888C140.6 240.272 141.056 240.464 141.6 240.464C142.144 240.464 142.608 240.272 142.992 239.888C143.36 239.52 143.544 239.064 143.544 238.52ZM140.496 240.752C140.496 240.384 140.376 240.072 140.136 239.816C139.88 239.576 139.568 239.456 139.2 239.456C138.832 239.456 138.528 239.576 138.288 239.816C138.032 240.072 137.904 240.384 137.904 240.752C137.904 241.12 138.032 241.424 138.288 241.664C138.528 241.92 138.832 242.048 139.2 242.048C139.568 242.048 139.88 241.92 140.136 241.664C140.376 241.424 140.496 241.12 140.496 240.752ZM135.696 240.752C135.696 240.384 135.576 240.072 135.336 239.816C135.08 239.576 134.768 239.456 134.4 239.456C134.032 239.456 133.728 239.576 133.488 239.816C133.232 240.072 133.104 240.384 133.104 240.752C133.104 241.12 133.232 241.424 133.488 241.664C133.728 241.92 134.032 242.048 134.4 242.048C134.768 242.048 135.08 241.92 135.336 241.664C135.576 241.424 135.696 241.12 135.696 240.752ZM145.296 240.752C145.296 240.384 145.168 240.072 144.912 239.816C144.672 239.576 144.368 239.456 144 239.456C143.632 239.456 143.328 239.576 143.088 239.816C142.832 240.072 142.704 240.384 142.704 240.752C142.704 241.12 142.832 241.424 143.088 241.664C143.328 241.92 143.632 242.048 144 242.048C144.368 242.048 144.672 241.92 144.912 241.664C145.168 241.424 145.296 241.12 145.296 240.752ZM138.744 242.984C138.744 242.44 138.56 241.976 138.192 241.592C137.808 241.224 137.344 241.04 136.8 241.04C136.256 241.04 135.8 241.224 135.432 241.592C135.048 241.976 134.856 242.44 134.856 242.984C134.856 243.528 135.048 243.984 135.432 244.352C135.8 244.736 136.256 244.928 136.8 244.928C137.344 244.928 137.808 244.736 138.192 244.352C138.56 243.984 138.744 243.528 138.744 242.984ZM133.944 242.984C133.944 242.44 133.76 241.976 133.392 241.592C133.008 241.224 132.544 241.04 132 241.04C131.456 241.04 131 241.224 130.632 241.592C130.248 241.976 130.056 242.44 130.056 242.984C130.056 243.528 130.248 243.984 130.632 244.352C131 244.736 131.456 244.928 132 244.928C132.544 244.928 133.008 244.736 133.392 244.352C133.76 243.984 133.944 243.528 133.944 242.984ZM143.544 242.984C143.544 242.44 143.36 241.976 142.992 241.592C142.608 241.224 142.144 241.04 141.6 241.04C141.056 241.04 140.6 241.224 140.232 241.592C139.848 241.976 139.656 242.44 139.656 242.984C139.656 243.528 139.848 243.984 140.232 244.352C140.6 244.736 141.056 244.928 141.6 244.928C142.144 244.928 142.608 244.736 142.992 244.352C143.36 243.984 143.544 243.528 143.544 242.984ZM140.496 245.216C140.496 244.848 140.376 244.536 140.136 244.28C139.88 244.04 139.568 243.92 139.2 243.92C138.832 243.92 138.528 244.04 138.288 244.28C138.032 244.536 137.904 244.848 137.904 245.216C137.904 245.584 138.032 245.888 138.288 246.128C138.528 246.384 138.832 246.512 139.2 246.512C139.568 246.512 139.88 246.384 140.136 246.128C140.376 245.888 140.496 245.584 140.496 245.216ZM135.696 245.216C135.696 244.848 135.576 244.536 135.336 244.28C135.08 244.04 134.768 243.92 134.4 243.92C134.032 243.92 133.728 244.04 133.488 244.28C133.232 244.536 133.104 244.848 133.104 245.216C133.104 245.584 133.232 245.888 133.488 246.128C133.728 246.384 134.032 246.512 134.4 246.512C134.768 246.512 135.08 246.384 135.336 246.128C135.576 245.888 135.696 245.584 135.696 245.216ZM145.296 245.216C145.296 244.848 145.168 244.536 144.912 244.28C144.672 244.04 144.368 243.92 144 243.92C143.632 243.92 143.328 244.04 143.088 244.28C142.832 244.536 142.704 244.848 142.704 245.216C142.704 245.584 142.832 245.888 143.088 246.128C143.328 246.384 143.632 246.512 144 246.512C144.368 246.512 144.672 246.384 144.912 246.128C145.168 245.888 145.296 245.584 145.296 245.216ZM119.385 216.2C119.385 215.832 119.265 215.528 119.025 215.288C118.769 215.032 118.457 214.904 118.089 214.904C117.721 214.904 117.417 215.032 117.177 215.288C116.921 215.528 116.793 215.832 116.793 216.2C116.793 216.568 116.921 216.88 117.177 217.136C117.417 217.376 117.721 217.496 118.089 217.496C118.457 217.496 118.769 217.376 119.025 217.136C119.265 216.88 119.385 216.568 119.385 216.2ZM124.185 216.2C124.185 215.832 124.065 215.528 123.825 215.288C123.569 215.032 123.257 214.904 122.889 214.904C122.521 214.904 122.217 215.032 121.977 215.288C121.721 215.528 121.593 215.832 121.593 216.2C121.593 216.568 121.721 216.88 121.977 217.136C122.217 217.376 122.521 217.496 122.889 217.496C123.257 217.496 123.569 217.376 123.825 217.136C124.065 216.88 124.185 216.568 124.185 216.2ZM128.985 216.2C128.985 215.832 128.865 215.528 128.625 215.288C128.369 215.032 128.057 214.904 127.689 214.904C127.321 214.904 127.017 215.032 126.777 215.288C126.521 215.528 126.393 215.832 126.393 216.2C126.393 216.568 126.521 216.88 126.777 217.136C127.017 217.376 127.321 217.496 127.689 217.496C128.057 217.496 128.369 217.376 128.625 217.136C128.865 216.88 128.985 216.568 128.985 216.2ZM121.353 218.432C121.353 218.192 121.273 217.992 121.113 217.832C120.937 217.656 120.729 217.568 120.489 217.568C120.249 217.568 120.049 217.656 119.889 217.832C119.713 217.992 119.625 218.192 119.625 218.432C119.625 218.672 119.713 218.88 119.889 219.056C120.049 219.216 120.249 219.296 120.489 219.296C120.729 219.296 120.937 219.216 121.113 219.056C121.273 218.88 121.353 218.672 121.353 218.432ZM126.153 218.432C126.153 218.192 126.073 217.992 125.913 217.832C125.737 217.656 125.529 217.568 125.289 217.568C125.049 217.568 124.849 217.656 124.689 217.832C124.513 217.992 124.425 218.192 124.425 218.432C124.425 218.672 124.513 218.88 124.689 219.056C124.849 219.216 125.049 219.296 125.289 219.296C125.529 219.296 125.737 219.216 125.913 219.056C126.073 218.88 126.153 218.672 126.153 218.432ZM130.953 218.432C130.953 218.192 130.865 217.992 130.689 217.832C130.529 217.656 130.329 217.568 130.089 217.568C129.849 217.568 129.649 217.656 129.489 217.832C129.313 217.992 129.225 218.192 129.225 218.432C129.225 218.672 129.313 218.88 129.489 219.056C129.649 219.216 129.849 219.296 130.089 219.296C130.329 219.296 130.529 219.216 130.689 219.056C130.865 218.88 130.953 218.672 130.953 218.432ZM119.385 220.664C119.385 220.296 119.265 219.992 119.025 219.752C118.769 219.496 118.457 219.368 118.089 219.368C117.721 219.368 117.417 219.496 117.177 219.752C116.921 219.992 116.793 220.296 116.793 220.664C116.793 221.032 116.921 221.344 117.177 221.6C117.417 221.84 117.721 221.96 118.089 221.96C118.457 221.96 118.769 221.84 119.025 221.6C119.265 221.344 119.385 221.032 119.385 220.664ZM128.985 220.664C128.985 220.296 128.865 219.992 128.625 219.752C128.369 219.496 128.057 219.368 127.689 219.368C127.321 219.368 127.017 219.496 126.777 219.752C126.521 219.992 126.393 220.296 126.393 220.664C126.393 221.032 126.521 221.344 126.777 221.6C127.017 221.84 127.321 221.96 127.689 221.96C128.057 221.96 128.369 221.84 128.625 221.6C128.865 221.344 128.985 221.032 128.985 220.664ZM124.185 220.664C124.185 220.296 124.065 219.992 123.825 219.752C123.569 219.496 123.257 219.368 122.889 219.368C122.521 219.368 122.217 219.496 121.977 219.752C121.721 219.992 121.593 220.296 121.593 220.664C121.593 221.032 121.721 221.344 121.977 221.6C122.217 221.84 122.521 221.96 122.889 221.96C123.257 221.96 123.569 221.84 123.825 221.6C124.065 221.344 124.185 221.032 124.185 220.664ZM121.353 222.896C121.353 222.656 121.273 222.456 121.113 222.296C120.937 222.12 120.729 222.032 120.489 222.032C120.249 222.032 120.049 222.12 119.889 222.296C119.713 222.456 119.625 222.656 119.625 222.896C119.625 223.136 119.713 223.344 119.889 223.52C120.049 223.68 120.249 223.76 120.489 223.76C120.729 223.76 120.937 223.68 121.113 223.52C121.273 223.344 121.353 223.136 121.353 222.896ZM130.953 222.896C130.953 222.656 130.865 222.456 130.689 222.296C130.529 222.12 130.329 222.032 130.089 222.032C129.849 222.032 129.649 222.12 129.489 222.296C129.313 222.456 129.225 222.656 129.225 222.896C129.225 223.136 129.313 223.344 129.489 223.52C129.649 223.68 129.849 223.76 130.089 223.76C130.329 223.76 130.529 223.68 130.689 223.52C130.865 223.344 130.953 223.136 130.953 222.896ZM126.153 222.896C126.153 222.656 126.073 222.456 125.913 222.296C125.737 222.12 125.529 222.032 125.289 222.032C125.049 222.032 124.849 222.12 124.689 222.296C124.513 222.456 124.425 222.656 124.425 222.896C124.425 223.136 124.513 223.344 124.689 223.52C124.849 223.68 125.049 223.76 125.289 223.76C125.529 223.76 125.737 223.68 125.913 223.52C126.073 223.344 126.153 223.136 126.153 222.896ZM119.385 225.128C119.385 224.76 119.265 224.456 119.025 224.216C118.769 223.96 118.457 223.832 118.089 223.832C117.721 223.832 117.417 223.96 117.177 224.216C116.921 224.456 116.793 224.76 116.793 225.128C116.793 225.496 116.921 225.808 117.177 226.064C117.417 226.304 117.721 226.424 118.089 226.424C118.457 226.424 118.769 226.304 119.025 226.064C119.265 225.808 119.385 225.496 119.385 225.128ZM128.985 225.128C128.985 224.76 128.865 224.456 128.625 224.216C128.369 223.96 128.057 223.832 127.689 223.832C127.321 223.832 127.017 223.96 126.777 224.216C126.521 224.456 126.393 224.76 126.393 225.128C126.393 225.496 126.521 225.808 126.777 226.064C127.017 226.304 127.321 226.424 127.689 226.424C128.057 226.424 128.369 226.304 128.625 226.064C128.865 225.808 128.985 225.496 128.985 225.128ZM124.185 225.128C124.185 224.76 124.065 224.456 123.825 224.216C123.569 223.96 123.257 223.832 122.889 223.832C122.521 223.832 122.217 223.96 121.977 224.216C121.721 224.456 121.593 224.76 121.593 225.128C121.593 225.496 121.721 225.808 121.977 226.064C122.217 226.304 122.521 226.424 122.889 226.424C123.257 226.424 123.569 226.304 123.825 226.064C124.065 225.808 124.185 225.496 124.185 225.128ZM121.353 227.36C121.353 227.12 121.273 226.92 121.113 226.76C120.937 226.584 120.729 226.496 120.489 226.496C120.249 226.496 120.049 226.584 119.889 226.76C119.713 226.92 119.625 227.12 119.625 227.36C119.625 227.6 119.713 227.808 119.889 227.984C120.049 228.144 120.249 228.224 120.489 228.224C120.729 228.224 120.937 228.144 121.113 227.984C121.273 227.808 121.353 227.6 121.353 227.36ZM130.953 227.36C130.953 227.12 130.865 226.92 130.689 226.76C130.529 226.584 130.329 226.496 130.089 226.496C129.849 226.496 129.649 226.584 129.489 226.76C129.313 226.92 129.225 227.12 129.225 227.36C129.225 227.6 129.313 227.808 129.489 227.984C129.649 228.144 129.849 228.224 130.089 228.224C130.329 228.224 130.529 228.144 130.689 227.984C130.865 227.808 130.953 227.6 130.953 227.36ZM126.153 227.36C126.153 227.12 126.073 226.92 125.913 226.76C125.737 226.584 125.529 226.496 125.289 226.496C125.049 226.496 124.849 226.584 124.689 226.76C124.513 226.92 124.425 227.12 124.425 227.36C124.425 227.6 124.513 227.808 124.689 227.984C124.849 228.144 125.049 228.224 125.289 228.224C125.529 228.224 125.737 228.144 125.913 227.984C126.073 227.808 126.153 227.6 126.153 227.36ZM124.185 229.592C124.185 229.224 124.065 228.92 123.825 228.68C123.569 228.424 123.257 228.296 122.889 228.296C122.521 228.296 122.217 228.424 121.977 228.68C121.721 228.92 121.593 229.224 121.593 229.592C121.593 229.96 121.721 230.272 121.977 230.528C122.217 230.768 122.521 230.888 122.889 230.888C123.257 230.888 123.569 230.768 123.825 230.528C124.065 230.272 124.185 229.96 124.185 229.592ZM128.985 229.592C128.985 229.224 128.865 228.92 128.625 228.68C128.369 228.424 128.057 228.296 127.689 228.296C127.321 228.296 127.017 228.424 126.777 228.68C126.521 228.92 126.393 229.224 126.393 229.592C126.393 229.96 126.521 230.272 126.777 230.528C127.017 230.768 127.321 230.888 127.689 230.888C128.057 230.888 128.369 230.768 128.625 230.528C128.865 230.272 128.985 229.96 128.985 229.592ZM119.385 229.592C119.385 229.224 119.265 228.92 119.025 228.68C118.769 228.424 118.457 228.296 118.089 228.296C117.721 228.296 117.417 228.424 117.177 228.68C116.921 228.92 116.793 229.224 116.793 229.592C116.793 229.96 116.921 230.272 117.177 230.528C117.417 230.768 117.721 230.888 118.089 230.888C118.457 230.888 118.769 230.768 119.025 230.528C119.265 230.272 119.385 229.96 119.385 229.592ZM126.153 231.824C126.153 231.584 126.073 231.384 125.913 231.224C125.737 231.048 125.529 230.96 125.289 230.96C125.049 230.96 124.849 231.048 124.689 231.224C124.513 231.384 124.425 231.584 124.425 231.824C124.425 232.064 124.513 232.272 124.689 232.448C124.849 232.608 125.049 232.688 125.289 232.688C125.529 232.688 125.737 232.608 125.913 232.448C126.073 232.272 126.153 232.064 126.153 231.824ZM130.953 231.824C130.953 231.584 130.865 231.384 130.689 231.224C130.529 231.048 130.329 230.96 130.089 230.96C129.849 230.96 129.649 231.048 129.489 231.224C129.313 231.384 129.225 231.584 129.225 231.824C129.225 232.064 129.313 232.272 129.489 232.448C129.649 232.608 129.849 232.688 130.089 232.688C130.329 232.688 130.529 232.608 130.689 232.448C130.865 232.272 130.953 232.064 130.953 231.824ZM121.353 231.824C121.353 231.584 121.273 231.384 121.113 231.224C120.937 231.048 120.729 230.96 120.489 230.96C120.249 230.96 120.049 231.048 119.889 231.224C119.713 231.384 119.625 231.584 119.625 231.824C119.625 232.064 119.713 232.272 119.889 232.448C120.049 232.608 120.249 232.688 120.489 232.688C120.729 232.688 120.937 232.608 121.113 232.448C121.273 232.272 121.353 232.064 121.353 231.824ZM124.185 234.056C124.185 233.688 124.065 233.384 123.825 233.144C123.569 232.888 123.257 232.76 122.889 232.76C122.521 232.76 122.217 232.888 121.977 233.144C121.721 233.384 121.593 233.688 121.593 234.056C121.593 234.424 121.721 234.736 121.977 234.992C122.217 235.232 122.521 235.352 122.889 235.352C123.257 235.352 123.569 235.232 123.825 234.992C124.065 234.736 124.185 234.424 124.185 234.056ZM119.385 234.056C119.385 233.688 119.265 233.384 119.025 233.144C118.769 232.888 118.457 232.76 118.089 232.76C117.721 232.76 117.417 232.888 117.177 233.144C116.921 233.384 116.793 233.688 116.793 234.056C116.793 234.424 116.921 234.736 117.177 234.992C117.417 235.232 117.721 235.352 118.089 235.352C118.457 235.352 118.769 235.232 119.025 234.992C119.265 234.736 119.385 234.424 119.385 234.056ZM128.985 234.056C128.985 233.688 128.865 233.384 128.625 233.144C128.369 232.888 128.057 232.76 127.689 232.76C127.321 232.76 127.017 232.888 126.777 233.144C126.521 233.384 126.393 233.688 126.393 234.056C126.393 234.424 126.521 234.736 126.777 234.992C127.017 235.232 127.321 235.352 127.689 235.352C128.057 235.352 128.369 235.232 128.625 234.992C128.865 234.736 128.985 234.424 128.985 234.056ZM126.153 236.288C126.153 236.048 126.073 235.848 125.913 235.688C125.737 235.512 125.529 235.424 125.289 235.424C125.049 235.424 124.849 235.512 124.689 235.688C124.513 235.848 124.425 236.048 124.425 236.288C124.425 236.528 124.513 236.736 124.689 236.912C124.849 237.072 125.049 237.152 125.289 237.152C125.529 237.152 125.737 237.072 125.913 236.912C126.073 236.736 126.153 236.528 126.153 236.288ZM121.353 236.288C121.353 236.048 121.273 235.848 121.113 235.688C120.937 235.512 120.729 235.424 120.489 235.424C120.249 235.424 120.049 235.512 119.889 235.688C119.713 235.848 119.625 236.048 119.625 236.288C119.625 236.528 119.713 236.736 119.889 236.912C120.049 237.072 120.249 237.152 120.489 237.152C120.729 237.152 120.937 237.072 121.113 236.912C121.273 236.736 121.353 236.528 121.353 236.288ZM130.953 236.288C130.953 236.048 130.865 235.848 130.689 235.688C130.529 235.512 130.329 235.424 130.089 235.424C129.849 235.424 129.649 235.512 129.489 235.688C129.313 235.848 129.225 236.048 129.225 236.288C129.225 236.528 129.313 236.736 129.489 236.912C129.649 237.072 129.849 237.152 130.089 237.152C130.329 237.152 130.529 237.072 130.689 236.912C130.865 236.736 130.953 236.528 130.953 236.288ZM124.185 238.52C124.185 238.152 124.065 237.848 123.825 237.608C123.569 237.352 123.257 237.224 122.889 237.224C122.521 237.224 122.217 237.352 121.977 237.608C121.721 237.848 121.593 238.152 121.593 238.52C121.593 238.888 121.721 239.192 121.977 239.432C122.217 239.688 122.521 239.816 122.889 239.816C123.257 239.816 123.569 239.688 123.825 239.432C124.065 239.192 124.185 238.888 124.185 238.52ZM119.385 238.52C119.385 238.152 119.265 237.848 119.025 237.608C118.769 237.352 118.457 237.224 118.089 237.224C117.721 237.224 117.417 237.352 117.177 237.608C116.921 237.848 116.793 238.152 116.793 238.52C116.793 238.888 116.921 239.192 117.177 239.432C117.417 239.688 117.721 239.816 118.089 239.816C118.457 239.816 118.769 239.688 119.025 239.432C119.265 239.192 119.385 238.888 119.385 238.52ZM128.985 238.52C128.985 238.152 128.865 237.848 128.625 237.608C128.369 237.352 128.057 237.224 127.689 237.224C127.321 237.224 127.017 237.352 126.777 237.608C126.521 237.848 126.393 238.152 126.393 238.52C126.393 238.888 126.521 239.192 126.777 239.432C127.017 239.688 127.321 239.816 127.689 239.816C128.057 239.816 128.369 239.688 128.625 239.432C128.865 239.192 128.985 238.888 128.985 238.52ZM126.153 240.752C126.153 240.512 126.073 240.304 125.913 240.128C125.737 239.968 125.529 239.888 125.289 239.888C125.049 239.888 124.849 239.968 124.689 240.128C124.513 240.304 124.425 240.512 124.425 240.752C124.425 240.992 124.513 241.192 124.689 241.352C124.849 241.528 125.049 241.616 125.289 241.616C125.529 241.616 125.737 241.528 125.913 241.352C126.073 241.192 126.153 240.992 126.153 240.752ZM121.353 240.752C121.353 240.512 121.273 240.304 121.113 240.128C120.937 239.968 120.729 239.888 120.489 239.888C120.249 239.888 120.049 239.968 119.889 240.128C119.713 240.304 119.625 240.512 119.625 240.752C119.625 240.992 119.713 241.192 119.889 241.352C120.049 241.528 120.249 241.616 120.489 241.616C120.729 241.616 120.937 241.528 121.113 241.352C121.273 241.192 121.353 240.992 121.353 240.752ZM130.953 240.752C130.953 240.512 130.865 240.304 130.689 240.128C130.529 239.968 130.329 239.888 130.089 239.888C129.849 239.888 129.649 239.968 129.489 240.128C129.313 240.304 129.225 240.512 129.225 240.752C129.225 240.992 129.313 241.192 129.489 241.352C129.649 241.528 129.849 241.616 130.089 241.616C130.329 241.616 130.529 241.528 130.689 241.352C130.865 241.192 130.953 240.992 130.953 240.752ZM124.185 242.984C124.185 242.616 124.065 242.304 123.825 242.048C123.569 241.808 123.257 241.688 122.889 241.688C122.521 241.688 122.217 241.808 121.977 242.048C121.721 242.304 121.593 242.616 121.593 242.984C121.593 243.352 121.721 243.656 121.977 243.896C122.217 244.152 122.521 244.28 122.889 244.28C123.257 244.28 123.569 244.152 123.825 243.896C124.065 243.656 124.185 243.352 124.185 242.984ZM119.385 242.984C119.385 242.616 119.265 242.304 119.025 242.048C118.769 241.808 118.457 241.688 118.089 241.688C117.721 241.688 117.417 241.808 117.177 242.048C116.921 242.304 116.793 242.616 116.793 242.984C116.793 243.352 116.921 243.656 117.177 243.896C117.417 244.152 117.721 244.28 118.089 244.28C118.457 244.28 118.769 244.152 119.025 243.896C119.265 243.656 119.385 243.352 119.385 242.984ZM128.985 242.984C128.985 242.616 128.865 242.304 128.625 242.048C128.369 241.808 128.057 241.688 127.689 241.688C127.321 241.688 127.017 241.808 126.777 242.048C126.521 242.304 126.393 242.616 126.393 242.984C126.393 243.352 126.521 243.656 126.777 243.896C127.017 244.152 127.321 244.28 127.689 244.28C128.057 244.28 128.369 244.152 128.625 243.896C128.865 243.656 128.985 243.352 128.985 242.984ZM126.153 245.216C126.153 244.976 126.073 244.768 125.913 244.592C125.737 244.432 125.529 244.352 125.289 244.352C125.049 244.352 124.849 244.432 124.689 244.592C124.513 244.768 124.425 244.976 124.425 245.216C124.425 245.456 124.513 245.656 124.689 245.816C124.849 245.992 125.049 246.08 125.289 246.08C125.529 246.08 125.737 245.992 125.913 245.816C126.073 245.656 126.153 245.456 126.153 245.216ZM121.353 245.216C121.353 244.976 121.273 244.768 121.113 244.592C120.937 244.432 120.729 244.352 120.489 244.352C120.249 244.352 120.049 244.432 119.889 244.592C119.713 244.768 119.625 244.976 119.625 245.216C119.625 245.456 119.713 245.656 119.889 245.816C120.049 245.992 120.249 246.08 120.489 246.08C120.729 246.08 120.937 245.992 121.113 245.816C121.273 245.656 121.353 245.456 121.353 245.216ZM130.953 245.216C130.953 244.976 130.865 244.768 130.689 244.592C130.529 244.432 130.329 244.352 130.089 244.352C129.849 244.352 129.649 244.432 129.489 244.592C129.313 244.768 129.225 244.976 129.225 245.216C129.225 245.456 129.313 245.656 129.489 245.816C129.649 245.992 129.849 246.08 130.089 246.08C130.329 246.08 130.529 245.992 130.689 245.816C130.865 245.656 130.953 245.456 130.953 245.216ZM105.043 216.2C105.043 215.96 104.963 215.76 104.803 215.6C104.627 215.424 104.419 215.336 104.179 215.336C103.939 215.336 103.739 215.424 103.579 215.6C103.403 215.76 103.315 215.96 103.315 216.2C103.315 216.44 103.403 216.648 103.579 216.824C103.739 216.984 103.939 217.064 104.179 217.064C104.419 217.064 104.627 216.984 104.803 216.824C104.963 216.648 105.043 216.44 105.043 216.2ZM109.843 216.2C109.843 215.96 109.763 215.76 109.603 215.6C109.427 215.424 109.219 215.336 108.979 215.336C108.739 215.336 108.539 215.424 108.379 215.6C108.203 215.76 108.115 215.96 108.115 216.2C108.115 216.44 108.203 216.648 108.379 216.824C108.539 216.984 108.739 217.064 108.979 217.064C109.219 217.064 109.427 216.984 109.603 216.824C109.763 216.648 109.843 216.44 109.843 216.2ZM114.643 216.2C114.643 215.96 114.563 215.76 114.403 215.6C114.227 215.424 114.019 215.336 113.779 215.336C113.539 215.336 113.339 215.424 113.179 215.6C113.003 215.76 112.915 215.96 112.915 216.2C112.915 216.44 113.003 216.648 113.179 216.824C113.339 216.984 113.539 217.064 113.779 217.064C114.019 217.064 114.227 216.984 114.403 216.824C114.563 216.648 114.643 216.44 114.643 216.2ZM107.155 218.432C107.155 218.272 107.099 218.136 106.987 218.024C106.875 217.912 106.739 217.856 106.579 217.856C106.419 217.856 106.283 217.912 106.171 218.024C106.059 218.136 106.003 218.272 106.003 218.432C106.003 218.592 106.059 218.728 106.171 218.84C106.283 218.952 106.419 219.008 106.579 219.008C106.739 219.008 106.875 218.952 106.987 218.84C107.099 218.728 107.155 218.592 107.155 218.432ZM111.955 218.432C111.955 218.272 111.899 218.136 111.787 218.024C111.675 217.912 111.539 217.856 111.379 217.856C111.219 217.856 111.083 217.912 110.971 218.024C110.859 218.136 110.803 218.272 110.803 218.432C110.803 218.592 110.859 218.728 110.971 218.84C111.083 218.952 111.219 219.008 111.379 219.008C111.539 219.008 111.675 218.952 111.787 218.84C111.899 218.728 111.955 218.592 111.955 218.432ZM116.755 218.432C116.755 218.272 116.699 218.136 116.587 218.024C116.475 217.912 116.339 217.856 116.179 217.856C116.019 217.856 115.883 217.912 115.771 218.024C115.659 218.136 115.603 218.272 115.603 218.432C115.603 218.592 115.659 218.728 115.771 218.84C115.883 218.952 116.019 219.008 116.179 219.008C116.339 219.008 116.475 218.952 116.587 218.84C116.699 218.728 116.755 218.592 116.755 218.432ZM105.043 220.664C105.043 220.424 104.963 220.224 104.803 220.064C104.627 219.888 104.419 219.8 104.179 219.8C103.939 219.8 103.739 219.888 103.579 220.064C103.403 220.224 103.315 220.424 103.315 220.664C103.315 220.904 103.403 221.112 103.579 221.288C103.739 221.448 103.939 221.528 104.179 221.528C104.419 221.528 104.627 221.448 104.803 221.288C104.963 221.112 105.043 220.904 105.043 220.664ZM114.643 220.664C114.643 220.424 114.563 220.224 114.403 220.064C114.227 219.888 114.019 219.8 113.779 219.8C113.539 219.8 113.339 219.888 113.179 220.064C113.003 220.224 112.915 220.424 112.915 220.664C112.915 220.904 113.003 221.112 113.179 221.288C113.339 221.448 113.539 221.528 113.779 221.528C114.019 221.528 114.227 221.448 114.403 221.288C114.563 221.112 114.643 220.904 114.643 220.664ZM109.843 220.664C109.843 220.424 109.763 220.224 109.603 220.064C109.427 219.888 109.219 219.8 108.979 219.8C108.739 219.8 108.539 219.888 108.379 220.064C108.203 220.224 108.115 220.424 108.115 220.664C108.115 220.904 108.203 221.112 108.379 221.288C108.539 221.448 108.739 221.528 108.979 221.528C109.219 221.528 109.427 221.448 109.603 221.288C109.763 221.112 109.843 220.904 109.843 220.664ZM107.155 222.896C107.155 222.736 107.099 222.6 106.987 222.488C106.875 222.376 106.739 222.32 106.579 222.32C106.419 222.32 106.283 222.376 106.171 222.488C106.059 222.6 106.003 222.736 106.003 222.896C106.003 223.056 106.059 223.192 106.171 223.304C106.283 223.416 106.419 223.472 106.579 223.472C106.739 223.472 106.875 223.416 106.987 223.304C107.099 223.192 107.155 223.056 107.155 222.896ZM116.755 222.896C116.755 222.736 116.699 222.6 116.587 222.488C116.475 222.376 116.339 222.32 116.179 222.32C116.019 222.32 115.883 222.376 115.771 222.488C115.659 222.6 115.603 222.736 115.603 222.896C115.603 223.056 115.659 223.192 115.771 223.304C115.883 223.416 116.019 223.472 116.179 223.472C116.339 223.472 116.475 223.416 116.587 223.304C116.699 223.192 116.755 223.056 116.755 222.896ZM111.955 222.896C111.955 222.736 111.899 222.6 111.787 222.488C111.675 222.376 111.539 222.32 111.379 222.32C111.219 222.32 111.083 222.376 110.971 222.488C110.859 222.6 110.803 222.736 110.803 222.896C110.803 223.056 110.859 223.192 110.971 223.304C111.083 223.416 111.219 223.472 111.379 223.472C111.539 223.472 111.675 223.416 111.787 223.304C111.899 223.192 111.955 223.056 111.955 222.896ZM105.043 225.128C105.043 224.888 104.963 224.688 104.803 224.528C104.627 224.352 104.419 224.264 104.179 224.264C103.939 224.264 103.739 224.352 103.579 224.528C103.403 224.688 103.315 224.888 103.315 225.128C103.315 225.368 103.403 225.576 103.579 225.752C103.739 225.912 103.939 225.992 104.179 225.992C104.419 225.992 104.627 225.912 104.803 225.752C104.963 225.576 105.043 225.368 105.043 225.128ZM114.643 225.128C114.643 224.888 114.563 224.688 114.403 224.528C114.227 224.352 114.019 224.264 113.779 224.264C113.539 224.264 113.339 224.352 113.179 224.528C113.003 224.688 112.915 224.888 112.915 225.128C112.915 225.368 113.003 225.576 113.179 225.752C113.339 225.912 113.539 225.992 113.779 225.992C114.019 225.992 114.227 225.912 114.403 225.752C114.563 225.576 114.643 225.368 114.643 225.128ZM109.843 225.128C109.843 224.888 109.763 224.688 109.603 224.528C109.427 224.352 109.219 224.264 108.979 224.264C108.739 224.264 108.539 224.352 108.379 224.528C108.203 224.688 108.115 224.888 108.115 225.128C108.115 225.368 108.203 225.576 108.379 225.752C108.539 225.912 108.739 225.992 108.979 225.992C109.219 225.992 109.427 225.912 109.603 225.752C109.763 225.576 109.843 225.368 109.843 225.128ZM107.155 227.264C107.155 227.104 107.099 226.968 106.987 226.856C106.875 226.744 106.739 226.688 106.579 226.688C106.419 226.688 106.283 226.744 106.171 226.856C106.059 226.968 106.003 227.104 106.003 227.264C106.003 227.424 106.059 227.56 106.171 227.672C106.283 227.784 106.419 227.84 106.579 227.84C106.739 227.84 106.875 227.784 106.987 227.672C107.099 227.56 107.155 227.424 107.155 227.264ZM116.755 227.264C116.755 227.104 116.699 226.968 116.587 226.856C116.475 226.744 116.339 226.688 116.179 226.688C116.019 226.688 115.883 226.744 115.771 226.856C115.659 226.968 115.603 227.104 115.603 227.264C115.603 227.424 115.659 227.56 115.771 227.672C115.883 227.784 116.019 227.84 116.179 227.84C116.339 227.84 116.475 227.784 116.587 227.672C116.699 227.56 116.755 227.424 116.755 227.264ZM111.955 227.264C111.955 227.104 111.899 226.968 111.787 226.856C111.675 226.744 111.539 226.688 111.379 226.688C111.219 226.688 111.083 226.744 110.971 226.856C110.859 226.968 110.803 227.104 110.803 227.264C110.803 227.424 110.859 227.56 110.971 227.672C111.083 227.784 111.219 227.84 111.379 227.84C111.539 227.84 111.675 227.784 111.787 227.672C111.899 227.56 111.955 227.424 111.955 227.264ZM109.843 229.496C109.843 229.256 109.763 229.056 109.603 228.896C109.427 228.72 109.219 228.632 108.979 228.632C108.739 228.632 108.539 228.72 108.379 228.896C108.203 229.056 108.115 229.256 108.115 229.496C108.115 229.736 108.203 229.944 108.379 230.12C108.539 230.28 108.739 230.36 108.979 230.36C109.219 230.36 109.427 230.28 109.603 230.12C109.763 229.944 109.843 229.736 109.843 229.496ZM114.643 229.496C114.643 229.256 114.563 229.056 114.403 228.896C114.227 228.72 114.019 228.632 113.779 228.632C113.539 228.632 113.339 228.72 113.179 228.896C113.003 229.056 112.915 229.256 112.915 229.496C112.915 229.736 113.003 229.944 113.179 230.12C113.339 230.28 113.539 230.36 113.779 230.36C114.019 230.36 114.227 230.28 114.403 230.12C114.563 229.944 114.643 229.736 114.643 229.496ZM105.043 229.496C105.043 229.256 104.963 229.056 104.803 228.896C104.627 228.72 104.419 228.632 104.179 228.632C103.939 228.632 103.739 228.72 103.579 228.896C103.403 229.056 103.315 229.256 103.315 229.496C103.315 229.736 103.403 229.944 103.579 230.12C103.739 230.28 103.939 230.36 104.179 230.36C104.419 230.36 104.627 230.28 104.803 230.12C104.963 229.944 105.043 229.736 105.043 229.496ZM111.955 231.728C111.955 231.568 111.899 231.432 111.787 231.32C111.675 231.208 111.539 231.152 111.379 231.152C111.219 231.152 111.083 231.208 110.971 231.32C110.859 231.432 110.803 231.568 110.803 231.728C110.803 231.888 110.859 232.024 110.971 232.136C111.083 232.248 111.219 232.304 111.379 232.304C111.539 232.304 111.675 232.248 111.787 232.136C111.899 232.024 111.955 231.888 111.955 231.728ZM116.755 231.728C116.755 231.568 116.699 231.432 116.587 231.32C116.475 231.208 116.339 231.152 116.179 231.152C116.019 231.152 115.883 231.208 115.771 231.32C115.659 231.432 115.603 231.568 115.603 231.728C115.603 231.888 115.659 232.024 115.771 232.136C115.883 232.248 116.019 232.304 116.179 232.304C116.339 232.304 116.475 232.248 116.587 232.136C116.699 232.024 116.755 231.888 116.755 231.728ZM107.155 231.728C107.155 231.568 107.099 231.432 106.987 231.32C106.875 231.208 106.739 231.152 106.579 231.152C106.419 231.152 106.283 231.208 106.171 231.32C106.059 231.432 106.003 231.568 106.003 231.728C106.003 231.888 106.059 232.024 106.171 232.136C106.283 232.248 106.419 232.304 106.579 232.304C106.739 232.304 106.875 232.248 106.987 232.136C107.099 232.024 107.155 231.888 107.155 231.728ZM109.843 233.96C109.843 233.72 109.763 233.52 109.603 233.36C109.427 233.184 109.219 233.096 108.979 233.096C108.739 233.096 108.539 233.184 108.379 233.36C108.203 233.52 108.115 233.72 108.115 233.96C108.115 234.2 108.203 234.408 108.379 234.584C108.539 234.744 108.739 234.824 108.979 234.824C109.219 234.824 109.427 234.744 109.603 234.584C109.763 234.408 109.843 234.2 109.843 233.96ZM105.043 233.96C105.043 233.72 104.963 233.52 104.803 233.36C104.627 233.184 104.419 233.096 104.179 233.096C103.939 233.096 103.739 233.184 103.579 233.36C103.403 233.52 103.315 233.72 103.315 233.96C103.315 234.2 103.403 234.408 103.579 234.584C103.739 234.744 103.939 234.824 104.179 234.824C104.419 234.824 104.627 234.744 104.803 234.584C104.963 234.408 105.043 234.2 105.043 233.96ZM114.643 233.96C114.643 233.72 114.563 233.52 114.403 233.36C114.227 233.184 114.019 233.096 113.779 233.096C113.539 233.096 113.339 233.184 113.179 233.36C113.003 233.52 112.915 233.72 112.915 233.96C112.915 234.2 113.003 234.408 113.179 234.584C113.339 234.744 113.539 234.824 113.779 234.824C114.019 234.824 114.227 234.744 114.403 234.584C114.563 234.408 114.643 234.2 114.643 233.96ZM111.955 236.192C111.955 236.032 111.899 235.896 111.787 235.784C111.675 235.672 111.539 235.616 111.379 235.616C111.219 235.616 111.083 235.672 110.971 235.784C110.859 235.896 110.803 236.032 110.803 236.192C110.803 236.352 110.859 236.488 110.971 236.6C111.083 236.712 111.219 236.768 111.379 236.768C111.539 236.768 111.675 236.712 111.787 236.6C111.899 236.488 111.955 236.352 111.955 236.192ZM107.155 236.192C107.155 236.032 107.099 235.896 106.987 235.784C106.875 235.672 106.739 235.616 106.579 235.616C106.419 235.616 106.283 235.672 106.171 235.784C106.059 235.896 106.003 236.032 106.003 236.192C106.003 236.352 106.059 236.488 106.171 236.6C106.283 236.712 106.419 236.768 106.579 236.768C106.739 236.768 106.875 236.712 106.987 236.6C107.099 236.488 107.155 236.352 107.155 236.192ZM116.755 236.192C116.755 236.032 116.699 235.896 116.587 235.784C116.475 235.672 116.339 235.616 116.179 235.616C116.019 235.616 115.883 235.672 115.771 235.784C115.659 235.896 115.603 236.032 115.603 236.192C115.603 236.352 115.659 236.488 115.771 236.6C115.883 236.712 116.019 236.768 116.179 236.768C116.339 236.768 116.475 236.712 116.587 236.6C116.699 236.488 116.755 236.352 116.755 236.192ZM109.843 238.424C109.843 238.184 109.763 237.984 109.603 237.824C109.427 237.648 109.219 237.56 108.979 237.56C108.739 237.56 108.539 237.648 108.379 237.824C108.203 237.984 108.115 238.184 108.115 238.424C108.115 238.664 108.203 238.864 108.379 239.024C108.539 239.2 108.739 239.288 108.979 239.288C109.219 239.288 109.427 239.2 109.603 239.024C109.763 238.864 109.843 238.664 109.843 238.424ZM105.043 238.424C105.043 238.184 104.963 237.984 104.803 237.824C104.627 237.648 104.419 237.56 104.179 237.56C103.939 237.56 103.739 237.648 103.579 237.824C103.403 237.984 103.315 238.184 103.315 238.424C103.315 238.664 103.403 238.864 103.579 239.024C103.739 239.2 103.939 239.288 104.179 239.288C104.419 239.288 104.627 239.2 104.803 239.024C104.963 238.864 105.043 238.664 105.043 238.424ZM114.643 238.424C114.643 238.184 114.563 237.984 114.403 237.824C114.227 237.648 114.019 237.56 113.779 237.56C113.539 237.56 113.339 237.648 113.179 237.824C113.003 237.984 112.915 238.184 112.915 238.424C112.915 238.664 113.003 238.864 113.179 239.024C113.339 239.2 113.539 239.288 113.779 239.288C114.019 239.288 114.227 239.2 114.403 239.024C114.563 238.864 114.643 238.664 114.643 238.424ZM111.955 240.656C111.955 240.496 111.899 240.36 111.787 240.248C111.675 240.136 111.539 240.08 111.379 240.08C111.219 240.08 111.083 240.136 110.971 240.248C110.859 240.36 110.803 240.496 110.803 240.656C110.803 240.816 110.859 240.952 110.971 241.064C111.083 241.176 111.219 241.232 111.379 241.232C111.539 241.232 111.675 241.176 111.787 241.064C111.899 240.952 111.955 240.816 111.955 240.656ZM107.155 240.656C107.155 240.496 107.099 240.36 106.987 240.248C106.875 240.136 106.739 240.08 106.579 240.08C106.419 240.08 106.283 240.136 106.171 240.248C106.059 240.36 106.003 240.496 106.003 240.656C106.003 240.816 106.059 240.952 106.171 241.064C106.283 241.176 106.419 241.232 106.579 241.232C106.739 241.232 106.875 241.176 106.987 241.064C107.099 240.952 107.155 240.816 107.155 240.656ZM116.755 240.656C116.755 240.496 116.699 240.36 116.587 240.248C116.475 240.136 116.339 240.08 116.179 240.08C116.019 240.08 115.883 240.136 115.771 240.248C115.659 240.36 115.603 240.496 115.603 240.656C115.603 240.816 115.659 240.952 115.771 241.064C115.883 241.176 116.019 241.232 116.179 241.232C116.339 241.232 116.475 241.176 116.587 241.064C116.699 240.952 116.755 240.816 116.755 240.656ZM109.843 242.888C109.843 242.648 109.763 242.44 109.603 242.264C109.427 242.104 109.219 242.024 108.979 242.024C108.739 242.024 108.539 242.104 108.379 242.264C108.203 242.44 108.115 242.648 108.115 242.888C108.115 243.128 108.203 243.328 108.379 243.488C108.539 243.664 108.739 243.752 108.979 243.752C109.219 243.752 109.427 243.664 109.603 243.488C109.763 243.328 109.843 243.128 109.843 242.888ZM105.043 242.888C105.043 242.648 104.963 242.44 104.803 242.264C104.627 242.104 104.419 242.024 104.179 242.024C103.939 242.024 103.739 242.104 103.579 242.264C103.403 242.44 103.315 242.648 103.315 242.888C103.315 243.128 103.403 243.328 103.579 243.488C103.739 243.664 103.939 243.752 104.179 243.752C104.419 243.752 104.627 243.664 104.803 243.488C104.963 243.328 105.043 243.128 105.043 242.888ZM114.643 242.888C114.643 242.648 114.563 242.44 114.403 242.264C114.227 242.104 114.019 242.024 113.779 242.024C113.539 242.024 113.339 242.104 113.179 242.264C113.003 242.44 112.915 242.648 112.915 242.888C112.915 243.128 113.003 243.328 113.179 243.488C113.339 243.664 113.539 243.752 113.779 243.752C114.019 243.752 114.227 243.664 114.403 243.488C114.563 243.328 114.643 243.128 114.643 242.888ZM111.955 245.12C111.955 244.96 111.899 244.824 111.787 244.712C111.675 244.6 111.539 244.544 111.379 244.544C111.219 244.544 111.083 244.6 110.971 244.712C110.859 244.824 110.803 244.96 110.803 245.12C110.803 245.28 110.859 245.416 110.971 245.528C111.083 245.64 111.219 245.696 111.379 245.696C111.539 245.696 111.675 245.64 111.787 245.528C111.899 245.416 111.955 245.28 111.955 245.12ZM107.155 245.12C107.155 244.96 107.099 244.824 106.987 244.712C106.875 244.6 106.739 244.544 106.579 244.544C106.419 244.544 106.283 244.6 106.171 244.712C106.059 244.824 106.003 244.96 106.003 245.12C106.003 245.28 106.059 245.416 106.171 245.528C106.283 245.64 106.419 245.696 106.579 245.696C106.739 245.696 106.875 245.64 106.987 245.528C107.099 245.416 107.155 245.28 107.155 245.12ZM116.755 245.12C116.755 244.96 116.699 244.824 116.587 244.712C116.475 244.6 116.339 244.544 116.179 244.544C116.019 244.544 115.883 244.6 115.771 244.712C115.659 244.824 115.603 244.96 115.603 245.12C115.603 245.28 115.659 245.416 115.771 245.528C115.883 245.64 116.019 245.696 116.179 245.696C116.339 245.696 116.475 245.64 116.587 245.528C116.699 245.416 116.755 245.28 116.755 245.12ZM133.944 247.2C133.944 246.656 133.76 246.2 133.392 245.832C133.008 245.448 132.544 245.256 132 245.256C131.456 245.256 131 245.448 130.632 245.832C130.248 246.2 130.056 246.656 130.056 247.2C130.056 247.744 130.248 248.208 130.632 248.592C131 248.96 131.456 249.144 132 249.144C132.544 249.144 133.008 248.96 133.392 248.592C133.76 248.208 133.944 247.744 133.944 247.2ZM138.744 247.2C138.744 246.656 138.56 246.2 138.192 245.832C137.808 245.448 137.344 245.256 136.8 245.256C136.256 245.256 135.8 245.448 135.432 245.832C135.048 246.2 134.856 246.656 134.856 247.2C134.856 247.744 135.048 248.208 135.432 248.592C135.8 248.96 136.256 249.144 136.8 249.144C137.344 249.144 137.808 248.96 138.192 248.592C138.56 248.208 138.744 247.744 138.744 247.2ZM143.544 247.2C143.544 246.656 143.36 246.2 142.992 245.832C142.608 245.448 142.144 245.256 141.6 245.256C141.056 245.256 140.6 245.448 140.232 245.832C139.848 246.2 139.656 246.656 139.656 247.2C139.656 247.744 139.848 248.208 140.232 248.592C140.6 248.96 141.056 249.144 141.6 249.144C142.144 249.144 142.608 248.96 142.992 248.592C143.36 248.208 143.544 247.744 143.544 247.2ZM135.696 249.432C135.696 249.064 135.576 248.76 135.336 248.52C135.08 248.264 134.768 248.136 134.4 248.136C134.032 248.136 133.728 248.264 133.488 248.52C133.232 248.76 133.104 249.064 133.104 249.432C133.104 249.8 133.232 250.112 133.488 250.368C133.728 250.608 134.032 250.728 134.4 250.728C134.768 250.728 135.08 250.608 135.336 250.368C135.576 250.112 135.696 249.8 135.696 249.432ZM140.496 249.432C140.496 249.064 140.376 248.76 140.136 248.52C139.88 248.264 139.568 248.136 139.2 248.136C138.832 248.136 138.528 248.264 138.288 248.52C138.032 248.76 137.904 249.064 137.904 249.432C137.904 249.8 138.032 250.112 138.288 250.368C138.528 250.608 138.832 250.728 139.2 250.728C139.568 250.728 139.88 250.608 140.136 250.368C140.376 250.112 140.496 249.8 140.496 249.432ZM145.296 249.432C145.296 249.064 145.168 248.76 144.912 248.52C144.672 248.264 144.368 248.136 144 248.136C143.632 248.136 143.328 248.264 143.088 248.52C142.832 248.76 142.704 249.064 142.704 249.432C142.704 249.8 142.832 250.112 143.088 250.368C143.328 250.608 143.632 250.728 144 250.728C144.368 250.728 144.672 250.608 144.912 250.368C145.168 250.112 145.296 249.8 145.296 249.432ZM133.944 251.664C133.944 251.12 133.76 250.664 133.392 250.296C133.008 249.912 132.544 249.72 132 249.72C131.456 249.72 131 249.912 130.632 250.296C130.248 250.664 130.056 251.12 130.056 251.664C130.056 252.208 130.248 252.672 130.632 253.056C131 253.424 131.456 253.608 132 253.608C132.544 253.608 133.008 253.424 133.392 253.056C133.76 252.672 133.944 252.208 133.944 251.664ZM143.544 251.664C143.544 251.12 143.36 250.664 142.992 250.296C142.608 249.912 142.144 249.72 141.6 249.72C141.056 249.72 140.6 249.912 140.232 250.296C139.848 250.664 139.656 251.12 139.656 251.664C139.656 252.208 139.848 252.672 140.232 253.056C140.6 253.424 141.056 253.608 141.6 253.608C142.144 253.608 142.608 253.424 142.992 253.056C143.36 252.672 143.544 252.208 143.544 251.664ZM138.744 251.664C138.744 251.12 138.56 250.664 138.192 250.296C137.808 249.912 137.344 249.72 136.8 249.72C136.256 249.72 135.8 249.912 135.432 250.296C135.048 250.664 134.856 251.12 134.856 251.664C134.856 252.208 135.048 252.672 135.432 253.056C135.8 253.424 136.256 253.608 136.8 253.608C137.344 253.608 137.808 253.424 138.192 253.056C138.56 252.672 138.744 252.208 138.744 251.664ZM135.696 253.896C135.696 253.528 135.576 253.224 135.336 252.984C135.08 252.728 134.768 252.6 134.4 252.6C134.032 252.6 133.728 252.728 133.488 252.984C133.232 253.224 133.104 253.528 133.104 253.896C133.104 254.264 133.232 254.576 133.488 254.832C133.728 255.072 134.032 255.192 134.4 255.192C134.768 255.192 135.08 255.072 135.336 254.832C135.576 254.576 135.696 254.264 135.696 253.896ZM145.296 253.896C145.296 253.528 145.168 253.224 144.912 252.984C144.672 252.728 144.368 252.6 144 252.6C143.632 252.6 143.328 252.728 143.088 252.984C142.832 253.224 142.704 253.528 142.704 253.896C142.704 254.264 142.832 254.576 143.088 254.832C143.328 255.072 143.632 255.192 144 255.192C144.368 255.192 144.672 255.072 144.912 254.832C145.168 254.576 145.296 254.264 145.296 253.896ZM140.496 253.896C140.496 253.528 140.376 253.224 140.136 252.984C139.88 252.728 139.568 252.6 139.2 252.6C138.832 252.6 138.528 252.728 138.288 252.984C138.032 253.224 137.904 253.528 137.904 253.896C137.904 254.264 138.032 254.576 138.288 254.832C138.528 255.072 138.832 255.192 139.2 255.192C139.568 255.192 139.88 255.072 140.136 254.832C140.376 254.576 140.496 254.264 140.496 253.896ZM133.944 256.128C133.944 255.584 133.76 255.128 133.392 254.76C133.008 254.376 132.544 254.184 132 254.184C131.456 254.184 131 254.376 130.632 254.76C130.248 255.128 130.056 255.584 130.056 256.128C130.056 256.672 130.248 257.136 130.632 257.52C131 257.888 131.456 258.072 132 258.072C132.544 258.072 133.008 257.888 133.392 257.52C133.76 257.136 133.944 256.672 133.944 256.128ZM143.544 256.128C143.544 255.584 143.36 255.128 142.992 254.76C142.608 254.376 142.144 254.184 141.6 254.184C141.056 254.184 140.6 254.376 140.232 254.76C139.848 255.128 139.656 255.584 139.656 256.128C139.656 256.672 139.848 257.136 140.232 257.52C140.6 257.888 141.056 258.072 141.6 258.072C142.144 258.072 142.608 257.888 142.992 257.52C143.36 257.136 143.544 256.672 143.544 256.128ZM138.744 256.128C138.744 255.584 138.56 255.128 138.192 254.76C137.808 254.376 137.344 254.184 136.8 254.184C136.256 254.184 135.8 254.376 135.432 254.76C135.048 255.128 134.856 255.584 134.856 256.128C134.856 256.672 135.048 257.136 135.432 257.52C135.8 257.888 136.256 258.072 136.8 258.072C137.344 258.072 137.808 257.888 138.192 257.52C138.56 257.136 138.744 256.672 138.744 256.128ZM135.696 258.36C135.696 257.992 135.576 257.688 135.336 257.448C135.08 257.192 134.768 257.064 134.4 257.064C134.032 257.064 133.728 257.192 133.488 257.448C133.232 257.688 133.104 257.992 133.104 258.36C133.104 258.728 133.232 259.04 133.488 259.296C133.728 259.536 134.032 259.656 134.4 259.656C134.768 259.656 135.08 259.536 135.336 259.296C135.576 259.04 135.696 258.728 135.696 258.36ZM145.296 258.36C145.296 257.992 145.168 257.688 144.912 257.448C144.672 257.192 144.368 257.064 144 257.064C143.632 257.064 143.328 257.192 143.088 257.448C142.832 257.688 142.704 257.992 142.704 258.36C142.704 258.728 142.832 259.04 143.088 259.296C143.328 259.536 143.632 259.656 144 259.656C144.368 259.656 144.672 259.536 144.912 259.296C145.168 259.04 145.296 258.728 145.296 258.36ZM140.496 258.36C140.496 257.992 140.376 257.688 140.136 257.448C139.88 257.192 139.568 257.064 139.2 257.064C138.832 257.064 138.528 257.192 138.288 257.448C138.032 257.688 137.904 257.992 137.904 258.36C137.904 258.728 138.032 259.04 138.288 259.296C138.528 259.536 138.832 259.656 139.2 259.656C139.568 259.656 139.88 259.536 140.136 259.296C140.376 259.04 140.496 258.728 140.496 258.36ZM138.744 260.592C138.744 260.048 138.56 259.592 138.192 259.224C137.808 258.84 137.344 258.648 136.8 258.648C136.256 258.648 135.8 258.84 135.432 259.224C135.048 259.592 134.856 260.048 134.856 260.592C134.856 261.136 135.048 261.6 135.432 261.984C135.8 262.352 136.256 262.536 136.8 262.536C137.344 262.536 137.808 262.352 138.192 261.984C138.56 261.6 138.744 261.136 138.744 260.592ZM143.544 260.592C143.544 260.048 143.36 259.592 142.992 259.224C142.608 258.84 142.144 258.648 141.6 258.648C141.056 258.648 140.6 258.84 140.232 259.224C139.848 259.592 139.656 260.048 139.656 260.592C139.656 261.136 139.848 261.6 140.232 261.984C140.6 262.352 141.056 262.536 141.6 262.536C142.144 262.536 142.608 262.352 142.992 261.984C143.36 261.6 143.544 261.136 143.544 260.592ZM133.944 260.592C133.944 260.048 133.76 259.592 133.392 259.224C133.008 258.84 132.544 258.648 132 258.648C131.456 258.648 131 258.84 130.632 259.224C130.248 259.592 130.056 260.048 130.056 260.592C130.056 261.136 130.248 261.6 130.632 261.984C131 262.352 131.456 262.536 132 262.536C132.544 262.536 133.008 262.352 133.392 261.984C133.76 261.6 133.944 261.136 133.944 260.592ZM140.496 262.824C140.496 262.456 140.376 262.152 140.136 261.912C139.88 261.656 139.568 261.528 139.2 261.528C138.832 261.528 138.528 261.656 138.288 261.912C138.032 262.152 137.904 262.456 137.904 262.824C137.904 263.192 138.032 263.504 138.288 263.76C138.528 264 138.832 264.12 139.2 264.12C139.568 264.12 139.88 264 140.136 263.76C140.376 263.504 140.496 263.192 140.496 262.824ZM145.296 262.824C145.296 262.456 145.168 262.152 144.912 261.912C144.672 261.656 144.368 261.528 144 261.528C143.632 261.528 143.328 261.656 143.088 261.912C142.832 262.152 142.704 262.456 142.704 262.824C142.704 263.192 142.832 263.504 143.088 263.76C143.328 264 143.632 264.12 144 264.12C144.368 264.12 144.672 264 144.912 263.76C145.168 263.504 145.296 263.192 145.296 262.824ZM135.696 262.824C135.696 262.456 135.576 262.152 135.336 261.912C135.08 261.656 134.768 261.528 134.4 261.528C134.032 261.528 133.728 261.656 133.488 261.912C133.232 262.152 133.104 262.456 133.104 262.824C133.104 263.192 133.232 263.504 133.488 263.76C133.728 264 134.032 264.12 134.4 264.12C134.768 264.12 135.08 264 135.336 263.76C135.576 263.504 135.696 263.192 135.696 262.824ZM138.744 265.056C138.744 264.512 138.56 264.056 138.192 263.688C137.808 263.304 137.344 263.112 136.8 263.112C136.256 263.112 135.8 263.304 135.432 263.688C135.048 264.056 134.856 264.512 134.856 265.056C134.856 265.6 135.048 266.064 135.432 266.448C135.8 266.816 136.256 267 136.8 267C137.344 267 137.808 266.816 138.192 266.448C138.56 266.064 138.744 265.6 138.744 265.056ZM133.944 265.056C133.944 264.512 133.76 264.056 133.392 263.688C133.008 263.304 132.544 263.112 132 263.112C131.456 263.112 131 263.304 130.632 263.688C130.248 264.056 130.056 264.512 130.056 265.056C130.056 265.6 130.248 266.064 130.632 266.448C131 266.816 131.456 267 132 267C132.544 267 133.008 266.816 133.392 266.448C133.76 266.064 133.944 265.6 133.944 265.056ZM143.544 265.056C143.544 264.512 143.36 264.056 142.992 263.688C142.608 263.304 142.144 263.112 141.6 263.112C141.056 263.112 140.6 263.304 140.232 263.688C139.848 264.056 139.656 264.512 139.656 265.056C139.656 265.6 139.848 266.064 140.232 266.448C140.6 266.816 141.056 267 141.6 267C142.144 267 142.608 266.816 142.992 266.448C143.36 266.064 143.544 265.6 143.544 265.056ZM140.496 267.288C140.496 266.92 140.376 266.616 140.136 266.376C139.88 266.12 139.568 265.992 139.2 265.992C138.832 265.992 138.528 266.12 138.288 266.376C138.032 266.616 137.904 266.92 137.904 267.288C137.904 267.656 138.032 267.968 138.288 268.224C138.528 268.464 138.832 268.584 139.2 268.584C139.568 268.584 139.88 268.464 140.136 268.224C140.376 267.968 140.496 267.656 140.496 267.288ZM135.696 267.288C135.696 266.92 135.576 266.616 135.336 266.376C135.08 266.12 134.768 265.992 134.4 265.992C134.032 265.992 133.728 266.12 133.488 266.376C133.232 266.616 133.104 266.92 133.104 267.288C133.104 267.656 133.232 267.968 133.488 268.224C133.728 268.464 134.032 268.584 134.4 268.584C134.768 268.584 135.08 268.464 135.336 268.224C135.576 267.968 135.696 267.656 135.696 267.288ZM145.296 267.288C145.296 266.92 145.168 266.616 144.912 266.376C144.672 266.12 144.368 265.992 144 265.992C143.632 265.992 143.328 266.12 143.088 266.376C142.832 266.616 142.704 266.92 142.704 267.288C142.704 267.656 142.832 267.968 143.088 268.224C143.328 268.464 143.632 268.584 144 268.584C144.368 268.584 144.672 268.464 144.912 268.224C145.168 267.968 145.296 267.656 145.296 267.288ZM138.744 269.52C138.744 268.976 138.56 268.52 138.192 268.152C137.808 267.768 137.344 267.576 136.8 267.576C136.256 267.576 135.8 267.768 135.432 268.152C135.048 268.52 134.856 268.976 134.856 269.52C134.856 270.064 135.048 270.52 135.432 270.888C135.8 271.272 136.256 271.464 136.8 271.464C137.344 271.464 137.808 271.272 138.192 270.888C138.56 270.52 138.744 270.064 138.744 269.52ZM133.944 269.52C133.944 268.976 133.76 268.52 133.392 268.152C133.008 267.768 132.544 267.576 132 267.576C131.456 267.576 131 267.768 130.632 268.152C130.248 268.52 130.056 268.976 130.056 269.52C130.056 270.064 130.248 270.52 130.632 270.888C131 271.272 131.456 271.464 132 271.464C132.544 271.464 133.008 271.272 133.392 270.888C133.76 270.52 133.944 270.064 133.944 269.52ZM143.544 269.52C143.544 268.976 143.36 268.52 142.992 268.152C142.608 267.768 142.144 267.576 141.6 267.576C141.056 267.576 140.6 267.768 140.232 268.152C139.848 268.52 139.656 268.976 139.656 269.52C139.656 270.064 139.848 270.52 140.232 270.888C140.6 271.272 141.056 271.464 141.6 271.464C142.144 271.464 142.608 271.272 142.992 270.888C143.36 270.52 143.544 270.064 143.544 269.52ZM140.496 271.752C140.496 271.384 140.376 271.072 140.136 270.816C139.88 270.576 139.568 270.456 139.2 270.456C138.832 270.456 138.528 270.576 138.288 270.816C138.032 271.072 137.904 271.384 137.904 271.752C137.904 272.12 138.032 272.424 138.288 272.664C138.528 272.92 138.832 273.048 139.2 273.048C139.568 273.048 139.88 272.92 140.136 272.664C140.376 272.424 140.496 272.12 140.496 271.752ZM135.696 271.752C135.696 271.384 135.576 271.072 135.336 270.816C135.08 270.576 134.768 270.456 134.4 270.456C134.032 270.456 133.728 270.576 133.488 270.816C133.232 271.072 133.104 271.384 133.104 271.752C133.104 272.12 133.232 272.424 133.488 272.664C133.728 272.92 134.032 273.048 134.4 273.048C134.768 273.048 135.08 272.92 135.336 272.664C135.576 272.424 135.696 272.12 135.696 271.752ZM145.296 271.752C145.296 271.384 145.168 271.072 144.912 270.816C144.672 270.576 144.368 270.456 144 270.456C143.632 270.456 143.328 270.576 143.088 270.816C142.832 271.072 142.704 271.384 142.704 271.752C142.704 272.12 142.832 272.424 143.088 272.664C143.328 272.92 143.632 273.048 144 273.048C144.368 273.048 144.672 272.92 144.912 272.664C145.168 272.424 145.296 272.12 145.296 271.752ZM138.744 273.984C138.744 273.44 138.56 272.976 138.192 272.592C137.808 272.224 137.344 272.04 136.8 272.04C136.256 272.04 135.8 272.224 135.432 272.592C135.048 272.976 134.856 273.44 134.856 273.984C134.856 274.528 135.048 274.984 135.432 275.352C135.8 275.736 136.256 275.928 136.8 275.928C137.344 275.928 137.808 275.736 138.192 275.352C138.56 274.984 138.744 274.528 138.744 273.984ZM133.944 273.984C133.944 273.44 133.76 272.976 133.392 272.592C133.008 272.224 132.544 272.04 132 272.04C131.456 272.04 131 272.224 130.632 272.592C130.248 272.976 130.056 273.44 130.056 273.984C130.056 274.528 130.248 274.984 130.632 275.352C131 275.736 131.456 275.928 132 275.928C132.544 275.928 133.008 275.736 133.392 275.352C133.76 274.984 133.944 274.528 133.944 273.984ZM143.544 273.984C143.544 273.44 143.36 272.976 142.992 272.592C142.608 272.224 142.144 272.04 141.6 272.04C141.056 272.04 140.6 272.224 140.232 272.592C139.848 272.976 139.656 273.44 139.656 273.984C139.656 274.528 139.848 274.984 140.232 275.352C140.6 275.736 141.056 275.928 141.6 275.928C142.144 275.928 142.608 275.736 142.992 275.352C143.36 274.984 143.544 274.528 143.544 273.984ZM140.496 276.216C140.496 275.848 140.376 275.536 140.136 275.28C139.88 275.04 139.568 274.92 139.2 274.92C138.832 274.92 138.528 275.04 138.288 275.28C138.032 275.536 137.904 275.848 137.904 276.216C137.904 276.584 138.032 276.888 138.288 277.128C138.528 277.384 138.832 277.512 139.2 277.512C139.568 277.512 139.88 277.384 140.136 277.128C140.376 276.888 140.496 276.584 140.496 276.216ZM135.696 276.216C135.696 275.848 135.576 275.536 135.336 275.28C135.08 275.04 134.768 274.92 134.4 274.92C134.032 274.92 133.728 275.04 133.488 275.28C133.232 275.536 133.104 275.848 133.104 276.216C133.104 276.584 133.232 276.888 133.488 277.128C133.728 277.384 134.032 277.512 134.4 277.512C134.768 277.512 135.08 277.384 135.336 277.128C135.576 276.888 135.696 276.584 135.696 276.216ZM145.296 276.216C145.296 275.848 145.168 275.536 144.912 275.28C144.672 275.04 144.368 274.92 144 274.92C143.632 274.92 143.328 275.04 143.088 275.28C142.832 275.536 142.704 275.848 142.704 276.216C142.704 276.584 142.832 276.888 143.088 277.128C143.328 277.384 143.632 277.512 144 277.512C144.368 277.512 144.672 277.384 144.912 277.128C145.168 276.888 145.296 276.584 145.296 276.216ZM119.385 247.2C119.385 246.832 119.265 246.528 119.025 246.288C118.769 246.032 118.457 245.904 118.089 245.904C117.721 245.904 117.417 246.032 117.177 246.288C116.921 246.528 116.793 246.832 116.793 247.2C116.793 247.568 116.921 247.88 117.177 248.136C117.417 248.376 117.721 248.496 118.089 248.496C118.457 248.496 118.769 248.376 119.025 248.136C119.265 247.88 119.385 247.568 119.385 247.2ZM124.185 247.2C124.185 246.832 124.065 246.528 123.825 246.288C123.569 246.032 123.257 245.904 122.889 245.904C122.521 245.904 122.217 246.032 121.977 246.288C121.721 246.528 121.593 246.832 121.593 247.2C121.593 247.568 121.721 247.88 121.977 248.136C122.217 248.376 122.521 248.496 122.889 248.496C123.257 248.496 123.569 248.376 123.825 248.136C124.065 247.88 124.185 247.568 124.185 247.2ZM128.985 247.2C128.985 246.832 128.865 246.528 128.625 246.288C128.369 246.032 128.057 245.904 127.689 245.904C127.321 245.904 127.017 246.032 126.777 246.288C126.521 246.528 126.393 246.832 126.393 247.2C126.393 247.568 126.521 247.88 126.777 248.136C127.017 248.376 127.321 248.496 127.689 248.496C128.057 248.496 128.369 248.376 128.625 248.136C128.865 247.88 128.985 247.568 128.985 247.2ZM121.353 249.432C121.353 249.192 121.273 248.992 121.113 248.832C120.937 248.656 120.729 248.568 120.489 248.568C120.249 248.568 120.049 248.656 119.889 248.832C119.713 248.992 119.625 249.192 119.625 249.432C119.625 249.672 119.713 249.88 119.889 250.056C120.049 250.216 120.249 250.296 120.489 250.296C120.729 250.296 120.937 250.216 121.113 250.056C121.273 249.88 121.353 249.672 121.353 249.432ZM126.153 249.432C126.153 249.192 126.073 248.992 125.913 248.832C125.737 248.656 125.529 248.568 125.289 248.568C125.049 248.568 124.849 248.656 124.689 248.832C124.513 248.992 124.425 249.192 124.425 249.432C124.425 249.672 124.513 249.88 124.689 250.056C124.849 250.216 125.049 250.296 125.289 250.296C125.529 250.296 125.737 250.216 125.913 250.056C126.073 249.88 126.153 249.672 126.153 249.432ZM130.953 249.432C130.953 249.192 130.865 248.992 130.689 248.832C130.529 248.656 130.329 248.568 130.089 248.568C129.849 248.568 129.649 248.656 129.489 248.832C129.313 248.992 129.225 249.192 129.225 249.432C129.225 249.672 129.313 249.88 129.489 250.056C129.649 250.216 129.849 250.296 130.089 250.296C130.329 250.296 130.529 250.216 130.689 250.056C130.865 249.88 130.953 249.672 130.953 249.432ZM119.385 251.664C119.385 251.296 119.265 250.992 119.025 250.752C118.769 250.496 118.457 250.368 118.089 250.368C117.721 250.368 117.417 250.496 117.177 250.752C116.921 250.992 116.793 251.296 116.793 251.664C116.793 252.032 116.921 252.344 117.177 252.6C117.417 252.84 117.721 252.96 118.089 252.96C118.457 252.96 118.769 252.84 119.025 252.6C119.265 252.344 119.385 252.032 119.385 251.664ZM128.985 251.664C128.985 251.296 128.865 250.992 128.625 250.752C128.369 250.496 128.057 250.368 127.689 250.368C127.321 250.368 127.017 250.496 126.777 250.752C126.521 250.992 126.393 251.296 126.393 251.664C126.393 252.032 126.521 252.344 126.777 252.6C127.017 252.84 127.321 252.96 127.689 252.96C128.057 252.96 128.369 252.84 128.625 252.6C128.865 252.344 128.985 252.032 128.985 251.664ZM124.185 251.664C124.185 251.296 124.065 250.992 123.825 250.752C123.569 250.496 123.257 250.368 122.889 250.368C122.521 250.368 122.217 250.496 121.977 250.752C121.721 250.992 121.593 251.296 121.593 251.664C121.593 252.032 121.721 252.344 121.977 252.6C122.217 252.84 122.521 252.96 122.889 252.96C123.257 252.96 123.569 252.84 123.825 252.6C124.065 252.344 124.185 252.032 124.185 251.664ZM121.353 253.896C121.353 253.656 121.273 253.456 121.113 253.296C120.937 253.12 120.729 253.032 120.489 253.032C120.249 253.032 120.049 253.12 119.889 253.296C119.713 253.456 119.625 253.656 119.625 253.896C119.625 254.136 119.713 254.344 119.889 254.52C120.049 254.68 120.249 254.76 120.489 254.76C120.729 254.76 120.937 254.68 121.113 254.52C121.273 254.344 121.353 254.136 121.353 253.896ZM130.953 253.896C130.953 253.656 130.865 253.456 130.689 253.296C130.529 253.12 130.329 253.032 130.089 253.032C129.849 253.032 129.649 253.12 129.489 253.296C129.313 253.456 129.225 253.656 129.225 253.896C129.225 254.136 129.313 254.344 129.489 254.52C129.649 254.68 129.849 254.76 130.089 254.76C130.329 254.76 130.529 254.68 130.689 254.52C130.865 254.344 130.953 254.136 130.953 253.896ZM126.153 253.896C126.153 253.656 126.073 253.456 125.913 253.296C125.737 253.12 125.529 253.032 125.289 253.032C125.049 253.032 124.849 253.12 124.689 253.296C124.513 253.456 124.425 253.656 124.425 253.896C124.425 254.136 124.513 254.344 124.689 254.52C124.849 254.68 125.049 254.76 125.289 254.76C125.529 254.76 125.737 254.68 125.913 254.52C126.073 254.344 126.153 254.136 126.153 253.896ZM119.385 256.128C119.385 255.76 119.265 255.456 119.025 255.216C118.769 254.96 118.457 254.832 118.089 254.832C117.721 254.832 117.417 254.96 117.177 255.216C116.921 255.456 116.793 255.76 116.793 256.128C116.793 256.496 116.921 256.808 117.177 257.064C117.417 257.304 117.721 257.424 118.089 257.424C118.457 257.424 118.769 257.304 119.025 257.064C119.265 256.808 119.385 256.496 119.385 256.128ZM128.985 256.128C128.985 255.76 128.865 255.456 128.625 255.216C128.369 254.96 128.057 254.832 127.689 254.832C127.321 254.832 127.017 254.96 126.777 255.216C126.521 255.456 126.393 255.76 126.393 256.128C126.393 256.496 126.521 256.808 126.777 257.064C127.017 257.304 127.321 257.424 127.689 257.424C128.057 257.424 128.369 257.304 128.625 257.064C128.865 256.808 128.985 256.496 128.985 256.128ZM124.185 256.128C124.185 255.76 124.065 255.456 123.825 255.216C123.569 254.96 123.257 254.832 122.889 254.832C122.521 254.832 122.217 254.96 121.977 255.216C121.721 255.456 121.593 255.76 121.593 256.128C121.593 256.496 121.721 256.808 121.977 257.064C122.217 257.304 122.521 257.424 122.889 257.424C123.257 257.424 123.569 257.304 123.825 257.064C124.065 256.808 124.185 256.496 124.185 256.128ZM121.353 258.36C121.353 258.12 121.273 257.92 121.113 257.76C120.937 257.584 120.729 257.496 120.489 257.496C120.249 257.496 120.049 257.584 119.889 257.76C119.713 257.92 119.625 258.12 119.625 258.36C119.625 258.6 119.713 258.808 119.889 258.984C120.049 259.144 120.249 259.224 120.489 259.224C120.729 259.224 120.937 259.144 121.113 258.984C121.273 258.808 121.353 258.6 121.353 258.36ZM130.953 258.36C130.953 258.12 130.865 257.92 130.689 257.76C130.529 257.584 130.329 257.496 130.089 257.496C129.849 257.496 129.649 257.584 129.489 257.76C129.313 257.92 129.225 258.12 129.225 258.36C129.225 258.6 129.313 258.808 129.489 258.984C129.649 259.144 129.849 259.224 130.089 259.224C130.329 259.224 130.529 259.144 130.689 258.984C130.865 258.808 130.953 258.6 130.953 258.36ZM126.153 258.36C126.153 258.12 126.073 257.92 125.913 257.76C125.737 257.584 125.529 257.496 125.289 257.496C125.049 257.496 124.849 257.584 124.689 257.76C124.513 257.92 124.425 258.12 124.425 258.36C124.425 258.6 124.513 258.808 124.689 258.984C124.849 259.144 125.049 259.224 125.289 259.224C125.529 259.224 125.737 259.144 125.913 258.984C126.073 258.808 126.153 258.6 126.153 258.36ZM124.185 260.592C124.185 260.224 124.065 259.92 123.825 259.68C123.569 259.424 123.257 259.296 122.889 259.296C122.521 259.296 122.217 259.424 121.977 259.68C121.721 259.92 121.593 260.224 121.593 260.592C121.593 260.96 121.721 261.272 121.977 261.528C122.217 261.768 122.521 261.888 122.889 261.888C123.257 261.888 123.569 261.768 123.825 261.528C124.065 261.272 124.185 260.96 124.185 260.592ZM128.985 260.592C128.985 260.224 128.865 259.92 128.625 259.68C128.369 259.424 128.057 259.296 127.689 259.296C127.321 259.296 127.017 259.424 126.777 259.68C126.521 259.92 126.393 260.224 126.393 260.592C126.393 260.96 126.521 261.272 126.777 261.528C127.017 261.768 127.321 261.888 127.689 261.888C128.057 261.888 128.369 261.768 128.625 261.528C128.865 261.272 128.985 260.96 128.985 260.592ZM119.385 260.592C119.385 260.224 119.265 259.92 119.025 259.68C118.769 259.424 118.457 259.296 118.089 259.296C117.721 259.296 117.417 259.424 117.177 259.68C116.921 259.92 116.793 260.224 116.793 260.592C116.793 260.96 116.921 261.272 117.177 261.528C117.417 261.768 117.721 261.888 118.089 261.888C118.457 261.888 118.769 261.768 119.025 261.528C119.265 261.272 119.385 260.96 119.385 260.592ZM126.153 262.824C126.153 262.584 126.073 262.384 125.913 262.224C125.737 262.048 125.529 261.96 125.289 261.96C125.049 261.96 124.849 262.048 124.689 262.224C124.513 262.384 124.425 262.584 124.425 262.824C124.425 263.064 124.513 263.272 124.689 263.448C124.849 263.608 125.049 263.688 125.289 263.688C125.529 263.688 125.737 263.608 125.913 263.448C126.073 263.272 126.153 263.064 126.153 262.824ZM130.953 262.824C130.953 262.584 130.865 262.384 130.689 262.224C130.529 262.048 130.329 261.96 130.089 261.96C129.849 261.96 129.649 262.048 129.489 262.224C129.313 262.384 129.225 262.584 129.225 262.824C129.225 263.064 129.313 263.272 129.489 263.448C129.649 263.608 129.849 263.688 130.089 263.688C130.329 263.688 130.529 263.608 130.689 263.448C130.865 263.272 130.953 263.064 130.953 262.824ZM121.353 262.824C121.353 262.584 121.273 262.384 121.113 262.224C120.937 262.048 120.729 261.96 120.489 261.96C120.249 261.96 120.049 262.048 119.889 262.224C119.713 262.384 119.625 262.584 119.625 262.824C119.625 263.064 119.713 263.272 119.889 263.448C120.049 263.608 120.249 263.688 120.489 263.688C120.729 263.688 120.937 263.608 121.113 263.448C121.273 263.272 121.353 263.064 121.353 262.824ZM124.185 265.056C124.185 264.688 124.065 264.384 123.825 264.144C123.569 263.888 123.257 263.76 122.889 263.76C122.521 263.76 122.217 263.888 121.977 264.144C121.721 264.384 121.593 264.688 121.593 265.056C121.593 265.424 121.721 265.736 121.977 265.992C122.217 266.232 122.521 266.352 122.889 266.352C123.257 266.352 123.569 266.232 123.825 265.992C124.065 265.736 124.185 265.424 124.185 265.056ZM119.385 265.056C119.385 264.688 119.265 264.384 119.025 264.144C118.769 263.888 118.457 263.76 118.089 263.76C117.721 263.76 117.417 263.888 117.177 264.144C116.921 264.384 116.793 264.688 116.793 265.056C116.793 265.424 116.921 265.736 117.177 265.992C117.417 266.232 117.721 266.352 118.089 266.352C118.457 266.352 118.769 266.232 119.025 265.992C119.265 265.736 119.385 265.424 119.385 265.056ZM128.985 265.056C128.985 264.688 128.865 264.384 128.625 264.144C128.369 263.888 128.057 263.76 127.689 263.76C127.321 263.76 127.017 263.888 126.777 264.144C126.521 264.384 126.393 264.688 126.393 265.056C126.393 265.424 126.521 265.736 126.777 265.992C127.017 266.232 127.321 266.352 127.689 266.352C128.057 266.352 128.369 266.232 128.625 265.992C128.865 265.736 128.985 265.424 128.985 265.056ZM126.153 267.288C126.153 267.048 126.073 266.848 125.913 266.688C125.737 266.512 125.529 266.424 125.289 266.424C125.049 266.424 124.849 266.512 124.689 266.688C124.513 266.848 124.425 267.048 124.425 267.288C124.425 267.528 124.513 267.736 124.689 267.912C124.849 268.072 125.049 268.152 125.289 268.152C125.529 268.152 125.737 268.072 125.913 267.912C126.073 267.736 126.153 267.528 126.153 267.288ZM121.353 267.288C121.353 267.048 121.273 266.848 121.113 266.688C120.937 266.512 120.729 266.424 120.489 266.424C120.249 266.424 120.049 266.512 119.889 266.688C119.713 266.848 119.625 267.048 119.625 267.288C119.625 267.528 119.713 267.736 119.889 267.912C120.049 268.072 120.249 268.152 120.489 268.152C120.729 268.152 120.937 268.072 121.113 267.912C121.273 267.736 121.353 267.528 121.353 267.288ZM130.953 267.288C130.953 267.048 130.865 266.848 130.689 266.688C130.529 266.512 130.329 266.424 130.089 266.424C129.849 266.424 129.649 266.512 129.489 266.688C129.313 266.848 129.225 267.048 129.225 267.288C129.225 267.528 129.313 267.736 129.489 267.912C129.649 268.072 129.849 268.152 130.089 268.152C130.329 268.152 130.529 268.072 130.689 267.912C130.865 267.736 130.953 267.528 130.953 267.288ZM124.185 269.52C124.185 269.152 124.065 268.848 123.825 268.608C123.569 268.352 123.257 268.224 122.889 268.224C122.521 268.224 122.217 268.352 121.977 268.608C121.721 268.848 121.593 269.152 121.593 269.52C121.593 269.888 121.721 270.192 121.977 270.432C122.217 270.688 122.521 270.816 122.889 270.816C123.257 270.816 123.569 270.688 123.825 270.432C124.065 270.192 124.185 269.888 124.185 269.52ZM119.385 269.52C119.385 269.152 119.265 268.848 119.025 268.608C118.769 268.352 118.457 268.224 118.089 268.224C117.721 268.224 117.417 268.352 117.177 268.608C116.921 268.848 116.793 269.152 116.793 269.52C116.793 269.888 116.921 270.192 117.177 270.432C117.417 270.688 117.721 270.816 118.089 270.816C118.457 270.816 118.769 270.688 119.025 270.432C119.265 270.192 119.385 269.888 119.385 269.52ZM128.985 269.52C128.985 269.152 128.865 268.848 128.625 268.608C128.369 268.352 128.057 268.224 127.689 268.224C127.321 268.224 127.017 268.352 126.777 268.608C126.521 268.848 126.393 269.152 126.393 269.52C126.393 269.888 126.521 270.192 126.777 270.432C127.017 270.688 127.321 270.816 127.689 270.816C128.057 270.816 128.369 270.688 128.625 270.432C128.865 270.192 128.985 269.888 128.985 269.52ZM126.153 271.752C126.153 271.512 126.073 271.304 125.913 271.128C125.737 270.968 125.529 270.888 125.289 270.888C125.049 270.888 124.849 270.968 124.689 271.128C124.513 271.304 124.425 271.512 124.425 271.752C124.425 271.992 124.513 272.192 124.689 272.352C124.849 272.528 125.049 272.616 125.289 272.616C125.529 272.616 125.737 272.528 125.913 272.352C126.073 272.192 126.153 271.992 126.153 271.752ZM121.353 271.752C121.353 271.512 121.273 271.304 121.113 271.128C120.937 270.968 120.729 270.888 120.489 270.888C120.249 270.888 120.049 270.968 119.889 271.128C119.713 271.304 119.625 271.512 119.625 271.752C119.625 271.992 119.713 272.192 119.889 272.352C120.049 272.528 120.249 272.616 120.489 272.616C120.729 272.616 120.937 272.528 121.113 272.352C121.273 272.192 121.353 271.992 121.353 271.752ZM130.953 271.752C130.953 271.512 130.865 271.304 130.689 271.128C130.529 270.968 130.329 270.888 130.089 270.888C129.849 270.888 129.649 270.968 129.489 271.128C129.313 271.304 129.225 271.512 129.225 271.752C129.225 271.992 129.313 272.192 129.489 272.352C129.649 272.528 129.849 272.616 130.089 272.616C130.329 272.616 130.529 272.528 130.689 272.352C130.865 272.192 130.953 271.992 130.953 271.752ZM124.185 273.984C124.185 273.616 124.065 273.304 123.825 273.048C123.569 272.808 123.257 272.688 122.889 272.688C122.521 272.688 122.217 272.808 121.977 273.048C121.721 273.304 121.593 273.616 121.593 273.984C121.593 274.352 121.721 274.656 121.977 274.896C122.217 275.152 122.521 275.28 122.889 275.28C123.257 275.28 123.569 275.152 123.825 274.896C124.065 274.656 124.185 274.352 124.185 273.984ZM119.385 273.984C119.385 273.616 119.265 273.304 119.025 273.048C118.769 272.808 118.457 272.688 118.089 272.688C117.721 272.688 117.417 272.808 117.177 273.048C116.921 273.304 116.793 273.616 116.793 273.984C116.793 274.352 116.921 274.656 117.177 274.896C117.417 275.152 117.721 275.28 118.089 275.28C118.457 275.28 118.769 275.152 119.025 274.896C119.265 274.656 119.385 274.352 119.385 273.984ZM128.985 273.984C128.985 273.616 128.865 273.304 128.625 273.048C128.369 272.808 128.057 272.688 127.689 272.688C127.321 272.688 127.017 272.808 126.777 273.048C126.521 273.304 126.393 273.616 126.393 273.984C126.393 274.352 126.521 274.656 126.777 274.896C127.017 275.152 127.321 275.28 127.689 275.28C128.057 275.28 128.369 275.152 128.625 274.896C128.865 274.656 128.985 274.352 128.985 273.984ZM126.153 276.216C126.153 275.976 126.073 275.768 125.913 275.592C125.737 275.432 125.529 275.352 125.289 275.352C125.049 275.352 124.849 275.432 124.689 275.592C124.513 275.768 124.425 275.976 124.425 276.216C124.425 276.456 124.513 276.656 124.689 276.816C124.849 276.992 125.049 277.08 125.289 277.08C125.529 277.08 125.737 276.992 125.913 276.816C126.073 276.656 126.153 276.456 126.153 276.216ZM121.353 276.216C121.353 275.976 121.273 275.768 121.113 275.592C120.937 275.432 120.729 275.352 120.489 275.352C120.249 275.352 120.049 275.432 119.889 275.592C119.713 275.768 119.625 275.976 119.625 276.216C119.625 276.456 119.713 276.656 119.889 276.816C120.049 276.992 120.249 277.08 120.489 277.08C120.729 277.08 120.937 276.992 121.113 276.816C121.273 276.656 121.353 276.456 121.353 276.216ZM130.953 276.216C130.953 275.976 130.865 275.768 130.689 275.592C130.529 275.432 130.329 275.352 130.089 275.352C129.849 275.352 129.649 275.432 129.489 275.592C129.313 275.768 129.225 275.976 129.225 276.216C129.225 276.456 129.313 276.656 129.489 276.816C129.649 276.992 129.849 277.08 130.089 277.08C130.329 277.08 130.529 276.992 130.689 276.816C130.865 276.656 130.953 276.456 130.953 276.216ZM105.043 247.2C105.043 246.96 104.963 246.76 104.803 246.6C104.627 246.424 104.419 246.336 104.179 246.336C103.939 246.336 103.739 246.424 103.579 246.6C103.403 246.76 103.315 246.96 103.315 247.2C103.315 247.44 103.403 247.648 103.579 247.824C103.739 247.984 103.939 248.064 104.179 248.064C104.419 248.064 104.627 247.984 104.803 247.824C104.963 247.648 105.043 247.44 105.043 247.2ZM109.843 247.2C109.843 246.96 109.763 246.76 109.603 246.6C109.427 246.424 109.219 246.336 108.979 246.336C108.739 246.336 108.539 246.424 108.379 246.6C108.203 246.76 108.115 246.96 108.115 247.2C108.115 247.44 108.203 247.648 108.379 247.824C108.539 247.984 108.739 248.064 108.979 248.064C109.219 248.064 109.427 247.984 109.603 247.824C109.763 247.648 109.843 247.44 109.843 247.2ZM114.643 247.2C114.643 246.96 114.563 246.76 114.403 246.6C114.227 246.424 114.019 246.336 113.779 246.336C113.539 246.336 113.339 246.424 113.179 246.6C113.003 246.76 112.915 246.96 112.915 247.2C112.915 247.44 113.003 247.648 113.179 247.824C113.339 247.984 113.539 248.064 113.779 248.064C114.019 248.064 114.227 247.984 114.403 247.824C114.563 247.648 114.643 247.44 114.643 247.2ZM107.155 249.432C107.155 249.272 107.099 249.136 106.987 249.024C106.875 248.912 106.739 248.856 106.579 248.856C106.419 248.856 106.283 248.912 106.171 249.024C106.059 249.136 106.003 249.272 106.003 249.432C106.003 249.592 106.059 249.728 106.171 249.84C106.283 249.952 106.419 250.008 106.579 250.008C106.739 250.008 106.875 249.952 106.987 249.84C107.099 249.728 107.155 249.592 107.155 249.432ZM111.955 249.432C111.955 249.272 111.899 249.136 111.787 249.024C111.675 248.912 111.539 248.856 111.379 248.856C111.219 248.856 111.083 248.912 110.971 249.024C110.859 249.136 110.803 249.272 110.803 249.432C110.803 249.592 110.859 249.728 110.971 249.84C111.083 249.952 111.219 250.008 111.379 250.008C111.539 250.008 111.675 249.952 111.787 249.84C111.899 249.728 111.955 249.592 111.955 249.432ZM116.755 249.432C116.755 249.272 116.699 249.136 116.587 249.024C116.475 248.912 116.339 248.856 116.179 248.856C116.019 248.856 115.883 248.912 115.771 249.024C115.659 249.136 115.603 249.272 115.603 249.432C115.603 249.592 115.659 249.728 115.771 249.84C115.883 249.952 116.019 250.008 116.179 250.008C116.339 250.008 116.475 249.952 116.587 249.84C116.699 249.728 116.755 249.592 116.755 249.432ZM105.043 251.664C105.043 251.424 104.963 251.224 104.803 251.064C104.627 250.888 104.419 250.8 104.179 250.8C103.939 250.8 103.739 250.888 103.579 251.064C103.403 251.224 103.315 251.424 103.315 251.664C103.315 251.904 103.403 252.112 103.579 252.288C103.739 252.448 103.939 252.528 104.179 252.528C104.419 252.528 104.627 252.448 104.803 252.288C104.963 252.112 105.043 251.904 105.043 251.664ZM114.643 251.664C114.643 251.424 114.563 251.224 114.403 251.064C114.227 250.888 114.019 250.8 113.779 250.8C113.539 250.8 113.339 250.888 113.179 251.064C113.003 251.224 112.915 251.424 112.915 251.664C112.915 251.904 113.003 252.112 113.179 252.288C113.339 252.448 113.539 252.528 113.779 252.528C114.019 252.528 114.227 252.448 114.403 252.288C114.563 252.112 114.643 251.904 114.643 251.664ZM109.843 251.664C109.843 251.424 109.763 251.224 109.603 251.064C109.427 250.888 109.219 250.8 108.979 250.8C108.739 250.8 108.539 250.888 108.379 251.064C108.203 251.224 108.115 251.424 108.115 251.664C108.115 251.904 108.203 252.112 108.379 252.288C108.539 252.448 108.739 252.528 108.979 252.528C109.219 252.528 109.427 252.448 109.603 252.288C109.763 252.112 109.843 251.904 109.843 251.664ZM107.155 253.896C107.155 253.736 107.099 253.6 106.987 253.488C106.875 253.376 106.739 253.32 106.579 253.32C106.419 253.32 106.283 253.376 106.171 253.488C106.059 253.6 106.003 253.736 106.003 253.896C106.003 254.056 106.059 254.192 106.171 254.304C106.283 254.416 106.419 254.472 106.579 254.472C106.739 254.472 106.875 254.416 106.987 254.304C107.099 254.192 107.155 254.056 107.155 253.896ZM116.755 253.896C116.755 253.736 116.699 253.6 116.587 253.488C116.475 253.376 116.339 253.32 116.179 253.32C116.019 253.32 115.883 253.376 115.771 253.488C115.659 253.6 115.603 253.736 115.603 253.896C115.603 254.056 115.659 254.192 115.771 254.304C115.883 254.416 116.019 254.472 116.179 254.472C116.339 254.472 116.475 254.416 116.587 254.304C116.699 254.192 116.755 254.056 116.755 253.896ZM111.955 253.896C111.955 253.736 111.899 253.6 111.787 253.488C111.675 253.376 111.539 253.32 111.379 253.32C111.219 253.32 111.083 253.376 110.971 253.488C110.859 253.6 110.803 253.736 110.803 253.896C110.803 254.056 110.859 254.192 110.971 254.304C111.083 254.416 111.219 254.472 111.379 254.472C111.539 254.472 111.675 254.416 111.787 254.304C111.899 254.192 111.955 254.056 111.955 253.896ZM105.043 256.128C105.043 255.888 104.963 255.688 104.803 255.528C104.627 255.352 104.419 255.264 104.179 255.264C103.939 255.264 103.739 255.352 103.579 255.528C103.403 255.688 103.315 255.888 103.315 256.128C103.315 256.368 103.403 256.576 103.579 256.752C103.739 256.912 103.939 256.992 104.179 256.992C104.419 256.992 104.627 256.912 104.803 256.752C104.963 256.576 105.043 256.368 105.043 256.128ZM114.643 256.128C114.643 255.888 114.563 255.688 114.403 255.528C114.227 255.352 114.019 255.264 113.779 255.264C113.539 255.264 113.339 255.352 113.179 255.528C113.003 255.688 112.915 255.888 112.915 256.128C112.915 256.368 113.003 256.576 113.179 256.752C113.339 256.912 113.539 256.992 113.779 256.992C114.019 256.992 114.227 256.912 114.403 256.752C114.563 256.576 114.643 256.368 114.643 256.128ZM109.843 256.128C109.843 255.888 109.763 255.688 109.603 255.528C109.427 255.352 109.219 255.264 108.979 255.264C108.739 255.264 108.539 255.352 108.379 255.528C108.203 255.688 108.115 255.888 108.115 256.128C108.115 256.368 108.203 256.576 108.379 256.752C108.539 256.912 108.739 256.992 108.979 256.992C109.219 256.992 109.427 256.912 109.603 256.752C109.763 256.576 109.843 256.368 109.843 256.128ZM107.155 258.264C107.155 258.104 107.099 257.968 106.987 257.856C106.875 257.744 106.739 257.688 106.579 257.688C106.419 257.688 106.283 257.744 106.171 257.856C106.059 257.968 106.003 258.104 106.003 258.264C106.003 258.424 106.059 258.56 106.171 258.672C106.283 258.784 106.419 258.84 106.579 258.84C106.739 258.84 106.875 258.784 106.987 258.672C107.099 258.56 107.155 258.424 107.155 258.264ZM116.755 258.264C116.755 258.104 116.699 257.968 116.587 257.856C116.475 257.744 116.339 257.688 116.179 257.688C116.019 257.688 115.883 257.744 115.771 257.856C115.659 257.968 115.603 258.104 115.603 258.264C115.603 258.424 115.659 258.56 115.771 258.672C115.883 258.784 116.019 258.84 116.179 258.84C116.339 258.84 116.475 258.784 116.587 258.672C116.699 258.56 116.755 258.424 116.755 258.264ZM111.955 258.264C111.955 258.104 111.899 257.968 111.787 257.856C111.675 257.744 111.539 257.688 111.379 257.688C111.219 257.688 111.083 257.744 110.971 257.856C110.859 257.968 110.803 258.104 110.803 258.264C110.803 258.424 110.859 258.56 110.971 258.672C111.083 258.784 111.219 258.84 111.379 258.84C111.539 258.84 111.675 258.784 111.787 258.672C111.899 258.56 111.955 258.424 111.955 258.264ZM109.843 260.496C109.843 260.256 109.763 260.056 109.603 259.896C109.427 259.72 109.219 259.632 108.979 259.632C108.739 259.632 108.539 259.72 108.379 259.896C108.203 260.056 108.115 260.256 108.115 260.496C108.115 260.736 108.203 260.944 108.379 261.12C108.539 261.28 108.739 261.36 108.979 261.36C109.219 261.36 109.427 261.28 109.603 261.12C109.763 260.944 109.843 260.736 109.843 260.496ZM114.643 260.496C114.643 260.256 114.563 260.056 114.403 259.896C114.227 259.72 114.019 259.632 113.779 259.632C113.539 259.632 113.339 259.72 113.179 259.896C113.003 260.056 112.915 260.256 112.915 260.496C112.915 260.736 113.003 260.944 113.179 261.12C113.339 261.28 113.539 261.36 113.779 261.36C114.019 261.36 114.227 261.28 114.403 261.12C114.563 260.944 114.643 260.736 114.643 260.496ZM105.043 260.496C105.043 260.256 104.963 260.056 104.803 259.896C104.627 259.72 104.419 259.632 104.179 259.632C103.939 259.632 103.739 259.72 103.579 259.896C103.403 260.056 103.315 260.256 103.315 260.496C103.315 260.736 103.403 260.944 103.579 261.12C103.739 261.28 103.939 261.36 104.179 261.36C104.419 261.36 104.627 261.28 104.803 261.12C104.963 260.944 105.043 260.736 105.043 260.496ZM111.955 262.728C111.955 262.568 111.899 262.432 111.787 262.32C111.675 262.208 111.539 262.152 111.379 262.152C111.219 262.152 111.083 262.208 110.971 262.32C110.859 262.432 110.803 262.568 110.803 262.728C110.803 262.888 110.859 263.024 110.971 263.136C111.083 263.248 111.219 263.304 111.379 263.304C111.539 263.304 111.675 263.248 111.787 263.136C111.899 263.024 111.955 262.888 111.955 262.728ZM116.755 262.728C116.755 262.568 116.699 262.432 116.587 262.32C116.475 262.208 116.339 262.152 116.179 262.152C116.019 262.152 115.883 262.208 115.771 262.32C115.659 262.432 115.603 262.568 115.603 262.728C115.603 262.888 115.659 263.024 115.771 263.136C115.883 263.248 116.019 263.304 116.179 263.304C116.339 263.304 116.475 263.248 116.587 263.136C116.699 263.024 116.755 262.888 116.755 262.728ZM107.155 262.728C107.155 262.568 107.099 262.432 106.987 262.32C106.875 262.208 106.739 262.152 106.579 262.152C106.419 262.152 106.283 262.208 106.171 262.32C106.059 262.432 106.003 262.568 106.003 262.728C106.003 262.888 106.059 263.024 106.171 263.136C106.283 263.248 106.419 263.304 106.579 263.304C106.739 263.304 106.875 263.248 106.987 263.136C107.099 263.024 107.155 262.888 107.155 262.728ZM109.843 264.96C109.843 264.72 109.763 264.52 109.603 264.36C109.427 264.184 109.219 264.096 108.979 264.096C108.739 264.096 108.539 264.184 108.379 264.36C108.203 264.52 108.115 264.72 108.115 264.96C108.115 265.2 108.203 265.408 108.379 265.584C108.539 265.744 108.739 265.824 108.979 265.824C109.219 265.824 109.427 265.744 109.603 265.584C109.763 265.408 109.843 265.2 109.843 264.96ZM105.043 264.96C105.043 264.72 104.963 264.52 104.803 264.36C104.627 264.184 104.419 264.096 104.179 264.096C103.939 264.096 103.739 264.184 103.579 264.36C103.403 264.52 103.315 264.72 103.315 264.96C103.315 265.2 103.403 265.408 103.579 265.584C103.739 265.744 103.939 265.824 104.179 265.824C104.419 265.824 104.627 265.744 104.803 265.584C104.963 265.408 105.043 265.2 105.043 264.96ZM114.643 264.96C114.643 264.72 114.563 264.52 114.403 264.36C114.227 264.184 114.019 264.096 113.779 264.096C113.539 264.096 113.339 264.184 113.179 264.36C113.003 264.52 112.915 264.72 112.915 264.96C112.915 265.2 113.003 265.408 113.179 265.584C113.339 265.744 113.539 265.824 113.779 265.824C114.019 265.824 114.227 265.744 114.403 265.584C114.563 265.408 114.643 265.2 114.643 264.96ZM111.955 267.192C111.955 267.032 111.899 266.896 111.787 266.784C111.675 266.672 111.539 266.616 111.379 266.616C111.219 266.616 111.083 266.672 110.971 266.784C110.859 266.896 110.803 267.032 110.803 267.192C110.803 267.352 110.859 267.488 110.971 267.6C111.083 267.712 111.219 267.768 111.379 267.768C111.539 267.768 111.675 267.712 111.787 267.6C111.899 267.488 111.955 267.352 111.955 267.192ZM107.155 267.192C107.155 267.032 107.099 266.896 106.987 266.784C106.875 266.672 106.739 266.616 106.579 266.616C106.419 266.616 106.283 266.672 106.171 266.784C106.059 266.896 106.003 267.032 106.003 267.192C106.003 267.352 106.059 267.488 106.171 267.6C106.283 267.712 106.419 267.768 106.579 267.768C106.739 267.768 106.875 267.712 106.987 267.6C107.099 267.488 107.155 267.352 107.155 267.192ZM116.755 267.192C116.755 267.032 116.699 266.896 116.587 266.784C116.475 266.672 116.339 266.616 116.179 266.616C116.019 266.616 115.883 266.672 115.771 266.784C115.659 266.896 115.603 267.032 115.603 267.192C115.603 267.352 115.659 267.488 115.771 267.6C115.883 267.712 116.019 267.768 116.179 267.768C116.339 267.768 116.475 267.712 116.587 267.6C116.699 267.488 116.755 267.352 116.755 267.192ZM109.843 269.424C109.843 269.184 109.763 268.984 109.603 268.824C109.427 268.648 109.219 268.56 108.979 268.56C108.739 268.56 108.539 268.648 108.379 268.824C108.203 268.984 108.115 269.184 108.115 269.424C108.115 269.664 108.203 269.864 108.379 270.024C108.539 270.2 108.739 270.288 108.979 270.288C109.219 270.288 109.427 270.2 109.603 270.024C109.763 269.864 109.843 269.664 109.843 269.424ZM105.043 269.424C105.043 269.184 104.963 268.984 104.803 268.824C104.627 268.648 104.419 268.56 104.179 268.56C103.939 268.56 103.739 268.648 103.579 268.824C103.403 268.984 103.315 269.184 103.315 269.424C103.315 269.664 103.403 269.864 103.579 270.024C103.739 270.2 103.939 270.288 104.179 270.288C104.419 270.288 104.627 270.2 104.803 270.024C104.963 269.864 105.043 269.664 105.043 269.424ZM114.643 269.424C114.643 269.184 114.563 268.984 114.403 268.824C114.227 268.648 114.019 268.56 113.779 268.56C113.539 268.56 113.339 268.648 113.179 268.824C113.003 268.984 112.915 269.184 112.915 269.424C112.915 269.664 113.003 269.864 113.179 270.024C113.339 270.2 113.539 270.288 113.779 270.288C114.019 270.288 114.227 270.2 114.403 270.024C114.563 269.864 114.643 269.664 114.643 269.424ZM111.955 271.656C111.955 271.496 111.899 271.36 111.787 271.248C111.675 271.136 111.539 271.08 111.379 271.08C111.219 271.08 111.083 271.136 110.971 271.248C110.859 271.36 110.803 271.496 110.803 271.656C110.803 271.816 110.859 271.952 110.971 272.064C111.083 272.176 111.219 272.232 111.379 272.232C111.539 272.232 111.675 272.176 111.787 272.064C111.899 271.952 111.955 271.816 111.955 271.656ZM107.155 271.656C107.155 271.496 107.099 271.36 106.987 271.248C106.875 271.136 106.739 271.08 106.579 271.08C106.419 271.08 106.283 271.136 106.171 271.248C106.059 271.36 106.003 271.496 106.003 271.656C106.003 271.816 106.059 271.952 106.171 272.064C106.283 272.176 106.419 272.232 106.579 272.232C106.739 272.232 106.875 272.176 106.987 272.064C107.099 271.952 107.155 271.816 107.155 271.656ZM116.755 271.656C116.755 271.496 116.699 271.36 116.587 271.248C116.475 271.136 116.339 271.08 116.179 271.08C116.019 271.08 115.883 271.136 115.771 271.248C115.659 271.36 115.603 271.496 115.603 271.656C115.603 271.816 115.659 271.952 115.771 272.064C115.883 272.176 116.019 272.232 116.179 272.232C116.339 272.232 116.475 272.176 116.587 272.064C116.699 271.952 116.755 271.816 116.755 271.656ZM109.843 273.888C109.843 273.648 109.763 273.44 109.603 273.264C109.427 273.104 109.219 273.024 108.979 273.024C108.739 273.024 108.539 273.104 108.379 273.264C108.203 273.44 108.115 273.648 108.115 273.888C108.115 274.128 108.203 274.328 108.379 274.488C108.539 274.664 108.739 274.752 108.979 274.752C109.219 274.752 109.427 274.664 109.603 274.488C109.763 274.328 109.843 274.128 109.843 273.888ZM105.043 273.888C105.043 273.648 104.963 273.44 104.803 273.264C104.627 273.104 104.419 273.024 104.179 273.024C103.939 273.024 103.739 273.104 103.579 273.264C103.403 273.44 103.315 273.648 103.315 273.888C103.315 274.128 103.403 274.328 103.579 274.488C103.739 274.664 103.939 274.752 104.179 274.752C104.419 274.752 104.627 274.664 104.803 274.488C104.963 274.328 105.043 274.128 105.043 273.888ZM114.643 273.888C114.643 273.648 114.563 273.44 114.403 273.264C114.227 273.104 114.019 273.024 113.779 273.024C113.539 273.024 113.339 273.104 113.179 273.264C113.003 273.44 112.915 273.648 112.915 273.888C112.915 274.128 113.003 274.328 113.179 274.488C113.339 274.664 113.539 274.752 113.779 274.752C114.019 274.752 114.227 274.664 114.403 274.488C114.563 274.328 114.643 274.128 114.643 273.888ZM111.955 276.12C111.955 275.96 111.899 275.824 111.787 275.712C111.675 275.6 111.539 275.544 111.379 275.544C111.219 275.544 111.083 275.6 110.971 275.712C110.859 275.824 110.803 275.96 110.803 276.12C110.803 276.28 110.859 276.416 110.971 276.528C111.083 276.64 111.219 276.696 111.379 276.696C111.539 276.696 111.675 276.64 111.787 276.528C111.899 276.416 111.955 276.28 111.955 276.12ZM107.155 276.12C107.155 275.96 107.099 275.824 106.987 275.712C106.875 275.6 106.739 275.544 106.579 275.544C106.419 275.544 106.283 275.6 106.171 275.712C106.059 275.824 106.003 275.96 106.003 276.12C106.003 276.28 106.059 276.416 106.171 276.528C106.283 276.64 106.419 276.696 106.579 276.696C106.739 276.696 106.875 276.64 106.987 276.528C107.099 276.416 107.155 276.28 107.155 276.12ZM116.755 276.12C116.755 275.96 116.699 275.824 116.587 275.712C116.475 275.6 116.339 275.544 116.179 275.544C116.019 275.544 115.883 275.6 115.771 275.712C115.659 275.824 115.603 275.96 115.603 276.12C115.603 276.28 115.659 276.416 115.771 276.528C115.883 276.64 116.019 276.696 116.179 276.696C116.339 276.696 116.475 276.64 116.587 276.528C116.699 276.416 116.755 276.28 116.755 276.12Z` - -export function TierAvatarBorder({ className }: TierAvatarBorderProps) { - return ( - - - - {/* right pattern */} - - {/* left pattern (mirrored) */} - - - - - - - - - - - - - ) -} diff --git a/src/features/dashboard/billing/types.ts b/src/features/dashboard/billing/types.ts deleted file mode 100644 index 45fd3d40e..000000000 --- a/src/features/dashboard/billing/types.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { TeamItems } from '@/core/modules/billing/models' -import type { TeamLimits } from '@/core/modules/teams/models' - -export interface BillingData { - items: TeamItems - limits: TeamLimits -} - -export interface BillingTierData { - base: TeamItems['tiers']['available'][0] | undefined - pro: TeamItems['tiers']['available'][0] | undefined - selected: TeamItems['tiers']['available'][0] | undefined -} - -export interface BillingAddonData { - current: TeamItems['addons']['current'][0] | undefined - available: TeamItems['addons']['available'][0] | undefined - canPurchase: boolean -} diff --git a/src/features/dashboard/billing/utils.ts b/src/features/dashboard/billing/utils.ts deleted file mode 100644 index a0aaa4bed..000000000 --- a/src/features/dashboard/billing/utils.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { TeamItems } from '@/core/modules/billing/models' -import { l } from '@/core/shared/clients/logger/logger' -import { ADDON_500_SANDBOXES_ID, TIER_BASE_ID, TIER_PRO_ID } from './constants' -import type { BillingAddonData, BillingTierData } from './types' - -export function formatTierDisplayName(name: string): string { - if (name.toLowerCase().includes('base')) return 'Hobby' - if (name.toLowerCase().includes('pro')) return 'Professional' - return name -} - -export const MIB_TO_GB = 1024 - -export function formatMibToGb(mib: number): string { - const gb = Math.round(mib / MIB_TO_GB) - return `${gb}GB` -} - -export function formatHours(hours: number): string { - return `${hours}h` -} - -export function formatAddonQuantity( - quantity: number, - priceCents: number -): Array<{ label: string; price_cents: number }> { - return Array.from({ length: quantity }, () => ({ - label: '+500 Concurrent Sandboxes', - price_cents: priceCents, - })) -} - -export function extractTierData(items: TeamItems): BillingTierData { - const { tiers } = items - - const base = tiers.available.find((t) => t.id === TIER_BASE_ID) - const pro = tiers.available.find((t) => t.id === TIER_PRO_ID) - const selected = tiers.available.find((t) => t.id === tiers.current) - - if (!selected) { - l.error( - { - key: 'billing_page:missing_selected_tier', - context: { - currentTier: tiers.current, - availableTiers: tiers.available?.map((t) => t.id) || [], - }, - }, - 'billing_page: Could not find selected tier in available tiers' - ) - } - - if (!pro || !base) { - l.error( - { - key: 'billing_page:missing_expected_tiers', - context: { - pro: !!pro, - base: !!base, - availableTiers: tiers.available?.map((t) => t.id) || [], - }, - }, - `billing_page: Expected "pro" and "base" tiers, found pro: ${!!pro}, base: ${!!base}` - ) - } - - return { base, pro, selected } -} - -export function extractAddonData( - items: TeamItems, - selectedTierId?: string -): BillingAddonData { - const { addons } = items - - const current = addons.current?.find((a) => a.id === ADDON_500_SANDBOXES_ID) - const available = addons.available.find( - (a) => a.id === ADDON_500_SANDBOXES_ID - ) - - const isOnProTier = selectedTierId === TIER_PRO_ID - const canPurchase = isOnProTier && !!available - - return { current, available, canPurchase } -} diff --git a/src/features/dashboard/byoc/byoc-setup.tsx b/src/features/dashboard/byoc/byoc-setup.tsx deleted file mode 100644 index 14f231197..000000000 --- a/src/features/dashboard/byoc/byoc-setup.tsx +++ /dev/null @@ -1,174 +0,0 @@ -'use client' - -import { useState } from 'react' -import type { ByocSetupConfig } from '@/core/modules/feature-flags/definitions' -import { Page } from '@/features/dashboard/layouts/page' -import { CodeBlock } from '@/ui/code-block' -import { CloudIcon, InfoIcon, TerminalIcon } from '@/ui/primitives/icons' -import { Input } from '@/ui/primitives/input' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/ui/primitives/select' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/ui/primitives/tabs' -import { isValidGcpProjectId, renderByocSetupTemplate } from './terraform' - -const PROJECT_PLACEHOLDER = 'YOUR_GCP_PROJECT_ID' - -export function ByocSetup({ config }: { config: ByocSetupConfig }) { - const [projectId, setProjectId] = useState('') - const [region, setRegion] = useState(() => config.regions[0] ?? '') - const trimmedProjectId = projectId.trim() - const projectIdValid = isValidGcpProjectId(trimmedProjectId) - const projectIdInvalid = trimmedProjectId.length > 0 && !projectIdValid - const templateValues = { - principal: config.principal, - projectId: projectIdValid ? trimmedProjectId : PROJECT_PLACEHOLDER, - region, - } - const templates = { - gcloud: renderByocSetupTemplate({ - ...templateValues, - template: config.templates.gcloud, - }), - terraform: renderByocSetupTemplate({ - ...templateValues, - template: config.templates.terraform, - }), - } - - return ( - -
-
-
- -

Set up BYOC

-
-

- Create the project-local identity E2B will use to deploy and operate - your region. E2B receives impersonation access, never a service - account key. -

-
- -
-
-
-

Configuration

-

- Choose the destination -

-
- - - - - -
- -
-

- E2B access principal -

-

- {config.principal} -

-
-
-
- -
-
-
-

Generated file

-

- Access setup -

-
-

- Choose Terraform or gcloud -

-
- - - - Terraform - - - gcloud - - - - } - lang="hcl" - title="main.tf" - viewportProps={{ className: 'max-h-[560px] max-w-full' }} - > - {templates.terraform} - - - - } - lang="bash" - title="Run in Cloud Shell" - viewportProps={{ className: 'max-h-[560px] max-w-full' }} - > - {templates.gcloud} - - - -
-
-
-
- ) -} diff --git a/src/features/dashboard/byoc/terraform.ts b/src/features/dashboard/byoc/terraform.ts deleted file mode 100644 index 77e528287..000000000 --- a/src/features/dashboard/byoc/terraform.ts +++ /dev/null @@ -1,22 +0,0 @@ -const GCP_PROJECT_ID_PATTERN = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/ - -export function isValidGcpProjectId(projectId: string) { - return GCP_PROJECT_ID_PATTERN.test(projectId) -} - -export function renderByocSetupTemplate({ - principal, - projectId, - region, - template, -}: { - principal: string - projectId: string - region: string - template: string -}) { - return template - .replaceAll('{{PROJECT_ID}}', projectId) - .replaceAll('{{REGION}}', region) - .replaceAll('{{E2B_PRINCIPAL}}', principal) -} diff --git a/src/features/dashboard/connections/catalog.ts b/src/features/dashboard/connections/catalog.ts deleted file mode 100644 index 986bb368c..000000000 --- a/src/features/dashboard/connections/catalog.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { DevelopmentConnection } from '@/core/modules/feature-flags/definitions' - -export type ConnectionCatalogEntry = DevelopmentConnection - -export const DECLARED_CONNECTIONS: readonly ConnectionCatalogEntry[] = [] - -export function mergeConnectionCatalog( - declared: readonly ConnectionCatalogEntry[], - additional: readonly ConnectionCatalogEntry[] -): ConnectionCatalogEntry[] { - const catalog = [...declared] - const templates = new Set(declared.map((connection) => connection.template)) - - for (const connection of additional) { - if (templates.has(connection.template)) { - continue - } - - catalog.push(connection) - templates.add(connection.template) - } - - return catalog -} diff --git a/src/features/dashboard/layouts/footer.tsx b/src/features/dashboard/layouts/footer.tsx index 33e6b0c88..40460677a 100644 --- a/src/features/dashboard/layouts/footer.tsx +++ b/src/features/dashboard/layouts/footer.tsx @@ -3,7 +3,6 @@ import { usePathname } from 'next/navigation' import { getDashboardLayoutConfig } from '@/configs/layout' import { useDashboard } from '@/features/dashboard/context' -import TeamBlockedIndicator from './team-blocked-indicator' interface DashboardLayoutFooterProps { statusBanner: React.ReactNode @@ -28,8 +27,6 @@ export default function DashboardLayoutFooter({ {`:${footerTitle}`} - - {statusBanner ? (
{statusBanner}
) : null} diff --git a/src/features/dashboard/layouts/layout.tsx b/src/features/dashboard/layouts/layout.tsx index 16440c8ac..7a2c7ef35 100644 --- a/src/features/dashboard/layouts/layout.tsx +++ b/src/features/dashboard/layouts/layout.tsx @@ -1,8 +1,5 @@ -import { INCLUDE_STATUS_INDICATOR } from '@/configs/env-flags' -import { LiveSandboxCounterServer } from '../sandboxes/live-counter.server' import DashboardLayoutFooter from './footer' import DashboardLayoutHeader from './header' -import DashboardStatusBadgeServer from './status-indicator.server' import DashboardLayoutWrapper from './wrapper' interface DashboardLayoutProps { @@ -10,21 +7,12 @@ interface DashboardLayoutProps { params: Promise<{ teamSlug: string }> } -export default function DashboardLayout({ - children, - params, -}: DashboardLayoutProps) { +export default function DashboardLayout({ children }: DashboardLayoutProps) { return (
- - - + {children} - : null - } - /> +
) } diff --git a/src/features/dashboard/layouts/status-indicator.server.tsx b/src/features/dashboard/layouts/status-indicator.server.tsx deleted file mode 100644 index 11983c048..000000000 --- a/src/features/dashboard/layouts/status-indicator.server.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import 'server-only' - -import { cacheLife } from 'next/cache' -import Link from 'next/link' -import { l } from '@/core/shared/clients/logger/logger' -import { LiveDot } from '@/ui/live' -import { - type AggregateState, - getStatusPageStateFromSummary, - type IncidentIOStatusPageSummaryResponse, - STATUS_PAGE_LINK_URL, - STATUS_PAGE_SUMMARY_URL, -} from './status-indicator' - -const STATUS_PAGE_FETCH_TIMEOUT_MS = 5_000 -const STATUS_PAGE_CACHE_SECONDS = 30 - -interface StatusUI { - label: string - dotCircleClassName: string - dotClassName: string -} - -function getStatusUI(state: AggregateState): StatusUI { - switch (state) { - case 'operational': - return { - label: 'ALL SYSTEMS OPERATIONAL', - dotCircleClassName: 'bg-accent-positive-highlight/30', - dotClassName: 'bg-accent-positive-highlight', - } - case 'degraded': - return { - label: 'SYSTEMS DEGRADED', - dotCircleClassName: 'bg-accent-warning-highlight/30', - dotClassName: 'bg-accent-warning-highlight', - } - case 'downtime': - return { - label: 'DOWNTIME', - dotCircleClassName: 'bg-accent-error-highlight/30', - dotClassName: 'bg-accent-error-highlight', - } - case 'maintenance': - return { - label: 'MAINTENANCE', - dotCircleClassName: 'bg-accent-info-highlight/30', - dotClassName: 'bg-accent-info-highlight', - } - default: - return { - label: 'UNKNOWN SYSTEM STATUS', - dotCircleClassName: 'bg-icon-secondary/30', - dotClassName: 'bg-icon-secondary', - } - } -} - -async function getStatusPageState(): Promise { - 'use cache' - cacheLife({ - stale: STATUS_PAGE_CACHE_SECONDS, - revalidate: STATUS_PAGE_CACHE_SECONDS, - expire: STATUS_PAGE_CACHE_SECONDS * 2, - }) - - try { - const response = await fetch(STATUS_PAGE_SUMMARY_URL, { - cache: 'force-cache', - next: { revalidate: STATUS_PAGE_CACHE_SECONDS }, - signal: AbortSignal.timeout(STATUS_PAGE_FETCH_TIMEOUT_MS), - }) - - if (!response.ok) { - l.error( - { - key: 'dashboard:status-indicator:get_status_page_state:fetch_failed', - error: response.statusText, - status: response.status, - }, - `Failed to fetch status page state: ${response.statusText}` - ) - return 'unknown' - } - - const data = (await response.json()) as IncidentIOStatusPageSummaryResponse - return getStatusPageStateFromSummary(data) - } catch { - return 'unknown' - } -} - -export default async function DashboardStatusBadgeServer() { - const status = await getStatusPageState() - const ui = getStatusUI(status) - - return ( - - - - {ui.label} - - - ) -} diff --git a/src/features/dashboard/layouts/status-indicator.ts b/src/features/dashboard/layouts/status-indicator.ts deleted file mode 100644 index 280aa5edd..000000000 --- a/src/features/dashboard/layouts/status-indicator.ts +++ /dev/null @@ -1,82 +0,0 @@ -export type AggregateState = - | 'operational' - | 'degraded' - | 'downtime' - | 'maintenance' - | 'unknown' - -export interface IncidentIOStatusPageSummaryResponse { - status?: { - indicator?: string - } - components?: Array<{ - status?: string - }> - scheduled_maintenances?: Array<{ - status?: string - }> -} - -export const STATUS_PAGE_LINK_URL = 'https://status.e2b.dev' -const INCIDENT_IO_STATUS_PAGE_URL = 'https://statuspage.incident.io/e2b-service' -export const STATUS_PAGE_SUMMARY_URL = `${INCIDENT_IO_STATUS_PAGE_URL}/api/v2/summary.json` - -function stateFromIndicator(indicator: string | undefined) { - if (indicator === 'none') return 'operational' - if (indicator === 'minor') return 'degraded' - if (indicator === 'major') return 'degraded' - if (indicator === 'critical') return 'downtime' - if (indicator === 'maintenance') return 'maintenance' - - return undefined -} - -function getWorstComponentState( - components: IncidentIOStatusPageSummaryResponse['components'] -): AggregateState | undefined { - const componentStatuses = - components?.map((component) => component.status) ?? [] - - if (componentStatuses.includes('major_outage')) return 'downtime' - if (componentStatuses.includes('partial_outage')) return 'degraded' - if (componentStatuses.includes('degraded_performance')) return 'degraded' - if (componentStatuses.includes('under_maintenance')) return 'maintenance' - - return undefined -} - -function hasMaintenanceInProgress( - maintenances: IncidentIOStatusPageSummaryResponse['scheduled_maintenances'] -): boolean { - return ( - maintenances?.some( - (maintenance) => - maintenance.status === 'in_progress' || - maintenance.status === 'maintenance_in_progress' - ) ?? false - ) -} - -export function getStatusPageStateFromSummary( - data: IncidentIOStatusPageSummaryResponse -): AggregateState { - const indicatorState = stateFromIndicator(data.status?.indicator) - const componentState = getWorstComponentState(data.components) - - if (indicatorState === 'downtime' || componentState === 'downtime') - return 'downtime' - - if (indicatorState === 'degraded' || componentState === 'degraded') - return 'degraded' - - if ( - indicatorState === 'maintenance' || - componentState === 'maintenance' || - hasMaintenanceInProgress(data.scheduled_maintenances) - ) - return 'maintenance' - - if (indicatorState === 'operational') return 'operational' - - return 'unknown' -} diff --git a/src/features/dashboard/layouts/team-blocked-indicator.tsx b/src/features/dashboard/layouts/team-blocked-indicator.tsx deleted file mode 100644 index d0d50b62d..000000000 --- a/src/features/dashboard/layouts/team-blocked-indicator.tsx +++ /dev/null @@ -1,156 +0,0 @@ -'use client' - -import Link from 'next/link' -import { useEffect, useMemo, useState } from 'react' -import { useSessionStorage } from 'usehooks-ts' -import { TEAM_BLOCKED_REASONS } from '@/core/modules/teams/constants' -import type { TeamBlockedReason } from '@/core/modules/teams/models' -import { useDashboard } from '@/features/dashboard/context' -import { Button } from '@/ui/primitives/button' -import { BlockIcon } from '@/ui/primitives/icons' -import { - MissingPaymentMethodDialog, - VerificationRequiredDialog, -} from '../team-blocked' -import { getBlockedDialogStorageKey } from '../team-blocked/team-blocked-dialog-storage' -import { - type BlockedMessage, - getBlockedMessage, -} from '../team-blocked/team-blocked-message' - -interface TeamBlockedIndicatorContentProps { - message: BlockedMessage - onDialogAction?: () => void -} - -const TeamBlockedIndicatorContent = ({ - message, - onDialogAction, -}: TeamBlockedIndicatorContentProps) => { - return ( -
- - - {message.text} - {message.cta && ( - <> - {' '} - {message.href ? ( - - {message.cta} - - ) : onDialogAction ? ( - - ) : null} - - )} - -
- ) -} - -const useBlockedMessage = ( - slug: string, - blockedReason: string | null -): BlockedMessage => { - return useMemo( - () => getBlockedMessage(slug, blockedReason), - [slug, blockedReason] - ) -} - -interface TeamBlockedDialogControllerProps { - blockedReasonDialog: TeamBlockedReason - message: BlockedMessage -} - -const TeamBlockedDialogController = ({ - blockedReasonDialog, - message, -}: TeamBlockedDialogControllerProps) => { - const { team } = useDashboard() - const [openDialog, setOpenDialog] = useState(null) - const dismissedStorageKey = getBlockedDialogStorageKey( - team.slug, - blockedReasonDialog - ) - const [hasDismissedDialog, setHasDismissedDialog] = useSessionStorage( - dismissedStorageKey, - false - ) - - useEffect(() => { - if (hasDismissedDialog) { - setOpenDialog(null) - return - } - - setOpenDialog(blockedReasonDialog) - }, [blockedReasonDialog, hasDismissedDialog]) - - const handleDialogAction = () => { - setOpenDialog(blockedReasonDialog) - } - - const handleDialogOpenChange = (open: boolean, dialog: TeamBlockedReason) => { - setOpenDialog(open ? dialog : null) - } - - const handleDialogDismiss = () => { - setHasDismissedDialog(true) - setOpenDialog(null) - } - - return ( - <> - - { - handleDialogOpenChange(open, TEAM_BLOCKED_REASONS.missingPayment) - }} - onDismiss={handleDialogDismiss} - /> - { - handleDialogOpenChange(open, TEAM_BLOCKED_REASONS.verification) - }} - onDismiss={handleDialogDismiss} - /> - - ) -} - -export default function TeamBlockedIndicator() { - const { team } = useDashboard() - const message = useBlockedMessage(team.slug, team.blockedReason) - const reason = team.blockedReason?.toLowerCase() ?? '' - const blockedReasonDialog: TeamBlockedReason | null = - Object.values(TEAM_BLOCKED_REASONS).find((blockedReason) => - reason.includes(blockedReason) - ) ?? null - - if (!team.isBlocked) return null - - if (!blockedReasonDialog) - return - - return ( - - ) -} diff --git a/src/features/dashboard/limits/alert-ascii-icon.tsx b/src/features/dashboard/limits/alert-ascii-icon.tsx deleted file mode 100644 index 4a3b48d1e..000000000 --- a/src/features/dashboard/limits/alert-ascii-icon.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { AsciiIcon } from './ascii-icon' - -const INACTIVE_LINES = [ - ' ', - ' ', - ' ', - ' ', - ' -********- ', - ' -*-- -*- ', - ' -*- -*- ', - ' **- -** ', - ' -*- -*- ', - ' -**----------**- ', - ' ----**----**---- ', - ' -******- ', - ' ', - ' ', - ' ', - ' ', -] - -const ACTIVE_LINES = [ - ' ', - ' ', - ' ', - ' ', - ' -********- ', - [ - { text: ' -*-- ' }, - { className: 'text-accent-main-highlight', text: '-' }, - { text: '*- ' }, - ], - ' -*- -*- ', - [ - { text: ' *' }, - { className: 'text-accent-main-highlight', text: '*' }, - { text: '- -' }, - { className: 'text-accent-main-highlight', text: '*' }, - { text: '* ' }, - ], - ' -*- -*- ', - [ - { text: ' -*' }, - { className: 'text-accent-main-highlight', text: '*' }, - { text: '----------**- ' }, - ], - [ - { text: ' ----**' }, - { className: 'text-accent-main-highlight', text: '-' }, - { text: '---**---- ' }, - ], - ' -******- ', - ' ', - ' ', - ' ', - ' ', -] - -interface AlertAsciiIconProps { - active?: boolean - className?: string -} - -export const AlertAsciiIcon = ({ - active = false, - className, -}: AlertAsciiIconProps) => ( - -) diff --git a/src/features/dashboard/limits/ascii-icon.tsx b/src/features/dashboard/limits/ascii-icon.tsx deleted file mode 100644 index addc491b6..000000000 --- a/src/features/dashboard/limits/ascii-icon.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { type CSSProperties, Fragment } from 'react' -import { cn } from '@/lib/utils' - -type AsciiLineSegment = { - className?: string - text: string -} - -type AsciiLine = string | readonly AsciiLineSegment[] - -const TEXT_STYLE = { - fontFamily: 'var(--font-mono)', - fontFeatureSettings: "'ss03' 1", - fontSize: '3.802px', - fontWeight: 600, - letterSpacing: '-0.038px', - lineHeight: '4px', -} satisfies CSSProperties - -interface AsciiIconProps { - className?: string - lines: readonly AsciiLine[] -} - -const renderAsciiLine = (line: AsciiLine) => { - if (typeof line === 'string') return line - - return line.map((segment, index) => ( - - {segment.className ? ( - {segment.text} - ) : ( - segment.text - )} - - )) -} - -export const AsciiIcon = ({ className, lines }: AsciiIconProps) => ( -
-
- {lines.map((line, index) => ( -

- {renderAsciiLine(line)} -

- ))} -
-
-) diff --git a/src/features/dashboard/limits/currency-input.ts b/src/features/dashboard/limits/currency-input.ts deleted file mode 100644 index a31cd82f9..000000000 --- a/src/features/dashboard/limits/currency-input.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { z } from 'zod' - -// Validates a string as a positive whole USD amount. Example: "1250" -> valid, "abc" -> invalid. -const CurrencyInputSchema = z - .string() - .trim() - .min(1, 'Enter a value.') - .regex(/^\d+$/, 'Enter a whole USD amount.') - .refine((value) => Number(value) >= 1, 'Value must be at least 1.') - -// Removes non-digits from a USD draft value. Example: "$1,250" -> "1250". -const sanitizeCurrencyInput = (value: string) => value.replace(/\D+/g, '') - -export { CurrencyInputSchema, sanitizeCurrencyInput } diff --git a/src/features/dashboard/limits/focus-block-input.ts b/src/features/dashboard/limits/focus-block-input.ts deleted file mode 100644 index 5f3baae02..000000000 --- a/src/features/dashboard/limits/focus-block-input.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { MouseEvent } from 'react' - -const INTERACTIVE_ELEMENT_SELECTOR = - 'button, input, textarea, select, a, [role="button"], [data-no-input-focus]' - -// Focuses the first editable input in a block, e.g. block click -> input focused, button click -> unchanged. -const focusBlockInputOnMouseDown = (event: MouseEvent) => { - if (!(event.target instanceof Element)) return - if (event.target.closest(INTERACTIVE_ELEMENT_SELECTOR)) return - - const input = event.currentTarget.querySelector('input') - if (!(input instanceof HTMLInputElement)) return - if (input.disabled || input.readOnly) return - - event.preventDefault() - input.focus() - - const cursorPosition = input.value.length - input.setSelectionRange(cursorPosition, cursorPosition) -} - -export { focusBlockInputOnMouseDown } diff --git a/src/features/dashboard/limits/limit-ascii-icon.tsx b/src/features/dashboard/limits/limit-ascii-icon.tsx deleted file mode 100644 index 7e9520237..000000000 --- a/src/features/dashboard/limits/limit-ascii-icon.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { AsciiIcon } from './ascii-icon' - -const INACTIVE_LINES = [ - ' ', - ' ', - ' ', - ' ', - ' ---**--- ', - ' -**------**- ', - ' -**- - -**- ', - ' ** -**- ** ', - ' *- --- -* ', - ' ** ** ', - ' -**- -**- ', - ' -- -- ', - ' ', - ' ', - ' ', - ' ', -] - -const ACTIVE_LINES = [ - ' ', - ' ', - ' ', - ' ', - ' ---**--- ', - [ - { text: ' -**-----' }, - { className: 'text-accent-secondary-error-highlight', text: '-' }, - { text: '**- ' }, - ], - ' -**- - -**- ', - [ - { text: ' ' }, - { className: 'text-accent-main-highlight', text: '**' }, - { text: ' -**- ** ' }, - ], - [ - { text: ' *- -' }, - { className: 'text-accent-main-highlight', text: '-' }, - { text: '- -* ' }, - ], - ' ** ** ', - ' -**- -**- ', - [ - { text: ' -- ' }, - { className: 'text-accent-main-highlight', text: '-' }, - { text: '- ' }, - ], - ' ', - ' ', - ' ', - ' ', -] - -interface LimitAsciiIconProps { - active?: boolean - className?: string -} - -export const LimitAsciiIcon = ({ - active = false, - className, -}: LimitAsciiIconProps) => ( - -) diff --git a/src/features/dashboard/limits/remove-usage-limit-dialog.tsx b/src/features/dashboard/limits/remove-usage-limit-dialog.tsx deleted file mode 100644 index bafa922bd..000000000 --- a/src/features/dashboard/limits/remove-usage-limit-dialog.tsx +++ /dev/null @@ -1,129 +0,0 @@ -'use client' - -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { useState } from 'react' -import type { BillingLimit } from '@/core/modules/billing/models' -import { - defaultErrorToast, - defaultSuccessToast, - useToast, -} from '@/lib/hooks/use-toast' -import { useTRPC } from '@/trpc/client' -import { Button } from '@/ui/primitives/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogTitle, - DialogTrigger, -} from '@/ui/primitives/dialog' -import { RemoveIcon } from '@/ui/primitives/icons' - -interface RemoveUsageLimitDialogProps { - disabled?: boolean - hideTrigger?: boolean - onRemoved: () => void - onOpenChange?: (open: boolean) => void - open?: boolean - teamSlug: string -} - -export const RemoveUsageLimitDialog = ({ - disabled = false, - hideTrigger = false, - onRemoved, - onOpenChange, - open, - teamSlug, -}: RemoveUsageLimitDialogProps) => { - const [internalIsOpen, setInternalIsOpen] = useState(false) - const { toast } = useToast() - const trpc = useTRPC() - const queryClient = useQueryClient() - const isControlled = open !== undefined - const isOpen = isControlled ? open : internalIsOpen - - const setIsOpen = (nextOpen: boolean) => { - if (!isControlled) setInternalIsOpen(nextOpen) - onOpenChange?.(nextOpen) - } - - const limitsQueryKey = trpc.billing.getLimits.queryOptions({ - teamSlug, - }).queryKey - - const clearLimitMutation = useMutation( - trpc.billing.clearLimit.mutationOptions({ - onSuccess: () => { - queryClient.setQueryData( - limitsQueryKey, - (limits) => { - if (!limits) return limits - return { ...limits, limit_amount_gte: null } - } - ) - toast(defaultSuccessToast('Limit removed successfully.')) - onRemoved() - setIsOpen(false) - queryClient.invalidateQueries({ queryKey: limitsQueryKey }) - }, - onError: (error) => { - toast( - defaultErrorToast(error.message || 'Failed to remove billing limit.') - ) - }, - }) - ) - - return ( - - {!hideTrigger && ( - - - - )} - -
-
- Remove usage limit? - - API limits will be removed and usage will become uncapped - -
-
- - -
-
-
-
- ) -} diff --git a/src/features/dashboard/limits/set-usage-limit-dialog.tsx b/src/features/dashboard/limits/set-usage-limit-dialog.tsx deleted file mode 100644 index 45d0f1b07..000000000 --- a/src/features/dashboard/limits/set-usage-limit-dialog.tsx +++ /dev/null @@ -1,86 +0,0 @@ -'use client' - -import { Button } from '@/ui/primitives/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogTitle, - DialogTrigger, -} from '@/ui/primitives/dialog' -import { WarningIcon } from '@/ui/primitives/icons' - -interface SetUsageLimitDialogProps { - confirmDisabled: boolean - loading: boolean - onConfirm: () => void - onOpenChange: (open: boolean) => void - open: boolean - triggerDisabled: boolean - title: string -} - -export const SetUsageLimitDialog = ({ - confirmDisabled, - loading, - onConfirm, - onOpenChange, - open, - title, - triggerDisabled, -}: SetUsageLimitDialogProps) => { - return ( - - - - - -
-
-
-
- -
- {title} -
- - If your API usage hits this limit, all requests—including sandbox - creation—will be blocked. - -

- This may disrupt your services. -

-
-
- - -
-
-
-
- ) -} diff --git a/src/features/dashboard/limits/usage-alert-form.tsx b/src/features/dashboard/limits/usage-alert-form.tsx deleted file mode 100644 index 994d41def..000000000 --- a/src/features/dashboard/limits/usage-alert-form.tsx +++ /dev/null @@ -1,282 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import type { FormEvent } from 'react' -import { useEffect, useRef, useState } from 'react' -import { Controller, useForm } from 'react-hook-form' -import { z } from 'zod' -import type { BillingLimit } from '@/core/modules/billing/models' -import { - defaultErrorToast, - defaultSuccessToast, - useToast, -} from '@/lib/hooks/use-toast' -import { cn } from '@/lib/utils' -import { formatNumber } from '@/lib/utils/formatting' -import { useTRPC } from '@/trpc/client' -import { Button } from '@/ui/primitives/button' -import { EditIcon, RemoveIcon } from '@/ui/primitives/icons' -import { Input } from '@/ui/primitives/input' -import { CurrencyInputSchema, sanitizeCurrencyInput } from './currency-input' -import { focusBlockInputOnMouseDown } from './focus-block-input' - -const AlertFormSchema = z.object({ - amount: CurrencyInputSchema, -}) - -type AlertFormValues = z.infer - -interface UsageAlertFormProps { - className?: string - originalValue: number | null - teamSlug: string -} - -export const UsageAlertForm = ({ - className, - originalValue, - teamSlug, -}: UsageAlertFormProps) => { - const hasMountedRef = useRef(false) - const inputRef = useRef(null) - const [isEditing, setIsEditing] = useState(originalValue === null) - const { toast } = useToast() - const trpc = useTRPC() - const queryClient = useQueryClient() - const formattedOriginalValue = - originalValue === null ? '' : formatNumber(originalValue) - - const limitsQueryKey = trpc.billing.getLimits.queryOptions({ - teamSlug, - }).queryKey - - const form = useForm({ - resolver: zodResolver(AlertFormSchema), - mode: 'onChange', - defaultValues: { - amount: formattedOriginalValue, - }, - }) - - const draftValue = form.watch('amount') - - useEffect(() => { - form.reset({ - amount: formattedOriginalValue, - }) - setIsEditing(originalValue === null) - }, [formattedOriginalValue, originalValue, form.reset]) - - useEffect(() => { - if (!hasMountedRef.current) { - hasMountedRef.current = true - return - } - - if (!isEditing) return - inputRef.current?.focus() - const inputLength = inputRef.current?.value.length ?? 0 - inputRef.current?.setSelectionRange(inputLength, inputLength) - }, [isEditing]) - - const setAlertMutation = useMutation( - trpc.billing.setLimit.mutationOptions({ - onSuccess: (_, variables) => { - queryClient.setQueryData( - limitsQueryKey, - (limits) => { - if (!limits) return limits - return { ...limits, alert_amount_gte: variables.value } - } - ) - toast(defaultSuccessToast('Alert set successfully.')) - form.reset({ amount: formatNumber(variables.value) }) - setIsEditing(false) - queryClient.invalidateQueries({ queryKey: limitsQueryKey }) - }, - onError: (error) => { - toast( - defaultErrorToast(error.message || 'Failed to save billing alert.') - ) - }, - }) - ) - - const clearAlertMutation = useMutation( - trpc.billing.clearLimit.mutationOptions({ - onSuccess: () => { - queryClient.setQueryData( - limitsQueryKey, - (limits) => { - if (!limits) return limits - return { ...limits, alert_amount_gte: null } - } - ) - toast(defaultSuccessToast('Alert removed successfully.')) - form.reset({ amount: '' }) - setIsEditing(true) - queryClient.invalidateQueries({ queryKey: limitsQueryKey }) - }, - onError: (error) => { - toast( - defaultErrorToast(error.message || 'Failed to remove billing alert.') - ) - }, - }) - ) - - const clearAlert = (): void => - clearAlertMutation.mutate({ teamSlug, type: 'alert' }) - - const isMutating = setAlertMutation.isPending || clearAlertMutation.isPending - const nextValue = form.formState.isValid ? Number(draftValue) : null - const isClearIntent = - isEditing && originalValue !== null && draftValue.length === 0 - const canSave = - isEditing && - (isClearIntent || (nextValue !== null && nextValue !== originalValue)) && - !isMutating - const isInputEditable = isEditing || originalValue === null - const shouldShowCancel = - isEditing && (originalValue !== null || draftValue.length > 0) - - const startEditing = (): void => { - if (originalValue === null) return - form.reset({ amount: formattedOriginalValue }) - setIsEditing(true) - } - - const handleCancel = (): void => { - const activeElement = document.activeElement - if (activeElement instanceof HTMLElement) activeElement.blur() - inputRef.current?.blur() - - if (originalValue === null) { - form.reset({ amount: '' }) - return - } - - form.reset({ amount: formattedOriginalValue }) - setIsEditing(false) - } - - const handleSubmit = async (event: FormEvent) => { - event.preventDefault() - if (!isEditing) return - - if (isClearIntent) { - clearAlert() - return - } - - const isValid = await form.trigger() - if (!isValid) { - toast( - defaultErrorToast( - form.formState.errors.amount?.message || - 'Enter a billing alert amount.' - ) - ) - return - } - - const value = Number(form.getValues('amount')) - if (value === originalValue) return - setAlertMutation.mutate({ teamSlug, type: 'alert', value }) - } - - return ( -
-
- $ - ( - { - field.ref(el) - inputRef.current = el - }} - className="prose-value-big text-fg h-auto border-0 bg-transparent px-0 py-0 font-mono shadow-none placeholder:text-fg-tertiary hover:bg-transparent focus:bg-transparent focus:[border-bottom:0] focus:outline-none" - disabled={isMutating} - inputMode="numeric" - onChange={(event) => { - if (!isEditing) return - field.onChange(sanitizeCurrencyInput(event.target.value)) - }} - onBlur={field.onBlur} - onMouseDown={(event) => { - if (isInputEditable) return - event.preventDefault() - }} - placeholder="--" - readOnly={!isEditing && originalValue !== null} - tabIndex={isInputEditable ? undefined : -1} - value={field.value} - /> - )} - /> -
-
- {originalValue !== null && !isEditing ? ( - <> - - - - ) : ( - <> - {shouldShowCancel && ( - - )} - - - )} -
-
- ) -} diff --git a/src/features/dashboard/limits/usage-alert-section.tsx b/src/features/dashboard/limits/usage-alert-section.tsx deleted file mode 100644 index 3dea37b7c..000000000 --- a/src/features/dashboard/limits/usage-alert-section.tsx +++ /dev/null @@ -1,62 +0,0 @@ -'use client' - -import { cn } from '@/lib/utils' -import { formatNumber } from '@/lib/utils/formatting' -import { AlertAsciiIcon } from './alert-ascii-icon' -import { UsageAlertForm } from './usage-alert-form' - -interface UsageAlertSectionProps { - className?: string - email: string - teamSlug: string - value: number | null -} - -const UsageAlertSectionInfo = ({ - email, - value, -}: Pick) => { - const thresholdText = - value === null ? 'this threshold' : `the $${formatNumber(value)} threshold` - - return ( -

- Informative alert will be sent to - {email} - - {' when '} - - {thresholdText} is reached - -

- ) -} - -export const UsageAlertSection = ({ - className, - email, - teamSlug, - value, -}: UsageAlertSectionProps) => { - const isValueSet = value !== null - - return ( -
-

Usage Alert

-
-
- -
-
- -
-
- -
- ) -} diff --git a/src/features/dashboard/limits/usage-limit-form.tsx b/src/features/dashboard/limits/usage-limit-form.tsx deleted file mode 100644 index f0affc737..000000000 --- a/src/features/dashboard/limits/usage-limit-form.tsx +++ /dev/null @@ -1,286 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import type { FormEvent } from 'react' -import { useEffect, useRef, useState } from 'react' -import { Controller, useForm } from 'react-hook-form' -import { z } from 'zod' -import type { BillingLimit } from '@/core/modules/billing/models' -import { - defaultErrorToast, - defaultSuccessToast, - useToast, -} from '@/lib/hooks/use-toast' -import { cn } from '@/lib/utils' -import { formatNumber } from '@/lib/utils/formatting' -import { useTRPC } from '@/trpc/client' -import { Button } from '@/ui/primitives/button' -import { EditIcon, RemoveIcon } from '@/ui/primitives/icons' -import { Input } from '@/ui/primitives/input' -import { CurrencyInputSchema, sanitizeCurrencyInput } from './currency-input' -import { focusBlockInputOnMouseDown } from './focus-block-input' -import { RemoveUsageLimitDialog } from './remove-usage-limit-dialog' -import { SetUsageLimitDialog } from './set-usage-limit-dialog' - -const limitFormSchema = z.object({ - amount: CurrencyInputSchema, -}) - -type LimitFormValues = z.infer - -interface UsageLimitFormProps { - className?: string - originalValue: number | null - teamSlug: string -} - -export const UsageLimitForm = ({ - className, - originalValue, - teamSlug, -}: UsageLimitFormProps) => { - const inputRef = useRef(null) - const [isEditing, setIsEditing] = useState(originalValue === null) - const [isRemoveDialogOpen, setIsRemoveDialogOpen] = useState(false) - const [isSetDialogOpen, setIsSetDialogOpen] = useState(false) - const { toast } = useToast() - const trpc = useTRPC() - const queryClient = useQueryClient() - const formattedOriginalValue = - originalValue === null ? '' : formatNumber(originalValue) - - const limitsQueryKey = trpc.billing.getLimits.queryOptions({ - teamSlug, - }).queryKey - - const form = useForm({ - resolver: zodResolver(limitFormSchema), - mode: 'onChange', - defaultValues: { - amount: formattedOriginalValue, - }, - }) - - const draftValue = form.watch('amount') - - useEffect(() => { - form.reset({ - amount: formattedOriginalValue, - }) - setIsEditing(originalValue === null) - }, [formattedOriginalValue, originalValue, form.reset]) - - useEffect(() => { - if (!isEditing) return - inputRef.current?.focus() - const inputLength = inputRef.current?.value.length ?? 0 - inputRef.current?.setSelectionRange(inputLength, inputLength) - }, [isEditing]) - - const setLimitMutation = useMutation( - trpc.billing.setLimit.mutationOptions({ - onSuccess: (_, variables) => { - queryClient.setQueryData( - limitsQueryKey, - (limits) => { - if (!limits) return limits - return { ...limits, limit_amount_gte: variables.value } - } - ) - toast(defaultSuccessToast('Limit set successfully.')) - form.reset({ amount: formatNumber(variables.value) }) - setIsEditing(false) - setIsSetDialogOpen(false) - queryClient.invalidateQueries({ queryKey: limitsQueryKey }) - }, - onError: (error) => { - toast( - defaultErrorToast(error.message || 'Failed to save billing limit.') - ) - }, - }) - ) - - const nextValue = form.formState.isValid ? Number(draftValue) : null - const isMutating = setLimitMutation.isPending - const isRemoveIntent = - isEditing && originalValue !== null && draftValue.length === 0 - const canSave = - isEditing && - form.formState.isValid && - nextValue !== originalValue && - !isMutating - const isInputEditable = isEditing || originalValue === null - const shouldShowCancel = - isEditing && (originalValue !== null || draftValue.length > 0) - const setLimitTitle = `Set $${nextValue === null ? '--' : formatNumber(nextValue)} usage limit?` - - const startEditing = (): void => { - if (originalValue === null) return - form.reset({ amount: formattedOriginalValue }) - setIsEditing(true) - } - - const openRemoveDialog = (): void => setIsRemoveDialogOpen(true) - - const handleCancel = (): void => { - const activeElement = document.activeElement - if (activeElement instanceof HTMLElement) activeElement.blur() - inputRef.current?.blur() - - if (originalValue === null) { - form.reset({ amount: '' }) - return - } - - form.reset({ amount: formattedOriginalValue }) - setIsEditing(false) - } - - const handleSetConfirm = (): void => { - if (!form.formState.isValid) return - const value = Number(form.getValues('amount')) - if (value === originalValue) return - setLimitMutation.mutate({ teamSlug, type: 'limit', value }) - } - - const handleSubmit = async (event: FormEvent) => { - event.preventDefault() - if (!isEditing) return - - if (isRemoveIntent) { - setIsRemoveDialogOpen(true) - return - } - - const isValid = await form.trigger() - if (!isValid) { - toast( - defaultErrorToast( - form.formState.errors.amount?.message || - 'Enter a billing limit amount.' - ) - ) - return - } - - const value = Number(form.getValues('amount')) - if (value === originalValue || isMutating) return - setIsSetDialogOpen(true) - } - - return ( -
-
- $ - ( - { - field.ref(el) - inputRef.current = el - }} - className="prose-value-big text-fg h-auto border-0 bg-transparent px-0 py-0 font-mono shadow-none placeholder:text-fg-tertiary hover:bg-transparent focus:bg-transparent focus:[border-bottom:0] focus:outline-none" - disabled={isMutating} - inputMode="numeric" - onChange={(event) => { - if (!isEditing) return - field.onChange(sanitizeCurrencyInput(event.target.value)) - }} - onBlur={field.onBlur} - onMouseDown={(event) => { - if (isInputEditable) return - event.preventDefault() - }} - placeholder="--" - readOnly={!isEditing && originalValue !== null} - tabIndex={isInputEditable ? undefined : -1} - value={field.value} - /> - )} - /> -
-
- {originalValue !== null && !isEditing ? ( - <> - - - - ) : ( - <> - {shouldShowCancel && ( - - )} - {isRemoveIntent ? ( - - ) : ( - - )} - - )} -
- { - form.reset({ amount: '' }) - setIsEditing(true) - }} - /> - - ) -} diff --git a/src/features/dashboard/limits/usage-limit-section.tsx b/src/features/dashboard/limits/usage-limit-section.tsx deleted file mode 100644 index e321eaab8..000000000 --- a/src/features/dashboard/limits/usage-limit-section.tsx +++ /dev/null @@ -1,79 +0,0 @@ -'use client' - -import { cn } from '@/lib/utils' -import { formatNumber } from '@/lib/utils/formatting' -import { AlertIcon, WarningIcon } from '@/ui/primitives/icons' -import { LimitAsciiIcon } from './limit-ascii-icon' -import { UsageLimitForm } from './usage-limit-form' - -interface UsageLimitSectionProps { - className?: string - email: string - teamSlug: string - value: number | null -} - -const UsageLimitSectionInfo = ({ - email, - value, -}: Pick) => { - const isValueSet = value !== null - const limitMessage = isValueSet - ? `All API requests are blocked after reaching $${formatNumber(value)} in the current billing period` - : 'All API requests are blocked after reaching this limit in the current billing period' - - return ( -
-

- - {limitMessage} -

-

- - - Automatic alerts at 50%, 80%, 90% and 100% sent to{' '} - {email} - -

-
- ) -} - -export const UsageLimitSection = ({ - className, - email, - teamSlug, - value, -}: UsageLimitSectionProps) => { - const isValueSet = value !== null - - return ( -
-

Usage Limit

-
-
- -
-
- -
-
- -
- ) -} diff --git a/src/features/dashboard/limits/usage-limits.tsx b/src/features/dashboard/limits/usage-limits.tsx deleted file mode 100644 index 7cc81d210..000000000 --- a/src/features/dashboard/limits/usage-limits.tsx +++ /dev/null @@ -1,65 +0,0 @@ -'use client' - -import { useQuery } from '@tanstack/react-query' -import { useRouteParams } from '@/lib/hooks/use-route-params' -import { cn } from '@/lib/utils' -import { useTRPC } from '@/trpc/client' -import { Skeleton } from '@/ui/primitives/skeleton' -import { useDashboard } from '../context' -import { UsageAlertSection } from './usage-alert-section' -import { UsageLimitSection } from './usage-limit-section' - -interface UsageLimitsProps { - className?: string -} - -export const UsageLimits = ({ className }: UsageLimitsProps) => { - const { teamSlug } = useRouteParams<'/dashboard/[teamSlug]/limits'>() - const { team } = useDashboard() - const trpc = useTRPC() - - const { data: limits, isLoading } = useQuery({ - ...trpc.billing.getLimits.queryOptions({ teamSlug }), - throwOnError: true, - }) - - return ( -
- {isLoading || !limits ? ( - <> - - - - ) : ( - <> - - - - )} -
- ) -} - -const LimitsSectionSkeleton = () => ( -
- - -
- - -
-
-) diff --git a/src/features/dashboard/members/add-member-dialog.tsx b/src/features/dashboard/members/add-member-dialog.tsx deleted file mode 100644 index cfb47eee1..000000000 --- a/src/features/dashboard/members/add-member-dialog.tsx +++ /dev/null @@ -1,34 +0,0 @@ -'use client' - -import { useState } from 'react' -import { AddMemberForm } from '@/features/dashboard/members/add-member-form' -import { Button } from '@/ui/primitives/button' -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/ui/primitives/dialog' -import { AddIcon } from '@/ui/primitives/icons' - -export const AddMemberDialog = () => { - const [open, setOpen] = useState(false) - - return ( - - - - - - - Add new member - - setOpen(false)} /> - - - ) -} diff --git a/src/features/dashboard/members/add-member-form.tsx b/src/features/dashboard/members/add-member-form.tsx deleted file mode 100644 index 454067797..000000000 --- a/src/features/dashboard/members/add-member-form.tsx +++ /dev/null @@ -1,116 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { useForm } from 'react-hook-form' -import { z } from 'zod' -import { - defaultErrorToast, - defaultSuccessToast, - useToast, -} from '@/lib/hooks/use-toast' -import { cn } from '@/lib/utils' -import { useTRPC } from '@/trpc/client' -import { Button } from '@/ui/primitives/button' -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/ui/primitives/form' -import { AddIcon } from '@/ui/primitives/icons' -import { Input } from '@/ui/primitives/input' -import { useDashboard } from '../context' - -const addMemberSchema = z.object({ - email: z.email(), -}) - -type AddMemberForm = z.infer - -interface AddMemberFormProps { - className?: string - onSuccess?: () => void -} - -export const AddMemberForm = ({ className, onSuccess }: AddMemberFormProps) => { - 'use no memo' - - const { team } = useDashboard() - const trpc = useTRPC() - const queryClient = useQueryClient() - const { toast } = useToast() - - const form = useForm({ - resolver: zodResolver(addMemberSchema), - mode: 'onChange', - defaultValues: { - email: '', - }, - }) - - const addMemberMutation = useMutation( - trpc.teams.addMember.mutationOptions({ - onSuccess: async () => { - await queryClient.invalidateQueries({ - queryKey: trpc.teams.members.queryKey({ teamSlug: team.slug }), - }) - toast(defaultSuccessToast('The member has been added to the project.')) - form.reset() - onSuccess?.() - }, - onError: (error) => { - toast(defaultErrorToast(error.message || 'An error occurred.')) - }, - }) - ) - - const onSubmit = (data: AddMemberForm) => { - if (!team) return - - addMemberMutation.mutate({ - teamSlug: team.slug, - email: data.email, - }) - } - - return ( -
- - ( - - Email - - - - - - )} - /> - - - - ) -} diff --git a/src/features/dashboard/members/member-table-row.tsx b/src/features/dashboard/members/member-table-row.tsx deleted file mode 100644 index 7a9b197d3..000000000 --- a/src/features/dashboard/members/member-table-row.tsx +++ /dev/null @@ -1,283 +0,0 @@ -'use client' - -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { useRouter } from 'next/navigation' -import { useState } from 'react' -import type { IconType } from 'react-icons' -import { FaGithub, FaGoogle } from 'react-icons/fa' -import { FiMail } from 'react-icons/fi' -import { PROTECTED_URLS } from '@/configs/urls' -import type { TeamMember } from '@/core/modules/teams/models' -import { getTeamDisplayName } from '@/core/modules/teams/utils' -import { UserAvatar } from '@/features/dashboard/shared' -import { useTimezone } from '@/features/dashboard/timezone' -import { - defaultErrorToast, - defaultSuccessToast, - useToast, -} from '@/lib/hooks/use-toast' -import { formatDate } from '@/lib/utils/formatting' -import { useTRPC } from '@/trpc/client' -import { E2BSquareBadge } from '@/ui/brand' -import { Avatar, AvatarFallback, AvatarImage } from '@/ui/primitives/avatar' -import { Badge } from '@/ui/primitives/badge' -import { IconButton } from '@/ui/primitives/icon-button' -import { RemoveIcon } from '@/ui/primitives/icons' -import { TableCell, TableRow } from '@/ui/primitives/table' -import { useDashboard } from '../context' -import { - shouldShowRemoveMemberAction, - wasAddedBySystem, -} from './member-table.utils' -import { RemoveMemberDialog } from './remove-member-dialog' - -interface TableRowProps { - member: TeamMember - addedByMember?: TeamMember -} - -type MemberProvider = { - key: string - label: string - Icon: IconType -} - -function normalizeProvider(provider: string): string { - const value = provider.toLowerCase() - if (value.includes('google')) return 'google' - if (value.includes('github')) return 'github' - if (value.includes('email')) return 'email' - return value -} - -function toMemberProvider(provider: string): MemberProvider | null { - const normalized = normalizeProvider(provider) - if (normalized === 'google') - return { key: normalized, label: 'Google', Icon: FaGoogle } - if (normalized === 'github') - return { key: normalized, label: 'GitHub', Icon: FaGithub } - if (normalized === 'email') - return { key: normalized, label: 'Email', Icon: FiMail } - return null -} - -export const MemberTableRow = ({ member, addedByMember }: TableRowProps) => { - const { toast } = useToast() - const router = useRouter() - const trpc = useTRPC() - const queryClient = useQueryClient() - const { team, user } = useDashboard() - const { timezone } = useTimezone() - const [removeDialogOpen, setRemoveDialogOpen] = useState(false) - - const removeMemberMutation = useMutation( - trpc.teams.removeMember.mutationOptions({ - onSuccess: async (_, input) => { - if (input.userId === user?.id) { - await queryClient.invalidateQueries({ - queryKey: trpc.teams.list.queryKey(), - }) - - router.push(PROTECTED_URLS.DASHBOARD) - - toast(defaultSuccessToast('You have left the project.')) - } else { - await queryClient.invalidateQueries({ - queryKey: trpc.teams.members.queryKey({ teamSlug: team.slug }), - }) - toast( - defaultSuccessToast('The member has been removed from the project.') - ) - } - }, - onError: (error) => { - toast(defaultErrorToast(error.message || 'Unknown error.')) - }, - onSettled: () => { - setRemoveDialogOpen(false) - }, - }) - ) - - const handleRemoveMember = (userId: string) => { - removeMemberMutation.mutate({ teamSlug: team.slug, userId }) - } - - const providers = - member.info.providers - ?.map(toMemberProvider) - .filter((provider): provider is MemberProvider => provider !== null) ?? [] - - const isCurrentUser = member.info.id === user?.id - const showRemove = shouldShowRemoveMemberAction(member, user?.id) - const dateStr = member.info.createdAt - ? formatDate(member.info.createdAt, { timezone }) - : null - const addedBySystem = wasAddedBySystem(member, addedByMember) - - return ( - - - - handleRemoveMember(member.info.id)} - removeDialogOpen={removeDialogOpen} - setRemoveDialogOpen={setRemoveDialogOpen} - showRemove={showRemove} - teamName={getTeamDisplayName(team)} - /> - - ) -} - -const NameCell = ({ - avatarUrl, - email, - isCurrentUser, - name, -}: { - avatarUrl?: string - email: string - isCurrentUser: boolean - name?: string -}) => ( - -
- - - - {(name?.charAt(0) ?? email.charAt(0)).toUpperCase()} - - -
-
- - {name ?? email} - - {isCurrentUser ? ( - - You - - ) : null} -
- {name ? ( - - {email} - - ) : null} -
-
-
-) - -const ProvidersCell = ({ providers }: { providers: MemberProvider[] }) => ( - - {providers.length > 0 ? ( - <> -
- {providers.map(({ key, label, Icon }) => ( - - - {label} - - ))} -
-
- {providers.map(({ key, label, Icon }) => ( - - - {label} - - ))} -
- - ) : ( - -- - )} -
-) - -const AddedCell = ({ - addedByMember, - addedBySystem, - dateStr, - isRemoving, - memberEmail, - memberName, - onRemove, - removeDialogOpen, - setRemoveDialogOpen, - showRemove, - teamName, -}: { - addedByMember?: TeamMember - addedBySystem: boolean - dateStr: string | null - isRemoving: boolean - memberEmail: string - memberName?: string - onRemove: () => void - removeDialogOpen: boolean - setRemoveDialogOpen: (v: boolean) => void - showRemove: boolean - teamName?: string | null -}) => ( - -
- - {dateStr ?? '—'} - - {addedBySystem ? ( -
- -
- ) : ( - - )} - {showRemove ? ( - - - - } - /> - ) : null} -
-
-) diff --git a/src/features/dashboard/members/member-table.tsx b/src/features/dashboard/members/member-table.tsx deleted file mode 100644 index 3948e02fb..000000000 --- a/src/features/dashboard/members/member-table.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { FC, ReactNode } from 'react' -import { cn } from '@/lib/utils' -import { - Table, - TableBody, - TableHead, - TableHeader, - TableRow, -} from '@/ui/primitives/table' - -interface MemberTableProps { - children: ReactNode - className?: string -} - -export const MemberTable: FC = ({ children, className }) => ( - - - - - - - - - - NAME - - - PROVIDERS - - - ADDED - - - - {children} -
-) diff --git a/src/features/dashboard/members/member-table.utils.ts b/src/features/dashboard/members/member-table.utils.ts deleted file mode 100644 index 638fbb5af..000000000 --- a/src/features/dashboard/members/member-table.utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { TeamMember } from '@/core/modules/teams/models' - -const getAddedByMember = ( - allMembers: TeamMember[], - addedById: string | null -): TeamMember | undefined => { - if (!addedById) return undefined - return allMembers.find((member) => member.info.id === addedById) -} - -const wasAddedBySystem = ( - member: TeamMember, - addedByMember?: TeamMember -): boolean => !addedByMember || addedByMember.info.id === member.info.id - -const shouldShowRemoveMemberAction = ( - member: TeamMember, - currentUserId?: string -): boolean => !member.relation.is_default && member.info.id !== currentUserId - -export { getAddedByMember, shouldShowRemoveMemberAction, wasAddedBySystem } diff --git a/src/features/dashboard/members/members-page-content.tsx b/src/features/dashboard/members/members-page-content.tsx deleted file mode 100644 index fd50882b0..000000000 --- a/src/features/dashboard/members/members-page-content.tsx +++ /dev/null @@ -1,123 +0,0 @@ -'use client' - -import { useSuspenseQuery } from '@tanstack/react-query' -import { Suspense, useMemo, useState } from 'react' -import { useDashboard } from '@/features/dashboard/context' -import { cn } from '@/lib/utils' -import { pluralize } from '@/lib/utils/formatting' -import { useTRPC } from '@/trpc/client' -import { CatchErrorBoundary } from '@/ui/error' -import { Card, CardContent } from '@/ui/primitives/card' -import { SearchIcon } from '@/ui/primitives/icons' -import { Input } from '@/ui/primitives/input' -import { Skeleton } from '@/ui/primitives/skeleton' -import { TableEmptyState, TableLoadingState } from '@/ui/primitives/table' -import { AddMemberDialog } from './add-member-dialog' -import { MemberTable } from './member-table' -import { getAddedByMember } from './member-table.utils' -import { MemberTableRow } from './member-table-row' - -const useMembersQuery = () => { - const { team } = useDashboard() - const trpc = useTRPC() - return useSuspenseQuery( - trpc.teams.members.queryOptions({ teamSlug: team.slug }) - ) -} - -const MembersTotal = () => { - const { data: members } = useMembersQuery() - return ( -

- {members.length} {pluralize(members.length, 'member')} total -

- ) -} - -const MembersTableRows = ({ query }: { query: string }) => { - const { data: members } = useMembersQuery() - - const filtered = useMemo(() => { - const q = query.trim().toLowerCase() - if (!q) return members - - return members.filter((m) => { - const name = (m.info.name ?? '').toLowerCase() - const email = m.info.email.toLowerCase() - return name.includes(q) || email.includes(q) - }) - }, [members, query]) - - if (filtered.length === 0) { - return ( - -

- {members.length === 0 - ? 'No project members found.' - : 'No members match your search.'} -

-
- ) - } - - return filtered.map((member) => ( - - )) -} - -interface MembersPageContentProps { - className?: string -} - -export const MembersPageContent = ({ className }: MembersPageContentProps) => { - const [query, setQuery] = useState('') - - return ( - - -
-
- - setQuery(e.target.value)} - placeholder="Search by name or email" - type="search" - value={query} - /> -
- -
- - -
-

All members have the same roles & permissions

- }> - - -
- -
- - - } - > - - - -
-
-
-
- ) -} diff --git a/src/features/dashboard/members/remove-member-dialog.tsx b/src/features/dashboard/members/remove-member-dialog.tsx deleted file mode 100644 index e6aff74b3..000000000 --- a/src/features/dashboard/members/remove-member-dialog.tsx +++ /dev/null @@ -1,78 +0,0 @@ -'use client' - -import type { ReactNode } from 'react' -import { Button } from '@/ui/primitives/button' -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/ui/primitives/dialog' -import { RemoveIcon } from '@/ui/primitives/icons' - -interface RemoveMemberDialogProps { - isRemoving: boolean - memberEmail: string - memberName?: string - onRemove: () => void - open: boolean - setOpen: (v: boolean) => void - teamName?: string | null - trigger: ReactNode -} - -export const RemoveMemberDialog = ({ - isRemoving, - memberEmail, - memberName, - onRemove, - open, - setOpen, - teamName, - trigger, -}: RemoveMemberDialogProps) => { - const shortMemberName = memberName?.trim().split(/\s+/)[0] || memberEmail - const fullMemberName = memberName ?? memberEmail - const teamLabel = teamName || 'this project' - - return ( - - {trigger} - -
- - Remove {shortMemberName}? - - {fullMemberName} will be removed from {teamLabel} - - -
- - - - -
-
-
-
- ) -} diff --git a/src/features/dashboard/navbar/file-drop-zone.tsx b/src/features/dashboard/navbar/file-drop-zone.tsx deleted file mode 100644 index 8d65ff7d9..000000000 --- a/src/features/dashboard/navbar/file-drop-zone.tsx +++ /dev/null @@ -1,129 +0,0 @@ -'use client' - -import { useCallback, useRef, useState } from 'react' -import { cn } from '@/lib/utils' -import { UploadIcon } from '@/ui/primitives/icons' - -interface FileDropZoneProps { - onFilesSelected: (files: File[]) => void - maxFiles: number - currentFileCount: number - isUploading: boolean - disabled?: boolean - accept?: string -} - -export default function FileDropZone({ - onFilesSelected, - maxFiles, - currentFileCount, - isUploading, - disabled = false, - accept, -}: FileDropZoneProps) { - const [isDragOver, setIsDragOver] = useState(false) - const fileInputRef = useRef(null) - const remaining = maxFiles - currentFileCount - - const handleFiles = useCallback( - (fileList: FileList | null) => { - if (!fileList || fileList.length === 0) return - const files = Array.from(fileList).slice(0, remaining) - if (files.length > 0) { - onFilesSelected(files) - } - }, - [onFilesSelected, remaining] - ) - - const handleDragOver = useCallback( - (e: React.DragEvent) => { - e.preventDefault() - e.stopPropagation() - if (!disabled && remaining > 0) { - setIsDragOver(true) - } - }, - [disabled, remaining] - ) - - const handleDragLeave = useCallback((e: React.DragEvent) => { - e.preventDefault() - e.stopPropagation() - setIsDragOver(false) - }, []) - - const handleDrop = useCallback( - (e: React.DragEvent) => { - e.preventDefault() - e.stopPropagation() - setIsDragOver(false) - if (!disabled && remaining > 0) { - handleFiles(e.dataTransfer.files) - } - }, - [disabled, remaining, handleFiles] - ) - - const handleClick = useCallback(() => { - if (!disabled && remaining > 0) { - fileInputRef.current?.click() - } - }, [disabled, remaining]) - - const handleInputChange = useCallback( - (e: React.ChangeEvent) => { - handleFiles(e.target.files) - if (fileInputRef.current) { - fileInputRef.current.value = '' - } - }, - [handleFiles] - ) - - const isDisabled = disabled || remaining <= 0 - - return ( - - ) -} diff --git a/src/features/dashboard/navbar/report-issue-dialog.tsx b/src/features/dashboard/navbar/report-issue-dialog.tsx deleted file mode 100644 index b65db71cd..000000000 --- a/src/features/dashboard/navbar/report-issue-dialog.tsx +++ /dev/null @@ -1,282 +0,0 @@ -'use client' - -import { zodResolver } from '@hookform/resolvers/zod' -import { useMutation } from '@tanstack/react-query' -import { usePathname, useRouter, useSearchParams } from 'next/navigation' -import { usePostHog } from 'posthog-js/react' -import { useCallback, useEffect, useState } from 'react' -import { useForm } from 'react-hook-form' -import { toast } from 'sonner' -import { z } from 'zod' -import { useDashboard } from '@/features/dashboard/context' -import { useTRPC } from '@/trpc/client' -import { Button } from '@/ui/primitives/button' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@/ui/primitives/dialog' -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from '@/ui/primitives/form' -import { CloseIcon, FileIcon } from '@/ui/primitives/icons' -import { Textarea } from '@/ui/primitives/textarea' -import FileDropZone from './file-drop-zone' - -const MAX_ATTACHMENTS = 5 -const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB per file -const E2B_API_KEY_REGEX = /e2b_[a-f0-9]{40}/i - -const ACCEPTED_FILE_TYPES = - 'image/jpeg,image/png,image/gif,image/webp,application/pdf,text/plain' - -const supportFormSchema = z.object({ - description: z.string().min(1, 'Please describe how we can help'), -}) - -type SupportFormValues = z.infer - -function fileToBase64(file: File): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader() - reader.onload = () => { - const result = reader.result as string | null - // Strip the data URL prefix (e.g. "data:image/png;base64,") - resolve(result?.split(',')[1] ?? '') - } - reader.onerror = reject - reader.readAsDataURL(file) - }) -} - -interface ContactSupportDialogProps { - trigger: React.ReactNode -} - -export default function ContactSupportDialog({ - trigger, -}: ContactSupportDialogProps) { - const posthog = usePostHog() - const { team } = useDashboard() - const searchParams = useSearchParams() - const router = useRouter() - const pathname = usePathname() - const trpc = useTRPC() - - const [isOpen, setIsOpen] = useState(false) - const [wasSubmitted, setWasSubmitted] = useState(false) - const [files, setFiles] = useState([]) - - // Auto-open dialog when ?support=true is in the URL - useEffect(() => { - if (searchParams.get('support') === 'true') { - setIsOpen(true) - const params = new URLSearchParams(searchParams.toString()) - params.delete('support') - const query = params.toString() - router.replace(`${pathname}${query ? `?${query}` : ''}`, { - scroll: false, - }) - } - }, [searchParams, router, pathname]) - - const form = useForm({ - resolver: zodResolver(supportFormSchema), - defaultValues: { - description: '', - }, - }) - - const contactSupportMutation = useMutation( - trpc.support.contactSupport.mutationOptions({ - onSuccess: (data) => { - posthog.capture('support_request_submitted', { - thread_id: data?.threadId, - team_id: team.id, - tier: team.tier, - attachment_count: files.length, - }) - setWasSubmitted(true) - toast.success( - 'Message sent successfully. Our team will get back to you shortly.' - ) - setIsOpen(false) - resetForm() - setTimeout(() => { - setWasSubmitted(false) - }, 100) - }, - onError: (error) => { - toast.error( - error.message ?? 'Failed to send message. Please try again.' - ) - }, - }) - ) - - const resetForm = useCallback(() => { - form.reset() - setFiles([]) - }, [form]) - - const handleOpenChange = useCallback( - (open: boolean) => { - if (open) { - posthog.capture('support_form_shown') - } - if (!open && !wasSubmitted) { - posthog.capture('support_form_dismissed') - } - setIsOpen(open) - if (!open) { - resetForm() - } - }, - [posthog, wasSubmitted, resetForm] - ) - - const handleFilesSelected = useCallback((newFiles: File[]) => { - const oversized = newFiles.filter((f) => f.size > MAX_FILE_SIZE) - if (oversized.length > 0) { - toast.error( - `${oversized.length} file${oversized.length > 1 ? 's' : ''} exceeded the 10MB limit and ${oversized.length > 1 ? 'were' : 'was'} not added.` - ) - } - const valid = newFiles.filter((f) => f.size <= MAX_FILE_SIZE) - setFiles((prev) => { - const remaining = MAX_ATTACHMENTS - prev.length - return [...prev, ...valid.slice(0, remaining)] - }) - }, []) - - const removeFile = useCallback((index: number) => { - setFiles((prev) => prev.filter((_, i) => i !== index)) - }, []) - - const onSubmit = async (values: SupportFormValues) => { - const description = values.description.trim() - - if (E2B_API_KEY_REGEX.test(description)) { - toast.error( - 'Your message contains an API key. Please remove it before sending.' - ) - return - } - - const filePayloads = await Promise.all( - files.map(async (file) => ({ - name: file.name, - type: file.type, - base64: await fileToBase64(file), - })) - ) - - contactSupportMutation.mutate({ - teamSlug: team.slug, - description, - files: filePayloads.length > 0 ? filePayloads : undefined, - }) - } - - const isSubmitting = contactSupportMutation.isPending - - return ( - - {trigger} - - - - Contact Support - - Tell us how we can help. Our team will get back to you shortly. - - - -
- - ( - - Message - -