statikapi 0.2.0 → 0.4.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/README.md +0 -2
- package/package.json +40 -12
- package/src/commands/build.js +0 -1
- package/src/commands/dev.js +43 -5
- package/src/help.js +1 -5
- 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-BevI1ZS7.js +142 -0
- package/ui/assets/index-BevI1ZS7.js.map +1 -0
- package/ui/index.html +15 -2
- package/src/commands/init.js +0 -5
- package/src/commands/preview.js +0 -7
- 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/README.md
CHANGED
|
@@ -23,10 +23,8 @@ pnpm add -D statikapi
|
|
|
23
23
|
statikapi <command> [options]
|
|
24
24
|
|
|
25
25
|
Commands:
|
|
26
|
-
init Scaffold a new StatikAPI project
|
|
27
26
|
build Build static JSON endpoints
|
|
28
27
|
dev Watch & rebuild on changes
|
|
29
|
-
preview Serve the built JSON files + UI
|
|
30
28
|
|
|
31
29
|
Global:
|
|
32
30
|
-h, --help Show help
|
package/package.json
CHANGED
|
@@ -1,29 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "statikapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/zonayedpca/statikapi",
|
|
7
7
|
"directory": "packages/cli"
|
|
8
8
|
},
|
|
9
|
-
"bugs": {
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/zonayedpca/statikapi/issues"
|
|
11
|
+
},
|
|
10
12
|
"homepage": "https://github.com/zonayedpca/statikapi#readme",
|
|
11
13
|
"type": "module",
|
|
12
|
-
"bin": {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
14
|
+
"bin": {
|
|
15
|
+
"statikapi": "bin/statikapi.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin",
|
|
19
|
+
"dist",
|
|
20
|
+
"ui",
|
|
21
|
+
"src",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": "./src/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18"
|
|
32
|
+
},
|
|
16
33
|
"license": "MIT",
|
|
17
|
-
"keywords": [
|
|
18
|
-
|
|
34
|
+
"keywords": [
|
|
35
|
+
"static",
|
|
36
|
+
"json",
|
|
37
|
+
"api",
|
|
38
|
+
"cli",
|
|
39
|
+
"ssg"
|
|
40
|
+
],
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"provenance": true
|
|
44
|
+
},
|
|
19
45
|
"dependencies": {
|
|
20
46
|
"chokidar": "^3.6.0",
|
|
21
47
|
"sirv": "^2.0.4",
|
|
22
|
-
"polka": "^0.5.2"
|
|
48
|
+
"polka": "^0.5.2",
|
|
49
|
+
"esbuild": "^0.23.0"
|
|
23
50
|
},
|
|
24
51
|
"scripts": {
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"prepack": "
|
|
52
|
+
"dev": "node bin/statikapi.js dev --port 8788",
|
|
53
|
+
"build": "node bin/statikapi.js build",
|
|
54
|
+
"prepack": "node scripts/embed-ui.js",
|
|
55
|
+
"test": "node --test"
|
|
28
56
|
}
|
|
29
57
|
}
|
package/src/commands/build.js
CHANGED
package/src/commands/dev.js
CHANGED
|
@@ -55,14 +55,20 @@ function toParams(segTokens, concreteRoute) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export default async function devCmd(argv) {
|
|
58
|
-
|
|
59
|
-
|
|
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) {
|
|
60
67
|
console.log('statikapi dev → starting dev server (stub)');
|
|
61
68
|
|
|
62
69
|
return 0;
|
|
63
70
|
}
|
|
64
71
|
|
|
65
|
-
const flags = readFlags(argv);
|
|
66
72
|
const { config } = await loadConfig({ flags });
|
|
67
73
|
|
|
68
74
|
// Where to notify preview
|
|
@@ -128,7 +134,6 @@ export default async function devCmd(argv) {
|
|
|
128
134
|
bytes: Buffer.byteLength(json),
|
|
129
135
|
mtime: st ? st.mtimeMs : Date.now(),
|
|
130
136
|
hash: digest(json),
|
|
131
|
-
revalidate: null,
|
|
132
137
|
};
|
|
133
138
|
|
|
134
139
|
manifestByRoute.set(route, entry);
|
|
@@ -204,7 +209,7 @@ export default async function devCmd(argv) {
|
|
|
204
209
|
if (rel.startsWith('_')) return false;
|
|
205
210
|
const ext = path.extname(rel);
|
|
206
211
|
|
|
207
|
-
return
|
|
212
|
+
return ['.js', '.mjs', '.cjs', '.ts', '.tsx'].includes(ext);
|
|
208
213
|
}
|
|
209
214
|
|
|
210
215
|
async function buildOne(fileAbs, kind) {
|
|
@@ -362,6 +367,39 @@ export default async function devCmd(argv) {
|
|
|
362
367
|
return;
|
|
363
368
|
}
|
|
364
369
|
|
|
370
|
+
// 6) Serve built JSON directly from api-out
|
|
371
|
+
{
|
|
372
|
+
const outRoot = config.paths.outAbs;
|
|
373
|
+
// strip leading slash and normalize
|
|
374
|
+
const rel = pathname.replace(/^\/+/, '');
|
|
375
|
+
const candidates = [];
|
|
376
|
+
|
|
377
|
+
// If the request ends with .json, try that file directly
|
|
378
|
+
if (rel.endsWith('.json')) {
|
|
379
|
+
candidates.push(path.join(outRoot, rel));
|
|
380
|
+
} else {
|
|
381
|
+
// Otherwise, try a folder with index.json (e.g. "/" or "/users/1/")
|
|
382
|
+
candidates.push(path.join(outRoot, rel, 'index.json'));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
for (const cand of candidates) {
|
|
386
|
+
const file = path.resolve(cand);
|
|
387
|
+
// prevent path traversal
|
|
388
|
+
if (!file.startsWith(path.resolve(outRoot))) continue;
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const st = await fs.stat(file);
|
|
392
|
+
if (st.isFile()) {
|
|
393
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
394
|
+
fss.createReadStream(file).pipe(res);
|
|
395
|
+
return; // served
|
|
396
|
+
}
|
|
397
|
+
} catch {
|
|
398
|
+
// try next candidate
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
365
403
|
// Otherwise: 404
|
|
366
404
|
res.statusCode = 404;
|
|
367
405
|
res.end('Not Found');
|
package/src/help.js
CHANGED
|
@@ -3,19 +3,15 @@ export const HELP = `statikapi — Static API generator
|
|
|
3
3
|
Usage:
|
|
4
4
|
statikapi <command> [options]
|
|
5
5
|
|
|
6
|
-
Commands:
|
|
7
|
-
init Scaffold a new StatikAPI project
|
|
6
|
+
Commands:Scaffold a new StatikAPI project
|
|
8
7
|
build Build static JSON endpoints
|
|
9
8
|
dev Start dev mode (watch & rebuild)
|
|
10
|
-
preview Serve the built JSON files
|
|
11
9
|
|
|
12
10
|
Global options:
|
|
13
11
|
-h, --help Show help
|
|
14
12
|
-v, --version Show version
|
|
15
13
|
|
|
16
14
|
Examples:
|
|
17
|
-
statikapi init
|
|
18
15
|
statikapi build
|
|
19
16
|
statikapi dev
|
|
20
|
-
statikapi preview
|
|
21
17
|
`;
|
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%}
|