diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index cd1a4fe..3fb6414 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,11 +8,17 @@
"name": "radius-frontend",
"version": "0.1.0",
"dependencies": {
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
"htm": "^3.1.1",
+ "radix-vue": "^1.9.17",
+ "tailwind-merge": "^3.4.0",
"vue": "^3.4.38"
},
"devDependencies": {
+ "@tailwindcss/vite": "^4.1.17",
"@vitejs/plugin-vue": "^5.0.5",
+ "tailwindcss": "^4.1.17",
"vite": "^5.4.8"
}
},
@@ -453,12 +459,135 @@
"node": ">=12"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.3",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
+ "node_modules/@floating-ui/vue": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.9.tgz",
+ "integrity": "sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.4",
+ "@floating-ui/utils": "^0.2.10",
+ "vue-demi": ">=0.13.0"
+ }
+ },
+ "node_modules/@floating-ui/vue/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@internationalized/date": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz",
+ "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@internationalized/number": {
+ "version": "3.6.5",
+ "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz",
+ "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.2.tgz",
@@ -767,6 +896,313 @@
"win32"
]
},
+ "node_modules/@swc/helpers": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.17.tgz",
+ "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.17.tgz",
+ "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.17",
+ "@tailwindcss/oxide-darwin-x64": "4.1.17",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.17",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.17",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.17",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.17",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.17"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz",
+ "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz",
+ "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz",
+ "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz",
+ "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz",
+ "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz",
+ "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz",
+ "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz",
+ "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz",
+ "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz",
+ "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.6.0",
+ "@emnapi/runtime": "^1.6.0",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.0.7",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz",
+ "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz",
+ "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.17.tgz",
+ "integrity": "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.17",
+ "@tailwindcss/oxide": "4.1.17",
+ "tailwindcss": "4.1.17"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.13.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
+ "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/vue-virtual": {
+ "version": "3.13.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.12.tgz",
+ "integrity": "sha512-vhF7kEU9EXWXh+HdAwKJ2m3xaOnTTmgcdXcF2pim8g4GvI7eRrk2YRuV5nUlZnd/NbCIX4/Ja2OZu5EjJL06Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.13.12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "vue": "^2.7.0 || ^3.0.0"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -774,6 +1210,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+ "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
+ "license": "MIT"
+ },
"node_modules/@vitejs/plugin-vue": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
@@ -888,12 +1330,163 @@
"integrity": "sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==",
"license": "MIT"
},
+ "node_modules/@vueuse/core": {
+ "version": "10.11.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz",
+ "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.20",
+ "@vueuse/metadata": "10.11.1",
+ "@vueuse/shared": "10.11.1",
+ "vue-demi": ">=0.14.8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/core/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "10.11.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
+ "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "10.11.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz",
+ "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
+ "license": "MIT",
+ "dependencies": {
+ "vue-demi": ">=0.14.8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared/node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
+ "node_modules/defu": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
+ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.3",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
+ "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -951,6 +1544,12 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT"
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -966,16 +1565,294 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/htm": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz",
"integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==",
"license": "Apache-2.0"
},
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
"node_modules/magic-string": {
- "version": "0.30.19",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
- "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@@ -1033,6 +1910,46 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/radix-vue": {
+ "version": "1.9.17",
+ "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.17.tgz",
+ "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.6.7",
+ "@floating-ui/vue": "^1.1.0",
+ "@internationalized/date": "^3.5.4",
+ "@internationalized/number": "^3.5.3",
+ "@tanstack/vue-virtual": "^3.8.1",
+ "@vueuse/core": "^10.11.0",
+ "@vueuse/shared": "^10.11.0",
+ "aria-hidden": "^1.2.4",
+ "defu": "^6.1.4",
+ "fast-deep-equal": "^3.1.3",
+ "nanoid": "^5.0.7"
+ },
+ "peerDependencies": {
+ "vue": ">= 3.2.0"
+ }
+ },
+ "node_modules/radix-vue/node_modules/nanoid": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
+ "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
"node_modules/rollup": {
"version": "4.52.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.2.tgz",
@@ -1084,6 +2001,43 @@
"node": ">=0.10.0"
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
+ "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.17",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
+ "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
"node_modules/vite": {
"version": "5.4.20",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.20.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 5837c3b..3a820a6 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,11 +9,17 @@
"preview": "vite preview"
},
"dependencies": {
- "vue": "^3.4.38",
- "htm": "^3.1.1"
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "htm": "^3.1.1",
+ "radix-vue": "^1.9.17",
+ "tailwind-merge": "^3.4.0",
+ "vue": "^3.4.38"
},
"devDependencies": {
+ "@tailwindcss/vite": "^4.1.17",
"@vitejs/plugin-vue": "^5.0.5",
+ "tailwindcss": "^4.1.17",
"vite": "^5.4.8"
}
}
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index 066c112..fd23d1d 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -1,180 +1,17 @@
-
-
- RADIUS Nucleo
-
-
- 🏠 Inicio
-
-
-
Estado: {{ statusText }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ importFilename }}
-
-
-
{{ importError }}
-
-
-
-
-
-
-
-
+
+
+
+
+ RADIUS Nucleo
+
+
+
+
Estado: {{ statusText }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Usuarios y Dispositivos
+
+
Página {{ userPage+1 }} / {{ Math.max(1, Math.ceil(filteredUsersAll.length / pageSize)) }}
+
Página {{ devicePage+1 }} / {{ Math.max(1, Math.ceil(devicesAll.length / pageSize)) }}
+
+
+
+
+
+
+
+
+
+ Cargando usuarios…
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ importFilename }}
+
+
+
{{ importError }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app.css b/frontend/src/app.css
new file mode 100644
index 0000000..f247768
--- /dev/null
+++ b/frontend/src/app.css
@@ -0,0 +1,180 @@
+@import "tailwindcss";
+
+/* ========================================
+ VARIABLES CSS BASE (tema dark/light)
+ ======================================== */
+/* Light mode (default when no .dark class) */
+:root {
+ --bg: 245 245 248;
+ --fg: 20 20 22;
+ --muted: 110 110 120;
+ --accent: 18 108 242;
+ --card: 255 255 255 / 0.6;
+ --border: 0 0 0 / 0.08;
+ --glass-blur: 14px;
+
+ /* Scrollbar */
+ --sb-size: 10px;
+ --sb-thumb: rgba(255, 127, 187, 0.65);
+ --sb-thumb-hover: rgba(255, 127, 187, 0.82);
+ --sb-thumb-active: rgba(255, 110, 178, 0.95);
+ --sb-track: rgba(0,0,0,0.06);
+}
+
+/* Dark mode */
+:root.dark {
+ --bg: 15 15 18;
+ --fg: 235 235 240;
+ --muted: 180 180 190;
+ --accent: 80 160 255;
+ --card: 28 28 34 / 0.55;
+ --border: 255 255 255 / 0.12;
+ --sb-thumb: rgba(255, 159, 203, 0.55);
+ --sb-thumb-hover: rgba(255, 159, 203, 0.75);
+ --sb-thumb-active: rgba(255, 127, 187, 0.9);
+ --sb-track: rgba(255,255,255,0.05);
+}
+
+/* ========================================
+ TAILWIND THEME (colores custom)
+ ======================================== */
+@theme {
+ /* Colores base usando CSS variables */
+ --color-background: rgb(var(--bg));
+ --color-foreground: rgb(var(--fg));
+ --color-muted: rgb(var(--muted));
+ --color-accent: rgb(var(--accent));
+ --color-card: rgba(var(--card));
+ --color-border: rgba(var(--border));
+
+ /* Colores pink/magenta (accent del diseño) */
+ --color-pink-50: #fff0f6;
+ --color-pink-100: #ffe0ed;
+ --color-pink-200: #ffcfe4;
+ --color-pink-300: #ff9fc7;
+ --color-pink-400: #ff7fbf;
+ --color-pink-500: #ff4da6;
+ --color-pink-600: #ff2e86;
+ --color-pink-700: #e01a6e;
+ --color-pink-800: #b8155a;
+ --color-pink-900: #99174d;
+
+ /* Border radius custom */
+ --radius-sm: 8px;
+ --radius-md: 10px;
+ --radius-lg: 14px;
+ --radius-xl: 16px;
+
+ /* Animations */
+ --animate-fade-in: fade-in 0.15s ease;
+ --animate-slide-in: slide-in 0.2s ease;
+ --animate-slide-out: slide-out 0.2s ease;
+
+ @keyframes fade-in {
+ from { opacity: 0; }
+ to { opacity: 1; }
+ }
+
+ @keyframes slide-in {
+ from { opacity: 0; transform: translateY(-10px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+
+ @keyframes slide-out {
+ from { opacity: 1; transform: translateY(0); }
+ to { opacity: 0; transform: translateY(-10px); }
+ }
+}
+
+/* ========================================
+ BASE STYLES
+ ======================================== */
+* { box-sizing: border-box; }
+html, body, #app { height: 100%; }
+html, body {
+ margin: 0;
+ padding: 0;
+ background: rgb(var(--bg));
+ color: rgb(var(--fg));
+}
+body {
+ font: 14px/1.45 system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
+}
+button { cursor: pointer; }
+a { color: inherit; }
+
+/* ========================================
+ GLASSMORPHISM UTILITIES
+ ======================================== */
+@utility glass {
+ backdrop-filter: blur(var(--glass-blur));
+ background: rgba(var(--card));
+}
+
+@utility glass-border {
+ border: 1px solid rgba(var(--border));
+}
+
+@utility glass-card {
+ backdrop-filter: blur(var(--glass-blur));
+ background: rgba(var(--card));
+ border: 1px solid rgba(var(--border));
+ border-radius: var(--radius-lg);
+}
+
+@utility glass-panel {
+ backdrop-filter: blur(var(--glass-blur));
+ background: linear-gradient(rgba(var(--card)), rgba(var(--card))) padding-box;
+ border-radius: var(--radius-lg);
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ min-height: 0;
+}
+
+/* ========================================
+ CUSTOM SCROLLBARS
+ ======================================== */
+/* Firefox */
+html, body, .scroll-custom {
+ scrollbar-width: thin;
+ scrollbar-color: var(--sb-thumb) transparent;
+}
+
+/* WebKit */
+html::-webkit-scrollbar,
+body::-webkit-scrollbar,
+.scroll-custom::-webkit-scrollbar {
+ width: var(--sb-size);
+ height: var(--sb-size);
+}
+
+html::-webkit-scrollbar-track,
+body::-webkit-scrollbar-track,
+.scroll-custom::-webkit-scrollbar-track {
+ background: transparent;
+}
+
+html::-webkit-scrollbar-thumb,
+body::-webkit-scrollbar-thumb,
+.scroll-custom::-webkit-scrollbar-thumb {
+ background: var(--sb-thumb);
+ border-radius: 999px;
+ border: 2px solid transparent;
+ background-clip: content-box;
+ box-shadow: 0 0 10px rgba(255, 46, 134, 0.15);
+}
+
+html::-webkit-scrollbar-thumb:hover,
+body::-webkit-scrollbar-thumb:hover,
+.scroll-custom::-webkit-scrollbar-thumb:hover {
+ background: var(--sb-thumb-hover);
+ background-clip: content-box;
+}
+
+html::-webkit-scrollbar-thumb:active,
+body::-webkit-scrollbar-thumb:active,
+.scroll-custom::-webkit-scrollbar-thumb:active {
+ background: var(--sb-thumb-active);
+ background-clip: content-box;
+}
diff --git a/frontend/src/components/DeviceForm.vue b/frontend/src/components/DeviceForm.vue
index 7d272fd..4278f16 100644
--- a/frontend/src/components/DeviceForm.vue
+++ b/frontend/src/components/DeviceForm.vue
@@ -1,35 +1,6 @@
-
-
-
-
+
+
+
diff --git a/frontend/src/components/DispositivoCard.vue b/frontend/src/components/DispositivoCard.vue
index 55dc412..76da7b1 100644
--- a/frontend/src/components/DispositivoCard.vue
+++ b/frontend/src/components/DispositivoCard.vue
@@ -1,29 +1,6 @@
-
-
-
- {{ device.nombre || device.mac }}
- MAC: {{ device.mac }}
- ID: {{ device.id }}
- Conectado
-
-
-
-
-
-
-
Nombre: {{ device.nombre }}
-
Descripción: {{ device.descripcion }}
-
-
-
-
-
+
+
+
+
+ {{ device.nombre || device.mac }}
+ MAC: {{ device.mac }}
+ ID: {{ device.id }}
+
+ Conectado
+
+
+
+
+
+
+
+
+
Nombre: {{ device.nombre }}
+
Descripción: {{ device.descripcion }}
+
+
+
+
+
diff --git a/frontend/src/components/EventCard.js b/frontend/src/components/EventCard.js
deleted file mode 100644
index 43c48d0..0000000
--- a/frontend/src/components/EventCard.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { defineComponent, h } from 'vue';
-import htm from 'htm';
-const html = htm.bind(h);
-
-export default defineComponent({
- name: 'EventCard',
- props: { ev: { type: Object, required: true } },
- setup(props) {
- return () => {
- const a = props.ev.attrs || {};
- return html`
-
- ${props.ev.type}
- ${props.ev.ts}
- ${props.ev.decision ? html`Decision: ${props.ev.decision}` : ''}
- ${props.ev.error ? html`Error` : ''}
-
-
- ${a['User-Name'] || a['User-Name*0'] ? html`User: ${a['User-Name'] || a['User-Name*0']}` : ''}
- ${a['NAS-IP-Address'] ? html` — NAS: ${a['NAS-IP-Address']}` : ''}
- ${a['Calling-Station-Id'] ? html` — STA: ${a['Calling-Station-Id']}` : ''}
-
-
`;
- };
- }
-});
-
diff --git a/frontend/src/components/EventCard.vue b/frontend/src/components/EventCard.vue
new file mode 100644
index 0000000..59513d8
--- /dev/null
+++ b/frontend/src/components/EventCard.vue
@@ -0,0 +1,28 @@
+
+
+
+
+
+ {{ ev.type }}
+ {{ ev.ts }}
+ Decision: {{ ev.decision }}
+ Error
+
+
+
+ User: {{ userName }}
+ — NAS: {{ attrs['NAS-IP-Address'] }}
+ — STA: {{ attrs['Calling-Station-Id'] }}
+
+
+
diff --git a/frontend/src/components/Modal.vue b/frontend/src/components/Modal.vue
index 13f9586..f3e4599 100644
--- a/frontend/src/components/Modal.vue
+++ b/frontend/src/components/Modal.vue
@@ -1,23 +1,36 @@
-
-
-
-
+
+
+
+
diff --git a/frontend/src/components/RawDbViewer.vue b/frontend/src/components/RawDbViewer.vue
index f35e8f2..3f1e2c3 100644
--- a/frontend/src/components/RawDbViewer.vue
+++ b/frontend/src/components/RawDbViewer.vue
@@ -1,59 +1,9 @@
-
-
-
-
-
-
-
-
-
-
{{ offset + 1 }}–{{ Math.min(offset + limit, total) }} / {{ total }}
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
{{ offset + 1 }}–{{ Math.min(offset + limit, total) }} / {{ total }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/Toast.vue b/frontend/src/components/Toast.vue
index de3e0fe..c753a6c 100644
--- a/frontend/src/components/Toast.vue
+++ b/frontend/src/components/Toast.vue
@@ -1,32 +1,6 @@
-
-
-
-
-
-
-
{{ getIcon(toast.type) }}
-
-
{{ toast.title }}
-
{{ toast.message }}
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {{ getIcon(toast.type) }}
+
+
+
+ {{ toast.title }}
+
+
+ {{ toast.message }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/UserCard.vue b/frontend/src/components/UserCard.vue
index 6032b3f..a9ca4c6 100644
--- a/frontend/src/components/UserCard.vue
+++ b/frontend/src/components/UserCard.vue
@@ -1,79 +1,7 @@
-
-
-
-
-
-
-
- vlan
- {{ item.vlan }}
-
-
- estado
- {{ item.disabled ? 'deshabilitado' : 'activo' }}
-
-
- conexión
- conectado
-
-
- etiquetas
-
- {{ tag }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/frontend/src/components/UserForm.vue b/frontend/src/components/UserForm.vue
index 50e34a8..91be81c 100644
--- a/frontend/src/components/UserForm.vue
+++ b/frontend/src/components/UserForm.vue
@@ -1,40 +1,6 @@
-
-
-
-
+
+
+
+
diff --git a/frontend/src/components/VlanForm.vue b/frontend/src/components/VlanForm.vue
index 0dbf77b..ff6f19e 100644
--- a/frontend/src/components/VlanForm.vue
+++ b/frontend/src/components/VlanForm.vue
@@ -1,29 +1,6 @@
-
-
-
-
+
+
+
diff --git a/frontend/src/components/auth/GroupCheckButton.vue b/frontend/src/components/auth/GroupCheckButton.vue
index 54f3c6e..9c5b663 100644
--- a/frontend/src/components/auth/GroupCheckButton.vue
+++ b/frontend/src/components/auth/GroupCheckButton.vue
@@ -1,19 +1,8 @@
-
-
-
-
-
+
+
+
diff --git a/frontend/src/components/auth/SessionStatusButton.vue b/frontend/src/components/auth/SessionStatusButton.vue
index 126f2b6..d5dfeb4 100644
--- a/frontend/src/components/auth/SessionStatusButton.vue
+++ b/frontend/src/components/auth/SessionStatusButton.vue
@@ -1,12 +1,6 @@
-
-
-
-
-
+
+
+
diff --git a/frontend/src/components/auth/UserAvatar.vue b/frontend/src/components/auth/UserAvatar.vue
index 812ca63..c6ceaa0 100644
--- a/frontend/src/components/auth/UserAvatar.vue
+++ b/frontend/src/components/auth/UserAvatar.vue
@@ -1,15 +1,5 @@
-
-
-
![]()
-
-
-
-
+
+
+
diff --git a/frontend/src/components/auth/UserDropdown.vue b/frontend/src/components/auth/UserDropdown.vue
index 68ded65..1613742 100644
--- a/frontend/src/components/auth/UserDropdown.vue
+++ b/frontend/src/components/auth/UserDropdown.vue
@@ -1,81 +1,7 @@
-
-
-
-
- Cargando...
-
-
-
-
- No autenticado
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/avatar/Avatar.vue b/frontend/src/components/ui/avatar/Avatar.vue
new file mode 100644
index 0000000..557ae2f
--- /dev/null
+++ b/frontend/src/components/ui/avatar/Avatar.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+ {{ initials }}
+
+
+
diff --git a/frontend/src/components/ui/avatar/index.js b/frontend/src/components/ui/avatar/index.js
new file mode 100644
index 0000000..5005ac5
--- /dev/null
+++ b/frontend/src/components/ui/avatar/index.js
@@ -0,0 +1 @@
+export { default as Avatar } from './Avatar.vue';
diff --git a/frontend/src/components/ui/badge/Badge.vue b/frontend/src/components/ui/badge/Badge.vue
new file mode 100644
index 0000000..8eb88d2
--- /dev/null
+++ b/frontend/src/components/ui/badge/Badge.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/badge/index.js b/frontend/src/components/ui/badge/index.js
new file mode 100644
index 0000000..8757dc0
--- /dev/null
+++ b/frontend/src/components/ui/badge/index.js
@@ -0,0 +1 @@
+export { default as Badge } from './Badge.vue';
diff --git a/frontend/src/components/ui/button/Button.vue b/frontend/src/components/ui/button/Button.vue
new file mode 100644
index 0000000..3e54217
--- /dev/null
+++ b/frontend/src/components/ui/button/Button.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/button/index.js b/frontend/src/components/ui/button/index.js
new file mode 100644
index 0000000..652e795
--- /dev/null
+++ b/frontend/src/components/ui/button/index.js
@@ -0,0 +1 @@
+export { default as Button } from './Button.vue';
diff --git a/frontend/src/components/ui/card/Card.vue b/frontend/src/components/ui/card/Card.vue
new file mode 100644
index 0000000..f5ea26e
--- /dev/null
+++ b/frontend/src/components/ui/card/Card.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/card/CardActions.vue b/frontend/src/components/ui/card/CardActions.vue
new file mode 100644
index 0000000..fd0e479
--- /dev/null
+++ b/frontend/src/components/ui/card/CardActions.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/card/CardContent.vue b/frontend/src/components/ui/card/CardContent.vue
new file mode 100644
index 0000000..3dffe31
--- /dev/null
+++ b/frontend/src/components/ui/card/CardContent.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/card/CardHeader.vue b/frontend/src/components/ui/card/CardHeader.vue
new file mode 100644
index 0000000..c7fc386
--- /dev/null
+++ b/frontend/src/components/ui/card/CardHeader.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/card/CardTitle.vue b/frontend/src/components/ui/card/CardTitle.vue
new file mode 100644
index 0000000..f1ec3cd
--- /dev/null
+++ b/frontend/src/components/ui/card/CardTitle.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/card/index.js b/frontend/src/components/ui/card/index.js
new file mode 100644
index 0000000..efd46af
--- /dev/null
+++ b/frontend/src/components/ui/card/index.js
@@ -0,0 +1,5 @@
+export { default as Card } from './Card.vue';
+export { default as CardHeader } from './CardHeader.vue';
+export { default as CardTitle } from './CardTitle.vue';
+export { default as CardContent } from './CardContent.vue';
+export { default as CardActions } from './CardActions.vue';
diff --git a/frontend/src/components/ui/dialog/Dialog.vue b/frontend/src/components/ui/dialog/Dialog.vue
new file mode 100644
index 0000000..d65f4e9
--- /dev/null
+++ b/frontend/src/components/ui/dialog/Dialog.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/dialog/DialogClose.vue b/frontend/src/components/ui/dialog/DialogClose.vue
new file mode 100644
index 0000000..f9aa333
--- /dev/null
+++ b/frontend/src/components/ui/dialog/DialogClose.vue
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/dialog/DialogFooter.vue b/frontend/src/components/ui/dialog/DialogFooter.vue
new file mode 100644
index 0000000..6a8af62
--- /dev/null
+++ b/frontend/src/components/ui/dialog/DialogFooter.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/dialog/DialogHeader.vue b/frontend/src/components/ui/dialog/DialogHeader.vue
new file mode 100644
index 0000000..d52c2df
--- /dev/null
+++ b/frontend/src/components/ui/dialog/DialogHeader.vue
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/dialog/DialogTitle.vue b/frontend/src/components/ui/dialog/DialogTitle.vue
new file mode 100644
index 0000000..5761c41
--- /dev/null
+++ b/frontend/src/components/ui/dialog/DialogTitle.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/dialog/index.js b/frontend/src/components/ui/dialog/index.js
new file mode 100644
index 0000000..d57866c
--- /dev/null
+++ b/frontend/src/components/ui/dialog/index.js
@@ -0,0 +1,6 @@
+export { default as Dialog } from './Dialog.vue';
+export { default as DialogHeader } from './DialogHeader.vue';
+export { default as DialogTitle } from './DialogTitle.vue';
+export { default as DialogFooter } from './DialogFooter.vue';
+export { default as DialogClose } from './DialogClose.vue';
+export { DialogTrigger, DialogDescription } from 'radix-vue';
diff --git a/frontend/src/components/ui/dropdown-menu/DropdownMenu.vue b/frontend/src/components/ui/dropdown-menu/DropdownMenu.vue
new file mode 100644
index 0000000..fbc1af9
--- /dev/null
+++ b/frontend/src/components/ui/dropdown-menu/DropdownMenu.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/dropdown-menu/DropdownMenuItem.vue b/frontend/src/components/ui/dropdown-menu/DropdownMenuItem.vue
new file mode 100644
index 0000000..9563fff
--- /dev/null
+++ b/frontend/src/components/ui/dropdown-menu/DropdownMenuItem.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/dropdown-menu/DropdownMenuLabel.vue b/frontend/src/components/ui/dropdown-menu/DropdownMenuLabel.vue
new file mode 100644
index 0000000..3fe225d
--- /dev/null
+++ b/frontend/src/components/ui/dropdown-menu/DropdownMenuLabel.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue b/frontend/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue
new file mode 100644
index 0000000..a0a3513
--- /dev/null
+++ b/frontend/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/dropdown-menu/index.js b/frontend/src/components/ui/dropdown-menu/index.js
new file mode 100644
index 0000000..d88ddf9
--- /dev/null
+++ b/frontend/src/components/ui/dropdown-menu/index.js
@@ -0,0 +1,4 @@
+export { default as DropdownMenu } from './DropdownMenu.vue';
+export { default as DropdownMenuItem } from './DropdownMenuItem.vue';
+export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue';
+export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue';
diff --git a/frontend/src/components/ui/index.js b/frontend/src/components/ui/index.js
new file mode 100644
index 0000000..8c22793
--- /dev/null
+++ b/frontend/src/components/ui/index.js
@@ -0,0 +1,26 @@
+// Button
+export { Button } from './button';
+
+// Card
+export { Card, CardHeader, CardTitle, CardContent, CardActions } from './card';
+
+// Input
+export { Input } from './input';
+
+// Textarea
+export { Textarea } from './textarea';
+
+// Label
+export { Label } from './label';
+
+// Badge
+export { Badge } from './badge';
+
+// Dialog
+export { Dialog, DialogHeader, DialogTitle, DialogFooter, DialogClose, DialogTrigger, DialogDescription } from './dialog';
+
+// Avatar
+export { Avatar } from './avatar';
+
+// Dropdown Menu
+export { DropdownMenu, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuLabel } from './dropdown-menu';
diff --git a/frontend/src/components/ui/input/Input.vue b/frontend/src/components/ui/input/Input.vue
new file mode 100644
index 0000000..6d37217
--- /dev/null
+++ b/frontend/src/components/ui/input/Input.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/input/index.js b/frontend/src/components/ui/input/index.js
new file mode 100644
index 0000000..c5248c5
--- /dev/null
+++ b/frontend/src/components/ui/input/index.js
@@ -0,0 +1 @@
+export { default as Input } from './Input.vue';
diff --git a/frontend/src/components/ui/label/Label.vue b/frontend/src/components/ui/label/Label.vue
new file mode 100644
index 0000000..450651f
--- /dev/null
+++ b/frontend/src/components/ui/label/Label.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/label/index.js b/frontend/src/components/ui/label/index.js
new file mode 100644
index 0000000..c98e59f
--- /dev/null
+++ b/frontend/src/components/ui/label/index.js
@@ -0,0 +1 @@
+export { default as Label } from './Label.vue';
diff --git a/frontend/src/components/ui/textarea/Textarea.vue b/frontend/src/components/ui/textarea/Textarea.vue
new file mode 100644
index 0000000..7876d66
--- /dev/null
+++ b/frontend/src/components/ui/textarea/Textarea.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/textarea/index.js b/frontend/src/components/ui/textarea/index.js
new file mode 100644
index 0000000..e2d4773
--- /dev/null
+++ b/frontend/src/components/ui/textarea/index.js
@@ -0,0 +1 @@
+export { default as Textarea } from './Textarea.vue';
diff --git a/frontend/src/lib/utils.js b/frontend/src/lib/utils.js
new file mode 100644
index 0000000..7833021
--- /dev/null
+++ b/frontend/src/lib/utils.js
@@ -0,0 +1,12 @@
+import { clsx } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+
+/**
+ * Utility function to merge Tailwind CSS classes
+ * Combines clsx for conditional classes with tailwind-merge for deduplication
+ * @param {...any} inputs - Class names, arrays, or objects
+ * @returns {string} Merged class string
+ */
+export function cn(...inputs) {
+ return twMerge(clsx(inputs));
+}
diff --git a/frontend/src/main.js b/frontend/src/main.js
index 9b680f9..2aed706 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -1,6 +1,6 @@
import { createApp } from 'vue';
import App from './App.vue';
-import './styles.css';
+import './app.css';
const app = createApp(App);
app.mount('#app');
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
deleted file mode 100644
index c4c9503..0000000
--- a/frontend/src/styles.css
+++ /dev/null
@@ -1,182 +0,0 @@
-:root {
- --bg: 15 15 18;
- --fg: 235 235 240;
- --muted: 180 180 190;
- --accent: 80 160 255;
- --card: 28 28 34 / 0.55;
- --border: 255 255 255 / 0.12;
- --glass-blur: 14px;
- --radius: 14px;
- /* Scrollbar */
- --sb-size: 10px;
- --sb-thumb: rgba(255, 159, 203, 0.55);
- --sb-thumb-hover: rgba(255, 159, 203, 0.75);
- --sb-thumb-active: rgba(255, 127, 187, 0.9);
- --sb-track: rgba(255,255,255,0.05);
-}
-:root.light {
- --bg: 245 245 248;
- --fg: 20 20 22;
- --muted: 110 110 120;
- --accent: 18 108 242;
- --card: 255 255 255 / 0.6;
- --border: 0 0 0 / 0.08;
- --sb-thumb: rgba(255, 127, 187, 0.65);
- --sb-thumb-hover: rgba(255, 127, 187, 0.82);
- --sb-thumb-active: rgba(255, 110, 178, 0.95);
- --sb-track: rgba(0,0,0,0.06);
-}
-* { box-sizing: border-box; }
-html, body, #app { height: 100%; }
-html, body { margin: 0; padding: 0; background: rgb(var(--bg)); color: rgb(var(--fg)); }
-body { font: 14px/1.45 system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji"; }
-button { cursor: pointer; }
-a { color: inherit; }
-
-/* Top bar */
-.topbar {
- position: sticky; top: 0; z-index: 10;
- display: flex; flex-wrap: wrap; align-items: center;
- gap: 10px; padding: 10px 14px; backdrop-filter: blur(var(--glass-blur));
- border: 1px solid transparent;
- background:
- linear-gradient(rgba(var(--card)), rgba(var(--card))) padding-box,
- linear-gradient(135deg,
- rgba(255, 159, 203, 0.95) 0%,
- rgba(255, 159, 203, 0.60) 10%,
- rgba(255, 127, 187, 0.45) 18%,
- rgba(0, 0, 0, 0.20) 28%,
- rgba(0, 0, 0, 0.06) 50%,
- rgba(0, 0, 0, 0.00) 70%
- ) border-box;
-}
-:root:not(.light) .topbar {
- background:
- linear-gradient(rgba(var(--card)), rgba(var(--card))) padding-box,
- linear-gradient(135deg,
- rgba(255, 46, 134, 0.95) 0%,
- rgba(255, 46, 134, 0.60) 10%,
- rgba(255, 107, 176, 0.45) 18%,
- rgba(0, 0, 0, 0.28) 28%,
- rgba(0, 0, 0, 0.10) 50%,
- rgba(0, 0, 0, 0.00) 70%
- ) border-box;
- border-width: 0.5px; /* borde más delgado en dark */
-}
-.title { font-size: 16px; font-weight: 700; letter-spacing: .2px; flex: 1 1 auto; }
-.actions { display: inline-flex; flex-wrap: wrap; gap: 8px; align-items: center; }
-.icon-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 8px 10px; border-radius: 10px; border: 1px solid rgba(var(--border)); background: rgba(var(--card)); color: inherit; transition: background .2s;
- backdrop-filter: blur(var(--glass-blur)); }
-.icon-btn:hover { background: rgba(var(--card)); }
-.icon { width: 16px; height: 16px; opacity: .9; }
-.dropdown { position: relative; }
-.menu { position: absolute; right: 0; top: calc(100% + 6px); background: rgba(var(--card)); border: 1px solid #ffcfe4; border-radius: 10px; padding: 6px; backdrop-filter: blur(var(--glass-blur)); min-width: 180px; box-shadow: 0 10px 24px rgba(0,0,0,.18); }
-.menu button { display: block; width: 100%; text-align: left; padding: 8px 10px; border-radius: 8px; border: 1px solid rgba(var(--border)); background: rgba(var(--card)); }
-.menu button:hover { transform: none; background: rgba(255,255,255,0.06); }
-
-/* Layout */
-.shell { height: calc(100vh - 54px); display: grid; grid-template-columns: 360px 1fr; grid-template-areas: "sidebar main"; gap: 12px; padding: 12px; }
-.shell > aside { grid-area: sidebar; }
-.shell > main { grid-area: main; }
-.panel {
- border: 1px solid #ffcfe4; /* light más claro */
- background: linear-gradient(rgba(var(--card)), rgba(var(--card))) padding-box;
- border-radius: var(--radius);
- backdrop-filter: blur(var(--glass-blur));
- overflow: hidden; display: flex; flex-direction: column; min-height: 0;
-}
-:root:not(.light) .panel { border-color: #ff2e86; border-width: 0.5px; /* borde más delgado en dark */ }
-.panel-header { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; padding: 10px 12px; border-bottom: 1px solid rgba(var(--border)); }
-.panel-title { font-weight: 600; }
-.panel-actions { display: inline-flex; flex-wrap: wrap; gap: 6px; }
-.scroll { overflow: auto; padding: 10px; }
-
-/* Cards */
-.card { border: 1px solid rgba(var(--border)); background: rgba(var(--card)); border-radius: 12px; padding: 10px; transition: box-shadow .2s ease; box-shadow: 0 4px 14px rgba(0,0,0,.08); }
-.card:hover { box-shadow: 0 8px 20px rgba(0,0,0,.12); }
-.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 10px; }
-
-/* Botones de colapsar: solo visibles en móvil */
-.collapse-btn {
- display: none;
-}
-
-/* Responsive */
-@media (max-width: 980px) {
- .shell { grid-template-columns: 1fr; grid-template-areas:
- "main"
- "sidebar"; }
-
- /* Mostrar botones de colapsar solo en móvil */
- .collapse-btn {
- display: inline-flex;
- }
-
- /* En móvil, desactivar el sistema de grid de 52px para contenedores colapsados */
- .shell:has(> aside.collapsed) { grid-template-columns: 1fr; }
- .shell:has(> main.collapsed) { grid-template-columns: 1fr; }
-
- /* En móvil, los paneles colapsados solo ocupan el espacio del header */
- .panel.collapsed {
- height: auto;
- min-height: auto;
- }
-}
-
-/* When a panel is collapsed, hide its scroll and shrink its grid track to show only header */
-.panel.collapsed .scroll { display: none; }
-
-/* Desktop-only: Using :has to adapt the grid columns when a side is collapsed (modern browsers) */
-@media (min-width: 981px) {
- .shell:has(> aside.collapsed) { grid-template-columns: 52px 1fr; }
- .shell:has(> main.collapsed) { grid-template-columns: 1fr 52px; }
-}
-
-/* Modal */
-.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.35); backdrop-filter: blur(4px); display: grid; place-items: center; z-index: 20; animation: fadeIn .15s ease;
-}
-.modal { width: min(680px, 92vw); border-radius: 14px; border: 1px solid rgba(var(--border)); background: rgba(var(--card)); padding: 14px; box-shadow: 0 10px 32px rgba(0,0,0,.2); }
-.modal.fullscreen { width: 96vw; max-width: 96vw; height: 92vh; max-height: 92vh; display: flex; flex-direction: column; }
-.modal.fullscreen > .modal-header { position: sticky; top: 0; background: rgba(var(--card)); z-index: 1; }
-.modal.fullscreen > .modal-footer { position: sticky; bottom: 0; background: rgba(var(--card)); z-index: 1; }
-.modal.fullscreen > div:nth-child(2) { flex: 1 1 auto; overflow: auto; }
-.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
-.modal-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }
-@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
-
-/* Small bits */
-.chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; border: 1px solid rgba(var(--border)); border-radius: 999px; background: rgba(var(--card)); font-size: 12px; }
-.muted { color: rgb(var(--muted)); }
-.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
-.spacer { flex: 1; }
-.toggle { padding: 6px 10px; border-radius: 8px; border: 1px solid rgba(var(--border)); background: rgba(var(--card)); }
-
-/* Scrollbars */
-/* Firefox */
-html, body, .scroll {
- scrollbar-width: thin;
- scrollbar-color: var(--sb-thumb) transparent;
-}
-/* WebKit */
-html::-webkit-scrollbar, body::-webkit-scrollbar, .scroll::-webkit-scrollbar {
- width: var(--sb-size);
- height: var(--sb-size);
-}
-html::-webkit-scrollbar-track, body::-webkit-scrollbar-track, .scroll::-webkit-scrollbar-track {
- background: transparent;
-}
-html::-webkit-scrollbar-thumb, body::-webkit-scrollbar-thumb, .scroll::-webkit-scrollbar-thumb {
- background: var(--sb-thumb);
- border-radius: 999px;
- border: 2px solid transparent; /* creates inset padding */
- background-clip: content-box;
- box-shadow: 0 0 10px rgba(255, 46, 134, 0.15);
-}
-html::-webkit-scrollbar-thumb:hover, body::-webkit-scrollbar-thumb:hover, .scroll::-webkit-scrollbar-thumb:hover {
- background: var(--sb-thumb-hover);
- background-clip: content-box;
-}
-html::-webkit-scrollbar-thumb:active, body::-webkit-scrollbar-thumb:active, .scroll::-webkit-scrollbar-thumb:active {
- background: var(--sb-thumb-active);
- background-clip: content-box;
-}
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index a023ca8..cd708f5 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -1,8 +1,15 @@
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
+import tailwindcss from '@tailwindcss/vite';
+import { fileURLToPath, URL } from 'node:url';
export default defineConfig({
- plugins: [vue()],
+ plugins: [vue(), tailwindcss()],
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url))
+ }
+ },
server: {
port: 5173,
proxy: {