diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 920d7fdc..00000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,69 +0,0 @@
-{
-  "env": {
-    "browser": true,
-    "es2024": true,
-    "jest": true,
-    "node": true
-  },
-  "extends": [
-    "eslint:recommended",
-    "next",
-    "next/core-web-vitals",
-    "plugin:@typescript-eslint/eslint-recommended",
-    "plugin:@typescript-eslint/recommended",
-    "plugin:prettier/recommended"
-  ],
-  "parser": "@typescript-eslint/parser",
-  "parserOptions": {
-    "ecmaFeatures": {
-      "jsx": true
-    },
-    "ecmaVersion": 2024,
-    "project": ["src/ui/tsconfig.json", "tsconfig.test.json", "tsconfig.cypress.json"],
-    "sourceType": "module"
-  },
-  "plugins": ["@typescript-eslint", "import", "jest", "no-secrets", "react"],
-  "root": true,
-  "rules": {
-    "complexity": ["warn", { "max": 8 }],
-    "curly": ["error", "all"],
-    "import/order": [
-      "error",
-      {
-        "groups": [
-          ["builtin", "external"],
-          ["internal", "parent", "sibling", "index"]
-        ],
-        "newlines-between": "always-and-inside-groups",
-        "pathGroups": [
-          {
-            "pattern": "@{app,assets,classes,components,hooks,lib,pages,store,tests,types,utils}/**",
-            "group": "internal",
-            "position": "before"
-          },
-          {
-            "pattern": "{.,..}/**",
-            "group": "internal",
-            "position": "after"
-          }
-        ],
-        "pathGroupsExcludedImportTypes": ["builtin"],
-        "alphabetize": { "order": "asc", "caseInsensitive": true }
-      }
-    ],
-    "import/no-extraneous-dependencies": ["error"],
-    "no-secrets/no-secrets": ["error", { "additionalRegexes": {}, "ignoreContent": [] }]
-  },
-  "settings": {
-    "next": { "rootDir": ["src/ui/", "tests/ui/"] },
-    "import/resolver": {
-      "typescript": {
-        "project": [
-          "src/ui/tsconfig.json",
-          "tsconfig.test.json",
-          "tsconfig.cypress.json"
-        ]
-      }
-    }
-  }
-}
diff --git a/.gitignore b/.gitignore
index ede4e03d..ebbf9b09 100644
--- a/.gitignore
+++ b/.gitignore
@@ -223,8 +223,8 @@ src/ui/next-env.d.ts
 # Root-level UI config files that should be tracked
 !package.json
 !package-lock.json
-!.eslintrc.json
 !tsconfig.json
 !tsconfig.*.json
 !src/ui/lib
 !src/ui/public/manifest.json
+.eslintcache
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 00000000..2312dc58
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+npx lint-staged
diff --git a/.prettierignore b/.prettierignore
index cae160a1..5d4291c5 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -12,7 +12,7 @@
 !tests/ui/**/*.json
 
 # Root-level configs to format
-!/.eslintrc.json
+!/.eslint.config.js
 !/tsconfig*.json
 !/*.config.{js,ts}
 !/jest.setup.ts
diff --git a/DEVELOPING.md b/DEVELOPING.md
index 1a3cc921..dde51744 100644
--- a/DEVELOPING.md
+++ b/DEVELOPING.md
@@ -185,7 +185,13 @@ The GuideLLM project includes a frontend UI located in `src/ui`, built using [Ne
 
 ### Getting Started
 
-To start the local development server:
+First, install dependencies:
+
+```bash
+npm install
+```
+
+Then, start the local development server:
 
 ```bash
 npm run dev
@@ -215,6 +221,12 @@ npm run build
   npm run test:integration
   ```
 
+- **Integration+Unit tests with coverage**:
+
+  ```bash
+  npm run coverage
+  ```
+
 - **End-to-end tests** (using Cypress, ensure live dev server):
 
   ```bash
@@ -241,6 +253,20 @@ npm run build
   npm run type-checks
   ```
 
+##### Tagging Tests
+
+Reference [https://www.npmjs.com/package/jest-runner-groups](jest-runner-groups) Add @group with the tag in a docblock at the top of the test file to indicate which types of tests are contained within. Can't distinguish between different types of tests in the same file.
+
+```
+/**
+ * Admin dashboard tests
+ *
+ * @group smoke
+ * @group sanity
+ * @group regression
+ */
+```
+
 ## Additional Resources
 
 - [CONTRIBUTING.md](https://github.com/neuralmagic/guidellm/blob/main/CONTRIBUTING.md): Guidelines for contributing to the project.
diff --git a/README.md b/README.md
index e31c0e44..fb70f072 100644
--- a/README.md
+++ b/README.md
@@ -153,6 +153,48 @@ The `guidellm benchmark` command is used to run benchmarks against a generative
 
 - `--output-path`: Defines the path to save the benchmark results. Supports JSON, YAML, or CSV formats. If a directory is provided, the results will be saved as `benchmarks.json` in that directory. If not set, the results will be saved in the current working directory.
 
+### GuideLLM UI
+
+GuideLLM UI is a companion frontend for visualizing the results of a GuideLLM benchmark run.
+
+### 🛠 Running the UI
+
+1. Use the Hosted Build (Recommended for Most Users)
+
+After running a benchmark with GuideLLM, a report.html file will be generated (by default at guidellm_report/report.html). This file references the latest stable version of the UI hosted at:
+
+```
+https://neuralmagic.github.io/guidellm/ui/dev/
+```
+
+Open the file in your browser and you're done—no setup required.
+
+2. Build and Serve the UI Locally (For Development) This option is useful if:
+
+- You are actively developing the UI
+
+- You want to test changes to the UI before publishing
+
+- You want full control over how the report is displayed
+
+```bash
+npm install
+npm run build
+npx serve out
+```
+
+This will start a local server (e.g., at http://localhost:3000). Then, in your GuideLLM config or CLI flags, point to this local server as the asset base for report generation.
+
+### 🧪 Development Notes
+
+During UI development, it can be helpful to view sample data. We include a sample benchmark run wired into the Redux store under:
+
+```
+src/lib/store/[runInfo/workloadDetails/benchmarks]WindowData.ts
+```
+
+In the future this will be replaced by a configurable untracked file for dev use.
+
 ## Resources
 
 ### Documentation
diff --git a/cypress.config.ts b/cypress.config.ts
index 846b2111..e7bdffc8 100644
--- a/cypress.config.ts
+++ b/cypress.config.ts
@@ -4,6 +4,6 @@ export default defineConfig({
   e2e: {
     specPattern: 'tests/ui/cypress/e2e/**/*.cy.{js,jsx,ts,tsx}',
     supportFile: 'tests/ui/cypress/support/e2e.ts',
-    baseUrl: 'http://localhost:3000', // optional, but good practice
+    baseUrl: 'http://localhost:3000', // optional
   },
 });
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 00000000..178aaccb
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,181 @@
+// @ts-check
+
+import eslint from '@eslint/js';
+import typescriptPlugin from '@typescript-eslint/eslint-plugin';
+import typescriptParser from '@typescript-eslint/parser';
+import { FlatCompat } from '@eslint/eslintrc';
+import reactPlugin from 'eslint-plugin-react';
+import hooksPlugin from 'eslint-plugin-react-hooks';
+import importPlugin from 'eslint-plugin-import';
+import jestPlugin from 'eslint-plugin-jest';
+import noSecretsPlugin from 'eslint-plugin-no-secrets';
+import prettierPlugin from 'eslint-plugin-prettier';
+import prettierConfig from 'eslint-config-prettier';
+import globals from 'globals';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const compat = new FlatCompat({
+  baseDirectory: __dirname,
+  recommendedConfig: eslint.configs.recommended,
+});
+
+export default [
+  // Base configuration
+  eslint.configs.recommended,
+
+  // Next.js configuration using FlatCompat
+  ...compat.extends('next/core-web-vitals'),
+
+  // --- Main Configuration ---
+  {
+    files: ['src/**/*.{js,jsx,ts,tsx}', 'tests/**/*.{js,jsx,ts,tsx}'],
+    languageOptions: {
+      parser: typescriptParser,
+      ecmaVersion: 2024,
+      sourceType: 'module',
+      globals: {
+        ...globals.browser,
+        ...globals.node,
+        ...globals.jest,
+      },
+      parserOptions: {
+        ecmaFeatures: {
+          jsx: true,
+        },
+        project: [
+          './src/ui/tsconfig.json',
+          './tsconfig.test.json',
+          './tsconfig.cypress.json',
+        ],
+        tsconfigRootDir: import.meta.dirname,
+        noWarnOnMultipleProjects: true,
+      },
+    },
+    plugins: {
+      '@typescript-eslint': typescriptPlugin,
+      react: reactPlugin,
+      'react-hooks': hooksPlugin,
+      import: importPlugin,
+      jest: jestPlugin,
+      'no-secrets': noSecretsPlugin,
+      prettier: prettierPlugin,
+    },
+    rules: {
+      // Ccustom rules
+      complexity: ['warn', { max: 8 }],
+      curly: ['error', 'all'],
+      'no-unused-vars': 'off',
+
+      // TypeScript rules
+      '@typescript-eslint/no-unused-vars': [
+        'warn',
+        {
+          argsIgnorePattern: '^_',
+          varsIgnorePattern: '^_',
+          caughtErrorsIgnorePattern: '^_',
+        },
+      ],
+      '@typescript-eslint/no-explicit-any': 'warn',
+
+      // Next.js overrides
+      '@next/next/no-img-element': 'off', // Allow img tags if needed
+      '@next/next/no-page-custom-font': 'warn',
+
+      // React rules
+      'react/react-in-jsx-scope': 'off', // Not needed in Next.js
+      'react/prop-types': 'off', // Using TypeScript
+      'react-hooks/rules-of-hooks': 'error',
+      'react-hooks/exhaustive-deps': 'warn',
+
+      // Import rules
+      'import/no-extraneous-dependencies': [
+        'error',
+        {
+          devDependencies: [
+            '**/*.test.{js,jsx,ts,tsx}',
+            '**/*.d.ts',
+            '**/*.interfaces.ts',
+            '**/*.setup.{js,ts}',
+            '**/*.config.{js,mjs,ts}',
+            'tests/**/*',
+            'cypress/**/*',
+          ],
+          optionalDependencies: false,
+          peerDependencies: false,
+        },
+      ],
+      'import/order': [
+        'error',
+        {
+          groups: [
+            ['builtin', 'external'],
+            ['internal', 'parent', 'sibling', 'index'],
+          ],
+          'newlines-between': 'always-and-inside-groups',
+          pathGroups: [
+            {
+              pattern:
+                '@{app,assets,classes,components,hooks,lib,pages,store,tests,types,utils}/**',
+              group: 'internal',
+              position: 'before',
+            },
+            {
+              pattern: '{.,..}/**',
+              group: 'internal',
+              position: 'after',
+            },
+          ],
+          pathGroupsExcludedImportTypes: ['builtin'],
+          alphabetize: { order: 'asc', caseInsensitive: true },
+        },
+      ],
+
+      // Security
+      'no-secrets/no-secrets': ['error', { additionalRegexes: {}, ignoreContent: [] }],
+
+      // Prettier
+      'prettier/prettier': 'error',
+    },
+    settings: {
+      next: {
+        rootDir: ['src/ui/', 'tests/ui/'],
+      },
+      'import/resolver': {
+        typescript: {
+          project: [
+            './src/ui/tsconfig.json',
+            './tsconfig.test.json',
+            './tsconfig.cypress.json',
+          ],
+          noWarnOnMultipleProjects: true,
+        },
+      },
+      react: {
+        version: 'detect',
+      },
+    },
+  },
+
+  // Jest-specific rules for test files
+  {
+    files: [
+      'tests/**/*.{js,jsx,ts,tsx}',
+      '**/*.test.{js,jsx,ts,tsx}',
+      '**/*.spec.{js,jsx,ts,tsx}',
+    ],
+    rules: {
+      'jest/expect-expect': 'error',
+      'jest/no-focused-tests': 'error',
+      'jest/no-identical-title': 'error',
+      'jest/prefer-to-have-length': 'warn',
+      'jest/valid-expect': 'error',
+    },
+  },
+
+  // Prettier config (disables conflicting rules)
+  prettierConfig,
+];
diff --git a/jest.config.js b/jest.config.cjs
similarity index 91%
rename from jest.config.js
rename to jest.config.cjs
index 35e6ad25..7c46d874 100644
--- a/jest.config.js
+++ b/jest.config.cjs
@@ -6,11 +6,11 @@ const createJestConfig = nextJest({
 });
 
 const customJestConfig = {
-  collectCoverage: true,
+  collectCoverage: false,
   collectCoverageFrom: ['./src/ui/**/*.{ts,tsx}'],
   coverageDirectory: './coverage',
   coverageProvider: 'v8',
-  coverageReporters: ['json', 'text-summary', 'lcov'],
+  coverageReporters: ['text-summary', 'lcov', 'json-summary'],
   moduleFileExtensions: ['ts', 'tsx', 'js'],
   moduleNameMapper: {
     '^.+\\.(svg)$': '<rootDir>/tests/ui/__mocks__/svg.js',
diff --git a/jest.setup.ts b/jest.setup.ts
index 7b0828bf..eb162bb7 100644
--- a/jest.setup.ts
+++ b/jest.setup.ts
@@ -1 +1,22 @@
 import '@testing-library/jest-dom';
+import 'cross-fetch/polyfill';
+
+jest.mock('@nivo/bar');
+jest.mock('@nivo/line');
+jest.mock('@nivo/core');
+
+jest.mock('next/dynamic', () => ({
+  __esModule: true,
+  default: (...props: any[]) => {
+    const dynamicModule = jest.requireActual('next/dynamic');
+    const dynamicActualComp = dynamicModule.default;
+    const RequiredComponent = dynamicActualComp(props[0]);
+    // eslint-disable-next-line no-unused-expressions, @typescript-eslint/no-unused-expressions
+    RequiredComponent.preload
+      ? RequiredComponent.preload()
+      : RequiredComponent.render.preload();
+    return RequiredComponent;
+  },
+}));
+
+global.fetch = jest.fn();
diff --git a/package-lock.json b/package-lock.json
index 2603ffa4..7bf63e9a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,15 +13,23 @@
         "@emotion/styled": "^11.14.0",
         "@mui/material": "^5.11.7",
         "@mui/material-nextjs": "^5.16.6",
+        "@nivo/bar": "^0.88.0",
+        "@nivo/core": "^0.88.0",
+        "@nivo/line": "^0.88.0",
+        "@nivo/scales": "^0.88.0",
+        "@nivo/tooltip": "^0.88.0",
         "@reduxjs/toolkit": "^2.2.7",
+        "filesize": "^10.1.6",
         "next": "15.3.2",
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
+        "react-material-ui-carousel": "^3.4.2",
         "react-redux": "^9.1.2"
       },
       "devDependencies": {
         "@eslint/eslintrc": "^3",
         "@mui/types": "^7.2.14",
+        "@next/eslint-plugin-next": "^15.3.3",
         "@svgr/webpack": "^8.1.0",
         "@testing-library/jest-dom": "^5.16.5",
         "@testing-library/react": "^16.0.0",
@@ -33,6 +41,7 @@
         "@types/testing-library__jest-dom": "^5.14.9",
         "@typescript-eslint/eslint-plugin": "^8.33.1",
         "@typescript-eslint/parser": "^8.33.1",
+        "cross-fetch": "^4.1.0",
         "cypress": "^13.13.3",
         "eslint": "^9.0.0",
         "eslint-config-next": "15.3.2",
@@ -45,18 +54,22 @@
         "eslint-plugin-prettier": "^5.4.0",
         "eslint-plugin-react": "^7.31.10",
         "eslint-plugin-react-hooks": "^5.2.0",
+        "globals": "^16.2.0",
         "husky": "^9.1.7",
         "jest": "^29.7.0",
         "jest-coverage-badges": "^1.1.2",
         "jest-environment-jsdom": "^29.7.0",
         "jest-runner-groups": "^2.2.0",
         "jest-transform-stub": "^2.0.0",
-        "nyc": "^17.1.0",
         "prettier": "^3.5.3",
         "typescript": "^5"
       },
       "engines": {
         "node": ">=22"
+      },
+      "optionalDependencies": {
+        "@next/swc-linux-x64-gnu": "^15.3.3",
+        "@next/swc-linux-x64-musl": "^15.3.3"
       }
     },
     "node_modules/@adobe/css-tools": {
@@ -135,15 +148,12 @@
         "url": "https://opencollective.com/babel"
       }
     },
-    "node_modules/@babel/core/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+    "node_modules/@babel/core/node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
       "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
+      "license": "MIT"
     },
     "node_modules/@babel/generator": {
       "version": "7.27.5",
@@ -191,16 +201,6 @@
         "node": ">=6.9.0"
       }
     },
-    "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/@babel/helper-create-class-features-plugin": {
       "version": "7.27.1",
       "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz",
@@ -223,16 +223,6 @@
         "@babel/core": "^7.0.0"
       }
     },
-    "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/@babel/helper-create-regexp-features-plugin": {
       "version": "7.27.1",
       "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz",
@@ -251,16 +241,6 @@
         "@babel/core": "^7.0.0"
       }
     },
-    "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/@babel/helper-define-polyfill-provider": {
       "version": "0.6.4",
       "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz",
@@ -440,14 +420,14 @@
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.27.4",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz",
-      "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==",
+      "version": "7.27.6",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz",
+      "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/template": "^7.27.2",
-        "@babel/types": "^7.27.3"
+        "@babel/types": "^7.27.6"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -1873,16 +1853,6 @@
         "@babel/core": "^7.0.0-0"
       }
     },
-    "node_modules/@babel/preset-env/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/@babel/preset-modules": {
       "version": "0.1.6-no-external-plugins",
       "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz",
@@ -1940,9 +1910,9 @@
       }
     },
     "node_modules/@babel/runtime": {
-      "version": "7.27.4",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.4.tgz",
-      "integrity": "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==",
+      "version": "7.27.6",
+      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz",
+      "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==",
       "license": "MIT",
       "engines": {
         "node": ">=6.9.0"
@@ -1990,9 +1960,9 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.27.3",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz",
-      "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==",
+      "version": "7.27.6",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz",
+      "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
       "license": "MIT",
       "dependencies": {
         "@babel/helper-string-parser": "^7.27.1",
@@ -2071,40 +2041,6 @@
         "ms": "^2.1.1"
       }
     },
-    "node_modules/@emnapi/core": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
-      "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "@emnapi/wasi-threads": "1.0.2",
-        "tslib": "^2.4.0"
-      }
-    },
-    "node_modules/@emnapi/runtime": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
-      "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "tslib": "^2.4.0"
-      }
-    },
-    "node_modules/@emnapi/wasi-threads": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz",
-      "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "tslib": "^2.4.0"
-      }
-    },
     "node_modules/@emotion/babel-plugin": {
       "version": "11.13.5",
       "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
@@ -2124,21 +2060,6 @@
         "stylis": "4.2.0"
       }
     },
-    "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
-      "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
-      "license": "MIT"
-    },
-    "node_modules/@emotion/babel-plugin/node_modules/source-map": {
-      "version": "0.5.7",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-      "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
-      "license": "BSD-3-Clause",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/@emotion/cache": {
       "version": "11.14.0",
       "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
@@ -2357,6 +2278,19 @@
         "url": "https://opencollective.com/eslint"
       }
     },
+    "node_modules/@eslint/eslintrc/node_modules/globals": {
+      "version": "14.0.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/@eslint/js": {
       "version": "9.28.0",
       "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz",
@@ -2525,6 +2459,16 @@
         "sprintf-js": "~1.0.2"
       }
     },
+    "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -2940,6 +2884,13 @@
         "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
+    "node_modules/@jest/transform/node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@jest/types": {
       "version": "29.6.3",
       "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
@@ -3016,6 +2967,32 @@
         "url": "https://opencollective.com/mui-org"
       }
     },
+    "node_modules/@mui/icons-material": {
+      "version": "5.17.1",
+      "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.17.1.tgz",
+      "integrity": "sha512-CN86LocjkunFGG0yPlO4bgqHkNGgaEOEc3X/jG5Bzm401qYw79/SaLrofA7yAKCCXAGdIGnLoMHohc3+ubs95A==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.23.9"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/mui-org"
+      },
+      "peerDependencies": {
+        "@mui/material": "^5.0.0",
+        "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+        "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@mui/material": {
       "version": "5.17.1",
       "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.17.1.tgz",
@@ -3110,12 +3087,6 @@
         }
       }
     },
-    "node_modules/@mui/material/node_modules/react-is": {
-      "version": "19.1.0",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
-      "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
-      "license": "MIT"
-    },
     "node_modules/@mui/private-theming": {
       "version": "5.17.1",
       "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz",
@@ -3291,25 +3262,6 @@
         }
       }
     },
-    "node_modules/@mui/utils/node_modules/react-is": {
-      "version": "19.1.0",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
-      "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
-      "license": "MIT"
-    },
-    "node_modules/@napi-rs/wasm-runtime": {
-      "version": "0.2.10",
-      "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.10.tgz",
-      "integrity": "sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "@emnapi/core": "^1.4.3",
-        "@emnapi/runtime": "^1.4.3",
-        "@tybys/wasm-util": "^0.9.0"
-      }
-    },
     "node_modules/@next/env": {
       "version": "15.3.2",
       "resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.2.tgz",
@@ -3317,9 +3269,9 @@
       "license": "MIT"
     },
     "node_modules/@next/eslint-plugin-next": {
-      "version": "15.3.2",
-      "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.2.tgz",
-      "integrity": "sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ==",
+      "version": "15.3.3",
+      "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.3.tgz",
+      "integrity": "sha512-VKZJEiEdpKkfBmcokGjHu0vGDG+8CehGs90tBEy/IDoDDKGngeyIStt2MmE5FYNyU9BhgR7tybNWTAJY/30u+Q==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -3418,12 +3370,13 @@
       }
     },
     "node_modules/@next/swc-linux-x64-gnu": {
-      "version": "15.3.2",
-      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz",
-      "integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==",
+      "version": "15.3.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.3.tgz",
+      "integrity": "sha512-jJ8HRiF3N8Zw6hGlytCj5BiHyG/K+fnTKVDEKvUCyiQ/0r5tgwO7OgaRiOjjRoIx2vwLR+Rz8hQoPrnmFbJdfw==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -3433,12 +3386,13 @@
       }
     },
     "node_modules/@next/swc-linux-x64-musl": {
-      "version": "15.3.2",
-      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz",
-      "integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==",
+      "version": "15.3.3",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.3.tgz",
+      "integrity": "sha512-HrUcTr4N+RgiiGn3jjeT6Oo208UT/7BuTr7K0mdKRBtTbT4v9zJqCDKO97DUqqoBK1qyzP1RwvrWTvU6EPh/Cw==",
       "cpu": [
         "x64"
       ],
+      "license": "MIT",
       "optional": true,
       "os": [
         "linux"
@@ -3477,6 +3431,198 @@
         "node": ">= 10"
       }
     },
+    "node_modules/@nivo/annotations": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/annotations/-/annotations-0.88.0.tgz",
+      "integrity": "sha512-NXE+1oIUn+EGWMQpnpeRMLgi2wyuzhGDoJQY4OUHissCUiNotid2oNQ/PXJwN0toiu+/j9SyhzI32xr70OPi7Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@nivo/colors": "0.88.0",
+        "@nivo/core": "0.88.0",
+        "@react-spring/web": "9.4.5 || ^9.7.2",
+        "lodash": "^4.17.21"
+      },
+      "peerDependencies": {
+        "react": ">= 16.14.0 < 19.0.0"
+      }
+    },
+    "node_modules/@nivo/axes": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/axes/-/axes-0.88.0.tgz",
+      "integrity": "sha512-jF7aIxzTNayV5cI1J/b9Q1FfpMBxTXGk3OwSigXMSfYWlliskDn2u0qGRLiYhuXFdQAWIp4oXsO1GcAQ0eRVdg==",
+      "license": "MIT",
+      "dependencies": {
+        "@nivo/core": "0.88.0",
+        "@nivo/scales": "0.88.0",
+        "@react-spring/web": "9.4.5 || ^9.7.2",
+        "@types/d3-format": "^1.4.1",
+        "@types/d3-time-format": "^2.3.1",
+        "d3-format": "^1.4.4",
+        "d3-time-format": "^3.0.0"
+      },
+      "peerDependencies": {
+        "react": ">= 16.14.0 < 19.0.0"
+      }
+    },
+    "node_modules/@nivo/bar": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/bar/-/bar-0.88.0.tgz",
+      "integrity": "sha512-wckwuHWeCikxGvvdRfGL+dVFsUD9uHk1r9s7bWUfOD+p8BWhxtYqfXpHolEfgGg3UyPaHtpGA7P4zgE5vgo7gQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@nivo/annotations": "0.88.0",
+        "@nivo/axes": "0.88.0",
+        "@nivo/colors": "0.88.0",
+        "@nivo/core": "0.88.0",
+        "@nivo/legends": "0.88.0",
+        "@nivo/scales": "0.88.0",
+        "@nivo/tooltip": "0.88.0",
+        "@react-spring/web": "9.4.5 || ^9.7.2",
+        "@types/d3-scale": "^4.0.8",
+        "@types/d3-shape": "^3.1.6",
+        "d3-scale": "^4.0.2",
+        "d3-shape": "^3.2.0",
+        "lodash": "^4.17.21"
+      },
+      "peerDependencies": {
+        "react": ">= 16.14.0 < 19.0.0"
+      }
+    },
+    "node_modules/@nivo/colors": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.88.0.tgz",
+      "integrity": "sha512-IZ+leYIqAlo7dyLHmsQwujanfRgXyoQ5H7PU3RWLEn1PP0zxDKLgEjFEDADpDauuslh2Tx0L81GNkWR6QSP0Mw==",
+      "license": "MIT",
+      "dependencies": {
+        "@nivo/core": "0.88.0",
+        "@types/d3-color": "^3.0.0",
+        "@types/d3-scale": "^4.0.8",
+        "@types/d3-scale-chromatic": "^3.0.0",
+        "@types/prop-types": "^15.7.2",
+        "d3-color": "^3.1.0",
+        "d3-scale": "^4.0.2",
+        "d3-scale-chromatic": "^3.0.0",
+        "lodash": "^4.17.21",
+        "prop-types": "^15.7.2"
+      },
+      "peerDependencies": {
+        "react": ">= 16.14.0 < 19.0.0"
+      }
+    },
+    "node_modules/@nivo/core": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/core/-/core-0.88.0.tgz",
+      "integrity": "sha512-XjUkA5MmwjLP38bdrJwn36Gj7T5SYMKD55LYQp/1nIJPdxqJ38dUfE4XyBDfIEgfP6yrHOihw3C63cUdnUBoiw==",
+      "license": "MIT",
+      "dependencies": {
+        "@nivo/tooltip": "0.88.0",
+        "@react-spring/web": "9.4.5 || ^9.7.2",
+        "@types/d3-shape": "^3.1.6",
+        "d3-color": "^3.1.0",
+        "d3-format": "^1.4.4",
+        "d3-interpolate": "^3.0.1",
+        "d3-scale": "^4.0.2",
+        "d3-scale-chromatic": "^3.0.0",
+        "d3-shape": "^3.2.0",
+        "d3-time-format": "^3.0.0",
+        "lodash": "^4.17.21",
+        "prop-types": "^15.7.2"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nivo/donate"
+      },
+      "peerDependencies": {
+        "react": ">= 16.14.0 < 19.0.0"
+      }
+    },
+    "node_modules/@nivo/legends": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/legends/-/legends-0.88.0.tgz",
+      "integrity": "sha512-d4DF9pHbD8LmGJlp/Gp1cF4e8y2wfQTcw3jVhbZj9zkb7ZWB7JfeF60VHRfbXNux9bjQ9U78/SssQqueVDPEmg==",
+      "license": "MIT",
+      "dependencies": {
+        "@nivo/colors": "0.88.0",
+        "@nivo/core": "0.88.0",
+        "@types/d3-scale": "^4.0.8",
+        "d3-scale": "^4.0.2"
+      },
+      "peerDependencies": {
+        "react": ">= 16.14.0 < 19.0.0"
+      }
+    },
+    "node_modules/@nivo/line": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/line/-/line-0.88.0.tgz",
+      "integrity": "sha512-hFTyZ3BdAZvq2HwdwMj2SJGUeodjEW+7DLtFMIIoVIxmjZlAs3z533HcJ9cJd3it928fDm8SF/rgHs0TztYf9Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@nivo/annotations": "0.88.0",
+        "@nivo/axes": "0.88.0",
+        "@nivo/colors": "0.88.0",
+        "@nivo/core": "0.88.0",
+        "@nivo/legends": "0.88.0",
+        "@nivo/scales": "0.88.0",
+        "@nivo/tooltip": "0.88.0",
+        "@nivo/voronoi": "0.88.0",
+        "@react-spring/web": "9.4.5 || ^9.7.2",
+        "d3-shape": "^3.2.0"
+      },
+      "peerDependencies": {
+        "react": ">= 16.14.0 < 19.0.0"
+      }
+    },
+    "node_modules/@nivo/scales": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/scales/-/scales-0.88.0.tgz",
+      "integrity": "sha512-HbpxkQp6tHCltZ1yDGeqdLcaJl5ze54NPjurfGtx/Uq+H5IQoBd4Tln49bUar5CsFAMsXw8yF1HQvASr7I1SIA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-scale": "^4.0.8",
+        "@types/d3-time": "^1.1.1",
+        "@types/d3-time-format": "^3.0.0",
+        "d3-scale": "^4.0.2",
+        "d3-time": "^1.0.11",
+        "d3-time-format": "^3.0.0",
+        "lodash": "^4.17.21"
+      }
+    },
+    "node_modules/@nivo/scales/node_modules/@types/d3-time-format": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-3.0.4.tgz",
+      "integrity": "sha512-or9DiDnYI1h38J9hxKEsw513+KVuFbEVhl7qdxcaudoiqWWepapUen+2vAriFGexr6W5+P4l9+HJrB39GG+oRg==",
+      "license": "MIT"
+    },
+    "node_modules/@nivo/tooltip": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/tooltip/-/tooltip-0.88.0.tgz",
+      "integrity": "sha512-iEjVfQA8gumAzg/yUinjTwswygCkE5Iwuo8opwnrbpNIqMrleBV+EAKIgB0PrzepIoW8CFG/SJhoiRfbU8jhOw==",
+      "license": "MIT",
+      "dependencies": {
+        "@nivo/core": "0.88.0",
+        "@react-spring/web": "9.4.5 || ^9.7.2"
+      },
+      "peerDependencies": {
+        "react": ">= 16.14.0 < 19.0.0"
+      }
+    },
+    "node_modules/@nivo/voronoi": {
+      "version": "0.88.0",
+      "resolved": "https://registry.npmjs.org/@nivo/voronoi/-/voronoi-0.88.0.tgz",
+      "integrity": "sha512-MyiNLvODthFoMjQ7Wjp693nogbTmVEx8Yn/7QkJhyPQbFyyA37TF/D1a/ox4h2OslXtP6K9QFN+42gB/zu7ixw==",
+      "license": "MIT",
+      "dependencies": {
+        "@nivo/core": "0.88.0",
+        "@nivo/tooltip": "0.88.0",
+        "@types/d3-delaunay": "^6.0.4",
+        "@types/d3-scale": "^4.0.8",
+        "d3-delaunay": "^6.0.4",
+        "d3-scale": "^4.0.2"
+      },
+      "peerDependencies": {
+        "react": ">= 16.14.0 < 19.0.0"
+      }
+    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3548,38 +3694,110 @@
         "url": "https://opencollective.com/popperjs"
       }
     },
-    "node_modules/@reduxjs/toolkit": {
-      "version": "2.8.2",
-      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz",
-      "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==",
+    "node_modules/@react-spring/animated": {
+      "version": "9.7.5",
+      "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz",
+      "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==",
       "license": "MIT",
       "dependencies": {
-        "@standard-schema/spec": "^1.0.0",
-        "@standard-schema/utils": "^0.3.0",
-        "immer": "^10.0.3",
-        "redux": "^5.0.1",
-        "redux-thunk": "^3.1.0",
-        "reselect": "^5.1.0"
+        "@react-spring/shared": "~9.7.5",
+        "@react-spring/types": "~9.7.5"
       },
       "peerDependencies": {
-        "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
-        "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
-      },
-      "peerDependenciesMeta": {
-        "react": {
-          "optional": true
-        },
-        "react-redux": {
-          "optional": true
-        }
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
       }
     },
-    "node_modules/@rtsao/scc": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
-      "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
-      "dev": true,
-      "license": "MIT"
+    "node_modules/@react-spring/core": {
+      "version": "9.7.5",
+      "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz",
+      "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==",
+      "license": "MIT",
+      "dependencies": {
+        "@react-spring/animated": "~9.7.5",
+        "@react-spring/shared": "~9.7.5",
+        "@react-spring/types": "~9.7.5"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/react-spring/donate"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
+    "node_modules/@react-spring/rafz": {
+      "version": "9.7.5",
+      "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz",
+      "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==",
+      "license": "MIT"
+    },
+    "node_modules/@react-spring/shared": {
+      "version": "9.7.5",
+      "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz",
+      "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==",
+      "license": "MIT",
+      "dependencies": {
+        "@react-spring/rafz": "~9.7.5",
+        "@react-spring/types": "~9.7.5"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
+    "node_modules/@react-spring/types": {
+      "version": "9.7.5",
+      "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz",
+      "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==",
+      "license": "MIT"
+    },
+    "node_modules/@react-spring/web": {
+      "version": "9.7.5",
+      "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.5.tgz",
+      "integrity": "sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@react-spring/animated": "~9.7.5",
+        "@react-spring/core": "~9.7.5",
+        "@react-spring/shared": "~9.7.5",
+        "@react-spring/types": "~9.7.5"
+      },
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+        "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
+    "node_modules/@reduxjs/toolkit": {
+      "version": "2.8.2",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.8.2.tgz",
+      "integrity": "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A==",
+      "license": "MIT",
+      "dependencies": {
+        "@standard-schema/spec": "^1.0.0",
+        "@standard-schema/utils": "^0.3.0",
+        "immer": "^10.0.3",
+        "redux": "^5.0.1",
+        "redux-thunk": "^3.1.0",
+        "reselect": "^5.1.0"
+      },
+      "peerDependencies": {
+        "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
+        "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
+      },
+      "peerDependenciesMeta": {
+        "react": {
+          "optional": true
+        },
+        "react-redux": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@rtsao/scc": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+      "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/@rushstack/eslint-patch": {
       "version": "1.11.0",
@@ -3811,19 +4029,6 @@
         "url": "https://github.com/sponsors/gregberge"
       }
     },
-    "node_modules/@svgr/core/node_modules/camelcase": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
-      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/@svgr/hast-util-to-babel-ast": {
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz",
@@ -3842,19 +4047,6 @@
         "url": "https://github.com/sponsors/gregberge"
       }
     },
-    "node_modules/@svgr/hast-util-to-babel-ast/node_modules/entities": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
-      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-      "dev": true,
-      "license": "BSD-2-Clause",
-      "engines": {
-        "node": ">=0.12"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1"
-      }
-    },
     "node_modules/@svgr/plugin-jsx": {
       "version": "8.1.0",
       "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz",
@@ -4062,17 +4254,6 @@
         "node": ">=10.13.0"
       }
     },
-    "node_modules/@tybys/wasm-util": {
-      "version": "0.9.0",
-      "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz",
-      "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==",
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "tslib": "^2.4.0"
-      }
-    },
     "node_modules/@types/aria-query": {
       "version": "5.0.4",
       "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
@@ -4126,10 +4307,70 @@
         "@babel/types": "^7.20.7"
       }
     },
+    "node_modules/@types/d3-color": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
+      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-delaunay": {
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+      "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-format": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-1.4.5.tgz",
+      "integrity": "sha512-mLxrC1MSWupOSncXN/HOlWUAAIffAEBaI4+PKy2uMPsKe4FNZlk7qrbTjmzJXITQQqBHivaks4Td18azgqnotA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-path": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
+      "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-scale": {
+      "version": "4.0.9",
+      "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
+      "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-time": "*"
+      }
+    },
+    "node_modules/@types/d3-scale-chromatic": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+      "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-shape": {
+      "version": "3.1.7",
+      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
+      "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/d3-path": "*"
+      }
+    },
+    "node_modules/@types/d3-time": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-1.1.4.tgz",
+      "integrity": "sha512-JIvy2HjRInE+TXOmIGN5LCmeO0hkFZx5f9FZ7kiN+D+YTcc8pptsiLiuHsvwxwC7VVKmJ2ExHUgNlAiV7vQM9g==",
+      "license": "MIT"
+    },
+    "node_modules/@types/d3-time-format": {
+      "version": "2.3.4",
+      "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-2.3.4.tgz",
+      "integrity": "sha512-xdDXbpVO74EvadI3UDxjxTdR6QIxm1FKzEA/+F8tL4GWWUg/hgvBqf6chql64U5A9ZUGWo7pEu4eNlyLwbKdhg==",
+      "license": "MIT"
+    },
     "node_modules/@types/estree": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
-      "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+      "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
       "dev": true,
       "license": "MIT"
     },
@@ -4208,9 +4449,9 @@
       "license": "MIT"
     },
     "node_modules/@types/node": {
-      "version": "22.15.29",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz",
-      "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==",
+      "version": "22.15.30",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz",
+      "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -4331,17 +4572,17 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz",
-      "integrity": "sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz",
+      "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "8.33.1",
-        "@typescript-eslint/type-utils": "8.33.1",
-        "@typescript-eslint/utils": "8.33.1",
-        "@typescript-eslint/visitor-keys": "8.33.1",
+        "@typescript-eslint/scope-manager": "8.34.0",
+        "@typescript-eslint/type-utils": "8.34.0",
+        "@typescript-eslint/utils": "8.34.0",
+        "@typescript-eslint/visitor-keys": "8.34.0",
         "graphemer": "^1.4.0",
         "ignore": "^7.0.0",
         "natural-compare": "^1.4.0",
@@ -4355,7 +4596,7 @@
         "url": "https://opencollective.com/typescript-eslint"
       },
       "peerDependencies": {
-        "@typescript-eslint/parser": "^8.33.1",
+        "@typescript-eslint/parser": "^8.34.0",
         "eslint": "^8.57.0 || ^9.0.0",
         "typescript": ">=4.8.4 <5.9.0"
       }
@@ -4371,16 +4612,16 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.33.1.tgz",
-      "integrity": "sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz",
+      "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/scope-manager": "8.33.1",
-        "@typescript-eslint/types": "8.33.1",
-        "@typescript-eslint/typescript-estree": "8.33.1",
-        "@typescript-eslint/visitor-keys": "8.33.1",
+        "@typescript-eslint/scope-manager": "8.34.0",
+        "@typescript-eslint/types": "8.34.0",
+        "@typescript-eslint/typescript-estree": "8.34.0",
+        "@typescript-eslint/visitor-keys": "8.34.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -4396,14 +4637,14 @@
       }
     },
     "node_modules/@typescript-eslint/project-service": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.33.1.tgz",
-      "integrity": "sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz",
+      "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/tsconfig-utils": "^8.33.1",
-        "@typescript-eslint/types": "^8.33.1",
+        "@typescript-eslint/tsconfig-utils": "^8.34.0",
+        "@typescript-eslint/types": "^8.34.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -4418,14 +4659,14 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz",
-      "integrity": "sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz",
+      "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.33.1",
-        "@typescript-eslint/visitor-keys": "8.33.1"
+        "@typescript-eslint/types": "8.34.0",
+        "@typescript-eslint/visitor-keys": "8.34.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4436,9 +4677,9 @@
       }
     },
     "node_modules/@typescript-eslint/tsconfig-utils": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz",
-      "integrity": "sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz",
+      "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -4453,14 +4694,14 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz",
-      "integrity": "sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz",
+      "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "8.33.1",
-        "@typescript-eslint/utils": "8.33.1",
+        "@typescript-eslint/typescript-estree": "8.34.0",
+        "@typescript-eslint/utils": "8.34.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^2.1.0"
       },
@@ -4477,9 +4718,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.33.1.tgz",
-      "integrity": "sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz",
+      "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -4491,16 +4732,16 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz",
-      "integrity": "sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz",
+      "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/project-service": "8.33.1",
-        "@typescript-eslint/tsconfig-utils": "8.33.1",
-        "@typescript-eslint/types": "8.33.1",
-        "@typescript-eslint/visitor-keys": "8.33.1",
+        "@typescript-eslint/project-service": "8.34.0",
+        "@typescript-eslint/tsconfig-utils": "8.34.0",
+        "@typescript-eslint/types": "8.34.0",
+        "@typescript-eslint/visitor-keys": "8.34.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
@@ -4545,17 +4786,30 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/@typescript-eslint/utils": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.33.1.tgz",
-      "integrity": "sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz",
+      "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.7.0",
-        "@typescript-eslint/scope-manager": "8.33.1",
-        "@typescript-eslint/types": "8.33.1",
-        "@typescript-eslint/typescript-estree": "8.33.1"
+        "@typescript-eslint/scope-manager": "8.34.0",
+        "@typescript-eslint/types": "8.34.0",
+        "@typescript-eslint/typescript-estree": "8.34.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4570,13 +4824,13 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.33.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz",
-      "integrity": "sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==",
+      "version": "8.34.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz",
+      "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.33.1",
+        "@typescript-eslint/types": "8.34.0",
         "eslint-visitor-keys": "^4.2.0"
       },
       "engines": {
@@ -4588,9 +4842,9 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
-      "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+      "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
       "dev": true,
       "license": "Apache-2.0",
       "engines": {
@@ -4601,9 +4855,9 @@
       }
     },
     "node_modules/@unrs/resolver-binding-darwin-arm64": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.11.tgz",
-      "integrity": "sha512-i3/wlWjQJXMh1uiGtiv7k1EYvrrS3L1hdwmWJJiz1D8jWy726YFYPIxQWbEIVPVAgrfRR0XNlLrTQwq17cuCGw==",
+      "version": "1.7.12",
+      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.7.12.tgz",
+      "integrity": "sha512-C//UObaqVcGKpRMMThzBCDxbqM9YQg2dtWy3OwcERLu+qzLa781AqvGdgqwqakRO+cWCK6dl75ebAcsSozmARg==",
       "cpu": [
         "arm64"
       ],
@@ -4614,233 +4868,6 @@
         "darwin"
       ]
     },
-    "node_modules/@unrs/resolver-binding-darwin-x64": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.7.11.tgz",
-      "integrity": "sha512-8XXyFvc6w6kmMmi6VYchZhjd5CDcp+Lv6Cn1YmUme0ypsZ/0Kzd+9ESrWtDrWibKPTgSteDTxp75cvBOY64FQQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "darwin"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-freebsd-x64": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.7.11.tgz",
-      "integrity": "sha512-0qJBYzP8Qk24CZ05RSWDQUjdiQUeIJGfqMMzbtXgCKl/a5xa6thfC0MQkGIr55LCLd6YmMyO640ifYUa53lybQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "freebsd"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.7.11.tgz",
-      "integrity": "sha512-1sGwpgvx+WZf0GFT6vkkOm6UJ+mlsVnjw+Yv9esK71idWeRAG3bbpkf3AoY8KIqKqmnzJExi0uKxXpakQ5Pcbg==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.7.11.tgz",
-      "integrity": "sha512-D/1F/2lTe+XAl3ohkYj51NjniVly8sIqkA/n1aOND3ZMO418nl2JNU95iVa1/RtpzaKcWEsNTtHRogykrUflJg==",
-      "cpu": [
-        "arm"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.7.11.tgz",
-      "integrity": "sha512-7vFWHLCCNFLEQlmwKQfVy066ohLLArZl+AV/AdmrD1/pD1FlmqM+FKbtnONnIwbHtgetFUCV/SRi1q4D49aTlw==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.7.11.tgz",
-      "integrity": "sha512-tYkGIx8hjWPh4zcn17jLEHU8YMmdP2obRTGkdaB3BguGHh31VCS3ywqC4QjTODjmhhNyZYkj/1Dz/+0kKvg9YA==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.7.11.tgz",
-      "integrity": "sha512-6F328QIUev29vcZeRX6v6oqKxfUoGwIIAhWGD8wSysnBYFY0nivp25jdWmAb1GildbCCaQvOKEhCok7YfWkj4Q==",
-      "cpu": [
-        "ppc64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.7.11.tgz",
-      "integrity": "sha512-NqhWmiGJGdzbZbeucPZIG9Iav4lyYLCarEnxAceguMx9qlpeEF7ENqYKOwB8Zqk7/CeuYMEcLYMaW2li6HyDzQ==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.7.11.tgz",
-      "integrity": "sha512-J2RPIFKMdTrLtBdfR1cUMKl8Gcy05nlQ+bEs/6al7EdWLk9cs3tnDREHZ7mV9uGbeghpjo4i8neNZNx3PYUY9w==",
-      "cpu": [
-        "riscv64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.7.11.tgz",
-      "integrity": "sha512-bDpGRerHvvHdhun7MmFUNDpMiYcJSqWckwAVVRTJf8F+RyqYJOp/mx04PDc7DhpNPeWdnTMu91oZRMV+gGaVcQ==",
-      "cpu": [
-        "s390x"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.7.11.tgz",
-      "integrity": "sha512-G9U7bVmylzRLma3cK39RBm3guoD1HOvY4o0NS4JNm37AD0lS7/xyMt7kn0JejYyc0Im8J+rH69/dXGM9DAJcSQ==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-linux-x64-musl": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.7.11.tgz",
-      "integrity": "sha512-7qL20SBKomekSunm7M9Fe5L93bFbn+FbHiGJbfTlp0RKhPVoJDP73vOxf1QrmJHyDPECsGWPFnKa/f8fO2FsHw==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "linux"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-wasm32-wasi": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.7.11.tgz",
-      "integrity": "sha512-jisvIva8MidjI+B1lFRZZMfCPaCISePgTyR60wNT1MeQvIh5Ksa0G3gvI+Iqyj3jqYbvOHByenpa5eDGcSdoSg==",
-      "cpu": [
-        "wasm32"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "dependencies": {
-        "@napi-rs/wasm-runtime": "^0.2.10"
-      },
-      "engines": {
-        "node": ">=14.0.0"
-      }
-    },
-    "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.7.11.tgz",
-      "integrity": "sha512-G+H5nQZ8sRZ8ebMY6mRGBBvTEzMYEcgVauLsNHpvTUavZoCCRVP1zWkCZgOju2dW3O22+8seTHniTdl1/uLz3g==",
-      "cpu": [
-        "arm64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.7.11.tgz",
-      "integrity": "sha512-Hfy46DBfFzyv0wgR0MMOwFFib2W2+Btc8oE5h4XlPhpelnSyA6nFxkVIyTgIXYGTdFaLoZFNn62fmqx3rjEg3A==",
-      "cpu": [
-        "ia32"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
-    "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.7.11.tgz",
-      "integrity": "sha512-7L8NdsQlCJ8T106Gbz/AjzM4QKWVsoQbKpB9bMBGcIZswUuAnJMHpvbqGW3RBqLHCIwX4XZ5fxSBHEFcK2h9wA==",
-      "cpu": [
-        "x64"
-      ],
-      "dev": true,
-      "license": "MIT",
-      "optional": true,
-      "os": [
-        "win32"
-      ]
-    },
     "node_modules/abab": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -4850,9 +4877,9 @@
       "license": "BSD-3-Clause"
     },
     "node_modules/acorn": {
-      "version": "8.14.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
-      "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
+      "version": "8.15.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+      "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
       "dev": true,
       "license": "MIT",
       "bin": {
@@ -5006,19 +5033,6 @@
         "node": ">= 8"
       }
     },
-    "node_modules/append-transform": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz",
-      "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "default-require-extensions": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/arch": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
@@ -5040,13 +5054,6 @@
       ],
       "license": "MIT"
     },
-    "node_modules/archy": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
-      "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/argparse": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -5404,16 +5411,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/babel-plugin-istanbul/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/babel-plugin-jest-hoist": {
       "version": "29.6.3",
       "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
@@ -5476,16 +5473,6 @@
         "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
       }
     },
-    "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/babel-plugin-polyfill-corejs3": {
       "version": "0.11.1",
       "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz",
@@ -5746,61 +5733,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/caching-transform": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz",
-      "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "hasha": "^5.0.0",
-        "make-dir": "^3.0.0",
-        "package-hash": "^4.0.0",
-        "write-file-atomic": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/caching-transform/node_modules/make-dir": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
-      "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "semver": "^6.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/caching-transform/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
-    "node_modules/caching-transform/node_modules/write-file-atomic": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
-      "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "imurmurhash": "^0.1.4",
-        "is-typedarray": "^1.0.0",
-        "signal-exit": "^3.0.2",
-        "typedarray-to-buffer": "^3.1.5"
-      }
-    },
     "node_modules/call-bind": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -5861,13 +5793,16 @@
       }
     },
     "node_modules/camelcase": {
-      "version": "5.3.1",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
-      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+      "version": "6.3.0",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
       "dev": true,
       "license": "MIT",
       "engines": {
-        "node": ">=6"
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/caniuse-lite": {
@@ -6146,13 +6081,6 @@
         "node": ">=4.0.0"
       }
     },
-    "node_modules/commondir": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
-      "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -6161,20 +6089,19 @@
       "license": "MIT"
     },
     "node_modules/convert-source-map": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
-      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
-      "dev": true,
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+      "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
       "license": "MIT"
     },
     "node_modules/core-js-compat": {
-      "version": "3.42.0",
-      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz",
-      "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==",
+      "version": "3.43.0",
+      "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz",
+      "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "browserslist": "^4.24.4"
+        "browserslist": "^4.25.0"
       },
       "funding": {
         "type": "opencollective",
@@ -6237,6 +6164,16 @@
         "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
       }
     },
+    "node_modules/cross-fetch": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz",
+      "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "node-fetch": "^2.7.0"
+      }
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.6",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -6431,6 +6368,19 @@
         "node": "^16.0.0 || ^18.0.0 || >=20.0.0"
       }
     },
+    "node_modules/cypress/node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/cypress/node_modules/supports-color": {
       "version": "8.1.1",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
@@ -6447,6 +6397,134 @@
         "url": "https://github.com/chalk/supports-color?sponsor=1"
       }
     },
+    "node_modules/d3-array": {
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
+      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
+      "license": "ISC",
+      "dependencies": {
+        "internmap": "1 - 2"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-color": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
+      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-delaunay": {
+      "version": "6.0.4",
+      "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
+      "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
+      "license": "ISC",
+      "dependencies": {
+        "delaunator": "5"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-format": {
+      "version": "1.4.5",
+      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
+      "integrity": "sha512-J0piedu6Z8iB6TbIGfZgDzfXxUFN3qQRMofy2oPdXzQibYGqPB/9iMcxr/TGalU+2RsyDO+U4f33id8tbnSRMQ==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/d3-interpolate": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
+      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-color": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-path": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
+      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-scale": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
+      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "2.10.0 - 3",
+        "d3-format": "1 - 3",
+        "d3-interpolate": "1.2.0 - 3",
+        "d3-time": "2.1.1 - 3",
+        "d3-time-format": "2 - 4"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-scale-chromatic": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz",
+      "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-color": "1 - 3",
+        "d3-interpolate": "1 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-scale/node_modules/d3-time": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
+      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-array": "2 - 3"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-shape": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
+      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
+      "license": "ISC",
+      "dependencies": {
+        "d3-path": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/d3-time": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz",
+      "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/d3-time-format": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-3.0.0.tgz",
+      "integrity": "sha512-UXJh6EKsHBTjopVqZBhFysQcoXSv/5yLONZvkQ5Kk3qbwiUYkdX17Xa1PT6U1ZWXGGfB1ey5L8dKMlFq2DO0Ag==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "d3-time": "1 - 2"
+      }
+    },
     "node_modules/damerau-levenshtein": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@@ -6560,16 +6638,6 @@
         }
       }
     },
-    "node_modules/decamelize": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/decimal.js": {
       "version": "10.5.0",
       "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
@@ -6604,25 +6672,9 @@
       "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
       "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
       "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/default-require-extensions": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz",
-      "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "strip-bom": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
       }
     },
     "node_modules/define-data-property": {
@@ -6661,6 +6713,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/delaunator": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
+      "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
+      "license": "ISC",
+      "dependencies": {
+        "robust-predicates": "^3.0.2"
+      }
+    },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -6756,19 +6817,6 @@
         "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
       }
     },
-    "node_modules/dom-serializer/node_modules/entities": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
-      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
-      "dev": true,
-      "license": "BSD-2-Clause",
-      "engines": {
-        "node": ">=0.12"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/entities?sponsor=1"
-      }
-    },
     "node_modules/domelementtype": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
@@ -6865,9 +6913,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.165",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz",
-      "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==",
+      "version": "1.5.166",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz",
+      "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==",
       "dev": true,
       "license": "ISC"
     },
@@ -6916,9 +6964,9 @@
       }
     },
     "node_modules/entities": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
-      "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
       "dev": true,
       "license": "BSD-2-Clause",
       "engines": {
@@ -7114,13 +7162,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/es6-error": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
-      "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/escalade": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -7165,6 +7206,17 @@
         "source-map": "~0.6.1"
       }
     },
+    "node_modules/escodegen/node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "optional": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/eslint": {
       "version": "9.28.0",
       "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz",
@@ -7254,6 +7306,16 @@
         }
       }
     },
+    "node_modules/eslint-config-next/node_modules/@next/eslint-plugin-next": {
+      "version": "15.3.2",
+      "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.2.tgz",
+      "integrity": "sha512-ijVRTXBgnHT33aWnDtmlG+LJD+5vhc9AKTJPquGG5NKXjpKNjc62woIhFtrAcWdBobt8kqjCoaJ0q6sDQoX7aQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fast-glob": "3.3.1"
+      }
+    },
     "node_modules/eslint-config-next/node_modules/eslint-import-resolver-typescript": {
       "version": "3.10.1",
       "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz",
@@ -7289,6 +7351,36 @@
         }
       }
     },
+    "node_modules/eslint-config-next/node_modules/fast-glob": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
+      "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@nodelib/fs.stat": "^2.0.2",
+        "@nodelib/fs.walk": "^1.2.3",
+        "glob-parent": "^5.1.2",
+        "merge2": "^1.3.0",
+        "micromatch": "^4.0.4"
+      },
+      "engines": {
+        "node": ">=8.6.0"
+      }
+    },
+    "node_modules/eslint-config-next/node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/eslint-config-prettier": {
       "version": "8.10.0",
       "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
@@ -7456,20 +7548,10 @@
         "ms": "^2.1.1"
       }
     },
-    "node_modules/eslint-plugin-import/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/eslint-plugin-jest": {
-      "version": "28.12.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.12.0.tgz",
-      "integrity": "sha512-J6zmDp8WiQ9tyvYXE+3RFy7/+l4hraWLzmsabYXyehkmmDd36qV4VQFc7XzcsD8C1PTNt646MSx25bO1mdd9Yw==",
+      "version": "28.13.0",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.13.0.tgz",
+      "integrity": "sha512-4AuBcFWOriOeEqy6s4Zup/dQ7E1EPTyyfDaMYmM2YP9xEWPWwK3yYifH1dzY6aHRvyx7y53qMSIyT5s+jrorsQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -7641,20 +7723,10 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/eslint-plugin-react/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/eslint-scope": {
-      "version": "8.3.0",
-      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
-      "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==",
+      "version": "8.4.0",
+      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+      "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
       "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
@@ -7682,9 +7754,9 @@
       }
     },
     "node_modules/eslint/node_modules/eslint-visitor-keys": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
-      "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+      "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
       "dev": true,
       "license": "Apache-2.0",
       "engines": {
@@ -7695,15 +7767,15 @@
       }
     },
     "node_modules/espree": {
-      "version": "10.3.0",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
-      "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
+      "version": "10.4.0",
+      "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+      "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
       "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
-        "acorn": "^8.14.0",
+        "acorn": "^8.15.0",
         "acorn-jsx": "^5.3.2",
-        "eslint-visitor-keys": "^4.2.0"
+        "eslint-visitor-keys": "^4.2.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -7713,9 +7785,9 @@
       }
     },
     "node_modules/espree/node_modules/eslint-visitor-keys": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
-      "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+      "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
       "dev": true,
       "license": "Apache-2.0",
       "engines": {
@@ -8097,6 +8169,15 @@
         "node": ">=16.0.0"
       }
     },
+    "node_modules/filesize": {
+      "version": "10.1.6",
+      "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz",
+      "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==",
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">= 10.4.0"
+      }
+    },
     "node_modules/fill-range": {
       "version": "7.1.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -8110,50 +8191,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/find-cache-dir": {
-      "version": "3.3.2",
-      "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
-      "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "commondir": "^1.0.1",
-        "make-dir": "^3.0.2",
-        "pkg-dir": "^4.1.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
-      }
-    },
-    "node_modules/find-cache-dir/node_modules/make-dir": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
-      "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "semver": "^6.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/find-cache-dir/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
-    },
     "node_modules/find-root": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
@@ -8214,36 +8251,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/foreground-child": {
-      "version": "3.3.1",
-      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
-      "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "cross-spawn": "^7.0.6",
-        "signal-exit": "^4.0.1"
-      },
-      "engines": {
-        "node": ">=14"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
-    "node_modules/foreground-child/node_modules/signal-exit": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
-      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
-      "dev": true,
-      "license": "ISC",
-      "engines": {
-        "node": ">=14"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
-    },
     "node_modules/forever-agent": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -8255,41 +8262,67 @@
       }
     },
     "node_modules/form-data": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
-      "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
+      "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "asynckit": "^0.4.0",
         "combined-stream": "^1.0.8",
         "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
         "mime-types": "^2.1.12"
       },
       "engines": {
         "node": ">= 6"
       }
     },
-    "node_modules/fromentries": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz",
-      "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==",
-      "dev": true,
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/feross"
-        },
-        {
-          "type": "patreon",
-          "url": "https://www.patreon.com/feross"
-        },
-        {
-          "type": "consulting",
-          "url": "https://feross.org/support"
-        }
-      ],
-      "license": "MIT"
+    "node_modules/framer-motion": {
+      "version": "4.1.17",
+      "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-4.1.17.tgz",
+      "integrity": "sha512-thx1wvKzblzbs0XaK2X0G1JuwIdARcoNOW7VVwjO8BUltzXPyONGAElLu6CiCScsOQRI7FIk/45YTFtJw5Yozw==",
+      "license": "MIT",
+      "dependencies": {
+        "framesync": "5.3.0",
+        "hey-listen": "^1.0.8",
+        "popmotion": "9.3.6",
+        "style-value-types": "4.1.4",
+        "tslib": "^2.1.0"
+      },
+      "optionalDependencies": {
+        "@emotion/is-prop-valid": "^0.8.2"
+      },
+      "peerDependencies": {
+        "react": ">=16.8 || ^17.0.0",
+        "react-dom": ">=16.8 || ^17.0.0"
+      }
+    },
+    "node_modules/framer-motion/node_modules/@emotion/is-prop-valid": {
+      "version": "0.8.8",
+      "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
+      "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@emotion/memoize": "0.7.4"
+      }
+    },
+    "node_modules/framer-motion/node_modules/@emotion/memoize": {
+      "version": "0.7.4",
+      "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
+      "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/framesync": {
+      "version": "5.3.0",
+      "resolved": "https://registry.npmjs.org/framesync/-/framesync-5.3.0.tgz",
+      "integrity": "sha512-oc5m68HDO/tuK2blj7ZcdEBRx3p1PjrgHazL8GYEpvULhrtGIFbQArN6cQS2QhW8mitffaB+VYzMjDqBxxQeoA==",
+      "license": "MIT",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      }
     },
     "node_modules/fs-extra": {
       "version": "9.1.0",
@@ -8557,9 +8590,9 @@
       }
     },
     "node_modules/globals": {
-      "version": "14.0.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
-      "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+      "version": "16.2.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz",
+      "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -8694,33 +8727,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/hasha": {
-      "version": "5.2.2",
-      "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
-      "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "is-stream": "^2.0.0",
-        "type-fest": "^0.8.0"
-      },
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/hasha/node_modules/type-fest": {
-      "version": "0.8.1",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
-      "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
-      "dev": true,
-      "license": "(MIT OR CC0-1.0)",
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/hasown": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -8733,6 +8739,12 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/hey-listen": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/hey-listen/-/hey-listen-1.0.8.tgz",
+      "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==",
+      "license": "MIT"
+    },
     "node_modules/hoist-non-react-statics": {
       "version": "3.3.2",
       "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@@ -8992,6 +9004,15 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/internmap": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
+      "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/is-array-buffer": {
       "version": "3.0.5",
       "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -9079,6 +9100,19 @@
         "semver": "^7.7.1"
       }
     },
+    "node_modules/is-bun-module/node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/is-callable": {
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -9485,16 +9519,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/is-windows": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
-      "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/isarray": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
@@ -9519,22 +9543,9 @@
     "node_modules/istanbul-lib-coverage": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
-      "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
-      "dev": true,
-      "license": "BSD-3-Clause",
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/istanbul-lib-hook": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz",
-      "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==",
+      "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
       "dev": true,
       "license": "BSD-3-Clause",
-      "dependencies": {
-        "append-transform": "^2.0.0"
-      },
       "engines": {
         "node": ">=8"
       }
@@ -9556,35 +9567,17 @@
         "node": ">=10"
       }
     },
-    "node_modules/istanbul-lib-processinfo": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz",
-      "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==",
+    "node_modules/istanbul-lib-instrument/node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
       "dev": true,
       "license": "ISC",
-      "dependencies": {
-        "archy": "^1.0.0",
-        "cross-spawn": "^7.0.3",
-        "istanbul-lib-coverage": "^3.2.0",
-        "p-map": "^3.0.0",
-        "rimraf": "^3.0.0",
-        "uuid": "^8.3.2"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/istanbul-lib-processinfo/node_modules/p-map": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
-      "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "aggregate-error": "^3.0.0"
+      "bin": {
+        "semver": "bin/semver.js"
       },
       "engines": {
-        "node": ">=8"
+        "node": ">=10"
       }
     },
     "node_modules/istanbul-lib-report": {
@@ -9617,6 +9610,16 @@
         "node": ">=10"
       }
     },
+    "node_modules/istanbul-lib-source-maps/node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/istanbul-reports": {
       "version": "3.1.7",
       "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
@@ -10567,6 +10570,19 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/jest-snapshot/node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/jest-transform-stub": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/jest-transform-stub/-/jest-transform-stub-2.0.0.tgz",
@@ -10639,19 +10655,6 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
-    "node_modules/jest-validate/node_modules/camelcase": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
-      "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=10"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/jest-validate/node_modules/pretty-format": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -11063,7 +11066,6 @@
       "version": "4.17.21",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
       "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
-      "dev": true,
       "license": "MIT"
     },
     "node_modules/lodash.debounce": {
@@ -11073,13 +11075,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/lodash.flattendeep": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
-      "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
-      "dev": true,
-      "license": "MIT"
-    },
     "node_modules/lodash.merge": {
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -11222,6 +11217,19 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/make-dir/node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/makeerror": {
       "version": "1.0.12",
       "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
@@ -11468,312 +11476,152 @@
         }
       }
     },
-    "node_modules/no-case": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
-      "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "lower-case": "^2.0.2",
-        "tslib": "^2.0.3"
-      }
-    },
-    "node_modules/node-int64": {
-      "version": "0.4.0",
-      "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
-      "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/node-preload": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz",
-      "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "process-on-spawn": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/node-releases": {
-      "version": "2.0.19",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
-      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/normalize-path": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
-      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/npm-run-path": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
-      "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
-      "dev": true,
+    "node_modules/next/node_modules/@next/swc-linux-x64-gnu": {
+      "version": "15.3.2",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz",
+      "integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==",
+      "cpu": [
+        "x64"
+      ],
       "license": "MIT",
-      "dependencies": {
-        "path-key": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/nth-check": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
-      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
-      "dev": true,
-      "license": "BSD-2-Clause",
-      "dependencies": {
-        "boolbase": "^1.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/fb55/nth-check?sponsor=1"
-      }
-    },
-    "node_modules/nwsapi": {
-      "version": "2.2.20",
-      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
-      "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/nyc": {
-      "version": "17.1.0",
-      "resolved": "https://registry.npmjs.org/nyc/-/nyc-17.1.0.tgz",
-      "integrity": "sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "@istanbuljs/load-nyc-config": "^1.0.0",
-        "@istanbuljs/schema": "^0.1.2",
-        "caching-transform": "^4.0.0",
-        "convert-source-map": "^1.7.0",
-        "decamelize": "^1.2.0",
-        "find-cache-dir": "^3.2.0",
-        "find-up": "^4.1.0",
-        "foreground-child": "^3.3.0",
-        "get-package-type": "^0.1.0",
-        "glob": "^7.1.6",
-        "istanbul-lib-coverage": "^3.0.0",
-        "istanbul-lib-hook": "^3.0.0",
-        "istanbul-lib-instrument": "^6.0.2",
-        "istanbul-lib-processinfo": "^2.0.2",
-        "istanbul-lib-report": "^3.0.0",
-        "istanbul-lib-source-maps": "^4.0.0",
-        "istanbul-reports": "^3.0.2",
-        "make-dir": "^3.0.0",
-        "node-preload": "^0.2.1",
-        "p-map": "^3.0.0",
-        "process-on-spawn": "^1.0.0",
-        "resolve-from": "^5.0.0",
-        "rimraf": "^3.0.0",
-        "signal-exit": "^3.0.2",
-        "spawn-wrap": "^2.0.0",
-        "test-exclude": "^6.0.0",
-        "yargs": "^15.0.2"
-      },
-      "bin": {
-        "nyc": "bin/nyc.js"
-      },
+      "optional": true,
+      "os": [
+        "linux"
+      ],
       "engines": {
-        "node": ">=18"
-      }
-    },
-    "node_modules/nyc/node_modules/cliui": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
-      "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "string-width": "^4.2.0",
-        "strip-ansi": "^6.0.0",
-        "wrap-ansi": "^6.2.0"
+        "node": ">= 10"
       }
     },
-    "node_modules/nyc/node_modules/convert-source-map": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
-      "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
-      "dev": true,
-      "license": "MIT"
-    },
-    "node_modules/nyc/node_modules/find-up": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
-      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
-      "dev": true,
+    "node_modules/next/node_modules/@next/swc-linux-x64-musl": {
+      "version": "15.3.2",
+      "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz",
+      "integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==",
+      "cpu": [
+        "x64"
+      ],
       "license": "MIT",
-      "dependencies": {
-        "locate-path": "^5.0.0",
-        "path-exists": "^4.0.0"
-      },
+      "optional": true,
+      "os": [
+        "linux"
+      ],
       "engines": {
-        "node": ">=8"
+        "node": ">= 10"
       }
     },
-    "node_modules/nyc/node_modules/locate-path": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
-      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+    "node_modules/no-case": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
+      "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "p-locate": "^4.1.0"
-      },
-      "engines": {
-        "node": ">=8"
+        "lower-case": "^2.0.2",
+        "tslib": "^2.0.3"
       }
     },
-    "node_modules/nyc/node_modules/make-dir": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
-      "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+    "node_modules/node-fetch": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "semver": "^6.0.0"
+        "whatwg-url": "^5.0.0"
       },
       "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/nyc/node_modules/p-limit": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "p-try": "^2.0.0"
+        "node": "4.x || >=6.0.0"
       },
-      "engines": {
-        "node": ">=6"
+      "peerDependencies": {
+        "encoding": "^0.1.0"
       },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
       }
     },
-    "node_modules/nyc/node_modules/p-locate": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
-      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+    "node_modules/node-fetch/node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
       "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "p-limit": "^2.2.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
+      "license": "MIT"
     },
-    "node_modules/nyc/node_modules/p-map": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz",
-      "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==",
+    "node_modules/node-fetch/node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
       "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "aggregate-error": "^3.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
+      "license": "BSD-2-Clause"
     },
-    "node_modules/nyc/node_modules/resolve-from": {
+    "node_modules/node-fetch/node_modules/whatwg-url": {
       "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
-      "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
       "dev": true,
       "license": "MIT",
-      "engines": {
-        "node": ">=8"
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
       }
     },
-    "node_modules/nyc/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+    "node_modules/node-int64": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+      "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
       "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
-      }
+      "license": "MIT"
     },
-    "node_modules/nyc/node_modules/wrap-ansi": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
-      "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+    "node_modules/node-releases": {
+      "version": "2.0.19",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
+      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
       "dev": true,
       "license": "MIT",
-      "dependencies": {
-        "ansi-styles": "^4.0.0",
-        "string-width": "^4.1.0",
-        "strip-ansi": "^6.0.0"
-      },
       "engines": {
-        "node": ">=8"
+        "node": ">=0.10.0"
       }
     },
-    "node_modules/nyc/node_modules/y18n": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
-      "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
-      "dev": true,
-      "license": "ISC"
-    },
-    "node_modules/nyc/node_modules/yargs": {
-      "version": "15.4.1",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
-      "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+    "node_modules/npm-run-path": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+      "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "cliui": "^6.0.0",
-        "decamelize": "^1.2.0",
-        "find-up": "^4.1.0",
-        "get-caller-file": "^2.0.1",
-        "require-directory": "^2.1.1",
-        "require-main-filename": "^2.0.0",
-        "set-blocking": "^2.0.0",
-        "string-width": "^4.2.0",
-        "which-module": "^2.0.0",
-        "y18n": "^4.0.0",
-        "yargs-parser": "^18.1.2"
+        "path-key": "^3.0.0"
       },
       "engines": {
         "node": ">=8"
       }
     },
-    "node_modules/nyc/node_modules/yargs-parser": {
-      "version": "18.1.3",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
-      "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+    "node_modules/nth-check": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
       "dev": true,
-      "license": "ISC",
+      "license": "BSD-2-Clause",
       "dependencies": {
-        "camelcase": "^5.0.0",
-        "decamelize": "^1.2.0"
+        "boolbase": "^1.0.0"
       },
-      "engines": {
-        "node": ">=6"
+      "funding": {
+        "url": "https://github.com/fb55/nth-check?sponsor=1"
       }
     },
+    "node_modules/nwsapi": {
+      "version": "2.2.20",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
+      "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/object-assign": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -12023,22 +11871,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/package-hash": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz",
-      "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "graceful-fs": "^4.1.15",
-        "hasha": "^5.0.0",
-        "lodash.flattendeep": "^4.4.0",
-        "release-zalgo": "^1.0.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -12082,6 +11914,19 @@
         "url": "https://github.com/inikulin/parse5?sponsor=1"
       }
     },
+    "node_modules/parse5/node_modules/entities": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+      "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+      "dev": true,
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
     "node_modules/path-exists": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -12249,6 +12094,18 @@
         "node": ">=8"
       }
     },
+    "node_modules/popmotion": {
+      "version": "9.3.6",
+      "resolved": "https://registry.npmjs.org/popmotion/-/popmotion-9.3.6.tgz",
+      "integrity": "sha512-ZTbXiu6zIggXzIliMi8LGxXBF5ST+wkpXGEjeTUDUOCdSQ356hij/xjeUdv0F8zCQNeqB1+PR5/BB+gC+QLAPw==",
+      "license": "MIT",
+      "dependencies": {
+        "framesync": "5.3.0",
+        "hey-listen": "^1.0.8",
+        "style-value-types": "4.1.4",
+        "tslib": "^2.1.0"
+      }
+    },
     "node_modules/possible-typed-array-names": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
@@ -12367,6 +12224,13 @@
         "url": "https://github.com/chalk/ansi-styles?sponsor=1"
       }
     },
+    "node_modules/pretty-format/node_modules/react-is": {
+      "version": "17.0.2",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/process": {
       "version": "0.11.10",
       "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -12377,19 +12241,6 @@
         "node": ">= 0.6.0"
       }
     },
-    "node_modules/process-on-spawn": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz",
-      "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "fromentries": "^1.2.0"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
     "node_modules/prompts": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -12549,12 +12400,34 @@
       }
     },
     "node_modules/react-is": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
-      "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
-      "dev": true,
+      "version": "19.1.0",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz",
+      "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==",
       "license": "MIT"
     },
+    "node_modules/react-material-ui-carousel": {
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/react-material-ui-carousel/-/react-material-ui-carousel-3.4.2.tgz",
+      "integrity": "sha512-jUbC5aBWqbbbUOOdUe3zTVf4kMiZFwKJqwhxzHgBfklaXQbSopis4iWAHvEOLcZtSIJk4JAGxKE0CmxDoxvUuw==",
+      "license": "MIT",
+      "dependencies": {
+        "@emotion/react": "^11.7.1",
+        "@emotion/styled": "^11.6.0",
+        "@mui/icons-material": "^5.4.1",
+        "@mui/material": "^5.4.1",
+        "@mui/system": "^5.4.1",
+        "framer-motion": "^4.1.17"
+      },
+      "peerDependencies": {
+        "@emotion/react": "^11.4.1",
+        "@emotion/styled": "^11.3.0",
+        "@mui/icons-material": "^5.0.0",
+        "@mui/material": "^5.0.0",
+        "@mui/system": "^5.0.0",
+        "react": "^17.0.1 || ^18.0.0",
+        "react-dom": "^17.0.2 || ^18.0.0"
+      }
+    },
     "node_modules/react-redux": {
       "version": "9.2.0",
       "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
@@ -12738,19 +12611,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/release-zalgo": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
-      "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "es6-error": "^4.0.1"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
     "node_modules/request-progress": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
@@ -12771,13 +12631,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/require-main-filename": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
-      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
-      "dev": true,
-      "license": "ISC"
-    },
     "node_modules/requires-port": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
@@ -12895,22 +12748,11 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/rimraf": {
+    "node_modules/robust-predicates": {
       "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
-      "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
-      "deprecated": "Rimraf versions prior to v4 are no longer supported",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "glob": "^7.1.3"
-      },
-      "bin": {
-        "rimraf": "bin.js"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/isaacs"
-      }
+      "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
+      "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
+      "license": "Unlicense"
     },
     "node_modules/run-parallel": {
       "version": "1.2.0",
@@ -13052,25 +12894,15 @@
       }
     },
     "node_modules/semver": {
-      "version": "7.7.2",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
-      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
-      "devOptional": true,
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "dev": true,
       "license": "ISC",
       "bin": {
         "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
       }
     },
-    "node_modules/set-blocking": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
-      "dev": true,
-      "license": "ISC"
-    },
     "node_modules/set-function-length": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -13162,6 +12994,19 @@
         "@img/sharp-win32-x64": "0.34.2"
       }
     },
+    "node_modules/sharp/node_modules/semver": {
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "license": "ISC",
+      "optional": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -13329,10 +13174,9 @@
       }
     },
     "node_modules/source-map": {
-      "version": "0.6.1",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-      "dev": true,
+      "version": "0.5.7",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+      "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
       "license": "BSD-3-Clause",
       "engines": {
         "node": ">=0.10.0"
@@ -13358,62 +13202,14 @@
         "source-map": "^0.6.0"
       }
     },
-    "node_modules/spawn-wrap": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz",
-      "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "foreground-child": "^2.0.0",
-        "is-windows": "^1.0.2",
-        "make-dir": "^3.0.0",
-        "rimraf": "^3.0.0",
-        "signal-exit": "^3.0.2",
-        "which": "^2.0.1"
-      },
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/spawn-wrap/node_modules/foreground-child": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz",
-      "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==",
-      "dev": true,
-      "license": "ISC",
-      "dependencies": {
-        "cross-spawn": "^7.0.0",
-        "signal-exit": "^3.0.2"
-      },
-      "engines": {
-        "node": ">=8.0.0"
-      }
-    },
-    "node_modules/spawn-wrap/node_modules/make-dir": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
-      "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+    "node_modules/source-map-support/node_modules/source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
       "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "semver": "^6.0.0"
-      },
+      "license": "BSD-3-Clause",
       "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/spawn-wrap/node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "dev": true,
-      "license": "ISC",
-      "bin": {
-        "semver": "bin/semver.js"
+        "node": ">=0.10.0"
       }
     },
     "node_modules/sprintf-js": {
@@ -13719,6 +13515,16 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/style-value-types": {
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/style-value-types/-/style-value-types-4.1.4.tgz",
+      "integrity": "sha512-LCJL6tB+vPSUoxgUBt9juXIlNJHtBMy8jkXzUJSBzeHWdBu6lhzHqCvLVkXFGsFIlNa2ln1sQHya/gzaFmB2Lg==",
+      "license": "MIT",
+      "dependencies": {
+        "hey-listen": "^1.0.8",
+        "tslib": "^2.1.0"
+      }
+    },
     "node_modules/styled-jsx": {
       "version": "5.1.6",
       "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
@@ -14191,16 +13997,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/typedarray-to-buffer": {
-      "version": "3.1.5",
-      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
-      "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "is-typedarray": "^1.0.0"
-      }
-    },
     "node_modules/typescript": {
       "version": "5.8.3",
       "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -14296,9 +14092,9 @@
       }
     },
     "node_modules/unrs-resolver": {
-      "version": "1.7.11",
-      "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.11.tgz",
-      "integrity": "sha512-OhuAzBImFPjKNgZ2JwHMfGFUA6NSbRegd1+BPjC1Y0E6X9Y/vJ4zKeGmIMqmlYboj6cMNEwKI+xQisrg4J0HaQ==",
+      "version": "1.7.12",
+      "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.7.12.tgz",
+      "integrity": "sha512-pfcdDxrVoUc5ZB3VCVJNSWbs63lgQVYLVw4k/rCr8Smi/V2Sxi1odEckVq6Zf803OtbYia1+YpiGCZoODfWLsQ==",
       "dev": true,
       "hasInstallScript": true,
       "license": "MIT",
@@ -14309,23 +14105,23 @@
         "url": "https://opencollective.com/unrs-resolver"
       },
       "optionalDependencies": {
-        "@unrs/resolver-binding-darwin-arm64": "1.7.11",
-        "@unrs/resolver-binding-darwin-x64": "1.7.11",
-        "@unrs/resolver-binding-freebsd-x64": "1.7.11",
-        "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.11",
-        "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.11",
-        "@unrs/resolver-binding-linux-arm64-gnu": "1.7.11",
-        "@unrs/resolver-binding-linux-arm64-musl": "1.7.11",
-        "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.11",
-        "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.11",
-        "@unrs/resolver-binding-linux-riscv64-musl": "1.7.11",
-        "@unrs/resolver-binding-linux-s390x-gnu": "1.7.11",
-        "@unrs/resolver-binding-linux-x64-gnu": "1.7.11",
-        "@unrs/resolver-binding-linux-x64-musl": "1.7.11",
-        "@unrs/resolver-binding-wasm32-wasi": "1.7.11",
-        "@unrs/resolver-binding-win32-arm64-msvc": "1.7.11",
-        "@unrs/resolver-binding-win32-ia32-msvc": "1.7.11",
-        "@unrs/resolver-binding-win32-x64-msvc": "1.7.11"
+        "@unrs/resolver-binding-darwin-arm64": "1.7.12",
+        "@unrs/resolver-binding-darwin-x64": "1.7.12",
+        "@unrs/resolver-binding-freebsd-x64": "1.7.12",
+        "@unrs/resolver-binding-linux-arm-gnueabihf": "1.7.12",
+        "@unrs/resolver-binding-linux-arm-musleabihf": "1.7.12",
+        "@unrs/resolver-binding-linux-arm64-gnu": "1.7.12",
+        "@unrs/resolver-binding-linux-arm64-musl": "1.7.12",
+        "@unrs/resolver-binding-linux-ppc64-gnu": "1.7.12",
+        "@unrs/resolver-binding-linux-riscv64-gnu": "1.7.12",
+        "@unrs/resolver-binding-linux-riscv64-musl": "1.7.12",
+        "@unrs/resolver-binding-linux-s390x-gnu": "1.7.12",
+        "@unrs/resolver-binding-linux-x64-gnu": "1.7.12",
+        "@unrs/resolver-binding-linux-x64-musl": "1.7.12",
+        "@unrs/resolver-binding-wasm32-wasi": "1.7.12",
+        "@unrs/resolver-binding-win32-arm64-msvc": "1.7.12",
+        "@unrs/resolver-binding-win32-ia32-msvc": "1.7.12",
+        "@unrs/resolver-binding-win32-x64-msvc": "1.7.12"
       }
     },
     "node_modules/untildify": {
@@ -14424,6 +14220,13 @@
         "node": ">=10.12.0"
       }
     },
+    "node_modules/v8-to-istanbul/node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/verror": {
       "version": "1.10.0",
       "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
@@ -14592,13 +14395,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/which-module": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
-      "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
-      "dev": true,
-      "license": "ISC"
-    },
     "node_modules/which-typed-array": {
       "version": "1.1.19",
       "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz",
diff --git a/package.json b/package.json
index f9fa04ee..958d3491 100644
--- a/package.json
+++ b/package.json
@@ -1,41 +1,45 @@
 {
   "name": "guidellm",
   "version": "0.1.0",
+  "type": "module",
   "scripts": {
     "dev": "next dev src/ui",
     "build": "next build src/ui",
     "lint": "next lint src/ui",
     "type-check": "tsc -p src/ui/tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p tsconfig.cypress.json --noEmit",
     "format": "prettier --write .",
-    "prepare": "husky install",
-    "test": "jest --config jest.config.js tests/ui",
-    "test:unit": "jest --config jest.config.js --coverage --coverageDirectory=coverage/unit tests/ui/unit",
-    "test:integration": "jest --config jest.config.js --coverage --coverageDirectory=coverage/integration tests/ui/integration",
+    "prepare": "husky",
+    "test": "jest --config jest.config.cjs tests/ui",
+    "test:watch": "jest --watch tests/ui",
+    "test:unit": "jest --config jest.config.cjs tests/ui/unit",
+    "test:integration": "jest --config jest.config.cjs tests/ui/integration",
     "test:e2e": "cypress run --headless",
-    "coverage:merge": "nyc merge coverage/unit > coverage/tmp.json && nyc merge coverage/integration coverage/tmp.json && mv coverage/tmp.json coverage/coverage-final.json",
-    "coverage:collect":
-    "mkdir -p .nyc_output && cp coverage/coverage-final.json .nyc_output/merged.json",
-    "coverage:report":
-    "nyc report --reporter=json-summary --reporter=lcov --report-dir=coverage",
-    "coverage:badge":
-    "jest-coverage-badges --input coverage/coverage-summary.json --output coverage/.coverage"
+    "coverage": "jest --config jest.config.cjs --coverage tests/ui",
+    "coverage:badge": "jest-coverage-badges --input coverage/coverage-summary.json --output coverage/.coverage"
   },
-
   "dependencies": {
     "@emotion/cache": "^11.13.1",
     "@emotion/react": "^11.14.0",
     "@emotion/styled": "^11.14.0",
     "@mui/material": "^5.11.7",
     "@mui/material-nextjs": "^5.16.6",
+    "@nivo/bar": "^0.88.0",
+    "@nivo/core": "^0.88.0",
+    "@nivo/line": "^0.88.0",
+    "@nivo/scales": "^0.88.0",
+    "@nivo/tooltip": "^0.88.0",
     "@reduxjs/toolkit": "^2.2.7",
+    "filesize": "^10.1.6",
     "next": "15.3.2",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "react-material-ui-carousel": "^3.4.2",
     "react-redux": "^9.1.2"
   },
   "devDependencies": {
     "@eslint/eslintrc": "^3",
     "@mui/types": "^7.2.14",
+    "@next/eslint-plugin-next": "^15.3.3",
     "@svgr/webpack": "^8.1.0",
     "@testing-library/jest-dom": "^5.16.5",
     "@testing-library/react": "^16.0.0",
@@ -47,6 +51,7 @@
     "@types/testing-library__jest-dom": "^5.14.9",
     "@typescript-eslint/eslint-plugin": "^8.33.1",
     "@typescript-eslint/parser": "^8.33.1",
+    "cross-fetch": "^4.1.0",
     "cypress": "^13.13.3",
     "eslint": "^9.0.0",
     "eslint-config-next": "15.3.2",
@@ -59,16 +64,20 @@
     "eslint-plugin-prettier": "^5.4.0",
     "eslint-plugin-react": "^7.31.10",
     "eslint-plugin-react-hooks": "^5.2.0",
+    "globals": "^16.2.0",
     "husky": "^9.1.7",
     "jest": "^29.7.0",
     "jest-coverage-badges": "^1.1.2",
     "jest-environment-jsdom": "^29.7.0",
     "jest-runner-groups": "^2.2.0",
     "jest-transform-stub": "^2.0.0",
-    "nyc": "^17.1.0",
     "prettier": "^3.5.3",
     "typescript": "^5"
   },
+  "optionalDependencies": {
+    "@next/swc-linux-x64-gnu": "^15.3.3",
+    "@next/swc-linux-x64-musl": "^15.3.3"
+  },
   "lint-staged": {
     "*.js": "eslint --cache --fix",
     "*.ts": "eslint --cache --fix",
diff --git a/src/guidellm/benchmark/benchmark.py b/src/guidellm/benchmark/benchmark.py
index 9f683f8e..1e2a5f4b 100644
--- a/src/guidellm/benchmark/benchmark.py
+++ b/src/guidellm/benchmark/benchmark.py
@@ -815,10 +815,7 @@ def from_stats(
                         req.first_token_time or req.start_time
                         for req in total_with_output_first
                     ],
-                    iter_counts=[
-                        req.output_tokens
-                        for req in total_with_output_first
-                    ],
+                    iter_counts=[req.output_tokens for req in total_with_output_first],
                     first_iter_counts=[
                         req.prompt_tokens for req in total_with_output_first
                     ],
diff --git a/src/ui/.env.development b/src/ui/.env.development
new file mode 100644
index 00000000..66c8d235
--- /dev/null
+++ b/src/ui/.env.development
@@ -0,0 +1,3 @@
+ASSET_PREFIX=https://review.neuralmagic.com/guidellm-ui/dev/_next
+BASE_PATH=/guidellm-ui/dev
+NEXT_PUBLIC_USE_MOCK_API=true
diff --git a/src/ui/.env.example b/src/ui/.env.example
new file mode 100644
index 00000000..06812a30
--- /dev/null
+++ b/src/ui/.env.example
@@ -0,0 +1,3 @@
+ASSET_PREFIX=http://localhost:3000
+BASE_PATH=http://localhost:3000
+NEXT_PUBLIC_USE_MOCK_API=true
diff --git a/src/ui/.env.local b/src/ui/.env.local
new file mode 100644
index 00000000..44ab168b
--- /dev/null
+++ b/src/ui/.env.local
@@ -0,0 +1,4 @@
+ASSET_PREFIX=http://localhost:3000
+BASE_PATH=http://localhost:3000
+NEXT_PUBLIC_USE_MOCK_API=true
+USE_MOCK_DATA=true
diff --git a/src/ui/.env.production b/src/ui/.env.production
new file mode 100644
index 00000000..66c8d235
--- /dev/null
+++ b/src/ui/.env.production
@@ -0,0 +1,3 @@
+ASSET_PREFIX=https://review.neuralmagic.com/guidellm-ui/dev/_next
+BASE_PATH=/guidellm-ui/dev
+NEXT_PUBLIC_USE_MOCK_API=true
diff --git a/src/ui/.env.staging b/src/ui/.env.staging
new file mode 100644
index 00000000..20142e5d
--- /dev/null
+++ b/src/ui/.env.staging
@@ -0,0 +1,3 @@
+ASSET_PREFIX=https://staging.guidellm.neuralmagic.com
+BASE_PATH=/guidellm-ui/staging
+NEXT_PUBLIC_USE_MOCK_API=true
diff --git a/src/ui/app/assets/icons/guidellm-icon-dark.png b/src/ui/app/assets/icons/guidellm-icon-dark.png
new file mode 100644
index 00000000..2e570c52
Binary files /dev/null and b/src/ui/app/assets/icons/guidellm-icon-dark.png differ
diff --git a/src/ui/app/assets/icons/guidellm-logo-light.png b/src/ui/app/assets/icons/guidellm-logo-light.png
new file mode 100644
index 00000000..7c8e2d6b
Binary files /dev/null and b/src/ui/app/assets/icons/guidellm-logo-light.png differ
diff --git a/src/ui/app/assets/icons/index.tsx b/src/ui/app/assets/icons/index.tsx
index be01d7a6..f3ac0773 100644
--- a/src/ui/app/assets/icons/index.tsx
+++ b/src/ui/app/assets/icons/index.tsx
@@ -2,18 +2,20 @@ import ArrowDown from './arrow-down.svg';
 import ArrowUp from './arrow-up.svg';
 import CheckCircle from './check-circle.svg';
 import Expand from './expand.svg';
+import guideLLMIconDark from './guidellm-icon-dark.png';
+import guideLLMLogoLight from './guidellm-logo-light.png';
 import Info from './info.svg';
-import NeuralMagicTitleV2 from './nm-logo-with-name.svg';
 import Open from './open.svg';
 import WarningCircle from './warning-circle.svg';
 
 export {
   ArrowDown,
   ArrowUp,
-  Expand,
   CheckCircle,
-  WarningCircle,
+  Expand,
+  guideLLMIconDark,
+  guideLLMLogoLight,
   Info,
-  NeuralMagicTitleV2,
   Open,
+  WarningCircle,
 };
diff --git a/src/ui/app/layout.tsx b/src/ui/app/layout.tsx
index 04102556..99696f12 100644
--- a/src/ui/app/layout.tsx
+++ b/src/ui/app/layout.tsx
@@ -1,6 +1,9 @@
 import type { Metadata, Viewport } from 'next';
 import React from 'react';
 
+import { benchmarksScript } from '@/lib/store/benchmarksWindowData';
+import { runInfoScript } from '@/lib/store/runInfoWindowData';
+import { workloadDetailsScript } from '@/lib/store/workloadDetailsWindowData';
 import './globals.css';
 
 export async function generateMetadata(): Promise<Metadata> {
@@ -10,7 +13,7 @@ export async function generateMetadata(): Promise<Metadata> {
     title: 'GuideLLM',
     description: 'LLM Benchmarking Tool',
     icons: {
-      icon: `${assetPrefix}/favicon.ico`,
+      icon: `${assetPrefix}/favicon.png`,
       apple: `${assetPrefix}/favicon-192x192.png`,
     },
     manifest: `${assetPrefix}/manifest.json`,
@@ -28,8 +31,39 @@ export default function RootLayout({
 }: Readonly<{
   children: React.ReactNode;
 }>) {
+  const emptyDataScript = (
+    <script
+      dangerouslySetInnerHTML={{
+        __html:
+          'window.run_info = {}; window.workload_details = {}; window.benchmarks = {};',
+      }}
+    />
+  );
+  const mockDataScript = (
+    <>
+      <script
+        dangerouslySetInnerHTML={{
+          __html: runInfoScript,
+        }}
+      />
+      <script
+        dangerouslySetInnerHTML={{
+          __html: workloadDetailsScript,
+        }}
+      />
+      <script
+        dangerouslySetInnerHTML={{
+          __html: benchmarksScript,
+        }}
+      />
+    </>
+  );
+  const dataScript =
+    process.env.USE_MOCK_DATA === 'true' ? mockDataScript : emptyDataScript;
+
   return (
     <html lang="en">
+      <head>{dataScript}</head>
       <body>{children}</body>
     </html>
   );
diff --git a/src/ui/app/not-found.tsx b/src/ui/app/not-found.tsx
new file mode 100644
index 00000000..60b5a096
--- /dev/null
+++ b/src/ui/app/not-found.tsx
@@ -0,0 +1,4 @@
+'use client';
+export default function Custom404() {
+  return <h1>404 - Page Not Found</h1>;
+}
diff --git a/src/ui/app/page.tsx b/src/ui/app/page.tsx
index 13102b0b..21c53304 100644
--- a/src/ui/app/page.tsx
+++ b/src/ui/app/page.tsx
@@ -1,16 +1,19 @@
 'use client';
-import { Typography, useTheme } from '@mui/material';
+import { useTheme } from '@mui/material';
 import { ThemeProvider } from '@mui/material/styles';
 import { AppRouterCacheProvider } from '@mui/material-nextjs/v13-appRouter';
 import Script from 'next/script';
 import React, { ReactNode } from 'react';
 
 import { muiThemeV3Dark } from '@/app/theme';
+import { MetricsSummary } from '@/lib/components/MetricsSummary';
 import { PageFooter } from '@/lib/components/PageFooter';
-
-import { FullPageWithHeaderAndFooterLayout } from '../lib/layouts/FullPageWithHeaderAndFooterLayout';
-import { ContentCenterer } from '../lib/layouts/helpers/ContentCenterer';
-import { ReduxProvider } from '../lib/store/provider';
+import { PageHeader } from '@/lib/components/PageHeader';
+import { WorkloadDetails } from '@/lib/components/WorkloadDetails';
+import { WorkloadMetrics } from '@/lib/components/WorkloadMetrics';
+import { FullPageWithHeaderAndFooterLayout } from '@/lib/layouts/FullPageWithHeaderAndFooterLayout';
+import { ContentCenterer } from '@/lib/layouts/helpers/ContentCenterer';
+import { ReduxProvider } from '@/lib/store/provider';
 
 interface MyProps {
   children?: ReactNode;
@@ -25,9 +28,7 @@ const Content = () => {
 
   const header = (
     <ContentCenterer>
-      <Typography color="white" variant="h1">
-        Header
-      </Typography>
+      <PageHeader />
     </ContentCenterer>
   );
   const footer = (
@@ -37,9 +38,9 @@ const Content = () => {
   );
   const body = (
     <ContentCenterer>
-      <Typography color="white" variant="h3">
-        GuideLLM
-      </Typography>
+      <WorkloadDetails />
+      <MetricsSummary />
+      <WorkloadMetrics />
     </ContentCenterer>
   );
   return (
diff --git a/src/ui/app/types/images.d.ts b/src/ui/app/types/images.d.ts
new file mode 100644
index 00000000..546475dd
--- /dev/null
+++ b/src/ui/app/types/images.d.ts
@@ -0,0 +1,4 @@
+declare module '*.png' {
+  const content: import('next/image').StaticImageData;
+  export default content;
+}
diff --git a/src/ui/lib/components/Badge/Badge.component.tsx b/src/ui/lib/components/Badge/Badge.component.tsx
new file mode 100644
index 00000000..16f6e35f
--- /dev/null
+++ b/src/ui/lib/components/Badge/Badge.component.tsx
@@ -0,0 +1,8 @@
+import { FC } from 'react';
+
+import { BadgeProps } from './Badge.interfaces';
+import { StyledTypography } from './Badge.styles';
+
+export const Component: FC<BadgeProps> = ({ label }) => {
+  return <StyledTypography variant="body2">{label}</StyledTypography>;
+};
diff --git a/src/ui/lib/components/Badge/Badge.interfaces.ts b/src/ui/lib/components/Badge/Badge.interfaces.ts
new file mode 100644
index 00000000..854f38e1
--- /dev/null
+++ b/src/ui/lib/components/Badge/Badge.interfaces.ts
@@ -0,0 +1,3 @@
+export interface BadgeProps {
+  label: string;
+}
diff --git a/src/ui/lib/components/Badge/Badge.styles.tsx b/src/ui/lib/components/Badge/Badge.styles.tsx
new file mode 100644
index 00000000..e1469946
--- /dev/null
+++ b/src/ui/lib/components/Badge/Badge.styles.tsx
@@ -0,0 +1,11 @@
+import { styled, Typography } from '@mui/material';
+
+export const StyledTypography = styled(Typography)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerHigh,
+  borderRadius: '6px',
+  padding: '6px',
+  marginRight: '6px',
+  display: 'inline-block',
+  paddingTop: '8px',
+  color: theme.palette.surface.onSurfaceAccent,
+}));
diff --git a/src/ui/lib/components/Badge/index.tsx b/src/ui/lib/components/Badge/index.tsx
new file mode 100644
index 00000000..6ddc5f53
--- /dev/null
+++ b/src/ui/lib/components/Badge/index.tsx
@@ -0,0 +1,2 @@
+export { Component as Badge } from './Badge.component';
+export type { BadgeProps } from './Badge.interfaces';
diff --git a/src/ui/lib/components/BlockHeader/BlockHeader.component.tsx b/src/ui/lib/components/BlockHeader/BlockHeader.component.tsx
new file mode 100644
index 00000000..8e8587ee
--- /dev/null
+++ b/src/ui/lib/components/BlockHeader/BlockHeader.component.tsx
@@ -0,0 +1,26 @@
+'use client';
+import { Box, Typography, useTheme } from '@mui/material';
+
+import { Info } from '@assets/icons';
+
+import { SvgContainer } from '@/lib/utils/SvgContainer';
+
+import { BlockHeaderProps } from './BlockHeader.interfaces';
+import { CustomDivider } from './BlockHeader.styles';
+
+export const Component = ({ label, withDivider = false }: BlockHeaderProps) => {
+  const theme = useTheme();
+  return (
+    <Box display="flex" alignItems="center" my={3}>
+      <Typography variant="h5" color="surface.onSurface">
+        {label}
+      </Typography>
+      <Box ml={2}>
+        <SvgContainer color={theme.palette.surface.onSurfaceAccent}>
+          <Info />
+        </SvgContainer>
+      </Box>
+      {withDivider && <CustomDivider />}
+    </Box>
+  );
+};
diff --git a/src/ui/lib/components/BlockHeader/BlockHeader.interfaces.ts b/src/ui/lib/components/BlockHeader/BlockHeader.interfaces.ts
new file mode 100644
index 00000000..fffb9e88
--- /dev/null
+++ b/src/ui/lib/components/BlockHeader/BlockHeader.interfaces.ts
@@ -0,0 +1,4 @@
+export interface BlockHeaderProps {
+  label: string;
+  withDivider?: boolean;
+}
diff --git a/src/ui/lib/components/BlockHeader/BlockHeader.styles.tsx b/src/ui/lib/components/BlockHeader/BlockHeader.styles.tsx
new file mode 100644
index 00000000..9f063d8f
--- /dev/null
+++ b/src/ui/lib/components/BlockHeader/BlockHeader.styles.tsx
@@ -0,0 +1,8 @@
+import { Divider, styled } from '@mui/material';
+
+export const CustomDivider = styled(Divider)(({ theme }) => ({
+  height: '1px',
+  flex: 1,
+  marginLeft: '48px',
+  backgroundColor: theme.palette.outline.subdued,
+}));
diff --git a/src/ui/lib/components/BlockHeader/index.tsx b/src/ui/lib/components/BlockHeader/index.tsx
new file mode 100644
index 00000000..7035c622
--- /dev/null
+++ b/src/ui/lib/components/BlockHeader/index.tsx
@@ -0,0 +1,2 @@
+export { Component as BlockHeader } from './BlockHeader.component';
+export type { BlockHeaderProps } from './BlockHeader.interfaces';
diff --git a/src/ui/lib/components/Carousel/Carousel.component.tsx b/src/ui/lib/components/Carousel/Carousel.component.tsx
new file mode 100644
index 00000000..079e494c
--- /dev/null
+++ b/src/ui/lib/components/Carousel/Carousel.component.tsx
@@ -0,0 +1,58 @@
+import { Box, Typography, useTheme } from '@mui/material';
+import dynamic from 'next/dynamic';
+import { FC } from 'react';
+
+import { CarouselProps } from './Carousel.interfaces';
+import { PromptWrapper } from './Carousel.styles';
+
+const Carousel = dynamic(
+  () => import('react-material-ui-carousel').then((mod) => mod.default),
+  {
+    ssr: false,
+  }
+);
+
+function truncateString(str: string, limit: number) {
+  if (str.length <= limit) {
+    return str;
+  }
+  let cutIndex = limit;
+  while (
+    cutIndex < str.length &&
+    (str[cutIndex] !== ' ' || /[.,!?]/.test(str[cutIndex - 1]))
+  ) {
+    cutIndex++;
+  }
+  return cutIndex < str.length ? str.slice(0, cutIndex) + '...' : str;
+}
+
+export const Component: FC<CarouselProps> = ({ label, items }) => {
+  const theme = useTheme();
+  return (
+    <Box sx={{ width: '100%' }}>
+      <Typography
+        variant="overline2"
+        color="surface.onSurface"
+        textTransform="uppercase"
+      >
+        {label}
+      </Typography>
+      <Carousel
+        interval={10000}
+        duration={1000}
+        animation="fade"
+        IndicatorIcon={null}
+        navButtonsAlwaysInvisible={true}
+        sx={{ marginTop: '6px' }}
+      >
+        {items.map((item, i) => (
+          <PromptWrapper key={i} data-id="prompt-wrapper">
+            <Typography variant="body2" color={theme.palette.primary.main}>
+              {truncateString(item, 200)}
+            </Typography>
+          </PromptWrapper>
+        ))}
+      </Carousel>
+    </Box>
+  );
+};
diff --git a/src/ui/lib/components/Carousel/Carousel.interfaces.ts b/src/ui/lib/components/Carousel/Carousel.interfaces.ts
new file mode 100644
index 00000000..5cc386f5
--- /dev/null
+++ b/src/ui/lib/components/Carousel/Carousel.interfaces.ts
@@ -0,0 +1,4 @@
+export interface CarouselProps {
+  label: string;
+  items: string[];
+}
diff --git a/src/ui/lib/components/Carousel/Carousel.styles.tsx b/src/ui/lib/components/Carousel/Carousel.styles.tsx
new file mode 100644
index 00000000..249c10d5
--- /dev/null
+++ b/src/ui/lib/components/Carousel/Carousel.styles.tsx
@@ -0,0 +1,11 @@
+import { Box, styled } from '@mui/material';
+
+export const PromptWrapper = styled(Box)(({ theme }) => ({
+  backgroundColor: theme.palette.primary.container,
+  borderRadius: '8px',
+  padding: '8px',
+  minWidth: '304px',
+  minHeight: '84px',
+  width: 'auto',
+  overflow: 'hidden',
+}));
diff --git a/src/ui/lib/components/Carousel/index.tsx b/src/ui/lib/components/Carousel/index.tsx
new file mode 100644
index 00000000..0f7ee7e5
--- /dev/null
+++ b/src/ui/lib/components/Carousel/index.tsx
@@ -0,0 +1,2 @@
+export { Component as Carousel } from './Carousel.component';
+export type { CarouselProps } from './Carousel.interfaces';
diff --git a/src/ui/lib/components/Charts/Combined/Combined.component.tsx b/src/ui/lib/components/Charts/Combined/Combined.component.tsx
new file mode 100644
index 00000000..68b53496
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/Combined.component.tsx
@@ -0,0 +1,153 @@
+import { useTheme } from '@mui/material';
+import { BarCustomLayerProps, ResponsiveBar } from '@nivo/bar';
+import { Point } from '@nivo/core';
+import React from 'react';
+
+import { CombinedProps } from './Combined.interfaces';
+import CustomBars from './components/CustomBars';
+import CustomGrid from './components/CustomGrid';
+import { CustomLegendLayer } from './components/CustomLegendLayer';
+import CustomTick from './components/CustomTick';
+import DottedLines from './components/DottedLines';
+import useChartScales from '../common/useChartScales';
+
+export const Component = ({
+  bars,
+  lines,
+  width,
+  height,
+  margins,
+  xLegend,
+  yLegend,
+}: CombinedProps) => {
+  const theme = useTheme();
+  const combinedGraphTheme = {
+    axis: {
+      legend: {
+        text: {
+          fill: theme.palette.surface.onSurface,
+          fontSize: theme.typography.axisTitle.fontSize,
+          fontWeight: theme.typography.axisTitle.fontWeight,
+          fontFamily: theme.typography.axisTitle.fontFamily,
+        },
+      },
+      ticks: {
+        text: {
+          fill: theme.palette.surface.onSurface,
+        },
+      },
+    },
+  };
+  const defaultMargins = { top: 10, left: 10, right: 10, bottom: 10 };
+  const finalMargins = {
+    ...defaultMargins,
+    ...margins,
+    bottom: (margins?.bottom || defaultMargins.bottom) + 20,
+  };
+  const { xTicks, yTicks, fnScaleX, fnScaleY, innerHeight } = useChartScales({
+    bars,
+    lines,
+    width,
+    height,
+    margins: finalMargins,
+  });
+
+  const CustomGridLayer = () => {
+    const scaledWidth = fnScaleX(xTicks[xTicks.length - 1]);
+    const scaledHeight = innerHeight - fnScaleY(yTicks[0]);
+
+    return (
+      <CustomGrid
+        xScale={(d: number) => fnScaleX(d)}
+        yScale={(d: number) => innerHeight - fnScaleY(d)}
+        width={scaledWidth}
+        height={innerHeight}
+        xTicks={xTicks}
+        yTicks={yTicks}
+        scaledHeight={scaledHeight}
+      />
+    );
+  };
+
+  const CustomBarLayer = (props: BarCustomLayerProps<Point>) => {
+    const heightOffset = innerHeight - fnScaleY(yTicks[0]);
+    return (
+      <CustomBars
+        {...props}
+        xScaleFunc={(d: number) => fnScaleX(d)}
+        yScaleFunc={(d: number) => innerHeight - fnScaleY(d)}
+        heightOffset={heightOffset}
+      />
+    );
+  };
+  return (
+    <div style={{ width: width + 'px', height: height + 'px', position: 'relative' }}>
+      <ResponsiveBar
+        animate={true}
+        data={bars}
+        keys={['y']}
+        indexBy="x"
+        margin={{
+          top: finalMargins.top,
+          right: finalMargins.right,
+          bottom: finalMargins.bottom,
+          left: finalMargins.left,
+        }}
+        padding={0.5}
+        // TODO: change colors scheme
+        // colors={{ scheme: 'category10' }}
+        colors={[theme.palette.primary.shades.B80]}
+        axisLeft={{
+          tickValues: yTicks,
+          legend: yLegend,
+          legendPosition: 'middle',
+          legendOffset: -30,
+          tickSize: 5,
+          tickPadding: 5,
+          tickRotation: 0,
+          renderTick: (tick) => (
+            <CustomTick
+              isXAxis={false}
+              scale={(d: number) => Math.abs(innerHeight - fnScaleY(d))}
+              tick={yTicks[tick.tickIndex]}
+              isFirst={tick.tickIndex === 0}
+              isLast={tick.tickIndex === yTicks.length - 1}
+            />
+          ),
+        }}
+        axisBottom={{
+          tickValues: xTicks,
+          legend: xLegend,
+          legendPosition: 'middle',
+          legendOffset: 30,
+          tickSize: 5,
+          tickPadding: 5,
+          tickRotation: 0,
+          renderTick: (tick) => (
+            <CustomTick
+              isXAxis={true}
+              scale={(d: number) => fnScaleX(d)}
+              tick={xTicks[tick.tickIndex]}
+              isFirst={tick.tickIndex === 0}
+              isLast={tick.tickIndex === xTicks.length - 1}
+            />
+          ),
+        }}
+        layers={[
+          'axes',
+          CustomGridLayer,
+          CustomBarLayer,
+          () => <CustomLegendLayer series={lines} height={height} />,
+        ]}
+        theme={combinedGraphTheme}
+      />
+      <DottedLines
+        lines={lines}
+        leftMargin={finalMargins.left}
+        topMargin={finalMargins.top}
+        innerHeight={innerHeight}
+        xScale={fnScaleX}
+      />
+    </div>
+  );
+};
diff --git a/src/ui/lib/components/Charts/Combined/Combined.interfaces.ts b/src/ui/lib/components/Charts/Combined/Combined.interfaces.ts
new file mode 100644
index 00000000..a81a9f7c
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/Combined.interfaces.ts
@@ -0,0 +1,11 @@
+import { LinesSeries, Margins, Point } from '../common/interfaces';
+
+export interface CombinedProps {
+  bars: Point[];
+  lines: LinesSeries[];
+  width: number;
+  height: number;
+  margins?: Margins;
+  xLegend: string;
+  yLegend: string;
+}
diff --git a/src/ui/lib/components/Charts/Combined/components/CustomBars/CustomBars.interfaces.ts b/src/ui/lib/components/Charts/Combined/components/CustomBars/CustomBars.interfaces.ts
new file mode 100644
index 00000000..faad401e
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/CustomBars/CustomBars.interfaces.ts
@@ -0,0 +1,7 @@
+import { BarCustomLayerProps } from '@nivo/bar';
+
+export interface CustomBarsProps<T> extends BarCustomLayerProps<T> {
+  xScaleFunc: (d: number) => number;
+  yScaleFunc: (d: number) => number;
+  heightOffset: number;
+}
diff --git a/src/ui/lib/components/Charts/Combined/components/CustomBars/index.tsx b/src/ui/lib/components/Charts/Combined/components/CustomBars/index.tsx
new file mode 100644
index 00000000..20c38203
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/CustomBars/index.tsx
@@ -0,0 +1,55 @@
+import { ComputedBarDatum } from '@nivo/bar';
+import { Point } from '@nivo/core';
+import { useTooltip, BasicTooltip } from '@nivo/tooltip';
+import React from 'react';
+
+import { CustomBarsProps } from './CustomBars.interfaces';
+
+const CustomBars = ({
+  bars,
+  xScaleFunc,
+  yScaleFunc,
+  heightOffset,
+}: CustomBarsProps<Point>) => {
+  const { showTooltipFromEvent, hideTooltip } = useTooltip();
+
+  const handleMouseEnter = (
+    event: React.MouseEvent<SVGRectElement>,
+    bar: ComputedBarDatum<Point>
+  ) => {
+    showTooltipFromEvent(
+      <BasicTooltip
+        id={`x: ${bar.data.data.x}, y: ${bar.data.data.y}`}
+        enableChip={true}
+        color={bar.color}
+      />,
+      event
+    );
+  };
+
+  const handleMouseLeave = () => {
+    hideTooltip();
+  };
+
+  return (
+    <g transform={`translate(0,-${heightOffset})`}>
+      {bars.map((bar) => {
+        return (
+          <rect
+            key={bar.key}
+            x={xScaleFunc(Number(bar.data.data.x)) - bar.width / 2}
+            y={yScaleFunc(Number(bar.data.data.y))}
+            width={bar.width}
+            height={bar.height}
+            fill={bar.color}
+            onMouseEnter={(event) => handleMouseEnter(event, bar)}
+            onMouseLeave={handleMouseLeave}
+            rx={bar.height > 8 ? 8 : 1}
+          />
+        );
+      })}
+    </g>
+  );
+};
+
+export default CustomBars;
diff --git a/src/ui/lib/components/Charts/Combined/components/CustomGrid/CustomGrid.interfaces.ts b/src/ui/lib/components/Charts/Combined/components/CustomGrid/CustomGrid.interfaces.ts
new file mode 100644
index 00000000..2e440fba
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/CustomGrid/CustomGrid.interfaces.ts
@@ -0,0 +1,10 @@
+export interface CustomGridProps {
+  xScale: (d: number) => number;
+  yScale: (d: number) => number;
+  width: number;
+  height: number;
+  xTicks: number[];
+  yTicks: number[];
+  scaledHeight: number;
+  fullGrid?: boolean;
+}
diff --git a/src/ui/lib/components/Charts/Combined/components/CustomGrid/index.tsx b/src/ui/lib/components/Charts/Combined/components/CustomGrid/index.tsx
new file mode 100644
index 00000000..69726062
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/CustomGrid/index.tsx
@@ -0,0 +1,72 @@
+import { useTheme } from '@mui/material';
+
+import { CustomGridProps } from './CustomGrid.interfaces';
+
+const CustomGrid = ({
+  xScale,
+  yScale,
+  width,
+  height,
+  xTicks,
+  yTicks,
+  scaledHeight,
+  fullGrid = false,
+}: CustomGridProps) => {
+  const theme = useTheme();
+  const xTick = xTicks[0];
+  const yTick = yTicks[yTicks.length - 1];
+
+  const renderAxlesOnly = (
+    <>
+      <line
+        key={`x${xTick}`}
+        x1={xScale(xTick)}
+        x2={xScale(xTick)}
+        y1={scaledHeight}
+        y2={height}
+        stroke={theme.palette.outline.subdued}
+      />
+      <line
+        key={`y${yTick}`}
+        x1={0}
+        x2={width}
+        y1={yScale(yTick)}
+        y2={yScale(yTick)}
+        stroke={theme.palette.outline.subdued}
+      />
+    </>
+  );
+
+  const renderFullGrid = (
+    <>
+      {xTicks.map((tick) => (
+        <line
+          key={`x${tick}`}
+          x1={xScale(tick)}
+          x2={xScale(tick)}
+          y1={scaledHeight}
+          y2={height}
+          stroke={theme.palette.outline.subdued}
+        />
+      ))}
+      {yTicks.map((tick) => (
+        <line
+          key={`y${tick}`}
+          x1={0}
+          x2={width}
+          y1={yScale(tick)}
+          y2={yScale(tick)}
+          stroke={theme.palette.outline.subdued}
+        />
+      ))}
+    </>
+  );
+
+  return (
+    <g transform={`translate(0,-${scaledHeight})`} id="grid">
+      {fullGrid ? renderFullGrid : renderAxlesOnly}
+    </g>
+  );
+};
+
+export default CustomGrid;
diff --git a/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/CustomLegendLayer.interfaces.ts b/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/CustomLegendLayer.interfaces.ts
new file mode 100644
index 00000000..de0abde5
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/CustomLegendLayer.interfaces.ts
@@ -0,0 +1,6 @@
+import { LinesSeries } from '../../../common/interfaces';
+
+export interface CustomLegendLayerProps {
+  series: LinesSeries[];
+  height: number;
+}
diff --git a/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/index.tsx b/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/index.tsx
new file mode 100644
index 00000000..3f390695
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/CustomLegendLayer/index.tsx
@@ -0,0 +1,42 @@
+import { useTheme } from '@mui/material';
+
+import useLineColors from '../../../common/useLineColors';
+
+import { CustomLegendLayerProps } from './CustomLegendLayer.interfaces';
+
+const LEGEND_HEIGHT = 20;
+
+export const CustomLegendLayer = ({ series, ...rest }: CustomLegendLayerProps) => {
+  const theme = useTheme();
+  const lineColor = useLineColors();
+  return (
+    <g transform={`translate(20, ${rest.height - LEGEND_HEIGHT})`}>
+      {series.map((item, index) => (
+        <g key={item.id} transform={`translate(${index * 100}, 0)`}>
+          <line
+            x1="0"
+            y1="0"
+            x2="20"
+            y2="0"
+            stroke={lineColor[index]}
+            strokeWidth={2}
+            strokeDasharray={'4,4'}
+          />
+          <text
+            x="30"
+            y="0"
+            fill={theme.palette.surface.onSurface}
+            style={{
+              fontSize: theme.typography.caption.fontSize,
+              fontWeight: theme.typography.caption.fontWeight,
+              fontFamily: theme.typography.caption.fontFamily,
+            }}
+            alignmentBaseline="middle"
+          >
+            {item.id}
+          </text>
+        </g>
+      ))}
+    </g>
+  );
+};
diff --git a/src/ui/lib/components/Charts/Combined/components/CustomTick/CustomTick.interfaces.ts b/src/ui/lib/components/Charts/Combined/components/CustomTick/CustomTick.interfaces.ts
new file mode 100644
index 00000000..efe5ef04
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/CustomTick/CustomTick.interfaces.ts
@@ -0,0 +1,8 @@
+export interface CustomTickProps {
+  scale: (d: number) => number;
+  isXAxis: boolean;
+  tick: number;
+  withTicks?: boolean;
+  isFirst: boolean;
+  isLast: boolean;
+}
diff --git a/src/ui/lib/components/Charts/Combined/components/CustomTick/index.tsx b/src/ui/lib/components/Charts/Combined/components/CustomTick/index.tsx
new file mode 100644
index 00000000..6358d5be
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/CustomTick/index.tsx
@@ -0,0 +1,67 @@
+import { useTheme } from '@mui/material';
+
+import { CustomTickProps } from './CustomTick.interfaces';
+
+function CustomTick({
+  isXAxis,
+  tick,
+  scale,
+  withTicks = false,
+  isFirst,
+  isLast,
+}: CustomTickProps) {
+  const theme = useTheme();
+
+  function getGroupPosition() {
+    if (isXAxis) {
+      let x = scale(tick);
+      if (isFirst) {
+        x = 4;
+      }
+      if (isLast) {
+        x -= 4;
+      }
+      return { x, y: 0 };
+    }
+    return { x: 0, y: scale(tick) };
+  }
+
+  function renderTick() {
+    const commonProps = {
+      fontFamily: theme.typography.axisLabel.fontFamily,
+      fontWeight: theme.typography.axisLabel.fontWeight,
+      fontSize: theme.typography.axisLabel.fontSize,
+      fill: theme.palette.surface.onSurface,
+    };
+
+    return isXAxis ? (
+      <>
+        {withTicks && (
+          <line x1={0} y1={0} x2={0} y2={6} stroke={theme.palette.surface.onSurface} />
+        )}
+        <text textAnchor="middle" y={10} {...commonProps}>
+          {tick}
+        </text>
+      </>
+    ) : (
+      <>
+        {withTicks && (
+          <line x1={0} y1={0} x2={-6} y2={0} stroke={theme.palette.surface.onSurface} />
+        )}
+        <text textAnchor="end" x={-5} dominantBaseline="middle" {...commonProps}>
+          {tick}
+        </text>
+      </>
+    );
+  }
+
+  const { x, y } = getGroupPosition();
+
+  return (
+    <g key={tick} transform={`translate(${x}, ${y})`}>
+      {renderTick()}
+    </g>
+  );
+}
+
+export default CustomTick;
diff --git a/src/ui/lib/components/Charts/Combined/components/DottedLines/DottedLines.interfaces.ts b/src/ui/lib/components/Charts/Combined/components/DottedLines/DottedLines.interfaces.ts
new file mode 100644
index 00000000..9044e15b
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/DottedLines/DottedLines.interfaces.ts
@@ -0,0 +1,9 @@
+import { LinesSeries } from '../../../common/interfaces';
+
+export interface DottedLinesProps {
+  lines: LinesSeries[];
+  leftMargin: number;
+  topMargin: number;
+  innerHeight: number;
+  xScale: (d: number) => number;
+}
diff --git a/src/ui/lib/components/Charts/Combined/components/DottedLines/index.tsx b/src/ui/lib/components/Charts/Combined/components/DottedLines/index.tsx
new file mode 100644
index 00000000..b4df2145
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/components/DottedLines/index.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+
+import useLineColors from '../../../common/useLineColors';
+
+import { DottedLinesProps } from './DottedLines.interfaces';
+
+const DottedLines = ({
+  lines,
+  leftMargin,
+  topMargin,
+  xScale,
+  innerHeight,
+}: DottedLinesProps) => {
+  const lineColor = useLineColors();
+  return (
+    <svg
+      style={{ position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }}
+      width="100%"
+      height="100%"
+    >
+      <g transform={`translate(${leftMargin}, ${topMargin})`}>
+        {lines.map((line, i) => (
+          <line
+            key={i}
+            x1={xScale(line.x)}
+            y1="0"
+            x2={xScale(line.x)}
+            y2={innerHeight}
+            stroke={lineColor[i]}
+            strokeWidth="1.5"
+            strokeDasharray="3,3"
+          />
+        ))}
+      </g>
+    </svg>
+  );
+};
+
+export default DottedLines;
diff --git a/src/ui/lib/components/Charts/Combined/index.tsx b/src/ui/lib/components/Charts/Combined/index.tsx
new file mode 100644
index 00000000..199b1554
--- /dev/null
+++ b/src/ui/lib/components/Charts/Combined/index.tsx
@@ -0,0 +1,2 @@
+export { Component as Combined } from './Combined.component';
+export type { CombinedProps } from './Combined.interfaces';
diff --git a/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx b/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx
new file mode 100644
index 00000000..a3b9ab87
--- /dev/null
+++ b/src/ui/lib/components/Charts/DashedLine/DashedLine.component.tsx
@@ -0,0 +1,139 @@
+import { useTheme } from '@mui/material';
+import { ResponsiveLine, Serie } from '@nivo/line';
+
+import { CustomLegendLayer } from './components/CustomLegendLayer';
+import { DashedSolidLine } from './components/DashedSolidLine';
+import { DashedLineProps, ScaleType } from './DashedLine.interfaces';
+import { spacedLogValues } from './helpers';
+
+export const getMinTick = (data: readonly Serie[]) => {
+  return Math.max(
+    ...data.map((lineData) =>
+      Math.min(...lineData.data.map((point) => point.y as number))
+    )
+  );
+};
+
+export const getMaxTick = (data: readonly Serie[]) => {
+  return Math.max(
+    ...data.map((lineData) =>
+      Math.max(...lineData.data.map((point) => point.y as number))
+    )
+  );
+};
+
+export const Component = ({
+  data,
+  xLegend,
+  yLegend,
+  margins,
+  minX,
+  yScaleType = ScaleType.log,
+}: DashedLineProps) => {
+  const theme = useTheme();
+  const defaultMargins = { top: 10, left: 10, right: 10, bottom: 10 };
+  const finalMargins = {
+    ...defaultMargins,
+    ...margins,
+    bottom: (margins?.bottom || defaultMargins.bottom) + 50,
+  };
+
+  const dashedLineTheme = {
+    textColor: theme.palette.surface.onSurface,
+    fontSize: 14,
+    axis: {
+      domain: {
+        line: {
+          stroke: theme.palette.outline.subdued,
+          strokeWidth: 1,
+        },
+      },
+      ticks: {
+        line: {
+          stroke: theme.palette.outline.subdued,
+          strokeWidth: 1,
+        },
+        text: {
+          fill: theme.palette.surface.onSurface,
+          fontSize: theme.typography.axisTitle.fontSize,
+          fontFamily: theme.typography.axisTitle.fontFamily,
+          fontWeight: theme.typography.axisTitle.fontWeight,
+        },
+      },
+      legend: {
+        text: {
+          fill: theme.palette.surface.onSurface,
+          fontSize: theme.typography.axisTitle.fontSize,
+          fontFamily: theme.typography.axisTitle.fontFamily,
+          fontWeight: theme.typography.axisTitle.fontWeight,
+        },
+      },
+    },
+    grid: {
+      line: {
+        stroke: theme.palette.outline.subdued,
+        strokeWidth: 1,
+      },
+    },
+  };
+
+  let extraLeftAxisOptions = {};
+  let extraYScaleOptions = {};
+  if (yScaleType === ScaleType.log) {
+    const ticks = spacedLogValues(getMinTick(data), getMaxTick(data), 6);
+    extraLeftAxisOptions = {
+      tickValues: ticks,
+    };
+    extraYScaleOptions = {
+      max: ticks[ticks.length - 1],
+    };
+  }
+
+  return (
+    <div style={{ height: '100%', width: '100%' }}>
+      <ResponsiveLine
+        data={data}
+        enablePoints={false}
+        curve="monotoneX"
+        margin={finalMargins}
+        axisTop={null}
+        axisRight={null}
+        axisBottom={{
+          tickSize: 0,
+          tickPadding: 5,
+          tickRotation: 0,
+          legend: xLegend,
+          legendOffset: 36,
+          legendPosition: 'middle',
+        }}
+        axisLeft={{
+          tickSize: 0,
+          tickPadding: 5,
+          tickRotation: 0,
+          legend: yLegend,
+          legendOffset: -40,
+          legendPosition: 'middle',
+          ...extraLeftAxisOptions,
+        }}
+        xScale={{
+          min: minX,
+          type: 'linear',
+        }}
+        yScale={{
+          type: yScaleType,
+          ...extraYScaleOptions,
+        }}
+        colors={{ scheme: 'category10' }}
+        layers={[
+          'markers',
+          'axes',
+          'points',
+          'legends',
+          DashedSolidLine,
+          CustomLegendLayer,
+        ]}
+        theme={dashedLineTheme}
+      />
+    </div>
+  );
+};
diff --git a/src/ui/lib/components/Charts/DashedLine/DashedLine.interfaces.ts b/src/ui/lib/components/Charts/DashedLine/DashedLine.interfaces.ts
new file mode 100644
index 00000000..16193ab4
--- /dev/null
+++ b/src/ui/lib/components/Charts/DashedLine/DashedLine.interfaces.ts
@@ -0,0 +1,16 @@
+import { LineSvgProps } from '@nivo/line';
+
+import { Margins } from '../common/interfaces';
+
+export enum ScaleType {
+  log = 'symlog',
+  linear = 'linear',
+}
+
+export interface DashedLineProps extends LineSvgProps {
+  margins?: Margins;
+  xLegend: string;
+  yLegend: string;
+  minX?: number;
+  yScaleType?: ScaleType;
+}
diff --git a/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/CustomLegendLayer.interfaces.ts b/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/CustomLegendLayer.interfaces.ts
new file mode 100644
index 00000000..813bb5e2
--- /dev/null
+++ b/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/CustomLegendLayer.interfaces.ts
@@ -0,0 +1,5 @@
+import { CustomLayerProps } from '@nivo/line';
+
+export interface CustomLegendLayerProps extends CustomLayerProps {
+  height?: number;
+}
diff --git a/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/index.tsx b/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/index.tsx
new file mode 100644
index 00000000..81bd8c20
--- /dev/null
+++ b/src/ui/lib/components/Charts/DashedLine/components/CustomLegendLayer/index.tsx
@@ -0,0 +1,64 @@
+import { useTheme } from '@mui/material';
+
+import { CustomLegendLayerProps } from './CustomLegendLayer.interfaces';
+
+const LEGEND_HEIGHT = 40;
+
+export const CustomLegendLayer = ({ series, ...rest }: CustomLegendLayerProps) => {
+  const theme = useTheme();
+  const palette = theme.palette;
+
+  const colors = [
+    palette.surface.onSurface,
+    palette.secondary.main,
+    palette.tertiary.main,
+    palette.quarternary.main,
+  ];
+  const solidColor = palette.primary.main;
+
+  const getColor = (isSolid = false) => {
+    if (isSolid) {
+      return solidColor;
+    }
+
+    if (colors.length === 0) {
+      throw new Error('No more colors available');
+    }
+
+    return colors.splice(0, 1)[0];
+  };
+  return (
+    <g
+      transform={`translate(20, ${(rest?.height || rest.innerHeight) - LEGEND_HEIGHT})`}
+    >
+      {series.map((item, index) => {
+        return (
+          <g key={item.id} transform={`translate(${index * 100}, 0)`}>
+            <line
+              x1="0"
+              y1="0"
+              x2="20"
+              y2="0"
+              stroke={getColor(item.solid)}
+              strokeWidth={2}
+              strokeDasharray={item?.solid ? '' : '4,4'}
+            />
+            <text
+              x="30"
+              y="0"
+              fill={theme.palette.surface.onSurface}
+              style={{
+                fontSize: theme.typography.caption.fontSize,
+                fontWeight: theme.typography.caption.fontWeight,
+                fontFamily: theme.typography.caption.fontFamily,
+              }}
+              alignmentBaseline="middle"
+            >
+              {item.id}
+            </text>
+          </g>
+        );
+      })}
+    </g>
+  );
+};
diff --git a/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/DashedSolidLine.interfaces.ts b/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/DashedSolidLine.interfaces.ts
new file mode 100644
index 00000000..fdb4537a
--- /dev/null
+++ b/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/DashedSolidLine.interfaces.ts
@@ -0,0 +1,7 @@
+import type { CustomLayerProps } from '@nivo/line';
+import type { ScaleLinear } from '@nivo/scales';
+
+export type DashedSolidLineProps = Omit<CustomLayerProps, 'xScale' | 'yScale'> & {
+  xScale: ScaleLinear<number>;
+  yScale: ScaleLinear<number>;
+};
diff --git a/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/index.tsx b/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/index.tsx
new file mode 100644
index 00000000..dd786b6a
--- /dev/null
+++ b/src/ui/lib/components/Charts/DashedLine/components/DashedSolidLine/index.tsx
@@ -0,0 +1,76 @@
+import { useTheme } from '@mui/material';
+
+import { toNumberValue } from '../../helpers';
+
+import { DashedSolidLineProps } from './DashedSolidLine.interfaces';
+
+export const DashedSolidLine = ({
+  series,
+  lineGenerator,
+  xScale,
+  yScale,
+}: DashedSolidLineProps) => {
+  const theme = useTheme();
+  const palette = theme.palette;
+
+  const colors = [
+    palette.surface.onSurface,
+    palette.secondary.main,
+    palette.tertiary.main,
+    palette.quarternary.main,
+  ];
+  const solidColor = palette.primary.main;
+
+  const getColor = (isSolid: boolean) => {
+    if (isSolid) {
+      return solidColor;
+    }
+
+    if (colors.length === 0) {
+      throw new Error('No more colors available');
+    }
+
+    return colors.splice(0, 1)[0];
+  };
+
+  return series.map(({ id, data, solid = false }) => {
+    return (
+      <g key={id}>
+        <path
+          key={id}
+          d={(() => {
+            const linePath = lineGenerator(
+              data.map((d) => ({
+                x: xScale(toNumberValue(d.data.x)),
+                y: yScale(toNumberValue(d.data.y)),
+              }))
+            );
+            return linePath !== null ? linePath : undefined;
+          })()}
+          fill="none"
+          stroke={getColor(solid)}
+          style={
+            !solid
+              ? {
+                  strokeDasharray: '2.4 2.4',
+                  strokeWidth: 1.5,
+                }
+              : {
+                  strokeWidth: 1.5,
+                }
+          }
+        />
+        {solid &&
+          data.map((d, pointIndex) => (
+            <circle
+              key={`${id}-${pointIndex}`}
+              cx={xScale(toNumberValue(d.data.x))}
+              cy={yScale(toNumberValue(d.data.y))}
+              r={4}
+              fill={getColor(solid)}
+            />
+          ))}
+      </g>
+    );
+  });
+};
diff --git a/src/ui/lib/components/Charts/DashedLine/helpers.ts b/src/ui/lib/components/Charts/DashedLine/helpers.ts
new file mode 100644
index 00000000..c73405ed
--- /dev/null
+++ b/src/ui/lib/components/Charts/DashedLine/helpers.ts
@@ -0,0 +1,78 @@
+import { DatumValue } from '@nivo/line';
+
+type NumberValue = number | { valueOf(): number };
+
+export const toNumberValue = (value: DatumValue | null | undefined): NumberValue => {
+  if (value === null || value === undefined) {
+    return 0;
+  }
+  return value as NumberValue;
+};
+
+const allowedMultipliers = [
+  1, 1.2, 1.4, 1.5, 1.6, 1.8, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 7.5, 8, 9, 10,
+];
+
+export function roundUpNice(x: number) {
+  if (x <= 0) {
+    return x;
+  }
+  const exponent = Math.floor(Math.log10(x));
+  const base = Math.pow(10, exponent);
+  const fraction = x / base;
+  for (const m of allowedMultipliers) {
+    if (m >= fraction) {
+      return Math.round(m * base);
+    }
+  }
+  return Math.round(10 * base);
+}
+
+export function roundNearestNice(x: number) {
+  if (x <= 0) {
+    return x;
+  }
+  const exponent = Math.floor(Math.log10(x));
+  const base = Math.pow(10, exponent);
+  const fraction = x / base;
+  let best = allowedMultipliers[0];
+  let bestDiff = Math.abs(fraction - best);
+  for (const m of allowedMultipliers) {
+    const diff = Math.abs(fraction - m);
+    if (diff < bestDiff) {
+      best = m;
+      bestDiff = diff;
+    }
+  }
+  return Math.round(best * base);
+}
+
+export function spacedLogValues(min: number, max: number, steps: number) {
+  if (steps < 2) {
+    return [];
+  }
+
+  if (min === 0) {
+    const nonzeroCount = steps - 1;
+    const exponent = Math.floor(Math.log10(max)) - (nonzeroCount - 1);
+    const lowerNonZero = roundNearestNice(Math.pow(10, exponent));
+    const upperTick = roundUpNice(max);
+    const r = Math.pow(upperTick / lowerNonZero, 1 / (nonzeroCount - 1));
+    const ticks = [0];
+    for (let i = 0; i < nonzeroCount; i++) {
+      const value = lowerNonZero * Math.pow(r, i);
+      ticks.push(roundNearestNice(value));
+    }
+    return ticks;
+  } else {
+    const lowerTick = roundUpNice(min);
+    const upperTick = roundUpNice(max);
+    const r = Math.pow(upperTick / lowerTick, 1 / (steps - 1));
+    const ticks = [];
+    for (let i = 0; i < steps; i++) {
+      const value = lowerTick * Math.pow(r, i);
+      ticks.push(roundNearestNice(value));
+    }
+    return ticks;
+  }
+}
diff --git a/src/ui/lib/components/Charts/DashedLine/index.tsx b/src/ui/lib/components/Charts/DashedLine/index.tsx
new file mode 100644
index 00000000..67fbf071
--- /dev/null
+++ b/src/ui/lib/components/Charts/DashedLine/index.tsx
@@ -0,0 +1,2 @@
+export { Component as DashedLine } from './DashedLine.component';
+export type { DashedLineProps } from './DashedLine.interfaces';
diff --git a/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx b/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx
new file mode 100644
index 00000000..f99c98f1
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/MetricLine.component.tsx
@@ -0,0 +1,122 @@
+import { useTheme } from '@mui/material';
+import { ResponsiveLine } from '@nivo/line';
+import React, { FC } from 'react';
+
+import { MetricLineProps } from '.';
+import { useColor } from '@/lib/hooks/useColor';
+
+import CustomAxes from './components/CustomAxes';
+import ThresholdBar from './components/ThresholdBar';
+import { ScaleType } from '../DashedLine/DashedLine.interfaces';
+
+export const Component: FC<MetricLineProps> = ({
+  data,
+  threshold,
+  lineColor,
+  yScaleType = ScaleType.log,
+}) => {
+  const theme = useTheme();
+  const selectedColor = useColor(lineColor);
+  const lineTheme = {
+    axis: {
+      legend: {
+        text: {
+          fill: theme.palette.surface.onSurface,
+          fontSize: theme.typography.axisTitle.fontSize,
+          fontWeight: theme.typography.axisTitle.fontWeight,
+          fontFamily: theme.typography.axisTitle.fontFamily,
+        },
+      },
+      ticks: {
+        text: {
+          fill: theme.palette.surface.onSurface,
+        },
+      },
+    },
+  };
+  const xValues = data[0].data.map((d) => d.x) as Array<number>;
+  const yValues = data[0].data.map((d) => d.y) as Array<number>;
+
+  const maxX = Math.max(...xValues);
+  const minX = Math.min(...xValues);
+  const maxY = Math.ceil(Math.max(...yValues));
+  const minY = Math.floor(Math.min(...yValues));
+
+  let extraYScaleOptions = {};
+  if (yScaleType === ScaleType.linear) {
+    extraYScaleOptions = {
+      stacked: true,
+      reverse: false,
+    };
+  }
+
+  return (
+    <ResponsiveLine
+      curve="monotoneX"
+      data={data}
+      colors={[selectedColor]}
+      margin={{ top: 20, right: 10, bottom: 20, left: 35.5 }}
+      xScale={{ type: 'linear', min: minX }}
+      yScale={{
+        type: yScaleType,
+        min: 'auto',
+        max: 'auto',
+        ...extraYScaleOptions,
+      }}
+      axisBottom={null}
+      axisLeft={{
+        legendOffset: -30,
+        tickRotation: 0,
+        tickSize: 5,
+        tickPadding: 5,
+        tickValues: [minY, maxY],
+        renderTick: ({ value, x, y, tickIndex }) => {
+          return (
+            <g transform={`translate(${x},${y})`} data-id="ticks">
+              <text
+                x={-4}
+                y={tickIndex === 0 ? 0 : 6}
+                textAnchor="end"
+                style={{
+                  fontFamily: theme.typography.axisTitle.fontFamily,
+                  fontWeight: theme.typography.axisLabel.fontWeight,
+                  fontSize: theme.typography.axisLabel.fontSize,
+                  fill: theme.palette.surface.onSurfaceSubdued,
+                }}
+              >
+                {value}
+              </text>
+            </g>
+          );
+        },
+      }}
+      enableGridX={false}
+      enableGridY={false}
+      pointSize={0}
+      useMesh={true}
+      layers={[
+        CustomAxes,
+        ({
+          xScale,
+          yScale,
+        }: {
+          xScale: (value: number) => number;
+          yScale: (value: number) => number;
+        }) => (
+          <ThresholdBar
+            threshold={threshold}
+            yScale={yScale}
+            xScale={xScale}
+            minX={minX}
+            maxX={maxX}
+            minY={minY}
+            maxY={maxY}
+          />
+        ),
+        'axes',
+        'lines',
+      ]}
+      theme={lineTheme}
+    />
+  );
+};
diff --git a/src/ui/lib/components/Charts/MetricLine/MetricLine.interface.ts b/src/ui/lib/components/Charts/MetricLine/MetricLine.interface.ts
new file mode 100644
index 00000000..6df9fb47
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/MetricLine.interface.ts
@@ -0,0 +1,15 @@
+import { LineSvgProps } from '@nivo/line';
+
+import { ScaleType } from '../DashedLine/DashedLine.interfaces';
+
+export enum LineColor {
+  Primary,
+  Secondary,
+  Tertiary,
+  Quarternary,
+}
+export interface MetricLineProps extends LineSvgProps {
+  threshold?: number;
+  lineColor: LineColor;
+  yScaleType?: ScaleType;
+}
diff --git a/src/ui/lib/components/Charts/MetricLine/components/CustomAxes/CustomAxes.interfaces.ts b/src/ui/lib/components/Charts/MetricLine/components/CustomAxes/CustomAxes.interfaces.ts
new file mode 100644
index 00000000..66fad024
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/components/CustomAxes/CustomAxes.interfaces.ts
@@ -0,0 +1,6 @@
+import { CustomLayerProps } from '@nivo/line';
+import type { ScaleLinear } from '@nivo/scales';
+
+export type CustomLineLayerProps = Omit<CustomLayerProps, 'xScale' | 'yScale'> & {
+  yScale: ScaleLinear<number>;
+};
diff --git a/src/ui/lib/components/Charts/MetricLine/components/CustomAxes/index.tsx b/src/ui/lib/components/Charts/MetricLine/components/CustomAxes/index.tsx
new file mode 100644
index 00000000..b5275349
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/components/CustomAxes/index.tsx
@@ -0,0 +1,23 @@
+import { useTheme } from '@mui/material';
+import React from 'react';
+
+import { CustomLineLayerProps } from './CustomAxes.interfaces';
+
+const CustomAxes = ({ yScale }: CustomLineLayerProps) => {
+  const theme = useTheme();
+  const minY2 = yScale.domain()[0];
+  const maxY2 = yScale.domain()[1];
+  return (
+    <>
+      <line
+        x1={0}
+        x2={0}
+        y1={yScale(minY2)}
+        y2={yScale(maxY2)}
+        stroke={theme.palette.surface.onSurfaceSubdued}
+      />
+    </>
+  );
+};
+
+export default CustomAxes;
diff --git a/src/ui/lib/components/Charts/MetricLine/components/CustomGrid/CustomGrid.interfaces.ts b/src/ui/lib/components/Charts/MetricLine/components/CustomGrid/CustomGrid.interfaces.ts
new file mode 100644
index 00000000..2e440fba
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/components/CustomGrid/CustomGrid.interfaces.ts
@@ -0,0 +1,10 @@
+export interface CustomGridProps {
+  xScale: (d: number) => number;
+  yScale: (d: number) => number;
+  width: number;
+  height: number;
+  xTicks: number[];
+  yTicks: number[];
+  scaledHeight: number;
+  fullGrid?: boolean;
+}
diff --git a/src/ui/lib/components/Charts/MetricLine/components/CustomGrid/index.tsx b/src/ui/lib/components/Charts/MetricLine/components/CustomGrid/index.tsx
new file mode 100644
index 00000000..69726062
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/components/CustomGrid/index.tsx
@@ -0,0 +1,72 @@
+import { useTheme } from '@mui/material';
+
+import { CustomGridProps } from './CustomGrid.interfaces';
+
+const CustomGrid = ({
+  xScale,
+  yScale,
+  width,
+  height,
+  xTicks,
+  yTicks,
+  scaledHeight,
+  fullGrid = false,
+}: CustomGridProps) => {
+  const theme = useTheme();
+  const xTick = xTicks[0];
+  const yTick = yTicks[yTicks.length - 1];
+
+  const renderAxlesOnly = (
+    <>
+      <line
+        key={`x${xTick}`}
+        x1={xScale(xTick)}
+        x2={xScale(xTick)}
+        y1={scaledHeight}
+        y2={height}
+        stroke={theme.palette.outline.subdued}
+      />
+      <line
+        key={`y${yTick}`}
+        x1={0}
+        x2={width}
+        y1={yScale(yTick)}
+        y2={yScale(yTick)}
+        stroke={theme.palette.outline.subdued}
+      />
+    </>
+  );
+
+  const renderFullGrid = (
+    <>
+      {xTicks.map((tick) => (
+        <line
+          key={`x${tick}`}
+          x1={xScale(tick)}
+          x2={xScale(tick)}
+          y1={scaledHeight}
+          y2={height}
+          stroke={theme.palette.outline.subdued}
+        />
+      ))}
+      {yTicks.map((tick) => (
+        <line
+          key={`y${tick}`}
+          x1={0}
+          x2={width}
+          y1={yScale(tick)}
+          y2={yScale(tick)}
+          stroke={theme.palette.outline.subdued}
+        />
+      ))}
+    </>
+  );
+
+  return (
+    <g transform={`translate(0,-${scaledHeight})`} id="grid">
+      {fullGrid ? renderFullGrid : renderAxlesOnly}
+    </g>
+  );
+};
+
+export default CustomGrid;
diff --git a/src/ui/lib/components/Charts/MetricLine/components/CustomTick/CustomTick.interfaces.ts b/src/ui/lib/components/Charts/MetricLine/components/CustomTick/CustomTick.interfaces.ts
new file mode 100644
index 00000000..efe5ef04
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/components/CustomTick/CustomTick.interfaces.ts
@@ -0,0 +1,8 @@
+export interface CustomTickProps {
+  scale: (d: number) => number;
+  isXAxis: boolean;
+  tick: number;
+  withTicks?: boolean;
+  isFirst: boolean;
+  isLast: boolean;
+}
diff --git a/src/ui/lib/components/Charts/MetricLine/components/CustomTick/index.tsx b/src/ui/lib/components/Charts/MetricLine/components/CustomTick/index.tsx
new file mode 100644
index 00000000..3784ff86
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/components/CustomTick/index.tsx
@@ -0,0 +1,73 @@
+import { useTheme } from '@mui/material';
+
+import { CustomTickProps } from './CustomTick.interfaces';
+
+const CustomTick = ({
+  isXAxis,
+  tick,
+  scale,
+  withTicks = false,
+  isFirst,
+  isLast,
+}: CustomTickProps) => {
+  const theme = useTheme();
+
+  const getGroupPosition = () => {
+    if (!isXAxis) {
+      return { x: 0, y: scale(tick) };
+    }
+
+    let x = scale(tick);
+    if (isFirst) {
+      x = 4;
+    }
+    if (isLast) {
+      x -= 4;
+    }
+    return { x, y: 0 };
+  };
+
+  const renderTickContent = (
+    textAnchor: 'middle' | 'end',
+    x: number,
+    y: number,
+    lineX: number,
+    lineY: number
+  ) => (
+    <>
+      {withTicks && (
+        <line
+          x1={0}
+          y1={0}
+          x2={lineX}
+          y2={lineY}
+          stroke={theme.palette.surface.onSurface}
+        />
+      )}
+      <text
+        textAnchor={textAnchor}
+        x={x}
+        y={y}
+        dominantBaseline={isXAxis ? undefined : 'middle'}
+        fontFamily={theme.typography.axisLabel.fontFamily}
+        fontWeight={theme.typography.axisLabel.fontWeight}
+        fontSize={theme.typography.axisLabel.fontSize}
+        fill={theme.palette.surface.onSurface}
+      >
+        {tick}
+      </text>
+    </>
+  );
+
+  const { x, y } = getGroupPosition();
+
+  return (
+    <g key={tick} transform={`translate(${x}, ${y})`}>
+      {isXAxis
+        ? renderTickContent('middle', 0, 10, 0, 6)
+        : renderTickContent('end', -5, 0, -6, 0)}
+    </g>
+  );
+};
+
+export default CustomTick;
diff --git a/src/ui/lib/components/Charts/MetricLine/components/ThresholdBar/ThresholdBar.interfaces.ts b/src/ui/lib/components/Charts/MetricLine/components/ThresholdBar/ThresholdBar.interfaces.ts
new file mode 100644
index 00000000..266b4c16
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/components/ThresholdBar/ThresholdBar.interfaces.ts
@@ -0,0 +1,9 @@
+export interface ThresholdBarProps {
+  xScale: (value: number) => number;
+  yScale: (value: number) => number;
+  threshold?: number;
+  minX: number;
+  minY: number;
+  maxX: number;
+  maxY: number;
+}
diff --git a/src/ui/lib/components/Charts/MetricLine/components/ThresholdBar/index.tsx b/src/ui/lib/components/Charts/MetricLine/components/ThresholdBar/index.tsx
new file mode 100644
index 00000000..c4027104
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/components/ThresholdBar/index.tsx
@@ -0,0 +1,53 @@
+import { useTheme } from '@mui/material';
+import React, { FC } from 'react';
+
+import { ThresholdBarProps } from './ThresholdBar.interfaces';
+
+const ThresholdBar: FC<ThresholdBarProps> = ({
+  xScale,
+  yScale,
+  threshold,
+  minX,
+  maxX,
+  minY,
+  maxY,
+}) => {
+  const theme = useTheme();
+
+  if (threshold === undefined || threshold <= 0 || threshold > maxY) {
+    return null;
+  }
+
+  const x0 = xScale(minX);
+  const y0 = yScale(minY);
+  const xMax = xScale(maxX);
+  const yThreshold = yScale(threshold);
+
+  if ([x0, y0, xMax, yThreshold].some((value) => value === undefined)) {
+    return null;
+  }
+
+  return (
+    <g>
+      <line
+        x1={x0}
+        y1={yThreshold}
+        x2={xMax - x0}
+        y2={yThreshold}
+        stroke={theme.palette.outline.main}
+        strokeWidth="1.5"
+        strokeDasharray="2.4 2.4"
+      />
+      <rect
+        id="threshold"
+        x={x0}
+        y={yThreshold}
+        width={xMax - x0}
+        height={y0 - yThreshold}
+        fill={theme.palette.surface.surfaceContainerHighest}
+      />
+    </g>
+  );
+};
+
+export default ThresholdBar;
diff --git a/src/ui/lib/components/Charts/MetricLine/index.tsx b/src/ui/lib/components/Charts/MetricLine/index.tsx
new file mode 100644
index 00000000..22a9636d
--- /dev/null
+++ b/src/ui/lib/components/Charts/MetricLine/index.tsx
@@ -0,0 +1,3 @@
+export { Component as MetricLine } from './MetricLine.component';
+export { LineColor } from './MetricLine.interface';
+export type { MetricLineProps } from './MetricLine.interface';
diff --git a/src/ui/lib/components/Charts/MiniCombined/MiniCombined.component.tsx b/src/ui/lib/components/Charts/MiniCombined/MiniCombined.component.tsx
new file mode 100644
index 00000000..96341cfe
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/MiniCombined.component.tsx
@@ -0,0 +1,131 @@
+import { useTheme } from '@mui/material';
+import { BarCustomLayerProps, ResponsiveBar } from '@nivo/bar';
+import { Point } from '@nivo/core';
+import React from 'react';
+
+import useChartScales from '../common/useChartScales';
+import CustomBars from './components/CustomBars';
+import CustomGrid from './components/CustomGrid';
+import CustomTick from './components/CustomTick';
+import { MiniCombinedWithResizeProps } from './MiniCombined.interfaces';
+
+export const Component = ({
+  bars,
+  lines,
+  margins,
+  xLegend,
+  containerSize,
+}: MiniCombinedWithResizeProps) => {
+  const theme = useTheme();
+  const combinedGraphTheme = {
+    axis: {
+      legend: {
+        text: {
+          fill: theme.palette.surface.onSurface,
+          fontSize: theme.typography.axisTitle.fontSize,
+          fontWeight: theme.typography.axisTitle.fontWeight,
+          fontFamily: theme.typography.axisTitle.fontFamily,
+        },
+      },
+      ticks: {
+        text: {
+          fill: theme.palette.surface.onSurface,
+        },
+      },
+    },
+  };
+  const defaultMargins = { top: 10, left: 10, right: 10, bottom: 10 };
+  const finalMargins = {
+    ...defaultMargins,
+    ...margins,
+    bottom: margins?.bottom || defaultMargins.bottom,
+  };
+  const { xTicks, yTicks, fnScaleX, fnScaleY, innerHeight } = useChartScales({
+    bars,
+    lines,
+    width: containerSize.width,
+    height: containerSize.height,
+    margins: finalMargins,
+  });
+
+  const CustomGridLayer = () => {
+    const scaledWidth = fnScaleX(xTicks[xTicks.length - 1]);
+    const scaledHeight = innerHeight - fnScaleY(yTicks[0]);
+
+    return (
+      <CustomGrid
+        xScale={(d: number) => fnScaleX(d)}
+        yScale={(d: number) => innerHeight - fnScaleY(d)}
+        width={scaledWidth}
+        height={innerHeight}
+        xTicks={xTicks}
+        yTicks={yTicks}
+        scaledHeight={scaledHeight}
+      />
+    );
+  };
+
+  const CustomBarLayer = (props: BarCustomLayerProps<Point>) => {
+    const heightOffset = innerHeight - fnScaleY(yTicks[0]);
+    return (
+      <CustomBars
+        {...props}
+        xScaleFunc={(d: number) => fnScaleX(d)}
+        yScaleFunc={(d: number) => innerHeight - fnScaleY(d)}
+        heightOffset={heightOffset}
+      />
+    );
+  };
+  return (
+    <div
+      style={{
+        width: containerSize.width + 'px',
+        height: containerSize.height + 'px',
+        position: 'relative',
+      }}
+    >
+      <ResponsiveBar
+        animate={true}
+        data={bars}
+        keys={['y']}
+        indexBy="x"
+        margin={{
+          top: finalMargins.top,
+          right: finalMargins.right,
+          bottom: finalMargins.bottom,
+          left: finalMargins.left,
+        }}
+        padding={0.5}
+        colors={[theme.palette.primary.shades['0']]}
+        axisLeft={null}
+        axisBottom={{
+          tickValues: xTicks,
+          legend: xLegend,
+          legendPosition: 'middle',
+          legendOffset: 20,
+          tickSize: 5,
+          tickPadding: 5,
+          tickRotation: 0,
+          renderTick: (tick) => (
+            <CustomTick
+              isXAxis={true}
+              scale={(d: number) => fnScaleX(d)}
+              tick={xTicks[tick.tickIndex]}
+              isFirst={tick.tickIndex === 0}
+              isLast={tick.tickIndex === xTicks.length - 1}
+            />
+          ),
+        }}
+        layers={['axes', CustomGridLayer, CustomBarLayer]}
+        theme={combinedGraphTheme}
+      />
+      {/* <DottedLines
+        lines={lines}
+        leftMargin={finalMargins.left}
+        topMargin={finalMargins.top}
+        innerHeight={innerHeight}
+        xScale={fnScaleX}
+      /> */}
+    </div>
+  );
+};
diff --git a/src/ui/lib/components/Charts/MiniCombined/MiniCombined.interfaces.ts b/src/ui/lib/components/Charts/MiniCombined/MiniCombined.interfaces.ts
new file mode 100644
index 00000000..6e2b6b13
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/MiniCombined.interfaces.ts
@@ -0,0 +1,15 @@
+import { LinesSeries, Margins, Point } from '../common/interfaces';
+import { ContainerSize } from './components/ContainerSizeWrapper';
+
+export interface MiniCombinedProps {
+  bars: Point[];
+  lines: LinesSeries[];
+  width: number;
+  height: number;
+  margins?: Margins;
+  xLegend: string;
+}
+
+export interface MiniCombinedWithResizeProps extends MiniCombinedProps {
+  containerSize: ContainerSize;
+}
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/ContainerSizeWrapper/index.tsx b/src/ui/lib/components/Charts/MiniCombined/components/ContainerSizeWrapper/index.tsx
new file mode 100644
index 00000000..032df6f8
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/components/ContainerSizeWrapper/index.tsx
@@ -0,0 +1,42 @@
+import React, { useState, useEffect, useRef } from 'react';
+
+interface ContainerSizeWrapperProps {
+  children: (containerSize: ContainerSize) => React.ReactNode;
+}
+
+export interface ContainerSize {
+  width: number;
+  height: number;
+}
+
+const ContainerSizeWrapper: React.FC<ContainerSizeWrapperProps> = ({ children }) => {
+  const [containerSize, setContainerSize] = useState<ContainerSize>({
+    width: 0,
+    height: 0,
+  });
+  const containerRef = useRef<HTMLDivElement>(null);
+
+  useEffect(() => {
+    const updateSize = () => {
+      if (containerRef.current) {
+        setContainerSize({
+          width: containerRef.current.offsetWidth,
+          height: containerRef.current.offsetHeight,
+        });
+      }
+    };
+
+    updateSize();
+    window.addEventListener('resize', updateSize);
+
+    return () => window.removeEventListener('resize', updateSize);
+  }, []);
+
+  return (
+    <div ref={containerRef} style={{ width: '100%', height: '100%' }}>
+      {children(containerSize)}
+    </div>
+  );
+};
+
+export default ContainerSizeWrapper;
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/CustomBars/CustomBars.interfaces.ts b/src/ui/lib/components/Charts/MiniCombined/components/CustomBars/CustomBars.interfaces.ts
new file mode 100644
index 00000000..faad401e
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/components/CustomBars/CustomBars.interfaces.ts
@@ -0,0 +1,7 @@
+import { BarCustomLayerProps } from '@nivo/bar';
+
+export interface CustomBarsProps<T> extends BarCustomLayerProps<T> {
+  xScaleFunc: (d: number) => number;
+  yScaleFunc: (d: number) => number;
+  heightOffset: number;
+}
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/CustomBars/index.tsx b/src/ui/lib/components/Charts/MiniCombined/components/CustomBars/index.tsx
new file mode 100644
index 00000000..5ecc6e27
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/components/CustomBars/index.tsx
@@ -0,0 +1,94 @@
+import { ComputedBarDatum } from '@nivo/bar';
+import { Point } from '@nivo/core';
+import { useTooltip, BasicTooltip } from '@nivo/tooltip';
+import React from 'react';
+
+import { CustomBarsProps } from './CustomBars.interfaces';
+
+const CustomBars = ({
+  bars,
+  xScaleFunc,
+  yScaleFunc,
+  heightOffset,
+}: CustomBarsProps<Point>) => {
+  const { showTooltipFromEvent, hideTooltip } = useTooltip();
+  const minX =
+    bars.length === 1
+      ? 0
+      : Math.min(...bars.map((bar) => xScaleFunc(bar.data.data.x || 0)));
+  const maxX = Math.max(...bars.map((bar) => xScaleFunc(bar.data.data.x || 0)));
+  const handleMouseEnter = (
+    event: React.MouseEvent<SVGPathElement>,
+    bar: ComputedBarDatum<Point>
+  ) => {
+    const x = xScaleFunc(bar.data.data.x || 0) || 0;
+    const normalizedPosition = (x - minX) / (maxX - minX);
+    const maxPadding = 80;
+
+    let paddingLeft = 0;
+    let paddingRight = 0;
+    // log scale padding so that tooltip doesn't get cut off by the edges
+    if (normalizedPosition < 0.5) {
+      const logFactor = -Math.log(2 * normalizedPosition + 0.01) / Math.log(100);
+      paddingLeft = Math.min(maxPadding, Math.max(0, logFactor * maxPadding));
+    } else {
+      const logFactor = -Math.log(2 * (1 - normalizedPosition) + 0.01) / Math.log(100);
+      paddingRight = Math.min(maxPadding, Math.max(0, logFactor * maxPadding));
+    }
+    showTooltipFromEvent(
+      <div
+        style={{
+          paddingLeft: `${paddingLeft}px`,
+          paddingRight: `${paddingRight}px`,
+        }}
+      >
+        <BasicTooltip
+          id={`x: ${bar.data.data.x || 0}, y: ${bar.data.data.y}`}
+          enableChip={true}
+          color={bar.color}
+        />
+      </div>,
+      event
+    );
+  };
+
+  const handleMouseLeave = () => {
+    hideTooltip();
+  };
+
+  return (
+    <g transform={`translate(0,-${heightOffset})`}>
+      {bars.map((bar) => {
+        const barWidth = Math.min(10, bar.width);
+        const maxRadius = Math.floor(barWidth / 2);
+        const x = xScaleFunc(Number(bar.data.data.x || 0)) - barWidth / 2;
+        const y = yScaleFunc(Number(bar.data.data.y));
+        let r;
+        if (bar.height < 4) {
+          r = Math.min(1, maxRadius);
+        } else if (bar.height < barWidth) {
+          r = Math.min(Math.floor(bar.height / 2), maxRadius);
+        } else {
+          r = maxRadius;
+        }
+        r = Math.min(r, Math.floor(bar.height / 2));
+        const v = Math.max(0, bar.height - r);
+        const path = `M ${x + r},${y} h ${
+          barWidth - 2 * r
+        } a ${r},${r} 0 0 1 ${r},${r} v ${v} h -${barWidth} v -${v} a ${r},${r} 0 0 1 ${r},-${r} z`;
+
+        return (
+          <path
+            key={bar.key}
+            d={path}
+            fill={bar.color}
+            onMouseEnter={(event) => handleMouseEnter(event, bar)}
+            onMouseLeave={handleMouseLeave}
+          />
+        );
+      })}
+    </g>
+  );
+};
+
+export default CustomBars;
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/CustomGrid/CustomGrid.interfaces.ts b/src/ui/lib/components/Charts/MiniCombined/components/CustomGrid/CustomGrid.interfaces.ts
new file mode 100644
index 00000000..2e440fba
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/components/CustomGrid/CustomGrid.interfaces.ts
@@ -0,0 +1,10 @@
+export interface CustomGridProps {
+  xScale: (d: number) => number;
+  yScale: (d: number) => number;
+  width: number;
+  height: number;
+  xTicks: number[];
+  yTicks: number[];
+  scaledHeight: number;
+  fullGrid?: boolean;
+}
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/CustomGrid/index.tsx b/src/ui/lib/components/Charts/MiniCombined/components/CustomGrid/index.tsx
new file mode 100644
index 00000000..0ef400b3
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/components/CustomGrid/index.tsx
@@ -0,0 +1,72 @@
+import { useTheme } from '@mui/material';
+
+import { CustomGridProps } from './CustomGrid.interfaces';
+
+const CustomGrid = ({
+  xScale,
+  yScale,
+  width,
+  height,
+  xTicks,
+  yTicks,
+  scaledHeight,
+  fullGrid = false,
+}: CustomGridProps) => {
+  const theme = useTheme();
+  // const xTick = xTicks[0];
+  const yTick = yTicks[yTicks.length - 1];
+
+  const renderAxlesOnly = (
+    <>
+      {/*<line*/}
+      {/*  key={`x${xTick}`}*/}
+      {/*  x1={xScale(xTick)}*/}
+      {/*  x2={xScale(xTick)}*/}
+      {/*  y1={scaledHeight}*/}
+      {/*  y2={height}*/}
+      {/*  stroke={theme.palette.outline.subdued}*/}
+      {/*/>*/}
+      <line
+        key={`y${yTick}`}
+        x1={0}
+        x2={width}
+        y1={yScale(yTick)}
+        y2={yScale(yTick)}
+        stroke={theme.palette.outline.subdued}
+      />
+    </>
+  );
+
+  const renderFullGrid = (
+    <>
+      {xTicks.map((tick) => (
+        <line
+          key={`x${tick}`}
+          x1={xScale(tick)}
+          x2={xScale(tick)}
+          y1={scaledHeight}
+          y2={height}
+          stroke={theme.palette.outline.subdued}
+        />
+      ))}
+      {yTicks.map((tick) => (
+        <line
+          key={`y${tick}`}
+          x1={0}
+          x2={width}
+          y1={yScale(tick)}
+          y2={yScale(tick)}
+          stroke={theme.palette.outline.subdued}
+        />
+      ))}
+    </>
+  );
+
+  return (
+    <g transform={`translate(0,-${scaledHeight})`} id="grid">
+      {fullGrid ? renderFullGrid : renderAxlesOnly}
+    </g>
+  );
+};
+
+export default CustomGrid;
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/CustomTick.interfaces.ts b/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/CustomTick.interfaces.ts
new file mode 100644
index 00000000..efe5ef04
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/CustomTick.interfaces.ts
@@ -0,0 +1,8 @@
+export interface CustomTickProps {
+  scale: (d: number) => number;
+  isXAxis: boolean;
+  tick: number;
+  withTicks?: boolean;
+  isFirst: boolean;
+  isLast: boolean;
+}
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/index.tsx b/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/index.tsx
new file mode 100644
index 00000000..c7941c97
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/components/CustomTick/index.tsx
@@ -0,0 +1,62 @@
+import { useTheme } from '@mui/material';
+
+import { CustomTickProps } from './CustomTick.interfaces';
+
+const CustomTick = ({
+  isXAxis,
+  tick,
+  scale,
+  withTicks = false,
+  isFirst,
+  isLast,
+}: CustomTickProps) => {
+  const theme = useTheme();
+
+  if (isXAxis && !isFirst && !isLast) {
+    return null;
+  }
+
+  const getGroupPosition = () => {
+    if (isXAxis) {
+      let x = scale(tick);
+      if (isFirst) {
+        x = 4;
+      }
+      if (isLast) {
+        x -= 4;
+      }
+      return { x, y: 0 };
+    }
+    return { x: 0, y: scale(tick) };
+  };
+
+  const renderTickContent = (textAnchor: 'middle' | 'end', x: number, y: number) => (
+    <>
+      {withTicks && (
+        <line x1={0} y1={0} x2={x} y2={y} stroke={theme.palette.surface.onSurface} />
+      )}
+      <text
+        textAnchor={textAnchor}
+        x={x === 0 ? undefined : x}
+        y={y === 0 ? undefined : y}
+        dominantBaseline={isXAxis ? undefined : 'middle'}
+        fontFamily={theme.typography.axisLabel.fontFamily}
+        fontWeight={theme.typography.axisLabel.fontWeight}
+        fontSize={theme.typography.axisLabel.fontSize}
+        fill={theme.palette.surface.onSurface}
+      >
+        {tick}
+      </text>
+    </>
+  );
+
+  const { x, y } = getGroupPosition();
+
+  return (
+    <g key={tick} transform={`translate(${x}, ${y})`}>
+      {isXAxis ? renderTickContent('middle', 0, 10) : renderTickContent('end', -5, 0)}
+    </g>
+  );
+};
+
+export default CustomTick;
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/DottedLines/DottedLines.interfaces.ts b/src/ui/lib/components/Charts/MiniCombined/components/DottedLines/DottedLines.interfaces.ts
new file mode 100644
index 00000000..be9085ee
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/components/DottedLines/DottedLines.interfaces.ts
@@ -0,0 +1,9 @@
+import { Point } from '../../../common/interfaces';
+
+export interface DottedLinesProps {
+  lines: Point[];
+  leftMargin: number;
+  topMargin: number;
+  innerHeight: number;
+  xScale: (d: number) => number;
+}
diff --git a/src/ui/lib/components/Charts/MiniCombined/components/DottedLines/index.tsx b/src/ui/lib/components/Charts/MiniCombined/components/DottedLines/index.tsx
new file mode 100644
index 00000000..52b8cbce
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/components/DottedLines/index.tsx
@@ -0,0 +1,37 @@
+import useLineColors from '../../../common/useLineColors';
+
+import { DottedLinesProps } from './DottedLines.interfaces';
+
+const DottedLines = ({
+  lines,
+  leftMargin,
+  topMargin,
+  xScale,
+  innerHeight,
+}: DottedLinesProps) => {
+  const lineColor = useLineColors();
+  return (
+    <svg
+      style={{ position: 'absolute', top: 0, left: 0, pointerEvents: 'none' }}
+      width="100%"
+      height="100%"
+    >
+      <g transform={`translate(${leftMargin}, ${topMargin})`}>
+        {lines.map((line, i) => (
+          <line
+            key={i}
+            x1={xScale(line.x)}
+            y1="0"
+            x2={xScale(line.x)}
+            y2={innerHeight}
+            stroke={lineColor[i]}
+            strokeWidth="1.5"
+            strokeDasharray="3,3"
+          />
+        ))}
+      </g>
+    </svg>
+  );
+};
+
+export default DottedLines;
diff --git a/src/ui/lib/components/Charts/MiniCombined/index.tsx b/src/ui/lib/components/Charts/MiniCombined/index.tsx
new file mode 100644
index 00000000..6e363fdc
--- /dev/null
+++ b/src/ui/lib/components/Charts/MiniCombined/index.tsx
@@ -0,0 +1,5 @@
+export { Component as MiniCombined } from './MiniCombined.component';
+export type {
+  MiniCombinedProps,
+  MiniCombinedWithResizeProps,
+} from './MiniCombined.interfaces';
diff --git a/src/ui/lib/components/Charts/common/interfaces.ts b/src/ui/lib/components/Charts/common/interfaces.ts
new file mode 100644
index 00000000..468fb4af
--- /dev/null
+++ b/src/ui/lib/components/Charts/common/interfaces.ts
@@ -0,0 +1,34 @@
+import { MiniCombinedProps } from '../MiniCombined/MiniCombined.interfaces';
+
+export type Point = {
+  x: number;
+  y: number;
+};
+
+export type LinesSeries = Point & {
+  id: string;
+};
+
+export type Margins = {
+  top?: number;
+  bottom?: number;
+  left?: number;
+  right?: number;
+};
+
+type RequiredCombinedProps = Required<MiniCombinedProps>;
+type RequiredMargins = Required<Margins>;
+type BaseChartScalesProps = Omit<RequiredCombinedProps, 'xLegend' | 'yLegend'>;
+export type ChartScalesProps = BaseChartScalesProps & {
+  margins: RequiredMargins;
+};
+
+export interface CombinedProps {
+  bars: Point[];
+  lines: LinesSeries[];
+  width: number;
+  height: number;
+  margins?: Margins;
+  xLegend: string;
+  yLegend: string;
+}
diff --git a/src/ui/lib/components/Charts/common/useChartScales.ts b/src/ui/lib/components/Charts/common/useChartScales.ts
new file mode 100644
index 00000000..43f0a81f
--- /dev/null
+++ b/src/ui/lib/components/Charts/common/useChartScales.ts
@@ -0,0 +1,51 @@
+import { useMemo } from 'react';
+
+import { ChartScalesProps } from './interfaces';
+
+export const calculateStep = (min: number, max: number) => {
+  const range = max - min;
+  const approxStep = range / 10;
+  const magnitude = Math.pow(10, Math.floor(Math.log10(approxStep)));
+  return Math.ceil(approxStep / magnitude) * magnitude;
+};
+
+const useChartScales = ({ bars, width, height, margins }: ChartScalesProps) => {
+  return useMemo(() => {
+    const xValues = bars.map((d) => d.x);
+    const yValues = bars.map((d) => d.y);
+
+    const xMin = xValues.length === 1 ? 0 : Math.min(...xValues);
+    const xMax = Math.max(...xValues);
+    const yMin = Math.min(...yValues) > 0 ? 0 : Math.min(...yValues);
+    const yMax = Math.max(...yValues);
+
+    const xStep = calculateStep(xMin, xMax);
+    const yStep = calculateStep(yMin, yMax);
+
+    const xTicks = Array.from(
+      { length: Math.ceil((xMax - xMin) / xStep) + 1 },
+      (_, i) => xMin + i * xStep
+    );
+    const yTicks = Array.from(
+      { length: Math.ceil((yMax - yMin) / yStep) + 1 },
+      (_, i) => yMin + i * yStep
+    ).reverse();
+
+    const innerHeight = height - margins.top - margins.bottom;
+    const innerWidth = width - margins.left - margins.right;
+    const fnScaleX = (d: number) =>
+      innerWidth * Math.min(1, (d - xMin) / (xMax - xMin));
+    const fnScaleY = (d: number) => (innerHeight * (d - yMin)) / (yMax - yMin);
+
+    return {
+      xTicks,
+      yTicks,
+      fnScaleX,
+      fnScaleY,
+      innerWidth,
+      innerHeight,
+    };
+  }, [bars, width, height, margins]);
+};
+
+export default useChartScales;
diff --git a/src/ui/lib/components/Charts/common/useLineColors.tsx b/src/ui/lib/components/Charts/common/useLineColors.tsx
new file mode 100644
index 00000000..b0c9fe07
--- /dev/null
+++ b/src/ui/lib/components/Charts/common/useLineColors.tsx
@@ -0,0 +1,14 @@
+import { useTheme } from '@mui/material';
+
+const useLineColors = () => {
+  const theme = useTheme();
+  const palette = theme.palette;
+  return [
+    palette.primary.shades.W100,
+    palette.secondary.main,
+    palette.tertiary.main,
+    palette.quarternary.main,
+  ];
+};
+
+export default useLineColors;
diff --git a/src/ui/lib/components/DataPanel/DataPanel.component.tsx b/src/ui/lib/components/DataPanel/DataPanel.component.tsx
new file mode 100644
index 00000000..19d94e30
--- /dev/null
+++ b/src/ui/lib/components/DataPanel/DataPanel.component.tsx
@@ -0,0 +1,35 @@
+import { Typography } from '@mui/material';
+
+import { DataPanelProps } from './DataPanel.interfaces';
+import {
+  BottomCell,
+  InnerContainer,
+  TopCell,
+  HeaderContainer,
+} from './DataPanel.styles';
+
+export const Component = ({
+  header,
+  topContainer,
+  bottomContainer,
+}: DataPanelProps) => {
+  return (
+    <InnerContainer item xs={4}>
+      <HeaderContainer item xs={12}>
+        <Typography variant="overline1" color="surface.onSurface">
+          {header}
+        </Typography>
+      </HeaderContainer>
+
+      <TopCell item xs={12} data-id="top-cell">
+        {topContainer}
+      </TopCell>
+
+      {bottomContainer && (
+        <BottomCell item xs={12} data-id="bottom-cell">
+          {bottomContainer}
+        </BottomCell>
+      )}
+    </InnerContainer>
+  );
+};
diff --git a/src/ui/lib/components/DataPanel/DataPanel.interfaces.ts b/src/ui/lib/components/DataPanel/DataPanel.interfaces.ts
new file mode 100644
index 00000000..3ea55a8d
--- /dev/null
+++ b/src/ui/lib/components/DataPanel/DataPanel.interfaces.ts
@@ -0,0 +1,7 @@
+import { ReactNode } from 'react';
+
+export interface DataPanelProps {
+  header: string;
+  topContainer: ReactNode;
+  bottomContainer?: ReactNode;
+}
diff --git a/src/ui/lib/components/DataPanel/DataPanel.styles.tsx b/src/ui/lib/components/DataPanel/DataPanel.styles.tsx
new file mode 100644
index 00000000..7c364ab8
--- /dev/null
+++ b/src/ui/lib/components/DataPanel/DataPanel.styles.tsx
@@ -0,0 +1,35 @@
+import { Grid, styled } from '@mui/material';
+
+import { HeaderContainer as HeaderContainerBase } from '../MetricsContainer/MetricsContainer.styles';
+
+export const InnerContainer = styled(Grid)(({ theme }) => ({
+  borderWidth: '1px',
+  borderStyle: 'solid',
+  borderColor: theme.palette.outline.subdued,
+  borderRadius: '8px',
+  overflow: 'hidden',
+  backgroundColor: theme.palette.surface.surfaceContainerLow,
+}));
+
+export const TopCell = styled(Grid)({
+  display: 'flex',
+  alignItems: 'flex-start',
+  height: '180px',
+  padding: '24px !important',
+});
+
+export const BottomCell = styled(Grid)(({ theme }) => ({
+  display: 'flex',
+  alignItems: 'center',
+  minHeight: '220px',
+  height: '180px',
+  borderTopWidth: '1px',
+  borderTopStyle: 'solid',
+  borderTopColor: theme.palette.outline.subdued,
+  padding: '24px !important',
+}));
+
+export const HeaderContainer = styled(HeaderContainerBase)({
+  display: 'flex',
+  justifyContent: 'flex-start',
+});
diff --git a/src/ui/lib/components/DataPanel/index.tsx b/src/ui/lib/components/DataPanel/index.tsx
new file mode 100644
index 00000000..73cfde05
--- /dev/null
+++ b/src/ui/lib/components/DataPanel/index.tsx
@@ -0,0 +1,2 @@
+export { Component as DataPanel } from './DataPanel.component';
+export type { DataPanelProps } from './DataPanel.interfaces';
diff --git a/src/ui/lib/components/DistributionPercentiles/DistributionPercentiles.component.tsx b/src/ui/lib/components/DistributionPercentiles/DistributionPercentiles.component.tsx
new file mode 100644
index 00000000..b9cadc06
--- /dev/null
+++ b/src/ui/lib/components/DistributionPercentiles/DistributionPercentiles.component.tsx
@@ -0,0 +1,26 @@
+import { Box, Typography } from '@mui/material';
+
+import { Badge } from '../Badge';
+import { Section } from '../Section';
+import { DistributionPercentilesProps } from './DistributionPercentiles.interfaces';
+
+export const Component = ({ list, rpsValue, units }: DistributionPercentilesProps) => {
+  return (
+    <Box>
+      <Box flexDirection="row" display="flex" alignItems="center" gap={'12px'}>
+        <Typography
+          variant="overline1"
+          color="surface.onSurfaceSubdued"
+          textTransform="uppercase"
+        >
+          Distribution At
+        </Typography>
+        <Badge label={`${rpsValue} rps`} />
+      </Box>
+
+      {list.map((item) => (
+        <Section key={item.label} label={item.label} value={`${item.value} ${units}`} />
+      ))}
+    </Box>
+  );
+};
diff --git a/src/ui/lib/components/DistributionPercentiles/DistributionPercentiles.interfaces.tsx b/src/ui/lib/components/DistributionPercentiles/DistributionPercentiles.interfaces.tsx
new file mode 100644
index 00000000..23e86cfb
--- /dev/null
+++ b/src/ui/lib/components/DistributionPercentiles/DistributionPercentiles.interfaces.tsx
@@ -0,0 +1,10 @@
+export type PercentileItem = {
+  label: string;
+  value: string;
+};
+
+export interface DistributionPercentilesProps {
+  list: PercentileItem[];
+  rpsValue: number;
+  units: string;
+}
diff --git a/src/ui/lib/components/DistributionPercentiles/DistributionPercentiles.styles.tsx b/src/ui/lib/components/DistributionPercentiles/DistributionPercentiles.styles.tsx
new file mode 100644
index 00000000..19c7bd34
--- /dev/null
+++ b/src/ui/lib/components/DistributionPercentiles/DistributionPercentiles.styles.tsx
@@ -0,0 +1,8 @@
+import { Box, styled } from '@mui/material';
+
+export const BadgeContainer = styled(Box)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerHigh,
+  borderRadius: '6px',
+  padding: '4px 6px',
+  marginLeft: '12px',
+}));
diff --git a/src/ui/lib/components/DistributionPercentiles/index.tsx b/src/ui/lib/components/DistributionPercentiles/index.tsx
new file mode 100644
index 00000000..2e75fad0
--- /dev/null
+++ b/src/ui/lib/components/DistributionPercentiles/index.tsx
@@ -0,0 +1,5 @@
+export { Component as DistributionPercentiles } from './DistributionPercentiles.component';
+export type {
+  PercentileItem,
+  DistributionPercentilesProps,
+} from './DistributionPercentiles.interfaces';
diff --git a/src/ui/lib/components/GraphTitle/GraphTitle.component.tsx b/src/ui/lib/components/GraphTitle/GraphTitle.component.tsx
new file mode 100644
index 00000000..a7a40602
--- /dev/null
+++ b/src/ui/lib/components/GraphTitle/GraphTitle.component.tsx
@@ -0,0 +1,16 @@
+import { Typography, useTheme } from '@mui/material';
+
+import { GraphTitleProps } from './GraphTitle.interfaces';
+
+export const Component = ({ title }: GraphTitleProps) => {
+  const theme = useTheme();
+  return (
+    <Typography
+      variant="subtitle2"
+      color={theme.palette.surface.onSurfaceSubdued}
+      mb={2}
+    >
+      {title}
+    </Typography>
+  );
+};
diff --git a/src/ui/lib/components/GraphTitle/GraphTitle.interfaces.ts b/src/ui/lib/components/GraphTitle/GraphTitle.interfaces.ts
new file mode 100644
index 00000000..70853edb
--- /dev/null
+++ b/src/ui/lib/components/GraphTitle/GraphTitle.interfaces.ts
@@ -0,0 +1,3 @@
+export interface GraphTitleProps {
+  title: string;
+}
diff --git a/src/ui/lib/components/GraphTitle/index.tsx b/src/ui/lib/components/GraphTitle/index.tsx
new file mode 100644
index 00000000..0dad3c2b
--- /dev/null
+++ b/src/ui/lib/components/GraphTitle/index.tsx
@@ -0,0 +1,2 @@
+export { Component as GraphTitle } from './GraphTitle.component';
+export type { GraphTitleProps } from './GraphTitle.interfaces';
diff --git a/src/ui/lib/components/Input/Input.component.tsx b/src/ui/lib/components/Input/Input.component.tsx
new file mode 100644
index 00000000..2fd68b6b
--- /dev/null
+++ b/src/ui/lib/components/Input/Input.component.tsx
@@ -0,0 +1,64 @@
+import { Box, Typography } from '@mui/material';
+import React, { FC } from 'react';
+
+import { useColor } from '@/lib/hooks/useColor';
+
+import { InputProps } from './Input.interfaces';
+import { StyledTextField, InputContainer, ErrorMessage } from './Input.styles';
+
+export const Component: FC<InputProps> = ({
+  label,
+  prefix,
+  disabled = false,
+  value,
+  onChange,
+  fullWidth = false,
+  fontColor,
+  error,
+  isNumber = false,
+}) => {
+  const selectedColor = useColor(fontColor);
+  return (
+    <Box>
+      <InputContainer
+        className={disabled ? 'disabled' : ''}
+        fullWidth={fullWidth}
+        data-id="input-wrapper"
+      >
+        <Typography variant="overline2" color="surface.onSurface">
+          {label}
+        </Typography>
+        <Box display="flex" alignItems="baseline">
+          {prefix && (
+            <Typography
+              variant="body1"
+              color="surface.onSurfaceAccent"
+              sx={{ marginRight: 1 }}
+            >
+              {prefix}
+            </Typography>
+          )}
+          <StyledTextField
+            fontColor={selectedColor}
+            disabled={disabled}
+            placeholder={label}
+            variant="standard"
+            type="number"
+            value={value}
+            onChange={onChange}
+            {...(isNumber && {
+              inputProps: {
+                type: 'number',
+                step: '1',
+                min: '0',
+              },
+            })}
+          />
+        </Box>
+      </InputContainer>
+      <ErrorMessage variant="caption" color="error.main">
+        {error || ' '}
+      </ErrorMessage>
+    </Box>
+  );
+};
diff --git a/src/ui/lib/components/Input/Input.interfaces.ts b/src/ui/lib/components/Input/Input.interfaces.ts
new file mode 100644
index 00000000..820132c3
--- /dev/null
+++ b/src/ui/lib/components/Input/Input.interfaces.ts
@@ -0,0 +1,23 @@
+import { ChangeEvent } from 'react';
+
+import { LineColor } from '@/lib/components/Charts/MetricLine';
+
+export interface InputProps {
+  label: string;
+  prefix?: string;
+  disabled?: boolean;
+  value: number | undefined;
+  onChange: (event: ChangeEvent<HTMLInputElement>) => void;
+  fullWidth?: boolean;
+  fontColor?: LineColor;
+  error?: string | undefined;
+  isNumber?: boolean;
+}
+
+export type CustomInputContainer = {
+  fullWidth?: boolean;
+};
+
+export type CustomInputProps = {
+  fontColor: string;
+};
diff --git a/src/ui/lib/components/Input/Input.styles.tsx b/src/ui/lib/components/Input/Input.styles.tsx
new file mode 100644
index 00000000..1d5a832e
--- /dev/null
+++ b/src/ui/lib/components/Input/Input.styles.tsx
@@ -0,0 +1,60 @@
+import { Box, styled, TextField, Typography } from '@mui/material';
+
+import { CustomInputContainer, CustomInputProps } from './Input.interfaces';
+
+export const StyledTextField = styled(TextField, {
+  shouldForwardProp: (propName) => propName !== 'fontColor',
+})<CustomInputProps>(({ theme, fontColor }) => ({
+  '& .MuiInputBase-input': {
+    color: fontColor,
+    fontSize: theme.typography.body1.fontSize,
+    fontWeight: theme.typography.body1.fontWeight,
+    padding: '0',
+  },
+  '& .MuiInput-underline:before': {
+    borderBottomColor: 'transparent',
+  },
+  '& .MuiInput-underline:hover:not(.Mui-disabled):before': {
+    borderBottomColor: 'transparent',
+  },
+  '& .MuiInput-underline:after': {
+    borderBottomColor: 'transparent',
+  },
+  '& input[type=number]': {
+    MozAppearance: 'textfield',
+  },
+  '& input[type=number]::-webkit-outer-spin-button': {
+    WebkitAppearance: 'none',
+    margin: 0,
+  },
+  '& input[type=number]::-webkit-inner-spin-button': {
+    WebkitAppearance: 'none',
+    margin: 0,
+  },
+  '& .Mui-disabled': {
+    color: theme.palette.surface.onSurfaceSubdued + ' !important',
+    WebkitTextFillColor: theme.palette.surface.onSurfaceSubdued + ' !important',
+  },
+}));
+
+export const InputContainer = styled(Box, {
+  shouldForwardProp: (propName) => propName !== 'fullWidth',
+})<CustomInputContainer>(({ theme, fullWidth }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerHigh,
+  marginBottom: '8px',
+  padding: '8px 12px 11px 12px',
+  display: fullWidth ? 'block' : 'inline-block',
+  borderRadius: '8px',
+  minWidth: '168px',
+  width: 'auto',
+  flex: 1,
+  '&.disabled': { opacity: 0.4 },
+}));
+
+export const ErrorMessage = styled(Typography)({
+  display: 'flex',
+  justifyContent: 'center',
+  alignItems: 'center',
+  textAlign: 'center',
+  minHeight: '15px',
+});
diff --git a/src/ui/lib/components/Input/index.tsx b/src/ui/lib/components/Input/index.tsx
new file mode 100644
index 00000000..70ca783d
--- /dev/null
+++ b/src/ui/lib/components/Input/index.tsx
@@ -0,0 +1,2 @@
+export { Component as Input } from './Input.component';
+export type { InputProps } from './Input.interfaces';
diff --git a/src/ui/lib/components/MeanMetricSummary/MeanMetricSummary.component.tsx b/src/ui/lib/components/MeanMetricSummary/MeanMetricSummary.component.tsx
new file mode 100644
index 00000000..904db20a
--- /dev/null
+++ b/src/ui/lib/components/MeanMetricSummary/MeanMetricSummary.component.tsx
@@ -0,0 +1,30 @@
+import { Box, Typography } from '@mui/material';
+
+import { Badge } from '../Badge';
+import { MeanMetricSummaryProps } from './MeanMetricSummary.interfaces';
+
+export const Component = ({
+  meanValue,
+  meanUnit,
+  rpsValue,
+}: MeanMetricSummaryProps) => {
+  return (
+    <Box flexDirection="column">
+      <Box flexDirection="row" display="flex" alignItems="center" gap={'12px'}>
+        <Typography
+          variant="overline1"
+          color="surface.onSurfaceSubdued"
+          textTransform="uppercase"
+        >
+          Mean At
+        </Typography>
+        <Badge label={`${rpsValue} rps`} />
+      </Box>
+      <Box flexDirection="row" display="flex">
+        <Typography variant="h4" color="surface.onSurfaceAccent" pt={1}>
+          {meanValue} {meanUnit}
+        </Typography>
+      </Box>
+    </Box>
+  );
+};
diff --git a/src/ui/lib/components/MeanMetricSummary/MeanMetricSummary.interfaces.tsx b/src/ui/lib/components/MeanMetricSummary/MeanMetricSummary.interfaces.tsx
new file mode 100644
index 00000000..d7561593
--- /dev/null
+++ b/src/ui/lib/components/MeanMetricSummary/MeanMetricSummary.interfaces.tsx
@@ -0,0 +1,5 @@
+export interface MeanMetricSummaryProps {
+  meanValue: string;
+  meanUnit: string;
+  rpsValue: number;
+}
diff --git a/src/ui/lib/components/MeanMetricSummary/index.tsx b/src/ui/lib/components/MeanMetricSummary/index.tsx
new file mode 100644
index 00000000..86d23f77
--- /dev/null
+++ b/src/ui/lib/components/MeanMetricSummary/index.tsx
@@ -0,0 +1,2 @@
+export { Component as MeanMetricSummary } from './MeanMetricSummary.component';
+export type { MeanMetricSummaryProps } from './MeanMetricSummary.interfaces';
diff --git a/src/ui/lib/components/MetricsContainer/MetricsContainer.component.tsx b/src/ui/lib/components/MetricsContainer/MetricsContainer.component.tsx
new file mode 100644
index 00000000..456d14d4
--- /dev/null
+++ b/src/ui/lib/components/MetricsContainer/MetricsContainer.component.tsx
@@ -0,0 +1,68 @@
+import { Grid, Typography } from '@mui/material';
+
+import { MetricsContainerProps } from './MetricsContainer.interfaces';
+import {
+  HeaderContainer,
+  InnerContainer,
+  MainContainer,
+  RightColumn,
+} from './MetricsContainer.styles';
+
+export const Component = ({
+  header,
+  leftColumn,
+  rightColumn,
+  children,
+}: MetricsContainerProps) => {
+  return (
+    <InnerContainer container spacing={2}>
+      <HeaderContainer
+        xs={12}
+        item
+        flexDirection="row"
+        display="flex"
+        justifyContent="space-between"
+      >
+        <Typography variant="overline1" color="surface.onSurface">
+          {header}
+        </Typography>
+      </HeaderContainer>
+
+      <MainContainer
+        xs={12}
+        item
+        padding="24px"
+        flexDirection="column"
+        justifyContent="center"
+        alignItems="center"
+        display="flex"
+      >
+        {children}
+      </MainContainer>
+
+      {/* middle containers  */}
+      <Grid
+        xs={rightColumn ? 6 : 12}
+        item
+        display="flex"
+        alignItems="center"
+        padding="24px"
+        minHeight="232px"
+      >
+        {leftColumn}
+      </Grid>
+      {rightColumn && (
+        <RightColumn
+          xs={6}
+          item
+          display="flex"
+          alignItems="center"
+          padding="24px"
+          minHeight="232px"
+        >
+          {rightColumn}
+        </RightColumn>
+      )}
+    </InnerContainer>
+  );
+};
diff --git a/src/ui/lib/components/MetricsContainer/MetricsContainer.interfaces.ts b/src/ui/lib/components/MetricsContainer/MetricsContainer.interfaces.ts
new file mode 100644
index 00000000..cd709539
--- /dev/null
+++ b/src/ui/lib/components/MetricsContainer/MetricsContainer.interfaces.ts
@@ -0,0 +1,8 @@
+import { ReactNode } from 'react';
+
+export interface MetricsContainerProps {
+  header: string;
+  leftColumn: ReactNode;
+  rightColumn?: ReactNode;
+  children: ReactNode;
+}
diff --git a/src/ui/lib/components/MetricsContainer/MetricsContainer.styles.tsx b/src/ui/lib/components/MetricsContainer/MetricsContainer.styles.tsx
new file mode 100644
index 00000000..2117afaa
--- /dev/null
+++ b/src/ui/lib/components/MetricsContainer/MetricsContainer.styles.tsx
@@ -0,0 +1,33 @@
+import { Grid, styled } from '@mui/material';
+
+export const InnerContainer = styled(Grid)(({ theme }) => ({
+  borderWidth: '1px',
+  borderStyle: 'solid',
+  borderColor: theme.palette.outline.subdued,
+  borderRadius: '8px',
+  overflow: 'hidden',
+  backgroundColor: theme.palette.surface.surfaceContainerLow,
+  marginLeft: 0,
+  marginTop: 0,
+}));
+
+export const HeaderContainer = styled(Grid)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainer,
+  borderBottomWidth: '1px',
+  borderBottomStyle: 'solid',
+  borderBottomColor: theme.palette.outline.subdued,
+  padding: '18px 24px',
+  margin: 0,
+}));
+
+export const RightColumn = styled(Grid)(({ theme }) => ({
+  borderLeftWidth: '1px',
+  borderLeftStyle: 'solid',
+  borderLeftColor: theme.palette.outline.subdued,
+}));
+
+export const MainContainer = styled(Grid)(({ theme }) => ({
+  borderBottomWidth: '1px',
+  borderBottomStyle: 'solid',
+  borderBottomColor: theme.palette.outline.subdued,
+}));
diff --git a/src/ui/lib/components/MetricsContainer/index.tsx b/src/ui/lib/components/MetricsContainer/index.tsx
new file mode 100644
index 00000000..d6bbc5f2
--- /dev/null
+++ b/src/ui/lib/components/MetricsContainer/index.tsx
@@ -0,0 +1,2 @@
+export { Component as MetricsContainer } from './MetricsContainer.component';
+export type { MetricsContainerProps } from './MetricsContainer.interfaces';
diff --git a/src/ui/lib/components/MetricsSummary/MetricSummary.interfaces.ts b/src/ui/lib/components/MetricsSummary/MetricSummary.interfaces.ts
new file mode 100644
index 00000000..b806b4e6
--- /dev/null
+++ b/src/ui/lib/components/MetricsSummary/MetricSummary.interfaces.ts
@@ -0,0 +1,3 @@
+export type CustomSelectProps = {
+  placeholder?: string;
+};
diff --git a/src/ui/lib/components/MetricsSummary/MetricsSummary.component.tsx b/src/ui/lib/components/MetricsSummary/MetricsSummary.component.tsx
new file mode 100644
index 00000000..ef10d209
--- /dev/null
+++ b/src/ui/lib/components/MetricsSummary/MetricsSummary.component.tsx
@@ -0,0 +1,311 @@
+import { Box, Button, Typography, useTheme } from '@mui/material';
+import React, { ElementType } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { Expand } from '@assets/icons';
+
+import {
+  selectInterpolatedMetrics,
+  selectMetricsSummaryLineData,
+  useGetBenchmarksQuery,
+} from '../../store/slices/benchmarks';
+import { selectRunInfo } from '../../store/slices/runInfo';
+import { formatNumber } from '../../utils/helpers';
+import { MetricLine, LineColor } from '@/lib/components/Charts/MetricLine';
+import { selectSloState } from '@/lib/store/slices/slo/slo.selectors';
+import { setCurrentRequestRate } from '@/lib/store/slices/slo/slo.slice';
+
+import { BlockHeader } from '../BlockHeader';
+import { Input } from '../Input';
+import { MetricValue } from './components/MetricValue';
+import {
+  CustomSlider,
+  FieldCell,
+  FieldsContainer,
+  FooterLeftCell,
+  FooterRightCell,
+  GraphContainer,
+  HeaderLeftCell,
+  HeaderRightCell,
+  MetricsSummaryContainer,
+  MiddleColumn,
+  InputContainer,
+  StyledSelect,
+  StyledInputLabel,
+  OptionItem,
+  StyledFormControl,
+} from './MetricsSummary.styles';
+import { useSummary } from './useSummary';
+
+const percentileOptions = ['p50', 'p90', 'p95', 'p99'];
+
+export const Component = () => {
+  const theme = useTheme();
+  const dispatch = useDispatch();
+  const { data } = useGetBenchmarksQuery();
+
+  const lineDataByRps = useSelector(selectMetricsSummaryLineData);
+  const interpolatedMetricData = useSelector(selectInterpolatedMetrics);
+  const runInfo = useSelector(selectRunInfo);
+
+  const { currentRequestRate } = useSelector(selectSloState);
+  const handleSliderChange = (event: Event, newValue: number | number[]) => {
+    dispatch(setCurrentRequestRate(newValue as number));
+  };
+
+  const {
+    ttft: ttftSLO,
+    tpot: tpotSLO,
+    timePerRequest: timePerRequestSLO,
+    throughput: throughputSLO,
+    percentile,
+    minX,
+    maxX,
+    errors,
+    handleTtft,
+    handleTpot,
+    handleTimePerRequest,
+    handleThroughput,
+    handlePercentileChange,
+    handleReset,
+  } = useSummary();
+
+  const isTtftMatch = Boolean(
+    ttftSLO && interpolatedMetricData.ttft.enforcedPercentileValue <= ttftSLO
+  );
+  const isTpotMatch = Boolean(
+    tpotSLO && interpolatedMetricData.tpot.enforcedPercentileValue <= tpotSLO
+  );
+  const isTprMatch = Boolean(
+    timePerRequestSLO &&
+      interpolatedMetricData.timePerRequest.enforcedPercentileValue <= timePerRequestSLO
+  );
+  const isThroughputMatch = Boolean(
+    throughputSLO &&
+      interpolatedMetricData.throughput.enforcedPercentileValue >= throughputSLO
+  );
+
+  const sliderMarks = [
+    {
+      value: minX,
+      label: minX,
+    },
+    {
+      value: maxX,
+      label: maxX,
+    },
+  ];
+
+  if ((data?.benchmarks?.length ?? 0) <= 1) {
+    return <></>;
+  }
+
+  return (
+    <>
+      <BlockHeader label="Metrics Summary" />
+      <MetricsSummaryContainer container>
+        <HeaderLeftCell item xs={9}>
+          <Box display="flex" flexDirection="row" justifyContent="space-between">
+            <Typography variant="h6" color="surface.onSurface" mb={2}>
+              Service Level Objectives for{' '}
+              <span style={{ color: theme.palette.surface.onSurfaceAccent }}>
+                [{runInfo?.task || 'N/A'}]
+              </span>
+              :
+            </Typography>
+
+            <Button onClick={handleReset}>
+              <Typography variant="button" color="surface.onSurfaceAccent" mb={2}>
+                RESET TO DEFAULT
+              </Typography>
+            </Button>
+          </Box>
+
+          <FieldsContainer data-id="fields-container">
+            <FieldCell data-id="field-cell-1">
+              <Input
+                label="TTFT (ms)"
+                value={ttftSLO}
+                onChange={handleTtft}
+                fullWidth
+                fontColor={LineColor.Primary}
+                error={errors?.ttft}
+              />
+            </FieldCell>
+            <FieldCell data-id="field-cell-2">
+              <Input
+                label="TPOT (ms)"
+                value={tpotSLO}
+                onChange={handleTpot}
+                fullWidth
+                fontColor={LineColor.Secondary}
+                error={errors?.tpot}
+              />
+            </FieldCell>
+            <FieldCell data-id="field-cell-3">
+              <Input
+                label="TIME PER REQUEST (Ms)"
+                value={timePerRequestSLO}
+                onChange={handleTimePerRequest}
+                fullWidth
+                fontColor={LineColor.Tertiary}
+                error={errors?.timePerRequest}
+              />
+            </FieldCell>
+            <FieldCell data-id="field-cell-4">
+              <Input
+                label="THROUGHPUT (tok/s)"
+                value={throughputSLO}
+                onChange={handleThroughput}
+                fullWidth
+                fontColor={LineColor.Quarternary}
+                error={errors?.throughput}
+              />
+            </FieldCell>
+          </FieldsContainer>
+        </HeaderLeftCell>
+        <HeaderRightCell item xs={3}>
+          <Typography variant="h6" color="surface.onSurface" mb={'23px'}>
+            Observed Values at:
+          </Typography>
+
+          <InputContainer>
+            <StyledFormControl
+              fullWidth
+              sx={{ fieldset: { legend: { maxWidth: '100%' } } }}
+            >
+              <StyledInputLabel shrink={true}>percentile</StyledInputLabel>
+              <StyledSelect
+                value={percentile}
+                onChange={handlePercentileChange}
+                autoWidth
+                placeholder="Select percentile"
+                IconComponent={Expand as ElementType}
+                displayEmpty
+                MenuProps={{
+                  PaperProps: {
+                    sx: {
+                      backgroundColor: theme.palette.surface.surfaceContainerLow,
+                      borderRadius: '8px',
+                    },
+                  },
+                }}
+              >
+                {percentileOptions.map((value) => (
+                  <OptionItem key={value} value={value} sx={{ minWidth: '168px' }}>
+                    {value}
+                  </OptionItem>
+                ))}
+              </StyledSelect>
+            </StyledFormControl>
+          </InputContainer>
+        </HeaderRightCell>
+        {/* graphs */}
+
+        <MiddleColumn sx={{ paddingLeft: '0px !important' }} item xs={9}>
+          <GraphContainer>
+            <MetricLine
+              data={[{ id: 'ttft', data: lineDataByRps.ttft || [] }]}
+              threshold={ttftSLO}
+              lineColor={LineColor.Primary}
+            />
+          </GraphContainer>
+        </MiddleColumn>
+        <MiddleColumn item xs={3}>
+          <MetricValue
+            label="TTFT"
+            value={`${formatNumber(interpolatedMetricData.ttft.enforcedPercentileValue)} ms`}
+            match={isTtftMatch}
+            valueColor={LineColor.Primary}
+          />
+        </MiddleColumn>
+
+        <MiddleColumn sx={{ paddingLeft: '0px !important' }} item xs={9}>
+          <GraphContainer>
+            <MetricLine
+              data={[{ id: 'tpot', data: lineDataByRps.tpot || [] }]}
+              threshold={tpotSLO}
+              lineColor={LineColor.Secondary}
+            />
+          </GraphContainer>
+        </MiddleColumn>
+        <MiddleColumn item xs={3}>
+          <MetricValue
+            label="TPOT"
+            value={`${formatNumber(interpolatedMetricData.tpot.enforcedPercentileValue)} ms`}
+            match={isTpotMatch}
+            valueColor={LineColor.Secondary}
+          />
+        </MiddleColumn>
+
+        <MiddleColumn sx={{ paddingLeft: '0px !important' }} item xs={9}>
+          <GraphContainer>
+            <MetricLine
+              data={[
+                { id: 'time per request', data: lineDataByRps.timePerRequest || [] },
+              ]}
+              threshold={timePerRequestSLO}
+              lineColor={LineColor.Tertiary}
+            />
+          </GraphContainer>
+        </MiddleColumn>
+        <MiddleColumn item xs={3}>
+          <MetricValue
+            label="time per request"
+            value={`${formatNumber(
+              interpolatedMetricData.timePerRequest.enforcedPercentileValue
+            )} ms`}
+            match={isTprMatch}
+            valueColor={LineColor.Tertiary}
+          />
+        </MiddleColumn>
+
+        <MiddleColumn sx={{ paddingLeft: '0px !important' }} item xs={9}>
+          <GraphContainer>
+            <MetricLine
+              data={[{ id: 'throughput', data: lineDataByRps.throughput || [] }]}
+              threshold={throughputSLO}
+              lineColor={LineColor.Quarternary}
+            />
+          </GraphContainer>
+        </MiddleColumn>
+        <MiddleColumn item xs={3}>
+          <MetricValue
+            value={`${formatNumber(
+              interpolatedMetricData.throughput.enforcedPercentileValue
+            )} tok/s`}
+            label="throughput"
+            match={isThroughputMatch}
+            valueColor={LineColor.Quarternary}
+          />
+        </MiddleColumn>
+
+        {/* slider */}
+        <FooterLeftCell item xs={9}>
+          <CustomSlider
+            size="medium"
+            step={0.01}
+            value={formatNumber(currentRequestRate, 2)}
+            min={minX}
+            max={maxX}
+            marks={sliderMarks}
+            valueLabelDisplay="on"
+            onChange={handleSliderChange}
+          />
+        </FooterLeftCell>
+        <FooterRightCell item xs={3}>
+          <Typography
+            variant="overline1"
+            color="surface.onSurface"
+            textTransform="uppercase"
+          >
+            Maximum RPS per gpu
+          </Typography>
+          <Typography variant="metric1" color="primary">
+            {formatNumber(currentRequestRate)} rps
+          </Typography>
+        </FooterRightCell>
+      </MetricsSummaryContainer>
+    </>
+  );
+};
diff --git a/src/ui/lib/components/MetricsSummary/MetricsSummary.styles.tsx b/src/ui/lib/components/MetricsSummary/MetricsSummary.styles.tsx
new file mode 100644
index 00000000..83b5ce1d
--- /dev/null
+++ b/src/ui/lib/components/MetricsSummary/MetricsSummary.styles.tsx
@@ -0,0 +1,213 @@
+import {
+  Box,
+  FormControl,
+  Grid,
+  InputLabel,
+  MenuItem,
+  Select,
+  Slider,
+  styled,
+} from '@mui/material';
+
+import { CustomSelectProps } from './MetricSummary.interfaces';
+
+export const MetricsSummaryContainer = styled(Grid)(({ theme }) => ({
+  borderWidth: '1px',
+  borderStyle: 'solid',
+  borderColor: theme.palette.outline.subdued,
+  borderRadius: '8px',
+  overflow: 'hidden',
+  backgroundColor: theme.palette.surface.surfaceContainerLow,
+}));
+
+export const MiddleColumn = styled(Grid)(({ theme }) => ({
+  borderBottomWidth: 1,
+  borderBottomColor: theme.palette.outline.subdued,
+  borderBottomStyle: 'solid',
+  padding: '16px',
+}));
+
+export const FieldsContainer = styled('div')({
+  display: 'flex',
+  justifyContent: 'space-between',
+  gap: '16px',
+});
+
+export const FieldCell = styled('div')({
+  flex: 1,
+  minWidth: 'calc((100% - 3 * 16px) / 4)',
+  boxSizing: 'border-box',
+});
+
+export const HeaderLeftCell = styled(Grid)(({ theme }) => ({
+  borderBottomWidth: 1,
+  borderBottomColor: theme.palette.outline.subdued,
+  borderBottomStyle: 'solid',
+  paddingTop: '16px',
+  paddingLeft: '16px',
+  paddingBottom: '16px',
+  paddingRight: '8px',
+}));
+
+export const HeaderRightCell = styled(Grid)(({ theme }) => ({
+  borderBottomWidth: 1,
+  borderBottomColor: theme.palette.outline.subdued,
+  borderBottomStyle: 'solid',
+  borderLeftWidth: 1,
+  borderLeftColor: theme.palette.outline.subdued,
+  borderLeftStyle: 'solid',
+  paddingTop: '16px',
+  paddingLeft: '8px',
+  paddingBottom: '16px',
+  paddingRight: '24px',
+}));
+
+export const FooterRightCell = styled(Grid)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerHigh,
+  display: 'flex',
+  flexDirection: 'column',
+  alignItems: 'flex-end',
+  padding: '16px',
+}));
+
+export const FooterLeftCell = styled(Grid)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerHigh,
+  paddingTop: '32px',
+  paddingBottom: '16px',
+  paddingLeft: '36px',
+  paddingRight: '26px',
+}));
+
+export const CustomSlider = styled(Slider)(({ theme }) => ({
+  '& .MuiSlider-valueLabel': {
+    backgroundColor: theme.palette.surface.surfaceContainer,
+    color: theme.palette.surface.onSurface,
+    opacity: 1,
+    fontSize: theme.typography.caption.fontSize,
+    fontFamily: theme.typography.caption.fontFamily,
+    fontWeight: theme.typography.caption.fontWeight,
+    lineHeight: theme.typography.caption.lineHeight,
+  },
+  '& .MuiSlider-markLabel': {
+    color: theme.palette.surface.onSurface,
+    opacity: 1,
+    fontSize: theme.typography.caption.fontSize,
+    fontFamily: theme.typography.caption.fontFamily,
+    fontWeight: theme.typography.caption.fontWeight,
+    lineHeight: theme.typography.caption.lineHeight,
+  },
+  '& .MuiSlider-thumb': {
+    position: 'relative',
+    '&::before, &::after': {
+      borderRadius: 0,
+      content: '""',
+      position: 'absolute',
+      top: '-510px',
+      left: '50%',
+      transform: 'translateX(-50%)',
+      width: '1px',
+      height: '470px',
+      display: 'block',
+      borderLeftColor: theme.palette.primary.main,
+      borderLeftWidth: '1px',
+      borderLeftStyle: 'dashed',
+    },
+    // '&::after': {
+    //   left: 'calc(50% - 1px)',
+    // },
+  },
+}));
+
+export const GraphContainer = styled('div')({
+  width: '100%',
+  height: '90px',
+  overflow: 'visible',
+});
+
+export const InputContainer = styled(Box)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerHigh,
+  padding: '8px 12px',
+  display: 'block',
+  borderRadius: '8px',
+  minWidth: '168px',
+  width: 'auto',
+  height: '50px',
+  overflow: 'hidden',
+  '&.disabled': { opacity: 0.4 },
+}));
+
+export const StyledSelect = styled(Select, {
+  shouldForwardProp: (propName) => propName !== 'placeholder',
+})<CustomSelectProps>(({ theme, placeholder }) => ({
+  '& .MuiSelect-select .notranslate::after': placeholder
+    ? {
+        content: `"${placeholder}"`,
+        opacity: 0.42,
+      }
+    : {},
+  '& .MuiOutlinedInput-root': {
+    '& fieldset': {
+      borderColor: 'transparent',
+    },
+    '&:hover fieldset': {
+      borderColor: 'transparent',
+    },
+    '&.Mui-focused fieldset': {
+      borderColor: 'transparent',
+    },
+  },
+  '& .MuiSelect-select': {
+    padding: '24px 14px',
+  },
+  '& .MuiInputLabel-root': {
+    color: theme.palette.text.secondary,
+    marginTop: '8px',
+    marginBottom: '8px',
+  },
+  '& .MuiInputLabel-root.Mui-focused': {
+    color: theme.palette.surface.onSurfaceSubdued,
+  },
+  '& .MuiInputLabel-root.MuiFormLabel-filled': {
+    color: theme.palette.surface.onSurfaceSubdued,
+  },
+  '& .MuiInputBase-input': {
+    color: theme.palette.surface.onSurfaceSubdued,
+  },
+}));
+
+export const StyledInputLabel = styled(InputLabel)(({ theme }) => ({
+  color: theme.palette.surface.onSurface,
+  textTransform: 'uppercase',
+  marginTop: '6px',
+  '&.MuiInputLabel-shrink': {
+    transform: 'translate(14px, 0px) scale(0.75)',
+    color: theme.palette.surface.onSurface,
+  },
+}));
+
+export const StyledFormControl = styled(FormControl)({
+  '& .MuiOutlinedInput-root': {
+    '& fieldset': {
+      borderColor: 'transparent',
+    },
+    '&:hover fieldset': {
+      borderColor: 'transparent',
+    },
+    '&.Mui-focused fieldset': {
+      borderColor: 'transparent',
+    },
+  },
+});
+
+export const OptionItem = styled(MenuItem)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerLow,
+  color: theme.palette.surface.onSurface,
+  '&:hover': {
+    backgroundColor: theme.palette.surface.surfaceContainerHigh,
+    color: theme.palette.surface.onSurface,
+  },
+  '&.Mui-selected': {
+    backgroundColor: theme.palette.surface.surfaceContainerHigh,
+    color: theme.palette.surface.onSurface,
+  },
+}));
diff --git a/src/ui/lib/components/MetricsSummary/components/MetricValue/MetricValue.component.tsx b/src/ui/lib/components/MetricsSummary/components/MetricValue/MetricValue.component.tsx
new file mode 100644
index 00000000..4d524858
--- /dev/null
+++ b/src/ui/lib/components/MetricsSummary/components/MetricValue/MetricValue.component.tsx
@@ -0,0 +1,37 @@
+import { Box, Typography } from '@mui/material';
+import { FC } from 'react';
+
+import { CheckCircle, WarningCircle } from '@assets/icons';
+
+import { useColor } from '@/lib/hooks/useColor';
+
+import { MetricValueProps } from './MetricValue.interfaces';
+
+export const Component: FC<MetricValueProps> = ({
+  label,
+  value,
+  match,
+  valueColor,
+}) => {
+  const selectedColor = useColor(valueColor);
+  return (
+    <Box display="flex" flexDirection="column" alignItems="flex-end" gap={1}>
+      <Box display="flex" flexDirection="row">
+        <Typography
+          variant="overline2"
+          color="surface.onSurface"
+          textTransform="uppercase"
+        >
+          {label}
+        </Typography>
+        <Typography variant="overline2" color="surface.onSurfaceSubdued" pl={0.5}>
+          (observed)
+        </Typography>
+      </Box>
+      <Typography variant="metric1" color={selectedColor}>
+        {value}
+      </Typography>
+      {match ? <CheckCircle /> : <WarningCircle />}
+    </Box>
+  );
+};
diff --git a/src/ui/lib/components/MetricsSummary/components/MetricValue/MetricValue.interfaces.ts b/src/ui/lib/components/MetricsSummary/components/MetricValue/MetricValue.interfaces.ts
new file mode 100644
index 00000000..fb3155e3
--- /dev/null
+++ b/src/ui/lib/components/MetricsSummary/components/MetricValue/MetricValue.interfaces.ts
@@ -0,0 +1,8 @@
+import { LineColor } from '@/lib/components/Charts/MetricLine';
+
+export interface MetricValueProps {
+  label: string;
+  value: string;
+  match: boolean;
+  valueColor: LineColor;
+}
diff --git a/src/ui/lib/components/MetricsSummary/components/MetricValue/index.tsx b/src/ui/lib/components/MetricsSummary/components/MetricValue/index.tsx
new file mode 100644
index 00000000..45775c65
--- /dev/null
+++ b/src/ui/lib/components/MetricsSummary/components/MetricValue/index.tsx
@@ -0,0 +1,2 @@
+export { Component as MetricValue } from './MetricValue.component';
+export type { MetricValueProps } from './MetricValue.interfaces';
diff --git a/src/ui/lib/components/MetricsSummary/index.tsx b/src/ui/lib/components/MetricsSummary/index.tsx
new file mode 100644
index 00000000..ef1a8e00
--- /dev/null
+++ b/src/ui/lib/components/MetricsSummary/index.tsx
@@ -0,0 +1 @@
+export { Component as MetricsSummary } from './MetricsSummary.component';
diff --git a/src/ui/lib/components/MetricsSummary/useSummary.ts b/src/ui/lib/components/MetricsSummary/useSummary.ts
new file mode 100644
index 00000000..e9445266
--- /dev/null
+++ b/src/ui/lib/components/MetricsSummary/useSummary.ts
@@ -0,0 +1,120 @@
+import { SelectChangeEvent } from '@mui/material';
+import { ChangeEvent, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { selectMetricsSummaryLineData } from '../../store/slices/benchmarks';
+import { selectSloState } from '../../store/slices/slo/slo.selectors';
+import { setEnforcedPercentile, setSloValue } from '../../store/slices/slo/slo.slice';
+import { ceil, floor } from '../../utils/helpers';
+import { Point } from '@/lib/components/Charts/common/interfaces';
+
+type Errors = { [key: string]: string | undefined };
+
+const initErrorsState: Errors = {
+  ttft: undefined,
+  tpot: undefined,
+  timePerRequest: undefined,
+  throughput: undefined,
+};
+
+const findMinMax = (lineData: Point[]) => {
+  let minX = Infinity;
+  let maxX = -Infinity;
+  let minY = Infinity;
+  let maxY = -Infinity;
+
+  for (let i = 0; i < lineData.length; i++) {
+    const { x, y } = lineData[i];
+    if (x < minX) {
+      minX = ceil(x, 2);
+    }
+    if (x > maxX) {
+      maxX = floor(x, 2);
+    }
+    if (y < minY) {
+      minY = ceil(y, 2);
+    }
+    if (y > maxY) {
+      maxY = floor(y, 2);
+    }
+  }
+
+  return { minX, maxX, minY, maxY };
+};
+
+export const useSummary = () => {
+  const dispatch = useDispatch();
+
+  const { current, enforcedPercentile, tasksDefaults } = useSelector(selectSloState);
+  const { ttft, tpot, timePerRequest, throughput } = useSelector(
+    selectMetricsSummaryLineData
+  );
+
+  const [errors, setErrors] = useState<Errors>(initErrorsState);
+
+  const ttftLimits = findMinMax(ttft || []);
+  const tpotLimits = findMinMax(tpot || []);
+  const timePerRequestLimits = findMinMax(timePerRequest || []);
+  const throughputLimits = findMinMax(throughput || []);
+
+  const limitsByMetric = {
+    ttft: ttftLimits,
+    tpot: tpotLimits,
+    timePerRequest: timePerRequestLimits,
+    throughput: throughputLimits,
+  };
+
+  const validateInput = (field: keyof typeof current, value?: number) => {
+    let error: string | undefined;
+    const limits = limitsByMetric[field];
+    if (value === undefined) {
+      error = 'Invalid value';
+    } else if (value > limits.maxY) {
+      error = 'Error: Larger than maximum';
+    } else if (value < limits.minY) {
+      error = 'Error: Smaller than minimum';
+    }
+    setErrors((prev) => ({ ...prev, [field]: error }));
+  };
+
+  const sanitizeInput = (value: string) => {
+    const sanitizedValue = value.replace(/\D/g, '');
+    return sanitizedValue !== '' ? Number(sanitizedValue) : undefined;
+  };
+
+  const handleChange =
+    (metric: keyof typeof current) => (event: ChangeEvent<HTMLInputElement>) => {
+      const newValue = sanitizeInput(event.target.value);
+      validateInput(metric, newValue);
+      if (newValue !== undefined) {
+        dispatch(setSloValue({ metric, value: newValue }));
+      }
+    };
+
+  const handlePercentileChange = (event: SelectChangeEvent<unknown>) => {
+    // TODO: need to validate slos on percentile change
+    const newValue = `${event.target.value}`;
+    dispatch(setEnforcedPercentile(newValue));
+  };
+
+  const handleReset = () => {
+    Object.entries(tasksDefaults).forEach(([metric, value]) => {
+      dispatch(setSloValue({ metric: metric as keyof typeof current, value }));
+    });
+    setErrors(initErrorsState);
+  };
+
+  return {
+    ...current,
+    percentile: enforcedPercentile,
+    minX: ttftLimits.minX,
+    maxX: ttftLimits.maxX,
+    errors,
+    handleTtft: handleChange('ttft'),
+    handleTpot: handleChange('tpot'),
+    handleTimePerRequest: handleChange('timePerRequest'),
+    handleThroughput: handleChange('throughput'),
+    handlePercentileChange,
+    handleReset,
+  };
+};
diff --git a/src/ui/lib/components/PageFooter/PageFooter.component.tsx b/src/ui/lib/components/PageFooter/PageFooter.component.tsx
index 1efb29f4..64082f9c 100644
--- a/src/ui/lib/components/PageFooter/PageFooter.component.tsx
+++ b/src/ui/lib/components/PageFooter/PageFooter.component.tsx
@@ -2,7 +2,7 @@
 import { Box, Link, Typography } from '@mui/material';
 import React from 'react';
 
-import { NeuralMagicTitleV2 } from '@assets/icons';
+import { guideLLMLogoLight } from '@assets/icons';
 
 export const Component = () => {
   return (
@@ -26,7 +26,7 @@ export const Component = () => {
         </Link>
       </Box>
       <Box>
-        <NeuralMagicTitleV2 />
+        <img width="150" alt="guidellm logo" src={guideLLMLogoLight.src} />
       </Box>
     </Box>
   );
diff --git a/src/ui/lib/components/PageHeader/PageHeader.component.tsx b/src/ui/lib/components/PageHeader/PageHeader.component.tsx
new file mode 100644
index 00000000..92fc9bc8
--- /dev/null
+++ b/src/ui/lib/components/PageHeader/PageHeader.component.tsx
@@ -0,0 +1,99 @@
+'use client';
+import { Box, Link, Typography, useTheme } from '@mui/material';
+import dynamic from 'next/dynamic';
+import NextLink from 'next/link';
+
+import { Open } from '@assets/icons';
+
+import { useGetRunInfoQuery } from '../../store/slices/runInfo';
+import { formateDate, getFileSize } from '../../utils/helpers';
+import { SvgContainer } from '../../utils/SvgContainer';
+
+import { SpecBadge } from '../SpecBadge';
+import { HeaderCell, HeaderWrapper } from './PageHeader.styles';
+
+const Component = () => {
+  const theme = useTheme();
+  const { data } = useGetRunInfoQuery();
+  const modelSize = getFileSize(data?.model?.size || 0);
+
+  return (
+    <Box py={2}>
+      <Typography variant="subtitle2" color="surface.onSurfaceAccent">
+        GuideLLM
+      </Typography>
+      <Typography variant="h4" color="surface.onSurface" my={'12px'}>
+        Workload Report
+      </Typography>
+      <HeaderWrapper container>
+        <HeaderCell item xs={5} withDivider sx={{ paddingLeft: 0 }}>
+          <SpecBadge
+            label="Model"
+            value={data?.model?.name || 'N/A'}
+            variant="metric2"
+            withTooltip
+          />
+          <SpecBadge
+            label="Model size"
+            value={data?.model?.size ? `${modelSize?.size} ${modelSize?.units}` : '0B'}
+            variant="body1"
+          />
+        </HeaderCell>
+        {/*<HeaderCell item xs={2} withDivider>*/}
+        {/*  <SpecBadge*/}
+        {/*    label="Hardware"*/}
+        {/*    value="A10"*/}
+        {/*    variant="metric2"*/}
+        {/*    additionalValue={*/}
+        {/*      <Chip*/}
+        {/*        label="x3"*/}
+        {/*        color="primary"*/}
+        {/*        variant="filled"*/}
+        {/*        size="small"*/}
+        {/*        sx={{ padding: '0 5px' }}*/}
+        {/*      />*/}
+        {/*    }*/}
+        {/*    key="Hardware"*/}
+        {/*  />*/}
+        {/*  <SpecBadge label="Interconnect" value="PCIE" variant="body1" key="Interconnect" />*/}
+        {/*  <SpecBadge*/}
+        {/*    label="Version"*/}
+        {/*    value={reportData.server.hardware_driver}*/}
+        {/*    variant="body1"*/}
+        {/*    key="Version"*/}
+        {/*  />*/}
+        {/*</HeaderCell>*/}
+        <HeaderCell item xs={2} withDivider>
+          <SpecBadge label="Task" value={data?.task || 'n/a'} variant="metric2" />
+        </HeaderCell>
+        <HeaderCell item xs={3} withDivider>
+          <SpecBadge
+            label="Dataset"
+            value={data?.dataset?.name || 'n/a'}
+            variant="metric2"
+            additionalValue={
+              <Link href="https://example.com" target="_blank" component={NextLink}>
+                <SvgContainer color={theme.palette.primary.main}>
+                  <Open />
+                </SvgContainer>
+              </Link>
+            }
+          />
+        </HeaderCell>
+        <HeaderCell item xs={2} sx={{ paddingRight: 0 }}>
+          <SpecBadge
+            label="Time Stamp"
+            value={data?.timestamp ? formateDate(data?.timestamp) : 'n/a'}
+            variant="caption"
+          />
+        </HeaderCell>
+      </HeaderWrapper>
+    </Box>
+  );
+};
+
+const DynamicComponent = dynamic(() => Promise.resolve(Component), {
+  ssr: false,
+});
+
+export { DynamicComponent as Component };
diff --git a/src/ui/lib/components/PageHeader/PageHeader.interfaces.ts b/src/ui/lib/components/PageHeader/PageHeader.interfaces.ts
new file mode 100644
index 00000000..af994cdc
--- /dev/null
+++ b/src/ui/lib/components/PageHeader/PageHeader.interfaces.ts
@@ -0,0 +1,3 @@
+export type HeaderCellProps = {
+  withDivider?: boolean;
+};
diff --git a/src/ui/lib/components/PageHeader/PageHeader.styles.tsx b/src/ui/lib/components/PageHeader/PageHeader.styles.tsx
new file mode 100644
index 00000000..a292becf
--- /dev/null
+++ b/src/ui/lib/components/PageHeader/PageHeader.styles.tsx
@@ -0,0 +1,19 @@
+import { Grid, styled } from '@mui/material';
+
+import { HeaderCellProps } from './PageHeader.interfaces';
+
+export const HeaderWrapper = styled(Grid)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerLow,
+  padding: '0 16px',
+  borderRadius: '8px',
+}));
+
+export const HeaderCell = styled(Grid, {
+  shouldForwardProp: (propName) => propName !== 'withDivider',
+})<HeaderCellProps>(({ theme, withDivider }) => ({
+  overflow: 'hidden',
+  padding: '8px',
+  ...(withDivider && {
+    borderRight: `1px solid ${theme.palette.outline.subdued}`,
+  }),
+}));
diff --git a/src/ui/lib/components/PageHeader/index.tsx b/src/ui/lib/components/PageHeader/index.tsx
new file mode 100644
index 00000000..86e81a23
--- /dev/null
+++ b/src/ui/lib/components/PageHeader/index.tsx
@@ -0,0 +1 @@
+export { Component as PageHeader } from './PageHeader.component';
diff --git a/src/ui/lib/components/RequestOverTime/RequestOverTime.component.tsx b/src/ui/lib/components/RequestOverTime/RequestOverTime.component.tsx
new file mode 100644
index 00000000..3d508960
--- /dev/null
+++ b/src/ui/lib/components/RequestOverTime/RequestOverTime.component.tsx
@@ -0,0 +1,62 @@
+import { Box, Grid, Typography } from '@mui/material';
+import { FC } from 'react';
+
+import { Badge } from '../../components/Badge';
+import { SpecBadge } from '../../components/SpecBadge';
+
+import { RequestOverTimeProps } from './RequestOverTime.interfaces';
+import { MiniCombined } from '../Charts/MiniCombined';
+import ContainerSizeWrapper, {
+  ContainerSize,
+} from '../Charts/MiniCombined/components/ContainerSizeWrapper';
+
+export const Component: FC<RequestOverTimeProps> = ({
+  benchmarksCount,
+  barData,
+  rateType,
+  lines = [],
+}) => (
+  <Box
+    display="flex"
+    flexDirection="column"
+    sx={{ width: '100%' }}
+    justifyItems="center"
+  >
+    <Grid container spacing={2}>
+      <Grid item xs={12}>
+        <SpecBadge
+          label="Number of Benchmarks"
+          value={benchmarksCount?.toString()}
+          variant="body2"
+        />
+      </Grid>
+      <Grid item>
+        <Typography
+          variant="overline2"
+          color="surface.onSurface"
+          textTransform="uppercase"
+        >
+          Rate Type
+        </Typography>
+      </Grid>
+      <Grid item>
+        <Badge label={rateType} />
+      </Grid>
+    </Grid>
+    <div style={{ width: '100%', height: '85px' }}>
+      <ContainerSizeWrapper>
+        {(containerSize: ContainerSize) => (
+          <MiniCombined
+            bars={barData}
+            lines={lines}
+            width={312}
+            height={85}
+            xLegend="time"
+            margins={{ bottom: 30 }}
+            containerSize={containerSize}
+          />
+        )}
+      </ContainerSizeWrapper>
+    </div>
+  </Box>
+);
diff --git a/src/ui/lib/components/RequestOverTime/RequestOverTime.interfaces.ts b/src/ui/lib/components/RequestOverTime/RequestOverTime.interfaces.ts
new file mode 100644
index 00000000..b40c7da2
--- /dev/null
+++ b/src/ui/lib/components/RequestOverTime/RequestOverTime.interfaces.ts
@@ -0,0 +1,9 @@
+export interface RequestOverTimeProps {
+  benchmarksCount: number;
+  barData: {
+    x: number;
+    y: number;
+  }[];
+  rateType: string;
+  lines?: { x: number; y: number; id: string }[];
+}
diff --git a/src/ui/lib/components/RequestOverTime/index.tsx b/src/ui/lib/components/RequestOverTime/index.tsx
new file mode 100644
index 00000000..cdb73593
--- /dev/null
+++ b/src/ui/lib/components/RequestOverTime/index.tsx
@@ -0,0 +1,2 @@
+export { Component as RequestOverTime } from './RequestOverTime.component';
+export type { RequestOverTimeProps } from './RequestOverTime.interfaces';
diff --git a/src/ui/lib/components/RowContainer/RowContainer.component.tsx b/src/ui/lib/components/RowContainer/RowContainer.component.tsx
new file mode 100644
index 00000000..31c5ac82
--- /dev/null
+++ b/src/ui/lib/components/RowContainer/RowContainer.component.tsx
@@ -0,0 +1,18 @@
+import { Grid, Typography } from '@mui/material';
+
+import { RowContainerProps } from './RowContainer.interfaces';
+
+export const Component = ({ label, children }: RowContainerProps) => {
+  return (
+    <Grid container alignItems="center">
+      <Grid item xs={2}>
+        <Typography variant="h6" color="surface.onSurface">
+          {label}
+        </Typography>
+      </Grid>
+      <Grid item xs={10}>
+        {children}
+      </Grid>
+    </Grid>
+  );
+};
diff --git a/src/ui/lib/components/RowContainer/RowContainer.interfaces.tsx b/src/ui/lib/components/RowContainer/RowContainer.interfaces.tsx
new file mode 100644
index 00000000..d25aab89
--- /dev/null
+++ b/src/ui/lib/components/RowContainer/RowContainer.interfaces.tsx
@@ -0,0 +1,6 @@
+import { ReactNode } from 'react';
+
+export interface RowContainerProps {
+  label: string;
+  children: ReactNode;
+}
diff --git a/src/ui/lib/components/RowContainer/index.tsx b/src/ui/lib/components/RowContainer/index.tsx
new file mode 100644
index 00000000..c9aa63de
--- /dev/null
+++ b/src/ui/lib/components/RowContainer/index.tsx
@@ -0,0 +1,2 @@
+export { Component as RowContainer } from './RowContainer.component';
+export type { RowContainerProps } from './RowContainer.interfaces';
diff --git a/src/ui/lib/components/Section/Section.component.tsx b/src/ui/lib/components/Section/Section.component.tsx
new file mode 100644
index 00000000..11969a7a
--- /dev/null
+++ b/src/ui/lib/components/Section/Section.component.tsx
@@ -0,0 +1,19 @@
+import { Grid, Typography } from '@mui/material';
+
+import { SectionProps } from './Section.interfaces';
+import { ValueCell } from './Section.styles';
+
+export const Component = ({ label, value }: SectionProps) => {
+  return (
+    <Grid container display="flex" flexDirection="row" m="6px" sx={{ width: 'auto' }}>
+      <Grid item alignContent="center">
+        <Typography variant="subtitle2" color="surface.onSurfaceSubdued">
+          {label}
+        </Typography>
+      </Grid>
+      <ValueCell alignContent="center" item>
+        {value}
+      </ValueCell>
+    </Grid>
+  );
+};
diff --git a/src/ui/lib/components/Section/Section.interfaces.ts b/src/ui/lib/components/Section/Section.interfaces.ts
new file mode 100644
index 00000000..792bfe67
--- /dev/null
+++ b/src/ui/lib/components/Section/Section.interfaces.ts
@@ -0,0 +1,4 @@
+export interface SectionProps {
+  label: string;
+  value: string;
+}
diff --git a/src/ui/lib/components/Section/Section.styles.tsx b/src/ui/lib/components/Section/Section.styles.tsx
new file mode 100644
index 00000000..4ae5d1f2
--- /dev/null
+++ b/src/ui/lib/components/Section/Section.styles.tsx
@@ -0,0 +1,10 @@
+import { Grid, styled } from '@mui/material';
+
+export const ValueCell = styled(Grid)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerHigh,
+  color: theme.palette.surface.onSurface,
+  borderRadius: '6px',
+  padding: '6px',
+  gap: 10,
+  marginLeft: '12px',
+}));
diff --git a/src/ui/lib/components/Section/index.tsx b/src/ui/lib/components/Section/index.tsx
new file mode 100644
index 00000000..8182a403
--- /dev/null
+++ b/src/ui/lib/components/Section/index.tsx
@@ -0,0 +1,2 @@
+export { Component as Section } from './Section.component';
+export type { SectionProps } from './Section.interfaces';
diff --git a/src/ui/lib/components/SectionContainer/SectionContainer.component.tsx b/src/ui/lib/components/SectionContainer/SectionContainer.component.tsx
new file mode 100644
index 00000000..cec8a89e
--- /dev/null
+++ b/src/ui/lib/components/SectionContainer/SectionContainer.component.tsx
@@ -0,0 +1,74 @@
+'use client';
+import { Grid, useTheme } from '@mui/material';
+import { MutableRefObject, useEffect, useRef, useState } from 'react';
+
+import { ArrowDown, ArrowUp } from '@assets/icons';
+
+import { SvgContainer } from '../../utils/SvgContainer';
+
+import { SectionContainerProps } from './SectionContainer.interfaces';
+import { Container, RoundedButton } from './SectionContainer.styles';
+
+export const Component = ({ children }: SectionContainerProps) => {
+  const theme = useTheme();
+  const [expanded, setExpanded] = useState(false);
+  const [showButton, setShowButton] = useState(false);
+  const containerRef: MutableRefObject<HTMLDivElement | null> = useRef(null);
+
+  useEffect(() => {
+    const checkOverflow = () => {
+      const container = containerRef.current;
+      if (container) {
+        const hasOverflow = container.scrollHeight > container.clientHeight;
+        setShowButton(hasOverflow);
+      }
+    };
+
+    checkOverflow();
+    window.addEventListener('resize', checkOverflow);
+
+    return () => {
+      window.removeEventListener('resize', checkOverflow);
+    };
+  }, []);
+
+  return (
+    <Container display="flex">
+      <Grid
+        flexGrow={1}
+        flexWrap="wrap"
+        direction="row"
+        display="flex"
+        ref={containerRef}
+        sx={{ maxHeight: expanded ? 'none' : '42px', overflow: 'hidden' }}
+      >
+        {children}
+      </Grid>
+      <Grid
+        sx={{ width: '72px' }}
+        justifyContent="flex-end"
+        alignItems="flex-start"
+        display="flex"
+      >
+        {showButton && (
+          <RoundedButton
+            onClick={() => setExpanded(!expanded)}
+            sx={{ marginTop: '4px' }}
+          >
+            <SvgContainer color={theme.palette.primary.onContainer}>
+              {expanded ? (
+                <SvgContainer color={theme.palette.primary.onContainer}>
+                  <ArrowDown />
+                </SvgContainer>
+              ) : (
+                <SvgContainer color={theme.palette.primary.onContainer}>
+                  <ArrowUp />
+                </SvgContainer>
+              )}
+            </SvgContainer>
+          </RoundedButton>
+        )}
+      </Grid>
+    </Container>
+  );
+};
diff --git a/src/ui/lib/components/SectionContainer/SectionContainer.interfaces.ts b/src/ui/lib/components/SectionContainer/SectionContainer.interfaces.ts
new file mode 100644
index 00000000..629b06b6
--- /dev/null
+++ b/src/ui/lib/components/SectionContainer/SectionContainer.interfaces.ts
@@ -0,0 +1,5 @@
+import { ReactElement } from 'react';
+
+export interface SectionContainerProps {
+  children: ReactElement;
+}
diff --git a/src/ui/lib/components/SectionContainer/SectionContainer.styles.tsx b/src/ui/lib/components/SectionContainer/SectionContainer.styles.tsx
new file mode 100644
index 00000000..9e4e3695
--- /dev/null
+++ b/src/ui/lib/components/SectionContainer/SectionContainer.styles.tsx
@@ -0,0 +1,21 @@
+import { Grid, styled } from '@mui/material';
+
+export const Container = styled(Grid)(({ theme }) => ({
+  backgroundColor: theme.palette.surface.surfaceContainerLow,
+  borderRadius: '6px',
+  padding: '12px',
+  width: 'auto',
+  margin: '8px',
+}));
+
+export const RoundedButton = styled('div')(({ theme }) => ({
+  backgroundColor: theme.palette.primary.shades.B80,
+  width: '32px',
+  height: '32px',
+  borderRadius: '16px',
+  justifyContent: 'center',
+  alignItems: 'center',
+  display: 'flex',
+  cursor: 'pointer',
+  userSelect: 'none',
+}));
diff --git a/src/ui/lib/components/SectionContainer/index.tsx b/src/ui/lib/components/SectionContainer/index.tsx
new file mode 100644
index 00000000..1b857fe8
--- /dev/null
+++ b/src/ui/lib/components/SectionContainer/index.tsx
@@ -0,0 +1,2 @@
+export { Component as SectionContainer } from './SectionContainer.component';
+export type { SectionContainerProps } from './SectionContainer.interfaces';
diff --git a/src/ui/lib/components/SpecBadge/SpecBadge.component.tsx b/src/ui/lib/components/SpecBadge/SpecBadge.component.tsx
new file mode 100644
index 00000000..f3c63c8d
--- /dev/null
+++ b/src/ui/lib/components/SpecBadge/SpecBadge.component.tsx
@@ -0,0 +1,34 @@
+import { Box, Typography, Tooltip } from '@mui/material';
+import { FC } from 'react';
+
+import { SpecBadgeProps } from './SpecBadge.interfaces';
+import { Container, EllipsisTypography, ValueWrapper } from './SpecBadge.styles';
+
+export const Component: FC<SpecBadgeProps> = ({
+  label,
+  value,
+  variant,
+  additionalValue,
+  withTooltip = false,
+}) => {
+  const tooltipContent = (
+    <EllipsisTypography variant={variant} color="primary">
+      {value}
+    </EllipsisTypography>
+  );
+  return (
+    <Container>
+      <Typography variant="overline2" color="surface.onSurface">
+        {label}
+      </Typography>
+      <ValueWrapper>
+        {withTooltip ? (
+          <Tooltip title={value}>{tooltipContent}</Tooltip>
+        ) : (
+          tooltipContent
+        )}
+        {additionalValue && <Box ml={1}>{additionalValue}</Box>}
+      </ValueWrapper>
+    </Container>
+  );
+};
diff --git a/src/ui/lib/components/SpecBadge/SpecBadge.interfaces.ts b/src/ui/lib/components/SpecBadge/SpecBadge.interfaces.ts
new file mode 100644
index 00000000..5bc778b3
--- /dev/null
+++ b/src/ui/lib/components/SpecBadge/SpecBadge.interfaces.ts
@@ -0,0 +1,12 @@
+import { Variant } from '@mui/material/styles/createTypography';
+import { TypographyPropsVariantOverrides } from '@mui/material/Typography/Typography';
+import { OverridableStringUnion } from '@mui/types';
+import { ReactNode } from 'react';
+
+export interface SpecBadgeProps {
+  label: string;
+  value: string;
+  variant: OverridableStringUnion<Variant | 'inherit', TypographyPropsVariantOverrides>;
+  additionalValue?: ReactNode;
+  withTooltip?: boolean;
+}
diff --git a/src/ui/lib/components/SpecBadge/SpecBadge.styles.tsx b/src/ui/lib/components/SpecBadge/SpecBadge.styles.tsx
new file mode 100644
index 00000000..3057d7f2
--- /dev/null
+++ b/src/ui/lib/components/SpecBadge/SpecBadge.styles.tsx
@@ -0,0 +1,21 @@
+import { Box, styled, Typography } from '@mui/material';
+
+export const EllipsisTypography = styled(Typography)({
+  whiteSpace: 'nowrap',
+  overflow: 'hidden',
+  textOverflow: 'ellipsis',
+});
+
+export const Container = styled(Box)({
+  display: 'flex',
+  flexDirection: 'column',
+  alignItems: 'flex-start',
+  marginBottom: '8px',
+});
+
+export const ValueWrapper = styled(Box)({
+  display: 'flex',
+  alignItems: 'center',
+  maxWidth: '100%',
+  width: 'auto',
+});
diff --git a/src/ui/lib/components/SpecBadge/index.tsx b/src/ui/lib/components/SpecBadge/index.tsx
new file mode 100644
index 00000000..54052cc2
--- /dev/null
+++ b/src/ui/lib/components/SpecBadge/index.tsx
@@ -0,0 +1,2 @@
+export { Component as SpecBadge } from './SpecBadge.component';
+export type { SpecBadgeProps } from './SpecBadge.interfaces';
diff --git a/src/ui/lib/components/TokenLength/TokenLength.component.tsx b/src/ui/lib/components/TokenLength/TokenLength.component.tsx
new file mode 100644
index 00000000..1b8f6681
--- /dev/null
+++ b/src/ui/lib/components/TokenLength/TokenLength.component.tsx
@@ -0,0 +1,47 @@
+import { Box, Typography } from '@mui/material';
+import { FC } from 'react';
+
+import { MiniCombined } from '../../components/Charts/MiniCombined';
+import ContainerSizeWrapper, {
+  ContainerSize,
+} from '../../components/Charts/MiniCombined/components/ContainerSizeWrapper';
+
+import { TokenLengthProps } from './TokenLength.interfaces';
+
+export const Component: FC<TokenLengthProps> = ({ label, tokenCount, bars, lines }) => (
+  <Box
+    display="flex"
+    flexDirection="column"
+    sx={{ width: '100%' }}
+    justifyItems="center"
+    alignItems="space-between"
+  >
+    <Box display="flex" flexDirection="column">
+      <Box>
+        <Typography variant="overline2" color="surface.onSurface">
+          {label}
+        </Typography>
+      </Box>
+      <Box mt="8px" mb="16px">
+        <Typography variant="metric1" color="primary">
+          {`${tokenCount} token${tokenCount !== 1 ? 's' : ''}`}
+        </Typography>
+      </Box>
+    </Box>
+    <div style={{ width: '100%', height: '85px' }}>
+      <ContainerSizeWrapper>
+        {(containerSize: ContainerSize) => (
+          <MiniCombined
+            bars={bars}
+            lines={lines}
+            width={312}
+            height={85}
+            xLegend="length (tokens)"
+            margins={{ bottom: 30 }}
+            containerSize={containerSize}
+          />
+        )}
+      </ContainerSizeWrapper>
+    </div>
+  </Box>
+);
diff --git a/src/ui/lib/components/TokenLength/TokenLength.interfaces.ts b/src/ui/lib/components/TokenLength/TokenLength.interfaces.ts
new file mode 100644
index 00000000..d8e9dd15
--- /dev/null
+++ b/src/ui/lib/components/TokenLength/TokenLength.interfaces.ts
@@ -0,0 +1,6 @@
+export interface TokenLengthProps {
+  label: string;
+  tokenCount: number;
+  bars: { x: number; y: number }[];
+  lines: { x: number; y: number; id: string }[];
+}
diff --git a/src/ui/lib/components/TokenLength/index.tsx b/src/ui/lib/components/TokenLength/index.tsx
new file mode 100644
index 00000000..e0eba389
--- /dev/null
+++ b/src/ui/lib/components/TokenLength/index.tsx
@@ -0,0 +1,2 @@
+export { Component as TokenLength } from './TokenLength.component';
+export type { TokenLengthProps } from './TokenLength.interfaces';
diff --git a/src/ui/lib/components/WorkloadDetails/WorkloadDetails.component.tsx b/src/ui/lib/components/WorkloadDetails/WorkloadDetails.component.tsx
new file mode 100644
index 00000000..3ed90c8e
--- /dev/null
+++ b/src/ui/lib/components/WorkloadDetails/WorkloadDetails.component.tsx
@@ -0,0 +1,117 @@
+'use client';
+import { Box, Grid } from '@mui/material';
+import dynamic from 'next/dynamic';
+import { useSelector } from 'react-redux';
+
+import {
+  selectGenerationsHistogramBarData,
+  selectGenerationsHistogramLineData,
+  selectPromptsHistogramBarData,
+  selectPromptsHistogramLineData,
+  selectRequestOverTimeBarData,
+  useGetWorkloadDetailsQuery,
+} from '../../store/slices/workloadDetails';
+
+import { formatNumber, parseUrlParts } from '../../utils/helpers';
+
+import { BlockHeader } from '../BlockHeader';
+import { Carousel } from '../Carousel';
+import { DataPanel } from '../DataPanel';
+import { RequestOverTime } from '../RequestOverTime';
+import { SpecBadge } from '../SpecBadge';
+
+import { TokenLength } from '../TokenLength';
+
+const Component = () => {
+  const { data } = useGetWorkloadDetailsQuery();
+  const promptsBarData = useSelector(selectPromptsHistogramBarData);
+  const promptsLineData = useSelector(selectPromptsHistogramLineData);
+  const generationsBarData = useSelector(selectGenerationsHistogramBarData);
+  const generationsLineData = useSelector(selectGenerationsHistogramLineData);
+  const { barChartData: requestOverTimeBarData } = useSelector(
+    selectRequestOverTimeBarData
+  );
+  const { type, target, port } = parseUrlParts(data?.server?.target || '');
+
+  return (
+    <>
+      <BlockHeader label="Workload Details" />
+      <Grid
+        container
+        spacing={0}
+        gap={2}
+        data-id="workload-details"
+        sx={{ marginLeft: 0, flexWrap: 'nowrap' }}
+        justifyContent="space-between"
+      >
+        <DataPanel
+          header="Prompt"
+          topContainer={
+            <Carousel items={data?.prompts?.samples || []} label="Sample Prompt" />
+          }
+          bottomContainer={
+            <TokenLength
+              label={'Mean Prompt Length'}
+              tokenCount={formatNumber(
+                data?.prompts?.tokenDistributions.statistics.mean ?? 0
+              )}
+              bars={promptsBarData || []}
+              lines={promptsLineData}
+            />
+          }
+          key="dp-1"
+        />
+        <DataPanel
+          header="Server"
+          topContainer={
+            <Box display="flex" flexDirection="column" sx={{ width: '100%' }}>
+              <SpecBadge label="Target" value={target} variant="body2" />
+              <Grid container spacing={2}>
+                <Grid item xs={6}>
+                  <SpecBadge label="Type" value={type} variant="body2" />
+                </Grid>
+                <Grid item xs={6}>
+                  <SpecBadge label="Port" value={port} variant="body2" />
+                </Grid>
+              </Grid>
+            </Box>
+          }
+          bottomContainer={
+            <RequestOverTime
+              benchmarksCount={data?.requestsOverTime?.numBenchmarks || 0}
+              barData={requestOverTimeBarData || []}
+              rateType={data?.rateType ?? ''}
+            />
+          }
+          key="dp-2"
+        />
+        <DataPanel
+          header="Generated"
+          topContainer={
+            <Carousel
+              items={data?.generations?.samples || []}
+              label="Sample Generated"
+            />
+          }
+          bottomContainer={
+            <TokenLength
+              label={'Mean Generated Length'}
+              tokenCount={formatNumber(
+                data?.generations?.tokenDistributions.statistics.mean ?? 0
+              )}
+              bars={generationsBarData || []}
+              lines={generationsLineData}
+            />
+          }
+          key="dp-3"
+        />
+      </Grid>
+    </>
+  );
+};
+
+const DynamicComponent = dynamic(() => Promise.resolve(Component), {
+  ssr: false,
+});
+
+export { DynamicComponent as Component };
diff --git a/src/ui/lib/components/WorkloadDetails/index.tsx b/src/ui/lib/components/WorkloadDetails/index.tsx
new file mode 100644
index 00000000..702f16ad
--- /dev/null
+++ b/src/ui/lib/components/WorkloadDetails/index.tsx
@@ -0,0 +1 @@
+export { Component as WorkloadDetails } from './WorkloadDetails.component';
diff --git a/src/ui/lib/components/WorkloadMetrics/WorkloadMetricSummary.interfaces.tsx b/src/ui/lib/components/WorkloadMetrics/WorkloadMetricSummary.interfaces.tsx
new file mode 100644
index 00000000..e69de29b
diff --git a/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.component.tsx b/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.component.tsx
new file mode 100644
index 00000000..8162dffa
--- /dev/null
+++ b/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.component.tsx
@@ -0,0 +1,152 @@
+'use client';
+import { Box } from '@mui/material';
+import { useSelector } from 'react-redux';
+
+import { DashedLine } from '../../components/Charts/DashedLine';
+import {
+  DistributionPercentiles,
+  PercentileItem,
+} from '../../components/DistributionPercentiles';
+import { MeanMetricSummary } from '../../components/MeanMetricSummary';
+import {
+  selectInterpolatedMetrics,
+  selectMetricsDetailsLineData,
+  useGetBenchmarksQuery,
+} from '../../store/slices/benchmarks';
+
+import { selectSloState } from '../../store/slices/slo/slo.selectors';
+
+import { formatNumber } from '../../utils/helpers';
+
+import { BlockHeader } from '../BlockHeader';
+import { GraphTitle } from '../GraphTitle';
+import { MetricsContainer } from '../MetricsContainer';
+import { GraphsWrapper } from './WorkloadMetrics.styles';
+
+export const columnContent = (
+  rpsValue: number,
+  percentiles: PercentileItem[],
+  units: string
+) => <DistributionPercentiles list={percentiles} rpsValue={rpsValue} units={units} />;
+
+export const leftColumn = (rpsValue: number, value: number, units: string) => (
+  <MeanMetricSummary meanValue={`${value}`} meanUnit={units} rpsValue={rpsValue} />
+);
+
+export const leftColumn3 = (rpsValue: number, value: number, units: string) => (
+  <MeanMetricSummary meanValue={`${value}`} meanUnit={units} rpsValue={rpsValue} />
+);
+
+export const Component = () => {
+  const { data } = useGetBenchmarksQuery();
+  const { ttft, tpot, timePerRequest, throughput } = useSelector(
+    selectMetricsDetailsLineData
+  );
+  const { currentRequestRate } = useSelector(selectSloState);
+  const formattedRequestRate = formatNumber(currentRequestRate);
+  const {
+    ttft: ttftAtRPS,
+    tpot: tpotAtRPS,
+    timePerRequest: timePerRequestAtRPS,
+    throughput: throughputAtRPS,
+  } = useSelector(selectInterpolatedMetrics);
+
+  const minX = Math.floor(
+    Math.min(...(data?.benchmarks?.map((bm) => bm.requestsPerSecond) || []))
+  );
+  if ((data?.benchmarks?.length ?? 0) <= 1) {
+    return <></>;
+  }
+  return (
+    <>
+      <BlockHeader label="Metrics Details" />
+      <Box display="flex" flexDirection="row" gap={3} mt={3}>
+        <MetricsContainer
+          header="TTFT"
+          leftColumn={leftColumn(
+            formattedRequestRate,
+            formatNumber(ttftAtRPS.mean),
+            'ms'
+          )}
+          rightColumn={columnContent(formattedRequestRate, ttftAtRPS.percentiles, 'ms')}
+        >
+          <GraphTitle title="TTFS vs RPS" />
+          <GraphsWrapper>
+            <DashedLine
+              data={ttft}
+              margins={{ left: 50, bottom: 50 }}
+              xLegend="request per sec"
+              yLegend="ttft (ms)"
+              minX={minX}
+            />
+          </GraphsWrapper>
+        </MetricsContainer>
+        <MetricsContainer
+          header="TPOT"
+          leftColumn={leftColumn3(
+            formattedRequestRate,
+            formatNumber(tpotAtRPS.mean),
+            'ms'
+          )}
+          rightColumn={columnContent(formattedRequestRate, tpotAtRPS.percentiles, 'ms')}
+        >
+          <GraphTitle title="TPOT vs RPS" />
+          <GraphsWrapper>
+            <DashedLine
+              data={tpot}
+              margins={{ left: 50, bottom: 50 }}
+              xLegend="request per sec"
+              yLegend="tpot (ms)"
+              minX={minX}
+            />
+          </GraphsWrapper>
+        </MetricsContainer>
+      </Box>
+      <Box display="flex" flexDirection="row" gap={3} mt={3}>
+        <MetricsContainer
+          header="E2E Latency"
+          leftColumn={leftColumn(
+            formattedRequestRate,
+            formatNumber(timePerRequestAtRPS.mean),
+            'ms'
+          )}
+          rightColumn={columnContent(
+            formattedRequestRate,
+            timePerRequestAtRPS.percentiles,
+            'ms'
+          )}
+        >
+          <GraphTitle title="E2E Latency vs RPS" />
+          <GraphsWrapper>
+            <DashedLine
+              data={timePerRequest}
+              margins={{ left: 50, bottom: 50 }}
+              xLegend="request per sec"
+              yLegend="latency (ms)"
+              minX={minX}
+            />
+          </GraphsWrapper>
+        </MetricsContainer>
+        <MetricsContainer
+          header="Throughput"
+          leftColumn={leftColumn3(
+            formattedRequestRate,
+            formatNumber(throughputAtRPS.mean),
+            'ms'
+          )}
+        >
+          <GraphTitle title="Throughput vs RPS" />
+          <GraphsWrapper>
+            <DashedLine
+              data={throughput}
+              margins={{ left: 50, bottom: 50 }}
+              xLegend="request per sec"
+              yLegend="throughput (tok/s)"
+              minX={minX}
+            />
+          </GraphsWrapper>
+        </MetricsContainer>
+      </Box>
+    </>
+  );
+};
diff --git a/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.styles.tsx b/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.styles.tsx
new file mode 100644
index 00000000..2e59c0f5
--- /dev/null
+++ b/src/ui/lib/components/WorkloadMetrics/WorkloadMetrics.styles.tsx
@@ -0,0 +1,6 @@
+import { styled } from '@mui/material';
+
+export const GraphsWrapper = styled('div')({
+  width: '540px',
+  height: '288px',
+});
diff --git a/src/ui/lib/components/WorkloadMetrics/index.tsx b/src/ui/lib/components/WorkloadMetrics/index.tsx
new file mode 100644
index 00000000..611578ba
--- /dev/null
+++ b/src/ui/lib/components/WorkloadMetrics/index.tsx
@@ -0,0 +1 @@
+export { Component as WorkloadMetrics } from './WorkloadMetrics.component';
diff --git a/src/ui/lib/hooks/useColor.ts b/src/ui/lib/hooks/useColor.ts
new file mode 100644
index 00000000..bc519bad
--- /dev/null
+++ b/src/ui/lib/hooks/useColor.ts
@@ -0,0 +1,19 @@
+import { useTheme } from '@mui/material';
+
+import { LineColor } from '@/lib/components/Charts/MetricLine';
+
+export const useColor = (colorType: LineColor | undefined) => {
+  const theme = useTheme();
+  switch (colorType) {
+    case LineColor.Primary:
+      return theme.palette.primary.main;
+    case LineColor.Secondary:
+      return theme.palette.secondary.main;
+    case LineColor.Tertiary:
+      return theme.palette.tertiary.main as string;
+    case LineColor.Quarternary:
+      return theme.palette.quarternary.main as string;
+    default:
+      return theme.palette.surface.onSurfaceAccent;
+  }
+};
diff --git a/src/ui/lib/store/benchmarksWindowData.ts b/src/ui/lib/store/benchmarksWindowData.ts
new file mode 100644
index 00000000..e8a5cc40
--- /dev/null
+++ b/src/ui/lib/store/benchmarksWindowData.ts
@@ -0,0 +1,1154 @@
+export const benchmarksScript = `window.benchmarks = {
+  "benchmarks": [
+    {
+      "requestsPerSecond": 0.6668550387660497,
+      "tpot": {
+        "statistics": {
+          "total": 80,
+          "mean": 23.00635663936911,
+          "median": 22.959455611213805,
+          "min": 22.880917503720237,
+          "max": 24.14080301920573,
+          "std": 0.18918760384209338
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 22.959455611213805
+          },
+          {
+            "percentile": "p90",
+            "value": 23.01789086962503
+          },
+          {
+            "percentile": "p95",
+            "value": 23.30297423947242
+          },
+          {
+            "percentile": "p99",
+            "value": 24.14080301920573
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 80,
+          "mean": 49.64659512042999,
+          "median": 49.23129081726074,
+          "min": 44.538259506225586,
+          "max": 55.47308921813965,
+          "std": 1.7735485090634995
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 49.23129081726074
+          },
+          {
+            "percentile": "p90",
+            "value": 50.16160011291504
+          },
+          {
+            "percentile": "p95",
+            "value": 54.918766021728516
+          },
+          {
+            "percentile": "p99",
+            "value": 55.47308921813965
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 210,
+          "mean": 42.58702991319684,
+          "median": 43.536023084668,
+          "min": 0.0,
+          "max": 43.68247620237872,
+          "std": 4.559764488536857
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 43.536023084668
+          },
+          {
+            "percentile": "p90",
+            "value": 43.62613633999709
+          },
+          {
+            "percentile": "p95",
+            "value": 43.64020767654067
+          },
+          {
+            "percentile": "p99",
+            "value": 43.68202126662431
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 80,
+          "mean": 1496.706646680832,
+          "median": 1496.1087703704834,
+          "min": 1490.584135055542,
+          "max": 1505.8784484863281,
+          "std": 3.4553340533022667
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 1496.1087703704834
+          },
+          {
+            "percentile": "p90",
+            "value": 1500.9305477142334
+          },
+          {
+            "percentile": "p95",
+            "value": 1505.3200721740723
+          },
+          {
+            "percentile": "p99",
+            "value": 1505.8784484863281
+          }
+        ]
+      }
+    },
+    {
+      "requestsPerSecond": 28.075330129628725,
+      "tpot": {
+        "statistics": {
+          "total": 3416,
+          "mean": 126.08707076148656,
+          "median": 125.30853256346687,
+          "min": 23.034303907364134,
+          "max": 138.08223756693178,
+          "std": 3.508992115582193
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 125.30853256346687
+          },
+          {
+            "percentile": "p90",
+            "value": 129.21135009281218
+          },
+          {
+            "percentile": "p95",
+            "value": 129.52291770059554
+          },
+          {
+            "percentile": "p99",
+            "value": 132.21229490686636
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 3416,
+          "mean": 8585.486161415694,
+          "median": 8965.316534042358,
+          "min": 110.53991317749023,
+          "max": 12575.379610061646,
+          "std": 1929.5632525234505
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 8965.316534042358
+          },
+          {
+            "percentile": "p90",
+            "value": 9231.79316520691
+          },
+          {
+            "percentile": "p95",
+            "value": 9485.00108718872
+          },
+          {
+            "percentile": "p99",
+            "value": 12096.465587615967
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 15981,
+          "mean": 1795.4403743554367,
+          "median": 670.1236619268253,
+          "min": 0.0,
+          "max": 838860.8,
+          "std": 5196.545581836957
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 670.1236619268253
+          },
+          {
+            "percentile": "p90",
+            "value": 4068.1901066925316
+          },
+          {
+            "percentile": "p95",
+            "value": 6374.322188449848
+          },
+          {
+            "percentile": "p99",
+            "value": 16194.223938223939
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 3416,
+          "mean": 16526.811318389147,
+          "median": 17058.441638946533,
+          "min": 1711.3444805145264,
+          "max": 20646.55351638794,
+          "std": 2054.9553770234484
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 17058.441638946533
+          },
+          {
+            "percentile": "p90",
+            "value": 17143.84412765503
+          },
+          {
+            "percentile": "p95",
+            "value": 17248.060703277588
+          },
+          {
+            "percentile": "p99",
+            "value": 20116.52660369873
+          }
+        ]
+      }
+    },
+    {
+      "requestsPerSecond": 4.071681142252993,
+      "tpot": {
+        "statistics": {
+          "total": 488,
+          "mean": 24.898151556004148,
+          "median": 24.889995181371294,
+          "min": 24.822999560643755,
+          "max": 26.217273871103924,
+          "std": 0.11227504505081555
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 24.889995181371294
+          },
+          {
+            "percentile": "p90",
+            "value": 24.90483389960395
+          },
+          {
+            "percentile": "p95",
+            "value": 24.965975019666885
+          },
+          {
+            "percentile": "p99",
+            "value": 25.306613214554325
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 488,
+          "mean": 58.341102033364976,
+          "median": 58.38632583618164,
+          "min": 44.857025146484375,
+          "max": 111.23061180114746,
+          "std": 8.190008649880411
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 58.38632583618164
+          },
+          {
+            "percentile": "p90",
+            "value": 67.66843795776367
+          },
+          {
+            "percentile": "p95",
+            "value": 68.76754760742188
+          },
+          {
+            "percentile": "p99",
+            "value": 71.46525382995605
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 11338,
+          "mean": 260.42072092623033,
+          "median": 47.630070406540995,
+          "min": 0.0,
+          "max": 838860.8,
+          "std": 886.8274389295076
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 47.630070406540995
+          },
+          {
+            "percentile": "p90",
+            "value": 604.8895298528987
+          },
+          {
+            "percentile": "p95",
+            "value": 1621.9273008507348
+          },
+          {
+            "percentile": "p99",
+            "value": 3054.846321922797
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 488,
+          "mean": 1626.5668087318297,
+          "median": 1626.236915588379,
+          "min": 1611.9341850280762,
+          "max": 1690.2406215667725,
+          "std": 8.871477705542668
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 1626.236915588379
+          },
+          {
+            "percentile": "p90",
+            "value": 1635.761022567749
+          },
+          {
+            "percentile": "p95",
+            "value": 1637.390375137329
+          },
+          {
+            "percentile": "p99",
+            "value": 1643.500804901123
+          }
+        ]
+      }
+    },
+    {
+      "requestsPerSecond": 7.466101414346809,
+      "tpot": {
+        "statistics": {
+          "total": 895,
+          "mean": 27.56459906601014,
+          "median": 27.525402250744047,
+          "min": 26.69054911686824,
+          "max": 29.5785041082473,
+          "std": 0.18545649185329754
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 27.525402250744047
+          },
+          {
+            "percentile": "p90",
+            "value": 27.62497795952691
+          },
+          {
+            "percentile": "p95",
+            "value": 27.947206345815506
+          },
+          {
+            "percentile": "p99",
+            "value": 28.41202157442687
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 895,
+          "mean": 64.73036744741088,
+          "median": 62.484025955200195,
+          "min": 48.038482666015625,
+          "max": 256.4809322357178,
+          "std": 21.677914089867077
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 62.484025955200195
+          },
+          {
+            "percentile": "p90",
+            "value": 72.04723358154297
+          },
+          {
+            "percentile": "p95",
+            "value": 72.50738143920898
+          },
+          {
+            "percentile": "p99",
+            "value": 229.35032844543457
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 12465,
+          "mean": 477.5134940335642,
+          "median": 49.76925541382379,
+          "min": 0.0,
+          "max": 1677721.6,
+          "std": 2472.852317203968
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 49.76925541382379
+          },
+          {
+            "percentile": "p90",
+            "value": 1191.5636363636363
+          },
+          {
+            "percentile": "p95",
+            "value": 2501.075730471079
+          },
+          {
+            "percentile": "p99",
+            "value": 7025.634840871022
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 895,
+          "mean": 1800.9132816804852,
+          "median": 1797.5835800170898,
+          "min": 1756.2305927276611,
+          "max": 1994.28129196167,
+          "std": 24.24935353039552
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 1797.5835800170898
+          },
+          {
+            "percentile": "p90",
+            "value": 1808.2549571990967
+          },
+          {
+            "percentile": "p95",
+            "value": 1813.141107559204
+          },
+          {
+            "percentile": "p99",
+            "value": 1967.8056240081787
+          }
+        ]
+      }
+    },
+    {
+      "requestsPerSecond": 10.83989165148388,
+      "tpot": {
+        "statistics": {
+          "total": 1300,
+          "mean": 31.6048062981453,
+          "median": 31.577579558841766,
+          "min": 30.171105355927438,
+          "max": 33.10690323511759,
+          "std": 0.15146862300990216
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 31.577579558841766
+          },
+          {
+            "percentile": "p90",
+            "value": 31.63230986822219
+          },
+          {
+            "percentile": "p95",
+            "value": 31.682415614052424
+          },
+          {
+            "percentile": "p99",
+            "value": 32.138043834317116
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 1300,
+          "mean": 66.61205951984113,
+          "median": 65.78803062438965,
+          "min": 51.81550979614258,
+          "max": 244.69709396362305,
+          "std": 14.858653160342651
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 65.78803062438965
+          },
+          {
+            "percentile": "p90",
+            "value": 76.70044898986816
+          },
+          {
+            "percentile": "p95",
+            "value": 77.78120040893555
+          },
+          {
+            "percentile": "p99",
+            "value": 88.29903602600098
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 12708,
+          "mean": 693.3695002980695,
+          "median": 55.59272071785492,
+          "min": 0.0,
+          "max": 838860.8,
+          "std": 2454.288991845712
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 55.59272071785492
+          },
+          {
+            "percentile": "p90",
+            "value": 1897.875113122172
+          },
+          {
+            "percentile": "p95",
+            "value": 2931.030048916841
+          },
+          {
+            "percentile": "p99",
+            "value": 7108.989830508474
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 1300,
+          "mean": 2057.3723330864545,
+          "median": 2056.5311908721924,
+          "min": 2027.0307064056396,
+          "max": 2233.853578567505,
+          "std": 16.334707021033957
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 2056.5311908721924
+          },
+          {
+            "percentile": "p90",
+            "value": 2065.953254699707
+          },
+          {
+            "percentile": "p95",
+            "value": 2067.810297012329
+          },
+          {
+            "percentile": "p99",
+            "value": 2087.8031253814697
+          }
+        ]
+      }
+    },
+    {
+      "requestsPerSecond": 14.211845819540324,
+      "tpot": {
+        "statistics": {
+          "total": 1704,
+          "mean": 35.695500394825224,
+          "median": 35.60370869106717,
+          "min": 34.798149078611345,
+          "max": 38.94662857055664,
+          "std": 0.24967658675392423
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 35.60370869106717
+          },
+          {
+            "percentile": "p90",
+            "value": 35.84100708128914
+          },
+          {
+            "percentile": "p95",
+            "value": 36.09923778041716
+          },
+          {
+            "percentile": "p99",
+            "value": 36.71476489207784
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 1704,
+          "mean": 74.19940031750102,
+          "median": 71.50626182556152,
+          "min": 53.643226623535156,
+          "max": 322.6609230041504,
+          "std": 23.98415146629138
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 71.50626182556152
+          },
+          {
+            "percentile": "p90",
+            "value": 83.71734619140625
+          },
+          {
+            "percentile": "p95",
+            "value": 98.2356071472168
+          },
+          {
+            "percentile": "p99",
+            "value": 113.44718933105469
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 15532,
+          "mean": 908.715763654939,
+          "median": 98.84067397195712,
+          "min": 0.0,
+          "max": 838860.8,
+          "std": 3628.67537220603
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 98.84067397195712
+          },
+          {
+            "percentile": "p90",
+            "value": 2205.2071503680336
+          },
+          {
+            "percentile": "p95",
+            "value": 3775.251125112511
+          },
+          {
+            "percentile": "p99",
+            "value": 10512.040100250626
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 1704,
+          "mean": 2321.92987861208,
+          "median": 2313.3785724639893,
+          "min": 2290.93074798584,
+          "max": 2594.4881439208984,
+          "std": 29.46118583560937
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 2313.3785724639893
+          },
+          {
+            "percentile": "p90",
+            "value": 2339.4439220428467
+          },
+          {
+            "percentile": "p95",
+            "value": 2341.9249057769775
+          },
+          {
+            "percentile": "p99",
+            "value": 2370.450496673584
+          }
+        ]
+      }
+    },
+    {
+      "requestsPerSecond": 17.5623040970073,
+      "tpot": {
+        "statistics": {
+          "total": 2106,
+          "mean": 39.546438065771135,
+          "median": 39.47442675393725,
+          "min": 38.74176740646362,
+          "max": 43.32651032341851,
+          "std": 0.3121106751660994
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 39.47442675393725
+          },
+          {
+            "percentile": "p90",
+            "value": 39.722594003828746
+          },
+          {
+            "percentile": "p95",
+            "value": 40.083578654697966
+          },
+          {
+            "percentile": "p99",
+            "value": 40.73049983040231
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 2106,
+          "mean": 85.68002797259905,
+          "median": 89.88213539123535,
+          "min": 57.360172271728516,
+          "max": 362.8504276275635,
+          "std": 27.802786177158218
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 89.88213539123535
+          },
+          {
+            "percentile": "p90",
+            "value": 101.7305850982666
+          },
+          {
+            "percentile": "p95",
+            "value": 103.26790809631348
+          },
+          {
+            "percentile": "p99",
+            "value": 138.88931274414062
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 15121,
+          "mean": 1123.0284569989917,
+          "median": 99.91909855397003,
+          "min": 0.0,
+          "max": 932067.5555555555,
+          "std": 4358.833642800455
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 99.91909855397003
+          },
+          {
+            "percentile": "p90",
+            "value": 2868.8809849521203
+          },
+          {
+            "percentile": "p95",
+            "value": 4848.906358381503
+          },
+          {
+            "percentile": "p99",
+            "value": 12905.55076923077
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 2106,
+          "mean": 2575.916517267653,
+          "median": 2573.6281871795654,
+          "min": 2533.904790878296,
+          "max": 2894.4458961486816,
+          "std": 33.18594265783404
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 2573.6281871795654
+          },
+          {
+            "percentile": "p90",
+            "value": 2588.9015197753906
+          },
+          {
+            "percentile": "p95",
+            "value": 2591.136932373047
+          },
+          {
+            "percentile": "p99",
+            "value": 2700.568437576294
+          }
+        ]
+      }
+    },
+    {
+      "requestsPerSecond": 20.885632360055222,
+      "tpot": {
+        "statistics": {
+          "total": 2505,
+          "mean": 44.20494748431818,
+          "median": 44.02147020612444,
+          "min": 42.981475591659546,
+          "max": 52.62617986710345,
+          "std": 1.0422073399474652
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 44.02147020612444
+          },
+          {
+            "percentile": "p90",
+            "value": 44.47330747331892
+          },
+          {
+            "percentile": "p95",
+            "value": 45.131300316482296
+          },
+          {
+            "percentile": "p99",
+            "value": 50.400745301019576
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 2505,
+          "mean": 98.4621736103903,
+          "median": 95.84355354309082,
+          "min": 61.09285354614258,
+          "max": 524.099588394165,
+          "std": 34.20521833421915
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 95.84355354309082
+          },
+          {
+            "percentile": "p90",
+            "value": 109.4822883605957
+          },
+          {
+            "percentile": "p95",
+            "value": 111.46354675292969
+          },
+          {
+            "percentile": "p99",
+            "value": 334.31243896484375
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 14779,
+          "mean": 1335.7133120200747,
+          "median": 104.45284522475407,
+          "min": 0.0,
+          "max": 1677721.6,
+          "std": 5200.1934248077005
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 104.45284522475407
+          },
+          {
+            "percentile": "p90",
+            "value": 3472.1059602649007
+          },
+          {
+            "percentile": "p95",
+            "value": 5882.6143057503505
+          },
+          {
+            "percentile": "p99",
+            "value": 15768.060150375939
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 2505,
+          "mean": 2882.6246785070603,
+          "median": 2869.71378326416,
+          "min": 2826.8485069274902,
+          "max": 3324.9876499176025,
+          "std": 78.07038363701177
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 2869.71378326416
+          },
+          {
+            "percentile": "p90",
+            "value": 2888.715982437134
+          },
+          {
+            "percentile": "p95",
+            "value": 2937.7262592315674
+          },
+          {
+            "percentile": "p99",
+            "value": 3282.898426055908
+          }
+        ]
+      }
+    },
+    {
+      "requestsPerSecond": 24.179871480414207,
+      "tpot": {
+        "statistics": {
+          "total": 2900,
+          "mean": 51.023722283946924,
+          "median": 50.24327550615583,
+          "min": 47.58137645143451,
+          "max": 60.63385087935651,
+          "std": 2.0749227872708285
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 50.24327550615583
+          },
+          {
+            "percentile": "p90",
+            "value": 52.928451507810564
+          },
+          {
+            "percentile": "p95",
+            "value": 57.28437408568367
+          },
+          {
+            "percentile": "p99",
+            "value": 58.51330454387362
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 2900,
+          "mean": 123.56691516678907,
+          "median": 115.33927917480469,
+          "min": 88.05131912231445,
+          "max": 594.1901206970215,
+          "std": 44.50765227271787
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 115.33927917480469
+          },
+          {
+            "percentile": "p90",
+            "value": 141.8297290802002
+          },
+          {
+            "percentile": "p95",
+            "value": 144.49095726013184
+          },
+          {
+            "percentile": "p99",
+            "value": 375.5221366882324
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 14925,
+          "mean": 1546.3194569459229,
+          "median": 138.59511614843208,
+          "min": 0.0,
+          "max": 1677721.6,
+          "std": 5844.302138842639
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 138.59511614843208
+          },
+          {
+            "percentile": "p90",
+            "value": 3916.250233426704
+          },
+          {
+            "percentile": "p95",
+            "value": 6678.828025477707
+          },
+          {
+            "percentile": "p99",
+            "value": 17924.37606837607
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 2900,
+          "mean": 3336.9750574539444,
+          "median": 3282.672882080078,
+          "min": 3228.010654449463,
+          "max": 3863.8863563537598,
+          "std": 141.37106520368962
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 3282.672882080078
+          },
+          {
+            "percentile": "p90",
+            "value": 3561.7692470550537
+          },
+          {
+            "percentile": "p95",
+            "value": 3737.921953201294
+          },
+          {
+            "percentile": "p99",
+            "value": 3811.5434646606445
+          }
+        ]
+      }
+    },
+    {
+      "requestsPerSecond": 27.382251189847466,
+      "tpot": {
+        "statistics": {
+          "total": 3285,
+          "mean": 62.44881585866599,
+          "median": 60.908238093058266,
+          "min": 58.94644298250713,
+          "max": 72.59870383699061,
+          "std": 2.9764436606898887
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 60.908238093058266
+          },
+          {
+            "percentile": "p90",
+            "value": 68.3861043718126
+          },
+          {
+            "percentile": "p95",
+            "value": 69.21934324597555
+          },
+          {
+            "percentile": "p99",
+            "value": 70.13290269034249
+          }
+        ]
+      },
+      "ttft": {
+        "statistics": {
+          "total": 3285,
+          "mean": 142.7834399758953,
+          "median": 129.18686866760254,
+          "min": 92.2248363494873,
+          "max": 802.5562763214111,
+          "std": 54.896961282893
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 129.18686866760254
+          },
+          {
+            "percentile": "p90",
+            "value": 158.26964378356934
+          },
+          {
+            "percentile": "p95",
+            "value": 166.79859161376953
+          },
+          {
+            "percentile": "p99",
+            "value": 422.8503704071045
+          }
+        ]
+      },
+      "throughput": {
+        "statistics": {
+          "total": 15706,
+          "mean": 1751.1720673421933,
+          "median": 318.5950626661603,
+          "min": 0.0,
+          "max": 1677721.6,
+          "std": 6434.120608249914
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 318.5950626661603
+          },
+          {
+            "percentile": "p90",
+            "value": 4165.147964250248
+          },
+          {
+            "percentile": "p95",
+            "value": 7194.346483704974
+          },
+          {
+            "percentile": "p99",
+            "value": 19878.218009478675
+          }
+        ]
+      },
+      "timePerRequest": {
+        "statistics": {
+          "total": 3285,
+          "mean": 4076.002237894764,
+          "median": 3972.564697265625,
+          "min": 3890.990972518921,
+          "max": 4623.138666152954,
+          "std": 197.81266460135544
+        },
+        "percentiles": [
+          {
+            "percentile": "p50",
+            "value": 3972.564697265625
+          },
+          {
+            "percentile": "p90",
+            "value": 4444.445371627808
+          },
+          {
+            "percentile": "p95",
+            "value": 4506.659030914307
+          },
+          {
+            "percentile": "p99",
+            "value": 4553.745985031128
+          }
+        ]
+      }
+    }
+  ]
+};`;
diff --git a/src/ui/lib/store/index.ts b/src/ui/lib/store/index.ts
index c1671d75..39ec0aa4 100644
--- a/src/ui/lib/store/index.ts
+++ b/src/ui/lib/store/index.ts
@@ -1,21 +1,28 @@
-import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';
+import { configureStore } from '@reduxjs/toolkit';
 
-const defaultSlice = createSlice({
-  name: 'model.state',
-  initialState: { model: 'meta-llama/Llama-2-7b' },
-  reducers: {
-    setDefaultData: (state, action: PayloadAction<string>) => {
-      return { ...state, model: action.payload };
-    },
-  },
-});
-
-export const { setDefaultData } = defaultSlice.actions;
+import { benchmarksApi, benchmarksReducer } from './slices/benchmarks';
+import metricsReducer from './slices/metrics/metrics.slice';
+import { runInfoApi, infoReducer } from './slices/runInfo';
+import sloReducer from './slices/slo/slo.slice';
+import { workloadDetailsApi, workloadDetailsReducer } from './slices/workloadDetails';
 
 export const store = configureStore({
   reducer: {
-    default: defaultSlice.reducer,
+    metrics: metricsReducer,
+    slo: sloReducer,
+    runInfo: infoReducer,
+    [runInfoApi.reducerPath]: runInfoApi.reducer,
+    benchmarks: benchmarksReducer,
+    [benchmarksApi.reducerPath]: benchmarksApi.reducer,
+    workloadDetails: workloadDetailsReducer,
+    [workloadDetailsApi.reducerPath]: workloadDetailsApi.reducer,
   },
+  middleware: (getDefaultMiddleware) =>
+    getDefaultMiddleware().concat(
+      runInfoApi.middleware,
+      benchmarksApi.middleware,
+      workloadDetailsApi.middleware
+    ),
 });
 
 export type RootState = ReturnType<typeof store.getState>;
diff --git a/src/ui/lib/store/mockData.ts b/src/ui/lib/store/mockData.ts
new file mode 100644
index 00000000..8295c60c
--- /dev/null
+++ b/src/ui/lib/store/mockData.ts
@@ -0,0 +1,191 @@
+export const runInfo = {
+  model: {
+    name: 'Model name',
+    size: 12345678,
+  },
+  task: 'Task name',
+  dataset: {
+    name: 'Dataset name',
+  },
+  timestamp: '1686700800',
+};
+
+export const workloadDetails = {
+  prompts: {
+    samples: ['string'],
+    tokenDistributions: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 0,
+        },
+      ],
+      buckets: [
+        {
+          value: 0,
+          count: 0,
+        },
+      ],
+      bucketWidth: 0,
+    },
+  },
+  generations: {
+    samples: ['string'],
+    tokenDistributions: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 0,
+        },
+      ],
+      buckets: [
+        {
+          value: 0,
+          count: 0,
+        },
+      ],
+      bucketWidth: 0,
+    },
+  },
+  server: {
+    target: '128.0.0.1',
+    protocolType: 'string',
+    port: '8000',
+  },
+};
+
+export const benchmarks = [
+  {
+    ttft: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 0,
+        },
+      ],
+      buckets: [
+        {
+          value: 0,
+          count: 0,
+        },
+      ],
+      bucketWidth: 0,
+    },
+    tpot: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 0,
+        },
+      ],
+      buckets: [
+        {
+          value: 0,
+          count: 0,
+        },
+      ],
+      bucketWidth: 0,
+    },
+    timePerRequest: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 0,
+        },
+      ],
+      buckets: [
+        {
+          value: 0,
+          count: 0,
+        },
+      ],
+      bucketWidth: 0,
+    },
+    requestOverTime: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 0,
+        },
+      ],
+      buckets: [
+        {
+          value: 0,
+          count: 0,
+        },
+      ],
+      bucketWidth: 0,
+    },
+    throughput: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 0,
+        },
+      ],
+      buckets: [
+        {
+          value: 0,
+          count: 0,
+        },
+      ],
+      bucketWidth: 0,
+    },
+  },
+];
diff --git a/src/ui/lib/store/runInfoWindowData.ts b/src/ui/lib/store/runInfoWindowData.ts
new file mode 100644
index 00000000..1fe0e21d
--- /dev/null
+++ b/src/ui/lib/store/runInfoWindowData.ts
@@ -0,0 +1,11 @@
+export const runInfoScript = `window.run_info = {
+  "model": {
+    "name": "neuralmagic/Qwen2.5-7B-quantized.w8a8",
+    "size": 0
+  },
+  "task": "N/A",
+  "dataset": {
+    "name": "N/A"
+  },
+  "timestamp": 1744310555.0286171
+};`;
diff --git a/src/ui/lib/store/slices/benchmarks/benchmarks.api.ts b/src/ui/lib/store/slices/benchmarks/benchmarks.api.ts
new file mode 100644
index 00000000..f1a7b52e
--- /dev/null
+++ b/src/ui/lib/store/slices/benchmarks/benchmarks.api.ts
@@ -0,0 +1,97 @@
+import { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit';
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+
+import { formatNumber } from '../../../utils/helpers';
+
+import { Benchmarks, MetricData } from './benchmarks.interfaces';
+import { defaultPercentile } from '../slo/slo.constants';
+import { SloState } from '../slo/slo.interfaces';
+import { setSloData } from '../slo/slo.slice';
+
+const USE_MOCK_API = process.env.NEXT_PUBLIC_USE_MOCK_API === 'true';
+
+const fetchBenchmarks = async () => {
+  return { data: window.benchmarks as Benchmarks };
+};
+
+const getAverageValueForPercentile = (
+  firstMetric: MetricData,
+  lastMetric: MetricData,
+  percentile: string
+) => {
+  const firstPercentile = firstMetric.percentiles.find(
+    (p) => p.percentile === percentile
+  );
+  const lastPercentile = lastMetric.percentiles.find(
+    (p) => p.percentile === percentile
+  );
+  return ((firstPercentile?.value ?? 0) + (lastPercentile?.value ?? 0)) / 2;
+};
+
+const setDefaultSLOs = (
+  data: Benchmarks,
+  dispatch: ThunkDispatch<SloState, unknown, UnknownAction>
+) => {
+  // temporarily set default slo values, long term the backend should set default slos that will not just be the avg at the default percentile
+  const firstBM = data.benchmarks[0];
+  const lastBM = data.benchmarks[data.benchmarks.length - 1];
+
+  const ttftAvg = getAverageValueForPercentile(
+    firstBM.ttft,
+    lastBM.ttft,
+    defaultPercentile
+  );
+  const tpotAvg = getAverageValueForPercentile(
+    firstBM.tpot,
+    lastBM.tpot,
+    defaultPercentile
+  );
+  const timePerRequestAvg = getAverageValueForPercentile(
+    firstBM.timePerRequest,
+    lastBM.timePerRequest,
+    defaultPercentile
+  );
+  const throughputAvg = getAverageValueForPercentile(
+    firstBM.throughput,
+    lastBM.throughput,
+    defaultPercentile
+  );
+
+  dispatch(
+    setSloData({
+      currentRequestRate: firstBM.requestsPerSecond,
+      current: {
+        ttft: formatNumber(ttftAvg, 0),
+        tpot: formatNumber(tpotAvg, 0),
+        timePerRequest: formatNumber(timePerRequestAvg, 0),
+        throughput: formatNumber(throughputAvg, 0),
+      },
+      tasksDefaults: {
+        ttft: formatNumber(ttftAvg, 0),
+        tpot: formatNumber(tpotAvg, 0),
+        timePerRequest: formatNumber(timePerRequestAvg, 0),
+        throughput: formatNumber(throughputAvg, 0),
+      },
+    })
+  );
+};
+
+export const benchmarksApi = createApi({
+  reducerPath: 'benchmarksApi',
+  baseQuery: USE_MOCK_API ? fetchBenchmarks : fetchBaseQuery({ baseUrl: '/api' }),
+  endpoints: (builder) => ({
+    getBenchmarks: builder.query<Benchmarks, void>({
+      query: () => 'benchmarks',
+      async onQueryStarted(_, { dispatch, queryFulfilled }) {
+        try {
+          const { data } = await queryFulfilled;
+          setDefaultSLOs(data, dispatch);
+        } catch (err) {
+          console.error('Failed to fetch benchmarks:', err);
+        }
+      },
+    }),
+  }),
+});
+
+export const { useGetBenchmarksQuery } = benchmarksApi;
diff --git a/src/ui/lib/store/slices/benchmarks/benchmarks.constants.ts b/src/ui/lib/store/slices/benchmarks/benchmarks.constants.ts
new file mode 100644
index 00000000..deb444b2
--- /dev/null
+++ b/src/ui/lib/store/slices/benchmarks/benchmarks.constants.ts
@@ -0,0 +1,7 @@
+import { Benchmarks, Name } from './benchmarks.interfaces';
+
+export const name: Readonly<Name> = 'benchmarks';
+
+export const initialState: Benchmarks = {
+  benchmarks: [],
+};
diff --git a/src/ui/lib/store/slices/benchmarks/benchmarks.interfaces.ts b/src/ui/lib/store/slices/benchmarks/benchmarks.interfaces.ts
new file mode 100644
index 00000000..4dc755b2
--- /dev/null
+++ b/src/ui/lib/store/slices/benchmarks/benchmarks.interfaces.ts
@@ -0,0 +1,44 @@
+export type Name = 'benchmarks';
+
+interface Statistics {
+  total: number;
+  mean: number;
+  std: number;
+  median: number;
+  min: number;
+  max: number;
+}
+
+export type PercentileValues = 'p50' | 'p90' | 'p95' | 'p99';
+
+interface Percentile {
+  percentile: string;
+  value: number;
+}
+
+interface Bucket {
+  value: number;
+  count: number;
+}
+
+export interface MetricData {
+  statistics: Statistics;
+  percentiles: Percentile[];
+  buckets: Bucket[];
+  bucketWidth: number;
+}
+
+export interface BenchmarkMetrics {
+  ttft: MetricData;
+  tpot: MetricData;
+  timePerRequest: MetricData;
+  throughput: MetricData;
+}
+
+export interface Benchmark extends BenchmarkMetrics {
+  requestsPerSecond: number;
+}
+
+export type Benchmarks = {
+  benchmarks: Benchmark[];
+};
diff --git a/src/ui/lib/store/slices/benchmarks/benchmarks.selectors.ts b/src/ui/lib/store/slices/benchmarks/benchmarks.selectors.ts
new file mode 100644
index 00000000..4a86d422
--- /dev/null
+++ b/src/ui/lib/store/slices/benchmarks/benchmarks.selectors.ts
@@ -0,0 +1,173 @@
+import { createSelector } from '@reduxjs/toolkit';
+
+import { PercentileItem } from '../../../components/DistributionPercentiles';
+import { formatNumber } from '../../../utils/helpers';
+import { createMonotoneSpline } from '../../../utils/interpolation';
+import { RootState } from '../../index';
+import { Point } from '@/lib/components/Charts/common/interfaces';
+
+import { BenchmarkMetrics, PercentileValues } from './benchmarks.interfaces';
+import { selectSloState } from '../slo/slo.selectors';
+
+export const selectBenchmarks = (state: RootState) => state.benchmarks.data;
+
+export const selectMetricsSummaryLineData = createSelector(
+  [selectBenchmarks, selectSloState],
+  (benchmarks, sloState) => {
+    const sortedByRPS = benchmarks?.benchmarks
+      ?.slice()
+      ?.sort((bm1, bm2) => (bm1.requestsPerSecond > bm2.requestsPerSecond ? 1 : -1));
+    const selectedPercentile = sloState.enforcedPercentile;
+
+    const lineData: { [K in keyof BenchmarkMetrics]: Point[] } = {
+      ttft: [],
+      tpot: [],
+      timePerRequest: [],
+      throughput: [],
+    };
+    const metrics: (keyof BenchmarkMetrics)[] = [
+      'ttft',
+      'tpot',
+      'timePerRequest',
+      'throughput',
+    ];
+    metrics.forEach((metric) => {
+      const data: Point[] = [];
+      sortedByRPS?.forEach((benchmark) => {
+        const percentile = benchmark[metric].percentiles.find(
+          (p) => p.percentile === selectedPercentile
+        );
+        data.push({
+          x: benchmark.requestsPerSecond,
+          y: percentile?.value ?? 0,
+        });
+      });
+
+      lineData[metric] = data;
+    });
+    return lineData;
+  }
+);
+
+const getDefaultMetricValues = () => ({
+  enforcedPercentileValue: 0,
+  mean: 0,
+  percentiles: [],
+});
+
+export const selectInterpolatedMetrics = createSelector(
+  [selectBenchmarks, selectSloState],
+  (benchmarks, sloState) => {
+    const sortedByRPS = benchmarks?.benchmarks
+      ?.slice()
+      ?.sort((bm1, bm2) => (bm1.requestsPerSecond > bm2.requestsPerSecond ? 1 : -1));
+    const requestRates = sortedByRPS?.map((bm) => bm.requestsPerSecond) || [];
+    const { enforcedPercentile, currentRequestRate } = sloState;
+    const metricData: {
+      [K in keyof BenchmarkMetrics | 'mean']: {
+        enforcedPercentileValue: number;
+        mean: number;
+        percentiles: PercentileItem[];
+      };
+    } = {
+      ttft: getDefaultMetricValues(),
+      tpot: getDefaultMetricValues(),
+      timePerRequest: getDefaultMetricValues(),
+      throughput: getDefaultMetricValues(),
+      mean: getDefaultMetricValues(),
+    };
+    const metrics: (keyof BenchmarkMetrics)[] = [
+      'ttft',
+      'tpot',
+      'timePerRequest',
+      'throughput',
+    ];
+    if (!sortedByRPS || sortedByRPS.length === 0) {
+      return metricData;
+    }
+    const invalidRps =
+      currentRequestRate < sortedByRPS[0].requestsPerSecond ||
+      currentRequestRate > sortedByRPS[sortedByRPS.length - 1].requestsPerSecond;
+    if (invalidRps) {
+      return metricData;
+    }
+    metrics.forEach((metric) => {
+      const meanValues = sortedByRPS.map((bm) => bm[metric].statistics.mean);
+      const interpolateMeanAt = createMonotoneSpline(requestRates, meanValues);
+      const interpolatedMeanValue: number = interpolateMeanAt(currentRequestRate) || 0;
+      const percentiles: PercentileValues[] = ['p50', 'p90', 'p95', 'p99'];
+      const valuesByPercentile = percentiles.map((p) => {
+        const bmValuesAtP = sortedByRPS.map((bm) => {
+          const result =
+            bm[metric].percentiles.find((percentile) => percentile.percentile === p)
+              ?.value || 0;
+          return result;
+        });
+        const interpolateValueAtP = createMonotoneSpline(requestRates, bmValuesAtP);
+        const interpolatedValueAtP = formatNumber(
+          interpolateValueAtP(currentRequestRate)
+        );
+        return { label: p, value: `${interpolatedValueAtP}` } as PercentileItem;
+      });
+      const interpolatedPercentileValue =
+        Number(valuesByPercentile.find((p) => p.label === enforcedPercentile)?.value) ||
+        0;
+      metricData[metric] = {
+        enforcedPercentileValue: interpolatedPercentileValue,
+        mean: interpolatedMeanValue,
+        percentiles: valuesByPercentile,
+      };
+    });
+    return metricData;
+  }
+);
+
+export const selectMetricsDetailsLineData = createSelector(
+  [selectBenchmarks],
+  (benchmarks) => {
+    const sortedByRPS =
+      benchmarks?.benchmarks
+        ?.slice()
+        ?.sort((bm1, bm2) =>
+          bm1.requestsPerSecond > bm2.requestsPerSecond ? 1 : -1
+        ) || [];
+
+    const lineData: {
+      [K in keyof BenchmarkMetrics]: { data: Point[]; id: string; solid?: boolean }[];
+    } = {
+      ttft: [],
+      tpot: [],
+      timePerRequest: [],
+      throughput: [],
+    };
+    const props: (keyof BenchmarkMetrics)[] = [
+      'ttft',
+      'tpot',
+      'timePerRequest',
+      'throughput',
+    ];
+    props.forEach((prop) => {
+      if (sortedByRPS.length === 0) {
+        return;
+      }
+      const data: { [key: string]: { data: Point[]; id: string; solid?: boolean } } =
+        {};
+      sortedByRPS[0].ttft.percentiles.forEach((p) => {
+        data[p.percentile] = { data: [], id: p.percentile };
+      });
+      data.mean = { data: [], id: 'mean', solid: true };
+      sortedByRPS?.forEach((benchmark) => {
+        const rps = benchmark.requestsPerSecond;
+        benchmark[prop].percentiles.forEach((p) => {
+          data[p.percentile].data.push({ x: rps, y: p.value });
+        });
+        const mean = benchmark[prop].statistics.mean;
+        data.mean.data.push({ x: rps, y: mean });
+      });
+      lineData[prop] = Object.keys(data).map((key) => {
+        return data[key];
+      });
+    });
+    return lineData;
+  }
+);
diff --git a/src/ui/lib/store/slices/benchmarks/benchmarks.slice.ts b/src/ui/lib/store/slices/benchmarks/benchmarks.slice.ts
new file mode 100644
index 00000000..1090da3e
--- /dev/null
+++ b/src/ui/lib/store/slices/benchmarks/benchmarks.slice.ts
@@ -0,0 +1,34 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+import { benchmarksApi } from './benchmarks.api';
+import * as Constants from './benchmarks.constants';
+import { Benchmarks } from './benchmarks.interfaces';
+
+interface BenchmarksState {
+  data: Benchmarks | null;
+}
+
+const initialState: BenchmarksState = {
+  data: null,
+};
+
+const benchmarksSlice = createSlice({
+  name: Constants.name,
+  initialState,
+  reducers: {
+    setBenchmarks: (state, action: PayloadAction<Benchmarks>) => {
+      state.data = action.payload;
+    },
+  },
+  extraReducers: (builder) => {
+    builder.addMatcher(
+      benchmarksApi.endpoints.getBenchmarks.matchFulfilled,
+      (state, action) => {
+        state.data = action.payload;
+      }
+    );
+  },
+});
+
+export const { setBenchmarks } = benchmarksSlice.actions;
+export default benchmarksSlice.reducer;
diff --git a/src/ui/lib/store/slices/benchmarks/index.ts b/src/ui/lib/store/slices/benchmarks/index.ts
new file mode 100644
index 00000000..953b52c8
--- /dev/null
+++ b/src/ui/lib/store/slices/benchmarks/index.ts
@@ -0,0 +1,4 @@
+export * from './benchmarks.api';
+export { default as benchmarksReducer } from './benchmarks.slice';
+export * from './benchmarks.selectors';
+export * from './benchmarks.interfaces';
diff --git a/src/ui/lib/store/slices/metrics/metrics.constants.ts b/src/ui/lib/store/slices/metrics/metrics.constants.ts
new file mode 100644
index 00000000..a9ae8414
--- /dev/null
+++ b/src/ui/lib/store/slices/metrics/metrics.constants.ts
@@ -0,0 +1,10 @@
+import { MetricsState, Name } from './metrics.interfaces';
+export const name: Readonly<Name> = 'metrics.state';
+
+export const initialState: MetricsState = {
+  currentRequestRate: 0,
+  timePerRequest: { valuesByRps: {} },
+  ttft: { valuesByRps: {} },
+  tpot: { valuesByRps: {} },
+  throughput: { valuesByRps: {} },
+};
diff --git a/src/ui/lib/store/slices/metrics/metrics.interfaces.ts b/src/ui/lib/store/slices/metrics/metrics.interfaces.ts
new file mode 100644
index 00000000..b38dc98b
--- /dev/null
+++ b/src/ui/lib/store/slices/metrics/metrics.interfaces.ts
@@ -0,0 +1,13 @@
+export type Name = 'metrics.state';
+
+export interface MetricsState {
+  currentRequestRate: number;
+  timePerRequest: SingleMetricsState;
+  ttft: SingleMetricsState;
+  tpot: SingleMetricsState;
+  throughput: SingleMetricsState;
+}
+
+export type SingleMetricsState = {
+  valuesByRps: Record<number, number>;
+};
diff --git a/src/ui/lib/store/slices/metrics/metrics.selectors.ts b/src/ui/lib/store/slices/metrics/metrics.selectors.ts
new file mode 100644
index 00000000..9aa4d46a
--- /dev/null
+++ b/src/ui/lib/store/slices/metrics/metrics.selectors.ts
@@ -0,0 +1,3 @@
+import { RootState } from '../../index';
+
+export const selectMetricsState = (state: RootState) => state.metrics;
diff --git a/src/ui/lib/store/slices/metrics/metrics.slice.ts b/src/ui/lib/store/slices/metrics/metrics.slice.ts
new file mode 100644
index 00000000..40f16dc1
--- /dev/null
+++ b/src/ui/lib/store/slices/metrics/metrics.slice.ts
@@ -0,0 +1,20 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+import * as Constants from './metrics.constants';
+import { MetricsState } from './metrics.interfaces';
+
+const metricsSlice = createSlice({
+  name: Constants.name,
+  initialState: Constants.initialState,
+  reducers: {
+    setMetricsData: (state, action: PayloadAction<MetricsState>) => {
+      return { ...state, ...action.payload };
+    },
+    setSliderRps: (state, action: PayloadAction<number>) => {
+      state.currentRequestRate = action.payload;
+    },
+  },
+});
+
+export const { setMetricsData, setSliderRps } = metricsSlice.actions;
+export default metricsSlice.reducer;
diff --git a/src/ui/lib/store/slices/runInfo/index.ts b/src/ui/lib/store/slices/runInfo/index.ts
new file mode 100644
index 00000000..5595ffac
--- /dev/null
+++ b/src/ui/lib/store/slices/runInfo/index.ts
@@ -0,0 +1,4 @@
+export * from './runInfo.api';
+export { default as infoReducer } from './runInfo.slice';
+export * from './runInfo.selectors';
+export * from './runInfo.interfaces';
diff --git a/src/ui/lib/store/slices/runInfo/runInfo.api.ts b/src/ui/lib/store/slices/runInfo/runInfo.api.ts
new file mode 100644
index 00000000..9c510b5b
--- /dev/null
+++ b/src/ui/lib/store/slices/runInfo/runInfo.api.ts
@@ -0,0 +1,21 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+
+import { RunInfo } from './runInfo.interfaces';
+
+const USE_MOCK_API = process.env.NEXT_PUBLIC_USE_MOCK_API === 'true';
+
+const fetchRunInfo = async () => {
+  return { data: window.run_info as RunInfo };
+};
+
+export const runInfoApi = createApi({
+  reducerPath: 'runInfoApi',
+  baseQuery: USE_MOCK_API ? fetchRunInfo : fetchBaseQuery({ baseUrl: '/api' }),
+  endpoints: (builder) => ({
+    getRunInfo: builder.query<RunInfo, void>({
+      query: () => 'run-info',
+    }),
+  }),
+});
+
+export const { useGetRunInfoQuery } = runInfoApi;
diff --git a/src/ui/lib/store/slices/runInfo/runInfo.constants.ts b/src/ui/lib/store/slices/runInfo/runInfo.constants.ts
new file mode 100644
index 00000000..af773759
--- /dev/null
+++ b/src/ui/lib/store/slices/runInfo/runInfo.constants.ts
@@ -0,0 +1,3 @@
+import { Name } from './runInfo.interfaces';
+
+export const name: Readonly<Name> = 'runInfo';
diff --git a/src/ui/lib/store/slices/runInfo/runInfo.interfaces.ts b/src/ui/lib/store/slices/runInfo/runInfo.interfaces.ts
new file mode 100644
index 00000000..6fb8b393
--- /dev/null
+++ b/src/ui/lib/store/slices/runInfo/runInfo.interfaces.ts
@@ -0,0 +1,13 @@
+export type Name = 'runInfo';
+
+export interface RunInfo {
+  model: {
+    name: string;
+    size: number;
+  };
+  task: string;
+  dataset: {
+    name: string;
+  };
+  timestamp: string;
+}
diff --git a/src/ui/lib/store/slices/runInfo/runInfo.selectors.ts b/src/ui/lib/store/slices/runInfo/runInfo.selectors.ts
new file mode 100644
index 00000000..1f8338ca
--- /dev/null
+++ b/src/ui/lib/store/slices/runInfo/runInfo.selectors.ts
@@ -0,0 +1,3 @@
+import { RootState } from '../../index';
+
+export const selectRunInfo = (state: RootState) => state.runInfo.data;
diff --git a/src/ui/lib/store/slices/runInfo/runInfo.slice.ts b/src/ui/lib/store/slices/runInfo/runInfo.slice.ts
new file mode 100644
index 00000000..8bb48425
--- /dev/null
+++ b/src/ui/lib/store/slices/runInfo/runInfo.slice.ts
@@ -0,0 +1,34 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+import { runInfoApi } from './runInfo.api';
+import * as Constants from './runInfo.constants';
+import { RunInfo } from './runInfo.interfaces';
+
+interface RunInfoState {
+  data: RunInfo | null;
+}
+
+const initialState: RunInfoState = {
+  data: null,
+};
+
+const runInfoSlice = createSlice({
+  name: Constants.name,
+  initialState,
+  reducers: {
+    setRunInfo: (state, action: PayloadAction<RunInfo>) => {
+      state.data = action.payload;
+    },
+  },
+  extraReducers: (builder) => {
+    builder.addMatcher(
+      runInfoApi.endpoints.getRunInfo.matchFulfilled,
+      (state, action) => {
+        state.data = action.payload;
+      }
+    );
+  },
+});
+
+export const { setRunInfo } = runInfoSlice.actions;
+export default runInfoSlice.reducer;
diff --git a/src/ui/lib/store/slices/slo/slo.constants.ts b/src/ui/lib/store/slices/slo/slo.constants.ts
new file mode 100644
index 00000000..f58ccc05
--- /dev/null
+++ b/src/ui/lib/store/slices/slo/slo.constants.ts
@@ -0,0 +1,22 @@
+import { Name, SloState } from './slo.interfaces';
+
+export const name: Readonly<Name> = 'slo.state';
+
+export const defaultPercentile = 'p90';
+
+export const initialState: SloState = {
+  currentRequestRate: 0,
+  enforcedPercentile: defaultPercentile,
+  current: {
+    timePerRequest: 0,
+    ttft: 0,
+    tpot: 0,
+    throughput: 0,
+  },
+  tasksDefaults: {
+    timePerRequest: 0,
+    ttft: 0,
+    tpot: 0,
+    throughput: 0,
+  },
+};
diff --git a/src/ui/lib/store/slices/slo/slo.interfaces.ts b/src/ui/lib/store/slices/slo/slo.interfaces.ts
new file mode 100644
index 00000000..0d59baa2
--- /dev/null
+++ b/src/ui/lib/store/slices/slo/slo.interfaces.ts
@@ -0,0 +1,18 @@
+export type Name = 'slo.state';
+
+export interface SloState {
+  currentRequestRate: number;
+  enforcedPercentile: string;
+  current: {
+    timePerRequest: number;
+    ttft: number;
+    tpot: number;
+    throughput: number;
+  };
+  tasksDefaults: {
+    timePerRequest: number;
+    ttft: number;
+    tpot: number;
+    throughput: number;
+  };
+}
diff --git a/src/ui/lib/store/slices/slo/slo.selectors.ts b/src/ui/lib/store/slices/slo/slo.selectors.ts
new file mode 100644
index 00000000..38000830
--- /dev/null
+++ b/src/ui/lib/store/slices/slo/slo.selectors.ts
@@ -0,0 +1,3 @@
+import { RootState } from '../../index';
+
+export const selectSloState = (state: RootState) => state.slo;
diff --git a/src/ui/lib/store/slices/slo/slo.slice.ts b/src/ui/lib/store/slices/slo/slo.slice.ts
new file mode 100644
index 00000000..5b3b8853
--- /dev/null
+++ b/src/ui/lib/store/slices/slo/slo.slice.ts
@@ -0,0 +1,47 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+import * as Constants from './slo.constants';
+import { SloState } from './slo.interfaces';
+
+const sloSlice = createSlice({
+  name: Constants.name,
+  initialState: Constants.initialState,
+  reducers: {
+    setSloData: (state, action: PayloadAction<Partial<SloState>>) => {
+      if (action.payload.enforcedPercentile !== undefined) {
+        state.enforcedPercentile = action.payload.enforcedPercentile;
+      }
+      if (action.payload.current) {
+        state.current = { ...state.current, ...action.payload.current };
+      }
+      if (action.payload.tasksDefaults) {
+        state.tasksDefaults = {
+          ...state.tasksDefaults,
+          ...action.payload.tasksDefaults,
+        };
+      }
+      if (action.payload.currentRequestRate) {
+        state.currentRequestRate = action.payload.currentRequestRate;
+      }
+    },
+    setEnforcedPercentile: (state, action: PayloadAction<string>) => {
+      state.enforcedPercentile = action.payload;
+    },
+    setCurrentRequestRate: (state, action: PayloadAction<number>) => {
+      state.currentRequestRate = action.payload;
+    },
+    setSloValue: (
+      state,
+      action: PayloadAction<{ metric: keyof SloState['current']; value: number }>
+    ) => {
+      const { metric, value } = action.payload;
+      if (value >= 0) {
+        state.current[metric] = value;
+      }
+    },
+  },
+});
+
+export const { setCurrentRequestRate, setEnforcedPercentile, setSloData, setSloValue } =
+  sloSlice.actions;
+export default sloSlice.reducer;
diff --git a/src/ui/lib/store/slices/workloadDetails/index.ts b/src/ui/lib/store/slices/workloadDetails/index.ts
new file mode 100644
index 00000000..e5cd556b
--- /dev/null
+++ b/src/ui/lib/store/slices/workloadDetails/index.ts
@@ -0,0 +1,4 @@
+export * from './workloadDetails.api';
+export { default as workloadDetailsReducer } from './workloadDetails.slice';
+export * from './workloadDetails.selectors';
+export * from './workloadDetails.interfaces';
diff --git a/src/ui/lib/store/slices/workloadDetails/workloadDetails.api.ts b/src/ui/lib/store/slices/workloadDetails/workloadDetails.api.ts
new file mode 100644
index 00000000..6720f865
--- /dev/null
+++ b/src/ui/lib/store/slices/workloadDetails/workloadDetails.api.ts
@@ -0,0 +1,21 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
+
+import { WorkloadDetails } from './workloadDetails.interfaces';
+
+const USE_MOCK_API = process.env.NEXT_PUBLIC_USE_MOCK_API === 'true';
+
+const fetchWorkloadDetails = async () => {
+  return { data: window.workload_details as WorkloadDetails };
+};
+
+export const workloadDetailsApi = createApi({
+  reducerPath: 'workloadDetailsApi',
+  baseQuery: USE_MOCK_API ? fetchWorkloadDetails : fetchBaseQuery({ baseUrl: '/api' }),
+  endpoints: (builder) => ({
+    getWorkloadDetails: builder.query<WorkloadDetails, void>({
+      query: () => 'workload-details',
+    }),
+  }),
+});
+
+export const { useGetWorkloadDetailsQuery } = workloadDetailsApi;
diff --git a/src/ui/lib/store/slices/workloadDetails/workloadDetails.constants.ts b/src/ui/lib/store/slices/workloadDetails/workloadDetails.constants.ts
new file mode 100644
index 00000000..c45efa76
--- /dev/null
+++ b/src/ui/lib/store/slices/workloadDetails/workloadDetails.constants.ts
@@ -0,0 +1,58 @@
+import { Name, WorkloadDetails } from './workloadDetails.interfaces';
+
+export const name: Readonly<Name> = 'workloadDetails';
+
+export const initialState: WorkloadDetails = {
+  prompts: {
+    samples: [],
+    tokenDistributions: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [],
+      buckets: [],
+      bucketWidth: 0,
+    },
+  },
+  generations: {
+    samples: [],
+    tokenDistributions: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [],
+      buckets: [],
+      bucketWidth: 0,
+    },
+  },
+  requestsOverTime: {
+    numBenchmarks: 0,
+    requestsOverTime: {
+      statistics: {
+        total: 0,
+        mean: 0,
+        std: 0,
+        median: 0,
+        min: 0,
+        max: 0,
+      },
+      percentiles: [],
+      buckets: [],
+      bucketWidth: 0,
+    },
+  },
+  rateType: '',
+  server: {
+    target: '',
+  },
+};
diff --git a/src/ui/lib/store/slices/workloadDetails/workloadDetails.interfaces.ts b/src/ui/lib/store/slices/workloadDetails/workloadDetails.interfaces.ts
new file mode 100644
index 00000000..2aa7619f
--- /dev/null
+++ b/src/ui/lib/store/slices/workloadDetails/workloadDetails.interfaces.ts
@@ -0,0 +1,49 @@
+export type Name = 'workloadDetails';
+
+interface Statistics {
+  total: number;
+  mean: number;
+  std: number;
+  median: number;
+  min: number;
+  max: number;
+}
+
+interface Percentile {
+  percentile: string;
+  value: number;
+}
+
+interface Bucket {
+  value: number;
+  count: number;
+}
+
+interface Distribution {
+  statistics: Statistics;
+  percentiles: Percentile[];
+  buckets: Bucket[];
+  bucketWidth: number;
+}
+
+interface TokenData {
+  samples: string[];
+  tokenDistributions: Distribution;
+}
+
+interface BenchmarkData {
+  numBenchmarks: number;
+  requestsOverTime: Distribution;
+}
+
+interface Server {
+  target: string;
+}
+
+export interface WorkloadDetails {
+  prompts: TokenData;
+  generations: TokenData;
+  requestsOverTime: BenchmarkData;
+  rateType: string;
+  server: Server;
+}
diff --git a/src/ui/lib/store/slices/workloadDetails/workloadDetails.selectors.ts b/src/ui/lib/store/slices/workloadDetails/workloadDetails.selectors.ts
new file mode 100644
index 00000000..a3fde606
--- /dev/null
+++ b/src/ui/lib/store/slices/workloadDetails/workloadDetails.selectors.ts
@@ -0,0 +1,79 @@
+import { createSelector } from '@reduxjs/toolkit';
+
+import { formatNumber } from '../../../utils/helpers';
+import { RootState } from '../../index';
+
+export const selectWorkloadDetails = (state: RootState) => state.workloadDetails.data;
+
+export const selectPromptsHistogramBarData = createSelector(
+  [selectWorkloadDetails],
+  (workloadDetails) => {
+    return workloadDetails?.prompts?.tokenDistributions.buckets.map((bucket) => ({
+      x: formatNumber(bucket.value),
+      y: bucket.count,
+    }));
+  }
+);
+
+export const selectGenerationsHistogramBarData = createSelector(
+  [selectWorkloadDetails],
+  (workloadDetails) => {
+    return workloadDetails?.generations?.tokenDistributions.buckets.map((bucket) => ({
+      x: formatNumber(bucket.value),
+      y: bucket.count,
+    }));
+  }
+);
+
+export const selectPromptsHistogramLineData = createSelector(
+  [selectWorkloadDetails],
+  (workloadDetails) => [
+    {
+      x: formatNumber(
+        workloadDetails?.prompts?.tokenDistributions.statistics.mean ?? 0
+      ),
+      y: 35,
+      id: 'mean',
+    },
+    {
+      x: formatNumber(
+        workloadDetails?.prompts?.tokenDistributions.statistics.median ?? 0
+      ),
+      y: 35,
+      id: 'median',
+    },
+  ]
+);
+
+export const selectGenerationsHistogramLineData = createSelector(
+  [selectWorkloadDetails],
+  (workloadDetails) => [
+    {
+      x: formatNumber(
+        workloadDetails?.generations?.tokenDistributions.statistics.mean ?? 0
+      ),
+      y: 35,
+      id: 'mean',
+    },
+    {
+      x: formatNumber(
+        workloadDetails?.generations?.tokenDistributions.statistics.median ?? 0
+      ),
+      y: 35,
+      id: 'median',
+    },
+  ]
+);
+
+export const selectRequestOverTimeBarData = createSelector(
+  [selectWorkloadDetails],
+  (workloadDetails) => {
+    const requestObjs = workloadDetails?.requestsOverTime?.requestsOverTime;
+    return {
+      barChartData: requestObjs?.buckets?.map((bucket) => ({
+        x: formatNumber(bucket.value),
+        y: bucket.count,
+      })),
+    };
+  }
+);
diff --git a/src/ui/lib/store/slices/workloadDetails/workloadDetails.slice.ts b/src/ui/lib/store/slices/workloadDetails/workloadDetails.slice.ts
new file mode 100644
index 00000000..7d05a0d7
--- /dev/null
+++ b/src/ui/lib/store/slices/workloadDetails/workloadDetails.slice.ts
@@ -0,0 +1,34 @@
+import { createSlice, PayloadAction } from '@reduxjs/toolkit';
+
+import { workloadDetailsApi } from './workloadDetails.api';
+import * as Constants from './workloadDetails.constants';
+import { WorkloadDetails } from './workloadDetails.interfaces';
+
+interface WorkloadDetailsState {
+  data: WorkloadDetails | null;
+}
+
+const initialState: WorkloadDetailsState = {
+  data: null,
+};
+
+const workloadDetailsSlice = createSlice({
+  name: Constants.name,
+  initialState,
+  reducers: {
+    setWorkloadDetails: (state, action: PayloadAction<WorkloadDetails>) => {
+      state.data = action.payload;
+    },
+  },
+  extraReducers: (builder) => {
+    builder.addMatcher(
+      workloadDetailsApi.endpoints.getWorkloadDetails.matchFulfilled,
+      (state, action) => {
+        state.data = action.payload;
+      }
+    );
+  },
+});
+
+export const { setWorkloadDetails } = workloadDetailsSlice.actions;
+export default workloadDetailsSlice.reducer;
diff --git a/src/ui/lib/store/workloadDetailsWindowData.ts b/src/ui/lib/store/workloadDetailsWindowData.ts
new file mode 100644
index 00000000..8913f5c2
--- /dev/null
+++ b/src/ui/lib/store/workloadDetailsWindowData.ts
@@ -0,0 +1,167 @@
+export const workloadDetailsScript = `window.workload_details = {
+  "prompts": {
+    "samples": [
+      "such a sacrifice to her advantage as years of gratitude cannot enough acknowledge. By this time she is actually with them! If such goodness does not make her miserable now, she will never deserve to be happy! What a meeting for her, when she first sees my aunt! We must endeavour to forget all that has passed on either side, said Jane I hope and trust they will yet be happy. His consenting to marry her is a proof, I will believe, that he is come to a right way of thinking. Their mutual affection will steady them; and I flatter myself they will settle so quietly, and live in so rational a manner",
+      "a reconciliation; and, after a little further resistance on the part of his aunt, her resentment gave way, either to her affection for him, or her curiosity to see how his wife conducted herself; and she condescended to wait on them at Pemberley, in spite of that pollution which its woods had received, not merely from the presence of such a mistress, but the visits of her uncle and aunt from the city. With the Gardiners they were always on the most intimate terms. Darcy, as well as Elizabeth, really loved them; and they were both ever sensible of the warmest gratitude towards the persons who,",
+      "struck her, that _she_ was selected from among her sisters as worthy of being the mistress of Hunsford Parsonage, and of assisting to form a quadrille table at Rosings, in the absence of more eligible visitors. The idea soon reached to conviction, as she observed his increasing civilities towards herself, and heard his frequent attempt at a compliment on her wit and vivacity; and though more astonished than gratified herself by this effect of her charms, it was not long before her mother gave her to understand that the probability of their marriage was exceedingly agreeable to _her_. Elizabeth, however, did not choose",
+      "were comfortable on this subject. Day after day passed away without bringing any other tidings of him than the report which shortly prevailed in Meryton of his coming no more to Netherfield the whole winter; a report which highly incensed Mrs. Bennet, and which she never failed to contradict as a most scandalous falsehood. Even Elizabeth began to fear not that Bingley was indifferent but that his sisters would be successful in keeping him away. Unwilling as she was to admit an idea so destructive to Jane s happiness, and so dishonourable to the stability of her lover, she could not prevent its frequently recurring",
+      "? cried Elizabeth, brightening up for a moment. Upon my word, said Mrs. Gardiner, I begin to be of your uncle s opinion. It is really too great a violation of decency, honour, and interest, for him to be guilty of it. I cannot think so very ill of Wickham. Can you, yourself, Lizzie, so wholly give him up, as to believe him capable of it? Not perhaps of neglecting his own interest. But of every other neglect I can believe him capable. If, indeed, it should be so! But I dare not hope it. Why should they not go on"
+    ],
+    "tokenDistributions": {
+      "statistics": {
+        "mean": 128.07115246019785
+      },
+      "buckets": [
+        {
+          "value": 128,
+          "count": 14389
+        },
+        {
+          "value": 130,
+          "count": 182
+        },
+        {
+          "value": 129,
+          "count": 677
+        },
+        {
+          "value": 131,
+          "count": 15
+        }
+      ]
+    }
+  },
+  "generations": {
+    "samples": [
+      ", that his relations could not choose but be struck with their pleasing and advantageous change. Ten years of reproofs, threats, and chastisements, may not have given them all they ought to know; but surely evidence and conviction will soon do the rest, thanks to the happy truth-so-discovered is Bess",
+      " for her sake, had generously given up the one interest, and risked the other, the only real friends whom she could ever be able to acknowledge. Miss Gardiner had attended her nephew, the season before, and it was some consolation to Elizabeth to see his sisters again at Pemberley. Henry, of course",
+      " to attend to any reasoning. He was already so far her friend, and the habit of their acquaintance, attended as it was by a new share of regard, could produce but good offices among them, and we must take our comfort as well as we may. This comfort he soon united with, in possessesing her esteem",
+      " to her by the conduct of her mother and sisters. Charlotte was ins   el      amorous, silly, and headstrong; but he is, or seems to be, really in love with her.   El   ,   The      rury waning of his best songs, and no man over 50",
+      ", with a certain tolerable income, in the genteel style they had known before? Why should not you, your father, and their cousins, protect and assist them, if you could prevail on your uncle to indemnify them sufficiently? -- But to deal out money recklessly, is indeed disgraceful. Had he"
+    ],
+    "tokenDistributions": {
+      "statistics": {
+        "mean": 63.951778811504944
+      },
+      "buckets": [
+        {
+          "value": 64,
+          "count": 14618
+        },
+        {
+          "value": 62,
+          "count": 147
+        },
+        {
+          "value": 63,
+          "count": 431
+        },
+        {
+          "value": 61,
+          "count": 19
+        },
+        {
+          "value": 65,
+          "count": 40
+        },
+        {
+          "value": 66,
+          "count": 4
+        },
+        {
+          "value": 67,
+          "count": 2
+        },
+        {
+          "value": 60,
+          "count": 2
+        }
+      ]
+    }
+  },
+  "requestsOverTime": {
+    "numBenchmarks": 10,
+    "requestsOverTime": {
+      "statistic": {},
+      "percentiles": [],
+      "buckets": [
+        {
+          "value": 0.12647485733032227,
+          "count": 46
+        },
+        {
+          "value": 68.87534944216411,
+          "count": 831
+        },
+        {
+          "value": 137.6242240269979,
+          "count": 2076
+        },
+        {
+          "value": 206.37309861183167,
+          "count": 629
+        },
+        {
+          "value": 275.12197319666546,
+          "count": 282
+        },
+        {
+          "value": 343.87084778149926,
+          "count": 397
+        },
+        {
+          "value": 412.619722366333,
+          "count": 517
+        },
+        {
+          "value": 481.3685969511668,
+          "count": 669
+        },
+        {
+          "value": 550.1174715360006,
+          "count": 732
+        },
+        {
+          "value": 618.8663461208344,
+          "count": 958
+        },
+        {
+          "value": 687.6152207056682,
+          "count": 962
+        },
+        {
+          "value": 756.364095290502,
+          "count": 1224
+        },
+        {
+          "value": 825.1129698753357,
+          "count": 1197
+        },
+        {
+          "value": 893.8618444601696,
+          "count": 1458
+        },
+        {
+          "value": 962.6107190450033,
+          "count": 1435
+        },
+        {
+          "value": 1031.359593629837,
+          "count": 1695
+        },
+        {
+          "value": 1100.108468214671,
+          "count": 1640
+        },
+        {
+          "value": 1168.8573427995047,
+          "count": 1931
+        }
+      ]
+    }
+  },
+  "rateType": "sweep",
+  "server": {
+    "target": "http://192.168.4.13:8000"
+  }
+};`;
diff --git a/src/ui/lib/utils/helpers.ts b/src/ui/lib/utils/helpers.ts
new file mode 100644
index 00000000..e96d2ea6
--- /dev/null
+++ b/src/ui/lib/utils/helpers.ts
@@ -0,0 +1,68 @@
+import { filesize } from 'filesize';
+
+export const formatValue = (value: number, fractionDigits = 2) =>
+  `$ ${value.toFixed(fractionDigits)}`;
+
+export const ceil = (number: number, precision = 0) => {
+  const n = number * Math.pow(10, precision);
+  return Math.ceil(n) / Math.pow(10, precision);
+};
+
+export const floor = (number: number, precision = 0) => {
+  const n = number * Math.pow(10, precision);
+  return Math.floor(n) / Math.pow(10, precision);
+};
+
+export const formatNumber = (number: number, precision = 2) =>
+  Number(number.toFixed(precision));
+
+export const parseUrlParts = (urlString: string) => {
+  try {
+    const url = new URL(urlString);
+    return {
+      type: url.protocol.replace(':', ''),
+      target: url.hostname,
+      port: url.port || '',
+      path: url.pathname,
+    };
+  } catch (_) {
+    return {
+      type: '',
+      target: '',
+      port: '',
+      path: '',
+    };
+  }
+};
+
+export const getFileSize = (
+  size?: string | number | null,
+  roundDecimal?: number,
+  bits?: boolean
+): { size: string; units: string } | undefined => {
+  if (size) {
+    const round = roundDecimal === 0 ? 0 : roundDecimal || 1;
+    const fileSize = `${filesize(size, { round, bits })}`.split(' ');
+
+    return {
+      size: fileSize[0],
+      units: fileSize[1].toUpperCase(),
+    };
+  }
+};
+
+export const formateDate = (timestamp: string) => {
+  const date = new Date(Number(timestamp) * 1000);
+
+  const options: Intl.DateTimeFormatOptions = {
+    year: 'numeric',
+    month: 'long',
+    day: 'numeric',
+    hour: '2-digit',
+    minute: '2-digit',
+    second: '2-digit',
+    hour12: false,
+  };
+
+  return date.toLocaleString('en-US', options).replace(',', '');
+};
diff --git a/src/ui/lib/utils/interpolation.ts b/src/ui/lib/utils/interpolation.ts
new file mode 100644
index 00000000..50e559a0
--- /dev/null
+++ b/src/ui/lib/utils/interpolation.ts
@@ -0,0 +1,67 @@
+export function createMonotoneSpline(xs: number[], ys: number[]) {
+  const n = xs.length;
+  if (n < 2) {
+    throw new Error('Need at least two points');
+  }
+  const dx = new Array<number>(n - 1);
+  const dy = new Array<number>(n - 1);
+  const m = new Array<number>(n - 1);
+  const c1 = new Array<number>(n);
+
+  for (let i = 0; i < n - 1; i++) {
+    dx[i] = xs[i + 1] - xs[i];
+    if (dx[i] === 0) {
+      throw new Error(`xs[${i}] == xs[${i + 1}]`);
+    }
+    dy[i] = ys[i + 1] - ys[i];
+    m[i] = dy[i] / dx[i];
+  }
+
+  c1[0] = m[0];
+  for (let i = 1; i < n - 1; i++) {
+    if (m[i - 1] * m[i] <= 0) {
+      c1[i] = 0;
+    } else {
+      const dx1 = dx[i - 1],
+        dx2 = dx[i],
+        common = dx1 + dx2;
+      c1[i] = (3 * common) / ((common + dx2) / m[i - 1] + (common + dx1) / m[i]);
+    }
+  }
+  c1[n - 1] = m[n - 2];
+
+  return function (x: number) {
+    // Binary search for interval i: xs[i] <= x < xs[i+1]
+    let lo = 0,
+      hi = n - 2;
+    while (lo <= hi) {
+      const mid = (lo + hi) >> 1;
+      if (x < xs[mid]) {
+        hi = mid - 1;
+      } else if (x > xs[mid + 1]) {
+        lo = mid + 1;
+      } else {
+        lo = mid;
+        break;
+      }
+    }
+    let i = lo;
+    if (i < 0) {
+      i = 0;
+    } else if (i > n - 2) {
+      i = n - 2;
+    }
+
+    const h = dx[i];
+    const t = (x - xs[i]) / h;
+    const t2 = t * t,
+      t3 = t2 * t;
+
+    const h00 = 2 * t3 - 3 * t2 + 1;
+    const h10 = t3 - 2 * t2 + t;
+    const h01 = -2 * t3 + 3 * t2;
+    const h11 = t3 - t2;
+
+    return h00 * ys[i] + h10 * h * c1[i] + h01 * ys[i + 1] + h11 * h * c1[i + 1];
+  };
+}
diff --git a/src/ui/next.config.ts b/src/ui/next.config.ts
index 0e244f5b..a9b9d123 100644
--- a/src/ui/next.config.ts
+++ b/src/ui/next.config.ts
@@ -1,6 +1,7 @@
 import type { NextConfig } from 'next';
 
 const nextConfig: NextConfig = {
+  images: { unoptimized: true },
   output: 'export',
   webpack(config) {
     // Grab the existing rule that handles SVG imports
diff --git a/src/ui/public/android-chrome-192x192.png b/src/ui/public/android-chrome-192x192.png
index 6694fc06..60cb9c65 100644
Binary files a/src/ui/public/android-chrome-192x192.png and b/src/ui/public/android-chrome-192x192.png differ
diff --git a/src/ui/public/apple-touch-icon.png b/src/ui/public/apple-touch-icon.png
index 06b38634..2e570c52 100644
Binary files a/src/ui/public/apple-touch-icon.png and b/src/ui/public/apple-touch-icon.png differ
diff --git a/src/ui/public/favicon-16x16.png b/src/ui/public/favicon-16x16.png
index 0573eede..3e7203d6 100644
Binary files a/src/ui/public/favicon-16x16.png and b/src/ui/public/favicon-16x16.png differ
diff --git a/src/ui/public/favicon-192x192.png b/src/ui/public/favicon-192x192.png
index 6694fc06..60cb9c65 100644
Binary files a/src/ui/public/favicon-192x192.png and b/src/ui/public/favicon-192x192.png differ
diff --git a/src/ui/public/favicon-32x32.png b/src/ui/public/favicon-32x32.png
index 155c0930..56e58bd7 100644
Binary files a/src/ui/public/favicon-32x32.png and b/src/ui/public/favicon-32x32.png differ
diff --git a/src/ui/public/favicon.png b/src/ui/public/favicon.png
new file mode 100644
index 00000000..2e570c52
Binary files /dev/null and b/src/ui/public/favicon.png differ
diff --git a/src/ui/public/file.svg b/src/ui/public/file.svg
deleted file mode 100644
index 16fe3d3a..00000000
--- a/src/ui/public/file.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
diff --git a/src/ui/public/globe.svg b/src/ui/public/globe.svg
deleted file mode 100644
index c7215fe0..00000000
--- a/src/ui/public/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
diff --git a/src/ui/public/manifest.json b/src/ui/public/manifest.json
index 3f06b147..89605c2d 100644
--- a/src/ui/public/manifest.json
+++ b/src/ui/public/manifest.json
@@ -3,19 +3,14 @@
   "name": "guidellm-ui",
   "icons": [
     {
-      "src": "favicon.ico",
-      "sizes": "64x64 32x32 24x24 16x16",
-      "type": "image/x-icon"
+      "src": "favicon.png",
+      "sizes": "119x119",
+      "type": "image/png"
     },
     {
       "src": "favicon-192x192.png",
       "type": "image/png",
       "sizes": "192x192"
-    },
-    {
-      "src": "favicon-512x512.png",
-      "type": "image/png",
-      "sizes": "512x512"
     }
   ],
   "start_url": ".",
diff --git a/src/ui/public/next.svg b/src/ui/public/next.svg
deleted file mode 100644
index 5bb00d40..00000000
--- a/src/ui/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
diff --git a/src/ui/public/vercel.svg b/src/ui/public/vercel.svg
deleted file mode 100644
index 52151572..00000000
--- a/src/ui/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
diff --git a/src/ui/public/window.svg b/src/ui/public/window.svg
deleted file mode 100644
index d05e7a1b..00000000
--- a/src/ui/public/window.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
diff --git a/src/ui/tsconfig.json b/src/ui/tsconfig.json
index 25ccf9e8..172c959e 100644
--- a/src/ui/tsconfig.json
+++ b/src/ui/tsconfig.json
@@ -1,10 +1,15 @@
 {
   "extends": "../../tsconfig.base.json",
   "compilerOptions": {
-    /* only runtime types here */
-    "types": ["node"],
     "allowJs": false,
     "isolatedModules": true
   },
-  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app"]
+  "include": [
+    "next-env.d.ts",
+    "**/*.ts",
+    "**/*.tsx",
+    ".next/types/**/*.ts",
+    "app",
+    "./app/types/images.d.ts"
+  ]
 }
diff --git a/src/ui/types/declaration.d.ts b/src/ui/types/declaration.d.ts
new file mode 100644
index 00000000..6a070e24
--- /dev/null
+++ b/src/ui/types/declaration.d.ts
@@ -0,0 +1,11 @@
+import { Benchmarks } from './src/lib/store/slices/benchmarks/benchmarks.interfaces';
+import { RunInfo } from './src/lib/store/slices/runInfo/runInfo.interfaces';
+import { WorkloadDetails } from './src/lib/store/slices/workloadDetails/workloadDetails.interfaces';
+
+declare global {
+  interface Window {
+    run_info?: RunInfo;
+    workload_details?: WorkloadDetails;
+    benchmarks?: Benchmarks;
+  }
+}
diff --git a/tests/ui/__mocks__/@nivo/bar.tsx b/tests/ui/__mocks__/@nivo/bar.tsx
new file mode 100644
index 00000000..a99b6dec
--- /dev/null
+++ b/tests/ui/__mocks__/@nivo/bar.tsx
@@ -0,0 +1,2 @@
+export const ResponsiveBar = () => null;
+export const BarCustomLayerProps = () => null;
diff --git a/tests/ui/__mocks__/@nivo/core.tsx b/tests/ui/__mocks__/@nivo/core.tsx
new file mode 100644
index 00000000..66e2da8a
--- /dev/null
+++ b/tests/ui/__mocks__/@nivo/core.tsx
@@ -0,0 +1 @@
+export const Point = () => null;
diff --git a/tests/ui/__mocks__/@nivo/line.tsx b/tests/ui/__mocks__/@nivo/line.tsx
new file mode 100644
index 00000000..bf985ef3
--- /dev/null
+++ b/tests/ui/__mocks__/@nivo/line.tsx
@@ -0,0 +1 @@
+export const ResponsiveLine = () => null;
diff --git a/tests/ui/integration/page.test.tsx b/tests/ui/integration/page.test.tsx
index caa2aab4..3e765a94 100644
--- a/tests/ui/integration/page.test.tsx
+++ b/tests/ui/integration/page.test.tsx
@@ -1,10 +1,41 @@
-import { render } from '@testing-library/react';
+import { render, waitFor } from '@testing-library/react';
 
 import Home from '@/app/page';
 
+import mockBenchmarks from '../unit/mocks/mockBenchmarks';
+
+const jsonResponse = (data: unknown, status = 200) =>
+  Promise.resolve(
+    new Response(JSON.stringify(data), {
+      status,
+      headers: { 'Content-Type': 'application/json' },
+    })
+  );
+
+const route = (input: RequestInfo) => {
+  const url = typeof input === 'string' ? input : input.url;
+
+  if (url.endsWith('/run-info')) return jsonResponse({});
+  if (url.endsWith('/workload-details')) return jsonResponse({});
+  if (url.endsWith('/benchmarks'))
+    return jsonResponse({
+      benchmarks: mockBenchmarks,
+    });
+
+  /* fall-through → 404 */
+  return { ok: false, status: 404, json: () => Promise.resolve({}) };
+};
+
+beforeEach(() => {
+  jest.resetAllMocks();
+  (global.fetch as jest.Mock).mockImplementation(route);
+});
+
 describe('Home Page', () => {
-  it('renders the homepage content', () => {
+  it('renders the homepage content', async () => {
     const { getByText } = render(<Home />);
-    expect(getByText('GuideLLM')).toBeInTheDocument();
+    await waitFor(() => {
+      expect(getByText('GuideLLM')).toBeInTheDocument();
+    });
   });
 });
diff --git a/tests/ui/test.helper.tsx b/tests/ui/test.helper.tsx
new file mode 100644
index 00000000..7d93b4e7
--- /dev/null
+++ b/tests/ui/test.helper.tsx
@@ -0,0 +1,17 @@
+import { ThemeProvider } from '@mui/material/styles';
+import React, { ReactNode } from 'react';
+
+import { muiThemeV3Dark } from '@/app/theme';
+import { ReduxProvider } from '@/lib/store/provider';
+
+interface TestProvidersProps {
+  children: ReactNode;
+}
+
+export const MockedWrapper = ({ children }: TestProvidersProps) => {
+  return (
+    <ReduxProvider>
+      <ThemeProvider theme={muiThemeV3Dark}>{children}</ThemeProvider>
+    </ReduxProvider>
+  );
+};
diff --git a/tests/ui/unit/components/Charts/DashedLine/helpers.test.ts b/tests/ui/unit/components/Charts/DashedLine/helpers.test.ts
new file mode 100644
index 00000000..e8a75732
--- /dev/null
+++ b/tests/ui/unit/components/Charts/DashedLine/helpers.test.ts
@@ -0,0 +1,86 @@
+import {
+  roundNearestNice,
+  roundUpNice,
+  spacedLogValues,
+} from '@/lib/components/Charts/DashedLine/helpers';
+
+describe('roundNearestNice', () => {
+  it('rounds to a nearby nice number', () => {
+    expect([10, 12]).toContain(roundNearestNice(11));
+    expect([25, 30]).toContain(roundNearestNice(27));
+    expect([50]).toContain(roundNearestNice(49));
+    expect([75, 80, 85]).toContain(roundNearestNice(81));
+    expect([800]).toContain(roundNearestNice(810));
+    expect([1300, 1400, 1500]).toContain(roundNearestNice(1342));
+  });
+  it("doesn't round some nice numbers", () => {
+    expect(roundNearestNice(15)).toBe(15);
+    expect(roundNearestNice(20)).toBe(20);
+    expect(roundNearestNice(30)).toBe(30);
+    expect(roundNearestNice(40)).toBe(40);
+    expect(roundNearestNice(75)).toBe(75);
+    expect(roundNearestNice(100)).toBe(100);
+    expect(roundNearestNice(150)).toBe(150);
+    expect(roundNearestNice(200)).toBe(200);
+    expect(roundNearestNice(400)).toBe(400);
+    expect(roundNearestNice(1000)).toBe(1000);
+    expect(roundNearestNice(1200)).toBe(1200);
+  });
+});
+
+describe('roundUpNice', () => {
+  it('rounds up to a nearby nice number', () => {
+    expect([12, 15]).toContain(roundUpNice(11));
+    expect([30]).toContain(roundUpNice(27));
+    expect([50]).toContain(roundUpNice(49));
+    expect([80, 85, 90]).toContain(roundUpNice(79));
+    expect([85, 90]).toContain(roundUpNice(81));
+    expect([850, 900, 1000]).toContain(roundUpNice(810));
+    expect([1350, 1400, 1500]).toContain(roundUpNice(1342));
+  });
+  it("doesn't round some nice numbers", () => {
+    expect(roundUpNice(15)).toBe(15);
+    expect(roundUpNice(20)).toBe(20);
+    expect(roundUpNice(30)).toBe(30);
+    expect(roundUpNice(40)).toBe(40);
+    expect(roundUpNice(75)).toBe(75);
+    expect(roundUpNice(100)).toBe(100);
+    expect(roundUpNice(150)).toBe(150);
+    expect(roundUpNice(200)).toBe(200);
+    expect(roundUpNice(400)).toBe(400);
+    expect(roundUpNice(1000)).toBe(1000);
+    expect(roundUpNice(1200)).toBe(1200);
+  });
+});
+
+describe('spacedLogValues', () => {
+  const checkValuesRoughlyLogSpaced = (values: number[]) => {
+    const valuesRatios = [];
+    for (let i = 1; i < values.length; i++) {
+      valuesRatios.push(values[i] / values[i - 1]);
+    }
+    const valuesRatiosAvg = valuesRatios.reduce((a, b) => a + b) / valuesRatios.length;
+    valuesRatios.forEach((ratio) => {
+      expect(ratio).toBeCloseTo(valuesRatiosAvg, -0.5);
+    });
+  };
+
+  it('generates an array of roughly log-scale spaced values', () => {
+    expect(spacedLogValues(1, 1000, 4)).toEqual([1, 10, 100, 1000]);
+    checkValuesRoughlyLogSpaced(spacedLogValues(1, 1324, 4));
+    checkValuesRoughlyLogSpaced(spacedLogValues(123, 12324, 6));
+    checkValuesRoughlyLogSpaced(spacedLogValues(1, 122, 6));
+    checkValuesRoughlyLogSpaced(spacedLogValues(1, 122, 9));
+  });
+  it('generates an array of nice round numbers', () => {
+    for (const value of spacedLogValues(1, 1000, 4)) {
+      expect([roundUpNice(value), roundNearestNice(value)]).toContain(value);
+    }
+    for (const value of spacedLogValues(1, 1324, 4)) {
+      expect([roundUpNice(value), roundNearestNice(value)]).toContain(value);
+    }
+    for (const value of spacedLogValues(1, 132, 7)) {
+      expect([roundUpNice(value), roundNearestNice(value)]).toContain(value);
+    }
+  });
+});
diff --git a/tests/ui/unit/layout.test.tsx b/tests/ui/unit/layout.test.tsx
deleted file mode 100644
index f2a85d28..00000000
--- a/tests/ui/unit/layout.test.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { render } from '@testing-library/react';
-
-import RootLayout from '@/app/layout';
-
-describe('RootLayout', () => {
-  it('renders children inside the layout', () => {
-    const { getByText } = render(
-      <RootLayout>
-        <p>Test Child</p>
-      </RootLayout>
-    );
-
-    expect(getByText('Test Child')).toBeInTheDocument();
-  });
-});
diff --git a/tests/ui/unit/mocks/mockBenchmarks.ts b/tests/ui/unit/mocks/mockBenchmarks.ts
new file mode 100644
index 00000000..29b9232e
--- /dev/null
+++ b/tests/ui/unit/mocks/mockBenchmarks.ts
@@ -0,0 +1,232 @@
+export default [
+  {
+    requestsPerSecond: 0.6668550387660497,
+    tpot: {
+      statistics: {
+        total: 80,
+        mean: 23.00635663936911,
+        median: 22.959455611213805,
+        min: 22.880917503720237,
+        max: 24.14080301920573,
+        std: 0.18918760384209338,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 22.959455611213805,
+        },
+        {
+          percentile: 'p90',
+          value: 23.01789086962503,
+        },
+        {
+          percentile: 'p95',
+          value: 23.30297423947242,
+        },
+        {
+          percentile: 'p99',
+          value: 24.14080301920573,
+        },
+      ],
+    },
+    ttft: {
+      statistics: {
+        total: 80,
+        mean: 49.64659512042999,
+        median: 49.23129081726074,
+        min: 44.538259506225586,
+        max: 55.47308921813965,
+        std: 1.7735485090634995,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 49.23129081726074,
+        },
+        {
+          percentile: 'p90',
+          value: 50.16160011291504,
+        },
+        {
+          percentile: 'p95',
+          value: 54.918766021728516,
+        },
+        {
+          percentile: 'p99',
+          value: 55.47308921813965,
+        },
+      ],
+    },
+    throughput: {
+      statistics: {
+        total: 210,
+        mean: 42.58702991319684,
+        median: 43.536023084668,
+        min: 0.0,
+        max: 43.68247620237872,
+        std: 4.559764488536857,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 43.536023084668,
+        },
+        {
+          percentile: 'p90',
+          value: 43.62613633999709,
+        },
+        {
+          percentile: 'p95',
+          value: 43.64020767654067,
+        },
+        {
+          percentile: 'p99',
+          value: 43.68202126662431,
+        },
+      ],
+    },
+    timePerRequest: {
+      statistics: {
+        total: 80,
+        mean: 1496.706646680832,
+        median: 1496.1087703704834,
+        min: 1490.584135055542,
+        max: 1505.8784484863281,
+        std: 3.4553340533022667,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 1496.1087703704834,
+        },
+        {
+          percentile: 'p90',
+          value: 1500.9305477142334,
+        },
+        {
+          percentile: 'p95',
+          value: 1505.3200721740723,
+        },
+        {
+          percentile: 'p99',
+          value: 1505.8784484863281,
+        },
+      ],
+    },
+  },
+  {
+    requestsPerSecond: 28.075330129628725,
+    tpot: {
+      statistics: {
+        total: 3416,
+        mean: 126.08707076148656,
+        median: 125.30853256346687,
+        min: 23.034303907364134,
+        max: 138.08223756693178,
+        std: 3.508992115582193,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 125.30853256346687,
+        },
+        {
+          percentile: 'p90',
+          value: 129.21135009281218,
+        },
+        {
+          percentile: 'p95',
+          value: 129.52291770059554,
+        },
+        {
+          percentile: 'p99',
+          value: 132.21229490686636,
+        },
+      ],
+    },
+    ttft: {
+      statistics: {
+        total: 3416,
+        mean: 8585.486161415694,
+        median: 8965.316534042358,
+        min: 110.53991317749023,
+        max: 12575.379610061646,
+        std: 1929.5632525234505,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 8965.316534042358,
+        },
+        {
+          percentile: 'p90',
+          value: 9231.79316520691,
+        },
+        {
+          percentile: 'p95',
+          value: 9485.00108718872,
+        },
+        {
+          percentile: 'p99',
+          value: 12096.465587615967,
+        },
+      ],
+    },
+    throughput: {
+      statistics: {
+        total: 15981,
+        mean: 1795.4403743554367,
+        median: 670.1236619268253,
+        min: 0.0,
+        max: 838860.8,
+        std: 5196.545581836957,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 670.1236619268253,
+        },
+        {
+          percentile: 'p90',
+          value: 4068.1901066925316,
+        },
+        {
+          percentile: 'p95',
+          value: 6374.322188449848,
+        },
+        {
+          percentile: 'p99',
+          value: 16194.223938223939,
+        },
+      ],
+    },
+    timePerRequest: {
+      statistics: {
+        total: 3416,
+        mean: 16526.811318389147,
+        median: 17058.441638946533,
+        min: 1711.3444805145264,
+        max: 20646.55351638794,
+        std: 2054.9553770234484,
+      },
+      percentiles: [
+        {
+          percentile: 'p50',
+          value: 17058.441638946533,
+        },
+        {
+          percentile: 'p90',
+          value: 17143.84412765503,
+        },
+        {
+          percentile: 'p95',
+          value: 17248.060703277588,
+        },
+        {
+          percentile: 'p99',
+          value: 20116.52660369873,
+        },
+      ],
+    },
+  },
+];
diff --git a/tests/ui/unit/store/slices/slo.test.tsx b/tests/ui/unit/store/slices/slo.test.tsx
new file mode 100644
index 00000000..254d2818
--- /dev/null
+++ b/tests/ui/unit/store/slices/slo.test.tsx
@@ -0,0 +1,19 @@
+import { initialState } from '@/lib/store/slices/slo/slo.constants';
+import { SloState } from '@/lib/store/slices/slo/slo.interfaces';
+import sloReducer, { setSloData } from '@/lib/store/slices/slo/slo.slice';
+
+test('should handle initial state', () => {
+  expect(sloReducer(undefined, { type: '' })).toEqual(initialState);
+});
+
+test('should set slo data', () => {
+  const slo = {
+    enforcedPercentile: 'p50',
+  } as Partial<SloState>;
+
+  const fullSlos = {
+    ...initialState,
+    ...slo,
+  };
+  expect(sloReducer(undefined, setSloData(slo))).toEqual(fullSlos);
+});
diff --git a/tests/ui/unit/utils/interpolation.test.ts b/tests/ui/unit/utils/interpolation.test.ts
new file mode 100644
index 00000000..d0173548
--- /dev/null
+++ b/tests/ui/unit/utils/interpolation.test.ts
@@ -0,0 +1,50 @@
+import { createMonotoneSpline } from '@/lib/utils/interpolation';
+
+test('should reproduce points on a straight line', () => {
+  const xs = [0, 1, 2, 3];
+  const ys = [0, 2, 4, 6];
+  const interpolate = createMonotoneSpline(xs, ys);
+
+  [0, 0.5, 1.5, 3].forEach((x) => {
+    expect(interpolate(x)).toBeCloseTo(2 * x, 1e-6);
+  });
+});
+
+test('should return constant data on flat line', () => {
+  const xs = [0, 1, 2, 3];
+  const ys = [5, 5, 5, 5];
+  const interpolate = createMonotoneSpline(xs, ys);
+
+  [0, 1.5, 2].forEach((x) => {
+    expect(interpolate(x)).toBeCloseTo(5, 1e-6);
+  });
+});
+
+test('should hit each point precisely', () => {
+  const xs = [0, 2, 5];
+  const ys = [1, 4, 2];
+  const interpolate = createMonotoneSpline(xs, ys);
+
+  xs.forEach((x, i) => {
+    expect(interpolate(x)).toBeCloseTo(ys[i], 1e-6);
+  });
+});
+
+test('no local extremas added', () => {
+  // generate wavy line
+  const xs = Array.from(Array(50)).map((_, i) => (i + 1) / 10);
+  const ys = xs.map((x) => 1 + Math.sin((3 * Math.PI * x) / 10));
+  // check that each interpolated point is between its two bounding points
+  const interpolate = createMonotoneSpline(xs, ys);
+  for (let i = xs[0]; i < xs[xs.length - 1]; i += 0.01) {
+    const upperIndex = xs.findIndex((x) => x >= i);
+    if (upperIndex === 0) {
+      expect(interpolate(i)).toBeCloseTo(ys[0]);
+      continue;
+    }
+    const lowerY = ys[upperIndex - 1];
+    const upperY = ys[upperIndex];
+    expect(interpolate(i)).toBeLessThanOrEqual(Math.max(lowerY, upperY));
+    expect(interpolate(i)).toBeGreaterThanOrEqual(Math.min(lowerY, upperY));
+  }
+});
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 0169c3ba..409e7aba 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -1,15 +1,12 @@
 {
   "$schema": "https://json.schemastore.org/tsconfig",
   "compilerOptions": {
-    /* --- language / emit --- */
     "target": "ES2022",
     "lib": ["dom", "dom.iterable", "esnext"],
     "module": "esnext",
     "jsx": "preserve",
     "incremental": true,
     "noEmit": true,
-
-    /* --- module resolution --- */
     "moduleResolution": "node",
     "baseUrl": ".",
     "paths": {
@@ -20,8 +17,6 @@
     "esModuleInterop": true,
     "allowSyntheticDefaultImports": true,
     "plugins": [{ "name": "next" }],
-
-    /* --- correctness / style --- */
     "strict": true,
     "skipLibCheck": true,
     "forceConsistentCasingInFileNames": true,
diff --git a/tsconfig.json b/tsconfig.json
index 40711aeb..222ab61a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,22 +1,13 @@
 {
   "extends": "./tsconfig.base.json",
 
-  /* Extra options that apply only to Node-side files */
   "compilerOptions": {
-    "types": ["node"], // Buffer, process, __dirname, etc.
+    "types": ["node"],
     "allowJs": false,
     "isolatedModules": true
   },
 
-  /* Narrow include so we don’t compile the entire repo twice */
-  "include": [
-    "*.ts",
-    "*.cts",
-    "*.mts", // a couple of root-level scripts
-    "scripts/**/*",
-    "types/**/*"
-  ],
+  "include": ["*.ts", "*.cts", "*.mts", "scripts/**/*", "types/**/*"],
 
-  /* Don’t touch the Next.js source or tests from here */
   "exclude": ["src/ui", "tests", "node_modules"]
 }