diff --git a/frontend/astro.config.mjs b/frontend/astro.config.mjs
index 8d50f98..573f0b9 100644
--- a/frontend/astro.config.mjs
+++ b/frontend/astro.config.mjs
@@ -8,5 +8,10 @@ import tailwind from "@astrojs/tailwind";
// https://astro.build/config
export default defineConfig({
- integrations: [svelte(), tailwind()],
+ integrations: [
+ svelte(),
+ tailwind({
+ applyBaseStyles: false,
+ }),
+ ],
});
diff --git a/frontend/components.json b/frontend/components.json
new file mode 100644
index 0000000..9041a39
--- /dev/null
+++ b/frontend/components.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://shadcn-svelte.com/schema.json",
+ "style": "default",
+ "tailwind": {
+ "config": "tailwind.config.mjs",
+ "css": "src/styles/app.css",
+ "baseColor": "slate"
+ },
+ "aliases": {
+ "components": "$lib/components",
+ "utils": "$lib/utils"
+ },
+ "typescript": true
+}
\ No newline at end of file
diff --git a/frontend/package.json b/frontend/package.json
index d8bcf05..bb5ad13 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -19,10 +19,15 @@
"typescript": "^5.8.2"
},
"devDependencies": {
+ "bits-ui": "0.22.0",
+ "clsx": "^2.1.1",
"prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-svelte": "^3.3.3",
- "prettier-plugin-tailwindcss": "^0.6.11"
+ "prettier-plugin-tailwindcss": "^0.6.11",
+ "tailwind-merge": "^3.0.2",
+ "tailwind-variants": "^1.0.0",
+ "vaul-svelte": "^0.3.2"
},
"pnpm": {
"onlyBuiltDependencies": [
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 1dbfa36..cd2a498 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -30,6 +30,12 @@ importers:
specifier: ^5.8.2
version: 5.8.2
devDependencies:
+ bits-ui:
+ specifier: 0.22.0
+ version: 0.22.0(svelte@5.22.6)
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
prettier:
specifier: ^3.5.3
version: 3.5.3
@@ -42,6 +48,15 @@ importers:
prettier-plugin-tailwindcss:
specifier: ^0.6.11
version: 0.6.11(prettier-plugin-astro@0.14.1)(prettier-plugin-svelte@3.3.3(prettier@3.5.3)(svelte@5.22.6))(prettier@3.5.3)
+ tailwind-merge:
+ specifier: ^3.0.2
+ version: 3.0.2
+ tailwind-variants:
+ specifier: ^1.0.0
+ version: 1.0.0(tailwindcss@3.4.17)
+ vaul-svelte:
+ specifier: ^0.3.2
+ version: 0.3.2(svelte@5.22.6)
packages:
@@ -296,6 +311,15 @@ packages:
cpu: [x64]
os: [win32]
+ '@floating-ui/core@1.6.9':
+ resolution: {integrity: sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==}
+
+ '@floating-ui/dom@1.6.13':
+ resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
+
+ '@floating-ui/utils@0.2.9':
+ resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
+
'@img/sharp-darwin-arm64@0.33.5':
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
@@ -401,6 +425,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@internationalized/date@3.7.0':
+ resolution: {integrity: sha512-VJ5WS3fcVx0bejE/YHfbDKR/yawZgKqn/if+oEeLqNwBtPzVB06olkfcnojTmEMX+gTpH+FlQ69SHNitJ8/erQ==}
+
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -423,6 +450,11 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+ '@melt-ui/svelte@0.76.2':
+ resolution: {integrity: sha512-7SbOa11tXUS95T3fReL+dwDs5FyJtCEqrqG3inRziDws346SYLsxOQ6HmX+4BkIsQh1R8U3XNa+EMmdMt38lMA==}
+ peerDependencies:
+ svelte: '>=3 <5'
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -587,6 +619,9 @@ packages:
svelte: ^5.0.0
vite: ^6.0.0
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
@@ -719,6 +754,16 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
+ bits-ui@0.21.16:
+ resolution: {integrity: sha512-XFZ7/bK7j/K+5iktxX/ZpmoFHjYjpPzP5EOO/4bWiaFg5TG1iMcfjDhlBTQnJxD6BoVoHuqeZPHZvaTgF4Iv3Q==}
+ peerDependencies:
+ svelte: ^4.0.0 || ^5.0.0-next.118
+
+ bits-ui@0.22.0:
+ resolution: {integrity: sha512-r7Fw1HNgA4YxZBRcozl7oP0bheQ8EHh+kfMBZJgyFISix8t4p/nqDcHLmBgIiJ3T5XjYnJRorYDjIWaCfhb5fw==}
+ peerDependencies:
+ svelte: ^4.0.0 || ^5.0.0
+
boxen@8.0.1:
resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==}
engines: {node: '>=18'}
@@ -990,6 +1035,9 @@ packages:
resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==}
engines: {node: '>=8'}
+ focus-trap@7.6.4:
+ resolution: {integrity: sha512-xx560wGBk7seZ6y933idtjJQc1l+ck+pI3sKvhKozdBV1dRZoKhkW5xoCaFv9tQiX5RH1xfSxjuNu6g+lmN/gw==}
+
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
@@ -1362,6 +1410,11 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
+ nanoid@5.1.3:
+ resolution: {integrity: sha512-zAbEOEr7u2CbxwoMRlz/pNSpRP0FdAU4pRaYunCdEezWohXFs+a0Xw7RfkKaezMsmSM1vttcLthJtwRnVtOfHQ==}
+ engines: {node: ^18 || >=20}
+ hasBin: true
+
neotraverse@0.6.18:
resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==}
engines: {node: '>= 10'}
@@ -1814,6 +1867,18 @@ packages:
resolution: {integrity: sha512-dxHyh3USJyayafSt5I5QD7KuoCM5ZGdIOtLQiKHEro7tymdh0jMcNkiSBVHW+LOA2jEqZEHhyfwN6/pCjx0Fug==}
engines: {node: '>=18'}
+ tabbable@6.2.0:
+ resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
+
+ tailwind-merge@3.0.2:
+ resolution: {integrity: sha512-l7z+OYZ7mu3DTqrL88RiKrKIqO3NcpEO8V/Od04bNpvk0kiIFndGEoqfuzvj4yuhRkHKjRkII2z+KS2HfPcSxw==}
+
+ tailwind-variants@1.0.0:
+ resolution: {integrity: sha512-2WSbv4ulEEyuBKomOunut65D8UZwxrHoRfYnxGcQNnHqlSCp2+B7Yz2W+yrNDrxRodOXtGD/1oCcKGNBnUqMqA==}
+ engines: {node: '>=16.x', pnpm: '>=7.x'}
+ peerDependencies:
+ tailwindcss: '*'
+
tailwindcss@3.4.17:
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
engines: {node: '>=14.0.0'}
@@ -1981,6 +2046,11 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ vaul-svelte@0.3.2:
+ resolution: {integrity: sha512-X4OGWttSTVUl417qGDsSFgOvIx24DoiMRY/jaP9z0v9FL8LQQJ0RQ1ZM0QpdyQPRlNd24ewjNQHh5EgYDtfNpw==}
+ peerDependencies:
+ svelte: ^4.0.0 || ^5.0.0-next.1
+
vfile-location@5.0.3:
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
@@ -2480,6 +2550,17 @@ snapshots:
'@esbuild/win32-x64@0.25.0':
optional: true
+ '@floating-ui/core@1.6.9':
+ dependencies:
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/dom@1.6.13':
+ dependencies:
+ '@floating-ui/core': 1.6.9
+ '@floating-ui/utils': 0.2.9
+
+ '@floating-ui/utils@0.2.9': {}
+
'@img/sharp-darwin-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.4
@@ -2555,6 +2636,10 @@ snapshots:
'@img/sharp-win32-x64@0.33.5':
optional: true
+ '@internationalized/date@3.7.0':
+ dependencies:
+ '@swc/helpers': 0.5.15
+
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -2581,6 +2666,16 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
+ '@melt-ui/svelte@0.76.2(svelte@5.22.6)':
+ dependencies:
+ '@floating-ui/core': 1.6.9
+ '@floating-ui/dom': 1.6.13
+ '@internationalized/date': 3.7.0
+ dequal: 2.0.3
+ focus-trap: 7.6.4
+ nanoid: 5.1.3
+ svelte: 5.22.6
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -2724,6 +2819,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
'@types/cookie@0.6.0': {}
'@types/debug@4.1.12':
@@ -2959,6 +3058,20 @@ snapshots:
binary-extensions@2.3.0: {}
+ bits-ui@0.21.16(svelte@5.22.6):
+ dependencies:
+ '@internationalized/date': 3.7.0
+ '@melt-ui/svelte': 0.76.2(svelte@5.22.6)
+ nanoid: 5.1.3
+ svelte: 5.22.6
+
+ bits-ui@0.22.0(svelte@5.22.6):
+ dependencies:
+ '@internationalized/date': 3.7.0
+ '@melt-ui/svelte': 0.76.2(svelte@5.22.6)
+ nanoid: 5.1.3
+ svelte: 5.22.6
+
boxen@8.0.1:
dependencies:
ansi-align: 3.0.1
@@ -3217,6 +3330,10 @@ snapshots:
flattie@1.1.1: {}
+ focus-trap@7.6.4:
+ dependencies:
+ tabbable: 6.2.0
+
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
@@ -3810,6 +3927,8 @@ snapshots:
nanoid@3.3.9: {}
+ nanoid@5.1.3: {}
+
neotraverse@0.6.18: {}
nlcst-to-string@4.0.0:
@@ -4297,6 +4416,15 @@ snapshots:
magic-string: 0.30.17
zimmerframe: 1.1.2
+ tabbable@6.2.0: {}
+
+ tailwind-merge@3.0.2: {}
+
+ tailwind-variants@1.0.0(tailwindcss@3.4.17):
+ dependencies:
+ tailwind-merge: 3.0.2
+ tailwindcss: 3.4.17
+
tailwindcss@3.4.17:
dependencies:
'@alloc/quick-lru': 5.2.0
@@ -4442,6 +4570,11 @@ snapshots:
util-deprecate@1.0.2: {}
+ vaul-svelte@0.3.2(svelte@5.22.6):
+ dependencies:
+ bits-ui: 0.21.16(svelte@5.22.6)
+ svelte: 5.22.6
+
vfile-location@5.0.3:
dependencies:
'@types/unist': 3.0.3
diff --git a/frontend/src/components/Container.svelte b/frontend/src/components/Container.svelte
index 407ca75..787cd19 100644
--- a/frontend/src/components/Container.svelte
+++ b/frontend/src/components/Container.svelte
@@ -3,6 +3,7 @@
import type { TrackerData } from "../utils/types";
import Drag from "./Drag.svelte";
import Slider from "./Slider.svelte";
+ import Settings from "./Settings.svelte";
let ws: WebSocket | null = $state(null);
let image: HTMLImageElement | null = $state(null);
@@ -65,7 +66,9 @@
-
+
+
+

+ import * as Drawer from "$lib/components/ui/drawer/index.js";
+ import { Button } from "$lib/components/ui/button/index.js";
+ import TrackerSetting from "./TrackerSetting.svelte";
+
+ const addTracker = async () => {
+ const response = await fetch("/tracker", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ id: 4,
+ }),
+ });
+
+ console.log(response);
+ };
+
+ const deleteTracker = async () => {
+ const response = await fetch("/tracker", {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ id: 4,
+ }),
+ });
+ console.log(response);
+ };
+
+
+
+
+
+
+
+
+ Are you sure absolutely sure?
+ This action cannot be undone.
+
+
+
+
+
+
+
diff --git a/frontend/src/components/Slider.svelte b/frontend/src/components/Slider.svelte
index 679a811..b01880d 100644
--- a/frontend/src/components/Slider.svelte
+++ b/frontend/src/components/Slider.svelte
@@ -20,15 +20,19 @@
let arena_state: "full_arena" | "scene_only" = $state("scene_only");
+ let tracker = trackers.find((ball) => {
+ ball.id === selected;
+ });
+
const onchange = () => {
- const x_send = trackers[selected].x / width;
- const y_send = trackers[selected].y / height;
+ const x_send = tracker!.x / width;
+ const y_send = tracker!.y / height;
ws?.send(
JSON.stringify({
- id: trackers[selected].id,
+ id: tracker!.id,
x: x_send,
y: y_send,
- z: trackers[selected].z,
+ z: tracker!.z,
}),
);
};
@@ -52,7 +56,7 @@
});
};
- let z_viz = $derived(trackers[selected].z.toFixed(2));
+ let z_viz = $derived(tracker?.z.toFixed(2));
@@ -64,7 +68,6 @@
max="4"
step="0.01"
oninput={onchange}
- bind:value={trackers[selected].z}
/>
diff --git a/frontend/src/components/TrackerSetting.svelte b/frontend/src/components/TrackerSetting.svelte
new file mode 100644
index 0000000..015737b
--- /dev/null
+++ b/frontend/src/components/TrackerSetting.svelte
@@ -0,0 +1,17 @@
+
+
+
+
{text}
+
+
+
diff --git a/frontend/src/components/ui/button/button.svelte b/frontend/src/components/ui/button/button.svelte
new file mode 100644
index 0000000..86827f3
--- /dev/null
+++ b/frontend/src/components/ui/button/button.svelte
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/button/index.ts b/frontend/src/components/ui/button/index.ts
new file mode 100644
index 0000000..af1e188
--- /dev/null
+++ b/frontend/src/components/ui/button/index.ts
@@ -0,0 +1,49 @@
+import { type VariantProps, tv } from "tailwind-variants";
+import type { Button as ButtonPrimitive } from "bits-ui";
+import Root from "./button.svelte";
+
+const buttonVariants = tv({
+ base: "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
+ outline:
+ "border-input bg-background hover:bg-accent hover:text-accent-foreground border",
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-10 px-4 py-2",
+ sm: "h-9 rounded-md px-3",
+ lg: "h-11 rounded-md px-8",
+ icon: "h-10 w-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+});
+
+type Variant = VariantProps
["variant"];
+type Size = VariantProps["size"];
+
+type Props = ButtonPrimitive.Props & {
+ variant?: Variant;
+ size?: Size;
+};
+
+type Events = ButtonPrimitive.Events;
+
+export {
+ Root,
+ type Props,
+ type Events,
+ //
+ Root as Button,
+ type Props as ButtonProps,
+ type Events as ButtonEvents,
+ buttonVariants,
+};
diff --git a/frontend/src/components/ui/drawer/drawer-content.svelte b/frontend/src/components/ui/drawer/drawer-content.svelte
new file mode 100644
index 0000000..4f05317
--- /dev/null
+++ b/frontend/src/components/ui/drawer/drawer-content.svelte
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/ui/drawer/drawer-description.svelte b/frontend/src/components/ui/drawer/drawer-description.svelte
new file mode 100644
index 0000000..ef761a7
--- /dev/null
+++ b/frontend/src/components/ui/drawer/drawer-description.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/drawer/drawer-footer.svelte b/frontend/src/components/ui/drawer/drawer-footer.svelte
new file mode 100644
index 0000000..c6c07ad
--- /dev/null
+++ b/frontend/src/components/ui/drawer/drawer-footer.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/drawer/drawer-header.svelte b/frontend/src/components/ui/drawer/drawer-header.svelte
new file mode 100644
index 0000000..f857176
--- /dev/null
+++ b/frontend/src/components/ui/drawer/drawer-header.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/drawer/drawer-nested.svelte b/frontend/src/components/ui/drawer/drawer-nested.svelte
new file mode 100644
index 0000000..79b68e3
--- /dev/null
+++ b/frontend/src/components/ui/drawer/drawer-nested.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/drawer/drawer-overlay.svelte b/frontend/src/components/ui/drawer/drawer-overlay.svelte
new file mode 100644
index 0000000..ccc7322
--- /dev/null
+++ b/frontend/src/components/ui/drawer/drawer-overlay.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/drawer/drawer-title.svelte b/frontend/src/components/ui/drawer/drawer-title.svelte
new file mode 100644
index 0000000..cfbe596
--- /dev/null
+++ b/frontend/src/components/ui/drawer/drawer-title.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/drawer/drawer.svelte b/frontend/src/components/ui/drawer/drawer.svelte
new file mode 100644
index 0000000..40eae5e
--- /dev/null
+++ b/frontend/src/components/ui/drawer/drawer.svelte
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/frontend/src/components/ui/drawer/index.ts b/frontend/src/components/ui/drawer/index.ts
new file mode 100644
index 0000000..76932e3
--- /dev/null
+++ b/frontend/src/components/ui/drawer/index.ts
@@ -0,0 +1,41 @@
+import { Drawer as DrawerPrimitive } from "vaul-svelte";
+
+import Root from "./drawer.svelte";
+import Content from "./drawer-content.svelte";
+import Description from "./drawer-description.svelte";
+import Overlay from "./drawer-overlay.svelte";
+import Footer from "./drawer-footer.svelte";
+import Header from "./drawer-header.svelte";
+import Title from "./drawer-title.svelte";
+import NestedRoot from "./drawer-nested.svelte";
+
+const Trigger = DrawerPrimitive.Trigger;
+const Portal = DrawerPrimitive.Portal;
+const Close = DrawerPrimitive.Close;
+
+export {
+ Root,
+ NestedRoot,
+ Content,
+ Description,
+ Overlay,
+ Footer,
+ Header,
+ Title,
+ Trigger,
+ Portal,
+ Close,
+
+ //
+ Root as Drawer,
+ NestedRoot as DrawerNestedRoot,
+ Content as DrawerContent,
+ Description as DrawerDescription,
+ Overlay as DrawerOverlay,
+ Footer as DrawerFooter,
+ Header as DrawerHeader,
+ Title as DrawerTitle,
+ Trigger as DrawerTrigger,
+ Portal as DrawerPortal,
+ Close as DrawerClose,
+};
diff --git a/frontend/src/components/ui/input/index.ts b/frontend/src/components/ui/input/index.ts
new file mode 100644
index 0000000..75e3bc2
--- /dev/null
+++ b/frontend/src/components/ui/input/index.ts
@@ -0,0 +1,29 @@
+import Root from "./input.svelte";
+
+export type FormInputEvent = T & {
+ currentTarget: EventTarget & HTMLInputElement;
+};
+export type InputEvents = {
+ blur: FormInputEvent;
+ change: FormInputEvent;
+ click: FormInputEvent;
+ focus: FormInputEvent;
+ focusin: FormInputEvent;
+ focusout: FormInputEvent;
+ keydown: FormInputEvent;
+ keypress: FormInputEvent;
+ keyup: FormInputEvent;
+ mouseover: FormInputEvent;
+ mouseenter: FormInputEvent;
+ mouseleave: FormInputEvent;
+ mousemove: FormInputEvent;
+ paste: FormInputEvent;
+ input: FormInputEvent;
+ wheel: FormInputEvent;
+};
+
+export {
+ Root,
+ //
+ Root as Input,
+};
diff --git a/frontend/src/components/ui/input/input.svelte b/frontend/src/components/ui/input/input.svelte
new file mode 100644
index 0000000..cab1457
--- /dev/null
+++ b/frontend/src/components/ui/input/input.svelte
@@ -0,0 +1,42 @@
+
+
+
diff --git a/frontend/src/pages/index.astro b/frontend/src/pages/index.astro
index b433749..08719cb 100644
--- a/frontend/src/pages/index.astro
+++ b/frontend/src/pages/index.astro
@@ -1,5 +1,6 @@
---
import Container from "../components/Container.svelte";
+import "$lib/styles/app.css";
---
diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css
new file mode 100644
index 0000000..1460463
--- /dev/null
+++ b/frontend/src/styles/app.css
@@ -0,0 +1,78 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 222.2 84% 4.9%;
+
+ --muted: 210 40% 96.1%;
+ --muted-foreground: 215.4 16.3% 46.9%;
+
+ --popover: 0 0% 100%;
+ --popover-foreground: 222.2 84% 4.9%;
+
+ --card: 0 0% 100%;
+ --card-foreground: 222.2 84% 4.9%;
+
+ --border: 214.3 31.8% 91.4%;
+ --input: 214.3 31.8% 91.4%;
+
+ --primary: 222.2 47.4% 11.2%;
+ --primary-foreground: 210 40% 98%;
+
+ --secondary: 210 40% 96.1%;
+ --secondary-foreground: 222.2 47.4% 11.2%;
+
+ --accent: 210 40% 96.1%;
+ --accent-foreground: 222.2 47.4% 11.2%;
+
+ --destructive: 0 72.2% 50.6%;
+ --destructive-foreground: 210 40% 98%;
+
+ --ring: 222.2 84% 4.9%;
+
+ --radius: 0.5rem;
+ }
+
+ .dark {
+ --background: 222.2 84% 4.9%;
+ --foreground: 210 40% 98%;
+
+ --muted: 217.2 32.6% 17.5%;
+ --muted-foreground: 215 20.2% 65.1%;
+
+ --popover: 222.2 84% 4.9%;
+ --popover-foreground: 210 40% 98%;
+
+ --card: 222.2 84% 4.9%;
+ --card-foreground: 210 40% 98%;
+
+ --border: 217.2 32.6% 17.5%;
+ --input: 217.2 32.6% 17.5%;
+
+ --primary: 210 40% 98%;
+ --primary-foreground: 222.2 47.4% 11.2%;
+
+ --secondary: 217.2 32.6% 17.5%;
+ --secondary-foreground: 210 40% 98%;
+
+ --accent: 217.2 32.6% 17.5%;
+ --accent-foreground: 210 40% 98%;
+
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 210 40% 98%;
+
+ --ring: 212.7 26.8% 83.9%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts
new file mode 100644
index 0000000..8871245
--- /dev/null
+++ b/frontend/src/utils.ts
@@ -0,0 +1,62 @@
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+import { cubicOut } from "svelte/easing";
+import type { TransitionConfig } from "svelte/transition";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+
+type FlyAndScaleParams = {
+ y?: number;
+ x?: number;
+ start?: number;
+ duration?: number;
+};
+
+export const flyAndScale = (
+ node: Element,
+ params: FlyAndScaleParams = { y: -8, x: 0, start: 0.95, duration: 150 }
+): TransitionConfig => {
+ const style = getComputedStyle(node);
+ const transform = style.transform === "none" ? "" : style.transform;
+
+ const scaleConversion = (
+ valueA: number,
+ scaleA: [number, number],
+ scaleB: [number, number]
+ ) => {
+ const [minA, maxA] = scaleA;
+ const [minB, maxB] = scaleB;
+
+ const percentage = (valueA - minA) / (maxA - minA);
+ const valueB = percentage * (maxB - minB) + minB;
+
+ return valueB;
+ };
+
+ const styleToString = (
+ style: Record
+ ): string => {
+ return Object.keys(style).reduce((str, key) => {
+ if (style[key] === undefined) return str;
+ return str + `${key}:${style[key]};`;
+ }, "");
+ };
+
+ return {
+ duration: params.duration ?? 200,
+ delay: 0,
+ css: (t) => {
+ const y = scaleConversion(t, [0, 1], [params.y ?? 5, 0]);
+ const x = scaleConversion(t, [0, 1], [params.x ?? 0, 0]);
+ const scale = scaleConversion(t, [0, 1], [params.start ?? 0.95, 1]);
+
+ return styleToString({
+ transform: `${transform} translate3d(${x}px, ${y}px, 0) scale(${scale})`,
+ opacity: t
+ });
+ },
+ easing: cubicOut
+ };
+};
\ No newline at end of file
diff --git a/frontend/tailwind.config.mjs b/frontend/tailwind.config.mjs
index f8a2096..fa1cb36 100644
--- a/frontend/tailwind.config.mjs
+++ b/frontend/tailwind.config.mjs
@@ -1,8 +1,64 @@
+import { fontFamily } from "tailwindcss/defaultTheme";
+
/** @type {import('tailwindcss').Config} */
-export default {
- content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
- theme: {
- extend: {},
- },
- plugins: [],
+const config = {
+ darkMode: ["class"],
+ content: ["./src/**/*.{html,js,svelte,ts}"],
+ safelist: ["dark"],
+ theme: {
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px"
+ }
+ },
+ extend: {
+ colors: {
+ border: "hsl(var(--border) / )",
+ input: "hsl(var(--input) / )",
+ ring: "hsl(var(--ring) / )",
+ background: "hsl(var(--background) / )",
+ foreground: "hsl(var(--foreground) / )",
+ primary: {
+ DEFAULT: "hsl(var(--primary) / )",
+ foreground: "hsl(var(--primary-foreground) / )"
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary) / )",
+ foreground: "hsl(var(--secondary-foreground) / )"
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive) / )",
+ foreground: "hsl(var(--destructive-foreground) / )"
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted) / )",
+ foreground: "hsl(var(--muted-foreground) / )"
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent) / )",
+ foreground: "hsl(var(--accent-foreground) / )"
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover) / )",
+ foreground: "hsl(var(--popover-foreground) / )"
+ },
+ card: {
+ DEFAULT: "hsl(var(--card) / )",
+ foreground: "hsl(var(--card-foreground) / )"
+ }
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)"
+ },
+ fontFamily: {
+ sans: [...fontFamily.sans]
+ }
+ }
+ },
};
+
+export default config;