myapikey 0.34.6 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/packages/core/src/cli/index.ts +63 -0
- package/packages/core/src/server/admin.ts +52 -5
- package/packages/core/src/server/proxy.ts +56 -3
- package/packages/core/src/server/store.ts +86 -1
- package/packages/core/src/shared/types.ts +43 -0
- package/packages/web/dist/assets/index-BhE0o2aD.css +1 -0
- package/packages/web/dist/assets/index-DBb7JCKv.js +334 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/assets/index-Cl2nRbtQ.js +0 -324
- package/packages/web/dist/assets/index-Dk7PZDM5.css +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myapikey",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Personal LLM API gateway & proxy — one address + one API key for all your models. Forwards OpenAI & Anthropic calls to your backends with failover and a circuit breaker. Pure passthrough, no format translation. Self-hosted (CLI + web UI).",
|
|
6
6
|
"keywords": [
|
|
@@ -283,6 +283,69 @@ model.command("remove <name>").description("remove a model entirely (both format
|
|
|
283
283
|
console.log(`Removed ${name}.`);
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
+
model
|
|
287
|
+
.command("debug <name> [action] [index]")
|
|
288
|
+
.description("debug capture: on|off toggles recording of the last 50 actual requests/responses (failed attempts are ALWAYS recorded in a global net, last 50); show lists everything (add an index to dump one in full)")
|
|
289
|
+
.action(async (name: string, action = "show", indexRaw?: string) => {
|
|
290
|
+
const path = `/admin/models/${encodeURIComponent(name)}/debug`;
|
|
291
|
+
if (action === "on" || action === "off") {
|
|
292
|
+
const r = (await api(ctx(), "PUT", path, { enabled: action === "on" })) as { enabled: boolean };
|
|
293
|
+
console.log(
|
|
294
|
+
r.enabled
|
|
295
|
+
? `Debug capture ON for ${name} — the last 50 upstream attempts (request + response) are recorded in memory; turn off to clear.`
|
|
296
|
+
: `Debug capture OFF for ${name}; captured content cleared (auto-recorded failures stay until they age out of the net).`,
|
|
297
|
+
);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (action !== "show") throw new Error(`Unknown action '${action}' (use on | off | show).`);
|
|
301
|
+
const r = (await api(ctx(), "GET", path)) as { enabled: boolean; captures: any[]; failures: any[] };
|
|
302
|
+
if (!r.enabled) console.log(`Debug capture is OFF for ${name} (failed calls are still auto-recorded).`);
|
|
303
|
+
const isFail = (c: any) => c.status >= 400 || c.status === 0;
|
|
304
|
+
// One newest-first timeline; a failure made while the switch was on sits
|
|
305
|
+
// in both server buffers — show it once, from the net, tagged AUTO.
|
|
306
|
+
const rows = [
|
|
307
|
+
...(r.failures ?? []).map((c) => ({ c, auto: true })),
|
|
308
|
+
...(r.captures ?? []).filter((c) => !isFail(c)).map((c) => ({ c, auto: false })),
|
|
309
|
+
].sort((a, b) => b.c.ts - a.c.ts);
|
|
310
|
+
if (indexRaw !== undefined) {
|
|
311
|
+
const row = rows[Number(indexRaw) - 1];
|
|
312
|
+
if (!row) throw new Error(`No capture #${indexRaw} (list is newest-first, ${rows.length} recorded).`);
|
|
313
|
+
const c = row.c;
|
|
314
|
+
console.log(`#${Number(indexRaw)}${row.auto ? " (auto)" : ""} ${new Date(c.ts).toLocaleTimeString("en-GB", { hour12: false })} ${c.provider} [${c.format}] status=${c.status} ${c.ms}ms${c.upstreamModel ? ` model=${c.upstreamModel}` : ""}${c.error ? ` error=${c.error}` : ""}${c.truncated ? " (truncated)" : ""}`);
|
|
315
|
+
console.log("\n--- request (forwarded verbatim) ---");
|
|
316
|
+
console.log(prettyJson(c.request));
|
|
317
|
+
if (c.response !== undefined) {
|
|
318
|
+
console.log("\n--- response (upstream) ---");
|
|
319
|
+
console.log(prettyJson(c.response));
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (!rows.length) return console.log("Nothing recorded yet — call the model, then `show` again (or pass an index to dump one).");
|
|
324
|
+
console.log(`${name}: ${rows.length} recorded (newest first; AUTO = failed attempt, always recorded)`);
|
|
325
|
+
for (let i = 0; i < rows.length; i++) {
|
|
326
|
+
const { c, auto } = rows[i];
|
|
327
|
+
console.log(
|
|
328
|
+
` #${i + 1}${auto ? " AUTO" : " "} ${new Date(c.ts).toLocaleTimeString("en-GB", { hour12: false })} ${c.provider} [${c.format}] status=${c.status} ${c.ms}ms ${fmtBytes(c.request)} req / ${fmtBytes(c.response ?? "")} resp${c.upstreamModel ? ` model=${c.upstreamModel}` : ""}${c.error ? ` ${c.error}` : ""}`,
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
console.log("\nDump one: myapikey model debug <name> show <index>");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
/** Pretty-print when the text parses as JSON (a forwarded request body, a JSON
|
|
335
|
+
* response); raw text (e.g. an SSE stream) goes through untouched. */
|
|
336
|
+
function prettyJson(s: string): string {
|
|
337
|
+
try {
|
|
338
|
+
return JSON.stringify(JSON.parse(s), null, 2);
|
|
339
|
+
} catch {
|
|
340
|
+
return s;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function fmtBytes(s: string): string {
|
|
345
|
+
const n = Buffer.byteLength(s, "utf8");
|
|
346
|
+
return n >= 1024 ? `${(n / 1024).toFixed(1)}KB` : `${n}B`;
|
|
347
|
+
}
|
|
348
|
+
|
|
286
349
|
// ---------------------------------------------------------------------------
|
|
287
350
|
// call
|
|
288
351
|
// ---------------------------------------------------------------------------
|
|
@@ -163,6 +163,7 @@ function projectModel(name: string, e: ModelEntry, byId: Map<string, Provider>)
|
|
|
163
163
|
anthropic: proj(e.anthropic),
|
|
164
164
|
responses: proj(e.responses),
|
|
165
165
|
paceRpm: e.paceRpm ?? 0,
|
|
166
|
+
debugCapture: e.debugCapture === true,
|
|
166
167
|
};
|
|
167
168
|
}
|
|
168
169
|
|
|
@@ -566,6 +567,7 @@ export function adminApi(store: Store, auth: MiddlewareHandler, openai: Hono, an
|
|
|
566
567
|
const cfg = store.get();
|
|
567
568
|
if (!cfg.models[name]) return c.json({ error: { message: "model not found" } }, 404);
|
|
568
569
|
if (cfg.models[next]) return c.json({ error: { message: `model already exists: ${next}` } }, 409);
|
|
570
|
+
store.clearCaptures(name); // captured bodies keyed by the old name are unreachable now
|
|
569
571
|
await store.update((d) => {
|
|
570
572
|
const rebuilt: typeof d.models = {};
|
|
571
573
|
for (const [k, v] of Object.entries(d.models)) rebuilt[k === name ? next : k] = v;
|
|
@@ -692,12 +694,56 @@ export function adminApi(store: Store, auth: MiddlewareHandler, openai: Hono, an
|
|
|
692
694
|
errStatus = 404;
|
|
693
695
|
return;
|
|
694
696
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
697
|
+
if (rpm) entry.paceRpm = rpm;
|
|
698
|
+
else delete entry.paceRpm;
|
|
699
|
+
});
|
|
700
|
+
if (errStatus === 404) return c.json({ error: { message: "model not found" } }, 404);
|
|
701
|
+
return c.json({ ok: true, paceRpm: rpm ?? 0 });
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
// Debug capture switch (pure debugging aid — see ModelEntry.debugCapture).
|
|
705
|
+
// ON: dispatch records every upstream attempt for this model (exact forwarded
|
|
706
|
+
// request + response bodies) into an in-memory ring buffer, last 50. OFF: the
|
|
707
|
+
// buffer is cleared immediately — captured conversations shouldn't outlive the
|
|
708
|
+
// debugging session. Independently, FAILED attempts always land in a global
|
|
709
|
+
// in-memory net (last 50 across all models) so an error can be inspected even
|
|
710
|
+
// when the switch was never on; `failures` here is that net filtered to this
|
|
711
|
+
// model. The switch is config state (persists in data.json); the captured
|
|
712
|
+
// bodies do not (in-memory only, restart clears them too).
|
|
713
|
+
app.get("/models/:name/debug", (c) => {
|
|
714
|
+
const name = c.req.param("name");
|
|
715
|
+
if (!store.get().models[name]) return c.json({ error: { message: "model not found" } }, 404);
|
|
716
|
+
return c.json({ enabled: store.isDebug(name), captures: store.getCaptures(name), failures: store.getFailCaptures(name) });
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
app.put("/models/:name/debug", async (c) => {
|
|
720
|
+
const name = c.req.param("name");
|
|
721
|
+
const body = await readJson<{ enabled?: unknown }>(c.req.raw);
|
|
722
|
+
const enabled = body?.enabled === true;
|
|
723
|
+
let errStatus = 0;
|
|
724
|
+
await store.update((d) => {
|
|
725
|
+
const entry = d.models[name];
|
|
726
|
+
if (!entry) {
|
|
727
|
+
errStatus = 404;
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (enabled) entry.debugCapture = true;
|
|
731
|
+
else delete entry.debugCapture;
|
|
700
732
|
});
|
|
733
|
+
if (errStatus === 404) return c.json({ error: { message: "model not found" } }, 404);
|
|
734
|
+
if (!enabled) store.clearCaptures(name);
|
|
735
|
+
return c.json({ ok: true, enabled });
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// Manual scrub of this model's rows in the always-on failure net (the switch
|
|
739
|
+
// only governs the per-model buffer; failures age out of the global ring on
|
|
740
|
+
// their own — this drops them now).
|
|
741
|
+
app.delete("/models/:name/debug/fails", (c) => {
|
|
742
|
+
const name = c.req.param("name");
|
|
743
|
+
if (!store.get().models[name]) return c.json({ error: { message: "model not found" } }, 404);
|
|
744
|
+
store.clearFailCaptures(name);
|
|
745
|
+
return c.json({ ok: true });
|
|
746
|
+
});
|
|
701
747
|
|
|
702
748
|
// Set (or clear) the upstream-model mapping for ONE chain slot (addressed by
|
|
703
749
|
// `index`). An empty `model` clears it (back to identity — send the public
|
|
@@ -904,6 +950,7 @@ export function adminApi(store: Store, auth: MiddlewareHandler, openai: Hono, an
|
|
|
904
950
|
await store.update((d) => {
|
|
905
951
|
delete d.models[name];
|
|
906
952
|
});
|
|
953
|
+
store.clearCaptures(name);
|
|
907
954
|
return c.json({ ok: true });
|
|
908
955
|
});
|
|
909
956
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Hono, type Context, type MiddlewareHandler } from "hono";
|
|
2
2
|
import { trimBase } from "../shared/config";
|
|
3
|
-
import type { Format, Provider, RouteKey, Usage } from "../shared/types";
|
|
4
|
-
import type
|
|
3
|
+
import type { DebugCapture, Format, Provider, RouteKey, Usage } from "../shared/types";
|
|
4
|
+
import { CAPTURE_BODY_MAX, type Store } from "./store";
|
|
5
5
|
import { UsageCollector } from "./tokens";
|
|
6
6
|
|
|
7
7
|
/** HTTP statuses that should trigger failover to the next provider. 401/403
|
|
@@ -342,6 +342,10 @@ function observedBody(
|
|
|
342
342
|
/** The original request's `messages`, used only to estimate prompt tokens
|
|
343
343
|
* on the openai-chat-stream fallback path (see tokens.ts). */
|
|
344
344
|
requestMessages?: unknown;
|
|
345
|
+
/** Called with each decoded text chunk AS IT FLOWS — the debug capture's
|
|
346
|
+
* tee point (the proxy keeps forwarding bytes verbatim regardless).
|
|
347
|
+
* Optional: absent = zero capture overhead. */
|
|
348
|
+
onText?: (txt: string) => void;
|
|
345
349
|
onSettle: (info: SettleInfo) => void;
|
|
346
350
|
},
|
|
347
351
|
): ReadableStream<Uint8Array> {
|
|
@@ -385,6 +389,7 @@ function observedBody(
|
|
|
385
389
|
}
|
|
386
390
|
const txt = dec.decode(value, { stream: true });
|
|
387
391
|
usage.feed(txt, { stream: opts.stream, key: opts.key });
|
|
392
|
+
opts.onText?.(txt);
|
|
388
393
|
if (!terminal && markers.length) {
|
|
389
394
|
const win = tail + txt;
|
|
390
395
|
if (markers.some((m) => win.includes(m))) terminal = true;
|
|
@@ -608,15 +613,47 @@ export function proxyApi(
|
|
|
608
613
|
// Count this attempt toward the source's RPM window — but not for a pinned
|
|
609
614
|
// probe, which (like circuit state) takes no routing side-effects.
|
|
610
615
|
if (pinIndex == null) store.recordDispatch(provider.id);
|
|
616
|
+
// Debug capture: EVERY upstream attempt is offered to the store —
|
|
617
|
+
// failed attempts always land in the global failure net (the safety
|
|
618
|
+
// net for after-the-fact debugging), everything lands in the model's
|
|
619
|
+
// own buffer while its switch is on. The row carries the exact
|
|
620
|
+
// forwarded body (this slot's model rewrite + thinking injection are
|
|
621
|
+
// already applied) and the response as it flowed. UI probes are
|
|
622
|
+
// excluded (not real conversations). One entry per attempt: a
|
|
623
|
+
// failover chain writes several, each showing what THAT source
|
|
624
|
+
// actually received.
|
|
625
|
+
const attemptStart = Date.now();
|
|
626
|
+
const reqText = JSON.stringify(body);
|
|
627
|
+
const capture = (status: number, response: string | undefined, truncated: boolean, error?: string) => {
|
|
628
|
+
if (isProbe) return;
|
|
629
|
+
const row: DebugCapture = {
|
|
630
|
+
ts: Date.now(),
|
|
631
|
+
model,
|
|
632
|
+
provider: provider.name,
|
|
633
|
+
providerId: provider.id,
|
|
634
|
+
format: wire,
|
|
635
|
+
status,
|
|
636
|
+
ms: Date.now() - attemptStart,
|
|
637
|
+
stream,
|
|
638
|
+
request: reqText,
|
|
639
|
+
...(upstreamModel ? { upstreamModel } : {}),
|
|
640
|
+
...(think ? { thinking: think } : {}),
|
|
641
|
+
...(response ? { response } : {}),
|
|
642
|
+
...(truncated ? { truncated: true } : {}),
|
|
643
|
+
...(error ? { error } : {}),
|
|
644
|
+
};
|
|
645
|
+
store.pushCapture(model, row);
|
|
646
|
+
};
|
|
611
647
|
let upstream: Response;
|
|
612
648
|
try {
|
|
613
649
|
upstream = await fetch(upstreamTarget(provider, key).url, {
|
|
614
650
|
method: "POST",
|
|
615
651
|
headers: upstreamHeaders(provider, wire, clientVersion),
|
|
616
|
-
body:
|
|
652
|
+
body: reqText,
|
|
617
653
|
});
|
|
618
654
|
} catch {
|
|
619
655
|
// Network error / DNS / timeout → try next provider.
|
|
656
|
+
capture(0, undefined, false, "network error");
|
|
620
657
|
lastStatus = 502;
|
|
621
658
|
lastErr = "network error";
|
|
622
659
|
if (pinIndex != null) break; // per-source probe: fail fast, no circuit impact.
|
|
@@ -642,20 +679,34 @@ export function proxyApi(
|
|
|
642
679
|
// the body's end (so the row reflects the real outcome, not just the
|
|
643
680
|
// headers). See observedBody() for the detection rules.
|
|
644
681
|
const ttfb = Date.now() - start;
|
|
682
|
+
// Debug capture tee: accumulate the decoded chunks into a bounded
|
|
683
|
+
// string (the proxy keeps forwarding bytes verbatim regardless).
|
|
684
|
+
const capAcc = { text: "", truncated: false };
|
|
645
685
|
const out = observedBody(upstream, {
|
|
646
686
|
stream,
|
|
647
687
|
key,
|
|
648
688
|
requestMessages: body.messages,
|
|
689
|
+
onText: (txt: string) => {
|
|
690
|
+
const room = CAPTURE_BODY_MAX - capAcc.text.length;
|
|
691
|
+
if (room <= 0) {
|
|
692
|
+
capAcc.truncated = true;
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (txt.length > room) capAcc.truncated = true;
|
|
696
|
+
capAcc.text += txt.slice(0, room);
|
|
697
|
+
},
|
|
649
698
|
onSettle: (info) => {
|
|
650
699
|
if (info.ok) {
|
|
651
700
|
store.recordCircuitSuccess(provider.id);
|
|
652
701
|
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: 200, ms: ttfb, stream, thinking: think, usage: info.usage });
|
|
702
|
+
capture(200, capAcc.text, capAcc.truncated);
|
|
653
703
|
} else {
|
|
654
704
|
// A pinned per-source probe takes no circuit side-effects (a manual
|
|
655
705
|
// test must not trip the breaker) — mirrors the retryable branch.
|
|
656
706
|
if (pinIndex == null) store.recordCircuitFailure(provider.id, info.status, info.error || "stream failed");
|
|
657
707
|
if (!isProbe) rt.warn(`proxy stream failed: provider '${provider.name}' status=${info.status} (${info.error || "stream failed"})`);
|
|
658
708
|
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: info.status, ms: ttfb, stream, thinking: think, error: info.error });
|
|
709
|
+
capture(info.status, capAcc.text, capAcc.truncated, info.error);
|
|
659
710
|
}
|
|
660
711
|
},
|
|
661
712
|
});
|
|
@@ -667,6 +718,7 @@ export function proxyApi(
|
|
|
667
718
|
// reason for the log (this branch never streams back to the client).
|
|
668
719
|
const txt = await upstream.text().catch(() => "");
|
|
669
720
|
lastErr = shortError(txt) || `HTTP ${upstream.status}`;
|
|
721
|
+
capture(upstream.status, txt, false, lastErr);
|
|
670
722
|
if (pinIndex != null) break; // per-source probe: fail fast, no circuit impact.
|
|
671
723
|
sayFailover(provider, `HTTP ${lastStatus} (${lastErr})`);
|
|
672
724
|
// A 429/overloaded upstream usually carries Retry-After; honoring it
|
|
@@ -687,6 +739,7 @@ export function proxyApi(
|
|
|
687
739
|
// error text off a CLONE so the original body still streams back.
|
|
688
740
|
const errText = await upstream.clone().text().catch(() => "");
|
|
689
741
|
store.pushLog({ ts: Date.now(), model, upstreamModel, provider: provider.name, providerId: provider.id, format: wire, status: upstream.status, ms: Date.now() - start, stream, thinking: think, error: shortError(errText) || `HTTP ${upstream.status}` });
|
|
742
|
+
capture(upstream.status, errText, false, shortError(errText) || `HTTP ${upstream.status}`);
|
|
690
743
|
return passThrough(upstream, isProbe ? provider.name : undefined);
|
|
691
744
|
}
|
|
692
745
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { defaultConfig, newApiKey, CONFIG_VERSION } from "../shared/config";
|
|
4
|
-
import type { GateConfig, LogEntry, Provider } from "../shared/types";
|
|
4
|
+
import type { DebugCapture, GateConfig, LogEntry, Provider } from "../shared/types";
|
|
5
5
|
import { createLogger, type Logger } from "./logger";
|
|
6
6
|
|
|
7
7
|
/** Call-log retention: the log is bounded two ways — never older than this, and
|
|
@@ -45,6 +45,20 @@ const RPM_WINDOW_MS = 60_000;
|
|
|
45
45
|
* time and the queue depth (at N rpm / 60s wait, at most ~N requests queue). */
|
|
46
46
|
const PACE_MAX_WAIT_MS = 60_000;
|
|
47
47
|
|
|
48
|
+
/** Debug capture (in-memory ring buffers, never persisted). Two tiers:
|
|
49
|
+
* - FAIL_NET (global): the last CAPTURE_FAIL_MAX FAILED upstream attempts
|
|
50
|
+
* across ALL models, recorded ALWAYS (that's the safety net — errors are
|
|
51
|
+
* debugged after the fact, when nobody pre-armed a switch). 50 × 2 × 256KB
|
|
52
|
+
* ≈ 25MB worst case, typical far less (failed responses are tiny).
|
|
53
|
+
* - per-model switch buffer: last CAPTURE_MAX attempts of a model whose
|
|
54
|
+
* ModelEntry.debugCapture is on — success AND failure.
|
|
55
|
+
* Bodies (conversations) are sensitive: everything lives only in memory,
|
|
56
|
+
* never touches disk, ages out of the rings, and the switch-off / clear
|
|
57
|
+
* endpoints drop it on demand. */
|
|
58
|
+
export const CAPTURE_MAX = 50;
|
|
59
|
+
export const CAPTURE_FAIL_MAX = 50;
|
|
60
|
+
export const CAPTURE_BODY_MAX = 256 * 1024;
|
|
61
|
+
|
|
48
62
|
/** Per-provider circuit state (in-memory, never persisted). */
|
|
49
63
|
interface CircuitEntry {
|
|
50
64
|
fails: number;
|
|
@@ -191,6 +205,15 @@ export class Store {
|
|
|
191
205
|
/** Per-model even-pacing queue: model name -> epoch ms of the next free
|
|
192
206
|
* release slot. In-memory, NOT persisted (resets on restart). */
|
|
193
207
|
private pace = new Map<string, number>();
|
|
208
|
+
/** Per-model debug-capture ring buffers: model name -> last CAPTURE_MAX
|
|
209
|
+
* upstream attempts (oldest first) while the model's switch is on.
|
|
210
|
+
* In-memory, NOT persisted — bodies are conversation content, so they live
|
|
211
|
+
* only while the switch is on and evaporate on restart or toggle-off. */
|
|
212
|
+
private captures = new Map<string, DebugCapture[]>();
|
|
213
|
+
/** The always-on failure net: last CAPTURE_FAIL_MAX FAILED upstream attempts
|
|
214
|
+
* across all models (oldest first), regardless of any switch. In-memory,
|
|
215
|
+
* NOT persisted; entries age out as new failures push them off. */
|
|
216
|
+
private failCaptures: DebugCapture[] = [];
|
|
194
217
|
|
|
195
218
|
constructor(dataDir: string, opts: { logger?: Logger } = {}) {
|
|
196
219
|
this.dataDir = dataDir;
|
|
@@ -649,6 +672,68 @@ export class Store {
|
|
|
649
672
|
return wait;
|
|
650
673
|
}
|
|
651
674
|
|
|
675
|
+
// --- debug capture (per model, in-memory ring buffer; never persisted) ---
|
|
676
|
+
|
|
677
|
+
/** Whether a model's debug capture switch is on (config state, persisted). */
|
|
678
|
+
isDebug(model: string): boolean {
|
|
679
|
+
return !!this.data.models[model]?.debugCapture;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** Record one upstream attempt. Routing: FAILED attempts (4xx/5xx/network)
|
|
683
|
+
* always land in the global failure net (the safety net for after-the-fact
|
|
684
|
+
* debugging); EVERYTHING lands in the model's own buffer while its switch
|
|
685
|
+
* is on. When the switch is off, successes are dropped. Bodies are capped
|
|
686
|
+
* at CAPTURE_BODY_MAX chars each (flagging `truncated`). */
|
|
687
|
+
pushCapture(model: string, entry: DebugCapture): void {
|
|
688
|
+
const e = this.capped({ ...entry, model }); // row's model = the routing key
|
|
689
|
+
const failed = e.status >= 400 || e.status === 0;
|
|
690
|
+
if (failed) {
|
|
691
|
+
this.failCaptures.push(e);
|
|
692
|
+
if (this.failCaptures.length > CAPTURE_FAIL_MAX) this.failCaptures.splice(0, this.failCaptures.length - CAPTURE_FAIL_MAX);
|
|
693
|
+
}
|
|
694
|
+
if (!this.isDebug(model)) return;
|
|
695
|
+
let arr = this.captures.get(model);
|
|
696
|
+
if (!arr) this.captures.set(model, (arr = []));
|
|
697
|
+
arr.push(e);
|
|
698
|
+
if (arr.length > CAPTURE_MAX) arr.splice(0, arr.length - CAPTURE_MAX);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/** Copy of an entry with both bodies clamped to CAPTURE_BODY_MAX. */
|
|
702
|
+
private capped(entry: DebugCapture): DebugCapture {
|
|
703
|
+
const e = { ...entry };
|
|
704
|
+
if (e.request.length > CAPTURE_BODY_MAX) {
|
|
705
|
+
e.request = e.request.slice(0, CAPTURE_BODY_MAX);
|
|
706
|
+
e.truncated = true;
|
|
707
|
+
}
|
|
708
|
+
if (e.response !== undefined && e.response.length > CAPTURE_BODY_MAX) {
|
|
709
|
+
e.response = e.response.slice(0, CAPTURE_BODY_MAX);
|
|
710
|
+
e.truncated = true;
|
|
711
|
+
}
|
|
712
|
+
return e;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/** The model's switch-captured attempts, newest first. */
|
|
716
|
+
getCaptures(model: string): DebugCapture[] {
|
|
717
|
+
const arr = this.captures.get(model);
|
|
718
|
+
return arr ? [...arr].reverse() : [];
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/** The model's always-recorded failed attempts (from the global net),
|
|
722
|
+
* newest first. */
|
|
723
|
+
getFailCaptures(model: string): DebugCapture[] {
|
|
724
|
+
return this.failCaptures.filter((c) => c.model === model).reverse();
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** Drop a model's switch-capture buffer (toggle-off, model delete/rename). */
|
|
728
|
+
clearCaptures(model: string): void {
|
|
729
|
+
this.captures.delete(model);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/** Scrub a model's entries from the global failure net (manual clear). */
|
|
733
|
+
clearFailCaptures(model: string): void {
|
|
734
|
+
this.failCaptures = this.failCaptures.filter((c) => c.model !== model);
|
|
735
|
+
}
|
|
736
|
+
|
|
652
737
|
/** Snapshot of every configured provider's circuit state for GET /admin/circuit.
|
|
653
738
|
* Healthy providers appear as state "open"; a provider deleted while cooling
|
|
654
739
|
* simply drops out (we iterate the live config, not the map). */
|
|
@@ -100,6 +100,14 @@ export interface ModelEntry {
|
|
|
100
100
|
* independent of Provider.rpm (that one skips a busy source and fails over);
|
|
101
101
|
* this one paces the model across all three route slots. Absent = unlimited. */
|
|
102
102
|
paceRpm?: number;
|
|
103
|
+
/** Debug capture (pure debugging aid, never on by default). When true, dispatch
|
|
104
|
+
* records every UPSTREAM ATTEMPT for this model — the exact forwarded request
|
|
105
|
+
* body and the response body — into an in-memory ring buffer (last 50, see
|
|
106
|
+
* Store.pushCapture). Turning it off clears the buffer; restarts clear it too.
|
|
107
|
+
* Independently of this switch, FAILED attempts always land in a global
|
|
108
|
+
* in-memory net (last 50 across all models) so an error can be inspected
|
|
109
|
+
* after the fact. Nothing here ever touches logs.jsonl. */
|
|
110
|
+
debugCapture?: boolean;
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
export interface Account {
|
|
@@ -181,3 +189,38 @@ export interface LogEntry {
|
|
|
181
189
|
cooldownMs?: number;
|
|
182
190
|
fails?: number;
|
|
183
191
|
}
|
|
192
|
+
|
|
193
|
+
/** One debug-captured upstream attempt (in-memory ring buffer ONLY — never
|
|
194
|
+
* persisted to logs.jsonl or data.json; see ModelEntry.debugCapture). One
|
|
195
|
+
* client call that fails over produces several entries, one per attempt,
|
|
196
|
+
* because each attempt's forwarded body can differ (per-slot model rewrite +
|
|
197
|
+
* thinking injection). Bodies are captured VERBATIM: `request` is the exact
|
|
198
|
+
* JSON string sent upstream, `response` the upstream body as it flowed (raw
|
|
199
|
+
* SSE text for streams). Headers are never captured — provider api keys stay
|
|
200
|
+
* out of the buffer. */
|
|
201
|
+
export interface DebugCapture {
|
|
202
|
+
ts: number;
|
|
203
|
+
model: string;
|
|
204
|
+
provider: string;
|
|
205
|
+
providerId: string;
|
|
206
|
+
format: Format;
|
|
207
|
+
/** The upstream model name actually sent this attempt (post per-slot
|
|
208
|
+
* rewrite). Absent when the public name went through verbatim. */
|
|
209
|
+
upstreamModel?: string;
|
|
210
|
+
/** Upstream HTTP status (0 = network error / never reached). */
|
|
211
|
+
status: number;
|
|
212
|
+
/** Latency of THIS attempt (ms). */
|
|
213
|
+
ms: number;
|
|
214
|
+
stream: boolean;
|
|
215
|
+
/** The exact forwarded request body (JSON text). */
|
|
216
|
+
request: string;
|
|
217
|
+
/** The upstream response body as it flowed. Absent when nothing was read
|
|
218
|
+
* (network error) or the client cancelled before the body flowed. */
|
|
219
|
+
response?: string;
|
|
220
|
+
/** True when a body was cut at the capture size cap. */
|
|
221
|
+
truncated?: boolean;
|
|
222
|
+
/** Short failure reason (mirrors LogEntry.error). */
|
|
223
|
+
error?: string;
|
|
224
|
+
/** The thinking level this attempt ran with (same shape as LogEntry.thinking). */
|
|
225
|
+
thinking?: { value: string; from: "client" | "default" };
|
|
226
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@keyframes row-flash-35cb6f16{0%{background-color:color-mix(in oklab,var(--primary) 16%,transparent)}to{background-color:transparent}}.row-flash[data-v-35cb6f16]{animation:row-flash-35cb6f16 1.4s ease-out}/*! tailwindcss v4.3.3 | 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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-space-y-reverse:0;--tw-divide-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-outline-style:solid;--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;--tw-duration:initial;--tw-ease:initial;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-500:oklch(63.7% .237 25.331);--color-orange-400:oklch(75% .183 55.934);--color-orange-500:oklch(70.5% .213 47.604);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-700:oklch(55.3% .195 38.402);--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-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--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-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-teal-400:oklch(77.7% .152 181.912);--color-teal-500:oklch(70.4% .14 182.503);--color-teal-600:oklch(60% .118 184.704);--color-cyan-400:oklch(78.9% .154 211.53);--color-cyan-500:oklch(71.5% .143 215.221);--color-cyan-600:oklch(60.9% .126 221.723);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-600:oklch(58.8% .158 241.966);--color-blue-500:oklch(62.3% .214 259.815);--color-indigo-400:oklch(67.3% .182 276.935);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-violet-300:oklch(81.1% .111 293.571);--color-violet-400:oklch(70.2% .183 293.541);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-600:oklch(54.1% .281 293.009);--color-violet-700:oklch(49.1% .27 292.581);--color-fuchsia-400:oklch(74% .238 322.16);--color-fuchsia-500:oklch(66.7% .295 322.15);--color-fuchsia-600:oklch(59.1% .293 322.896);--color-pink-400:oklch(71.8% .202 349.761);--color-pink-500:oklch(65.6% .241 354.308);--color-pink-600:oklch(59.2% .249 .584);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-3xl:48rem;--container-5xl:64rem;--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-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--leading-tight:1.25;--leading-relaxed:1.625;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--animate-spin:spin 1s linear infinite;--animate-ping:ping 1s cubic-bezier(0, 0, .2, 1) infinite;--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)}}@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,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, 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:where(:not(iframe)){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}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-feature-settings:"rlig" 1,"calt" 1}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.inset-x-0{inset-inline:0}.top-0{top:0}.top-1{top:var(--spacing)}.top-1\/2{top:50%}.top-4{top:calc(var(--spacing) * 4)}.top-\[50\%\]{top:50%}.right-1{right:var(--spacing)}.right-2{right:calc(var(--spacing) * 2)}.right-4{right:calc(var(--spacing) * 4)}.bottom-0{bottom:0}.bottom-full{bottom:100%}.left-1\/2{left:50%}.left-2{left:calc(var(--spacing) * 2)}.left-2\.5{left:calc(var(--spacing) * 2.5)}.left-\[50\%\]{left:50%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.z-\[60\]{z-index:60}.-mx-1{margin-inline:calc(var(--spacing) * -1)}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.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-4{margin-top:calc(var(--spacing) * 4)}.-mr-1{margin-right:calc(var(--spacing) * -1)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.size-3{width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-11{height:calc(var(--spacing) * 11)}.h-14{height:calc(var(--spacing) * 14)}.h-40{height:calc(var(--spacing) * 40)}.h-full{height:100%}.h-px{height:1px}.max-h-56{max-height:calc(var(--spacing) * 56)}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-80{max-height:calc(var(--spacing) * 80)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[90vh\]{max-height:90vh}.min-h-0{min-height:0}.min-h-screen{min-height:100vh}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-1\/3{width:33.3333%}.w-2{width:calc(var(--spacing) * 2)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-11{width:calc(var(--spacing) * 11)}.w-12{width:calc(var(--spacing) * 12)}.w-28{width:calc(var(--spacing) * 28)}.w-32{width:calc(var(--spacing) * 32)}.w-36{width:calc(var(--spacing) * 36)}.w-72{width:calc(var(--spacing) * 72)}.w-\[128px\]{width:128px}.w-\[168px\]{width:168px}.w-auto{width:auto}.w-full{width:100%}.w-px{width:1px}.max-w-3xl{max-width:var(--container-3xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-40{max-width:calc(var(--spacing) * 40)}.max-w-\[120px\]{max-width:120px}.max-w-\[160px\]{max-width:160px}.max-w-\[300px\]{max-width:300px}.max-w-full{max-width:100%}.max-w-lg{max-width:var(--container-lg)}.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-4{min-width:calc(var(--spacing) * 4)}.min-w-\[8rem\]{min-width:8rem}.min-w-\[180px\]{min-width:180px}.min-w-\[var\(--reka-select-trigger-width\)\]{min-width:var(--reka-select-trigger-width)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.caption-bottom{caption-side:bottom}.-translate-x-1\/2{--tw-translate-x: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-x-\[-50\%\]{--tw-translate-x:-50%;translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-2{--tw-translate-y:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.translate-y-\[-50\%\]{--tw-translate-y:-50%;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-180{rotate:180deg}.animate-ping{animation:var(--animate-ping)}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize{resize:both}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-\[3\.5rem_1fr\]{grid-template-columns:3.5rem 1fr}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-row{flex-direction:row}.flex-wrap{flex-wrap:wrap}.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}.justify-end{justify-content:flex-end}.gap-0{gap:0}.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-px{gap:1px}:where(.space-y-0>:not(:last-child)){--tw-space-y-reverse:0;margin-block:0}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}: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)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-1\.5{column-gap:calc(var(--spacing) * 1.5)}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:var(--spacing)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[4px\]{border-radius:4px}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:calc(var(--radius) + 4px)}.rounded-t-sm{border-top-left-radius:calc(var(--radius) - 4px);border-top-right-radius:calc(var(--radius) - 4px)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.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-dashed{--tw-border-style:dashed;border-style:dashed}.border-amber-500\/30{border-color:#f99c004d}@supports (color:color-mix(in lab,red,red)){.border-amber-500\/30{border-color:color-mix(in oklab,var(--color-amber-500) 30%,transparent)}}.border-border{border-color:hsl(var(--border))}.border-destructive\/40{border-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.border-destructive\/40{border-color:color-mix(in oklab,hsl(var(--destructive)) 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-input{border-color:hsl(var(--input))}.border-orange-500\/30{border-color:#fe6e004d}@supports (color:color-mix(in lab,red,red)){.border-orange-500\/30{border-color:color-mix(in oklab,var(--color-orange-500) 30%,transparent)}}.border-primary,.border-primary\/40{border-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.border-primary\/40{border-color:color-mix(in oklab,hsl(var(--primary)) 40%,transparent)}}.border-transparent{border-color:#0000}.border-violet-500\/30{border-color:#8d54ff4d}@supports (color:color-mix(in lab,red,red)){.border-violet-500\/30{border-color:color-mix(in oklab,var(--color-violet-500) 30%,transparent)}}.bg-accent{background-color:hsl(var(--accent))}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-500\/5{background-color:#f99c000d}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/5{background-color:color-mix(in oklab,var(--color-amber-500) 5%,transparent)}}.bg-amber-500\/15{background-color:#f99c0026}@supports (color:color-mix(in lab,red,red)){.bg-amber-500\/15{background-color:color-mix(in oklab,var(--color-amber-500) 15%,transparent)}}.bg-background,.bg-background\/50{background-color:hsl(var(--background))}@supports (color:color-mix(in lab,red,red)){.bg-background\/50{background-color:color-mix(in oklab,hsl(var(--background)) 50%,transparent)}}.bg-background\/60{background-color:hsl(var(--background))}@supports (color:color-mix(in lab,red,red)){.bg-background\/60{background-color:color-mix(in oklab,hsl(var(--background)) 60%,transparent)}}.bg-background\/80{background-color:hsl(var(--background))}@supports (color:color-mix(in lab,red,red)){.bg-background\/80{background-color:color-mix(in oklab,hsl(var(--background)) 80%,transparent)}}.bg-black\/80{background-color:#000c}@supports (color:color-mix(in lab,red,red)){.bg-black\/80{background-color:color-mix(in oklab,var(--color-black) 80%,transparent)}}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-current{background-color:currentColor}.bg-cyan-500{background-color:var(--color-cyan-500)}.bg-cyan-500\/15{background-color:#00b7d726}@supports (color:color-mix(in lab,red,red)){.bg-cyan-500\/15{background-color:color-mix(in oklab,var(--color-cyan-500) 15%,transparent)}}.bg-destructive,.bg-destructive\/10{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/10{background-color:color-mix(in oklab,hsl(var(--destructive)) 10%,transparent)}}.bg-destructive\/15{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/15{background-color:color-mix(in oklab,hsl(var(--destructive)) 15%,transparent)}}.bg-destructive\/70{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.bg-destructive\/70{background-color:color-mix(in oklab,hsl(var(--destructive)) 70%,transparent)}}.bg-emerald-500{background-color:var(--color-emerald-500)}.bg-emerald-500\/5{background-color:#00bb7f0d}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/5{background-color:color-mix(in oklab,var(--color-emerald-500) 5%,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-emerald-500\/15{background-color:#00bb7f26}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/15{background-color:color-mix(in oklab,var(--color-emerald-500) 15%,transparent)}}.bg-emerald-500\/70{background-color:#00bb7fb3}@supports (color:color-mix(in lab,red,red)){.bg-emerald-500\/70{background-color:color-mix(in oklab,var(--color-emerald-500) 70%,transparent)}}.bg-fuchsia-500{background-color:var(--color-fuchsia-500)}.bg-fuchsia-500\/15{background-color:#e12afb26}@supports (color:color-mix(in lab,red,red)){.bg-fuchsia-500\/15{background-color:color-mix(in oklab,var(--color-fuchsia-500) 15%,transparent)}}.bg-indigo-500{background-color:var(--color-indigo-500)}.bg-indigo-500\/15{background-color:#625fff26}@supports (color:color-mix(in lab,red,red)){.bg-indigo-500\/15{background-color:color-mix(in oklab,var(--color-indigo-500) 15%,transparent)}}.bg-muted,.bg-muted\/20{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/20{background-color:color-mix(in oklab,hsl(var(--muted)) 20%,transparent)}}.bg-muted\/30{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/30{background-color:color-mix(in oklab,hsl(var(--muted)) 30%,transparent)}}.bg-muted\/40{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/40{background-color:color-mix(in oklab,hsl(var(--muted)) 40%,transparent)}}.bg-muted\/50{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/50{background-color:color-mix(in oklab,hsl(var(--muted)) 50%,transparent)}}.bg-muted\/60{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.bg-muted\/60{background-color:color-mix(in oklab,hsl(var(--muted)) 60%,transparent)}}.bg-orange-500{background-color:var(--color-orange-500)}.bg-orange-500\/5{background-color:#fe6e000d}@supports (color:color-mix(in lab,red,red)){.bg-orange-500\/5{background-color:color-mix(in oklab,var(--color-orange-500) 5%,transparent)}}.bg-orange-500\/15{background-color:#fe6e0026}@supports (color:color-mix(in lab,red,red)){.bg-orange-500\/15{background-color:color-mix(in oklab,var(--color-orange-500) 15%,transparent)}}.bg-pink-500{background-color:var(--color-pink-500)}.bg-pink-500\/15{background-color:#f6339a26}@supports (color:color-mix(in lab,red,red)){.bg-pink-500\/15{background-color:color-mix(in oklab,var(--color-pink-500) 15%,transparent)}}.bg-popover,.bg-popover\/95{background-color:hsl(var(--popover))}@supports (color:color-mix(in lab,red,red)){.bg-popover\/95{background-color:color-mix(in oklab,hsl(var(--popover)) 95%,transparent)}}.bg-primary,.bg-primary\/5{background-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.bg-primary\/5{background-color:color-mix(in oklab,hsl(var(--primary)) 5%,transparent)}}.bg-primary\/15{background-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.bg-primary\/15{background-color:color-mix(in oklab,hsl(var(--primary)) 15%,transparent)}}.bg-primary\/50{background-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.bg-primary\/50{background-color:color-mix(in oklab,hsl(var(--primary)) 50%,transparent)}}.bg-rose-500{background-color:var(--color-rose-500)}.bg-rose-500\/15{background-color:#ff235726}@supports (color:color-mix(in lab,red,red)){.bg-rose-500\/15{background-color:color-mix(in oklab,var(--color-rose-500) 15%,transparent)}}.bg-secondary{background-color:hsl(var(--secondary))}.bg-sky-500{background-color:var(--color-sky-500)}.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-teal-500{background-color:var(--color-teal-500)}.bg-teal-500\/15{background-color:#00baa726}@supports (color:color-mix(in lab,red,red)){.bg-teal-500\/15{background-color:color-mix(in oklab,var(--color-teal-500) 15%,transparent)}}.bg-transparent{background-color:#0000}.bg-violet-500{background-color:var(--color-violet-500)}.bg-violet-500\/5{background-color:#8d54ff0d}@supports (color:color-mix(in lab,red,red)){.bg-violet-500\/5{background-color:color-mix(in oklab,var(--color-violet-500) 5%,transparent)}}.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)}}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-1{padding:var(--spacing)}.p-1\.5{padding:calc(var(--spacing) * 1.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-6{padding:calc(var(--spacing) * 6)}.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)}.px-8{padding-inline:calc(var(--spacing) * 8)}.py-0{padding-block:0}.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-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)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-0{padding-top:0}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-9{padding-top:calc(var(--spacing) * 9)}.pr-1{padding-right:var(--spacing)}.pr-1\.5{padding-right:calc(var(--spacing) * 1.5)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pr-9{padding-right:calc(var(--spacing) * 9)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-2\.5{padding-left:calc(var(--spacing) * 2.5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.pl-7{padding-left:calc(var(--spacing) * 7)}.pl-8{padding-left:calc(var(--spacing) * 8)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.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-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--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-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.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-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-accent-foreground{color:hsl(var(--accent-foreground))}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-blue-500{color:var(--color-blue-500)}.text-card-foreground{color:hsl(var(--card-foreground))}.text-current{color:currentColor}.text-cyan-600{color:var(--color-cyan-600)}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-emerald-500{color:var(--color-emerald-500)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-700{color:var(--color-emerald-700)}.text-foreground,.text-foreground\/80{color:hsl(var(--foreground))}@supports (color:color-mix(in lab,red,red)){.text-foreground\/80{color:color-mix(in oklab,hsl(var(--foreground)) 80%,transparent)}}.text-fuchsia-600{color:var(--color-fuchsia-600)}.text-indigo-600{color:var(--color-indigo-600)}.text-muted-foreground,.text-muted-foreground\/40{color:hsl(var(--muted-foreground))}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/40{color:color-mix(in oklab,hsl(var(--muted-foreground)) 40%,transparent)}}.text-muted-foreground\/50{color:hsl(var(--muted-foreground))}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/50{color:color-mix(in oklab,hsl(var(--muted-foreground)) 50%,transparent)}}.text-muted-foreground\/60{color:hsl(var(--muted-foreground))}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/60{color:color-mix(in oklab,hsl(var(--muted-foreground)) 60%,transparent)}}.text-muted-foreground\/70{color:hsl(var(--muted-foreground))}@supports (color:color-mix(in lab,red,red)){.text-muted-foreground\/70{color:color-mix(in oklab,hsl(var(--muted-foreground)) 70%,transparent)}}.text-orange-600{color:var(--color-orange-600)}.text-orange-700{color:var(--color-orange-700)}.text-pink-600{color:var(--color-pink-600)}.text-popover-foreground{color:hsl(var(--popover-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-red-500{color:var(--color-red-500)}.text-rose-600{color:var(--color-rose-600)}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-sky-600{color:var(--color-sky-600)}.text-teal-600{color:var(--color-teal-600)}.text-violet-600{color:var(--color-violet-600)}.text-violet-700{color:var(--color-violet-700)}.text-white{color:var(--color-white)}.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}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.shadow{--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-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px 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-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px 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-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)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-0{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-primary\/60{--tw-ring-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.ring-primary\/60{--tw-ring-color:color-mix(in oklab, hsl(var(--primary)) 60%, transparent)}}.ring-offset-background{--tw-ring-offset-color:hsl(var(--background))}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);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,)}.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,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.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))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.running{animation-play-state:running}@media (hover:hover){.group-hover\:block:is(:where(.group):hover *){display:block}.group-hover\:bg-destructive:is(:where(.group):hover *){background-color:hsl(var(--destructive))}.group-hover\:bg-emerald-500:is(:where(.group):hover *){background-color:var(--color-emerald-500)}.group-hover\:opacity-100:is(:where(.group):hover *){opacity:1}}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.focus-within\:opacity-100:focus-within{opacity:1}@media (hover:hover){.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-amber-500\/10:hover{background-color:#f99c001a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-amber-500\/10:hover{background-color:color-mix(in oklab,var(--color-amber-500) 10%,transparent)}}.hover\:bg-destructive\/10:hover{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/10:hover{background-color:color-mix(in oklab,hsl(var(--destructive)) 10%,transparent)}}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-destructive\/90:hover{background-color:color-mix(in oklab,hsl(var(--destructive)) 90%,transparent)}}.hover\:bg-muted:hover,.hover\:bg-muted\/30:hover{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-muted\/30:hover{background-color:color-mix(in oklab,hsl(var(--muted)) 30%,transparent)}}.hover\:bg-muted\/40:hover{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-muted\/40:hover{background-color:color-mix(in oklab,hsl(var(--muted)) 40%,transparent)}}.hover\:bg-muted\/50:hover{background-color:hsl(var(--muted))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-muted\/50:hover{background-color:color-mix(in oklab,hsl(var(--muted)) 50%,transparent)}}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab,hsl(var(--primary)) 90%,transparent)}}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary))}@supports (color:color-mix(in lab,red,red)){.hover\:bg-secondary\/80:hover{background-color:color-mix(in oklab,hsl(var(--secondary)) 80%,transparent)}}.hover\:bg-transparent:hover{background-color:#0000}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-destructive:hover{color:hsl(var(--destructive))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:bg-destructive\/10:focus{background-color:hsl(var(--destructive))}@supports (color:color-mix(in lab,red,red)){.focus\:bg-destructive\/10:focus{background-color:color-mix(in oklab,hsl(var(--destructive)) 10%,transparent)}}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:text-destructive:focus{color:hsl(var(--destructive))}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-ring:focus{--tw-ring-color:hsl(var(--ring))}.focus\:ring-offset-1:focus{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus\:ring-offset-background:focus{--tw-ring-offset-color:hsl(var(--background))}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.focus-visible\:opacity-100:focus-visible{opacity:1}.focus-visible\:ring-1:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color:hsl(var(--ring))}.focus-visible\:ring-offset-1:focus-visible{--tw-ring-offset-width:1px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color)}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color:hsl(var(--background))}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[inset\]\:pl-8[data-inset]{padding-left:calc(var(--spacing) * 8)}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:calc(2*var(--spacing)*-1)}.data-\[side\=left\]\:translate-x-1[data-side=left]{--tw-translate-x:var(--spacing);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:calc(2*var(--spacing))}.data-\[side\=right\]\:-translate-x-1[data-side=right]{--tw-translate-x:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:calc(2*var(--spacing)*-1)}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:calc(var(--spacing) * -1);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:calc(2*var(--spacing))}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:hsl(var(--background))}.data-\[state\=active\]\:text-foreground[data-state=active]{color:hsl(var(--foreground))}.data-\[state\=active\]\:shadow[data-state=active]{--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)}.data-\[state\=checked\]\:translate-x-4[data-state=checked]{--tw-translate-x:calc(var(--spacing) * 4);translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:hsl(var(--primary))}.data-\[state\=checked\]\:text-primary-foreground[data-state=checked]{color:hsl(var(--primary-foreground))}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation:exit var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=open\]\:animate-in[data-state=open]{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[state\=selected\]\:bg-muted[data-state=selected]{background-color:hsl(var(--muted))}.data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked]{--tw-translate-x:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.data-\[state\=unchecked\]\:bg-input[data-state=unchecked]{background-color:hsl(var(--input))}@media (min-width:40rem){.sm\:max-w-sm{max-width:var(--container-sm)}.sm\:translate-x-2{--tw-translate-x:calc(var(--spacing) * 2);translate:var(--tw-translate-x) var(--tw-translate-y)}.sm\:translate-y-0{--tw-translate-y:0px;translate:var(--tw-translate-x) var(--tw-translate-y)}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-\[auto_1fr_auto_1fr\]{grid-template-columns:auto 1fr auto 1fr}.sm\:flex-row{flex-direction:row}.sm\:items-end{align-items:flex-end}.sm\:justify-end{justify-content:flex-end}.sm\:rounded-lg{border-radius:var(--radius)}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:64rem){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}}.dark\:text-amber-300:is(.dark *){color:var(--color-amber-300)}.dark\:text-amber-400:is(.dark *){color:var(--color-amber-400)}.dark\:text-cyan-400:is(.dark *){color:var(--color-cyan-400)}.dark\:text-emerald-300:is(.dark *){color:var(--color-emerald-300)}.dark\:text-emerald-400:is(.dark *){color:var(--color-emerald-400)}.dark\:text-fuchsia-400:is(.dark *){color:var(--color-fuchsia-400)}.dark\:text-indigo-400:is(.dark *){color:var(--color-indigo-400)}.dark\:text-orange-400:is(.dark *){color:var(--color-orange-400)}.dark\:text-pink-400:is(.dark *){color:var(--color-pink-400)}.dark\:text-rose-400:is(.dark *){color:var(--color-rose-400)}.dark\:text-sky-400:is(.dark *){color:var(--color-sky-400)}.dark\:text-teal-400:is(.dark *){color:var(--color-teal-400)}.dark\:text-violet-300:is(.dark *){color:var(--color-violet-300)}.dark\:text-violet-400:is(.dark *){color:var(--color-violet-400)}.\[\&_svg\]\:size-4 svg{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_tr\]\:border-b tr{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-style:var(--tw-border-style);border-width:0}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:0}.\[\&\>span\]\:line-clamp-1>span{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}:root{--background:0 0% 100%;--foreground:240 10% 3.9%;--card:0 0% 100%;--card-foreground:240 10% 3.9%;--popover:0 0% 100%;--popover-foreground:240 10% 3.9%;--primary:222 89% 55%;--primary-foreground:0 0% 100%;--secondary:240 4.8% 95.9%;--secondary-foreground:240 5.9% 10%;--muted:240 4.8% 95.9%;--muted-foreground:240 3.8% 46.1%;--accent:240 4.8% 95.9%;--accent-foreground:240 5.9% 10%;--destructive:0 72% 51%;--destructive-foreground:0 0% 98%;--border:240 5.9% 90%;--input:240 5.9% 90%;--ring:222 89% 55%;--radius:.6rem}.dark{--background:222 22% 7%;--foreground:210 20% 96%;--card:222 20% 9%;--card-foreground:210 20% 96%;--popover:222 20% 9%;--popover-foreground:210 20% 96%;--primary:217 91% 60%;--primary-foreground:222 47% 11%;--secondary:222 16% 16%;--secondary-foreground:210 20% 96%;--muted:222 16% 14%;--muted-foreground:215 16% 62%;--accent:222 16% 16%;--accent-foreground:210 20% 96%;--destructive:0 62% 45%;--destructive-foreground:0 0% 98%;--border:222 14% 18%;--input:222 14% 18%;--ring:217 91% 60%}*{scrollbar-width:thin;scrollbar-color:hsl(var(--border)) transparent}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-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-outline-style{syntax:"*";inherits:false;initial-value:solid}@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}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}@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));filter:blur(var(--tw-enter-blur,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));filter:blur(var(--tw-exit-blur,0))}}
|