github-reporadar 0.1.4 → 0.2.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/dist/index.html CHANGED
@@ -16,8 +16,8 @@
16
16
  href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap"
17
17
  rel="stylesheet"
18
18
  />
19
- <script type="module" crossorigin src="/assets/index-LfkmcOcl.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-B5UAXuco.css">
19
+ <script type="module" crossorigin src="/assets/index-DwMah6Dr.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-CwLJknHY.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
package/dist-cli/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { execFileSync, execFile } from "node:child_process";
2
- import { readFileSync, mkdirSync, writeFileSync, createReadStream, existsSync, statSync } from "node:fs";
2
+ import { readFileSync, mkdirSync, writeFileSync, existsSync, createReadStream, statSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { join, resolve, extname, normalize, sep, dirname } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -44,9 +44,9 @@ function buildRepoStats(repos, activity, days, now) {
44
44
  };
45
45
  });
46
46
  }
47
- function sortRepoStats(stats, key) {
47
+ function sortRepoStats(stats, key2) {
48
48
  const sorted = [...stats];
49
- switch (key) {
49
+ switch (key2) {
50
50
  case "active":
51
51
  sorted.sort((a, b) => b.totalCommits - a.totalCommits);
52
52
  break;
@@ -245,17 +245,54 @@ async function listCommits(token, repo, since, author) {
245
245
  }
246
246
  return commits.filter((c) => c.author?.type !== "Bot");
247
247
  }
248
- function parsePrMeta(prs) {
248
+ function nodeToMeta(n) {
249
+ return {
250
+ author: n.author?.login ?? null,
251
+ reviewers: (n.reviewRequests?.nodes ?? []).map((r) => r?.requestedReviewer?.login).filter((l) => typeof l === "string"),
252
+ reviewDecision: n.reviewDecision ?? null,
253
+ isDraft: n.isDraft,
254
+ // 権限で読めないissueは node が null で来る
255
+ linkedIssues: (n.closingIssuesReferences?.nodes ?? []).filter((x) => x != null).map((x) => x.number),
256
+ reviewCount: n.reviews?.totalCount ?? 0,
257
+ lastReview: (() => {
258
+ const r = (n.reviews?.nodes ?? []).filter((x) => x != null)[0];
259
+ return r ? { author: r.author?.login ?? "", state: r.state, at: r.submittedAt } : null;
260
+ })(),
261
+ lastCommitAt: (n.commits?.nodes ?? []).filter((x) => x != null)[0]?.commit.committedDate ?? null
262
+ };
263
+ }
264
+ const PR_FIELDS = "number isDraft reviewDecision author { login } reviewRequests(first: 10) { nodes { requestedReviewer { ... on User { login } ... on Team { name } } } } closingIssuesReferences(first: 10) { nodes { number } } reviews(last: 1) { totalCount nodes { author { login } state submittedAt } } commits(last: 1) { nodes { commit { committedDate } } }";
265
+ function prMetaQuery(refs) {
266
+ const fields = refs.map((r, j) => {
267
+ const [owner, name] = r.repo.split("/");
268
+ return `p${j}: repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(name)}) { pullRequest(number: ${r.number}) { ${PR_FIELDS} } }`;
269
+ }).join("\n");
270
+ return `query {
271
+ ${fields}
272
+ }`;
273
+ }
274
+ const PR_META_CHUNK = 40;
275
+ async function fetchPrMeta(token, refs, limit) {
249
276
  const out = /* @__PURE__ */ new Map();
250
- for (const n of prs?.nodes ?? []) {
251
- out.set(n.number, {
252
- author: n.author?.login ?? null,
253
- reviewers: n.reviewRequests.nodes.map((r) => r.requestedReviewer?.login).filter((l) => typeof l === "string"),
254
- reviewDecision: n.reviewDecision ?? null,
255
- isDraft: n.isDraft,
256
- linkedIssues: (n.closingIssuesReferences?.nodes ?? []).map((x) => x.number)
257
- });
258
- }
277
+ const chunks = [];
278
+ for (let i = 0; i < refs.length; i += PR_META_CHUNK) chunks.push(refs.slice(i, i + PR_META_CHUNK));
279
+ await Promise.all(
280
+ chunks.map(
281
+ (chunk) => limit(async () => {
282
+ const res = await fetch(`${API}/graphql`, {
283
+ method: "POST",
284
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
285
+ body: JSON.stringify({ query: prMetaQuery(chunk) })
286
+ });
287
+ if (!res.ok) throw new Error(`GitHub GraphQL HTTP ${res.status}: ${await res.text()}`);
288
+ const json = await res.json();
289
+ chunk.forEach((r, j) => {
290
+ const node = json.data?.[`p${j}`]?.pullRequest;
291
+ if (node) out.set(`${r.repo}#${r.number}`, nodeToMeta(node));
292
+ });
293
+ })
294
+ )
295
+ );
259
296
  return out;
260
297
  }
261
298
  async function fetchOpenCounts(token, fullNames, limit) {
@@ -266,7 +303,7 @@ async function fetchOpenCounts(token, fullNames, limit) {
266
303
  await Promise.all(chunks.map((chunk) => limit(async () => {
267
304
  const fields = chunk.map((name, j) => {
268
305
  const [owner, repo] = name.split("/");
269
- return `r${j}: repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(repo)}) { issues(states: OPEN) { totalCount } pullRequests(states: OPEN, first: 50) { totalCount nodes { number isDraft reviewDecision author { login } reviewRequests(first: 10) { nodes { requestedReviewer { ... on User { login } ... on Team { name } } } } closingIssuesReferences(first: 10) { nodes { number } } } } }`;
306
+ return `r${j}: repository(owner: ${JSON.stringify(owner)}, name: ${JSON.stringify(repo)}) { issues(states: OPEN) { totalCount } pullRequests(states: OPEN) { totalCount } }`;
270
307
  }).join("\n");
271
308
  const res = await fetch(`${API}/graphql`, {
272
309
  method: "POST",
@@ -281,8 +318,7 @@ ${fields}
281
318
  const node = json.data?.[`r${j}`];
282
319
  result.set(name, {
283
320
  pr: node?.pullRequests.totalCount ?? 0,
284
- issue: node?.issues.totalCount ?? 0,
285
- prMeta: parsePrMeta(node?.pullRequests)
321
+ issue: node?.issues.totalCount ?? 0
286
322
  });
287
323
  });
288
324
  })));
@@ -330,6 +366,33 @@ async function listOpenIssues(token, repo, login) {
330
366
  }
331
367
  return items;
332
368
  }
369
+ async function fetchIssuesOnly(token, fullNames, opts) {
370
+ const limit = createLimiter(CONCURRENCY);
371
+ const counts = await fetchOpenCounts(token, fullNames, limit);
372
+ const targets = fullNames.filter((n) => {
373
+ const c = counts.get(n);
374
+ return c != null && c.pr + c.issue > 0;
375
+ });
376
+ const lists = await Promise.all(
377
+ targets.map((n) => {
378
+ const [owner] = n.split("/");
379
+ const login = owner.toLowerCase() === opts.ownLogin.toLowerCase() ? null : opts.ownLogin;
380
+ return limit(() => listOpenIssues(token, { full_name: n }, login));
381
+ })
382
+ );
383
+ const flat = lists.flat();
384
+ const prMeta = await fetchPrMeta(
385
+ token,
386
+ flat.filter((it) => it.type === "pr").map((it) => ({ repo: it.repo, number: it.number })),
387
+ limit
388
+ );
389
+ const issues = flat.map((it) => {
390
+ if (it.type !== "pr") return it;
391
+ const meta = prMeta.get(`${it.repo}#${it.number}`);
392
+ return meta ? { ...it, ...meta } : it;
393
+ });
394
+ return { issues, counts };
395
+ }
333
396
  function createProgress(repoTotal, issueTotal) {
334
397
  const enabled = process.stdout.isTTY === true;
335
398
  let repos = 0;
@@ -411,18 +474,74 @@ async function fetchActivity(token, from, opts) {
411
474
  }))
412
475
  });
413
476
  });
414
- const issues = issuesByRepo.flat().map((it) => {
477
+ const flat = issuesByRepo.flat();
478
+ const prMeta = await fetchPrMeta(
479
+ token,
480
+ flat.filter((it) => it.type === "pr").map((it) => ({ repo: it.repo, number: it.number })),
481
+ limit
482
+ );
483
+ const issues = flat.map((it) => {
415
484
  if (it.type !== "pr") return it;
416
- const meta = openCounts.get(it.repo)?.prMeta.get(it.number);
485
+ const meta = prMeta.get(`${it.repo}#${it.number}`);
417
486
  return meta ? { ...it, ...meta } : it;
418
487
  });
419
488
  return { login, repos, activity, issues };
420
489
  }
490
+ const same = (a, b) => a != null && a.toLowerCase() === b.toLowerCase();
491
+ const key = (i) => `${i.repo}#${i.number}`;
492
+ function diffEvents(prev, next, login, now, opts = {}) {
493
+ if (!login || prev === null) return [];
494
+ const before = new Map(prev.filter((i) => i.type === "pr").map((i) => [key(i), i]));
495
+ const knownRepo = (repo) => !opts.prevRepos || opts.prevRepos.has(repo);
496
+ const repoStillHere = (repo) => !opts.nextRepos || opts.nextRepos.has(repo);
497
+ const out = [];
498
+ const base = (p) => ({ at: now, repo: p.repo, number: p.number, title: p.title, url: p.url });
499
+ for (const p of next) {
500
+ if (p.type !== "pr") continue;
501
+ const b = before.get(key(p));
502
+ if (!b && !knownRepo(p.repo)) continue;
503
+ const mine = same(p.author, login);
504
+ const wasReviewer = (b?.reviewers ?? []).some((r) => same(r, login));
505
+ const isReviewer = (p.reviewers ?? []).some((r) => same(r, login));
506
+ if (!mine && isReviewer && !wasReviewer) out.push({ id: `${key(p)}:requested`, kind: "review_requested", ...base(p), actor: p.author ?? void 0 });
507
+ if (!mine) continue;
508
+ const prevCount = b?.reviewCount ?? 0;
509
+ const count = p.reviewCount ?? 0;
510
+ const last = p.lastReview;
511
+ if (count > prevCount && last && !same(last.author, login)) {
512
+ const kind = last.state === "APPROVED" ? "approved" : last.state === "CHANGES_REQUESTED" ? "changes_requested" : "reviewed";
513
+ out.push({ id: `${key(p)}:review:${count}`, kind, ...base(p), actor: last.author });
514
+ }
515
+ if (p.reviewDecision === "CHANGES_REQUESTED" && p.lastCommitAt && b?.lastCommitAt && p.lastCommitAt > b.lastCommitAt && last && p.lastCommitAt > last.at) {
516
+ out.push({ id: `${key(p)}:addressed:${p.lastCommitAt}`, kind: "addressed", ...base(p) });
517
+ }
518
+ }
519
+ const nextKeys = new Set(next.filter((i) => i.type === "pr").map(key));
520
+ for (const [k, b] of before) {
521
+ if (same(b.author, login) && !nextKeys.has(k) && repoStillHere(b.repo)) out.push({ id: `${k}:closed`, kind: "closed", ...base(b) });
522
+ }
523
+ return out;
524
+ }
525
+ function mergeEvents(existing, fresh, max = 200) {
526
+ const seen = new Set(existing.map((e) => e.id));
527
+ const added = fresh.filter((e) => !seen.has(e.id));
528
+ return [...added, ...existing].sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0).slice(0, max);
529
+ }
421
530
  const REPO_CACHE_DIR = join(import.meta.dirname, "..", "public", "data");
422
531
  const DEFAULT_WINDOW_DAYS = 90;
423
532
  async function runSync(opts) {
424
533
  const windowDays = opts.days ?? DEFAULT_WINDOW_DAYS;
425
534
  const log = opts.log ?? ((line) => console.log(line));
535
+ const readJson = (name) => {
536
+ const path = join(opts.dataDir, name);
537
+ if (!existsSync(path)) return null;
538
+ try {
539
+ return JSON.parse(readFileSync(path, "utf8"));
540
+ } catch {
541
+ return null;
542
+ }
543
+ };
544
+ if (opts.mode === "light") return runLightSync(opts, log, readJson);
426
545
  if (!Number.isFinite(windowDays) || windowDays < 1 || windowDays > 365) {
427
546
  throw new Error(`--days は 1〜365 で指定してください: ${windowDays}`);
428
547
  }
@@ -434,6 +553,15 @@ async function runSync(opts) {
434
553
  if (orgs.length > 0) log(`組織も取得: ${orgs.join(", ")}(自分がauthorのコミットだけ数える)`);
435
554
  const { login, repos, activity, issues } = await fetchActivity(token, from, { includePrivate: true, orgs });
436
555
  mkdirSync(opts.dataDir, { recursive: true });
556
+ const prevIssues = readJson("issues.json");
557
+ const prevRepos = readJson("repos.json");
558
+ const fresh = diffEvents(prevIssues, issues, login, to.toISOString(), {
559
+ prevRepos: prevRepos ? new Set(prevRepos.map((r) => r.nameWithOwner)) : void 0,
560
+ nextRepos: new Set(repos.map((r) => r.nameWithOwner))
561
+ });
562
+ const events = mergeEvents(readJson("events.json") ?? [], fresh);
563
+ writeFileSync(join(opts.dataDir, "events.json"), JSON.stringify(events, null, 2));
564
+ if (fresh.length > 0) log(`受信箱に${fresh.length}件`);
437
565
  const meta = { lastSyncAt: to.toISOString(), login, windowDays, includesPrivate: true, ...orgs.length > 0 ? { orgs } : {} };
438
566
  writeFileSync(join(opts.dataDir, "repos.json"), JSON.stringify(repos, null, 2));
439
567
  writeFileSync(join(opts.dataDir, "activity.json"), JSON.stringify(activity, null, 2));
@@ -441,7 +569,35 @@ async function runSync(opts) {
441
569
  writeFileSync(join(opts.dataDir, "meta.json"), JSON.stringify(meta, null, 2));
442
570
  const totalCommits = activity.reduce((sum, a) => sum + a.commits, 0);
443
571
  log(`OK: ${repos.length} repos / ${totalCommits} commits / ${issues.length} open issues+PRs → ${opts.dataDir}`);
444
- return { login, repos: repos.length, commits: totalCommits, issues: issues.length };
572
+ return { login, repos: repos.length, commits: totalCommits, issues: issues.length, events: fresh.length };
573
+ }
574
+ async function runLightSync(opts, log, readJson) {
575
+ const prevRepos = readJson("repos.json");
576
+ if (!prevRepos || prevRepos.length === 0) {
577
+ log("前回の同期が無いので完全同期します");
578
+ return runSync({ ...opts, mode: "full" });
579
+ }
580
+ const token = getToken();
581
+ const login = readJson("meta.json")?.login;
582
+ if (!login) return runSync({ ...opts, mode: "full" });
583
+ const names = prevRepos.map((r) => r.nameWithOwner);
584
+ const { issues, counts } = await fetchIssuesOnly(token, names, { ownLogin: login });
585
+ const to = /* @__PURE__ */ new Date();
586
+ const prevIssues = readJson("issues.json");
587
+ const repoSet = new Set(names);
588
+ const fresh = diffEvents(prevIssues, issues, login, to.toISOString(), { prevRepos: repoSet, nextRepos: repoSet });
589
+ const events = mergeEvents(readJson("events.json") ?? [], fresh);
590
+ const repos = prevRepos.map((r) => {
591
+ const c = counts.get(r.nameWithOwner);
592
+ return c ? { ...r, openPrCount: c.pr, openIssueCount: c.issue } : r;
593
+ });
594
+ const meta = readJson("meta.json");
595
+ writeFileSync(join(opts.dataDir, "issues.json"), JSON.stringify(issues, null, 2));
596
+ writeFileSync(join(opts.dataDir, "events.json"), JSON.stringify(events, null, 2));
597
+ writeFileSync(join(opts.dataDir, "repos.json"), JSON.stringify(repos, null, 2));
598
+ if (meta) writeFileSync(join(opts.dataDir, "meta.json"), JSON.stringify({ ...meta, lastSyncAt: to.toISOString() }, null, 2));
599
+ log(`更新: open ${issues.length}件${fresh.length > 0 ? ` / 受信箱に${fresh.length}件` : ""}`);
600
+ return { login, repos: repos.length, commits: 0, issues: issues.length, events: fresh.length };
445
601
  }
446
602
  const isMain = process.argv[1]?.endsWith("sync.ts") || process.argv[1]?.endsWith("sync.js");
447
603
  if (isMain) {
@@ -465,7 +621,8 @@ const DEFAULTS = {
465
621
  includePrivate: false,
466
622
  sort: "active",
467
623
  limit: 15,
468
- orgs: []
624
+ orgs: [],
625
+ watchSeconds: null
469
626
  };
470
627
  function intIn(flag, raw, min, max) {
471
628
  if (raw === void 0 || raw.startsWith("--")) throw new RangeError(`${flag} には値が要ります`);
@@ -509,6 +666,15 @@ function parseArgs(argv) {
509
666
  out.dataDir = v;
510
667
  break;
511
668
  }
669
+ case "--watch": {
670
+ const v = argv[++i];
671
+ const m = v && !v.startsWith("--") ? /^(\d+)(s|m|h)$/.exec(v) : null;
672
+ if (!m) throw new RangeError(`--watchは 5m / 90s / 1h のように指定してください(受け取った値: ${v ?? "無し"})`);
673
+ const sec = Number(m[1]) * (m[2] === "h" ? 3600 : m[2] === "m" ? 60 : 1);
674
+ if (sec < 30) throw new RangeError(`--watchは30秒以上にしてください(GitHubのレート制限を守る)。受け取った値: ${v}`);
675
+ out.watchSeconds = sec;
676
+ break;
677
+ }
512
678
  case "--org": {
513
679
  const v = argv[++i];
514
680
  if (v === void 0 || v.startsWith("--")) throw new RangeError(`${a}には値が要ります`);
@@ -550,6 +716,7 @@ const USAGE = [
550
716
  row("--port N", "待ち受けポート(既定5177。使用中なら次を探す)"),
551
717
  row("--no-open", "ブラウザを開かない"),
552
718
  row("--no-sync", "同期せずに手元のデータで開く"),
719
+ row("--watch 1m", "開いたまま、この間隔でopen issue/PRを取り直す(30秒以上)。受信箱に自分に起きたことが溜まる"),
553
720
  row("--org NAME", "組織のrepoも取得する(繰り返し可)。組織では自分がauthorのコミットと、自分が作ったか担当のissue/PRだけ数える"),
554
721
  row("--data-dir DIR", "データの置き場(既定: $XDG_CACHE_HOME か ~/.cache の github-reporadar/data)"),
555
722
  row("--sort KEY", "brief の並び: active | recent | stale"),
@@ -565,7 +732,7 @@ function defaultDataDir(env, home) {
565
732
  return join(base, "github-reporadar", "data");
566
733
  }
567
734
  const HOST = "127.0.0.1";
568
- const DATA_FILES = /* @__PURE__ */ new Set(["repos.json", "activity.json", "issues.json", "meta.json"]);
735
+ const DATA_FILES = /* @__PURE__ */ new Set(["repos.json", "activity.json", "issues.json", "meta.json", "events.json"]);
569
736
  const PORT_TRIES = 20;
570
737
  const TYPES = {
571
738
  ".html": "text/html; charset=utf-8",
@@ -706,7 +873,33 @@ async function main() {
706
873
  console.log(`GitHub RepoRadar: ${url} (データ: ${dataDir})`);
707
874
  console.log("止めるにはCtrl+C");
708
875
  if (args.open) openBrowser(url);
876
+ let timer = null;
877
+ if (args.watchSeconds) {
878
+ const FULL_EVERY_MS = 30 * 60 * 1e3;
879
+ console.log(`${args.watchSeconds}秒ごとにopen issue/PRを取り直します(コミットは30分ごと)`);
880
+ if (args.watchSeconds < 60) {
881
+ console.log("60秒未満はGitHubの制限に対する余裕が少なくなります(1時間あたり上限の約9割)");
882
+ }
883
+ let busy = false;
884
+ let lastFull = Date.now();
885
+ timer = setInterval(() => {
886
+ if (busy) return;
887
+ busy = true;
888
+ const full = Date.now() - lastFull >= FULL_EVERY_MS;
889
+ if (full) lastFull = Date.now();
890
+ runSync({
891
+ dataDir,
892
+ days: args.days,
893
+ orgs: args.orgs,
894
+ mode: full ? "full" : "light",
895
+ log: (line) => console.log(`[watch] ${line}`)
896
+ }).catch((err) => console.error(`[watch] 同期に失敗: ${err instanceof Error ? err.message : String(err)}`)).finally(() => {
897
+ busy = false;
898
+ });
899
+ }, args.watchSeconds * 1e3);
900
+ }
709
901
  const stop = () => {
902
+ if (timer) clearInterval(timer);
710
903
  server.close().then(() => process.exit(0));
711
904
  };
712
905
  process.on("SIGINT", stop);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-reporadar",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "自分のGitHub活動を一晩ぶん眺める計器盤。npx github-reporadarでローカルに開く",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -1 +0,0 @@
1
- /*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial}}}@layer theme{:root,:host{--font-sans:"IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;--font-mono:"IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-900:oklch(37.8% .077 168.94);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-500:oklch(68.5% .169 237.323);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-500:oklch(60.6% .25 292.717);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-slate-300:oklch(86.9% .022 252.894);--color-slate-400:oklch(70.4% .04 256.788);--color-slate-500:oklch(55.4% .046 257.417);--color-slate-600:oklch(44.6% .043 257.281);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-xl:36rem;--container-2xl:42rem;--container-4xl:56rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height: 1.2 ;--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-ink:#0a0c10;--color-panel:#13171e;--color-panel-2:#0d1015;--color-line:#262d3a;--color-line-strong:#5d6779;--color-fg:#f2f4f8;--color-fg-muted:#a3acbd;--color-fg-faint:#858e9f}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}:root{color-scheme:dark}html,body,#root{height:100%}body{font-family:var(--font-sans);background-color:var(--color-ink);color:var(--color-fg);-webkit-font-smoothing:antialiased}body:before{content:"";pointer-events:none;background:radial-gradient(58% 42% at 80% -6%,#10b98112,#0000 70%);position:fixed;top:0;right:0;bottom:0;left:0}}@layer components;@layer utilities{.visible{visibility:visible}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.top-0{top:0}.z-10{z-index:10}.z-50{z-index:50}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-6{height:calc(var(--spacing) * 6)}.h-full{height:100%}.max-h-64{max-height:calc(var(--spacing) * 64)}.min-h-16{min-height:calc(var(--spacing) * 16)}.min-h-screen{min-height:100vh}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-6{width:calc(var(--spacing) * 6)}.w-11{width:calc(var(--spacing) * 11)}.w-14{width:calc(var(--spacing) * 14)}.w-48{width:calc(var(--spacing) * 48)}.w-64{width:calc(var(--spacing) * 64)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-\[Nch\]{max-width:Nch}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:0}.min-w-44{min-width:calc(var(--spacing) * 44)}.min-w-\[220px\]{min-width:220px}.min-w-\[240px\]{min-width:240px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.rotate-180{rotate:180deg}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-grab{cursor:grab}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.resize-none{resize:none}.grid-cols-\[auto_minmax\(0\,1fr\)_auto\]{grid-template-columns:auto minmax(0,1fr) auto}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.self-end{align-self:flex-end}.self-start{align-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-l-lg{border-top-left-radius:var(--radius-lg);border-bottom-left-radius:var(--radius-lg)}.rounded-r-lg{border-top-right-radius:var(--radius-lg);border-bottom-right-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-\[\#\.\.\]{border-color:#..}.border-amber-500\/25{border-color:#f99c0040}@supports (color:color-mix(in lab,red,red)){.border-amber-500\/25{border-color:color-mix(in oklab,var(--color-amber-500) 25%,transparent)}}.border-amber-500\/40{border-color:#f99c0066}@supports (color:color-mix(in lab,red,red)){.border-amber-500\/40{border-color:color-mix(in oklab,var(--color-amber-500) 40%,transparent)}}.border-emerald-500\/30{border-color:#00bb7f4d}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/30{border-color:color-mix(in oklab,var(--color-emerald-500) 30%,transparent)}}.border-emerald-500\/40{border-color:#00bb7f66}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/40{border-color:color-mix(in oklab,var(--color-emerald-500) 40%,transparent)}}.border-emerald-500\/50{border-color:#00bb7f80}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/50{border-color:color-mix(in oklab,var(--color-emerald-500) 50%,transparent)}}.border-emerald-500\/60{border-color:#00bb7f99}@supports (color:color-mix(in lab,red,red)){.border-emerald-500\/60{border-color:color-mix(in oklab,var(--color-emerald-500) 60%,transparent)}}.border-line{border-color:var(--color-line)}.border-line-strong{border-color:var(--color-line-strong)}.border-line\/60{border-color:#262d3a99}@supports (color:color-mix(in lab,red,red)){.border-line\/60{border-color:color-mix(in oklab,var(--color-line) 60%,transparent)}}.border-rose-500\/40{border-color:#ff235766}@supports (color:color-mix(in lab,red,red)){.border-rose-500\/40{border-color:color-mix(in oklab,var(--color-rose-500) 40%,transparent)}}.bg-\[\#\.\.\]{background-color:#..}.bg-amber-400{background-color:var(--color-amber-400)}.bg-amber-500\/10{background-color:#f99c001a}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/10{background-color:color-mix(in oklab,var(--color-amber-500) 10%,transparent)}}.bg-amber-500\/\[0\.07\]{background-color:#f99c0012}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/\[0\.07\]{background-color:color-mix(in oklab,var(--color-amber-500) 7%,transparent)}}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-emerald-400\/10{background-color:#00d2941a}@supports (color:color-mix(in lab,red,red)){.bg-emerald-400\/10{background-color:color-mix(in oklab,var(--color-emerald-400) 10%,transparent)}}.bg-emerald-400\/\[0\.04\]{background-color:#00d2940a}@supports (color:color-mix(in lab,red,red)){.bg-emerald-400\/\[0\.04\]{background-color:color-mix(in oklab,var(--color-emerald-400) 4%,transparent)}}.bg-emerald-400\/\[0\.06\]{background-color:#00d2940f}@supports (color:color-mix(in lab,red,red)){.bg-emerald-400\/\[0\.06\]{background-color:color-mix(in oklab,var(--color-emerald-400) 6%,transparent)}}.bg-emerald-500\/10{background-color:#00bb7f1a}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/10{background-color:color-mix(in oklab,var(--color-emerald-500) 10%,transparent)}}.bg-ink{background-color:var(--color-ink)}.bg-ink\/80{background-color:#0a0c10cc}@supports (color:color-mix(in lab,red,red)){.bg-ink\/80{background-color:color-mix(in oklab,var(--color-ink) 80%,transparent)}}.bg-ink\/85{background-color:#0a0c10d9}@supports (color:color-mix(in lab,red,red)){.bg-ink\/85{background-color:color-mix(in oklab,var(--color-ink) 85%,transparent)}}.bg-panel{background-color:var(--color-panel)}.bg-panel-2{background-color:var(--color-panel-2)}.bg-panel-2\/40{background-color:#0d101566}@supports (color:color-mix(in lab,red,red)){.bg-panel-2\/40{background-color:color-mix(in oklab,var(--color-panel-2) 40%,transparent)}}.bg-panel-2\/50{background-color:#0d101580}@supports (color:color-mix(in lab,red,red)){.bg-panel-2\/50{background-color:color-mix(in oklab,var(--color-panel-2) 50%,transparent)}}.bg-panel-2\/60{background-color:#0d101599}@supports (color:color-mix(in lab,red,red)){.bg-panel-2\/60{background-color:color-mix(in oklab,var(--color-panel-2) 60%,transparent)}}.bg-panel\/85{background-color:#13171ed9}@supports (color:color-mix(in lab,red,red)){.bg-panel\/85{background-color:color-mix(in oklab,var(--color-panel) 85%,transparent)}}.bg-rose-400{background-color:var(--color-rose-400)}.bg-rose-500\/10{background-color:#ff23571a}@supports (color:color-mix(in lab,red,red)){.bg-rose-500\/10{background-color:color-mix(in oklab,var(--color-rose-500) 10%,transparent)}}.bg-sky-500\/15{background-color:#00a5ef26}@supports (color:color-mix(in lab,red,red)){.bg-sky-500\/15{background-color:color-mix(in oklab,var(--color-sky-500) 15%,transparent)}}.bg-slate-300{background-color:var(--color-slate-300)}.bg-slate-400{background-color:var(--color-slate-400)}.bg-slate-500{background-color:var(--color-slate-500)}.bg-violet-500\/15{background-color:#8d54ff26}@supports (color:color-mix(in lab,red,red)){.bg-violet-500\/15{background-color:color-mix(in oklab,var(--color-violet-500) 15%,transparent)}}.bg-white\/5{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.bg-white\/5{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.fill-\[\#\.\.\]{fill:#..}.fill-\[\#161b22\]{fill:#161b22}.fill-emerald-300{fill:var(--color-emerald-300)}.fill-emerald-400{fill:var(--color-emerald-400)}.fill-emerald-400\/12{fill:#00d2941f}@supports (color:color-mix(in lab,red,red)){.fill-emerald-400\/12{fill:color-mix(in oklab,var(--color-emerald-400) 12%,transparent)}}.fill-emerald-400\/85{fill:#00d294d9}@supports (color:color-mix(in lab,red,red)){.fill-emerald-400\/85{fill:color-mix(in oklab,var(--color-emerald-400) 85%,transparent)}}.fill-emerald-500{fill:var(--color-emerald-500)}.fill-emerald-700{fill:var(--color-emerald-700)}.fill-emerald-900{fill:var(--color-emerald-900)}.fill-fg-faint{fill:var(--color-fg-faint)}.fill-fg-muted{fill:var(--color-fg-muted)}.fill-line{fill:var(--color-line)}.fill-panel{fill:var(--color-panel)}.stroke-emerald-400{stroke:var(--color-emerald-400)}.stroke-emerald-500\/20{stroke:#00bb7f33}@supports (color:color-mix(in lab,red,red)){.stroke-emerald-500\/20{stroke:color-mix(in oklab,var(--color-emerald-500) 20%,transparent)}}.stroke-emerald-500\/25{stroke:#00bb7f40}@supports (color:color-mix(in lab,red,red)){.stroke-emerald-500\/25{stroke:color-mix(in oklab,var(--color-emerald-500) 25%,transparent)}}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2{padding:calc(var(--spacing) * 2)}.p-2\.5{padding:calc(var(--spacing) * 2.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-0\.5{padding-inline:calc(var(--spacing) * .5)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-0\.5{padding-top:calc(var(--spacing) * .5)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#\.\.\]{color:#..}.text-\[\#…\]{color:#…}.text-\[Npx\]{color:Npx}.text-amber-200{color:var(--color-amber-200)}.text-amber-200\/70{color:#fee685b3}@supports (color:color-mix(in lab,red,red)){.text-amber-200\/70{color:color-mix(in oklab,var(--color-amber-200) 70%,transparent)}}.text-amber-300{color:var(--color-amber-300)}.text-amber-400{color:var(--color-amber-400)}.text-emerald-300{color:var(--color-emerald-300)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-400\/70{color:#00d294b3}@supports (color:color-mix(in lab,red,red)){.text-emerald-400\/70{color:color-mix(in oklab,var(--color-emerald-400) 70%,transparent)}}.text-fg{color:var(--color-fg)}.text-fg-faint{color:var(--color-fg-faint)}.text-fg-muted{color:var(--color-fg-muted)}.text-rose-300{color:var(--color-rose-300)}.text-rose-400{color:var(--color-rose-400)}.text-sky-300{color:var(--color-sky-300)}.text-slate-300{color:var(--color-slate-300)}.text-slate-400{color:var(--color-slate-400)}.text-slate-500{color:var(--color-slate-500)}.text-slate-600{color:var(--color-slate-600)}.text-violet-300{color:var(--color-violet-300)}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline-offset-4{text-underline-offset:4px}.accent-emerald-500{accent-color:var(--color-emerald-500)}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a), 0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.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,)}.backdrop-filter{-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-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}@media(hover:hover){.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.placeholder\:text-fg-faint::placeholder{color:var(--color-fg-faint)}@media(hover:hover){.hover\:border-emerald-500\/40:hover{border-color:#00bb7f66}@supports (color:color-mix(in lab,red,red)){.hover\:border-emerald-500\/40:hover{border-color:color-mix(in oklab,var(--color-emerald-500) 40%,transparent)}}.hover\:border-rose-500\/40:hover{border-color:#ff235766}@supports (color:color-mix(in lab,red,red)){.hover\:border-rose-500\/40:hover{border-color:color-mix(in oklab,var(--color-rose-500) 40%,transparent)}}.hover\:bg-white\/\[0\.06\]:hover{background-color:#ffffff0f}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/\[0\.06\]:hover{background-color:color-mix(in oklab,var(--color-white) 6%,transparent)}}.hover\:bg-white\/\[0\.025\]:hover{background-color:#ffffff06}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/\[0\.025\]:hover{background-color:color-mix(in oklab,var(--color-white) 2.5%,transparent)}}.hover\:text-emerald-400:hover{color:var(--color-emerald-400)}.hover\:text-fg:hover{color:var(--color-fg)}.hover\:text-fg-muted:hover{color:var(--color-fg-muted)}.hover\:text-rose-400:hover{color:var(--color-rose-400)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}}.focus\:border-emerald-500\/50:focus{border-color:#00bb7f80}@supports (color:color-mix(in lab,red,red)){.focus\:border-emerald-500\/50:focus{border-color:color-mix(in oklab,var(--color-emerald-500) 50%,transparent)}}.focus\:bg-white\/\[0\.06\]:focus{background-color:#ffffff0f}@supports (color:color-mix(in lab,red,red)){.focus\:bg-white\/\[0\.06\]:focus{background-color:color-mix(in oklab,var(--color-white) 6%,transparent)}}.focus\:text-fg:focus{color:var(--color-fg)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:opacity-40:disabled{opacity:.4}@media(hover:hover){.disabled\:hover\:text-fg-muted:disabled:hover{color:var(--color-fg-muted)}}@media(min-width:40rem){.sm\:block{display:block}.sm\:flex{display:flex}.sm\:gap-4{gap:calc(var(--spacing) * 4)}}@media(min-width:64rem){.lg\:block{display:block}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:hidden{display:none}.lg\:h-full{height:100%}.lg\:h-screen{height:100vh}.lg\:min-h-0{min-height:0}.lg\:w-auto{width:auto}.lg\:flex-none{flex:none}.lg\:grid-cols-\[minmax\(0\,1fr\)_380px\]{grid-template-columns:minmax(0,1fr) 380px}.lg\:flex-nowrap{flex-wrap:nowrap}.lg\:justify-between{justify-content:space-between}.lg\:overflow-hidden{overflow:hidden}.lg\:overflow-y-auto{overflow-y:auto}.lg\:border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.lg\:border-line{border-color:var(--color-line)}.lg\:px-5{padding-inline:calc(var(--spacing) * 5)}.lg\:py-1{padding-block:var(--spacing)}.lg\:pl-3{padding-left:calc(var(--spacing) * 3)}}}@keyframes rr-fade-up{0%{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}.rr-fade-up{animation:.45s cubic-bezier(.2,.7,.2,1) both rr-fade-up}@keyframes rr-spin{to{transform:rotate(360deg)}}.rr-sweep{transform-box:view-box;transform-origin:12px 12px;animation:3.4s linear infinite rr-spin}@media(prefers-reduced-motion:reduce){.rr-fade-up,.rr-sweep{animation:none}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}