summaryrefslogtreecommitdiff
path: root/apps
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-06-22 13:07:02 +0200
committerMaze Winther <mazewinther@gmail.com>2025-06-22 13:07:02 +0200
commitbe970248686c0af7d8506cba8c64bf507419da3d (patch)
tree3fd8a716b7adae452f1be6dcdd946533da47ad8a /apps
parentf2d27e578e6214598d36779f44fb1612764c7b3b (diff)
downloadopencut-be970248686c0af7d8506cba8c64bf507419da3d.tar.gz
so much stuff???
Diffstat (limited to 'apps')
-rw-r--r--apps/web/drizzle.config.ts23
-rw-r--r--apps/web/migrations/0000_hot_the_fallen.sql54
-rw-r--r--apps/web/migrations/meta/0000_snapshot.json319
-rw-r--r--apps/web/migrations/meta/_journal.json13
-rw-r--r--apps/web/package-lock.json1149
-rw-r--r--apps/web/package.json11
-rw-r--r--apps/web/src/app/api/auth/[...all]/route.ts4
-rw-r--r--apps/web/src/app/editor/page.tsx66
-rw-r--r--apps/web/src/app/fonts/GeistMonoVF.woffbin67864 -> 0 bytes
-rw-r--r--apps/web/src/app/fonts/GeistVF.woffbin66268 -> 0 bytes
-rw-r--r--apps/web/src/app/globals.css6
-rw-r--r--apps/web/src/app/layout.tsx31
-rw-r--r--apps/web/src/app/page.tsx12
-rw-r--r--apps/web/src/components/app-provider.tsx42
-rw-r--r--apps/web/src/components/editor-header.tsx56
-rw-r--r--apps/web/src/components/editor/media-panel.tsx133
-rw-r--r--apps/web/src/components/editor/preview-panel.tsx (renamed from apps/web/src/components/preview-panel.tsx)0
-rw-r--r--apps/web/src/components/editor/properties-panel.tsx (renamed from apps/web/src/components/properties-panel.tsx)10
-rw-r--r--apps/web/src/components/editor/timeline.tsx (renamed from apps/web/src/components/timeline.tsx)84
-rw-r--r--apps/web/src/components/header-base.tsx41
-rw-r--r--apps/web/src/components/header.tsx37
-rw-r--r--apps/web/src/components/landing/hero.tsx72
-rw-r--r--apps/web/src/components/media-panel.tsx39
-rw-r--r--apps/web/src/lib/auth-client.ts4
-rw-r--r--apps/web/src/lib/auth.ts23
-rw-r--r--apps/web/src/lib/db/index.tsx14
-rw-r--r--apps/web/src/lib/db/schema.ts61
-rw-r--r--apps/web/src/stores/app-store.ts35
-rw-r--r--apps/web/src/stores/media-store.ts35
-rw-r--r--apps/web/src/stores/panel-store.ts79
-rw-r--r--apps/web/src/stores/project-store.ts28
-rw-r--r--apps/web/src/stores/timeline-store.ts61
-rw-r--r--apps/web/src/types/project.ts6
-rw-r--r--apps/web/tailwind.config.ts6
34 files changed, 2443 insertions, 111 deletions
diff --git a/apps/web/drizzle.config.ts b/apps/web/drizzle.config.ts
new file mode 100644
index 00000000..37ff3cc1
--- /dev/null
+++ b/apps/web/drizzle.config.ts
@@ -0,0 +1,23 @@
+import type { Config } from "drizzle-kit";
+import * as dotenv from "dotenv";
+
+// Load the right env file based on environment
+if (process.env.NODE_ENV === "production") {
+ dotenv.config({ path: ".env.production" });
+} else {
+ dotenv.config({ path: ".env.local" });
+}
+
+if (!process.env.DATABASE_URL) {
+ throw new Error("DATABASE_URL is not set");
+}
+
+export default {
+ schema: "./src/lib/db/schema.ts",
+ dialect: "postgresql",
+ dbCredentials: {
+ url: process.env.DATABASE_URL,
+ },
+ out: "./migrations",
+ strict: process.env.NODE_ENV === "production",
+} satisfies Config;
diff --git a/apps/web/migrations/0000_hot_the_fallen.sql b/apps/web/migrations/0000_hot_the_fallen.sql
new file mode 100644
index 00000000..321043f0
--- /dev/null
+++ b/apps/web/migrations/0000_hot_the_fallen.sql
@@ -0,0 +1,54 @@
+CREATE TABLE "accounts" (
+ "id" text PRIMARY KEY NOT NULL,
+ "account_id" text NOT NULL,
+ "provider_id" text NOT NULL,
+ "user_id" text NOT NULL,
+ "access_token" text,
+ "refresh_token" text,
+ "id_token" text,
+ "access_token_expires_at" timestamp,
+ "refresh_token_expires_at" timestamp,
+ "scope" text,
+ "password" text,
+ "created_at" timestamp NOT NULL,
+ "updated_at" timestamp NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "accounts" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
+CREATE TABLE "sessions" (
+ "id" text PRIMARY KEY NOT NULL,
+ "expires_at" timestamp NOT NULL,
+ "token" text NOT NULL,
+ "created_at" timestamp NOT NULL,
+ "updated_at" timestamp NOT NULL,
+ "ip_address" text,
+ "user_agent" text,
+ "user_id" text NOT NULL,
+ CONSTRAINT "sessions_token_unique" UNIQUE("token")
+);
+--> statement-breakpoint
+ALTER TABLE "sessions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
+CREATE TABLE "users" (
+ "id" text PRIMARY KEY NOT NULL,
+ "name" text NOT NULL,
+ "email" text NOT NULL,
+ "email_verified" boolean NOT NULL,
+ "image" text,
+ "created_at" timestamp NOT NULL,
+ "updated_at" timestamp NOT NULL,
+ CONSTRAINT "users_email_unique" UNIQUE("email")
+);
+--> statement-breakpoint
+ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
+CREATE TABLE "verifications" (
+ "id" text PRIMARY KEY NOT NULL,
+ "identifier" text NOT NULL,
+ "value" text NOT NULL,
+ "expires_at" timestamp NOT NULL,
+ "created_at" timestamp,
+ "updated_at" timestamp
+);
+--> statement-breakpoint
+ALTER TABLE "verifications" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
+ALTER TABLE "accounts" ADD CONSTRAINT "accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "sessions" ADD CONSTRAINT "sessions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file
diff --git a/apps/web/migrations/meta/0000_snapshot.json b/apps/web/migrations/meta/0000_snapshot.json
new file mode 100644
index 00000000..7ea2b944
--- /dev/null
+++ b/apps/web/migrations/meta/0000_snapshot.json
@@ -0,0 +1,319 @@
+{
+ "id": "4440fb90-cf0e-4cb2-afad-08250ce3dc1e",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounts": {
+ "name": "accounts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "accounts_user_id_users_id_fk": {
+ "name": "accounts_user_id_users_id_fk",
+ "tableFrom": "accounts",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.sessions": {
+ "name": "sessions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sessions_user_id_users_id_fk": {
+ "name": "sessions_user_id_users_id_fk",
+ "tableFrom": "sessions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "sessions_token_unique": {
+ "name": "sessions_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ },
+ "public.verifications": {
+ "name": "verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": true
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+} \ No newline at end of file
diff --git a/apps/web/migrations/meta/_journal.json b/apps/web/migrations/meta/_journal.json
new file mode 100644
index 00000000..3f918785
--- /dev/null
+++ b/apps/web/migrations/meta/_journal.json
@@ -0,0 +1,13 @@
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1750581188229,
+ "tag": "0000_hot_the_fallen",
+ "breakpoints": true
+ }
+ ]
+} \ No newline at end of file
diff --git a/apps/web/package-lock.json b/apps/web/package-lock.json
index b68b9689..5fd9e57d 100644
--- a/apps/web/package-lock.json
+++ b/apps/web/package-lock.json
@@ -1,11 +1,11 @@
{
- "name": "auth-starterpack",
+ "name": "next-template",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "auth-starterpack",
+ "name": "next-template",
"version": "0.1.0",
"dependencies": {
"@hookform/resolvers": "^3.9.1",
@@ -36,17 +36,22 @@
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.5",
+ "@types/pg": "^8.15.4",
"@vercel/analytics": "^1.4.1",
+ "better-auth": "^1.2.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
+ "drizzle-orm": "^0.44.2",
"embla-carousel-react": "^8.5.1",
"framer-motion": "^11.13.1",
"input-otp": "^1.4.1",
"lucide-react": "^0.468.0",
+ "motion": "^12.18.1",
"next": "^15.2.0",
"next-themes": "^0.4.4",
+ "pg": "^8.16.2",
"react": "^18.2.0",
"react-day-picker": "^8.10.1",
"react-dom": "^18.2.0",
@@ -65,6 +70,7 @@
"@types/bun": "latest",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
+ "drizzle-kit": "^0.31.1",
"postcss": "^8",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.1",
@@ -96,6 +102,28 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@better-auth/utils": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.2.5.tgz",
+ "integrity": "sha512-uI2+/8h/zVsH8RrYdG8eUErbuGBk16rZKQfz8CjxQOyCE6v7BqFYEbFwvOkvl1KbUdxhqOnXp78+uE5h8qVEgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "typescript": "^5.8.2",
+ "uncrypto": "^0.1.3"
+ }
+ },
+ "node_modules/@better-fetch/fetch": {
+ "version": "1.1.18",
+ "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.18.tgz",
+ "integrity": "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA=="
+ },
+ "node_modules/@drizzle-team/brocli": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz",
+ "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
"node_modules/@emnapi/runtime": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz",
@@ -106,6 +134,442 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@esbuild-kit/core-utils": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz",
+ "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==",
+ "deprecated": "Merged into tsx: https://tsx.is",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "~0.18.20",
+ "source-map-support": "^0.5.21"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": {
+ "version": "0.18.20",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.18.20",
+ "@esbuild/android-arm64": "0.18.20",
+ "@esbuild/android-x64": "0.18.20",
+ "@esbuild/darwin-arm64": "0.18.20",
+ "@esbuild/darwin-x64": "0.18.20",
+ "@esbuild/freebsd-arm64": "0.18.20",
+ "@esbuild/freebsd-x64": "0.18.20",
+ "@esbuild/linux-arm": "0.18.20",
+ "@esbuild/linux-arm64": "0.18.20",
+ "@esbuild/linux-ia32": "0.18.20",
+ "@esbuild/linux-loong64": "0.18.20",
+ "@esbuild/linux-mips64el": "0.18.20",
+ "@esbuild/linux-ppc64": "0.18.20",
+ "@esbuild/linux-riscv64": "0.18.20",
+ "@esbuild/linux-s390x": "0.18.20",
+ "@esbuild/linux-x64": "0.18.20",
+ "@esbuild/netbsd-x64": "0.18.20",
+ "@esbuild/openbsd-x64": "0.18.20",
+ "@esbuild/sunos-x64": "0.18.20",
+ "@esbuild/win32-arm64": "0.18.20",
+ "@esbuild/win32-ia32": "0.18.20",
+ "@esbuild/win32-x64": "0.18.20"
+ }
+ },
+ "node_modules/@esbuild-kit/esm-loader": {
+ "version": "2.6.5",
+ "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz",
+ "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==",
+ "deprecated": "Merged into tsx: https://tsx.is",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@esbuild-kit/core-utils": "^3.3.2",
+ "get-tsconfig": "^4.7.0"
+ }
+ },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
@@ -569,6 +1033,12 @@
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
"license": "MIT"
},
+ "node_modules/@hexagon/base64": {
+ "version": "1.1.28",
+ "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
+ "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==",
+ "license": "MIT"
+ },
"node_modules/@hookform/resolvers": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
@@ -1020,6 +1490,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@levischuck/tiny-cbor": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz",
+ "integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==",
+ "license": "MIT"
+ },
"node_modules/@next/env": {
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.1.tgz",
@@ -1154,6 +1630,27 @@
"node": ">= 10"
}
},
+ "node_modules/@noble/ciphers": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.6.0.tgz",
+ "integrity": "sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1189,6 +1686,64 @@
"node": ">= 8"
}
},
+ "node_modules/@peculiar/asn1-android": {
+ "version": "2.3.16",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.3.16.tgz",
+ "integrity": "sha512-a1viIv3bIahXNssrOIkXZIlI2ePpZaNmR30d4aBL99mu2rO+mT9D6zBsp7H6eROWGtmwv0Ionp5olJurIo09dw==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.3.15",
+ "asn1js": "^3.0.5",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-ecc": {
+ "version": "2.3.15",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.3.15.tgz",
+ "integrity": "sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.3.15",
+ "@peculiar/asn1-x509": "^2.3.15",
+ "asn1js": "^3.0.5",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-rsa": {
+ "version": "2.3.15",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.3.15.tgz",
+ "integrity": "sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.3.15",
+ "@peculiar/asn1-x509": "^2.3.15",
+ "asn1js": "^3.0.5",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-schema": {
+ "version": "2.3.15",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.3.15.tgz",
+ "integrity": "sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==",
+ "license": "MIT",
+ "dependencies": {
+ "asn1js": "^3.0.5",
+ "pvtsutils": "^1.3.6",
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/@peculiar/asn1-x509": {
+ "version": "2.3.15",
+ "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.3.15.tgz",
+ "integrity": "sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==",
+ "license": "MIT",
+ "dependencies": {
+ "@peculiar/asn1-schema": "^2.3.15",
+ "asn1js": "^3.0.5",
+ "pvtsutils": "^1.3.6",
+ "tslib": "^2.8.1"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -2515,6 +3070,30 @@
"integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
"license": "MIT"
},
+ "node_modules/@simplewebauthn/browser": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.1.0.tgz",
+ "integrity": "sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==",
+ "license": "MIT"
+ },
+ "node_modules/@simplewebauthn/server": {
+ "version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.1.1.tgz",
+ "integrity": "sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==",
+ "license": "MIT",
+ "dependencies": {
+ "@hexagon/base64": "^1.1.27",
+ "@levischuck/tiny-cbor": "^0.2.2",
+ "@peculiar/asn1-android": "^2.3.10",
+ "@peculiar/asn1-ecc": "^2.3.8",
+ "@peculiar/asn1-rsa": "^2.3.8",
+ "@peculiar/asn1-schema": "^2.3.8",
+ "@peculiar/asn1-x509": "^2.3.8"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ }
+ },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -2607,12 +3186,22 @@
"version": "22.14.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz",
"integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
+ "node_modules/@types/pg": {
+ "version": "8.15.4",
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz",
+ "integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "pg-protocol": "*",
+ "pg-types": "^2.2.0"
+ }
+ },
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
@@ -2740,12 +3329,57 @@
"node": ">=10"
}
},
+ "node_modules/asn1js": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz",
+ "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "pvtsutils": "^1.3.6",
+ "pvutils": "^1.1.3",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
+ "node_modules/better-auth": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.2.10.tgz",
+ "integrity": "sha512-nEj1RG4DdLUuJiV5CR93ORyPCptGRBwksaPPCkUtGo9ka+UIlTpaiKoTaTqVLLYlqwX4bOj9tJ32oBNdf2G3Kg==",
+ "license": "MIT",
+ "dependencies": {
+ "@better-auth/utils": "0.2.5",
+ "@better-fetch/fetch": "^1.1.18",
+ "@noble/ciphers": "^0.6.0",
+ "@noble/hashes": "^1.6.1",
+ "@simplewebauthn/browser": "^13.0.0",
+ "@simplewebauthn/server": "^13.0.0",
+ "better-call": "^1.0.8",
+ "defu": "^6.1.4",
+ "jose": "^5.9.6",
+ "kysely": "^0.28.2",
+ "nanostores": "^0.11.3",
+ "zod": "^3.24.1"
+ }
+ },
+ "node_modules/better-call": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.0.9.tgz",
+ "integrity": "sha512-Qfm0gjk0XQz0oI7qvTK1hbqTsBY4xV2hsHAxF8LZfUYl3RaECCIifXuVqtPpZJWvlCCMlQSvkvhhyuApGUba6g==",
+ "dependencies": {
+ "@better-fetch/fetch": "^1.1.4",
+ "rou3": "^0.5.1",
+ "set-cookie-parser": "^2.7.1",
+ "uncrypto": "^0.1.3"
+ }
+ },
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@@ -2779,11 +3413,18 @@
"node": ">=8"
}
},
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/bun-types": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.2.10.tgz",
"integrity": "sha512-b5ITZMnVdf3m1gMvJHG+gIfeJHiQPJak0f7925Hxu6ZN5VKA8AGy4GZ4lM+Xkn6jtWxg5S3ldWvfmXdvnkp3GQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
@@ -3135,12 +3776,36 @@
"url": "https://github.com/sponsors/kossnocorp"
}
},
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==",
"license": "MIT"
},
+ "node_modules/defu": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
+ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
+ "license": "MIT"
+ },
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@@ -3179,6 +3844,147 @@
"csstype": "^3.0.2"
}
},
+ "node_modules/drizzle-kit": {
+ "version": "0.31.1",
+ "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.1.tgz",
+ "integrity": "sha512-PUjYKWtzOzPtdtQlTHQG3qfv4Y0XT8+Eas6UbxCmxTj7qgMf+39dDujf1BP1I+qqZtw9uzwTh8jYtkMuCq+B0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@drizzle-team/brocli": "^0.10.2",
+ "@esbuild-kit/esm-loader": "^2.5.5",
+ "esbuild": "^0.25.2",
+ "esbuild-register": "^3.5.0"
+ },
+ "bin": {
+ "drizzle-kit": "bin.cjs"
+ }
+ },
+ "node_modules/drizzle-orm": {
+ "version": "0.44.2",
+ "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.44.2.tgz",
+ "integrity": "sha512-zGAqBzWWkVSFjZpwPOrmCrgO++1kZ5H/rZ4qTGeGOe18iXGVJWf3WPfHOVwFIbmi8kHjfJstC6rJomzGx8g/dQ==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@aws-sdk/client-rds-data": ">=3",
+ "@cloudflare/workers-types": ">=4",
+ "@electric-sql/pglite": ">=0.2.0",
+ "@libsql/client": ">=0.10.0",
+ "@libsql/client-wasm": ">=0.10.0",
+ "@neondatabase/serverless": ">=0.10.0",
+ "@op-engineering/op-sqlite": ">=2",
+ "@opentelemetry/api": "^1.4.1",
+ "@planetscale/database": ">=1.13",
+ "@prisma/client": "*",
+ "@tidbcloud/serverless": "*",
+ "@types/better-sqlite3": "*",
+ "@types/pg": "*",
+ "@types/sql.js": "*",
+ "@upstash/redis": ">=1.34.7",
+ "@vercel/postgres": ">=0.8.0",
+ "@xata.io/client": "*",
+ "better-sqlite3": ">=7",
+ "bun-types": "*",
+ "expo-sqlite": ">=14.0.0",
+ "gel": ">=2",
+ "knex": "*",
+ "kysely": "*",
+ "mysql2": ">=2",
+ "pg": ">=8",
+ "postgres": ">=3",
+ "sql.js": ">=1",
+ "sqlite3": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/client-rds-data": {
+ "optional": true
+ },
+ "@cloudflare/workers-types": {
+ "optional": true
+ },
+ "@electric-sql/pglite": {
+ "optional": true
+ },
+ "@libsql/client": {
+ "optional": true
+ },
+ "@libsql/client-wasm": {
+ "optional": true
+ },
+ "@neondatabase/serverless": {
+ "optional": true
+ },
+ "@op-engineering/op-sqlite": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@planetscale/database": {
+ "optional": true
+ },
+ "@prisma/client": {
+ "optional": true
+ },
+ "@tidbcloud/serverless": {
+ "optional": true
+ },
+ "@types/better-sqlite3": {
+ "optional": true
+ },
+ "@types/pg": {
+ "optional": true
+ },
+ "@types/sql.js": {
+ "optional": true
+ },
+ "@upstash/redis": {
+ "optional": true
+ },
+ "@vercel/postgres": {
+ "optional": true
+ },
+ "@xata.io/client": {
+ "optional": true
+ },
+ "better-sqlite3": {
+ "optional": true
+ },
+ "bun-types": {
+ "optional": true
+ },
+ "expo-sqlite": {
+ "optional": true
+ },
+ "gel": {
+ "optional": true
+ },
+ "knex": {
+ "optional": true
+ },
+ "kysely": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "pg": {
+ "optional": true
+ },
+ "postgres": {
+ "optional": true
+ },
+ "prisma": {
+ "optional": true
+ },
+ "sql.js": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ }
+ }
+ },
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -3260,6 +4066,19 @@
"@esbuild/win32-x64": "0.25.2"
}
},
+ "node_modules/esbuild-register": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
+ "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4"
+ },
+ "peerDependencies": {
+ "esbuild": ">=0.12 <1"
+ }
+ },
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
@@ -3599,12 +4418,30 @@
"jiti": "bin/jiti.js"
}
},
+ "node_modules/jose": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
+ "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
+ "node_modules/kysely": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.2.tgz",
+ "integrity": "sha512-4YAVLoF0Sf0UTqlhgQMFU9iQECdah7n+13ANkiuVfRvlK+uI0Etbgd7bVP36dKlG+NXWbhGua8vnGt+sdhvT7A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/libphonenumber-js": {
"version": "1.12.7",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.7.tgz",
@@ -3708,6 +4545,32 @@
"node": ">=16 || 14 >=14.17"
}
},
+ "node_modules/motion": {
+ "version": "12.18.1",
+ "resolved": "https://registry.npmjs.org/motion/-/motion-12.18.1.tgz",
+ "integrity": "sha512-w1ns2hWQ4COhOvnZf4rg4mW0Pl36mzcShpgt0fSfI6qJxKUbi3kHho/HSKeJFRoY0TO1m5/7C8lG1+Li0uC9Fw==",
+ "license": "MIT",
+ "dependencies": {
+ "framer-motion": "^12.18.1",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/motion-dom": {
"version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
@@ -3723,6 +4586,55 @@
"integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==",
"license": "MIT"
},
+ "node_modules/motion/node_modules/framer-motion": {
+ "version": "12.18.1",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.18.1.tgz",
+ "integrity": "sha512-6o4EDuRPLk4LSZ1kRnnEOurbQ86MklVk+Y1rFBUKiF+d2pCdvMjWVu0ZkyMVCTwl5UyTH2n/zJEJx+jvTYuxow==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.18.1",
+ "motion-utils": "^12.18.1",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/motion/node_modules/motion-dom": {
+ "version": "12.18.1",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.18.1.tgz",
+ "integrity": "sha512-dR/4EYT23Snd+eUSLrde63Ws3oXQtJNw/krgautvTfwrN/2cHfCZMdu6CeTxVfRRWREW3Fy1f5vobRDiBb/q+w==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.18.1"
+ }
+ },
+ "node_modules/motion/node_modules/motion-utils": {
+ "version": "12.18.1",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.18.1.tgz",
+ "integrity": "sha512-az26YDU4WoDP0ueAkUtABLk2BIxe28d8NH1qWT8jPGhPyf44XTdDUh8pDk9OPphaSrR9McgpcJlgwSOIw/sfkA==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -3752,6 +4664,21 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/nanostores": {
+ "version": "0.11.4",
+ "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.11.4.tgz",
+ "integrity": "sha512-k1oiVNN4hDK8NcNERSZLQiMfRzEGtfnvZvdBvey3SQbgn8Dcrk0h1I6vpxApjb10PFUflZrgJ2WEZyJQ+5v7YQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
"node_modules/next": {
"version": "15.3.1",
"resolved": "https://registry.npmjs.org/next/-/next-15.3.1.tgz",
@@ -3908,6 +4835,95 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/pg": {
+ "version": "8.16.2",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.2.tgz",
+ "integrity": "sha512-OtLWF0mKLmpxelOt9BqVq83QV6bTfsS0XLegIeAKqKjurRnRKie1Dc1iL89MugmSLhftxw6NNCyZhm1yQFLMEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-connection-string": "^2.9.1",
+ "pg-pool": "^3.10.1",
+ "pg-protocol": "^1.10.2",
+ "pg-types": "2.2.0",
+ "pgpass": "1.0.5"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "optionalDependencies": {
+ "pg-cloudflare": "^1.2.6"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-cloudflare": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.6.tgz",
+ "integrity": "sha512-uxmJAnmIgmYgnSFzgOf2cqGQBzwnRYcrEgXuFjJNEkpedEIPBSEzxY7ph4uA9k1mI+l/GR0HjPNS6FKNZe8SBQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz",
+ "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==",
+ "license": "MIT"
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz",
+ "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.2.tgz",
+ "integrity": "sha512-Ci7jy8PbaWxfsck2dwZdERcDG2A0MG8JoQILs+uZNjABFuBuItAZCWUNz8sXRDMoui24rJw7WlXqgpMdBSN/vQ==",
+ "license": "MIT"
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -4052,6 +5068,45 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT"
},
+ "node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+ "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
@@ -4079,6 +5134,24 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/pvtsutils": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
+ "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.8.1"
+ }
+ },
+ "node_modules/pvutils": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.3.tgz",
+ "integrity": "sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -4401,6 +5474,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rou3": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz",
+ "integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==",
+ "license": "MIT"
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -4446,6 +5525,12 @@
"node": ">=10"
}
},
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
+ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
+ "license": "MIT"
+ },
"node_modules/sharp": {
"version": "0.34.1",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.1.tgz",
@@ -4540,6 +5625,16 @@
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
}
},
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -4549,6 +5644,26 @@
"node": ">=0.10.0"
}
},
+ "node_modules/source-map-support": {
+ "version": "0.5.21",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@@ -4876,7 +5991,6 @@
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
- "dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -4886,11 +6000,16 @@
"node": ">=14.17"
}
},
+ "node_modules/uncrypto": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz",
+ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==",
+ "license": "MIT"
+ },
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/use-callback-ref": {
@@ -5083,6 +6202,15 @@
"node": ">=8"
}
},
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/yaml": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz",
@@ -5095,6 +6223,15 @@
"node": ">= 14"
}
},
+ "node_modules/zod": {
+ "version": "3.25.67",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.67.tgz",
+ "integrity": "sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
"node_modules/zustand": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz",
diff --git a/apps/web/package.json b/apps/web/package.json
index 98372884..e68c84fd 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -6,7 +6,10 @@
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "db:generate": "drizzle-kit generate",
+ "db:push:local": "cross-env NODE_ENV=development drizzle-kit push",
+ "db:push:prod": "cross-env NODE_ENV=production drizzle-kit push"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
@@ -37,17 +40,22 @@
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.5",
+ "@types/pg": "^8.15.4",
"@vercel/analytics": "^1.4.1",
+ "better-auth": "^1.2.7",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"date-fns": "^3.6.0",
+ "drizzle-orm": "^0.44.2",
"embla-carousel-react": "^8.5.1",
"framer-motion": "^11.13.1",
"input-otp": "^1.4.1",
"lucide-react": "^0.468.0",
+ "motion": "^12.18.1",
"next": "^15.2.0",
"next-themes": "^0.4.4",
+ "pg": "^8.16.2",
"react": "^18.2.0",
"react-day-picker": "^8.10.1",
"react-dom": "^18.2.0",
@@ -66,6 +74,7 @@
"@types/bun": "latest",
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
+ "drizzle-kit": "^0.31.1",
"postcss": "^8",
"prettier": "^3.4.2",
"tailwindcss": "^3.4.1",
diff --git a/apps/web/src/app/api/auth/[...all]/route.ts b/apps/web/src/app/api/auth/[...all]/route.ts
new file mode 100644
index 00000000..1fb57443
--- /dev/null
+++ b/apps/web/src/app/api/auth/[...all]/route.ts
@@ -0,0 +1,4 @@
+import { auth } from "@/lib/auth";
+import { toNextJsHandler } from "better-auth/next-js";
+
+export const { POST, GET } = toNextJsHandler(auth); \ No newline at end of file
diff --git a/apps/web/src/app/editor/page.tsx b/apps/web/src/app/editor/page.tsx
index 51a448d9..f9f64ee6 100644
--- a/apps/web/src/app/editor/page.tsx
+++ b/apps/web/src/app/editor/page.tsx
@@ -1,36 +1,80 @@
+"use client";
+
+import { useEffect } from "react";
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "../../components/ui/resizable";
-import { MediaPanel } from "../../components/media-panel";
-import { PropertiesPanel } from "../../components/properties-panel";
-import { Timeline } from "../../components/timeline";
-import { PreviewPanel } from "../../components/preview-panel";
+import { MediaPanel } from "../../components/editor/media-panel";
+import { PropertiesPanel } from "../../components/editor/properties-panel";
+import { Timeline } from "../../components/editor/timeline";
+import { PreviewPanel } from "../../components/editor/preview-panel";
+import { EditorHeader } from "@/components/editor-header";
+import { usePanelStore } from "@/stores/panel-store";
+import { useProjectStore } from "@/stores/project-store";
export default function Editor() {
+ const {
+ toolsPanel,
+ previewPanel,
+ propertiesPanel,
+ mainContent,
+ timeline,
+ setToolsPanel,
+ setPreviewPanel,
+ setPropertiesPanel,
+ setMainContent,
+ setTimeline,
+ } = usePanelStore();
+
+ const { activeProject, createNewProject } = useProjectStore();
+
+ // Initialize a new project if none exists
+ useEffect(() => {
+ if (!activeProject) {
+ createNewProject("Untitled Project");
+ }
+ }, [activeProject, createNewProject]);
+
return (
<div className="h-screen w-screen flex flex-col bg-background">
+ <EditorHeader />
<ResizablePanelGroup direction="vertical">
- <ResizablePanel defaultSize={50} minSize={30}>
+ <ResizablePanel
+ defaultSize={mainContent}
+ minSize={30}
+ onResize={setMainContent}
+ >
{/* Main content area */}
<ResizablePanelGroup direction="horizontal">
{/* Tools Panel */}
- <ResizablePanel defaultSize={20} minSize={15}>
+ <ResizablePanel
+ defaultSize={toolsPanel}
+ minSize={15}
+ onResize={setToolsPanel}
+ >
<MediaPanel />
</ResizablePanel>
<ResizableHandle withHandle />
{/* Preview Area */}
- <ResizablePanel defaultSize={60}>
+ <ResizablePanel
+ defaultSize={previewPanel}
+ onResize={setPreviewPanel}
+ >
<PreviewPanel />
</ResizablePanel>
<ResizableHandle withHandle />
{/* Properties Panel */}
- <ResizablePanel defaultSize={20} minSize={15}>
+ <ResizablePanel
+ defaultSize={propertiesPanel}
+ minSize={15}
+ onResize={setPropertiesPanel}
+ >
<PropertiesPanel />
</ResizablePanel>
</ResizablePanelGroup>
@@ -39,7 +83,11 @@ export default function Editor() {
<ResizableHandle withHandle />
{/* Timeline */}
- <ResizablePanel defaultSize={50} minSize={15}>
+ <ResizablePanel
+ defaultSize={timeline}
+ minSize={15}
+ onResize={setTimeline}
+ >
<Timeline />
</ResizablePanel>
</ResizablePanelGroup>
diff --git a/apps/web/src/app/fonts/GeistMonoVF.woff b/apps/web/src/app/fonts/GeistMonoVF.woff
deleted file mode 100644
index f2ae185c..00000000
--- a/apps/web/src/app/fonts/GeistMonoVF.woff
+++ /dev/null
Binary files differ
diff --git a/apps/web/src/app/fonts/GeistVF.woff b/apps/web/src/app/fonts/GeistVF.woff
deleted file mode 100644
index 1b62daac..00000000
--- a/apps/web/src/app/fonts/GeistVF.woff
+++ /dev/null
Binary files differ
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index 31bd3692..2169dbbc 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -2,10 +2,6 @@
@tailwind components;
@tailwind utilities;
-body {
- font-family: Arial, Helvetica, sans-serif;
-}
-
@layer base {
:root {
--background: 0 0% 100%;
@@ -32,7 +28,7 @@ body {
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
- --radius: 0.6rem;
+ --radius: 1rem;
--sidebar-background: 0 0% 100%;
--sidebar-foreground: 0 0% 3.9%;
--sidebar-primary: 0 0% 9%;
diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx
index d7dd18a5..6696e884 100644
--- a/apps/web/src/app/layout.tsx
+++ b/apps/web/src/app/layout.tsx
@@ -1,20 +1,15 @@
import type { Metadata } from "next";
-import localFont from "next/font/local";
+import { Inter } from "next/font/google";
import { ThemeProvider } from "next-themes";
import { Analytics } from "@vercel/analytics/react";
import "./globals.css";
import { Toaster } from "../components/ui/sonner";
import { TooltipProvider } from "../components/ui/tooltip";
+import { AppProvider } from "@/components/app-provider";
-const geistSans = localFont({
- src: "./fonts/GeistVF.woff",
- variable: "--font-geist-sans",
- weight: "100 900",
-});
-const geistMono = localFont({
- src: "./fonts/GeistMonoVF.woff",
- variable: "--font-geist-mono",
- weight: "100 900",
+const inter = Inter({
+ subsets: ["latin"],
+ variable: "--font-inter",
});
export const metadata: Metadata = {
@@ -29,15 +24,15 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning>
- <body
- className={`${geistSans.variable} ${geistMono.variable} antialiased`}
- >
+ <body className={`${inter.variable} font-sans antialiased`}>
<ThemeProvider attribute="class" forcedTheme="dark" enableSystem>
- <TooltipProvider>
- {children}
- <Analytics />
- <Toaster />
- </TooltipProvider>
+ <AppProvider>
+ <TooltipProvider>
+ {children}
+ <Analytics />
+ <Toaster />
+ </TooltipProvider>
+ </AppProvider>
</ThemeProvider>
</body>
</html>
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx
index 9b8a74c6..57967b7a 100644
--- a/apps/web/src/app/page.tsx
+++ b/apps/web/src/app/page.tsx
@@ -1,3 +1,11 @@
+import { Hero } from "@/components/landing/hero";
+import { Header } from "@/components/header";
+
export default function Home() {
- return <div>Hello World</div>;
-} \ No newline at end of file
+ return (
+ <div>
+ <Header />
+ <Hero />
+ </div>
+ );
+}
diff --git a/apps/web/src/components/app-provider.tsx b/apps/web/src/components/app-provider.tsx
new file mode 100644
index 00000000..257fc8fa
--- /dev/null
+++ b/apps/web/src/components/app-provider.tsx
@@ -0,0 +1,42 @@
+"use client";
+
+import { useEffect } from "react";
+import { Loader2 } from "lucide-react";
+import { useAppStore } from "@/stores/app-store";
+import { usePanelStore } from "@/stores/panel-store";
+
+interface AppProviderProps {
+ children: React.ReactNode;
+}
+
+export function AppProvider({ children }: AppProviderProps) {
+ const { isInitializing, isPanelsReady, initializeApp } = useAppStore();
+ const { setInitialized } = usePanelStore();
+
+ useEffect(() => {
+ const initialize = async () => {
+ // Initialize the app
+ await initializeApp();
+
+ // Initialize panel store for future resize events
+ setInitialized();
+ };
+
+ initialize();
+ }, [initializeApp, setInitialized]);
+
+ // Show loading screen while initializing
+ if (isInitializing || !isPanelsReady) {
+ return (
+ <div className="h-screen w-screen flex items-center justify-center bg-background">
+ <div className="flex flex-col items-center gap-4">
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
+ <p className="text-sm text-muted-foreground">Loading editor...</p>
+ </div>
+ </div>
+ );
+ }
+
+ // App is ready, render children
+ return <>{children}</>;
+}
diff --git a/apps/web/src/components/editor-header.tsx b/apps/web/src/components/editor-header.tsx
new file mode 100644
index 00000000..f59193d0
--- /dev/null
+++ b/apps/web/src/components/editor-header.tsx
@@ -0,0 +1,56 @@
+"use client";
+
+import Link from "next/link";
+import { Button } from "./ui/button";
+import { ChevronLeft, Download } from "lucide-react";
+import { useProjectStore } from "@/stores/project-store";
+import { useMediaStore } from "@/stores/media-store";
+import { useTimelineStore } from "@/stores/timeline-store";
+import { HeaderBase } from "./header-base";
+
+export function EditorHeader() {
+ const { activeProject } = useProjectStore();
+ const { mediaItems } = useMediaStore();
+ const { tracks } = useTimelineStore();
+
+ const handleExport = () => {
+ // TODO: Implement export functionality
+ console.log("Export project");
+ };
+
+ const leftContent = (
+ <Link
+ href="/"
+ className="font-medium tracking-tight flex items-center gap-2 hover:opacity-80 transition-opacity"
+ >
+ <ChevronLeft className="h-4 w-4" />
+ <span className="text-sm">{activeProject?.name || "Loading..."}</span>
+ </Link>
+ );
+
+ const centerContent = (
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
+ <span>{mediaItems.length} media</span>
+ <span>•</span>
+ <span>{tracks.length} tracks</span>
+ </div>
+ );
+
+ const rightContent = (
+ <nav className="flex items-center gap-2">
+ <Button size="sm" onClick={handleExport}>
+ <Download className="h-4 w-4" />
+ <span className="text-sm">Export</span>
+ </Button>
+ </nav>
+ );
+
+ return (
+ <HeaderBase
+ leftContent={leftContent}
+ centerContent={centerContent}
+ rightContent={rightContent}
+ className="bg-background border-b"
+ />
+ );
+}
diff --git a/apps/web/src/components/editor/media-panel.tsx b/apps/web/src/components/editor/media-panel.tsx
new file mode 100644
index 00000000..1e5aa465
--- /dev/null
+++ b/apps/web/src/components/editor/media-panel.tsx
@@ -0,0 +1,133 @@
+"use client";
+
+import { Button } from "../ui/button";
+import { AspectRatio } from "../ui/aspect-ratio";
+import { useMediaStore } from "@/stores/media-store";
+import { Plus, Image, Video, Music, Upload } from "lucide-react";
+import { useState, useRef } from "react";
+
+export function MediaPanel() {
+ const { mediaItems, addMediaItem } = useMediaStore();
+ const [isDragOver, setIsDragOver] = useState(false);
+ const dragCounterRef = useRef(0);
+
+ const handleAddSampleMedia = () => {
+ // Just for testing - add a sample media item
+ addMediaItem({
+ name: `Sample ${mediaItems.length + 1}`,
+ type: "image",
+ });
+ };
+
+ const getMediaIcon = (type: string) => {
+ switch (type) {
+ case "video":
+ return <Video className="h-4 w-4" />;
+ case "audio":
+ return <Music className="h-4 w-4" />;
+ default:
+ return <Image className="h-4 w-4" />;
+ }
+ };
+
+ const handleDragEnter = (e: React.DragEvent) => {
+ e.preventDefault();
+ dragCounterRef.current += 1;
+ if (!isDragOver) {
+ setIsDragOver(true);
+ }
+ };
+
+ const handleDragOver = (e: React.DragEvent) => {
+ e.preventDefault();
+ };
+
+ const handleDragLeave = (e: React.DragEvent) => {
+ e.preventDefault();
+ dragCounterRef.current -= 1;
+ if (dragCounterRef.current === 0) {
+ setIsDragOver(false);
+ }
+ };
+
+ const handleDrop = (e: React.DragEvent) => {
+ e.preventDefault();
+ setIsDragOver(false);
+ dragCounterRef.current = 0;
+ // TODO: Handle file drop functionality
+ };
+
+ return (
+ <div
+ className={`h-full overflow-y-auto transition-colors duration-200 relative ${
+ isDragOver ? "bg-accent/30 border-accent" : ""
+ }`}
+ onDragEnter={handleDragEnter}
+ onDragOver={handleDragOver}
+ onDragLeave={handleDragLeave}
+ onDrop={handleDrop}
+ >
+ {/* Drag Overlay */}
+ {isDragOver && (
+ <div className="absolute inset-0 bg-accent/20 backdrop-blur-lg border-2 border-dashed border-accent rounded-lg flex items-center justify-center z-10 pointer-events-none">
+ <div className="text-center">
+ <Upload className="h-8 w-8 text-accent mx-auto mb-2" />
+ <p className="text-sm font-medium text-accent">Drop files here</p>
+ <p className="text-xs text-muted-foreground">
+ Images, videos, and audio files
+ </p>
+ </div>
+ </div>
+ )}
+
+ <div className="space-y-4 p-2 h-full">
+ {/* Media Grid */}
+ {mediaItems.length === 0 ? (
+ <EmptyMedia onAddSample={handleAddSampleMedia} />
+ ) : (
+ <div className="grid grid-cols-2 gap-2">
+ {mediaItems.map((item) => (
+ <Button
+ key={item.id}
+ variant="outline"
+ className="flex flex-col gap-2 p-2 h-auto overflow-hidden"
+ >
+ <AspectRatio ratio={16 / 9} className="w-full">
+ <div className="w-full h-full bg-muted/30 flex flex-col items-center justify-center text-muted-foreground">
+ {getMediaIcon(item.type)}
+ <span className="text-xs mt-1 truncate max-w-full px-1">
+ {item.name}
+ </span>
+ </div>
+ </AspectRatio>
+ </Button>
+ ))}
+ </div>
+ )}
+ </div>
+ </div>
+ );
+}
+
+function EmptyMedia({ onAddSample }: { onAddSample: () => void }) {
+ return (
+ <div className="flex flex-col items-center justify-center py-8 text-center h-full">
+ <div className="w-16 h-16 rounded-full bg-muted/30 flex items-center justify-center mb-4">
+ <Image className="h-8 w-8 text-muted-foreground" />
+ </div>
+ <p className="text-sm text-muted-foreground">No media in project</p>
+ <p className="text-xs text-muted-foreground/70 mt-1">
+ Drag files or click to add media
+ </p>
+ <Button
+ variant="outline"
+ size="sm"
+ className="mt-4"
+ onClick={onAddSample}
+ >
+ <Plus className="h-4 w-4 mr-2" />
+ Add Sample
+ </Button>
+ </div>
+ );
+}
diff --git a/apps/web/src/components/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx
index 4361e967..4361e967 100644
--- a/apps/web/src/components/preview-panel.tsx
+++ b/apps/web/src/components/editor/preview-panel.tsx
diff --git a/apps/web/src/components/properties-panel.tsx b/apps/web/src/components/editor/properties-panel.tsx
index 612e2ef0..280b6dad 100644
--- a/apps/web/src/components/properties-panel.tsx
+++ b/apps/web/src/components/editor/properties-panel.tsx
@@ -1,10 +1,10 @@
"use client";
-import { Input } from "./ui/input";
-import { Label } from "./ui/label";
-import { Slider } from "./ui/slider";
-import { ScrollArea } from "./ui/scroll-area";
-import { Separator } from "./ui/separator";
+import { Input } from "../ui/input";
+import { Label } from "../ui/label";
+import { Slider } from "../ui/slider";
+import { ScrollArea } from "../ui/scroll-area";
+import { Separator } from "../ui/separator";
export function PropertiesPanel() {
return (
diff --git a/apps/web/src/components/timeline.tsx b/apps/web/src/components/editor/timeline.tsx
index cde982ae..da44022e 100644
--- a/apps/web/src/components/timeline.tsx
+++ b/apps/web/src/components/editor/timeline.tsx
@@ -1,7 +1,7 @@
"use client";
-import { ScrollArea } from "./ui/scroll-area";
-import { Button } from "./ui/button";
+import { ScrollArea } from "../ui/scroll-area";
+import { Button } from "../ui/button";
import {
Scissors,
ArrowLeftToLine,
@@ -16,14 +16,17 @@ import {
TooltipContent,
TooltipTrigger,
TooltipProvider,
-} from "./ui/tooltip";
+} from "../ui/tooltip";
+import { useTimelineStore, type TimelineTrack } from "@/stores/timeline-store";
export function Timeline() {
+ const { tracks, addTrack } = useTimelineStore();
+
return (
<div className="h-full flex flex-col">
{/* Toolbar */}
<div className="border-b flex items-center px-2 py-1 gap-1">
- <TooltipProvider delayDuration={0}>
+ <TooltipProvider delayDuration={500}>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
@@ -104,38 +107,67 @@ export function Timeline() {
))}
</div>
- {/* Video Track */}
- <div className="flex flex-col gap-2.5">
- <div className="flex items-center px-2">
- <div className="flex-1">
- <TimelineClip />
+ {/* Timeline Tracks */}
+ {tracks.length === 0 ? (
+ <div className="flex flex-col items-center justify-center py-12 text-center">
+ <div className="w-16 h-16 rounded-full bg-muted/30 flex items-center justify-center mb-4">
+ <SplitSquareHorizontal className="h-8 w-8 text-muted-foreground" />
</div>
+ <p className="text-sm text-muted-foreground">
+ No tracks in timeline
+ </p>
+ <p className="text-xs text-muted-foreground/70 mt-1">
+ Add a video or audio track to get started
+ </p>
</div>
-
- {/* Audio Track */}
- <div className="flex items-center px-2">
- <div className="flex-1">
- <TimelineClip />
- </div>
+ ) : (
+ <div className="flex flex-col gap-2.5">
+ {tracks.map((track) => (
+ <TimelineTrackComponent key={track.id} track={track} />
+ ))}
</div>
-
- {/* Effects Track */}
- <div className="flex items-center px-2">
- <div className="flex-1">
- <TimelineClip />
- </div>
- </div>
- </div>
+ )}
</div>
</ScrollArea>
</div>
);
}
-function TimelineClip() {
+function TimelineTrackComponent({ track }: { track: TimelineTrack }) {
+ const getTrackColor = (type: string) => {
+ switch (type) {
+ case "video":
+ return "bg-blue-500/20 border-blue-500/30";
+ case "audio":
+ return "bg-green-500/20 border-green-500/30";
+ case "effects":
+ return "bg-purple-500/20 border-purple-500/30";
+ default:
+ return "bg-gray-500/20 border-gray-500/30";
+ }
+ };
+
return (
- <div className="flex-1 h-[3.8rem]">
- <div className="h-full bg-blue-500/20 border border-blue-500/30 rounded-sm mx-3 cursor-pointer transition-colors" />
+ <div className="flex items-center px-2">
+ <div className="w-24 text-xs text-muted-foreground flex-shrink-0 mr-2">
+ {track.name}
+ </div>
+
+ <div className="flex-1 h-[60px]">
+ {track.clips.length === 0 ? (
+ <div className="h-full rounded-sm border-2 border-dashed border-muted/30 flex items-center justify-center text-xs text-muted-foreground">
+ Drop media here
+ </div>
+ ) : (
+ <div
+ className={`h-full rounded-sm border cursor-pointer transition-colors ${getTrackColor(track.type)} flex items-center px-2`}
+ >
+ <span className="text-xs text-foreground/80">
+ {track.clips.length} clip{track.clips.length !== 1 ? "s" : ""}
+ </span>
+ </div>
+ )}
+ </div>
</div>
);
}
diff --git a/apps/web/src/components/header-base.tsx b/apps/web/src/components/header-base.tsx
new file mode 100644
index 00000000..5f7b2b8d
--- /dev/null
+++ b/apps/web/src/components/header-base.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import { ReactNode } from "react";
+
+interface HeaderBaseProps {
+ leftContent?: ReactNode;
+ centerContent?: ReactNode;
+ rightContent?: ReactNode;
+ className?: string;
+ children?: ReactNode;
+}
+
+export function HeaderBase({
+ leftContent,
+ centerContent,
+ rightContent,
+ className,
+ children,
+}: HeaderBaseProps) {
+ // If children is provided, render it directly without the grid layout
+ if (children) {
+ return (
+ <header className={cn("px-6 h-16 flex items-center", className)}>
+ {children}
+ </header>
+ );
+ }
+
+ return (
+ <header
+ className={cn("px-6 h-16 flex justify-between items-center", className)}
+ >
+ {leftContent && <div className="flex items-center">{leftContent}</div>}
+ {centerContent && (
+ <div className="flex items-center">{centerContent}</div>
+ )}
+ {rightContent && <div className="flex items-center">{rightContent}</div>}
+ </header>
+ );
+}
diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx
new file mode 100644
index 00000000..a2ac5016
--- /dev/null
+++ b/apps/web/src/components/header.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import Link from "next/link";
+import { Button } from "./ui/button";
+import { ArrowRight } from "lucide-react";
+import { HeaderBase } from "./header-base";
+
+export function Header() {
+ const leftContent = (
+ <Link href="/" className="font-medium tracking-tight">
+ AppCut
+ </Link>
+ );
+
+ const rightContent = (
+ <nav className="flex items-center">
+ <Link href="/editor">
+ <Button variant="ghost" className="text-sm">
+ Open editor
+ </Button>
+ </Link>
+ <Link href="https://github.com/mazeincoding/AppCut" target="_blank">
+ <Button variant="ghost" className="text-sm">
+ GitHub
+ </Button>
+ </Link>
+ <Link href="/editor">
+ <Button size="sm" className="text-sm ml-4">
+ Start editing
+ <ArrowRight className="h-4 w-4" />
+ </Button>
+ </Link>
+ </nav>
+ );
+
+ return <HeaderBase leftContent={leftContent} rightContent={rightContent} />;
+}
diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx
new file mode 100644
index 00000000..8821cb71
--- /dev/null
+++ b/apps/web/src/components/landing/hero.tsx
@@ -0,0 +1,72 @@
+"use client";
+
+import { motion } from "motion/react";
+import { Button } from "../ui/button";
+import { ArrowRight } from "lucide-react";
+import Link from "next/link";
+
+export function Hero() {
+ return (
+ <div className="relative min-h-screen flex flex-col items-center justify-center text-center px-4">
+ <motion.div
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1 }}
+ transition={{ duration: 1 }}
+ className="max-w-3xl mx-auto"
+ >
+ <motion.div
+ initial={{ opacity: 0, y: 20 }}
+ animate={{ opacity: 1, y: 0 }}
+ transition={{ delay: 0.2, duration: 0.8 }}
+ className="inline-block"
+ >
+ <h1 className="text-5xl sm:text-7xl font-bold tracking-tighter">
+ The open source
+ </h1>
+ <h1 className="text-5xl sm:text-7xl font-bold tracking-tighter mt-2">
+ CapCut alternative.
+ </h1>
+ </motion.div>
+
+ <motion.p
+ className="mt-12 text-lg sm:text-xl text-muted-foreground font-light tracking-wide max-w-xl mx-auto"
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1 }}
+ transition={{ delay: 0.4, duration: 0.8 }}
+ >
+ A simple but powerful video editor that gets the job done. In your
+ browser.
+ </motion.p>
+
+ <motion.div
+ className="mt-12 flex gap-8 justify-center"
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1 }}
+ transition={{ delay: 0.6, duration: 0.8 }}
+ >
+ <Link href="/editor">
+ <Button size="lg" className="px-6 h-11 text-base">
+ <span className="relative z-10">Start editing</span>
+ <ArrowRight className="relative z-10 ml-0.5 h-4 w-4 inline-block" />
+ </Button>
+ </Link>
+ </motion.div>
+ </motion.div>
+
+ <motion.div
+ className="absolute bottom-12 left-0 right-0 text-center text-sm text-muted-foreground/60"
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1 }}
+ transition={{ delay: 0.8, duration: 0.8 }}
+ >
+ Currently in beta • Open source on{" "}
+ <Link
+ href="https://github.com/mazeincoding/AppCut"
+ className="text-foreground underline"
+ >
+ GitHub
+ </Link>
+ </motion.div>
+ </div>
+ );
+}
diff --git a/apps/web/src/components/media-panel.tsx b/apps/web/src/components/media-panel.tsx
deleted file mode 100644
index 10fc0fb5..00000000
--- a/apps/web/src/components/media-panel.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-"use client";
-
-import { Button } from "./ui/button";
-import { ScrollArea } from "./ui/scroll-area";
-import { AspectRatio } from "./ui/aspect-ratio";
-
-const mediaItems = [
- { name: "Sample 1", type: "image" },
- { name: "Sample 2", type: "image" },
- { name: "Sample 3", type: "image" },
- { name: "Sample 4", type: "image" },
- { name: "Sample 5", type: "image" },
- { name: "Sample 6", type: "image" },
-] as const;
-
-export function MediaPanel() {
- return (
- <ScrollArea className="h-full">
- <div className="space-y-4 p-2">
- {/* Media Grid */}
- <div className="grid grid-cols-2 gap-2">
- {mediaItems.map((item) => (
- <Button
- key={item.name}
- variant="outline"
- className="flex flex-col gap-2 p-0 h-auto overflow-hidden"
- >
- <AspectRatio ratio={16 / 9} className="w-full">
- <div className="w-full h-full bg-muted/30 flex items-center justify-center text-muted-foreground text-xs">
- {item.name}
- </div>
- </AspectRatio>
- </Button>
- ))}
- </div>
- </div>
- </ScrollArea>
- );
-}
diff --git a/apps/web/src/lib/auth-client.ts b/apps/web/src/lib/auth-client.ts
new file mode 100644
index 00000000..7c467e7e
--- /dev/null
+++ b/apps/web/src/lib/auth-client.ts
@@ -0,0 +1,4 @@
+import { createAuthClient } from "better-auth/react";
+export const authClient = createAuthClient({
+ baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL!,
+});
diff --git a/apps/web/src/lib/auth.ts b/apps/web/src/lib/auth.ts
new file mode 100644
index 00000000..84409b6d
--- /dev/null
+++ b/apps/web/src/lib/auth.ts
@@ -0,0 +1,23 @@
+import { betterAuth } from "better-auth";
+import { drizzleAdapter } from "better-auth/adapters/drizzle";
+import { db } from "./db";
+
+export const auth = betterAuth({
+ database: drizzleAdapter(db, {
+ provider: "pg",
+ usePlural: true,
+ }),
+ secret: process.env.BETTER_AUTH_SECRET!,
+ user: {
+ deleteUser: {
+ enabled: true,
+ },
+ },
+ emailAndPassword: {
+ enabled: false,
+ },
+ appName: "AppCut",
+ trustedOrigins: ["http://localhost:3000"],
+});
+
+export type Auth = typeof auth;
diff --git a/apps/web/src/lib/db/index.tsx b/apps/web/src/lib/db/index.tsx
new file mode 100644
index 00000000..b66c6e39
--- /dev/null
+++ b/apps/web/src/lib/db/index.tsx
@@ -0,0 +1,14 @@
+import { drizzle } from "drizzle-orm/node-postgres";
+import { Pool } from "pg";
+import * as schema from "./schema";
+
+const pool = new Pool({
+ connectionString: process.env.DATABASE_URL,
+ max: 3,
+ idleTimeoutMillis: 10000,
+ connectionTimeoutMillis: 15000,
+ query_timeout: 20000,
+ statement_timeout: 20000,
+});
+
+export const db = drizzle(pool, { schema });
diff --git a/apps/web/src/lib/db/schema.ts b/apps/web/src/lib/db/schema.ts
new file mode 100644
index 00000000..02df5d4a
--- /dev/null
+++ b/apps/web/src/lib/db/schema.ts
@@ -0,0 +1,61 @@
+import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";
+
+export const users = pgTable("users", {
+ id: text("id").primaryKey(),
+ name: text("name").notNull(),
+ email: text("email").notNull().unique(),
+ emailVerified: boolean("email_verified")
+ .$defaultFn(() => false)
+ .notNull(),
+ image: text("image"),
+ createdAt: timestamp("created_at")
+ .$defaultFn(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ updatedAt: timestamp("updated_at")
+ .$defaultFn(() => /* @__PURE__ */ new Date())
+ .notNull(),
+}).enableRLS();
+
+export const sessions = pgTable("sessions", {
+ id: text("id").primaryKey(),
+ expiresAt: timestamp("expires_at").notNull(),
+ token: text("token").notNull().unique(),
+ createdAt: timestamp("created_at").notNull(),
+ updatedAt: timestamp("updated_at").notNull(),
+ ipAddress: text("ip_address"),
+ userAgent: text("user_agent"),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+}).enableRLS();
+
+export const accounts = pgTable("accounts", {
+ id: text("id").primaryKey(),
+ accountId: text("account_id").notNull(),
+ providerId: text("provider_id").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ accessToken: text("access_token"),
+ refreshToken: text("refresh_token"),
+ idToken: text("id_token"),
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
+ scope: text("scope"),
+ password: text("password"),
+ createdAt: timestamp("created_at").notNull(),
+ updatedAt: timestamp("updated_at").notNull(),
+}).enableRLS();
+
+export const verifications = pgTable("verifications", {
+ id: text("id").primaryKey(),
+ identifier: text("identifier").notNull(),
+ value: text("value").notNull(),
+ expiresAt: timestamp("expires_at").notNull(),
+ createdAt: timestamp("created_at").$defaultFn(
+ () => /* @__PURE__ */ new Date()
+ ),
+ updatedAt: timestamp("updated_at").$defaultFn(
+ () => /* @__PURE__ */ new Date()
+ ),
+}).enableRLS();
diff --git a/apps/web/src/stores/app-store.ts b/apps/web/src/stores/app-store.ts
new file mode 100644
index 00000000..746fdc45
--- /dev/null
+++ b/apps/web/src/stores/app-store.ts
@@ -0,0 +1,35 @@
+import { create } from "zustand";
+
+interface AppState {
+ // Loading states
+ isInitializing: boolean;
+ isPanelsReady: boolean;
+
+ // Actions
+ setInitializing: (loading: boolean) => void;
+ setPanelsReady: (ready: boolean) => void;
+ initializeApp: () => Promise<void>;
+}
+
+export const useAppStore = create<AppState>((set, get) => ({
+ // Initial states
+ isInitializing: true,
+ isPanelsReady: false,
+
+ // Actions
+ setInitializing: (loading) => {
+ set({ isInitializing: loading });
+ },
+
+ setPanelsReady: (ready) => {
+ set({ isPanelsReady: ready });
+ },
+
+ initializeApp: async () => {
+ console.log("Initializing video editor...");
+ set({ isInitializing: true, isPanelsReady: false });
+
+ set({ isPanelsReady: true, isInitializing: false });
+ console.log("Video editor ready");
+ },
+}));
diff --git a/apps/web/src/stores/media-store.ts b/apps/web/src/stores/media-store.ts
new file mode 100644
index 00000000..25d11af8
--- /dev/null
+++ b/apps/web/src/stores/media-store.ts
@@ -0,0 +1,35 @@
+import { create } from "zustand";
+
+export interface MediaItem {
+ id: string;
+ name: string;
+ type: "image" | "video" | "audio";
+}
+
+interface MediaStore {
+ mediaItems: MediaItem[];
+
+ // Actions
+ addMediaItem: (item: Omit<MediaItem, "id">) => void;
+ removeMediaItem: (id: string) => void;
+}
+
+export const useMediaStore = create<MediaStore>((set, get) => ({
+ mediaItems: [],
+
+ addMediaItem: (item) => {
+ const newItem: MediaItem = {
+ ...item,
+ id: crypto.randomUUID(),
+ };
+ set((state) => ({
+ mediaItems: [...state.mediaItems, newItem],
+ }));
+ },
+
+ removeMediaItem: (id) => {
+ set((state) => ({
+ mediaItems: state.mediaItems.filter((item) => item.id !== id),
+ }));
+ },
+}));
diff --git a/apps/web/src/stores/panel-store.ts b/apps/web/src/stores/panel-store.ts
new file mode 100644
index 00000000..ea2dacb9
--- /dev/null
+++ b/apps/web/src/stores/panel-store.ts
@@ -0,0 +1,79 @@
+import { create } from "zustand";
+import { persist } from "zustand/middleware";
+
+interface PanelState {
+ // Horizontal panel sizes
+ toolsPanel: number;
+ previewPanel: number;
+ propertiesPanel: number;
+
+ // Vertical panel sizes
+ mainContent: number;
+ timeline: number;
+
+ // Flag to prevent initial overwrites
+ isInitialized: boolean;
+
+ // Actions
+ setToolsPanel: (size: number) => void;
+ setPreviewPanel: (size: number) => void;
+ setPropertiesPanel: (size: number) => void;
+ setMainContent: (size: number) => void;
+ setTimeline: (size: number) => void;
+ setInitialized: () => void;
+}
+
+export const usePanelStore = create<PanelState>()(
+ persist(
+ (set, get) => ({
+ // Default sizes
+ toolsPanel: 20,
+ previewPanel: 60,
+ propertiesPanel: 20,
+ mainContent: 50,
+ timeline: 50,
+ isInitialized: false,
+
+ // Actions
+ setToolsPanel: (size) => {
+ const state = get();
+ if (!state.isInitialized) return;
+ set({ toolsPanel: size });
+ },
+ setPreviewPanel: (size) => {
+ const state = get();
+ if (!state.isInitialized) return;
+ set({ previewPanel: size });
+ },
+ setPropertiesPanel: (size) => {
+ const state = get();
+ if (!state.isInitialized) return;
+ set({ propertiesPanel: size });
+ },
+ setMainContent: (size) => {
+ const state = get();
+ if (!state.isInitialized) return;
+ set({ mainContent: size });
+ },
+ setTimeline: (size) => {
+ const state = get();
+ if (!state.isInitialized) return;
+ set({ timeline: size });
+ },
+ setInitialized: () => {
+ console.log("Panel store initialized for resize events");
+ set({ isInitialized: true });
+ },
+ }),
+ {
+ name: "panel-sizes",
+ partialize: (state) => ({
+ toolsPanel: state.toolsPanel,
+ previewPanel: state.previewPanel,
+ propertiesPanel: state.propertiesPanel,
+ mainContent: state.mainContent,
+ timeline: state.timeline,
+ }),
+ }
+ )
+);
diff --git a/apps/web/src/stores/project-store.ts b/apps/web/src/stores/project-store.ts
new file mode 100644
index 00000000..734c07e2
--- /dev/null
+++ b/apps/web/src/stores/project-store.ts
@@ -0,0 +1,28 @@
+import { TProject } from "@/types/project";
+import { create } from "zustand";
+
+interface ProjectStore {
+ activeProject: TProject | null;
+
+ // Actions
+ createNewProject: (name: string) => void;
+ closeProject: () => void;
+}
+
+export const useProjectStore = create<ProjectStore>((set) => ({
+ activeProject: null,
+
+ createNewProject: (name: string) => {
+ const newProject: TProject = {
+ id: crypto.randomUUID(),
+ name,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ };
+ set({ activeProject: newProject });
+ },
+
+ closeProject: () => {
+ set({ activeProject: null });
+ },
+}));
diff --git a/apps/web/src/stores/timeline-store.ts b/apps/web/src/stores/timeline-store.ts
new file mode 100644
index 00000000..66fb085c
--- /dev/null
+++ b/apps/web/src/stores/timeline-store.ts
@@ -0,0 +1,61 @@
+import { create } from "zustand";
+
+export interface TimelineClip {
+ id: string;
+ mediaId: string;
+ name: string;
+ duration: number;
+}
+
+export interface TimelineTrack {
+ id: string;
+ name: string;
+ type: "video" | "audio" | "effects";
+ clips: TimelineClip[];
+}
+
+interface TimelineStore {
+ tracks: TimelineTrack[];
+
+ // Actions
+ addTrack: (type: "video" | "audio" | "effects") => void;
+ removeTrack: (trackId: string) => void;
+ addClipToTrack: (trackId: string, clip: Omit<TimelineClip, "id">) => void;
+}
+
+export const useTimelineStore = create<TimelineStore>((set) => ({
+ tracks: [],
+
+ addTrack: (type) => {
+ const newTrack: TimelineTrack = {
+ id: crypto.randomUUID(),
+ name: `${type.charAt(0).toUpperCase() + type.slice(1)} Track`,
+ type,
+ clips: [],
+ };
+ set((state) => ({
+ tracks: [...state.tracks, newTrack],
+ }));
+ },
+
+ removeTrack: (trackId) => {
+ set((state) => ({
+ tracks: state.tracks.filter((track) => track.id !== trackId),
+ }));
+ },
+
+ addClipToTrack: (trackId, clipData) => {
+ const newClip: TimelineClip = {
+ ...clipData,
+ id: crypto.randomUUID(),
+ };
+
+ set((state) => ({
+ tracks: state.tracks.map((track) =>
+ track.id === trackId
+ ? { ...track, clips: [...track.clips, newClip] }
+ : track
+ ),
+ }));
+ },
+}));
diff --git a/apps/web/src/types/project.ts b/apps/web/src/types/project.ts
new file mode 100644
index 00000000..74886b24
--- /dev/null
+++ b/apps/web/src/types/project.ts
@@ -0,0 +1,6 @@
+export interface TProject {
+ id: string;
+ name: string;
+ createdAt: Date;
+ updatedAt: Date;
+}
diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts
index 8f5bda01..c35ee1ad 100644
--- a/apps/web/tailwind.config.ts
+++ b/apps/web/tailwind.config.ts
@@ -8,6 +8,12 @@ export default {
],
theme: {
extend: {
+ fontSize: {
+ base: "0.95rem",
+ },
+ fontFamily: {
+ sans: ["var(--font-inter)", "sans-serif"],
+ },
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",