@tonyclaw/agent-inspector 3.0.13 → 3.0.15
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/.output/nitro.json +1 -1
- package/.output/public/assets/{CompareDrawer-BRV9ztWd.js → CompareDrawer-UaIxykqz.js} +1 -1
- package/.output/public/assets/ProxyViewerContainer-DlpfcX56.js +106 -0
- package/.output/public/assets/{ReplayDialog-B4b6aD0q.js → ReplayDialog-BFFomU1m.js} +1 -1
- package/.output/public/assets/{RequestAnatomy-ybH1l2Bp.js → RequestAnatomy-DKQJBt_c.js} +1 -1
- package/.output/public/assets/{ResponseView-DpPKgH-Y.js → ResponseView-D7yotmne.js} +2 -2
- package/.output/public/assets/{StreamingChunkSequence-9f_rtMzz.js → StreamingChunkSequence-Da-6WNFG.js} +1 -1
- package/.output/public/assets/{_sessionId-gEmO-b2f.js → _sessionId-ByTXLS-w.js} +1 -1
- package/.output/public/assets/{index-JgHKqbP1.js → index-BrM8t5mc.js} +1 -1
- package/.output/public/assets/index-CGpPRty8.js +1 -0
- package/.output/public/assets/index-HRfrFVYw.css +1 -0
- package/.output/public/assets/{json-viewer-DFpIPmLo.js → json-viewer-DyjXQdVu.js} +1 -1
- package/.output/public/assets/{main-YvdO47OP.js → main-Zv_gjZMb.js} +2 -2
- package/.output/server/_libs/lucide-react.mjs +216 -202
- package/.output/server/{_sessionId-wSX-l7m2.mjs → _sessionId-GcFhWT0i.mjs} +3 -3
- package/.output/server/_ssr/{CompareDrawer-R4d8V0Rv.mjs → CompareDrawer-BV7dw1TN.mjs} +3 -3
- package/.output/server/_ssr/{ProxyViewerContainer-CDtJhGg8.mjs → ProxyViewerContainer-CcgfvaS4.mjs} +931 -393
- package/.output/server/_ssr/{ReplayDialog-BvmLERpW.mjs → ReplayDialog-B7sBKxIO.mjs} +4 -4
- package/.output/server/_ssr/{RequestAnatomy-CH-oO6mY.mjs → RequestAnatomy-s_9Veje2.mjs} +3 -3
- package/.output/server/_ssr/{ResponseView-D7shbJw0.mjs → ResponseView-DsOFPCvF.mjs} +3 -3
- package/.output/server/_ssr/{StreamingChunkSequence-DB2YHGJj.mjs → StreamingChunkSequence-CyA1XtiF.mjs} +3 -3
- package/.output/server/_ssr/{index-CS4QLxuv.mjs → index-CDDRt201.mjs} +2 -2
- package/.output/server/_ssr/index.mjs +2 -2
- package/.output/server/_ssr/{json-viewer-HxOn73kS.mjs → json-viewer-BFV-xtRM.mjs} +3 -3
- package/.output/server/_ssr/{router-l4O3mQ4T.mjs → router-B2Q7vHPM.mjs} +321 -121
- package/.output/server/_tanstack-start-manifest_v-Dm_GmuRz.mjs +4 -0
- package/.output/server/index.mjs +73 -73
- package/README.md +24 -0
- package/package.json +1 -1
- package/src/components/ProxyViewer.tsx +470 -197
- package/src/components/ProxyViewerContainer.tsx +145 -17
- package/src/components/ecosystem/AgentLabDialog.tsx +221 -10
- package/src/components/proxy-viewer/ToolTraceEvents.tsx +36 -10
- package/src/components/proxy-viewer/TurnGroup.tsx +4 -1
- package/src/lib/ecosystemContract.ts +21 -1
- package/src/proxy/ecosystemRegistry.ts +52 -0
- package/src/proxy/ecosystemTasks.ts +95 -1
- package/src/proxy/formats/openai/codexMetadata.ts +61 -0
- package/src/proxy/formats/openai/handler.ts +8 -2
- package/src/routes/api/ecosystem.packages.ts +2 -1
- package/src/routes/api/ecosystem.recipes.$recipeId.run.ts +16 -0
- package/styles/globals.css +54 -0
- package/.output/public/assets/ProxyViewerContainer-BVsv8OFb.js +0 -106
- package/.output/public/assets/index-BIgsQvaF.css +0 -1
- package/.output/public/assets/index-CF3MPYb3.js +0 -1
- package/.output/server/_tanstack-start-manifest_v-Dckf_NHV.mjs +0 -4
|
@@ -5,6 +5,7 @@ import packageJson from "../../package.json";
|
|
|
5
5
|
import type {
|
|
6
6
|
EcosystemPackage,
|
|
7
7
|
EcosystemPackageState,
|
|
8
|
+
EcosystemRecipe,
|
|
8
9
|
EcosystemRunnerPreset,
|
|
9
10
|
} from "../lib/ecosystemContract";
|
|
10
11
|
import { npmSpawnCommand } from "./platformCommands";
|
|
@@ -106,6 +107,53 @@ export const RUNNER_PRESETS: EcosystemRunnerPreset[] = [
|
|
|
106
107
|
},
|
|
107
108
|
];
|
|
108
109
|
|
|
110
|
+
export const LAB_RECIPES: EcosystemRecipe[] = [
|
|
111
|
+
{
|
|
112
|
+
id: "inspector-mcp-smoke",
|
|
113
|
+
title: "Inspector MCP smoke",
|
|
114
|
+
stage: "Validate",
|
|
115
|
+
packageId: "eval-harness",
|
|
116
|
+
description: "Check that the running Inspector exposes the MCP tools eval-harness expects.",
|
|
117
|
+
command: "npx @tonyclaw/eval-harness check-inspector-mcp",
|
|
118
|
+
requiresSession: false,
|
|
119
|
+
runnable: true,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "session-evidence-smoke",
|
|
123
|
+
title: "Session evidence smoke",
|
|
124
|
+
stage: "Capture",
|
|
125
|
+
packageId: "eval-harness",
|
|
126
|
+
description:
|
|
127
|
+
"Create a lightweight Inspector evidence run from the latest captured logs or selected session.",
|
|
128
|
+
command:
|
|
129
|
+
'npx @tonyclaw/eval-harness inspector-smoke --title "TonyClaw Lab smoke" --latest-log-limit 5',
|
|
130
|
+
requiresSession: false,
|
|
131
|
+
runnable: true,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: "runner-presets-json",
|
|
135
|
+
title: "Runner presets JSON",
|
|
136
|
+
stage: "Connect",
|
|
137
|
+
packageId: "eval-harness",
|
|
138
|
+
description: "Export runner adapter baselines as machine-readable JSON for CI or docs.",
|
|
139
|
+
command: "npx @tonyclaw/eval-harness runner-presets --format json",
|
|
140
|
+
requiresSession: false,
|
|
141
|
+
runnable: true,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: "local-smoke-batch",
|
|
145
|
+
title: "Local smoke batch",
|
|
146
|
+
stage: "Evaluate",
|
|
147
|
+
packageId: "eval-harness",
|
|
148
|
+
description:
|
|
149
|
+
"Run one short local Jenkins-like evaluation using explicit project/work directories.",
|
|
150
|
+
command:
|
|
151
|
+
"npx @tonyclaw/eval-harness run-once --smoke --project-source <project> --work-source <work> --count 1 --parallel 1",
|
|
152
|
+
requiresSession: false,
|
|
153
|
+
runnable: false,
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
|
|
109
157
|
const NPM_TIMEOUT_MS = 4000;
|
|
110
158
|
|
|
111
159
|
function runNpm(args: readonly string[]): Promise<NpmResult> {
|
|
@@ -242,3 +290,7 @@ export async function listEcosystemPackages(): Promise<EcosystemPackage[]> {
|
|
|
242
290
|
export function findEcosystemPackage(packageId: string): PackageDefinition | null {
|
|
243
291
|
return ECOSYSTEM_PACKAGES.find((definition) => definition.id === packageId) ?? null;
|
|
244
292
|
}
|
|
293
|
+
|
|
294
|
+
export function findEcosystemRecipe(recipeId: string): EcosystemRecipe | null {
|
|
295
|
+
return LAB_RECIPES.find((recipe) => recipe.id === recipeId) ?? null;
|
|
296
|
+
}
|
|
@@ -6,13 +6,14 @@ import type {
|
|
|
6
6
|
EcosystemTaskAction,
|
|
7
7
|
EcosystemTaskStatus,
|
|
8
8
|
} from "../lib/ecosystemContract";
|
|
9
|
-
import { findEcosystemPackage } from "./ecosystemRegistry";
|
|
9
|
+
import { findEcosystemPackage, findEcosystemRecipe } from "./ecosystemRegistry";
|
|
10
10
|
import { npmCommand, npmSpawnCommand } from "./platformCommands";
|
|
11
11
|
|
|
12
12
|
const MAX_TASKS = 50;
|
|
13
13
|
const OUTPUT_LIMIT = 40_000;
|
|
14
14
|
const INSTALL_TIMEOUT_MS = 120_000;
|
|
15
15
|
const HELP_TIMEOUT_MS = 30_000;
|
|
16
|
+
const RECIPE_TIMEOUT_MS = 90_000;
|
|
16
17
|
|
|
17
18
|
const tasks = new Map<string, EcosystemTask>();
|
|
18
19
|
|
|
@@ -47,7 +48,32 @@ function packageActionArgs(action: EcosystemTaskAction, npmName: string): string
|
|
|
47
48
|
return ["exec", "--yes", npmName, "--", "--help"];
|
|
48
49
|
case "runner-presets":
|
|
49
50
|
return ["exec", "--yes", "@tonyclaw/eval-harness", "--", "runner-presets"];
|
|
51
|
+
case "recipe":
|
|
52
|
+
return ["exec", "--yes", npmName, "--", "--help"];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function recipeActionArgs(recipeId: string): string[] | null {
|
|
57
|
+
const execPrefix = ["exec", "--yes", "@tonyclaw/eval-harness", "--"];
|
|
58
|
+
switch (recipeId) {
|
|
59
|
+
case "inspector-mcp-smoke":
|
|
60
|
+
return [...execPrefix, "check-inspector-mcp"];
|
|
61
|
+
case "session-evidence-smoke":
|
|
62
|
+
return [
|
|
63
|
+
...execPrefix,
|
|
64
|
+
"inspector-smoke",
|
|
65
|
+
"--title",
|
|
66
|
+
"TonyClaw Lab smoke",
|
|
67
|
+
"--latest-log-limit",
|
|
68
|
+
"5",
|
|
69
|
+
];
|
|
70
|
+
case "runner-presets-json":
|
|
71
|
+
return [...execPrefix, "runner-presets", "--format", "json"];
|
|
72
|
+
case "local-smoke-batch":
|
|
73
|
+
return null;
|
|
50
74
|
}
|
|
75
|
+
|
|
76
|
+
return null;
|
|
51
77
|
}
|
|
52
78
|
|
|
53
79
|
function packageActionTimeout(action: EcosystemTaskAction): number {
|
|
@@ -58,6 +84,8 @@ function packageActionTimeout(action: EcosystemTaskAction): number {
|
|
|
58
84
|
case "help":
|
|
59
85
|
case "runner-presets":
|
|
60
86
|
return HELP_TIMEOUT_MS;
|
|
87
|
+
case "recipe":
|
|
88
|
+
return RECIPE_TIMEOUT_MS;
|
|
61
89
|
}
|
|
62
90
|
}
|
|
63
91
|
|
|
@@ -101,6 +129,7 @@ export function startEcosystemTask(
|
|
|
101
129
|
id: randomUUID(),
|
|
102
130
|
packageId: definition.id,
|
|
103
131
|
npmName: definition.npmName,
|
|
132
|
+
recipeId: null,
|
|
104
133
|
action,
|
|
105
134
|
status: "queued",
|
|
106
135
|
command: taskCommand(args),
|
|
@@ -151,3 +180,68 @@ export function startEcosystemTask(
|
|
|
151
180
|
|
|
152
181
|
return task;
|
|
153
182
|
}
|
|
183
|
+
|
|
184
|
+
export function startEcosystemRecipeTask(recipeId: string): EcosystemTask | null {
|
|
185
|
+
const recipe = findEcosystemRecipe(recipeId);
|
|
186
|
+
if (recipe === null) return null;
|
|
187
|
+
if (!recipe.runnable) return null;
|
|
188
|
+
const definition = findEcosystemPackage(recipe.packageId);
|
|
189
|
+
if (definition === null) return null;
|
|
190
|
+
const args = recipeActionArgs(recipe.id);
|
|
191
|
+
if (args === null) return null;
|
|
192
|
+
|
|
193
|
+
const task: EcosystemTask = {
|
|
194
|
+
id: randomUUID(),
|
|
195
|
+
packageId: definition.id,
|
|
196
|
+
npmName: definition.npmName,
|
|
197
|
+
recipeId: recipe.id,
|
|
198
|
+
action: "recipe",
|
|
199
|
+
status: "queued",
|
|
200
|
+
command: taskCommand(args),
|
|
201
|
+
output: "",
|
|
202
|
+
createdAt: nowIso(),
|
|
203
|
+
startedAt: null,
|
|
204
|
+
finishedAt: null,
|
|
205
|
+
exitCode: null,
|
|
206
|
+
error: null,
|
|
207
|
+
};
|
|
208
|
+
rememberTask(task);
|
|
209
|
+
|
|
210
|
+
const spawnCommand = npmSpawnCommand(args);
|
|
211
|
+
const child = spawn(spawnCommand.command, spawnCommand.args, {
|
|
212
|
+
windowsHide: true,
|
|
213
|
+
shell: false,
|
|
214
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
215
|
+
});
|
|
216
|
+
const timeout = setTimeout(() => {
|
|
217
|
+
child.kill();
|
|
218
|
+
appendTaskOutput(task, `\nTimed out after ${String(RECIPE_TIMEOUT_MS / 1000)}s.\n`);
|
|
219
|
+
}, RECIPE_TIMEOUT_MS);
|
|
220
|
+
|
|
221
|
+
task.status = "running";
|
|
222
|
+
task.startedAt = nowIso();
|
|
223
|
+
appendTaskOutput(task, `$ ${task.command}\n`);
|
|
224
|
+
|
|
225
|
+
child.stdout.on("data", (chunk: Buffer) => {
|
|
226
|
+
appendTaskOutput(task, chunk.toString("utf8"));
|
|
227
|
+
});
|
|
228
|
+
child.stderr.on("data", (chunk: Buffer) => {
|
|
229
|
+
appendTaskOutput(task, chunk.toString("utf8"));
|
|
230
|
+
});
|
|
231
|
+
child.on("error", (error) => {
|
|
232
|
+
clearTimeout(timeout);
|
|
233
|
+
appendTaskOutput(task, `\n${error.message}\n`);
|
|
234
|
+
completeTask(task, "failed", null, error.message);
|
|
235
|
+
});
|
|
236
|
+
child.on("close", (code) => {
|
|
237
|
+
clearTimeout(timeout);
|
|
238
|
+
if (task.status === "failed") return;
|
|
239
|
+
if (code === 0) {
|
|
240
|
+
completeTask(task, "succeeded", code, null);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
completeTask(task, "failed", code, `Command exited with code ${String(code ?? "unknown")}`);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return task;
|
|
247
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
type CodexClientMetadata = {
|
|
2
|
+
sessionId: string | null;
|
|
3
|
+
threadId: string | null;
|
|
4
|
+
turnId: string | null;
|
|
5
|
+
installationId: string | null;
|
|
6
|
+
windowId: string | null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const EMPTY_CODEX_CLIENT_METADATA: CodexClientMetadata = {
|
|
10
|
+
sessionId: null,
|
|
11
|
+
threadId: null,
|
|
12
|
+
turnId: null,
|
|
13
|
+
installationId: null,
|
|
14
|
+
windowId: null,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function readObjectField(value: unknown, field: string): unknown {
|
|
18
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
19
|
+
return Object.getOwnPropertyDescriptor(value, field)?.value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readStringField(value: unknown, field: string): string | null {
|
|
23
|
+
const fieldValue = readObjectField(value, field);
|
|
24
|
+
return typeof fieldValue === "string" && fieldValue.length > 0 ? fieldValue : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseNestedTurnMetadata(value: unknown): unknown {
|
|
28
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(value);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function extractCodexClientMetadata(rawRequest: unknown): CodexClientMetadata {
|
|
37
|
+
const clientMetadata = readObjectField(rawRequest, "client_metadata");
|
|
38
|
+
if (clientMetadata === undefined) return EMPTY_CODEX_CLIENT_METADATA;
|
|
39
|
+
|
|
40
|
+
const turnMetadata = parseNestedTurnMetadata(
|
|
41
|
+
readObjectField(clientMetadata, "x-codex-turn-metadata"),
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
sessionId:
|
|
46
|
+
readStringField(clientMetadata, "session_id") ?? readStringField(turnMetadata, "session_id"),
|
|
47
|
+
threadId:
|
|
48
|
+
readStringField(clientMetadata, "thread_id") ?? readStringField(turnMetadata, "thread_id"),
|
|
49
|
+
turnId: readStringField(clientMetadata, "turn_id") ?? readStringField(turnMetadata, "turn_id"),
|
|
50
|
+
installationId:
|
|
51
|
+
readStringField(clientMetadata, "x-codex-installation-id") ??
|
|
52
|
+
readStringField(turnMetadata, "installation_id"),
|
|
53
|
+
windowId:
|
|
54
|
+
readStringField(clientMetadata, "x-codex-window-id") ??
|
|
55
|
+
readStringField(turnMetadata, "window_id"),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function extractCodexSessionId(rawRequest: unknown): string | null {
|
|
60
|
+
return extractCodexClientMetadata(rawRequest).sessionId;
|
|
61
|
+
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
parseOpenAIResponse,
|
|
8
8
|
} from "./schemas";
|
|
9
9
|
import { extractOpenAIStream } from "./stream";
|
|
10
|
+
import { extractCodexSessionId } from "./codexMetadata";
|
|
10
11
|
|
|
11
12
|
function emptyTokens(): TokenUsage {
|
|
12
13
|
return {
|
|
@@ -17,6 +18,11 @@ function emptyTokens(): TokenUsage {
|
|
|
17
18
|
};
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
function headerSessionId(headers: Headers | undefined): string | null {
|
|
22
|
+
if (headers === undefined) return null;
|
|
23
|
+
return headers.get("x-agent-inspector-session-id") ?? headers.get("x-session-affinity");
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
export const OpenAIFormatHandler: FormatHandler = {
|
|
21
27
|
format: "openai",
|
|
22
28
|
|
|
@@ -27,14 +33,14 @@ export const OpenAIFormatHandler: FormatHandler = {
|
|
|
27
33
|
if (result.success) {
|
|
28
34
|
return {
|
|
29
35
|
model: result.data.model,
|
|
30
|
-
sessionId:
|
|
36
|
+
sessionId: extractCodexSessionId(json) ?? headerSessionId(headers),
|
|
31
37
|
};
|
|
32
38
|
}
|
|
33
39
|
const responsesResult = OpenAIResponsesRequestSchema.safeParse(json);
|
|
34
40
|
if (responsesResult.success) {
|
|
35
41
|
return {
|
|
36
42
|
model: responsesResult.data.model,
|
|
37
|
-
sessionId:
|
|
43
|
+
sessionId: extractCodexSessionId(json) ?? headerSessionId(headers),
|
|
38
44
|
};
|
|
39
45
|
}
|
|
40
46
|
return null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
-
import { listEcosystemPackages, RUNNER_PRESETS } from "../../proxy/ecosystemRegistry";
|
|
2
|
+
import { LAB_RECIPES, listEcosystemPackages, RUNNER_PRESETS } from "../../proxy/ecosystemRegistry";
|
|
3
3
|
|
|
4
4
|
export const Route = createFileRoute("/api/ecosystem/packages")({
|
|
5
5
|
server: {
|
|
@@ -9,6 +9,7 @@ export const Route = createFileRoute("/api/ecosystem/packages")({
|
|
|
9
9
|
checkedAt: new Date().toISOString(),
|
|
10
10
|
packages: await listEcosystemPackages(),
|
|
11
11
|
runnerPresets: RUNNER_PRESETS,
|
|
12
|
+
recipes: LAB_RECIPES,
|
|
12
13
|
});
|
|
13
14
|
},
|
|
14
15
|
},
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
2
|
+
import { startEcosystemRecipeTask } from "../../proxy/ecosystemTasks";
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute("/api/ecosystem/recipes/$recipeId/run")({
|
|
5
|
+
server: {
|
|
6
|
+
handlers: {
|
|
7
|
+
POST: ({ params }: { params: { recipeId: string } }) => {
|
|
8
|
+
const task = startEcosystemRecipeTask(params.recipeId);
|
|
9
|
+
if (task === null) {
|
|
10
|
+
return Response.json({ error: "Unknown ecosystem recipe" }, { status: 404 });
|
|
11
|
+
}
|
|
12
|
+
return Response.json({ task });
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
});
|
package/styles/globals.css
CHANGED
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
--animate-crab-appear: crab-appear 300ms ease-out;
|
|
45
45
|
--animate-crab-settle: crab-settle 400ms ease-in-out;
|
|
46
46
|
--animate-crab-piano-pop: crab-piano-pop 400ms cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
|
|
47
|
+
--animate-crab-short-leg-body: crab-short-leg-body 720ms ease-in-out infinite;
|
|
48
|
+
--animate-crab-short-leg-step: crab-short-leg-step 360ms ease-in-out infinite;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
:root {
|
|
@@ -165,10 +167,42 @@
|
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
169
|
|
|
170
|
+
@keyframes crab-short-leg-body {
|
|
171
|
+
0%,
|
|
172
|
+
100% {
|
|
173
|
+
transform: translateY(0) rotate(-1deg);
|
|
174
|
+
}
|
|
175
|
+
25% {
|
|
176
|
+
transform: translateY(1px) rotate(1.5deg);
|
|
177
|
+
}
|
|
178
|
+
50% {
|
|
179
|
+
transform: translateY(-1px) rotate(-1.5deg);
|
|
180
|
+
}
|
|
181
|
+
75% {
|
|
182
|
+
transform: translateY(1px) rotate(1deg);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@keyframes crab-short-leg-step {
|
|
187
|
+
0%,
|
|
188
|
+
100% {
|
|
189
|
+
transform: translateY(0) scaleY(0.72) rotate(var(--crab-leg-rest, 0deg));
|
|
190
|
+
opacity: 0.42;
|
|
191
|
+
}
|
|
192
|
+
50% {
|
|
193
|
+
transform: translateY(3px) scaleY(1.15) rotate(var(--crab-leg-step, 0deg));
|
|
194
|
+
opacity: 0.9;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
168
198
|
@media (prefers-reduced-motion: reduce) {
|
|
169
199
|
.animate-crab-piano-pop {
|
|
170
200
|
animation: none !important;
|
|
171
201
|
}
|
|
202
|
+
.floating-crab-body,
|
|
203
|
+
.floating-crab-leg {
|
|
204
|
+
animation: none !important;
|
|
205
|
+
}
|
|
172
206
|
}
|
|
173
207
|
|
|
174
208
|
[data-deep-link-highlight="true"] {
|
|
@@ -205,6 +239,26 @@
|
|
|
205
239
|
}
|
|
206
240
|
|
|
207
241
|
@layer utilities {
|
|
242
|
+
.floating-crab-body {
|
|
243
|
+
animation: var(--animate-crab-short-leg-body);
|
|
244
|
+
transform-origin: 50% 74%;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.floating-crab-leg {
|
|
248
|
+
animation: var(--animate-crab-short-leg-step);
|
|
249
|
+
transform-origin: 50% 0%;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.floating-crab-leg-left {
|
|
253
|
+
--crab-leg-rest: -10deg;
|
|
254
|
+
--crab-leg-step: 14deg;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.floating-crab-leg-right {
|
|
258
|
+
--crab-leg-rest: 10deg;
|
|
259
|
+
--crab-leg-step: -14deg;
|
|
260
|
+
}
|
|
261
|
+
|
|
208
262
|
.inspector-scrollbar {
|
|
209
263
|
scrollbar-color: color-mix(in srgb, var(--muted-foreground) 32%, transparent) transparent;
|
|
210
264
|
scrollbar-width: thin;
|