@portkey-ai/hoot 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/README.md +42 -232
- package/bin/hoot.js +6 -6
- package/dist/assets/index-B5fBcVNv.css +1 -0
- package/dist/assets/index-D-Gxc6zl.js +179 -0
- package/dist/index.html +2 -2
- package/mcp-backend-server.js +17 -6
- package/package.json +1 -4
- package/dist/assets/index-CKVADtm3.js +0 -175
- package/dist/assets/index-DoH7MPA9.css +0 -1
package/README.md
CHANGED
|
@@ -1,277 +1,87 @@
|
|
|
1
|
-
# 🦉
|
|
1
|
+
# 🦉 hoot
|
|
2
|
+
> **⚠️ early beta** - things might break. if they do, [open an issue](https://github.com/Portkey-AI/hoot/issues).
|
|
3
|
+
>
|
|
4
|
+
> **🤝 contributions welcome** - see something that could be better? PRs are appreciated!
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
testing tool for MCP servers. like postman but for MCP.
|
|
4
7
|
|
|
5
|
-
##
|
|
8
|
+
## why
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
- 🔐 **Full OAuth 2.1 Support** - PKCE, automatic token refresh, secure storage
|
|
9
|
-
- 🌐 **No CORS Issues** - Backend relay architecture eliminates CORS problems
|
|
10
|
-
- 🎨 **Beautiful UI** - Ayu Mirage theme, smooth animations
|
|
11
|
-
- 📋 **Tool Testing** - Execute tools, view results, copy responses
|
|
12
|
-
- 💾 **Persistent Storage** - Servers, tools, and history cached locally
|
|
13
|
-
- 🐛 **Dev Logger** - Download console logs for debugging
|
|
14
|
-
- ⚡ **Real-time Feedback** - Live execution timer, toast notifications
|
|
15
|
-
- 🎯 **Smart UX** - Empty states, error boundaries, loading skeletons
|
|
10
|
+
needed a quick way to test MCP servers without spinning up a whole AI chat interface.
|
|
16
11
|
|
|
17
|
-
##
|
|
12
|
+
## install
|
|
18
13
|
|
|
19
|
-
### Run with npx (Recommended)
|
|
20
14
|
```bash
|
|
21
15
|
npx -y @portkey-ai/hoot
|
|
22
16
|
```
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
- ✅ Install Hoot if not already installed
|
|
26
|
-
- ✅ Start the backend server on `http://localhost:3002`
|
|
27
|
-
- ✅ Start the frontend UI on `http://localhost:5173`
|
|
28
|
-
- ✅ Open your browser automatically
|
|
18
|
+
that's it. opens on localhost:8009
|
|
29
19
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
### Install Globally (Alternative)
|
|
20
|
+
or install globally if you want:
|
|
33
21
|
```bash
|
|
34
22
|
npm install -g @portkey-ai/hoot
|
|
35
23
|
hoot
|
|
36
24
|
```
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
If you want to contribute or develop locally:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
# Clone the repository
|
|
43
|
-
git clone https://github.com/yourusername/hoot.git
|
|
44
|
-
cd hoot
|
|
45
|
-
|
|
46
|
-
# Install dependencies
|
|
47
|
-
npm install
|
|
48
|
-
|
|
49
|
-
# Start both backend and frontend
|
|
50
|
-
npm run dev:full
|
|
51
|
-
|
|
52
|
-
# Or start them separately
|
|
53
|
-
npm run backend # Terminal 1
|
|
54
|
-
npm run dev # Terminal 2
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
This starts:
|
|
58
|
-
- **Backend MCP Server** on `http://localhost:3002` (handles MCP connections)
|
|
59
|
-
- **Hoot UI** on `http://localhost:5173` (Vite dev server)
|
|
60
|
-
|
|
61
|
-
## 📖 Usage
|
|
26
|
+
## what works
|
|
62
27
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- Auth: None / Headers / OAuth
|
|
70
|
-
4. **Connect** - Server appears in sidebar with tool count
|
|
71
|
-
5. **Select Tool** - Click any tool to test it
|
|
72
|
-
6. **Execute** - Fill params, click "EXECUTE TOOL"
|
|
73
|
-
7. **View Results** - Response, Raw JSON, or Request tabs
|
|
28
|
+
- connect to MCP servers (http/sse)
|
|
29
|
+
- see what tools they have
|
|
30
|
+
- execute tools with params
|
|
31
|
+
- view responses
|
|
32
|
+
- oauth 2.1 if your server needs it
|
|
33
|
+
- copy stuff to clipboard
|
|
74
34
|
|
|
75
|
-
##
|
|
35
|
+
## how it works
|
|
76
36
|
|
|
77
|
-
|
|
37
|
+
runs a node.js backend that connects to MCP servers (because CORS is annoying). react frontend talks to the backend over localhost.
|
|
78
38
|
|
|
79
39
|
```
|
|
80
|
-
|
|
81
|
-
(UI) (MCP Client) (External)
|
|
40
|
+
browser → backend → mcp servers
|
|
82
41
|
```
|
|
83
42
|
|
|
84
|
-
|
|
85
|
-
- **Backend**: Node.js server with MCP SDK (handles actual connections)
|
|
86
|
-
- **Communication**: REST API over localhost (no CORS issues)
|
|
43
|
+
no cors issues. backend handles oauth tokens in sqlite.
|
|
87
44
|
|
|
88
|
-
|
|
45
|
+
### persistence
|
|
89
46
|
|
|
90
|
-
|
|
47
|
+
- **server configs & tools**: saved in browser localStorage (survives page refreshes)
|
|
48
|
+
- **oauth tokens**: stored in `~/.hoot/hoot-mcp.db` (persists across npx runs)
|
|
91
49
|
|
|
92
|
-
|
|
50
|
+
your servers stay configured between sessions, even when running with `npx`!
|
|
93
51
|
|
|
94
|
-
|
|
95
|
-
- ✅ Works with any MCP server (no CORS configuration needed)
|
|
96
|
-
- ✅ More secure (credentials stay on backend)
|
|
97
|
-
- ✅ Better performance (persistent connections)
|
|
98
|
-
- ✅ Full OAuth 2.1 support maintained
|
|
99
|
-
|
|
100
|
-
**Old CORS Proxy (deprecated)**: The old proxy method is still available via `npm run dev:with-proxy`, but the backend relay is now the recommended approach.
|
|
101
|
-
|
|
102
|
-
**See [docs/BACKEND_ARCHITECTURE.md](./docs/BACKEND_ARCHITECTURE.md) for detailed architecture documentation.**
|
|
103
|
-
|
|
104
|
-
## 🔐 OAuth 2.1 Support
|
|
105
|
-
|
|
106
|
-
Hoot supports full OAuth 2.1 authorization flow:
|
|
107
|
-
|
|
108
|
-
- ✅ Authorization redirect with PKCE
|
|
109
|
-
- ✅ Automatic token exchange
|
|
110
|
-
- ✅ Token refresh
|
|
111
|
-
- ✅ Secure storage (SQLite database)
|
|
112
|
-
- ✅ Multiple servers with different auth
|
|
113
|
-
|
|
114
|
-
**See [docs/AUTHENTICATION.md](./docs/AUTHENTICATION.md) for details.**
|
|
115
|
-
|
|
116
|
-
## 🎨 UI Features
|
|
117
|
-
|
|
118
|
-
### Toasts
|
|
119
|
-
- Connection errors
|
|
120
|
-
- Execution errors
|
|
121
|
-
- Proxy status changes
|
|
122
|
-
- Copy failures
|
|
123
|
-
|
|
124
|
-
### Empty States
|
|
125
|
-
- No servers: "Add your first server"
|
|
126
|
-
- No tools: "Server doesn't expose tools"
|
|
127
|
-
- No tool selected: "Select a tool to test"
|
|
128
|
-
|
|
129
|
-
### Live Feedback
|
|
130
|
-
- **Execute button timer**: Shows elapsed time during execution
|
|
131
|
-
- **Spinning icons**: Refresh actions
|
|
132
|
-
- **Copy buttons**: One-click copy with checkmark feedback
|
|
133
|
-
- **Status dots**: Green (connected) / Gray (disconnected)
|
|
134
|
-
|
|
135
|
-
### Dev Logger
|
|
136
|
-
```javascript
|
|
137
|
-
// In browser console:
|
|
138
|
-
hootLogger.download() // Download logs to file
|
|
139
|
-
hootLogger.get() // View in console
|
|
140
|
-
hootLogger.clear() // Clear logs
|
|
141
|
-
hootLogger.count() // Get log count
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## 📂 Project Structure
|
|
145
|
-
|
|
146
|
-
```
|
|
147
|
-
hoot/
|
|
148
|
-
├── bin/
|
|
149
|
-
│ └── hoot.js # CLI entry point
|
|
150
|
-
├── src/
|
|
151
|
-
│ ├── components/ # React components
|
|
152
|
-
│ │ ├── ServerSidebar.tsx
|
|
153
|
-
│ │ ├── ToolsSidebar.tsx
|
|
154
|
-
│ │ ├── MainArea.tsx
|
|
155
|
-
│ │ └── ...
|
|
156
|
-
│ ├── stores/ # Zustand state management
|
|
157
|
-
│ │ ├── appStore.ts
|
|
158
|
-
│ │ └── toastStore.ts
|
|
159
|
-
│ ├── hooks/ # Custom React hooks
|
|
160
|
-
│ │ ├── useMCP.ts
|
|
161
|
-
│ │ └── useAutoReconnect.ts
|
|
162
|
-
│ ├── lib/ # Core libraries
|
|
163
|
-
│ │ ├── mcpClient.ts # MCP SDK wrapper
|
|
164
|
-
│ │ ├── backendClient.ts # Backend API client
|
|
165
|
-
│ │ ├── oauthProvider.ts # OAuth implementation
|
|
166
|
-
│ │ └── logger.ts # Dev logger
|
|
167
|
-
│ └── types/ # TypeScript types
|
|
168
|
-
├── docs/ # Documentation
|
|
169
|
-
│ ├── ARCHITECTURE.md
|
|
170
|
-
│ ├── AUTHENTICATION.md
|
|
171
|
-
│ ├── BACKEND_ARCHITECTURE.md
|
|
172
|
-
│ └── ...
|
|
173
|
-
├── mcp-backend-server.js # Backend MCP relay server
|
|
174
|
-
└── package.json
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## 📚 Documentation
|
|
178
|
-
|
|
179
|
-
- [Architecture Overview](./docs/ARCHITECTURE.md)
|
|
180
|
-
- [Backend Architecture](./docs/BACKEND_ARCHITECTURE.md)
|
|
181
|
-
- [Authentication & OAuth](./docs/AUTHENTICATION.md)
|
|
182
|
-
- [Design System](./docs/DESIGN_HOOT.md)
|
|
183
|
-
- [Quick Start Guide](./docs/QUICKSTART.md)
|
|
184
|
-
- [Troubleshooting](./docs/TROUBLESHOOTING.md)
|
|
185
|
-
- [Full Documentation Index](./docs/README.md)
|
|
186
|
-
|
|
187
|
-
## 🛠️ npm Scripts
|
|
52
|
+
## running from source
|
|
188
53
|
|
|
189
54
|
```bash
|
|
190
|
-
|
|
191
|
-
npm
|
|
192
|
-
npm run dev
|
|
193
|
-
npm run backend # Start backend server only
|
|
194
|
-
npm run dev:full # Start both (concurrently)
|
|
195
|
-
npm run build # Build for production
|
|
196
|
-
npm run preview # Preview production build
|
|
55
|
+
git clone <repo>
|
|
56
|
+
npm install
|
|
57
|
+
npm run dev:full
|
|
197
58
|
```
|
|
198
59
|
|
|
199
|
-
|
|
60
|
+
backend runs on 8008, frontend on 8009.
|
|
200
61
|
|
|
201
|
-
|
|
202
|
-
- **TypeScript** - Type safety
|
|
203
|
-
- **Vite** - Build tool
|
|
204
|
-
- **Zustand** - State management
|
|
205
|
-
- **MCP SDK** - Model Context Protocol
|
|
206
|
-
- **Express** - Proxy server
|
|
207
|
-
- **Lucide React** - Icons
|
|
62
|
+
## debugging
|
|
208
63
|
|
|
209
|
-
|
|
64
|
+
there's a logger in the console:
|
|
210
65
|
|
|
211
|
-
- Chrome/Edge 90+
|
|
212
|
-
- Firefox 88+
|
|
213
|
-
- Safari 14+
|
|
214
|
-
|
|
215
|
-
## 🐛 Debugging
|
|
216
|
-
|
|
217
|
-
### Enable Dev Logger
|
|
218
|
-
Automatically enabled in development. Access via browser console:
|
|
219
66
|
```javascript
|
|
220
|
-
hootLogger.download()
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### Check Proxy Status
|
|
224
|
-
```bash
|
|
225
|
-
curl http://localhost:3001/health
|
|
67
|
+
hootLogger.download() // get logs
|
|
226
68
|
```
|
|
227
69
|
|
|
228
|
-
|
|
229
|
-
All MCP operations are logged with emojis:
|
|
230
|
-
- 🦉 Proxy server
|
|
231
|
-
- 🔧 Transport creation
|
|
232
|
-
- 🔌 Connections
|
|
233
|
-
- 🔐 OAuth flows
|
|
234
|
-
- ✓ Success
|
|
235
|
-
- ❌ Errors
|
|
70
|
+
## what's missing
|
|
236
71
|
|
|
237
|
-
|
|
72
|
+
- resources (coming)
|
|
73
|
+
- prompts (coming)
|
|
74
|
+
- keyboard shortcuts (maybe)
|
|
75
|
+
- tests (oops)
|
|
238
76
|
|
|
239
|
-
|
|
240
|
-
- [x] Full OAuth 2.1 support
|
|
241
|
-
- [x] CORS proxy
|
|
242
|
-
- [x] UI polish (toasts, empty states, copy buttons)
|
|
243
|
-
- [x] Live execution timer
|
|
244
|
-
- [x] Dev logger
|
|
77
|
+
## tech
|
|
245
78
|
|
|
246
|
-
|
|
247
|
-
- [ ] Resource testing UI
|
|
248
|
-
- [ ] Prompt testing UI
|
|
249
|
-
- [ ] Keyboard shortcuts (Cmd+K, Cmd+E)
|
|
250
|
-
- [ ] Browser extension (CORS bypass)
|
|
79
|
+
react 19, typescript, vite, zustand, express, MCP SDK
|
|
251
80
|
|
|
252
|
-
|
|
253
|
-
- [ ] Electron app (stdio transport support)
|
|
254
|
-
- [ ] Encrypted credential storage
|
|
255
|
-
- [ ] Advanced testing features
|
|
256
|
-
|
|
257
|
-
## 🤝 Contributing
|
|
258
|
-
|
|
259
|
-
Contributions welcome! Please:
|
|
260
|
-
1. Fork the repo
|
|
261
|
-
2. Create a feature branch
|
|
262
|
-
3. Make your changes
|
|
263
|
-
4. Submit a pull request
|
|
264
|
-
|
|
265
|
-
## 📄 License
|
|
81
|
+
## license
|
|
266
82
|
|
|
267
83
|
MIT
|
|
268
84
|
|
|
269
|
-
## 🙏 Acknowledgments
|
|
270
|
-
|
|
271
|
-
- **MCP SDK** - [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
272
|
-
- **Ayu Theme** - Color palette inspiration
|
|
273
|
-
- **Lucide** - Beautiful icons
|
|
274
|
-
|
|
275
85
|
---
|
|
276
86
|
|
|
277
|
-
|
|
87
|
+
made this because i was tired of curl-ing MCP servers. hope it helps.
|
package/bin/hoot.js
CHANGED
|
@@ -42,7 +42,7 @@ process.on('SIGINT', cleanup);
|
|
|
42
42
|
process.on('SIGTERM', cleanup);
|
|
43
43
|
|
|
44
44
|
// Start backend server
|
|
45
|
-
console.log('📡 Starting backend server on port
|
|
45
|
+
console.log('📡 Starting backend server on port 8008...');
|
|
46
46
|
const backend = spawn('node', [join(rootDir, 'mcp-backend-server.js')], {
|
|
47
47
|
cwd: rootDir,
|
|
48
48
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -78,7 +78,7 @@ async function waitForBackend() {
|
|
|
78
78
|
const maxAttempts = 30; // 30 seconds max
|
|
79
79
|
for (let i = 0; i < maxAttempts; i++) {
|
|
80
80
|
try {
|
|
81
|
-
const response = await fetch('http://localhost:
|
|
81
|
+
const response = await fetch('http://localhost:8008/health');
|
|
82
82
|
if (response.ok) {
|
|
83
83
|
return true;
|
|
84
84
|
}
|
|
@@ -107,11 +107,11 @@ setTimeout(async () => {
|
|
|
107
107
|
const srcExists = existsSync(join(rootDir, 'src'));
|
|
108
108
|
const mode = srcExists ? 'development' : 'production';
|
|
109
109
|
|
|
110
|
-
console.log(`🌐 Starting frontend in ${mode} mode on port
|
|
110
|
+
console.log(`🌐 Starting frontend in ${mode} mode on port 8009...`);
|
|
111
111
|
|
|
112
112
|
// Start vite - use dev mode if src exists, preview mode if using built dist/
|
|
113
113
|
const viteCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
114
|
-
const viteArgs = srcExists ? ['vite', '--open'] : ['vite', 'preview', '--open', '--port', '
|
|
114
|
+
const viteArgs = srcExists ? ['vite', '--open'] : ['vite', 'preview', '--open', '--port', '8009'];
|
|
115
115
|
|
|
116
116
|
const frontend = spawn(viteCommand, viteArgs, {
|
|
117
117
|
cwd: rootDir,
|
|
@@ -149,12 +149,12 @@ setTimeout(async () => {
|
|
|
149
149
|
|
|
150
150
|
// Wait a bit for frontend to fully start, then show success message
|
|
151
151
|
setTimeout(() => {
|
|
152
|
-
const port = srcExists ?
|
|
152
|
+
const port = srcExists ? 8009 : 8010;
|
|
153
153
|
console.log(`
|
|
154
154
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
155
155
|
✅ Hoot is running!
|
|
156
156
|
|
|
157
|
-
Backend: http://localhost:
|
|
157
|
+
Backend: http://localhost:8008
|
|
158
158
|
Frontend: http://localhost:${port}
|
|
159
159
|
|
|
160
160
|
Press Ctrl+C to stop
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap";.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--spacing-3xl) var(--spacing-2xl);text-align:center;min-height:300px}.empty-state-icon{display:inline-flex;align-items:center;justify-content:center;width:96px;height:96px;border-radius:50%;background:var(--bg-tertiary);color:var(--text-tertiary);margin-bottom:var(--spacing-xl);animation:fadeIn .4s ease-out}.empty-state-title{font-size:20px;font-weight:700;color:var(--text-white);margin-bottom:var(--spacing-sm);letter-spacing:-.3px}.empty-state-description{font-size:14px;color:var(--text-secondary);line-height:1.6;max-width:400px;margin-bottom:var(--spacing-xl)}.empty-state .btn{margin-top:var(--spacing-md)}@keyframes fadeIn{0%{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}.server-sidebar{width:260px;background:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column;transform:translateZ(0)}.sidebar-header{padding:var(--spacing-2xl);border-bottom:1px solid var(--border-color);background:var(--bg-primary)}.logo{display:flex;align-items:center;gap:var(--spacing-md);margin-bottom:var(--spacing-sm)}.logo-icon{font-size:28px;line-height:1;animation:pulse 3s ease-in-out infinite}.logo h1{font-size:24px;font-weight:700;color:var(--text-white);letter-spacing:-.5px}.tagline{font-size:11px;color:var(--text-tertiary);text-transform:uppercase;letter-spacing:1px;margin-bottom:var(--spacing-md);font-weight:500}.add-server-btn{width:100%;padding:12px 16px;background:linear-gradient(135deg,var(--blue-500) 0%,var(--blue-600) 100%);color:var(--bg-primary);border:none;border-radius:var(--radius-md);cursor:pointer;font-size:14px;font-weight:600;transition:all var(--transition-fast);display:flex;align-items:center;justify-content:center;gap:var(--spacing-sm);box-shadow:0 2px 8px #5ccfe633}.add-server-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px #5ccfe64d}.add-server-btn:active{transform:translateY(0)}.servers-list{flex:1;overflow-y:auto;padding:var(--spacing-md);-webkit-overflow-scrolling:touch}.server-item{padding:var(--spacing-md);margin-bottom:var(--spacing-sm);background:var(--bg-tertiary);border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-fast);will-change:background;border:1px solid transparent}.server-item:hover{background:var(--bg-hover);border-color:var(--border-bright)}.server-item.active{background:var(--bg-active);border-color:var(--blue-500);box-shadow:0 0 0 1px var(--blue-500) inset}.server-header{display:flex;align-items:center;gap:var(--spacing-sm);margin-bottom:var(--spacing-sm)}.server-footer{display:flex;align-items:center;justify-content:space-between}.server-actions{display:flex;gap:4px;opacity:0;transition:opacity var(--transition-fast)}.server-item:hover .server-actions{opacity:1}.action-btn{background:var(--bg-hover);border:1px solid var(--border-color);border-radius:var(--radius-sm);width:28px;height:28px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all var(--transition-fast);padding:0;color:var(--text-secondary)}.action-btn:hover{transform:scale(1.05)}.edit-btn:hover{background:var(--blue-500);border-color:var(--blue-500);color:var(--bg-primary)}.delete-btn:hover{background:var(--red-500);border-color:var(--red-500);color:var(--bg-primary)}.status-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;position:relative}.status-dot.connected{background:var(--green-500);box-shadow:0 0 8px var(--green-500)}.status-dot.connected:after{content:"";position:absolute;inset:-2px;border-radius:50%;border:1px solid var(--green-500);opacity:.3;animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.status-dot.disconnected{background:var(--red-500);opacity:.6}.server-name{font-weight:500;font-size:14px;color:var(--text-white);flex-grow:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tool-count{background:var(--bg-hover);padding:2px 8px;border-radius:10px;font-size:11px;color:var(--orange-500);font-weight:600;font-family:var(--font-mono)}.server-meta{display:flex;align-items:center;gap:var(--spacing-sm)}.transport-badge{font-size:10px;padding:2px 7px;border-radius:10px;font-family:var(--font-mono);font-weight:600;letter-spacing:.3px;display:inline-flex;align-items:center;gap:4px;line-height:1}.transport-badge[data-transport=http]{background:#5ccfe626;color:var(--ayu-accent-cyan);border:1px solid rgba(92,207,230,.3)}.transport-badge[data-transport=sse]{background:#73d0ff26;color:var(--ayu-accent-blue);border:1px solid rgba(115,208,255,.3)}.transport-badge[data-transport=stdio]{background:#bae67e26;color:var(--ayu-accent-green);border:1px solid rgba(186,230,126,.3)}.auth-badge{font-size:10px;padding:2px 7px;border-radius:10px;font-family:var(--font-mono);font-weight:600;letter-spacing:.3px;display:inline-flex;align-items:center;gap:4px;line-height:1}.auth-badge[data-auth-type=headers]{background:#ffd17326;color:var(--ayu-accent-yellow);border:1px solid rgba(255,209,115,.3)}.auth-badge[data-auth-type=oauth]{background:#d4bfff26;color:var(--ayu-accent-purple);border:1px solid rgba(212,191,255,.3)}.server-actions{position:relative}.action-btn{background:transparent;border:none;color:var(--text-tertiary);cursor:pointer;padding:4px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;transition:all var(--transition-fast)}.action-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.menu-btn:hover{background:var(--bg-active);color:var(--blue-500)}.dropdown-menu{position:absolute;top:calc(100% + 4px);right:0;background:var(--bg-tertiary);border:1px solid var(--border-bright);border-radius:var(--radius-md);box-shadow:var(--shadow-lg);min-width:180px;z-index:1000;animation:fadeIn .15s ease-out;overflow:hidden}.dropdown-item{width:100%;padding:10px 14px;background:transparent;border:none;color:var(--text-primary);font-size:13px;font-weight:500;text-align:left;cursor:pointer;display:flex;align-items:center;gap:10px;transition:all var(--transition-fast);border-bottom:1px solid transparent}.dropdown-item:hover:not(:disabled){background:var(--bg-hover);color:var(--text-white)}.dropdown-item:disabled{opacity:.5;cursor:not-allowed}.dropdown-item.danger{color:var(--red-500)}.dropdown-item.danger:hover:not(:disabled){background:#f287791a;color:var(--red-500)}.dropdown-item svg{flex-shrink:0;opacity:.7}.dropdown-item:hover svg{opacity:1}.dropdown-divider{height:1px;background:var(--border-color);margin:4px 0}.spinning{animation:spin 1s linear infinite}.tools-sidebar{width:300px;background:var(--bg-secondary);border-right:1px solid var(--border-color);display:flex;flex-direction:column;transform:translateZ(0)}.tools-header{padding:var(--spacing-lg) var(--spacing-xl);border-bottom:1px solid var(--border-color);background:var(--bg-primary);display:flex;align-items:center;justify-content:space-between}.tools-header h3{font-size:14px;font-weight:700;color:var(--text-white);text-transform:uppercase;letter-spacing:1px}.tools-count{background:var(--bg-hover);padding:4px 10px;border-radius:12px;font-size:12px;color:var(--blue-500);font-weight:600;font-family:var(--font-mono)}.tools-search{padding:var(--spacing-md);border-bottom:1px solid var(--border-color);background:var(--bg-primary)}.search-box{width:100%;padding:10px 12px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:var(--radius-md);color:var(--text-primary);font-size:13px;transition:all var(--transition-fast);font-family:var(--font-sans)}.search-box:focus{border-color:var(--blue-500);background:var(--bg-hover);box-shadow:0 0 0 3px #5ccfe61a;outline:none}.search-box::placeholder{color:var(--text-tertiary)}.tools-list{flex:1;overflow-y:auto;padding:var(--spacing-sm);-webkit-overflow-scrolling:touch}.tool-item{padding:12px var(--spacing-md);margin-bottom:6px;background:var(--bg-tertiary);border-radius:var(--radius-md);cursor:pointer;transition:all var(--transition-fast);border:1px solid transparent}.tool-item:hover{background:var(--bg-hover);border-color:var(--border-bright);transform:translate(2px)}.tool-item.active{background:var(--bg-active);border-color:var(--blue-500);box-shadow:0 0 0 1px var(--blue-500) inset}.tool-name{font-size:13px;font-weight:600;color:var(--text-white);margin-bottom:4px;font-family:var(--font-mono);word-break:break-word}.tool-description{font-size:11px;color:var(--text-secondary);line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.tools-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;padding:var(--spacing-2xl);text-align:center;color:var(--text-tertiary)}.empty-icon{font-size:48px;margin-bottom:var(--spacing-lg);opacity:.6}.tools-empty p{font-size:13px;color:var(--text-secondary);line-height:1.5}.no-results{padding:var(--spacing-2xl) var(--spacing-lg);text-align:center}.no-results p{font-size:13px;color:var(--text-tertiary);line-height:1.5}.copy-btn{display:inline-flex;align-items:center;gap:var(--spacing-xs);padding:6px 12px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:var(--radius-sm);color:var(--text-secondary);font-size:13px;font-weight:500;cursor:pointer;transition:all var(--transition-fast)}.copy-btn:hover{background:var(--bg-hover);border-color:var(--border-bright);color:var(--text-primary)}.copy-btn:active{transform:scale(.98)}.copy-btn svg{flex-shrink:0}.copy-btn-sm{padding:4px 8px;font-size:12px}.copy-btn:has(svg.lucide-check){color:var(--green-500);border-color:var(--green-500);background:#bae67e1a}.json-viewer{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:var(--spacing-lg);font-family:var(--font-mono);font-size:12px;white-space:pre-wrap;word-break:break-word;max-height:400px;overflow-y:auto;line-height:1.6;color:var(--text-primary);-webkit-user-select:text;user-select:text}.json-key{color:var(--ayu-accent-blue);font-weight:500}.json-string{color:var(--ayu-accent-green)}.json-number{color:var(--ayu-accent-orange)}.json-boolean{color:var(--ayu-accent-purple);font-weight:600}.json-null{color:var(--ayu-accent-purple);font-weight:600;font-style:italic}.json-bracket{color:var(--ayu-text-secondary);font-weight:600}.json-punctuation{color:var(--ayu-text-tertiary)}.json-collapse-toggle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;margin-right:2px;cursor:pointer;color:var(--text-tertiary);transition:color var(--transition-fast);vertical-align:middle;-webkit-user-select:none;user-select:none}.json-collapse-toggle:hover{color:var(--blue-500)}.json-collapse-toggle svg{flex-shrink:0}.json-ellipsis{color:var(--text-tertiary);font-style:italic;margin:0 4px}.json-viewer::selection,.json-viewer *::selection{background:var(--ayu-accent-blue);color:var(--bg-primary)}.json-editor-wrapper{position:relative;width:100%;min-height:200px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:var(--radius-md);overflow:hidden;transition:border-color var(--transition-fast)}.json-editor-wrapper:focus-within{border-color:var(--blue-500)}.json-editor-highlight{position:absolute;inset:0;padding:var(--spacing-lg);pointer-events:none;overflow:hidden;white-space:pre-wrap;word-wrap:break-word;font-family:var(--font-mono);font-size:13px;line-height:1.5;color:transparent}.json-editor-highlight pre{margin:0;padding:0;font-family:inherit;font-size:inherit;line-height:inherit;white-space:pre-wrap;word-wrap:break-word}.json-editor-input{position:relative;width:100%;min-height:200px;padding:var(--spacing-lg);background:transparent;border:none;color:var(--text-primary);font-family:var(--font-mono);font-size:13px;line-height:1.5;resize:vertical;outline:none;white-space:pre-wrap;word-wrap:break-word;caret-color:var(--text-primary)}.json-editor-input::placeholder{color:var(--text-tertiary)}.json-editor-highlight .json-key{color:var(--ayu-accent-blue);font-weight:500}.json-editor-highlight .json-string{color:var(--ayu-accent-green)}.json-editor-highlight .json-number{color:var(--ayu-accent-orange)}.json-editor-highlight .json-boolean{color:var(--ayu-accent-purple);font-weight:600}.json-editor-highlight .json-null{color:var(--ayu-accent-purple);font-weight:600;font-style:italic}.json-editor-highlight .json-bracket{color:var(--ayu-text-secondary);font-weight:600}.json-editor-highlight .json-punctuation{color:var(--ayu-text-tertiary)}.main-area{flex:1;display:flex;flex-direction:column;overflow:hidden;transform:translateZ(0)}.main-header{padding:var(--spacing-2xl);border-bottom:1px solid var(--border-color);background:var(--bg-secondary)}.main-header h2{font-size:22px;font-weight:700;color:var(--text-white);margin-bottom:var(--spacing-sm);font-family:var(--font-mono);letter-spacing:-.5px}.main-header p{font-size:14px;color:var(--text-secondary);line-height:1.5}.description-container{position:relative}.main-tool-description{font-size:14px;color:var(--text-secondary);line-height:1.5;margin:0;white-space:pre-wrap;word-break:break-word}.main-tool-description.collapsed{display:-webkit-box;-webkit-line-clamp:3;line-clamp:3;-webkit-box-orient:vertical;overflow:hidden;text-overflow:ellipsis}.description-toggle{margin-top:var(--spacing-xs);padding:0;background:transparent;border:none;color:var(--text-tertiary);cursor:pointer;font-size:12px;font-weight:400;transition:color var(--transition-fast);text-decoration:underline;text-underline-offset:2px}.description-toggle:hover{color:var(--blue-500)}.content-area{flex:1;overflow-y:auto;padding:var(--spacing-2xl);-webkit-overflow-scrolling:touch}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-tertiary)}.empty-state-icon{font-size:48px;margin-bottom:var(--spacing-lg)}.empty-state h3{margin-bottom:var(--spacing-sm)}.section{margin-bottom:32px}.section-title{font-size:12px;font-weight:600;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.5px;margin-bottom:var(--spacing-md)}.schema-viewer{background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:var(--spacing-lg);font-family:var(--font-mono);font-size:12px;color:var(--text-primary);overflow-x:auto;line-height:1.6}.schema-viewer::selection{background:var(--blue-600);color:var(--bg-primary)}.input-section{margin-top:var(--spacing-2xl)}.mode-toggle{display:flex;gap:var(--spacing-sm);margin-bottom:var(--spacing-md)}.mode-btn{padding:6px 12px;background:var(--bg-tertiary);border:1px solid var(--border-color);color:var(--text-secondary);border-radius:var(--radius-sm);cursor:pointer;font-size:12px;transition:all var(--transition-fast)}.mode-btn.active{background:var(--blue-500);border-color:var(--blue-500);color:#fff}.mode-btn:hover:not(.active){background:var(--bg-hover)}.json-error{margin-top:var(--spacing-md);padding:10px 12px;background:#f287791a;border:1px solid rgba(242,135,121,.3);border-radius:var(--radius-md);color:var(--red-500);font-size:12px;font-family:var(--font-mono);line-height:1.5}.form-fields{display:flex;flex-direction:column;gap:var(--spacing-lg)}.form-field{display:flex;flex-direction:column}.form-label{font-size:13px;font-weight:500;color:var(--text-primary);margin-bottom:6px;display:flex;align-items:center;gap:var(--spacing-xs)}.form-label.checkbox-label{flex-direction:row;cursor:pointer;margin-bottom:0}.form-label.checkbox-label span{display:flex;align-items:center;gap:var(--spacing-xs)}.param-name{border-bottom:1px dashed var(--text-tertiary);cursor:help}.field-type{font-size:11px;color:var(--text-tertiary);font-weight:400;font-family:var(--font-mono)}.form-label .required{color:var(--red-500)}.form-checkbox{width:18px;height:18px;cursor:pointer;accent-color:var(--blue-500)}.form-input{padding:10px 12px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:var(--radius-md);color:var(--text-primary);font-size:13px;transition:border-color var(--transition-fast)}.form-input:focus{border-color:var(--blue-500)}.form-textarea{padding:10px 12px;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:var(--radius-md);color:var(--text-primary);font-size:13px;font-family:var(--font-mono);resize:vertical;min-height:80px;transition:border-color var(--transition-fast);line-height:1.5}.form-textarea:focus{border-color:var(--blue-500)}.execute-btn{padding:12px 24px;background:linear-gradient(135deg,var(--green-500) 0%,var(--green-600) 100%);color:var(--bg-primary);border:none;border-radius:var(--radius-md);cursor:pointer;font-size:14px;font-weight:700;margin-top:var(--spacing-lg);transition:all var(--transition-fast);box-shadow:0 2px 8px #bae67e33;text-transform:uppercase;letter-spacing:.5px;display:flex;align-items:center;justify-content:center;gap:var(--spacing-md)}.execute-timer{font-family:var(--font-mono);font-size:14px;padding:4px 12px;background:#0003;border-radius:var(--radius-sm);font-weight:600;letter-spacing:.5px;animation:pulse 1s ease-in-out infinite}@keyframes pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.8;transform:scale(1.05)}}.execute-btn:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 6px 16px #bae67e4d}.execute-btn:active:not(:disabled){transform:translateY(0)}.execute-btn:disabled{cursor:not-allowed}.execute-btn:disabled:not(.executing){background:var(--bg-hover);opacity:.5;box-shadow:none}.result-section{margin-top:32px}.result-header{display:flex;align-items:center;justify-content:space-between;gap:var(--spacing-md);padding:var(--spacing-md);background:#bae67e1a;border-radius:var(--radius-md);border-left:3px solid var(--green-500);margin-bottom:var(--spacing-md)}.result-header-left{display:flex;align-items:center;gap:var(--spacing-md)}.result-header.error{background:#f287791a;border-left-color:var(--red-500)}.result-status{font-size:14px;font-weight:700;color:var(--green-500);font-family:var(--font-mono)}.result-status.error{color:var(--red-500)}.result-time{font-size:12px;color:var(--text-tertiary);font-family:var(--font-mono)}.result-tabs{display:flex;gap:4px;margin-bottom:var(--spacing-md);border-bottom:1px solid var(--border-color)}.result-tab{padding:8px 16px;background:transparent;border:none;color:var(--text-secondary);cursor:pointer;font-size:13px;border-bottom:2px solid transparent;transition:all var(--transition-fast)}.result-tab.active{color:var(--blue-500);border-bottom-color:var(--blue-500)}.result-tab:hover:not(.active){color:var(--text-primary)}.modal-overlay{position:fixed;inset:0;background:#1f2430e6;display:flex;align-items:center;justify-content:center;z-index:9999;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);animation:fadeIn .2s ease-out}.modal{background:var(--bg-secondary);border-radius:var(--radius-lg);width:90%;max-width:520px;padding:32px;border:1px solid var(--border-bright);box-shadow:var(--shadow-lg);animation:slideUp .25s ease-out;transform:translateZ(0)}.modal-small{max-width:420px;padding:24px}.modal-header h2{font-size:20px;font-weight:700;margin-bottom:24px;color:var(--text-white);letter-spacing:-.3px}.modal-body{margin-bottom:24px}.modal-body .form-field{margin-bottom:20px}.modal-body .form-field:last-child{margin-bottom:0}.modal-body .form-label{display:block;font-size:13px;font-weight:600;color:var(--text-primary);margin-bottom:8px;letter-spacing:.2px}.modal-body .form-input{width:100%;padding:10px 12px;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:var(--radius-md);color:var(--text-primary);font-size:14px;font-family:var(--font-sans);transition:all var(--transition-fast)}.modal-body .form-input:focus{border-color:var(--blue-500);background:var(--bg-hover);box-shadow:0 0 0 3px #5ccfe61a;outline:none}.modal-body .form-input::placeholder{color:var(--text-tertiary)}.error-message{background:#f287791a;border:1px solid rgba(242,135,121,.3);color:var(--red-500);padding:12px 14px;border-radius:var(--radius-md);font-size:13px;margin-bottom:20px;line-height:1.5;white-space:pre-line;max-height:300px;overflow-y:auto}.info-message{background:#5ccfe61a;border:1px solid rgba(92,207,230,.3);color:var(--blue-500);padding:10px 12px;border-radius:var(--radius-md);font-size:12px;line-height:1.5}.modal-footer{display:flex;gap:12px;justify-content:flex-end;padding-top:8px}.btn{padding:11px 24px;border:none;border-radius:var(--radius-md);cursor:pointer;font-size:14px;font-weight:600;transition:all var(--transition-fast);letter-spacing:.2px}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-secondary{background:var(--bg-hover);color:var(--text-primary)}.btn-secondary:hover:not(:disabled){background:#404040}.btn-primary{background:linear-gradient(135deg,var(--blue-500) 0%,var(--blue-600) 100%);color:var(--bg-primary);font-weight:700;box-shadow:0 2px 8px #5ccfe633}.btn-primary:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 4px 12px #5ccfe64d}.btn-primary:active:not(:disabled){transform:translateY(0)}.btn-danger{background:linear-gradient(135deg,var(--red-500) 0%,#d96459 100%);color:var(--bg-primary);font-weight:700;box-shadow:0 2px 8px #f2877933}.btn-danger:hover:not(:disabled){transform:translateY(-2px);box-shadow:0 4px 12px #f287794d}.btn-danger:active:not(:disabled){transform:translateY(0)}.radio-group{display:flex;gap:20px;margin-bottom:4px}.radio-option{display:flex;align-items:center;gap:8px;cursor:pointer;padding:4px 0}.radio-option input[type=radio]{accent-color:var(--blue-500);cursor:pointer;width:16px;height:16px}.radio-option span{font-size:14px;color:var(--text-primary);font-weight:500}.oauth-callback{display:flex;align-items:center;justify-content:center;min-height:100vh;background:var(--bg-primary);padding:var(--spacing-2xl)}.oauth-callback-content{max-width:520px;width:100%;background:var(--bg-secondary);border:1px solid var(--border-color);border-radius:var(--radius-lg);padding:48px 40px;text-align:center;animation:fadeIn .3s ease-out}.oauth-icon{font-size:72px;margin-bottom:32px;line-height:1}.spinner{animation:pulse 1.5s ease-in-out infinite}.success-icon{color:var(--green-500);font-size:72px;animation:scaleIn .4s cubic-bezier(.175,.885,.32,1.275)}.error-icon{color:var(--red-500);font-size:72px;animation:shake .5s ease-in-out}.oauth-title{font-size:26px;font-weight:700;color:var(--text-white);margin-bottom:16px;letter-spacing:-.5px;line-height:1.3}.oauth-message{font-size:15px;color:var(--text-secondary);margin-bottom:32px;line-height:1.6;max-width:400px;margin-left:auto;margin-right:auto}.oauth-redirect{font-size:13px;color:var(--text-tertiary);font-style:italic;margin-top:24px;opacity:.8}@keyframes scaleIn{0%{transform:scale(0);opacity:0}50%{transform:scale(1.1)}to{transform:scale(1);opacity:1}}@keyframes shake{0%,to{transform:translate(0)}25%{transform:translate(-10px)}75%{transform:translate(10px)}}.toast-container{position:fixed;top:var(--spacing-2xl);right:var(--spacing-2xl);z-index:9999;display:flex;flex-direction:column;gap:var(--spacing-md);pointer-events:none}.toast{min-width:320px;max-width:480px;background:var(--bg-secondary);border:1px solid var(--border-bright);border-radius:var(--radius-lg);padding:var(--spacing-lg);display:flex;align-items:flex-start;gap:var(--spacing-md);box-shadow:var(--shadow-lg);pointer-events:all;animation:slideIn .3s cubic-bezier(.16,1,.3,1)}.toast-icon{flex-shrink:0;display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:var(--radius-md)}.toast-success .toast-icon{color:var(--green-500);background:#bae67e26}.toast-error .toast-icon{color:var(--red-500);background:#f2877926}.toast-warning .toast-icon{color:var(--orange-500);background:#ffb27326}.toast-info .toast-icon{color:var(--blue-500);background:#73d0ff26}.toast-content{flex:1;min-width:0}.toast-message{font-size:14px;font-weight:600;color:var(--text-white);margin-bottom:4px;line-height:1.4}.toast-description{font-size:13px;color:var(--text-secondary);line-height:1.5}.toast-close{flex-shrink:0;background:transparent;border:none;color:var(--text-tertiary);cursor:pointer;padding:4px;border-radius:var(--radius-sm);display:flex;align-items:center;justify-content:center;transition:all var(--transition-fast)}.toast-close:hover{background:var(--bg-hover);color:var(--text-primary)}@keyframes slideIn{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@media(max-width:768px){.toast-container{top:var(--spacing-lg);right:var(--spacing-lg);left:var(--spacing-lg)}.toast{min-width:unset;width:100%}}.error-boundary{display:flex;align-items:center;justify-content:center;min-height:400px;padding:var(--spacing-2xl);background:var(--bg-primary)}.error-boundary-content{max-width:520px;text-align:center}.error-icon{display:inline-flex;align-items:center;justify-content:center;width:96px;height:96px;border-radius:50%;background:#f2877926;color:var(--red-500);margin-bottom:var(--spacing-xl);animation:shake .5s ease-in-out}.error-title{font-size:24px;font-weight:700;color:var(--text-white);margin-bottom:var(--spacing-md);letter-spacing:-.5px}.error-message{font-size:15px;color:var(--text-secondary);line-height:1.6;margin-bottom:var(--spacing-xl)}.error-details{margin:var(--spacing-xl) 0;text-align:left;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:var(--radius-md);padding:var(--spacing-md)}.error-details summary{font-size:13px;font-weight:600;color:var(--text-primary);cursor:pointer;-webkit-user-select:none;user-select:none;padding:var(--spacing-sm)}.error-details summary:hover{color:var(--text-white)}.error-stack{margin-top:var(--spacing-md);font-family:var(--font-mono);font-size:12px;color:var(--text-tertiary);overflow-x:auto;padding:var(--spacing-md);background:var(--bg-primary);border-radius:var(--radius-sm);line-height:1.6}.error-boundary .btn{display:inline-flex;align-items:center;gap:var(--spacing-sm)}@keyframes shake{0%,to{transform:translate(0)}20%,60%{transform:translate(-8px)}40%,80%{transform:translate(8px)}}.app{display:flex;height:100vh;overflow:hidden}:root{--ayu-bg-primary: #1f2430;--ayu-bg-secondary: #232834;--ayu-bg-tertiary: #2a2f3d;--ayu-bg-hover: #2d3241;--ayu-bg-active: #34405e;--ayu-border: #33415e;--ayu-border-bright: #3e4b59;--ayu-text-primary: #cccac2;--ayu-text-secondary: #707a8c;--ayu-text-tertiary: #5c6773;--ayu-text-white: #d9d7ce;--ayu-accent-cyan: #5ccfe6;--ayu-accent-blue: #73d0ff;--ayu-accent-green: #bae67e;--ayu-accent-orange: #ffae57;--ayu-accent-red: #f28779;--ayu-accent-purple: #d4bfff;--ayu-accent-yellow: #ffd173;--bg-primary: var(--ayu-bg-primary);--bg-secondary: var(--ayu-bg-secondary);--bg-tertiary: var(--ayu-bg-tertiary);--bg-hover: var(--ayu-bg-hover);--bg-active: var(--ayu-bg-active);--text-primary: var(--ayu-text-primary);--text-secondary: var(--ayu-text-secondary);--text-tertiary: var(--ayu-text-tertiary);--text-white: var(--ayu-text-white);--border-color: var(--ayu-border);--border-bright: var(--ayu-border-bright);--blue-500: var(--ayu-accent-cyan);--blue-600: var(--ayu-accent-blue);--green-500: var(--ayu-accent-green);--green-600: #95c74e;--red-500: var(--ayu-accent-red);--yellow-500: var(--ayu-accent-yellow);--purple-500: var(--ayu-accent-purple);--orange-500: var(--ayu-accent-orange);--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--font-mono: "JetBrains Mono", "Monaco", "Menlo", "Courier New", monospace;--spacing-xs: 4px;--spacing-sm: 8px;--spacing-md: 12px;--spacing-lg: 16px;--spacing-xl: 20px;--spacing-2xl: 24px;--radius-sm: 4px;--radius-md: 6px;--radius-lg: 8px;--shadow-sm: 0 1px 3px rgba(0, 0, 0, .3);--shadow-md: 0 4px 12px rgba(0, 0, 0, .4);--shadow-lg: 0 8px 24px rgba(0, 0, 0, .5);--transition-fast: .15s cubic-bezier(.4, 0, .2, 1);--transition-base: .2s cubic-bezier(.4, 0, .2, 1)}*{margin:0;padding:0;box-sizing:border-box}body{font-family:var(--font-sans);background:var(--bg-primary);color:var(--text-primary);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-feature-settings:"liga" 1,"calt" 1}#root{height:100vh;overflow:hidden;transform:translateZ(0);will-change:auto}button,.clickable{-webkit-user-select:none;user-select:none}code,pre{font-family:var(--font-mono);font-variant-ligatures:common-ligatures}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-track{background:var(--bg-secondary)}::-webkit-scrollbar-thumb{background:var(--ayu-border-bright);border-radius:4px;border:2px solid var(--bg-secondary)}::-webkit-scrollbar-thumb:hover{background:var(--text-tertiary)}*:focus-visible{outline:2px solid var(--blue-500);outline-offset:2px}::selection{background:var(--ayu-accent-blue);color:var(--ayu-bg-primary)}@keyframes fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes slideIn{0%{opacity:0;transform:translate(-8px)}to{opacity:1;transform:translate(0)}}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.animate-fade-in{animation:fadeIn .2s ease-out}.animate-slide-in{animation:slideIn .3s ease-out}.animate-spin{animation:spin 1s linear infinite}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.gpu-accelerated{transform:translateZ(0);backface-visibility:hidden;perspective:1000px}.font-mono{font-family:var(--font-mono)}
|