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 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.2.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": { "url": "https://github.com/zonayedpca/statikapi/issues" },
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": { "statikapi": "bin/statikapi.js" },
13
- "files": ["bin", "dist", "ui", "src", "README.md", "LICENSE"],
14
- "exports": { ".": { "import": "./src/index.js" } },
15
- "engines": { "node": ">=18" },
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": ["static", "json", "api", "cli", "ssg"],
18
- "publishConfig": { "access": "public", "provenance": true },
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
- "build": "node ./scripts/build.js || true",
26
- "sync-ui": "rm -rf ui && mkdir -p ui && cp -R ../ui/dist/* ui/",
27
- "prepack": "pnpm -w --filter @statikapi/ui build && pnpm run sync-ui"
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
  }
@@ -99,7 +99,6 @@ export default async function buildCmd(argv) {
99
99
  bytes: Buffer.byteLength(json),
100
100
  mtime: st ? st.mtimeMs : Date.now(),
101
101
  hash: digest(json),
102
- revalidate: null,
103
102
  };
104
103
  manifest.push(entry);
105
104
  }
@@ -55,14 +55,20 @@ function toParams(segTokens, concreteRoute) {
55
55
  }
56
56
 
57
57
  export default async function devCmd(argv) {
58
- // In non-TTY (like node --test), behave like the old stub so tests don't hang.
59
- if (!process.stdout.isTTY) {
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 ext === '.js' || ext === '.mjs' || ext === '.cjs';
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
- const u = new URL(pathToFileURL(fileAbs).href);
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
  }
@@ -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
- const u = new URL(pathToFileURL(fileAbs).href);
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
  }
@@ -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,2 @@
1
+ This folder is generated during publish by packages/cli/scripts/embed-ui.js
2
+ and contains the prebuilt StatikAPI UI that the CLI serves at /_ui.
@@ -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%}