diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..9fa815b
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,9 @@
+# Ignore certain directories
+.git/
+node_modules/
+
+# Ignore certain files
+Dockerfile*
+docker-compose*
+compose*
+README*
diff --git a/.github/workflows/lint-test.yaml b/.github/workflows/lint.yaml
similarity index 53%
rename from .github/workflows/lint-test.yaml
rename to .github/workflows/lint.yaml
index 4a5a32e..357995f 100644
--- a/.github/workflows/lint-test.yaml
+++ b/.github/workflows/lint.yaml
@@ -1,4 +1,4 @@
-name: Lint and Test
+name: Lint
on:
# Manual Invocation
@@ -7,12 +7,9 @@ on:
# Invocation from workflow
workflow_call:
-env:
- CI: true
-
jobs:
- lint:
- name: Lint
+ lint-be:
+ name: Lint Backend
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
@@ -31,8 +28,8 @@ jobs:
- name: Run lint
uses: golangci/golangci-lint-action@v8
- test:
- name: Test
+ lint-fe:
+ name: Lint Frontend
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
@@ -43,20 +40,22 @@ jobs:
- name: Checkout repo
uses: actions/checkout@v5
- - name: Setup go
- uses: actions/setup-go@v6
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
with:
- go-version-file: go.mod
+ package_json_file: frontend/package.json
- - name: Setup task
- uses: arduino/setup-task@v2
+ - name: Setup node
+ uses: actions/setup-node@v6
with:
- repo-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Download dependencies
- run: go mod download
-
- - name: Run test
- run: task test
- env:
- TWICKETS_API_KEY: ${{ secrets.TWICKETS_API_KEY }}
+ node-version-file: frontend/package.json
+ cache: pnpm
+ cache-dependency-path: frontend/pnpm-lock.yaml
+
+ - name: Install dependencies
+ working-directory: frontend
+ run: pnpm install
+
+ - name: Run lint
+ working-directory: frontend
+ run: pnpm run lint
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index 33bc689..0a69ec9 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -6,15 +6,20 @@ on:
- main
jobs:
- lint-test:
- name: Lint and Test
- uses: ./.github/workflows/lint-test.yaml
+ lint:
+ name: Lint
+ uses: ./.github/workflows/lint.yaml
+ secrets: inherit
+
+ test:
+ name: Test
+ uses: ./.github/workflows/test.yaml
secrets: inherit
build-push:
name: Build and Push
uses: ./.github/workflows/build-push.yaml
- needs: [lint-test]
+ needs: [lint, test]
secrets: inherit
with:
ref: ${{ github.sha }}
diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml
index 8e033d4..41637cd 100644
--- a/.github/workflows/pull-request.yaml
+++ b/.github/workflows/pull-request.yaml
@@ -6,7 +6,12 @@ on:
- main
jobs:
- lint-test:
- name: Lint and Test
- uses: ./.github/workflows/lint-test.yaml
+ lint:
+ name: Lint
+ uses: ./.github/workflows/lint.yaml
+ secrets: inherit
+
+ test:
+ name: Test
+ uses: ./.github/workflows/test.yaml
secrets: inherit
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 0000000..8d7d457
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,35 @@
+name: Test
+
+on:
+ # Manual Invocation
+ workflow_dispatch:
+
+ # Invocation from workflow
+ workflow_call:
+
+jobs:
+ test-backend:
+ name: Test Backend
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ defaults:
+ run:
+ shell: bash
+
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v5
+
+ - name: Setup go
+ uses: actions/setup-go@v6
+ with:
+ go-version-file: go.mod
+
+ - name: Install dependencies
+ run: go mod download
+
+ - name: Run test
+ run: go test ./...
+ env:
+ TWICKETS_API_KEY: ${{ secrets.TWICKETS_API_KEY }}
+ CI: true
diff --git a/.gitignore b/.gitignore
index 089c45c..5e32a4d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,11 +70,6 @@ web_modules/
# Yarn Integrity file
.yarn-integrity
-# dotenv environment variable files
-.env
-.env.*
-!.env.example
-
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
diff --git a/.vscode/launch.json b/.vscode/launch.json
index de86ef3..cc891b1 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
- "name": "Twitchets",
+ "name": "Backend",
"type": "go",
"request": "launch",
"mode": "auto",
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 60c7172..d72c285 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -16,6 +16,9 @@
"go.toolsManagement.autoUpdate": true,
"go.buildTags": "",
+ // ---- Javascript Settings ----
+ "biome.configurationPath": "frontend/biome.json",
+
// Code Spell Checker
"cSpell.words": ["twickets", "unmarshaller"]
}
diff --git a/Dockerfile b/Dockerfile
index f5990dc..18f5556 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,12 +1,34 @@
-# Builder Image
-FROM golang:1.24 AS builder
+# Frontend builder Image
+FROM node:25-slim AS frontend-builder
-WORKDIR /twitchets
+WORKDIR /app
+# Setup pnpm
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME:$PATH"
+RUN corepack enable
+
+# Install dependencies
+COPY ./frontend/package.json .
+COPY ./frontend/pnpm-lock.yaml .
+RUN pnpm install --prod --frozen-lockfile
+
+# Build
+COPY ./frontend .
+RUN pnpm run build
+
+# Backend builder Image
+FROM golang:1.24 AS backend-builder
+
+WORKDIR /app
+
+# Install dependencies
COPY go.mod go.sum ./
RUN go mod download
+# Build
COPY . .
+COPY --from=frontend-builder /app/dist ./frontend/dist
RUN go build -v -o ./bin/ .
# Distribution Image
@@ -14,10 +36,8 @@ FROM alpine:latest
RUN apk add --no-cache libc6-compat
-COPY --from=builder /twitchets/bin/twitchets /usr/bin/twitchets
-
-WORKDIR /twitchets
+COPY --from=backend-builder /app/bin/twitchets /usr/bin/twitchets
-EXPOSE 5656
+EXPOSE 9000
ENTRYPOINT ["/usr/bin/twitchets"]
diff --git a/README.md b/README.md
index 46ef1f6..4d94da7 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,10 @@ Powered by [twigots](https://github.com/ahobsonsayers/twigots), a Go package to
- No need to have the Twickets app or an account
- Choose from various notification services (Telegram, Ntfy, Gotify currently supported)
+### And a fancy configuration UI!
+
+
+
## Getting an API Key
To use this tool, you will need a Twickets API key. Although Twickets doesn't provide a free API, you can easily obtain a key by following these steps:
diff --git a/Taskfile.yaml b/Taskfile.yaml
index c5d05ef..a113ce1 100644
--- a/Taskfile.yaml
+++ b/Taskfile.yaml
@@ -3,19 +3,48 @@ version: "3"
tasks:
format:
+ - task: format:be
+ - task: format:fe
+
+ format:be:
cmds:
- go tool gofumpt -l -w .
- lint:
+ format:fe:
+ dir: frontend
+ cmds:
+ - pnpm run format
+
+ lint:be:
cmds:
- go tool golangci-lint run --fix ./...
+ lint:
+ - task: lint:be
+ - task: lint:fe
+
+ lint:fe:
+ dir: frontend
+ cmds:
+ - pnpm run lint
+
generate:
+ - task: build:be
+ - task: build:fe
+
+ generate:be:
aliases:
- - gen
+ - gen:be
cmds:
- go generate ./...
+ generate:fe:
+ dir: frontend
+ aliases:
+ - gen:fe
+ cmds:
+ - pnpm run generate
+
test:
cmds:
- go test ./...
@@ -26,17 +55,31 @@ tasks:
- task: test
build:
+ - task: build:fe
+ - task: build:be
+
+ build:be:
cmds:
- go build -v -o ./bin/ .
+ build:fe:
+ dir: frontend
+ cmds:
+ - run build
+
build:docker:
cmds:
- docker build . -t arranhs/twitchets
- run:
+ run:be:
cmds:
- go run .
+ run:fe:
+ dir: frontend
+ cmds:
+ - pnpm run dev
+
run:docker:
deps: [build:docker]
cmds:
diff --git a/assets/frontend.mp4 b/assets/frontend.mp4
new file mode 100644
index 0000000..a1fa037
Binary files /dev/null and b/assets/frontend.mp4 differ
diff --git a/config.example.yaml b/config.example.yaml
index 027dce3..1908a19 100644
--- a/config.example.yaml
+++ b/config.example.yaml
@@ -32,11 +32,11 @@ global:
regions:
- GBLO # London only
- # Event name similarity matching (0.0 - 1.0)
+ # Required event name similarity (0.0 - 1.0)
# Default: 0.9 (allows for minor naming differences)
eventSimilarity: 0.9
- # Minimum number of tickets required in listing
+ # Required Number of tickets
# Default: Any number of tickets
numTickets: 2 # Exactly two tickets
diff --git a/frontend/.env.development b/frontend/.env.development
new file mode 100644
index 0000000..6cadbb7
--- /dev/null
+++ b/frontend/.env.development
@@ -0,0 +1,2 @@
+# API Base URL is set in development so it take priority over window.location.origin
+VITE_API_BASE_URL=http://localhost:9000/api
diff --git a/frontend/.env.production b/frontend/.env.production
new file mode 100644
index 0000000..a8c649c
--- /dev/null
+++ b/frontend/.env.production
@@ -0,0 +1,2 @@
+# API Base URL is not set in production so window.location.origin is used
+VITE_API_BASE_URL=
diff --git a/frontend/.prettierignore b/frontend/.prettierignore
new file mode 100644
index 0000000..ae11835
--- /dev/null
+++ b/frontend/.prettierignore
@@ -0,0 +1,5 @@
+# Ignore certain directories
+src/components/ui/
+
+# Ignore certain files
+src/types/openapi.d.ts
diff --git a/frontend/.prettierrc b/frontend/.prettierrc
new file mode 100644
index 0000000..8a0a5c7
--- /dev/null
+++ b/frontend/.prettierrc
@@ -0,0 +1,10 @@
+{
+ "semi": true,
+ "singleQuote": false,
+ "tabWidth": 2,
+ "tailwindStylesheet": "./frontend/src/index.css",
+ "plugins": [
+ "@trivago/prettier-plugin-sort-imports",
+ "prettier-plugin-tailwindcss"
+ ]
+}
diff --git a/frontend/biome.json b/frontend/biome.json
new file mode 100644
index 0000000..cc0be5c
--- /dev/null
+++ b/frontend/biome.json
@@ -0,0 +1,23 @@
+{
+ "$schema": "https://biomejs.dev/schemas/2.1.3/schema.json",
+ "files": {
+ "includes": ["**", "!src/components/ui", "!dist"],
+ "ignoreUnknown": true
+ },
+ "formatter": {
+ "enabled": false
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true
+ }
+ },
+ "assist": {
+ "actions": {
+ "source": {
+ "organizeImports": "off"
+ }
+ }
+ }
+}
diff --git a/frontend/components.json b/frontend/components.json
new file mode 100644
index 0000000..13e1db0
--- /dev/null
+++ b/frontend/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/index.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/frontend/dist/.gitkeep b/frontend/dist/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/embed.go b/frontend/embed.go
new file mode 100644
index 0000000..a0126b0
--- /dev/null
+++ b/frontend/embed.go
@@ -0,0 +1,12 @@
+package frontend
+
+import (
+ "embed"
+ "io/fs"
+)
+
+//go:embed dist/*
+var distFS embed.FS
+
+// DistDirFS contains the embedded dist directory files (without the "dist" prefix)
+var DistFS, _ = fs.Sub(distFS, "dist")
diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..2e84ce6
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Twitchets
+
+
+
+
+
+
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000..bd5956a
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,56 @@
+{
+ "name": "twitchets",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "engines": {
+ "node": "^25"
+ },
+ "packageManager": "pnpm@10.13.1",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "biome lint . --fix --unsafe",
+ "format": "prettier . -w",
+ "generate": "openapi-typescript ../schema/server.openapi.yaml ../schema/models.openapi.yaml -o src/types/openapi.d.ts",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@radix-ui/react-alert-dialog": "^1.1.15",
+ "@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-collapsible": "^1.1.11",
+ "@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-select": "^2.2.5",
+ "@radix-ui/react-separator": "^1.1.7",
+ "@radix-ui/react-slot": "^1.2.3",
+ "@radix-ui/react-tooltip": "^1.2.8",
+ "@tailwindcss/vite": "^4.1.11",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "immer": "^10.1.1",
+ "lodash": "^4.17.21",
+ "lucide-react": "^0.536.0",
+ "openapi-fetch": "^0.15.0",
+ "react": "^19.1.0",
+ "react-dom": "^19.1.0",
+ "react-number-format": "^5.4.4",
+ "tailwind-merge": "^3.3.1",
+ "tailwindcss": "^4.1.11"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "2.1.3",
+ "@trivago/prettier-plugin-sort-imports": "^5.2.2",
+ "@types/lodash": "^4.17.20",
+ "@types/node": "^24.1.0",
+ "@types/react": "^19.1.8",
+ "@types/react-dom": "^19.1.6",
+ "@vitejs/plugin-react": "^4.6.0",
+ "globals": "^16.3.0",
+ "openapi-typescript": "^7.10.1",
+ "prettier": "^3.6.2",
+ "prettier-plugin-tailwindcss": "^0.6.14",
+ "tw-animate-css": "^1.3.6",
+ "typescript": "~5.8.3",
+ "vite": "^7.0.4"
+ }
+}
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
new file mode 100644
index 0000000..b5ed4bd
--- /dev/null
+++ b/frontend/pnpm-lock.yaml
@@ -0,0 +1,3697 @@
+lockfileVersion: "9.0"
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+ .:
+ dependencies:
+ "@radix-ui/react-alert-dialog":
+ specifier: ^1.1.15
+ version: 1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-checkbox":
+ specifier: ^1.3.3
+ version: 1.3.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-collapsible":
+ specifier: ^1.1.11
+ version: 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-label":
+ specifier: ^2.1.7
+ version: 2.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-select":
+ specifier: ^2.2.5
+ version: 2.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-separator":
+ specifier: ^1.1.7
+ version: 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-slot":
+ specifier: ^1.2.3
+ version: 1.2.3(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-tooltip":
+ specifier: ^1.2.8
+ version: 1.2.8(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@tailwindcss/vite":
+ specifier: ^4.1.11
+ version: 4.1.11(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1))
+ class-variance-authority:
+ specifier: ^0.7.1
+ version: 0.7.1
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ immer:
+ specifier: ^10.1.1
+ version: 10.1.1
+ lodash:
+ specifier: ^4.17.21
+ version: 4.17.21
+ lucide-react:
+ specifier: ^0.536.0
+ version: 0.536.0(react@19.1.1)
+ openapi-fetch:
+ specifier: ^0.15.0
+ version: 0.15.0
+ react:
+ specifier: ^19.1.0
+ version: 19.1.1
+ react-dom:
+ specifier: ^19.1.0
+ version: 19.1.1(react@19.1.1)
+ react-number-format:
+ specifier: ^5.4.4
+ version: 5.4.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ tailwind-merge:
+ specifier: ^3.3.1
+ version: 3.3.1
+ tailwindcss:
+ specifier: ^4.1.11
+ version: 4.1.11
+ devDependencies:
+ "@biomejs/biome":
+ specifier: 2.1.3
+ version: 2.1.3
+ "@trivago/prettier-plugin-sort-imports":
+ specifier: ^5.2.2
+ version: 5.2.2(prettier@3.6.2)
+ "@types/lodash":
+ specifier: ^4.17.20
+ version: 4.17.20
+ "@types/node":
+ specifier: ^24.1.0
+ version: 24.1.0
+ "@types/react":
+ specifier: ^19.1.8
+ version: 19.1.9
+ "@types/react-dom":
+ specifier: ^19.1.6
+ version: 19.1.7(@types/react@19.1.9)
+ "@vitejs/plugin-react":
+ specifier: ^4.6.0
+ version: 4.7.0(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1))
+ globals:
+ specifier: ^16.3.0
+ version: 16.3.0
+ openapi-typescript:
+ specifier: ^7.10.1
+ version: 7.10.1(typescript@5.8.3)
+ prettier:
+ specifier: ^3.6.2
+ version: 3.6.2
+ prettier-plugin-tailwindcss:
+ specifier: ^0.6.14
+ version: 0.6.14(@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.6.2))(prettier@3.6.2)
+ tw-animate-css:
+ specifier: ^1.3.6
+ version: 1.3.6
+ typescript:
+ specifier: ~5.8.3
+ version: 5.8.3
+ vite:
+ specifier: ^7.0.4
+ version: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)
+
+packages:
+ "@ampproject/remapping@2.3.0":
+ resolution:
+ {
+ integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==,
+ }
+ engines: { node: ">=6.0.0" }
+
+ "@babel/code-frame@7.27.1":
+ resolution:
+ {
+ integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/compat-data@7.28.0":
+ resolution:
+ {
+ integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/core@7.28.0":
+ resolution:
+ {
+ integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/generator@7.28.0":
+ resolution:
+ {
+ integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helper-compilation-targets@7.27.2":
+ resolution:
+ {
+ integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helper-globals@7.28.0":
+ resolution:
+ {
+ integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helper-module-imports@7.27.1":
+ resolution:
+ {
+ integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helper-module-transforms@7.27.3":
+ resolution:
+ {
+ integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0
+
+ "@babel/helper-plugin-utils@7.27.1":
+ resolution:
+ {
+ integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helper-string-parser@7.27.1":
+ resolution:
+ {
+ integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helper-validator-identifier@7.27.1":
+ resolution:
+ {
+ integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helper-validator-option@7.27.1":
+ resolution:
+ {
+ integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/helpers@7.28.2":
+ resolution:
+ {
+ integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/parser@7.28.0":
+ resolution:
+ {
+ integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==,
+ }
+ engines: { node: ">=6.0.0" }
+ hasBin: true
+
+ "@babel/plugin-transform-react-jsx-self@7.27.1":
+ resolution:
+ {
+ integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/plugin-transform-react-jsx-source@7.27.1":
+ resolution:
+ {
+ integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==,
+ }
+ engines: { node: ">=6.9.0" }
+ peerDependencies:
+ "@babel/core": ^7.0.0-0
+
+ "@babel/template@7.27.2":
+ resolution:
+ {
+ integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/traverse@7.28.0":
+ resolution:
+ {
+ integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@babel/types@7.28.2":
+ resolution:
+ {
+ integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ "@biomejs/biome@2.1.3":
+ resolution:
+ {
+ integrity: sha512-KE/tegvJIxTkl7gJbGWSgun7G6X/n2M6C35COT6ctYrAy7SiPyNvi6JtoQERVK/VRbttZfgGq96j2bFmhmnH4w==,
+ }
+ engines: { node: ">=14.21.3" }
+ hasBin: true
+
+ "@biomejs/cli-darwin-arm64@2.1.3":
+ resolution:
+ {
+ integrity: sha512-LFLkSWRoSGS1wVUD/BE6Nlt2dSn0ulH3XImzg2O/36BoToJHKXjSxzPEMAqT9QvwVtk7/9AQhZpTneERU9qaXA==,
+ }
+ engines: { node: ">=14.21.3" }
+ cpu: [arm64]
+ os: [darwin]
+
+ "@biomejs/cli-darwin-x64@2.1.3":
+ resolution:
+ {
+ integrity: sha512-Q/4OTw8P9No9QeowyxswcWdm0n2MsdCwWcc5NcKQQvzwPjwuPdf8dpPPf4r+x0RWKBtl1FLiAUtJvBlri6DnYw==,
+ }
+ engines: { node: ">=14.21.3" }
+ cpu: [x64]
+ os: [darwin]
+
+ "@biomejs/cli-linux-arm64-musl@2.1.3":
+ resolution:
+ {
+ integrity: sha512-KXouFSBnoxAWZYDQrnNRzZBbt5s9UJkIm40hdvSL9mBxSSoxRFQJbtg1hP3aa8A2SnXyQHxQfpiVeJlczZt76w==,
+ }
+ engines: { node: ">=14.21.3" }
+ cpu: [arm64]
+ os: [linux]
+
+ "@biomejs/cli-linux-arm64@2.1.3":
+ resolution:
+ {
+ integrity: sha512-2hS6LgylRqMFmAZCOFwYrf77QMdUwJp49oe8PX/O8+P2yKZMSpyQTf3Eo5ewnsMFUEmYbPOskafdV1ds1MZMJA==,
+ }
+ engines: { node: ">=14.21.3" }
+ cpu: [arm64]
+ os: [linux]
+
+ "@biomejs/cli-linux-x64-musl@2.1.3":
+ resolution:
+ {
+ integrity: sha512-KaLAxnROouzIWtl6a0Y88r/4hW5oDUJTIqQorOTVQITaKQsKjZX4XCUmHIhdEk8zMnaiLZzRTAwk1yIAl+mIew==,
+ }
+ engines: { node: ">=14.21.3" }
+ cpu: [x64]
+ os: [linux]
+
+ "@biomejs/cli-linux-x64@2.1.3":
+ resolution:
+ {
+ integrity: sha512-NxlSCBhLvQtWGagEztfAZ4WcE1AkMTntZV65ZvR+J9jp06+EtOYEBPQndA70ZGhHbEDG57bR6uNvqkd1WrEYVA==,
+ }
+ engines: { node: ">=14.21.3" }
+ cpu: [x64]
+ os: [linux]
+
+ "@biomejs/cli-win32-arm64@2.1.3":
+ resolution:
+ {
+ integrity: sha512-V9CUZCtWH4u0YwyCYbQ3W5F4ZGPWp2C2TYcsiWFNNyRfmOW1j/TY/jAurl33SaRjgZPO5UUhGyr9m6BN9t84NQ==,
+ }
+ engines: { node: ">=14.21.3" }
+ cpu: [arm64]
+ os: [win32]
+
+ "@biomejs/cli-win32-x64@2.1.3":
+ resolution:
+ {
+ integrity: sha512-dxy599q6lgp8ANPpR8sDMscwdp9oOumEsVXuVCVT9N2vAho8uYXlCz53JhxX6LtJOXaE73qzgkGQ7QqvFlMC0g==,
+ }
+ engines: { node: ">=14.21.3" }
+ cpu: [x64]
+ os: [win32]
+
+ "@esbuild/aix-ppc64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==,
+ }
+ engines: { node: ">=18" }
+ cpu: [ppc64]
+ os: [aix]
+
+ "@esbuild/android-arm64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [android]
+
+ "@esbuild/android-arm@0.25.8":
+ resolution:
+ {
+ integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm]
+ os: [android]
+
+ "@esbuild/android-x64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [android]
+
+ "@esbuild/darwin-arm64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [darwin]
+
+ "@esbuild/darwin-x64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [darwin]
+
+ "@esbuild/freebsd-arm64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [freebsd]
+
+ "@esbuild/freebsd-x64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [freebsd]
+
+ "@esbuild/linux-arm64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [linux]
+
+ "@esbuild/linux-arm@0.25.8":
+ resolution:
+ {
+ integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm]
+ os: [linux]
+
+ "@esbuild/linux-ia32@0.25.8":
+ resolution:
+ {
+ integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [ia32]
+ os: [linux]
+
+ "@esbuild/linux-loong64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [loong64]
+ os: [linux]
+
+ "@esbuild/linux-mips64el@0.25.8":
+ resolution:
+ {
+ integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [mips64el]
+ os: [linux]
+
+ "@esbuild/linux-ppc64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [ppc64]
+ os: [linux]
+
+ "@esbuild/linux-riscv64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [riscv64]
+ os: [linux]
+
+ "@esbuild/linux-s390x@0.25.8":
+ resolution:
+ {
+ integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [s390x]
+ os: [linux]
+
+ "@esbuild/linux-x64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [linux]
+
+ "@esbuild/netbsd-arm64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [netbsd]
+
+ "@esbuild/netbsd-x64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [netbsd]
+
+ "@esbuild/openbsd-arm64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [openbsd]
+
+ "@esbuild/openbsd-x64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [openbsd]
+
+ "@esbuild/openharmony-arm64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [openharmony]
+
+ "@esbuild/sunos-x64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [sunos]
+
+ "@esbuild/win32-arm64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==,
+ }
+ engines: { node: ">=18" }
+ cpu: [arm64]
+ os: [win32]
+
+ "@esbuild/win32-ia32@0.25.8":
+ resolution:
+ {
+ integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==,
+ }
+ engines: { node: ">=18" }
+ cpu: [ia32]
+ os: [win32]
+
+ "@esbuild/win32-x64@0.25.8":
+ resolution:
+ {
+ integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==,
+ }
+ engines: { node: ">=18" }
+ cpu: [x64]
+ os: [win32]
+
+ "@floating-ui/core@1.7.3":
+ resolution:
+ {
+ integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==,
+ }
+
+ "@floating-ui/dom@1.7.3":
+ resolution:
+ {
+ integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==,
+ }
+
+ "@floating-ui/react-dom@2.1.5":
+ resolution:
+ {
+ integrity: sha512-HDO/1/1oH9fjj4eLgegrlH3dklZpHtUYYFiVwMUwfGvk9jWDRWqkklA2/NFScknrcNSspbV868WjXORvreDX+Q==,
+ }
+ peerDependencies:
+ react: ">=16.8.0"
+ react-dom: ">=16.8.0"
+
+ "@floating-ui/utils@0.2.10":
+ resolution:
+ {
+ integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==,
+ }
+
+ "@isaacs/fs-minipass@4.0.1":
+ resolution:
+ {
+ integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==,
+ }
+ engines: { node: ">=18.0.0" }
+
+ "@jridgewell/gen-mapping@0.3.12":
+ resolution:
+ {
+ integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==,
+ }
+
+ "@jridgewell/resolve-uri@3.1.2":
+ resolution:
+ {
+ integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==,
+ }
+ engines: { node: ">=6.0.0" }
+
+ "@jridgewell/sourcemap-codec@1.5.4":
+ resolution:
+ {
+ integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==,
+ }
+
+ "@jridgewell/trace-mapping@0.3.29":
+ resolution:
+ {
+ integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==,
+ }
+
+ "@radix-ui/number@1.1.1":
+ resolution:
+ {
+ integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==,
+ }
+
+ "@radix-ui/primitive@1.1.2":
+ resolution:
+ {
+ integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==,
+ }
+
+ "@radix-ui/primitive@1.1.3":
+ resolution:
+ {
+ integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==,
+ }
+
+ "@radix-ui/react-alert-dialog@1.1.15":
+ resolution:
+ {
+ integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-arrow@1.1.7":
+ resolution:
+ {
+ integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-checkbox@1.3.3":
+ resolution:
+ {
+ integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-collapsible@1.1.11":
+ resolution:
+ {
+ integrity: sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-collection@1.1.7":
+ resolution:
+ {
+ integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-compose-refs@1.1.2":
+ resolution:
+ {
+ integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-context@1.1.2":
+ resolution:
+ {
+ integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-dialog@1.1.15":
+ resolution:
+ {
+ integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-direction@1.1.1":
+ resolution:
+ {
+ integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-dismissable-layer@1.1.10":
+ resolution:
+ {
+ integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-dismissable-layer@1.1.11":
+ resolution:
+ {
+ integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-focus-guards@1.1.2":
+ resolution:
+ {
+ integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-focus-guards@1.1.3":
+ resolution:
+ {
+ integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-focus-scope@1.1.7":
+ resolution:
+ {
+ integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-id@1.1.1":
+ resolution:
+ {
+ integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-label@2.1.7":
+ resolution:
+ {
+ integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-popper@1.2.7":
+ resolution:
+ {
+ integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-popper@1.2.8":
+ resolution:
+ {
+ integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-portal@1.1.9":
+ resolution:
+ {
+ integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-presence@1.1.4":
+ resolution:
+ {
+ integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-presence@1.1.5":
+ resolution:
+ {
+ integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-primitive@2.1.3":
+ resolution:
+ {
+ integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-select@2.2.5":
+ resolution:
+ {
+ integrity: sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-separator@1.1.7":
+ resolution:
+ {
+ integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-slot@1.2.3":
+ resolution:
+ {
+ integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-tooltip@1.2.8":
+ resolution:
+ {
+ integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/react-use-callback-ref@1.1.1":
+ resolution:
+ {
+ integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-use-controllable-state@1.2.2":
+ resolution:
+ {
+ integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-use-effect-event@0.0.2":
+ resolution:
+ {
+ integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-use-escape-keydown@1.1.1":
+ resolution:
+ {
+ integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-use-layout-effect@1.1.1":
+ resolution:
+ {
+ integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-use-previous@1.1.1":
+ resolution:
+ {
+ integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-use-rect@1.1.1":
+ resolution:
+ {
+ integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-use-size@1.1.1":
+ resolution:
+ {
+ integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ "@radix-ui/react-visually-hidden@1.2.3":
+ resolution:
+ {
+ integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==,
+ }
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+
+ "@radix-ui/rect@1.1.1":
+ resolution:
+ {
+ integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==,
+ }
+
+ "@redocly/ajv@8.17.1":
+ resolution:
+ {
+ integrity: sha512-EDtsGZS964mf9zAUXAl9Ew16eYbeyAFWhsPr0fX6oaJxgd8rApYlPBf0joyhnUHz88WxrigyFtTaqqzXNzPgqw==,
+ }
+
+ "@redocly/config@0.22.2":
+ resolution:
+ {
+ integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==,
+ }
+
+ "@redocly/openapi-core@1.34.5":
+ resolution:
+ {
+ integrity: sha512-0EbE8LRbkogtcCXU7liAyC00n9uNG9hJ+eMyHFdUsy9lB/WGqnEBgwjA9q2cyzAVcdTkQqTBBU1XePNnN3OijA==,
+ }
+ engines: { node: ">=18.17.0", npm: ">=9.5.0" }
+
+ "@rolldown/pluginutils@1.0.0-beta.27":
+ resolution:
+ {
+ integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==,
+ }
+
+ "@rollup/rollup-android-arm-eabi@4.45.3":
+ resolution:
+ {
+ integrity: sha512-8oQkCTve4H4B4JpmD2FV7fV2ZPTxJHN//bRhCqPUU8v6c5APlxteAXyc7BFaEb4aGpUzrPLU4PoAcGhwmRzZTA==,
+ }
+ cpu: [arm]
+ os: [android]
+
+ "@rollup/rollup-android-arm64@4.45.3":
+ resolution:
+ {
+ integrity: sha512-StOsmdXHU2hx3UFTTs6yYxCSwSIgLsfjUBICXyWj625M32OOjakXlaZuGKL+jA3Nvv35+hMxrm/64eCoT07SYQ==,
+ }
+ cpu: [arm64]
+ os: [android]
+
+ "@rollup/rollup-darwin-arm64@4.45.3":
+ resolution:
+ {
+ integrity: sha512-6CfLF3eqKhCdhK0GUnR5ZS99OFz+dtOeB/uePznLKxjCsk5QjT/V0eSEBb4vj+o/ri3i35MseSEQHCLLAgClVw==,
+ }
+ cpu: [arm64]
+ os: [darwin]
+
+ "@rollup/rollup-darwin-x64@4.45.3":
+ resolution:
+ {
+ integrity: sha512-QLWyWmAJG9elNTNLdcSXUT/M+J7DhEmvs1XPHYcgYkse3UHf9iWTJ+yTPlKMIetiQnNi+cNp+gY4gvjDpREfKw==,
+ }
+ cpu: [x64]
+ os: [darwin]
+
+ "@rollup/rollup-freebsd-arm64@4.45.3":
+ resolution:
+ {
+ integrity: sha512-ZOvBq+5nL0yrZIEo1eq6r7MPvkJ8kC1XATS/yHvcq3WbDNKNKBQ1uIF4hicyzDMoJt72G+sn1nKsFXpifZyRDA==,
+ }
+ cpu: [arm64]
+ os: [freebsd]
+
+ "@rollup/rollup-freebsd-x64@4.45.3":
+ resolution:
+ {
+ integrity: sha512-AYvGR07wecEnyYSovyJ71pTOulbNvsrpRpK6i/IM1b0UGX1vFx51afYuPYPxnvE9aCl5xPnhQicEvdIMxClRgQ==,
+ }
+ cpu: [x64]
+ os: [freebsd]
+
+ "@rollup/rollup-linux-arm-gnueabihf@4.45.3":
+ resolution:
+ {
+ integrity: sha512-Yx8Cp38tfRRToVLuIWzBHV25/QPzpUreOPIiUuNV7KahNPurYg2pYQ4l7aYnvpvklO1riX4643bXLvDsYSBIrA==,
+ }
+ cpu: [arm]
+ os: [linux]
+
+ "@rollup/rollup-linux-arm-musleabihf@4.45.3":
+ resolution:
+ {
+ integrity: sha512-4dIYRNxlXGDKnO6qgcda6LxnObPO6r1OBU9HG8F9pAnHHLtfbiOqCzDvkeHknx+5mfFVH4tWOl+h+cHylwsPWA==,
+ }
+ cpu: [arm]
+ os: [linux]
+
+ "@rollup/rollup-linux-arm64-gnu@4.45.3":
+ resolution:
+ {
+ integrity: sha512-M6uVlWKmhLN7LguLDu6396K1W5IBlAaRonjlHQgc3s4dOGceu0FeBuvbXiUPYvup/6b5Ln7IEX7XNm68DN4vrg==,
+ }
+ cpu: [arm64]
+ os: [linux]
+
+ "@rollup/rollup-linux-arm64-musl@4.45.3":
+ resolution:
+ {
+ integrity: sha512-emaYiOTQJUd6fC9a6jcw9zIWtzaUiuBC+vomggaM4In2iOra/lA6IMHlqZqQZr08NYXrOPMVigreLMeSAwv3Uw==,
+ }
+ cpu: [arm64]
+ os: [linux]
+
+ "@rollup/rollup-linux-loongarch64-gnu@4.45.3":
+ resolution:
+ {
+ integrity: sha512-3P77T5AQ4UfVRJSrTKLiUZDJ6XsxeP80027bp6mOFh8sevSD038mYuIYFiUtrSJxxgFb+NgRJFF9oIa0rlUsmg==,
+ }
+ cpu: [loong64]
+ os: [linux]
+
+ "@rollup/rollup-linux-ppc64-gnu@4.45.3":
+ resolution:
+ {
+ integrity: sha512-/VPH3ZVeSlmCBPhZdx/+4dMXDjaGMhDsWOBo9EwSkGbw2+OAqaslL53Ao2OqCxR0GgYjmmssJ+OoG+qYGE7IBg==,
+ }
+ cpu: [ppc64]
+ os: [linux]
+
+ "@rollup/rollup-linux-riscv64-gnu@4.45.3":
+ resolution:
+ {
+ integrity: sha512-Hs5if0PjROl1MGMmZX3xMAIfqcGxQE2SJWUr/CpDQsOQn43Wq4IvXXxUMWtiY/BrzdqCCJlRgJ5DKxzS3qWkCw==,
+ }
+ cpu: [riscv64]
+ os: [linux]
+
+ "@rollup/rollup-linux-riscv64-musl@4.45.3":
+ resolution:
+ {
+ integrity: sha512-Qm0WOwh3Lk388+HJFl1ILGbd2iOoQf6yl4fdGqOjBzEA+5JYbLcwd+sGsZjs5pkt8Cr/1G42EiXmlRp9ZeTvFA==,
+ }
+ cpu: [riscv64]
+ os: [linux]
+
+ "@rollup/rollup-linux-s390x-gnu@4.45.3":
+ resolution:
+ {
+ integrity: sha512-VJdknTaYw+TqXzlh9c7vaVMh/fV2sU8Khfk4a9vAdYXJawpjf6z3U1k7vDWx2IQ9ZOPoOPxgVpDfYOYhxD7QUA==,
+ }
+ cpu: [s390x]
+ os: [linux]
+
+ "@rollup/rollup-linux-x64-gnu@4.45.3":
+ resolution:
+ {
+ integrity: sha512-SUDXU5YabLAMl86FpupSQQEWzVG8X0HM+Q/famnJusbPiUgQnTGuSxtxg4UAYgv1ZmRV1nioYYXsgtSokU/7+Q==,
+ }
+ cpu: [x64]
+ os: [linux]
+
+ "@rollup/rollup-linux-x64-musl@4.45.3":
+ resolution:
+ {
+ integrity: sha512-ezmqknOUFgZMN6wW+Avlo4sXF3Frswd+ncrwMz4duyZ5Eqd+dAYgJ+A1MY+12LNZ7XDhCiijJceueYvtnzdviw==,
+ }
+ cpu: [x64]
+ os: [linux]
+
+ "@rollup/rollup-win32-arm64-msvc@4.45.3":
+ resolution:
+ {
+ integrity: sha512-1YfXoUEE++gIW66zNB9Twd0Ua5xCXpfYppFUxVT/Io5ZT3fO6Se+C/Jvmh3usaIHHyi53t3kpfjydO2GAy5eBA==,
+ }
+ cpu: [arm64]
+ os: [win32]
+
+ "@rollup/rollup-win32-ia32-msvc@4.45.3":
+ resolution:
+ {
+ integrity: sha512-Iok2YA3PvC163rVZf2Zy81A0g88IUcSPeU5pOilcbICXre2EP1mxn1Db/l09Z/SK1vdSLtpJXAnwGuMOyf5O9g==,
+ }
+ cpu: [ia32]
+ os: [win32]
+
+ "@rollup/rollup-win32-x64-msvc@4.45.3":
+ resolution:
+ {
+ integrity: sha512-HwHCH5GQTOeGYP5wBEBXFVhfQecwRl24Rugoqhh8YwGarsU09bHhOKuqlyW4ZolZCan3eTUax7UJbGSmKSM51A==,
+ }
+ cpu: [x64]
+ os: [win32]
+
+ "@tailwindcss/node@4.1.11":
+ resolution:
+ {
+ integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==,
+ }
+
+ "@tailwindcss/oxide-android-arm64@4.1.11":
+ resolution:
+ {
+ integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [arm64]
+ os: [android]
+
+ "@tailwindcss/oxide-darwin-arm64@4.1.11":
+ resolution:
+ {
+ integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [arm64]
+ os: [darwin]
+
+ "@tailwindcss/oxide-darwin-x64@4.1.11":
+ resolution:
+ {
+ integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [x64]
+ os: [darwin]
+
+ "@tailwindcss/oxide-freebsd-x64@4.1.11":
+ resolution:
+ {
+ integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [x64]
+ os: [freebsd]
+
+ "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11":
+ resolution:
+ {
+ integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [arm]
+ os: [linux]
+
+ "@tailwindcss/oxide-linux-arm64-gnu@4.1.11":
+ resolution:
+ {
+ integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [arm64]
+ os: [linux]
+
+ "@tailwindcss/oxide-linux-arm64-musl@4.1.11":
+ resolution:
+ {
+ integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [arm64]
+ os: [linux]
+
+ "@tailwindcss/oxide-linux-x64-gnu@4.1.11":
+ resolution:
+ {
+ integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [x64]
+ os: [linux]
+
+ "@tailwindcss/oxide-linux-x64-musl@4.1.11":
+ resolution:
+ {
+ integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [x64]
+ os: [linux]
+
+ "@tailwindcss/oxide-wasm32-wasi@4.1.11":
+ resolution:
+ {
+ integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==,
+ }
+ engines: { node: ">=14.0.0" }
+ cpu: [wasm32]
+ bundledDependencies:
+ - "@napi-rs/wasm-runtime"
+ - "@emnapi/core"
+ - "@emnapi/runtime"
+ - "@tybys/wasm-util"
+ - "@emnapi/wasi-threads"
+ - tslib
+
+ "@tailwindcss/oxide-win32-arm64-msvc@4.1.11":
+ resolution:
+ {
+ integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [arm64]
+ os: [win32]
+
+ "@tailwindcss/oxide-win32-x64-msvc@4.1.11":
+ resolution:
+ {
+ integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==,
+ }
+ engines: { node: ">= 10" }
+ cpu: [x64]
+ os: [win32]
+
+ "@tailwindcss/oxide@4.1.11":
+ resolution:
+ {
+ integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==,
+ }
+ engines: { node: ">= 10" }
+
+ "@tailwindcss/vite@4.1.11":
+ resolution:
+ {
+ integrity: sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==,
+ }
+ peerDependencies:
+ vite: ^5.2.0 || ^6 || ^7
+
+ "@trivago/prettier-plugin-sort-imports@5.2.2":
+ resolution:
+ {
+ integrity: sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==,
+ }
+ engines: { node: ">18.12" }
+ peerDependencies:
+ "@vue/compiler-sfc": 3.x
+ prettier: 2.x - 3.x
+ prettier-plugin-svelte: 3.x
+ svelte: 4.x || 5.x
+ peerDependenciesMeta:
+ "@vue/compiler-sfc":
+ optional: true
+ prettier-plugin-svelte:
+ optional: true
+ svelte:
+ optional: true
+
+ "@types/babel__core@7.20.5":
+ resolution:
+ {
+ integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==,
+ }
+
+ "@types/babel__generator@7.27.0":
+ resolution:
+ {
+ integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==,
+ }
+
+ "@types/babel__template@7.4.4":
+ resolution:
+ {
+ integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==,
+ }
+
+ "@types/babel__traverse@7.28.0":
+ resolution:
+ {
+ integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==,
+ }
+
+ "@types/estree@1.0.8":
+ resolution:
+ {
+ integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==,
+ }
+
+ "@types/lodash@4.17.20":
+ resolution:
+ {
+ integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==,
+ }
+
+ "@types/node@24.1.0":
+ resolution:
+ {
+ integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==,
+ }
+
+ "@types/react-dom@19.1.7":
+ resolution:
+ {
+ integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==,
+ }
+ peerDependencies:
+ "@types/react": ^19.0.0
+
+ "@types/react@19.1.9":
+ resolution:
+ {
+ integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==,
+ }
+
+ "@vitejs/plugin-react@4.7.0":
+ resolution:
+ {
+ integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==,
+ }
+ engines: { node: ^14.18.0 || >=16.0.0 }
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
+ agent-base@7.1.4:
+ resolution:
+ {
+ integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==,
+ }
+ engines: { node: ">= 14" }
+
+ ansi-colors@4.1.3:
+ resolution:
+ {
+ integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==,
+ }
+ engines: { node: ">=6" }
+
+ argparse@2.0.1:
+ resolution:
+ {
+ integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==,
+ }
+
+ aria-hidden@1.2.6:
+ resolution:
+ {
+ integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==,
+ }
+ engines: { node: ">=10" }
+
+ balanced-match@1.0.2:
+ resolution:
+ {
+ integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==,
+ }
+
+ brace-expansion@2.0.2:
+ resolution:
+ {
+ integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==,
+ }
+
+ browserslist@4.25.1:
+ resolution:
+ {
+ integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==,
+ }
+ engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 }
+ hasBin: true
+
+ caniuse-lite@1.0.30001731:
+ resolution:
+ {
+ integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==,
+ }
+
+ change-case@5.4.4:
+ resolution:
+ {
+ integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==,
+ }
+
+ chownr@3.0.0:
+ resolution:
+ {
+ integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==,
+ }
+ engines: { node: ">=18" }
+
+ class-variance-authority@0.7.1:
+ resolution:
+ {
+ integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==,
+ }
+
+ clsx@2.1.1:
+ resolution:
+ {
+ integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==,
+ }
+ engines: { node: ">=6" }
+
+ colorette@1.4.0:
+ resolution:
+ {
+ integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==,
+ }
+
+ convert-source-map@2.0.0:
+ resolution:
+ {
+ integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==,
+ }
+
+ csstype@3.1.3:
+ resolution:
+ {
+ integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==,
+ }
+
+ debug@4.4.1:
+ resolution:
+ {
+ integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==,
+ }
+ engines: { node: ">=6.0" }
+ peerDependencies:
+ supports-color: "*"
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ detect-libc@2.0.4:
+ resolution:
+ {
+ integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==,
+ }
+ engines: { node: ">=8" }
+
+ detect-node-es@1.1.0:
+ resolution:
+ {
+ integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==,
+ }
+
+ electron-to-chromium@1.5.194:
+ resolution:
+ {
+ integrity: sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==,
+ }
+
+ enhanced-resolve@5.18.2:
+ resolution:
+ {
+ integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==,
+ }
+ engines: { node: ">=10.13.0" }
+
+ esbuild@0.25.8:
+ resolution:
+ {
+ integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==,
+ }
+ engines: { node: ">=18" }
+ hasBin: true
+
+ escalade@3.2.0:
+ resolution:
+ {
+ integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==,
+ }
+ engines: { node: ">=6" }
+
+ fast-deep-equal@3.1.3:
+ resolution:
+ {
+ integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==,
+ }
+
+ fast-uri@3.1.0:
+ resolution:
+ {
+ integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==,
+ }
+
+ fdir@6.4.6:
+ resolution:
+ {
+ integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==,
+ }
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ fsevents@2.3.3:
+ resolution:
+ {
+ integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==,
+ }
+ engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 }
+ os: [darwin]
+
+ gensync@1.0.0-beta.2:
+ resolution:
+ {
+ integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==,
+ }
+ engines: { node: ">=6.9.0" }
+
+ get-nonce@1.0.1:
+ resolution:
+ {
+ integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==,
+ }
+ engines: { node: ">=6" }
+
+ globals@16.3.0:
+ resolution:
+ {
+ integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==,
+ }
+ engines: { node: ">=18" }
+
+ graceful-fs@4.2.11:
+ resolution:
+ {
+ integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==,
+ }
+
+ https-proxy-agent@7.0.6:
+ resolution:
+ {
+ integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==,
+ }
+ engines: { node: ">= 14" }
+
+ immer@10.1.1:
+ resolution:
+ {
+ integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==,
+ }
+
+ index-to-position@1.2.0:
+ resolution:
+ {
+ integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==,
+ }
+ engines: { node: ">=18" }
+
+ javascript-natural-sort@0.7.1:
+ resolution:
+ {
+ integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==,
+ }
+
+ jiti@2.5.1:
+ resolution:
+ {
+ integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==,
+ }
+ hasBin: true
+
+ js-levenshtein@1.1.6:
+ resolution:
+ {
+ integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==,
+ }
+ engines: { node: ">=0.10.0" }
+
+ js-tokens@4.0.0:
+ resolution:
+ {
+ integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==,
+ }
+
+ js-yaml@4.1.1:
+ resolution:
+ {
+ integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==,
+ }
+ hasBin: true
+
+ jsesc@3.1.0:
+ resolution:
+ {
+ integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==,
+ }
+ engines: { node: ">=6" }
+ hasBin: true
+
+ json-schema-traverse@1.0.0:
+ resolution:
+ {
+ integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==,
+ }
+
+ json5@2.2.3:
+ resolution:
+ {
+ integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==,
+ }
+ engines: { node: ">=6" }
+ hasBin: true
+
+ lightningcss-darwin-arm64@1.30.1:
+ resolution:
+ {
+ integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm64]
+ os: [darwin]
+
+ lightningcss-darwin-x64@1.30.1:
+ resolution:
+ {
+ integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [darwin]
+
+ lightningcss-freebsd-x64@1.30.1:
+ resolution:
+ {
+ integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [freebsd]
+
+ lightningcss-linux-arm-gnueabihf@1.30.1:
+ resolution:
+ {
+ integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm]
+ os: [linux]
+
+ lightningcss-linux-arm64-gnu@1.30.1:
+ resolution:
+ {
+ integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-arm64-musl@1.30.1:
+ resolution:
+ {
+ integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm64]
+ os: [linux]
+
+ lightningcss-linux-x64-gnu@1.30.1:
+ resolution:
+ {
+ integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-linux-x64-musl@1.30.1:
+ resolution:
+ {
+ integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [linux]
+
+ lightningcss-win32-arm64-msvc@1.30.1:
+ resolution:
+ {
+ integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [arm64]
+ os: [win32]
+
+ lightningcss-win32-x64-msvc@1.30.1:
+ resolution:
+ {
+ integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==,
+ }
+ engines: { node: ">= 12.0.0" }
+ cpu: [x64]
+ os: [win32]
+
+ lightningcss@1.30.1:
+ resolution:
+ {
+ integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==,
+ }
+ engines: { node: ">= 12.0.0" }
+
+ lodash@4.17.21:
+ resolution:
+ {
+ integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==,
+ }
+
+ lru-cache@5.1.1:
+ resolution:
+ {
+ integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==,
+ }
+
+ lucide-react@0.536.0:
+ resolution:
+ {
+ integrity: sha512-2PgvNa9v+qz4Jt/ni8vPLt4jwoFybXHuubQT8fv4iCW5TjDxkbZjNZZHa485ad73NSEn/jdsEtU57eE1g+ma8A==,
+ }
+ peerDependencies:
+ react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ magic-string@0.30.17:
+ resolution:
+ {
+ integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==,
+ }
+
+ minimatch@5.1.6:
+ resolution:
+ {
+ integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==,
+ }
+ engines: { node: ">=10" }
+
+ minipass@7.1.2:
+ resolution:
+ {
+ integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==,
+ }
+ engines: { node: ">=16 || 14 >=14.17" }
+
+ minizlib@3.0.2:
+ resolution:
+ {
+ integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==,
+ }
+ engines: { node: ">= 18" }
+
+ mkdirp@3.0.1:
+ resolution:
+ {
+ integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==,
+ }
+ engines: { node: ">=10" }
+ hasBin: true
+
+ ms@2.1.3:
+ resolution:
+ {
+ integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==,
+ }
+
+ nanoid@3.3.11:
+ resolution:
+ {
+ integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==,
+ }
+ engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 }
+ hasBin: true
+
+ node-releases@2.0.19:
+ resolution:
+ {
+ integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==,
+ }
+
+ openapi-fetch@0.15.0:
+ resolution:
+ {
+ integrity: sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==,
+ }
+
+ openapi-typescript-helpers@0.0.15:
+ resolution:
+ {
+ integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==,
+ }
+
+ openapi-typescript@7.10.1:
+ resolution:
+ {
+ integrity: sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw==,
+ }
+ hasBin: true
+ peerDependencies:
+ typescript: ^5.x
+
+ parse-json@8.3.0:
+ resolution:
+ {
+ integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==,
+ }
+ engines: { node: ">=18" }
+
+ picocolors@1.1.1:
+ resolution:
+ {
+ integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==,
+ }
+
+ picomatch@4.0.3:
+ resolution:
+ {
+ integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==,
+ }
+ engines: { node: ">=12" }
+
+ pluralize@8.0.0:
+ resolution:
+ {
+ integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==,
+ }
+ engines: { node: ">=4" }
+
+ postcss@8.5.6:
+ resolution:
+ {
+ integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==,
+ }
+ engines: { node: ^10 || ^12 || >=14 }
+
+ prettier-plugin-tailwindcss@0.6.14:
+ resolution:
+ {
+ integrity: sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==,
+ }
+ engines: { node: ">=14.21.3" }
+ peerDependencies:
+ "@ianvs/prettier-plugin-sort-imports": "*"
+ "@prettier/plugin-hermes": "*"
+ "@prettier/plugin-oxc": "*"
+ "@prettier/plugin-pug": "*"
+ "@shopify/prettier-plugin-liquid": "*"
+ "@trivago/prettier-plugin-sort-imports": "*"
+ "@zackad/prettier-plugin-twig": "*"
+ prettier: ^3.0
+ prettier-plugin-astro: "*"
+ prettier-plugin-css-order: "*"
+ prettier-plugin-import-sort: "*"
+ prettier-plugin-jsdoc: "*"
+ prettier-plugin-marko: "*"
+ prettier-plugin-multiline-arrays: "*"
+ prettier-plugin-organize-attributes: "*"
+ prettier-plugin-organize-imports: "*"
+ prettier-plugin-sort-imports: "*"
+ prettier-plugin-style-order: "*"
+ prettier-plugin-svelte: "*"
+ peerDependenciesMeta:
+ "@ianvs/prettier-plugin-sort-imports":
+ optional: true
+ "@prettier/plugin-hermes":
+ optional: true
+ "@prettier/plugin-oxc":
+ optional: true
+ "@prettier/plugin-pug":
+ optional: true
+ "@shopify/prettier-plugin-liquid":
+ optional: true
+ "@trivago/prettier-plugin-sort-imports":
+ optional: true
+ "@zackad/prettier-plugin-twig":
+ optional: true
+ prettier-plugin-astro:
+ optional: true
+ prettier-plugin-css-order:
+ optional: true
+ prettier-plugin-import-sort:
+ optional: true
+ prettier-plugin-jsdoc:
+ optional: true
+ prettier-plugin-marko:
+ optional: true
+ prettier-plugin-multiline-arrays:
+ optional: true
+ prettier-plugin-organize-attributes:
+ optional: true
+ prettier-plugin-organize-imports:
+ optional: true
+ prettier-plugin-sort-imports:
+ optional: true
+ prettier-plugin-style-order:
+ optional: true
+ prettier-plugin-svelte:
+ optional: true
+
+ prettier@3.6.2:
+ resolution:
+ {
+ integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==,
+ }
+ engines: { node: ">=14" }
+ hasBin: true
+
+ react-dom@19.1.1:
+ resolution:
+ {
+ integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==,
+ }
+ peerDependencies:
+ react: ^19.1.1
+
+ react-number-format@5.4.4:
+ resolution:
+ {
+ integrity: sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==,
+ }
+ peerDependencies:
+ react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ react-refresh@0.17.0:
+ resolution:
+ {
+ integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==,
+ }
+ engines: { node: ">=0.10.0" }
+
+ react-remove-scroll-bar@2.3.8:
+ resolution:
+ {
+ integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==,
+ }
+ engines: { node: ">=10" }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ react-remove-scroll@2.7.1:
+ resolution:
+ {
+ integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==,
+ }
+ engines: { node: ">=10" }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ react-style-singleton@2.2.3:
+ resolution:
+ {
+ integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==,
+ }
+ engines: { node: ">=10" }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ react@19.1.1:
+ resolution:
+ {
+ integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==,
+ }
+ engines: { node: ">=0.10.0" }
+
+ require-from-string@2.0.2:
+ resolution:
+ {
+ integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==,
+ }
+ engines: { node: ">=0.10.0" }
+
+ rollup@4.45.3:
+ resolution:
+ {
+ integrity: sha512-STwyHZF3G+CrmZhB+qDiROq9s8B5PrOCYN6dtmOvwz585XBnyeHk1GTEhHJtUVb355/9uZhOazyVclTt5uahzA==,
+ }
+ engines: { node: ">=18.0.0", npm: ">=8.0.0" }
+ hasBin: true
+
+ scheduler@0.26.0:
+ resolution:
+ {
+ integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==,
+ }
+
+ semver@6.3.1:
+ resolution:
+ {
+ integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==,
+ }
+ hasBin: true
+
+ source-map-js@1.2.1:
+ resolution:
+ {
+ integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==,
+ }
+ engines: { node: ">=0.10.0" }
+
+ supports-color@10.2.2:
+ resolution:
+ {
+ integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==,
+ }
+ engines: { node: ">=18" }
+
+ tailwind-merge@3.3.1:
+ resolution:
+ {
+ integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==,
+ }
+
+ tailwindcss@4.1.11:
+ resolution:
+ {
+ integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==,
+ }
+
+ tapable@2.2.2:
+ resolution:
+ {
+ integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==,
+ }
+ engines: { node: ">=6" }
+
+ tar@7.4.3:
+ resolution:
+ {
+ integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==,
+ }
+ engines: { node: ">=18" }
+
+ tinyglobby@0.2.14:
+ resolution:
+ {
+ integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==,
+ }
+ engines: { node: ">=12.0.0" }
+
+ tslib@2.8.1:
+ resolution:
+ {
+ integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==,
+ }
+
+ tw-animate-css@1.3.6:
+ resolution:
+ {
+ integrity: sha512-9dy0R9UsYEGmgf26L8UcHiLmSFTHa9+D7+dAt/G/sF5dCnPePZbfgDYinc7/UzAM7g/baVrmS6m9yEpU46d+LA==,
+ }
+
+ type-fest@4.41.0:
+ resolution:
+ {
+ integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==,
+ }
+ engines: { node: ">=16" }
+
+ typescript@5.8.3:
+ resolution:
+ {
+ integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==,
+ }
+ engines: { node: ">=14.17" }
+ hasBin: true
+
+ undici-types@7.8.0:
+ resolution:
+ {
+ integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==,
+ }
+
+ update-browserslist-db@1.1.3:
+ resolution:
+ {
+ integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==,
+ }
+ hasBin: true
+ peerDependencies:
+ browserslist: ">= 4.21.0"
+
+ use-callback-ref@1.3.3:
+ resolution:
+ {
+ integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==,
+ }
+ engines: { node: ">=10" }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ use-sidecar@1.1.3:
+ resolution:
+ {
+ integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==,
+ }
+ engines: { node: ">=10" }
+ peerDependencies:
+ "@types/react": "*"
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+
+ vite@7.0.6:
+ resolution:
+ {
+ integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==,
+ }
+ engines: { node: ^20.19.0 || >=22.12.0 }
+ hasBin: true
+ peerDependencies:
+ "@types/node": ^20.19.0 || >=22.12.0
+ jiti: ">=1.21.0"
+ less: ^4.0.0
+ lightningcss: ^1.21.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: ">=0.54.8"
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ "@types/node":
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ yallist@3.1.1:
+ resolution:
+ {
+ integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==,
+ }
+
+ yallist@5.0.0:
+ resolution:
+ {
+ integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==,
+ }
+ engines: { node: ">=18" }
+
+ yaml-ast-parser@0.0.43:
+ resolution:
+ {
+ integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==,
+ }
+
+ yargs-parser@21.1.1:
+ resolution:
+ {
+ integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==,
+ }
+ engines: { node: ">=12" }
+
+snapshots:
+ "@ampproject/remapping@2.3.0":
+ dependencies:
+ "@jridgewell/gen-mapping": 0.3.12
+ "@jridgewell/trace-mapping": 0.3.29
+
+ "@babel/code-frame@7.27.1":
+ dependencies:
+ "@babel/helper-validator-identifier": 7.27.1
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ "@babel/compat-data@7.28.0": {}
+
+ "@babel/core@7.28.0":
+ dependencies:
+ "@ampproject/remapping": 2.3.0
+ "@babel/code-frame": 7.27.1
+ "@babel/generator": 7.28.0
+ "@babel/helper-compilation-targets": 7.27.2
+ "@babel/helper-module-transforms": 7.27.3(@babel/core@7.28.0)
+ "@babel/helpers": 7.28.2
+ "@babel/parser": 7.28.0
+ "@babel/template": 7.27.2
+ "@babel/traverse": 7.28.0
+ "@babel/types": 7.28.2
+ convert-source-map: 2.0.0
+ debug: 4.4.1(supports-color@10.2.2)
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ "@babel/generator@7.28.0":
+ dependencies:
+ "@babel/parser": 7.28.0
+ "@babel/types": 7.28.2
+ "@jridgewell/gen-mapping": 0.3.12
+ "@jridgewell/trace-mapping": 0.3.29
+ jsesc: 3.1.0
+
+ "@babel/helper-compilation-targets@7.27.2":
+ dependencies:
+ "@babel/compat-data": 7.28.0
+ "@babel/helper-validator-option": 7.27.1
+ browserslist: 4.25.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ "@babel/helper-globals@7.28.0": {}
+
+ "@babel/helper-module-imports@7.27.1":
+ dependencies:
+ "@babel/traverse": 7.28.0
+ "@babel/types": 7.28.2
+ transitivePeerDependencies:
+ - supports-color
+
+ "@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)":
+ dependencies:
+ "@babel/core": 7.28.0
+ "@babel/helper-module-imports": 7.27.1
+ "@babel/helper-validator-identifier": 7.27.1
+ "@babel/traverse": 7.28.0
+ transitivePeerDependencies:
+ - supports-color
+
+ "@babel/helper-plugin-utils@7.27.1": {}
+
+ "@babel/helper-string-parser@7.27.1": {}
+
+ "@babel/helper-validator-identifier@7.27.1": {}
+
+ "@babel/helper-validator-option@7.27.1": {}
+
+ "@babel/helpers@7.28.2":
+ dependencies:
+ "@babel/template": 7.27.2
+ "@babel/types": 7.28.2
+
+ "@babel/parser@7.28.0":
+ dependencies:
+ "@babel/types": 7.28.2
+
+ "@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)":
+ dependencies:
+ "@babel/core": 7.28.0
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)":
+ dependencies:
+ "@babel/core": 7.28.0
+ "@babel/helper-plugin-utils": 7.27.1
+
+ "@babel/template@7.27.2":
+ dependencies:
+ "@babel/code-frame": 7.27.1
+ "@babel/parser": 7.28.0
+ "@babel/types": 7.28.2
+
+ "@babel/traverse@7.28.0":
+ dependencies:
+ "@babel/code-frame": 7.27.1
+ "@babel/generator": 7.28.0
+ "@babel/helper-globals": 7.28.0
+ "@babel/parser": 7.28.0
+ "@babel/template": 7.27.2
+ "@babel/types": 7.28.2
+ debug: 4.4.1(supports-color@10.2.2)
+ transitivePeerDependencies:
+ - supports-color
+
+ "@babel/types@7.28.2":
+ dependencies:
+ "@babel/helper-string-parser": 7.27.1
+ "@babel/helper-validator-identifier": 7.27.1
+
+ "@biomejs/biome@2.1.3":
+ optionalDependencies:
+ "@biomejs/cli-darwin-arm64": 2.1.3
+ "@biomejs/cli-darwin-x64": 2.1.3
+ "@biomejs/cli-linux-arm64": 2.1.3
+ "@biomejs/cli-linux-arm64-musl": 2.1.3
+ "@biomejs/cli-linux-x64": 2.1.3
+ "@biomejs/cli-linux-x64-musl": 2.1.3
+ "@biomejs/cli-win32-arm64": 2.1.3
+ "@biomejs/cli-win32-x64": 2.1.3
+
+ "@biomejs/cli-darwin-arm64@2.1.3":
+ optional: true
+
+ "@biomejs/cli-darwin-x64@2.1.3":
+ optional: true
+
+ "@biomejs/cli-linux-arm64-musl@2.1.3":
+ optional: true
+
+ "@biomejs/cli-linux-arm64@2.1.3":
+ optional: true
+
+ "@biomejs/cli-linux-x64-musl@2.1.3":
+ optional: true
+
+ "@biomejs/cli-linux-x64@2.1.3":
+ optional: true
+
+ "@biomejs/cli-win32-arm64@2.1.3":
+ optional: true
+
+ "@biomejs/cli-win32-x64@2.1.3":
+ optional: true
+
+ "@esbuild/aix-ppc64@0.25.8":
+ optional: true
+
+ "@esbuild/android-arm64@0.25.8":
+ optional: true
+
+ "@esbuild/android-arm@0.25.8":
+ optional: true
+
+ "@esbuild/android-x64@0.25.8":
+ optional: true
+
+ "@esbuild/darwin-arm64@0.25.8":
+ optional: true
+
+ "@esbuild/darwin-x64@0.25.8":
+ optional: true
+
+ "@esbuild/freebsd-arm64@0.25.8":
+ optional: true
+
+ "@esbuild/freebsd-x64@0.25.8":
+ optional: true
+
+ "@esbuild/linux-arm64@0.25.8":
+ optional: true
+
+ "@esbuild/linux-arm@0.25.8":
+ optional: true
+
+ "@esbuild/linux-ia32@0.25.8":
+ optional: true
+
+ "@esbuild/linux-loong64@0.25.8":
+ optional: true
+
+ "@esbuild/linux-mips64el@0.25.8":
+ optional: true
+
+ "@esbuild/linux-ppc64@0.25.8":
+ optional: true
+
+ "@esbuild/linux-riscv64@0.25.8":
+ optional: true
+
+ "@esbuild/linux-s390x@0.25.8":
+ optional: true
+
+ "@esbuild/linux-x64@0.25.8":
+ optional: true
+
+ "@esbuild/netbsd-arm64@0.25.8":
+ optional: true
+
+ "@esbuild/netbsd-x64@0.25.8":
+ optional: true
+
+ "@esbuild/openbsd-arm64@0.25.8":
+ optional: true
+
+ "@esbuild/openbsd-x64@0.25.8":
+ optional: true
+
+ "@esbuild/openharmony-arm64@0.25.8":
+ optional: true
+
+ "@esbuild/sunos-x64@0.25.8":
+ optional: true
+
+ "@esbuild/win32-arm64@0.25.8":
+ optional: true
+
+ "@esbuild/win32-ia32@0.25.8":
+ optional: true
+
+ "@esbuild/win32-x64@0.25.8":
+ optional: true
+
+ "@floating-ui/core@1.7.3":
+ dependencies:
+ "@floating-ui/utils": 0.2.10
+
+ "@floating-ui/dom@1.7.3":
+ dependencies:
+ "@floating-ui/core": 1.7.3
+ "@floating-ui/utils": 0.2.10
+
+ "@floating-ui/react-dom@2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@floating-ui/dom": 1.7.3
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+
+ "@floating-ui/utils@0.2.10": {}
+
+ "@isaacs/fs-minipass@4.0.1":
+ dependencies:
+ minipass: 7.1.2
+
+ "@jridgewell/gen-mapping@0.3.12":
+ dependencies:
+ "@jridgewell/sourcemap-codec": 1.5.4
+ "@jridgewell/trace-mapping": 0.3.29
+
+ "@jridgewell/resolve-uri@3.1.2": {}
+
+ "@jridgewell/sourcemap-codec@1.5.4": {}
+
+ "@jridgewell/trace-mapping@0.3.29":
+ dependencies:
+ "@jridgewell/resolve-uri": 3.1.2
+ "@jridgewell/sourcemap-codec": 1.5.4
+
+ "@radix-ui/number@1.1.1": {}
+
+ "@radix-ui/primitive@1.1.2": {}
+
+ "@radix-ui/primitive@1.1.3": {}
+
+ "@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-dialog": 1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-collapsible@1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/primitive": 1.1.2
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-id": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-presence": 1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-context@1.1.2(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-dismissable-layer": 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-focus-guards": 1.1.3(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-id": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1)
+ aria-hidden: 1.2.6
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ react-remove-scroll: 2.7.1(@types/react@19.1.9)(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-direction@1.1.1(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/primitive": 1.1.2
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-escape-keydown": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-escape-keydown": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-focus-guards@1.1.2(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-id@1.1.1(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-label@2.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-popper@1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@floating-ui/react-dom": 2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-arrow": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-rect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/rect": 1.1.1
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@floating-ui/react-dom": 2.1.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-arrow": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-rect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-size": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/rect": 1.1.1
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-presence@1.1.4(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-select@2.2.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/number": 1.1.1
+ "@radix-ui/primitive": 1.1.2
+ "@radix-ui/react-collection": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-direction": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-dismissable-layer": 1.1.10(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-focus-guards": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-focus-scope": 1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-id": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-popper": 1.2.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-previous": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-visually-hidden": 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ aria-hidden: 1.2.6
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ react-remove-scroll: 2.7.1(@types/react@19.1.9)(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-slot@1.2.3(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/primitive": 1.1.3
+ "@radix-ui/react-compose-refs": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-context": 1.1.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-dismissable-layer": 1.1.11(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-id": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-popper": 1.2.8(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-portal": 1.1.9(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-presence": 1.1.5(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ "@radix-ui/react-slot": 1.2.3(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-controllable-state": 1.2.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-visually-hidden": 1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-use-effect-event": 0.0.2(@types/react@19.1.9)(react@19.1.1)
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-use-callback-ref": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-use-previous@1.1.1(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-use-rect@1.1.1(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ "@radix-ui/rect": 1.1.1
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-use-size@1.1.1(@types/react@19.1.9)(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-use-layout-effect": 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ "@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)":
+ dependencies:
+ "@radix-ui/react-primitive": 2.1.3(@types/react-dom@19.1.7(@types/react@19.1.9))(@types/react@19.1.9)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+ "@types/react-dom": 19.1.7(@types/react@19.1.9)
+
+ "@radix-ui/rect@1.1.1": {}
+
+ "@redocly/ajv@8.17.1":
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.0
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
+ "@redocly/config@0.22.2": {}
+
+ "@redocly/openapi-core@1.34.5(supports-color@10.2.2)":
+ dependencies:
+ "@redocly/ajv": 8.17.1
+ "@redocly/config": 0.22.2
+ colorette: 1.4.0
+ https-proxy-agent: 7.0.6(supports-color@10.2.2)
+ js-levenshtein: 1.1.6
+ js-yaml: 4.1.1
+ minimatch: 5.1.6
+ pluralize: 8.0.0
+ yaml-ast-parser: 0.0.43
+ transitivePeerDependencies:
+ - supports-color
+
+ "@rolldown/pluginutils@1.0.0-beta.27": {}
+
+ "@rollup/rollup-android-arm-eabi@4.45.3":
+ optional: true
+
+ "@rollup/rollup-android-arm64@4.45.3":
+ optional: true
+
+ "@rollup/rollup-darwin-arm64@4.45.3":
+ optional: true
+
+ "@rollup/rollup-darwin-x64@4.45.3":
+ optional: true
+
+ "@rollup/rollup-freebsd-arm64@4.45.3":
+ optional: true
+
+ "@rollup/rollup-freebsd-x64@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-arm-gnueabihf@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-arm-musleabihf@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-arm64-gnu@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-arm64-musl@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-loongarch64-gnu@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-ppc64-gnu@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-riscv64-gnu@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-riscv64-musl@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-s390x-gnu@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-x64-gnu@4.45.3":
+ optional: true
+
+ "@rollup/rollup-linux-x64-musl@4.45.3":
+ optional: true
+
+ "@rollup/rollup-win32-arm64-msvc@4.45.3":
+ optional: true
+
+ "@rollup/rollup-win32-ia32-msvc@4.45.3":
+ optional: true
+
+ "@rollup/rollup-win32-x64-msvc@4.45.3":
+ optional: true
+
+ "@tailwindcss/node@4.1.11":
+ dependencies:
+ "@ampproject/remapping": 2.3.0
+ enhanced-resolve: 5.18.2
+ jiti: 2.5.1
+ lightningcss: 1.30.1
+ magic-string: 0.30.17
+ source-map-js: 1.2.1
+ tailwindcss: 4.1.11
+
+ "@tailwindcss/oxide-android-arm64@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-darwin-arm64@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-darwin-x64@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-freebsd-x64@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-linux-arm64-gnu@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-linux-arm64-musl@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-linux-x64-gnu@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-linux-x64-musl@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-wasm32-wasi@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-win32-arm64-msvc@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide-win32-x64-msvc@4.1.11":
+ optional: true
+
+ "@tailwindcss/oxide@4.1.11":
+ dependencies:
+ detect-libc: 2.0.4
+ tar: 7.4.3
+ optionalDependencies:
+ "@tailwindcss/oxide-android-arm64": 4.1.11
+ "@tailwindcss/oxide-darwin-arm64": 4.1.11
+ "@tailwindcss/oxide-darwin-x64": 4.1.11
+ "@tailwindcss/oxide-freebsd-x64": 4.1.11
+ "@tailwindcss/oxide-linux-arm-gnueabihf": 4.1.11
+ "@tailwindcss/oxide-linux-arm64-gnu": 4.1.11
+ "@tailwindcss/oxide-linux-arm64-musl": 4.1.11
+ "@tailwindcss/oxide-linux-x64-gnu": 4.1.11
+ "@tailwindcss/oxide-linux-x64-musl": 4.1.11
+ "@tailwindcss/oxide-wasm32-wasi": 4.1.11
+ "@tailwindcss/oxide-win32-arm64-msvc": 4.1.11
+ "@tailwindcss/oxide-win32-x64-msvc": 4.1.11
+
+ "@tailwindcss/vite@4.1.11(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1))":
+ dependencies:
+ "@tailwindcss/node": 4.1.11
+ "@tailwindcss/oxide": 4.1.11
+ tailwindcss: 4.1.11
+ vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)
+
+ "@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.6.2)":
+ dependencies:
+ "@babel/generator": 7.28.0
+ "@babel/parser": 7.28.0
+ "@babel/traverse": 7.28.0
+ "@babel/types": 7.28.2
+ javascript-natural-sort: 0.7.1
+ lodash: 4.17.21
+ prettier: 3.6.2
+ transitivePeerDependencies:
+ - supports-color
+
+ "@types/babel__core@7.20.5":
+ dependencies:
+ "@babel/parser": 7.28.0
+ "@babel/types": 7.28.2
+ "@types/babel__generator": 7.27.0
+ "@types/babel__template": 7.4.4
+ "@types/babel__traverse": 7.28.0
+
+ "@types/babel__generator@7.27.0":
+ dependencies:
+ "@babel/types": 7.28.2
+
+ "@types/babel__template@7.4.4":
+ dependencies:
+ "@babel/parser": 7.28.0
+ "@babel/types": 7.28.2
+
+ "@types/babel__traverse@7.28.0":
+ dependencies:
+ "@babel/types": 7.28.2
+
+ "@types/estree@1.0.8": {}
+
+ "@types/lodash@4.17.20": {}
+
+ "@types/node@24.1.0":
+ dependencies:
+ undici-types: 7.8.0
+
+ "@types/react-dom@19.1.7(@types/react@19.1.9)":
+ dependencies:
+ "@types/react": 19.1.9
+
+ "@types/react@19.1.9":
+ dependencies:
+ csstype: 3.1.3
+
+ "@vitejs/plugin-react@4.7.0(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1))":
+ dependencies:
+ "@babel/core": 7.28.0
+ "@babel/plugin-transform-react-jsx-self": 7.27.1(@babel/core@7.28.0)
+ "@babel/plugin-transform-react-jsx-source": 7.27.1(@babel/core@7.28.0)
+ "@rolldown/pluginutils": 1.0.0-beta.27
+ "@types/babel__core": 7.20.5
+ react-refresh: 0.17.0
+ vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)
+ transitivePeerDependencies:
+ - supports-color
+
+ agent-base@7.1.4: {}
+
+ ansi-colors@4.1.3: {}
+
+ argparse@2.0.1: {}
+
+ aria-hidden@1.2.6:
+ dependencies:
+ tslib: 2.8.1
+
+ balanced-match@1.0.2: {}
+
+ brace-expansion@2.0.2:
+ dependencies:
+ balanced-match: 1.0.2
+
+ browserslist@4.25.1:
+ dependencies:
+ caniuse-lite: 1.0.30001731
+ electron-to-chromium: 1.5.194
+ node-releases: 2.0.19
+ update-browserslist-db: 1.1.3(browserslist@4.25.1)
+
+ caniuse-lite@1.0.30001731: {}
+
+ change-case@5.4.4: {}
+
+ chownr@3.0.0: {}
+
+ class-variance-authority@0.7.1:
+ dependencies:
+ clsx: 2.1.1
+
+ clsx@2.1.1: {}
+
+ colorette@1.4.0: {}
+
+ convert-source-map@2.0.0: {}
+
+ csstype@3.1.3: {}
+
+ debug@4.4.1(supports-color@10.2.2):
+ dependencies:
+ ms: 2.1.3
+ optionalDependencies:
+ supports-color: 10.2.2
+
+ detect-libc@2.0.4: {}
+
+ detect-node-es@1.1.0: {}
+
+ electron-to-chromium@1.5.194: {}
+
+ enhanced-resolve@5.18.2:
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.2.2
+
+ esbuild@0.25.8:
+ optionalDependencies:
+ "@esbuild/aix-ppc64": 0.25.8
+ "@esbuild/android-arm": 0.25.8
+ "@esbuild/android-arm64": 0.25.8
+ "@esbuild/android-x64": 0.25.8
+ "@esbuild/darwin-arm64": 0.25.8
+ "@esbuild/darwin-x64": 0.25.8
+ "@esbuild/freebsd-arm64": 0.25.8
+ "@esbuild/freebsd-x64": 0.25.8
+ "@esbuild/linux-arm": 0.25.8
+ "@esbuild/linux-arm64": 0.25.8
+ "@esbuild/linux-ia32": 0.25.8
+ "@esbuild/linux-loong64": 0.25.8
+ "@esbuild/linux-mips64el": 0.25.8
+ "@esbuild/linux-ppc64": 0.25.8
+ "@esbuild/linux-riscv64": 0.25.8
+ "@esbuild/linux-s390x": 0.25.8
+ "@esbuild/linux-x64": 0.25.8
+ "@esbuild/netbsd-arm64": 0.25.8
+ "@esbuild/netbsd-x64": 0.25.8
+ "@esbuild/openbsd-arm64": 0.25.8
+ "@esbuild/openbsd-x64": 0.25.8
+ "@esbuild/openharmony-arm64": 0.25.8
+ "@esbuild/sunos-x64": 0.25.8
+ "@esbuild/win32-arm64": 0.25.8
+ "@esbuild/win32-ia32": 0.25.8
+ "@esbuild/win32-x64": 0.25.8
+
+ escalade@3.2.0: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-uri@3.1.0: {}
+
+ fdir@6.4.6(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ fsevents@2.3.3:
+ optional: true
+
+ gensync@1.0.0-beta.2: {}
+
+ get-nonce@1.0.1: {}
+
+ globals@16.3.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ https-proxy-agent@7.0.6(supports-color@10.2.2):
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.1(supports-color@10.2.2)
+ transitivePeerDependencies:
+ - supports-color
+
+ immer@10.1.1: {}
+
+ index-to-position@1.2.0: {}
+
+ javascript-natural-sort@0.7.1: {}
+
+ jiti@2.5.1: {}
+
+ js-levenshtein@1.1.6: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.1.1:
+ dependencies:
+ argparse: 2.0.1
+
+ jsesc@3.1.0: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json5@2.2.3: {}
+
+ lightningcss-darwin-arm64@1.30.1:
+ optional: true
+
+ lightningcss-darwin-x64@1.30.1:
+ optional: true
+
+ lightningcss-freebsd-x64@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm-gnueabihf@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm64-gnu@1.30.1:
+ optional: true
+
+ lightningcss-linux-arm64-musl@1.30.1:
+ optional: true
+
+ lightningcss-linux-x64-gnu@1.30.1:
+ optional: true
+
+ lightningcss-linux-x64-musl@1.30.1:
+ optional: true
+
+ lightningcss-win32-arm64-msvc@1.30.1:
+ optional: true
+
+ lightningcss-win32-x64-msvc@1.30.1:
+ optional: true
+
+ lightningcss@1.30.1:
+ dependencies:
+ detect-libc: 2.0.4
+ optionalDependencies:
+ lightningcss-darwin-arm64: 1.30.1
+ lightningcss-darwin-x64: 1.30.1
+ lightningcss-freebsd-x64: 1.30.1
+ lightningcss-linux-arm-gnueabihf: 1.30.1
+ lightningcss-linux-arm64-gnu: 1.30.1
+ lightningcss-linux-arm64-musl: 1.30.1
+ lightningcss-linux-x64-gnu: 1.30.1
+ lightningcss-linux-x64-musl: 1.30.1
+ lightningcss-win32-arm64-msvc: 1.30.1
+ lightningcss-win32-x64-msvc: 1.30.1
+
+ lodash@4.17.21: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lucide-react@0.536.0(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+
+ magic-string@0.30.17:
+ dependencies:
+ "@jridgewell/sourcemap-codec": 1.5.4
+
+ minimatch@5.1.6:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minipass@7.1.2: {}
+
+ minizlib@3.0.2:
+ dependencies:
+ minipass: 7.1.2
+
+ mkdirp@3.0.1: {}
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ node-releases@2.0.19: {}
+
+ openapi-fetch@0.15.0:
+ dependencies:
+ openapi-typescript-helpers: 0.0.15
+
+ openapi-typescript-helpers@0.0.15: {}
+
+ openapi-typescript@7.10.1(typescript@5.8.3):
+ dependencies:
+ "@redocly/openapi-core": 1.34.5(supports-color@10.2.2)
+ ansi-colors: 4.1.3
+ change-case: 5.4.4
+ parse-json: 8.3.0
+ supports-color: 10.2.2
+ typescript: 5.8.3
+ yargs-parser: 21.1.1
+
+ parse-json@8.3.0:
+ dependencies:
+ "@babel/code-frame": 7.27.1
+ index-to-position: 1.2.0
+ type-fest: 4.41.0
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.3: {}
+
+ pluralize@8.0.0: {}
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prettier-plugin-tailwindcss@0.6.14(@trivago/prettier-plugin-sort-imports@5.2.2(prettier@3.6.2))(prettier@3.6.2):
+ dependencies:
+ prettier: 3.6.2
+ optionalDependencies:
+ "@trivago/prettier-plugin-sort-imports": 5.2.2(prettier@3.6.2)
+
+ prettier@3.6.2: {}
+
+ react-dom@19.1.1(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+ scheduler: 0.26.0
+
+ react-number-format@5.4.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+
+ react-refresh@0.17.0: {}
+
+ react-remove-scroll-bar@2.3.8(@types/react@19.1.9)(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+ react-style-singleton: 2.2.3(@types/react@19.1.9)(react@19.1.1)
+ tslib: 2.8.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ react-remove-scroll@2.7.1(@types/react@19.1.9)(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+ react-remove-scroll-bar: 2.3.8(@types/react@19.1.9)(react@19.1.1)
+ react-style-singleton: 2.2.3(@types/react@19.1.9)(react@19.1.1)
+ tslib: 2.8.1
+ use-callback-ref: 1.3.3(@types/react@19.1.9)(react@19.1.1)
+ use-sidecar: 1.1.3(@types/react@19.1.9)(react@19.1.1)
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ react-style-singleton@2.2.3(@types/react@19.1.9)(react@19.1.1):
+ dependencies:
+ get-nonce: 1.0.1
+ react: 19.1.1
+ tslib: 2.8.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ react@19.1.1: {}
+
+ require-from-string@2.0.2: {}
+
+ rollup@4.45.3:
+ dependencies:
+ "@types/estree": 1.0.8
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi": 4.45.3
+ "@rollup/rollup-android-arm64": 4.45.3
+ "@rollup/rollup-darwin-arm64": 4.45.3
+ "@rollup/rollup-darwin-x64": 4.45.3
+ "@rollup/rollup-freebsd-arm64": 4.45.3
+ "@rollup/rollup-freebsd-x64": 4.45.3
+ "@rollup/rollup-linux-arm-gnueabihf": 4.45.3
+ "@rollup/rollup-linux-arm-musleabihf": 4.45.3
+ "@rollup/rollup-linux-arm64-gnu": 4.45.3
+ "@rollup/rollup-linux-arm64-musl": 4.45.3
+ "@rollup/rollup-linux-loongarch64-gnu": 4.45.3
+ "@rollup/rollup-linux-ppc64-gnu": 4.45.3
+ "@rollup/rollup-linux-riscv64-gnu": 4.45.3
+ "@rollup/rollup-linux-riscv64-musl": 4.45.3
+ "@rollup/rollup-linux-s390x-gnu": 4.45.3
+ "@rollup/rollup-linux-x64-gnu": 4.45.3
+ "@rollup/rollup-linux-x64-musl": 4.45.3
+ "@rollup/rollup-win32-arm64-msvc": 4.45.3
+ "@rollup/rollup-win32-ia32-msvc": 4.45.3
+ "@rollup/rollup-win32-x64-msvc": 4.45.3
+ fsevents: 2.3.3
+
+ scheduler@0.26.0: {}
+
+ semver@6.3.1: {}
+
+ source-map-js@1.2.1: {}
+
+ supports-color@10.2.2: {}
+
+ tailwind-merge@3.3.1: {}
+
+ tailwindcss@4.1.11: {}
+
+ tapable@2.2.2: {}
+
+ tar@7.4.3:
+ dependencies:
+ "@isaacs/fs-minipass": 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.2
+ minizlib: 3.0.2
+ mkdirp: 3.0.1
+ yallist: 5.0.0
+
+ tinyglobby@0.2.14:
+ dependencies:
+ fdir: 6.4.6(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tslib@2.8.1: {}
+
+ tw-animate-css@1.3.6: {}
+
+ type-fest@4.41.0: {}
+
+ typescript@5.8.3: {}
+
+ undici-types@7.8.0: {}
+
+ update-browserslist-db@1.1.3(browserslist@4.25.1):
+ dependencies:
+ browserslist: 4.25.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ use-callback-ref@1.3.3(@types/react@19.1.9)(react@19.1.1):
+ dependencies:
+ react: 19.1.1
+ tslib: 2.8.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ use-sidecar@1.1.3(@types/react@19.1.9)(react@19.1.1):
+ dependencies:
+ detect-node-es: 1.1.0
+ react: 19.1.1
+ tslib: 2.8.1
+ optionalDependencies:
+ "@types/react": 19.1.9
+
+ vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1):
+ dependencies:
+ esbuild: 0.25.8
+ fdir: 6.4.6(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.45.3
+ tinyglobby: 0.2.14
+ optionalDependencies:
+ "@types/node": 24.1.0
+ fsevents: 2.3.3
+ jiti: 2.5.1
+ lightningcss: 1.30.1
+
+ yallist@3.1.1: {}
+
+ yallist@5.0.0: {}
+
+ yaml-ast-parser@0.0.43: {}
+
+ yargs-parser@21.1.1: {}
diff --git a/frontend/src/app.css b/frontend/src/app.css
new file mode 100644
index 0000000..f4c1e9b
--- /dev/null
+++ b/frontend/src/app.css
@@ -0,0 +1,120 @@
+@import "tailwindcss";
+@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+:root {
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx
new file mode 100644
index 0000000..062a792
--- /dev/null
+++ b/frontend/src/app.tsx
@@ -0,0 +1,26 @@
+"use client";
+
+import { GeneralSettings } from "./components/configGeneral";
+import { GlobalSettings } from "./components/configGlobal";
+import { NotificationSettings } from "./components/configNotification";
+import { TicketsConfig } from "./components/configTickets";
+import { ConfigProvider } from "./providers/config";
+import { ThemeProvider } from "./providers/theme";
+
+function App() {
+ return (
+
+
+
+
Twitchets Configuration
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/frontend/src/components/buttonReset.tsx b/frontend/src/components/buttonReset.tsx
new file mode 100644
index 0000000..d2b9844
--- /dev/null
+++ b/frontend/src/components/buttonReset.tsx
@@ -0,0 +1,41 @@
+import { Button } from "./ui/button";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "./ui/tooltip";
+import { Globe, RotateCcw } from "lucide-react";
+
+interface ResetButtonProps {
+ onClick: () => void;
+ resetType: "default" | "global";
+}
+
+export function ResetButton({ onClick, resetType }: ResetButtonProps) {
+ const Icon = resetType === "default" ? RotateCcw : Globe;
+
+ const tooltip =
+ resetType === "default" ? "Reset to default" : "Reset to global setting";
+
+ return (
+
+
+
+
+
+
+
+
+ {tooltip}
+
+
+
+ );
+}
diff --git a/frontend/src/components/buttonsSaveDiscard.tsx b/frontend/src/components/buttonsSaveDiscard.tsx
new file mode 100644
index 0000000..707b3a8
--- /dev/null
+++ b/frontend/src/components/buttonsSaveDiscard.tsx
@@ -0,0 +1,33 @@
+import { Button } from "./ui/button";
+
+interface SaveDiscardButtonsProps {
+ onSave: () => void;
+ onDiscard: () => void;
+}
+
+export function SaveDiscardButtons({
+ onSave,
+ onDiscard,
+}: SaveDiscardButtonsProps) {
+ const handleSave = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ onSave();
+ };
+
+ const handleDiscard = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ onDiscard();
+ };
+
+ return (
+
+
+ Discard
+
+
+
+ Save
+
+
+ );
+}
diff --git a/frontend/src/components/cardCollapsible.tsx b/frontend/src/components/cardCollapsible.tsx
new file mode 100644
index 0000000..a67d456
--- /dev/null
+++ b/frontend/src/components/cardCollapsible.tsx
@@ -0,0 +1,69 @@
+"use client";
+
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger,
+} from "@radix-ui/react-collapsible";
+import { ChevronsDownUp, ChevronsUpDown } from "lucide-react";
+import { type ReactNode, useState } from "react";
+
+interface CollapsibleCardProps {
+ title: string;
+ description?: string;
+ isOpen?: boolean;
+ setIsOpen?: (isOpen: boolean) => void;
+ action?: ReactNode;
+ children?: ReactNode;
+}
+
+export function CollapsibleCard({
+ title,
+ description,
+ isOpen: isOpenProp,
+ setIsOpen: setIsOpenProp,
+ action,
+ children,
+}: CollapsibleCardProps) {
+ const [isOpenInternal, setIsOpenInteral] = useState(false);
+
+ const isOpen = isOpenProp ?? isOpenInternal;
+ const setIsOpen = setIsOpenProp ?? setIsOpenInteral;
+
+ const toggleCollapse = () => {
+ setIsOpen(!isOpen);
+ };
+
+ return (
+
+
+
+
+
+ {title}
+ {description && {description} }
+
+
+ {action}
+ {isOpen ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {children}
+
+
+
+ );
+}
diff --git a/frontend/src/components/configCommon.tsx b/frontend/src/components/configCommon.tsx
new file mode 100644
index 0000000..feaf5b3
--- /dev/null
+++ b/frontend/src/components/configCommon.tsx
@@ -0,0 +1,86 @@
+import { ConfigField } from "./configField";
+import { Regions } from "./configRegions";
+import type { CommonConfig } from "@/types/config";
+
+interface CommonFieldsProps {
+ commonConfig: CommonConfig;
+ globalCommonConfig?: CommonConfig; // Unset if common config IS global config
+ updateCommonConfig: (config: CommonConfig) => void;
+}
+
+export function CommonFields({
+ commonConfig: config,
+ globalCommonConfig: globalConfig,
+ updateCommonConfig: updateConfig,
+}: CommonFieldsProps) {
+ return (
+
+
{
+ updateConfig({ ...config, regions: value });
+ }}
+ />
+
+
+ {
+ updateConfig({ ...config, eventSimilarity: value });
+ }}
+ />
+
+ {
+ updateConfig({ ...config, numTickets: value });
+ }}
+ />
+
+ {
+ updateConfig({ ...config, maxTicketPrice: value });
+ }}
+ />
+
+ {
+ updateConfig({ ...config, discount: value });
+ }}
+ />
+
+
+ );
+}
diff --git a/frontend/src/components/configField.tsx b/frontend/src/components/configField.tsx
new file mode 100644
index 0000000..4a8602f
--- /dev/null
+++ b/frontend/src/components/configField.tsx
@@ -0,0 +1,129 @@
+import { LinkedStatusTooltip } from "./statusLinked";
+import { ResetButton } from "@/components/buttonReset";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { NumericFormat } from "react-number-format";
+
+interface ConfigFieldProps {
+ label: string;
+ description: string;
+ placeholder?: string;
+ type: "text" | "number" | "integer" | "fraction" | "percentage" | "price";
+ value?: T;
+ globalFallbackValue?: T;
+ withGlobalFallback?: boolean;
+ showReset?: boolean;
+ updateValue: (newValue?: T) => void;
+}
+
+export function ConfigField({
+ label,
+ description,
+ placeholder,
+ type,
+ value,
+ withGlobalFallback = false,
+ globalFallbackValue = undefined,
+ showReset = true,
+ updateValue,
+}: ConfigFieldProps) {
+ // Determine the field value to display
+ let fieldValue = value;
+ let isLinkedToGlobal = false;
+ if (fieldValue === undefined && withGlobalFallback) {
+ // If no value is set, and we want to use global fallback
+ fieldValue = globalFallbackValue;
+ isLinkedToGlobal = true;
+ } else if (typeof fieldValue === "number" && fieldValue < 0) {
+ // If value is negative number, set undefined to show placeholder
+ fieldValue = undefined;
+ }
+
+ // Determine the reset value based on type
+ let resetValue: T;
+ if (type === "text") {
+ resetValue = "" as T;
+ } else {
+ resetValue = -1 as T;
+ }
+
+ const renderInput = () => {
+ if (type === "text") {
+ return (
+ updateValue(event.target.value as T)}
+ />
+ );
+ }
+
+ // TODO better to use use-mask-input - but this currently reverses numbers???
+ return (
+ {
+ // Only update if the value is not undefined
+ // This prevents reverting from -1 (which is displayed as undefined) back to undefined
+ if (values.floatValue !== undefined) {
+ updateValue(values.floatValue as T);
+ }
+ }}
+ isAllowed={(values) => {
+ const val = values.floatValue;
+ if (val === undefined) return true;
+ if (type === "fraction") return val <= 1;
+ if (type === "percentage") return val <= 100;
+ return true;
+ }}
+ suffix={type === "percentage" ? "%" : ""}
+ prefix={type === "price" ? "£" : ""}
+ />
+ );
+ };
+
+ return (
+
+
+
+ {label}
+
+ {withGlobalFallback && (
+
+ )}
+
+
+
+ {withGlobalFallback && (
+ {
+ // The global button sets the value to "undefined".
+ // This causes the global value to be inherited.
+ updateValue(undefined);
+ }}
+ />
+ )}
+
+ {showReset && (
+ {
+ updateValue(resetValue);
+ }}
+ />
+ )}
+
+
+
+
{description}
+
+ {renderInput()}
+
+ );
+}
diff --git a/frontend/src/components/configGeneral.tsx b/frontend/src/components/configGeneral.tsx
new file mode 100644
index 0000000..306ab0e
--- /dev/null
+++ b/frontend/src/components/configGeneral.tsx
@@ -0,0 +1,119 @@
+"use client";
+
+import { useConfig } from "../providers/config";
+import type { Country } from "../types/config";
+import { SaveDiscardButtons } from "./buttonsSaveDiscard";
+import { CollapsibleCard } from "./cardCollapsible";
+import { Button } from "./ui/button";
+import { Input } from "./ui/input";
+import { Label } from "./ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectTrigger,
+ SelectValue,
+ SelectItem,
+} from "./ui/select";
+import { isEqual } from "lodash";
+import { Eye, EyeOff } from "lucide-react";
+import { useState, useEffect } from "react";
+
+export function GeneralSettings() {
+ const { config, updateConfig } = useConfig();
+
+ const [draft, setDraft] = useState({
+ apiKey: config.apiKey,
+ country: config.country,
+ });
+ const [showApiKey, setShowApiKey] = useState(false);
+
+ // If the canonical config changes, reset the draft
+ useEffect(() => {
+ setDraft({ apiKey: config.apiKey, country: config.country });
+ }, [config.apiKey, config.country]);
+
+ const hasChanges = !isEqual(draft, {
+ apiKey: config.apiKey,
+ country: config.country,
+ });
+
+ const toggleShowApiKey = () => {
+ setShowApiKey(!showApiKey);
+ };
+
+ return (
+ {
+ updateConfig((config) => {
+ config.apiKey = draft.apiKey;
+ config.country = draft.country;
+ });
+ }}
+ onDiscard={() => {
+ setDraft({
+ apiKey: config.apiKey,
+ country: config.country,
+ });
+ }}
+ />
+ )
+ }
+ >
+
+
+
API Key
+
+ Twickets API key (required)
+
+
+ {
+ setDraft((prev) => ({ ...prev, apiKey: event.target.value }));
+ }}
+ />
+
+ {showApiKey ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
Country
+
+ Currently only GB is supported
+
+
{
+ setDraft((prev) => ({ ...prev, country: value as Country }));
+ }}
+ >
+
+
+
+
+ GB (United Kingdom)
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/configGlobal.tsx b/frontend/src/components/configGlobal.tsx
new file mode 100644
index 0000000..b7d1f72
--- /dev/null
+++ b/frontend/src/components/configGlobal.tsx
@@ -0,0 +1,46 @@
+import { useConfig } from "../providers/config";
+import { SaveDiscardButtons } from "./buttonsSaveDiscard";
+import { CollapsibleCard } from "./cardCollapsible";
+import { CommonFields } from "./configCommon";
+import { isEqual } from "lodash";
+import { useState, useEffect } from "react";
+
+export function GlobalSettings() {
+ const { config, updateConfig } = useConfig();
+
+ const [draft, setDraft] = useState(config.global);
+
+ useEffect(() => {
+ setDraft(config.global);
+ }, [config.global]);
+
+ const hasChanges = !isEqual(draft, config.global);
+
+ return (
+ {
+ updateConfig((config) => {
+ config.global = draft;
+ });
+ }}
+ onDiscard={() => {
+ setDraft(config.global);
+ }}
+ />
+ )
+ }
+ >
+ {
+ setDraft(commonConfig);
+ }}
+ />
+
+ );
+}
diff --git a/frontend/src/components/configNotification.tsx b/frontend/src/components/configNotification.tsx
new file mode 100644
index 0000000..3523494
--- /dev/null
+++ b/frontend/src/components/configNotification.tsx
@@ -0,0 +1,190 @@
+"use client";
+
+import { useConfig } from "../providers/config";
+import { SaveDiscardButtons } from "./buttonsSaveDiscard";
+import { CollapsibleCard } from "./cardCollapsible";
+import { Button } from "./ui/button";
+import { Checkbox } from "./ui/checkbox";
+import { Input } from "./ui/input";
+import { Label } from "./ui/label";
+import { Separator } from "./ui/separator";
+import type { NtfyConfig } from "@/types/config";
+import { isEqual, omit } from "lodash";
+import { Eye, EyeOff } from "lucide-react";
+import { useState, useEffect } from "react";
+
+const defaultNtfyUrl = "https://ntfy.sh";
+
+function newNtfyConfig(): NtfyConfig {
+ return {
+ url: defaultNtfyUrl,
+ topic: "",
+ username: "",
+ password: "",
+ };
+}
+
+export function NotificationSettings() {
+ const { config, updateConfig } = useConfig();
+
+ const [draft, setDraft] = useState(config.notification);
+ const [showNtfyPassword, setShowNtfyPassword] = useState(false);
+
+ useEffect(() => {
+ setDraft(config.notification);
+ }, [config.notification]);
+
+ const hasChanges = !isEqual(draft, config.notification);
+
+ const toggleShowNtfyPassword = () => {
+ setShowNtfyPassword(!showNtfyPassword);
+ };
+
+ return (
+ {
+ updateConfig((config) => {
+ config.notification = draft;
+ });
+ }}
+ onDiscard={() => {
+ setDraft(config.notification);
+ }}
+ />
+ )
+ }
+ >
+
+
+
+
Ntfy
+ {
+ setDraft((prev) => {
+ if (checked) {
+ return {
+ ...prev,
+ ntfy: newNtfyConfig(),
+ };
+ } else {
+ return omit(prev, "ntfy");
+ }
+ });
+ }}
+ />
+ Enabled
+
+
+ {draft.ntfy && (
+ <>
+
+
+
+
+ Username
+ {
+ setDraft((prev) => ({
+ ...prev,
+ ntfy: {
+ ...(prev.ntfy || newNtfyConfig()),
+ username: event.target.value,
+ },
+ }));
+ }}
+ />
+
+
+
+
Password
+
+ {
+ setDraft((prev) => ({
+ ...prev,
+ ntfy: {
+ ...(prev.ntfy || newNtfyConfig()),
+ password: event.target.value,
+ },
+ }));
+ }}
+ />
+
+ {showNtfyPassword ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ >
+ )}
+
+
+
+ Telegram and Gotify notification settings not yet supported in user
+ interface
+
+
+
+ );
+}
diff --git a/frontend/src/components/configRegions.tsx b/frontend/src/components/configRegions.tsx
new file mode 100644
index 0000000..cdd32bc
--- /dev/null
+++ b/frontend/src/components/configRegions.tsx
@@ -0,0 +1,98 @@
+import { LinkedStatusTooltip } from "./statusLinked";
+import { Checkbox } from "./ui/checkbox";
+import { ResetButton } from "@/components/buttonReset";
+import { Label } from "@/components/ui/label";
+import { REGIONS } from "@/constants/regions";
+import type { Region } from "@/types/config";
+
+interface RegionsProps {
+ label?: string;
+ description?: string;
+ value?: Region[];
+ withGlobalFallback?: boolean;
+ globalFallbackValue?: Region[];
+ updateValue: (newValue?: Region[]) => void;
+}
+
+export function Regions({
+ label = "Regions",
+ description = "If no regions selected, all regions will be used",
+ value,
+ withGlobalFallback = false,
+ globalFallbackValue,
+ updateValue,
+}: RegionsProps) {
+ // Determine the field value to display
+ let fieldValue = value;
+ let isLinkedToGlobal = false;
+ if (fieldValue === undefined && withGlobalFallback) {
+ // If no value is set, and we want to use global fallback
+ fieldValue = globalFallbackValue;
+ isLinkedToGlobal = true;
+ }
+
+ const currentRegions = fieldValue || [];
+ const resetValue: Region[] = [];
+
+ const handleOnCheckedChange = (regionCode: Region, checked: boolean) => {
+ const regionSet = new Set(currentRegions);
+
+ if (checked) {
+ regionSet.add(regionCode);
+ } else {
+ regionSet.delete(regionCode);
+ }
+
+ updateValue(Array.from(regionSet).sort());
+ };
+
+ return (
+
+
+
+ {label}
+
+ {withGlobalFallback && (
+
+ )}
+
+
+
+ {withGlobalFallback && (
+ {
+ // The global button sets the value to "undefined".
+ // This causes the global value to be inherited.
+ updateValue(undefined);
+ }}
+ />
+ )}
+
+ {
+ updateValue(resetValue);
+ }}
+ />
+
+
+
+
{description}
+
+
+ {REGIONS.map((region) => (
+
+ {
+ handleOnCheckedChange(region.code as Region, checked);
+ }}
+ />
+ {region.name}
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/components/configTicket.tsx b/frontend/src/components/configTicket.tsx
new file mode 100644
index 0000000..2c3dd7c
--- /dev/null
+++ b/frontend/src/components/configTicket.tsx
@@ -0,0 +1,119 @@
+"use client";
+
+import { SaveDiscardButtons } from "./buttonsSaveDiscard";
+import { CollapsibleCard } from "./cardCollapsible";
+import { CommonFields } from "./configCommon";
+import { ConfigField } from "./configField";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog";
+import { Button } from "@/components/ui/button";
+import type { CommonConfig, TicketConfig } from "@/types/config";
+import { isEqual } from "lodash";
+import { Trash, } from "lucide-react";
+import { useEffect, useState } from "react";
+
+interface TicketProps {
+ ticketConfig: TicketConfig;
+ globalConfig: CommonConfig;
+ isOpen?: boolean;
+ onUpdate: (updatedTicket: TicketConfig) => void;
+ onRemove: () => void;
+}
+
+export function Ticket({
+ ticketConfig,
+ globalConfig,
+ onUpdate,
+ onRemove,
+}: TicketProps) {
+ const [draft, setDraft] = useState(ticketConfig);
+
+ useEffect(() => {
+ setDraft(ticketConfig);
+ }, [ticketConfig]);
+
+ const hasChanges = !isEqual(ticketConfig, draft);
+
+ return (
+
+ {hasChanges && (
+ {
+ onUpdate(draft);
+ }}
+ onDiscard={() => {
+ setDraft(ticketConfig);
+ }}
+ />
+ )}
+
+
+
+ e.stopPropagation()}
+ >
+
+
+
+
+
+ Delete Ticket
+
+ Are you sure you want to delete "{ticketConfig.event}"? This
+ action cannot be undone.
+
+
+
+ Cancel
+
+ Delete
+
+
+
+
+
+ }
+ >
+
+ {
+ if (typeof value === "string") {
+ setDraft((prev) => ({ ...prev, event: value }));
+ }
+ }}
+ />
+
+ {
+ setDraft((prev) => ({ ...prev, ...commonConfig }));
+ }}
+ />
+
+
+ );
+}
diff --git a/frontend/src/components/configTickets.tsx b/frontend/src/components/configTickets.tsx
new file mode 100644
index 0000000..8ad566b
--- /dev/null
+++ b/frontend/src/components/configTickets.tsx
@@ -0,0 +1,122 @@
+"use client";
+
+import { useConfig } from "../providers/config";
+import { Ticket } from "./configTicket";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "./ui/card";
+import { Input } from "./ui/input";
+import { Button } from "@/components/ui/button";
+import type { TicketConfig } from "@/types/config";
+import { Plus } from "lucide-react";
+import { useMemo, useState } from "react";
+
+export function TicketsConfig() {
+ const { config, updateConfig } = useConfig();
+
+ const [_, setIsTicketsConfigOpen] = useState(false);
+ const [filterText, setFilterText] = useState("");
+
+ const newEventName = "New Event";
+
+ // Get tickets (ensuring they are ordered)
+ const tickets = useMemo(() => {
+ return sortTickets(config.tickets);
+ }, [config.tickets]);
+
+ const handleAddTicket = () => {
+ const newTickets = [...tickets, { event: newEventName }];
+ updateConfig((config) => {
+ config.tickets = sortTickets(newTickets);
+ });
+ setIsTicketsConfigOpen(true);
+ };
+
+ const handleUpdateTicket = (updatedTicket: TicketConfig, index: number) => {
+ const newTickets = [...tickets];
+ newTickets[index] = updatedTicket;
+
+ updateConfig((config) => {
+ config.tickets = sortTickets(newTickets);
+ });
+ };
+
+ const handleRemoveTicket = (index: number) => {
+ updateConfig((config) => {
+ const newTickets = [...config.tickets];
+ newTickets.splice(index, 1);
+ config.tickets = newTickets;
+ });
+ };
+
+ return (
+
+
+
+ Ticket Configuration
+
+ Individual ticket configuration (overrides global configuration)
+
+
+ {
+ e.stopPropagation();
+ handleAddTicket();
+ }}
+ >
+
+ Add Ticket
+
+
+
+
+
setFilterText(event.target.value)}
+ />
+ {config.tickets.length === 0 ? (
+
+ No tickets configured. Click "Add Ticket" to get started.
+
+ ) : (
+ tickets
+ .filter((ticket) =>
+ ticket.event.toLowerCase().includes(filterText.toLowerCase()),
+ )
+ .map((ticket, ticketIndex) => {
+ return (
+
+ handleUpdateTicket(updatedTicket, ticketIndex)
+ }
+ onRemove={() => handleRemoveTicket(ticketIndex)}
+ />
+ );
+ })
+ )}
+
+
+
+ );
+}
+
+function sortTickets(tickets: TicketConfig[]): TicketConfig[] {
+ return [...tickets].sort((a, b) => {
+ // "New Event" should always appear first
+ if (a.event === "New Event") return -1;
+ if (b.event === "New Event") return 1;
+
+ // Otherwise, sort alphabetically
+ return a.event.localeCompare(b.event);
+ });
+}
diff --git a/frontend/src/components/statusLinked.tsx b/frontend/src/components/statusLinked.tsx
new file mode 100644
index 0000000..a1d0214
--- /dev/null
+++ b/frontend/src/components/statusLinked.tsx
@@ -0,0 +1,31 @@
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "./ui/tooltip";
+import { Link, Unlink } from "lucide-react";
+
+interface LinkedStatusTooltipProps {
+ isLinked: boolean;
+}
+
+export function LinkedStatusTooltip({ isLinked }: LinkedStatusTooltipProps) {
+ const Icon = isLinked ? Link : Unlink;
+ const text = isLinked
+ ? "Linked to global value"
+ : "Unlinked from global value";
+
+ return (
+
+
+
+
+
+
+ {text}
+
+
+
+ );
+}
diff --git a/frontend/src/components/ui/alert-dialog.tsx b/frontend/src/components/ui/alert-dialog.tsx
new file mode 100644
index 0000000..0863e40
--- /dev/null
+++ b/frontend/src/components/ui/alert-dialog.tsx
@@ -0,0 +1,157 @@
+"use client"
+
+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"
+
+function AlertDialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function AlertDialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ )
+}
+
+function AlertDialogHeader({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDialogFooter({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogAction({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function AlertDialogCancel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel,
+}
diff --git a/frontend/src/components/ui/alert.tsx b/frontend/src/components/ui/alert.tsx
new file mode 100644
index 0000000..1421354
--- /dev/null
+++ b/frontend/src/components/ui/alert.tsx
@@ -0,0 +1,66 @@
+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 px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
+ {
+ variants: {
+ variant: {
+ default: "bg-card text-card-foreground",
+ destructive:
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Alert, AlertTitle, AlertDescription }
diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx
new file mode 100644
index 0000000..21409a0
--- /dev/null
+++ b/frontend/src/components/ui/button.tsx
@@ -0,0 +1,60 @@
+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 transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ "icon-sm": "size-8",
+ "icon-lg": "size-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/frontend/src/components/ui/card.tsx b/frontend/src/components/ui/card.tsx
new file mode 100644
index 0000000..681ad98
--- /dev/null
+++ b/frontend/src/components/ui/card.tsx
@@ -0,0 +1,92 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export {
+ Card,
+ CardHeader,
+ CardFooter,
+ CardTitle,
+ CardAction,
+ CardDescription,
+ CardContent,
+}
diff --git a/frontend/src/components/ui/checkbox.tsx b/frontend/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..0e2a6cd
--- /dev/null
+++ b/frontend/src/components/ui/checkbox.tsx
@@ -0,0 +1,30 @@
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { CheckIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Checkbox({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+ )
+}
+
+export { Checkbox }
diff --git a/frontend/src/components/ui/collapsible.tsx b/frontend/src/components/ui/collapsible.tsx
new file mode 100644
index 0000000..ae9fad0
--- /dev/null
+++ b/frontend/src/components/ui/collapsible.tsx
@@ -0,0 +1,33 @@
+"use client"
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+function Collapsible({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function CollapsibleTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CollapsibleContent({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/frontend/src/components/ui/input.tsx b/frontend/src/components/ui/input.tsx
new file mode 100644
index 0000000..8916905
--- /dev/null
+++ b/frontend/src/components/ui/input.tsx
@@ -0,0 +1,21 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/frontend/src/components/ui/label.tsx b/frontend/src/components/ui/label.tsx
new file mode 100644
index 0000000..ef7133a
--- /dev/null
+++ b/frontend/src/components/ui/label.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+
+import { cn } from "@/lib/utils"
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Label }
diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx
new file mode 100644
index 0000000..25e5439
--- /dev/null
+++ b/frontend/src/components/ui/select.tsx
@@ -0,0 +1,187 @@
+"use client"
+
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "popper",
+ align = "center",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/frontend/src/components/ui/separator.tsx b/frontend/src/components/ui/separator.tsx
new file mode 100644
index 0000000..bb3ad74
--- /dev/null
+++ b/frontend/src/components/ui/separator.tsx
@@ -0,0 +1,26 @@
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/frontend/src/components/ui/tooltip.tsx b/frontend/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..715bf76
--- /dev/null
+++ b/frontend/src/components/ui/tooltip.tsx
@@ -0,0 +1,59 @@
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/lib/utils"
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ )
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/frontend/src/constants/regions.ts b/frontend/src/constants/regions.ts
new file mode 100644
index 0000000..1184c04
--- /dev/null
+++ b/frontend/src/constants/regions.ts
@@ -0,0 +1,19 @@
+export interface Region {
+ code: string;
+ name: string;
+}
+
+export const REGIONS: Region[] = [
+ { code: "GBLO", name: "London" },
+ { code: "GBSO", name: "South" },
+ { code: "GBSW", name: "South West" },
+ { code: "GBSE", name: "South East" },
+ { code: "GBMI", name: "Midlands" },
+ { code: "GBEA", name: "East Anglia" },
+ { code: "GBNO", name: "North" },
+ { code: "GBNE", name: "North East" },
+ { code: "GBNW", name: "North West" },
+ { code: "GBSC", name: "Scotland" },
+ { code: "GBWA", name: "Wales" },
+ { code: "GBNI", name: "Northern Ireland" },
+];
diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts
new file mode 100644
index 0000000..31575de
--- /dev/null
+++ b/frontend/src/lib/api.ts
@@ -0,0 +1,27 @@
+import type { Config } from "../types/config";
+import type { paths } from "../types/openapi";
+import createClient from "openapi-fetch";
+
+const client = createClient({
+ baseUrl: import.meta.env.VITE_API_BASE_URL || `${window.location.origin}/api`,
+});
+
+export async function getConfig(): Promise {
+ const { data, error } = await client.GET("/config", {});
+ if (error) {
+ throw new Error(`Failed to fetch config: ${error}`);
+ }
+ if (!data) {
+ throw new Error("No config data received");
+ }
+ return data;
+}
+
+export async function putConfig(config: Config): Promise {
+ const { error } = await client.PUT("/config", {
+ body: config,
+ });
+ if (error) {
+ throw new Error(`Failed to update config: ${error}`);
+ }
+}
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
new file mode 100644
index 0000000..a5ef193
--- /dev/null
+++ b/frontend/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/frontend/src/main.tsx b/frontend/src/main.tsx
new file mode 100644
index 0000000..409087b
--- /dev/null
+++ b/frontend/src/main.tsx
@@ -0,0 +1,14 @@
+import App from "./app";
+import "./app.css";
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+
+const node = document.getElementById("root");
+if (!node) throw new Error("Root element not found");
+
+const root = createRoot(node);
+root.render(
+
+
+ ,
+);
diff --git a/frontend/src/providers/config.tsx b/frontend/src/providers/config.tsx
new file mode 100644
index 0000000..9e7bb8a
--- /dev/null
+++ b/frontend/src/providers/config.tsx
@@ -0,0 +1,131 @@
+import { Alert, AlertDescription, AlertTitle } from "../components/ui/alert";
+import { getConfig, putConfig } from "../lib/api";
+import type { Config } from "../types/config";
+import { produce } from "immer";
+import { TriangleAlertIcon, Save } from "lucide-react";
+import {
+ createContext,
+ type ReactNode,
+ useContext,
+ useEffect,
+ useState,
+} from "react";
+
+function newConfig(): Config {
+ return {
+ apiKey: "",
+ country: "GB",
+ notification: {},
+ global: {},
+ tickets: [],
+ };
+}
+
+type ConfigUpdater = (config: Config) => void;
+
+type ConfigProviderState = {
+ config: Config;
+ updateConfig: (updater: ConfigUpdater) => void;
+ saved: boolean;
+ error: string | null;
+};
+
+const ConfigProviderContext = createContext({
+ config: newConfig(),
+ updateConfig: () => null,
+ saved: false,
+ error: null,
+});
+
+type ConfigProviderProps = {
+ children: ReactNode;
+};
+
+export function ConfigProvider({ children, ...props }: ConfigProviderProps) {
+ const [config, setConfig] = useState(newConfig());
+ const [saved, setSaved] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const loadConfig = async () => {
+ try {
+ const serverConfig = await getConfig();
+ setConfig(serverConfig);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to load config");
+ }
+ };
+
+ loadConfig();
+ }, []);
+
+ useEffect(() => {
+ if (!saved) return;
+ const timer = setTimeout(() => setSaved(false), 5000);
+ return () => clearTimeout(timer);
+ }, [saved]);
+
+ useEffect(() => {
+ if (!error) return;
+ const timer = setTimeout(() => setError(null), 5000);
+ return () => clearTimeout(timer);
+ }, [error]);
+
+ const updateConfig = async (updater: ConfigUpdater) => {
+ const newConfig = produce(config, updater);
+ setConfig(newConfig);
+
+ try {
+ await putConfig(newConfig);
+ setError(null);
+ setSaved(true);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Failed to update config");
+ // Revert on error
+ setConfig(config);
+ }
+ };
+
+ const value = {
+ config,
+ updateConfig,
+ saved,
+ error,
+ };
+
+ return (
+
+ {children}
+ {saved && (
+
+
+
+ Saved
+
+ Changes have been saved
+
+
+
+ )}
+ {error && (
+
+
+
+ Error
+
+ {error}
+
+
+
+ )}
+
+ );
+}
+
+export const useConfig = () => {
+ const context = useContext(ConfigProviderContext);
+ if (context === undefined)
+ throw new Error("useConfig must be used within a ConfigProvider");
+
+ return context;
+};
diff --git a/frontend/src/providers/theme.tsx b/frontend/src/providers/theme.tsx
new file mode 100644
index 0000000..424b3b5
--- /dev/null
+++ b/frontend/src/providers/theme.tsx
@@ -0,0 +1,71 @@
+import { createContext, useContext, useEffect, useState } from "react";
+
+type Theme = "dark" | "light" | "system";
+
+type ThemeProviderState = {
+ theme: Theme;
+ setTheme: (theme: Theme) => void;
+};
+
+const ThemeProviderContext = createContext({
+ theme: "system",
+ setTheme: () => null,
+});
+
+type ThemeProviderProps = {
+ children: React.ReactNode;
+ defaultTheme?: Theme;
+ storageKey?: string;
+};
+
+export function ThemeProvider({
+ children,
+ defaultTheme = "system",
+ storageKey = "vite-ui-theme",
+ ...props
+}: ThemeProviderProps) {
+ const [theme, setTheme] = useState(
+ () => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
+ );
+
+ useEffect(() => {
+ const root = window.document.documentElement;
+
+ root.classList.remove("light", "dark");
+
+ if (theme === "system") {
+ const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
+ .matches
+ ? "dark"
+ : "light";
+
+ root.classList.add(systemTheme);
+ return;
+ }
+
+ root.classList.add(theme);
+ }, [theme]);
+
+ const value = {
+ theme,
+ setTheme: (theme: Theme) => {
+ localStorage.setItem(storageKey, theme);
+ setTheme(theme);
+ },
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+export const useTheme = () => {
+ const context = useContext(ThemeProviderContext);
+
+ if (context === undefined)
+ throw new Error("useTheme must be used within a ThemeProvider");
+
+ return context;
+};
diff --git a/frontend/src/types/config.d.ts b/frontend/src/types/config.d.ts
new file mode 100644
index 0000000..0bad5dc
--- /dev/null
+++ b/frontend/src/types/config.d.ts
@@ -0,0 +1,8 @@
+import type { components } from "./openapi";
+
+export type CommonConfig = components["schemas"]["GlobalTicketListingConfig"];
+export type Config = components["schemas"]["Config"];
+export type Country = components["schemas"]["Country"];
+export type NtfyConfig = components["schemas"]["NtfyConfig"];
+export type Region = components["schemas"]["Region"];
+export type TicketConfig = components["schemas"]["TicketListingConfig"];
diff --git a/frontend/src/types/openapi.d.ts b/frontend/src/types/openapi.d.ts
new file mode 100644
index 0000000..d729077
--- /dev/null
+++ b/frontend/src/types/openapi.d.ts
@@ -0,0 +1,254 @@
+/**
+ * This file was auto-generated by openapi-typescript.
+ * Do not make direct changes to the file.
+ */
+
+export interface paths {
+ "/config": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /**
+ * Get current configuration
+ * @description Retrieve the current Twitchets configuration
+ */
+ get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["Config"];
+ };
+ };
+ /** @description Internal server error */
+ 500: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ /**
+ * Update configuration
+ * @description Update the Twitchets configuration
+ */
+ put: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": components["schemas"]["Config"];
+ };
+ };
+ responses: {
+ /** @description Configuration updated successfully */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ /** @description Invalid configuration */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ /** @description Internal server error */
+ 500: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content?: never;
+ };
+ };
+ };
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
+}
+export type webhooks = Record;
+export interface components {
+ schemas: {
+ /**
+ * @description Country code.
+ * Currently only GB is supported.
+ * @enum {string}
+ */
+ Country: "GB";
+ NtfyConfig: {
+ /** @description You can use the public instance at https://ntfy.sh */
+ url: string;
+ /** @description If using https://ntfy.sh, make sure this is unique to you! */
+ topic: string;
+ /** @description Optional: for authenticated instances */
+ username: string;
+ /** @description Optional: for authenticated instances */
+ password: string;
+ };
+ GotifyConfig: {
+ /** @description Your Gotify server URL */
+ url: string;
+ /** @description Application token from Gotify */
+ token: string;
+ };
+ TelegramConfig: {
+ /** @description Get from @BotFather on Telegram */
+ token: string;
+ /** @description Your chat ID or group chat ID */
+ chatId: number;
+ };
+ /** @description Notification service configuration */
+ NotificationConfig: {
+ ntfy?: components["schemas"]["NtfyConfig"];
+ gotify?: components["schemas"]["GotifyConfig"];
+ telegram?: components["schemas"]["TelegramConfig"];
+ };
+ /**
+ * @description Region code.
+ * Possible values are:
+ * - GBLO: London
+ * - GBSO: South
+ * - GBSW: South West
+ * - GBSE: South East
+ * - GBMI: Midlands
+ * - GBEA: East Anglia
+ * - GBNO: North
+ * - GBNE: North East
+ * - GBNW: North West
+ * - GBSC: Scotland
+ * - GBWA: Wales
+ * - GBNI: Northern Ireland
+ * @enum {string}
+ */
+ Region: "GBLO" | "GBSO" | "GBSW" | "GBSE" | "GBMI" | "GBEA" | "GBNO" | "GBNE" | "GBNW" | "GBSC" | "GBWA" | "GBNI";
+ /** @enum {string} */
+ NotificationType: "ntfy" | "gotify" | "telegram";
+ /**
+ * @description GlobalTicketListingConfig represents configuration settings that apply to all ticket listings
+ * unless explicitly overridden by a specific ticket configuration.
+ * Any setting not specified will use the default.
+ */
+ GlobalTicketListingConfig: {
+ /**
+ * Format: double
+ * @description Event name similarity matching (0.0 - 1.0).
+ * Default: 0.9 (allows for minor naming differences)
+ */
+ eventSimilarity?: number;
+ /**
+ * @description Geographic regions to search for tickets.
+ * Default: All regions if not specified.
+ * Full list: https://github.com/ahobsonsayers/twigots/blob/main/location.go#L79-L90
+ */
+ regions?: components["schemas"]["Region"][];
+ /**
+ * @description Minimum number of tickets required in listing
+ * Default: Any number of tickets.
+ */
+ numTickets?: number;
+ /**
+ * Format: double
+ * @description Minimum discount (including fee) on the original price as a percentage
+ * Default: Any discount (including no discount).
+ */
+ discount?: number;
+ /**
+ * Format: double
+ * @description Maximum price per ticket (including fee) in pounds (£)
+ * Default: Any price.
+ */
+ maxTicketPrice?: number;
+ /**
+ * @description Notification services to use
+ * Default: All configured services.
+ */
+ notification?: components["schemas"]["NotificationType"][];
+ };
+ Regions: components["schemas"]["Region"][];
+ Notifications: components["schemas"]["NotificationType"][];
+ /**
+ * @description TicketListingConfig represents configuration for specific ticket listings
+ * Configuration overrides global configuration
+ * To reset a global configuration to its default, use:
+ * - "" (empty string) for string values
+ * - [] (empty array) for list values
+ * - -1 for numeric values
+ */
+ TicketListingConfig: {
+ /** @description Event name */
+ event: string;
+ /**
+ * Format: double
+ * @description Event name similarity matching (0.0 - 1.0).
+ * Overrides global setting.
+ */
+ eventSimilarity?: number;
+ /**
+ * @description Geographic regions to search for tickets
+ * Overrides global setting. To reset to default (all regions), use an empty array [].
+ */
+ regions?: components["schemas"]["Regions"];
+ /**
+ * @description Number of tickets required in listing
+ * Overrides global setting. To reset to default (any number), use -1.
+ */
+ numTickets?: number;
+ /**
+ * Format: double
+ * @description Minimum discount on the original price as a percentage
+ * Overrides global setting. To reset to default (any discount), use -1.
+ */
+ discount?: number;
+ /**
+ * Format: double
+ * @description Maximum price per ticket (including fee) in pounds (£)
+ * Overrides global setting. To reset to default (any price), use -1.
+ */
+ maxTicketPrice?: number;
+ /**
+ * @description Notification services to use
+ * Overrides global setting. To reset to default (all configured services), use an empty array [].
+ */
+ notification?: components["schemas"]["Notifications"];
+ };
+ Config: {
+ /** @description REQUIRED: See README.md for details on how to obtain */
+ apiKey: string;
+ country: components["schemas"]["Country"];
+ notification: components["schemas"]["NotificationConfig"];
+ global: components["schemas"]["GlobalTicketListingConfig"];
+ tickets: components["schemas"]["TicketListingConfig"][];
+ };
+ };
+ responses: never;
+ parameters: never;
+ requestBodies: never;
+ headers: never;
+ pathItems: never;
+}
+export type $defs = Record;
+export type operations = Record;
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
new file mode 100644
index 0000000..db6e072
--- /dev/null
+++ b/frontend/src/vite-env.d.ts
@@ -0,0 +1,9 @@
+///
+
+export interface ImportMetaEnv {
+ readonly VITE_API_BASE_URL?: string;
+}
+
+export interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..935f37d
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "include": ["**/*.ts", "**/*.tsx"],
+
+ "compilerOptions": {
+ "module": "ESNext",
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "noEmit": true,
+ "jsx": "preserve",
+ "allowImportingTsExtensions": true,
+
+ /* Linting */
+ "strict": true,
+ "erasableSyntaxOnly": true,
+
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 0000000..6256a2e
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,14 @@
+import tailwindcss from "@tailwindcss/vite";
+import react from "@vitejs/plugin-react";
+import path from "node:path";
+import { defineConfig } from "vite";
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
+});
diff --git a/main.go b/main.go
index 5181936..b92930c 100644
--- a/main.go
+++ b/main.go
@@ -12,6 +12,7 @@ import (
"github.com/ahobsonsayers/twigots"
"github.com/ahobsonsayers/twitchets/config"
+ "github.com/ahobsonsayers/twitchets/frontend"
"github.com/ahobsonsayers/twitchets/notification"
"github.com/ahobsonsayers/twitchets/scanner"
"github.com/ahobsonsayers/twitchets/server"
@@ -80,7 +81,7 @@ func main() {
}()
// Run server
- err = server.Start(9000, userConfigPath)
+ err = server.Start(9000, frontend.DistFS, userConfigPath)
if err != nil {
log.Fatalf("error running server: %v", err)
}
diff --git a/server/serve.go b/server/serve.go
index a9a960f..4b88e40 100644
--- a/server/serve.go
+++ b/server/serve.go
@@ -3,6 +3,7 @@ package server
import (
"context"
"fmt"
+ "io/fs"
"log"
"log/slog"
"net/http"
@@ -16,7 +17,7 @@ import (
oapimiddleware "github.com/oapi-codegen/nethttp-middleware"
)
-func Start(port int, configPath string) error {
+func Start(port int, frontendFS fs.FS, configPath string) error {
address := fmt.Sprintf("0.0.0.0:%d", port)
// Create middlewares
@@ -34,6 +35,10 @@ func Start(port int, configPath string) error {
router := chi.NewRouter()
router.Use(loggerMiddleware, corsMiddleware)
+ // Register routes for frontend
+ fileServer := http.FileServer(http.FS(frontendFS))
+ router.Handle("/*", http.StripPrefix("/", fileServer))
+
// Create api router and mount
apiRouter := chi.NewRouter()
apiRouter.Use(openapiValidationMiddleware)