statikapi 0.1.4 → 0.3.0
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/package.json +9 -5
- package/src/commands/build.js +0 -1
- package/src/commands/dev.js +202 -18
- package/src/help.js +0 -2
- package/src/index.js +0 -6
- package/src/loader/importModule.js +38 -0
- package/src/loader/loadModuleValue.js +2 -4
- package/src/loader/loadPaths.js +2 -4
- package/src/router/mapRoutes.js +1 -1
- package/ui/EMBEDDED_UI_README.txt +2 -0
- package/ui/assets/index-BU1U7AZy.css +1 -0
- package/ui/assets/index-BkqZSp06.js +142 -0
- package/ui/assets/index-BkqZSp06.js.map +1 -0
- package/ui/index.html +15 -2
- package/src/commands/init.js +0 -5
- package/src/commands/preview.js +0 -326
- package/ui/assets/index-C7lyR6dJ.js +0 -57
- package/ui/assets/index-C7lyR6dJ.js.map +0 -1
- package/ui/assets/index-CnyB4RRg.css +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "statikapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zonayedpca/statikapi",
|
|
@@ -17,11 +17,15 @@
|
|
|
17
17
|
"keywords": ["static", "json", "api", "cli", "ssg"],
|
|
18
18
|
"publishConfig": { "access": "public", "provenance": true },
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"chokidar": "^3.6.0"
|
|
20
|
+
"chokidar": "^3.6.0",
|
|
21
|
+
"sirv": "^2.0.4",
|
|
22
|
+
"polka": "^0.5.2",
|
|
23
|
+
"esbuild": "^0.23.0"
|
|
21
24
|
},
|
|
22
25
|
"scripts": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"prepack": "
|
|
26
|
+
"dev": "node bin/statikapi.js dev --port 8788",
|
|
27
|
+
"build": "node bin/statikapi.js build",
|
|
28
|
+
"prepack": "node scripts/embed-ui.js",
|
|
29
|
+
"test": "node --test"
|
|
26
30
|
}
|
|
27
31
|
}
|
package/src/commands/build.js
CHANGED
package/src/commands/dev.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import chokidar from 'chokidar';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
|
+
import fss from 'node:fs'; // NEW: for createReadStream
|
|
4
5
|
import crypto from 'node:crypto';
|
|
6
|
+
import http from 'node:http'; // NEW: tiny HTTP server
|
|
7
|
+
import { fileURLToPath } from 'node:url'; // NEW: resolve UI dist
|
|
5
8
|
|
|
6
9
|
import { loadConfig } from '../config/loadConfig.js';
|
|
7
10
|
import { loadModuleValue } from '../loader/loadModuleValue.js';
|
|
@@ -11,6 +14,12 @@ import { readFlags } from '../util/readFlags.js';
|
|
|
11
14
|
import { writeFileEnsured } from '../util/fsx.js';
|
|
12
15
|
import { routeToOutPath } from '../build/routeOutPath.js';
|
|
13
16
|
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
19
|
+
function hasIndex(dir) {
|
|
20
|
+
return fss.existsSync(path.join(dir, 'index.html'));
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
function clearScreen() {
|
|
15
24
|
process.stdout.write('\x1Bc'); // ANSI "clear screen"
|
|
16
25
|
}
|
|
@@ -46,31 +55,45 @@ function toParams(segTokens, concreteRoute) {
|
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
export default async function devCmd(argv) {
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
const flags = readFlags(argv);
|
|
59
|
+
|
|
60
|
+
// Allow forcing long-running behavior even in non-TTY (e.g., under `concurrently`)
|
|
61
|
+
const forceKeepAlive =
|
|
62
|
+
!!(flags['keep-alive'] || flags.keepAlive || flags.serve) ||
|
|
63
|
+
process.env.STATIKAPI_FORCE_DEV === '1';
|
|
64
|
+
|
|
65
|
+
// In non-TTY (like node --test), behave like a stub unless explicitly forced.
|
|
66
|
+
if (!process.stdout.isTTY && !forceKeepAlive) {
|
|
51
67
|
console.log('statikapi dev → starting dev server (stub)');
|
|
52
68
|
|
|
53
69
|
return 0;
|
|
54
70
|
}
|
|
55
71
|
|
|
56
|
-
const flags = readFlags(argv);
|
|
57
72
|
const { config } = await loadConfig({ flags });
|
|
58
73
|
|
|
59
74
|
// Where to notify preview
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
// NEW: dev server + UI defaults
|
|
76
|
+
const host = String(flags.host ?? '127.0.0.1');
|
|
77
|
+
const port = Number.isFinite(flags.port) ? Number(flags.port) : 8788;
|
|
78
|
+
const noUi = !!(flags['no-ui'] || flags.noUi);
|
|
79
|
+
const noOpen = !!(flags['no-open'] || flags.noOpen);
|
|
80
|
+
|
|
81
|
+
// NEW: live SSE clients
|
|
82
|
+
const sseClients = new Set(); // each entry: { id, res }
|
|
83
|
+
function sseBroadcast(msg) {
|
|
84
|
+
const line = `data: ${msg}\n\n`;
|
|
85
|
+
for (const c of sseClients) {
|
|
86
|
+
try {
|
|
87
|
+
c.res.write(line);
|
|
88
|
+
} catch {
|
|
89
|
+
/* ignore */
|
|
90
|
+
}
|
|
72
91
|
}
|
|
73
92
|
}
|
|
93
|
+
async function notifyChanged(route) {
|
|
94
|
+
// Push to connected UIs
|
|
95
|
+
sseBroadcast(`changed:${route}`);
|
|
96
|
+
}
|
|
74
97
|
|
|
75
98
|
// Cache of outputs per source file (for deletions on subsequent rebuilds)
|
|
76
99
|
const lastEmitted = new Map(); // fileAbs -> Set<concreteRoute>
|
|
@@ -111,7 +134,6 @@ export default async function devCmd(argv) {
|
|
|
111
134
|
bytes: Buffer.byteLength(json),
|
|
112
135
|
mtime: st ? st.mtimeMs : Date.now(),
|
|
113
136
|
hash: digest(json),
|
|
114
|
-
revalidate: null,
|
|
115
137
|
};
|
|
116
138
|
|
|
117
139
|
manifestByRoute.set(route, entry);
|
|
@@ -187,7 +209,7 @@ export default async function devCmd(argv) {
|
|
|
187
209
|
if (rel.startsWith('_')) return false;
|
|
188
210
|
const ext = path.extname(rel);
|
|
189
211
|
|
|
190
|
-
return
|
|
212
|
+
return ['.js', '.mjs', '.cjs', '.ts', '.tsx'].includes(ext);
|
|
191
213
|
}
|
|
192
214
|
|
|
193
215
|
async function buildOne(fileAbs, kind) {
|
|
@@ -252,6 +274,116 @@ export default async function devCmd(argv) {
|
|
|
252
274
|
await writeManifest();
|
|
253
275
|
console.log(`[statikapi] ready. Watching ${path.relative(process.cwd(), config.paths.srcAbs)}/`);
|
|
254
276
|
|
|
277
|
+
// NEW: start HTTP server (UI + JSON helpers + SSE)
|
|
278
|
+
const server = http.createServer(async (req, res) => {
|
|
279
|
+
try {
|
|
280
|
+
let url;
|
|
281
|
+
try {
|
|
282
|
+
url = new URL(req.url || '/', `http://${host}:${port}`);
|
|
283
|
+
} catch {
|
|
284
|
+
// Extremely defensive fallback
|
|
285
|
+
url = new URL('/', `http://${host}:${port}`);
|
|
286
|
+
}
|
|
287
|
+
const pathname = url.pathname;
|
|
288
|
+
|
|
289
|
+
// 1) SSE: /_ui/events
|
|
290
|
+
if (pathname === '/_ui/events') {
|
|
291
|
+
res.writeHead(200, {
|
|
292
|
+
'Content-Type': 'text/event-stream',
|
|
293
|
+
'Cache-Control': 'no-cache',
|
|
294
|
+
Connection: 'keep-alive',
|
|
295
|
+
'X-Accel-Buffering': 'no', // for proxies
|
|
296
|
+
});
|
|
297
|
+
res.write('\n');
|
|
298
|
+
const client = { id: Date.now() + Math.random(), res };
|
|
299
|
+
sseClients.add(client);
|
|
300
|
+
req.on('close', () => sseClients.delete(client));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// 2) Manifest JSON for UI: /ui/index
|
|
305
|
+
if (pathname === '/ui/index' && req.method === 'GET') {
|
|
306
|
+
const list = Array.from(manifestByRoute.values()).sort((a, b) =>
|
|
307
|
+
a.route.localeCompare(b.route)
|
|
308
|
+
);
|
|
309
|
+
const body = JSON.stringify(list);
|
|
310
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
311
|
+
res.end(body);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 3) Serve built file content: /_ui/file?route=/path
|
|
316
|
+
if (pathname === '/_ui/file' && req.method === 'GET') {
|
|
317
|
+
const route = url.searchParams.get('route') || '';
|
|
318
|
+
const outFile = routeToOutPath({ outAbs: config.paths.outAbs, route });
|
|
319
|
+
// best-effort headers
|
|
320
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
321
|
+
try {
|
|
322
|
+
const rs = fss.createReadStream(outFile);
|
|
323
|
+
rs.on('error', () => {
|
|
324
|
+
res.statusCode = 404;
|
|
325
|
+
res.end(`Not found: ${route}`);
|
|
326
|
+
});
|
|
327
|
+
rs.pipe(res);
|
|
328
|
+
} catch {
|
|
329
|
+
res.statusCode = 404;
|
|
330
|
+
res.end(`Not found: ${route}`);
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// 4) Static React UI at /_ui/* (unless --no-ui)
|
|
336
|
+
if (!noUi && pathname.startsWith('/_ui/')) {
|
|
337
|
+
const uiRoot = resolveUiDist();
|
|
338
|
+
const rel = pathname.replace(/^\/_ui\//, '') || 'index.html';
|
|
339
|
+
const file = path.join(uiRoot, rel);
|
|
340
|
+
if (!file.startsWith(uiRoot)) {
|
|
341
|
+
res.statusCode = 403;
|
|
342
|
+
res.end('Forbidden');
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
const stat = await fs.stat(file);
|
|
347
|
+
if (stat.isDirectory()) {
|
|
348
|
+
// try index.html inside subdir
|
|
349
|
+
const idx = path.join(file, 'index.html');
|
|
350
|
+
await fs.access(idx);
|
|
351
|
+
streamFile(idx, res);
|
|
352
|
+
} else {
|
|
353
|
+
streamFile(file, res);
|
|
354
|
+
}
|
|
355
|
+
} catch {
|
|
356
|
+
// Fallback to index.html for SPA routes
|
|
357
|
+
const fallback = path.join(uiRoot, 'index.html');
|
|
358
|
+
streamFile(fallback, res);
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 5) Root → redirect to UI (unless --no-ui)
|
|
364
|
+
if (!noUi && pathname === '/') {
|
|
365
|
+
res.writeHead(302, { Location: '/_ui/' });
|
|
366
|
+
res.end();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Otherwise: 404
|
|
371
|
+
res.statusCode = 404;
|
|
372
|
+
res.end('Not Found');
|
|
373
|
+
} catch (e) {
|
|
374
|
+
console.log(e);
|
|
375
|
+
res.statusCode = 500;
|
|
376
|
+
res.end('Internal Server Error');
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
server.listen(port, host, () => {
|
|
381
|
+
console.log(`statikapi dev → serving on http://${host}:${port}${noUi ? '' : '/_ui/'}`);
|
|
382
|
+
if (!noUi && !noOpen) {
|
|
383
|
+
openInBrowser(`http://${host}:${port}/_ui/`).catch(() => {});
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
255
387
|
const watcher = chokidar.watch(config.paths.srcAbs, {
|
|
256
388
|
ignoreInitial: true,
|
|
257
389
|
ignored: (p) => path.basename(p).startsWith('_'),
|
|
@@ -263,10 +395,62 @@ export default async function devCmd(argv) {
|
|
|
263
395
|
|
|
264
396
|
// Keep process alive until SIGINT
|
|
265
397
|
await new Promise((resolve) => {
|
|
266
|
-
const stop = () =>
|
|
398
|
+
const stop = () =>
|
|
399
|
+
Promise.allSettled([watcher.close(), new Promise((r) => server.close(() => r()))]).then(() =>
|
|
400
|
+
resolve()
|
|
401
|
+
);
|
|
267
402
|
process.on('SIGINT', stop);
|
|
268
403
|
process.on('SIGTERM', stop);
|
|
269
404
|
});
|
|
270
405
|
|
|
271
406
|
return 0;
|
|
272
407
|
}
|
|
408
|
+
|
|
409
|
+
// NEW: helpers (static file & UI dist resolver & opener)
|
|
410
|
+
function streamFile(file, res) {
|
|
411
|
+
const ext = path.extname(file).toLowerCase();
|
|
412
|
+
const ctype =
|
|
413
|
+
ext === '.html'
|
|
414
|
+
? 'text/html; charset=utf-8'
|
|
415
|
+
: ext === '.js'
|
|
416
|
+
? 'text/javascript; charset=utf-8'
|
|
417
|
+
: ext === '.css'
|
|
418
|
+
? 'text/css; charset=utf-8'
|
|
419
|
+
: ext === '.json'
|
|
420
|
+
? 'application/json; charset=utf-8'
|
|
421
|
+
: ext === '.svg'
|
|
422
|
+
? 'image/svg+xml'
|
|
423
|
+
: ext === '.map'
|
|
424
|
+
? 'application/json; charset=utf-8'
|
|
425
|
+
: 'application/octet-stream';
|
|
426
|
+
res.setHeader('Content-Type', ctype);
|
|
427
|
+
fss.createReadStream(file).pipe(res);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function resolveUiDist() {
|
|
431
|
+
// 0) Optional override for power users
|
|
432
|
+
const fromEnv = process.env.STATIKAPI_UI_DIR;
|
|
433
|
+
if (fromEnv && hasIndex(fromEnv)) return fromEnv;
|
|
434
|
+
|
|
435
|
+
// 1) Bundled with the CLI: packages/cli/ui/ (your screenshot)
|
|
436
|
+
const bundled = path.resolve(__dirname, '..', '..', 'ui');
|
|
437
|
+
if (hasIndex(bundled)) return bundled;
|
|
438
|
+
|
|
439
|
+
// 2) Monorepo dev fallback: packages/ui/dist
|
|
440
|
+
const monorepoDist = path.resolve(__dirname, '..', '..', '..', 'ui', 'dist');
|
|
441
|
+
if (hasIndex(monorepoDist)) return monorepoDist;
|
|
442
|
+
|
|
443
|
+
// 3) Last resort: throw with a helpful hint
|
|
444
|
+
throw new Error(
|
|
445
|
+
'StatikAPI UI build not found. ' +
|
|
446
|
+
'Either keep a built UI at packages/cli/ui/ (index.html present), ' +
|
|
447
|
+
'or run: pnpm -w --filter @statikapi/ui build'
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function openInBrowser(url) {
|
|
452
|
+
const { exec } = await import('node:child_process');
|
|
453
|
+
const cmd =
|
|
454
|
+
process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
455
|
+
exec(`${cmd} "${url}"`);
|
|
456
|
+
}
|
package/src/help.js
CHANGED
package/src/index.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { HELP } from './help.js';
|
|
3
|
-
import initCmd from './commands/init.js';
|
|
4
3
|
import buildCmd from './commands/build.js';
|
|
5
4
|
import devCmd from './commands/dev.js';
|
|
6
|
-
import previewCmd from './commands/preview.js';
|
|
7
5
|
|
|
8
6
|
const require = createRequire(import.meta.url);
|
|
9
7
|
const { version } = require('../package.json');
|
|
@@ -24,14 +22,10 @@ export async function run(argv = process.argv.slice(2)) {
|
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
switch (cmd) {
|
|
27
|
-
case 'init':
|
|
28
|
-
return await initCmd(rest);
|
|
29
25
|
case 'build':
|
|
30
26
|
return await buildCmd(rest);
|
|
31
27
|
case 'dev':
|
|
32
28
|
return await devCmd(rest);
|
|
33
|
-
case 'preview':
|
|
34
|
-
return await previewCmd(rest);
|
|
35
29
|
default:
|
|
36
30
|
console.error(`Unknown command: ${cmd}\n`);
|
|
37
31
|
console.log(HELP);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { transform } from 'esbuild';
|
|
5
|
+
|
|
6
|
+
export async function importModule(fileAbs, { fresh = false } = {}) {
|
|
7
|
+
const ext = path.extname(fileAbs).toLowerCase();
|
|
8
|
+
const isTs = ext === '.ts' || ext === '.tsx';
|
|
9
|
+
|
|
10
|
+
// Non-TS: import by file URL; OK to use ?v= for cache-busting here.
|
|
11
|
+
if (!isTs) {
|
|
12
|
+
const u = pathToFileURL(fileAbs);
|
|
13
|
+
if (fresh) u.search = `v=${Date.now()}-${Math.random()}`;
|
|
14
|
+
return import(u.href);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// TS / TSX: transpile, then import via data: URL (no query params allowed!)
|
|
18
|
+
const src = await readFile(fileAbs, 'utf8');
|
|
19
|
+
const isTsx = ext === '.tsx';
|
|
20
|
+
|
|
21
|
+
// Make the module body unique when fresh=true so Node doesn’t reuse cache.
|
|
22
|
+
const nonce = fresh ? `\n/*__statikapi_v__=${Date.now()}-${Math.random()}*/` : '';
|
|
23
|
+
|
|
24
|
+
const { code } = await transform(src + nonce, {
|
|
25
|
+
loader: isTsx ? 'tsx' : 'ts',
|
|
26
|
+
format: 'esm',
|
|
27
|
+
sourcemap: 'inline',
|
|
28
|
+
target: 'es2022',
|
|
29
|
+
jsx: 'automatic',
|
|
30
|
+
sourcefile: fileAbs, // helps stack traces
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// IMPORTANT: no ?query. Include charset to keep Node happy.
|
|
34
|
+
const href =
|
|
35
|
+
'data:text/javascript;charset=utf-8;base64,' + Buffer.from(code, 'utf8').toString('base64');
|
|
36
|
+
|
|
37
|
+
return import(href);
|
|
38
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
2
|
|
|
4
3
|
import { LoaderError } from './errors.js';
|
|
4
|
+
import { importModule } from './importModule.js';
|
|
5
5
|
import { assertSerializable } from './serializeGuard.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -18,9 +18,7 @@ export async function loadModuleValue(fileAbs, args = {}) {
|
|
|
18
18
|
let mod;
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
|
-
|
|
22
|
-
if (fresh) u.search = `v=${Date.now()}-${Math.random()}`;
|
|
23
|
-
mod = await import(u.href);
|
|
21
|
+
mod = await importModule(fileAbs, { fresh });
|
|
24
22
|
} catch (e) {
|
|
25
23
|
throw new LoaderError(fileInfo, `Failed to import: ${e.message}`);
|
|
26
24
|
}
|
package/src/loader/loadPaths.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
2
|
|
|
4
3
|
import { LoaderError } from './errors.js';
|
|
4
|
+
import { importModule } from './importModule.js';
|
|
5
5
|
|
|
6
6
|
export async function loadPaths(fileAbs, { route, type, segments }, { fresh = false } = {}) {
|
|
7
7
|
const fileInfo = short(fileAbs);
|
|
8
8
|
let mod;
|
|
9
9
|
|
|
10
10
|
try {
|
|
11
|
-
|
|
12
|
-
if (fresh) u.search = `v=${Date.now()}-${Math.random()}`;
|
|
13
|
-
mod = await import(u.href);
|
|
11
|
+
mod = await importModule(fileAbs, { fresh });
|
|
14
12
|
} catch (e) {
|
|
15
13
|
throw new LoaderError(fileInfo, `Failed to import for paths(): ${e.message}`);
|
|
16
14
|
}
|
package/src/router/mapRoutes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
const VALID_EXT = new Set(['.js', '.mjs', '.cjs']);
|
|
4
|
+
const VALID_EXT = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx']);
|
|
5
5
|
|
|
6
6
|
export async function mapRoutes({ srcAbs }) {
|
|
7
7
|
const entries = await walk(srcAbs);
|
|
@@ -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: 0 0% 100%;--foreground: 0 0% 3.9%;--card: 0 0% 100%;--card-foreground: 0 0% 3.9%;--popover: 0 0% 100%;--popover-foreground: 0 0% 3.9%;--primary: 0 0% 9%;--primary-foreground: 0 0% 98%;--secondary: 0 0% 96.1%;--secondary-foreground: 0 0% 9%;--muted: 0 0% 96.1%;--muted-foreground: 0 0% 45.1%;--accent: 0 0% 96.1%;--accent-foreground: 0 0% 9%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 0 0% 98%;--border: 0 0% 89.8%;--input: 0 0% 89.8%;--ring: 0 0% 3.9%;--chart-1: 12 76% 61%;--chart-2: 173 58% 39%;--chart-3: 197 37% 24%;--chart-4: 43 74% 66%;--chart-5: 27 87% 67%;--radius: .5rem}.dark{--background: 0 0% 3.9%;--foreground: 0 0% 98%;--card: 0 0% 3.9%;--card-foreground: 0 0% 98%;--popover: 0 0% 3.9%;--popover-foreground: 0 0% 98%;--primary: 0 0% 98%;--primary-foreground: 0 0% 9%;--secondary: 0 0% 14.9%;--secondary-foreground: 0 0% 98%;--muted: 0 0% 14.9%;--muted-foreground: 0 0% 63.9%;--accent: 0 0% 14.9%;--accent-foreground: 0 0% 98%;--destructive: 0 62.8% 30.6%;--destructive-foreground: 0 0% 98%;--border: 0 0% 14.9%;--input: 0 0% 14.9%;--ring: 0 0% 83.1%;--chart-1: 220 70% 50%;--chart-2: 160 60% 45%;--chart-3: 30 80% 55%;--chart-4: 280 65% 60%;--chart-5: 340 75% 55%}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground))}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.left-2{left:.5rem}.top-0{top:0}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mt-0{margin-top:0}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.aspect-square{aspect-ratio:1 / 1}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[1px\]{height:1px}.h-\[calc\(100vh-12rem\)\]{height:calc(100vh - 12rem)}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-\[var\(--radix-dropdown-menu-content-available-height\)\]{max-height:var(--radix-dropdown-menu-content-available-height)}.min-h-0{min-height:0px}.w-10{width:2.5rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-\[1px\]{width:1px}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-10{min-width:2.5rem}.min-w-11{min-width:2.75rem}.min-w-9{min-width:2.25rem}.min-w-\[8rem\]{min-width:8rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.origin-\[--radix-dropdown-menu-content-transform-origin\]{transform-origin:var(--radix-dropdown-menu-content-transform-origin)}.origin-\[--radix-tooltip-content-transform-origin\]{transform-origin:var(--radix-tooltip-content-transform-origin)}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-\[20rem_minmax\(0\,1fr\)\]{grid-template-columns:20rem minmax(0,1fr)}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0{gap:0px}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.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))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre{white-space:pre}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded-\[inherit\]{border-radius:inherit}.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)}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-input{border-color:hsl(var(--input))}.border-transparent{border-color:transparent}.border-l-transparent{border-left-color:transparent}.border-t-transparent{border-top-color:transparent}.bg-accent\/50{background-color:hsl(var(--accent) / .5)}.bg-background{background-color:hsl(var(--background))}.bg-background\/80{background-color:hsl(var(--background) / .8)}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-muted{background-color:hsl(var(--muted))}.bg-popover{background-color:hsl(var(--popover))}.bg-primary{background-color:hsl(var(--primary))}.bg-secondary{background-color:hsl(var(--secondary))}.bg-transparent{background-color:transparent}.fill-current{fill:currentColor}.p-1{padding:.25rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[1px\]{padding:1px}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pl-8{padding-left:2rem}.pr-2{padding-right:.5rem}.pt-0{padding-top:0}.text-left{text-align:left}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[12px\]{font-size:12px}.text-base{font-size:1rem;line-height:1.5rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.leading-none{line-height:1}.tracking-tight{letter-spacing:-.025em}.tracking-widest{letter-spacing:.1em}.text-card-foreground{color:hsl(var(--card-foreground))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-foreground{color:hsl(var(--foreground))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-popover-foreground{color:hsl(var(--popover-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-sky-700{--tw-text-opacity: 1;color:rgb(3 105 161 / var(--tw-text-opacity, 1))}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.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-offset-background{--tw-ring-offset-color: hsl(var(--background))}.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{--tw-backdrop-blur: blur(8px);-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{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.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}@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-0{--tw-enter-opacity: 0}.zoom-in-95{--tw-enter-scale: .95}.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}.file\:text-foreground::file-selector-button{color:hsl(var(--foreground))}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-accent\/40:hover{background-color:hsl(var(--accent) / .4)}.hover\:bg-destructive\/80:hover{background-color:hsl(var(--destructive) / .8)}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}.hover\:bg-muted:hover{background-color:hsl(var(--muted))}.hover\:bg-primary\/80:hover{background-color:hsl(var(--primary) / .8)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-muted-foreground:hover{color:hsl(var(--muted-foreground))}.hover\:underline:hover{text-decoration-line:underline}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.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-ring:focus{--tw-ring-color: hsl(var(--ring))}.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}.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\:opacity-100{opacity:1}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:hsl(var(--background))}.data-\[state\=on\]\:bg-accent[data-state=on],.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:hsl(var(--accent))}.data-\[state\=active\]\:text-foreground[data-state=active]{color:hsl(var(--foreground))}.data-\[state\=on\]\:text-accent-foreground[data-state=on]{color:hsl(var(--accent-foreground))}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[state\=active\]\:shadow-sm[data-state=active]{--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)}.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-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y: -.5rem}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x: .5rem}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x: -.5rem}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y: .5rem}.dark\:text-sky-300:is(class *){--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity, 1))}@media (min-width: 640px){.sm\:inline{display:inline}.sm\:inline-flex{display:inline-flex}}@media (min-width: 768px){.md\:text-sm{font-size:.875rem;line-height:1.25rem}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-4 svg{width:1rem;height:1rem}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}:root{color-scheme:light dark}html,body,#root{height:100%}
|