diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..2102ca142a0bdd91d73f49cd43666d6181713b39
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,35 @@
+FROM node:18-alpine
+
+# Use the existing node user (usually UID 1000)
+# Set up environment variables for the node user
+ENV HOME=/home/node \
+    PATH=/home/node/.local/bin:$PATH
+
+# Create and set up app directory owned by node user
+# Go to user's home directory first to ensure it exists
+WORKDIR $HOME
+RUN mkdir -p $HOME/app && \
+    chown -R node:node $HOME/app && \
+    chmod -R 755 $HOME/app # Set initial permissions
+WORKDIR $HOME/app
+
+# Switch to the node user
+USER node
+
+# Copy package files (owned by node)
+COPY --chown=node:node package*.json ./
+
+# Install dependencies
+RUN npm install
+
+# Copy the entire viewer directory (owned by node)
+COPY --chown=node:node . .
+
+# Build the application
+RUN npm run build
+
+# Expose port
+EXPOSE 7860
+
+# Start the application
+CMD ["npm", "run", "preview", "--", "--port", "7860", "--host"]
diff --git a/README.md b/README.md
index e7b4c5152ca2f9f0ec158fa100b8e00aca3f12cd..083efe0f56d6974914d6e57ddb1fb175d68b8a91 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,84 @@
 ---
 title: LeLab
-emoji: 🌍
-colorFrom: pink
-colorTo: purple
+emoji: ⚡
+colorFrom: yellow
+colorTo: red
 sdk: docker
+app_port: 7860
 pinned: false
-license: mit
 short_description: Simple Interface to use LeRobot
 ---
 
-Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
+# Welcome to your Lovable project
+
+## Project info
+
+**URL**: https://lovable.dev/projects/58aee4a4-2f51-49a3-a4d9-56d3d66140b4
+
+## How can I edit this code?
+
+There are several ways of editing your application.
+
+**Use Lovable**
+
+Simply visit the [Lovable Project](https://lovable.dev/projects/58aee4a4-2f51-49a3-a4d9-56d3d66140b4) and start prompting.
+
+Changes made via Lovable will be committed automatically to this repo.
+
+**Use your preferred IDE**
+
+If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable.
+
+The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
+
+Follow these steps:
+
+```sh
+# Step 1: Clone the repository using the project's Git URL.
+git clone <YOUR_GIT_URL>
+
+# Step 2: Navigate to the project directory.
+cd <YOUR_PROJECT_NAME>
+
+# Step 3: Install the necessary dependencies.
+npm i
+
+# Step 4: Start the development server with auto-reloading and an instant preview.
+npm run dev
+```
+
+**Edit a file directly in GitHub**
+
+- Navigate to the desired file(s).
+- Click the "Edit" button (pencil icon) at the top right of the file view.
+- Make your changes and commit the changes.
+
+**Use GitHub Codespaces**
+
+- Navigate to the main page of your repository.
+- Click on the "Code" button (green button) near the top right.
+- Select the "Codespaces" tab.
+- Click on "New codespace" to launch a new Codespace environment.
+- Edit files directly within the Codespace and commit and push your changes once you're done.
+
+## What technologies are used for this project?
+
+This project is built with:
+
+- Vite
+- TypeScript
+- React
+- shadcn-ui
+- Tailwind CSS
+
+## How can I deploy this project?
+
+Simply open [Lovable](https://lovable.dev/projects/58aee4a4-2f51-49a3-a4d9-56d3d66140b4) and click on Share -> Publish.
+
+## Can I connect a custom domain to my Lovable project?
+
+Yes, you can!
+
+To connect a domain, navigate to Project > Settings > Domains and click Connect Domain.
+
+Read more here: [Setting up a custom domain](https://docs.lovable.dev/tips-tricks/custom-domain#step-by-step-guide)
diff --git a/bun.lockb b/bun.lockb
new file mode 100755
index 0000000000000000000000000000000000000000..b702660b4dc6db8e51738fda2a789e20b861a08b
--- /dev/null
+++ b/bun.lockb
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e9ef82613bb5c109eaa4b79a4432e742b40c028b8841047e5af1bd2941e15d91
+size 228980
diff --git a/components.json b/components.json
new file mode 100644
index 0000000000000000000000000000000000000000..f29e3f1610677762f3d97aaefc9ce40a9d410948
--- /dev/null
+++ b/components.json
@@ -0,0 +1,20 @@
+{
+  "$schema": "https://ui.shadcn.com/schema.json",
+  "style": "default",
+  "rsc": false,
+  "tsx": true,
+  "tailwind": {
+    "config": "tailwind.config.ts",
+    "css": "src/index.css",
+    "baseColor": "slate",
+    "cssVariables": true,
+    "prefix": ""
+  },
+  "aliases": {
+    "components": "@/components",
+    "utils": "@/lib/utils",
+    "ui": "@/components/ui",
+    "lib": "@/lib",
+    "hooks": "@/hooks"
+  }
+}
\ No newline at end of file
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..e67846f70fbbe40407fc84875913595ab31c4a47
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,29 @@
+import js from "@eslint/js";
+import globals from "globals";
+import reactHooks from "eslint-plugin-react-hooks";
+import reactRefresh from "eslint-plugin-react-refresh";
+import tseslint from "typescript-eslint";
+
+export default tseslint.config(
+  { ignores: ["dist"] },
+  {
+    extends: [js.configs.recommended, ...tseslint.configs.recommended],
+    files: ["**/*.{ts,tsx}"],
+    languageOptions: {
+      ecmaVersion: 2020,
+      globals: globals.browser,
+    },
+    plugins: {
+      "react-hooks": reactHooks,
+      "react-refresh": reactRefresh,
+    },
+    rules: {
+      ...reactHooks.configs.recommended.rules,
+      "react-refresh/only-export-components": [
+        "warn",
+        { allowConstantExport: true },
+      ],
+      "@typescript-eslint/no-unused-vars": "off",
+    },
+  }
+);
diff --git a/index.html b/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..cd01319749496c9c51cedb191b328208b3053b9f
--- /dev/null
+++ b/index.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>robot-insight-control-panel</title>
+    <meta name="description" content="Lovable Generated Project" />
+    <meta name="author" content="Lovable" />
+
+    <meta property="og:title" content="robot-insight-control-panel" />
+    <meta property="og:description" content="Lovable Generated Project" />
+    <meta property="og:type" content="website" />
+    <meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
+
+    <meta name="twitter:card" content="summary_large_image" />
+    <meta name="twitter:site" content="@lovable_dev" />
+    <meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
+  </head>
+
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.tsx"></script>
+  </body>
+</html>
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..d8b5178b4b101a99c3ae4fc58f8e090bf18f6940
--- /dev/null
+++ b/package.json
@@ -0,0 +1,88 @@
+{
+  "name": "vite_react_shadcn_ts",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "build:dev": "vite build --mode development",
+    "lint": "eslint .",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@hookform/resolvers": "^3.9.0",
+    "@radix-ui/react-accordion": "^1.2.0",
+    "@radix-ui/react-alert-dialog": "^1.1.1",
+    "@radix-ui/react-aspect-ratio": "^1.1.0",
+    "@radix-ui/react-avatar": "^1.1.0",
+    "@radix-ui/react-checkbox": "^1.1.1",
+    "@radix-ui/react-collapsible": "^1.1.0",
+    "@radix-ui/react-context-menu": "^2.2.1",
+    "@radix-ui/react-dialog": "^1.1.2",
+    "@radix-ui/react-dropdown-menu": "^2.1.1",
+    "@radix-ui/react-hover-card": "^1.1.1",
+    "@radix-ui/react-label": "^2.1.0",
+    "@radix-ui/react-menubar": "^1.1.1",
+    "@radix-ui/react-navigation-menu": "^1.2.0",
+    "@radix-ui/react-popover": "^1.1.1",
+    "@radix-ui/react-progress": "^1.1.0",
+    "@radix-ui/react-radio-group": "^1.2.0",
+    "@radix-ui/react-scroll-area": "^1.1.0",
+    "@radix-ui/react-select": "^2.1.1",
+    "@radix-ui/react-separator": "^1.1.0",
+    "@radix-ui/react-slider": "^1.2.0",
+    "@radix-ui/react-slot": "^1.1.0",
+    "@radix-ui/react-switch": "^1.1.0",
+    "@radix-ui/react-tabs": "^1.1.0",
+    "@radix-ui/react-toast": "^1.2.1",
+    "@radix-ui/react-toggle": "^1.1.0",
+    "@radix-ui/react-toggle-group": "^1.1.0",
+    "@radix-ui/react-tooltip": "^1.1.4",
+    "@react-three/drei": "^9.122.0",
+    "@react-three/fiber": "^8.18.0",
+    "@tanstack/react-query": "^5.56.2",
+    "class-variance-authority": "^0.7.1",
+    "clsx": "^2.1.1",
+    "cmdk": "^1.0.0",
+    "date-fns": "^3.6.0",
+    "embla-carousel-react": "^8.3.0",
+    "input-otp": "^1.2.4",
+    "jszip": "^3.10.1",
+    "lucide-react": "^0.462.0",
+    "next-themes": "^0.3.0",
+    "react": "^18.3.1",
+    "react-day-picker": "^8.10.1",
+    "react-dom": "^18.3.1",
+    "react-hook-form": "^7.53.0",
+    "react-resizable-panels": "^2.1.3",
+    "react-router-dom": "^6.26.2",
+    "recharts": "^2.12.7",
+    "sonner": "^1.5.0",
+    "tailwind-merge": "^2.5.2",
+    "tailwindcss-animate": "^1.0.7",
+    "three": "^0.177.0",
+    "urdf-loader": "^0.12.6",
+    "vaul": "^0.9.3",
+    "zod": "^3.23.8"
+  },
+  "devDependencies": {
+    "@eslint/js": "^9.9.0",
+    "@tailwindcss/typography": "^0.5.15",
+    "@types/node": "^22.5.5",
+    "@types/react": "^18.3.3",
+    "@types/react-dom": "^18.3.0",
+    "@vitejs/plugin-react-swc": "^3.5.0",
+    "autoprefixer": "^10.4.20",
+    "eslint": "^9.9.0",
+    "eslint-plugin-react-hooks": "^5.1.0-rc.0",
+    "eslint-plugin-react-refresh": "^0.4.9",
+    "globals": "^15.9.0",
+    "lovable-tagger": "^1.1.7",
+    "postcss": "^8.4.47",
+    "tailwindcss": "^3.4.11",
+    "typescript": "^5.5.3",
+    "typescript-eslint": "^8.0.1",
+    "vite": "^5.4.1"
+  }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..2e7af2b7f1a6f391da1631d93968a9d487ba977d
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+  plugins: {
+    tailwindcss: {},
+    autoprefixer: {},
+  },
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..dd5a12627d36db7eb9c19fa2f931ff1509f0323e
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png b/public/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png
new file mode 100644
index 0000000000000000000000000000000000000000..39595804e566c6c8298b4dfbb31bf7f314c2e562
Binary files /dev/null and b/public/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png differ
diff --git a/public/placeholder.svg b/public/placeholder.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e763910b27fdd9ac872f56baede51bc839402347
--- /dev/null
+++ b/public/placeholder.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
\ No newline at end of file
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6018e701fc7dd0317cda9eceea390524322e8a05
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,14 @@
+User-agent: Googlebot
+Allow: /
+
+User-agent: Bingbot
+Allow: /
+
+User-agent: Twitterbot
+Allow: /
+
+User-agent: facebookexternalhit
+Allow: /
+
+User-agent: *
+Allow: /
diff --git a/public/so-101-urdf/CMakeLists.txt b/public/so-101-urdf/CMakeLists.txt
new file mode 100755
index 0000000000000000000000000000000000000000..e33efd9b8bbbec3086795d7fb9f8fc8bcb1c2bd4
--- /dev/null
+++ b/public/so-101-urdf/CMakeLists.txt
@@ -0,0 +1,38 @@
+cmake_minimum_required(VERSION 3.10.2)
+
+project(so_arm_description)
+
+find_package(ament_cmake REQUIRED)
+find_package(urdf REQUIRED)
+
+# Install the mesh files from SO101/assets
+install(
+	DIRECTORY
+		SO101/assets/
+	DESTINATION
+		share/${PROJECT_NAME}/meshes
+	FILES_MATCHING PATTERN "*.stl"
+)
+
+# Install URDF files
+install(
+	DIRECTORY
+		urdf/
+	DESTINATION
+		share/${PROJECT_NAME}/urdf
+	FILES_MATCHING PATTERN "*.urdf"
+)
+
+# Install other directories
+install(
+	DIRECTORY
+		meshes
+		config
+		launch
+	DESTINATION
+		share/${PROJECT_NAME}
+	OPTIONAL
+)
+
+ament_package()
+
diff --git a/public/so-101-urdf/README.md b/public/so-101-urdf/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..dacc2193dbaf13bc1584bc208e28bf9ca32e9cbe
--- /dev/null
+++ b/public/so-101-urdf/README.md
@@ -0,0 +1,41 @@
+# SO-ARM ROS2 URDF Package
+
+A complete ROS2 package for the SO-ARM101 robotic arm with URDF description.
+
+## 📋 Overview
+
+This package provides a complete ROS2 implementation for the SO-ARM101 robotic arm, including:
+- URDF robot description with visual and collision meshes
+- RViz visualization with pre-configured displays
+- Launch files for easy robot visualization
+- Integration with MoveIt for motion planning
+- Joint state publishers for interactive control
+
+## 🎯 Original Source
+https://github.com/TheRobotStudio/SO-ARM100/tree/main/Simulation/SO101
+
+
+## 🚀 Key Improvements Made
+
+### 1. **Complete ROS2 Package Structure**
+- ✅ Proper `package.xml` with all necessary dependencies
+- ✅ CMakeLists.txt for ROS2 build system
+- ✅ Organized directory structure following ROS2 conventions
+
+### 2. **Enhanced Visualization**
+- ✅ Fixed mesh file paths for proper package integration
+
+
+### Build Instructions
+1. Clone this repository into your ROS2 workspace:
+   ```bash
+   cd ~/your_ros2_ws/src
+   git clone <your-repo-url> so_arm_description
+   ```
+
+2. Build the package:
+   ```bash
+   cd ~/your_ros2_ws
+   colcon build --packages-select so_arm_description
+   source install/setup.bash
+   ```
\ No newline at end of file
diff --git a/public/so-101-urdf/config/joint_names_so_arm_urdf.yaml b/public/so-101-urdf/config/joint_names_so_arm_urdf.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1f0c831755649001a8ba1a88e81e162b944a710c
--- /dev/null
+++ b/public/so-101-urdf/config/joint_names_so_arm_urdf.yaml
@@ -0,0 +1 @@
+controller_joint_names: ['', 'Rotation', 'Pitch', 'Elbow', 'Wrist_Pitch', 'Wrist_Roll', 'Jaw', ]
diff --git a/public/so-101-urdf/joints_properties.xml b/public/so-101-urdf/joints_properties.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0e2b2a5f6f2b16755821d1cd90d38884891b0e97
--- /dev/null
+++ b/public/so-101-urdf/joints_properties.xml
@@ -0,0 +1,12 @@
+<default>
+    <default class="sts3215">
+        <geom contype="0" conaffinity="0"/>
+        <joint damping="0.60" frictionloss="0.052" armature="0.028"/>
+        <position kp="17.8" kv="0.0" forcerange="-3.35 3.35"/>
+    </default>
+    <default class="backlash">
+        <!-- +/- 0.5° of backlash -->
+        <joint damping="0.01" frictionloss="0" armature="0.01" limited="true"
+            range="-0.008726646259971648 0.008726646259971648"/>
+    </default>
+</default>
\ No newline at end of file
diff --git a/public/so-101-urdf/meshes/base_motor_holder_so101_v1.stl b/public/so-101-urdf/meshes/base_motor_holder_so101_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..ac9c38076fe1036517faf0bccadea5de9dce0097
--- /dev/null
+++ b/public/so-101-urdf/meshes/base_motor_holder_so101_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8cd2f241037ea377af1191fffe0dd9d9006beea6dcc48543660ed41647072424
+size 1877084
diff --git a/public/so-101-urdf/meshes/base_so101_v2.stl b/public/so-101-urdf/meshes/base_so101_v2.stl
new file mode 100644
index 0000000000000000000000000000000000000000..503d30be06a91e401ba8d46ebb7e650866229550
--- /dev/null
+++ b/public/so-101-urdf/meshes/base_so101_v2.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bb12b7026575e1f70ccc7240051f9d943553bf34e5128537de6cd86fae33924d
+size 471584
diff --git a/public/so-101-urdf/meshes/motor_holder_so101_base_v1.stl b/public/so-101-urdf/meshes/motor_holder_so101_base_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..f8e3d75c027f28bb672f830ec6e0795567c1b7c9
--- /dev/null
+++ b/public/so-101-urdf/meshes/motor_holder_so101_base_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:31242ae6fb59d8b15c66617b88ad8e9bded62d57c35d11c0c43a70d2f4caa95b
+size 1129384
diff --git a/public/so-101-urdf/meshes/motor_holder_so101_wrist_v1.stl b/public/so-101-urdf/meshes/motor_holder_so101_wrist_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..e55b7194683c6ac301504c6f59137362f0ebd13e
--- /dev/null
+++ b/public/so-101-urdf/meshes/motor_holder_so101_wrist_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:887f92e6013cb64ea3a1ab8675e92da1e0beacfd5e001f972523540545e08011
+size 1052184
diff --git a/public/so-101-urdf/meshes/moving_jaw_so101_v1.stl b/public/so-101-urdf/meshes/moving_jaw_so101_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..eb17d253df8a84a88472ecc7f859d3b8b4d78884
--- /dev/null
+++ b/public/so-101-urdf/meshes/moving_jaw_so101_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:785a9dded2f474bc1d869e0d3dae398a3dcd9c0c345640040472210d2861fa9d
+size 1413584
diff --git a/public/so-101-urdf/meshes/rotation_pitch_so101_v1.stl b/public/so-101-urdf/meshes/rotation_pitch_so101_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..b536cb4100c1f204f8a9d9b182acdc4a3afbc66c
--- /dev/null
+++ b/public/so-101-urdf/meshes/rotation_pitch_so101_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9be900cc2a2bf718102841ef82ef8d2873842427648092c8ed2ca1e2ef4ffa34
+size 883684
diff --git a/public/so-101-urdf/meshes/sts3215_03a_no_horn_v1.stl b/public/so-101-urdf/meshes/sts3215_03a_no_horn_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..18e9335673f6d46ea8fd0a03a791516203eb6f4c
--- /dev/null
+++ b/public/so-101-urdf/meshes/sts3215_03a_no_horn_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:75ef3781b752e4065891aea855e34dc161a38a549549cd0970cedd07eae6f887
+size 865884
diff --git a/public/so-101-urdf/meshes/sts3215_03a_v1.stl b/public/so-101-urdf/meshes/sts3215_03a_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..a14c57b9033b82f1daa38f45e7e3c91343702df4
--- /dev/null
+++ b/public/so-101-urdf/meshes/sts3215_03a_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a37c871fb502483ab96c256baf457d36f2e97afc9205313d9c5ab275ef941cd0
+size 954084
diff --git a/public/so-101-urdf/meshes/under_arm_so101_v1.stl b/public/so-101-urdf/meshes/under_arm_so101_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..47b611ef939e452f791ae749756f717317922cfd
--- /dev/null
+++ b/public/so-101-urdf/meshes/under_arm_so101_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d01d1f2de365651dcad9d6669e94ff87ff7652b5bb2d10752a66a456a86dbc71
+size 1975884
diff --git a/public/so-101-urdf/meshes/upper_arm_so101_v1.stl b/public/so-101-urdf/meshes/upper_arm_so101_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..8832740f9540065e6006907a9a826b01f96cd122
--- /dev/null
+++ b/public/so-101-urdf/meshes/upper_arm_so101_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:475056e03a17e71919b82fd88ab9a0b898ab50164f2a7943652a6b2941bb2d4f
+size 1303484
diff --git a/public/so-101-urdf/meshes/waveshare_mounting_plate_so101_v2.stl b/public/so-101-urdf/meshes/waveshare_mounting_plate_so101_v2.stl
new file mode 100644
index 0000000000000000000000000000000000000000..e0d90d5b6554ab8ca91928fa6521a675089beb12
--- /dev/null
+++ b/public/so-101-urdf/meshes/waveshare_mounting_plate_so101_v2.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e197e24005a07d01bbc06a8c42311664eaeda415bf859f68fa247884d0f1a6e9
+size 62784
diff --git a/public/so-101-urdf/meshes/wrist_roll_follower_so101_v1.stl b/public/so-101-urdf/meshes/wrist_roll_follower_so101_v1.stl
new file mode 100644
index 0000000000000000000000000000000000000000..9a5fa8fe2d7d8e59cd4a30d4dba0ca337513ab4a
--- /dev/null
+++ b/public/so-101-urdf/meshes/wrist_roll_follower_so101_v1.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b17b410a12d64ec39554abc3e8054d8a97384b2dc4a8d95a5ecb2a93670f5f4
+size 1439884
diff --git a/public/so-101-urdf/meshes/wrist_roll_pitch_so101_v2.stl b/public/so-101-urdf/meshes/wrist_roll_pitch_so101_v2.stl
new file mode 100644
index 0000000000000000000000000000000000000000..2f531712f88ec01d09824ee8e27c791a4616516f
--- /dev/null
+++ b/public/so-101-urdf/meshes/wrist_roll_pitch_so101_v2.stl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6c7ec5525b4d8b9e397a30ab4bb0037156a5d5f38a4adf2c7d943d6c56eda5ae
+size 2699784
diff --git a/public/so-101-urdf/package.xml b/public/so-101-urdf/package.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ff52e481f026862b7094d3746579bc8a7ad9672d
--- /dev/null
+++ b/public/so-101-urdf/package.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
+<package format="3">
+  <name>so_arm_description</name>
+  <version>1.0.0</version>
+  <description>SO-ARM101 URDF Resources</description>
+
+  <author email="contact@lycheeai-hub.com">LycheeAI</author>
+
+  <maintainer email="contact@lycheeai-hub.com">LycheeAI</maintainer>
+
+  <license>BSD</license>
+  
+  <buildtool_depend>ament_cmake</buildtool_depend>
+  
+  <depend>urdf</depend>
+  <exec_depend>robot_state_publisher</exec_depend>
+  <exec_depend>joint_state_publisher</exec_depend>
+  <exec_depend>joint_state_publisher_gui</exec_depend>
+  <exec_depend>rviz2</exec_depend>
+  <exec_depend>xacro</exec_depend>
+  
+  <export>
+    <build_type>ament_cmake</build_type>
+  </export>
+</package> 
\ No newline at end of file
diff --git a/public/so-101-urdf/urdf/so101_new_calib.urdf b/public/so-101-urdf/urdf/so101_new_calib.urdf
new file mode 100644
index 0000000000000000000000000000000000000000..557c565bd3d227be739bc81de64d2f9ff3f80699
--- /dev/null
+++ b/public/so-101-urdf/urdf/so101_new_calib.urdf
@@ -0,0 +1,435 @@
+<?xml version='1.0' encoding='utf-8'?>
+<!-- Generated using onshape-to-robot -->
+<!-- Onshape https://cad.onshape.com/documents/7715cc284bb430fe6dab4ffd/w/4fd0791b683777b02f8d975a/e/826c553ede3b7592eb9ca800 -->
+<robot name="so101_new_calib">
+
+  <!-- Materials -->
+  <material name="3d_printed">
+    <color rgba="1.0 0.82 0.12 1.0"/>
+  </material>
+  <material name="sts3215">
+    <color rgba="0.1 0.1 0.1 1.0"/>
+  </material>
+
+  <!-- Link base -->
+  <link name="base">
+    <inertial>
+      <origin xyz="0.020739 0.00204287 0.065966" rpy="0 0 0"/>
+      <mass value="0.147"/>
+      <inertia ixx="0.000136117" ixy="4.59787e-07" ixz="9.75275e-08" iyy="0.000114686" iyz="-4.97151e-06" izz="0.000130364"/>
+    </inertial>
+    <!-- Part base_motor_holder_so101_v1 -->
+    <visual>
+      <origin xyz="0.0206915 0.0221255 0.0300817" rpy="1.5708 -1.23909e-16 2.33147e-15"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/base_motor_holder_so101_v1.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="0.0206915 0.0221255 0.0300817" rpy="1.5708 -1.23909e-16 2.33147e-15"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/base_motor_holder_so101_v1.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part base_so101_v2 -->
+    <visual>
+      <origin xyz="0.0207909 0.0221255 0.0300817" rpy="1.5708 -0 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/base_so101_v2.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="0.0207909 0.0221255 0.0300817" rpy="1.5708 -0 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/base_so101_v2.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part sts3215_03a_v1 -->
+    <visual>
+      <origin xyz="0.0207909 -0.0105745 0.0761817" rpy="-2.20282e-15 2.77556e-17 -1.5708"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+      <material name="sts3215"/>
+    </visual>
+    <collision>
+      <origin xyz="0.0207909 -0.0105745 0.0761817" rpy="-2.20282e-15 2.77556e-17 -1.5708"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part waveshare_mounting_plate_so101_v2 -->
+    <visual>
+      <origin xyz="0.0205915 0.0467435 0.0798817" rpy="1.5708 -1.21716e-14 2.33147e-15"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/waveshare_mounting_plate_so101_v2.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="0.0205915 0.0467435 0.0798817" rpy="1.5708 -1.21716e-14 2.33147e-15"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/waveshare_mounting_plate_so101_v2.stl"/>
+      </geometry>
+    </collision>
+  </link>
+
+  <!-- Link shoulder -->
+  <link name="shoulder">
+    <inertial>
+      <origin xyz="-0.0307604 -1.66727e-05 -0.0252713" rpy="0 0 0"/>
+      <mass value="0.100006"/>
+      <inertia ixx="8.3759e-05" ixy="7.55525e-08" ixz="-1.16342e-06" iyy="8.10403e-05" iyz="1.54663e-07" izz="2.39783e-05"/>
+    </inertial>
+    <!-- Part sts3215_03a_v1_2 -->
+    <visual>
+      <origin xyz="-0.0303992 0.000422241 -0.0417" rpy="1.5708 1.5708 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+      <material name="sts3215"/>
+    </visual>
+    <collision>
+      <origin xyz="-0.0303992 0.000422241 -0.0417" rpy="1.5708 1.5708 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part motor_holder_so101_base_v1 -->
+    <visual>
+      <origin xyz="-0.0675992 -0.000177759 0.0158499" rpy="1.5708 -1.5708 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/motor_holder_so101_base_v1.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="-0.0675992 -0.000177759 0.0158499" rpy="1.5708 -1.5708 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/motor_holder_so101_base_v1.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part rotation_pitch_so101_v1 -->
+    <visual>
+      <origin xyz="0.0122008 2.22413e-05 0.0464" rpy="-1.5708 -0 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/rotation_pitch_so101_v1.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="0.0122008 2.22413e-05 0.0464" rpy="-1.5708 -0 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/rotation_pitch_so101_v1.stl"/>
+      </geometry>
+    </collision>
+  </link>
+
+  <!-- Link upper_arm -->
+  <link name="upper_arm">
+    <inertial>
+      <origin xyz="-0.0898471 -0.00838224 0.0184089" rpy="0 0 0"/>
+      <mass value="0.103"/>
+      <inertia ixx="4.08002e-05" ixy="-1.97819e-05" ixz="-4.03016e-08" iyy="0.000147318" iyz="8.97326e-09" izz="0.000142487"/>
+    </inertial>
+    <!-- Part sts3215_03a_v1_3 -->
+    <visual>
+      <origin xyz="-0.11257 -0.0155 0.0187" rpy="-3.14159 -6.8695e-16 -1.5708"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+      <material name="sts3215"/>
+    </visual>
+    <collision>
+      <origin xyz="-0.11257 -0.0155 0.0187" rpy="-3.14159 -6.8695e-16 -1.5708"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part upper_arm_so101_v1 -->
+    <visual>
+      <origin xyz="-0.065085 0.012 0.0182" rpy="3.14159 -9.35612e-32 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/upper_arm_so101_v1.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="-0.065085 0.012 0.0182" rpy="3.14159 -9.35612e-32 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/upper_arm_so101_v1.stl"/>
+      </geometry>
+    </collision>
+  </link>
+
+  <!-- Link lower_arm -->
+  <link name="lower_arm">
+    <inertial>
+      <origin xyz="-0.0980701 0.00324376 0.0182831" rpy="0 0 0"/>
+      <mass value="0.104"/>
+      <inertia ixx="2.87438e-05" ixy="7.41152e-06" ixz="1.26409e-06" iyy="0.000159844" iyz="-4.90188e-08" izz="0.00014529"/>
+    </inertial>
+    <!-- Part under_arm_so101_v1 -->
+    <visual>
+      <origin xyz="-0.0648499 -0.032 0.0182" rpy="-3.14159 -0 3.9443e-31"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/under_arm_so101_v1.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="-0.0648499 -0.032 0.0182" rpy="-3.14159 -0 3.9443e-31"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/under_arm_so101_v1.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part motor_holder_so101_wrist_v1 -->
+    <visual>
+      <origin xyz="-0.0648499 -0.032 0.018" rpy="-3.14159 4.73317e-30 7.88861e-31"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/motor_holder_so101_wrist_v1.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="-0.0648499 -0.032 0.018" rpy="-3.14159 4.73317e-30 7.88861e-31"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/motor_holder_so101_wrist_v1.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part sts3215_03a_v1_4 -->
+    <visual>
+      <origin xyz="-0.1224 0.0052 0.0187" rpy="-3.14159 -3.58047e-15 -3.14159"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+      <material name="sts3215"/>
+    </visual>
+    <collision>
+      <origin xyz="-0.1224 0.0052 0.0187" rpy="-3.14159 -3.58047e-15 -3.14159"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+    </collision>
+  </link>
+
+  <!-- Link wrist -->
+  <link name="wrist">
+    <inertial>
+      <origin xyz="-0.000103312 -0.0386143 0.0281156" rpy="0 0 0"/>
+      <mass value="0.079"/>
+      <inertia ixx="3.68263e-05" ixy="1.7893e-08" ixz="-5.28128e-08" iyy="2.5391e-05" iyz="3.6412e-06" izz="2.1e-05"/>
+    </inertial>
+    <!-- Part sts3215_03a_no_horn_v1 -->
+    <visual>
+      <origin xyz="5.55112e-17 -0.0424 0.0306" rpy="1.5708 1.5708 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_no_horn_v1.stl"/>
+      </geometry>
+      <material name="sts3215"/>
+    </visual>
+    <collision>
+      <origin xyz="5.55112e-17 -0.0424 0.0306" rpy="1.5708 1.5708 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_no_horn_v1.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part wrist_roll_pitch_so101_v2 -->
+    <visual>
+      <origin xyz="0 -0.028 0.0181" rpy="-1.5708 -1.5708 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/wrist_roll_pitch_so101_v2.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="0 -0.028 0.0181" rpy="-1.5708 -1.5708 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/wrist_roll_pitch_so101_v2.stl"/>
+      </geometry>
+    </collision>
+  </link>
+
+  <!-- Link gripper -->
+  <link name="gripper">
+    <inertial>
+      <origin xyz="0.000213627 0.000245138 -0.025187" rpy="0 0 0"/>
+      <mass value="0.087"/>
+      <inertia ixx="2.75087e-05" ixy="-3.35241e-07" ixz="-5.7352e-06" iyy="4.33657e-05" iyz="-5.17847e-08" izz="3.45059e-05"/>
+    </inertial>
+    <!-- Part sts3215_03a_v1_5 -->
+    <visual>
+      <origin xyz="0.0077 0.0001 -0.0234" rpy="-1.5708 -5.55112e-17 -1.38213e-14"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+      <material name="sts3215"/>
+    </visual>
+    <collision>
+      <origin xyz="0.0077 0.0001 -0.0234" rpy="-1.5708 -5.55112e-17 -1.38213e-14"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/sts3215_03a_v1.stl"/>
+      </geometry>
+    </collision>
+    <!-- Part wrist_roll_follower_so101_v1 -->
+    <visual>
+      <origin xyz="5.55112e-17 -0.000218214 0.000949706" rpy="-3.14159 -5.55112e-17 -9.17912e-24"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/wrist_roll_follower_so101_v1.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="5.55112e-17 -0.000218214 0.000949706" rpy="-3.14159 -5.55112e-17 -9.17912e-24"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/wrist_roll_follower_so101_v1.stl"/>
+      </geometry>
+    </collision>
+  </link>
+
+  <!-- Link jaw -->
+  <link name="jaw">
+    <inertial>
+      <origin xyz="-0.00157495 -0.0300244 0.0192755" rpy="0 0 0"/>
+      <mass value="0.012"/>
+      <inertia ixx="6.61427e-06" ixy="-3.19807e-07" ixz="-5.90717e-09" iyy="1.89032e-06" iyz="-1.09945e-07" izz="5.28738e-06"/>
+    </inertial>
+    <!-- Part moving_jaw_so101_v1 -->
+    <visual>
+      <origin xyz="-5.55112e-17 -1.94746e-17 0.0189" rpy="9.53145e-17 -4.66093e-24 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/moving_jaw_so101_v1.stl"/>
+      </geometry>
+      <material name="3d_printed"/>
+    </visual>
+    <collision>
+      <origin xyz="-5.55112e-17 -1.94746e-17 0.0189" rpy="9.53145e-17 -4.66093e-24 0"/>
+      <geometry>
+        <mesh filename="package://so_arm_description/meshes/moving_jaw_so101_v1.stl"/>
+      </geometry>
+    </collision>
+  </link>
+
+  <!-- Joint from gripper to jaw -->
+  <joint name="Jaw" type="revolute">
+    <origin xyz="0.0202 0.0188 -0.0234" rpy="1.5708 -5.14108e-17 -1.38655e-14"/>
+    <parent link="gripper"/>
+    <child link="jaw"/>
+    <axis xyz="0 0 1"/>
+    <limit effort="10" velocity="10" lower="-0.174533" upper="1.74533"/>
+  </joint>
+
+  <transmission name="6_trans">
+    <type>transmission_interface/SimpleTransmission</type>
+    <joint name="Jaw">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+    </joint>
+    <actuator name="motor6">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+      <mechanicalReduction>1</mechanicalReduction>
+    </actuator>
+  </transmission>
+
+  <!-- Joint from wrist to gripper -->
+  <joint name="Wrist_Roll" type="revolute">
+    <origin xyz="0 -0.0611 0.0181" rpy="1.5708 -9.38083e-08 3.14159"/>
+    <parent link="wrist"/>
+    <child link="gripper"/>
+    <axis xyz="0 0 1"/>
+    <limit effort="10" velocity="10" lower="-2.79253" upper="2.79253"/>
+  </joint>
+
+  <transmission name="5_trans">
+    <type>transmission_interface/SimpleTransmission</type>
+    <joint name="Wrist_Roll">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+    </joint>
+    <actuator name="motor5">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+      <mechanicalReduction>1</mechanicalReduction>
+    </actuator>
+  </transmission>
+
+  <!-- Joint from lower_arm to wrist -->
+  <joint name="Wrist_Pitch" type="revolute">
+    <origin xyz="-0.1349 0.0052 1.65232e-16" rpy="3.2474e-15 2.86219e-15 -1.5708"/>
+    <parent link="lower_arm"/>
+    <child link="wrist"/>
+    <axis xyz="0 0 1"/>
+    <limit effort="10" velocity="10" lower="-1.65806" upper="1.65806"/>
+  </joint>
+
+  <transmission name="4_trans">
+    <type>transmission_interface/SimpleTransmission</type>
+    <joint name="Wrist_Pitch">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+    </joint>
+    <actuator name="motor4">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+      <mechanicalReduction>1</mechanicalReduction>
+    </actuator>
+  </transmission>
+
+  <!-- Joint from upper_arm to lower_arm -->
+  <joint name="Elbow" type="revolute">
+    <origin xyz="-0.11257 -0.028 2.46331e-16" rpy="-1.22818e-15 5.75928e-16 1.5708"/>
+    <parent link="upper_arm"/>
+    <child link="lower_arm"/>
+    <axis xyz="0 0 1"/>
+    <limit effort="10" velocity="10" lower="-1.74533" upper="1.5708"/>
+  </joint>
+
+  <transmission name="3_trans">
+    <type>transmission_interface/SimpleTransmission</type>
+    <joint name="Elbow">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+    </joint>
+    <actuator name="motor3">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+      <mechanicalReduction>1</mechanicalReduction>
+    </actuator>
+  </transmission>
+
+  <!-- Joint from shoulder to upper_arm -->
+  <joint name="Pitch" type="revolute">
+    <origin xyz="-0.0303992 -0.0182778 -0.0542" rpy="-1.5708 -1.5708 0"/>
+    <parent link="shoulder"/>
+    <child link="upper_arm"/>
+    <axis xyz="0 0 1"/>
+    <limit effort="10" velocity="10" lower="-1.74533" upper="1.74533"/>
+  </joint>
+
+  <transmission name="2_trans">
+    <type>transmission_interface/SimpleTransmission</type>
+    <joint name="Pitch">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+    </joint>
+    <actuator name="motor2">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+      <mechanicalReduction>1</mechanicalReduction>
+    </actuator>
+  </transmission>
+
+  <!-- Joint from base to shoulder -->
+  <joint name="Rotation" type="revolute">
+    <origin xyz="0.0207909 -0.0230745 0.0948817" rpy="-3.14159 6.03684e-16 1.5708"/>
+    <parent link="base"/>
+    <child link="shoulder"/>
+    <axis xyz="0 0 1"/>
+    <limit effort="10" velocity="10" lower="-1.91986" upper="1.91986"/>
+  </joint>
+
+  <transmission name="1_trans">
+    <type>transmission_interface/SimpleTransmission</type>
+    <joint name="Rotation">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+    </joint>
+    <actuator name="motor1">
+      <hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
+      <mechanicalReduction>1</mechanicalReduction>
+    </actuator>
+  </transmission>
+
+</robot>
\ No newline at end of file
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..40b878db5b1c97fc77049537a71bb2e249abe5dc
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/src/App.css b/src/App.css
new file mode 100644
index 0000000000000000000000000000000000000000..b9d355df2a5956b526c004531b7b0ffe412461e0
--- /dev/null
+++ b/src/App.css
@@ -0,0 +1,42 @@
+#root {
+  max-width: 1280px;
+  margin: 0 auto;
+  padding: 2rem;
+  text-align: center;
+}
+
+.logo {
+  height: 6em;
+  padding: 1.5em;
+  will-change: filter;
+  transition: filter 300ms;
+}
+.logo:hover {
+  filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.react:hover {
+  filter: drop-shadow(0 0 2em #61dafbaa);
+}
+
+@keyframes logo-spin {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+@media (prefers-reduced-motion: no-preference) {
+  a:nth-of-type(2) .logo {
+    animation: logo-spin infinite 20s linear;
+  }
+}
+
+.card {
+  padding: 2em;
+}
+
+.read-the-docs {
+  color: #888;
+}
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ea561c8cb3d1db7b0da5bfb69ca57c7f4653a715
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,40 @@
+import { Toaster } from "@/components/ui/toaster";
+import { Toaster as Sonner } from "@/components/ui/sonner";
+import { TooltipProvider } from "@/components/ui/tooltip";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { BrowserRouter, Routes, Route } from "react-router-dom";
+import Index from "./pages/Index";
+import NotFound from "./pages/NotFound";
+import Landing from "./pages/Landing";
+import TeleoperationPage from "./pages/Teleoperation";
+import Recording from "./pages/Recording";
+import Calibration from "./pages/Calibration";
+import Training from "./pages/Training";
+import { UrdfProvider } from "./contexts/UrdfContext";
+
+const queryClient = new QueryClient();
+
+const App = () => (
+  <QueryClientProvider client={queryClient}>
+    <TooltipProvider>
+      <Toaster />
+      <Sonner />
+      <UrdfProvider>
+        <BrowserRouter>
+          <Routes>
+            <Route path="/" element={<Landing />} />
+            <Route path="/control" element={<Index />} />
+            <Route path="/teleoperation" element={<TeleoperationPage />} />
+            <Route path="/recording" element={<Recording />} />
+            <Route path="/calibration" element={<Calibration />} />
+            <Route path="/training" element={<Training />} />
+            {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
+            <Route path="*" element={<NotFound />} />
+          </Routes>
+        </BrowserRouter>
+      </UrdfProvider>
+    </TooltipProvider>
+  </QueryClientProvider>
+);
+
+export default App;
diff --git a/src/components/UrdfProcessorInitializer.tsx b/src/components/UrdfProcessorInitializer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..29fec7193ab0aa64fa934379cfc2c07c0f0c40e0
--- /dev/null
+++ b/src/components/UrdfProcessorInitializer.tsx
@@ -0,0 +1,40 @@
+import React, { useEffect, useMemo } from "react";
+import { useUrdf } from "@/hooks/useUrdf";
+
+/**
+ * Component that only handles initializing the URDF processor
+ * This component doesn't render anything visible, just initializes the processor
+ */
+const UrdfProcessorInitializer: React.FC = () => {
+  const { registerUrdfProcessor } = useUrdf();
+
+  // Create the URDF processor
+  const urdfProcessor = useMemo(
+    () => ({
+      loadUrdf: (urdfPath: string) => {
+        console.log("📂 URDF path set:", urdfPath);
+        // This will be handled by the actual viewer component
+        return urdfPath;
+      },
+      setUrlModifierFunc: (func: (url: string) => string) => {
+        console.log("🔗 URL modifier function set");
+        return func;
+      },
+      getPackage: () => {
+        return "";
+      },
+    }),
+    []
+  );
+
+  // Register the URDF processor with the context
+  useEffect(() => {
+    console.log("🔧 Registering URDF processor");
+    registerUrdfProcessor(urdfProcessor);
+  }, [registerUrdfProcessor, urdfProcessor]);
+
+  // This component doesn't render anything
+  return null;
+};
+
+export default UrdfProcessorInitializer;
diff --git a/src/components/UrdfViewer.tsx b/src/components/UrdfViewer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..99c21dd90e69f4703c0479e7c048481fbc2a4936
--- /dev/null
+++ b/src/components/UrdfViewer.tsx
@@ -0,0 +1,236 @@
+import React, {
+  useEffect,
+  useRef,
+  useState,
+  useMemo,
+  useCallback,
+} from "react";
+import { cn } from "@/lib/utils";
+
+import URDFManipulator from "urdf-loader/src/urdf-manipulator-element.js";
+import { useUrdf } from "@/hooks/useUrdf";
+import { useRealTimeJoints } from "@/hooks/useRealTimeJoints";
+import {
+  createUrdfViewer,
+  setupMeshLoader,
+  setupJointHighlighting,
+  setupModelLoading,
+  URDFViewerElement,
+} from "@/lib/urdfViewerHelpers";
+
+// Register the URDFManipulator as a custom element if it hasn't been already
+if (typeof window !== "undefined" && !customElements.get("urdf-viewer")) {
+  customElements.define("urdf-viewer", URDFManipulator);
+}
+
+// Extend the interface for the URDF viewer element to include background property
+interface UrdfViewerElement extends HTMLElement {
+  background?: string;
+  setJointValue?: (jointName: string, value: number) => void;
+}
+
+const UrdfViewer: React.FC = () => {
+  const containerRef = useRef<HTMLDivElement>(null);
+  const [highlightedJoint, setHighlightedJoint] = useState<string | null>(null);
+  const { registerUrdfProcessor, alternativeUrdfModels, isDefaultModel } =
+    useUrdf();
+
+  // Add state for animation control
+  useState<boolean>(isDefaultModel);
+  const cleanupAnimationRef = useRef<(() => void) | null>(null);
+  const viewerRef = useRef<URDFViewerElement | null>(null);
+  const hasInitializedRef = useRef<boolean>(false);
+
+  // Real-time joint updates via WebSocket
+  const { isConnected: isWebSocketConnected } = useRealTimeJoints({
+    viewerRef,
+    enabled: isDefaultModel, // Only enable WebSocket for default model
+  });
+
+  // Add state for custom URDF path
+  const [customUrdfPath, setCustomUrdfPath] = useState<string | null>(null);
+  const [urlModifierFunc, setUrlModifierFunc] = useState<
+    ((url: string) => string) | null
+  >(null);
+
+  const packageRef = useRef<string>("");
+
+  // Implement UrdfProcessor interface for drag and drop
+  const urdfProcessor = useMemo(
+    () => ({
+      loadUrdf: (urdfPath: string) => {
+        setCustomUrdfPath(urdfPath);
+      },
+      setUrlModifierFunc: (func: (url: string) => string) => {
+        setUrlModifierFunc(() => func);
+      },
+      getPackage: () => {
+        return packageRef.current;
+      },
+    }),
+    []
+  );
+
+  // Register the URDF processor with the global drag and drop context
+  useEffect(() => {
+    registerUrdfProcessor(urdfProcessor);
+  }, [registerUrdfProcessor, urdfProcessor]);
+
+  // Create URL modifier function for default model
+  const defaultUrlModifier = useCallback((url: string) => {
+    console.log(`🔗 defaultUrlModifier called with: ${url}`);
+
+    // Handle various package:// URL formats for the default SO-101 model
+    if (url.startsWith("package://so_arm_description/meshes/")) {
+      const modifiedUrl = url.replace(
+        "package://so_arm_description/meshes/",
+        "/so-101-urdf/meshes/"
+      );
+      console.log(`🔗 Modified URL (package): ${modifiedUrl}`);
+      return modifiedUrl;
+    }
+
+    // Handle case where package path might be partially resolved
+    if (url.includes("so_arm_description/meshes/")) {
+      const modifiedUrl = url.replace(
+        /.*so_arm_description\/meshes\//,
+        "/so-101-urdf/meshes/"
+      );
+      console.log(`🔗 Modified URL (partial): ${modifiedUrl}`);
+      return modifiedUrl;
+    }
+
+    // Handle the specific problematic path pattern we're seeing in logs
+    if (url.includes("/so-101-urdf/so_arm_description/meshes/")) {
+      const modifiedUrl = url.replace(
+        "/so-101-urdf/so_arm_description/meshes/",
+        "/so-101-urdf/meshes/"
+      );
+      console.log(`🔗 Modified URL (problematic path): ${modifiedUrl}`);
+      return modifiedUrl;
+    }
+
+    // Handle relative paths that might need mesh folder prefix
+    if (
+      url.endsWith(".stl") &&
+      !url.startsWith("/") &&
+      !url.startsWith("http")
+    ) {
+      const modifiedUrl = `/so-101-urdf/meshes/${url}`;
+      console.log(`🔗 Modified URL (relative): ${modifiedUrl}`);
+      return modifiedUrl;
+    }
+
+    console.log(`🔗 Unmodified URL: ${url}`);
+    return url;
+  }, []);
+
+  // Main effect to create and setup the viewer only once
+  useEffect(() => {
+    if (!containerRef.current) return;
+
+    // Create and configure the URDF viewer element
+    const viewer = createUrdfViewer(containerRef.current, true);
+    viewerRef.current = viewer; // Store reference to the viewer
+
+    // Setup mesh loading function with appropriate URL modifier
+    const activeUrlModifier = isDefaultModel
+      ? defaultUrlModifier
+      : urlModifierFunc;
+    setupMeshLoader(viewer, activeUrlModifier);
+
+    // Determine which URDF to load - fixed path to match the actual available file
+    const urdfPath = isDefaultModel
+      ? "/so-101-urdf/urdf/so101_new_calib.urdf"
+      : customUrdfPath || "";
+
+    // Set the package path for the default model
+    if (isDefaultModel) {
+      packageRef.current = "/"; // Set to root so we can handle full path resolution in URL modifier
+    }
+
+    // Setup model loading if a path is available
+    let cleanupModelLoading = () => {};
+    if (urdfPath) {
+      cleanupModelLoading = setupModelLoading(
+        viewer,
+        urdfPath,
+        packageRef.current,
+        setCustomUrdfPath,
+        alternativeUrdfModels
+      );
+    }
+
+    // Setup joint highlighting
+    const cleanupJointHighlighting = setupJointHighlighting(
+      viewer,
+      setHighlightedJoint
+    );
+
+    // Setup animation event handler for the default model or when hasAnimation is true
+    const onModelProcessed = () => {
+      hasInitializedRef.current = true;
+      if ("setJointValue" in viewer) {
+        // Clear any existing animation
+        if (cleanupAnimationRef.current) {
+          cleanupAnimationRef.current();
+          cleanupAnimationRef.current = null;
+        }
+      }
+    };
+
+    viewer.addEventListener("urdf-processed", onModelProcessed);
+
+    // Return cleanup function
+    return () => {
+      if (cleanupAnimationRef.current) {
+        cleanupAnimationRef.current();
+        cleanupAnimationRef.current = null;
+      }
+      hasInitializedRef.current = false;
+      cleanupJointHighlighting();
+      cleanupModelLoading();
+      viewer.removeEventListener("urdf-processed", onModelProcessed);
+    };
+  }, [isDefaultModel, customUrdfPath, urlModifierFunc, defaultUrlModifier]);
+
+  return (
+    <div
+      className={cn(
+        "w-full h-full transition-all duration-300 ease-in-out relative",
+        "bg-gradient-to-br from-gray-900 to-gray-800"
+      )}
+    >
+      <div ref={containerRef} className="w-full h-full" />
+
+      {/* Joint highlight indicator */}
+      {highlightedJoint && (
+        <div className="absolute bottom-4 right-4 bg-black/70 text-white px-3 py-2 rounded-md text-sm font-mono z-10">
+          Joint: {highlightedJoint}
+        </div>
+      )}
+
+      {/* WebSocket connection status */}
+      {isDefaultModel && (
+        <div className="absolute top-4 right-4 z-10">
+          <div
+            className={`flex items-center gap-2 px-3 py-2 rounded-md text-sm font-mono ${
+              isWebSocketConnected
+                ? "bg-green-900/70 text-green-300"
+                : "bg-red-900/70 text-red-300"
+            }`}
+          >
+            <div
+              className={`w-2 h-2 rounded-full ${
+                isWebSocketConnected ? "bg-green-400" : "bg-red-400"
+              }`}
+            />
+            {isWebSocketConnected ? "Live Robot Data" : "Disconnected"}
+          </div>
+        </div>
+      )}
+    </div>
+  );
+};
+
+export default UrdfViewer;
diff --git a/src/components/control/CommandBar.tsx b/src/components/control/CommandBar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ea5032d6d0330981e08f39b4ce460da6d1918024
--- /dev/null
+++ b/src/components/control/CommandBar.tsx
@@ -0,0 +1,81 @@
+
+import React from 'react';
+import { Mic, MicOff, Send, Camera } from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+
+interface CommandBarProps {
+  command: string;
+  setCommand: (command: string) => void;
+  handleSendCommand: () => void;
+  isVoiceActive: boolean;
+  setIsVoiceActive: (isActive: boolean) => void;
+  showCamera: boolean;
+  setShowCamera: (show: boolean) => void;
+  handleEndSession: () => void;
+}
+
+const CommandBar: React.FC<CommandBarProps> = ({
+  command,
+  setCommand,
+  handleSendCommand,
+  isVoiceActive,
+  setIsVoiceActive,
+  showCamera,
+  setShowCamera,
+  handleEndSession
+}) => {
+  return (
+    <div className="bg-gray-900 p-4 space-y-4">
+      <div className="flex flex-col sm:flex-row gap-4 items-center max-w-4xl mx-auto w-full">
+        <Input
+          value={command}
+          onChange={(e) => setCommand(e.target.value)}
+          placeholder="Tell the robot what to do..."
+          className="flex-1 bg-gray-800 border-gray-600 text-white placeholder-gray-400 text-lg py-3"
+          onKeyPress={(e) => e.key === 'Enter' && handleSendCommand()}
+        />
+        <Button
+          onClick={handleSendCommand}
+          className="bg-orange-500 hover:bg-orange-600 px-6 py-3 self-stretch sm:self-auto"
+        >
+          <Send strokeWidth={1.5} />
+          Send
+        </Button>
+      </div>
+
+      <div className="flex justify-center items-center gap-6">
+        <div className="flex flex-wrap justify-center gap-2 sm:gap-4">
+          <Button
+            onClick={() => setIsVoiceActive(!isVoiceActive)}
+            className={`px-6 py-2 ${
+              isVoiceActive ? 'bg-gray-600 text-white hover:bg-gray-500' : 'bg-gray-800 text-gray-300 hover:bg-gray-700'
+            }`}
+          >
+            {isVoiceActive ? <Mic strokeWidth={1.5} /> : <MicOff strokeWidth={1.5} />}
+            Voice Command
+          </Button>
+
+          <Button
+            onClick={() => setShowCamera(!showCamera)}
+            className={`px-6 py-2 ${
+              showCamera ? 'bg-gray-600 text-white hover:bg-gray-500' : 'bg-gray-800 text-gray-300 hover:bg-gray-700'
+            }`}
+          >
+            <Camera strokeWidth={1.5} />
+            Show Camera
+          </Button>
+
+          <Button
+            onClick={handleEndSession}
+            className="bg-red-600 hover:bg-red-700 px-6 py-2"
+          >
+            End Session
+          </Button>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default CommandBar;
diff --git a/src/components/control/MetricsPanel.tsx b/src/components/control/MetricsPanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2485cfa9b79a0bb0d4d20129df4d82f26b4bac96
--- /dev/null
+++ b/src/components/control/MetricsPanel.tsx
@@ -0,0 +1,190 @@
+
+import React, { useEffect, useRef } from 'react';
+import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
+import { Camera, MicOff } from 'lucide-react';
+
+interface MetricsPanelProps {
+  activeTab: 'SENSORS' | 'MOTORS';
+  setActiveTab: (tab: 'SENSORS' | 'MOTORS') => void;
+  sensorData: any[];
+  motorData: any[];
+  hasPermissions: boolean;
+  streamRef: React.RefObject<MediaStream | null>;
+  isVoiceActive: boolean;
+  micLevel: number;
+}
+
+const MetricsPanel: React.FC<MetricsPanelProps> = ({
+  activeTab,
+  setActiveTab,
+  sensorData,
+  motorData,
+  hasPermissions,
+  streamRef,
+  isVoiceActive,
+  micLevel,
+}) => {
+  const sensorVideoRef = useRef<HTMLVideoElement>(null);
+
+  useEffect(() => {
+    if (activeTab === 'SENSORS' && hasPermissions && sensorVideoRef.current && streamRef.current) {
+      if (sensorVideoRef.current.srcObject !== streamRef.current) {
+        sensorVideoRef.current.srcObject = streamRef.current;
+      }
+    }
+  }, [activeTab, hasPermissions, streamRef]);
+
+  return (
+    <div className="w-full lg:w-1/2 p-2 sm:p-4">
+      <div className="bg-gray-900 rounded-lg p-4 h-full flex flex-col">
+        {/* Tab Headers */}
+        <div className="flex mb-4">
+          <button
+            onClick={() => setActiveTab('MOTORS')}
+            className={`px-6 py-2 rounded-t-lg text-sm sm:text-base ${
+              activeTab === 'MOTORS'
+                ? 'bg-orange-500 text-white'
+                : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
+            }`}
+          >
+            MOTORS
+          </button>
+          <button
+            onClick={() => setActiveTab('SENSORS')}
+            className={`px-6 py-2 rounded-t-lg ml-2 text-sm sm:text-base ${
+              activeTab === 'SENSORS'
+                ? 'bg-orange-500 text-white'
+                : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
+            }`}
+          >
+            SENSORS
+          </button>
+        </div>
+
+        {/* Chart Content */}
+        <div className="flex-1 overflow-y-auto">
+          {activeTab === 'SENSORS' && (
+            <div className="space-y-4">
+              {/* Webcam Feed */}
+              <div className="border border-gray-800 rounded p-2 flex flex-col h-64">
+                <h3 className="text-sm text-white font-medium mb-2">Live Camera Feed</h3>
+                {hasPermissions ? (
+                  <div className="flex-1 bg-black rounded overflow-hidden">
+                    <video
+                      ref={sensorVideoRef}
+                      autoPlay
+                      muted
+                      playsInline
+                      className="w-full h-full object-contain"
+                    />
+                  </div>
+                ) : (
+                  <div className="flex-1 flex items-center justify-center bg-black rounded">
+                    <div className="text-center">
+                      <Camera className="w-12 h-12 mx-auto text-gray-500 mb-2" />
+                      <p className="text-gray-400">Camera permission not granted.</p>
+                    </div>
+                  </div>
+                )}
+              </div>
+
+              {/* Mic Detection & Other Sensors */}
+              <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
+                <div className="border border-gray-800 rounded p-2 flex flex-col justify-center min-h-[120px]">
+                    <h3 className="text-sm text-center text-white font-medium mb-2">Voice Activity</h3>
+                  {hasPermissions ? (
+                    <div className="flex-1 flex flex-col items-center justify-center gap-2 text-center">
+                      <div className="flex items-end h-10 gap-px w-full justify-center">
+                        {[...Array(15)].map((_, i) => {
+                          const barIsActive = isVoiceActive && i < (micLevel / 120 * 15);
+                          return (
+                            <div
+                              key={i}
+                              className={`w-1.5 rounded-full transition-colors duration-75 ${barIsActive ? 'bg-orange-500' : 'bg-gray-700'}`}
+                              style={{ height: `${(i / 15 * 60) + 20}%` }}
+                            />
+                          );
+                        })}
+                      </div>
+                      <p className="text-xs text-gray-300">
+                        {isVoiceActive ? "Voice commands active" : "Voice commands muted"}
+                      </p>
+                    </div>
+                  ) : (
+                    <div className="flex-1 flex items-center justify-center bg-black rounded">
+                      <div className="text-center">
+                        <MicOff className="w-8 h-8 mx-auto text-gray-500 mb-2" />
+                        <p className="text-gray-400">Microphone permission not granted.</p>
+                      </div>
+                    </div>
+                  )}
+                </div>
+
+                {/* Sensor Charts */}
+                {['sensor3', 'sensor4'].map((sensor, index) => (
+                  <div key={sensor} className="border border-gray-800 rounded p-2 flex flex-col h-auto min-h-[120px]">
+                    <h3 className="text-sm text-white font-medium mb-2">Sensor {index + 3}</h3>
+                    <ResponsiveContainer width="100%" height="90%">
+                      <LineChart data={sensorData}>
+                        <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
+                        <XAxis hide />
+                        <YAxis fontSize={12} stroke="#9CA3AF" />
+                        <Tooltip 
+                          contentStyle={{ 
+                            backgroundColor: '#1F2937', 
+                            border: '1px solid #374151',
+                            color: '#fff'
+                          }} 
+                        />
+                        <Line 
+                          type="monotone" 
+                          dataKey={sensor} 
+                          stroke={index % 2 === 1 ? '#ff6b35' : '#ffdd44'} 
+                          strokeWidth={2}
+                          dot={false}
+                        />
+                      </LineChart>
+                    </ResponsiveContainer>
+                  </div>
+                ))}
+              </div>
+            </div>
+          )}
+
+          {activeTab === 'MOTORS' && (
+            <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
+              {['motor1', 'motor2', 'motor3', 'motor4', 'motor5', 'motor6'].map((motor, index) => (
+                <div key={motor} className="border border-gray-800 rounded p-2 h-40">
+                  <h3 className="text-sm text-white font-medium mb-2">Motor {index + 1}</h3>
+                  <ResponsiveContainer width="100%" height="80%">
+                    <LineChart data={motorData}>
+                      <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
+                      <XAxis hide />
+                      <YAxis fontSize={12} stroke="#9CA3AF" />
+                      <Tooltip 
+                        contentStyle={{ 
+                          backgroundColor: '#1F2937', 
+                          border: '1px solid #374151',
+                          color: '#fff'
+                        }} 
+                      />
+                      <Line 
+                        type="monotone" 
+                        dataKey={motor} 
+                        stroke={index % 2 === 0 ? '#ff6b35' : '#ffdd44'} 
+                        strokeWidth={2}
+                        dot={false}
+                      />
+                    </LineChart>
+                  </ResponsiveContainer>
+                </div>
+              ))}
+            </div>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default MetricsPanel;
diff --git a/src/components/control/RobotArm.tsx b/src/components/control/RobotArm.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1defa06aeaf2f4e8e83f38e02c2a7342ed4e5d5e
--- /dev/null
+++ b/src/components/control/RobotArm.tsx
@@ -0,0 +1,40 @@
+
+import React from 'react';
+
+const RobotArm = () => {
+  return (
+    <group>
+      {/* Base */}
+      <mesh position={[0, -0.25, 0]}>
+        <cylinderGeometry args={[1, 1, 0.5]} />
+        <meshPhongMaterial color="#333333" />
+      </mesh>
+      
+      {/* First joint */}
+      <mesh position={[0, 0.5, 0]}>
+        <boxGeometry args={[0.3, 1.5, 0.3]} />
+        <meshPhongMaterial color="#ff6b35" />
+      </mesh>
+      
+      {/* Second segment */}
+      <mesh position={[0.9, 1.2, 0]} rotation={[0, 0, 0.3]}>
+        <boxGeometry args={[1.8, 0.25, 0.25]} />
+        <meshPhongMaterial color="#ffdd44" />
+      </mesh>
+      
+      {/* Third segment */}
+      <mesh position={[1.8, 1.7, 0]} rotation={[0, 0, -0.5]}>
+        <boxGeometry args={[1.2, 0.2, 0.2]} />
+        <meshPhongMaterial color="#ff6b35" />
+      </mesh>
+      
+      {/* End effector */}
+      <mesh position={[2.3, 1.3, 0]}>
+        <boxGeometry args={[0.3, 0.3, 0.15]} />
+        <meshPhongMaterial color="#ffdd44" />
+      </mesh>
+    </group>
+  );
+};
+
+export default RobotArm;
diff --git a/src/components/control/VisualizerPanel.tsx b/src/components/control/VisualizerPanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1350a8efbcf83e3bb7e51fe35dacbcf5ecd7c0b4
--- /dev/null
+++ b/src/components/control/VisualizerPanel.tsx
@@ -0,0 +1,69 @@
+import React from "react";
+import { Button } from "@/components/ui/button";
+import { ArrowLeft } from "lucide-react";
+import { cn } from "@/lib/utils";
+import UrdfViewer from "../UrdfViewer";
+import UrdfProcessorInitializer from "../UrdfProcessorInitializer";
+
+interface VisualizerPanelProps {
+  onGoBack: () => void;
+  className?: string;
+}
+
+const VisualizerPanel: React.FC<VisualizerPanelProps> = ({
+  onGoBack,
+  className,
+}) => {
+  return (
+    <div
+      className={cn(
+        "w-full lg:w-1/2 p-2 sm:p-4 space-y-4 flex flex-col",
+        className
+      )}
+    >
+      <div className="bg-gray-900 rounded-lg p-4 flex-1 flex flex-col">
+        <div className="flex items-center justify-between mb-4">
+          <div className="flex items-center gap-3">
+            <img
+              src="/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png"
+              alt="LiveLab Logo"
+              className="h-8 w-8"
+            />
+            <h2 className="text-xl font-bold text-white">LiveLab</h2>
+          </div>
+          <Button
+            variant="ghost"
+            size="icon"
+            onClick={onGoBack}
+            className="text-gray-400 hover:text-white hover:bg-gray-800"
+          >
+            <ArrowLeft className="h-5 w-5" />
+          </Button>
+        </div>
+        <div className="flex-1 bg-black rounded border border-gray-800 min-h-[50vh] lg:min-h-0">
+          {/* <Canvas camera={{ position: [5, 3, 5], fov: 50 }}>
+            <ambientLight intensity={0.4} />
+            <directionalLight position={[10, 10, 5]} intensity={1} />
+            <RobotArm />
+            <OrbitControls enablePan={true} enableZoom={true} enableRotate={true} />
+          </Canvas> */}
+          <UrdfProcessorInitializer />
+          <UrdfViewer />
+        </div>
+      </div>
+
+      <div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
+        {[1, 2, 3, 4].map((cam) => (
+          <div
+            key={cam}
+            className="aspect-video bg-gray-900 rounded border border-gray-700 flex items-center justify-center"
+          >
+            <span className="text-gray-400 text-sm">Camera {cam}</span>
+          </div>
+        ))}
+      </div>
+    </div>
+  );
+};
+
+export default VisualizerPanel;
diff --git a/src/components/test/WebSocketTest.tsx b/src/components/test/WebSocketTest.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6500714834334dedae6df749b43be67af3e3e88a
--- /dev/null
+++ b/src/components/test/WebSocketTest.tsx
@@ -0,0 +1,131 @@
+import React, { useState, useEffect } from "react";
+import { Button } from "@/components/ui/button";
+
+interface JointData {
+  type: "joint_update";
+  joints: Record<string, number>;
+  timestamp: number;
+}
+
+const WebSocketTest: React.FC = () => {
+  const [isConnected, setIsConnected] = useState(false);
+  const [lastMessage, setLastMessage] = useState<JointData | null>(null);
+  const [connectionStatus, setConnectionStatus] =
+    useState<string>("Disconnected");
+  const [ws, setWs] = useState<WebSocket | null>(null);
+
+  const connect = () => {
+    // First test server health
+    fetch("http://localhost:8000/health")
+      .then((response) => response.json())
+      .then((data) => {
+        console.log("Server health:", data);
+
+        // Now try WebSocket connection
+        const websocket = new WebSocket("ws://localhost:8000/ws/joint-data");
+
+        websocket.onopen = () => {
+          console.log("WebSocket connected");
+          setIsConnected(true);
+          setConnectionStatus("Connected");
+          setWs(websocket);
+        };
+
+        websocket.onmessage = (event) => {
+          try {
+            const data: JointData = JSON.parse(event.data);
+            setLastMessage(data);
+            console.log("Received joint data:", data);
+          } catch (error) {
+            console.error("Error parsing message:", error);
+          }
+        };
+
+        websocket.onclose = (event) => {
+          console.log("WebSocket closed:", event.code, event.reason);
+          setIsConnected(false);
+          setConnectionStatus(`Closed (${event.code})`);
+          setWs(null);
+        };
+
+        websocket.onerror = (error) => {
+          console.error("WebSocket error:", error);
+          setConnectionStatus("Error");
+        };
+      })
+      .catch((error) => {
+        console.error("Server health check failed:", error);
+        setConnectionStatus("Server unreachable");
+      });
+  };
+
+  const disconnect = () => {
+    if (ws) {
+      ws.close();
+    }
+  };
+
+  useEffect(() => {
+    return () => {
+      if (ws) {
+        ws.close();
+      }
+    };
+  }, [ws]);
+
+  return (
+    <div className="p-4 bg-gray-900 text-white rounded-lg">
+      <h3 className="text-lg font-bold mb-4">WebSocket Connection Test</h3>
+
+      <div className="space-y-4">
+        <div className="flex items-center gap-4">
+          <div
+            className={`w-3 h-3 rounded-full ${
+              isConnected ? "bg-green-500" : "bg-red-500"
+            }`}
+          />
+          <span>Status: {connectionStatus}</span>
+        </div>
+
+        <div className="flex gap-2">
+          <Button onClick={connect} disabled={isConnected}>
+            Connect
+          </Button>
+          <Button
+            onClick={disconnect}
+            disabled={!isConnected}
+            variant="outline"
+          >
+            Disconnect
+          </Button>
+        </div>
+
+        {lastMessage && (
+          <div className="bg-gray-800 p-3 rounded">
+            <h4 className="font-semibold mb-2">Last Joint Data:</h4>
+            <div className="text-sm font-mono">
+              <div>
+                Timestamp:{" "}
+                {new Date(lastMessage.timestamp * 1000).toLocaleTimeString()}
+              </div>
+              <div className="mt-2">Joints:</div>
+              {Object.entries(lastMessage.joints).map(([joint, value]) => (
+                <div key={joint} className="ml-4">
+                  {joint}: {value.toFixed(4)} rad (
+                  {((value * 180) / Math.PI).toFixed(2)}°)
+                </div>
+              ))}
+            </div>
+          </div>
+        )}
+
+        <div className="text-sm text-gray-400">
+          <div>Expected URL: ws://localhost:8000/ws/joint-data</div>
+          <div>Make sure your FastAPI server is running!</div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default WebSocketTest;
diff --git a/src/components/ui/accordion.tsx b/src/components/ui/accordion.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e6a723d06574ee5cec8b00759b98f3fbe1ac7cc9
--- /dev/null
+++ b/src/components/ui/accordion.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+  React.ElementRef<typeof AccordionPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
+>(({ className, ...props }, ref) => (
+  <AccordionPrimitive.Item
+    ref={ref}
+    className={cn("border-b", className)}
+    {...props}
+  />
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+  React.ElementRef<typeof AccordionPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+  <AccordionPrimitive.Header className="flex">
+    <AccordionPrimitive.Trigger
+      ref={ref}
+      className={cn(
+        "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
+        className
+      )}
+      {...props}
+    >
+      {children}
+      <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
+    </AccordionPrimitive.Trigger>
+  </AccordionPrimitive.Header>
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+  React.ElementRef<typeof AccordionPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+  <AccordionPrimitive.Content
+    ref={ref}
+    className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
+    {...props}
+  >
+    <div className={cn("pb-4 pt-0", className)}>{children}</div>
+  </AccordionPrimitive.Content>
+))
+
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8722561cf6bda62d62f9a0c67730aefda971873a
--- /dev/null
+++ b/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,139 @@
+import * as React from "react"
+import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants } from "@/components/ui/button"
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Overlay
+    className={cn(
+      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+      className
+    )}
+    {...props}
+    ref={ref}
+  />
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPortal>
+    <AlertDialogOverlay />
+    <AlertDialogPrimitive.Content
+      ref={ref}
+      className={cn(
+        "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
+        className
+      )}
+      {...props}
+    />
+  </AlertDialogPortal>
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col space-y-2 text-center sm:text-left",
+      className
+    )}
+    {...props}
+  />
+)
+AlertDialogHeader.displayName = "AlertDialogHeader"
+
+const AlertDialogFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+      className
+    )}
+    {...props}
+  />
+)
+AlertDialogFooter.displayName = "AlertDialogFooter"
+
+const AlertDialogTitle = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Title
+    ref={ref}
+    className={cn("text-lg font-semibold", className)}
+    {...props}
+  />
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+AlertDialogDescription.displayName =
+  AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Action>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Action
+    ref={ref}
+    className={cn(buttonVariants(), className)}
+    {...props}
+  />
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
+  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
+>(({ className, ...props }, ref) => (
+  <AlertDialogPrimitive.Cancel
+    ref={ref}
+    className={cn(
+      buttonVariants({ variant: "outline" }),
+      "mt-2 sm:mt-0",
+      className
+    )}
+    {...props}
+  />
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+  AlertDialog,
+  AlertDialogPortal,
+  AlertDialogOverlay,
+  AlertDialogTrigger,
+  AlertDialogContent,
+  AlertDialogHeader,
+  AlertDialogFooter,
+  AlertDialogTitle,
+  AlertDialogDescription,
+  AlertDialogAction,
+  AlertDialogCancel,
+}
diff --git a/src/components/ui/alert.tsx b/src/components/ui/alert.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..41fa7e0561a3fdb5f986c1213a35e563de740e96
--- /dev/null
+++ b/src/components/ui/alert.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const alertVariants = cva(
+  "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
+  {
+    variants: {
+      variant: {
+        default: "bg-background text-foreground",
+        destructive:
+          "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+    },
+  }
+)
+
+const Alert = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
+>(({ className, variant, ...props }, ref) => (
+  <div
+    ref={ref}
+    role="alert"
+    className={cn(alertVariants({ variant }), className)}
+    {...props}
+  />
+))
+Alert.displayName = "Alert"
+
+const AlertTitle = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLHeadingElement>
+>(({ className, ...props }, ref) => (
+  <h5
+    ref={ref}
+    className={cn("mb-1 font-medium leading-none tracking-tight", className)}
+    {...props}
+  />
+))
+AlertTitle.displayName = "AlertTitle"
+
+const AlertDescription = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("text-sm [&_p]:leading-relaxed", className)}
+    {...props}
+  />
+))
+AlertDescription.displayName = "AlertDescription"
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/src/components/ui/aspect-ratio.tsx b/src/components/ui/aspect-ratio.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c4abbf37f217c715a0eaade7f45ac78600df419f
--- /dev/null
+++ b/src/components/ui/aspect-ratio.tsx
@@ -0,0 +1,5 @@
+import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
+
+const AspectRatio = AspectRatioPrimitive.Root
+
+export { AspectRatio }
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..991f56ecb117e96284bf0f6cad3b14ea2fdf5264
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,48 @@
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+  React.ElementRef<typeof AvatarPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
+>(({ className, ...props }, ref) => (
+  <AvatarPrimitive.Root
+    ref={ref}
+    className={cn(
+      "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
+      className
+    )}
+    {...props}
+  />
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+  React.ElementRef<typeof AvatarPrimitive.Image>,
+  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
+>(({ className, ...props }, ref) => (
+  <AvatarPrimitive.Image
+    ref={ref}
+    className={cn("aspect-square h-full w-full", className)}
+    {...props}
+  />
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+  React.ElementRef<typeof AvatarPrimitive.Fallback>,
+  React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
+>(({ className, ...props }, ref) => (
+  <AvatarPrimitive.Fallback
+    ref={ref}
+    className={cn(
+      "flex h-full w-full items-center justify-center rounded-full bg-muted",
+      className
+    )}
+    {...props}
+  />
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f000e3ef5176395b067dfc3f3e1256a80c450015
--- /dev/null
+++ b/src/components/ui/badge.tsx
@@ -0,0 +1,36 @@
+import * as React from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const badgeVariants = cva(
+  "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
+  {
+    variants: {
+      variant: {
+        default:
+          "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
+        secondary:
+          "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
+        destructive:
+          "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
+        outline: "text-foreground",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+    },
+  }
+)
+
+export interface BadgeProps
+  extends React.HTMLAttributes<HTMLDivElement>,
+    VariantProps<typeof badgeVariants> {}
+
+function Badge({ className, variant, ...props }: BadgeProps) {
+  return (
+    <div className={cn(badgeVariants({ variant }), className)} {...props} />
+  )
+}
+
+export { Badge, badgeVariants }
diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..71a5c325cdce2e6898d11cfeb4f2fdd458e3e2da
--- /dev/null
+++ b/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,115 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Breadcrumb = React.forwardRef<
+  HTMLElement,
+  React.ComponentPropsWithoutRef<"nav"> & {
+    separator?: React.ReactNode
+  }
+>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
+Breadcrumb.displayName = "Breadcrumb"
+
+const BreadcrumbList = React.forwardRef<
+  HTMLOListElement,
+  React.ComponentPropsWithoutRef<"ol">
+>(({ className, ...props }, ref) => (
+  <ol
+    ref={ref}
+    className={cn(
+      "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
+      className
+    )}
+    {...props}
+  />
+))
+BreadcrumbList.displayName = "BreadcrumbList"
+
+const BreadcrumbItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentPropsWithoutRef<"li">
+>(({ className, ...props }, ref) => (
+  <li
+    ref={ref}
+    className={cn("inline-flex items-center gap-1.5", className)}
+    {...props}
+  />
+))
+BreadcrumbItem.displayName = "BreadcrumbItem"
+
+const BreadcrumbLink = React.forwardRef<
+  HTMLAnchorElement,
+  React.ComponentPropsWithoutRef<"a"> & {
+    asChild?: boolean
+  }
+>(({ asChild, className, ...props }, ref) => {
+  const Comp = asChild ? Slot : "a"
+
+  return (
+    <Comp
+      ref={ref}
+      className={cn("transition-colors hover:text-foreground", className)}
+      {...props}
+    />
+  )
+})
+BreadcrumbLink.displayName = "BreadcrumbLink"
+
+const BreadcrumbPage = React.forwardRef<
+  HTMLSpanElement,
+  React.ComponentPropsWithoutRef<"span">
+>(({ className, ...props }, ref) => (
+  <span
+    ref={ref}
+    role="link"
+    aria-disabled="true"
+    aria-current="page"
+    className={cn("font-normal text-foreground", className)}
+    {...props}
+  />
+))
+BreadcrumbPage.displayName = "BreadcrumbPage"
+
+const BreadcrumbSeparator = ({
+  children,
+  className,
+  ...props
+}: React.ComponentProps<"li">) => (
+  <li
+    role="presentation"
+    aria-hidden="true"
+    className={cn("[&>svg]:size-3.5", className)}
+    {...props}
+  >
+    {children ?? <ChevronRight />}
+  </li>
+)
+BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
+
+const BreadcrumbEllipsis = ({
+  className,
+  ...props
+}: React.ComponentProps<"span">) => (
+  <span
+    role="presentation"
+    aria-hidden="true"
+    className={cn("flex h-9 w-9 items-center justify-center", className)}
+    {...props}
+  >
+    <MoreHorizontal className="h-4 w-4" />
+    <span className="sr-only">More</span>
+  </span>
+)
+BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
+
+export {
+  Breadcrumb,
+  BreadcrumbList,
+  BreadcrumbItem,
+  BreadcrumbLink,
+  BreadcrumbPage,
+  BreadcrumbSeparator,
+  BreadcrumbEllipsis,
+}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..36496a28727a3643b4212a14225d4f6cbd50bda5
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+  {
+    variants: {
+      variant: {
+        default: "bg-primary text-primary-foreground hover:bg-primary/90",
+        destructive:
+          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+        outline:
+          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
+        secondary:
+          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+        ghost: "hover:bg-accent hover:text-accent-foreground",
+        link: "text-primary underline-offset-4 hover:underline",
+      },
+      size: {
+        default: "h-10 px-4 py-2",
+        sm: "h-9 rounded-md px-3",
+        lg: "h-11 rounded-md px-8",
+        icon: "h-10 w-10",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  }
+)
+
+export interface ButtonProps
+  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
+    VariantProps<typeof buttonVariants> {
+  asChild?: boolean
+}
+
+const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
+  ({ className, variant, size, asChild = false, ...props }, ref) => {
+    const Comp = asChild ? Slot : "button"
+    return (
+      <Comp
+        className={cn(buttonVariants({ variant, size, className }))}
+        ref={ref}
+        {...props}
+      />
+    )
+  }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3160ad0f396d96ec2f499e3e76aae44656f58497
--- /dev/null
+++ b/src/components/ui/calendar.tsx
@@ -0,0 +1,64 @@
+import * as React from "react";
+import { ChevronLeft, ChevronRight } from "lucide-react";
+import { DayPicker } from "react-day-picker";
+
+import { cn } from "@/lib/utils";
+import { buttonVariants } from "@/components/ui/button";
+
+export type CalendarProps = React.ComponentProps<typeof DayPicker>;
+
+function Calendar({
+  className,
+  classNames,
+  showOutsideDays = true,
+  ...props
+}: CalendarProps) {
+  return (
+    <DayPicker
+      showOutsideDays={showOutsideDays}
+      className={cn("p-3", className)}
+      classNames={{
+        months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
+        month: "space-y-4",
+        caption: "flex justify-center pt-1 relative items-center",
+        caption_label: "text-sm font-medium",
+        nav: "space-x-1 flex items-center",
+        nav_button: cn(
+          buttonVariants({ variant: "outline" }),
+          "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
+        ),
+        nav_button_previous: "absolute left-1",
+        nav_button_next: "absolute right-1",
+        table: "w-full border-collapse space-y-1",
+        head_row: "flex",
+        head_cell:
+          "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
+        row: "flex w-full mt-2",
+        cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
+        day: cn(
+          buttonVariants({ variant: "ghost" }),
+          "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
+        ),
+        day_range_end: "day-range-end",
+        day_selected:
+          "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
+        day_today: "bg-accent text-accent-foreground",
+        day_outside:
+          "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
+        day_disabled: "text-muted-foreground opacity-50",
+        day_range_middle:
+          "aria-selected:bg-accent aria-selected:text-accent-foreground",
+        day_hidden: "invisible",
+        ...classNames,
+      }}
+      components={{
+        IconLeft: ({ ..._props }) => <ChevronLeft className="h-4 w-4" />,
+        IconRight: ({ ..._props }) => <ChevronRight className="h-4 w-4" />,
+      }}
+      {...props}
+    />
+  );
+}
+Calendar.displayName = "Calendar";
+
+export { Calendar };
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..afa13ecfa3bd0f4a553a510b856c5800382e139b
--- /dev/null
+++ b/src/components/ui/card.tsx
@@ -0,0 +1,79 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn(
+      "rounded-lg border bg-card text-card-foreground shadow-sm",
+      className
+    )}
+    {...props}
+  />
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("flex flex-col space-y-1.5 p-6", className)}
+    {...props}
+  />
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLHeadingElement>
+>(({ className, ...props }, ref) => (
+  <h3
+    ref={ref}
+    className={cn(
+      "text-2xl font-semibold leading-none tracking-tight",
+      className
+    )}
+    {...props}
+  />
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => (
+  <p
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+  <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    className={cn("flex items-center p-6 pt-0", className)}
+    {...props}
+  />
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/src/components/ui/carousel.tsx b/src/components/ui/carousel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9c2b9bf3705d8421bef00704c0c52e83d371ca11
--- /dev/null
+++ b/src/components/ui/carousel.tsx
@@ -0,0 +1,260 @@
+import * as React from "react"
+import useEmblaCarousel, {
+  type UseEmblaCarouselType,
+} from "embla-carousel-react"
+import { ArrowLeft, ArrowRight } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+
+type CarouselApi = UseEmblaCarouselType[1]
+type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
+type CarouselOptions = UseCarouselParameters[0]
+type CarouselPlugin = UseCarouselParameters[1]
+
+type CarouselProps = {
+  opts?: CarouselOptions
+  plugins?: CarouselPlugin
+  orientation?: "horizontal" | "vertical"
+  setApi?: (api: CarouselApi) => void
+}
+
+type CarouselContextProps = {
+  carouselRef: ReturnType<typeof useEmblaCarousel>[0]
+  api: ReturnType<typeof useEmblaCarousel>[1]
+  scrollPrev: () => void
+  scrollNext: () => void
+  canScrollPrev: boolean
+  canScrollNext: boolean
+} & CarouselProps
+
+const CarouselContext = React.createContext<CarouselContextProps | null>(null)
+
+function useCarousel() {
+  const context = React.useContext(CarouselContext)
+
+  if (!context) {
+    throw new Error("useCarousel must be used within a <Carousel />")
+  }
+
+  return context
+}
+
+const Carousel = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement> & CarouselProps
+>(
+  (
+    {
+      orientation = "horizontal",
+      opts,
+      setApi,
+      plugins,
+      className,
+      children,
+      ...props
+    },
+    ref
+  ) => {
+    const [carouselRef, api] = useEmblaCarousel(
+      {
+        ...opts,
+        axis: orientation === "horizontal" ? "x" : "y",
+      },
+      plugins
+    )
+    const [canScrollPrev, setCanScrollPrev] = React.useState(false)
+    const [canScrollNext, setCanScrollNext] = React.useState(false)
+
+    const onSelect = React.useCallback((api: CarouselApi) => {
+      if (!api) {
+        return
+      }
+
+      setCanScrollPrev(api.canScrollPrev())
+      setCanScrollNext(api.canScrollNext())
+    }, [])
+
+    const scrollPrev = React.useCallback(() => {
+      api?.scrollPrev()
+    }, [api])
+
+    const scrollNext = React.useCallback(() => {
+      api?.scrollNext()
+    }, [api])
+
+    const handleKeyDown = React.useCallback(
+      (event: React.KeyboardEvent<HTMLDivElement>) => {
+        if (event.key === "ArrowLeft") {
+          event.preventDefault()
+          scrollPrev()
+        } else if (event.key === "ArrowRight") {
+          event.preventDefault()
+          scrollNext()
+        }
+      },
+      [scrollPrev, scrollNext]
+    )
+
+    React.useEffect(() => {
+      if (!api || !setApi) {
+        return
+      }
+
+      setApi(api)
+    }, [api, setApi])
+
+    React.useEffect(() => {
+      if (!api) {
+        return
+      }
+
+      onSelect(api)
+      api.on("reInit", onSelect)
+      api.on("select", onSelect)
+
+      return () => {
+        api?.off("select", onSelect)
+      }
+    }, [api, onSelect])
+
+    return (
+      <CarouselContext.Provider
+        value={{
+          carouselRef,
+          api: api,
+          opts,
+          orientation:
+            orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
+          scrollPrev,
+          scrollNext,
+          canScrollPrev,
+          canScrollNext,
+        }}
+      >
+        <div
+          ref={ref}
+          onKeyDownCapture={handleKeyDown}
+          className={cn("relative", className)}
+          role="region"
+          aria-roledescription="carousel"
+          {...props}
+        >
+          {children}
+        </div>
+      </CarouselContext.Provider>
+    )
+  }
+)
+Carousel.displayName = "Carousel"
+
+const CarouselContent = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
+  const { carouselRef, orientation } = useCarousel()
+
+  return (
+    <div ref={carouselRef} className="overflow-hidden">
+      <div
+        ref={ref}
+        className={cn(
+          "flex",
+          orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
+          className
+        )}
+        {...props}
+      />
+    </div>
+  )
+})
+CarouselContent.displayName = "CarouselContent"
+
+const CarouselItem = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
+  const { orientation } = useCarousel()
+
+  return (
+    <div
+      ref={ref}
+      role="group"
+      aria-roledescription="slide"
+      className={cn(
+        "min-w-0 shrink-0 grow-0 basis-full",
+        orientation === "horizontal" ? "pl-4" : "pt-4",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+CarouselItem.displayName = "CarouselItem"
+
+const CarouselPrevious = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<typeof Button>
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+  const { orientation, scrollPrev, canScrollPrev } = useCarousel()
+
+  return (
+    <Button
+      ref={ref}
+      variant={variant}
+      size={size}
+      className={cn(
+        "absolute  h-8 w-8 rounded-full",
+        orientation === "horizontal"
+          ? "-left-12 top-1/2 -translate-y-1/2"
+          : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
+        className
+      )}
+      disabled={!canScrollPrev}
+      onClick={scrollPrev}
+      {...props}
+    >
+      <ArrowLeft className="h-4 w-4" />
+      <span className="sr-only">Previous slide</span>
+    </Button>
+  )
+})
+CarouselPrevious.displayName = "CarouselPrevious"
+
+const CarouselNext = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<typeof Button>
+>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
+  const { orientation, scrollNext, canScrollNext } = useCarousel()
+
+  return (
+    <Button
+      ref={ref}
+      variant={variant}
+      size={size}
+      className={cn(
+        "absolute h-8 w-8 rounded-full",
+        orientation === "horizontal"
+          ? "-right-12 top-1/2 -translate-y-1/2"
+          : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
+        className
+      )}
+      disabled={!canScrollNext}
+      onClick={scrollNext}
+      {...props}
+    >
+      <ArrowRight className="h-4 w-4" />
+      <span className="sr-only">Next slide</span>
+    </Button>
+  )
+})
+CarouselNext.displayName = "CarouselNext"
+
+export {
+  type CarouselApi,
+  Carousel,
+  CarouselContent,
+  CarouselItem,
+  CarouselPrevious,
+  CarouselNext,
+}
diff --git a/src/components/ui/chart.tsx b/src/components/ui/chart.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a21d77ee708c3d861fb246ecab7f6dc36e0e605b
--- /dev/null
+++ b/src/components/ui/chart.tsx
@@ -0,0 +1,363 @@
+import * as React from "react"
+import * as RechartsPrimitive from "recharts"
+
+import { cn } from "@/lib/utils"
+
+// Format: { THEME_NAME: CSS_SELECTOR }
+const THEMES = { light: "", dark: ".dark" } as const
+
+export type ChartConfig = {
+  [k in string]: {
+    label?: React.ReactNode
+    icon?: React.ComponentType
+  } & (
+    | { color?: string; theme?: never }
+    | { color?: never; theme: Record<keyof typeof THEMES, string> }
+  )
+}
+
+type ChartContextProps = {
+  config: ChartConfig
+}
+
+const ChartContext = React.createContext<ChartContextProps | null>(null)
+
+function useChart() {
+  const context = React.useContext(ChartContext)
+
+  if (!context) {
+    throw new Error("useChart must be used within a <ChartContainer />")
+  }
+
+  return context
+}
+
+const ChartContainer = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & {
+    config: ChartConfig
+    children: React.ComponentProps<
+      typeof RechartsPrimitive.ResponsiveContainer
+    >["children"]
+  }
+>(({ id, className, children, config, ...props }, ref) => {
+  const uniqueId = React.useId()
+  const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
+
+  return (
+    <ChartContext.Provider value={{ config }}>
+      <div
+        data-chart={chartId}
+        ref={ref}
+        className={cn(
+          "flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
+          className
+        )}
+        {...props}
+      >
+        <ChartStyle id={chartId} config={config} />
+        <RechartsPrimitive.ResponsiveContainer>
+          {children}
+        </RechartsPrimitive.ResponsiveContainer>
+      </div>
+    </ChartContext.Provider>
+  )
+})
+ChartContainer.displayName = "Chart"
+
+const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
+  const colorConfig = Object.entries(config).filter(
+    ([_, config]) => config.theme || config.color
+  )
+
+  if (!colorConfig.length) {
+    return null
+  }
+
+  return (
+    <style
+      dangerouslySetInnerHTML={{
+        __html: Object.entries(THEMES)
+          .map(
+            ([theme, prefix]) => `
+${prefix} [data-chart=${id}] {
+${colorConfig
+  .map(([key, itemConfig]) => {
+    const color =
+      itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
+      itemConfig.color
+    return color ? `  --color-${key}: ${color};` : null
+  })
+  .join("\n")}
+}
+`
+          )
+          .join("\n"),
+      }}
+    />
+  )
+}
+
+const ChartTooltip = RechartsPrimitive.Tooltip
+
+const ChartTooltipContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
+    React.ComponentProps<"div"> & {
+      hideLabel?: boolean
+      hideIndicator?: boolean
+      indicator?: "line" | "dot" | "dashed"
+      nameKey?: string
+      labelKey?: string
+    }
+>(
+  (
+    {
+      active,
+      payload,
+      className,
+      indicator = "dot",
+      hideLabel = false,
+      hideIndicator = false,
+      label,
+      labelFormatter,
+      labelClassName,
+      formatter,
+      color,
+      nameKey,
+      labelKey,
+    },
+    ref
+  ) => {
+    const { config } = useChart()
+
+    const tooltipLabel = React.useMemo(() => {
+      if (hideLabel || !payload?.length) {
+        return null
+      }
+
+      const [item] = payload
+      const key = `${labelKey || item.dataKey || item.name || "value"}`
+      const itemConfig = getPayloadConfigFromPayload(config, item, key)
+      const value =
+        !labelKey && typeof label === "string"
+          ? config[label as keyof typeof config]?.label || label
+          : itemConfig?.label
+
+      if (labelFormatter) {
+        return (
+          <div className={cn("font-medium", labelClassName)}>
+            {labelFormatter(value, payload)}
+          </div>
+        )
+      }
+
+      if (!value) {
+        return null
+      }
+
+      return <div className={cn("font-medium", labelClassName)}>{value}</div>
+    }, [
+      label,
+      labelFormatter,
+      payload,
+      hideLabel,
+      labelClassName,
+      config,
+      labelKey,
+    ])
+
+    if (!active || !payload?.length) {
+      return null
+    }
+
+    const nestLabel = payload.length === 1 && indicator !== "dot"
+
+    return (
+      <div
+        ref={ref}
+        className={cn(
+          "grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
+          className
+        )}
+      >
+        {!nestLabel ? tooltipLabel : null}
+        <div className="grid gap-1.5">
+          {payload.map((item, index) => {
+            const key = `${nameKey || item.name || item.dataKey || "value"}`
+            const itemConfig = getPayloadConfigFromPayload(config, item, key)
+            const indicatorColor = color || item.payload.fill || item.color
+
+            return (
+              <div
+                key={item.dataKey}
+                className={cn(
+                  "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
+                  indicator === "dot" && "items-center"
+                )}
+              >
+                {formatter && item?.value !== undefined && item.name ? (
+                  formatter(item.value, item.name, item, index, item.payload)
+                ) : (
+                  <>
+                    {itemConfig?.icon ? (
+                      <itemConfig.icon />
+                    ) : (
+                      !hideIndicator && (
+                        <div
+                          className={cn(
+                            "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
+                            {
+                              "h-2.5 w-2.5": indicator === "dot",
+                              "w-1": indicator === "line",
+                              "w-0 border-[1.5px] border-dashed bg-transparent":
+                                indicator === "dashed",
+                              "my-0.5": nestLabel && indicator === "dashed",
+                            }
+                          )}
+                          style={
+                            {
+                              "--color-bg": indicatorColor,
+                              "--color-border": indicatorColor,
+                            } as React.CSSProperties
+                          }
+                        />
+                      )
+                    )}
+                    <div
+                      className={cn(
+                        "flex flex-1 justify-between leading-none",
+                        nestLabel ? "items-end" : "items-center"
+                      )}
+                    >
+                      <div className="grid gap-1.5">
+                        {nestLabel ? tooltipLabel : null}
+                        <span className="text-muted-foreground">
+                          {itemConfig?.label || item.name}
+                        </span>
+                      </div>
+                      {item.value && (
+                        <span className="font-mono font-medium tabular-nums text-foreground">
+                          {item.value.toLocaleString()}
+                        </span>
+                      )}
+                    </div>
+                  </>
+                )}
+              </div>
+            )
+          })}
+        </div>
+      </div>
+    )
+  }
+)
+ChartTooltipContent.displayName = "ChartTooltip"
+
+const ChartLegend = RechartsPrimitive.Legend
+
+const ChartLegendContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> &
+    Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
+      hideIcon?: boolean
+      nameKey?: string
+    }
+>(
+  (
+    { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
+    ref
+  ) => {
+    const { config } = useChart()
+
+    if (!payload?.length) {
+      return null
+    }
+
+    return (
+      <div
+        ref={ref}
+        className={cn(
+          "flex items-center justify-center gap-4",
+          verticalAlign === "top" ? "pb-3" : "pt-3",
+          className
+        )}
+      >
+        {payload.map((item) => {
+          const key = `${nameKey || item.dataKey || "value"}`
+          const itemConfig = getPayloadConfigFromPayload(config, item, key)
+
+          return (
+            <div
+              key={item.value}
+              className={cn(
+                "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
+              )}
+            >
+              {itemConfig?.icon && !hideIcon ? (
+                <itemConfig.icon />
+              ) : (
+                <div
+                  className="h-2 w-2 shrink-0 rounded-[2px]"
+                  style={{
+                    backgroundColor: item.color,
+                  }}
+                />
+              )}
+              {itemConfig?.label}
+            </div>
+          )
+        })}
+      </div>
+    )
+  }
+)
+ChartLegendContent.displayName = "ChartLegend"
+
+// Helper to extract item config from a payload.
+function getPayloadConfigFromPayload(
+  config: ChartConfig,
+  payload: unknown,
+  key: string
+) {
+  if (typeof payload !== "object" || payload === null) {
+    return undefined
+  }
+
+  const payloadPayload =
+    "payload" in payload &&
+    typeof payload.payload === "object" &&
+    payload.payload !== null
+      ? payload.payload
+      : undefined
+
+  let configLabelKey: string = key
+
+  if (
+    key in payload &&
+    typeof payload[key as keyof typeof payload] === "string"
+  ) {
+    configLabelKey = payload[key as keyof typeof payload] as string
+  } else if (
+    payloadPayload &&
+    key in payloadPayload &&
+    typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
+  ) {
+    configLabelKey = payloadPayload[
+      key as keyof typeof payloadPayload
+    ] as string
+  }
+
+  return configLabelKey in config
+    ? config[configLabelKey]
+    : config[key as keyof typeof config]
+}
+
+export {
+  ChartContainer,
+  ChartTooltip,
+  ChartTooltipContent,
+  ChartLegend,
+  ChartLegendContent,
+  ChartStyle,
+}
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ddbdd01d8d1491ab772790db8d40c5ac0a2630f3
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,28 @@
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { Check } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Checkbox = React.forwardRef<
+  React.ElementRef<typeof CheckboxPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
+>(({ className, ...props }, ref) => (
+  <CheckboxPrimitive.Root
+    ref={ref}
+    className={cn(
+      "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
+      className
+    )}
+    {...props}
+  >
+    <CheckboxPrimitive.Indicator
+      className={cn("flex items-center justify-center text-current")}
+    >
+      <Check className="h-4 w-4" />
+    </CheckboxPrimitive.Indicator>
+  </CheckboxPrimitive.Root>
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a23e7a281287e18b1c332498491b6bcc8d8e2b70
--- /dev/null
+++ b/src/components/ui/collapsible.tsx
@@ -0,0 +1,9 @@
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+const Collapsible = CollapsiblePrimitive.Root
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..56a0979c02183994b324f8039cf710cdf786bcb7
--- /dev/null
+++ b/src/components/ui/command.tsx
@@ -0,0 +1,153 @@
+import * as React from "react"
+import { type DialogProps } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { Search } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+
+const Command = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive
+    ref={ref}
+    className={cn(
+      "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
+      className
+    )}
+    {...props}
+  />
+))
+Command.displayName = CommandPrimitive.displayName
+
+interface CommandDialogProps extends DialogProps {}
+
+const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
+  return (
+    <Dialog {...props}>
+      <DialogContent className="overflow-hidden p-0 shadow-lg">
+        <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
+          {children}
+        </Command>
+      </DialogContent>
+    </Dialog>
+  )
+}
+
+const CommandInput = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Input>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
+>(({ className, ...props }, ref) => (
+  <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
+    <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
+    <CommandPrimitive.Input
+      ref={ref}
+      className={cn(
+        "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
+        className
+      )}
+      {...props}
+    />
+  </div>
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.List>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive.List
+    ref={ref}
+    className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
+    {...props}
+  />
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Empty>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
+>((props, ref) => (
+  <CommandPrimitive.Empty
+    ref={ref}
+    className="py-6 text-center text-sm"
+    {...props}
+  />
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Group>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive.Group
+    ref={ref}
+    className={cn(
+      "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
+      className
+    )}
+    {...props}
+  />
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 h-px bg-border", className)}
+    {...props}
+  />
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef<
+  React.ElementRef<typeof CommandPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
+>(({ className, ...props }, ref) => (
+  <CommandPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
+      className
+    )}
+    {...props}
+  />
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+  return (
+    <span
+      className={cn(
+        "ml-auto text-xs tracking-widest text-muted-foreground",
+        className
+      )}
+      {...props}
+    />
+  )
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+  Command,
+  CommandDialog,
+  CommandInput,
+  CommandList,
+  CommandEmpty,
+  CommandGroup,
+  CommandItem,
+  CommandShortcut,
+  CommandSeparator,
+}
diff --git a/src/components/ui/context-menu.tsx b/src/components/ui/context-menu.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3e5299917fb55decf8ba88f03c964d984c43cb18
--- /dev/null
+++ b/src/components/ui/context-menu.tsx
@@ -0,0 +1,198 @@
+import * as React from "react"
+import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const ContextMenu = ContextMenuPrimitive.Root
+
+const ContextMenuTrigger = ContextMenuPrimitive.Trigger
+
+const ContextMenuGroup = ContextMenuPrimitive.Group
+
+const ContextMenuPortal = ContextMenuPrimitive.Portal
+
+const ContextMenuSub = ContextMenuPrimitive.Sub
+
+const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
+
+const ContextMenuSubTrigger = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
+    inset?: boolean
+  }
+>(({ className, inset, children, ...props }, ref) => (
+  <ContextMenuPrimitive.SubTrigger
+    ref={ref}
+    className={cn(
+      "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <ChevronRight className="ml-auto h-4 w-4" />
+  </ContextMenuPrimitive.SubTrigger>
+))
+ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
+
+const ContextMenuSubContent = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+  <ContextMenuPrimitive.SubContent
+    ref={ref}
+    className={cn(
+      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      className
+    )}
+    {...props}
+  />
+))
+ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
+
+const ContextMenuContent = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
+>(({ className, ...props }, ref) => (
+  <ContextMenuPrimitive.Portal>
+    <ContextMenuPrimitive.Content
+      ref={ref}
+      className={cn(
+        "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        className
+      )}
+      {...props}
+    />
+  </ContextMenuPrimitive.Portal>
+))
+ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
+
+const ContextMenuItem = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <ContextMenuPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
+
+const ContextMenuCheckboxItem = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+  <ContextMenuPrimitive.CheckboxItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    checked={checked}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <ContextMenuPrimitive.ItemIndicator>
+        <Check className="h-4 w-4" />
+      </ContextMenuPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </ContextMenuPrimitive.CheckboxItem>
+))
+ContextMenuCheckboxItem.displayName =
+  ContextMenuPrimitive.CheckboxItem.displayName
+
+const ContextMenuRadioItem = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+  <ContextMenuPrimitive.RadioItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <ContextMenuPrimitive.ItemIndicator>
+        <Circle className="h-2 w-2 fill-current" />
+      </ContextMenuPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </ContextMenuPrimitive.RadioItem>
+))
+ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
+
+const ContextMenuLabel = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.Label>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <ContextMenuPrimitive.Label
+    ref={ref}
+    className={cn(
+      "px-2 py-1.5 text-sm font-semibold text-foreground",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
+
+const ContextMenuSeparator = React.forwardRef<
+  React.ElementRef<typeof ContextMenuPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <ContextMenuPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 my-1 h-px bg-border", className)}
+    {...props}
+  />
+))
+ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
+
+const ContextMenuShortcut = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+  return (
+    <span
+      className={cn(
+        "ml-auto text-xs tracking-widest text-muted-foreground",
+        className
+      )}
+      {...props}
+    />
+  )
+}
+ContextMenuShortcut.displayName = "ContextMenuShortcut"
+
+export {
+  ContextMenu,
+  ContextMenuTrigger,
+  ContextMenuContent,
+  ContextMenuItem,
+  ContextMenuCheckboxItem,
+  ContextMenuRadioItem,
+  ContextMenuLabel,
+  ContextMenuSeparator,
+  ContextMenuShortcut,
+  ContextMenuGroup,
+  ContextMenuPortal,
+  ContextMenuSub,
+  ContextMenuSubContent,
+  ContextMenuSubTrigger,
+  ContextMenuRadioGroup,
+}
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c23630eb841533af4030574e54106f0fdf0fc677
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,120 @@
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <DialogPrimitive.Overlay
+    ref={ref}
+    className={cn(
+      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+      className
+    )}
+    {...props}
+  />
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+  <DialogPortal>
+    <DialogOverlay />
+    <DialogPrimitive.Content
+      ref={ref}
+      className={cn(
+        "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
+        className
+      )}
+      {...props}
+    >
+      {children}
+      <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
+        <X className="h-4 w-4" />
+        <span className="sr-only">Close</span>
+      </DialogPrimitive.Close>
+    </DialogPrimitive.Content>
+  </DialogPortal>
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col space-y-1.5 text-center sm:text-left",
+      className
+    )}
+    {...props}
+  />
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+      className
+    )}
+    {...props}
+  />
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <DialogPrimitive.Title
+    ref={ref}
+    className={cn(
+      "text-lg font-semibold leading-none tracking-tight",
+      className
+    )}
+    {...props}
+  />
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+  React.ElementRef<typeof DialogPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <DialogPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+  Dialog,
+  DialogPortal,
+  DialogOverlay,
+  DialogClose,
+  DialogTrigger,
+  DialogContent,
+  DialogHeader,
+  DialogFooter,
+  DialogTitle,
+  DialogDescription,
+}
diff --git a/src/components/ui/drawer.tsx b/src/components/ui/drawer.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..c17b0ccaa95dccfb96c75618e078efd263e54fd0
--- /dev/null
+++ b/src/components/ui/drawer.tsx
@@ -0,0 +1,116 @@
+import * as React from "react"
+import { Drawer as DrawerPrimitive } from "vaul"
+
+import { cn } from "@/lib/utils"
+
+const Drawer = ({
+  shouldScaleBackground = true,
+  ...props
+}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
+  <DrawerPrimitive.Root
+    shouldScaleBackground={shouldScaleBackground}
+    {...props}
+  />
+)
+Drawer.displayName = "Drawer"
+
+const DrawerTrigger = DrawerPrimitive.Trigger
+
+const DrawerPortal = DrawerPrimitive.Portal
+
+const DrawerClose = DrawerPrimitive.Close
+
+const DrawerOverlay = React.forwardRef<
+  React.ElementRef<typeof DrawerPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <DrawerPrimitive.Overlay
+    ref={ref}
+    className={cn("fixed inset-0 z-50 bg-black/80", className)}
+    {...props}
+  />
+))
+DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
+
+const DrawerContent = React.forwardRef<
+  React.ElementRef<typeof DrawerPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
+>(({ className, children, ...props }, ref) => (
+  <DrawerPortal>
+    <DrawerOverlay />
+    <DrawerPrimitive.Content
+      ref={ref}
+      className={cn(
+        "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
+        className
+      )}
+      {...props}
+    >
+      <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
+      {children}
+    </DrawerPrimitive.Content>
+  </DrawerPortal>
+))
+DrawerContent.displayName = "DrawerContent"
+
+const DrawerHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
+    {...props}
+  />
+)
+DrawerHeader.displayName = "DrawerHeader"
+
+const DrawerFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn("mt-auto flex flex-col gap-2 p-4", className)}
+    {...props}
+  />
+)
+DrawerFooter.displayName = "DrawerFooter"
+
+const DrawerTitle = React.forwardRef<
+  React.ElementRef<typeof DrawerPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <DrawerPrimitive.Title
+    ref={ref}
+    className={cn(
+      "text-lg font-semibold leading-none tracking-tight",
+      className
+    )}
+    {...props}
+  />
+))
+DrawerTitle.displayName = DrawerPrimitive.Title.displayName
+
+const DrawerDescription = React.forwardRef<
+  React.ElementRef<typeof DrawerPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <DrawerPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+DrawerDescription.displayName = DrawerPrimitive.Description.displayName
+
+export {
+  Drawer,
+  DrawerPortal,
+  DrawerOverlay,
+  DrawerTrigger,
+  DrawerClose,
+  DrawerContent,
+  DrawerHeader,
+  DrawerFooter,
+  DrawerTitle,
+  DrawerDescription,
+}
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..769ff7aa709f0d7a1afe2a87d180447fc26749e4
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,198 @@
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
+    inset?: boolean
+  }
+>(({ className, inset, children, ...props }, ref) => (
+  <DropdownMenuPrimitive.SubTrigger
+    ref={ref}
+    className={cn(
+      "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <ChevronRight className="ml-auto h-4 w-4" />
+  </DropdownMenuPrimitive.SubTrigger>
+))
+DropdownMenuSubTrigger.displayName =
+  DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+  <DropdownMenuPrimitive.SubContent
+    ref={ref}
+    className={cn(
+      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      className
+    )}
+    {...props}
+  />
+))
+DropdownMenuSubContent.displayName =
+  DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
+>(({ className, sideOffset = 4, ...props }, ref) => (
+  <DropdownMenuPrimitive.Portal>
+    <DropdownMenuPrimitive.Content
+      ref={ref}
+      sideOffset={sideOffset}
+      className={cn(
+        "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        className
+      )}
+      {...props}
+    />
+  </DropdownMenuPrimitive.Portal>
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <DropdownMenuPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+  <DropdownMenuPrimitive.CheckboxItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    checked={checked}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <DropdownMenuPrimitive.ItemIndicator>
+        <Check className="h-4 w-4" />
+      </DropdownMenuPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </DropdownMenuPrimitive.CheckboxItem>
+))
+DropdownMenuCheckboxItem.displayName =
+  DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+  <DropdownMenuPrimitive.RadioItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <DropdownMenuPrimitive.ItemIndicator>
+        <Circle className="h-2 w-2 fill-current" />
+      </DropdownMenuPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </DropdownMenuPrimitive.RadioItem>
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.Label>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <DropdownMenuPrimitive.Label
+    ref={ref}
+    className={cn(
+      "px-2 py-1.5 text-sm font-semibold",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+  React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <DropdownMenuPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 my-1 h-px bg-muted", className)}
+    {...props}
+  />
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+  return (
+    <span
+      className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
+      {...props}
+    />
+  )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+  DropdownMenu,
+  DropdownMenuTrigger,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuCheckboxItem,
+  DropdownMenuRadioItem,
+  DropdownMenuLabel,
+  DropdownMenuSeparator,
+  DropdownMenuShortcut,
+  DropdownMenuGroup,
+  DropdownMenuPortal,
+  DropdownMenuSub,
+  DropdownMenuSubContent,
+  DropdownMenuSubTrigger,
+  DropdownMenuRadioGroup,
+}
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4603f8b3d5eee5fe162bb69a02ce8546d4358cc3
--- /dev/null
+++ b/src/components/ui/form.tsx
@@ -0,0 +1,176 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { Slot } from "@radix-ui/react-slot"
+import {
+  Controller,
+  ControllerProps,
+  FieldPath,
+  FieldValues,
+  FormProvider,
+  useFormContext,
+} from "react-hook-form"
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+
+const Form = FormProvider
+
+type FormFieldContextValue<
+  TFieldValues extends FieldValues = FieldValues,
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
+> = {
+  name: TName
+}
+
+const FormFieldContext = React.createContext<FormFieldContextValue>(
+  {} as FormFieldContextValue
+)
+
+const FormField = <
+  TFieldValues extends FieldValues = FieldValues,
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
+>({
+  ...props
+}: ControllerProps<TFieldValues, TName>) => {
+  return (
+    <FormFieldContext.Provider value={{ name: props.name }}>
+      <Controller {...props} />
+    </FormFieldContext.Provider>
+  )
+}
+
+const useFormField = () => {
+  const fieldContext = React.useContext(FormFieldContext)
+  const itemContext = React.useContext(FormItemContext)
+  const { getFieldState, formState } = useFormContext()
+
+  const fieldState = getFieldState(fieldContext.name, formState)
+
+  if (!fieldContext) {
+    throw new Error("useFormField should be used within <FormField>")
+  }
+
+  const { id } = itemContext
+
+  return {
+    id,
+    name: fieldContext.name,
+    formItemId: `${id}-form-item`,
+    formDescriptionId: `${id}-form-item-description`,
+    formMessageId: `${id}-form-item-message`,
+    ...fieldState,
+  }
+}
+
+type FormItemContextValue = {
+  id: string
+}
+
+const FormItemContext = React.createContext<FormItemContextValue>(
+  {} as FormItemContextValue
+)
+
+const FormItem = React.forwardRef<
+  HTMLDivElement,
+  React.HTMLAttributes<HTMLDivElement>
+>(({ className, ...props }, ref) => {
+  const id = React.useId()
+
+  return (
+    <FormItemContext.Provider value={{ id }}>
+      <div ref={ref} className={cn("space-y-2", className)} {...props} />
+    </FormItemContext.Provider>
+  )
+})
+FormItem.displayName = "FormItem"
+
+const FormLabel = React.forwardRef<
+  React.ElementRef<typeof LabelPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
+>(({ className, ...props }, ref) => {
+  const { error, formItemId } = useFormField()
+
+  return (
+    <Label
+      ref={ref}
+      className={cn(error && "text-destructive", className)}
+      htmlFor={formItemId}
+      {...props}
+    />
+  )
+})
+FormLabel.displayName = "FormLabel"
+
+const FormControl = React.forwardRef<
+  React.ElementRef<typeof Slot>,
+  React.ComponentPropsWithoutRef<typeof Slot>
+>(({ ...props }, ref) => {
+  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+
+  return (
+    <Slot
+      ref={ref}
+      id={formItemId}
+      aria-describedby={
+        !error
+          ? `${formDescriptionId}`
+          : `${formDescriptionId} ${formMessageId}`
+      }
+      aria-invalid={!!error}
+      {...props}
+    />
+  )
+})
+FormControl.displayName = "FormControl"
+
+const FormDescription = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, ...props }, ref) => {
+  const { formDescriptionId } = useFormField()
+
+  return (
+    <p
+      ref={ref}
+      id={formDescriptionId}
+      className={cn("text-sm text-muted-foreground", className)}
+      {...props}
+    />
+  )
+})
+FormDescription.displayName = "FormDescription"
+
+const FormMessage = React.forwardRef<
+  HTMLParagraphElement,
+  React.HTMLAttributes<HTMLParagraphElement>
+>(({ className, children, ...props }, ref) => {
+  const { error, formMessageId } = useFormField()
+  const body = error ? String(error?.message) : children
+
+  if (!body) {
+    return null
+  }
+
+  return (
+    <p
+      ref={ref}
+      id={formMessageId}
+      className={cn("text-sm font-medium text-destructive", className)}
+      {...props}
+    >
+      {body}
+    </p>
+  )
+})
+FormMessage.displayName = "FormMessage"
+
+export {
+  useFormField,
+  Form,
+  FormItem,
+  FormLabel,
+  FormControl,
+  FormDescription,
+  FormMessage,
+  FormField,
+}
diff --git a/src/components/ui/hover-card.tsx b/src/components/ui/hover-card.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..863ff0148f0bb239b93c44a93baf14073cd3ae23
--- /dev/null
+++ b/src/components/ui/hover-card.tsx
@@ -0,0 +1,27 @@
+import * as React from "react"
+import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
+
+import { cn } from "@/lib/utils"
+
+const HoverCard = HoverCardPrimitive.Root
+
+const HoverCardTrigger = HoverCardPrimitive.Trigger
+
+const HoverCardContent = React.forwardRef<
+  React.ElementRef<typeof HoverCardPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+  <HoverCardPrimitive.Content
+    ref={ref}
+    align={align}
+    sideOffset={sideOffset}
+    className={cn(
+      "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      className
+    )}
+    {...props}
+  />
+))
+HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
+
+export { HoverCard, HoverCardTrigger, HoverCardContent }
diff --git a/src/components/ui/input-otp.tsx b/src/components/ui/input-otp.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ebf5c281ee7fbbf86462b33b7b9955d884d57580
--- /dev/null
+++ b/src/components/ui/input-otp.tsx
@@ -0,0 +1,69 @@
+import * as React from "react"
+import { OTPInput, OTPInputContext } from "input-otp"
+import { Dot } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const InputOTP = React.forwardRef<
+  React.ElementRef<typeof OTPInput>,
+  React.ComponentPropsWithoutRef<typeof OTPInput>
+>(({ className, containerClassName, ...props }, ref) => (
+  <OTPInput
+    ref={ref}
+    containerClassName={cn(
+      "flex items-center gap-2 has-[:disabled]:opacity-50",
+      containerClassName
+    )}
+    className={cn("disabled:cursor-not-allowed", className)}
+    {...props}
+  />
+))
+InputOTP.displayName = "InputOTP"
+
+const InputOTPGroup = React.forwardRef<
+  React.ElementRef<"div">,
+  React.ComponentPropsWithoutRef<"div">
+>(({ className, ...props }, ref) => (
+  <div ref={ref} className={cn("flex items-center", className)} {...props} />
+))
+InputOTPGroup.displayName = "InputOTPGroup"
+
+const InputOTPSlot = React.forwardRef<
+  React.ElementRef<"div">,
+  React.ComponentPropsWithoutRef<"div"> & { index: number }
+>(({ index, className, ...props }, ref) => {
+  const inputOTPContext = React.useContext(OTPInputContext)
+  const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
+
+  return (
+    <div
+      ref={ref}
+      className={cn(
+        "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
+        isActive && "z-10 ring-2 ring-ring ring-offset-background",
+        className
+      )}
+      {...props}
+    >
+      {char}
+      {hasFakeCaret && (
+        <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
+          <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
+        </div>
+      )}
+    </div>
+  )
+})
+InputOTPSlot.displayName = "InputOTPSlot"
+
+const InputOTPSeparator = React.forwardRef<
+  React.ElementRef<"div">,
+  React.ComponentPropsWithoutRef<"div">
+>(({ ...props }, ref) => (
+  <div ref={ref} role="separator" {...props}>
+    <Dot />
+  </div>
+))
+InputOTPSeparator.displayName = "InputOTPSeparator"
+
+export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..68551b9276b4164a8263aa58d385db30f81a4453
--- /dev/null
+++ b/src/components/ui/input.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
+  ({ className, type, ...props }, ref) => {
+    return (
+      <input
+        type={type}
+        className={cn(
+          "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
+          className
+        )}
+        ref={ref}
+        {...props}
+      />
+    )
+  }
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..683faa793819982d64e21cb2939666fd6d4a7b13
--- /dev/null
+++ b/src/components/ui/label.tsx
@@ -0,0 +1,24 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+  "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+  React.ElementRef<typeof LabelPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
+    VariantProps<typeof labelVariants>
+>(({ className, ...props }, ref) => (
+  <LabelPrimitive.Root
+    ref={ref}
+    className={cn(labelVariants(), className)}
+    {...props}
+  />
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/src/components/ui/menubar.tsx b/src/components/ui/menubar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d11c2993f279769a0df4ba4313e1f0c7c7c7cd28
--- /dev/null
+++ b/src/components/ui/menubar.tsx
@@ -0,0 +1,234 @@
+import * as React from "react"
+import * as MenubarPrimitive from "@radix-ui/react-menubar"
+import { Check, ChevronRight, Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const MenubarMenu = MenubarPrimitive.Menu
+
+const MenubarGroup = MenubarPrimitive.Group
+
+const MenubarPortal = MenubarPrimitive.Portal
+
+const MenubarSub = MenubarPrimitive.Sub
+
+const MenubarRadioGroup = MenubarPrimitive.RadioGroup
+
+const Menubar = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
+>(({ className, ...props }, ref) => (
+  <MenubarPrimitive.Root
+    ref={ref}
+    className={cn(
+      "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
+      className
+    )}
+    {...props}
+  />
+))
+Menubar.displayName = MenubarPrimitive.Root.displayName
+
+const MenubarTrigger = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+  <MenubarPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+      className
+    )}
+    {...props}
+  />
+))
+MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
+
+const MenubarSubTrigger = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
+    inset?: boolean
+  }
+>(({ className, inset, children, ...props }, ref) => (
+  <MenubarPrimitive.SubTrigger
+    ref={ref}
+    className={cn(
+      "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <ChevronRight className="ml-auto h-4 w-4" />
+  </MenubarPrimitive.SubTrigger>
+))
+MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
+
+const MenubarSubContent = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.SubContent>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
+>(({ className, ...props }, ref) => (
+  <MenubarPrimitive.SubContent
+    ref={ref}
+    className={cn(
+      "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      className
+    )}
+    {...props}
+  />
+))
+MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
+
+const MenubarContent = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
+>(
+  (
+    { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
+    ref
+  ) => (
+    <MenubarPrimitive.Portal>
+      <MenubarPrimitive.Content
+        ref={ref}
+        align={align}
+        alignOffset={alignOffset}
+        sideOffset={sideOffset}
+        className={cn(
+          "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+          className
+        )}
+        {...props}
+      />
+    </MenubarPrimitive.Portal>
+  )
+)
+MenubarContent.displayName = MenubarPrimitive.Content.displayName
+
+const MenubarItem = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <MenubarPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+MenubarItem.displayName = MenubarPrimitive.Item.displayName
+
+const MenubarCheckboxItem = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
+>(({ className, children, checked, ...props }, ref) => (
+  <MenubarPrimitive.CheckboxItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    checked={checked}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <MenubarPrimitive.ItemIndicator>
+        <Check className="h-4 w-4" />
+      </MenubarPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </MenubarPrimitive.CheckboxItem>
+))
+MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
+
+const MenubarRadioItem = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.RadioItem>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
+>(({ className, children, ...props }, ref) => (
+  <MenubarPrimitive.RadioItem
+    ref={ref}
+    className={cn(
+      "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <MenubarPrimitive.ItemIndicator>
+        <Circle className="h-2 w-2 fill-current" />
+      </MenubarPrimitive.ItemIndicator>
+    </span>
+    {children}
+  </MenubarPrimitive.RadioItem>
+))
+MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
+
+const MenubarLabel = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Label>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
+    inset?: boolean
+  }
+>(({ className, inset, ...props }, ref) => (
+  <MenubarPrimitive.Label
+    ref={ref}
+    className={cn(
+      "px-2 py-1.5 text-sm font-semibold",
+      inset && "pl-8",
+      className
+    )}
+    {...props}
+  />
+))
+MenubarLabel.displayName = MenubarPrimitive.Label.displayName
+
+const MenubarSeparator = React.forwardRef<
+  React.ElementRef<typeof MenubarPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <MenubarPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 my-1 h-px bg-muted", className)}
+    {...props}
+  />
+))
+MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
+
+const MenubarShortcut = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLSpanElement>) => {
+  return (
+    <span
+      className={cn(
+        "ml-auto text-xs tracking-widest text-muted-foreground",
+        className
+      )}
+      {...props}
+    />
+  )
+}
+MenubarShortcut.displayname = "MenubarShortcut"
+
+export {
+  Menubar,
+  MenubarMenu,
+  MenubarTrigger,
+  MenubarContent,
+  MenubarItem,
+  MenubarSeparator,
+  MenubarLabel,
+  MenubarCheckboxItem,
+  MenubarRadioGroup,
+  MenubarRadioItem,
+  MenubarPortal,
+  MenubarSubContent,
+  MenubarSubTrigger,
+  MenubarGroup,
+  MenubarSub,
+  MenubarShortcut,
+}
diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1419f56695be517ec78fe8ad26a6f7da3a7d53b2
--- /dev/null
+++ b/src/components/ui/navigation-menu.tsx
@@ -0,0 +1,128 @@
+import * as React from "react"
+import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
+import { cva } from "class-variance-authority"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const NavigationMenu = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
+>(({ className, children, ...props }, ref) => (
+  <NavigationMenuPrimitive.Root
+    ref={ref}
+    className={cn(
+      "relative z-10 flex max-w-max flex-1 items-center justify-center",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <NavigationMenuViewport />
+  </NavigationMenuPrimitive.Root>
+))
+NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
+
+const NavigationMenuList = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.List>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
+>(({ className, ...props }, ref) => (
+  <NavigationMenuPrimitive.List
+    ref={ref}
+    className={cn(
+      "group flex flex-1 list-none items-center justify-center space-x-1",
+      className
+    )}
+    {...props}
+  />
+))
+NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
+
+const NavigationMenuItem = NavigationMenuPrimitive.Item
+
+const navigationMenuTriggerStyle = cva(
+  "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
+)
+
+const NavigationMenuTrigger = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+  <NavigationMenuPrimitive.Trigger
+    ref={ref}
+    className={cn(navigationMenuTriggerStyle(), "group", className)}
+    {...props}
+  >
+    {children}{" "}
+    <ChevronDown
+      className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
+      aria-hidden="true"
+    />
+  </NavigationMenuPrimitive.Trigger>
+))
+NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
+
+const NavigationMenuContent = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
+>(({ className, ...props }, ref) => (
+  <NavigationMenuPrimitive.Content
+    ref={ref}
+    className={cn(
+      "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
+      className
+    )}
+    {...props}
+  />
+))
+NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
+
+const NavigationMenuLink = NavigationMenuPrimitive.Link
+
+const NavigationMenuViewport = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
+>(({ className, ...props }, ref) => (
+  <div className={cn("absolute left-0 top-full flex justify-center")}>
+    <NavigationMenuPrimitive.Viewport
+      className={cn(
+        "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
+        className
+      )}
+      ref={ref}
+      {...props}
+    />
+  </div>
+))
+NavigationMenuViewport.displayName =
+  NavigationMenuPrimitive.Viewport.displayName
+
+const NavigationMenuIndicator = React.forwardRef<
+  React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
+  React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
+>(({ className, ...props }, ref) => (
+  <NavigationMenuPrimitive.Indicator
+    ref={ref}
+    className={cn(
+      "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
+      className
+    )}
+    {...props}
+  >
+    <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
+  </NavigationMenuPrimitive.Indicator>
+))
+NavigationMenuIndicator.displayName =
+  NavigationMenuPrimitive.Indicator.displayName
+
+export {
+  navigationMenuTriggerStyle,
+  NavigationMenu,
+  NavigationMenuList,
+  NavigationMenuItem,
+  NavigationMenuContent,
+  NavigationMenuTrigger,
+  NavigationMenuLink,
+  NavigationMenuIndicator,
+  NavigationMenuViewport,
+}
diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ea40d196dc72673f36c4084bf56457385edc855e
--- /dev/null
+++ b/src/components/ui/pagination.tsx
@@ -0,0 +1,117 @@
+import * as React from "react"
+import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { ButtonProps, buttonVariants } from "@/components/ui/button"
+
+const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
+  <nav
+    role="navigation"
+    aria-label="pagination"
+    className={cn("mx-auto flex w-full justify-center", className)}
+    {...props}
+  />
+)
+Pagination.displayName = "Pagination"
+
+const PaginationContent = React.forwardRef<
+  HTMLUListElement,
+  React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+  <ul
+    ref={ref}
+    className={cn("flex flex-row items-center gap-1", className)}
+    {...props}
+  />
+))
+PaginationContent.displayName = "PaginationContent"
+
+const PaginationItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentProps<"li">
+>(({ className, ...props }, ref) => (
+  <li ref={ref} className={cn("", className)} {...props} />
+))
+PaginationItem.displayName = "PaginationItem"
+
+type PaginationLinkProps = {
+  isActive?: boolean
+} & Pick<ButtonProps, "size"> &
+  React.ComponentProps<"a">
+
+const PaginationLink = ({
+  className,
+  isActive,
+  size = "icon",
+  ...props
+}: PaginationLinkProps) => (
+  <a
+    aria-current={isActive ? "page" : undefined}
+    className={cn(
+      buttonVariants({
+        variant: isActive ? "outline" : "ghost",
+        size,
+      }),
+      className
+    )}
+    {...props}
+  />
+)
+PaginationLink.displayName = "PaginationLink"
+
+const PaginationPrevious = ({
+  className,
+  ...props
+}: React.ComponentProps<typeof PaginationLink>) => (
+  <PaginationLink
+    aria-label="Go to previous page"
+    size="default"
+    className={cn("gap-1 pl-2.5", className)}
+    {...props}
+  >
+    <ChevronLeft className="h-4 w-4" />
+    <span>Previous</span>
+  </PaginationLink>
+)
+PaginationPrevious.displayName = "PaginationPrevious"
+
+const PaginationNext = ({
+  className,
+  ...props
+}: React.ComponentProps<typeof PaginationLink>) => (
+  <PaginationLink
+    aria-label="Go to next page"
+    size="default"
+    className={cn("gap-1 pr-2.5", className)}
+    {...props}
+  >
+    <span>Next</span>
+    <ChevronRight className="h-4 w-4" />
+  </PaginationLink>
+)
+PaginationNext.displayName = "PaginationNext"
+
+const PaginationEllipsis = ({
+  className,
+  ...props
+}: React.ComponentProps<"span">) => (
+  <span
+    aria-hidden
+    className={cn("flex h-9 w-9 items-center justify-center", className)}
+    {...props}
+  >
+    <MoreHorizontal className="h-4 w-4" />
+    <span className="sr-only">More pages</span>
+  </span>
+)
+PaginationEllipsis.displayName = "PaginationEllipsis"
+
+export {
+  Pagination,
+  PaginationContent,
+  PaginationEllipsis,
+  PaginationItem,
+  PaginationLink,
+  PaginationNext,
+  PaginationPrevious,
+}
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..bbba7e0ebf26c29526552f2dc4e8e3ecea3d641a
--- /dev/null
+++ b/src/components/ui/popover.tsx
@@ -0,0 +1,29 @@
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverContent = React.forwardRef<
+  React.ElementRef<typeof PopoverPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+  <PopoverPrimitive.Portal>
+    <PopoverPrimitive.Content
+      ref={ref}
+      align={align}
+      sideOffset={sideOffset}
+      className={cn(
+        "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        className
+      )}
+      {...props}
+    />
+  </PopoverPrimitive.Portal>
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent }
diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..105fb650055b71797510142dfa041b5d80808041
--- /dev/null
+++ b/src/components/ui/progress.tsx
@@ -0,0 +1,26 @@
+import * as React from "react"
+import * as ProgressPrimitive from "@radix-ui/react-progress"
+
+import { cn } from "@/lib/utils"
+
+const Progress = React.forwardRef<
+  React.ElementRef<typeof ProgressPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
+>(({ className, value, ...props }, ref) => (
+  <ProgressPrimitive.Root
+    ref={ref}
+    className={cn(
+      "relative h-4 w-full overflow-hidden rounded-full bg-secondary",
+      className
+    )}
+    {...props}
+  >
+    <ProgressPrimitive.Indicator
+      className="h-full w-full flex-1 bg-primary transition-all"
+      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
+    />
+  </ProgressPrimitive.Root>
+))
+Progress.displayName = ProgressPrimitive.Root.displayName
+
+export { Progress }
diff --git a/src/components/ui/radio-group.tsx b/src/components/ui/radio-group.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..43b43b48b84d3045a442409006d5cb8c5f35adfd
--- /dev/null
+++ b/src/components/ui/radio-group.tsx
@@ -0,0 +1,42 @@
+import * as React from "react"
+import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
+import { Circle } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const RadioGroup = React.forwardRef<
+  React.ElementRef<typeof RadioGroupPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
+>(({ className, ...props }, ref) => {
+  return (
+    <RadioGroupPrimitive.Root
+      className={cn("grid gap-2", className)}
+      {...props}
+      ref={ref}
+    />
+  )
+})
+RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
+
+const RadioGroupItem = React.forwardRef<
+  React.ElementRef<typeof RadioGroupPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
+>(({ className, ...props }, ref) => {
+  return (
+    <RadioGroupPrimitive.Item
+      ref={ref}
+      className={cn(
+        "aspect-square h-4 w-4 rounded-full border border-primary text-primary ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+        className
+      )}
+      {...props}
+    >
+      <RadioGroupPrimitive.Indicator className="flex items-center justify-center">
+        <Circle className="h-2.5 w-2.5 fill-current text-current" />
+      </RadioGroupPrimitive.Indicator>
+    </RadioGroupPrimitive.Item>
+  )
+})
+RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
+
+export { RadioGroup, RadioGroupItem }
diff --git a/src/components/ui/resizable.tsx b/src/components/ui/resizable.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cd3cb0ec783fcd72906f275a372d50259d5853b8
--- /dev/null
+++ b/src/components/ui/resizable.tsx
@@ -0,0 +1,43 @@
+import { GripVertical } from "lucide-react"
+import * as ResizablePrimitive from "react-resizable-panels"
+
+import { cn } from "@/lib/utils"
+
+const ResizablePanelGroup = ({
+  className,
+  ...props
+}: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => (
+  <ResizablePrimitive.PanelGroup
+    className={cn(
+      "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
+      className
+    )}
+    {...props}
+  />
+)
+
+const ResizablePanel = ResizablePrimitive.Panel
+
+const ResizableHandle = ({
+  withHandle,
+  className,
+  ...props
+}: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
+  withHandle?: boolean
+}) => (
+  <ResizablePrimitive.PanelResizeHandle
+    className={cn(
+      "relative flex w-px items-center justify-center bg-border after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:-translate-y-1/2 data-[panel-group-direction=vertical]:after:translate-x-0 [&[data-panel-group-direction=vertical]>div]:rotate-90",
+      className
+    )}
+    {...props}
+  >
+    {withHandle && (
+      <div className="z-10 flex h-4 w-3 items-center justify-center rounded-sm border bg-border">
+        <GripVertical className="h-2.5 w-2.5" />
+      </div>
+    )}
+  </ResizablePrimitive.PanelResizeHandle>
+)
+
+export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cf253cf17056ce04827219643e484ea99c77cf6b
--- /dev/null
+++ b/src/components/ui/scroll-area.tsx
@@ -0,0 +1,46 @@
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/lib/utils"
+
+const ScrollArea = React.forwardRef<
+  React.ElementRef<typeof ScrollAreaPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
+>(({ className, children, ...props }, ref) => (
+  <ScrollAreaPrimitive.Root
+    ref={ref}
+    className={cn("relative overflow-hidden", className)}
+    {...props}
+  >
+    <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
+      {children}
+    </ScrollAreaPrimitive.Viewport>
+    <ScrollBar />
+    <ScrollAreaPrimitive.Corner />
+  </ScrollAreaPrimitive.Root>
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+  React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
+  React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
+>(({ className, orientation = "vertical", ...props }, ref) => (
+  <ScrollAreaPrimitive.ScrollAreaScrollbar
+    ref={ref}
+    orientation={orientation}
+    className={cn(
+      "flex touch-none select-none transition-colors",
+      orientation === "vertical" &&
+        "h-full w-2.5 border-l border-l-transparent p-[1px]",
+      orientation === "horizontal" &&
+        "h-2.5 flex-col border-t border-t-transparent p-[1px]",
+      className
+    )}
+    {...props}
+  >
+    <ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
+  </ScrollAreaPrimitive.ScrollAreaScrollbar>
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..fe56d4d3ad53f9c3ec9434f2d39feb7571a3b52f
--- /dev/null
+++ b/src/components/ui/select.tsx
@@ -0,0 +1,158 @@
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { Check, ChevronDown, ChevronUp } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Select = SelectPrimitive.Root
+
+const SelectGroup = SelectPrimitive.Group
+
+const SelectValue = SelectPrimitive.Value
+
+const SelectTrigger = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
+>(({ className, children, ...props }, ref) => (
+  <SelectPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
+      className
+    )}
+    {...props}
+  >
+    {children}
+    <SelectPrimitive.Icon asChild>
+      <ChevronDown className="h-4 w-4 opacity-50" />
+    </SelectPrimitive.Icon>
+  </SelectPrimitive.Trigger>
+))
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+
+const SelectScrollUpButton = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.ScrollUpButton
+    ref={ref}
+    className={cn(
+      "flex cursor-default items-center justify-center py-1",
+      className
+    )}
+    {...props}
+  >
+    <ChevronUp className="h-4 w-4" />
+  </SelectPrimitive.ScrollUpButton>
+))
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+
+const SelectScrollDownButton = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.ScrollDownButton
+    ref={ref}
+    className={cn(
+      "flex cursor-default items-center justify-center py-1",
+      className
+    )}
+    {...props}
+  >
+    <ChevronDown className="h-4 w-4" />
+  </SelectPrimitive.ScrollDownButton>
+))
+SelectScrollDownButton.displayName =
+  SelectPrimitive.ScrollDownButton.displayName
+
+const SelectContent = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
+>(({ className, children, position = "popper", ...props }, ref) => (
+  <SelectPrimitive.Portal>
+    <SelectPrimitive.Content
+      ref={ref}
+      className={cn(
+        "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+        position === "popper" &&
+          "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
+        className
+      )}
+      position={position}
+      {...props}
+    >
+      <SelectScrollUpButton />
+      <SelectPrimitive.Viewport
+        className={cn(
+          "p-1",
+          position === "popper" &&
+            "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
+        )}
+      >
+        {children}
+      </SelectPrimitive.Viewport>
+      <SelectScrollDownButton />
+    </SelectPrimitive.Content>
+  </SelectPrimitive.Portal>
+))
+SelectContent.displayName = SelectPrimitive.Content.displayName
+
+const SelectLabel = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Label>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.Label
+    ref={ref}
+    className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
+    {...props}
+  />
+))
+SelectLabel.displayName = SelectPrimitive.Label.displayName
+
+const SelectItem = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
+>(({ className, children, ...props }, ref) => (
+  <SelectPrimitive.Item
+    ref={ref}
+    className={cn(
+      "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
+      className
+    )}
+    {...props}
+  >
+    <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
+      <SelectPrimitive.ItemIndicator>
+        <Check className="h-4 w-4" />
+      </SelectPrimitive.ItemIndicator>
+    </span>
+
+    <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
+  </SelectPrimitive.Item>
+))
+SelectItem.displayName = SelectPrimitive.Item.displayName
+
+const SelectSeparator = React.forwardRef<
+  React.ElementRef<typeof SelectPrimitive.Separator>,
+  React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
+>(({ className, ...props }, ref) => (
+  <SelectPrimitive.Separator
+    ref={ref}
+    className={cn("-mx-1 my-1 h-px bg-muted", className)}
+    {...props}
+  />
+))
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+
+export {
+  Select,
+  SelectGroup,
+  SelectValue,
+  SelectTrigger,
+  SelectContent,
+  SelectLabel,
+  SelectItem,
+  SelectSeparator,
+  SelectScrollUpButton,
+  SelectScrollDownButton,
+}
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6d7f12265ba0338704f013930ce4d52c56527dd1
--- /dev/null
+++ b/src/components/ui/separator.tsx
@@ -0,0 +1,29 @@
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+const Separator = React.forwardRef<
+  React.ElementRef<typeof SeparatorPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
+>(
+  (
+    { className, orientation = "horizontal", decorative = true, ...props },
+    ref
+  ) => (
+    <SeparatorPrimitive.Root
+      ref={ref}
+      decorative={decorative}
+      orientation={orientation}
+      className={cn(
+        "shrink-0 bg-border",
+        orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
+        className
+      )}
+      {...props}
+    />
+  )
+)
+Separator.displayName = SeparatorPrimitive.Root.displayName
+
+export { Separator }
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7b1dbe4c4515258f91cad0234c685771859e962a
--- /dev/null
+++ b/src/components/ui/sheet.tsx
@@ -0,0 +1,131 @@
+import * as SheetPrimitive from "@radix-ui/react-dialog"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Overlay>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Overlay
+    className={cn(
+      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
+      className
+    )}
+    {...props}
+    ref={ref}
+  />
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+  "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
+  {
+    variants: {
+      side: {
+        top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+        bottom:
+          "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
+        left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+        right:
+          "inset-y-0 right-0 h-full w-3/4  border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+      },
+    },
+    defaultVariants: {
+      side: "right",
+    },
+  }
+)
+
+interface SheetContentProps
+  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
+  VariantProps<typeof sheetVariants> { }
+
+const SheetContent = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Content>,
+  SheetContentProps
+>(({ side = "right", className, children, ...props }, ref) => (
+  <SheetPortal>
+    <SheetOverlay />
+    <SheetPrimitive.Content
+      ref={ref}
+      className={cn(sheetVariants({ side }), className)}
+      {...props}
+    >
+      {children}
+      <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
+        <X className="h-4 w-4" />
+        <span className="sr-only">Close</span>
+      </SheetPrimitive.Close>
+    </SheetPrimitive.Content>
+  </SheetPortal>
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col space-y-2 text-center sm:text-left",
+      className
+    )}
+    {...props}
+  />
+)
+SheetHeader.displayName = "SheetHeader"
+
+const SheetFooter = ({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) => (
+  <div
+    className={cn(
+      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
+      className
+    )}
+    {...props}
+  />
+)
+SheetFooter.displayName = "SheetFooter"
+
+const SheetTitle = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Title>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Title
+    ref={ref}
+    className={cn("text-lg font-semibold text-foreground", className)}
+    {...props}
+  />
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef<
+  React.ElementRef<typeof SheetPrimitive.Description>,
+  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
+>(({ className, ...props }, ref) => (
+  <SheetPrimitive.Description
+    ref={ref}
+    className={cn("text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export {
+  Sheet, SheetClose,
+  SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger
+}
+
diff --git a/src/components/ui/sidebar.tsx b/src/components/ui/sidebar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..1a566bf5537500fec3e02f3e6249e51d0ec23cea
--- /dev/null
+++ b/src/components/ui/sidebar.tsx
@@ -0,0 +1,761 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { VariantProps, cva } from "class-variance-authority"
+import { PanelLeft } from "lucide-react"
+
+import { useIsMobile } from "@/hooks/use-mobile"
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Separator } from "@/components/ui/separator"
+import { Sheet, SheetContent } from "@/components/ui/sheet"
+import { Skeleton } from "@/components/ui/skeleton"
+import {
+  Tooltip,
+  TooltipContent,
+  TooltipProvider,
+  TooltipTrigger,
+} from "@/components/ui/tooltip"
+
+const SIDEBAR_COOKIE_NAME = "sidebar:state"
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
+const SIDEBAR_WIDTH = "16rem"
+const SIDEBAR_WIDTH_MOBILE = "18rem"
+const SIDEBAR_WIDTH_ICON = "3rem"
+const SIDEBAR_KEYBOARD_SHORTCUT = "b"
+
+type SidebarContext = {
+  state: "expanded" | "collapsed"
+  open: boolean
+  setOpen: (open: boolean) => void
+  openMobile: boolean
+  setOpenMobile: (open: boolean) => void
+  isMobile: boolean
+  toggleSidebar: () => void
+}
+
+const SidebarContext = React.createContext<SidebarContext | null>(null)
+
+function useSidebar() {
+  const context = React.useContext(SidebarContext)
+  if (!context) {
+    throw new Error("useSidebar must be used within a SidebarProvider.")
+  }
+
+  return context
+}
+
+const SidebarProvider = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & {
+    defaultOpen?: boolean
+    open?: boolean
+    onOpenChange?: (open: boolean) => void
+  }
+>(
+  (
+    {
+      defaultOpen = true,
+      open: openProp,
+      onOpenChange: setOpenProp,
+      className,
+      style,
+      children,
+      ...props
+    },
+    ref
+  ) => {
+    const isMobile = useIsMobile()
+    const [openMobile, setOpenMobile] = React.useState(false)
+
+    // This is the internal state of the sidebar.
+    // We use openProp and setOpenProp for control from outside the component.
+    const [_open, _setOpen] = React.useState(defaultOpen)
+    const open = openProp ?? _open
+    const setOpen = React.useCallback(
+      (value: boolean | ((value: boolean) => boolean)) => {
+        const openState = typeof value === "function" ? value(open) : value
+        if (setOpenProp) {
+          setOpenProp(openState)
+        } else {
+          _setOpen(openState)
+        }
+
+        // This sets the cookie to keep the sidebar state.
+        document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
+      },
+      [setOpenProp, open]
+    )
+
+    // Helper to toggle the sidebar.
+    const toggleSidebar = React.useCallback(() => {
+      return isMobile
+        ? setOpenMobile((open) => !open)
+        : setOpen((open) => !open)
+    }, [isMobile, setOpen, setOpenMobile])
+
+    // Adds a keyboard shortcut to toggle the sidebar.
+    React.useEffect(() => {
+      const handleKeyDown = (event: KeyboardEvent) => {
+        if (
+          event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
+          (event.metaKey || event.ctrlKey)
+        ) {
+          event.preventDefault()
+          toggleSidebar()
+        }
+      }
+
+      window.addEventListener("keydown", handleKeyDown)
+      return () => window.removeEventListener("keydown", handleKeyDown)
+    }, [toggleSidebar])
+
+    // We add a state so that we can do data-state="expanded" or "collapsed".
+    // This makes it easier to style the sidebar with Tailwind classes.
+    const state = open ? "expanded" : "collapsed"
+
+    const contextValue = React.useMemo<SidebarContext>(
+      () => ({
+        state,
+        open,
+        setOpen,
+        isMobile,
+        openMobile,
+        setOpenMobile,
+        toggleSidebar,
+      }),
+      [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+    )
+
+    return (
+      <SidebarContext.Provider value={contextValue}>
+        <TooltipProvider delayDuration={0}>
+          <div
+            style={
+              {
+                "--sidebar-width": SIDEBAR_WIDTH,
+                "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
+                ...style,
+              } as React.CSSProperties
+            }
+            className={cn(
+              "group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar",
+              className
+            )}
+            ref={ref}
+            {...props}
+          >
+            {children}
+          </div>
+        </TooltipProvider>
+      </SidebarContext.Provider>
+    )
+  }
+)
+SidebarProvider.displayName = "SidebarProvider"
+
+const Sidebar = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & {
+    side?: "left" | "right"
+    variant?: "sidebar" | "floating" | "inset"
+    collapsible?: "offcanvas" | "icon" | "none"
+  }
+>(
+  (
+    {
+      side = "left",
+      variant = "sidebar",
+      collapsible = "offcanvas",
+      className,
+      children,
+      ...props
+    },
+    ref
+  ) => {
+    const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
+
+    if (collapsible === "none") {
+      return (
+        <div
+          className={cn(
+            "flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground",
+            className
+          )}
+          ref={ref}
+          {...props}
+        >
+          {children}
+        </div>
+      )
+    }
+
+    if (isMobile) {
+      return (
+        <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
+          <SheetContent
+            data-sidebar="sidebar"
+            data-mobile="true"
+            className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
+            style={
+              {
+                "--sidebar-width": SIDEBAR_WIDTH_MOBILE,
+              } as React.CSSProperties
+            }
+            side={side}
+          >
+            <div className="flex h-full w-full flex-col">{children}</div>
+          </SheetContent>
+        </Sheet>
+      )
+    }
+
+    return (
+      <div
+        ref={ref}
+        className="group peer hidden md:block text-sidebar-foreground"
+        data-state={state}
+        data-collapsible={state === "collapsed" ? collapsible : ""}
+        data-variant={variant}
+        data-side={side}
+      >
+        {/* This is what handles the sidebar gap on desktop */}
+        <div
+          className={cn(
+            "duration-200 relative h-svh w-[--sidebar-width] bg-transparent transition-[width] ease-linear",
+            "group-data-[collapsible=offcanvas]:w-0",
+            "group-data-[side=right]:rotate-180",
+            variant === "floating" || variant === "inset"
+              ? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]"
+              : "group-data-[collapsible=icon]:w-[--sidebar-width-icon]"
+          )}
+        />
+        <div
+          className={cn(
+            "duration-200 fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] ease-linear md:flex",
+            side === "left"
+              ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
+              : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
+            // Adjust the padding for floating and inset variants.
+            variant === "floating" || variant === "inset"
+              ? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
+              : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
+            className
+          )}
+          {...props}
+        >
+          <div
+            data-sidebar="sidebar"
+            className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow"
+          >
+            {children}
+          </div>
+        </div>
+      </div>
+    )
+  }
+)
+Sidebar.displayName = "Sidebar"
+
+const SidebarTrigger = React.forwardRef<
+  React.ElementRef<typeof Button>,
+  React.ComponentProps<typeof Button>
+>(({ className, onClick, ...props }, ref) => {
+  const { toggleSidebar } = useSidebar()
+
+  return (
+    <Button
+      ref={ref}
+      data-sidebar="trigger"
+      variant="ghost"
+      size="icon"
+      className={cn("h-7 w-7", className)}
+      onClick={(event) => {
+        onClick?.(event)
+        toggleSidebar()
+      }}
+      {...props}
+    >
+      <PanelLeft />
+      <span className="sr-only">Toggle Sidebar</span>
+    </Button>
+  )
+})
+SidebarTrigger.displayName = "SidebarTrigger"
+
+const SidebarRail = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<"button">
+>(({ className, ...props }, ref) => {
+  const { toggleSidebar } = useSidebar()
+
+  return (
+    <button
+      ref={ref}
+      data-sidebar="rail"
+      aria-label="Toggle Sidebar"
+      tabIndex={-1}
+      onClick={toggleSidebar}
+      title="Toggle Sidebar"
+      className={cn(
+        "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
+        "[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize",
+        "[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
+        "group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar",
+        "[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
+        "[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarRail.displayName = "SidebarRail"
+
+const SidebarInset = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"main">
+>(({ className, ...props }, ref) => {
+  return (
+    <main
+      ref={ref}
+      className={cn(
+        "relative flex min-h-svh flex-1 flex-col bg-background",
+        "peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarInset.displayName = "SidebarInset"
+
+const SidebarInput = React.forwardRef<
+  React.ElementRef<typeof Input>,
+  React.ComponentProps<typeof Input>
+>(({ className, ...props }, ref) => {
+  return (
+    <Input
+      ref={ref}
+      data-sidebar="input"
+      className={cn(
+        "h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarInput.displayName = "SidebarInput"
+
+const SidebarHeader = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar="header"
+      className={cn("flex flex-col gap-2 p-2", className)}
+      {...props}
+    />
+  )
+})
+SidebarHeader.displayName = "SidebarHeader"
+
+const SidebarFooter = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar="footer"
+      className={cn("flex flex-col gap-2 p-2", className)}
+      {...props}
+    />
+  )
+})
+SidebarFooter.displayName = "SidebarFooter"
+
+const SidebarSeparator = React.forwardRef<
+  React.ElementRef<typeof Separator>,
+  React.ComponentProps<typeof Separator>
+>(({ className, ...props }, ref) => {
+  return (
+    <Separator
+      ref={ref}
+      data-sidebar="separator"
+      className={cn("mx-2 w-auto bg-sidebar-border", className)}
+      {...props}
+    />
+  )
+})
+SidebarSeparator.displayName = "SidebarSeparator"
+
+const SidebarContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar="content"
+      className={cn(
+        "flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarContent.displayName = "SidebarContent"
+
+const SidebarGroup = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => {
+  return (
+    <div
+      ref={ref}
+      data-sidebar="group"
+      className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
+      {...props}
+    />
+  )
+})
+SidebarGroup.displayName = "SidebarGroup"
+
+const SidebarGroupLabel = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : "div"
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar="group-label"
+      className={cn(
+        "duration-200 flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+        "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarGroupLabel.displayName = "SidebarGroupLabel"
+
+const SidebarGroupAction = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<"button"> & { asChild?: boolean }
+>(({ className, asChild = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : "button"
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar="group-action"
+      className={cn(
+        "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
+        // Increases the hit area of the button on mobile.
+        "after:absolute after:-inset-2 after:md:hidden",
+        "group-data-[collapsible=icon]:hidden",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarGroupAction.displayName = "SidebarGroupAction"
+
+const SidebarGroupContent = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    data-sidebar="group-content"
+    className={cn("w-full text-sm", className)}
+    {...props}
+  />
+))
+SidebarGroupContent.displayName = "SidebarGroupContent"
+
+const SidebarMenu = React.forwardRef<
+  HTMLUListElement,
+  React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+  <ul
+    ref={ref}
+    data-sidebar="menu"
+    className={cn("flex w-full min-w-0 flex-col gap-1", className)}
+    {...props}
+  />
+))
+SidebarMenu.displayName = "SidebarMenu"
+
+const SidebarMenuItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentProps<"li">
+>(({ className, ...props }, ref) => (
+  <li
+    ref={ref}
+    data-sidebar="menu-item"
+    className={cn("group/menu-item relative", className)}
+    {...props}
+  />
+))
+SidebarMenuItem.displayName = "SidebarMenuItem"
+
+const sidebarMenuButtonVariants = cva(
+  "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+  {
+    variants: {
+      variant: {
+        default: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
+        outline:
+          "bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
+      },
+      size: {
+        default: "h-8 text-sm",
+        sm: "h-7 text-xs",
+        lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  }
+)
+
+const SidebarMenuButton = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<"button"> & {
+    asChild?: boolean
+    isActive?: boolean
+    tooltip?: string | React.ComponentProps<typeof TooltipContent>
+  } & VariantProps<typeof sidebarMenuButtonVariants>
+>(
+  (
+    {
+      asChild = false,
+      isActive = false,
+      variant = "default",
+      size = "default",
+      tooltip,
+      className,
+      ...props
+    },
+    ref
+  ) => {
+    const Comp = asChild ? Slot : "button"
+    const { isMobile, state } = useSidebar()
+
+    const button = (
+      <Comp
+        ref={ref}
+        data-sidebar="menu-button"
+        data-size={size}
+        data-active={isActive}
+        className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
+        {...props}
+      />
+    )
+
+    if (!tooltip) {
+      return button
+    }
+
+    if (typeof tooltip === "string") {
+      tooltip = {
+        children: tooltip,
+      }
+    }
+
+    return (
+      <Tooltip>
+        <TooltipTrigger asChild>{button}</TooltipTrigger>
+        <TooltipContent
+          side="right"
+          align="center"
+          hidden={state !== "collapsed" || isMobile}
+          {...tooltip}
+        />
+      </Tooltip>
+    )
+  }
+)
+SidebarMenuButton.displayName = "SidebarMenuButton"
+
+const SidebarMenuAction = React.forwardRef<
+  HTMLButtonElement,
+  React.ComponentProps<"button"> & {
+    asChild?: boolean
+    showOnHover?: boolean
+  }
+>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
+  const Comp = asChild ? Slot : "button"
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar="menu-action"
+      className={cn(
+        "absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
+        // Increases the hit area of the button on mobile.
+        "after:absolute after:-inset-2 after:md:hidden",
+        "peer-data-[size=sm]/menu-button:top-1",
+        "peer-data-[size=default]/menu-button:top-1.5",
+        "peer-data-[size=lg]/menu-button:top-2.5",
+        "group-data-[collapsible=icon]:hidden",
+        showOnHover &&
+          "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarMenuAction.displayName = "SidebarMenuAction"
+
+const SidebarMenuBadge = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div">
+>(({ className, ...props }, ref) => (
+  <div
+    ref={ref}
+    data-sidebar="menu-badge"
+    className={cn(
+      "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground select-none pointer-events-none",
+      "peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
+      "peer-data-[size=sm]/menu-button:top-1",
+      "peer-data-[size=default]/menu-button:top-1.5",
+      "peer-data-[size=lg]/menu-button:top-2.5",
+      "group-data-[collapsible=icon]:hidden",
+      className
+    )}
+    {...props}
+  />
+))
+SidebarMenuBadge.displayName = "SidebarMenuBadge"
+
+const SidebarMenuSkeleton = React.forwardRef<
+  HTMLDivElement,
+  React.ComponentProps<"div"> & {
+    showIcon?: boolean
+  }
+>(({ className, showIcon = false, ...props }, ref) => {
+  // Random width between 50 to 90%.
+  const width = React.useMemo(() => {
+    return `${Math.floor(Math.random() * 40) + 50}%`
+  }, [])
+
+  return (
+    <div
+      ref={ref}
+      data-sidebar="menu-skeleton"
+      className={cn("rounded-md h-8 flex gap-2 px-2 items-center", className)}
+      {...props}
+    >
+      {showIcon && (
+        <Skeleton
+          className="size-4 rounded-md"
+          data-sidebar="menu-skeleton-icon"
+        />
+      )}
+      <Skeleton
+        className="h-4 flex-1 max-w-[--skeleton-width]"
+        data-sidebar="menu-skeleton-text"
+        style={
+          {
+            "--skeleton-width": width,
+          } as React.CSSProperties
+        }
+      />
+    </div>
+  )
+})
+SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton"
+
+const SidebarMenuSub = React.forwardRef<
+  HTMLUListElement,
+  React.ComponentProps<"ul">
+>(({ className, ...props }, ref) => (
+  <ul
+    ref={ref}
+    data-sidebar="menu-sub"
+    className={cn(
+      "mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5",
+      "group-data-[collapsible=icon]:hidden",
+      className
+    )}
+    {...props}
+  />
+))
+SidebarMenuSub.displayName = "SidebarMenuSub"
+
+const SidebarMenuSubItem = React.forwardRef<
+  HTMLLIElement,
+  React.ComponentProps<"li">
+>(({ ...props }, ref) => <li ref={ref} {...props} />)
+SidebarMenuSubItem.displayName = "SidebarMenuSubItem"
+
+const SidebarMenuSubButton = React.forwardRef<
+  HTMLAnchorElement,
+  React.ComponentProps<"a"> & {
+    asChild?: boolean
+    size?: "sm" | "md"
+    isActive?: boolean
+  }
+>(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
+  const Comp = asChild ? Slot : "a"
+
+  return (
+    <Comp
+      ref={ref}
+      data-sidebar="menu-sub-button"
+      data-size={size}
+      data-active={isActive}
+      className={cn(
+        "flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
+        "data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
+        size === "sm" && "text-xs",
+        size === "md" && "text-sm",
+        "group-data-[collapsible=icon]:hidden",
+        className
+      )}
+      {...props}
+    />
+  )
+})
+SidebarMenuSubButton.displayName = "SidebarMenuSubButton"
+
+export {
+  Sidebar,
+  SidebarContent,
+  SidebarFooter,
+  SidebarGroup,
+  SidebarGroupAction,
+  SidebarGroupContent,
+  SidebarGroupLabel,
+  SidebarHeader,
+  SidebarInput,
+  SidebarInset,
+  SidebarMenu,
+  SidebarMenuAction,
+  SidebarMenuBadge,
+  SidebarMenuButton,
+  SidebarMenuItem,
+  SidebarMenuSkeleton,
+  SidebarMenuSub,
+  SidebarMenuSubButton,
+  SidebarMenuSubItem,
+  SidebarProvider,
+  SidebarRail,
+  SidebarSeparator,
+  SidebarTrigger,
+  useSidebar,
+}
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..01b8b6d4f716ff7c26065bc9e46aebd932729fc1
--- /dev/null
+++ b/src/components/ui/skeleton.tsx
@@ -0,0 +1,15 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({
+  className,
+  ...props
+}: React.HTMLAttributes<HTMLDivElement>) {
+  return (
+    <div
+      className={cn("animate-pulse rounded-md bg-muted", className)}
+      {...props}
+    />
+  )
+}
+
+export { Skeleton }
diff --git a/src/components/ui/slider.tsx b/src/components/ui/slider.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e161daec03130190dcfb597c8632f9df4ba407f5
--- /dev/null
+++ b/src/components/ui/slider.tsx
@@ -0,0 +1,26 @@
+import * as React from "react"
+import * as SliderPrimitive from "@radix-ui/react-slider"
+
+import { cn } from "@/lib/utils"
+
+const Slider = React.forwardRef<
+  React.ElementRef<typeof SliderPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
+>(({ className, ...props }, ref) => (
+  <SliderPrimitive.Root
+    ref={ref}
+    className={cn(
+      "relative flex w-full touch-none select-none items-center",
+      className
+    )}
+    {...props}
+  >
+    <SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-secondary">
+      <SliderPrimitive.Range className="absolute h-full bg-primary" />
+    </SliderPrimitive.Track>
+    <SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
+  </SliderPrimitive.Root>
+))
+Slider.displayName = SliderPrimitive.Root.displayName
+
+export { Slider }
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..35418149c0656c2257baf517e85496ef759677f5
--- /dev/null
+++ b/src/components/ui/sonner.tsx
@@ -0,0 +1,29 @@
+import { useTheme } from "next-themes"
+import { Toaster as Sonner, toast } from "sonner"
+
+type ToasterProps = React.ComponentProps<typeof Sonner>
+
+const Toaster = ({ ...props }: ToasterProps) => {
+  const { theme = "system" } = useTheme()
+
+  return (
+    <Sonner
+      theme={theme as ToasterProps["theme"]}
+      className="toaster group"
+      toastOptions={{
+        classNames: {
+          toast:
+            "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
+          description: "group-[.toast]:text-muted-foreground",
+          actionButton:
+            "group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
+          cancelButton:
+            "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
+        },
+      }}
+      {...props}
+    />
+  )
+}
+
+export { Toaster, toast }
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..aa58baa29c676db7b14a37436a9662107b0111ec
--- /dev/null
+++ b/src/components/ui/switch.tsx
@@ -0,0 +1,27 @@
+import * as React from "react"
+import * as SwitchPrimitives from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+const Switch = React.forwardRef<
+  React.ElementRef<typeof SwitchPrimitives.Root>,
+  React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
+>(({ className, ...props }, ref) => (
+  <SwitchPrimitives.Root
+    className={cn(
+      "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
+      className
+    )}
+    {...props}
+    ref={ref}
+  >
+    <SwitchPrimitives.Thumb
+      className={cn(
+        "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0"
+      )}
+    />
+  </SwitchPrimitives.Root>
+))
+Switch.displayName = SwitchPrimitives.Root.displayName
+
+export { Switch }
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7f3502f8b2820be1d6f0aa4c1ffaa351799c1ed3
--- /dev/null
+++ b/src/components/ui/table.tsx
@@ -0,0 +1,117 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Table = React.forwardRef<
+  HTMLTableElement,
+  React.HTMLAttributes<HTMLTableElement>
+>(({ className, ...props }, ref) => (
+  <div className="relative w-full overflow-auto">
+    <table
+      ref={ref}
+      className={cn("w-full caption-bottom text-sm", className)}
+      {...props}
+    />
+  </div>
+))
+Table.displayName = "Table"
+
+const TableHeader = React.forwardRef<
+  HTMLTableSectionElement,
+  React.HTMLAttributes<HTMLTableSectionElement>
+>(({ className, ...props }, ref) => (
+  <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
+))
+TableHeader.displayName = "TableHeader"
+
+const TableBody = React.forwardRef<
+  HTMLTableSectionElement,
+  React.HTMLAttributes<HTMLTableSectionElement>
+>(({ className, ...props }, ref) => (
+  <tbody
+    ref={ref}
+    className={cn("[&_tr:last-child]:border-0", className)}
+    {...props}
+  />
+))
+TableBody.displayName = "TableBody"
+
+const TableFooter = React.forwardRef<
+  HTMLTableSectionElement,
+  React.HTMLAttributes<HTMLTableSectionElement>
+>(({ className, ...props }, ref) => (
+  <tfoot
+    ref={ref}
+    className={cn(
+      "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
+      className
+    )}
+    {...props}
+  />
+))
+TableFooter.displayName = "TableFooter"
+
+const TableRow = React.forwardRef<
+  HTMLTableRowElement,
+  React.HTMLAttributes<HTMLTableRowElement>
+>(({ className, ...props }, ref) => (
+  <tr
+    ref={ref}
+    className={cn(
+      "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
+      className
+    )}
+    {...props}
+  />
+))
+TableRow.displayName = "TableRow"
+
+const TableHead = React.forwardRef<
+  HTMLTableCellElement,
+  React.ThHTMLAttributes<HTMLTableCellElement>
+>(({ className, ...props }, ref) => (
+  <th
+    ref={ref}
+    className={cn(
+      "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
+      className
+    )}
+    {...props}
+  />
+))
+TableHead.displayName = "TableHead"
+
+const TableCell = React.forwardRef<
+  HTMLTableCellElement,
+  React.TdHTMLAttributes<HTMLTableCellElement>
+>(({ className, ...props }, ref) => (
+  <td
+    ref={ref}
+    className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
+    {...props}
+  />
+))
+TableCell.displayName = "TableCell"
+
+const TableCaption = React.forwardRef<
+  HTMLTableCaptionElement,
+  React.HTMLAttributes<HTMLTableCaptionElement>
+>(({ className, ...props }, ref) => (
+  <caption
+    ref={ref}
+    className={cn("mt-4 text-sm text-muted-foreground", className)}
+    {...props}
+  />
+))
+TableCaption.displayName = "TableCaption"
+
+export {
+  Table,
+  TableHeader,
+  TableBody,
+  TableFooter,
+  TableHead,
+  TableRow,
+  TableCell,
+  TableCaption,
+}
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f57fffdb5a07cd21b12f8c014513475a6980a469
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,53 @@
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+const Tabs = TabsPrimitive.Root
+
+const TabsList = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.List>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.List
+    ref={ref}
+    className={cn(
+      "inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
+      className
+    )}
+    {...props}
+  />
+))
+TabsList.displayName = TabsPrimitive.List.displayName
+
+const TabsTrigger = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.Trigger>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.Trigger
+    ref={ref}
+    className={cn(
+      "inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
+      className
+    )}
+    {...props}
+  />
+))
+TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
+
+const TabsContent = React.forwardRef<
+  React.ElementRef<typeof TabsPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
+>(({ className, ...props }, ref) => (
+  <TabsPrimitive.Content
+    ref={ref}
+    className={cn(
+      "mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
+      className
+    )}
+    {...props}
+  />
+))
+TabsContent.displayName = TabsPrimitive.Content.displayName
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9f9a6dc56193b728feda6d9b1a3d9302b8880f0b
--- /dev/null
+++ b/src/components/ui/textarea.tsx
@@ -0,0 +1,24 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+export interface TextareaProps
+  extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
+
+const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
+  ({ className, ...props }, ref) => {
+    return (
+      <textarea
+        className={cn(
+          "flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
+          className
+        )}
+        ref={ref}
+        {...props}
+      />
+    )
+  }
+)
+Textarea.displayName = "Textarea"
+
+export { Textarea }
diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a822477534192c4df5073e4015f7461e739d3344
--- /dev/null
+++ b/src/components/ui/toast.tsx
@@ -0,0 +1,127 @@
+import * as React from "react"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva, type VariantProps } from "class-variance-authority"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Viewport>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Viewport
+    ref={ref}
+    className={cn(
+      "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
+      className
+    )}
+    {...props}
+  />
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+  "group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+  {
+    variants: {
+      variant: {
+        default: "border bg-background text-foreground",
+        destructive:
+          "destructive group border-destructive bg-destructive text-destructive-foreground",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+    },
+  }
+)
+
+const Toast = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Root>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
+    VariantProps<typeof toastVariants>
+>(({ className, variant, ...props }, ref) => {
+  return (
+    <ToastPrimitives.Root
+      ref={ref}
+      className={cn(toastVariants({ variant }), className)}
+      {...props}
+    />
+  )
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Action>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Action
+    ref={ref}
+    className={cn(
+      "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
+      className
+    )}
+    {...props}
+  />
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Close>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Close
+    ref={ref}
+    className={cn(
+      "absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
+      className
+    )}
+    toast-close=""
+    {...props}
+  >
+    <X className="h-4 w-4" />
+  </ToastPrimitives.Close>
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Title>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Title
+    ref={ref}
+    className={cn("text-sm font-semibold", className)}
+    {...props}
+  />
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef<
+  React.ElementRef<typeof ToastPrimitives.Description>,
+  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
+>(({ className, ...props }, ref) => (
+  <ToastPrimitives.Description
+    ref={ref}
+    className={cn("text-sm opacity-90", className)}
+    {...props}
+  />
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
+
+type ToastActionElement = React.ReactElement<typeof ToastAction>
+
+export {
+  type ToastProps,
+  type ToastActionElement,
+  ToastProvider,
+  ToastViewport,
+  Toast,
+  ToastTitle,
+  ToastDescription,
+  ToastClose,
+  ToastAction,
+}
diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6c67edff67a48c6ac1c46f25a6d468dd461e66cb
--- /dev/null
+++ b/src/components/ui/toaster.tsx
@@ -0,0 +1,33 @@
+import { useToast } from "@/hooks/use-toast"
+import {
+  Toast,
+  ToastClose,
+  ToastDescription,
+  ToastProvider,
+  ToastTitle,
+  ToastViewport,
+} from "@/components/ui/toast"
+
+export function Toaster() {
+  const { toasts } = useToast()
+
+  return (
+    <ToastProvider>
+      {toasts.map(function ({ id, title, description, action, ...props }) {
+        return (
+          <Toast key={id} {...props}>
+            <div className="grid gap-1">
+              {title && <ToastTitle>{title}</ToastTitle>}
+              {description && (
+                <ToastDescription>{description}</ToastDescription>
+              )}
+            </div>
+            {action}
+            <ToastClose />
+          </Toast>
+        )
+      })}
+      <ToastViewport />
+    </ToastProvider>
+  )
+}
diff --git a/src/components/ui/toggle-group.tsx b/src/components/ui/toggle-group.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..afe5da62559ca05a0eb5c9415a1e20bd47b37af9
--- /dev/null
+++ b/src/components/ui/toggle-group.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
+import { type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { toggleVariants } from "@/components/ui/toggle"
+
+const ToggleGroupContext = React.createContext<
+  VariantProps<typeof toggleVariants>
+>({
+  size: "default",
+  variant: "default",
+})
+
+const ToggleGroup = React.forwardRef<
+  React.ElementRef<typeof ToggleGroupPrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
+    VariantProps<typeof toggleVariants>
+>(({ className, variant, size, children, ...props }, ref) => (
+  <ToggleGroupPrimitive.Root
+    ref={ref}
+    className={cn("flex items-center justify-center gap-1", className)}
+    {...props}
+  >
+    <ToggleGroupContext.Provider value={{ variant, size }}>
+      {children}
+    </ToggleGroupContext.Provider>
+  </ToggleGroupPrimitive.Root>
+))
+
+ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
+
+const ToggleGroupItem = React.forwardRef<
+  React.ElementRef<typeof ToggleGroupPrimitive.Item>,
+  React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
+    VariantProps<typeof toggleVariants>
+>(({ className, children, variant, size, ...props }, ref) => {
+  const context = React.useContext(ToggleGroupContext)
+
+  return (
+    <ToggleGroupPrimitive.Item
+      ref={ref}
+      className={cn(
+        toggleVariants({
+          variant: context.variant || variant,
+          size: context.size || size,
+        }),
+        className
+      )}
+      {...props}
+    >
+      {children}
+    </ToggleGroupPrimitive.Item>
+  )
+})
+
+ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
+
+export { ToggleGroup, ToggleGroupItem }
diff --git a/src/components/ui/toggle.tsx b/src/components/ui/toggle.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9ecac28ee639d98fbc161a904b5bca0e022e28d6
--- /dev/null
+++ b/src/components/ui/toggle.tsx
@@ -0,0 +1,43 @@
+import * as React from "react"
+import * as TogglePrimitive from "@radix-ui/react-toggle"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const toggleVariants = cva(
+  "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
+  {
+    variants: {
+      variant: {
+        default: "bg-transparent",
+        outline:
+          "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
+      },
+      size: {
+        default: "h-10 px-3",
+        sm: "h-9 px-2.5",
+        lg: "h-11 px-5",
+      },
+    },
+    defaultVariants: {
+      variant: "default",
+      size: "default",
+    },
+  }
+)
+
+const Toggle = React.forwardRef<
+  React.ElementRef<typeof TogglePrimitive.Root>,
+  React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
+    VariantProps<typeof toggleVariants>
+>(({ className, variant, size, ...props }, ref) => (
+  <TogglePrimitive.Root
+    ref={ref}
+    className={cn(toggleVariants({ variant, size, className }))}
+    {...props}
+  />
+))
+
+Toggle.displayName = TogglePrimitive.Root.displayName
+
+export { Toggle, toggleVariants }
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e121f0aea0b32952ae54d7db8965a2c15168b13e
--- /dev/null
+++ b/src/components/ui/tooltip.tsx
@@ -0,0 +1,28 @@
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/lib/utils"
+
+const TooltipProvider = TooltipPrimitive.Provider
+
+const Tooltip = TooltipPrimitive.Root
+
+const TooltipTrigger = TooltipPrimitive.Trigger
+
+const TooltipContent = React.forwardRef<
+  React.ElementRef<typeof TooltipPrimitive.Content>,
+  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
+>(({ className, sideOffset = 4, ...props }, ref) => (
+  <TooltipPrimitive.Content
+    ref={ref}
+    sideOffset={sideOffset}
+    className={cn(
+      "z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
+      className
+    )}
+    {...props}
+  />
+))
+TooltipContent.displayName = TooltipPrimitive.Content.displayName
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/src/components/ui/use-toast.ts b/src/components/ui/use-toast.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b0aef21be259883fe159d3176ab46c6841c28a07
--- /dev/null
+++ b/src/components/ui/use-toast.ts
@@ -0,0 +1,3 @@
+import { useToast, toast } from "@/hooks/use-toast";
+
+export { useToast, toast };
diff --git a/src/contexts/DragAndDropContext.tsx b/src/contexts/DragAndDropContext.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6d9989bd885998add143f17d73b955df3d6cb0f8
--- /dev/null
+++ b/src/contexts/DragAndDropContext.tsx
@@ -0,0 +1,123 @@
+import React, {
+  createContext,
+  useState,
+  useEffect,
+  ReactNode,
+  useCallback,
+} from "react";
+
+import { processDroppedFiles } from "@/lib/UrdfDragAndDrop";
+import { useUrdf } from "@/hooks/useUrdf";
+
+export type DragAndDropContextType = {
+  isDragging: boolean;
+  setIsDragging: (isDragging: boolean) => void;
+  handleDrop: (e: DragEvent) => Promise<void>;
+};
+
+export const DragAndDropContext = createContext<
+  DragAndDropContextType | undefined
+>(undefined);
+
+interface DragAndDropProviderProps {
+  children: ReactNode;
+}
+
+export const DragAndDropProvider: React.FC<DragAndDropProviderProps> = ({
+  children,
+}) => {
+  const [isDragging, setIsDragging] = useState(false);
+
+  // Get the Urdf context
+  const { urdfProcessor, processUrdfFiles } = useUrdf();
+
+  const handleDragOver = (e: DragEvent) => {
+    e.preventDefault();
+    e.stopPropagation();
+  };
+
+  const handleDragEnter = (e: DragEvent) => {
+    e.preventDefault();
+    e.stopPropagation();
+    setIsDragging(true);
+  };
+
+  const handleDragLeave = (e: DragEvent) => {
+    e.preventDefault();
+    e.stopPropagation();
+
+    // Only set isDragging to false if we're leaving the document
+    // This prevents flickering when moving between elements
+    if (!e.relatedTarget || !(e.relatedTarget as Element).closest("html")) {
+      setIsDragging(false);
+    }
+  };
+
+  const handleDrop = useCallback(
+    async (e: DragEvent) => {
+      e.preventDefault();
+      e.stopPropagation();
+      setIsDragging(false);
+
+      console.log("🔄 DragAndDropContext: Drop event detected");
+
+      if (!e.dataTransfer || !urdfProcessor) {
+        console.error("❌ No dataTransfer or urdfProcessor available");
+        return;
+      }
+
+      try {
+        console.log("🔍 Processing dropped files with urdfProcessor");
+
+        // Process files first
+        const { availableModels, files } = await processDroppedFiles(
+          e.dataTransfer,
+          urdfProcessor
+        );
+
+        // Delegate further processing to UrdfContext
+        await processUrdfFiles(files, availableModels);
+      } catch (error) {
+        console.error("❌ Error in handleDrop:", error);
+      }
+    },
+    [urdfProcessor, processUrdfFiles]
+  );
+
+  // Set up global event listeners
+  useEffect(() => {
+    document.addEventListener("dragover", handleDragOver);
+    document.addEventListener("dragenter", handleDragEnter);
+    document.addEventListener("dragleave", handleDragLeave);
+    document.addEventListener("drop", handleDrop);
+
+    return () => {
+      document.removeEventListener("dragover", handleDragOver);
+      document.removeEventListener("dragenter", handleDragEnter);
+      document.removeEventListener("dragleave", handleDragLeave);
+      document.removeEventListener("drop", handleDrop);
+    };
+  }, [handleDrop]); // Re-register when handleDrop changes
+
+  return (
+    <DragAndDropContext.Provider
+      value={{
+        isDragging,
+        setIsDragging,
+        handleDrop,
+      }}
+    >
+      {children}
+      {isDragging && (
+        <div className="fixed inset-0 bg-primary/10 pointer-events-none z-50 flex items-center justify-center">
+          <div className="bg-background p-8 rounded-lg shadow-lg text-center">
+            <div className="text-3xl font-bold mb-4">Drop Urdf Files Here</div>
+            <p className="text-muted-foreground">
+              Release to upload your robot model
+            </p>
+          </div>
+        </div>
+      )}
+    </DragAndDropContext.Provider>
+  );
+};
diff --git a/src/contexts/UrdfContext.tsx b/src/contexts/UrdfContext.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..279c916c59379af381b86120a539105d1b322ac8
--- /dev/null
+++ b/src/contexts/UrdfContext.tsx
@@ -0,0 +1,636 @@
+import React, {
+  createContext,
+  useState,
+  useCallback,
+  ReactNode,
+  useRef,
+  useEffect,
+} from "react";
+import { toast } from "sonner";
+import { UrdfProcessor, readUrdfFileContent } from "@/lib/UrdfDragAndDrop";
+import { UrdfData, UrdfFileModel } from "@/lib/types";
+import { useDefaultRobotData } from "@/hooks/useDefaultRobotData";
+import { RobotAnimationConfig } from "@/lib/types";
+
+// Define the result interface for Urdf detection
+interface UrdfDetectionResult {
+  hasUrdf: boolean;
+  modelName?: string;
+  parsedData?: UrdfData | null;
+}
+
+// Define the context type
+export type UrdfContextType = {
+  urdfProcessor: UrdfProcessor | null;
+  registerUrdfProcessor: (processor: UrdfProcessor) => void;
+  onUrdfDetected: (
+    callback: (result: UrdfDetectionResult) => void
+  ) => () => void;
+  processUrdfFiles: (
+    files: Record<string, File>,
+    availableModels: string[]
+  ) => Promise<void>;
+  urdfBlobUrls: Record<string, string>;
+  alternativeUrdfModels: string[];
+  isSelectionModalOpen: boolean;
+  setIsSelectionModalOpen: (isOpen: boolean) => void;
+  urdfModelOptions: UrdfFileModel[];
+  selectUrdfModel: (model: UrdfFileModel) => void;
+
+  // Centralized robot data management
+  currentRobotData: UrdfData | null;
+  isDefaultModel: boolean;
+  setIsDefaultModel: (isDefault: boolean) => void;
+  resetToDefaultModel: () => void;
+  urdfContent: string | null;
+
+  // Animation configuration management
+  currentAnimationConfig: RobotAnimationConfig | null;
+  setCurrentAnimationConfig: (config: RobotAnimationConfig | null) => void;
+
+  // These properties are kept for backward compatibility but are considered
+  // implementation details and should not be used directly in components.
+  // TODO: Remove these next three once the time is right
+  parsedRobotData: UrdfData | null; // Data from parsed Urdf
+  customModelName: string;
+  customModelDescription: string;
+};
+
+// Create the context
+export const UrdfContext = createContext<UrdfContextType | undefined>(
+  undefined
+);
+
+// Props for the provider component
+interface UrdfProviderProps {
+  children: ReactNode;
+}
+
+export const UrdfProvider: React.FC<UrdfProviderProps> = ({ children }) => {
+  // State for Urdf processor
+  const [urdfProcessor, setUrdfProcessor] = useState<UrdfProcessor | null>(
+    null
+  );
+
+  // State for blob URLs (replacing window.urdfBlobUrls)
+  const [urdfBlobUrls, setUrdfBlobUrls] = useState<Record<string, string>>({});
+
+  // State for alternative models (replacing window.alternativeUrdfModels)
+  const [alternativeUrdfModels, setAlternativeUrdfModels] = useState<string[]>(
+    []
+  );
+
+  // State for the Urdf selection modal
+  const [isSelectionModalOpen, setIsSelectionModalOpen] = useState(false);
+  const [urdfModelOptions, setUrdfModelOptions] = useState<UrdfFileModel[]>([]);
+
+  // New state for centralized robot data management
+  const [isDefaultModel, setIsDefaultModel] = useState(true);
+  const [parsedRobotData, setParsedRobotData] = useState<UrdfData | null>(null);
+  const [customModelName, setCustomModelName] = useState<string>("");
+  const [customModelDescription, setCustomModelDescription] =
+    useState<string>("");
+  const [urdfContent, setUrdfContent] = useState<string | null>(null);
+
+  // New state for animation configuration
+  const [currentAnimationConfig, setCurrentAnimationConfig] =
+    useState<RobotAnimationConfig | null>(null);
+
+  // Get default robot data from our hook
+  const { data: defaultRobotData } = useDefaultRobotData("so101");
+
+  // Compute the current robot data based on model state
+  const currentRobotData = isDefaultModel ? defaultRobotData : parsedRobotData;
+
+  // Fetch the default Urdf content when the component mounts
+  useEffect(() => {
+    // Only fetch if we don't have content and we're using the default model
+    if (isDefaultModel && !urdfContent) {
+      const fetchDefaultUrdf = async () => {
+        try {
+          // Path to the default T12 Urdf file
+          const defaultUrdfPath = "/so-101-urdf/urdf/so101_new_calib.urdf";
+
+          // Fetch the Urdf content
+          const response = await fetch(defaultUrdfPath);
+
+          if (!response.ok) {
+            throw new Error(
+              `Failed to fetch default Urdf: ${response.statusText}`
+            );
+          }
+
+          const defaultUrdfContent = await response.text();
+          console.log(
+            `📄 Default Urdf content loaded, length: ${defaultUrdfContent.length} characters`
+          );
+
+          // Set the Urdf content in state
+          setUrdfContent(defaultUrdfContent);
+        } catch (error) {
+          console.error("❌ Error loading default Urdf content:", error);
+        }
+      };
+
+      fetchDefaultUrdf();
+    }
+  }, [isDefaultModel, urdfContent]);
+
+  // Log data state changes for debugging
+  useEffect(() => {
+    console.log("🤖 Robot data context updated:", {
+      isDefaultModel,
+      hasDefaultData: !!defaultRobotData,
+      hasParsedData: !!parsedRobotData,
+      currentData: currentRobotData ? "available" : "null",
+    });
+  }, [isDefaultModel, defaultRobotData, parsedRobotData, currentRobotData]);
+
+  // Reference for callbacks
+  const urdfCallbacksRef = useRef<((result: UrdfDetectionResult) => void)[]>(
+    []
+  );
+
+  // Reset to default model
+  const resetToDefaultModel = useCallback(() => {
+    setIsDefaultModel(true);
+    setCustomModelName("");
+    setCustomModelDescription("");
+    setParsedRobotData(null);
+    setUrdfContent(null);
+    setCurrentAnimationConfig(null);
+
+    toast.info("Switched to default model", {
+      description: "The default ARM100 robot model is now displayed.",
+    });
+  }, []);
+
+  // Register a callback for Urdf detection
+  const onUrdfDetected = useCallback(
+    (callback: (result: UrdfDetectionResult) => void) => {
+      urdfCallbacksRef.current.push(callback);
+
+      return () => {
+        urdfCallbacksRef.current = urdfCallbacksRef.current.filter(
+          (cb) => cb !== callback
+        );
+      };
+    },
+    []
+  );
+
+  // Register a Urdf processor
+  const registerUrdfProcessor = useCallback((processor: UrdfProcessor) => {
+    setUrdfProcessor(processor);
+  }, []);
+
+  // Internal function to notify callbacks and update central state
+  const notifyUrdfCallbacks = useCallback(
+    (result: UrdfDetectionResult) => {
+      console.log("📣 Notifying Urdf callbacks with result:", result);
+
+      // Update our internal state based on the result
+      if (result.hasUrdf) {
+        // Always ensure we set isDefaultModel to false when we have a Urdf
+        setIsDefaultModel(false);
+
+        if (result.parsedData) {
+          // Create a copy of the parsed data with any missing fields filled from our state
+          const enhancedParsedData: UrdfData = {
+            ...result.parsedData,
+          };
+
+          // Set the name if available, or use the provided modelName as fallback
+          if (result.parsedData.name) {
+            setCustomModelName(result.parsedData.name);
+          } else if (result.modelName) {
+            setCustomModelName(result.modelName);
+            // Also update the parsed data with this name to be consistent
+            enhancedParsedData.name = result.modelName;
+          }
+
+          // Set description if available
+          if (result.parsedData.description) {
+            setCustomModelDescription(result.parsedData.description);
+          } else {
+            // If no description in parsed data, set a default one
+            const defaultDesc =
+              "A detailed 3D model of a robotic system with articulated joints and components.";
+            enhancedParsedData.description = defaultDesc;
+            setCustomModelDescription(defaultDesc);
+          }
+
+          // Update parsed data with the enhanced version
+          setParsedRobotData(enhancedParsedData);
+        } else if (result.modelName) {
+          // Only have model name, no parsed data
+          setCustomModelName(result.modelName);
+
+          // Create a minimal UrdfData object with at least the name
+          const minimalData: UrdfData = {
+            name: result.modelName,
+            description:
+              "A detailed 3D model of a robotic system with articulated joints and components.",
+          };
+          setParsedRobotData(minimalData);
+        }
+      } else {
+        // If no Urdf, reset to default
+        resetToDefaultModel();
+      }
+
+      // Call all registered callbacks
+      urdfCallbacksRef.current.forEach((callback) => callback(result));
+    },
+    [resetToDefaultModel]
+  );
+
+  // Helper function to process the selected Urdf model
+  const processSelectedUrdf = useCallback(
+    async (model: UrdfFileModel) => {
+      if (!urdfProcessor) return;
+
+      // Find the file in our files record
+      const files = Object.values(urdfBlobUrls)
+        .filter((url) => url === model.blobUrl)
+        .map((url) => {
+          const path = Object.keys(urdfBlobUrls).find(
+            (key) => urdfBlobUrls[key] === url
+          );
+          return path ? { path, url } : null;
+        })
+        .filter((item) => item !== null);
+
+      if (files.length === 0) {
+        console.error("❌ Could not find file for selected Urdf model");
+        return;
+      }
+
+      // Show a toast notification that we're loading the Urdf
+      const loadingToast = toast.loading("Loading Urdf model...", {
+        description: "Preparing 3D visualization",
+        duration: 5000,
+      });
+
+      try {
+        // Get the file from our record
+        const filePath = files[0]?.path;
+        if (!filePath || !urdfBlobUrls[filePath]) {
+          throw new Error("File not found in records");
+        }
+
+        // Get the actual File object
+        const response = await fetch(model.blobUrl);
+        const blob = await response.blob();
+        const file = new File(
+          [blob],
+          filePath.split("/").pop() || "model.urdf",
+          {
+            type: "application/xml",
+          }
+        );
+
+        // Read the Urdf content
+        const urdfContent = await readUrdfFileContent(file);
+
+        console.log(
+          `📏 Urdf content read, length: ${urdfContent.length} characters`
+        );
+
+        // Store the Urdf content in state
+        setUrdfContent(urdfContent);
+
+        // Dismiss the toast
+        toast.dismiss(loadingToast);
+
+        // Always set isDefaultModel to false when processing a custom Urdf
+        setIsDefaultModel(false);
+
+        // Success case - create basic model data
+        const modelDisplayName =
+          model.name || model.path.split("/").pop() || "Unknown";
+
+        // Create basic data structure with name and description
+        const basicData: UrdfData = {
+          name: modelDisplayName,
+          description:
+            "A detailed 3D model of a robotic system with articulated joints and components.",
+        };
+
+        // Update our state
+        setCustomModelName(modelDisplayName);
+        setCustomModelDescription(basicData.description);
+        setParsedRobotData(basicData);
+
+        toast.success("Urdf model loaded successfully", {
+          description: `Model: ${modelDisplayName}`,
+          duration: 3000,
+        });
+
+        // Notify callbacks with the basic data
+        notifyUrdfCallbacks({
+          hasUrdf: true,
+          modelName: modelDisplayName,
+          parsedData: basicData,
+        });
+      } catch (error) {
+        // Error case
+        console.error("❌ Error processing selected Urdf:", error);
+        toast.dismiss(loadingToast);
+        toast.error("Error loading Urdf", {
+          description: `Error: ${
+            error instanceof Error ? error.message : String(error)
+          }`,
+          duration: 3000,
+        });
+
+        // Keep showing the custom model even if loading failed
+        // No need to reset to default unless user explicitly chooses to
+      }
+    },
+    [urdfBlobUrls, urdfProcessor, notifyUrdfCallbacks]
+  );
+
+  // Function to handle selecting a Urdf model from the modal
+  const selectUrdfModel = useCallback(
+    (model: UrdfFileModel) => {
+      if (!urdfProcessor) {
+        console.error("❌ No Urdf processor available");
+        return;
+      }
+
+      console.log(`🤖 Selected model: ${model.name || model.path}`);
+
+      // Close the modal
+      setIsSelectionModalOpen(false);
+
+      // Extract model name
+      const modelName =
+        model.name ||
+        model.path
+          .split("/")
+          .pop()
+          ?.replace(/\.urdf$/i, "") ||
+        "Unknown";
+
+      // Load the selected Urdf model
+      urdfProcessor.loadUrdf(model.blobUrl);
+
+      // Update our state immediately even before parsing
+      setIsDefaultModel(false);
+      setCustomModelName(modelName);
+
+      // Show a toast notification that we're loading the model
+      toast.info(`Loading model: ${modelName}`, {
+        description: "Preparing 3D visualization",
+        duration: 2000,
+      });
+
+      // Notify callbacks about the selection before parsing
+      notifyUrdfCallbacks({
+        hasUrdf: true,
+        modelName,
+        parsedData: undefined, // Will use parseUrdf later to get the data
+      });
+
+      // Try to parse the model - this will update the UI when complete
+      processSelectedUrdf(model);
+    },
+    [urdfProcessor, notifyUrdfCallbacks, processSelectedUrdf]
+  );
+
+  // Process Urdf files - moved from DragAndDropContext
+  const processUrdfFiles = useCallback(
+    async (files: Record<string, File>, availableModels: string[]) => {
+      // Clear previous blob URLs to prevent memory leaks
+      Object.values(urdfBlobUrls).forEach(URL.revokeObjectURL);
+      setUrdfBlobUrls({});
+      setAlternativeUrdfModels([]);
+      setUrdfModelOptions([]);
+
+      try {
+        // Check if we have any Urdf files
+        if (availableModels.length > 0 && urdfProcessor) {
+          console.log(
+            `🤖 Found ${availableModels.length} Urdf models:`,
+            availableModels
+          );
+
+          // Create blob URLs for all models
+          const newUrdfBlobUrls: Record<string, string> = {};
+          availableModels.forEach((path) => {
+            if (files[path]) {
+              newUrdfBlobUrls[path] = URL.createObjectURL(files[path]);
+            }
+          });
+          setUrdfBlobUrls(newUrdfBlobUrls);
+
+          // Save alternative models for reference
+          setAlternativeUrdfModels(availableModels);
+
+          // Create model options for the selection modal
+          const modelOptions: UrdfFileModel[] = availableModels.map((path) => {
+            const fileName = path.split("/").pop() || "";
+            const modelName = fileName.replace(/\.urdf$/i, "");
+            return {
+              path,
+              blobUrl: newUrdfBlobUrls[path],
+              name: modelName,
+            };
+          });
+
+          setUrdfModelOptions(modelOptions);
+
+          // If there's only one model, use it directly
+          if (availableModels.length === 1) {
+            // Extract model name from the Urdf file
+            const fileName = availableModels[0].split("/").pop() || "";
+            const modelName = fileName.replace(/\.urdf$/i, "");
+            console.log(`📄 Using model: ${modelName} (${fileName})`);
+
+            // Use the blob URL instead of the file path
+            const blobUrl = newUrdfBlobUrls[availableModels[0]];
+            if (blobUrl) {
+              console.log(`🔗 Using blob URL for Urdf: ${blobUrl}`);
+              urdfProcessor.loadUrdf(blobUrl);
+
+              // Immediately update model state
+              setIsDefaultModel(false);
+              setCustomModelName(modelName);
+
+              // Process the Urdf file for content storage
+              if (files[availableModels[0]]) {
+                console.log("📄 Reading Urdf content...");
+
+                // Show a toast notification that we're loading the Urdf
+                const loadingToast = toast.loading("Loading Urdf model...", {
+                  description: "Preparing 3D visualization",
+                  duration: 5000,
+                });
+
+                try {
+                  const urdfContent = await readUrdfFileContent(
+                    files[availableModels[0]]
+                  );
+
+                  console.log(
+                    `📏 Urdf content read, length: ${urdfContent.length} characters`
+                  );
+
+                  // Store the Urdf content in state
+                  setUrdfContent(urdfContent);
+
+                  // Dismiss the loading toast
+                  toast.dismiss(loadingToast);
+
+                  toast.success("Urdf model loaded successfully", {
+                    description: `Model: ${modelName}`,
+                    duration: 3000,
+                  });
+
+                  // Create basic data structure with name and description
+                  const basicData: UrdfData = {
+                    name: modelName,
+                    description:
+                      "A detailed 3D model of a robotic system with articulated joints and components.",
+                  };
+
+                  // Update our state
+                  setCustomModelDescription(basicData.description);
+                  setParsedRobotData(basicData);
+
+                  // Notify callbacks with all the information
+                  notifyUrdfCallbacks({
+                    hasUrdf: true,
+                    modelName: modelName,
+                    parsedData: basicData,
+                  });
+                } catch (loadError) {
+                  console.error("❌ Error loading Urdf:", loadError);
+                  toast.dismiss(loadingToast);
+                  toast.error("Error loading Urdf", {
+                    description: `Error: ${
+                      loadError instanceof Error
+                        ? loadError.message
+                        : String(loadError)
+                    }`,
+                    duration: 3000,
+                  });
+
+                  // Still notify callbacks without detailed data
+                  notifyUrdfCallbacks({
+                    hasUrdf: true,
+                    modelName,
+                  });
+                }
+              } else {
+                console.error(
+                  "❌ Could not find file for Urdf model:",
+                  availableModels[0]
+                );
+                console.log("📦 Available files:", Object.keys(files));
+
+                // Still notify callbacks without detailed data
+                notifyUrdfCallbacks({
+                  hasUrdf: true,
+                  modelName,
+                });
+              }
+            } else {
+              console.warn(
+                `⚠️ No blob URL found for ${availableModels[0]}, using path directly`
+              );
+              urdfProcessor.loadUrdf(availableModels[0]);
+
+              // Update the state even without a blob URL
+              setIsDefaultModel(false);
+              setCustomModelName(modelName);
+
+              // Notify callbacks
+              notifyUrdfCallbacks({
+                hasUrdf: true,
+                modelName,
+              });
+            }
+          } else {
+            // Multiple Urdf files found, show selection modal
+            console.log(
+              "📋 Multiple Urdf files found, showing selection modal"
+            );
+            setIsSelectionModalOpen(true);
+
+            // Notify that Urdf files are available but selection is needed
+            notifyUrdfCallbacks({
+              hasUrdf: true,
+              modelName: "Multiple models available",
+            });
+          }
+        } else {
+          console.warn(
+            "❌ No Urdf models found in dropped files or no processor available"
+          );
+          notifyUrdfCallbacks({ hasUrdf: false, parsedData: null });
+
+          // Reset to default model when no Urdf files are found
+          resetToDefaultModel();
+
+          toast.error("No Urdf file found", {
+            description: "Please upload a folder containing a .urdf file.",
+            duration: 3000,
+          });
+        }
+      } catch (error) {
+        console.error("❌ Error processing Urdf files:", error);
+        toast.error("Error processing files", {
+          description: `Error: ${
+            error instanceof Error ? error.message : String(error)
+          }`,
+          duration: 3000,
+        });
+
+        // Reset to default model on error
+        resetToDefaultModel();
+      }
+    },
+    [notifyUrdfCallbacks, urdfBlobUrls, urdfProcessor, resetToDefaultModel]
+  );
+
+  // Clean up blob URLs when component unmounts
+  React.useEffect(() => {
+    return () => {
+      Object.values(urdfBlobUrls).forEach(URL.revokeObjectURL);
+    };
+  }, [urdfBlobUrls]);
+
+  // Create the context value
+  const contextValue: UrdfContextType = {
+    urdfProcessor,
+    registerUrdfProcessor,
+    onUrdfDetected,
+    processUrdfFiles,
+    urdfBlobUrls,
+    alternativeUrdfModels,
+    isSelectionModalOpen,
+    setIsSelectionModalOpen,
+    urdfModelOptions,
+    selectUrdfModel,
+
+    // New properties for centralized robot data management
+    currentRobotData,
+    isDefaultModel,
+    setIsDefaultModel,
+    parsedRobotData,
+    customModelName,
+    customModelDescription,
+    resetToDefaultModel,
+    urdfContent,
+
+    // Animation configuration management
+    currentAnimationConfig,
+    setCurrentAnimationConfig,
+  };
+
+  return (
+    <UrdfContext.Provider value={contextValue}>{children}</UrdfContext.Provider>
+  );
+};
diff --git a/src/hooks/use-mobile.tsx b/src/hooks/use-mobile.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..2b0fe1dfef3b17850bbac040665f514a8ffd0f15
--- /dev/null
+++ b/src/hooks/use-mobile.tsx
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
+
+  React.useEffect(() => {
+    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+    const onChange = () => {
+      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+    }
+    mql.addEventListener("change", onChange)
+    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+    return () => mql.removeEventListener("change", onChange)
+  }, [])
+
+  return !!isMobile
+}
diff --git a/src/hooks/use-toast.ts b/src/hooks/use-toast.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2c14125ac48b874740770da80d9e424daf1db58b
--- /dev/null
+++ b/src/hooks/use-toast.ts
@@ -0,0 +1,191 @@
+import * as React from "react"
+
+import type {
+  ToastActionElement,
+  ToastProps,
+} from "@/components/ui/toast"
+
+const TOAST_LIMIT = 1
+const TOAST_REMOVE_DELAY = 1000000
+
+type ToasterToast = ToastProps & {
+  id: string
+  title?: React.ReactNode
+  description?: React.ReactNode
+  action?: ToastActionElement
+}
+
+const actionTypes = {
+  ADD_TOAST: "ADD_TOAST",
+  UPDATE_TOAST: "UPDATE_TOAST",
+  DISMISS_TOAST: "DISMISS_TOAST",
+  REMOVE_TOAST: "REMOVE_TOAST",
+} as const
+
+let count = 0
+
+function genId() {
+  count = (count + 1) % Number.MAX_SAFE_INTEGER
+  return count.toString()
+}
+
+type ActionType = typeof actionTypes
+
+type Action =
+  | {
+      type: ActionType["ADD_TOAST"]
+      toast: ToasterToast
+    }
+  | {
+      type: ActionType["UPDATE_TOAST"]
+      toast: Partial<ToasterToast>
+    }
+  | {
+      type: ActionType["DISMISS_TOAST"]
+      toastId?: ToasterToast["id"]
+    }
+  | {
+      type: ActionType["REMOVE_TOAST"]
+      toastId?: ToasterToast["id"]
+    }
+
+interface State {
+  toasts: ToasterToast[]
+}
+
+const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
+
+const addToRemoveQueue = (toastId: string) => {
+  if (toastTimeouts.has(toastId)) {
+    return
+  }
+
+  const timeout = setTimeout(() => {
+    toastTimeouts.delete(toastId)
+    dispatch({
+      type: "REMOVE_TOAST",
+      toastId: toastId,
+    })
+  }, TOAST_REMOVE_DELAY)
+
+  toastTimeouts.set(toastId, timeout)
+}
+
+export const reducer = (state: State, action: Action): State => {
+  switch (action.type) {
+    case "ADD_TOAST":
+      return {
+        ...state,
+        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+      }
+
+    case "UPDATE_TOAST":
+      return {
+        ...state,
+        toasts: state.toasts.map((t) =>
+          t.id === action.toast.id ? { ...t, ...action.toast } : t
+        ),
+      }
+
+    case "DISMISS_TOAST": {
+      const { toastId } = action
+
+      // ! Side effects ! - This could be extracted into a dismissToast() action,
+      // but I'll keep it here for simplicity
+      if (toastId) {
+        addToRemoveQueue(toastId)
+      } else {
+        state.toasts.forEach((toast) => {
+          addToRemoveQueue(toast.id)
+        })
+      }
+
+      return {
+        ...state,
+        toasts: state.toasts.map((t) =>
+          t.id === toastId || toastId === undefined
+            ? {
+                ...t,
+                open: false,
+              }
+            : t
+        ),
+      }
+    }
+    case "REMOVE_TOAST":
+      if (action.toastId === undefined) {
+        return {
+          ...state,
+          toasts: [],
+        }
+      }
+      return {
+        ...state,
+        toasts: state.toasts.filter((t) => t.id !== action.toastId),
+      }
+  }
+}
+
+const listeners: Array<(state: State) => void> = []
+
+let memoryState: State = { toasts: [] }
+
+function dispatch(action: Action) {
+  memoryState = reducer(memoryState, action)
+  listeners.forEach((listener) => {
+    listener(memoryState)
+  })
+}
+
+type Toast = Omit<ToasterToast, "id">
+
+function toast({ ...props }: Toast) {
+  const id = genId()
+
+  const update = (props: ToasterToast) =>
+    dispatch({
+      type: "UPDATE_TOAST",
+      toast: { ...props, id },
+    })
+  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
+
+  dispatch({
+    type: "ADD_TOAST",
+    toast: {
+      ...props,
+      id,
+      open: true,
+      onOpenChange: (open) => {
+        if (!open) dismiss()
+      },
+    },
+  })
+
+  return {
+    id: id,
+    dismiss,
+    update,
+  }
+}
+
+function useToast() {
+  const [state, setState] = React.useState<State>(memoryState)
+
+  React.useEffect(() => {
+    listeners.push(setState)
+    return () => {
+      const index = listeners.indexOf(setState)
+      if (index > -1) {
+        listeners.splice(index, 1)
+      }
+    }
+  }, [state])
+
+  return {
+    ...state,
+    toast,
+    dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
+  }
+}
+
+export { useToast, toast }
diff --git a/src/hooks/useDefaultRobotData.ts b/src/hooks/useDefaultRobotData.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a82704167443a964e8dbc0854ca61c920f035f46
--- /dev/null
+++ b/src/hooks/useDefaultRobotData.ts
@@ -0,0 +1,48 @@
+import { useQuery } from "@tanstack/react-query";
+import { UrdfData } from "@/lib/types";
+
+/**
+ * Fetches default robot data from a JSON file
+ * @param robotName The name of the robot folder (e.g., 'T12')
+ */
+async function fetchRobotData(robotName: string): Promise<UrdfData> {
+  const response = await fetch(`/so-101-urdf/urdf/so101_new_calib.urdf`);
+
+  if (!response.ok) {
+    throw new Error(`Failed to fetch default robot data for ${robotName}`);
+  }
+
+  return await response.json();
+}
+
+/**
+ * Hook to fetch default robot model data from a JSON file using React Query
+ * @param robotName The name of the robot folder (e.g., 'T12')
+ * @returns The robot data query result
+ */
+export function useDefaultRobotData(robotName: string = "so101") {
+  return useQuery({
+    queryKey: ["defaultRobotData", robotName],
+    queryFn: () => fetchRobotData(robotName),
+    staleTime: 1000 * 60 * 5, // Consider data fresh for 5 minutes
+    retry: 2, // Retry failed requests twice
+  });
+}
+
+/**
+ * Function to load default robot data for use outside React components
+ * @param robotName The name of the robot folder (e.g., 'T12')
+ * @returns A promise that resolves to the robot data or null if loading fails
+ */
+export async function loadDefaultRobotData(
+  robotName: string = "so101"
+): Promise<UrdfData | null> {
+  try {
+    return await fetchRobotData(robotName);
+  } catch (err) {
+    console.error(`Error loading default robot data for ${robotName}:`, err);
+    return null;
+  }
+}
+
+export default useDefaultRobotData;
diff --git a/src/hooks/useDragAndDrop.tsx b/src/hooks/useDragAndDrop.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4d7275aa8d50c825e438e254c6cce1b502bbd830
--- /dev/null
+++ b/src/hooks/useDragAndDrop.tsx
@@ -0,0 +1,14 @@
+import {
+  DragAndDropContextType,
+  DragAndDropContext,
+} from "@/contexts/DragAndDropContext";
+import { useContext } from "react";
+
+// Custom hook to use the DragAndDrop context
+export const useDragAndDrop = (): DragAndDropContextType => {
+  const context = useContext(DragAndDropContext);
+  if (context === undefined) {
+    throw new Error("useDragAndDrop must be used within a DragAndDropProvider");
+  }
+  return context;
+};
diff --git a/src/hooks/useRealTimeJoints.ts b/src/hooks/useRealTimeJoints.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9a1522714f9a47425536aedc0632ca3ebc0a8fab
--- /dev/null
+++ b/src/hooks/useRealTimeJoints.ts
@@ -0,0 +1,184 @@
+import { useEffect, useRef, useCallback } from "react";
+import { URDFViewerElement } from "@/lib/urdfViewerHelpers";
+
+interface JointData {
+  type: "joint_update";
+  joints: Record<string, number>;
+  timestamp: number;
+}
+
+interface UseRealTimeJointsProps {
+  viewerRef: React.RefObject<URDFViewerElement>;
+  enabled?: boolean;
+  websocketUrl?: string;
+}
+
+export const useRealTimeJoints = ({
+  viewerRef,
+  enabled = true,
+  websocketUrl = "ws://localhost:8000/ws/joint-data",
+}: UseRealTimeJointsProps) => {
+  const wsRef = useRef<WebSocket | null>(null);
+  const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+  const isConnectedRef = useRef<boolean>(false);
+
+  const updateJointValues = useCallback(
+    (joints: Record<string, number>) => {
+      const viewer = viewerRef.current;
+      if (!viewer || typeof viewer.setJointValue !== "function") {
+        return;
+      }
+
+      // Update each joint value in the URDF viewer
+      Object.entries(joints).forEach(([jointName, value]) => {
+        try {
+          viewer.setJointValue(jointName, value);
+        } catch (error) {
+          console.warn(`Failed to set joint ${jointName}:`, error);
+        }
+      });
+    },
+    [viewerRef]
+  );
+
+  const connectWebSocket = useCallback(() => {
+    if (!enabled) return;
+
+    // First, test if the server is running
+    const testServerConnection = async () => {
+      try {
+        const response = await fetch("http://localhost:8000/health");
+        if (!response.ok) {
+          console.error("❌ Server health check failed:", response.status);
+          return false;
+        }
+        const data = await response.json();
+        console.log("✅ Server is running:", data);
+        return true;
+      } catch (error) {
+        console.error("❌ Server is not reachable:", error);
+        return false;
+      }
+    };
+
+    // Test server connection first
+    testServerConnection().then((serverAvailable) => {
+      if (!serverAvailable) {
+        console.error("❌ Cannot connect to WebSocket: Server is not running");
+        console.log(
+          "💡 Make sure to start the FastAPI server with: python -m uvicorn lerobot.livelab.app.main:app --reload"
+        );
+        return;
+      }
+
+      try {
+        console.log("🔗 Connecting to WebSocket:", websocketUrl);
+
+        const ws = new WebSocket(websocketUrl);
+        wsRef.current = ws;
+
+        ws.onopen = () => {
+          console.log("✅ WebSocket connected for real-time joints");
+          isConnectedRef.current = true;
+
+          // Clear any existing reconnect timeout
+          if (reconnectTimeoutRef.current) {
+            clearTimeout(reconnectTimeoutRef.current);
+            reconnectTimeoutRef.current = null;
+          }
+        };
+
+        ws.onmessage = (event) => {
+          try {
+            const data: JointData = JSON.parse(event.data);
+
+            if (data.type === "joint_update" && data.joints) {
+              updateJointValues(data.joints);
+            }
+          } catch (error) {
+            console.error("❌ Error parsing WebSocket message:", error);
+          }
+        };
+
+        ws.onclose = (event) => {
+          console.log(
+            "🔌 WebSocket connection closed:",
+            event.code,
+            event.reason
+          );
+          isConnectedRef.current = false;
+          wsRef.current = null;
+
+          // Provide more specific error information
+          if (event.code === 1006) {
+            console.error(
+              "❌ WebSocket connection failed - server may not be running or endpoint not found"
+            );
+          } else if (event.code === 1000) {
+            console.log("✅ WebSocket closed normally");
+          }
+
+          // Attempt to reconnect after a delay if enabled
+          if (enabled && !reconnectTimeoutRef.current && event.code !== 1000) {
+            reconnectTimeoutRef.current = setTimeout(() => {
+              console.log("🔄 Attempting to reconnect WebSocket...");
+              connectWebSocket();
+            }, 3000); // Reconnect after 3 seconds
+          }
+        };
+
+        ws.onerror = (error) => {
+          console.error("❌ WebSocket error:", error);
+          console.log("💡 Troubleshooting tips:");
+          console.log(
+            "  1. Make sure FastAPI server is running on localhost:8000"
+          );
+          console.log("  2. Check if the /ws/joint-data endpoint exists");
+          console.log(
+            "  3. Restart the server if you just added WebSocket support"
+          );
+          isConnectedRef.current = false;
+        };
+      } catch (error) {
+        console.error("❌ Failed to create WebSocket connection:", error);
+      }
+    });
+  }, [enabled, websocketUrl, updateJointValues]);
+
+  const disconnect = useCallback(() => {
+    // Clear reconnect timeout
+    if (reconnectTimeoutRef.current) {
+      clearTimeout(reconnectTimeoutRef.current);
+      reconnectTimeoutRef.current = null;
+    }
+
+    // Close WebSocket connection
+    if (wsRef.current) {
+      wsRef.current.close();
+      wsRef.current = null;
+    }
+
+    isConnectedRef.current = false;
+  }, []);
+
+  // Effect to manage WebSocket connection
+  useEffect(() => {
+    if (enabled) {
+      connectWebSocket();
+    } else {
+      disconnect();
+    }
+
+    // Cleanup on unmount
+    return () => {
+      disconnect();
+    };
+  }, [enabled, connectWebSocket, disconnect]);
+
+  // Return connection status and control functions
+  return {
+    isConnected: isConnectedRef.current,
+    disconnect,
+    reconnect: connectWebSocket,
+  };
+};
diff --git a/src/hooks/useTheme.tsx b/src/hooks/useTheme.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..d5712294e3c538c61e2e324e3384686222f34226
--- /dev/null
+++ b/src/hooks/useTheme.tsx
@@ -0,0 +1,14 @@
+import { useContext } from "react";
+import {
+  ThemeProviderContext,
+  ThemeProviderState,
+} from "../contexts/ThemeContext";
+
+export const useTheme = (): ThemeProviderState => {
+  const context = useContext(ThemeProviderContext);
+
+  if (context === undefined)
+    throw new Error("useTheme must be used within a ThemeProvider");
+
+  return context;
+};
diff --git a/src/hooks/useUrdf.ts b/src/hooks/useUrdf.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0c4b2f95c14ef3174debe7830ffe0c6a168539e2
--- /dev/null
+++ b/src/hooks/useUrdf.ts
@@ -0,0 +1,11 @@
+import { UrdfContextType, UrdfContext } from "@/contexts/UrdfContext";
+import { useContext } from "react";
+
+// Custom hook to use the Urdf context
+export const useUrdf = (): UrdfContextType => {
+  const context = useContext(UrdfContext);
+  if (context === undefined) {
+    throw new Error("useUrdf must be used within a UrdfProvider");
+  }
+  return context;
+};
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000000000000000000000000000000000000..a20aa5880822e15a7a8983c10fc9633a5314eed8
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,103 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+/* Definition of the design system. All colors, gradients, fonts, etc should be defined here. */
+
+@layer base {
+  :root {
+    --background: 0 0% 100%;
+    --foreground: 222.2 84% 4.9%;
+
+    --card: 0 0% 100%;
+    --card-foreground: 222.2 84% 4.9%;
+
+    --popover: 0 0% 100%;
+    --popover-foreground: 222.2 84% 4.9%;
+
+    --primary: 222.2 47.4% 11.2%;
+    --primary-foreground: 210 40% 98%;
+
+    --secondary: 210 40% 96.1%;
+    --secondary-foreground: 222.2 47.4% 11.2%;
+
+    --muted: 210 40% 96.1%;
+    --muted-foreground: 215.4 16.3% 46.9%;
+
+    --accent: 210 40% 96.1%;
+    --accent-foreground: 222.2 47.4% 11.2%;
+
+    --destructive: 0 84.2% 60.2%;
+    --destructive-foreground: 210 40% 98%;
+
+    --border: 214.3 31.8% 91.4%;
+    --input: 214.3 31.8% 91.4%;
+    --ring: 222.2 84% 4.9%;
+
+    --radius: 0.5rem;
+
+    --sidebar-background: 0 0% 98%;
+
+    --sidebar-foreground: 240 5.3% 26.1%;
+
+    --sidebar-primary: 240 5.9% 10%;
+
+    --sidebar-primary-foreground: 0 0% 98%;
+
+    --sidebar-accent: 240 4.8% 95.9%;
+
+    --sidebar-accent-foreground: 240 5.9% 10%;
+
+    --sidebar-border: 220 13% 91%;
+
+    --sidebar-ring: 217.2 91.2% 59.8%;
+  }
+
+  .dark {
+    --background: 222.2 84% 4.9%;
+    --foreground: 210 40% 98%;
+
+    --card: 222.2 84% 4.9%;
+    --card-foreground: 210 40% 98%;
+
+    --popover: 222.2 84% 4.9%;
+    --popover-foreground: 210 40% 98%;
+
+    --primary: 210 40% 98%;
+    --primary-foreground: 222.2 47.4% 11.2%;
+
+    --secondary: 217.2 32.6% 17.5%;
+    --secondary-foreground: 210 40% 98%;
+
+    --muted: 217.2 32.6% 17.5%;
+    --muted-foreground: 215 20.2% 65.1%;
+
+    --accent: 217.2 32.6% 17.5%;
+    --accent-foreground: 210 40% 98%;
+
+    --destructive: 0 62.8% 30.6%;
+    --destructive-foreground: 210 40% 98%;
+
+    --border: 217.2 32.6% 17.5%;
+    --input: 217.2 32.6% 17.5%;
+    --ring: 212.7 26.8% 83.9%;
+    --sidebar-background: 240 5.9% 10%;
+    --sidebar-foreground: 240 4.8% 95.9%;
+    --sidebar-primary: 224.3 76.3% 48%;
+    --sidebar-primary-foreground: 0 0% 100%;
+    --sidebar-accent: 240 3.7% 15.9%;
+    --sidebar-accent-foreground: 240 4.8% 95.9%;
+    --sidebar-border: 240 3.7% 15.9%;
+    --sidebar-ring: 217.2 91.2% 59.8%;
+  }
+}
+
+@layer base {
+  * {
+    @apply border-border;
+  }
+
+  body {
+    @apply bg-background text-foreground;
+  }
+}
\ No newline at end of file
diff --git a/src/lib/UrdfDragAndDrop.ts b/src/lib/UrdfDragAndDrop.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e39194bd5aa72b1af01fd21073724c9db2d2492e
--- /dev/null
+++ b/src/lib/UrdfDragAndDrop.ts
@@ -0,0 +1,415 @@
+/**
+ * Urdf Drag and Drop Utility
+ *
+ * This file provides functionality for handling drag and drop of Urdf folders.
+ * It converts the dropped files into accessible blobs for visualization.
+ */
+
+/**
+ * Converts a DataTransfer structure into an object with all paths and files.
+ * @param dataTransfer The DataTransfer object from the drop event
+ * @returns A promise that resolves with the file structure object
+ */
+export function dataTransferToFiles(
+  dataTransfer: DataTransfer
+): Promise<Record<string, File>> {
+  if (!(dataTransfer instanceof DataTransfer)) {
+    throw new Error('Data must be of type "DataTransfer"');
+  }
+
+  const files: Record<string, File> = {};
+
+  /**
+   * Recursively processes a directory entry to extract all files
+   * Using type 'unknown' and then type checking for safety with WebKit's non-standard API
+   */
+  function recurseDirectory(item: unknown): Promise<void> {
+    // Type guard for file entries
+    const isFileEntry = (
+      entry: unknown
+    ): entry is {
+      isFile: boolean;
+      fullPath: string;
+      file: (callback: (file: File) => void) => void;
+    } =>
+      entry !== null &&
+      typeof entry === "object" &&
+      "isFile" in entry &&
+      typeof (entry as Record<string, unknown>).file === "function" &&
+      "fullPath" in entry;
+
+    // Type guard for directory entries
+    const isDirEntry = (
+      entry: unknown
+    ): entry is {
+      isFile: boolean;
+      createReader: () => {
+        readEntries: (callback: (entries: unknown[]) => void) => void;
+      };
+    } =>
+      entry !== null &&
+      typeof entry === "object" &&
+      "isFile" in entry &&
+      typeof (entry as Record<string, unknown>).createReader === "function";
+
+    if (isFileEntry(item) && item.isFile) {
+      return new Promise((resolve) => {
+        item.file((file: File) => {
+          files[item.fullPath] = file;
+          resolve();
+        });
+      });
+    } else if (isDirEntry(item) && !item.isFile) {
+      const reader = item.createReader();
+
+      return new Promise((resolve) => {
+        const promises: Promise<void>[] = [];
+
+        // Exhaustively read all directory entries
+        function readNextEntries() {
+          reader.readEntries((entries: unknown[]) => {
+            if (entries.length === 0) {
+              Promise.all(promises).then(() => resolve());
+            } else {
+              entries.forEach((entry) => {
+                promises.push(recurseDirectory(entry));
+              });
+              readNextEntries();
+            }
+          });
+        }
+
+        readNextEntries();
+      });
+    }
+
+    return Promise.resolve();
+  }
+
+  return new Promise((resolve) => {
+    // Process dropped items
+    const dtitems = dataTransfer.items && Array.from(dataTransfer.items);
+    const dtfiles = Array.from(dataTransfer.files);
+
+    if (dtitems && dtitems.length && "webkitGetAsEntry" in dtitems[0]) {
+      const promises: Promise<void>[] = [];
+
+      for (let i = 0; i < dtitems.length; i++) {
+        const item = dtitems[i] as unknown as {
+          webkitGetAsEntry: () => unknown;
+        };
+
+        if (typeof item.webkitGetAsEntry === "function") {
+          const entry = item.webkitGetAsEntry();
+          if (entry) {
+            promises.push(recurseDirectory(entry));
+          }
+        }
+      }
+
+      Promise.all(promises).then(() => resolve(files));
+    } else {
+      // Add a '/' prefix to match the file directory entry on webkit browsers
+      dtfiles
+        .filter((f) => f.size !== 0)
+        .forEach((f) => (files["/" + f.name] = f));
+
+      resolve(files);
+    }
+  });
+}
+
+/**
+ * Cleans a file path by removing '..' and '.' tokens and normalizing slashes
+ */
+export function cleanFilePath(path: string): string {
+  return path
+    .replace(/\\/g, "/")
+    .split(/\//g)
+    .reduce((acc, el) => {
+      if (el === "..") acc.pop();
+      else if (el !== ".") acc.push(el);
+      return acc;
+    }, [] as string[])
+    .join("/");
+}
+
+/**
+ * Interface representing the structure of an Urdf processor
+ */
+export interface UrdfProcessor {
+  loadUrdf: (path: string) => void;
+  setUrlModifierFunc: (func: (url: string) => string) => void;
+  getPackage: () => string;
+}
+
+// Reference to hold the package path
+const packageRef = { current: "" };
+
+/**
+ * Reads the content of a Urdf file
+ * @param file The Urdf file object
+ * @returns A promise that resolves with the content of the file as a string
+ */
+export function readUrdfFileContent(file: File): Promise<string> {
+  return new Promise((resolve, reject) => {
+    const reader = new FileReader();
+    reader.onload = (event) => {
+      if (event.target && event.target.result) {
+        resolve(event.target.result as string);
+      } else {
+        reject(new Error("Failed to read Urdf file content"));
+      }
+    };
+    reader.onerror = () => reject(new Error("Error reading Urdf file"));
+    reader.readAsText(file);
+  });
+}
+
+/**
+ * Downloads a zip file from a URL and extracts its contents
+ * @param zipUrl URL of the zip file to download
+ * @param urdfProcessor The Urdf processor to use for loading
+ * @returns A promise that resolves with the extraction results
+ */
+export async function downloadAndExtractZip(
+  zipUrl: string,
+  urdfProcessor: UrdfProcessor
+): Promise<{
+  files: Record<string, File>;
+  availableModels: string[];
+  blobUrls: Record<string, string>;
+}> {
+  console.log("🔄 Downloading zip file from:", zipUrl);
+
+  try {
+    // Download the zip file
+    const response = await fetch(zipUrl);
+    if (!response.ok) {
+      throw new Error(`Failed to download zip: ${response.statusText}`);
+    }
+
+    const zipBlob = await response.blob();
+
+    // Load JSZip dynamically since it's much easier to work with than manual Blob handling
+    // We use dynamic import to avoid adding a dependency
+    const JSZip = (await import("jszip")).default;
+    const zip = new JSZip();
+
+    // Load the zip content
+    const contents = await zip.loadAsync(zipBlob);
+
+    // Convert zip contents to files
+    const files: Record<string, File> = {};
+    const filePromises: Promise<void>[] = [];
+
+    // Process each file in the zip
+    contents.forEach((relativePath, zipEntry) => {
+      if (!zipEntry.dir) {
+        const promise = zipEntry.async("blob").then((blob) => {
+          // Create a file with the proper name and path
+          const path = "/" + relativePath;
+          files[path] = new File(
+            [blob],
+            relativePath.split("/").pop() || "unknown",
+            {
+              type: getMimeType(relativePath.split(".").pop() || ""),
+            }
+          );
+        });
+        filePromises.push(promise);
+      }
+    });
+
+    // Wait for all files to be processed
+    await Promise.all(filePromises);
+
+    // Get all file paths and clean them
+    const fileNames = Object.keys(files).map((n) => cleanFilePath(n));
+
+    // Filter all files ending in Urdf
+    const availableModels = fileNames.filter((n) => /urdf$/i.test(n));
+
+    // Create blob URLs for Urdf files
+    const blobUrls: Record<string, string> = {};
+    availableModels.forEach((path) => {
+      blobUrls[path] = URL.createObjectURL(files[path]);
+    });
+
+    // Extract the package base path from the first Urdf model for reference
+    let packageBasePath = "";
+    if (availableModels.length > 0) {
+      // Extract the main directory path (e.g., '/cassie_description/')
+      const firstModel = availableModels[0];
+      const packageMatch = firstModel.match(/^(\/[^/]+\/)/);
+      if (packageMatch && packageMatch[1]) {
+        packageBasePath = packageMatch[1];
+      }
+    }
+
+    // Store the package path for future reference
+    const packagePathRef = packageBasePath;
+    urdfProcessor.setUrlModifierFunc((url) => {
+      // Find the matching file given the requested URL
+
+      // Store package reference for future use
+      if (packagePathRef) {
+        packageRef.current = packagePathRef;
+      }
+
+      // Simple approach: just find the first file that matches the end of the URL
+      const cleaned = cleanFilePath(url);
+
+      // Get the filename from the URL
+      const urlFilename = cleaned.split("/").pop() || "";
+
+      // Find the first file that ends with this filename
+      let fileName = fileNames.find((name) => name.endsWith(urlFilename));
+
+      // If no match found, just take the first file with a similar extension
+      if (!fileName && urlFilename.includes(".")) {
+        const extension = "." + urlFilename.split(".").pop();
+        fileName = fileNames.find((name) => name.endsWith(extension));
+      }
+
+      if (fileName !== undefined && fileName !== null) {
+        // Extract file extension for content type
+        const fileExtension = fileName.split(".").pop()?.toLowerCase() || "";
+
+        // Create blob URL with extension in the searchParams to help with format detection
+        const blob = new Blob([files[fileName]], {
+          type: getMimeType(fileExtension),
+        });
+        const blobUrl = URL.createObjectURL(blob) + "#." + fileExtension;
+
+        // Don't revoke immediately, wait for the mesh to be loaded
+        setTimeout(() => URL.revokeObjectURL(blobUrl), 5000);
+        return blobUrl;
+      }
+
+      console.warn(`No matching file found for: ${url}`);
+      return url;
+    });
+
+    return {
+      files,
+      availableModels,
+      blobUrls,
+    };
+  } catch (error) {
+    console.error("❌ Error downloading or extracting zip:", error);
+    throw error;
+  }
+}
+
+/**
+ * Processes dropped files and returns information about available Urdf models
+ */
+export async function processDroppedFiles(
+  dataTransfer: DataTransfer,
+  urdfProcessor: UrdfProcessor
+): Promise<{
+  files: Record<string, File>;
+  availableModels: string[];
+  blobUrls: Record<string, string>;
+}> {
+  // Reset the package reference
+  packageRef.current = "";
+
+  // Convert dropped files into a structured format
+  const files = await dataTransferToFiles(dataTransfer);
+
+  // Get all file paths and clean them
+  const fileNames = Object.keys(files).map((n) => cleanFilePath(n));
+
+  // Filter all files ending in Urdf
+  const availableModels = fileNames.filter((n) => /urdf$/i.test(n));
+
+  // Create blob URLs for Urdf files
+  const blobUrls: Record<string, string> = {};
+  availableModels.forEach((path) => {
+    blobUrls[path] = URL.createObjectURL(files[path]);
+  });
+
+  // Extract the package base path from the first Urdf model for reference
+  let packageBasePath = "";
+  if (availableModels.length > 0) {
+    // Extract the main directory path (e.g., '/cassie_description/')
+    const firstModel = availableModels[0];
+    const packageMatch = firstModel.match(/^(\/[^/]+\/)/);
+    if (packageMatch && packageMatch[1]) {
+      packageBasePath = packageMatch[1];
+    }
+  }
+
+  // Store the package path for future reference
+  const packagePathRef = packageBasePath;
+  urdfProcessor.setUrlModifierFunc((url) => {
+    // Find the matching file given the requested URL
+
+    // Store package reference for future use
+    if (packagePathRef) {
+      packageRef.current = packagePathRef;
+    }
+
+    // Simple approach: just find the first file that matches the end of the URL
+    const cleaned = cleanFilePath(url);
+
+    // Get the filename from the URL
+    const urlFilename = cleaned.split("/").pop() || "";
+
+    // Find the first file that ends with this filename
+    let fileName = fileNames.find((name) => name.endsWith(urlFilename));
+
+    // If no match found, just take the first file with a similar extension
+    if (!fileName && urlFilename.includes(".")) {
+      const extension = "." + urlFilename.split(".").pop();
+      fileName = fileNames.find((name) => name.endsWith(extension));
+    }
+
+    if (fileName !== undefined && fileName !== null) {
+      // Extract file extension for content type
+      const fileExtension = fileName.split(".").pop()?.toLowerCase() || "";
+
+      // Create blob URL with extension in the searchParams to help with format detection
+      const blob = new Blob([files[fileName]], {
+        type: getMimeType(fileExtension),
+      });
+      const blobUrl = URL.createObjectURL(blob) + "#." + fileExtension;
+
+      // Don't revoke immediately, wait for the mesh to be loaded
+      setTimeout(() => URL.revokeObjectURL(blobUrl), 5000);
+      return blobUrl;
+    }
+
+    console.warn(`No matching file found for: ${url}`);
+    return url;
+  });
+
+  return {
+    files,
+    availableModels,
+    blobUrls,
+  };
+}
+
+/**
+ * Get the MIME type for a file extension
+ */
+function getMimeType(extension: string): string {
+  switch (extension.toLowerCase()) {
+    case "stl":
+      return "model/stl";
+    case "obj":
+      return "model/obj";
+    case "gltf":
+    case "glb":
+      return "model/gltf+json";
+    case "dae":
+      return "model/vnd.collada+xml";
+    case "urdf":
+      return "application/xml";
+    default:
+      return "application/octet-stream";
+  }
+}
diff --git a/src/lib/meshLoaders.ts b/src/lib/meshLoaders.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6361f8a63d26cf9afbc783dd6c547f7beb397f7b
--- /dev/null
+++ b/src/lib/meshLoaders.ts
@@ -0,0 +1,111 @@
+import {
+  LoadingManager,
+  MeshPhongMaterial,
+  Mesh,
+  Color,
+  Object3D,
+  Group,
+  BoxGeometry,
+} from "three";
+import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
+import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
+import { ColladaLoader } from "three/examples/jsm/loaders/ColladaLoader.js";
+import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js";
+
+/**
+ * Loads mesh files of different formats
+ * @param path The path to the mesh file
+ * @param manager The THREE.js loading manager
+ * @param done Callback function when loading is complete
+ */
+export const loadMeshFile = (
+  path: string,
+  manager: LoadingManager,
+  done: (result: Object3D | Group | Mesh | null, err?: Error) => void
+) => {
+  // First try to get extension from the original path
+  let ext = path.split(/\./g).pop()?.toLowerCase();
+
+  // If the URL is a blob URL with a fragment containing the extension, use that
+  if (path.startsWith("blob:") && path.includes("#.")) {
+    const fragmentExt = path.split("#.").pop();
+    if (fragmentExt) {
+      ext = fragmentExt.toLowerCase();
+    }
+  }
+
+  // If we can't determine extension, try to check Content-Type
+  if (!ext) {
+    console.error(`Could not determine file extension for: ${path}`);
+    done(null, new Error(`Unsupported file format: ${path}`));
+    return;
+  }
+
+  switch (ext) {
+    case "gltf":
+    case "glb":
+      new GLTFLoader(manager).load(
+        path,
+        (result) => done(result.scene),
+        null,
+        (err) => done(null, err as Error)
+      );
+      break;
+    case "obj":
+      new OBJLoader(manager).load(
+        path,
+        (result) => done(result),
+        null,
+        (err) => done(null, err as Error)
+      );
+      break;
+    case "dae":
+      new ColladaLoader(manager).load(
+        path,
+        (result) => done(result.scene),
+        null,
+        (err) => done(null, err as Error)
+      );
+      break;
+    case "stl":
+      console.log(`🔧 Loading STL file: ${path}`);
+      new STLLoader(manager).load(
+        path,
+        (result) => {
+          console.log(`✅ STL loaded successfully: ${path}`);
+          const material = new MeshPhongMaterial();
+          const mesh = new Mesh(result, material);
+          done(mesh);
+        },
+        (progress) => {
+          console.log(`📊 STL loading progress: ${path}`, progress);
+        },
+        (err) => {
+          console.error(`❌ STL loading failed: ${path}`, err);
+
+          // Create a fallback basic geometry when STL fails to load
+          console.log(`🔄 Creating fallback geometry for: ${path}`);
+          const fallbackGeometry = new BoxGeometry(0.05, 0.05, 0.05); // Small 5cm cube
+          const fallbackMaterial = new MeshPhongMaterial({
+            color: 0xff6b35, // Orange color to indicate it's a fallback
+            transparent: true,
+            opacity: 0.7,
+          });
+          const fallbackMesh = new Mesh(fallbackGeometry, fallbackMaterial);
+          done(fallbackMesh);
+        }
+      );
+      break;
+    default:
+      done(null, new Error(`Unsupported file format: ${ext}`));
+  }
+};
+
+/**
+ * Creates a color in THREE.js format from a CSS color string
+ * @param color The CSS color string
+ * @returns A THREE.js Color
+ */
+export const createColor = (color: string): Color => {
+  return new Color(color);
+};
diff --git a/src/lib/mockData.ts b/src/lib/mockData.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e067f4316133b9145b01fbece7e8593e4de7b045
--- /dev/null
+++ b/src/lib/mockData.ts
@@ -0,0 +1,26 @@
+
+// Generate mock sensor data
+export const generateSensorData = () => {
+  const time = Date.now();
+  return {
+    time: time % 10000,
+    sensor1: Math.sin(time / 1000) * 50 + 50,
+    sensor2: Math.cos(time / 1500) * 30 + 70,
+    sensor3: Math.sin(time / 800) * 40 + 60,
+    sensor4: Math.cos(time / 1200) * 35 + 65,
+  };
+};
+
+// Generate mock motor data
+export const generateMotorData = () => {
+  const time = Date.now();
+  return {
+    time: time % 10000,
+    motor1: Math.sin(time / 1000) * 20 + 30,
+    motor2: Math.cos(time / 1200) * 25 + 45,
+    motor3: Math.sin(time / 900) * 30 + 50,
+    motor4: Math.cos(time / 1100) * 22 + 35,
+    motor5: Math.sin(time / 1300) * 28 + 40,
+    motor6: Math.cos(time / 1400) * 26 + 42,
+  };
+};
diff --git a/src/lib/types.ts b/src/lib/types.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c3f9d67a7956896b5139801aaed16957fe8cb7d5
--- /dev/null
+++ b/src/lib/types.ts
@@ -0,0 +1,97 @@
+/**
+ * Shared type definitions for Urdf parsing from supabase edge function
+ */
+
+export interface UrdfData {
+  name?: string;
+  description?: string;
+  mass?: number;
+  dofs?: number;
+  joints?: {
+    revolute?: number;
+    prismatic?: number;
+    continuous?: number;
+    fixed?: number;
+    other?: number;
+  };
+  links?: {
+    name?: string;
+    mass?: number;
+  }[];
+  materials?: {
+    name?: string;
+    percentage?: number;
+  }[];
+}
+
+/**
+ * Interface representing a Urdf file model
+ */
+export interface UrdfFileModel {
+  /**
+   * Path to the Urdf file
+   */
+  path: string;
+
+  /**
+   * Blob URL for accessing the file
+   */
+  blobUrl: string;
+
+  /**
+   * Name of the model extracted from the file path
+   */
+  name?: string;
+}
+
+/**
+ * Joint animation configuration interface
+ */
+export interface JointAnimationConfig {
+  /** Joint name in the Urdf */
+  name: string;
+  /** Animation type (sine, linear, etc.) */
+  type: "sine" | "linear" | "constant";
+  /** Minimum value for the joint */
+  min: number;
+  /** Maximum value for the joint */
+  max: number;
+  /** Speed multiplier for the animation (lower = slower) */
+  speed: number;
+  /** Phase offset in radians */
+  offset: number;
+  /** Whether angles are in degrees (will be converted to radians) */
+  isDegrees?: boolean;
+  /** For more complex movements, a custom function that takes time and returns a value between 0 and 1 */
+  customEasing?: (time: number) => number;
+}
+
+/**
+ * Robot animation configuration interface
+ */
+export interface RobotAnimationConfig {
+  /** Array of joint configurations */
+  joints: JointAnimationConfig[];
+  /** Global speed multiplier */
+  speedMultiplier?: number;
+}
+
+export interface AnimationRequest {
+  robotName: string;
+  urdfContent: string;
+  description: string; // Natural language description of the desired animation
+}
+
+export interface ContentItem {
+  id: string;
+  title: string;
+  imageUrl: string;
+  description?: string;
+  categories: string[];
+  urdfPath: string;
+}
+
+export interface Category {
+  id: string;
+  name: string;
+}
diff --git a/src/lib/urdfAnimationHelpers.ts b/src/lib/urdfAnimationHelpers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..05175ec5c4b7637600a1c0710b631bfaeb700ca7
--- /dev/null
+++ b/src/lib/urdfAnimationHelpers.ts
@@ -0,0 +1,283 @@
+import { MathUtils } from "three";
+import { RobotAnimationConfig } from "@/lib/types";
+
+// Define the interface for the Urdf viewer element
+export interface UrdfViewerElement extends HTMLElement {
+  setJointValue: (joint: string, value: number) => void;
+}
+
+/**
+ * Generalized animation function for any robot
+ * @param viewer The Urdf viewer element
+ * @param config Configuration for the robot's joint animations
+ * @returns A cleanup function to cancel the animation
+ */
+export function animateRobot(
+  viewer: UrdfViewerElement,
+  config: RobotAnimationConfig
+): () => void {
+  let animationFrameId: number | null = null;
+  let isRunning = true;
+  const speedMultiplier = config.speedMultiplier || 1;
+
+  const animate = () => {
+    if (!isRunning) return;
+
+    const time = Date.now() / 300; // Base time unit
+
+    try {
+      // Process each joint configuration
+      for (const joint of config.joints) {
+        // Calculate the animation ratio (0-1) based on the animation type
+        let ratio = 0;
+        const adjustedTime =
+          time * joint.speed * speedMultiplier + joint.offset;
+
+        switch (joint.type) {
+          case "sine":
+            // Sine wave oscillation mapped to 0-1
+            ratio = (Math.sin(adjustedTime) + 1) / 2;
+            break;
+          case "linear":
+            // Saw tooth pattern (0 to 1 repeated)
+            ratio = (adjustedTime % (2 * Math.PI)) / (2 * Math.PI);
+            break;
+          case "constant":
+            // Constant value (using max)
+            ratio = 1;
+            break;
+          default:
+            // Use custom easing if provided
+            if (joint.customEasing) {
+              ratio = joint.customEasing(adjustedTime);
+            }
+        }
+
+        // Calculate the joint value based on min/max and the ratio
+        let value = MathUtils.lerp(joint.min, joint.max, ratio);
+
+        // Convert from degrees to radians if specified
+        if (joint.isDegrees) {
+          value = (value * Math.PI) / 180;
+        }
+
+        // Set the joint value, catching errors for non-existent joints
+        try {
+          viewer.setJointValue(joint.name, value);
+        } catch (e) {
+          // Silently ignore if the joint doesn't exist
+        }
+      }
+    } catch (err) {
+      console.error("Error in robot animation:", err);
+    }
+
+    // Continue the animation loop
+    animationFrameId = requestAnimationFrame(animate);
+  };
+
+  // Start the animation
+  animationFrameId = requestAnimationFrame(animate);
+
+  // Return cleanup function
+  return () => {
+    isRunning = false;
+
+    if (animationFrameId) {
+      cancelAnimationFrame(animationFrameId);
+      animationFrameId = null;
+    }
+  };
+}
+
+/**
+ * Animates a hexapod robot (like T12) with walking motion
+ * @param viewer The Urdf viewer element
+ * @returns A cleanup function to cancel the animation
+ */
+export function animateHexapodRobot(viewer: UrdfViewerElement): () => void {
+  let animationFrameId: number | null = null;
+  let isRunning = true;
+
+  const animate = () => {
+    // Don't continue animation if we've been told to stop
+    if (!isRunning) return;
+
+    // Animate the legs (for T12 robot)
+    const time = Date.now() / 3e2;
+
+    try {
+      for (let i = 1; i <= 6; i++) {
+        const offset = (i * Math.PI) / 3;
+        const ratio = Math.max(0, Math.sin(time + offset));
+
+        // For a hexapod robot like T12
+        if (typeof viewer.setJointValue === "function") {
+          // Hip joints
+          viewer.setJointValue(
+            `HP${i}`,
+            (MathUtils.lerp(30, 0, ratio) * Math.PI) / 180
+          );
+          // Knee joints
+          viewer.setJointValue(
+            `KP${i}`,
+            (MathUtils.lerp(90, 150, ratio) * Math.PI) / 180
+          );
+          // Ankle joints
+          viewer.setJointValue(
+            `AP${i}`,
+            (MathUtils.lerp(-30, -60, ratio) * Math.PI) / 180
+          );
+
+          // Check if these joints exist before setting values
+          try {
+            // Tire/Contact joints
+            viewer.setJointValue(`TC${i}A`, MathUtils.lerp(0, 0.065, ratio));
+            viewer.setJointValue(`TC${i}B`, MathUtils.lerp(0, 0.065, ratio));
+            // Wheel rotation
+            viewer.setJointValue(`W${i}`, performance.now() * 0.001);
+          } catch (e) {
+            // Silently ignore if those joints don't exist
+          }
+        }
+      }
+    } catch (err) {
+      console.error("Error in animation:", err);
+    }
+
+    // Continue the animation loop
+    animationFrameId = requestAnimationFrame(animate);
+  };
+
+  // Start the animation
+  animationFrameId = requestAnimationFrame(animate);
+
+  // Return cleanup function
+  return () => {
+    // Mark animation as stopped but DO NOT reset joint positions
+    isRunning = false;
+
+    if (animationFrameId) {
+      cancelAnimationFrame(animationFrameId);
+      animationFrameId = null;
+    }
+  };
+}
+
+// Example: Walking animation for Cassie robot
+export const cassieWalkingConfig: RobotAnimationConfig = {
+  speedMultiplier: 0.5, // Adjust overall speed
+  joints: [
+    // Left leg
+    {
+      name: "hip_abduction_left",
+      type: "sine",
+      min: -0.1, // Small side-to-side movement
+      max: 0.1,
+      speed: 1,
+      offset: 0,
+      isDegrees: false, // Already in radians
+    },
+    {
+      name: "hip_rotation_left", // Assuming this joint exists
+      type: "sine",
+      min: -0.2,
+      max: 0.2,
+      speed: 1,
+      offset: Math.PI / 2, // 90 degrees out of phase
+      isDegrees: false,
+    },
+    {
+      name: "hip_flexion_left", // Assuming this joint exists
+      type: "sine",
+      min: -0.3,
+      max: 0.6,
+      speed: 1,
+      offset: 0,
+      isDegrees: false,
+    },
+    {
+      name: "knee_joint_left", // Assuming this joint exists
+      type: "sine",
+      min: 0.2,
+      max: 1.4,
+      speed: 1,
+      offset: Math.PI / 2, // 90 degrees phase shifted from hip
+      isDegrees: false,
+    },
+    {
+      name: "ankle_joint_left", // Assuming this joint exists
+      type: "sine",
+      min: -0.4,
+      max: 0.1,
+      speed: 1,
+      offset: Math.PI, // 180 degrees out of phase with hip
+      isDegrees: false,
+    },
+    {
+      name: "toe_joint_left", // Assuming this joint exists
+      type: "sine",
+      min: -0.2,
+      max: 0.2,
+      speed: 1,
+      offset: Math.PI * 1.5, // 270 degrees phase
+      isDegrees: false,
+    },
+
+    // Right leg (with appropriate phase shift to alternate with left leg)
+    {
+      name: "hip_abduction_right", // Assuming this joint exists
+      type: "sine",
+      min: -0.1,
+      max: 0.1,
+      speed: 1,
+      offset: Math.PI, // 180 degrees out of phase with left side
+      isDegrees: false,
+    },
+    {
+      name: "hip_rotation_right", // Assuming this joint exists
+      type: "sine",
+      min: -0.2,
+      max: 0.2,
+      speed: 1,
+      offset: Math.PI + Math.PI / 2, // 180 + 90 degrees phase
+      isDegrees: false,
+    },
+    {
+      name: "hip_flexion_right", // Assuming this joint exists
+      type: "sine",
+      min: -0.3,
+      max: 0.6,
+      speed: 1,
+      offset: Math.PI, // 180 degrees out of phase with left hip
+      isDegrees: false,
+    },
+    {
+      name: "knee_joint_right", // Assuming this joint exists
+      type: "sine",
+      min: 0.2,
+      max: 1.4,
+      speed: 1,
+      offset: Math.PI + Math.PI / 2, // 180 + 90 degrees phase
+      isDegrees: false,
+    },
+    {
+      name: "ankle_joint_right", // Assuming this joint exists
+      type: "sine",
+      min: -0.4,
+      max: 0.1,
+      speed: 1,
+      offset: 0, // 180 + 180 degrees = 360 = 0
+      isDegrees: false,
+    },
+    {
+      name: "toe_joint_right", // Assuming this joint exists
+      type: "sine",
+      min: -0.2,
+      max: 0.2,
+      speed: 1,
+      offset: Math.PI / 2, // 180 + 270 = 450 degrees = 90 degrees
+      isDegrees: false,
+    },
+  ],
+};
diff --git a/src/lib/urdfViewerHelpers.ts b/src/lib/urdfViewerHelpers.ts
new file mode 100644
index 0000000000000000000000000000000000000000..db2a1c7c8854b65683821b6d1dd6f445be4dc1c0
--- /dev/null
+++ b/src/lib/urdfViewerHelpers.ts
@@ -0,0 +1,259 @@
+import {
+  LoadingManager,
+  Object3D,
+  PerspectiveCamera,
+  Vector3,
+  Color,
+  AmbientLight,
+  DirectionalLight,
+  Scene,
+} from "three";
+import { toast } from "@/components/ui/sonner";
+import { loadMeshFile } from "./meshLoaders";
+
+// Define the interface for the URDF viewer element
+export interface URDFViewerElement extends HTMLElement {
+  setJointValue: (joint: string, value: number) => void;
+  loadMeshFunc?: (
+    path: string,
+    manager: LoadingManager,
+    done: (result: Object3D | null, err?: Error) => void
+  ) => void;
+
+  // Extended properties for camera fitting
+  camera: PerspectiveCamera;
+  controls: {
+    target: Vector3;
+    update: () => void;
+  };
+  robot: Object3D;
+  redraw: () => void;
+  up: string;
+  scene: Scene;
+}
+
+/**
+ * Creates and configures a URDF viewer element
+ */
+export function createUrdfViewer(
+  container: HTMLDivElement,
+  isDarkMode: boolean
+): URDFViewerElement {
+  // Clear any existing content
+  container.innerHTML = "";
+
+  // Create the urdf-viewer element
+  const viewer = document.createElement("urdf-viewer") as URDFViewerElement;
+  viewer.classList.add("w-full", "h-full");
+
+  // Add the element to the container
+  container.appendChild(viewer);
+
+  // Set initial viewer properties
+  viewer.setAttribute("up", "Z");
+  setViewerColor(viewer, isDarkMode ? "#2c2b3a" : "#eff4ff");
+  viewer.setAttribute("highlight-color", isDarkMode ? "#df6dd4" : "#b05ffe");
+  viewer.setAttribute("auto-redraw", "true");
+  // viewer.setAttribute("display-shadow", ""); // Enable shadows
+
+  // Add ambient light to the scene
+  const ambientLight = new AmbientLight(0xd6d6d6, 1); // Increased intensity to 0.4
+  viewer.scene.add(ambientLight);
+
+  // Add directional light for better shadows and depth
+  const directionalLight = new DirectionalLight(0xffffff, 0.8);
+  directionalLight.position.set(5, 30, 5);
+  directionalLight.castShadow = true;
+  viewer.scene.add(directionalLight);
+
+  // Set initial camera position for more zoomed-in view
+  // Wait for the viewer to be fully initialized before adjusting camera
+  setTimeout(() => {
+    if (viewer.camera) {
+      // Move camera closer to the robot for a more zoomed-in initial view
+      viewer.camera.position.set(0.5, 0.3, 0.5);
+      viewer.camera.lookAt(0, 0.2, 0); // Look at center of robot
+
+      // Update controls target if available
+      if (viewer.controls) {
+        viewer.controls.target.set(0, 0.2, 0);
+        viewer.controls.update();
+      }
+
+      // Trigger a redraw
+      if (viewer.redraw) {
+        viewer.redraw();
+      }
+    }
+  }, 100);
+
+  return viewer;
+}
+
+/**
+ * Setup mesh loading function for URDF viewer
+ */
+export function setupMeshLoader(
+  viewer: URDFViewerElement,
+  urlModifierFunc: ((url: string) => string) | null
+): void {
+  if ("loadMeshFunc" in viewer) {
+    viewer.loadMeshFunc = (
+      path: string,
+      manager: LoadingManager,
+      done: (result: Object3D | null, err?: Error) => void
+    ) => {
+      // Apply URL modifier if available (for custom uploads)
+      const modifiedPath = urlModifierFunc ? urlModifierFunc(path) : path;
+
+      // If loading fails, log the error but continue
+      try {
+        loadMeshFile(modifiedPath, manager, (result, err) => {
+          if (err) {
+            console.warn(`Error loading mesh ${modifiedPath}:`, err);
+            // Try to continue with other meshes
+            done(null);
+          } else {
+            done(result);
+          }
+        });
+      } catch (err) {
+        console.error(`Exception loading mesh ${modifiedPath}:`, err);
+        done(null, err as Error);
+      }
+    };
+  }
+}
+
+/**
+ * Setup event handlers for joint highlighting
+ */
+export function setupJointHighlighting(
+  viewer: URDFViewerElement,
+  setHighlightedJoint: (joint: string | null) => void
+): () => void {
+  const onJointMouseover = (e: Event) => {
+    const customEvent = e as CustomEvent;
+    setHighlightedJoint(customEvent.detail);
+  };
+
+  const onJointMouseout = () => {
+    setHighlightedJoint(null);
+  };
+
+  // Add event listeners
+  viewer.addEventListener("joint-mouseover", onJointMouseover);
+  viewer.addEventListener("joint-mouseout", onJointMouseout);
+
+  // Return cleanup function
+  return () => {
+    viewer.removeEventListener("joint-mouseover", onJointMouseover);
+    viewer.removeEventListener("joint-mouseout", onJointMouseout);
+  };
+}
+
+/**
+ * Setup model loading and error handling
+ */
+export function setupModelLoading(
+  viewer: URDFViewerElement,
+  urdfPath: string,
+  packagePath: string,
+  setCustomUrdfPath: (path: string) => void,
+  alternativeRobotModels: string[] = [] // Add parameter for alternative models
+): () => void {
+  // Add XML content type hint for blob URLs
+  const loadPath =
+    urdfPath.startsWith("blob:") && !urdfPath.includes("#.")
+      ? urdfPath + "#.urdf" // Add extension hint if it's a blob URL
+      : urdfPath;
+
+  // Set the URDF path
+  viewer.setAttribute("urdf", loadPath);
+  viewer.setAttribute("package", packagePath);
+
+  // Handle successful loading and set initial zoom
+  const onLoadSuccess = () => {
+    // Set more zoomed-in camera position after model loads
+    setTimeout(() => {
+      if (viewer.camera && viewer.robot) {
+        // Position camera closer for better initial view
+        viewer.camera.position.set(0.4, 0.25, 0.4);
+        viewer.camera.lookAt(0, 0.15, 0);
+
+        if (viewer.controls) {
+          viewer.controls.target.set(0, 0.15, 0);
+          viewer.controls.update();
+        }
+
+        if (viewer.redraw) {
+          viewer.redraw();
+        }
+      }
+    }, 50);
+  };
+
+  // Handle error loading
+  const onLoadError = () => {
+    // toast.error("Failed to load model", {
+    //   description: "There was an error loading the URDF model.",
+    //   duration: 3000,
+    // });
+
+    // Use the provided alternativeRobotModels instead of the global window object
+    if (alternativeRobotModels.length > 0) {
+      const nextModel = alternativeRobotModels[0];
+      if (nextModel) {
+        setCustomUrdfPath(nextModel);
+        toast.info("Trying alternative model...", {
+          description: `First model failed to load. Trying ${
+            nextModel.split("/").pop() || "alternative model"
+          }`,
+          duration: 2000,
+        });
+      }
+    }
+  };
+
+  viewer.addEventListener("error", onLoadError);
+  viewer.addEventListener("urdf-processed", onLoadSuccess);
+
+  // Return cleanup function
+  return () => {
+    viewer.removeEventListener("error", onLoadError);
+    viewer.removeEventListener("urdf-processed", onLoadSuccess);
+  };
+}
+
+/**
+ * Sets the background color of the URDF viewer
+ */
+export function setViewerColor(viewer: URDFViewerElement, color: string): void {
+  // Set the ambient color for the scene
+  // viewer.setAttribute("ambient-color", color);
+
+  // Set the background color on the viewer's parent container
+  const container = viewer.parentElement;
+  if (container) {
+    container.style.backgroundColor = color;
+  }
+}
+
+/**
+ * Updates the viewer's colors based on the current theme
+ */
+export function updateViewerTheme(
+  viewer: URDFViewerElement,
+  isDarkMode: boolean
+): void {
+  // Update the ambient color
+  setViewerColor(viewer, isDarkMode ? "#2c2b3a" : "#eff4ff");
+  viewer.setAttribute("highlight-color", isDarkMode ? "#df6dd4" : "#b05ffe");
+
+  // // Update the ambient light intensity based on theme
+  // viewer.scene.traverse((object) => {
+  //   if (object instanceof AmbientLight) {
+  //     object.intensity = isDarkMode ? 0.4 : 0.6; // Brighter in light mode
+  //   }
+  // });
+}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bd0c391ddd1088e9067844c48835bf4abcd61783
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+  return twMerge(clsx(inputs))
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..719464e3da4bc77d3adebed4b6c12d3327f5b89f
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,5 @@
+import { createRoot } from 'react-dom/client'
+import App from './App.tsx'
+import './index.css'
+
+createRoot(document.getElementById("root")!).render(<App />);
diff --git a/src/pages/Calibration.tsx b/src/pages/Calibration.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6f7b21ef74f728e225fbc786c9d49a2d43a8f5ad
--- /dev/null
+++ b/src/pages/Calibration.tsx
@@ -0,0 +1,739 @@
+import { useState, useEffect, useRef, useMemo } from "react";
+import { useNavigate } from "react-router-dom";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Badge } from "@/components/ui/badge";
+import { Separator } from "@/components/ui/separator";
+import {
+  ArrowLeft,
+  Settings,
+  Wrench,
+  Activity,
+  CheckCircle,
+  XCircle,
+  AlertCircle,
+  Loader2,
+  Play,
+  Square,
+  RefreshCw,
+  Trash2,
+  List,
+} from "lucide-react";
+import { useToast } from "@/hooks/use-toast";
+
+interface CalibrationStatus {
+  calibration_active: boolean;
+  status: string; // "idle", "connecting", "calibrating", "completed", "error", "stopping"
+  device_type: string | null;
+  error: string | null;
+  message: string;
+  console_output: string;
+}
+
+interface CalibrationRequest {
+  device_type: string; // "robot" or "teleop"
+  port: string;
+  config_file: string;
+}
+
+interface CalibrationConfig {
+  name: string;
+  filename: string;
+  size: number;
+  modified: number;
+}
+
+// ConfigsResponse interface removed since we're using text input
+
+const Calibration = () => {
+  const navigate = useNavigate();
+  const { toast } = useToast();
+
+  // Ref for auto-scrolling console
+  const consoleRef = useRef<HTMLDivElement>(null);
+
+  // Form state
+  const [deviceType, setDeviceType] = useState<string>("robot");
+  const [port, setPort] = useState<string>("");
+  const [configFile, setConfigFile] = useState<string>("");
+
+  // Config loading and management
+  const [isLoadingConfigs, setIsLoadingConfigs] = useState(false);
+  const [availableConfigs, setAvailableConfigs] = useState<CalibrationConfig[]>(
+    []
+  );
+
+  // Calibration state
+  const [calibrationStatus, setCalibrationStatus] = useState<CalibrationStatus>(
+    {
+      calibration_active: false,
+      status: "idle",
+      device_type: null,
+      error: null,
+      message: "",
+      console_output: "",
+    }
+  );
+  const [isPolling, setIsPolling] = useState(false);
+
+  // Config loading removed since we're using text input now
+
+  // Poll calibration status
+  const pollStatus = async () => {
+    try {
+      const response = await fetch("http://localhost:8000/calibration-status");
+      if (response.ok) {
+        const status = await response.json();
+        setCalibrationStatus(status);
+
+        // Stop polling if calibration is completed or error
+        if (
+          !status.calibration_active &&
+          (status.status === "completed" || status.status === "error")
+        ) {
+          setIsPolling(false);
+        }
+      }
+    } catch (error) {
+      console.error("Error polling status:", error);
+    }
+  };
+
+  // Start calibration
+  const handleStartCalibration = async () => {
+    if (!deviceType || !port || !configFile) {
+      toast({
+        title: "Missing Information",
+        description: "Please fill in all required fields",
+        variant: "destructive",
+      });
+      return;
+    }
+
+    const request: CalibrationRequest = {
+      device_type: deviceType,
+      port: port,
+      config_file: configFile,
+    };
+
+    try {
+      const response = await fetch("http://localhost:8000/start-calibration", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify(request),
+      });
+
+      const result = await response.json();
+
+      if (result.success) {
+        toast({
+          title: "Calibration Started",
+          description: `Calibration started for ${deviceType}`,
+        });
+        setIsPolling(true);
+      } else {
+        toast({
+          title: "Calibration Failed",
+          description: result.message || "Failed to start calibration",
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      console.error("Error starting calibration:", error);
+      toast({
+        title: "Error",
+        description: "Failed to start calibration",
+        variant: "destructive",
+      });
+    }
+  };
+
+  // Stop calibration
+  const handleStopCalibration = async () => {
+    try {
+      const response = await fetch("http://localhost:8000/stop-calibration", {
+        method: "POST",
+      });
+
+      const result = await response.json();
+
+      if (result.success) {
+        toast({
+          title: "Calibration Stopped",
+          description: "Calibration has been stopped",
+        });
+        setIsPolling(false);
+      } else {
+        toast({
+          title: "Error",
+          description: result.message || "Failed to stop calibration",
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      console.error("Error stopping calibration:", error);
+      toast({
+        title: "Error",
+        description: "Failed to stop calibration",
+        variant: "destructive",
+      });
+    }
+  };
+
+  // Reset form
+  const handleReset = () => {
+    setDeviceType("robot");
+    setPort("");
+    setConfigFile("");
+    setAvailableConfigs([]);
+    setCalibrationStatus({
+      calibration_active: false,
+      status: "idle",
+      device_type: null,
+      error: null,
+      message: "",
+      console_output: "",
+    });
+    setIsPolling(false);
+  };
+
+  // Load available configs for the selected device type
+  const loadAvailableConfigs = async (deviceType: string) => {
+    if (!deviceType) return;
+
+    setIsLoadingConfigs(true);
+    try {
+      const response = await fetch(
+        `http://localhost:8000/calibration-configs/${deviceType}`
+      );
+      const data = await response.json();
+
+      if (data.success) {
+        setAvailableConfigs(data.configs || []);
+      } else {
+        toast({
+          title: "Error Loading Configs",
+          description: data.message || "Could not load calibration configs",
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      toast({
+        title: "Error Loading Configs",
+        description: "Could not connect to the backend server",
+        variant: "destructive",
+      });
+    } finally {
+      setIsLoadingConfigs(false);
+    }
+  };
+
+  // Delete a config file
+  const handleDeleteConfig = async (configName: string) => {
+    if (!deviceType) return;
+
+    try {
+      const response = await fetch(
+        `http://localhost:8000/calibration-configs/${deviceType}/${configName}`,
+        { method: "DELETE" }
+      );
+      const data = await response.json();
+
+      if (data.success) {
+        toast({
+          title: "Config Deleted",
+          description: data.message,
+        });
+        // Reload the configs list
+        loadAvailableConfigs(deviceType);
+      } else {
+        toast({
+          title: "Delete Failed",
+          description: data.message || "Could not delete the configuration",
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      toast({
+        title: "Error",
+        description: "Could not delete the configuration",
+        variant: "destructive",
+      });
+    }
+  };
+
+  // Send Enter to calibration process
+  const handleSendEnter = async () => {
+    if (!calibrationStatus.calibration_active) return;
+
+    console.log("🔵 Enter button clicked - sending input...");
+
+    try {
+      const response = await fetch("http://localhost:8000/calibration-input", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify({ input: "\n" }), // Send actual newline character
+      });
+
+      const data = await response.json();
+      console.log("🔵 Server response:", data);
+
+      if (data.success) {
+        toast({
+          title: "Enter Sent",
+          description: "Enter key sent to calibration process",
+        });
+      } else {
+        toast({
+          title: "Input Failed",
+          description: data.message || "Could not send Enter",
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      console.error("🔴 Error sending Enter:", error);
+      toast({
+        title: "Error",
+        description: "Could not send Enter to calibration",
+        variant: "destructive",
+      });
+    }
+  };
+
+  // Config loading removed - using text input instead
+
+  // Set up polling
+  useEffect(() => {
+    let interval: NodeJS.Timeout;
+
+    if (isPolling) {
+      // Use ultra-fast polling during active calibration for real-time updates
+      const pollInterval =
+        calibrationStatus.status === "calibrating" ? 25 : 100;
+      interval = setInterval(pollStatus, pollInterval); // 25ms during calibration, 100ms otherwise
+      pollStatus(); // Initial poll
+    }
+
+    return () => {
+      if (interval) clearInterval(interval);
+    };
+  }, [isPolling, calibrationStatus.status]);
+
+  // Load configs when device type changes
+  useEffect(() => {
+    if (deviceType) {
+      loadAvailableConfigs(deviceType);
+    } else {
+      setAvailableConfigs([]);
+    }
+  }, [deviceType]);
+
+  // Auto-scroll console to bottom when output changes (with debounce)
+  useEffect(() => {
+    if (consoleRef.current && calibrationStatus.console_output) {
+      // Small delay to ensure DOM is updated before scrolling
+      const timeoutId = setTimeout(() => {
+        if (consoleRef.current) {
+          consoleRef.current.scrollTop = consoleRef.current.scrollHeight;
+        }
+      }, 10);
+
+      return () => clearTimeout(timeoutId);
+    }
+  }, [calibrationStatus.console_output]);
+
+  // Get status color and icon
+  const getStatusDisplay = () => {
+    switch (calibrationStatus.status) {
+      case "idle":
+        return {
+          color: "bg-gray-500",
+          icon: <Settings className="w-4 h-4" />,
+          text: "Idle",
+        };
+      case "connecting":
+        return {
+          color: "bg-yellow-500",
+          icon: <Loader2 className="w-4 h-4 animate-spin" />,
+          text: "Connecting",
+        };
+      case "calibrating":
+        return {
+          color: "bg-blue-500",
+          icon: <Activity className="w-4 h-4" />,
+          text: "Calibrating",
+        };
+      case "completed":
+        return {
+          color: "bg-green-500",
+          icon: <CheckCircle className="w-4 h-4" />,
+          text: "Completed",
+        };
+      case "error":
+        return {
+          color: "bg-red-500",
+          icon: <XCircle className="w-4 h-4" />,
+          text: "Error",
+        };
+      case "stopping":
+        return {
+          color: "bg-orange-500",
+          icon: <Square className="w-4 h-4" />,
+          text: "Stopping",
+        };
+      default:
+        return {
+          color: "bg-gray-500",
+          icon: <Settings className="w-4 h-4" />,
+          text: "Unknown",
+        };
+    }
+  };
+
+  const statusDisplay = getStatusDisplay();
+
+  // Memoize console output to prevent unnecessary re-renders
+  const memoizedConsoleOutput = useMemo(() => {
+    return (
+      calibrationStatus.console_output || "Waiting for calibration output..."
+    );
+  }, [calibrationStatus.console_output]);
+
+  return (
+    <div className="min-h-screen bg-gray-900 text-white p-4">
+      <div className="max-w-4xl mx-auto">
+        {/* Header */}
+        <div className="flex items-center gap-4 mb-6">
+          <Button
+            variant="outline"
+            size="sm"
+            onClick={() => navigate("/")}
+            className="border-gray-700 hover:bg-gray-800"
+          >
+            <ArrowLeft className="w-4 h-4 mr-2" />
+            Back to Home
+          </Button>
+          <div className="flex items-center gap-3">
+            <Wrench className="w-8 h-8 text-orange-500" />
+            <h1 className="text-3xl font-bold">Device Calibration</h1>
+          </div>
+        </div>
+
+        <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
+          {/* Configuration Panel */}
+          <Card className="bg-gray-800 border-gray-700">
+            <CardHeader>
+              <CardTitle className="flex items-center gap-2">
+                <Settings className="w-5 h-5" />
+                Calibration Configuration
+              </CardTitle>
+            </CardHeader>
+            <CardContent className="space-y-6">
+              {/* Device Type Selection */}
+              <div className="space-y-2">
+                <Label
+                  htmlFor="deviceType"
+                  className="text-sm font-medium text-gray-300"
+                >
+                  Device Type *
+                </Label>
+                <Select value={deviceType} onValueChange={setDeviceType}>
+                  <SelectTrigger className="bg-gray-700 border-gray-600 text-white">
+                    <SelectValue placeholder="Select device type" />
+                  </SelectTrigger>
+                  <SelectContent className="bg-gray-800 border-gray-700">
+                    <SelectItem
+                      value="robot"
+                      className="text-white hover:bg-gray-700"
+                    >
+                      Robot (Follower)
+                    </SelectItem>
+                    <SelectItem
+                      value="teleop"
+                      className="text-white hover:bg-gray-700"
+                    >
+                      Teleoperator (Leader)
+                    </SelectItem>
+                  </SelectContent>
+                </Select>
+              </div>
+
+              {/* Port Configuration */}
+              <div className="space-y-2">
+                <Label
+                  htmlFor="port"
+                  className="text-sm font-medium text-gray-300"
+                >
+                  Port *
+                </Label>
+                <Input
+                  id="port"
+                  value={port}
+                  onChange={(e) => setPort(e.target.value)}
+                  placeholder="/dev/tty.usbmodem5A460816421"
+                  className="bg-gray-700 border-gray-600 text-white"
+                />
+              </div>
+
+              {/* Config File Name */}
+              <div className="space-y-2">
+                <Label
+                  htmlFor="configFile"
+                  className="text-sm font-medium text-gray-300"
+                >
+                  Calibration Config *
+                </Label>
+                <Input
+                  id="configFile"
+                  value={configFile}
+                  onChange={(e) => setConfigFile(e.target.value)}
+                  placeholder="config_name (without .json extension)"
+                  className="bg-gray-700 border-gray-600 text-white"
+                />
+              </div>
+
+              {/* Available Configurations List */}
+              {deviceType && (
+                <div className="space-y-3">
+                  <div className="flex items-center gap-2">
+                    <List className="w-4 h-4 text-gray-400" />
+                    <Label className="text-sm font-medium text-gray-300">
+                      Available Configurations
+                    </Label>
+                    {isLoadingConfigs && (
+                      <Loader2 className="w-4 h-4 animate-spin text-gray-400" />
+                    )}
+                  </div>
+
+                  <div className="max-h-40 overflow-y-auto bg-gray-700 rounded-lg border border-gray-600">
+                    {availableConfigs.length === 0 ? (
+                      <div className="p-3 text-center text-gray-400 text-sm">
+                        {isLoadingConfigs
+                          ? "Loading..."
+                          : "No configurations found"}
+                      </div>
+                    ) : (
+                      <div className="space-y-1 p-2">
+                        {availableConfigs.map((config) => (
+                          <div
+                            key={config.name}
+                            className="flex items-center justify-between bg-gray-600 rounded px-3 py-2 hover:bg-gray-500 transition-colors"
+                          >
+                            <div className="flex-1 min-w-0">
+                              <button
+                                onClick={() => setConfigFile(config.name)}
+                                className="text-left w-full text-white hover:text-blue-300 font-medium truncate"
+                                title={`Click to select: ${config.name}`}
+                              >
+                                {config.name}
+                              </button>
+                              <div className="text-xs text-gray-400">
+                                {new Date(
+                                  config.modified * 1000
+                                ).toLocaleDateString()}
+                                {" • "}
+                                {(config.size / 1024).toFixed(1)} KB
+                              </div>
+                            </div>
+                            <button
+                              onClick={(e) => {
+                                e.stopPropagation();
+                                handleDeleteConfig(config.name);
+                              }}
+                              className="ml-3 p-1 text-red-400 hover:text-red-300 hover:bg-red-900/20 rounded transition-colors"
+                              title={`Delete ${config.name}`}
+                            >
+                              <Trash2 className="w-4 h-4" />
+                            </button>
+                          </div>
+                        ))}
+                      </div>
+                    )}
+                  </div>
+                </div>
+              )}
+
+              <Separator className="bg-gray-700" />
+
+              {/* Action Buttons */}
+              <div className="flex flex-col gap-3">
+                {!calibrationStatus.calibration_active ? (
+                  <Button
+                    onClick={handleStartCalibration}
+                    className="w-full bg-orange-500 hover:bg-orange-600 text-white py-6 text-lg"
+                    disabled={
+                      isLoadingConfigs || !deviceType || !port || !configFile
+                    }
+                  >
+                    <Play className="w-5 h-5 mr-2" />
+                    Start Calibration
+                  </Button>
+                ) : (
+                  <Button
+                    onClick={handleStopCalibration}
+                    variant="destructive"
+                    className="w-full py-6 text-lg"
+                  >
+                    <Square className="w-5 h-5 mr-2" />
+                    Stop Calibration
+                  </Button>
+                )}
+
+                <Button
+                  onClick={handleReset}
+                  variant="outline"
+                  className="w-full border-gray-600 hover:bg-gray-700 py-6 text-lg"
+                  disabled={calibrationStatus.calibration_active}
+                >
+                  <RefreshCw className="w-5 h-5 mr-2" />
+                  Reset
+                </Button>
+              </div>
+            </CardContent>
+          </Card>
+
+          {/* Status Panel */}
+          <Card className="bg-gray-800 border-gray-700">
+            <CardHeader>
+              <CardTitle className="flex items-center gap-2">
+                <Activity className="w-5 h-5" />
+                Calibration Status
+              </CardTitle>
+            </CardHeader>
+            <CardContent className="space-y-4">
+              {/* Current Status */}
+              <div className="flex items-center justify-between">
+                <span className="text-gray-300">Status:</span>
+                <Badge className={`${statusDisplay.color} text-white`}>
+                  {statusDisplay.icon}
+                  <span className="ml-2">{statusDisplay.text}</span>
+                </Badge>
+              </div>
+
+              {calibrationStatus.device_type && (
+                <div className="flex items-center justify-between">
+                  <span className="text-gray-300">Device:</span>
+                  <span className="text-white capitalize">
+                    {calibrationStatus.device_type}
+                  </span>
+                </div>
+              )}
+
+              {/* Calibration Console - Show during calibration */}
+              {calibrationStatus.calibration_active && (
+                <div className="space-y-3">
+                  <div className="flex items-center gap-2">
+                    <Settings className="w-4 h-4 text-gray-400" />
+                    <span className="text-sm font-medium text-gray-300">
+                      Calibration Console
+                    </span>
+                  </div>
+
+                  {/* Console Output */}
+                  <div className="bg-black rounded-lg p-4 font-mono text-sm">
+                    <div
+                      ref={consoleRef}
+                      className="text-green-400 h-80 overflow-y-auto whitespace-pre-wrap"
+                    >
+                      {memoizedConsoleOutput}
+                    </div>
+                  </div>
+
+                  {/* Enter Button */}
+                  <div className="flex justify-center">
+                    <Button
+                      onClick={handleSendEnter}
+                      disabled={!calibrationStatus.calibration_active}
+                      className="bg-blue-500 hover:bg-blue-600 px-8 py-2"
+                    >
+                      Press Enter
+                    </Button>
+                  </div>
+
+                  <div className="text-xs text-gray-400 text-center">
+                    Click the button above to send Enter to the calibration
+                    process
+                  </div>
+                </div>
+              )}
+
+              {/* Status Messages */}
+              {calibrationStatus.status === "connecting" && (
+                <Alert className="bg-yellow-900/50 border-yellow-700">
+                  <AlertCircle className="h-4 w-4" />
+                  <AlertDescription>
+                    Connecting to the device. Please ensure the device is
+                    properly connected.
+                  </AlertDescription>
+                </Alert>
+              )}
+
+              {calibrationStatus.status === "calibrating" && (
+                <Alert className="bg-blue-900/50 border-blue-700">
+                  <Activity className="h-4 w-4" />
+                  <AlertDescription>
+                    Calibration in progress. Please follow the instructions on
+                    the device and do not disconnect.
+                  </AlertDescription>
+                </Alert>
+              )}
+
+              {calibrationStatus.status === "completed" && (
+                <Alert className="bg-green-900/50 border-green-700">
+                  <CheckCircle className="h-4 w-4" />
+                  <AlertDescription>
+                    Calibration completed successfully! The device is now ready
+                    for use.
+                  </AlertDescription>
+                </Alert>
+              )}
+
+              {calibrationStatus.status === "error" &&
+                calibrationStatus.error && (
+                  <Alert className="bg-red-900/50 border-red-700">
+                    <XCircle className="h-4 w-4" />
+                    <AlertDescription>
+                      <strong>Error:</strong> {calibrationStatus.error}
+                    </AlertDescription>
+                  </Alert>
+                )}
+
+              {/* Instructions */}
+              <div className="bg-gray-700 p-4 rounded-lg">
+                <h4 className="font-semibold mb-2">
+                  Calibration Instructions:
+                </h4>
+                <ol className="text-sm text-gray-300 space-y-1">
+                  <li>1. Select the device type you want to calibrate</li>
+                  <li>2. Enter the correct port for your device</li>
+                  <li>3. Choose the appropriate calibration configuration</li>
+                  <li>4. Move the robot in a middle position</li>
+                  <li>
+                    5. Click "Start Calibration" and follow device prompts
+                  </li>
+                  <li>6. Move each motor all the way on both sides</li>
+                </ol>
+              </div>
+            </CardContent>
+          </Card>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default Calibration;
diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4b37fa2e5cd7e13fffd9a8632e3f7a6c285ac2aa
--- /dev/null
+++ b/src/pages/Index.tsx
@@ -0,0 +1,147 @@
+
+import React, { useState, useEffect, useRef } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useToast } from '@/hooks/use-toast';
+import { generateSensorData, generateMotorData } from '@/lib/mockData';
+import VisualizerPanel from '@/components/control/VisualizerPanel';
+import MetricsPanel from '@/components/control/MetricsPanel';
+import CommandBar from '@/components/control/CommandBar';
+
+const Index = () => {
+  const [command, setCommand] = useState('');
+  const [activeTab, setActiveTab] = useState<'SENSORS' | 'MOTORS'>('SENSORS');
+  const [isVoiceActive, setIsVoiceActive] = useState(true);
+  const [showCamera, setShowCamera] = useState(false);
+  const [hasPermissions, setHasPermissions] = useState(false);
+  const [micLevel, setMicLevel] = useState(0);
+  const [sensorData, setSensorData] = useState<any[]>([]);
+  const [motorData, setMotorData] = useState<any[]>([]);
+  const { toast } = useToast();
+  const navigate = useNavigate();
+
+  const videoRef = useRef<HTMLVideoElement>(null);
+  const streamRef = useRef<MediaStream | null>(null);
+
+  useEffect(() => {
+    let audioContext: AudioContext | null = null;
+    const getPermissions = async () => {
+      try {
+        const stream = await navigator.mediaDevices.getUserMedia({ 
+          video: true, 
+          audio: true 
+        });
+        
+        streamRef.current = stream;
+        if (videoRef.current) {
+          videoRef.current.srcObject = stream;
+        }
+        
+        setHasPermissions(true);
+        
+        const AudioContextClass = window.AudioContext || (window as any).webkitAudioContext;
+        if (AudioContextClass) {
+          audioContext = new AudioContextClass();
+          const analyser = audioContext.createAnalyser();
+          const source = audioContext.createMediaStreamSource(stream);
+          source.connect(analyser);
+          
+          let animationFrameId: number;
+          const dataArray = new Uint8Array(analyser.frequencyBinCount);
+          const updateMicLevel = () => {
+            if (audioContext?.state === 'closed') return;
+            analyser.getByteFrequencyData(dataArray);
+            const average = dataArray.reduce((a, b) => a + b) / dataArray.length;
+            setMicLevel(average);
+            animationFrameId = requestAnimationFrame(updateMicLevel);
+          };
+          updateMicLevel();
+
+          return () => {
+            cancelAnimationFrame(animationFrameId);
+            audioContext?.close();
+          };
+        }
+      } catch (error) {
+        console.error("Permission to access media devices was denied.", error);
+      }
+    };
+
+    let cleanup: (() => void) | undefined;
+    getPermissions().then(returnedCleanup => {
+      cleanup = returnedCleanup;
+    });
+
+    return () => {
+      if (streamRef.current) {
+        streamRef.current.getTracks().forEach(track => track.stop());
+      }
+      cleanup?.();
+    };
+  }, []);
+
+  useEffect(() => {
+    const interval = setInterval(() => {
+      setSensorData(prev => [...prev, generateSensorData()].slice(-50));
+      setMotorData(prev => [...prev, generateMotorData()].slice(-50));
+    }, 100);
+
+    return () => clearInterval(interval);
+  }, []);
+
+  const handleSendCommand = () => {
+    if (command.trim()) {
+      toast({
+        title: "Command Sent",
+        description: `Robot command: "${command}"`,
+      });
+      setCommand('');
+    }
+  };
+
+  const handleGoBack = () => {
+    navigate('/');
+  };
+
+  const handleEndSession = () => {
+    if (streamRef.current) {
+      streamRef.current.getTracks().forEach(track => track.stop());
+    }
+    toast({
+      title: "Session Ended",
+      description: "Robot control session terminated safely.",
+      variant: "destructive",
+    });
+    navigate('/');
+  };
+
+  return (
+    <div className="min-h-screen bg-black text-white flex flex-col">
+      <div className="flex-1 flex flex-col lg:flex-row">
+        <VisualizerPanel onGoBack={handleGoBack} />
+        <MetricsPanel
+          activeTab={activeTab}
+          setActiveTab={setActiveTab}
+          sensorData={sensorData}
+          motorData={motorData}
+          hasPermissions={hasPermissions}
+          streamRef={streamRef}
+          isVoiceActive={isVoiceActive}
+          micLevel={micLevel}
+        />
+      </div>
+
+      <CommandBar
+        command={command}
+        setCommand={setCommand}
+        handleSendCommand={handleSendCommand}
+        isVoiceActive={isVoiceActive}
+        setIsVoiceActive={setIsVoiceActive}
+        showCamera={showCamera}
+        setShowCamera={setShowCamera}
+        handleEndSession={handleEndSession}
+      />
+    </div>
+  );
+};
+
+export default Index;
diff --git a/src/pages/Landing.tsx b/src/pages/Landing.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b2fe3f55c45c26d7205753b8a39b95d85b02c785
--- /dev/null
+++ b/src/pages/Landing.tsx
@@ -0,0 +1,701 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate } from "react-router-dom";
+import { Button } from "@/components/ui/button";
+import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
+import { Label } from "@/components/ui/label";
+import { Input } from "@/components/ui/input";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import {
+  Dialog,
+  DialogContent,
+  DialogHeader,
+  DialogTitle,
+  DialogDescription,
+} from "@/components/ui/dialog";
+import { useToast } from "@/hooks/use-toast";
+import { Camera, Mic, Settings, Wrench, GraduationCap } from "lucide-react";
+
+const Landing = () => {
+  const [robotModel, setRobotModel] = useState("");
+  const [showPermissionModal, setShowPermissionModal] = useState(false);
+  const [showTeleoperationModal, setShowTeleoperationModal] = useState(false);
+  const [leaderPort, setLeaderPort] = useState("/dev/tty.usbmodem5A460816421");
+  const [followerPort, setFollowerPort] = useState(
+    "/dev/tty.usbmodem5A460816621"
+  );
+  const [leaderConfig, setLeaderConfig] = useState("");
+  const [followerConfig, setFollowerConfig] = useState("");
+  const [leaderConfigs, setLeaderConfigs] = useState<string[]>([]);
+  const [followerConfigs, setFollowerConfigs] = useState<string[]>([]);
+  const [isLoadingConfigs, setIsLoadingConfigs] = useState(false);
+
+  // Recording state
+  const [showRecordingModal, setShowRecordingModal] = useState(false);
+  const [recordLeaderPort, setRecordLeaderPort] = useState(
+    "/dev/tty.usbmodem5A460816421"
+  );
+  const [recordFollowerPort, setRecordFollowerPort] = useState(
+    "/dev/tty.usbmodem5A460816621"
+  );
+  const [recordLeaderConfig, setRecordLeaderConfig] = useState("");
+  const [recordFollowerConfig, setRecordFollowerConfig] = useState("");
+  const [datasetRepoId, setDatasetRepoId] = useState("");
+  const [singleTask, setSingleTask] = useState("");
+  const [numEpisodes, setNumEpisodes] = useState(5);
+
+  const navigate = useNavigate();
+  const { toast } = useToast();
+
+  const loadConfigs = async () => {
+    setIsLoadingConfigs(true);
+    try {
+      const response = await fetch("http://localhost:8000/get-configs");
+      const data = await response.json();
+      setLeaderConfigs(data.leader_configs || []);
+      setFollowerConfigs(data.follower_configs || []);
+    } catch (error) {
+      toast({
+        title: "Error Loading Configs",
+        description: "Could not load calibration configs from the backend.",
+        variant: "destructive",
+      });
+    } finally {
+      setIsLoadingConfigs(false);
+    }
+  };
+
+  const handleBeginSession = () => {
+    if (robotModel) {
+      setShowPermissionModal(true);
+    }
+  };
+
+  const handleTeleoperationClick = () => {
+    if (robotModel) {
+      setShowTeleoperationModal(true);
+      loadConfigs();
+    }
+  };
+
+  const handleRecordingClick = () => {
+    if (robotModel) {
+      setShowRecordingModal(true);
+      loadConfigs();
+    }
+  };
+
+  const handleCalibrationClick = () => {
+    if (robotModel) {
+      navigate("/calibration");
+    }
+  };
+
+  const handleTrainingClick = () => {
+    if (robotModel) {
+      navigate("/training");
+    }
+  };
+
+  const handleStartTeleoperation = async () => {
+    if (!leaderConfig || !followerConfig) {
+      toast({
+        title: "Missing Configuration",
+        description:
+          "Please select calibration configs for both leader and follower.",
+        variant: "destructive",
+      });
+      return;
+    }
+
+    try {
+      const response = await fetch("http://localhost:8000/move-arm", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify({
+          leader_port: leaderPort,
+          follower_port: followerPort,
+          leader_config: leaderConfig,
+          follower_config: followerConfig,
+        }),
+      });
+
+      const data = await response.json();
+
+      if (response.ok) {
+        toast({
+          title: "Teleoperation Started",
+          description:
+            data.message || "Successfully started teleoperation session.",
+        });
+        setShowTeleoperationModal(false);
+        navigate("/teleoperation");
+      } else {
+        toast({
+          title: "Error Starting Teleoperation",
+          description: data.message || "Failed to start teleoperation session.",
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      toast({
+        title: "Connection Error",
+        description: "Could not connect to the backend server.",
+        variant: "destructive",
+      });
+    }
+  };
+
+  const handleStartRecording = async () => {
+    if (
+      !recordLeaderConfig ||
+      !recordFollowerConfig ||
+      !datasetRepoId ||
+      !singleTask
+    ) {
+      toast({
+        title: "Missing Configuration",
+        description:
+          "Please fill in all required fields: calibration configs, dataset ID, and task name.",
+        variant: "destructive",
+      });
+      return;
+    }
+
+    // Navigate to recording page with configuration
+    const recordingConfig = {
+      leader_port: recordLeaderPort,
+      follower_port: recordFollowerPort,
+      leader_config: recordLeaderConfig,
+      follower_config: recordFollowerConfig,
+      dataset_repo_id: datasetRepoId,
+      single_task: singleTask,
+      num_episodes: numEpisodes,
+      episode_time_s: 60, // Default 60 seconds - use manual controls to end episodes early
+      reset_time_s: 15, // Default 15 seconds - use manual controls for faster resets
+      fps: 30,
+      video: true,
+      push_to_hub: false,
+      resume: false,
+    };
+
+    setShowRecordingModal(false);
+    navigate("/recording", { state: { recordingConfig } });
+  };
+
+  const handlePermissions = async (allow: boolean) => {
+    setShowPermissionModal(false);
+    if (allow) {
+      try {
+        const stream = await navigator.mediaDevices.getUserMedia({
+          video: true,
+          audio: true,
+        });
+        stream.getTracks().forEach((track) => track.stop());
+        toast({
+          title: "Permissions Granted",
+          description:
+            "Camera and microphone access enabled. Entering control session...",
+        });
+        navigate("/control");
+      } catch (error) {
+        toast({
+          title: "Permission Denied",
+          description:
+            "Camera and microphone access is required for robot control.",
+          variant: "destructive",
+        });
+      }
+    } else {
+      toast({
+        title: "Permission Denied",
+        description: "You can proceed, but with limited functionality.",
+        variant: "destructive",
+      });
+      navigate("/control");
+    }
+  };
+
+  return (
+    <div className="min-h-screen bg-black text-white flex flex-col items-center justify-center p-4">
+      <div className="text-center space-y-4 max-w-lg w-full">
+        <img
+          src="/lovable-uploads/5e648747-34b7-4d8f-93fd-4dbd00aeeefc.png"
+          alt="LiveLab Logo"
+          className="mx-auto h-20 w-20"
+        />
+        <h1 className="text-5xl font-bold tracking-tight">LiveLab</h1>
+        <p className="text-xl text-gray-400">
+          Control a robotic arm through telepresence. Your browser becomes both
+          sensor and controller.
+        </p>
+      </div>
+
+      <div className="mt-12 p-8 bg-gray-900 rounded-lg shadow-xl w-full max-w-lg space-y-6 border border-gray-700">
+        <h2 className="text-2xl font-semibold text-center text-white">
+          Select Robot Model
+        </h2>
+        <RadioGroup
+          value={robotModel}
+          onValueChange={setRobotModel}
+          className="space-y-4"
+        >
+          <div>
+            <RadioGroupItem value="SO100" id="so100" className="sr-only" />
+            <Label
+              htmlFor="so100"
+              className="flex items-center space-x-4 p-4 rounded-md bg-gray-800 border border-gray-700 cursor-pointer transition-all has-[:checked]:border-orange-500 has-[:checked]:bg-gray-800/50"
+            >
+              <span className="w-6 h-6 rounded-full border-2 border-gray-500 flex items-center justify-center group-has-[:checked]:border-orange-500">
+                {robotModel === "SO100" && (
+                  <span className="w-3 h-3 rounded-full bg-orange-500" />
+                )}
+              </span>
+              <span className="text-lg flex-1">SO100</span>
+            </Label>
+          </div>
+          <div>
+            <RadioGroupItem value="SO101" id="so101" className="sr-only" />
+            <Label
+              htmlFor="so101"
+              className="flex items-center space-x-4 p-4 rounded-md bg-gray-800 border border-gray-700 cursor-pointer transition-all has-[:checked]:border-orange-500 has-[:checked]:bg-gray-800/50"
+            >
+              <span className="w-6 h-6 rounded-full border-2 border-gray-500 flex items-center justify-center group-has-[:checked]:border-orange-500">
+                {robotModel === "SO101" && (
+                  <span className="w-3 h-3 rounded-full bg-orange-500" />
+                )}
+              </span>
+              <span className="text-lg flex-1">SO101</span>
+            </Label>
+          </div>
+        </RadioGroup>
+        <div className="flex flex-col sm:flex-row gap-4">
+          <Button
+            onClick={handleBeginSession}
+            disabled={!robotModel}
+            className="flex-1 bg-orange-500 hover:bg-orange-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
+          >
+            Begin Session
+          </Button>
+          <Button
+            onClick={handleCalibrationClick}
+            disabled={!robotModel}
+            className="flex-1 bg-blue-500 hover:bg-blue-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
+          >
+            <Wrench className="w-5 h-5 mr-2" />
+            Calibration
+          </Button>
+          <Button
+            onClick={handleTeleoperationClick}
+            disabled={!robotModel}
+            className="flex-1 bg-yellow-500 hover:bg-yellow-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
+          >
+            Teleoperation
+          </Button>
+          <Button
+            onClick={handleRecordingClick}
+            disabled={!robotModel}
+            className="flex-1 bg-red-500 hover:bg-red-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
+          >
+            Recording
+          </Button>
+          <Button
+            onClick={handleTrainingClick}
+            disabled={!robotModel}
+            className="flex-1 bg-purple-500 hover:bg-purple-600 text-white text-lg py-6 disabled:bg-gray-700 disabled:text-gray-400 disabled:cursor-not-allowed"
+          >
+            <GraduationCap className="w-5 h-5 mr-2" />
+            Training
+          </Button>
+        </div>
+      </div>
+
+      {/* Permission Modal */}
+      <Dialog open={showPermissionModal} onOpenChange={setShowPermissionModal}>
+        <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[480px] p-8">
+          <DialogHeader>
+            <div className="flex justify-center items-center gap-4 mb-4">
+              <Camera className="w-8 h-8 text-orange-500" />
+              <Mic className="w-8 h-8 text-orange-500" />
+            </div>
+            <DialogTitle className="text-white text-center text-2xl font-bold">
+              Enable Camera & Microphone
+            </DialogTitle>
+          </DialogHeader>
+          <div className="text-center space-y-6 py-4">
+            <DialogDescription className="text-gray-400 text-base leading-relaxed">
+              LiveLab requires access to your camera and microphone for a fully
+              immersive telepresence experience. This enables real-time video
+              feedback and voice command capabilities.
+            </DialogDescription>
+            <div className="flex flex-col sm:flex-row gap-4 justify-center pt-2">
+              <Button
+                onClick={() => handlePermissions(true)}
+                className="w-full sm:w-auto bg-orange-500 hover:bg-orange-600 text-white px-10 py-6 text-lg transition-all shadow-md shadow-orange-500/30 hover:shadow-lg hover:shadow-orange-500/40"
+              >
+                Allow Access
+              </Button>
+              <Button
+                onClick={() => handlePermissions(false)}
+                variant="outline"
+                className="w-full sm:w-auto border-gray-500 hover:border-gray-200 px-10 py-6 text-lg text-zinc-500 bg-zinc-900 hover:bg-zinc-800"
+              >
+                Continue without
+              </Button>
+            </div>
+          </div>
+        </DialogContent>
+      </Dialog>
+
+      {/* Teleoperation Configuration Modal */}
+      <Dialog
+        open={showTeleoperationModal}
+        onOpenChange={setShowTeleoperationModal}
+      >
+        <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[600px] p-8">
+          <DialogHeader>
+            <div className="flex justify-center items-center gap-4 mb-4">
+              <Settings className="w-8 h-8 text-yellow-500" />
+            </div>
+            <DialogTitle className="text-white text-center text-2xl font-bold">
+              Configure Teleoperation
+            </DialogTitle>
+          </DialogHeader>
+          <div className="space-y-6 py-4">
+            <DialogDescription className="text-gray-400 text-base leading-relaxed text-center">
+              Configure the robot arm ports and calibration settings for
+              teleoperation.
+            </DialogDescription>
+
+            <div className="grid grid-cols-1 gap-6">
+              <div className="space-y-2">
+                <Label
+                  htmlFor="leaderPort"
+                  className="text-sm font-medium text-gray-300"
+                >
+                  Leader Port
+                </Label>
+                <Input
+                  id="leaderPort"
+                  value={leaderPort}
+                  onChange={(e) => setLeaderPort(e.target.value)}
+                  placeholder="/dev/tty.usbmodem5A460816421"
+                  className="bg-gray-800 border-gray-700 text-white"
+                />
+              </div>
+
+              <div className="space-y-2">
+                <Label
+                  htmlFor="leaderConfig"
+                  className="text-sm font-medium text-gray-300"
+                >
+                  Leader Calibration Config
+                </Label>
+                <Select value={leaderConfig} onValueChange={setLeaderConfig}>
+                  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
+                    <SelectValue
+                      placeholder={
+                        isLoadingConfigs
+                          ? "Loading configs..."
+                          : "Select leader config"
+                      }
+                    />
+                  </SelectTrigger>
+                  <SelectContent className="bg-gray-800 border-gray-700">
+                    {leaderConfigs.map((config) => (
+                      <SelectItem
+                        key={config}
+                        value={config}
+                        className="text-white hover:bg-gray-700"
+                      >
+                        {config}
+                      </SelectItem>
+                    ))}
+                  </SelectContent>
+                </Select>
+              </div>
+
+              <div className="space-y-2">
+                <Label
+                  htmlFor="followerPort"
+                  className="text-sm font-medium text-gray-300"
+                >
+                  Follower Port
+                </Label>
+                <Input
+                  id="followerPort"
+                  value={followerPort}
+                  onChange={(e) => setFollowerPort(e.target.value)}
+                  placeholder="/dev/tty.usbmodem5A460816621"
+                  className="bg-gray-800 border-gray-700 text-white"
+                />
+              </div>
+
+              <div className="space-y-2">
+                <Label
+                  htmlFor="followerConfig"
+                  className="text-sm font-medium text-gray-300"
+                >
+                  Follower Calibration Config
+                </Label>
+                <Select
+                  value={followerConfig}
+                  onValueChange={setFollowerConfig}
+                >
+                  <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
+                    <SelectValue
+                      placeholder={
+                        isLoadingConfigs
+                          ? "Loading configs..."
+                          : "Select follower config"
+                      }
+                    />
+                  </SelectTrigger>
+                  <SelectContent className="bg-gray-800 border-gray-700">
+                    {followerConfigs.map((config) => (
+                      <SelectItem
+                        key={config}
+                        value={config}
+                        className="text-white hover:bg-gray-700"
+                      >
+                        {config}
+                      </SelectItem>
+                    ))}
+                  </SelectContent>
+                </Select>
+              </div>
+            </div>
+
+            <div className="flex flex-col sm:flex-row gap-4 justify-center pt-4">
+              <Button
+                onClick={handleStartTeleoperation}
+                className="w-full sm:w-auto bg-yellow-500 hover:bg-yellow-600 text-white px-10 py-6 text-lg transition-all shadow-md shadow-yellow-500/30 hover:shadow-lg hover:shadow-yellow-500/40"
+                disabled={isLoadingConfigs}
+              >
+                Start Teleoperation
+              </Button>
+              <Button
+                onClick={() => setShowTeleoperationModal(false)}
+                variant="outline"
+                className="w-full sm:w-auto border-gray-500 hover:border-gray-200 px-10 py-6 text-lg text-zinc-500 bg-zinc-900 hover:bg-zinc-800"
+              >
+                Cancel
+              </Button>
+            </div>
+          </div>
+        </DialogContent>
+      </Dialog>
+
+      {/* Recording Configuration Modal */}
+      <Dialog open={showRecordingModal} onOpenChange={setShowRecordingModal}>
+        <DialogContent className="bg-gray-900 border-gray-800 text-white sm:max-w-[600px] p-8 max-h-[90vh] overflow-y-auto">
+          <DialogHeader>
+            <div className="flex justify-center items-center gap-4 mb-4">
+              <div className="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center">
+                <span className="text-white font-bold text-sm">REC</span>
+              </div>
+            </div>
+            <DialogTitle className="text-white text-center text-2xl font-bold">
+              Configure Recording
+            </DialogTitle>
+          </DialogHeader>
+          <div className="space-y-6 py-4">
+            <DialogDescription className="text-gray-400 text-base leading-relaxed text-center">
+              Configure the robot arm settings and dataset parameters for
+              recording.
+            </DialogDescription>
+
+            <div className="grid grid-cols-1 gap-6">
+              {/* Robot Configuration */}
+              <div className="space-y-4">
+                <h3 className="text-lg font-semibold text-white border-b border-gray-700 pb-2">
+                  Robot Configuration
+                </h3>
+                <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
+                  <div className="space-y-2">
+                    <Label
+                      htmlFor="recordLeaderPort"
+                      className="text-sm font-medium text-gray-300"
+                    >
+                      Leader Port
+                    </Label>
+                    <Input
+                      id="recordLeaderPort"
+                      value={recordLeaderPort}
+                      onChange={(e) => setRecordLeaderPort(e.target.value)}
+                      placeholder="/dev/tty.usbmodem5A460816421"
+                      className="bg-gray-800 border-gray-700 text-white"
+                    />
+                  </div>
+                  <div className="space-y-2">
+                    <Label
+                      htmlFor="recordLeaderConfig"
+                      className="text-sm font-medium text-gray-300"
+                    >
+                      Leader Calibration Config
+                    </Label>
+                    <Select
+                      value={recordLeaderConfig}
+                      onValueChange={setRecordLeaderConfig}
+                    >
+                      <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
+                        <SelectValue
+                          placeholder={
+                            isLoadingConfigs
+                              ? "Loading configs..."
+                              : "Select leader config"
+                          }
+                        />
+                      </SelectTrigger>
+                      <SelectContent className="bg-gray-800 border-gray-700">
+                        {leaderConfigs.map((config) => (
+                          <SelectItem
+                            key={config}
+                            value={config}
+                            className="text-white hover:bg-gray-700"
+                          >
+                            {config}
+                          </SelectItem>
+                        ))}
+                      </SelectContent>
+                    </Select>
+                  </div>
+                  <div className="space-y-2">
+                    <Label
+                      htmlFor="recordFollowerPort"
+                      className="text-sm font-medium text-gray-300"
+                    >
+                      Follower Port
+                    </Label>
+                    <Input
+                      id="recordFollowerPort"
+                      value={recordFollowerPort}
+                      onChange={(e) => setRecordFollowerPort(e.target.value)}
+                      placeholder="/dev/tty.usbmodem5A460816621"
+                      className="bg-gray-800 border-gray-700 text-white"
+                    />
+                  </div>
+                  <div className="space-y-2">
+                    <Label
+                      htmlFor="recordFollowerConfig"
+                      className="text-sm font-medium text-gray-300"
+                    >
+                      Follower Calibration Config
+                    </Label>
+                    <Select
+                      value={recordFollowerConfig}
+                      onValueChange={setRecordFollowerConfig}
+                    >
+                      <SelectTrigger className="bg-gray-800 border-gray-700 text-white">
+                        <SelectValue
+                          placeholder={
+                            isLoadingConfigs
+                              ? "Loading configs..."
+                              : "Select follower config"
+                          }
+                        />
+                      </SelectTrigger>
+                      <SelectContent className="bg-gray-800 border-gray-700">
+                        {followerConfigs.map((config) => (
+                          <SelectItem
+                            key={config}
+                            value={config}
+                            className="text-white hover:bg-gray-700"
+                          >
+                            {config}
+                          </SelectItem>
+                        ))}
+                      </SelectContent>
+                    </Select>
+                  </div>
+                </div>
+              </div>
+
+              {/* Dataset Configuration */}
+              <div className="space-y-4">
+                <h3 className="text-lg font-semibold text-white border-b border-gray-700 pb-2">
+                  Dataset Configuration
+                </h3>
+                <div className="grid grid-cols-1 gap-4">
+                  <div className="space-y-2">
+                    <Label
+                      htmlFor="datasetRepoId"
+                      className="text-sm font-medium text-gray-300"
+                    >
+                      Dataset Repository ID *
+                    </Label>
+                    <Input
+                      id="datasetRepoId"
+                      value={datasetRepoId}
+                      onChange={(e) => setDatasetRepoId(e.target.value)}
+                      placeholder="username/dataset_name"
+                      className="bg-gray-800 border-gray-700 text-white"
+                    />
+                  </div>
+                  <div className="space-y-2">
+                    <Label
+                      htmlFor="singleTask"
+                      className="text-sm font-medium text-gray-300"
+                    >
+                      Task Name *
+                    </Label>
+                    <Input
+                      id="singleTask"
+                      value={singleTask}
+                      onChange={(e) => setSingleTask(e.target.value)}
+                      placeholder="e.g., pick_and_place"
+                      className="bg-gray-800 border-gray-700 text-white"
+                    />
+                  </div>
+                  <div className="space-y-2">
+                    <Label
+                      htmlFor="numEpisodes"
+                      className="text-sm font-medium text-gray-300"
+                    >
+                      Number of Episodes
+                    </Label>
+                    <Input
+                      id="numEpisodes"
+                      type="number"
+                      min="1"
+                      max="100"
+                      value={numEpisodes}
+                      onChange={(e) => setNumEpisodes(parseInt(e.target.value))}
+                      className="bg-gray-800 border-gray-700 text-white"
+                    />
+                  </div>
+                </div>
+              </div>
+            </div>
+
+            <div className="flex flex-col sm:flex-row gap-4 justify-center pt-4">
+              <Button
+                onClick={handleStartRecording}
+                className="w-full sm:w-auto bg-red-500 hover:bg-red-600 text-white px-10 py-6 text-lg transition-all shadow-md shadow-red-500/30 hover:shadow-lg hover:shadow-red-500/40"
+                disabled={isLoadingConfigs}
+              >
+                Start Recording
+              </Button>
+              <Button
+                onClick={() => setShowRecordingModal(false)}
+                variant="outline"
+                className="w-full sm:w-auto border-gray-500 hover:border-gray-200 px-10 py-6 text-lg text-zinc-500 bg-zinc-900 hover:bg-zinc-800"
+              >
+                Cancel
+              </Button>
+            </div>
+          </div>
+        </DialogContent>
+      </Dialog>
+    </div>
+  );
+};
+
+export default Landing;
diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cda36dad66f37ea2f094f433985694a220a70d63
--- /dev/null
+++ b/src/pages/NotFound.tsx
@@ -0,0 +1,27 @@
+import { useLocation } from "react-router-dom";
+import { useEffect } from "react";
+
+const NotFound = () => {
+  const location = useLocation();
+
+  useEffect(() => {
+    console.error(
+      "404 Error: User attempted to access non-existent route:",
+      location.pathname
+    );
+  }, [location.pathname]);
+
+  return (
+    <div className="min-h-screen flex items-center justify-center bg-gray-100">
+      <div className="text-center">
+        <h1 className="text-4xl font-bold mb-4">404</h1>
+        <p className="text-xl text-gray-600 mb-4">Oops! Page not found</p>
+        <a href="/" className="text-blue-500 hover:text-blue-700 underline">
+          Return to Home
+        </a>
+      </div>
+    </div>
+  );
+};
+
+export default NotFound;
diff --git a/src/pages/Recording.tsx b/src/pages/Recording.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..4fd2b21c029668aae2ce06d234c43c261cbe2a52
--- /dev/null
+++ b/src/pages/Recording.tsx
@@ -0,0 +1,561 @@
+import React, { useState, useEffect } from "react";
+import { useNavigate, useLocation } from "react-router-dom";
+import { Button } from "@/components/ui/button";
+import { useToast } from "@/hooks/use-toast";
+import {
+  ArrowLeft,
+  Square,
+  SkipForward,
+  RotateCcw,
+  Play,
+  GraduationCap,
+} from "lucide-react";
+
+interface RecordingConfig {
+  leader_port: string;
+  follower_port: string;
+  leader_config: string;
+  follower_config: string;
+  dataset_repo_id: string;
+  single_task: string;
+  num_episodes: number;
+  episode_time_s: number;
+  reset_time_s: number;
+  fps: number;
+  video: boolean;
+  push_to_hub: boolean;
+  resume: boolean;
+}
+
+interface BackendStatus {
+  recording_active: boolean;
+  current_phase: string;
+  current_episode?: number;
+  total_episodes?: number;
+  phase_elapsed_seconds?: number;
+  phase_time_limit_s?: number;
+  session_elapsed_seconds?: number;
+  available_controls: {
+    stop_recording: boolean;
+    exit_early: boolean;
+    rerecord_episode: boolean;
+  };
+}
+
+const Recording = () => {
+  const location = useLocation();
+  const navigate = useNavigate();
+  const { toast } = useToast();
+
+  // Get recording config from navigation state
+  const recordingConfig = location.state?.recordingConfig as RecordingConfig;
+
+  // Backend status state - this is the single source of truth
+  const [backendStatus, setBackendStatus] = useState<BackendStatus | null>(
+    null
+  );
+  const [recordingSessionStarted, setRecordingSessionStarted] = useState(false);
+
+  // Redirect if no config provided
+  useEffect(() => {
+    if (!recordingConfig) {
+      toast({
+        title: "No Configuration",
+        description: "Please start recording from the main page.",
+        variant: "destructive",
+      });
+      navigate("/");
+    }
+  }, [recordingConfig, navigate, toast]);
+
+  // Start recording session when component loads
+  useEffect(() => {
+    if (recordingConfig && !recordingSessionStarted) {
+      startRecordingSession();
+    }
+  }, [recordingConfig, recordingSessionStarted]);
+
+  // Poll backend status continuously to stay in sync
+  useEffect(() => {
+    let statusInterval: NodeJS.Timeout;
+
+    if (recordingSessionStarted) {
+      const pollStatus = async () => {
+        try {
+          const response = await fetch(
+            "http://localhost:8000/recording-status"
+          );
+          if (response.ok) {
+            const status = await response.json();
+            setBackendStatus(status);
+
+            // If backend recording stopped, session is complete
+            if (!status.recording_active && recordingSessionStarted) {
+              toast({
+                title: "Recording Complete!",
+                description: `All episodes have been recorded successfully.`,
+              });
+            }
+          }
+        } catch (error) {
+          console.error("Error polling recording status:", error);
+        }
+      };
+
+      // Poll immediately and then every second for real-time updates
+      pollStatus();
+      statusInterval = setInterval(pollStatus, 1000);
+    }
+
+    return () => {
+      if (statusInterval) clearInterval(statusInterval);
+    };
+  }, [recordingSessionStarted, toast]);
+
+  const formatTime = (seconds: number): string => {
+    const mins = Math.floor(seconds / 60);
+    const secs = seconds % 60;
+    return `${mins.toString().padStart(2, "0")}:${secs
+      .toString()
+      .padStart(2, "0")}`;
+  };
+
+  const startRecordingSession = async () => {
+    try {
+      const response = await fetch("http://localhost:8000/start-recording", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify(recordingConfig),
+      });
+
+      const data = await response.json();
+
+      if (response.ok) {
+        setRecordingSessionStarted(true);
+        toast({
+          title: "Recording Started",
+          description: `Started recording ${recordingConfig.num_episodes} episodes`,
+        });
+      } else {
+        toast({
+          title: "Error Starting Recording",
+          description: data.message || "Failed to start recording session.",
+          variant: "destructive",
+        });
+        navigate("/");
+      }
+    } catch (error) {
+      toast({
+        title: "Connection Error",
+        description: "Could not connect to the backend server.",
+        variant: "destructive",
+      });
+      navigate("/");
+    }
+  };
+
+  // Equivalent to pressing RIGHT ARROW key in original record.py
+  const handleExitEarly = async () => {
+    if (!backendStatus?.available_controls.exit_early) return;
+
+    try {
+      const response = await fetch(
+        "http://localhost:8000/recording-exit-early",
+        {
+          method: "POST",
+        }
+      );
+      const data = await response.json();
+
+      if (response.ok) {
+        const currentPhase = backendStatus.current_phase;
+        if (currentPhase === "recording") {
+          toast({
+            title: "Episode Recording Ended",
+            description: `Episode ${backendStatus.current_episode} recording completed. Moving to reset phase.`,
+          });
+        } else if (currentPhase === "resetting") {
+          toast({
+            title: "Reset Complete",
+            description: `Moving to next episode...`,
+          });
+        }
+      } else {
+        toast({
+          title: "Error",
+          description: data.message,
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      toast({
+        title: "Connection Error",
+        description: "Could not connect to the backend server.",
+        variant: "destructive",
+      });
+    }
+  };
+
+  // Equivalent to pressing LEFT ARROW key in original record.py
+  const handleRerecordEpisode = async () => {
+    if (!backendStatus?.available_controls.rerecord_episode) return;
+
+    try {
+      const response = await fetch(
+        "http://localhost:8000/recording-rerecord-episode",
+        {
+          method: "POST",
+        }
+      );
+      const data = await response.json();
+
+      if (response.ok) {
+        toast({
+          title: "Re-recording Episode",
+          description: `Episode ${backendStatus.current_episode} will be re-recorded.`,
+        });
+      } else {
+        toast({
+          title: "Error",
+          description: data.message,
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      toast({
+        title: "Connection Error",
+        description: "Could not connect to the backend server.",
+        variant: "destructive",
+      });
+    }
+  };
+
+  // Equivalent to pressing ESC key in original record.py
+  const handleStopRecording = async () => {
+    try {
+      const response = await fetch("http://localhost:8000/stop-recording", {
+        method: "POST",
+      });
+
+      toast({
+        title: "Recording Stopped",
+        description: "Recording session has been stopped.",
+      });
+      navigate("/");
+    } catch (error) {
+      toast({
+        title: "Error",
+        description: "Failed to stop recording.",
+        variant: "destructive",
+      });
+    }
+  };
+
+  if (!recordingConfig) {
+    return (
+      <div className="min-h-screen bg-black text-white flex items-center justify-center">
+        <div className="text-center">
+          <p className="text-lg">No recording configuration found.</p>
+          <Button onClick={() => navigate("/")} className="mt-4">
+            Return to Home
+          </Button>
+        </div>
+      </div>
+    );
+  }
+
+  // Show loading state while waiting for backend status
+  if (!backendStatus) {
+    return (
+      <div className="min-h-screen bg-black text-white flex items-center justify-center">
+        <div className="text-center">
+          <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-red-500 mx-auto mb-4"></div>
+          <p className="text-lg">Connecting to recording session...</p>
+        </div>
+      </div>
+    );
+  }
+
+  const currentPhase = backendStatus.current_phase;
+  const currentEpisode = backendStatus.current_episode || 1;
+  const totalEpisodes =
+    backendStatus.total_episodes || recordingConfig.num_episodes;
+  const phaseElapsedTime = backendStatus.phase_elapsed_seconds || 0;
+  const phaseTimeLimit =
+    backendStatus.phase_time_limit_s ||
+    (currentPhase === "recording"
+      ? recordingConfig.episode_time_s
+      : recordingConfig.reset_time_s);
+  const sessionElapsedTime = backendStatus.session_elapsed_seconds || 0;
+
+  const getPhaseTitle = () => {
+    if (currentPhase === "recording") return "Episode Recording Time";
+    if (currentPhase === "resetting") return "Environment Reset Time";
+    return "Phase Time";
+  };
+
+  const getStatusText = () => {
+    if (currentPhase === "recording")
+      return `RECORDING EPISODE ${currentEpisode}`;
+    if (currentPhase === "resetting") return "RESET THE ENVIRONMENT";
+    if (currentPhase === "preparing") return "PREPARING SESSION";
+    return "SESSION COMPLETE";
+  };
+
+  const getStatusColor = () => {
+    if (currentPhase === "recording") return "text-red-400";
+    if (currentPhase === "resetting") return "text-orange-400";
+    if (currentPhase === "preparing") return "text-yellow-400";
+    return "text-gray-400";
+  };
+
+  const getDotColor = () => {
+    if (currentPhase === "recording") return "bg-red-500 animate-pulse";
+    if (currentPhase === "resetting") return "bg-orange-500 animate-pulse";
+    if (currentPhase === "preparing") return "bg-yellow-500";
+    return "bg-gray-500";
+  };
+
+  return (
+    <div className="min-h-screen bg-black text-white p-8">
+      <div className="max-w-6xl mx-auto">
+        {/* Header */}
+        <div className="flex items-center justify-between mb-8">
+          <Button
+            onClick={() => navigate("/")}
+            variant="outline"
+            className="border-gray-500 hover:border-gray-200 text-gray-300 hover:text-white"
+          >
+            <ArrowLeft className="w-4 h-4 mr-2" />
+            Back to Home
+          </Button>
+
+          <div className="flex items-center gap-3">
+            <div className={`w-3 h-3 rounded-full ${getDotColor()}`}></div>
+            <h1 className="text-3xl font-bold">Recording Session</h1>
+          </div>
+        </div>
+
+        {/* Main Recording Dashboard */}
+        <div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
+          {/* Phase Timer */}
+          <div className="bg-gray-900 rounded-lg p-6 border border-gray-700 text-center">
+            <h2 className="text-sm text-gray-400 mb-2">{getPhaseTitle()}</h2>
+            <div
+              className={`text-4xl font-mono font-bold mb-2 ${
+                currentPhase === "recording"
+                  ? "text-green-400"
+                  : "text-orange-400"
+              }`}
+            >
+              {formatTime(phaseElapsedTime)}
+            </div>
+            <div className="text-sm text-gray-400">
+              / {formatTime(phaseTimeLimit)}
+            </div>
+            <div className="w-full bg-gray-700 rounded-full h-2 mt-3">
+              <div
+                className={`h-2 rounded-full transition-all duration-1000 ${
+                  currentPhase === "recording"
+                    ? "bg-green-500"
+                    : "bg-orange-500"
+                }`}
+                style={{
+                  width: `${Math.min(
+                    (phaseElapsedTime / phaseTimeLimit) * 100,
+                    100
+                  )}%`,
+                }}
+              ></div>
+            </div>
+          </div>
+
+          {/* Episode Progress */}
+          <div className="bg-gray-900 rounded-lg p-6 border border-gray-700 text-center">
+            <h2 className="text-sm text-gray-400 mb-2">Episode Progress</h2>
+            <div className="text-4xl font-bold text-blue-400 mb-2">
+              {currentEpisode} of {totalEpisodes}
+            </div>
+            <div className="text-sm text-gray-400">
+              {recordingConfig.single_task}
+            </div>
+            <div className="w-full bg-gray-700 rounded-full h-2 mt-3">
+              <div
+                className="bg-blue-500 h-2 rounded-full transition-all duration-500"
+                style={{ width: `${(currentEpisode / totalEpisodes) * 100}%` }}
+              ></div>
+            </div>
+          </div>
+
+          {/* Session Timer */}
+          <div className="bg-gray-900 rounded-lg p-6 border border-gray-700 text-center">
+            <h2 className="text-sm text-gray-400 mb-2">Total Session Time</h2>
+            <div className="text-4xl font-mono font-bold text-yellow-400 mb-2">
+              {formatTime(sessionElapsedTime)}
+            </div>
+            <div className="text-sm text-gray-400">
+              Dataset: {recordingConfig.dataset_repo_id}
+            </div>
+          </div>
+        </div>
+
+        {/* Status and Controls */}
+        <div className="bg-gray-900 rounded-lg p-6 border border-gray-700">
+          <div className="flex items-center justify-between mb-6">
+            <div>
+              <h2 className="text-xl font-semibold text-white mb-2">
+                Recording Status
+              </h2>
+              <div className="flex items-center gap-3">
+                <div className={`w-2 h-2 rounded-full ${getDotColor()}`}></div>
+                <span className={`font-semibold ${getStatusColor()}`}>
+                  {getStatusText()}
+                </span>
+              </div>
+            </div>
+          </div>
+
+          {/* Recording Phase Controls */}
+          {currentPhase === "recording" && (
+            <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
+              <Button
+                onClick={handleExitEarly}
+                disabled={!backendStatus.available_controls.exit_early}
+                className="bg-green-500 hover:bg-green-600 text-white font-semibold py-4 text-lg disabled:opacity-50"
+              >
+                <SkipForward className="w-5 h-5 mr-2" />
+                End Episode
+              </Button>
+
+              <Button
+                onClick={handleRerecordEpisode}
+                disabled={!backendStatus.available_controls.rerecord_episode}
+                className="bg-orange-500 hover:bg-orange-600 text-white font-semibold py-4 text-lg disabled:opacity-50"
+              >
+                <RotateCcw className="w-5 h-5 mr-2" />
+                Re-record Episode
+              </Button>
+
+              <Button
+                onClick={handleStopRecording}
+                disabled={!backendStatus.available_controls.stop_recording}
+                className="bg-red-500 hover:bg-red-600 text-white font-semibold py-4 text-lg disabled:opacity-50"
+              >
+                <Square className="w-5 h-5 mr-2" />
+                Stop Recording
+              </Button>
+            </div>
+          )}
+
+          {/* Reset Phase Controls */}
+          {currentPhase === "resetting" && (
+            <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
+              <Button
+                onClick={handleExitEarly}
+                disabled={!backendStatus.available_controls.exit_early}
+                className="bg-blue-500 hover:bg-blue-600 text-white font-semibold py-6 text-xl disabled:opacity-50"
+              >
+                <Play className="w-6 h-6 mr-2" />
+                Continue to Next Phase
+              </Button>
+
+              <Button
+                onClick={handleStopRecording}
+                disabled={!backendStatus.available_controls.stop_recording}
+                className="bg-red-500 hover:bg-red-600 text-white font-semibold py-6 text-xl disabled:opacity-50"
+              >
+                <Square className="w-5 h-5 mr-2" />
+                Stop Recording
+              </Button>
+            </div>
+          )}
+
+          {currentPhase === "completed" && (
+            <div className="text-center">
+              <p className="text-lg text-green-400 mb-6">
+                ✅ Recording session completed successfully!
+              </p>
+              <p className="text-gray-400 mb-6">
+                Dataset:{" "}
+                <span className="text-white font-semibold">
+                  {recordingConfig.dataset_repo_id}
+                </span>
+              </p>
+              <div className="flex flex-col sm:flex-row gap-4 justify-center">
+                <Button
+                  onClick={() => navigate("/training")}
+                  className="bg-purple-500 hover:bg-purple-600 text-white font-semibold py-3 px-6 text-lg"
+                >
+                  <GraduationCap className="w-5 h-5 mr-2" />
+                  Start Training
+                </Button>
+                <Button
+                  onClick={() => navigate("/")}
+                  variant="outline"
+                  className="bg-transparent border-gray-600 text-gray-300 hover:bg-gray-800 hover:text-white py-3 px-6 text-lg"
+                >
+                  Return to Home
+                </Button>
+              </div>
+            </div>
+          )}
+
+          {/* Instructions */}
+          <div className="mt-6 p-4 bg-gray-800 rounded-lg">
+            <h3 className="font-semibold mb-2">
+              {currentPhase === "recording"
+                ? "Episode Recording Instructions:"
+                : currentPhase === "resetting"
+                ? "Environment Reset Instructions:"
+                : "Session Instructions:"}
+            </h3>
+            {currentPhase === "recording" && (
+              <ul className="text-sm text-gray-400 space-y-1">
+                <li>
+                  • <strong>End Episode:</strong> Complete current episode and
+                  enter reset phase (Right Arrow)
+                </li>
+                <li>
+                  • <strong>Re-record Episode:</strong> Restart current episode
+                  after reset phase (Left Arrow)
+                </li>
+                <li>
+                  • <strong>Auto-end:</strong> Episode ends automatically after{" "}
+                  {formatTime(phaseTimeLimit)}
+                </li>
+                <li>
+                  • <strong>Stop Recording:</strong> End entire session (ESC
+                  key)
+                </li>
+              </ul>
+            )}
+            {currentPhase === "resetting" && (
+              <ul className="text-sm text-gray-400 space-y-1">
+                <li>
+                  • <strong>Continue to Next Phase:</strong> Skip reset phase
+                  and continue (Right Arrow)
+                </li>
+                <li>
+                  • <strong>Auto-continue:</strong> Automatically continues
+                  after {formatTime(phaseTimeLimit)}
+                </li>
+                <li>
+                  • <strong>Reset Phase:</strong> Use this time to prepare your
+                  environment for the next episode
+                </li>
+                <li>
+                  • <strong>Stop Recording:</strong> End entire session (ESC
+                  key)
+                </li>
+              </ul>
+            )}
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default Recording;
diff --git a/src/pages/Teleoperation.tsx b/src/pages/Teleoperation.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7548bc5aa3a434fb0793856aeedeaab8c7146057
--- /dev/null
+++ b/src/pages/Teleoperation.tsx
@@ -0,0 +1,62 @@
+import React from "react";
+import { useNavigate } from "react-router-dom";
+import VisualizerPanel from "@/components/control/VisualizerPanel";
+import { useToast } from "@/hooks/use-toast";
+
+const TeleoperationPage = () => {
+  const navigate = useNavigate();
+  const { toast } = useToast();
+
+  const handleGoBack = async () => {
+    try {
+      // Stop the teleoperation process before navigating back
+      console.log("🛑 Stopping teleoperation...");
+      const response = await fetch("http://localhost:8000/stop-teleoperation", {
+        method: "POST",
+      });
+
+      if (response.ok) {
+        const result = await response.json();
+        console.log("✅ Teleoperation stopped:", result.message);
+        toast({
+          title: "Teleoperation Stopped",
+          description:
+            result.message ||
+            "Robot teleoperation has been stopped successfully.",
+        });
+      } else {
+        const errorText = await response.text();
+        console.warn(
+          "⚠️ Failed to stop teleoperation:",
+          response.status,
+          errorText
+        );
+        toast({
+          title: "Warning",
+          description: `Failed to stop teleoperation properly. Status: ${response.status}`,
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      console.error("❌ Error stopping teleoperation:", error);
+      toast({
+        title: "Error",
+        description: "Failed to communicate with the robot server.",
+        variant: "destructive",
+      });
+    } finally {
+      // Navigate back regardless of the result
+      navigate("/");
+    }
+  };
+
+  return (
+    <div className="min-h-screen bg-black flex items-center justify-center p-2 sm:p-4">
+      <div className="w-full h-[95vh] flex">
+        <VisualizerPanel onGoBack={handleGoBack} className="lg:w-full" />
+      </div>
+    </div>
+  );
+};
+
+export default TeleoperationPage;
diff --git a/src/pages/Training.tsx b/src/pages/Training.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..279d24bc0dc2500dd2dffe106bc003308fb27a74
--- /dev/null
+++ b/src/pages/Training.tsx
@@ -0,0 +1,1245 @@
+import React, { useState, useEffect, useRef } from "react";
+import { useNavigate } from "react-router-dom";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+import { Switch } from "@/components/ui/switch";
+import {
+  Select,
+  SelectContent,
+  SelectItem,
+  SelectTrigger,
+  SelectValue,
+} from "@/components/ui/select";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Progress } from "@/components/ui/progress";
+import { useToast } from "@/components/ui/use-toast";
+import {
+  Play,
+  Square,
+  ArrowLeft,
+  Settings,
+  Activity,
+  FileText,
+  Cpu,
+  Database,
+  TrendingUp,
+  Clock,
+  AlertCircle,
+  CheckCircle,
+  Loader2,
+} from "lucide-react";
+
+interface TrainingConfig {
+  // Dataset configuration - exact matches from CLI
+  dataset_repo_id: string; // --dataset.repo_id
+  dataset_revision?: string; // --dataset.revision
+  dataset_root?: string; // --dataset.root
+  dataset_episodes?: number[]; // --dataset.episodes
+
+  // Policy configuration - only type is configurable at top level
+  policy_type: string; // --policy.type (act, diffusion, pi0, smolvla, tdmpc, vqbet, pi0fast, sac, reward_classifier)
+
+  // Core training parameters - exact matches from CLI
+  steps: number; // --steps
+  batch_size: number; // --batch_size
+  seed?: number; // --seed
+  num_workers: number; // --num_workers
+
+  // Logging and checkpointing - exact matches from CLI
+  log_freq: number; // --log_freq
+  save_freq: number; // --save_freq
+  eval_freq: number; // --eval_freq
+  save_checkpoint: boolean; // --save_checkpoint
+
+  // Output configuration - exact matches from CLI
+  output_dir: string; // --output_dir
+  resume: boolean; // --resume
+  job_name?: string; // --job_name
+
+  // Weights & Biases - exact matches from CLI
+  wandb_enable: boolean; // --wandb.enable
+  wandb_project?: string; // --wandb.project
+  wandb_entity?: string; // --wandb.entity
+  wandb_notes?: string; // --wandb.notes
+  wandb_run_id?: string; // --wandb.run_id
+  wandb_mode?: string; // --wandb.mode (online, offline, disabled)
+  wandb_disable_artifact: boolean; // --wandb.disable_artifact
+
+  // Environment and evaluation - exact matches from CLI
+  env_type?: string; // --env.type (aloha, pusht, xarm, gym_manipulator, hil)
+  env_task?: string; // --env.task
+  eval_n_episodes: number; // --eval.n_episodes
+  eval_batch_size: number; // --eval.batch_size
+  eval_use_async_envs: boolean; // --eval.use_async_envs
+
+  // Policy-specific parameters that are commonly used
+  policy_device?: string; // --policy.device
+  policy_use_amp: boolean; // --policy.use_amp
+
+  // Optimizer parameters - exact matches from CLI
+  optimizer_type?: string; // --optimizer.type (adam, adamw, sgd, multi_adam)
+  optimizer_lr?: number; // --optimizer.lr (will use policy default if not set)
+  optimizer_weight_decay?: number; // --optimizer.weight_decay
+  optimizer_grad_clip_norm?: number; // --optimizer.grad_clip_norm
+
+  // Advanced configuration
+  use_policy_training_preset: boolean; // --use_policy_training_preset
+  config_path?: string; // --config_path
+}
+
+interface TrainingStatus {
+  training_active: boolean;
+  current_step: number;
+  total_steps: number;
+  current_loss?: number;
+  current_lr?: number;
+  grad_norm?: number;
+  epoch_time?: number;
+  eta_seconds?: number;
+  available_controls: {
+    stop_training: boolean;
+    pause_training: boolean;
+    resume_training: boolean;
+  };
+}
+
+interface LogEntry {
+  timestamp: number;
+  message: string;
+}
+
+const Training = () => {
+  const navigate = useNavigate();
+  const { toast } = useToast();
+  const logContainerRef = useRef<HTMLDivElement>(null);
+
+  const [trainingConfig, setTrainingConfig] = useState<TrainingConfig>({
+    dataset_repo_id: "",
+    policy_type: "act",
+    steps: 10000,
+    batch_size: 8,
+    seed: 1000,
+    num_workers: 4,
+    log_freq: 250,
+    save_freq: 1000,
+    eval_freq: 0,
+    save_checkpoint: true,
+    output_dir: "outputs/train",
+    resume: false,
+    wandb_enable: false,
+    wandb_mode: "online",
+    wandb_disable_artifact: false,
+    eval_n_episodes: 10,
+    eval_batch_size: 50,
+    eval_use_async_envs: false,
+    policy_device: "cuda",
+    policy_use_amp: false,
+    optimizer_type: "adam",
+    use_policy_training_preset: true,
+  });
+
+  const [trainingStatus, setTrainingStatus] = useState<TrainingStatus>({
+    training_active: false,
+    current_step: 0,
+    total_steps: 0,
+    available_controls: {
+      stop_training: false,
+      pause_training: false,
+      resume_training: false,
+    },
+  });
+
+  const [logs, setLogs] = useState<LogEntry[]>([]);
+  const [isStartingTraining, setIsStartingTraining] = useState(false);
+  const [activeTab, setActiveTab] = useState<"config" | "monitoring">("config");
+
+  // Poll for training status and logs
+  useEffect(() => {
+    const pollInterval = setInterval(async () => {
+      if (trainingStatus.training_active) {
+        try {
+          // Get status
+          const statusResponse = await fetch("/training-status");
+          if (statusResponse.ok) {
+            const status = await statusResponse.json();
+            setTrainingStatus(status);
+          }
+
+          // Get logs
+          const logsResponse = await fetch("/training-logs");
+          if (logsResponse.ok) {
+            const logsData = await logsResponse.json();
+            if (logsData.logs && logsData.logs.length > 0) {
+              setLogs((prevLogs) => [...prevLogs, ...logsData.logs]);
+            }
+          }
+        } catch (error) {
+          console.error("Error polling training status:", error);
+        }
+      }
+    }, 1000);
+
+    return () => clearInterval(pollInterval);
+  }, [trainingStatus.training_active]);
+
+  // Auto-scroll logs
+  useEffect(() => {
+    if (logContainerRef.current) {
+      logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight;
+    }
+  }, [logs]);
+
+  const handleStartTraining = async () => {
+    if (!trainingConfig.dataset_repo_id.trim()) {
+      toast({
+        title: "Error",
+        description: "Dataset repository ID is required",
+        variant: "destructive",
+      });
+      return;
+    }
+
+    setIsStartingTraining(true);
+    try {
+      const response = await fetch("/start-training", {
+        method: "POST",
+        headers: {
+          "Content-Type": "application/json",
+        },
+        body: JSON.stringify(trainingConfig),
+      });
+
+      if (response.ok) {
+        const result = await response.json();
+        if (result.success) {
+          toast({
+            title: "Training Started",
+            description: "Training session has been started successfully",
+          });
+          setActiveTab("monitoring");
+          setLogs([]);
+        } else {
+          toast({
+            title: "Error",
+            description: result.message || "Failed to start training",
+            variant: "destructive",
+          });
+        }
+      } else {
+        toast({
+          title: "Error",
+          description: "Failed to start training",
+          variant: "destructive",
+        });
+      }
+    } catch (error) {
+      console.error("Error starting training:", error);
+      toast({
+        title: "Error",
+        description: "Failed to start training",
+        variant: "destructive",
+      });
+    } finally {
+      setIsStartingTraining(false);
+    }
+  };
+
+  const handleStopTraining = async () => {
+    try {
+      const response = await fetch("/stop-training", {
+        method: "POST",
+      });
+
+      if (response.ok) {
+        const result = await response.json();
+        if (result.success) {
+          toast({
+            title: "Training Stopped",
+            description: "Training session has been stopped",
+          });
+        } else {
+          toast({
+            title: "Error",
+            description: result.message || "Failed to stop training",
+            variant: "destructive",
+          });
+        }
+      }
+    } catch (error) {
+      console.error("Error stopping training:", error);
+      toast({
+        title: "Error",
+        description: "Failed to stop training",
+        variant: "destructive",
+      });
+    }
+  };
+
+  const updateConfig = <T extends keyof TrainingConfig>(
+    key: T,
+    value: TrainingConfig[T]
+  ) => {
+    setTrainingConfig((prev) => ({ ...prev, [key]: value }));
+  };
+
+  const formatTime = (seconds: number): string => {
+    const hours = Math.floor(seconds / 3600);
+    const minutes = Math.floor((seconds % 3600) / 60);
+    const secs = Math.floor(seconds % 60);
+    return `${hours.toString().padStart(2, "0")}:${minutes
+      .toString()
+      .padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
+  };
+
+  const getProgressPercentage = () => {
+    if (trainingStatus.total_steps === 0) return 0;
+    return (trainingStatus.current_step / trainingStatus.total_steps) * 100;
+  };
+
+  const getStatusColor = () => {
+    if (trainingStatus.training_active) return "text-green-400";
+    return "text-gray-400";
+  };
+
+  const getStatusText = () => {
+    if (trainingStatus.training_active) return "Training Active";
+    return "Ready to Train";
+  };
+
+  return (
+    <div className="min-h-screen bg-gray-950 text-white p-4">
+      <div className="max-w-7xl mx-auto">
+        {/* Header */}
+        <div className="flex items-center justify-between mb-8">
+          <div className="flex items-center gap-4">
+            <Button
+              variant="ghost"
+              size="sm"
+              onClick={() => navigate("/")}
+              className="text-gray-400 hover:text-white"
+            >
+              <ArrowLeft className="w-4 h-4 mr-2" />
+              Back to Home
+            </Button>
+            <h1 className="text-4xl font-bold text-white">
+              Training Dashboard
+            </h1>
+          </div>
+
+          <div className="flex items-center gap-3">
+            <div
+              className={`w-2 h-2 rounded-full ${
+                trainingStatus.training_active ? "bg-green-400" : "bg-gray-400"
+              }`}
+            ></div>
+            <span className={`font-semibold ${getStatusColor()}`}>
+              {getStatusText()}
+            </span>
+          </div>
+        </div>
+
+        {/* Tab Navigation */}
+        <div className="flex gap-2 mb-6">
+          <Button
+            variant={activeTab === "config" ? "default" : "ghost"}
+            onClick={() => setActiveTab("config")}
+            className="flex items-center gap-2"
+          >
+            <Settings className="w-4 h-4" />
+            Configuration
+          </Button>
+          <Button
+            variant={activeTab === "monitoring" ? "default" : "ghost"}
+            onClick={() => setActiveTab("monitoring")}
+            className="flex items-center gap-2"
+          >
+            <Activity className="w-4 h-4" />
+            Monitoring
+          </Button>
+        </div>
+
+        {/* Configuration Tab */}
+        {activeTab === "config" && (
+          <div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
+            {/* Dataset Configuration */}
+            <Card className="bg-gray-900 border-gray-700">
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2 text-white">
+                  <Database className="w-5 h-5" />
+                  Dataset Configuration
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                <div>
+                  <Label htmlFor="dataset_repo_id" className="text-gray-300">
+                    Dataset Repository ID *
+                  </Label>
+                  <Input
+                    id="dataset_repo_id"
+                    value={trainingConfig.dataset_repo_id}
+                    onChange={(e) =>
+                      updateConfig("dataset_repo_id", e.target.value)
+                    }
+                    placeholder="e.g., your-username/your-dataset"
+                    className="bg-gray-800 border-gray-600 text-white"
+                  />
+                  <p className="text-xs text-gray-500 mt-1">
+                    HuggingFace Hub dataset repository ID
+                  </p>
+                </div>
+
+                <div>
+                  <Label htmlFor="dataset_revision" className="text-gray-300">
+                    Dataset Revision (optional)
+                  </Label>
+                  <Input
+                    id="dataset_revision"
+                    value={trainingConfig.dataset_revision || ""}
+                    onChange={(e) =>
+                      updateConfig(
+                        "dataset_revision",
+                        e.target.value || undefined
+                      )
+                    }
+                    placeholder="main"
+                    className="bg-gray-800 border-gray-600 text-white"
+                  />
+                  <p className="text-xs text-gray-500 mt-1">
+                    Git revision (branch, tag, or commit hash)
+                  </p>
+                </div>
+
+                <div>
+                  <Label htmlFor="dataset_root" className="text-gray-300">
+                    Dataset Root Directory (optional)
+                  </Label>
+                  <Input
+                    id="dataset_root"
+                    value={trainingConfig.dataset_root || ""}
+                    onChange={(e) =>
+                      updateConfig("dataset_root", e.target.value || undefined)
+                    }
+                    placeholder="./data"
+                    className="bg-gray-800 border-gray-600 text-white"
+                  />
+                </div>
+              </CardContent>
+            </Card>
+
+            {/* Policy Configuration */}
+            <Card className="bg-gray-900 border-gray-700">
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2 text-white">
+                  <Cpu className="w-5 h-5" />
+                  Policy Configuration
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                <div>
+                  <Label htmlFor="policy_type" className="text-gray-300">
+                    Policy Type
+                  </Label>
+                  <Select
+                    value={trainingConfig.policy_type}
+                    onValueChange={(value) =>
+                      updateConfig("policy_type", value)
+                    }
+                  >
+                    <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
+                      <SelectValue />
+                    </SelectTrigger>
+                    <SelectContent className="bg-gray-800 border-gray-600">
+                      <SelectItem value="act">
+                        ACT (Action Chunking Transformer)
+                      </SelectItem>
+                      <SelectItem value="diffusion">
+                        Diffusion Policy
+                      </SelectItem>
+                      <SelectItem value="pi0">PI0</SelectItem>
+                      <SelectItem value="smolvla">SmolVLA</SelectItem>
+                      <SelectItem value="tdmpc">TD-MPC</SelectItem>
+                      <SelectItem value="vqbet">VQ-BeT</SelectItem>
+                      <SelectItem value="pi0fast">PI0 Fast</SelectItem>
+                      <SelectItem value="sac">SAC</SelectItem>
+                      <SelectItem value="reward_classifier">
+                        Reward Classifier
+                      </SelectItem>
+                    </SelectContent>
+                  </Select>
+                </div>
+
+                <div>
+                  <Label htmlFor="policy_device" className="text-gray-300">
+                    Device
+                  </Label>
+                  <Select
+                    value={trainingConfig.policy_device || "cuda"}
+                    onValueChange={(value) =>
+                      updateConfig("policy_device", value)
+                    }
+                  >
+                    <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
+                      <SelectValue />
+                    </SelectTrigger>
+                    <SelectContent className="bg-gray-800 border-gray-600">
+                      <SelectItem value="cuda">CUDA (GPU)</SelectItem>
+                      <SelectItem value="cpu">CPU</SelectItem>
+                      <SelectItem value="mps">MPS (Apple Silicon)</SelectItem>
+                    </SelectContent>
+                  </Select>
+                </div>
+
+                <div className="flex items-center space-x-3">
+                  <Switch
+                    id="policy_use_amp"
+                    checked={trainingConfig.policy_use_amp}
+                    onCheckedChange={(checked) =>
+                      updateConfig("policy_use_amp", checked)
+                    }
+                  />
+                  <Label htmlFor="policy_use_amp" className="text-gray-300">
+                    Use Automatic Mixed Precision (AMP)
+                  </Label>
+                </div>
+              </CardContent>
+            </Card>
+
+            {/* Training Parameters */}
+            <Card className="bg-gray-900 border-gray-700">
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2 text-white">
+                  <TrendingUp className="w-5 h-5" />
+                  Training Parameters
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                <div className="grid grid-cols-2 gap-4">
+                  <div>
+                    <Label htmlFor="steps" className="text-gray-300">
+                      Training Steps
+                    </Label>
+                    <Input
+                      id="steps"
+                      type="number"
+                      value={trainingConfig.steps}
+                      onChange={(e) =>
+                        updateConfig("steps", parseInt(e.target.value))
+                      }
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+
+                  <div>
+                    <Label htmlFor="batch_size" className="text-gray-300">
+                      Batch Size
+                    </Label>
+                    <Input
+                      id="batch_size"
+                      type="number"
+                      value={trainingConfig.batch_size}
+                      onChange={(e) =>
+                        updateConfig("batch_size", parseInt(e.target.value))
+                      }
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+                </div>
+
+                <div className="grid grid-cols-2 gap-4">
+                  <div>
+                    <Label htmlFor="seed" className="text-gray-300">
+                      Random Seed
+                    </Label>
+                    <Input
+                      id="seed"
+                      type="number"
+                      value={trainingConfig.seed || ""}
+                      onChange={(e) =>
+                        updateConfig(
+                          "seed",
+                          e.target.value ? parseInt(e.target.value) : undefined
+                        )
+                      }
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+
+                  <div>
+                    <Label htmlFor="num_workers" className="text-gray-300">
+                      Number of Workers
+                    </Label>
+                    <Input
+                      id="num_workers"
+                      type="number"
+                      value={trainingConfig.num_workers}
+                      onChange={(e) =>
+                        updateConfig("num_workers", parseInt(e.target.value))
+                      }
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+                </div>
+              </CardContent>
+            </Card>
+
+            {/* Optimizer Configuration */}
+            <Card className="bg-gray-900 border-gray-700">
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2 text-white">
+                  <Settings className="w-5 h-5" />
+                  Optimizer Configuration
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                <div>
+                  <Label htmlFor="optimizer_type" className="text-gray-300">
+                    Optimizer Type
+                  </Label>
+                  <Select
+                    value={trainingConfig.optimizer_type || "adam"}
+                    onValueChange={(value) =>
+                      updateConfig("optimizer_type", value)
+                    }
+                  >
+                    <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
+                      <SelectValue />
+                    </SelectTrigger>
+                    <SelectContent className="bg-gray-800 border-gray-600">
+                      <SelectItem value="adam">Adam</SelectItem>
+                      <SelectItem value="adamw">AdamW</SelectItem>
+                      <SelectItem value="sgd">SGD</SelectItem>
+                      <SelectItem value="multi_adam">Multi Adam</SelectItem>
+                    </SelectContent>
+                  </Select>
+                </div>
+
+                <div className="grid grid-cols-3 gap-4">
+                  <div>
+                    <Label htmlFor="optimizer_lr" className="text-gray-300">
+                      Learning Rate
+                    </Label>
+                    <Input
+                      id="optimizer_lr"
+                      type="number"
+                      step="0.0001"
+                      value={trainingConfig.optimizer_lr || ""}
+                      onChange={(e) =>
+                        updateConfig(
+                          "optimizer_lr",
+                          e.target.value
+                            ? parseFloat(e.target.value)
+                            : undefined
+                        )
+                      }
+                      placeholder="Use policy default"
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+
+                  <div>
+                    <Label
+                      htmlFor="optimizer_weight_decay"
+                      className="text-gray-300"
+                    >
+                      Weight Decay
+                    </Label>
+                    <Input
+                      id="optimizer_weight_decay"
+                      type="number"
+                      step="0.0001"
+                      value={trainingConfig.optimizer_weight_decay || ""}
+                      onChange={(e) =>
+                        updateConfig(
+                          "optimizer_weight_decay",
+                          e.target.value
+                            ? parseFloat(e.target.value)
+                            : undefined
+                        )
+                      }
+                      placeholder="Use policy default"
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+
+                  <div>
+                    <Label
+                      htmlFor="optimizer_grad_clip_norm"
+                      className="text-gray-300"
+                    >
+                      Gradient Clipping
+                    </Label>
+                    <Input
+                      id="optimizer_grad_clip_norm"
+                      type="number"
+                      value={trainingConfig.optimizer_grad_clip_norm || ""}
+                      onChange={(e) =>
+                        updateConfig(
+                          "optimizer_grad_clip_norm",
+                          e.target.value
+                            ? parseFloat(e.target.value)
+                            : undefined
+                        )
+                      }
+                      placeholder="Use policy default"
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+                </div>
+              </CardContent>
+            </Card>
+
+            {/* Logging Configuration */}
+            <Card className="bg-gray-900 border-gray-700">
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2 text-white">
+                  <FileText className="w-5 h-5" />
+                  Logging & Checkpointing
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                <div className="grid grid-cols-3 gap-4">
+                  <div>
+                    <Label htmlFor="log_freq" className="text-gray-300">
+                      Log Frequency
+                    </Label>
+                    <Input
+                      id="log_freq"
+                      type="number"
+                      value={trainingConfig.log_freq}
+                      onChange={(e) =>
+                        updateConfig("log_freq", parseInt(e.target.value))
+                      }
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+
+                  <div>
+                    <Label htmlFor="save_freq" className="text-gray-300">
+                      Save Frequency
+                    </Label>
+                    <Input
+                      id="save_freq"
+                      type="number"
+                      value={trainingConfig.save_freq}
+                      onChange={(e) =>
+                        updateConfig("save_freq", parseInt(e.target.value))
+                      }
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+
+                  <div>
+                    <Label htmlFor="eval_freq" className="text-gray-300">
+                      Eval Frequency
+                    </Label>
+                    <Input
+                      id="eval_freq"
+                      type="number"
+                      value={trainingConfig.eval_freq}
+                      onChange={(e) =>
+                        updateConfig("eval_freq", parseInt(e.target.value))
+                      }
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+                </div>
+
+                <div>
+                  <Label htmlFor="output_dir" className="text-gray-300">
+                    Output Directory
+                  </Label>
+                  <Input
+                    id="output_dir"
+                    value={trainingConfig.output_dir}
+                    onChange={(e) => updateConfig("output_dir", e.target.value)}
+                    className="bg-gray-800 border-gray-600 text-white"
+                  />
+                </div>
+
+                <div>
+                  <Label htmlFor="job_name" className="text-gray-300">
+                    Job Name (optional)
+                  </Label>
+                  <Input
+                    id="job_name"
+                    value={trainingConfig.job_name || ""}
+                    onChange={(e) =>
+                      updateConfig("job_name", e.target.value || undefined)
+                    }
+                    className="bg-gray-800 border-gray-600 text-white"
+                  />
+                </div>
+
+                <div className="flex items-center space-x-3">
+                  <Switch
+                    id="save_checkpoint"
+                    checked={trainingConfig.save_checkpoint}
+                    onCheckedChange={(checked) =>
+                      updateConfig("save_checkpoint", checked)
+                    }
+                  />
+                  <Label htmlFor="save_checkpoint" className="text-gray-300">
+                    Save Checkpoints
+                  </Label>
+                </div>
+
+                <div className="flex items-center space-x-3">
+                  <Switch
+                    id="resume"
+                    checked={trainingConfig.resume}
+                    onCheckedChange={(checked) =>
+                      updateConfig("resume", checked)
+                    }
+                  />
+                  <Label htmlFor="resume" className="text-gray-300">
+                    Resume from Checkpoint
+                  </Label>
+                </div>
+              </CardContent>
+            </Card>
+
+            {/* Weights & Biases Configuration */}
+            <Card className="bg-gray-900 border-gray-700">
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2 text-white">
+                  <TrendingUp className="w-5 h-5" />
+                  Weights & Biases
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                <div className="flex items-center space-x-3">
+                  <Switch
+                    id="wandb_enable"
+                    checked={trainingConfig.wandb_enable}
+                    onCheckedChange={(checked) =>
+                      updateConfig("wandb_enable", checked)
+                    }
+                  />
+                  <Label htmlFor="wandb_enable" className="text-gray-300">
+                    Enable Weights & Biases Logging
+                  </Label>
+                </div>
+
+                {trainingConfig.wandb_enable && (
+                  <>
+                    <div>
+                      <Label htmlFor="wandb_project" className="text-gray-300">
+                        W&B Project Name
+                      </Label>
+                      <Input
+                        id="wandb_project"
+                        value={trainingConfig.wandb_project || ""}
+                        onChange={(e) =>
+                          updateConfig(
+                            "wandb_project",
+                            e.target.value || undefined
+                          )
+                        }
+                        placeholder="my-robotics-project"
+                        className="bg-gray-800 border-gray-600 text-white"
+                      />
+                    </div>
+
+                    <div>
+                      <Label htmlFor="wandb_entity" className="text-gray-300">
+                        W&B Entity (optional)
+                      </Label>
+                      <Input
+                        id="wandb_entity"
+                        value={trainingConfig.wandb_entity || ""}
+                        onChange={(e) =>
+                          updateConfig(
+                            "wandb_entity",
+                            e.target.value || undefined
+                          )
+                        }
+                        placeholder="your-username"
+                        className="bg-gray-800 border-gray-600 text-white"
+                      />
+                    </div>
+
+                    <div>
+                      <Label htmlFor="wandb_notes" className="text-gray-300">
+                        W&B Notes (optional)
+                      </Label>
+                      <Input
+                        id="wandb_notes"
+                        value={trainingConfig.wandb_notes || ""}
+                        onChange={(e) =>
+                          updateConfig(
+                            "wandb_notes",
+                            e.target.value || undefined
+                          )
+                        }
+                        placeholder="Training run notes..."
+                        className="bg-gray-800 border-gray-600 text-white"
+                      />
+                    </div>
+
+                    <div>
+                      <Label htmlFor="wandb_mode" className="text-gray-300">
+                        W&B Mode
+                      </Label>
+                      <Select
+                        value={trainingConfig.wandb_mode || "online"}
+                        onValueChange={(value) =>
+                          updateConfig("wandb_mode", value)
+                        }
+                      >
+                        <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
+                          <SelectValue />
+                        </SelectTrigger>
+                        <SelectContent className="bg-gray-800 border-gray-600">
+                          <SelectItem value="online">Online</SelectItem>
+                          <SelectItem value="offline">Offline</SelectItem>
+                          <SelectItem value="disabled">Disabled</SelectItem>
+                        </SelectContent>
+                      </Select>
+                    </div>
+
+                    <div className="flex items-center space-x-3">
+                      <Switch
+                        id="wandb_disable_artifact"
+                        checked={trainingConfig.wandb_disable_artifact}
+                        onCheckedChange={(checked) =>
+                          updateConfig("wandb_disable_artifact", checked)
+                        }
+                      />
+                      <Label
+                        htmlFor="wandb_disable_artifact"
+                        className="text-gray-300"
+                      >
+                        Disable Artifacts
+                      </Label>
+                    </div>
+                  </>
+                )}
+              </CardContent>
+            </Card>
+
+            {/* Environment & Evaluation Configuration */}
+            <Card className="bg-gray-900 border-gray-700">
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2 text-white">
+                  <Activity className="w-5 h-5" />
+                  Environment & Evaluation
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                <div>
+                  <Label htmlFor="env_type" className="text-gray-300">
+                    Environment Type (optional)
+                  </Label>
+                  <Select
+                    value={trainingConfig.env_type || "none"}
+                    onValueChange={(value) =>
+                      updateConfig(
+                        "env_type",
+                        value === "none" ? undefined : value
+                      )
+                    }
+                  >
+                    <SelectTrigger className="bg-gray-800 border-gray-600 text-white">
+                      <SelectValue placeholder="Select environment type" />
+                    </SelectTrigger>
+                    <SelectContent className="bg-gray-800 border-gray-600">
+                      <SelectItem value="none">None</SelectItem>
+                      <SelectItem value="aloha">Aloha</SelectItem>
+                      <SelectItem value="pusht">PushT</SelectItem>
+                      <SelectItem value="xarm">XArm</SelectItem>
+                      <SelectItem value="gym_manipulator">
+                        Gym Manipulator
+                      </SelectItem>
+                      <SelectItem value="hil">HIL</SelectItem>
+                    </SelectContent>
+                  </Select>
+                </div>
+
+                <div>
+                  <Label htmlFor="env_task" className="text-gray-300">
+                    Environment Task (optional)
+                  </Label>
+                  <Input
+                    id="env_task"
+                    value={trainingConfig.env_task || ""}
+                    onChange={(e) =>
+                      updateConfig("env_task", e.target.value || undefined)
+                    }
+                    placeholder="e.g., insertion_human"
+                    className="bg-gray-800 border-gray-600 text-white"
+                  />
+                </div>
+
+                <div className="grid grid-cols-2 gap-4">
+                  <div>
+                    <Label htmlFor="eval_n_episodes" className="text-gray-300">
+                      Eval Episodes
+                    </Label>
+                    <Input
+                      id="eval_n_episodes"
+                      type="number"
+                      value={trainingConfig.eval_n_episodes}
+                      onChange={(e) =>
+                        updateConfig(
+                          "eval_n_episodes",
+                          parseInt(e.target.value)
+                        )
+                      }
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+
+                  <div>
+                    <Label htmlFor="eval_batch_size" className="text-gray-300">
+                      Eval Batch Size
+                    </Label>
+                    <Input
+                      id="eval_batch_size"
+                      type="number"
+                      value={trainingConfig.eval_batch_size}
+                      onChange={(e) =>
+                        updateConfig(
+                          "eval_batch_size",
+                          parseInt(e.target.value)
+                        )
+                      }
+                      className="bg-gray-800 border-gray-600 text-white"
+                    />
+                  </div>
+                </div>
+
+                <div className="flex items-center space-x-3">
+                  <Switch
+                    id="eval_use_async_envs"
+                    checked={trainingConfig.eval_use_async_envs}
+                    onCheckedChange={(checked) =>
+                      updateConfig("eval_use_async_envs", checked)
+                    }
+                  />
+                  <Label
+                    htmlFor="eval_use_async_envs"
+                    className="text-gray-300"
+                  >
+                    Use Asynchronous Environments
+                  </Label>
+                </div>
+              </CardContent>
+            </Card>
+
+            {/* Advanced Options */}
+            <Card className="bg-gray-900 border-gray-700">
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2 text-white">
+                  <Settings className="w-5 h-5" />
+                  Advanced Options
+                </CardTitle>
+              </CardHeader>
+              <CardContent className="space-y-4">
+                <div>
+                  <Label htmlFor="config_path" className="text-gray-300">
+                    Config Path (optional)
+                  </Label>
+                  <Input
+                    id="config_path"
+                    value={trainingConfig.config_path || ""}
+                    onChange={(e) =>
+                      updateConfig("config_path", e.target.value || undefined)
+                    }
+                    placeholder="path/to/config.yaml"
+                    className="bg-gray-800 border-gray-600 text-white"
+                  />
+                </div>
+
+                <div className="flex items-center space-x-3">
+                  <Switch
+                    id="use_policy_training_preset"
+                    checked={trainingConfig.use_policy_training_preset}
+                    onCheckedChange={(checked) =>
+                      updateConfig("use_policy_training_preset", checked)
+                    }
+                  />
+                  <Label
+                    htmlFor="use_policy_training_preset"
+                    className="text-gray-300"
+                  >
+                    Use Policy Training Preset
+                  </Label>
+                </div>
+              </CardContent>
+            </Card>
+          </div>
+        )}
+
+        {/* Monitoring Tab */}
+        {activeTab === "monitoring" && (
+          <div className="space-y-6">
+            {/* Training Progress */}
+            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
+              <Card className="bg-gray-900 border-gray-700">
+                <CardContent className="p-6 text-center">
+                  <h3 className="text-sm text-gray-400 mb-2">
+                    Training Progress
+                  </h3>
+                  <div className="text-3xl font-bold text-blue-400 mb-2">
+                    {trainingStatus.current_step} / {trainingStatus.total_steps}
+                  </div>
+                  <Progress value={getProgressPercentage()} className="mb-2" />
+                  <div className="text-sm text-gray-400">
+                    {getProgressPercentage().toFixed(1)}% Complete
+                  </div>
+                </CardContent>
+              </Card>
+
+              <Card className="bg-gray-900 border-gray-700">
+                <CardContent className="p-6 text-center">
+                  <h3 className="text-sm text-gray-400 mb-2">Current Loss</h3>
+                  <div className="text-3xl font-bold text-green-400 mb-2">
+                    {trainingStatus.current_loss?.toFixed(4) || "N/A"}
+                  </div>
+                  <div className="text-sm text-gray-400">Training Loss</div>
+                </CardContent>
+              </Card>
+
+              <Card className="bg-gray-900 border-gray-700">
+                <CardContent className="p-6 text-center">
+                  <h3 className="text-sm text-gray-400 mb-2">Learning Rate</h3>
+                  <div className="text-3xl font-bold text-orange-400 mb-2">
+                    {trainingStatus.current_lr?.toExponential(2) || "N/A"}
+                  </div>
+                  <div className="text-sm text-gray-400">Current LR</div>
+                </CardContent>
+              </Card>
+
+              <Card className="bg-gray-900 border-gray-700">
+                <CardContent className="p-6 text-center">
+                  <h3 className="text-sm text-gray-400 mb-2">ETA</h3>
+                  <div className="text-3xl font-bold text-purple-400 mb-2">
+                    {trainingStatus.eta_seconds
+                      ? formatTime(trainingStatus.eta_seconds)
+                      : "N/A"}
+                  </div>
+                  <div className="text-sm text-gray-400">Estimated Time</div>
+                </CardContent>
+              </Card>
+
+              <Card className="bg-gray-900 border-gray-700">
+                <CardContent className="p-6 text-center">
+                  <h3 className="text-sm text-gray-400 mb-2">Gradient Norm</h3>
+                  <div className="text-3xl font-bold text-cyan-400 mb-2">
+                    {trainingStatus.grad_norm?.toFixed(3) || "N/A"}
+                  </div>
+                  <div className="text-sm text-gray-400">Gradient Clipping</div>
+                </CardContent>
+              </Card>
+
+              <Card className="bg-gray-900 border-gray-700">
+                <CardContent className="p-6 text-center">
+                  <h3 className="text-sm text-gray-400 mb-2">
+                    Training Status
+                  </h3>
+                  <div className="text-2xl font-bold text-yellow-400 mb-2">
+                    {trainingStatus.training_active ? "Active" : "Stopped"}
+                  </div>
+                  <div className="text-sm text-gray-400">Current State</div>
+                </CardContent>
+              </Card>
+
+              <Card className="bg-gray-900 border-gray-700">
+                <CardContent className="p-6 text-center">
+                  <h3 className="text-sm text-gray-400 mb-2">Dataset</h3>
+                  <div className="text-lg font-bold text-pink-400 mb-2 truncate">
+                    {trainingConfig.dataset_repo_id || "Not Set"}
+                  </div>
+                  <div className="text-sm text-gray-400">Repository ID</div>
+                </CardContent>
+              </Card>
+
+              <Card className="bg-gray-900 border-gray-700">
+                <CardContent className="p-6 text-center">
+                  <h3 className="text-sm text-gray-400 mb-2">Policy</h3>
+                  <div className="text-lg font-bold text-indigo-400 mb-2 uppercase">
+                    {trainingConfig.policy_type}
+                  </div>
+                  <div className="text-sm text-gray-400">Model Type</div>
+                </CardContent>
+              </Card>
+            </div>
+
+            {/* Training Logs */}
+            <Card className="bg-gray-900 border-gray-700">
+              <CardHeader>
+                <CardTitle className="flex items-center gap-2 text-white">
+                  <FileText className="w-5 h-5" />
+                  Training Logs
+                </CardTitle>
+              </CardHeader>
+              <CardContent>
+                <div
+                  ref={logContainerRef}
+                  className="bg-gray-950 rounded-lg p-4 h-96 overflow-y-auto font-mono text-sm"
+                >
+                  {logs.length === 0 ? (
+                    <div className="text-gray-500 text-center py-8">
+                      No training logs yet. Start training to see output.
+                    </div>
+                  ) : (
+                    logs.map((log, index) => (
+                      <div key={index} className="mb-1">
+                        <span className="text-gray-500">
+                          {new Date(log.timestamp * 1000).toLocaleTimeString()}
+                        </span>
+                        <span className="ml-2 text-gray-300">
+                          {log.message}
+                        </span>
+                      </div>
+                    ))
+                  )}
+                </div>
+              </CardContent>
+            </Card>
+          </div>
+        )}
+
+        {/* Control Buttons */}
+        <div className="fixed bottom-6 right-6 flex gap-3">
+          {!trainingStatus.training_active ? (
+            <Button
+              onClick={handleStartTraining}
+              disabled={
+                isStartingTraining || !trainingConfig.dataset_repo_id.trim()
+              }
+              size="lg"
+              className="bg-green-500 hover:bg-green-600 text-white font-semibold px-8 py-4 text-lg shadow-lg"
+            >
+              {isStartingTraining ? (
+                <>
+                  <Loader2 className="w-5 h-5 mr-2 animate-spin" />
+                  Starting...
+                </>
+              ) : (
+                <>
+                  <Play className="w-5 h-5 mr-2" />
+                  Start Training
+                </>
+              )}
+            </Button>
+          ) : (
+            <Button
+              onClick={handleStopTraining}
+              disabled={!trainingStatus.available_controls.stop_training}
+              size="lg"
+              className="bg-red-500 hover:bg-red-600 text-white font-semibold px-8 py-4 text-lg shadow-lg"
+            >
+              <Square className="w-5 h-5 mr-2" />
+              Stop Training
+            </Button>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default Training;
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..11f02fe2a0061d6e6e1f271b21da95423b448b32
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+/// <reference types="vite/client" />
diff --git a/tailwind.config.ts b/tailwind.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8706086e12b025e554ac5e9b4c7b07022008921b
--- /dev/null
+++ b/tailwind.config.ts
@@ -0,0 +1,96 @@
+import type { Config } from "tailwindcss";
+
+export default {
+	darkMode: ["class"],
+	content: [
+		"./pages/**/*.{ts,tsx}",
+		"./components/**/*.{ts,tsx}",
+		"./app/**/*.{ts,tsx}",
+		"./src/**/*.{ts,tsx}",
+	],
+	prefix: "",
+	theme: {
+		container: {
+			center: true,
+			padding: '2rem',
+			screens: {
+				'2xl': '1400px'
+			}
+		},
+		extend: {
+			colors: {
+				border: 'hsl(var(--border))',
+				input: 'hsl(var(--input))',
+				ring: 'hsl(var(--ring))',
+				background: 'hsl(var(--background))',
+				foreground: 'hsl(var(--foreground))',
+				primary: {
+					DEFAULT: 'hsl(var(--primary))',
+					foreground: 'hsl(var(--primary-foreground))'
+				},
+				secondary: {
+					DEFAULT: 'hsl(var(--secondary))',
+					foreground: 'hsl(var(--secondary-foreground))'
+				},
+				destructive: {
+					DEFAULT: 'hsl(var(--destructive))',
+					foreground: 'hsl(var(--destructive-foreground))'
+				},
+				muted: {
+					DEFAULT: 'hsl(var(--muted))',
+					foreground: 'hsl(var(--muted-foreground))'
+				},
+				accent: {
+					DEFAULT: 'hsl(var(--accent))',
+					foreground: 'hsl(var(--accent-foreground))'
+				},
+				popover: {
+					DEFAULT: 'hsl(var(--popover))',
+					foreground: 'hsl(var(--popover-foreground))'
+				},
+				card: {
+					DEFAULT: 'hsl(var(--card))',
+					foreground: 'hsl(var(--card-foreground))'
+				},
+				sidebar: {
+					DEFAULT: 'hsl(var(--sidebar-background))',
+					foreground: 'hsl(var(--sidebar-foreground))',
+					primary: 'hsl(var(--sidebar-primary))',
+					'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
+					accent: 'hsl(var(--sidebar-accent))',
+					'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
+					border: 'hsl(var(--sidebar-border))',
+					ring: 'hsl(var(--sidebar-ring))'
+				}
+			},
+			borderRadius: {
+				lg: 'var(--radius)',
+				md: 'calc(var(--radius) - 2px)',
+				sm: 'calc(var(--radius) - 4px)'
+			},
+			keyframes: {
+				'accordion-down': {
+					from: {
+						height: '0'
+					},
+					to: {
+						height: 'var(--radix-accordion-content-height)'
+					}
+				},
+				'accordion-up': {
+					from: {
+						height: 'var(--radix-accordion-content-height)'
+					},
+					to: {
+						height: '0'
+					}
+				}
+			},
+			animation: {
+				'accordion-down': 'accordion-down 0.2s ease-out',
+				'accordion-up': 'accordion-up 0.2s ease-out'
+			}
+		}
+	},
+	plugins: [require("tailwindcss-animate")],
+} satisfies Config;
diff --git a/tsconfig.app.json b/tsconfig.app.json
new file mode 100644
index 0000000000000000000000000000000000000000..0b0e43e6bc8079ff97d899dddb025d5b09f20ee2
--- /dev/null
+++ b/tsconfig.app.json
@@ -0,0 +1,30 @@
+{
+  "compilerOptions": {
+    "target": "ES2020",
+    "useDefineForClassFields": true,
+    "lib": ["ES2020", "DOM", "DOM.Iterable"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+    "jsx": "react-jsx",
+
+    /* Linting */
+    "strict": false,
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "noImplicitAny": false,
+    "noFallthroughCasesInSwitch": false,
+
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    }
+  },
+  "include": ["src"]
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..129b1a30fdbe84d753a2526f13a20e0857f420ba
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,19 @@
+{
+  "files": [],
+  "references": [
+    { "path": "./tsconfig.app.json" },
+    { "path": "./tsconfig.node.json" }
+  ],
+  "compilerOptions": {
+    "baseUrl": ".",
+    "paths": {
+      "@/*": ["./src/*"]
+    },
+    "noImplicitAny": false,
+    "noUnusedParameters": false,
+    "skipLibCheck": true,
+    "allowJs": true,
+    "noUnusedLocals": false,
+    "strictNullChecks": false
+  }
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000000000000000000000000000000000000..3133162c20350e1741b94b8d2ff77b811bec8363
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,22 @@
+{
+  "compilerOptions": {
+    "target": "ES2022",
+    "lib": ["ES2023"],
+    "module": "ESNext",
+    "skipLibCheck": true,
+
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "isolatedModules": true,
+    "moduleDetection": "force",
+    "noEmit": true,
+
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": false,
+    "noUnusedParameters": false,
+    "noFallthroughCasesInSwitch": true
+  },
+  "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..28d31ecbe5c514f36e019ab74fc4301a129599c9
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,47 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react-swc";
+import path from "path";
+import { componentTagger } from "lovable-tagger";
+
+// https://vitejs.dev/config/
+export default defineConfig(({ mode }) => ({
+  server: {
+    host: "::",
+    port: 8080,
+    proxy: {
+      "/start-training": "http://localhost:8000",
+      "/stop-training": "http://localhost:8000",
+      "/training-status": "http://localhost:8000",
+      "/training-logs": "http://localhost:8000",
+      "/start-recording": "http://localhost:8000",
+      "/stop-recording": "http://localhost:8000",
+      "/recording-status": "http://localhost:8000",
+      "/recording-exit-early": "http://localhost:8000",
+      "/recording-rerecord-episode": "http://localhost:8000",
+      "/start-calibration": "http://localhost:8000",
+      "/stop-calibration": "http://localhost:8000",
+      "/calibration-status": "http://localhost:8000",
+      "/calibration-input": "http://localhost:8000",
+      "/calibration-debug": "http://localhost:8000",
+      "/calibration-configs": "http://localhost:8000",
+      "/move-arm": "http://localhost:8000",
+      "/stop-teleoperation": "http://localhost:8000",
+      "/teleoperation-status": "http://localhost:8000",
+      "/joint-positions": "http://localhost:8000",
+      "/get-configs": "http://localhost:8000",
+      "/health": "http://localhost:8000",
+      "/ws": {
+        target: "ws://localhost:8000",
+        ws: true,
+      },
+    },
+  },
+  plugins: [react(), mode === "development" && componentTagger()].filter(
+    Boolean
+  ),
+  resolve: {
+    alias: {
+      "@": path.resolve(__dirname, "./src"),
+    },
+  },
+}));