@canivel/ralph 0.2.1 → 0.3.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/.agents/ralph/PROMPT_build.md +38 -38
- package/.agents/ralph/loop.sh +1265 -1027
- package/.agents/tasks/prd.json +531 -0
- package/.agents/tasks/prd.overview.md +34 -0
- package/README.md +161 -0
- package/bin/ralph +165 -0
- package/dashboard/server/public/assets/index-BsXe-adb.css +1 -0
- package/dashboard/server/public/assets/index-baxd-uax.js +453 -0
- package/dashboard/server/public/index.html +14 -0
- package/package.json +33 -5
- package/AGENTS.md +0 -20
- package/diagram.svg +0 -55
- package/examples/commands.md +0 -46
- package/ralph.webp +0 -0
- package/tests/agent-loops.mjs +0 -79
- package/tests/agent-ping.mjs +0 -39
- package/tests/audit.md +0 -56
- package/tests/cli-smoke.mjs +0 -47
- package/tests/real-agents.mjs +0 -127
package/README.md
CHANGED
|
@@ -8,6 +8,16 @@ Ralph is a minimal, file-based agent loop for autonomous coding. Each iteration
|
|
|
8
8
|
|
|
9
9
|
## What's New in This Fork
|
|
10
10
|
|
|
11
|
+
### Latest Updates
|
|
12
|
+
|
|
13
|
+
- **Web Dashboard** - Real-time monitoring UI for managing multiple projects from a single interface (`ralph dashboard`) -- see [Dashboard](#dashboard)
|
|
14
|
+
- **Automatic Retry** - Transient errors (API timeouts, rate limits) trigger automatic retries with exponential backoff
|
|
15
|
+
- **Story Blocking** - Stories that fail repeatedly are automatically blocked to prevent infinite loops
|
|
16
|
+
- **Git Cleanup** - Failed iterations automatically clean up uncommitted changes
|
|
17
|
+
- **Lean Progress Log** - Detailed run info moved to `.ralph/runs/`, keeping progress.md concise
|
|
18
|
+
|
|
19
|
+
### Original Fork Features
|
|
20
|
+
|
|
11
21
|
- **First-run agent selection** - Prompted to choose your default agent on first use
|
|
12
22
|
- **`ralph config` command** - Reconfigure your default agent anytime
|
|
13
23
|
- **Improved Claude support** - Uses stdin mode (`-p -`) for reliable prompt passing
|
|
@@ -88,6 +98,7 @@ Ralph Configuration
|
|
|
88
98
|
| `ralph overview` | Generate human-readable overview from PRD |
|
|
89
99
|
| `ralph ping` | Health check for agent connectivity |
|
|
90
100
|
| `ralph log "<message>"` | Append to activity log |
|
|
101
|
+
| `ralph dashboard` | Start the Ralph dashboard web server |
|
|
91
102
|
| `ralph help` | Show help message |
|
|
92
103
|
|
|
93
104
|
### Options
|
|
@@ -199,9 +210,70 @@ The build loop automatically updates story status:
|
|
|
199
210
|
| `open` | Available for selection |
|
|
200
211
|
| `in_progress` | Currently being worked on (with `startedAt`) |
|
|
201
212
|
| `done` | Completed (with `completedAt`) |
|
|
213
|
+
| `blocked` | Too many failures (with `blockedReason`) |
|
|
202
214
|
|
|
203
215
|
If a loop crashes while a story is `in_progress`, set `STALE_SECONDS` in config to auto-reopen stalled stories.
|
|
204
216
|
|
|
217
|
+
## Error Handling & Retry
|
|
218
|
+
|
|
219
|
+
Ralph includes robust error handling for transient failures:
|
|
220
|
+
|
|
221
|
+
### Automatic Retry
|
|
222
|
+
|
|
223
|
+
When the agent fails with a transient error (API timeouts, rate limits, connection resets), Ralph automatically retries:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
227
|
+
║ Transient error detected, retrying (1/3)...
|
|
228
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Detected transient errors:
|
|
232
|
+
- "No messages returned" (Claude CLI)
|
|
233
|
+
- Rate limit errors
|
|
234
|
+
- API overload errors
|
|
235
|
+
- Connection resets (ECONNRESET, ETIMEDOUT)
|
|
236
|
+
- Socket hang up
|
|
237
|
+
|
|
238
|
+
### Story Blocking
|
|
239
|
+
|
|
240
|
+
If a story fails repeatedly (default: 3 times), it's automatically marked as `blocked`:
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"id": "US-001",
|
|
245
|
+
"status": "blocked",
|
|
246
|
+
"blockedReason": "Failed 3 times"
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Blocked stories are skipped in subsequent iterations. To retry a blocked story, manually change its status back to `open` in the PRD JSON.
|
|
251
|
+
|
|
252
|
+
### Git Cleanup
|
|
253
|
+
|
|
254
|
+
When an iteration fails, Ralph automatically cleans up uncommitted changes to prevent polluting the next iteration:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# Automatic cleanup on failure
|
|
258
|
+
git checkout -- .
|
|
259
|
+
git clean -fd
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Configuration
|
|
263
|
+
|
|
264
|
+
Control retry behavior in `.agents/ralph/config.sh`:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# Number of retries per iteration for transient errors (default: 3)
|
|
268
|
+
MAX_RETRIES=3
|
|
269
|
+
|
|
270
|
+
# Delay between retries in seconds (default: 5)
|
|
271
|
+
RETRY_DELAY=5
|
|
272
|
+
|
|
273
|
+
# Max failures before blocking a story (default: 3)
|
|
274
|
+
MAX_STORY_FAILURES=3
|
|
275
|
+
```
|
|
276
|
+
|
|
205
277
|
## State Files
|
|
206
278
|
|
|
207
279
|
All state is stored in `.ralph/` in your project:
|
|
@@ -214,6 +286,95 @@ All state is stored in `.ralph/` in your project:
|
|
|
214
286
|
| `errors.log` | Repeated failures and notes |
|
|
215
287
|
| `runs/` | Raw run logs and summaries |
|
|
216
288
|
|
|
289
|
+
## Dashboard
|
|
290
|
+
|
|
291
|
+
Ralph includes a real-time web dashboard for monitoring and managing multiple projects from a single interface. Run several autonomous builds in parallel and track all of them from one place.
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
ralph dashboard --open
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Managing Multiple Projects
|
|
298
|
+
|
|
299
|
+
The dashboard landing page shows every registered project as a card. Each card displays the project name, path, story completion progress, and last activity time. Click any card to drill into that project's full detail view.
|
|
300
|
+
|
|
301
|
+

|
|
302
|
+
|
|
303
|
+
**Adding projects:**
|
|
304
|
+
|
|
305
|
+
There are three ways to register projects with the dashboard:
|
|
306
|
+
|
|
307
|
+
1. **Auto-detect** -- If the current directory contains a `.ralph/` folder, it is registered automatically when you start the dashboard.
|
|
308
|
+
2. **CLI flag** -- Pass multiple project paths at launch:
|
|
309
|
+
```bash
|
|
310
|
+
ralph dashboard --projects ~/project-a,~/project-b,~/project-c
|
|
311
|
+
```
|
|
312
|
+
3. **From the UI** -- Click the **Add Project** button in the top-right corner, browse to a directory that contains a `.ralph/` folder, and add it. Projects can be added at any time without restarting the server.
|
|
313
|
+
|
|
314
|
+
Any directory with a `.ralph/` folder is a valid Ralph project. You can mix and match projects that use different agents or PRDs.
|
|
315
|
+
|
|
316
|
+
### Project Detail View
|
|
317
|
+
|
|
318
|
+
Click a project card to open its detail view. The header shows the project name, status (Idle / Running), path, story progress bar, and run count. Below that, seven tabs give you full visibility into the build.
|
|
319
|
+
|
|
320
|
+

|
|
321
|
+
|
|
322
|
+
| Tab | What It Shows |
|
|
323
|
+
|-----|---------------|
|
|
324
|
+
| **Overview** | Story progress bar, quality gates, recent activity feed, current iteration status |
|
|
325
|
+
| **Stories** | Kanban board with **Open**, **In Progress**, and **Done** columns. Cards show story ID, title, blocked/dependency status, and elapsed time |
|
|
326
|
+
| **Runs** | Timeline of every iteration with duration, success/failure status, and expandable raw output |
|
|
327
|
+
| **Logs** | Browsable activity log, error log, and per-run logs with search and filtering |
|
|
328
|
+
| **Progress** | Rendered `progress.md` with syntax highlighting |
|
|
329
|
+
| **Guardrails** | Rendered `guardrails.md` with color-coded Sign entries |
|
|
330
|
+
| **Metrics** | Charts for completion rate, iteration durations, and success/failure rates over time |
|
|
331
|
+
|
|
332
|
+
### Key Capabilities
|
|
333
|
+
|
|
334
|
+
- **Real-time Updates** -- WebSocket-based live updates as files change on disk; no manual refresh needed
|
|
335
|
+
- **Multi-project Support** -- Switch between projects from the landing page or the sidebar to monitor parallel builds
|
|
336
|
+
- **Story Dependency Tracking** -- Blocked stories show which dependency they are waiting on (e.g. "Waiting on: US-005")
|
|
337
|
+
- **Elapsed Time Tracking** -- In-progress and completed stories display elapsed time
|
|
338
|
+
- **Connection Status** -- A live indicator in the header shows the WebSocket connection state
|
|
339
|
+
- **Dark / Light Mode** -- Toggle between themes
|
|
340
|
+
- **Responsive Design** -- Works on desktop and mobile
|
|
341
|
+
|
|
342
|
+
### Dashboard Options
|
|
343
|
+
|
|
344
|
+
| Option | Description |
|
|
345
|
+
|--------|-------------|
|
|
346
|
+
| `--port <number>` | Port to run the server on (default: 4242) |
|
|
347
|
+
| `--host <hostname>` | Host to bind to (default: localhost) |
|
|
348
|
+
| `--open` | Open browser automatically after starting |
|
|
349
|
+
| `--projects <paths>` | Comma-separated project paths to register |
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
# Examples
|
|
353
|
+
ralph dashboard # default port 4242
|
|
354
|
+
ralph dashboard --port 8080 # custom port
|
|
355
|
+
ralph dashboard --open # auto-open browser
|
|
356
|
+
ralph dashboard --host 0.0.0.0 # expose on all interfaces
|
|
357
|
+
ralph dashboard --projects ~/app-a,~/app-b,~/app-c # register three projects
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Tech Stack
|
|
361
|
+
|
|
362
|
+
- **Frontend**: React 19, TypeScript, Vite, Tailwind CSS, shadcn/ui, Recharts
|
|
363
|
+
- **Backend**: Express, WebSocket (ws), chokidar (file watching)
|
|
364
|
+
- **State**: Zustand for client state management
|
|
365
|
+
|
|
366
|
+
### Running Dashboard Tests
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
# Run all dashboard tests (113 tests)
|
|
370
|
+
npm run test:dashboard
|
|
371
|
+
|
|
372
|
+
# Tests cover:
|
|
373
|
+
# - Data parsers (PRD, activity log, errors log, progress.md, guardrails.md)
|
|
374
|
+
# - Project manager service
|
|
375
|
+
# - API endpoints
|
|
376
|
+
```
|
|
377
|
+
|
|
217
378
|
## Advanced
|
|
218
379
|
|
|
219
380
|
### Multiple PRD Files
|
package/bin/ralph
CHANGED
|
@@ -17,6 +17,10 @@ let installForce = false;
|
|
|
17
17
|
let prdPath = null;
|
|
18
18
|
let progressPath = null;
|
|
19
19
|
let prdOutPath = null;
|
|
20
|
+
let dashboardPort = 4242;
|
|
21
|
+
let dashboardHost = "localhost";
|
|
22
|
+
let dashboardOpen = false;
|
|
23
|
+
let dashboardProjects = null;
|
|
20
24
|
|
|
21
25
|
function exists(p) {
|
|
22
26
|
try {
|
|
@@ -78,6 +82,7 @@ Commands:
|
|
|
78
82
|
build [n] [--no-commit] Run build loop (default)
|
|
79
83
|
overview Render a human overview from PRD JSON
|
|
80
84
|
config Configure default agent
|
|
85
|
+
dashboard Start the Ralph dashboard web server
|
|
81
86
|
help Show this message
|
|
82
87
|
|
|
83
88
|
Options:
|
|
@@ -86,6 +91,12 @@ Options:
|
|
|
86
91
|
--progress <path> Override progress log path
|
|
87
92
|
--agent <codex|claude|droid|opencode> Override agent runner
|
|
88
93
|
|
|
94
|
+
Dashboard options:
|
|
95
|
+
--port <number> Port to run on (default: 4242)
|
|
96
|
+
--host <hostname> Host to bind to (default: localhost)
|
|
97
|
+
--open Open browser after starting
|
|
98
|
+
--projects <paths> Comma-separated project paths to register
|
|
99
|
+
|
|
89
100
|
Notes:
|
|
90
101
|
- Uses local .agents/ralph if present; otherwise uses bundled defaults.
|
|
91
102
|
- State and logs are written to .ralph/ in the project.
|
|
@@ -93,6 +104,43 @@ Notes:
|
|
|
93
104
|
`);
|
|
94
105
|
}
|
|
95
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Show detailed help for the dashboard command
|
|
109
|
+
*/
|
|
110
|
+
function dashboardUsage() {
|
|
111
|
+
console.log(`ralph dashboard - Start the Ralph dashboard web server
|
|
112
|
+
|
|
113
|
+
Usage:
|
|
114
|
+
ralph dashboard [options]
|
|
115
|
+
|
|
116
|
+
Options:
|
|
117
|
+
--port <number> Port to run the server on (default: 4242)
|
|
118
|
+
--host <hostname> Host to bind to (default: localhost)
|
|
119
|
+
--open Open browser automatically after starting
|
|
120
|
+
--projects <paths> Comma-separated project paths to register
|
|
121
|
+
|
|
122
|
+
Description:
|
|
123
|
+
Starts a web-based dashboard for monitoring Ralph projects in real-time.
|
|
124
|
+
The dashboard provides:
|
|
125
|
+
|
|
126
|
+
- Project Overview: See all registered projects with story progress
|
|
127
|
+
- Kanban Board: Visualize stories by status (Open/In Progress/Done)
|
|
128
|
+
- Run History: View all iteration runs with duration and status
|
|
129
|
+
- Log Viewer: Browse activity logs, error logs, and run logs
|
|
130
|
+
- Progress Tracking: View progress.md with collapsible story sections
|
|
131
|
+
- Guardrails: View guardrails.md with highlighted Sign entries
|
|
132
|
+
- Metrics: Charts showing completion rate, iteration durations, and more
|
|
133
|
+
|
|
134
|
+
The current directory is auto-registered if it contains a .ralph/ folder.
|
|
135
|
+
|
|
136
|
+
Examples:
|
|
137
|
+
ralph dashboard # Start on port 4242
|
|
138
|
+
ralph dashboard --port 8080 # Start on custom port
|
|
139
|
+
ralph dashboard --open # Start and open browser
|
|
140
|
+
ralph dashboard --projects /a,/b # Register multiple projects
|
|
141
|
+
`);
|
|
142
|
+
}
|
|
143
|
+
|
|
96
144
|
function getGlobalConfigPath() {
|
|
97
145
|
return path.join(os.homedir(), ".ralph", "config.json");
|
|
98
146
|
}
|
|
@@ -248,6 +296,37 @@ for (let i = 0; i < rawArgs.length; i += 1) {
|
|
|
248
296
|
i += 1;
|
|
249
297
|
continue;
|
|
250
298
|
}
|
|
299
|
+
if (arg.startsWith("--port=")) {
|
|
300
|
+
dashboardPort = parseInt(arg.split("=").slice(1).join("="), 10);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (arg === "--port") {
|
|
304
|
+
dashboardPort = parseInt(rawArgs[i + 1], 10);
|
|
305
|
+
i += 1;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (arg.startsWith("--host=")) {
|
|
309
|
+
dashboardHost = arg.split("=").slice(1).join("=");
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (arg === "--host") {
|
|
313
|
+
dashboardHost = rawArgs[i + 1];
|
|
314
|
+
i += 1;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (arg === "--open") {
|
|
318
|
+
dashboardOpen = true;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (arg.startsWith("--projects=")) {
|
|
322
|
+
dashboardProjects = arg.split("=").slice(1).join("=");
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (arg === "--projects") {
|
|
326
|
+
dashboardProjects = rawArgs[i + 1];
|
|
327
|
+
i += 1;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
251
330
|
args.push(arg);
|
|
252
331
|
}
|
|
253
332
|
|
|
@@ -577,6 +656,92 @@ async function main() {
|
|
|
577
656
|
process.exit(0);
|
|
578
657
|
}
|
|
579
658
|
|
|
659
|
+
if (cmd === "dashboard") {
|
|
660
|
+
// Handle dashboard --help
|
|
661
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
662
|
+
dashboardUsage();
|
|
663
|
+
process.exit(0);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const dashboardServerPath = path.join(repoRoot, "dashboard", "server", "dist", "index.js");
|
|
667
|
+
const projectManagerPath = path.join(repoRoot, "dashboard", "server", "dist", "services", "projectManager.js");
|
|
668
|
+
|
|
669
|
+
if (!exists(dashboardServerPath)) {
|
|
670
|
+
console.error("Dashboard server not found. Run 'npm run build:dashboard:server' first.");
|
|
671
|
+
process.exit(1);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Set environment variable for the server to find client dist
|
|
675
|
+
process.env.RALPH_REPO_ROOT = repoRoot;
|
|
676
|
+
|
|
677
|
+
const { startServer, shutdown, server } = require(dashboardServerPath);
|
|
678
|
+
const { registerProject, isValidRalphProject } = require(projectManagerPath);
|
|
679
|
+
|
|
680
|
+
// Auto-register cwd if it's a Ralph project (.ralph/ exists)
|
|
681
|
+
const cwdRalphDir = path.join(cwd, ".ralph");
|
|
682
|
+
if (exists(cwdRalphDir)) {
|
|
683
|
+
registerProject(cwd).then((result) => {
|
|
684
|
+
if (result.success) {
|
|
685
|
+
console.log(`Auto-registered current directory: ${cwd}`);
|
|
686
|
+
}
|
|
687
|
+
// If already registered, that's fine - no error message needed
|
|
688
|
+
}).catch(() => {
|
|
689
|
+
// Ignore registration errors during startup
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Register projects from --projects option
|
|
694
|
+
if (dashboardProjects) {
|
|
695
|
+
const projectPaths = dashboardProjects.split(",").map((p) => p.trim()).filter(Boolean);
|
|
696
|
+
for (const projectPath of projectPaths) {
|
|
697
|
+
const absolutePath = path.isAbsolute(projectPath) ? projectPath : path.resolve(cwd, projectPath);
|
|
698
|
+
registerProject(absolutePath).then((result) => {
|
|
699
|
+
if (result.success) {
|
|
700
|
+
console.log(`Registered project: ${absolutePath}`);
|
|
701
|
+
} else {
|
|
702
|
+
console.warn(`Failed to register project: ${result.error}`);
|
|
703
|
+
}
|
|
704
|
+
}).catch((err) => {
|
|
705
|
+
console.warn(`Failed to register project ${absolutePath}: ${err.message}`);
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Start the server
|
|
711
|
+
const port = dashboardPort || 4242;
|
|
712
|
+
const host = dashboardHost || "localhost";
|
|
713
|
+
const serverUrl = `http://${host}:${port}`;
|
|
714
|
+
|
|
715
|
+
server.listen(port, host, () => {
|
|
716
|
+
console.log(`Ralph Dashboard server running at ${serverUrl}`);
|
|
717
|
+
console.log("Press Ctrl+C to stop the server.");
|
|
718
|
+
|
|
719
|
+
// Open browser if --open flag is set
|
|
720
|
+
if (dashboardOpen) {
|
|
721
|
+
const openCommand =
|
|
722
|
+
process.platform === "darwin" ? "open" :
|
|
723
|
+
process.platform === "win32" ? "start" : "xdg-open";
|
|
724
|
+
spawnSync(openCommand, [serverUrl], { shell: true, stdio: "ignore" });
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
// Handle graceful shutdown on SIGINT
|
|
729
|
+
process.on("SIGINT", () => {
|
|
730
|
+
console.log("\nShutting down gracefully...");
|
|
731
|
+
shutdown();
|
|
732
|
+
process.exit(0);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
process.on("SIGTERM", () => {
|
|
736
|
+
console.log("Received SIGTERM, shutting down...");
|
|
737
|
+
shutdown();
|
|
738
|
+
process.exit(0);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
// Keep the process running
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
|
|
580
745
|
if (cmd === "prd") {
|
|
581
746
|
let request = args.slice(1).join(" ").trim();
|
|
582
747
|
if (!request) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--background: 0 0% 100%;--foreground: 222.2 84% 4.9%;--card: 0 0% 100%;--card-foreground: 222.2 84% 4.9%;--popover: 0 0% 100%;--popover-foreground: 222.2 84% 4.9%;--primary: 222.2 47.4% 11.2%;--primary-foreground: 210 40% 98%;--secondary: 210 40% 96.1%;--secondary-foreground: 222.2 47.4% 11.2%;--muted: 210 40% 96.1%;--muted-foreground: 215.4 16.3% 46.9%;--accent: 210 40% 96.1%;--accent-foreground: 222.2 47.4% 11.2%;--destructive: 0 84.2% 60.2%;--destructive-foreground: 210 40% 98%;--border: 214.3 31.8% 91.4%;--input: 214.3 31.8% 91.4%;--ring: 222.2 84% 4.9%;--radius: .5rem}.dark{--background: 222.2 84% 4.9%;--foreground: 210 40% 98%;--card: 222.2 84% 4.9%;--card-foreground: 210 40% 98%;--popover: 222.2 84% 4.9%;--popover-foreground: 210 40% 98%;--primary: 210 40% 98%;--primary-foreground: 222.2 47.4% 11.2%;--secondary: 217.2 32.6% 17.5%;--secondary-foreground: 210 40% 98%;--muted: 217.2 32.6% 17.5%;--muted-foreground: 215 20.2% 65.1%;--accent: 217.2 32.6% 17.5%;--accent-foreground: 210 40% 98%;--destructive: 0 62.8% 30.6%;--destructive-foreground: 210 40% 98%;--border: 217.2 32.6% 17.5%;--input: 217.2 32.6% 17.5%;--ring: 212.7 26.8% 83.9%}*{border-color:hsl(var(--border))}body{background-color:hsl(var(--background));color:hsl(var(--foreground))}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.bottom-0{bottom:0}.left-0{left:0}.left-3{left:.75rem}.left-\[50\%\]{left:50%}.right-0{right:0}.right-4{right:1rem}.top-0{top:0}.top-1\/2{top:50%}.top-4{top:1rem}.top-\[50\%\]{top:50%}.top-full{top:100%}.z-40{z-index:40}.z-50{z-index:50}.col-span-full{grid-column:1 / -1}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.my-4{margin-top:1rem;margin-bottom:1rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-1\.5{margin-top:.375rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.line-clamp-2{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[200px\]{height:200px}.h-\[280px\]{height:280px}.h-full{height:100%}.h-screen{height:100vh}.max-h-64{max-height:16rem}.max-h-80{max-height:20rem}.max-h-\[600px\]{max-height:600px}.max-h-\[80vh\]{max-height:80vh}.max-h-\[calc\(100vh-300px\)\]{max-height:calc(100vh - 300px)}.min-h-\[400px\]{min-height:400px}.w-1\.5{width:.375rem}.w-10{width:2.5rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4{width:1rem}.w-40{width:10rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-8{width:2rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[120px\]{min-width:120px}.max-w-2xl{max-width:42rem}.max-w-\[200px\]{max-width:200px}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-none{max-width:none}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-y-1\/2{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-\[-50\%\]{--tw-translate-x: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-\[-50\%\]{--tw-translate-y: -50%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes ping{75%,to{transform:scale(2);opacity:0}}.animate-ping{animation:ping 1s cubic-bezier(0,0,.2,1) infinite}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.list-decimal{list-style-type:decimal}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-\[auto_1fr_auto_auto_auto_auto\]{grid-template-columns:auto 1fr auto auto auto auto}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(0px * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.375rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-b-lg{border-bottom-right-radius:var(--radius);border-bottom-left-radius:var(--radius)}.rounded-t-lg{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-500\/30{border-color:#f59e0b4d}.border-blue-500\/30{border-color:#3b82f64d}.border-blue-500\/50{border-color:#3b82f680}.border-border{border-color:hsl(var(--border))}.border-destructive\/50{border-color:hsl(var(--destructive) / .5)}.border-input{border-color:hsl(var(--input))}.border-primary\/20{border-color:hsl(var(--primary) / .2)}.border-primary\/50{border-color:hsl(var(--primary) / .5)}.border-yellow-500\/30{border-color:#eab3084d}.border-l-blue-500{--tw-border-opacity: 1;border-left-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-l-gray-400{--tw-border-opacity: 1;border-left-color:rgb(156 163 175 / var(--tw-border-opacity, 1))}.border-l-green-500{--tw-border-opacity: 1;border-left-color:rgb(34 197 94 / var(--tw-border-opacity, 1))}.border-l-red-500{--tw-border-opacity: 1;border-left-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-l-transparent{border-left-color:transparent}.border-l-yellow-500{--tw-border-opacity: 1;border-left-color:rgb(234 179 8 / var(--tw-border-opacity, 1))}.border-t-transparent{border-top-color:transparent}.bg-accent{background-color:hsl(var(--accent))}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-background{background-color:hsl(var(--background))}.bg-black\/80{background-color:#000c}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-blue-500\/10{background-color:#3b82f61a}.bg-blue-500\/5{background-color:#3b82f60d}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-destructive\/10{background-color:hsl(var(--destructive) / .1)}.bg-destructive\/5{background-color:hsl(var(--destructive) / .05)}.bg-gray-400{--tw-bg-opacity: 1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(107 114 128 / var(--tw-bg-opacity, 1))}.bg-gray-500\/10{background-color:#6b72801a}.bg-green-400{--tw-bg-opacity: 1;background-color:rgb(74 222 128 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-green-500\/10{background-color:#22c55e1a}.bg-green-500\/5{background-color:#22c55e0d}.bg-muted{background-color:hsl(var(--muted))}.bg-muted\/10{background-color:hsl(var(--muted) / .1)}.bg-muted\/20{background-color:hsl(var(--muted) / .2)}.bg-muted\/30{background-color:hsl(var(--muted) / .3)}.bg-muted\/50{background-color:hsl(var(--muted) / .5)}.bg-popover{background-color:hsl(var(--popover))}.bg-primary{background-color:hsl(var(--primary))}.bg-primary\/5{background-color:hsl(var(--primary) / .05)}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-500\/5{background-color:#ef44440d}.bg-secondary{background-color:hsl(var(--secondary))}.bg-yellow-300{--tw-bg-opacity: 1;background-color:rgb(253 224 71 / var(--tw-bg-opacity, 1))}.bg-yellow-500\/10{background-color:#eab3081a}.stroke-muted{stroke:hsl(var(--muted))}.p-0{padding:0}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[1px\]{padding:1px}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-20{padding-top:5rem;padding-bottom:5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pl-4{padding-left:1rem}.pl-6{padding-left:1.5rem}.pl-7{padding-left:1.75rem}.pl-9{padding-left:2.25rem}.pr-2{padding-right:.5rem}.pt-0{padding-top:0}.pt-0\.5{padding-top:.125rem}.pt-2{padding-top:.5rem}.pt-4{padding-top:1rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.capitalize{text-transform:capitalize}.italic{font-style:italic}.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)}.leading-none{line-height:1}.leading-tight{line-height:1.25}.tracking-tight{letter-spacing:-.025em}.text-accent-foreground{color:hsl(var(--accent-foreground))}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-amber-600{--tw-text-opacity: 1;color:rgb(217 119 6 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-destructive\/50{color:hsl(var(--destructive) / .5)}.text-foreground{color:hsl(var(--foreground))}.text-gray-500{--tw-text-opacity: 1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity: 1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-muted-foreground\/30{color:hsl(var(--muted-foreground) / .3)}.text-muted-foreground\/50{color:hsl(var(--muted-foreground) / .5)}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-red-500\/50{color:#ef444480}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.text-yellow-600{--tw-text-opacity: 1;color:rgb(202 138 4 / var(--tw-text-opacity, 1))}.underline-offset-4{text-underline-offset:4px}.opacity-70{opacity:.7}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.ring-offset-background{--tw-ring-offset-color: hsl(var(--background))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.file\:border-0::file-selector-button{border-width:0px}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.first\:mt-0:first-child{margin-top:0}.last\:mb-0:last-child{margin-bottom:0}.last\:border-b-0:last-child{border-bottom-width:0px}.hover\:border-primary\/50:hover{border-color:hsl(var(--primary) / .5)}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}.hover\:bg-muted\/50:hover{background-color:hsl(var(--muted) / .5)}.hover\:bg-primary\/10:hover{background-color:hsl(var(--primary) / .1)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary) / .9)}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary) / .8)}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-md:hover{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-ring:focus{--tw-ring-color: hsl(var(--ring))}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: hsl(var(--ring))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group.toaster .group-\[\.toaster\]\:border-border{border-color:hsl(var(--border))}.group.toast .group-\[\.toast\]\:bg-muted{background-color:hsl(var(--muted))}.group.toast .group-\[\.toast\]\:bg-primary{background-color:hsl(var(--primary))}.group.toaster .group-\[\.toaster\]\:bg-background{background-color:hsl(var(--background))}.group.toast .group-\[\.toast\]\:text-muted-foreground{color:hsl(var(--muted-foreground))}.group.toast .group-\[\.toast\]\:text-primary-foreground{color:hsl(var(--primary-foreground))}.group.toaster .group-\[\.toaster\]\:text-foreground{color:hsl(var(--foreground))}.group.toaster .group-\[\.toaster\]\:shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.data-\[state\=active\]\:bg-background[data-state=active]{background-color:hsl(var(--background))}.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:hsl(var(--accent))}.data-\[state\=active\]\:text-foreground[data-state=active]{color:hsl(var(--foreground))}.data-\[state\=open\]\:text-muted-foreground[data-state=open]{color:hsl(var(--muted-foreground))}.data-\[state\=active\]\:shadow-sm[data-state=active]{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark\:bg-yellow-700:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(161 98 7 / var(--tw-bg-opacity, 1))}.dark\:text-amber-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.dark\:text-blue-400:is(.dark *){--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:text-gray-400:is(.dark *){--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.dark\:text-green-400:is(.dark *){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.dark\:text-red-400:is(.dark *){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.dark\:text-yellow-400:is(.dark *){--tw-text-opacity: 1;color:rgb(250 204 21 / var(--tw-text-opacity, 1))}@media (min-width: 640px){.sm\:block{display:block}.sm\:max-w-xs{max-width:20rem}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:justify-end{justify-content:flex-end}.sm\:space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.sm\:rounded-lg{border-radius:var(--radius)}.sm\:text-left{text-align:left}}@media (min-width: 768px){.md\:block{display:block}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}
|