summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-18 08:26:30 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-18 08:26:30 +0800
commit06a7b1668b44f111eca5d08bc8c2c9d41d8e617e (patch)
tree0c7220a22b866d3e298f756b42f565f4f674152c /frontend
parent95215e84e045602db2370586f6089a7f9f91b33b (diff)
downloadstrategy-lab-06a7b1668b44f111eca5d08bc8c2c9d41d8e617e.tar.gz
chore(frontend): shadcn-svelte / Tailwind / layerchart 工具链初始化
[变更性质] 本轮为维护性依赖与配置初始化,不改变业务页面(页面迁移见后续提交)。 [维护内容] 按 Leader 返工指令,前端 UI 组件库全量迁移 shadcn-svelte,图表渲染 从 echarts 换为 layerchart,需要一次性引入配套依赖与构建配置。 [实现方案] package.json/pnpm-lock.yaml:tailwindcss@4、@tailwindcss/vite、 tw-animate-css、shadcn-svelte、bits-ui、tailwind-variants、clsx、 tailwind-merge、@lucide/svelte、layerchart、@fontsource-variable/inter 与 @types/node;echarts 保留至页面迁移完成后移除。pnpm-workspace.yaml 声明 allowBuilds(esbuild、@tailwindcss/oxide)。components.json 采用 mira preset,别名 $lib。tsconfig 新增 $lib paths;vite.config.ts 加入 tailwindcss 插件、$lib alias(vitest 需要)与 test 环境配置。 src/lib/components/ 为 shadcn 生成的 UI 组件源码(button/input/label/ card/badge/alert/select/table/tabs/separator/chart),src/lib/utils.ts 为 cn 工具。app.css 引入 tailwind 与 shadcn CSS 变量并调整为 teal 主题(primary #0f766e、radius 0.375rem、双主题 chart-1..5),保留 legacy 选择器(banner/beat/table.data/metric 等),mobileCss.test.ts 依赖其语义断言。 [影响范围] 构建链与主题变量;后续页面提交逐步消费这些组件,npm run check/test/ build 已全绿验证。
Diffstat (limited to 'frontend')
-rw-r--r--frontend/components.json20
-rw-r--r--frontend/package.json18
-rw-r--r--frontend/pnpm-lock.yaml2900
-rw-r--r--frontend/pnpm-workspace.yaml3
-rw-r--r--frontend/src/app.css125
-rw-r--r--frontend/src/lib/components/ui/alert/alert-action.svelte20
-rw-r--r--frontend/src/lib/components/ui/alert/alert-description.svelte23
-rw-r--r--frontend/src/lib/components/ui/alert/alert-title.svelte23
-rw-r--r--frontend/src/lib/components/ui/alert/alert.svelte43
-rw-r--r--frontend/src/lib/components/ui/alert/index.ts17
-rw-r--r--frontend/src/lib/components/ui/badge/badge.svelte49
-rw-r--r--frontend/src/lib/components/ui/badge/index.ts2
-rw-r--r--frontend/src/lib/components/ui/button/button.svelte82
-rw-r--r--frontend/src/lib/components/ui/button/index.ts17
-rw-r--r--frontend/src/lib/components/ui/card/card-action.svelte23
-rw-r--r--frontend/src/lib/components/ui/card/card-content.svelte20
-rw-r--r--frontend/src/lib/components/ui/card/card-description.svelte20
-rw-r--r--frontend/src/lib/components/ui/card/card-footer.svelte20
-rw-r--r--frontend/src/lib/components/ui/card/card-header.svelte23
-rw-r--r--frontend/src/lib/components/ui/card/card-title.svelte20
-rw-r--r--frontend/src/lib/components/ui/card/card.svelte22
-rw-r--r--frontend/src/lib/components/ui/card/index.ts25
-rw-r--r--frontend/src/lib/components/ui/chart/chart-container.svelte80
-rw-r--r--frontend/src/lib/components/ui/chart/chart-style.svelte37
-rw-r--r--frontend/src/lib/components/ui/chart/chart-tooltip.svelte180
-rw-r--r--frontend/src/lib/components/ui/chart/chart-utils.ts68
-rw-r--r--frontend/src/lib/components/ui/chart/index.ts6
-rw-r--r--frontend/src/lib/components/ui/input/index.ts7
-rw-r--r--frontend/src/lib/components/ui/input/input.svelte48
-rw-r--r--frontend/src/lib/components/ui/label/index.ts7
-rw-r--r--frontend/src/lib/components/ui/label/label.svelte20
-rw-r--r--frontend/src/lib/components/ui/select/index.ts40
-rw-r--r--frontend/src/lib/components/ui/select/select-content.svelte45
-rw-r--r--frontend/src/lib/components/ui/select/select-group-heading.svelte21
-rw-r--r--frontend/src/lib/components/ui/select/select-group.svelte17
-rw-r--r--frontend/src/lib/components/ui/select/select-item.svelte42
-rw-r--r--frontend/src/lib/components/ui/select/select-label.svelte20
-rw-r--r--frontend/src/lib/components/ui/select/select-portal.svelte7
-rw-r--r--frontend/src/lib/components/ui/select/select-scroll-down-button.svelte20
-rw-r--r--frontend/src/lib/components/ui/select/select-scroll-up-button.svelte20
-rw-r--r--frontend/src/lib/components/ui/select/select-separator.svelte18
-rw-r--r--frontend/src/lib/components/ui/select/select-trigger.svelte29
-rw-r--r--frontend/src/lib/components/ui/select/select-value.svelte7
-rw-r--r--frontend/src/lib/components/ui/select/select.svelte11
-rw-r--r--frontend/src/lib/components/ui/separator/index.ts7
-rw-r--r--frontend/src/lib/components/ui/separator/separator.svelte21
-rw-r--r--frontend/src/lib/components/ui/table/index.ts28
-rw-r--r--frontend/src/lib/components/ui/table/table-body.svelte15
-rw-r--r--frontend/src/lib/components/ui/table/table-caption.svelte20
-rw-r--r--frontend/src/lib/components/ui/table/table-cell.svelte15
-rw-r--r--frontend/src/lib/components/ui/table/table-footer.svelte20
-rw-r--r--frontend/src/lib/components/ui/table/table-head.svelte15
-rw-r--r--frontend/src/lib/components/ui/table/table-header.svelte20
-rw-r--r--frontend/src/lib/components/ui/table/table-row.svelte20
-rw-r--r--frontend/src/lib/components/ui/table/table.svelte17
-rw-r--r--frontend/src/lib/components/ui/tabs/index.ts18
-rw-r--r--frontend/src/lib/components/ui/tabs/tabs-content.svelte17
-rw-r--r--frontend/src/lib/components/ui/tabs/tabs-list.svelte40
-rw-r--r--frontend/src/lib/components/ui/tabs/tabs-trigger.svelte23
-rw-r--r--frontend/src/lib/components/ui/tabs/tabs.svelte19
-rw-r--r--frontend/src/lib/utils.ts13
-rw-r--r--frontend/tsconfig.json9
-rw-r--r--frontend/vite.config.ts11
63 files changed, 4605 insertions, 8 deletions
diff --git a/frontend/components.json b/frontend/components.json
new file mode 100644
index 0000000..f385b1d
--- /dev/null
+++ b/frontend/components.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://shadcn-svelte.com/schema.json",
+ "tailwind": {
+ "css": "src/app.css",
+ "baseColor": "neutral"
+ },
+ "aliases": {
+ "components": "$lib/components",
+ "utils": "$lib/utils",
+ "ui": "$lib/components/ui",
+ "hooks": "$lib/hooks",
+ "lib": "$lib"
+ },
+ "typescript": true,
+ "registry": "https://shadcn-svelte.com/registry",
+ "style": "mira",
+ "iconLibrary": "lucide",
+ "menuColor": "default",
+ "menuAccent": "subtle"
+}
diff --git a/frontend/package.json b/frontend/package.json
index a60fa56..a294b49 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,14 +12,20 @@
"test:watch": "vitest"
},
"devDependencies": {
+ "@fontsource-variable/inter": "^5.3.0",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
+ "@tailwindcss/vite": "^4.3.3",
"@testing-library/svelte": "^5.2.7",
"@tsconfig/svelte": "^5.0.8",
"@types/diff": "^7.0.2",
+ "@types/node": "^22.20.3",
"jsdom": "^26.1.0",
"playwright": "^1.63.0",
+ "shadcn-svelte": "^1.6.1",
"svelte": "^5.19.0",
"svelte-check": "^4.1.4",
+ "tailwindcss": "^4.3.3",
+ "tw-animate-css": "^1.4.0",
"typescript": "^5.7.3",
"vite": "^6.3.5",
"vitest": "^3.2.4"
@@ -29,11 +35,19 @@
"@codemirror/merge": "^6.7.3",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.4",
+ "@lucide/svelte": "^1.46.0",
+ "bits-ui": "^2.19.2",
+ "clsx": "^2.1.1",
"codemirror": "^6.0.1",
"diff": "^7.0.0",
- "echarts": "^5.6.0"
+ "echarts": "^5.6.0",
+ "layerchart": "^2.5.0",
+ "tailwind-merge": "^3.7.0",
+ "tailwind-variants": "^3.3.1"
},
"allowScripts": {
+ "@tailwindcss/[email protected]": true
}
}
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
new file mode 100644
index 0000000..dbd9ea6
--- /dev/null
+++ b/frontend/pnpm-lock.yaml
@@ -0,0 +1,2900 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@codemirror/lang-python':
+ specifier: ^6.1.7
+ version: 6.2.1
+ '@codemirror/merge':
+ specifier: ^6.7.3
+ version: 6.12.2
+ '@codemirror/state':
+ specifier: ^6.5.2
+ version: 6.7.5
+ '@codemirror/view':
+ specifier: ^6.36.4
+ version: 6.43.12
+ '@lucide/svelte':
+ specifier: ^1.46.0
+ version: 1.46.0([email protected])
+ bits-ui:
+ specifier: ^2.19.2
+ version: 2.19.2(@internationalized/[email protected])([email protected])
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
+ codemirror:
+ specifier: ^6.0.1
+ version: 6.0.2
+ diff:
+ specifier: ^7.0.0
+ version: 7.0.0
+ echarts:
+ specifier: ^5.6.0
+ version: 5.6.0
+ layerchart:
+ specifier: ^2.5.0
+ version: 2.5.0([email protected])
+ tailwind-merge:
+ specifier: ^3.7.0
+ version: 3.7.0
+ tailwind-variants:
+ specifier: ^3.3.1
+ devDependencies:
+ '@fontsource-variable/inter':
+ specifier: ^5.3.0
+ version: 5.3.0
+ '@sveltejs/vite-plugin-svelte':
+ specifier: ^5.0.3
+ '@tailwindcss/vite':
+ specifier: ^4.3.3
+ '@testing-library/svelte':
+ specifier: ^5.2.7
+ '@tsconfig/svelte':
+ specifier: ^5.0.8
+ version: 5.0.8
+ '@types/diff':
+ specifier: ^7.0.2
+ version: 7.0.2
+ '@types/node':
+ specifier: ^22.20.3
+ version: 22.20.3
+ jsdom:
+ specifier: ^26.1.0
+ version: 26.1.0
+ playwright:
+ specifier: ^1.63.0
+ version: 1.63.0
+ shadcn-svelte:
+ specifier: ^1.6.1
+ version: 1.6.1([email protected])
+ svelte:
+ specifier: ^5.19.0
+ version: 5.57.0
+ svelte-check:
+ specifier: ^4.1.4
+ tailwindcss:
+ specifier: ^4.3.3
+ version: 4.3.3
+ tw-animate-css:
+ specifier: ^1.4.0
+ version: 1.4.0
+ typescript:
+ specifier: ^5.7.3
+ version: 5.9.3
+ vite:
+ specifier: ^6.3.5
+ vitest:
+ specifier: ^3.2.4
+
+packages:
+
+ '@asamuzakjp/[email protected]':
+ resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
+ '@babel/[email protected]':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/[email protected]':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/[email protected]':
+ resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==}
+ engines: {node: '>=6.9.0'}
+
+ '@codemirror/[email protected]':
+ resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==}
+
+ '@codemirror/[email protected]':
+ resolution: {integrity: sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==}
+
+ '@codemirror/[email protected]':
+ resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==}
+
+ '@codemirror/[email protected]':
+ resolution: {integrity: sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==}
+
+ '@codemirror/[email protected]':
+ resolution: {integrity: sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==}
+
+ '@codemirror/[email protected]':
+ resolution: {integrity: sha512-V8JvyAPjHbPupqP7BeMcsdsYCbyPij74jxIbaIJDORI+VZzW44zFmon8bF+oxGWvOKhcRmkiUMXd8MxHr3YA2w==}
+
+ '@codemirror/[email protected]':
+ resolution: {integrity: sha512-gUYkYhT2+n/+VGZ+8EzE5WFkYZUZYm1VOKDudIsNqh42uRVQJ0a6Yss9sdKT3MeOYfuL1N6AZA57oza0Oyr0LA==}
+
+ '@codemirror/[email protected]':
+ resolution: {integrity: sha512-QjLbZmY1Au3JiRrDVYFLRD0BZ3SOKS9pR3yjIkd7u27YY8TFD9/Q9fhPnLV5l1mHFSo3hHU/N31vpwEJOx4owQ==}
+
+ '@codemirror/[email protected]':
+ resolution: {integrity: sha512-Nv0vxQ19NAqvB/c2pFzjIzFlzzJl7jmdtNkwOwGbn0Ks9mFAzibvumz7cQem5cRsFA2cEw2fg+uHZGbcHupLQQ==}
+
+ '@csstools/[email protected]':
+ resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
+ engines: {node: '>=18'}
+
+ '@csstools/[email protected]':
+ resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/[email protected]':
+ resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/[email protected]':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/[email protected]':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+ engines: {node: '>=18'}
+
+ '@dagrejs/[email protected]':
+ resolution: {integrity: sha512-J6vCWTNpicHF4zFlZG1cS5DkGzMr9941gddYkakjrg3ZNev4bbqEgLHFTWiFrcJm7UCRu7olO3K6IRDd9gSGhA==}
+
+ '@dagrejs/[email protected]':
+ resolution: {integrity: sha512-HxZ7fCvAwTLCWCO0WjDkzAFQze8LdC6iOpKbetDKHIuDfIgMlIzYzqZ4nxwLlclQX+3ZVeZ1K2OuaOE2WWcyOg==}
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/[email protected]':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@floating-ui/[email protected]':
+ resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
+
+ '@floating-ui/[email protected]':
+ resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
+
+ '@floating-ui/[email protected]':
+ resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
+
+ '@fontsource-variable/[email protected]':
+ resolution: {integrity: sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==}
+
+ '@internationalized/[email protected]':
+ resolution: {integrity: sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==}
+
+ '@jridgewell/[email protected]':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/[email protected]':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/[email protected]':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/[email protected]':
+ resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
+
+ '@jridgewell/[email protected]':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@layerstack/[email protected]':
+ resolution: {integrity: sha512-OtFteDyvTUR6F7dSKPkpD/S77K7vbCHStApSlx8djJBxDKJHU3zxA4gYP6Z71Nc6HB/wLCfjZopkvxnMblbRRw==}
+
+ '@layerstack/[email protected]':
+ resolution: {integrity: sha512-K+SO4dYd2D9OSCnj92Ugo6i5OfVKZoHHA1DA7gs2ynigylRKInicniGI/OmRpp+002n3mhE/i2r3Kfctj8eAdw==}
+
+ '@layerstack/[email protected]':
+ resolution: {integrity: sha512-Qgp2EpmEHmjtura8MQzWicR6ztBRSsRvddakFtx9ShrLMz6jWzd6bCMVVRu44Q3ZOrtXmSu4QxjCZWu1ytvuPg==}
+
+ '@layerstack/[email protected]':
+ resolution: {integrity: sha512-HyXCEz9pge4rFWklrSgFr3YIwdE1gP8WM6GPetmnKPDfYLF1/oetRFKI9FqiE+dw4brnULGPKJsVhjM3vQfD4A==}
+
+ '@lezer/[email protected]':
+ resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==}
+
+ '@lezer/[email protected]':
+ resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
+
+ '@lezer/[email protected]':
+ resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==}
+
+ '@lezer/[email protected]':
+ resolution: {integrity: sha512-MhQIURHRytsNzP/YXnqpYKW6la6voAH3kyplTOOiCdjyFY6cWWGFVmYVdHIPrElqSDf4iCDktQCockB9FxuhzQ==}
+
+ '@lucide/[email protected]':
+ resolution: {integrity: sha512-Ru8jDjoCVLJ7J7pPRUdY+odSBV2BI6rj68JTmT+ENq/ZwYOPGPAiKVvKz+XLyAoK4HAwlRTzZHpsMvrHfsetqg==}
+ peerDependencies:
+ svelte: ^5
+
+ '@marijn/[email protected]':
+ resolution: {integrity: sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==}
+
+ '@napi-rs/[email protected]':
+ resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==}
+ engines: {node: ^22.20 || ^24.12 || >=25}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-w3Jnvi1ocaVm/c7yVPpfB98XeSRBMyzp6njL5MVVbGyXjpmUkN+s6Hp4t0PqhGCCaI1ZHMKXt/w0lA1RCaLVcw==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-uI/ESiaIbbRYAEhzy8PCUWDp1hB0bjAqM06mW9flOoNO4Q8DQpeoREhBR5Hegfl+wpXiguyJv6XSPzEN7OxyHQ==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-oxhrd1jmXLwWZ83eQYDXxuqRdkqkzrjR3JobKeuUyfdNZo11FuQIvqEOZhyIT7OBHxXoGslDDjN0cQcM6T0TqQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-7/YiIMghVE8DrxKvNdorAaJVdriOFgOIpdStnPx8ppx5zfTwC3jBCSEAIzB7JD5404m65THl6H93UTTVUvypmg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-GXFZRRoMAytaI5z6N3Zhfw0WL18Q0M8r95D5hlC4GqE/lGk8pbSJNUBoOWDfbm6dTciqHj2nU87tI5f6XhQiOg==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-77W+8X3ddYgPxUpB8nZFQs2Mq+wc4HVlcSRtApXLjYBcnPMkttrSnU8VwKQjeWYhMsITHFs5cWBQ8vz1Q+5RHQ==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-FVkwK+iUC+mq+GipVK46rRVticfAPtvPUNlqlGXUDxdVk/UGjQiiiUVPUrEXdSpU2ufU0XxLGyTqDtBidDOVmg==}
+ cpu: [arm]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-+aGU1t3398yQOVj1Bz8o3e+KtswxAPvO+mtxtNdfXYMkXIHu7XhhkCD7/DEH9q8tF8uhDnMWvfpUKI8y1sZJsg==}
+ cpu: [arm]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-cR0kjpRXR2KJ2oQK8E2KTPtphs+b9hZ8IhTZubNryt/RsqgdOZBQ2Zq0q5UedtiIi0rs3jVhJh55RE1ZHUVGUA==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-y1RYi4Q3/9ByVWSSt9kX2ustE0B7kFYbJ6zZdVZVyqopZs3yhCTwRfrjIX4vezUJInma/Gs6BOFDJg7yZmJ0IQ==}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-DNhEA5viIj3Z5bZLE4z4oV8N5ozWqDwyt7T6KG7VdLDJ0nW+rNOYlphBl4/3HQkK75qipPLsVOfStHHOwN9WSg==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-17gQCqrIpXBX2Cmi9/TygnVOqGbzsba/iaqcYSL8FY7lNugg+7AiYNs5c5nKWD+NRQha36Sa0CqkJqH4XVHwnQ==}
+ cpu: [loong64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-6LwVnZRIyINpdku/yOcI8Tm9YqLmhHK5emmlOOnW9tO0SYEm1FmKPcsSAGp0NBlqR2P04xaND4jvN6sTHqhq8A==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-xMUqkTXlEUtI/p5AAukMwBRr1enU3efsTeF+bskeFfk8t1C9rcC8sLREcZXmTfAXEbvRdJVSonVJez3TMlbR3w==}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-S3E94co9F9WRRqEaUoQZ38K1gCz6KiM+nL7/3ijq7fDGF3OznjS5TasgYITlvl27GQKtu4lOAOsr5MFwkijvOA==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-1QtRDwG42x5BJI3s9mxu5rEjDnfbSnk20HQ9/ylTAYnSwYwxMVb+Vgu34wzzTQ7ogqBybebgQNUDAvZVQ38DbA==}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-BQhejF6ZXOpxbngiNTP12GCGQeaDVL2QXGeBVViKIYzFHM5RKxTxwUMB1fr1BeNFphFMpnRqC5QSXFSa4z6UQw==}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-SXagRwnI2Wlwlitllu59UK/nGVbD1CKPcNqDplHwIC4BqJcpXFjD32d1R/RbuISa95HdQrZM3/7v4bKiowFaLA==}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-2IPozoEALRCziGqE8O9KMK60PMu5TS1huv4fwoeCexj+WjmcwFtX9CTOVbfXCUqcELAubEwRFPYlzb/WvwY2HQ==}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-AoxqosUHT9IX54hFn2TiN6A7d6ZKTtE6pd2bqWtqkkNJ6HJGaU6FRouGX8L1O7R/ZwsnCnpQrHzb4pDEx+UHRQ==}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-d+CaftKgmkFBzCwezMqqy1d0QNNYugqLCMcYVQWBy5SS2YfeMP8Q8ripkgx9O8IyBXXLHrJ+aaCV4U96usv6Yg==}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-xXlDF6nR1eOuXbdDy5Hl5fmtY7teUDevF/k0O7IPoZe4Tpmdv+lgdE5JRsnhQtt37ql9P0VF2kAN9a0OCZdo+Q==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-YtXAgLN+JP7Ay6qG3eWhc7IHMQPzLc8r3uvhAvlJIoCz/4Q32+Bl9Fmnywidh8v1GOIMmymjovfqY9ETAtysvA==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-WuWtSJRNo549vzcfZyEgfqb6zeSgn1F+UE5kQ+BCjzz0W4MGCjntUHkZVc1VRuAM7+ULaSyhiPxD1spyewFvkQ==}
+ cpu: [x64]
+ os: [win32]
+
+ '@rollup/[email protected]':
+ resolution: {integrity: sha512-+lIKX7O0+IGe7WuhATaAMMeT7B76vfhXH/l9wLQL+nvyhbw2ohYCKIdWL56JfDu75CWt5oKRP4QFH/jkMtBquA==}
+ cpu: [x64]
+ os: [win32]
+
+ '@sveltejs/[email protected]':
+ resolution: {integrity: sha512-wgKggnhZVL9Bfx1OaKKTrYY9BFRk6C8UAkQNUcIv1+llzYrIqy+RZm5HPKzn0NpEBvTVhTqB4kQyllZywsRBRQ==}
+ peerDependencies:
+ acorn: ^8.9.0
+
+ '@sveltejs/[email protected]':
+ resolution: {integrity: sha512-VT3qmUb8pRV2QrZjd8iAmtg8lf4W0TIjZbvXtz5MKei/q96teWZgGJyyidJzOjzZzvdq616eSRVeMYIQChUTAQ==}
+ engines: {node: '>= 18.0.0'}
+
+ '@sveltejs/[email protected]':
+ resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22}
+ peerDependencies:
+ '@sveltejs/vite-plugin-svelte': ^5.0.0
+ svelte: ^5.0.0
+ vite: ^6.0.0
+
+ '@sveltejs/[email protected]':
+ resolution: {integrity: sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22}
+ peerDependencies:
+ svelte: ^5.0.0
+ vite: ^6.0.0
+
+ resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==}
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [android]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==}
+ engines: {node: '>= 20'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+ bundledDependencies:
+ - '@napi-rs/wasm-runtime'
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ - '@tybys/wasm-util'
+ - '@emnapi/wasi-threads'
+ - tslib
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==}
+ engines: {node: '>= 20'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==}
+ engines: {node: '>= 20'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==}
+ engines: {node: '>= 20'}
+
+ '@tailwindcss/[email protected]':
+ resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==}
+ peerDependencies:
+ vite: ^5.2.0 || ^6 || ^7 || ^8
+
+ '@testing-library/[email protected]':
+ resolution: {integrity: sha512-yzr2S9HyAIdhz2/6qHgbs665Q7PKVcDF05vsOlHPxG1mo36gKVesdYVeDLnXgfjJ03CrKRk08knc6+E/9m8v2Q==}
+ engines: {node: '>=18'}
+
+ '@testing-library/[email protected]':
+ resolution: {integrity: sha512-KkMAvXeWorxN2Yn0kdC1lfoAItxpoj4uOWzxK5leDrNxonLvS5nwBFvztrroyTszQ0Wf/EU6iLT8JhY5qcn22g==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ svelte: ^3 || ^4 || ^5 || ^5.0.0-next.0
+
+ '@testing-library/[email protected]':
+ resolution: {integrity: sha512-4o31E4HGo5BU5KwPkulNRocEden+7Tt9JYm9uhln5ajF7DULeyFA46BBWVfKJ8Ms9B3JmOFPTIiVamH7n3KpuQ==}
+ engines: {node: '>= 10'}
+ peerDependencies:
+ svelte: ^3 || ^4 || ^5 || ^5.0.0-next.0
+ vite: '*'
+ vitest: '*'
+ peerDependenciesMeta:
+ vite:
+ optional: true
+ vitest:
+ optional: true
+
+ '@tsconfig/[email protected]':
+ resolution: {integrity: sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==}
+
+ '@types/[email protected]':
+ resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
+
+ '@types/[email protected]':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
+ '@types/[email protected]':
+ resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
+
+ '@types/[email protected]':
+ resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==}
+
+ '@types/[email protected]':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
+ '@types/[email protected]':
+ resolution: {integrity: sha512-JSWRMozjFKsGlEjiiKajUjIJVKuKdE3oVy2DNtK+fUo8q82nhFZ2CPQwicAIkXrofahDXrWJ7mjelvZphMS98Q==}
+
+ '@types/[email protected]':
+ resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
+
+ '@types/[email protected]':
+ resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==}
+
+ '@types/[email protected]':
+ resolution: {integrity: sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A==}
+
+ '@vitest/[email protected]':
+ resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==}
+
+ '@vitest/[email protected]':
+ resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/[email protected]':
+ resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==}
+
+ '@vitest/[email protected]':
+ resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==}
+
+ '@vitest/[email protected]':
+ resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==}
+
+ '@vitest/[email protected]':
+ resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==}
+
+ '@vitest/[email protected]':
+ resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==}
+
+ resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+
+ resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==}
+ engines: {node: '>= 0.4'}
+
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
+ engines: {node: '>= 0.4'}
+
+ resolution: {integrity: sha512-W9upilTmLTIWjuVAD6Ta7XoS6gxRflmCQnXamj+WN1eYxZecghqGENpuSMAKQmVENMLXhh85BZb04LCrBd2+Ng==}
+ engines: {node: '>=20'}
+ peerDependencies:
+ '@internationalized/date': ^3.8.1
+ svelte: ^5.33.0
+
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
+ resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
+ engines: {node: '>= 16'}
+
+ resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+ engines: {node: '>= 14.16.0'}
+
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
+ resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==}
+
+ resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
+ engines: {node: '>=20'}
+
+ resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
+ engines: {node: '>= 10'}
+
+ resolution: {integrity: sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==}
+
+ resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==}
+
+ resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==}
+ engines: {node: '>=12'}
+ hasBin: true
+
+ resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-kqE4yYuOjPbKdBXG0xztCacPwkVSK2REF1opSNrnqqtXJmNcM++UbwQ8SxvwP6IQTj9RvIjjK4qeiVsEfj0Z2Q==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-tZYtGXxBmbgHsIc9Wms6LS5u4w6KbP8C09a4/ZYc4KLMYYqub57rRBUgpUr2CIarIrJEpdAWWxWQvofgaMpbKQ==}
+
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==}
+
+ resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==}
+
+ resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==}
+
+ resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-79fnTKpPMPDS5xQ0xuS9ir0165NEwwkFpe/DSOmc2Gl9ldYzKKRDWogmTTE8wAJ8NA7PMapNfEcyKhI9Lxdu5Q==}
+
+ resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-G7gHKj89n2owmkGb6WX6ixcnQ0Kf/0wpa9VIh9DGdbHu8wdrlaHU4ir3/bFNERl8N8nn4G7e7qbtBG8N9caihQ==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
+ resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
+ engines: {node: '>=6'}
+
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
+ resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==}
+
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ resolution: {integrity: sha512-po4PAY5c53tw5XMocSnf8A/5OHhbbUftpr93aEN6BBoAdntUmK7vu7wOATqvt7cXO7m1Cl4gMVn6p7n6n4mj0w==}
+
+ resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==}
+ engines: {node: '>=0.3.1'}
+
+ resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
+ resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
+
+ resolution: {integrity: sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==}
+ engines: {node: '>=10.13.0'}
+
+ resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+ engines: {node: '>=0.12'}
+
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==}
+
+ resolution: {integrity: sha512-n2nf7fZR3c9yXf0BPEuHuXqT+KW0SJVj4cN5FMEkpCZ3scLjOQWpiccyCxVzCC2q1wubTghuEGzngJY/7Ah0Ow==}
+ peerDependencies:
+ '@typescript-eslint/types': ^8.2.0
+ peerDependenciesMeta:
+ '@typescript-eslint/types':
+ optional: true
+
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
+ resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
+ engines: {node: '>=12.0.0'}
+
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
+ resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
+
+ resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==}
+
+ resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
+ resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==}
+
+ resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
+ hasBin: true
+
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+
+ resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^3.0.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+
+ resolution: {integrity: sha512-9xOVLKXeurBiFduMFKx7g/rU9/IyGYb2pTFRNJmabXEg/QRoY+ILwOBcWH5dGBEpFWiFlzVfcHDtbcbaymaETw==}
+ peerDependencies:
+ svelte: ^5.0.0
+
+ resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==}
+ engines: {node: '>= 12.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==}
+ engines: {node: '>= 12.0.0'}
+
+ resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==}
+
+ resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
+
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
+ resolution: {integrity: sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
+ engines: {node: '>=4'}
+
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ resolution: {integrity: sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
+
+ resolution: {integrity: sha512-gQPNF78qebCQ6tvVFBYrvJdBNOrYZm90ZlXgpIFm06p6qHDHq/XC4TnJftN6OMbxVE0UTBAoRgcsDeJBBooITw==}
+
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
+ engines: {node: '>= 14.16'}
+
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==}
+ engines: {node: '>=20'}
+ hasBin: true
+
+ resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==}
+ engines: {node: '>=20'}
+ hasBin: true
+
+ resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
+
+ resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
+
+ resolution: {integrity: sha512-1i2XreiAoMMXuPGD6Msj2xWrMMkHojNRKivInxGQcg7/1KuPuYlfUutLyh4drnOxUTHX9cHI4wFoat8D/NKaBw==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+
+ resolution: {integrity: sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q==}
+ peerDependencies:
+ '@sveltejs/kit': ^2.21.0
+ svelte: ^5.7.0
+ peerDependenciesMeta:
+ '@sveltejs/kit':
+ optional: true
+
+ resolution: {integrity: sha512-MeFY73xBW8IueWBm012nNFIGy19WUGPLtknavyUPMpnyt350M47PhGSGrGoSLbidwn+Zlt/O0cp8/OZE3LASWA==}
+ peerDependencies:
+ '@sveltejs/kit': ^2.21.0
+ svelte: ^5.7.0
+ zod: ^4.1.0
+ peerDependenciesMeta:
+ '@sveltejs/kit':
+ optional: true
+ zod:
+ optional: true
+
+ resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==}
+
+ resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
+ engines: {node: '>=6'}
+
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
+ resolution: {integrity: sha512-qmbdBQ3GLO+kfLWlF7H+UZ0YbGOv4UnTy3r3LP6yK5QfO5SW+989ohKoAjKe+caHYWz7oFYqflVZablIewuBiw==}
+ hasBin: true
+ peerDependencies:
+ svelte: ^5.0.0
+
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
+ resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+
+ resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
+
+ resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
+
+ resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
+
+ resolution: {integrity: sha512-t2scM//ZuVbSY/T2w6FSBw1v9s2NEmh/g+sy1lqtosW5ylBV5AF4wFb1Ts9Kf3MbfPDUDJDZ9L436YT0SPTdvw==}
+ engines: {node: '>= 18.0.0'}
+ hasBin: true
+ peerDependencies:
+ svelte: ^4.0.0 || ^5.0.0-next.0
+ typescript: ^5.0.0 || ^6.0.0
+
+ resolution: {integrity: sha512-YWuX+RE+CnWYx09yseAe4ZVMM7e7GRFZM6OYWpBKOb++s+SQ8RBIMMe+Bs/CznBMc0QPLjr+vDBxTAkozXsFXQ==}
+ engines: {node: '>=18', pnpm: '>=8.7.0'}
+ peerDependencies:
+ svelte: ^5.30.2
+
+ resolution: {integrity: sha512-NdbDn7fl4be1ViUG0oq/lvG6OZy3oENolV2ONjiqqsfVoeAfzaQAKUcEX3MrQod/Bebv1PgwET9rfXhgn9s4Kg==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
+ resolution: {integrity: sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==}
+
+ resolution: {integrity: sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==}
+
+ resolution: {integrity: sha512-4pAvwUtM4HKBiRZftncAbpn6V9Hhwoa5Fl7O2u5zbp7Z5Cvu+/o/6+176WY3WCEES209543quG8zFIcXCsc5Jw==}
+ engines: {node: '>=16.9.x', pnpm: '>=7.x'}
+ peerDependencies:
+ tailwind-merge: '>=3.0.0'
+ tailwindcss: '*'
+ peerDependenciesMeta:
+ tailwind-merge:
+ optional: true
+ tailwindcss:
+ optional: true
+
+ resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==}
+
+ resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
+ engines: {node: '>=6'}
+
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
+ engines: {node: '>=12.0.0'}
+
+ resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+
+ resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
+ engines: {node: '>=14.0.0'}
+
+ resolution: {integrity: sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==}
+ engines: {node: '>=14.0.0'}
+
+ resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
+
+ resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+ hasBin: true
+
+ resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+ engines: {node: '>=16'}
+
+ resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
+
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
+
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+
+ resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ jiti: '>=1.21.0'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ sass-embedded: '*'
+ stylus: '*'
+ sugarss: '*'
+ 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
+
+ resolution: {integrity: sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==}
+ peerDependencies:
+ vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
+ peerDependenciesMeta:
+ vite:
+ optional: true
+
+ resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/debug': ^4.1.12
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ '@vitest/browser': 3.2.7
+ '@vitest/ui': 3.2.7
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/debug':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
+ resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
+
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
+ resolution: {integrity: sha512-msJxIvYDYcoNL+PJsu+7qmpDWsYmAxTY+2TNYXXF0hzBzBk0BMecOqDOG/EckUoKCuKwObfbugIl8QpqHDXeFA==}
+
+ resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
+
+snapshots:
+
+ '@asamuzakjp/[email protected]':
+ dependencies:
+ '@csstools/css-calc': 2.1.4(@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])
+ '@csstools/css-color-parser': 3.1.0(@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/[email protected])
+ '@csstools/css-tokenizer': 3.0.4
+ lru-cache: 10.4.3
+
+ '@babel/[email protected]':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/[email protected]': {}
+
+ '@babel/[email protected]': {}
+
+ '@codemirror/[email protected]':
+ dependencies:
+ '@codemirror/language': 6.12.4
+ '@codemirror/state': 6.7.5
+ '@codemirror/view': 6.43.12
+ '@lezer/common': 1.5.2
+
+ '@codemirror/[email protected]':
+ dependencies:
+ '@codemirror/language': 6.12.4
+ '@codemirror/state': 6.7.5
+ '@codemirror/view': 6.43.12
+ '@lezer/common': 1.5.2
+
+ '@codemirror/[email protected]':
+ dependencies:
+ '@codemirror/autocomplete': 6.20.3
+ '@codemirror/language': 6.12.4
+ '@codemirror/state': 6.7.5
+ '@lezer/common': 1.5.2
+ '@lezer/python': 1.1.19
+
+ '@codemirror/[email protected]':
+ dependencies:
+ '@codemirror/state': 6.7.5
+ '@codemirror/view': 6.43.12
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+ style-mod: 4.1.3
+
+ '@codemirror/[email protected]':
+ dependencies:
+ '@codemirror/state': 6.7.5
+ '@codemirror/view': 6.43.12
+ crelt: 1.0.7
+
+ '@codemirror/[email protected]':
+ dependencies:
+ '@codemirror/language': 6.12.4
+ '@codemirror/state': 6.7.5
+ '@codemirror/view': 6.43.12
+ '@lezer/highlight': 1.2.3
+ style-mod: 4.1.3
+
+ '@codemirror/[email protected]':
+ dependencies:
+ '@codemirror/state': 6.7.5
+ '@codemirror/view': 6.43.12
+ crelt: 1.0.7
+
+ '@codemirror/[email protected]':
+ dependencies:
+ '@marijn/find-cluster-break': 1.0.4
+
+ '@codemirror/[email protected]':
+ dependencies:
+ '@codemirror/state': 6.7.5
+ crelt: 1.0.7
+ style-mod: 4.1.3
+ w3c-keyname: 2.2.8
+
+ '@csstools/[email protected]': {}
+
+ '@csstools/[email protected](@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/[email protected])
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/[email protected](@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])':
+ dependencies:
+ '@csstools/color-helpers': 5.1.0
+ '@csstools/css-calc': 2.1.4(@csstools/[email protected](@csstools/[email protected]))(@csstools/[email protected])
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/[email protected])
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/[email protected](@csstools/[email protected])':
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/[email protected]': {}
+
+ '@dagrejs/[email protected]':
+ dependencies:
+ '@dagrejs/graphlib': 3.0.4
+
+ '@dagrejs/[email protected]': {}
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@esbuild/[email protected]':
+ optional: true
+
+ '@floating-ui/[email protected]':
+ dependencies:
+ '@floating-ui/utils': 0.2.12
+
+ '@floating-ui/[email protected]':
+ dependencies:
+ '@floating-ui/core': 1.8.0
+ '@floating-ui/utils': 0.2.12
+
+ '@floating-ui/[email protected]': {}
+
+ '@fontsource-variable/[email protected]': {}
+
+ '@internationalized/[email protected]':
+ dependencies:
+ '@swc/helpers': 0.5.23
+
+ '@jridgewell/[email protected]':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.6.0
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/[email protected]':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/[email protected]': {}
+
+ '@jridgewell/[email protected]': {}
+
+ '@jridgewell/[email protected]':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.6.0
+
+ '@layerstack/[email protected]':
+ dependencies:
+ '@floating-ui/dom': 1.8.0
+ '@layerstack/utils': 2.0.0-next.19
+ d3-scale: 4.0.2
+
+ '@layerstack/[email protected]':
+ dependencies:
+ '@layerstack/utils': 2.0.0-next.19
+
+ '@layerstack/[email protected]':
+ dependencies:
+ '@layerstack/utils': 2.0.0-next.19
+ clsx: 2.1.1
+ d3-array: 3.2.4
+ tailwind-merge: 3.7.0
+
+ '@layerstack/[email protected]':
+ dependencies:
+ d3-array: 3.2.4
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ '@lezer/[email protected]': {}
+
+ '@lezer/[email protected]':
+ dependencies:
+ '@lezer/common': 1.5.2
+
+ '@lezer/[email protected]':
+ dependencies:
+ '@lezer/common': 1.5.2
+
+ '@lezer/[email protected]':
+ dependencies:
+ '@lezer/common': 1.5.2
+ '@lezer/highlight': 1.2.3
+ '@lezer/lr': 1.4.10
+
+ dependencies:
+ svelte: 5.57.0
+
+ '@marijn/[email protected]': {}
+
+ '@napi-rs/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ '@rollup/[email protected]':
+ optional: true
+
+ dependencies:
+ acorn: 8.18.0
+
+ '@sveltejs/[email protected]': {}
+
+ dependencies:
+ debug: 4.4.3
+ svelte: 5.57.0
+ transitivePeerDependencies:
+ - supports-color
+
+ dependencies:
+ debug: 4.4.3
+ deepmerge: 4.3.1
+ kleur: 4.1.5
+ magic-string: 0.30.21
+ svelte: 5.57.0
+ transitivePeerDependencies:
+ - supports-color
+
+ dependencies:
+ tslib: 2.8.1
+
+ '@tailwindcss/[email protected]':
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ enhanced-resolve: 5.25.1
+ jiti: 2.7.0
+ lightningcss: 1.32.0
+ magic-string: 0.30.21
+ source-map-js: 1.2.1
+ tailwindcss: 4.3.3
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optional: true
+
+ '@tailwindcss/[email protected]':
+ optionalDependencies:
+ '@tailwindcss/oxide-android-arm64': 4.3.3
+ '@tailwindcss/oxide-darwin-arm64': 4.3.3
+ '@tailwindcss/oxide-darwin-x64': 4.3.3
+ '@tailwindcss/oxide-freebsd-x64': 4.3.3
+ '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3
+ '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3
+ '@tailwindcss/oxide-linux-arm64-musl': 4.3.3
+ '@tailwindcss/oxide-linux-x64-gnu': 4.3.3
+ '@tailwindcss/oxide-linux-x64-musl': 4.3.3
+ '@tailwindcss/oxide-wasm32-wasi': 4.3.3
+ '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3
+ '@tailwindcss/oxide-win32-x64-msvc': 4.3.3
+
+ dependencies:
+ '@tailwindcss/node': 4.3.3
+ '@tailwindcss/oxide': 4.3.3
+ tailwindcss: 4.3.3
+
+ '@testing-library/[email protected]':
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ '@babel/runtime': 7.29.7
+ '@types/aria-query': 5.0.4
+ aria-query: 5.3.0
+ dom-accessibility-api: 0.5.16
+ lz-string: 1.5.0
+ picocolors: 1.1.1
+ pretty-format: 27.5.1
+
+ '@testing-library/[email protected]([email protected])':
+ dependencies:
+ svelte: 5.57.0
+
+ dependencies:
+ '@testing-library/dom': 10.4.2
+ '@testing-library/svelte-core': 1.1.3([email protected])
+ svelte: 5.57.0
+ optionalDependencies:
+
+ '@tsconfig/[email protected]': {}
+
+ '@types/[email protected]': {}
+
+ '@types/[email protected]':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
+ '@types/[email protected]': {}
+
+ '@types/[email protected]':
+ dependencies:
+ '@types/d3-array': 3.2.2
+ '@types/geojson': 7946.0.16
+
+ '@types/[email protected]': {}
+
+ '@types/[email protected]': {}
+
+ '@types/[email protected]': {}
+
+ '@types/[email protected]': {}
+
+ '@types/[email protected]':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@vitest/[email protected]':
+ dependencies:
+ '@types/chai': 5.2.3
+ '@vitest/spy': 3.2.7
+ '@vitest/utils': 3.2.7
+ chai: 5.3.3
+ tinyrainbow: 2.0.0
+
+ dependencies:
+ '@vitest/spy': 3.2.7
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+
+ '@vitest/[email protected]':
+ dependencies:
+ tinyrainbow: 2.0.0
+
+ '@vitest/[email protected]':
+ dependencies:
+ '@vitest/utils': 3.2.7
+ pathe: 2.0.3
+ strip-literal: 3.1.0
+
+ '@vitest/[email protected]':
+ dependencies:
+ '@vitest/pretty-format': 3.2.7
+ magic-string: 0.30.21
+ pathe: 2.0.3
+
+ '@vitest/[email protected]':
+ dependencies:
+ tinyspy: 4.0.6
+
+ '@vitest/[email protected]':
+ dependencies:
+ '@vitest/pretty-format': 3.2.7
+ loupe: 3.2.1
+ tinyrainbow: 2.0.0
+
+
+
+
+
+ dependencies:
+ dequal: 2.0.3
+
+
+
+
+ dependencies:
+ '@floating-ui/core': 1.8.0
+ '@floating-ui/dom': 1.8.0
+ '@internationalized/date': 3.12.4
+ esm-env: 1.2.2
+ runed: 0.35.1([email protected])
+ svelte: 5.57.0
+ svelte-toolbelt: 0.10.6([email protected])
+ tabbable: 6.5.0
+ transitivePeerDependencies:
+ - '@sveltejs/kit'
+
+
+ dependencies:
+ assertion-error: 2.0.1
+ check-error: 2.1.3
+ deep-eql: 5.0.2
+ loupe: 3.2.1
+ pathval: 2.0.1
+
+
+ dependencies:
+ readdirp: 4.1.2
+
+
+ dependencies:
+ '@codemirror/autocomplete': 6.20.3
+ '@codemirror/commands': 6.11.1
+ '@codemirror/language': 6.12.4
+ '@codemirror/lint': 6.9.7
+ '@codemirror/search': 6.7.2
+ '@codemirror/state': 6.7.5
+ '@codemirror/view': 6.43.12
+
+
+
+
+ dependencies:
+ '@asamuzakjp/css-color': 3.2.0
+ rrweb-cssom: 0.8.0
+
+ dependencies:
+ internmap: 1.0.1
+
+ dependencies:
+ internmap: 2.0.3
+
+ dependencies:
+ d3-path: 3.1.0
+
+
+ dependencies:
+ d3-array: 3.2.4
+
+ dependencies:
+ delaunator: 5.1.0
+
+
+ dependencies:
+ commander: 7.2.0
+ iconv-lite: 0.6.3
+ rw: 1.3.3
+
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-timer: 3.0.1
+
+
+ dependencies:
+ d3-array: 3.2.4
+ d3-delaunay: 6.0.4
+ d3-geo: 3.1.1
+ d3-tricontour: 1.1.0
+
+ dependencies:
+ d3-array: 3.2.4
+
+
+
+ dependencies:
+ d3-color: 3.1.0
+
+
+
+
+
+
+ dependencies:
+ d3-array: 2.12.1
+ d3-shape: 1.3.7
+
+ dependencies:
+ d3-color: 3.1.0
+ d3-interpolate: 3.0.1
+
+ dependencies:
+ d3-array: 3.2.4
+ d3-format: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ dependencies:
+ d3-path: 1.0.9
+
+ dependencies:
+ d3-path: 3.1.0
+
+
+ dependencies:
+ d3-time: 3.1.0
+
+ dependencies:
+ d3-array: 3.2.4
+
+
+ dependencies:
+ d3-delaunay: 6.0.4
+ d3-scale: 4.0.2
+
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+
+ dependencies:
+ ms: 2.1.3
+
+
+
+
+ dependencies:
+ robust-predicates: 3.0.3
+
+
+
+
+
+
+ dependencies:
+ tslib: 2.3.0
+ zrender: 5.6.1
+
+ dependencies:
+ graceful-fs: 4.2.11
+ tapable: 2.3.3
+
+
+
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
+
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.6.0
+
+ dependencies:
+ '@types/estree': 1.0.9
+
+
+ optionalDependencies:
+ picomatch: 4.0.7
+
+ optional: true
+
+
+ dependencies:
+ whatwg-encoding: 3.1.1
+
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ dependencies:
+ safer-buffer: 2.1.2
+
+
+
+
+
+ dependencies:
+ '@types/estree': 1.0.9
+
+
+
+
+ dependencies:
+ cssstyle: 4.6.0
+ data-urls: 5.0.0
+ decimal.js: 10.6.0
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.27
+ parse5: 7.3.0
+ rrweb-cssom: 0.8.0
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 5.1.2
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+ ws: 8.21.3
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+
+ dependencies:
+ '@dagrejs/dagre': 2.0.4
+ '@layerstack/svelte-actions': 1.0.1-next.20
+ '@layerstack/svelte-state': 0.1.0-next.25
+ '@layerstack/tailwind': 2.0.0-next.21
+ '@layerstack/utils': 2.0.0-next.19
+ '@types/d3-contour': 3.0.6
+ d3-array: 3.2.4
+ d3-chord: 3.0.1
+ d3-color: 3.1.0
+ d3-contour: 4.0.2
+ d3-delaunay: 6.0.4
+ d3-dsv: 3.0.1
+ d3-force: 3.0.0
+ d3-geo: 3.1.1
+ d3-geo-voronoi: 2.1.0
+ d3-hierarchy: 3.1.2
+ d3-interpolate: 3.0.1
+ d3-interpolate-path: 2.3.0
+ d3-path: 3.1.0
+ d3-polygon: 3.0.1
+ d3-quadtree: 3.0.1
+ d3-random: 3.0.1
+ d3-sankey: 0.12.3
+ d3-scale: 4.0.2
+ d3-scale-chromatic: 3.1.0
+ d3-shape: 3.2.0
+ d3-tile: 1.0.0
+ d3-time: 3.1.0
+ memoize: 10.2.0
+ runed: 0.37.1([email protected])
+ svelte: 5.57.0
+ transitivePeerDependencies:
+ - '@sveltejs/kit'
+ - zod
+
+ optional: true
+
+ optional: true
+
+ optional: true
+
+ optional: true
+
+ optional: true
+
+ optional: true
+
+ optional: true
+
+ optional: true
+
+ optional: true
+
+ optional: true
+
+ optional: true
+
+ dependencies:
+ detect-libc: 2.1.2
+ optionalDependencies:
+ lightningcss-android-arm64: 1.32.0
+ lightningcss-darwin-arm64: 1.32.0
+ lightningcss-darwin-x64: 1.32.0
+ lightningcss-freebsd-x64: 1.32.0
+ lightningcss-linux-arm-gnueabihf: 1.32.0
+ lightningcss-linux-arm64-gnu: 1.32.0
+ lightningcss-linux-arm64-musl: 1.32.0
+ lightningcss-linux-x64-gnu: 1.32.0
+ lightningcss-linux-x64-musl: 1.32.0
+ lightningcss-win32-arm64-msvc: 1.32.0
+ lightningcss-win32-x64-msvc: 1.32.0
+
+
+
+
+
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.6.0
+
+ dependencies:
+ mimic-function: 5.0.1
+
+
+
+
+
+
+
+ dependencies:
+ entities: 6.0.1
+
+
+
+
+
+
+ dependencies:
+ playwright-core: 1.63.0
+
+ dependencies:
+ nanoid: 3.3.19
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ dependencies:
+ ansi-regex: 5.0.1
+ ansi-styles: 5.2.0
+ react-is: 17.0.2
+
+
+
+
+
+ dependencies:
+ '@types/estree': 1.0.9
+ optionalDependencies:
+ '@napi-rs/lzma-linux-x64-gnu': 1.5.1
+ '@rollup/rollup-android-arm-eabi': 4.63.3
+ '@rollup/rollup-android-arm64': 4.63.3
+ '@rollup/rollup-darwin-arm64': 4.63.3
+ '@rollup/rollup-darwin-x64': 4.63.3
+ '@rollup/rollup-freebsd-arm64': 4.63.3
+ '@rollup/rollup-freebsd-x64': 4.63.3
+ '@rollup/rollup-linux-arm-gnueabihf': 4.63.3
+ '@rollup/rollup-linux-arm-musleabihf': 4.63.3
+ '@rollup/rollup-linux-arm64-gnu': 4.63.3
+ '@rollup/rollup-linux-arm64-musl': 4.63.3
+ '@rollup/rollup-linux-loong64-gnu': 4.63.3
+ '@rollup/rollup-linux-loong64-musl': 4.63.3
+ '@rollup/rollup-linux-ppc64-gnu': 4.63.3
+ '@rollup/rollup-linux-ppc64-musl': 4.63.3
+ '@rollup/rollup-linux-riscv64-gnu': 4.63.3
+ '@rollup/rollup-linux-riscv64-musl': 4.63.3
+ '@rollup/rollup-linux-s390x-gnu': 4.63.3
+ '@rollup/rollup-linux-x64-gnu': 4.63.3
+ '@rollup/rollup-linux-x64-musl': 4.63.3
+ '@rollup/rollup-openbsd-x64': 4.63.3
+ '@rollup/rollup-openharmony-arm64': 4.63.3
+ '@rollup/rollup-win32-arm64-msvc': 4.63.3
+ '@rollup/rollup-win32-ia32-msvc': 4.63.3
+ '@rollup/rollup-win32-x64-gnu': 4.63.3
+ '@rollup/rollup-win32-x64-msvc': 4.63.3
+ fsevents: 2.3.3
+
+
+ dependencies:
+ dequal: 2.0.3
+ esm-env: 1.2.2
+ lz-string: 1.5.0
+ svelte: 5.57.0
+
+ dependencies:
+ dequal: 2.0.3
+ esm-env: 1.2.2
+ lz-string: 1.5.0
+ svelte: 5.57.0
+
+
+ dependencies:
+ mri: 1.2.0
+
+
+ dependencies:
+ xmlchars: 2.2.0
+
+ dependencies:
+ commander: 14.0.3
+ node-fetch-native: 1.6.7
+ postcss: 8.5.28
+ svelte: 5.57.0
+ tailwind-merge: 3.7.0
+
+
+
+
+
+ dependencies:
+ js-tokens: 9.0.1
+
+
+ dependencies:
+ inline-style-parser: 0.2.7
+
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ '@sveltejs/load-config': 0.2.3
+ chokidar: 4.0.3
+ fdir: 6.5.0([email protected])
+ picocolors: 1.1.1
+ sade: 1.8.1
+ svelte: 5.57.0
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - picomatch
+
+ dependencies:
+ clsx: 2.1.1
+ runed: 0.35.1([email protected])
+ style-to-object: 1.0.14
+ svelte: 5.57.0
+ transitivePeerDependencies:
+ - '@sveltejs/kit'
+
+ dependencies:
+ '@jridgewell/remapping': 2.3.5
+ '@jridgewell/sourcemap-codec': 1.6.0
+ '@sveltejs/acorn-typescript': 1.0.13([email protected])
+ '@types/estree': 1.0.9
+ acorn: 8.18.0
+ aria-query: 5.3.1
+ axobject-query: 4.1.0
+ clsx: 2.1.1
+ devalue: 5.9.2
+ esm-env: 1.2.2
+ esrap: 2.3.7
+ is-reference: 3.0.3
+ locate-character: 3.0.0
+ magic-string: 0.30.21
+ zimmerframe: 1.1.5
+ transitivePeerDependencies:
+ - '@typescript-eslint/types'
+
+
+
+
+ optionalDependencies:
+ tailwind-merge: 3.7.0
+ tailwindcss: 4.3.3
+
+
+
+
+
+ dependencies:
+ fdir: 6.5.0([email protected])
+ picomatch: 4.0.7
+
+
+
+
+
+ dependencies:
+ tldts-core: 6.1.86
+
+ dependencies:
+ tldts: 6.1.86
+
+ dependencies:
+ punycode: 2.3.1
+
+
+
+
+
+
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.3
+ es-module-lexer: 1.7.0
+ pathe: 2.0.3
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+ dependencies:
+ esbuild: 0.25.12
+ fdir: 6.5.0([email protected])
+ picomatch: 4.0.7
+ postcss: 8.5.28
+ rollup: 4.63.3
+ tinyglobby: 0.2.17
+ optionalDependencies:
+ '@types/node': 22.20.3
+ fsevents: 2.3.3
+ jiti: 2.7.0
+ lightningcss: 1.32.0
+
+ optionalDependencies:
+
+ dependencies:
+ '@types/chai': 5.2.3
+ '@vitest/expect': 3.2.7
+ '@vitest/pretty-format': 3.2.7
+ '@vitest/runner': 3.2.7
+ '@vitest/snapshot': 3.2.7
+ '@vitest/spy': 3.2.7
+ '@vitest/utils': 3.2.7
+ chai: 5.3.3
+ debug: 4.4.3
+ expect-type: 1.4.0
+ magic-string: 0.30.21
+ pathe: 2.0.3
+ picomatch: 4.0.7
+ std-env: 3.10.0
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.17
+ tinypool: 1.1.1
+ tinyrainbow: 2.0.0
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 22.20.3
+ jsdom: 26.1.0
+ transitivePeerDependencies:
+ - jiti
+ - less
+ - lightningcss
+ - msw
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
+
+ dependencies:
+ xml-name-validator: 5.0.0
+
+
+ dependencies:
+ iconv-lite: 0.6.3
+
+
+ dependencies:
+ tr46: 5.1.1
+ webidl-conversions: 7.0.0
+
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
+
+
+
+
+ dependencies:
+ tslib: 2.3.0
diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml
new file mode 100644
index 0000000..85b72ba
--- /dev/null
+++ b/frontend/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+allowBuilds:
+ esbuild: true
+ "@tailwindcss/oxide": true
diff --git a/frontend/src/app.css b/frontend/src/app.css
index c9ed4be..a3e02bd 100644
--- a/frontend/src/app.css
+++ b/frontend/src/app.css
@@ -1,3 +1,10 @@
+@import 'tailwindcss';
+@import 'tw-animate-css';
+@import "shadcn-svelte/tailwind.css";
+@import "@fontsource-variable/inter";
+
+@custom-variant dark (&:is(.dark *));
+
:root {
--bg: #f7fafb;
--surface: #ffffff;
@@ -17,11 +24,41 @@
--warn-soft: #fff9ec;
--ok: #15803d;
--ok-soft: #ecfdf3;
- --radius: 10px;
+ --radius: 0.375rem;
--shadow: 0 1px 2px rgba(16, 42, 49, 0.05), 0 2px 8px rgba(16, 42, 49, 0.05);
--mono: ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, Consolas, 'Noto Sans Mono CJK SC', monospace;
--font: -apple-system, 'SF Pro Text', BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
'Noto Sans CJK SC', 'Noto Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif;
+ --background: #f7fafb;
+ --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: #0f766e;
+ --primary-foreground: #ffffff;
+ --secondary: #f1f5f6;
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: #f1f5f6;
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: #f1f5f6;
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --input: #cbd5d8;
+ --ring: #0d9488;
+ --chart-1: #0f766e;
+ --chart-2: #b45309;
+ --chart-3: #1d4ed8;
+ --chart-4: #8b1d8b;
+ --chart-5: #64748b;
+ --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);
}
* { box-sizing: border-box; }
@@ -246,4 +283,90 @@ pre { margin: 0; white-space: pre-wrap; word-break: break-all; }
.btn { white-space: normal; }
}
+.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: #2dd4bf;
+ --primary-foreground: #04211f;
+ --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: #0f766e;
+ --chart-2: #b45309;
+ --chart-3: #1d4ed8;
+ --chart-4: #8b1d8b;
+ --chart-5: #64748b;
+ --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);
+}
+
+@theme inline {
+ --font-sans: 'Inter Variable', sans-serif;
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
+ --color-chart-5: var(--chart-5);
+ --color-chart-4: var(--chart-4);
+ --color-chart-3: var(--chart-3);
+ --color-chart-2: var(--chart-2);
+ --color-chart-1: var(--chart-1);
+ --color-ring: var(--ring);
+ --color-input: var(--input);
+ --color-border: var(--border);
+ --color-destructive: var(--destructive);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-accent: var(--accent);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-muted: var(--muted);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-secondary: var(--secondary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary: var(--primary);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-popover: var(--popover);
+ --color-card-foreground: var(--card-foreground);
+ --color-card: var(--card);
+ --color-foreground: var(--foreground);
+ --color-background: var(--background);
+ --radius-sm: calc(var(--radius) * 0.6);
+ --radius-md: calc(var(--radius) * 0.8);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) * 1.4);
+ --radius-2xl: calc(var(--radius) * 1.8);
+ --radius-3xl: calc(var(--radius) * 2.2);
+ --radius-4xl: calc(var(--radius) * 2.6);
+}
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+ html {
+ @apply font-sans;
+ }
+}
diff --git a/frontend/src/lib/components/ui/alert/alert-action.svelte b/frontend/src/lib/components/ui/alert/alert-action.svelte
new file mode 100644
index 0000000..c60d787
--- /dev/null
+++ b/frontend/src/lib/components/ui/alert/alert-action.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="alert-action"
+ class={cn("absolute top-1.5 right-2", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/alert/alert-description.svelte b/frontend/src/lib/components/ui/alert/alert-description.svelte
new file mode 100644
index 0000000..c5c6223
--- /dev/null
+++ b/frontend/src/lib/components/ui/alert/alert-description.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="alert-description"
+ class={cn(
+ "text-muted-foreground text-xs/relaxed text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/alert/alert-title.svelte b/frontend/src/lib/components/ui/alert/alert-title.svelte
new file mode 100644
index 0000000..371164e
--- /dev/null
+++ b/frontend/src/lib/components/ui/alert/alert-title.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="alert-title"
+ class={cn(
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/alert/alert.svelte b/frontend/src/lib/components/ui/alert/alert.svelte
new file mode 100644
index 0000000..e8f3489
--- /dev/null
+++ b/frontend/src/lib/components/ui/alert/alert.svelte
@@ -0,0 +1,43 @@
+<script lang="ts" module>
+ import { type VariantProps, tv } from "tailwind-variants";
+
+ export const alertVariants = tv({
+ base: "grid gap-0.5 rounded-lg border px-2 py-1.5 text-left text-xs/relaxed has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-1.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-3.5 group/alert relative w-full",
+ variants: {
+ variant: {
+ default: "bg-card text-card-foreground",
+ destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ });
+
+ export type AlertVariant = VariantProps<typeof alertVariants>["variant"];
+</script>
+
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ variant = "default",
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
+ variant?: AlertVariant;
+ } = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="alert"
+ role="alert"
+ class={cn(alertVariants({ variant }), className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/alert/index.ts b/frontend/src/lib/components/ui/alert/index.ts
new file mode 100644
index 0000000..501249b
--- /dev/null
+++ b/frontend/src/lib/components/ui/alert/index.ts
@@ -0,0 +1,17 @@
+import Action from "./alert-action.svelte";
+import Description from "./alert-description.svelte";
+import Title from "./alert-title.svelte";
+import Root from "./alert.svelte";
+export { alertVariants, type AlertVariant } from "./alert.svelte";
+
+export {
+ Root,
+ Description,
+ Title,
+ Action,
+ //
+ Root as Alert,
+ Description as AlertDescription,
+ Title as AlertTitle,
+ Action as AlertAction,
+};
diff --git a/frontend/src/lib/components/ui/badge/badge.svelte b/frontend/src/lib/components/ui/badge/badge.svelte
new file mode 100644
index 0000000..0b34af3
--- /dev/null
+++ b/frontend/src/lib/components/ui/badge/badge.svelte
@@ -0,0 +1,49 @@
+<script lang="ts" module>
+ import { type VariantProps, tv } from "tailwind-variants";
+
+ export const badgeVariants = tv({
+ base: "h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-2.5! group/badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none",
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
+ destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20",
+ outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30",
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ });
+
+ export type BadgeVariant = VariantProps<typeof badgeVariants>["variant"];
+</script>
+
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAnchorAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ href,
+ class: className,
+ variant = "default",
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAnchorAttributes> & {
+ variant?: BadgeVariant;
+ } = $props();
+</script>
+
+<svelte:element
+ this={href ? "a" : "span"}
+ bind:this={ref}
+ data-slot="badge"
+ {href}
+ class={cn(badgeVariants({ variant }), className)}
+ {...restProps}
+>
+ {@render children?.()}
+</svelte:element>
diff --git a/frontend/src/lib/components/ui/badge/index.ts b/frontend/src/lib/components/ui/badge/index.ts
new file mode 100644
index 0000000..64e0aa9
--- /dev/null
+++ b/frontend/src/lib/components/ui/badge/index.ts
@@ -0,0 +1,2 @@
+export { default as Badge } from "./badge.svelte";
+export { badgeVariants, type BadgeVariant } from "./badge.svelte";
diff --git a/frontend/src/lib/components/ui/button/button.svelte b/frontend/src/lib/components/ui/button/button.svelte
new file mode 100644
index 0000000..7669a5e
--- /dev/null
+++ b/frontend/src/lib/components/ui/button/button.svelte
@@ -0,0 +1,82 @@
+<script lang="ts" module>
+ import { type VariantProps, tv } from "tailwind-variants";
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from "svelte/elements";
+
+ export const buttonVariants = tv({
+ base: "focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border border-transparent bg-clip-padding text-xs/relaxed font-medium focus-visible:ring-2 aria-invalid:ring-2 active:not-aria-[haspopup]:translate-y-px [&_svg:not([class*='size-'])]:size-4 group/button inline-flex shrink-0 items-center justify-center whitespace-nowrap transition-all outline-none select-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/80",
+ outline: "border-border dark:bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
+ secondary: "bg-secondary text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)] aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
+ ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground",
+ destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-7 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
+ xs: "h-5 gap-1 rounded-sm px-2 text-[0.625rem] has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-2.5",
+ sm: "h-6 gap-1 px-2 text-xs/relaxed has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
+ lg: "h-8 gap-1 px-2.5 text-xs/relaxed has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-4",
+ icon: "size-7 [&_svg:not([class*='size-'])]:size-3.5",
+ "icon-xs": "size-5 rounded-sm [&_svg:not([class*='size-'])]:size-2.5",
+ "icon-sm": "size-6 [&_svg:not([class*='size-'])]:size-3",
+ "icon-lg": "size-8 [&_svg:not([class*='size-'])]:size-4",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ });
+
+ export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
+ export type ButtonSize = VariantProps<typeof buttonVariants>["size"];
+
+ export type ButtonProps = WithElementRef<HTMLButtonAttributes> &
+ WithElementRef<HTMLAnchorAttributes> & {
+ variant?: ButtonVariant;
+ size?: ButtonSize;
+ };
+</script>
+
+<script lang="ts">
+ let {
+ class: className,
+ variant = "default",
+ size = "default",
+ ref = $bindable(null),
+ href = undefined,
+ type = "button",
+ disabled,
+ children,
+ ...restProps
+ }: ButtonProps = $props();
+</script>
+
+{#if href}
+ <a
+ bind:this={ref}
+ data-slot="button"
+ class={cn(buttonVariants({ variant, size }), className)}
+ href={disabled ? undefined : href}
+ aria-disabled={disabled}
+ role={disabled ? "link" : undefined}
+ tabindex={disabled ? -1 : undefined}
+ {...restProps}
+ >
+ {@render children?.()}
+ </a>
+{:else}
+ <button
+ bind:this={ref}
+ data-slot="button"
+ class={cn(buttonVariants({ variant, size }), className)}
+ {type}
+ {disabled}
+ {...restProps}
+ >
+ {@render children?.()}
+ </button>
+{/if}
diff --git a/frontend/src/lib/components/ui/button/index.ts b/frontend/src/lib/components/ui/button/index.ts
new file mode 100644
index 0000000..fb585d7
--- /dev/null
+++ b/frontend/src/lib/components/ui/button/index.ts
@@ -0,0 +1,17 @@
+import Root, {
+ type ButtonProps,
+ type ButtonSize,
+ type ButtonVariant,
+ buttonVariants,
+} from "./button.svelte";
+
+export {
+ Root,
+ type ButtonProps as Props,
+ //
+ Root as Button,
+ buttonVariants,
+ type ButtonProps,
+ type ButtonSize,
+ type ButtonVariant,
+};
diff --git a/frontend/src/lib/components/ui/card/card-action.svelte b/frontend/src/lib/components/ui/card/card-action.svelte
new file mode 100644
index 0000000..53b3861
--- /dev/null
+++ b/frontend/src/lib/components/ui/card/card-action.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card-action"
+ class={cn(
+ "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/card/card-content.svelte b/frontend/src/lib/components/ui/card/card-content.svelte
new file mode 100644
index 0000000..bf5dac7
--- /dev/null
+++ b/frontend/src/lib/components/ui/card/card-content.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card-content"
+ class={cn("px-(--card-spacing)", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/card/card-description.svelte b/frontend/src/lib/components/ui/card/card-description.svelte
new file mode 100644
index 0000000..5d5c09a
--- /dev/null
+++ b/frontend/src/lib/components/ui/card/card-description.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLParagraphElement>> = $props();
+</script>
+
+<p
+ bind:this={ref}
+ data-slot="card-description"
+ class={cn("text-muted-foreground text-xs/relaxed", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</p>
diff --git a/frontend/src/lib/components/ui/card/card-footer.svelte b/frontend/src/lib/components/ui/card/card-footer.svelte
new file mode 100644
index 0000000..ce4a2c0
--- /dev/null
+++ b/frontend/src/lib/components/ui/card/card-footer.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card-footer"
+ class={cn("rounded-b-lg px-(--card-spacing) [.border-t]:pt-(--card-spacing) flex items-center", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/card/card-header.svelte b/frontend/src/lib/components/ui/card/card-header.svelte
new file mode 100644
index 0000000..019a3f9
--- /dev/null
+++ b/frontend/src/lib/components/ui/card/card-header.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card-header"
+ class={cn(
+ "gap-1 rounded-t-lg px-(--card-spacing) [.border-b]:pb-(--card-spacing) group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]",
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/card/card-title.svelte b/frontend/src/lib/components/ui/card/card-title.svelte
new file mode 100644
index 0000000..d95ec39
--- /dev/null
+++ b/frontend/src/lib/components/ui/card/card-title.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card-title"
+ class={cn("text-sm font-medium", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/card/card.svelte b/frontend/src/lib/components/ui/card/card.svelte
new file mode 100644
index 0000000..345eaf2
--- /dev/null
+++ b/frontend/src/lib/components/ui/card/card.svelte
@@ -0,0 +1,22 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ size = "default",
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { size?: "default" | "sm" } = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="card"
+ data-size={size}
+ class={cn("ring-foreground/10 bg-card text-card-foreground gap-(--card-spacing) overflow-hidden rounded-lg py-(--card-spacing) text-xs/relaxed ring-1 [--card-spacing:--spacing(4)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg group/card flex flex-col", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/card/index.ts b/frontend/src/lib/components/ui/card/index.ts
new file mode 100644
index 0000000..5705f13
--- /dev/null
+++ b/frontend/src/lib/components/ui/card/index.ts
@@ -0,0 +1,25 @@
+import Action from "./card-action.svelte";
+import Content from "./card-content.svelte";
+import Description from "./card-description.svelte";
+import Footer from "./card-footer.svelte";
+import Header from "./card-header.svelte";
+import Title from "./card-title.svelte";
+import Root from "./card.svelte";
+
+export {
+ Root,
+ Content,
+ Description,
+ Footer,
+ Header,
+ Title,
+ Action,
+ //
+ Root as Card,
+ Content as CardContent,
+ Description as CardDescription,
+ Footer as CardFooter,
+ Header as CardHeader,
+ Title as CardTitle,
+ Action as CardAction,
+};
diff --git a/frontend/src/lib/components/ui/chart/chart-container.svelte b/frontend/src/lib/components/ui/chart/chart-container.svelte
new file mode 100644
index 0000000..3687ea0
--- /dev/null
+++ b/frontend/src/lib/components/ui/chart/chart-container.svelte
@@ -0,0 +1,80 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import ChartStyle from "./chart-style.svelte";
+ import { setChartContext, type ChartConfig } from "./chart-utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ const uid = $props.id();
+
+ let {
+ ref = $bindable(null),
+ id = uid,
+ class: className,
+ children,
+ config,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> & {
+ config: ChartConfig;
+ } = $props();
+
+ const chartId = $derived(`chart-${id || uid.replace(/:/g, "")}`);
+
+ setChartContext({
+ get config() {
+ return config;
+ },
+ });
+</script>
+
+<div
+ bind:this={ref}
+ data-chart={chartId}
+ data-slot="chart"
+ class={cn(
+ "flex aspect-video justify-center overflow-visible text-xs",
+ // Overrides
+ //
+ // Stroke around dots/marks when hovering
+ "[&_.lc-highlight-point]:stroke-transparent",
+ // override the default stroke color of lines
+ "[&_.lc-line]:stroke-border/50",
+
+ // by default, layerchart shows a line intersecting the point when hovering, this hides that
+ "[&_.lc-highlight-line]:stroke-0",
+
+ // by default, when you hover a point on a stacked series chart, it will drop the opacity
+ // of the other series, this overrides that
+ "[&_.lc-area-path]:opacity-100 [&_.lc-highlight-line]:opacity-100 [&_.lc-highlight-point]:opacity-100 [&_.lc-spline-path]:opacity-100 [&_.lc-text]:text-xs [&_.lc-text-svg]:overflow-visible",
+
+ // We don't want the little tick lines between the axis labels and the chart, so we remove
+ // the stroke. The alternative is to manually disable `tickMarks` on the x/y axis of every
+ // chart.
+ "[&_.lc-axis-tick]:stroke-0",
+
+ // We don't want to display the rule on the x/y axis, as there is already going to be
+ // a grid line there and rule ends up overlapping the marks because it is rendered after
+ // the marks
+ "[&_.lc-rule-x-line:not(.lc-grid-x-rule)]:stroke-0 [&_.lc-rule-y-line:not(.lc-grid-y-rule)]:stroke-0",
+ "[&_.lc-grid-x-radial-circle]:stroke-border [&_.lc-grid-x-radial-line]:stroke-border",
+ "[&_.lc-grid-y-radial-circle]:stroke-border [&_.lc-grid-y-radial-line]:stroke-border",
+
+ // Legend adjustments
+ "[&_.lc-legend-swatch-button]:items-center [&_.lc-legend-swatch-button]:gap-1.5",
+ "[&_.lc-legend-swatch-group]:items-center [&_.lc-legend-swatch-group]:gap-4",
+ "[&_.lc-legend-swatch]:size-2.5 [&_.lc-legend-swatch]:rounded-[2px]",
+
+ // Labels
+ "[&_.lc-labels-text:not([fill])]:fill-foreground [&_text]:stroke-transparent",
+
+ // Tick labels on th x/y axes
+ "[&_.lc-axis-tick-label]:fill-muted-foreground [&_.lc-axis-tick-label]:font-normal",
+ "[&_.lc-tooltip-rects-g]:fill-transparent",
+ "[&_.lc-layout-svg-g]:fill-transparent",
+ "[&_.lc-root-container]:w-full",
+ className
+ )}
+ {...restProps}
+>
+ <ChartStyle id={chartId} {config} />
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/chart/chart-style.svelte b/frontend/src/lib/components/ui/chart/chart-style.svelte
new file mode 100644
index 0000000..74c2b8a
--- /dev/null
+++ b/frontend/src/lib/components/ui/chart/chart-style.svelte
@@ -0,0 +1,37 @@
+<script lang="ts">
+ import { THEMES, type ChartConfig } from "./chart-utils.js";
+
+ let { id, config }: { id: string; config: ChartConfig } = $props();
+
+ const colorConfig = $derived(
+ config ? Object.entries(config).filter(([, config]) => config.theme || config.color) : null
+ );
+
+ const themeContents = $derived.by(() => {
+ if (!colorConfig || !colorConfig.length) return;
+
+ const themeContents = [];
+ for (const [_theme, prefix] of Object.entries(THEMES)) {
+ let content = `${prefix} [data-chart=${id}] {\n`;
+ const color = colorConfig.map(([key, itemConfig]) => {
+ const theme = _theme as keyof typeof itemConfig.theme;
+ const color = itemConfig.theme?.[theme] || itemConfig.color;
+ return color ? `\t--color-${key}: ${color};` : null;
+ });
+
+ content += color.join("\n") +"\n}";
+
+ themeContents.push(content);
+ }
+
+ return themeContents.join("\n");
+ });
+</script>
+
+{#if themeContents}
+ {#key id}
+ <svelte:element this={"style"}>
+ {themeContents}
+ </svelte:element>
+ {/key}
+{/if}
diff --git a/frontend/src/lib/components/ui/chart/chart-tooltip.svelte b/frontend/src/lib/components/ui/chart/chart-tooltip.svelte
new file mode 100644
index 0000000..8811436
--- /dev/null
+++ b/frontend/src/lib/components/ui/chart/chart-tooltip.svelte
@@ -0,0 +1,180 @@
+<script lang="ts">
+ import { getChartContext, Tooltip as TooltipPrimitive } from "layerchart";
+ import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js";
+ import { getPayloadConfigFromPayload, useChart, type TooltipPayload } from "./chart-utils.js";
+ import type { Snippet } from "svelte";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ function defaultFormatter(value: any, _payload: TooltipPayload[]) {
+ return `${value}`;
+ }
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ hideLabel = false,
+ indicator = "dot",
+ hideIndicator = false,
+ labelKey,
+ label,
+ labelFormatter = defaultFormatter,
+ labelClassName,
+ formatter,
+ nameKey,
+ color,
+ ...restProps
+ }: WithoutChildren<WithElementRef<HTMLAttributes<HTMLDivElement>>> & {
+ hideLabel?: boolean;
+ label?: string;
+ indicator?: "line" | "dot" | "dashed";
+ nameKey?: string;
+ labelKey?: string;
+ hideIndicator?: boolean;
+ labelClassName?: string;
+ labelFormatter?: // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ((value: any, payload: TooltipPayload[]) => string | number | Snippet) | null;
+ formatter?: Snippet<
+ [
+ {
+ value: unknown;
+ name: string;
+ item: TooltipPayload;
+ index: number;
+ payload: TooltipPayload[];
+ },
+ ]
+ >;
+ } = $props();
+
+ const chart = useChart();
+ const chartCtx = getChartContext();
+
+ // Filter to series with defined values (important for item-based charts like Pie/Arc
+ // where only the hovered item has a value)
+ const visibleSeries = $derived(
+ chartCtx.tooltip.series.filter((s: TooltipPayload) => s.value !== undefined)
+ );
+
+ const formattedLabel = $derived.by(() => {
+ if (hideLabel || !visibleSeries?.length) return null;
+
+ const [item] = visibleSeries;
+ const tooltipData = chartCtx.tooltip.data;
+
+ // Get the x-axis label value from the raw tooltip data (e.g. a Date or month string)
+ const dataLabel = tooltipData != null ? chartCtx.x(tooltipData) : undefined;
+
+ const key = labelKey ?? item?.label ?? item?.key ?? "value";
+ const itemConfig = getPayloadConfigFromPayload(
+ chart.config,
+ item,
+ key,
+ tooltipData as Record<string, unknown> | null
+ );
+
+ let value: unknown;
+ if (!labelKey && typeof label === "string") {
+ value = chart.config[label as keyof typeof chart.config]?.label ?? label;
+ } else if (labelKey) {
+ value = itemConfig?.label ?? dataLabel;
+ } else {
+ value = dataLabel;
+ }
+
+ if (value === undefined) return null;
+ if (!labelFormatter) return value;
+ return labelFormatter(value, visibleSeries);
+ });
+
+ const nestLabel = $derived(visibleSeries.length === 1 && indicator !== "dot");
+</script>
+
+{#snippet TooltipLabel()}
+ {#if formattedLabel}
+ <div class={cn("font-medium", labelClassName)}>
+ {#if typeof formattedLabel === "function"}
+ {@render formattedLabel()}
+ {:else}
+ {formattedLabel}
+ {/if}
+ </div>
+ {/if}
+{/snippet}
+
+<TooltipPrimitive.Root variant="none">
+ <div
+ bind:this={ref}
+ class={cn(
+ "grid min-w-32 items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
+ className
+ )}
+ {...restProps}
+ >
+ {#if !nestLabel}
+ {@render TooltipLabel()}
+ {/if}
+ <div class="grid gap-1.5">
+ {#each visibleSeries as item, i (item.key + i)}
+ {@const key = `${nameKey || item.key || item.label || "value"}`}
+ {@const itemConfig = getPayloadConfigFromPayload(
+ chart.config,
+ item,
+ key,
+ chartCtx.tooltip.data
+ )}
+ {@const indicatorColor = color || item.config?.color || item.color}
+ <div
+ class={cn(
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
+ indicator === "dot" && "items-center"
+ )}
+ >
+ {#if formatter && item.value !== undefined && item.label}
+ {@render formatter({
+ value: item.value,
+ name: item.label,
+ item,
+ index: i,
+ payload: visibleSeries,
+ })}
+ {:else}
+ {#if itemConfig?.icon}
+ <itemConfig.icon />
+ {:else if !hideIndicator}
+ <div
+ style="--color-bg: {indicatorColor}; --color-border: {indicatorColor};"
+ class={cn("shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)", {
+ "h-2.5 w-2.5": indicator === "dot",
+ "h-full w-1": indicator === "line",
+ "w-0 border-[1.5px] border-dashed bg-transparent": indicator === "dashed",
+ "my-0.5": nestLabel && indicator === "dashed",
+ })}
+ ></div>
+ {/if}
+ <div
+ class={cn(
+ "flex flex-1 shrink-0 justify-between leading-none",
+ nestLabel ? "items-end" : "items-center"
+ )}
+ >
+ <div class="grid gap-1.5">
+ {#if nestLabel}
+ {@render TooltipLabel()}
+ {/if}
+ <span class="text-muted-foreground">
+ {itemConfig?.label || item.label}
+ </span>
+ </div>
+ {#if item.value !== undefined}
+ <span class="font-mono font-medium text-foreground tabular-nums">
+ {item.value.toLocaleString()}
+ </span>
+ {/if}
+ </div>
+ {/if}
+ </div>
+ {/each}
+ </div>
+ </div>
+</TooltipPrimitive.Root>
diff --git a/frontend/src/lib/components/ui/chart/chart-utils.ts b/frontend/src/lib/components/ui/chart/chart-utils.ts
new file mode 100644
index 0000000..6e1f000
--- /dev/null
+++ b/frontend/src/lib/components/ui/chart/chart-utils.ts
@@ -0,0 +1,68 @@
+import { getContext, setContext, type Component, type Snippet } from "svelte";
+import type { Tooltip } from "layerchart";
+
+export const THEMES = { light: "", dark: ".dark" } as const;
+
+export type ChartConfig = {
+ [k in string]: {
+ label?: string;
+ icon?: Component;
+ } & (
+ | { color?: string; theme?: never }
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
+ );
+};
+
+export type ExtractSnippetParams<T> = T extends Snippet<[infer P]> ? P : never;
+
+export type TooltipPayload = Tooltip.TooltipSeries;
+
+// Helper to extract item config from a payload.
+export function getPayloadConfigFromPayload(
+ config: ChartConfig,
+ payload: TooltipPayload,
+ key: string,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ data?: Record<string, any> | null
+) {
+ if (typeof payload !== "object" || payload === null) return undefined;
+
+ const payloadConfig =
+ "config" in payload && typeof payload.config === "object" && payload.config !== null
+ ? payload.config
+ : undefined;
+
+ let configLabelKey: string = key;
+
+ if (payload.key === key) {
+ configLabelKey = payload.key;
+ } else if (payload.label === key) {
+ configLabelKey = payload.label;
+ } else if (key in payload && typeof payload[key as keyof typeof payload] === "string") {
+ configLabelKey = payload[key as keyof typeof payload] as string;
+ } else if (
+ payloadConfig !== undefined &&
+ key in payloadConfig &&
+ typeof payloadConfig[key as keyof typeof payloadConfig] === "string"
+ ) {
+ configLabelKey = payloadConfig[key as keyof typeof payloadConfig] as string;
+ } else if (data != null && key in data && typeof data[key] === "string") {
+ configLabelKey = data[key] as string;
+ }
+
+ return configLabelKey in config ? config[configLabelKey] : config[key as keyof typeof config];
+}
+
+type ChartContextValue = {
+ config: ChartConfig;
+};
+
+const chartContextKey = Symbol("chart-context");
+
+export function setChartContext(value: ChartContextValue) {
+ return setContext(chartContextKey, value);
+}
+
+export function useChart() {
+ return getContext<ChartContextValue>(chartContextKey);
+}
diff --git a/frontend/src/lib/components/ui/chart/index.ts b/frontend/src/lib/components/ui/chart/index.ts
new file mode 100644
index 0000000..f22375e
--- /dev/null
+++ b/frontend/src/lib/components/ui/chart/index.ts
@@ -0,0 +1,6 @@
+import ChartContainer from "./chart-container.svelte";
+import ChartTooltip from "./chart-tooltip.svelte";
+
+export { getPayloadConfigFromPayload, type ChartConfig } from "./chart-utils.js";
+
+export { ChartContainer, ChartTooltip, ChartContainer as Container, ChartTooltip as Tooltip };
diff --git a/frontend/src/lib/components/ui/input/index.ts b/frontend/src/lib/components/ui/input/index.ts
new file mode 100644
index 0000000..f47b6d3
--- /dev/null
+++ b/frontend/src/lib/components/ui/input/index.ts
@@ -0,0 +1,7 @@
+import Root from "./input.svelte";
+
+export {
+ Root,
+ //
+ Root as Input,
+};
diff --git a/frontend/src/lib/components/ui/input/input.svelte b/frontend/src/lib/components/ui/input/input.svelte
new file mode 100644
index 0000000..549c6b8
--- /dev/null
+++ b/frontend/src/lib/components/ui/input/input.svelte
@@ -0,0 +1,48 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLInputAttributes, HTMLInputTypeAttribute } from "svelte/elements";
+
+ type InputType = Exclude<HTMLInputTypeAttribute, "file">;
+
+ type Props = WithElementRef<
+ Omit<HTMLInputAttributes, "type"> &
+ ({ type: "file"; files?: FileList } | { type?: InputType; files?: undefined })
+ >;
+
+ let {
+ ref = $bindable(null),
+ value = $bindable(),
+ type,
+ files = $bindable(),
+ class: className,
+ "data-slot": dataSlot = "input",
+ ...restProps
+ }: Props = $props();
+</script>
+
+{#if type === "file"}
+ <input
+ bind:this={ref}
+ data-slot={dataSlot}
+ class={cn(
+ "bg-input/20 dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-7 rounded-md border px-2 py-0.5 text-sm transition-colors file:h-6 file:text-xs/relaxed file:font-medium focus-visible:ring-2 aria-invalid:ring-2 md:text-xs/relaxed w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
+ className
+ )}
+ type="file"
+ bind:files
+ bind:value
+ {...restProps}
+ />
+{:else}
+ <input
+ bind:this={ref}
+ data-slot={dataSlot}
+ class={cn(
+ "bg-input/20 dark:bg-input/30 border-input focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-7 rounded-md border px-2 py-0.5 text-sm transition-colors file:h-6 file:text-xs/relaxed file:font-medium focus-visible:ring-2 aria-invalid:ring-2 md:text-xs/relaxed w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50",
+ className
+ )}
+ {type}
+ bind:value
+ {...restProps}
+ />
+{/if}
diff --git a/frontend/src/lib/components/ui/label/index.ts b/frontend/src/lib/components/ui/label/index.ts
new file mode 100644
index 0000000..8bfca0b
--- /dev/null
+++ b/frontend/src/lib/components/ui/label/index.ts
@@ -0,0 +1,7 @@
+import Root from "./label.svelte";
+
+export {
+ Root,
+ //
+ Root as Label,
+};
diff --git a/frontend/src/lib/components/ui/label/label.svelte b/frontend/src/lib/components/ui/label/label.svelte
new file mode 100644
index 0000000..f1e8c35
--- /dev/null
+++ b/frontend/src/lib/components/ui/label/label.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { Label as LabelPrimitive } from "bits-ui";
+ import { cn } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: LabelPrimitive.RootProps = $props();
+</script>
+
+<LabelPrimitive.Root
+ bind:ref
+ data-slot="label"
+ class={cn(
+ "gap-2 text-xs/relaxed leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed",
+ className
+ )}
+ {...restProps}
+/>
diff --git a/frontend/src/lib/components/ui/select/index.ts b/frontend/src/lib/components/ui/select/index.ts
new file mode 100644
index 0000000..4960d45
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/index.ts
@@ -0,0 +1,40 @@
+import Content from "./select-content.svelte";
+import GroupHeading from "./select-group-heading.svelte";
+import Group from "./select-group.svelte";
+import Item from "./select-item.svelte";
+import Label from "./select-label.svelte";
+import Portal from "./select-portal.svelte";
+import ScrollDownButton from "./select-scroll-down-button.svelte";
+import ScrollUpButton from "./select-scroll-up-button.svelte";
+import Separator from "./select-separator.svelte";
+import Trigger from "./select-trigger.svelte";
+import Value from "./select-value.svelte";
+import Root from "./select.svelte";
+
+export {
+ Root,
+ Group,
+ Label,
+ Item,
+ Content,
+ Trigger,
+ Separator,
+ ScrollDownButton,
+ ScrollUpButton,
+ GroupHeading,
+ Portal,
+ Value,
+ //
+ Root as Select,
+ Group as SelectGroup,
+ Label as SelectLabel,
+ Item as SelectItem,
+ Content as SelectContent,
+ Trigger as SelectTrigger,
+ Separator as SelectSeparator,
+ ScrollDownButton as SelectScrollDownButton,
+ ScrollUpButton as SelectScrollUpButton,
+ GroupHeading as SelectGroupHeading,
+ Portal as SelectPortal,
+ Value as SelectValue,
+};
diff --git a/frontend/src/lib/components/ui/select/select-content.svelte b/frontend/src/lib/components/ui/select/select-content.svelte
new file mode 100644
index 0000000..3ec8408
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-content.svelte
@@ -0,0 +1,45 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+ import { cn, type WithoutChild } from "$lib/utils.js";
+ import type { WithoutChildrenOrChild } from "$lib/utils.js";
+ import SelectPortal from "./select-portal.svelte";
+ import SelectScrollDownButton from "./select-scroll-down-button.svelte";
+ import SelectScrollUpButton from "./select-scroll-up-button.svelte";
+ import type { ComponentProps } from "svelte";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ sideOffset = 4,
+ portalProps,
+ children,
+ preventScroll = true,
+ ...restProps
+ }: WithoutChild<SelectPrimitive.ContentProps> & {
+ portalProps?: WithoutChildrenOrChild<ComponentProps<typeof SelectPortal>>;
+ } = $props();
+</script>
+
+<SelectPortal {...portalProps}>
+ <SelectPrimitive.Content
+ bind:ref
+ {sideOffset}
+ {preventScroll}
+ data-slot="select-content"
+ class={cn(
+ "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-32 rounded-lg shadow-md ring-1 duration-100 relative z-50 max-h-(--bits-select-content-available-height) origin-(--bits-select-content-transform-origin) overflow-x-hidden overflow-y-auto",
+ className
+ )}
+ {...restProps}
+ >
+ <SelectScrollUpButton />
+ <SelectPrimitive.Viewport
+ class={cn(
+ "h-(--bits-select-anchor-height) w-full min-w-(--bits-select-anchor-width) scroll-my-1"
+ )}
+ >
+ {@render children?.()}
+ </SelectPrimitive.Viewport>
+ <SelectScrollDownButton />
+ </SelectPrimitive.Content>
+</SelectPortal>
diff --git a/frontend/src/lib/components/ui/select/select-group-heading.svelte b/frontend/src/lib/components/ui/select/select-group-heading.svelte
new file mode 100644
index 0000000..1fab5f0
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-group-heading.svelte
@@ -0,0 +1,21 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+ import { cn } from "$lib/utils.js";
+ import type { ComponentProps } from "svelte";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: ComponentProps<typeof SelectPrimitive.GroupHeading> = $props();
+</script>
+
+<SelectPrimitive.GroupHeading
+ bind:ref
+ data-slot="select-group-heading"
+ class={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</SelectPrimitive.GroupHeading>
diff --git a/frontend/src/lib/components/ui/select/select-group.svelte b/frontend/src/lib/components/ui/select/select-group.svelte
new file mode 100644
index 0000000..f666cb2
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-group.svelte
@@ -0,0 +1,17 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+ import { cn } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: SelectPrimitive.GroupProps = $props();
+</script>
+
+<SelectPrimitive.Group
+ bind:ref
+ data-slot="select-group"
+ class={cn("scroll-my-1 p-1", className)}
+ {...restProps}
+/>
diff --git a/frontend/src/lib/components/ui/select/select-item.svelte b/frontend/src/lib/components/ui/select/select-item.svelte
new file mode 100644
index 0000000..446dfdc
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-item.svelte
@@ -0,0 +1,42 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+ import CheckIcon from '@lucide/svelte/icons/check';
+ import { cn, type WithoutChild } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ value,
+ label,
+ children: childrenProp,
+ ...restProps
+ }: WithoutChild<SelectPrimitive.ItemProps> = $props();
+</script>
+
+<SelectPrimitive.Item
+ bind:ref
+ {value}
+ {label}
+ data-slot="select-item"
+ class={cn(
+ "focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground min-h-7 gap-2 rounded-md px-2 py-1 text-xs/relaxed [&_svg:not([class*='size-'])]:size-3.5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground",
+ className
+ )}
+ {...restProps}
+>
+ {#snippet children({ selected, highlighted })}
+ <span class="pointer-events-none absolute right-2 flex items-center justify-center">
+ {#if selected}
+ <CheckIcon class="pointer-events-none" />
+ {/if}
+ </span>
+ <span class="flex flex-1 gap-2 shrink-0 whitespace-nowrap">
+ {#if childrenProp}
+ {@render childrenProp({ selected, highlighted })}
+ {:else}
+ {label || value}
+ {/if}
+ </span>
+ {/snippet}
+</SelectPrimitive.Item>
diff --git a/frontend/src/lib/components/ui/select/select-label.svelte b/frontend/src/lib/components/ui/select/select-label.svelte
new file mode 100644
index 0000000..4696025
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-label.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {} = $props();
+</script>
+
+<div
+ bind:this={ref}
+ data-slot="select-label"
+ class={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
diff --git a/frontend/src/lib/components/ui/select/select-portal.svelte b/frontend/src/lib/components/ui/select/select-portal.svelte
new file mode 100644
index 0000000..424bcdd
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-portal.svelte
@@ -0,0 +1,7 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+
+ let { ...restProps }: SelectPrimitive.PortalProps = $props();
+</script>
+
+<SelectPrimitive.Portal {...restProps} />
diff --git a/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte b/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte
new file mode 100644
index 0000000..7bc111e
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-scroll-down-button.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+ import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
+ import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: WithoutChildrenOrChild<SelectPrimitive.ScrollDownButtonProps> = $props();
+</script>
+
+<SelectPrimitive.ScrollDownButton
+ bind:ref
+ data-slot="select-scroll-down-button"
+ class={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5", className)}
+ {...restProps}
+>
+ <ChevronDownIcon />
+</SelectPrimitive.ScrollDownButton>
diff --git a/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte b/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte
new file mode 100644
index 0000000..6c066ff
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-scroll-up-button.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+ import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
+ import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: WithoutChildrenOrChild<SelectPrimitive.ScrollUpButtonProps> = $props();
+</script>
+
+<SelectPrimitive.ScrollUpButton
+ bind:ref
+ data-slot="select-scroll-up-button"
+ class={cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5", className)}
+ {...restProps}
+>
+ <ChevronUpIcon />
+</SelectPrimitive.ScrollUpButton>
diff --git a/frontend/src/lib/components/ui/select/select-separator.svelte b/frontend/src/lib/components/ui/select/select-separator.svelte
new file mode 100644
index 0000000..aac5ca3
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-separator.svelte
@@ -0,0 +1,18 @@
+<script lang="ts">
+ import { Separator } from "$lib/components/ui/separator/index.js";
+ import { cn } from "$lib/utils.js";
+ import type { Separator as SeparatorPrimitive } from "bits-ui";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: SeparatorPrimitive.RootProps = $props();
+</script>
+
+<Separator
+ bind:ref
+ data-slot="select-separator"
+ class={cn("bg-border/50 -mx-1 my-1 h-px pointer-events-none", className)}
+ {...restProps}
+/>
diff --git a/frontend/src/lib/components/ui/select/select-trigger.svelte b/frontend/src/lib/components/ui/select/select-trigger.svelte
new file mode 100644
index 0000000..3acff3b
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-trigger.svelte
@@ -0,0 +1,29 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+ import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
+ import { cn, type WithoutChild } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ size = "default",
+ ...restProps
+ }: WithoutChild<SelectPrimitive.TriggerProps> & {
+ size?: "sm" | "default";
+ } = $props();
+</script>
+
+<SelectPrimitive.Trigger
+ bind:ref
+ data-slot="select-trigger"
+ data-size={size}
+ class={cn(
+ "border-input data-placeholder:text-muted-foreground bg-input/20 dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 gap-1.5 rounded-md border px-2 py-1.5 text-xs/relaxed transition-colors focus-visible:ring-2 aria-invalid:ring-2 data-[size=default]:h-7 data-[size=sm]:h-6 *:data-[slot=select-value]:flex *:data-[slot=select-value]:gap-1.5 [&_svg:not([class*='size-'])]:size-3.5 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+ <ChevronDownIcon class="text-muted-foreground size-3.5 pointer-events-none" />
+</SelectPrimitive.Trigger>
diff --git a/frontend/src/lib/components/ui/select/select-value.svelte b/frontend/src/lib/components/ui/select/select-value.svelte
new file mode 100644
index 0000000..ff34b0e
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select-value.svelte
@@ -0,0 +1,7 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+
+ let { ref = $bindable(null), ...restProps }: SelectPrimitive.ValueProps = $props();
+</script>
+
+<SelectPrimitive.Value bind:ref data-slot="select-value" {...restProps} />
diff --git a/frontend/src/lib/components/ui/select/select.svelte b/frontend/src/lib/components/ui/select/select.svelte
new file mode 100644
index 0000000..05eb663
--- /dev/null
+++ b/frontend/src/lib/components/ui/select/select.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+ import { Select as SelectPrimitive } from "bits-ui";
+
+ let {
+ open = $bindable(false),
+ value = $bindable(),
+ ...restProps
+ }: SelectPrimitive.RootProps = $props();
+</script>
+
+<SelectPrimitive.Root bind:open bind:value={value as never} {...restProps} />
diff --git a/frontend/src/lib/components/ui/separator/index.ts b/frontend/src/lib/components/ui/separator/index.ts
new file mode 100644
index 0000000..82442d2
--- /dev/null
+++ b/frontend/src/lib/components/ui/separator/index.ts
@@ -0,0 +1,7 @@
+import Root from "./separator.svelte";
+
+export {
+ Root,
+ //
+ Root as Separator,
+};
diff --git a/frontend/src/lib/components/ui/separator/separator.svelte b/frontend/src/lib/components/ui/separator/separator.svelte
new file mode 100644
index 0000000..e91b176
--- /dev/null
+++ b/frontend/src/lib/components/ui/separator/separator.svelte
@@ -0,0 +1,21 @@
+<script lang="ts">
+ import { Separator as SeparatorPrimitive } from "bits-ui";
+ import { cn } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ "data-slot": dataSlot = "separator",
+ ...restProps
+ }: SeparatorPrimitive.RootProps = $props();
+</script>
+
+<SeparatorPrimitive.Root
+ bind:ref
+ data-slot={dataSlot}
+ class={cn(
+ "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
+ className
+ )}
+ {...restProps}
+/>
diff --git a/frontend/src/lib/components/ui/table/index.ts b/frontend/src/lib/components/ui/table/index.ts
new file mode 100644
index 0000000..841ee58
--- /dev/null
+++ b/frontend/src/lib/components/ui/table/index.ts
@@ -0,0 +1,28 @@
+import Body from "./table-body.svelte";
+import Caption from "./table-caption.svelte";
+import Cell from "./table-cell.svelte";
+import Footer from "./table-footer.svelte";
+import Head from "./table-head.svelte";
+import Header from "./table-header.svelte";
+import Row from "./table-row.svelte";
+import Root from "./table.svelte";
+
+export {
+ Root,
+ Body,
+ Caption,
+ Cell,
+ Footer,
+ Head,
+ Header,
+ Row,
+ //
+ Root as Table,
+ Body as TableBody,
+ Caption as TableCaption,
+ Cell as TableCell,
+ Footer as TableFooter,
+ Head as TableHead,
+ Header as TableHeader,
+ Row as TableRow,
+};
diff --git a/frontend/src/lib/components/ui/table/table-body.svelte b/frontend/src/lib/components/ui/table/table-body.svelte
new file mode 100644
index 0000000..935feae
--- /dev/null
+++ b/frontend/src/lib/components/ui/table/table-body.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
+</script>
+
+<tbody bind:this={ref} data-slot="table-body" class={cn("[&_tr:last-child]:border-0", className)} {...restProps}>
+ {@render children?.()}
+</tbody>
diff --git a/frontend/src/lib/components/ui/table/table-caption.svelte b/frontend/src/lib/components/ui/table/table-caption.svelte
new file mode 100644
index 0000000..b7a3e98
--- /dev/null
+++ b/frontend/src/lib/components/ui/table/table-caption.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLElement>> = $props();
+</script>
+
+<caption
+ bind:this={ref}
+ data-slot="table-caption"
+ class={cn("text-muted-foreground mt-4 text-xs", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</caption>
diff --git a/frontend/src/lib/components/ui/table/table-cell.svelte b/frontend/src/lib/components/ui/table/table-cell.svelte
new file mode 100644
index 0000000..a998bf6
--- /dev/null
+++ b/frontend/src/lib/components/ui/table/table-cell.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLTdAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLTdAttributes> = $props();
+</script>
+
+<td bind:this={ref} data-slot="table-cell" class={cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0", className)} {...restProps}>
+ {@render children?.()}
+</td>
diff --git a/frontend/src/lib/components/ui/table/table-footer.svelte b/frontend/src/lib/components/ui/table/table-footer.svelte
new file mode 100644
index 0000000..b9b14eb
--- /dev/null
+++ b/frontend/src/lib/components/ui/table/table-footer.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
+</script>
+
+<tfoot
+ bind:this={ref}
+ data-slot="table-footer"
+ class={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</tfoot>
diff --git a/frontend/src/lib/components/ui/table/table-head.svelte b/frontend/src/lib/components/ui/table/table-head.svelte
new file mode 100644
index 0000000..267c4e0
--- /dev/null
+++ b/frontend/src/lib/components/ui/table/table-head.svelte
@@ -0,0 +1,15 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLThAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLThAttributes> = $props();
+</script>
+
+<th bind:this={ref} data-slot="table-head" class={cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", className)} {...restProps}>
+ {@render children?.()}
+</th>
diff --git a/frontend/src/lib/components/ui/table/table-header.svelte b/frontend/src/lib/components/ui/table/table-header.svelte
new file mode 100644
index 0000000..f47d259
--- /dev/null
+++ b/frontend/src/lib/components/ui/table/table-header.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLTableSectionElement>> = $props();
+</script>
+
+<thead
+ bind:this={ref}
+ data-slot="table-header"
+ class={cn("[&_tr]:border-b", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</thead>
diff --git a/frontend/src/lib/components/ui/table/table-row.svelte b/frontend/src/lib/components/ui/table/table-row.svelte
new file mode 100644
index 0000000..c7b05c8
--- /dev/null
+++ b/frontend/src/lib/components/ui/table/table-row.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLTableRowElement>> = $props();
+</script>
+
+<tr
+ bind:this={ref}
+ data-slot="table-row"
+ class={cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors has-aria-expanded:bg-muted/50", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</tr>
diff --git a/frontend/src/lib/components/ui/table/table.svelte b/frontend/src/lib/components/ui/table/table.svelte
new file mode 100644
index 0000000..b2b477f
--- /dev/null
+++ b/frontend/src/lib/components/ui/table/table.svelte
@@ -0,0 +1,17 @@
+<script lang="ts">
+ import { cn, type WithElementRef } from "$lib/utils.js";
+ import type { HTMLTableAttributes } from "svelte/elements";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLTableAttributes> = $props();
+</script>
+
+<div data-slot="table-container" class="relative w-full overflow-x-auto">
+ <table bind:this={ref} data-slot="table" class={cn("w-full caption-bottom text-xs", className)} {...restProps}>
+ {@render children?.()}
+ </table>
+</div>
diff --git a/frontend/src/lib/components/ui/tabs/index.ts b/frontend/src/lib/components/ui/tabs/index.ts
new file mode 100644
index 0000000..581b188
--- /dev/null
+++ b/frontend/src/lib/components/ui/tabs/index.ts
@@ -0,0 +1,18 @@
+import Content from "./tabs-content.svelte";
+import Trigger from "./tabs-trigger.svelte";
+import Root from "./tabs.svelte";
+import List, { tabsListVariants, type TabsListVariant } from "./tabs-list.svelte";
+
+export {
+ Root,
+ Content,
+ List,
+ Trigger,
+ tabsListVariants,
+ type TabsListVariant,
+ //
+ Root as Tabs,
+ Content as TabsContent,
+ List as TabsList,
+ Trigger as TabsTrigger,
+};
diff --git a/frontend/src/lib/components/ui/tabs/tabs-content.svelte b/frontend/src/lib/components/ui/tabs/tabs-content.svelte
new file mode 100644
index 0000000..b9e4530
--- /dev/null
+++ b/frontend/src/lib/components/ui/tabs/tabs-content.svelte
@@ -0,0 +1,17 @@
+<script lang="ts">
+ import { Tabs as TabsPrimitive } from "bits-ui";
+ import { cn } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: TabsPrimitive.ContentProps = $props();
+</script>
+
+<TabsPrimitive.Content
+ bind:ref
+ data-slot="tabs-content"
+ class={cn("text-xs/relaxed flex-1 outline-none", className)}
+ {...restProps}
+/>
diff --git a/frontend/src/lib/components/ui/tabs/tabs-list.svelte b/frontend/src/lib/components/ui/tabs/tabs-list.svelte
new file mode 100644
index 0000000..4445c0f
--- /dev/null
+++ b/frontend/src/lib/components/ui/tabs/tabs-list.svelte
@@ -0,0 +1,40 @@
+<script lang="ts" module>
+ import { tv, type VariantProps } from "tailwind-variants";
+
+ export const tabsListVariants = tv({
+ base: "rounded-lg p-[3px] group-data-horizontal/tabs:h-8 data-[variant=line]:rounded-none group/tabs-list inline-flex w-fit items-center justify-center text-muted-foreground group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col",
+ variants: {
+ variant: {
+ default: "bg-muted",
+ line: "gap-1 bg-transparent",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ });
+
+ export type TabsListVariant = VariantProps<typeof tabsListVariants>["variant"];
+</script>
+
+<script lang="ts">
+ import { Tabs as TabsPrimitive } from "bits-ui";
+ import { cn } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ variant = "default",
+ class: className,
+ ...restProps
+ }: TabsPrimitive.ListProps & {
+ variant?: TabsListVariant;
+ } = $props();
+</script>
+
+<TabsPrimitive.List
+ bind:ref
+ data-slot="tabs-list"
+ data-variant={variant}
+ class={cn(tabsListVariants({ variant }), className)}
+ {...restProps}
+/>
diff --git a/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte b/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte
new file mode 100644
index 0000000..c243f83
--- /dev/null
+++ b/frontend/src/lib/components/ui/tabs/tabs-trigger.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import { Tabs as TabsPrimitive } from "bits-ui";
+ import { cn } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: TabsPrimitive.TriggerProps = $props();
+</script>
+
+<TabsPrimitive.Trigger
+ bind:ref
+ data-slot="tabs-trigger"
+ class={cn(
+ "gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-xs font-medium group-data-vertical/tabs:py-[calc(--spacing(1.25))] [&_svg:not([class*='size-'])]:size-3.5 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 relative inline-flex h-[calc(100%_-_1px)] flex-1 items-center justify-center whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
+ "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
+ "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
+ className
+ )}
+ {...restProps}
+/>
diff --git a/frontend/src/lib/components/ui/tabs/tabs.svelte b/frontend/src/lib/components/ui/tabs/tabs.svelte
new file mode 100644
index 0000000..a7b461a
--- /dev/null
+++ b/frontend/src/lib/components/ui/tabs/tabs.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+ import { Tabs as TabsPrimitive } from "bits-ui";
+ import { cn } from "$lib/utils.js";
+
+ let {
+ ref = $bindable(null),
+ value = $bindable(""),
+ class: className,
+ ...restProps
+ }: TabsPrimitive.RootProps = $props();
+</script>
+
+<TabsPrimitive.Root
+ bind:ref
+ bind:value
+ data-slot="tabs"
+ class={cn("gap-2 group/tabs flex data-horizontal:flex-col", className)}
+ {...restProps}
+/>
diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts
new file mode 100644
index 0000000..55b3a91
--- /dev/null
+++ b/frontend/src/lib/utils.ts
@@ -0,0 +1,13 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type WithoutChildren<T> = T extends { children?: any } ? Omit<T, "children"> : T;
+export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
+export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 9dea8ab..efcce9b 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -11,7 +11,12 @@
"moduleDetection": "force",
"verbatimModuleSyntax": true,
"types": ["vite/client", "vitest/globals"],
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "baseUrl": ".",
+ "paths": {
+ "$lib": ["./src/lib"],
+ "$lib/*": ["./src/lib/*"]
+ }
},
- "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
+ "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte", "vite.config.ts"]
}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 3178f19..8c48766 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -1,11 +1,16 @@
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
+import tailwindcss from '@tailwindcss/vite';
+import path from 'node:path';
export default defineConfig({
- plugins: [svelte()],
+ plugins: [svelte(), tailwindcss()],
// resolve svelte's browser (client) build so tests can actually mount
// components; production client bundles already resolve the same entry
- resolve: { conditions: ['browser'] },
+ resolve: {
+ conditions: ['browser'],
+ alias: { $lib: path.resolve(process.cwd(), 'src/lib') }
+ },
build: { target: 'es2020' },
test: {
environment: 'jsdom',
@@ -16,7 +21,7 @@ export default defineConfig({
// let vitest resolve the browser (client) build of svelte so component
// mounting is possible in jsdom regression tests
resolve: { conditions: ['browser'] }
- },
+ } as never,
server: {
proxy: {
'/api': {