@nextclaw/ui 0.3.10 → 0.3.12

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.
@@ -0,0 +1,472 @@
1
+ import { useEffect, useMemo, useState } from 'react';
2
+ import { useConfig, useConfigSchema, useUpdateRuntime } from '@/hooks/useConfig';
3
+ import type { AgentBindingView, AgentProfileView } from '@/api/types';
4
+ import { Button } from '@/components/ui/button';
5
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
6
+ import { Input } from '@/components/ui/input';
7
+ import { Switch } from '@/components/ui/switch';
8
+ import { hintForPath } from '@/lib/config-hints';
9
+ import { Plus, Save, Trash2 } from 'lucide-react';
10
+ import { toast } from 'sonner';
11
+
12
+ type DmScope = 'main' | 'per-peer' | 'per-channel-peer' | 'per-account-channel-peer';
13
+ type PeerKind = '' | 'direct' | 'group' | 'channel';
14
+
15
+ const DM_SCOPE_OPTIONS: Array<{ value: DmScope; label: string }> = [
16
+ { value: 'main', label: 'main' },
17
+ { value: 'per-peer', label: 'per-peer' },
18
+ { value: 'per-channel-peer', label: 'per-channel-peer' },
19
+ { value: 'per-account-channel-peer', label: 'per-account-channel-peer' }
20
+ ];
21
+
22
+ function createEmptyAgent(): AgentProfileView {
23
+ return {
24
+ id: '',
25
+ default: false,
26
+ workspace: '',
27
+ model: '',
28
+ maxTokens: undefined,
29
+ maxToolIterations: undefined
30
+ };
31
+ }
32
+
33
+ function createEmptyBinding(): AgentBindingView {
34
+ return {
35
+ agentId: '',
36
+ match: {
37
+ channel: '',
38
+ accountId: ''
39
+ }
40
+ };
41
+ }
42
+
43
+ function parseOptionalInt(value: string): number | undefined {
44
+ const trimmed = value.trim();
45
+ if (!trimmed) {
46
+ return undefined;
47
+ }
48
+ const parsed = Number.parseInt(trimmed, 10);
49
+ return Number.isFinite(parsed) ? parsed : undefined;
50
+ }
51
+
52
+ export function RuntimeConfig() {
53
+ const { data: config, isLoading } = useConfig();
54
+ const { data: schema } = useConfigSchema();
55
+ const updateRuntime = useUpdateRuntime();
56
+
57
+ const [agents, setAgents] = useState<AgentProfileView[]>([]);
58
+ const [bindings, setBindings] = useState<AgentBindingView[]>([]);
59
+ const [dmScope, setDmScope] = useState<DmScope>('per-channel-peer');
60
+ const [maxPingPongTurns, setMaxPingPongTurns] = useState(0);
61
+
62
+ useEffect(() => {
63
+ if (!config) {
64
+ return;
65
+ }
66
+ setAgents(
67
+ (config.agents.list ?? []).map((agent) => ({
68
+ id: agent.id ?? '',
69
+ default: Boolean(agent.default),
70
+ workspace: agent.workspace ?? '',
71
+ model: agent.model ?? '',
72
+ maxTokens: agent.maxTokens,
73
+ maxToolIterations: agent.maxToolIterations
74
+ }))
75
+ );
76
+ setBindings(
77
+ (config.bindings ?? []).map((binding) => ({
78
+ agentId: binding.agentId ?? '',
79
+ match: {
80
+ channel: binding.match?.channel ?? '',
81
+ accountId: binding.match?.accountId ?? '',
82
+ peer: binding.match?.peer
83
+ ? {
84
+ kind: binding.match.peer.kind,
85
+ id: binding.match.peer.id
86
+ }
87
+ : undefined
88
+ }
89
+ }))
90
+ );
91
+ setDmScope((config.session?.dmScope as DmScope) ?? 'per-channel-peer');
92
+ setMaxPingPongTurns(config.session?.agentToAgent?.maxPingPongTurns ?? 0);
93
+ }, [config]);
94
+
95
+ const uiHints = schema?.uiHints;
96
+ const dmScopeHint = hintForPath('session.dmScope', uiHints);
97
+ const maxPingHint = hintForPath('session.agentToAgent.maxPingPongTurns', uiHints);
98
+ const agentsHint = hintForPath('agents.list', uiHints);
99
+ const bindingsHint = hintForPath('bindings', uiHints);
100
+
101
+ const knownAgentIds = useMemo(() => {
102
+ const ids = new Set<string>(['main']);
103
+ agents.forEach((agent) => {
104
+ const id = agent.id.trim();
105
+ if (id) {
106
+ ids.add(id);
107
+ }
108
+ });
109
+ return ids;
110
+ }, [agents]);
111
+
112
+ const updateAgent = (index: number, patch: Partial<AgentProfileView>) => {
113
+ setAgents((prev) => prev.map((agent, cursor) => (cursor === index ? { ...agent, ...patch } : agent)));
114
+ };
115
+
116
+ const updateBinding = (index: number, next: AgentBindingView) => {
117
+ setBindings((prev) => prev.map((binding, cursor) => (cursor === index ? next : binding)));
118
+ };
119
+
120
+ const handleSave = () => {
121
+ try {
122
+ const normalizedAgents = agents.map((agent, index) => {
123
+ const id = agent.id.trim();
124
+ if (!id) {
125
+ throw new Error(`agents.list[${index}].id is required`);
126
+ }
127
+ const normalized: AgentProfileView = { id };
128
+ if (agent.default) {
129
+ normalized.default = true;
130
+ }
131
+ if (agent.workspace?.trim()) {
132
+ normalized.workspace = agent.workspace.trim();
133
+ }
134
+ if (agent.model?.trim()) {
135
+ normalized.model = agent.model.trim();
136
+ }
137
+ if (typeof agent.maxTokens === 'number') {
138
+ normalized.maxTokens = agent.maxTokens;
139
+ }
140
+ if (typeof agent.maxToolIterations === 'number') {
141
+ normalized.maxToolIterations = agent.maxToolIterations;
142
+ }
143
+ return normalized;
144
+ });
145
+
146
+ const duplicates = normalizedAgents
147
+ .map((agent) => agent.id)
148
+ .filter((id, index, all) => all.indexOf(id) !== index);
149
+ if (duplicates.length > 0) {
150
+ toast.error(`Duplicate agent id: ${duplicates[0]}`);
151
+ return;
152
+ }
153
+
154
+ const normalizedBindings = bindings.map((binding, index) => {
155
+ const agentId = binding.agentId.trim();
156
+ const channel = binding.match.channel.trim();
157
+ const accountId = binding.match.accountId?.trim() ?? '';
158
+ const peerKind = binding.match.peer?.kind;
159
+ const peerId = binding.match.peer?.id?.trim() ?? '';
160
+
161
+ if (!agentId) {
162
+ throw new Error(`bindings[${index}].agentId is required`);
163
+ }
164
+ if (!knownAgentIds.has(agentId)) {
165
+ throw new Error(`bindings[${index}].agentId '${agentId}' not found in agents.list/main`);
166
+ }
167
+ if (!channel) {
168
+ throw new Error(`bindings[${index}].match.channel is required`);
169
+ }
170
+
171
+ const normalized: AgentBindingView = {
172
+ agentId,
173
+ match: {
174
+ channel
175
+ }
176
+ };
177
+
178
+ if (accountId) {
179
+ normalized.match.accountId = accountId;
180
+ }
181
+
182
+ if (peerKind) {
183
+ if (!peerId) {
184
+ throw new Error(`bindings[${index}].match.peer.id is required when peer.kind is set`);
185
+ }
186
+ normalized.match.peer = {
187
+ kind: peerKind,
188
+ id: peerId
189
+ };
190
+ }
191
+
192
+ return normalized;
193
+ });
194
+
195
+ updateRuntime.mutate({
196
+ data: {
197
+ agents: { list: normalizedAgents },
198
+ bindings: normalizedBindings,
199
+ session: {
200
+ dmScope,
201
+ agentToAgent: {
202
+ maxPingPongTurns: Math.min(5, Math.max(0, maxPingPongTurns))
203
+ }
204
+ }
205
+ }
206
+ });
207
+ } catch (error) {
208
+ const message = error instanceof Error ? error.message : String(error);
209
+ toast.error(message);
210
+ }
211
+ };
212
+
213
+ if (isLoading || !config) {
214
+ return <div className="p-8 text-gray-400">Loading runtime settings...</div>;
215
+ }
216
+
217
+ return (
218
+ <div className="space-y-6 pb-20 animate-fade-in">
219
+ <div>
220
+ <h2 className="text-2xl font-bold text-gray-900">Routing & Runtime</h2>
221
+ <p className="text-sm text-gray-500 mt-1">
222
+ Align multi-agent routing with OpenClaw: bindings, agent pool, and DM scope.
223
+ </p>
224
+ </div>
225
+
226
+ <Card>
227
+ <CardHeader>
228
+ <CardTitle>{dmScopeHint?.label ?? 'DM Scope'}</CardTitle>
229
+ <CardDescription>{dmScopeHint?.help ?? 'Control how direct-message sessions are isolated.'}</CardDescription>
230
+ </CardHeader>
231
+ <CardContent className="space-y-4">
232
+ <div className="space-y-2">
233
+ <label className="text-sm font-medium text-gray-800">{dmScopeHint?.label ?? 'DM Scope'}</label>
234
+ <select
235
+ value={dmScope}
236
+ onChange={(event) => setDmScope(event.target.value as DmScope)}
237
+ className="flex h-10 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm"
238
+ >
239
+ {DM_SCOPE_OPTIONS.map((option) => (
240
+ <option key={option.value} value={option.value}>
241
+ {option.label}
242
+ </option>
243
+ ))}
244
+ </select>
245
+ </div>
246
+ <div className="space-y-2">
247
+ <label className="text-sm font-medium text-gray-800">
248
+ {maxPingHint?.label ?? 'Max Ping-Pong Turns'}
249
+ </label>
250
+ <Input
251
+ type="number"
252
+ min={0}
253
+ max={5}
254
+ value={maxPingPongTurns}
255
+ onChange={(event) => setMaxPingPongTurns(Math.max(0, Number.parseInt(event.target.value, 10) || 0))}
256
+ />
257
+ <p className="text-xs text-gray-500">
258
+ {maxPingHint?.help ?? 'Set to 0 to block automatic agent-to-agent ping-pong loops.'}
259
+ </p>
260
+ </div>
261
+ </CardContent>
262
+ </Card>
263
+
264
+ <Card>
265
+ <CardHeader>
266
+ <CardTitle>{agentsHint?.label ?? 'Agent List'}</CardTitle>
267
+ <CardDescription>{agentsHint?.help ?? 'Run multiple fixed-role agents in one gateway process.'}</CardDescription>
268
+ </CardHeader>
269
+ <CardContent className="space-y-3">
270
+ {agents.map((agent, index) => (
271
+ <div key={`${index}-${agent.id}`} className="rounded-xl border border-gray-200 p-3 space-y-3">
272
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
273
+ <Input
274
+ value={agent.id}
275
+ onChange={(event) => updateAgent(index, { id: event.target.value })}
276
+ placeholder="Agent ID (e.g. engineer)"
277
+ />
278
+ <Input
279
+ value={agent.workspace ?? ''}
280
+ onChange={(event) => updateAgent(index, { workspace: event.target.value })}
281
+ placeholder="Workspace override (optional)"
282
+ />
283
+ <Input
284
+ value={agent.model ?? ''}
285
+ onChange={(event) => updateAgent(index, { model: event.target.value })}
286
+ placeholder="Model override (optional)"
287
+ />
288
+ <div className="grid grid-cols-2 gap-2">
289
+ <Input
290
+ type="number"
291
+ min={1}
292
+ value={agent.maxTokens ?? ''}
293
+ onChange={(event) =>
294
+ updateAgent(index, {
295
+ maxTokens: parseOptionalInt(event.target.value)
296
+ })
297
+ }
298
+ placeholder="Max tokens"
299
+ />
300
+ <Input
301
+ type="number"
302
+ min={1}
303
+ value={agent.maxToolIterations ?? ''}
304
+ onChange={(event) =>
305
+ updateAgent(index, {
306
+ maxToolIterations: parseOptionalInt(event.target.value)
307
+ })
308
+ }
309
+ placeholder="Max tools"
310
+ />
311
+ </div>
312
+ </div>
313
+ <div className="flex items-center justify-between">
314
+ <div className="flex items-center gap-2 text-sm text-gray-600">
315
+ <Switch
316
+ checked={Boolean(agent.default)}
317
+ onCheckedChange={(checked) => {
318
+ if (!checked) {
319
+ updateAgent(index, { default: false });
320
+ return;
321
+ }
322
+ setAgents((prev) =>
323
+ prev.map((entry, cursor) => ({
324
+ ...entry,
325
+ default: cursor === index
326
+ }))
327
+ );
328
+ }}
329
+ />
330
+ <span>Default agent</span>
331
+ </div>
332
+ <Button type="button" variant="outline" size="sm" onClick={() => setAgents((prev) => prev.filter((_, cursor) => cursor !== index))}>
333
+ <Trash2 className="h-4 w-4 mr-1" />
334
+ Remove
335
+ </Button>
336
+ </div>
337
+ </div>
338
+ ))}
339
+
340
+ <Button type="button" variant="outline" onClick={() => setAgents((prev) => [...prev, createEmptyAgent()])}>
341
+ <Plus className="h-4 w-4 mr-2" />
342
+ Add Agent
343
+ </Button>
344
+ </CardContent>
345
+ </Card>
346
+
347
+ <Card>
348
+ <CardHeader>
349
+ <CardTitle>{bindingsHint?.label ?? 'Bindings'}</CardTitle>
350
+ <CardDescription>
351
+ {bindingsHint?.help ?? 'Route inbound message by channel + account + peer to target agent.'}
352
+ </CardDescription>
353
+ </CardHeader>
354
+ <CardContent className="space-y-3">
355
+ {bindings.map((binding, index) => {
356
+ const peerKind = (binding.match.peer?.kind ?? '') as PeerKind;
357
+ return (
358
+ <div key={`${index}-${binding.agentId}`} className="rounded-xl border border-gray-200 p-3 space-y-3">
359
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
360
+ <Input
361
+ value={binding.agentId}
362
+ onChange={(event) => updateBinding(index, { ...binding, agentId: event.target.value })}
363
+ placeholder="Target agent ID"
364
+ />
365
+ <Input
366
+ value={binding.match.channel}
367
+ onChange={(event) =>
368
+ updateBinding(index, {
369
+ ...binding,
370
+ match: {
371
+ ...binding.match,
372
+ channel: event.target.value
373
+ }
374
+ })
375
+ }
376
+ placeholder="Channel (e.g. discord)"
377
+ />
378
+ <Input
379
+ value={binding.match.accountId ?? ''}
380
+ onChange={(event) =>
381
+ updateBinding(index, {
382
+ ...binding,
383
+ match: {
384
+ ...binding.match,
385
+ accountId: event.target.value
386
+ }
387
+ })
388
+ }
389
+ placeholder="Account ID (optional)"
390
+ />
391
+ <select
392
+ value={peerKind}
393
+ onChange={(event) => {
394
+ const nextKind = event.target.value as PeerKind;
395
+ if (!nextKind) {
396
+ updateBinding(index, {
397
+ ...binding,
398
+ match: {
399
+ ...binding.match,
400
+ peer: undefined
401
+ }
402
+ });
403
+ return;
404
+ }
405
+ updateBinding(index, {
406
+ ...binding,
407
+ match: {
408
+ ...binding.match,
409
+ peer: {
410
+ kind: nextKind,
411
+ id: binding.match.peer?.id ?? ''
412
+ }
413
+ }
414
+ });
415
+ }}
416
+ className="flex h-10 w-full rounded-lg border border-gray-200 bg-white px-3 py-2 text-sm"
417
+ >
418
+ <option value="">Peer kind (optional)</option>
419
+ <option value="direct">direct</option>
420
+ <option value="group">group</option>
421
+ <option value="channel">channel</option>
422
+ </select>
423
+ <Input
424
+ value={binding.match.peer?.id ?? ''}
425
+ onChange={(event) =>
426
+ updateBinding(index, {
427
+ ...binding,
428
+ match: {
429
+ ...binding.match,
430
+ peer: peerKind
431
+ ? {
432
+ kind: peerKind,
433
+ id: event.target.value
434
+ }
435
+ : undefined
436
+ }
437
+ })
438
+ }
439
+ placeholder="Peer ID (requires peer kind)"
440
+ />
441
+ </div>
442
+ <div className="flex justify-end">
443
+ <Button
444
+ type="button"
445
+ variant="outline"
446
+ size="sm"
447
+ onClick={() => setBindings((prev) => prev.filter((_, cursor) => cursor !== index))}
448
+ >
449
+ <Trash2 className="h-4 w-4 mr-1" />
450
+ Remove
451
+ </Button>
452
+ </div>
453
+ </div>
454
+ );
455
+ })}
456
+
457
+ <Button type="button" variant="outline" onClick={() => setBindings((prev) => [...prev, createEmptyBinding()])}>
458
+ <Plus className="h-4 w-4 mr-2" />
459
+ Add Binding
460
+ </Button>
461
+ </CardContent>
462
+ </Card>
463
+
464
+ <div className="flex justify-end">
465
+ <Button type="button" onClick={handleSave} disabled={updateRuntime.isPending}>
466
+ <Save className="h-4 w-4 mr-2" />
467
+ {updateRuntime.isPending ? 'Saving...' : 'Save Runtime Settings'}
468
+ </Button>
469
+ </div>
470
+ </div>
471
+ );
472
+ }
@@ -1,6 +1,6 @@
1
1
  import { useUiStore } from '@/stores/ui.store';
2
2
  import { cn } from '@/lib/utils';
3
- import { Cpu, MessageSquare, Sparkles } from 'lucide-react';
3
+ import { Cpu, GitBranch, MessageSquare, Sparkles } from 'lucide-react';
4
4
 
5
5
  const navItems = [
6
6
  {
@@ -17,6 +17,11 @@ const navItems = [
17
17
  id: 'channels' as const,
18
18
  label: 'Channels',
19
19
  icon: MessageSquare,
20
+ },
21
+ {
22
+ id: 'runtime' as const,
23
+ label: 'Routing & Runtime',
24
+ icon: GitBranch,
20
25
  }
21
26
  ];
22
27
 
@@ -6,6 +6,7 @@ import {
6
6
  updateModel,
7
7
  updateProvider,
8
8
  updateChannel,
9
+ updateRuntime,
9
10
  executeConfigAction
10
11
  } from '@/api/config';
11
12
  import { toast } from 'sonner';
@@ -83,6 +84,22 @@ export function useUpdateChannel() {
83
84
  });
84
85
  }
85
86
 
87
+ export function useUpdateRuntime() {
88
+ const queryClient = useQueryClient();
89
+
90
+ return useMutation({
91
+ mutationFn: ({ data }: { data: unknown }) =>
92
+ updateRuntime(data as Parameters<typeof updateRuntime>[0]),
93
+ onSuccess: () => {
94
+ queryClient.invalidateQueries({ queryKey: ['config'] });
95
+ toast.success(t('configSavedApplied'));
96
+ },
97
+ onError: (error: Error) => {
98
+ toast.error(t('configSaveFailed') + ': ' + error.message);
99
+ }
100
+ });
101
+ }
102
+
86
103
  export function useExecuteConfigAction() {
87
104
  return useMutation({
88
105
  mutationFn: ({ actionId, data }: { actionId: string; data: unknown }) =>
package/src/lib/i18n.ts CHANGED
@@ -42,6 +42,8 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
42
42
  botToken: { zh: 'Bot Token', en: 'Bot Token' },
43
43
  appToken: { zh: 'App Token', en: 'App Token' },
44
44
  appId: { zh: 'App ID', en: 'App ID' },
45
+ corpId: { zh: '企业 ID', en: 'Corp ID' },
46
+ agentId: { zh: '应用 Agent ID', en: 'Agent ID' },
45
47
  appSecret: { zh: 'App Secret', en: 'App Secret' },
46
48
  markdownSupport: { zh: 'Markdown 支持', en: 'Markdown Support' },
47
49
  clientId: { zh: 'Client ID', en: 'Client ID' },
@@ -54,6 +56,8 @@ export const LABELS: Record<string, { zh: string; en: string }> = {
54
56
  intents: { zh: 'Intents', en: 'Intents' },
55
57
  mode: { zh: '模式', en: 'Mode' },
56
58
  webhookPath: { zh: 'Webhook 路径', en: 'Webhook Path' },
59
+ callbackPort: { zh: '回调端口', en: 'Callback Port' },
60
+ callbackPath: { zh: '回调路径', en: 'Callback Path' },
57
61
  groupPolicy: { zh: '群组策略', en: 'Group Policy' },
58
62
  consentGranted: { zh: '同意条款', en: 'Consent Granted' },
59
63
  imapHost: { zh: 'IMAP 服务器', en: 'IMAP Host' },
package/src/lib/logos.ts CHANGED
@@ -23,6 +23,7 @@ const CHANNEL_LOGOS: LogoMap = {
23
23
  qq: "qq.svg",
24
24
  feishu: "feishu.svg",
25
25
  dingtalk: "dingtalk.svg",
26
+ wecom: "wecom.svg",
26
27
  mochat: "mochat.svg",
27
28
  email: "email.svg"
28
29
  };
@@ -4,7 +4,7 @@ type ConnectionStatus = 'connected' | 'disconnected' | 'connecting';
4
4
 
5
5
  interface UiState {
6
6
  // Active configuration tab
7
- activeTab: 'model' | 'providers' | 'channels';
7
+ activeTab: 'model' | 'providers' | 'channels' | 'runtime';
8
8
  setActiveTab: (tab: UiState['activeTab']) => void;
9
9
 
10
10
  // Connection status
@@ -1 +0,0 @@
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:hsl(var(--gray-200))}: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:var(--font-sans);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:var(--font-mono);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:hsl(var(--gray-400))}input::placeholder,textarea::placeholder{opacity:1;color:hsl(var(--gray-400))}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{--brand-50: 217 100% 97%;--brand-100: 217 100% 94%;--brand-200: 217 95% 87%;--brand-300: 217 90% 77%;--brand-400: 217 85% 65%;--brand-500: 217 80% 55%;--brand-600: 217 75% 48%;--brand-700: 217 70% 40%;--brand-800: 217 65% 33%;--brand-900: 217 60% 25%;--gray-50: 210 20% 98%;--gray-100: 220 14% 96%;--gray-200: 220 13% 91%;--gray-300: 216 12% 84%;--gray-400: 218 11% 65%;--gray-500: 220 9% 46%;--gray-600: 215 14% 34%;--gray-700: 217 19% 27%;--gray-800: 215 28% 17%;--gray-900: 221 39% 11%;--background-secondary: 220 14% 96%;--background-tertiary: 220 13% 91%;--foreground-secondary: 215 28% 17%;--foreground-tertiary: 220 9% 46%;--foreground-muted: 218 11% 65%;--primary-hover: 217 75% 48%;--primary-active: 217 70% 40%;--secondary-hover: 220 13% 91%;--success: 142 76% 36%;--success-foreground: 0 0% 100%;--warning: 38 92% 50%;--warning-foreground: 0 0% 100%;--card-border: 220 13% 91%;--border-hover: 216 12% 84%;--border-active: 217 80% 55%;--input: 0 0% 100%;--input-border: 220 13% 91%;--input-focus: 217 80% 55%;--ring-offset: 0 0% 100%;--space-0: 0px;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--space-10: 40px;--space-12: 48px;--space-16: 64px;--space-20: 80px;--space-24: 96px;--radius-sm: 6px;--radius-md: 8px;--radius-lg: 12px;--radius-xl: 16px;--radius-2xl: 20px;--radius-3xl: 24px;--radius-full: 9999px;--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / .05);--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--shadow-md: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / .25);--shadow-card: 0 1px 3px 0 rgb(0 0 0 / .05), 0 1px 2px -1px rgb(0 0 0 / .05);--shadow-card-hover: 0 10px 15px -3px rgb(0 0 0 / .08), 0 4px 6px -4px rgb(0 0 0 / .05);--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;--font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;--text-xs: 12px;--text-sm: 14px;--text-base: 16px;--text-lg: 18px;--text-xl: 20px;--text-2xl: 24px;--text-3xl: 30px;--text-4xl: 36px;--text-5xl: 48px;--font-normal: 400;--font-medium: 500;--font-semibold: 600;--font-bold: 700;--leading-tight: 1.25;--leading-snug: 1.375;--leading-normal: 1.5;--leading-relaxed: 1.625;--tracking-tight: -.025em;--tracking-normal: 0;--tracking-wide: .025em;--transition-fast: .15s ease;--transition-base: .2s ease;--transition-slow: .3s ease;--z-dropdown: 100;--z-sticky: 200;--z-fixed: 300;--z-modal-backdrop: 400;--z-modal: 500;--z-popover: 600;--z-tooltip: 700;--background: 210 20% 98%;--foreground: 221 39% 11%;--card: 0 0% 100%;--card-foreground: 221 39% 11%;--popover: 0 0% 100%;--popover-foreground: 221 39% 11%;--primary: 217 80% 55%;--primary-foreground: 0 0% 100%;--secondary: 220 14% 96%;--secondary-foreground: 215 28% 17%;--muted: 220 14% 96%;--muted-foreground: 220 9% 46%;--accent: 217 100% 97%;--accent-foreground: 217 70% 40%;--destructive: 0 84% 60%;--destructive-foreground: 0 0% 98%;--border: 220 13% 91%;--input: 220 13% 91%;--ring: 217 80% 55%;--radius: .75rem;--milk-50: 210 20% 98%;--milk-100: 220 14% 96%;--milk-200: 220 13% 91%;--milk-300: 216 12% 84%;--milk-400: 218 11% 65%;--milk-500: 220 9% 46%;--milk-600: 215 14% 34%;--milk-700: 217 19% 27%;--milk-800: 215 28% 17%;--milk-900: 221 39% 11%}*{border-color:hsl(var(--border))}html{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{background-color:hsl(var(--background));color:hsl(var(--foreground));font-family:var(--font-sans);overflow:hidden}*{scrollbar-width:thin;scrollbar-color:hsl(var(--gray-300)) transparent}.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}.pointer-events-none{pointer-events:none}.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-\[50\%\]{left:50%}.right-0{right:0}.right-1\.5{right:.375rem}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-1\.5{top:.375rem}.top-1\/2{top:50%}.top-4{top:1rem}.top-\[50\%\]{top:50%}.z-10{z-index:10}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.mr-2{margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-auto{margin-top:auto}.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}.grid{display:grid}.h-0\.5{height:.125rem}.h-1{height:.25rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[180px\]{height:180px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[60vh\]{max-height:60vh}.max-h-\[85vh\]{max-height:85vh}.min-h-\[42px\]{min-height:42px}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-28{width:7rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.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-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-\[160px\]{width:160px}.w-\[240px\]{width:240px}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[100px\]{min-width:100px}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.max-w-\[200px\]{max-width:200px}.max-w-lg{max-width:32rem}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.-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-0{--tw-translate-x: 0px;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-5{--tw-translate-x: 1.25rem;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 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}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.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-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-12{gap:3rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-8{gap:2rem}.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-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-2\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.625rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.625rem * 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-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem * 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))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:calc(var(--radius) + 8px)}.rounded-\[2rem\]{border-radius:2rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-xl{border-radius:calc(var(--radius) + 4px)}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-\[hsl\(40\,10\%\,94\%\)\]{--tw-border-opacity: 1;border-color:hsl(40 10% 94% / var(--tw-border-opacity, 1))}.border-gray-200{border-color:hsl(var(--gray-200))}.border-input{border-color:hsl(var(--input))}.border-primary{border-color:hsl(var(--primary))}.border-transparent{border-color:transparent}.bg-\[hsl\(40\,20\%\,98\%\)\]{--tw-bg-opacity: 1;background-color:hsl(40 20% 98% / var(--tw-bg-opacity, 1))}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-background{background-color:hsl(var(--background))}.bg-black\/40{background-color:#0006}.bg-destructive{background-color:hsl(var(--destructive))}.bg-emerald-50{--tw-bg-opacity: 1;background-color:rgb(236 253 245 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-gray-200{background-color:hsl(var(--gray-200))}.bg-gray-400{background-color:hsl(var(--gray-400))}.bg-primary{background-color:hsl(var(--primary))}.bg-primary-100{background-color:hsl(var(--brand-100))}.bg-secondary{background-color:hsl(var(--secondary))}.bg-slate-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/80{background-color:#fffc}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-l{background-image:linear-gradient(to left,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-primary{--tw-gradient-from: hsl(var(--primary)) var(--tw-gradient-from-position);--tw-gradient-to: hsl(var(--primary) / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-purple-400{--tw-gradient-from: #c084fc var(--tw-gradient-from-position);--tw-gradient-to: rgb(192 132 252 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-rose-400{--tw-gradient-from: #fb7185 var(--tw-gradient-from-position);--tw-gradient-to: rgb(251 113 133 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-sky-400{--tw-gradient-from: #38bdf8 var(--tw-gradient-from-position);--tw-gradient-to: rgb(56 189 248 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-slate-400{--tw-gradient-from: #94a3b8 var(--tw-gradient-from-position);--tw-gradient-to: rgb(148 163 184 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.from-transparent{--tw-gradient-from: transparent var(--tw-gradient-from-position);--tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to)}.to-blue-500{--tw-gradient-to: #3b82f6 var(--tw-gradient-to-position)}.to-gray-500{--tw-gradient-to: hsl(var(--gray-500)) var(--tw-gradient-to-position)}.to-indigo-500{--tw-gradient-to: #6366f1 var(--tw-gradient-to-position)}.to-pink-500{--tw-gradient-to: #ec4899 var(--tw-gradient-to-position)}.to-primary-600{--tw-gradient-to: hsl(var(--brand-600)) var(--tw-gradient-to-position)}.to-white\/10{--tw-gradient-to: rgb(255 255 255 / .1) var(--tw-gradient-to-position)}.bg-clip-text{-webkit-background-clip:text;background-clip:text}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.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-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-16{padding-top:4rem;padding-bottom:4rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-20{padding-bottom:5rem}.pb-4{padding-bottom:1rem}.pr-1{padding-right:.25rem}.pr-2{padding-right:.5rem}.pr-20{padding-right:5rem}.pt-0{padding-top:0}.pt-2{padding-top:.5rem}.pt-3{padding-top:.75rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[12px\]{font-size:12px}.text-\[13px\]{font-size:13px}.text-\[15px\]{font-size:15px}.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-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-tight{line-height:1.25}.tracking-\[-0\.02em\]{letter-spacing:-.02em}.tracking-tight{letter-spacing:-.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-amber-600{--tw-text-opacity: 1;color:rgb(217 119 6 / var(--tw-text-opacity, 1))}.text-card-foreground{color:hsl(var(--card-foreground))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-emerald-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity, 1))}.text-gray-400{color:hsl(var(--gray-400))}.text-gray-700{color:hsl(var(--gray-700))}.text-primary{color:hsl(var(--primary))}.text-primary-700{color:hsl(var(--brand-700))}.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-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-transparent{color:transparent}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline-offset-4{text-underline-offset:4px}.accent-primary{accent-color:hsl(var(--primary))}.opacity-0{opacity:0}.opacity-70{opacity:.7}.opacity-90{opacity:.9}.mix-blend-multiply{mix-blend-mode:multiply}.shadow-card{--tw-shadow: var(--shadow-card);--tw-shadow-colored: var(--shadow-card);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)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px 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-card{--tw-shadow-color: hsl(var(--card));--tw-shadow: var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-0{--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(0px + 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)}.ring-offset-background{--tw-ring-offset-color: hsl(var(--background))}.ring-offset-white{--tw-ring-offset-color: #fff}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-md{--tw-backdrop-blur: blur(12px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-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}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}.duration-base{transition-duration:.2s}.duration-fast{transition-duration:.15s}.duration-slow{transition-duration:.3s}@keyframes enter{0%{opacity:var(--tw-enter-opacity, 1);transform:translate3d(var(--tw-enter-translate-x, 0),var(--tw-enter-translate-y, 0),0) scale3d(var(--tw-enter-scale, 1),var(--tw-enter-scale, 1),var(--tw-enter-scale, 1)) rotate(var(--tw-enter-rotate, 0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity, 1);transform:translate3d(var(--tw-exit-translate-x, 0),var(--tw-exit-translate-y, 0),0) scale3d(var(--tw-exit-scale, 1),var(--tw-exit-scale, 1),var(--tw-exit-scale, 1)) rotate(var(--tw-exit-rotate, 0))}}.animate-in{animation-name:enter;animation-duration:.15s;--tw-enter-opacity: initial;--tw-enter-scale: initial;--tw-enter-rotate: initial;--tw-enter-translate-x: initial;--tw-enter-translate-y: initial}.fade-in{--tw-enter-opacity: 0}.slide-in-from-left-2{--tw-enter-translate-x: -.5rem}.duration-300{animation-duration:.3s}.duration-base{animation-duration:.2s}.duration-fast{animation-duration:.15s}.duration-slow{animation-duration:.3s}.bg-gray-50{background-color:hsl(var(--gray-50))}.bg-gray-100{background-color:hsl(var(--gray-100))}.text-gray-500{color:hsl(var(--gray-500))}.text-gray-600{color:hsl(var(--gray-600))}.text-gray-900{color:hsl(var(--gray-900))}.shadow-card{box-shadow:var(--shadow-card)}.custom-scrollbar::-webkit-scrollbar{width:6px;height:6px}.custom-scrollbar::-webkit-scrollbar-track{background:transparent}.custom-scrollbar::-webkit-scrollbar-thumb{background:hsl(var(--gray-300));border-radius:6px}.custom-scrollbar::-webkit-scrollbar-thumb:hover{background:hsl(var(--gray-400))}.shadow-card{box-shadow:0 1px 3px #0000000d,0 1px 2px -1px #0000000d}@keyframes fadeIn{0%{opacity:0;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}}@keyframes slideIn{0%{opacity:0;transform:translate(-12px)}to{opacity:1;transform:translate(0)}}@keyframes scaleIn{0%{opacity:0;transform:scale(.97)}to{opacity:1;transform:scale(1)}}@keyframes pulse-soft{0%,to{opacity:1}50%{opacity:.8}}.animate-fade-in{animation:fadeIn .4s cubic-bezier(.16,1,.3,1) forwards}.animate-slide-in{animation:slideIn .35s cubic-bezier(.16,1,.3,1) forwards}.animate-scale-in{animation:scaleIn .35s cubic-bezier(.16,1,.3,1) forwards}.animate-pulse-soft{animation:pulse-soft 3s ease-in-out infinite}.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-gray-400::-moz-placeholder{color:hsl(var(--gray-400))}.placeholder\:text-gray-400::placeholder{color:hsl(var(--gray-400))}.hover\:-translate-y-0\.5:hover{--tw-translate-y: -.125rem;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))}.hover\:border-gray-300:hover{border-color:hsl(var(--gray-300))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive) / .9)}.hover\:bg-gray-200:hover{background-color:hsl(var(--gray-200))}.hover\:bg-gray-300:hover{background-color:hsl(var(--gray-300))}.hover\:bg-primary:hover{background-color:hsl(var(--primary))}.hover\:bg-primary-600:hover{background-color:hsl(var(--brand-600))}.hover\:bg-secondary:hover{background-color:hsl(var(--secondary))}.hover\:bg-white:hover{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.hover\:bg-white\/50:hover{background-color:#ffffff80}.hover\:text-gray-700:hover{color:hsl(var(--gray-700))}.hover\:text-primary-foreground:hover{color:hsl(var(--primary-foreground))}.hover\:text-red-300:hover{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.hover\:text-secondary-foreground:hover{color:hsl(var(--secondary-foreground))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-card-hover:hover{--tw-shadow: var(--shadow-card-hover);--tw-shadow-colored: var(--shadow-card-hover);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:bg-gray-100:hover{background-color:hsl(var(--gray-100))}.hover\:text-gray-600:hover{color:hsl(var(--gray-600))}.hover\:text-gray-900:hover{color:hsl(var(--gray-900))}.hover\:shadow-card-hover:hover{box-shadow:var(--shadow-card-hover);box-shadow:0 10px 15px -3px #00000014,0 4px 6px -4px #0000000d}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset: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-primary:focus-visible{--tw-ring-color: hsl(var(--primary))}.focus-visible\:ring-ring:focus-visible{--tw-ring-color: hsl(var(--ring))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width: 2px}.focus-visible\:ring-offset-white:focus-visible{--tw-ring-offset-color: #fff}.active\:bg-primary-700:active{background-color:hsl(var(--brand-700))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:translate-x-0\.5{--tw-translate-x: .125rem;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))}.group:hover .group-hover\:scale-110{--tw-scale-x: 1.1;--tw-scale-y: 1.1;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))}.group:hover .group-hover\:border-gray-300{border-color:hsl(var(--gray-300))}.group:hover .group-hover\:text-primary{color:hsl(var(--primary))}.group:hover .group-hover\:opacity-100{opacity:1}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.data-\[state\=checked\]\:bg-emerald-500[data-state=checked]{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.data-\[state\=open\]\:animate-in[data-state=open]{animation-name:enter;animation-duration:.15s;--tw-enter-opacity: initial;--tw-enter-scale: initial;--tw-enter-rotate: initial;--tw-enter-translate-x: initial;--tw-enter-translate-y: initial}.data-\[state\=closed\]\:animate-out[data-state=closed]{animation-name:exit;animation-duration:.15s;--tw-exit-opacity: initial;--tw-exit-scale: initial;--tw-exit-rotate: initial;--tw-exit-translate-x: initial;--tw-exit-translate-y: initial}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity: 0}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity: 0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale: .95}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale: .95}.data-\[state\=closed\]\:slide-out-to-left-1\/2[data-state=closed]{--tw-exit-translate-x: -50%}.data-\[state\=closed\]\:slide-out-to-top-\[48\%\][data-state=closed]{--tw-exit-translate-y: -48%}.data-\[state\=open\]\:slide-in-from-left-1\/2[data-state=open]{--tw-enter-translate-x: -50%}.data-\[state\=open\]\:slide-in-from-top-\[48\%\][data-state=open]{--tw-enter-translate-y: -48%}@media(min-width:640px){.sm\:max-w-\[500px\]{max-width:500px}.sm\:max-w-\[550px\]{max-width:550px}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:justify-end{justify-content:flex-end}.sm\:gap-3{gap:.75rem}.sm\:text-left{text-align:left}}@media(min-width:768px){.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))}}@media(min-width:1280px){.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}