nextclaw 0.2.3 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -36,6 +36,7 @@ import { join as join6, resolve } from "path";
36
36
  import { spawn, spawnSync } from "child_process";
37
37
  import { createInterface } from "readline";
38
38
  import { fileURLToPath } from "url";
39
+ import { createServer } from "net";
39
40
 
40
41
  // src/bus/queue.ts
41
42
  var AsyncQueue = class {
@@ -2412,6 +2413,13 @@ var ChannelManager = class {
2412
2413
  }
2413
2414
  async stopAll() {
2414
2415
  this.dispatching = false;
2416
+ await this.bus.publishOutbound({
2417
+ channel: "__control__",
2418
+ chatId: "",
2419
+ content: "",
2420
+ media: [],
2421
+ metadata: { reason: "shutdown" }
2422
+ });
2415
2423
  if (this.dispatchTask) {
2416
2424
  await this.dispatchTask;
2417
2425
  }
@@ -2890,6 +2898,57 @@ function updateUi(configPath, patch) {
2890
2898
  return next.ui;
2891
2899
  }
2892
2900
 
2901
+ // src/channels/feishu-probe.ts
2902
+ import * as Lark2 from "@larksuiteoapi/node-sdk";
2903
+ var isRecord = (value) => typeof value === "object" && value !== null;
2904
+ async function probeFeishu(appId, appSecret) {
2905
+ if (!appId || !appSecret) {
2906
+ return { ok: false, error: "missing credentials (appId, appSecret)" };
2907
+ }
2908
+ try {
2909
+ const client = new Lark2.Client({ appId, appSecret });
2910
+ const response = await client.request({
2911
+ method: "GET",
2912
+ url: "/open-apis/bot/v3/info",
2913
+ data: {}
2914
+ });
2915
+ if (!isRecord(response)) {
2916
+ return {
2917
+ ok: false,
2918
+ appId,
2919
+ error: "API error: invalid response"
2920
+ };
2921
+ }
2922
+ const code = typeof response.code === "number" ? response.code : null;
2923
+ if (code !== 0) {
2924
+ const msg = typeof response.msg === "string" ? response.msg : void 0;
2925
+ return {
2926
+ ok: false,
2927
+ appId,
2928
+ error: `API error: ${msg || `code ${code ?? "unknown"}`}`
2929
+ };
2930
+ }
2931
+ const botFromResponse = isRecord(response.bot) ? response.bot : void 0;
2932
+ const data = isRecord(response.data) ? response.data : void 0;
2933
+ const botFromData = data && isRecord(data.bot) ? data.bot : void 0;
2934
+ const bot = botFromResponse ?? botFromData;
2935
+ const botName = bot && typeof bot.bot_name === "string" ? bot.bot_name : void 0;
2936
+ const botOpenId = bot && typeof bot.open_id === "string" ? bot.open_id : void 0;
2937
+ return {
2938
+ ok: true,
2939
+ appId,
2940
+ botName,
2941
+ botOpenId
2942
+ };
2943
+ } catch (error) {
2944
+ return {
2945
+ ok: false,
2946
+ appId,
2947
+ error: error instanceof Error ? error.message : String(error)
2948
+ };
2949
+ }
2950
+ }
2951
+
2893
2952
  // src/ui/router.ts
2894
2953
  function ok(data) {
2895
2954
  return { ok: true, data };
@@ -2907,6 +2966,7 @@ async function readJson(req) {
2907
2966
  }
2908
2967
  function createUiRouter(options) {
2909
2968
  const app = new Hono();
2969
+ app.notFound((c) => c.json(err("NOT_FOUND", "endpoint not found"), 404));
2910
2970
  app.get("/api/health", (c) => c.json(ok({ status: "ok" })));
2911
2971
  app.get("/api/config", (c) => {
2912
2972
  const config = loadConfigOrDefault(options.configPath);
@@ -2951,6 +3011,24 @@ function createUiRouter(options) {
2951
3011
  options.publish({ type: "config.updated", payload: { path: `channels.${channel}` } });
2952
3012
  return c.json(ok(result));
2953
3013
  });
3014
+ app.post("/api/channels/feishu/probe", async (c) => {
3015
+ const config = loadConfigOrDefault(options.configPath);
3016
+ const feishu = config.channels.feishu;
3017
+ if (!feishu?.appId || !feishu?.appSecret) {
3018
+ return c.json(err("MISSING_CREDENTIALS", "Feishu appId/appSecret not configured"), 400);
3019
+ }
3020
+ const result = await probeFeishu(String(feishu.appId), String(feishu.appSecret));
3021
+ if (!result.ok) {
3022
+ return c.json(err("PROBE_FAILED", result.error), 400);
3023
+ }
3024
+ return c.json(
3025
+ ok({
3026
+ appId: result.appId,
3027
+ botName: result.botName ?? null,
3028
+ botOpenId: result.botOpenId ?? null
3029
+ })
3030
+ );
3031
+ });
2954
3032
  app.put("/api/config/ui", async (c) => {
2955
3033
  const body = await readJson(c.req.raw);
2956
3034
  if (!body.ok) {
@@ -2979,7 +3057,15 @@ function createUiRouter(options) {
2979
3057
 
2980
3058
  // src/ui/server.ts
2981
3059
  import { serveStatic } from "hono/serve-static";
2982
- var DEFAULT_CORS_ORIGINS = ["http://localhost:5173", "http://127.0.0.1:5173"];
3060
+ var DEFAULT_CORS_ORIGINS = (origin) => {
3061
+ if (!origin) {
3062
+ return void 0;
3063
+ }
3064
+ if (origin.startsWith("http://localhost:") || origin.startsWith("http://127.0.0.1:")) {
3065
+ return origin;
3066
+ }
3067
+ return void 0;
3068
+ };
2983
3069
  function startUiServer(options) {
2984
3070
  const app = new Hono2();
2985
3071
  const origin = options.corsOrigins ?? DEFAULT_CORS_ORIGINS;
@@ -3110,7 +3196,7 @@ program.command("ui").description(`Start the ${APP_NAME} UI with gateway`).optio
3110
3196
  }
3111
3197
  await startGateway({ uiOverrides, allowMissingProvider: true });
3112
3198
  });
3113
- program.command("start").description(`Start the ${APP_NAME} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server", false).option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => {
3199
+ program.command("start").description(`Start the ${APP_NAME} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => {
3114
3200
  const uiOverrides = {
3115
3201
  enabled: true,
3116
3202
  open: false
@@ -3121,6 +3207,31 @@ program.command("start").description(`Start the ${APP_NAME} gateway + UI in the
3121
3207
  if (opts.uiPort) {
3122
3208
  uiOverrides.port = Number(opts.uiPort);
3123
3209
  }
3210
+ const devMode = isDevRuntime();
3211
+ if (devMode) {
3212
+ const requestedUiPort = Number.isFinite(Number(opts.uiPort)) ? Number(opts.uiPort) : 18792;
3213
+ const requestedFrontendPort = Number.isFinite(Number(opts.frontendPort)) ? Number(opts.frontendPort) : 5174;
3214
+ const uiHost = uiOverrides.host ?? "127.0.0.1";
3215
+ const devUiPort = await findAvailablePort(requestedUiPort, uiHost);
3216
+ const shouldStartFrontend = opts.frontend === void 0 ? true : Boolean(opts.frontend);
3217
+ const devFrontendPort = shouldStartFrontend ? await findAvailablePort(requestedFrontendPort, "127.0.0.1") : requestedFrontendPort;
3218
+ uiOverrides.port = devUiPort;
3219
+ if (requestedUiPort !== devUiPort) {
3220
+ console.log(`Dev mode: UI port ${requestedUiPort} is in use, switched to ${devUiPort}.`);
3221
+ }
3222
+ if (shouldStartFrontend && requestedFrontendPort !== devFrontendPort) {
3223
+ console.log(`Dev mode: Frontend port ${requestedFrontendPort} is in use, switched to ${devFrontendPort}.`);
3224
+ }
3225
+ console.log(`Dev mode: UI ${devUiPort}, Frontend ${devFrontendPort}`);
3226
+ console.log("Dev mode runs in the foreground (Ctrl+C to stop).");
3227
+ await runForeground({
3228
+ uiOverrides,
3229
+ frontend: shouldStartFrontend,
3230
+ frontendPort: devFrontendPort,
3231
+ open: Boolean(opts.open)
3232
+ });
3233
+ return;
3234
+ }
3124
3235
  await startService({
3125
3236
  uiOverrides,
3126
3237
  frontend: Boolean(opts.frontend),
@@ -3128,7 +3239,7 @@ program.command("start").description(`Start the ${APP_NAME} gateway + UI in the
3128
3239
  open: Boolean(opts.open)
3129
3240
  });
3130
3241
  });
3131
- program.command("serve").description(`Run the ${APP_NAME} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server", false).option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => {
3242
+ program.command("serve").description(`Run the ${APP_NAME} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => {
3132
3243
  const uiOverrides = {
3133
3244
  enabled: true,
3134
3245
  open: false
@@ -3139,32 +3250,31 @@ program.command("serve").description(`Run the ${APP_NAME} gateway + UI in the fo
3139
3250
  if (opts.uiPort) {
3140
3251
  uiOverrides.port = Number(opts.uiPort);
3141
3252
  }
3142
- const config = loadConfig();
3143
- const uiConfig = resolveUiConfig(config, uiOverrides);
3144
- const staticDir = resolveUiStaticDir();
3145
- const shouldStartFrontend = Boolean(opts.frontend);
3146
- const frontendPort = shouldStartFrontend && Number.isFinite(Number(opts.frontendPort)) ? Number(opts.frontendPort) : 5173;
3147
- const frontendDir = shouldStartFrontend ? resolveUiFrontendDir() : null;
3148
- let frontendUrl = null;
3149
- if (shouldStartFrontend && frontendDir) {
3150
- const frontend = startUiFrontend({
3151
- apiBase: resolveUiApiBase(uiConfig.host, uiConfig.port),
3152
- port: frontendPort,
3153
- dir: frontendDir
3154
- });
3155
- frontendUrl = frontend?.url ?? null;
3156
- } else if (shouldStartFrontend && !frontendDir) {
3157
- console.log("Warning: UI frontend not found. Start it separately.");
3253
+ const devMode = isDevRuntime();
3254
+ if (devMode && uiOverrides.port === void 0) {
3255
+ uiOverrides.port = 18792;
3158
3256
  }
3159
- if (!frontendUrl && staticDir) {
3160
- frontendUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
3257
+ const shouldStartFrontend = Boolean(opts.frontend);
3258
+ const defaultFrontendPort = devMode ? 5174 : 5173;
3259
+ const requestedFrontendPort = Number.isFinite(Number(opts.frontendPort)) ? Number(opts.frontendPort) : defaultFrontendPort;
3260
+ if (devMode && uiOverrides.port !== void 0) {
3261
+ const uiHost = uiOverrides.host ?? "127.0.0.1";
3262
+ const uiPort = await findAvailablePort(uiOverrides.port, uiHost);
3263
+ if (uiPort !== uiOverrides.port) {
3264
+ console.log(`Dev mode: UI port ${uiOverrides.port} is in use, switched to ${uiPort}.`);
3265
+ uiOverrides.port = uiPort;
3266
+ }
3161
3267
  }
3162
- if (opts.open && frontendUrl) {
3163
- openBrowser(frontendUrl);
3164
- } else if (opts.open && !frontendUrl) {
3165
- console.log("Warning: UI frontend not started. Browser not opened.");
3268
+ const frontendPort = devMode && shouldStartFrontend ? await findAvailablePort(requestedFrontendPort, "127.0.0.1") : requestedFrontendPort;
3269
+ if (devMode && shouldStartFrontend && frontendPort !== requestedFrontendPort) {
3270
+ console.log(`Dev mode: Frontend port ${requestedFrontendPort} is in use, switched to ${frontendPort}.`);
3166
3271
  }
3167
- await startGateway({ uiOverrides, allowMissingProvider: true, uiStaticDir: staticDir ?? void 0 });
3272
+ await runForeground({
3273
+ uiOverrides,
3274
+ frontend: shouldStartFrontend,
3275
+ frontendPort,
3276
+ open: Boolean(opts.open)
3277
+ });
3168
3278
  });
3169
3279
  program.command("stop").description(`Stop the ${APP_NAME} background service`).action(async () => {
3170
3280
  await stopService();
@@ -3341,7 +3451,7 @@ async function startGateway(options = {}) {
3341
3451
  const cronStorePath = join6(getDataDir(), "cron", "jobs.json");
3342
3452
  const cron2 = new CronService(cronStorePath);
3343
3453
  const uiConfig = resolveUiConfig(config, options.uiOverrides);
3344
- const uiStaticDir = options.uiStaticDir ?? resolveUiStaticDir();
3454
+ const uiStaticDir = options.uiStaticDir === void 0 ? resolveUiStaticDir() : options.uiStaticDir;
3345
3455
  if (!provider) {
3346
3456
  if (uiConfig.enabled) {
3347
3457
  const uiServer = startUiServer({
@@ -3403,7 +3513,25 @@ async function startGateway(options = {}) {
3403
3513
  30 * 60,
3404
3514
  true
3405
3515
  );
3406
- const channels2 = new ChannelManager(config, bus, sessionManager);
3516
+ let channels2 = new ChannelManager(config, bus, sessionManager);
3517
+ let reloadTask = null;
3518
+ const reloadChannels = async () => {
3519
+ if (reloadTask) {
3520
+ await reloadTask;
3521
+ return;
3522
+ }
3523
+ reloadTask = (async () => {
3524
+ const nextConfig = loadConfig();
3525
+ await channels2.stopAll();
3526
+ channels2 = new ChannelManager(nextConfig, bus, sessionManager);
3527
+ await channels2.startAll();
3528
+ })();
3529
+ try {
3530
+ await reloadTask;
3531
+ } finally {
3532
+ reloadTask = null;
3533
+ }
3534
+ };
3407
3535
  if (channels2.enabledChannels.length) {
3408
3536
  console.log(`\u2713 Channels enabled: ${channels2.enabledChannels.join(", ")}`);
3409
3537
  } else {
@@ -3416,7 +3544,7 @@ async function startGateway(options = {}) {
3416
3544
  configPath: getConfigPath(),
3417
3545
  staticDir: uiStaticDir ?? void 0,
3418
3546
  onReload: async () => {
3419
- return;
3547
+ await reloadChannels();
3420
3548
  }
3421
3549
  });
3422
3550
  const uiUrl = `http://${uiServer.host}:${uiServer.port}`;
@@ -3445,6 +3573,83 @@ function resolveUiApiBase(host, port) {
3445
3573
  const normalizedHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
3446
3574
  return `http://${normalizedHost}:${port}`;
3447
3575
  }
3576
+ function isDevRuntime() {
3577
+ return import.meta.url.includes("/src/cli/") || process.env.NEXTCLAW_DEV === "1";
3578
+ }
3579
+ function normalizeHostForPortCheck(host) {
3580
+ return host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
3581
+ }
3582
+ async function findAvailablePort(port, host, attempts = 20) {
3583
+ const basePort = Number.isFinite(port) ? port : 0;
3584
+ let candidate = basePort;
3585
+ for (let i = 0; i < attempts; i += 1) {
3586
+ const ok2 = await isPortAvailable(candidate, host);
3587
+ if (ok2) {
3588
+ return candidate;
3589
+ }
3590
+ candidate += 1;
3591
+ }
3592
+ return basePort;
3593
+ }
3594
+ async function isPortAvailable(port, host) {
3595
+ const checkHost = normalizeHostForPortCheck(host);
3596
+ const hostsToCheck = [checkHost];
3597
+ if (checkHost === "127.0.0.1") {
3598
+ hostsToCheck.push("::1");
3599
+ } else if (checkHost === "::1") {
3600
+ hostsToCheck.push("127.0.0.1");
3601
+ }
3602
+ for (const hostToCheck of hostsToCheck) {
3603
+ const ok2 = await canBindPort(port, hostToCheck);
3604
+ if (!ok2) {
3605
+ return false;
3606
+ }
3607
+ }
3608
+ return true;
3609
+ }
3610
+ async function canBindPort(port, host) {
3611
+ return await new Promise((resolve2) => {
3612
+ const server = createServer();
3613
+ server.unref();
3614
+ server.once("error", () => resolve2(false));
3615
+ server.listen({ port, host }, () => {
3616
+ server.close(() => resolve2(true));
3617
+ });
3618
+ });
3619
+ }
3620
+ async function runForeground(options) {
3621
+ const config = loadConfig();
3622
+ const uiConfig = resolveUiConfig(config, options.uiOverrides);
3623
+ const shouldStartFrontend = options.frontend;
3624
+ const frontendPort = Number.isFinite(options.frontendPort) ? options.frontendPort : 5173;
3625
+ const frontendDir = shouldStartFrontend ? resolveUiFrontendDir() : null;
3626
+ const staticDir = resolveUiStaticDir();
3627
+ let frontendUrl = null;
3628
+ if (shouldStartFrontend && frontendDir) {
3629
+ const frontend = startUiFrontend({
3630
+ apiBase: resolveUiApiBase(uiConfig.host, uiConfig.port),
3631
+ port: frontendPort,
3632
+ dir: frontendDir
3633
+ });
3634
+ frontendUrl = frontend?.url ?? null;
3635
+ } else if (shouldStartFrontend && !frontendDir) {
3636
+ console.log("Warning: UI frontend not found. Start it separately.");
3637
+ }
3638
+ if (!frontendUrl && staticDir) {
3639
+ frontendUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
3640
+ }
3641
+ if (options.open && frontendUrl) {
3642
+ openBrowser(frontendUrl);
3643
+ } else if (options.open && !frontendUrl) {
3644
+ console.log("Warning: UI frontend not started. Browser not opened.");
3645
+ }
3646
+ const uiStaticDir = shouldStartFrontend && frontendDir ? null : staticDir;
3647
+ await startGateway({
3648
+ uiOverrides: options.uiOverrides,
3649
+ allowMissingProvider: true,
3650
+ uiStaticDir
3651
+ });
3652
+ }
3448
3653
  async function startService(options) {
3449
3654
  const config = loadConfig();
3450
3655
  const uiConfig = resolveUiConfig(config, options.uiOverrides);
@@ -3787,7 +3992,10 @@ function startUiFrontend(options) {
3787
3992
  }
3788
3993
  const args = [...runner.args];
3789
3994
  if (options.port) {
3790
- args.push("--", "--port", String(options.port));
3995
+ if (runner.useArgSeparator) {
3996
+ args.push("--");
3997
+ }
3998
+ args.push("--port", String(options.port));
3791
3999
  }
3792
4000
  const env = { ...process.env, VITE_API_BASE: options.apiBase };
3793
4001
  const child = spawn(runner.cmd, args, { cwd: uiDir, stdio: "inherit", env });
@@ -3802,10 +4010,10 @@ function startUiFrontend(options) {
3802
4010
  }
3803
4011
  function resolveUiFrontendRunner() {
3804
4012
  if (which("pnpm")) {
3805
- return { cmd: "pnpm", args: ["dev"] };
4013
+ return { cmd: "pnpm", args: ["dev"], useArgSeparator: false };
3806
4014
  }
3807
4015
  if (which("npm")) {
3808
- return { cmd: "npm", args: ["run", "dev"] };
4016
+ return { cmd: "npm", args: ["run", "dev"], useArgSeparator: true };
3809
4017
  }
3810
4018
  return null;
3811
4019
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.2.3",
3
+ "version": "0.2.6",
4
4
  "description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background: 40 20% 98%;--foreground: 30 15% 10%;--card: 0 0% 100%;--card-foreground: 30 15% 10%;--popover: 0 0% 100%;--popover-foreground: 30 15% 10%;--primary: 30 15% 10%;--primary-foreground: 0 0% 100%;--secondary: 40 10% 94%;--secondary-foreground: 30 15% 10%;--muted: 40 10% 94%;--muted-foreground: 30 8% 45%;--accent: 40 10% 92%;--accent-foreground: 30 15% 10%;--destructive: 0 84% 60%;--destructive-foreground: 0 0% 98%;--border: 40 10% 92%;--input: 40 10% 92%;--ring: 30 15% 10%;--radius: 1.25rem;--milk-50: 40 20% 98%;--milk-100: 40 15% 96%;--milk-200: 40 12% 92%;--milk-300: 40 10% 88%;--milk-400: 40 8% 70%;--milk-500: 30 8% 45%;--milk-600: 30 10% 35%;--milk-700: 30 12% 25%;--milk-800: 30 15% 15%;--milk-900: 30 20% 8%}*{border-color:hsl(var(--border));outline:2px solid transparent;outline-offset:2px;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.2s;animation-duration:.2s}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:hsl(var(--background));color:hsl(var(--foreground));font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;overflow:hidden}*{scrollbar-width:thin;scrollbar-color:hsl(var(--warm-gray-300)) transparent}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-\[50\%\]{left:50%}.right-0{right:0}.right-1\.5{right:.375rem}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-1\.5{top:.375rem}.top-1\/2{top:50%}.top-4{top:1rem}.top-\[50\%\]{top:50%}.z-10{z-index:10}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.mb-10{margin-bottom:2.5rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-auto{margin-top:auto}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-0\.5{height:.125rem}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[85vh\]{max-height:85vh}.min-h-\[42px\]{min-height:42px}.w-1\.5{width:.375rem}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-28{width:7rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-32{width:8rem}.w-36{width:9rem}.w-4{width:1rem}.w-40{width:10rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-\[160px\]{width:160px}.w-\[240px\]{width:240px}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[100px\]{min-width:100px}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.max-w-\[200px\]{max-width:200px}.max-w-lg{max-width:32rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\[-50\%\]{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-\[-50\%\]{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-12{gap:3rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-8{gap:2rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-2\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.625rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.625rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-\[1\.5rem\]{border-radius:1.5rem}.rounded-\[2\.5rem\]{border-radius:2.5rem}.rounded-\[2rem\]{border-radius:2rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-\[hsl\(40\,10\%\,92\%\)\]{--tw-border-opacity: 1;border-color:hsl(40 10% 92% / var(--tw-border-opacity, 1))}.border-\[hsl\(40\,10\%\,94\%\)\]{--tw-border-opacity: 1;border-color:hsl(40 10% 94% / var(--tw-border-opacity, 1))}.border-\[hsl\(40\,20\%\,90\%\)\]{--tw-border-opacity: 1;border-color:hsl(40 20% 90% / var(--tw-border-opacity, 1))}.border-input{border-color:hsl(var(--input))}.border-transparent{border-color:transparent}.bg-\[hsl\(30\,15\%\,10\%\)\]{--tw-bg-opacity: 1;background-color:hsl(30 15% 10% / var(--tw-bg-opacity, 1))}.bg-\[hsl\(30\,8\%\,55\%\)\]{--tw-bg-opacity: 1;background-color:hsl(30 8% 55% / var(--tw-bg-opacity, 1))}.bg-\[hsl\(40\,10\%\,92\%\)\]{--tw-bg-opacity: 1;background-color:hsl(40 10% 92% / var(--tw-bg-opacity, 1))}.bg-\[hsl\(40\,10\%\,98\%\)\]{--tw-bg-opacity: 1;background-color:hsl(40 10% 98% / var(--tw-bg-opacity, 1))}.bg-\[hsl\(40\,20\%\,94\%\)\]{--tw-bg-opacity: 1;background-color:hsl(40 20% 94% / var(--tw-bg-opacity, 1))}.bg-\[hsl\(40\,20\%\,96\%\)\]{--tw-bg-opacity: 1;background-color:hsl(40 20% 96% / var(--tw-bg-opacity, 1))}.bg-\[hsl\(40\,20\%\,98\%\)\]{--tw-bg-opacity: 1;background-color:hsl(40 20% 98% / var(--tw-bg-opacity, 1))}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-background{background-color:hsl(var(--background))}.bg-black\/50{background-color:#00000080}.bg-destructive{background-color:hsl(var(--destructive))}.bg-emerald-100{--tw-bg-opacity: 1;background-color:rgb(209 250 229 / var(--tw-bg-opacity, 1))}.bg-emerald-50{--tw-bg-opacity: 1;background-color:rgb(236 253 245 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-input{background-color:hsl(var(--input))}.bg-muted{background-color:hsl(var(--muted))}.bg-orange-500{--tw-bg-opacity: 1;background-color:rgb(249 115 22 / var(--tw-bg-opacity, 1))}.bg-primary{background-color:hsl(var(--primary))}.bg-secondary{background-color:hsl(var(--secondary))}.bg-slate-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/80{background-color:#fffc}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-l{background-image:linear-gradient(to left,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-emerald-400{--tw-gradient-from: #34d399 var(--tw-gradient-from-position);--tw-gradient-to: rgb(52 211 153 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-orange-400{--tw-gradient-from: #fb923c var(--tw-gradient-from-position);--tw-gradient-to: rgb(251 146 60 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-400{--tw-gradient-from: #c084fc var(--tw-gradient-from-position);--tw-gradient-to: rgb(192 132 252 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-rose-400{--tw-gradient-from: #fb7185 var(--tw-gradient-from-position);--tw-gradient-to: rgb(251 113 133 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-sky-400{--tw-gradient-from: #38bdf8 var(--tw-gradient-from-position);--tw-gradient-to: rgb(56 189 248 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-400{--tw-gradient-from: #94a3b8 var(--tw-gradient-from-position);--tw-gradient-to: rgb(148 163 184 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-transparent{--tw-gradient-from: transparent var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-amber-500{--tw-gradient-to: #f59e0b var(--tw-gradient-to-position)}.to-blue-500{--tw-gradient-to: #3b82f6 var(--tw-gradient-to-position)}.to-emerald-600{--tw-gradient-to: #059669 var(--tw-gradient-to-position)}.to-gray-500{--tw-gradient-to: #6b7280 var(--tw-gradient-to-position)}.to-indigo-500{--tw-gradient-to: #6366f1 var(--tw-gradient-to-position)}.to-pink-500{--tw-gradient-to: #ec4899 var(--tw-gradient-to-position)}.to-white\/10{--tw-gradient-to: rgb(255 255 255 / .1) var(--tw-gradient-to-position)}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-20{padding-bottom:5rem}.pb-4{padding-bottom:1rem}.pr-2{padding-right:.5rem}.pr-20{padding-right:5rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-\[hsl\(30\,10\%\,35\%\)\]{--tw-text-opacity: 1;color:hsl(30 10% 35% / var(--tw-text-opacity, 1))}.text-\[hsl\(30\,15\%\,10\%\)\]{--tw-text-opacity: 1;color:hsl(30 15% 10% / var(--tw-text-opacity, 1))}.text-\[hsl\(30\,20\%\,12\%\)\]{--tw-text-opacity: 1;color:hsl(30 20% 12% / var(--tw-text-opacity, 1))}.text-\[hsl\(30\,8\%\,45\%\)\]{--tw-text-opacity: 1;color:hsl(30 8% 45% / var(--tw-text-opacity, 1))}.text-\[hsl\(30\,8\%\,55\%\)\]{--tw-text-opacity: 1;color:hsl(30 8% 55% / var(--tw-text-opacity, 1))}.text-\[hsl\(30\,8\%\,65\%\)\]{--tw-text-opacity: 1;color:hsl(30 8% 65% / var(--tw-text-opacity, 1))}.text-amber-600{--tw-text-opacity: 1;color:rgb(217 119 6 / var(--tw-text-opacity, 1))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-emerald-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity, 1))}.text-foreground{color:hsl(var(--foreground))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline-offset-4{text-underline-offset:4px}.accent-\[hsl\(30\,15\%\,10\%\)\]{accent-color:hsl(30,15%,10%)}.opacity-0{opacity:0}.opacity-70{opacity:.7}.opacity-90{opacity:.9}.mix-blend-multiply{mix-blend-mode:multiply}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_8px_rgba\(16\,185\,129\,0\.5\)\]{--tw-shadow: 0 0 8px rgba(16,185,129,.5);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-0{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-offset-background{--tw-ring-offset-color: hsl(var(--background))}.ring-offset-white{--tw-ring-offset-color: #fff}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-md{--tw-backdrop-blur: blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}.animate-in{animation-name:enter;animation-duration:.15s;--tw-enter-opacity: initial;--tw-enter-scale: initial;--tw-enter-rotate: initial;--tw-enter-translate-x: initial;--tw-enter-translate-y: initial}.fade-in{--tw-enter-opacity: 0}.slide-in-from-left-2{--tw-enter-translate-x: -.5rem}.duration-200{animation-duration:.2s}.duration-300{animation-duration:.3s}.duration-500{animation-duration:.5s}.custom-scrollbar::-webkit-scrollbar{width:6px;height:6px}.custom-scrollbar::-webkit-scrollbar-track{background:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{background:hsl(var(--warm-gray-300));border-radius:6px}.custom-scrollbar::-webkit-scrollbar-thumb:hover{background:hsl(var(--warm-gray-400))}@keyframes fadeIn{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes slideIn{0%{opacity:0;transform:translate(-12px)}to{opacity:1;transform:translate(0)}}@keyframes scaleIn{0%{opacity:0;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes pulse-soft{0%,to{opacity:1}50%{opacity:.8}}.animate-fade-in{animation:fadeIn .4s cubic-bezier(.16,1,.3,1) forwards}.animate-slide-in{animation:slideIn .35s cubic-bezier(.16,1,.3,1) forwards}.animate-scale-in{animation:scaleIn .35s cubic-bezier(.16,1,.3,1) forwards}.animate-pulse-soft{animation:pulse-soft 3s ease-in-out infinite}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.hover\:border-\[hsl\(40\,10\%\,94\%\)\]:hover{--tw-border-opacity: 1;border-color:hsl(40 10% 94% / var(--tw-border-opacity, 1))}.hover\:bg-\[hsl\(30\,15\%\,20\%\)\]:hover{--tw-bg-opacity: 1;background-color:hsl(30 15% 20% / var(--tw-bg-opacity, 1))}.hover\:bg-\[hsl\(40\,10\%\,90\%\)\]:hover{--tw-bg-opacity: 1;background-color:hsl(40 10% 90% / var(--tw-bg-opacity, 1))}.hover\:bg-\[hsl\(40\,10\%\,94\%\)\]:hover{--tw-bg-opacity: 1;background-color:hsl(40 10% 94% / var(--tw-bg-opacity, 1))}.hover\:bg-\[hsl\(40\,10\%\,96\%\)\]:hover{--tw-bg-opacity: 1;background-color:hsl(40 10% 96% / var(--tw-bg-opacity, 1))}.hover\:bg-\[hsl\(40\,20\%\,94\%\)\]:hover{--tw-bg-opacity: 1;background-color:hsl(40 20% 94% / var(--tw-bg-opacity, 1))}.hover\:bg-\[hsl\(40\,20\%\,96\%\)\]:hover{--tw-bg-opacity: 1;background-color:hsl(40 20% 96% / var(--tw-bg-opacity, 1))}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-background\/50:hover{background-color:hsl(var(--background) / .5)}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:from-emerald-500:hover{--tw-gradient-from: #10b981 var(--tw-gradient-from-position);--tw-gradient-to: rgb(16 185 129 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:from-orange-500:hover{--tw-gradient-from: #f97316 var(--tw-gradient-from-position);--tw-gradient-to: rgb(249 115 22 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.hover\:to-amber-600:hover{--tw-gradient-to: #d97706 var(--tw-gradient-to-position)}.hover\:to-emerald-700:hover{--tw-gradient-to: #047857 var(--tw-gradient-to-position)}.hover\:text-\[hsl\(30\,15\%\,10\%\)\]:hover{--tw-text-opacity: 1;color:hsl(30 15% 10% / var(--tw-text-opacity, 1))}.hover\:text-\[hsl\(30\,20\%\,12\%\)\]:hover{--tw-text-opacity: 1;color:hsl(30 20% 12% / var(--tw-text-opacity, 1))}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-red-300:hover{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-premium:hover{box-shadow:0 10px 30px -10px #00000014,0 4px 10px -4px #0000000a}.focus\:bg-white:focus{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-\[hsl\(25\,95\%\,53\%\)\]:focus{--tw-ring-opacity: 1;--tw-ring-color: hsl(25 95% 53% / var(--tw-ring-opacity, 1))}.focus\:ring-\[hsl\(30\,15\%\,10\%\)\]:focus{--tw-ring-opacity: 1;--tw-ring-color: hsl(30 15% 10% / var(--tw-ring-opacity, 1))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: hsl(var(--ring))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color: hsl(var(--background))}.active\:scale-95:active{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.active\:scale-\[0\.98\]:active{--tw-scale-x: .98;--tw-scale-y: .98;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:scale-105{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:scale-110{--tw-scale-x: 1.1;--tw-scale-y: 1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:text-amber-600{--tw-text-opacity: 1;color:rgb(217 119 6 / var(--tw-text-opacity, 1))}.group:hover .group-hover\:opacity-100{opacity:1}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.data-\[state\=checked\]\:bg-emerald-500[data-state=checked]{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.data-\[state\=open\]\:bg-\[hsl\(40\,20\%\,96\%\)\][data-state=open]{--tw-bg-opacity: 1;background-color:hsl(40 20% 96% / var(--tw-bg-opacity, 1))}.data-\[state\=open\]\:text-\[hsl\(30\,8\%\,45\%\)\][data-state=open]{--tw-text-opacity: 1;color:hsl(30 8% 45% / var(--tw-text-opacity, 1))}.data-\[state\=open\]\:animate-in[data-state=open]{animation-name:enter;animation-duration:.15s;--tw-enter-opacity: initial;--tw-enter-scale: initial;--tw-enter-rotate: initial;--tw-enter-translate-x: initial;--tw-enter-translate-y: initial}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation-name:exit;animation-duration:.15s;--tw-exit-opacity: initial;--tw-exit-scale: initial;--tw-exit-rotate: initial;--tw-exit-translate-x: initial;--tw-exit-translate-y: initial}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity: 0}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity: 0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale: .95}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale: .95}.data-\[state\=closed\]\:slide-out-to-left-1\/2[data-state=closed]{--tw-exit-translate-x: -50%}.data-\[state\=closed\]\:slide-out-to-top-\[48\%\][data-state=closed]{--tw-exit-translate-y: -48%}.data-\[state\=open\]\:slide-in-from-left-1\/2[data-state=open]{--tw-enter-translate-x: -50%}.data-\[state\=open\]\:slide-in-from-top-\[48\%\][data-state=open]{--tw-enter-translate-y: -48%}@media(min-width:640px){.sm\:inline-flex{display:inline-flex}.sm\:max-w-\[500px\]{max-width:500px}.sm\:max-w-\[550px\]{max-width:550px}.sm\:flex-row{flex-direction:row}.sm\:justify-end{justify-content:flex-end}.sm\:gap-2{gap:.5rem}.sm\:text-left{text-align:left}}@media(min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}