@tonyclaw/agent-inspector 2.1.15 → 2.1.17

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.
Files changed (73) hide show
  1. package/.output/cli.js +57 -13
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-BcEd6V-V.js → CompareDrawer-B9sLBHw5.js} +1 -1
  4. package/.output/public/assets/ProxyViewerContainer-DbWjc_BE.js +106 -0
  5. package/.output/public/assets/{ReplayDialog-BNpC0548.js → ReplayDialog-D9I9W9n3.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-Ds1uRLVB.js → RequestAnatomy-DGXK_Rii.js} +1 -1
  7. package/.output/public/assets/{ResponseView-7KPVqKl5.js → ResponseView-df_JzwsS.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-BHQT261s.js → StreamingChunkSequence-i3DM5IKL.js} +1 -1
  9. package/.output/public/assets/_sessionId-CTpCHdFh.js +1 -0
  10. package/.output/public/assets/index-Blqvndcn.js +1 -0
  11. package/.output/public/assets/index-Cs79WRQj.css +1 -0
  12. package/.output/public/assets/{index-DjKt8XKe.js → index-Dqc2r1ea.js} +1 -1
  13. package/.output/public/assets/{json-viewer-CkCu-rka.js → json-viewer-D3QWQlSB.js} +1 -1
  14. package/.output/public/assets/{main-DpD1N0S8.js → main-ZhxhOCmF.js} +2 -2
  15. package/.output/server/_libs/lucide-react.mjs +255 -201
  16. package/.output/server/{_sessionId-DF9Sy8cP.mjs → _sessionId-YFrP0lZP.mjs} +2 -2
  17. package/.output/server/_ssr/{CompareDrawer-BoxztaO7.mjs → CompareDrawer-qC3adEeq.mjs} +3 -3
  18. package/.output/server/_ssr/{ProxyViewerContainer-CRBkqFlJ.mjs → ProxyViewerContainer-DCZIJC2b.mjs} +2732 -1849
  19. package/.output/server/_ssr/{ReplayDialog-Cc1dyDuK.mjs → ReplayDialog-CvX4XYCi.mjs} +4 -4
  20. package/.output/server/_ssr/{RequestAnatomy-CMGSsz5Z.mjs → RequestAnatomy-HQfrxjO1.mjs} +3 -3
  21. package/.output/server/_ssr/{ResponseView-Cp10DM1D.mjs → ResponseView-QSlSD11e.mjs} +3 -3
  22. package/.output/server/_ssr/{StreamingChunkSequence-B1VGxy3A.mjs → StreamingChunkSequence-DTAiwlnI.mjs} +3 -3
  23. package/.output/server/_ssr/{index-47XVPghS.mjs → index-D_EzfXaN.mjs} +2 -2
  24. package/.output/server/_ssr/index.mjs +2 -2
  25. package/.output/server/_ssr/{json-viewer-zDE2rrmJ.mjs → json-viewer-CrIX0e0Q.mjs} +3 -3
  26. package/.output/server/_ssr/{router-DVeuZFqI.mjs → router-__o2wrfO.mjs} +505 -136
  27. package/.output/server/{_tanstack-start-manifest_v-Bp8JxtPW.mjs → _tanstack-start-manifest_v-DbqyLpsI.mjs} +1 -1
  28. package/.output/server/index.mjs +63 -63
  29. package/README.md +57 -2
  30. package/package.json +1 -1
  31. package/src/cli/startupOutput.ts +16 -0
  32. package/src/cli.ts +61 -15
  33. package/src/components/ProxyViewer.tsx +535 -163
  34. package/src/components/ProxyViewerContainer.tsx +77 -67
  35. package/src/components/alerts/AlertsDialog.tsx +2 -1
  36. package/src/components/clients/ClientLogo.tsx +1 -1
  37. package/src/components/groups/GroupsDialog.tsx +19 -1
  38. package/src/components/providers/ProviderCard.tsx +133 -32
  39. package/src/components/providers/ProviderForm.tsx +61 -29
  40. package/src/components/providers/ProviderLogo.tsx +6 -1
  41. package/src/components/providers/ProvidersPanel.tsx +7 -7
  42. package/src/components/providers/SettingsDialog.tsx +13 -6
  43. package/src/components/proxy-viewer/AgentTraceSummary.tsx +6 -6
  44. package/src/components/proxy-viewer/ConversationGroup.tsx +43 -32
  45. package/src/components/proxy-viewer/ConversationGroupList.tsx +452 -89
  46. package/src/components/proxy-viewer/ConversationHeader.tsx +153 -120
  47. package/src/components/proxy-viewer/LogEntry.tsx +130 -43
  48. package/src/components/proxy-viewer/LogEntryHeader.tsx +357 -354
  49. package/src/components/proxy-viewer/ProviderLogoStack.tsx +2 -2
  50. package/src/components/proxy-viewer/RequestToolsPanel.tsx +9 -9
  51. package/src/components/proxy-viewer/ThreadConnector.tsx +102 -83
  52. package/src/components/proxy-viewer/TurnGroup.tsx +33 -69
  53. package/src/components/proxy-viewer/TurnGroupList.tsx +3 -0
  54. package/src/components/proxy-viewer/bodyHydration.ts +56 -0
  55. package/src/components/proxy-viewer/viewerState.ts +0 -4
  56. package/src/components/ui/icon-trigger.ts +2 -0
  57. package/src/components/ui/transient-toast.tsx +104 -0
  58. package/src/lib/providerTestContract.ts +56 -10
  59. package/src/lib/providerTestPrompt.ts +23 -1
  60. package/src/lib/sessionInfoContract.ts +4 -0
  61. package/src/lib/upstreamUrl.ts +105 -1
  62. package/src/mcp/server.ts +71 -27
  63. package/src/mcp/toolHandlers.ts +24 -5
  64. package/src/proxy/formats/openai/alibabaProvider.ts +0 -4
  65. package/src/proxy/sessionInfo.ts +31 -2
  66. package/src/proxy/sessionSupervisor.ts +20 -1
  67. package/src/proxy/store.ts +1 -1
  68. package/src/routes/api/providers.$providerId.test.log.ts +93 -14
  69. package/styles/globals.css +180 -180
  70. package/.output/public/assets/ProxyViewerContainer-h851qWNp.js +0 -106
  71. package/.output/public/assets/_sessionId-DWePGjnS.js +0 -1
  72. package/.output/public/assets/index-CI1-G8ua.js +0 -1
  73. package/.output/public/assets/index-DdhFqPsI.css +0 -1
@@ -60,6 +60,18 @@ function normalizeExplicitSessionId(sessionId: string | null | undefined): strin
60
60
  return trimmed.length > 0 ? trimmed : null;
61
61
  }
62
62
 
63
+ export function buildProviderTestSessionId(providerId: string): string {
64
+ const normalizedProviderId = normalizeExplicitSessionId(providerId);
65
+ if (normalizedProviderId === null) return PROVIDER_TEST_SESSION_ID;
66
+ return `${PROVIDER_TEST_SESSION_ID}:${normalizedProviderId}`;
67
+ }
68
+
69
+ function isProviderTestSessionId(sessionId: string): boolean {
70
+ return (
71
+ sessionId === PROVIDER_TEST_SESSION_ID || sessionId.startsWith(`${PROVIDER_TEST_SESSION_ID}:`)
72
+ );
73
+ }
74
+
63
75
  function buildClientSessionId(clientInfo: SessionClientInfo): SessionIdentity {
64
76
  const projectFolder = normalizeExplicitSessionId(clientInfo.projectFolder);
65
77
 
@@ -88,11 +100,18 @@ export function resolveSessionIdentity(input: {
88
100
  clientInfo?: SessionClientInfo;
89
101
  isTest?: boolean;
90
102
  }): SessionIdentity {
103
+ const explicit = normalizeExplicitSessionId(input.explicitSessionId);
104
+
91
105
  if (input.isTest === true) {
106
+ if (explicit !== null) {
107
+ return {
108
+ id: explicit,
109
+ source: isProviderTestSessionId(explicit) ? "provider-test" : "explicit",
110
+ };
111
+ }
92
112
  return { id: PROVIDER_TEST_SESSION_ID, source: "provider-test" };
93
113
  }
94
114
 
95
- const explicit = normalizeExplicitSessionId(input.explicitSessionId);
96
115
  if (explicit !== null) {
97
116
  return { id: explicit, source: "explicit" };
98
117
  }
@@ -322,7 +322,7 @@ function prepareLogForList(log: CapturedLog, includeBodies: boolean): CapturedLo
322
322
 
323
323
  /**
324
324
  * Add a test log entry to the in-memory store and persistent log file.
325
- * This is used by the provider test endpoint to seed the provider-test session.
325
+ * This is used by the provider test endpoint to seed provider-test sessions.
326
326
  */
327
327
  export async function addTestLogEntry(entry: Omit<CapturedLog, "id">): Promise<CapturedLog> {
328
328
  const id = await getNextLogId();
@@ -2,9 +2,11 @@ import { createFileRoute } from "@tanstack/react-router";
2
2
  import { getProvider, getModelUsageName } from "../../proxy/providers";
3
3
  import { addTestLogEntry } from "../../proxy/store";
4
4
  import { getConfig } from "../../proxy/config";
5
+ import { buildProviderTestSessionId } from "../../proxy/sessionSupervisor";
5
6
  import { buildProviderTestRequestBody } from "../../lib/providerTestPrompt";
6
7
  import {
7
8
  ProviderTestResultsSchema,
9
+ type ProviderModelTestResults,
8
10
  type ProviderTestResult as TestResult,
9
11
  type ProviderTestState,
10
12
  } from "../../lib/providerTestContract";
@@ -52,12 +54,11 @@ function getResultTokensPerSecond(result: TestResult): number | null {
52
54
  async function logModelResults(
53
55
  displayName: string,
54
56
  providerName: string,
57
+ providerSessionId: string,
55
58
  anthropicUrl: string | undefined,
56
- openaiUrl: string | undefined,
57
- modelResults: {
58
- anthropic: { nonStreaming: ProviderTestState; streaming: ProviderTestState };
59
- openai: { nonStreaming: ProviderTestState; streaming: ProviderTestState };
60
- },
59
+ openaiChatUrl: string | undefined,
60
+ openaiResponsesUrl: string | undefined,
61
+ modelResults: ProviderModelTestResults,
61
62
  captureFullDetails: boolean,
62
63
  ): Promise<void> {
63
64
  const usageModel = getModelUsageName(displayName, providerName);
@@ -76,7 +77,7 @@ async function logModelResults(
76
77
  method: "POST",
77
78
  path: "/v1/messages",
78
79
  model: nonStreamingResult.model ?? displayName,
79
- sessionId: null,
80
+ sessionId: providerSessionId,
80
81
  rawRequestBody: requestBody,
81
82
  responseStatus: getResultResponseStatus(nonStreamingResult),
82
83
  responseText: getResultResponseText(nonStreamingResult),
@@ -106,7 +107,7 @@ async function logModelResults(
106
107
  method: "POST",
107
108
  path: "/v1/messages",
108
109
  model: streamingResult.model ?? displayName,
109
- sessionId: null,
110
+ sessionId: providerSessionId,
110
111
  rawRequestBody: streamingRequestBody,
111
112
  responseStatus: getResultResponseStatus(streamingResult),
112
113
  responseText: getResultResponseText(streamingResult),
@@ -130,9 +131,9 @@ async function logModelResults(
130
131
  }
131
132
  }
132
133
 
133
- if (openaiUrl !== undefined) {
134
- const nsResult = modelResults.openai.nonStreaming;
135
- const sResult = modelResults.openai.streaming;
134
+ if (openaiChatUrl !== undefined) {
135
+ const nsResult = modelResults.openaiChat.nonStreaming;
136
+ const sResult = modelResults.openaiChat.streaming;
136
137
  if (hasSuccessField(nsResult) && hasSuccessField(sResult)) {
137
138
  const nonStreamingResult = nsResult;
138
139
  const streamingResult = sResult;
@@ -144,7 +145,7 @@ async function logModelResults(
144
145
  method: "POST",
145
146
  path: "/v1/chat/completions",
146
147
  model: nonStreamingResult.model ?? displayName,
147
- sessionId: null,
148
+ sessionId: providerSessionId,
148
149
  rawRequestBody: requestBody,
149
150
  responseStatus: getResultResponseStatus(nonStreamingResult),
150
151
  responseText: getResultResponseText(nonStreamingResult),
@@ -174,7 +175,7 @@ async function logModelResults(
174
175
  method: "POST",
175
176
  path: "/v1/chat/completions",
176
177
  model: streamingResult.model ?? displayName,
177
- sessionId: null,
178
+ sessionId: providerSessionId,
178
179
  rawRequestBody: streamingRequestBody,
179
180
  responseStatus: getResultResponseStatus(streamingResult),
180
181
  responseText: getResultResponseText(streamingResult),
@@ -197,6 +198,76 @@ async function logModelResults(
197
198
  });
198
199
  }
199
200
  }
201
+
202
+ if (openaiResponsesUrl !== undefined) {
203
+ const nsResult = modelResults.openaiResponses.nonStreaming;
204
+ const sResult = modelResults.openaiResponses.streaming;
205
+ if (hasSuccessField(nsResult) && hasSuccessField(sResult)) {
206
+ const nonStreamingResult = nsResult;
207
+ const streamingResult = sResult;
208
+
209
+ const requestBody = JSON.stringify(
210
+ buildProviderTestRequestBody(usageModel, "non-streaming", "responses"),
211
+ );
212
+
213
+ await addTestLogEntry({
214
+ timestamp: new Date().toISOString(),
215
+ method: "POST",
216
+ path: "/v1/responses",
217
+ model: nonStreamingResult.model ?? displayName,
218
+ sessionId: providerSessionId,
219
+ rawRequestBody: requestBody,
220
+ responseStatus: getResultResponseStatus(nonStreamingResult),
221
+ responseText: getResultResponseText(nonStreamingResult),
222
+ inputTokens: nonStreamingResult.inputTokens ?? null,
223
+ outputTokens: nonStreamingResult.outputTokens ?? null,
224
+ cacheCreationInputTokens: null,
225
+ cacheReadInputTokens: nonStreamingResult.cacheReadInputTokens ?? null,
226
+ elapsedMs: nonStreamingResult.latencyMs ?? 0,
227
+ firstChunkMs: null,
228
+ totalStreamMs: null,
229
+ tokensPerSecond: null,
230
+ streaming: false,
231
+ userAgent: "provider-test",
232
+ origin: null,
233
+ apiFormat: "openai",
234
+ isTest: true,
235
+ providerName,
236
+ headers: captureFullDetails ? getResultHeaders(nonStreamingResult) : undefined,
237
+ });
238
+
239
+ const streamingRequestBody = JSON.stringify(
240
+ buildProviderTestRequestBody(usageModel, "streaming", "responses"),
241
+ );
242
+
243
+ await addTestLogEntry({
244
+ timestamp: new Date().toISOString(),
245
+ method: "POST",
246
+ path: "/v1/responses",
247
+ model: streamingResult.model ?? displayName,
248
+ sessionId: providerSessionId,
249
+ rawRequestBody: streamingRequestBody,
250
+ responseStatus: getResultResponseStatus(streamingResult),
251
+ responseText: getResultResponseText(streamingResult),
252
+ inputTokens: streamingResult.inputTokens ?? null,
253
+ outputTokens: streamingResult.outputTokens ?? null,
254
+ cacheCreationInputTokens: null,
255
+ cacheReadInputTokens: streamingResult.cacheReadInputTokens ?? null,
256
+ elapsedMs: getResultTotalStreamMs(streamingResult) ?? 0,
257
+ firstChunkMs: getResultFirstChunkMs(streamingResult),
258
+ totalStreamMs: getResultTotalStreamMs(streamingResult),
259
+ tokensPerSecond: getResultTokensPerSecond(streamingResult),
260
+ streaming: true,
261
+ streamingChunks: captureFullDetails ? streamingResult.streamingChunks : undefined,
262
+ userAgent: "provider-test",
263
+ origin: null,
264
+ apiFormat: "openai",
265
+ isTest: true,
266
+ providerName,
267
+ headers: captureFullDetails ? getResultHeaders(streamingResult) : undefined,
268
+ });
269
+ }
270
+ }
200
271
  }
201
272
 
202
273
  export const Route = createFileRoute("/api/providers/$providerId/test/log")({
@@ -224,15 +295,21 @@ export const Route = createFileRoute("/api/providers/$providerId/test/log")({
224
295
  }
225
296
 
226
297
  const results = parsed.data;
298
+ const providerSessionId = buildProviderTestSessionId(params.providerId);
227
299
  const captureFullDetails = getConfig().captureMode === "full";
228
300
  const anthropicUrl =
229
301
  provider.anthropicBaseUrl !== undefined && provider.anthropicBaseUrl.length > 0
230
302
  ? provider.anthropicBaseUrl
231
303
  : undefined;
232
- const openaiUrl =
304
+ const openaiChatUrl =
233
305
  provider.openaiBaseUrl !== undefined && provider.openaiBaseUrl.length > 0
234
306
  ? provider.openaiBaseUrl
235
307
  : undefined;
308
+ const openaiResponsesUrl =
309
+ provider.openaiResponsesBaseUrl !== undefined &&
310
+ provider.openaiResponsesBaseUrl.length > 0
311
+ ? provider.openaiResponsesBaseUrl
312
+ : undefined;
236
313
 
237
314
  const entries: Promise<void>[] = [];
238
315
  if (results.models !== undefined) {
@@ -241,8 +318,10 @@ export const Route = createFileRoute("/api/providers/$providerId/test/log")({
241
318
  logModelResults(
242
319
  modelName,
243
320
  provider.name,
321
+ providerSessionId,
244
322
  anthropicUrl,
245
- openaiUrl,
323
+ openaiChatUrl,
324
+ openaiResponsesUrl,
246
325
  modelResult,
247
326
  captureFullDetails,
248
327
  ),
@@ -1,183 +1,183 @@
1
- @import "tailwindcss";
2
- @import "tw-animate-css";
3
- @plugin "@tailwindcss/typography";
4
-
5
- @custom-variant dark (&:is(.dark *));
6
-
7
- @theme inline {
8
- --color-background: var(--background);
9
- --color-foreground: var(--foreground);
10
- --color-sidebar-ring: var(--sidebar-ring);
11
- --color-sidebar-border: var(--sidebar-border);
12
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
13
- --color-sidebar-accent: var(--sidebar-accent);
14
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
15
- --color-sidebar-primary: var(--sidebar-primary);
16
- --color-sidebar-foreground: var(--sidebar-foreground);
17
- --color-sidebar: var(--sidebar);
18
- --color-chart-5: var(--chart-5);
19
- --color-chart-4: var(--chart-4);
20
- --color-chart-3: var(--chart-3);
21
- --color-chart-2: var(--chart-2);
22
- --color-chart-1: var(--chart-1);
23
- --color-ring: var(--ring);
24
- --color-input: var(--input);
25
- --color-border: var(--border);
26
- --color-destructive: var(--destructive);
27
- --color-accent-foreground: var(--accent-foreground);
28
- --color-accent: var(--accent);
29
- --color-muted-foreground: var(--muted-foreground);
30
- --color-muted: var(--muted);
31
- --color-secondary-foreground: var(--secondary-foreground);
32
- --color-secondary: var(--secondary);
33
- --color-primary-foreground: var(--primary-foreground);
34
- --color-primary: var(--primary);
35
- --color-popover-foreground: var(--popover-foreground);
36
- --color-popover: var(--popover);
37
- --color-card-foreground: var(--card-foreground);
38
- --color-card: var(--card);
39
- --radius-sm: calc(var(--radius) - 4px);
40
- --radius-md: calc(var(--radius) - 2px);
41
- --radius-lg: var(--radius);
42
- --radius-xl: calc(var(--radius) + 4px);
43
- --animate-crab-crawl: crab-crawl-down 0.6s ease-in-out infinite;
44
- --animate-crab-appear: crab-appear 300ms ease-out;
45
- --animate-crab-settle: crab-settle 400ms ease-in-out;
46
- --animate-crab-piano-pop: crab-piano-pop 400ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
47
- }
48
-
49
- :root {
50
- --radius: 0.625rem;
51
- --background: oklch(1 0 0);
52
- --foreground: oklch(0.145 0 0);
53
- --card: oklch(1 0 0);
54
- --card-foreground: oklch(0.145 0 0);
55
- --popover: oklch(1 0 0);
56
- --popover-foreground: oklch(0.145 0 0);
57
- --primary: oklch(0.205 0 0);
58
- --primary-foreground: oklch(0.985 0 0);
59
- --secondary: oklch(0.97 0 0);
60
- --secondary-foreground: oklch(0.205 0 0);
61
- --muted: oklch(0.97 0 0);
62
- --muted-foreground: oklch(0.556 0 0);
63
- --accent: oklch(0.97 0 0);
64
- --accent-foreground: oklch(0.205 0 0);
65
- --destructive: oklch(0.577 0.245 27.325);
66
- --border: oklch(0.922 0 0);
67
- --input: oklch(0.922 0 0);
68
- --ring: oklch(0.708 0 0);
69
- --chart-1: oklch(0.646 0.222 41.116);
70
- --chart-2: oklch(0.6 0.118 184.704);
71
- --chart-3: oklch(0.398 0.07 227.392);
72
- --chart-4: oklch(0.828 0.189 84.429);
73
- --chart-5: oklch(0.769 0.188 70.08);
74
- --sidebar: oklch(0.985 0 0);
75
- --sidebar-foreground: oklch(0.145 0 0);
76
- --sidebar-primary: oklch(0.205 0 0);
77
- --sidebar-primary-foreground: oklch(0.985 0 0);
78
- --sidebar-accent: oklch(0.97 0 0);
79
- --sidebar-accent-foreground: oklch(0.205 0 0);
80
- --sidebar-border: oklch(0.922 0 0);
81
- --sidebar-ring: oklch(0.708 0 0);
82
- }
83
-
84
- .dark {
85
- --background: oklch(0.145 0 0);
86
- --foreground: oklch(0.985 0 0);
87
- --card: oklch(0.205 0 0);
88
- --card-foreground: oklch(0.985 0 0);
89
- --popover: oklch(0.205 0 0);
90
- --popover-foreground: oklch(0.985 0 0);
91
- --primary: oklch(0.922 0 0);
92
- --primary-foreground: oklch(0.205 0 0);
93
- --secondary: oklch(0.269 0 0);
94
- --secondary-foreground: oklch(0.985 0 0);
95
- --muted: oklch(0.269 0 0);
96
- --muted-foreground: oklch(0.708 0 0);
97
- --accent: oklch(0.269 0 0);
98
- --accent-foreground: oklch(0.985 0 0);
99
- --destructive: oklch(0.704 0.191 22.216);
100
- --border: oklch(1 0 0 / 10%);
101
- --input: oklch(1 0 0 / 15%);
102
- --ring: oklch(0.556 0 0);
103
- --chart-1: oklch(0.488 0.243 264.376);
104
- --chart-2: oklch(0.696 0.17 162.48);
105
- --chart-3: oklch(0.769 0.188 70.08);
106
- --chart-4: oklch(0.627 0.265 303.9);
107
- --chart-5: oklch(0.645 0.246 16.439);
108
- --sidebar: oklch(0.205 0 0);
109
- --sidebar-foreground: oklch(0.985 0 0);
110
- --sidebar-primary: oklch(0.488 0.243 264.376);
111
- --sidebar-primary-foreground: oklch(0.985 0 0);
112
- --sidebar-accent: oklch(0.269 0 0);
113
- --sidebar-accent-foreground: oklch(0.985 0 0);
114
- --sidebar-border: oklch(1 0 0 / 10%);
115
- --sidebar-ring: oklch(0.556 0 0);
116
- }
117
-
118
- @keyframes crab-crawl-down {
119
- 0%, 100% {
120
- transform: translateY(0px);
121
- }
122
- 25% {
123
- transform: translateY(2px);
124
- }
125
- 75% {
126
- transform: translateY(-1px);
127
- }
128
- }
129
-
130
- @keyframes crab-appear {
131
- 0% {
132
- transform: scale(0.5);
133
- opacity: 0;
134
- }
135
- 100% {
136
- transform: scale(1);
137
- opacity: 1;
138
- }
139
- }
140
-
141
- @keyframes crab-settle {
142
- 0% {
143
- transform: scale(1);
144
- }
145
- 50% {
146
- transform: scale(1.15);
147
- }
148
- 100% {
149
- transform: scale(1);
150
- }
151
- }
152
-
153
- @keyframes crab-piano-pop {
154
- 0% {
155
- transform: scale(0) translateY(0);
156
- opacity: 0;
157
- }
158
- 60% {
159
- transform: scale(1.3) translateY(-4px);
160
- opacity: 1;
161
- }
162
- 100% {
163
- transform: scale(1) translateY(0);
164
- opacity: 1;
165
- }
166
- }
167
-
168
- @media (prefers-reduced-motion: reduce) {
169
- .animate-crab-piano-pop {
170
- animation: none !important;
171
- }
172
- }
173
-
174
- [data-deep-link-highlight="true"] {
175
- background: color-mix(in oklch, var(--chart-2) 18%, transparent);
176
- box-shadow:
177
- inset 0 0 0 1px color-mix(in oklch, var(--chart-2) 65%, transparent),
178
- 0 0 0 3px color-mix(in oklch, var(--chart-2) 20%, transparent);
179
- }
180
-
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @plugin "@tailwindcss/typography";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ @theme inline {
8
+ --color-background: var(--background);
9
+ --color-foreground: var(--foreground);
10
+ --color-sidebar-ring: var(--sidebar-ring);
11
+ --color-sidebar-border: var(--sidebar-border);
12
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
13
+ --color-sidebar-accent: var(--sidebar-accent);
14
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
15
+ --color-sidebar-primary: var(--sidebar-primary);
16
+ --color-sidebar-foreground: var(--sidebar-foreground);
17
+ --color-sidebar: var(--sidebar);
18
+ --color-chart-5: var(--chart-5);
19
+ --color-chart-4: var(--chart-4);
20
+ --color-chart-3: var(--chart-3);
21
+ --color-chart-2: var(--chart-2);
22
+ --color-chart-1: var(--chart-1);
23
+ --color-ring: var(--ring);
24
+ --color-input: var(--input);
25
+ --color-border: var(--border);
26
+ --color-destructive: var(--destructive);
27
+ --color-accent-foreground: var(--accent-foreground);
28
+ --color-accent: var(--accent);
29
+ --color-muted-foreground: var(--muted-foreground);
30
+ --color-muted: var(--muted);
31
+ --color-secondary-foreground: var(--secondary-foreground);
32
+ --color-secondary: var(--secondary);
33
+ --color-primary-foreground: var(--primary-foreground);
34
+ --color-primary: var(--primary);
35
+ --color-popover-foreground: var(--popover-foreground);
36
+ --color-popover: var(--popover);
37
+ --color-card-foreground: var(--card-foreground);
38
+ --color-card: var(--card);
39
+ --radius-sm: calc(var(--radius) - 4px);
40
+ --radius-md: calc(var(--radius) - 2px);
41
+ --radius-lg: var(--radius);
42
+ --radius-xl: calc(var(--radius) + 4px);
43
+ --animate-crab-crawl: crab-crawl-down 0.6s ease-in-out infinite;
44
+ --animate-crab-appear: crab-appear 300ms ease-out;
45
+ --animate-crab-settle: crab-settle 400ms ease-in-out;
46
+ --animate-crab-piano-pop: crab-piano-pop 400ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
47
+ }
48
+
49
+ :root {
50
+ --radius: 0.625rem;
51
+ --background: oklch(1 0 0);
52
+ --foreground: oklch(0.145 0 0);
53
+ --card: oklch(1 0 0);
54
+ --card-foreground: oklch(0.145 0 0);
55
+ --popover: oklch(1 0 0);
56
+ --popover-foreground: oklch(0.145 0 0);
57
+ --primary: oklch(0.205 0 0);
58
+ --primary-foreground: oklch(0.985 0 0);
59
+ --secondary: oklch(0.97 0 0);
60
+ --secondary-foreground: oklch(0.205 0 0);
61
+ --muted: oklch(0.97 0 0);
62
+ --muted-foreground: oklch(0.556 0 0);
63
+ --accent: oklch(0.97 0 0);
64
+ --accent-foreground: oklch(0.205 0 0);
65
+ --destructive: oklch(0.577 0.245 27.325);
66
+ --border: oklch(0.922 0 0);
67
+ --input: oklch(0.922 0 0);
68
+ --ring: oklch(0.708 0 0);
69
+ --chart-1: oklch(0.646 0.222 41.116);
70
+ --chart-2: oklch(0.6 0.118 184.704);
71
+ --chart-3: oklch(0.398 0.07 227.392);
72
+ --chart-4: oklch(0.828 0.189 84.429);
73
+ --chart-5: oklch(0.769 0.188 70.08);
74
+ --sidebar: oklch(0.985 0 0);
75
+ --sidebar-foreground: oklch(0.145 0 0);
76
+ --sidebar-primary: oklch(0.205 0 0);
77
+ --sidebar-primary-foreground: oklch(0.985 0 0);
78
+ --sidebar-accent: oklch(0.97 0 0);
79
+ --sidebar-accent-foreground: oklch(0.205 0 0);
80
+ --sidebar-border: oklch(0.922 0 0);
81
+ --sidebar-ring: oklch(0.708 0 0);
82
+ }
83
+
84
+ .dark {
85
+ --background: oklch(0.145 0 0);
86
+ --foreground: oklch(0.985 0 0);
87
+ --card: oklch(0.205 0 0);
88
+ --card-foreground: oklch(0.985 0 0);
89
+ --popover: oklch(0.205 0 0);
90
+ --popover-foreground: oklch(0.985 0 0);
91
+ --primary: oklch(0.922 0 0);
92
+ --primary-foreground: oklch(0.205 0 0);
93
+ --secondary: oklch(0.269 0 0);
94
+ --secondary-foreground: oklch(0.985 0 0);
95
+ --muted: oklch(0.269 0 0);
96
+ --muted-foreground: oklch(0.708 0 0);
97
+ --accent: oklch(0.269 0 0);
98
+ --accent-foreground: oklch(0.985 0 0);
99
+ --destructive: oklch(0.704 0.191 22.216);
100
+ --border: oklch(1 0 0 / 10%);
101
+ --input: oklch(1 0 0 / 15%);
102
+ --ring: oklch(0.556 0 0);
103
+ --chart-1: oklch(0.488 0.243 264.376);
104
+ --chart-2: oklch(0.696 0.17 162.48);
105
+ --chart-3: oklch(0.769 0.188 70.08);
106
+ --chart-4: oklch(0.627 0.265 303.9);
107
+ --chart-5: oklch(0.645 0.246 16.439);
108
+ --sidebar: oklch(0.205 0 0);
109
+ --sidebar-foreground: oklch(0.985 0 0);
110
+ --sidebar-primary: oklch(0.488 0.243 264.376);
111
+ --sidebar-primary-foreground: oklch(0.985 0 0);
112
+ --sidebar-accent: oklch(0.269 0 0);
113
+ --sidebar-accent-foreground: oklch(0.985 0 0);
114
+ --sidebar-border: oklch(1 0 0 / 10%);
115
+ --sidebar-ring: oklch(0.556 0 0);
116
+ }
117
+
118
+ @keyframes crab-crawl-down {
119
+ 0%, 100% {
120
+ transform: translateY(0px);
121
+ }
122
+ 25% {
123
+ transform: translateY(2px);
124
+ }
125
+ 75% {
126
+ transform: translateY(-1px);
127
+ }
128
+ }
129
+
130
+ @keyframes crab-appear {
131
+ 0% {
132
+ transform: scale(0.5);
133
+ opacity: 0;
134
+ }
135
+ 100% {
136
+ transform: scale(1);
137
+ opacity: 1;
138
+ }
139
+ }
140
+
141
+ @keyframes crab-settle {
142
+ 0% {
143
+ transform: scale(1);
144
+ }
145
+ 50% {
146
+ transform: scale(1.15);
147
+ }
148
+ 100% {
149
+ transform: scale(1);
150
+ }
151
+ }
152
+
153
+ @keyframes crab-piano-pop {
154
+ 0% {
155
+ transform: scale(0) translateY(0);
156
+ opacity: 0;
157
+ }
158
+ 60% {
159
+ transform: scale(1.3) translateY(-4px);
160
+ opacity: 1;
161
+ }
162
+ 100% {
163
+ transform: scale(1) translateY(0);
164
+ opacity: 1;
165
+ }
166
+ }
167
+
168
+ @media (prefers-reduced-motion: reduce) {
169
+ .animate-crab-piano-pop {
170
+ animation: none !important;
171
+ }
172
+ }
173
+
174
+ [data-deep-link-highlight="true"] {
175
+ background: color-mix(in oklch, var(--chart-2) 18%, transparent);
176
+ box-shadow:
177
+ inset 0 0 0 1px color-mix(in oklch, var(--chart-2) 65%, transparent),
178
+ 0 0 0 3px color-mix(in oklch, var(--chart-2) 20%, transparent);
179
+ }
180
+
181
181
  @layer base {
182
182
  * {
183
183
  @apply border-border outline-ring/50;