@aiready/visualizer 0.6.2 → 0.6.3
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/dist/cli.js +8 -0
- package/dist/cli.js.map +1 -1
- package/dist/graph/index.d.ts +12 -3
- package/dist/graph/index.js +8 -0
- package/dist/graph/index.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/web/dist/assets/index-SEmv1C_v.css +989 -0
- package/web/dist/assets/index-dG0hWy9V.js +31736 -0
- package/web/dist/assets/index-dG0hWy9V.js.map +1 -0
- package/web/dist/index.html +2 -2
- package/web/package.json +1 -0
- package/web/src/App.tsx +3 -254
- package/web/src/types.ts +2 -41
- package/web/src/utils.ts +4 -4
- package/web/vite.config.ts +3 -1
- package/web/dist/assets/index-Caal1pcl.css +0 -1
- package/web/dist/assets/index-tkacLlNv.js +0 -48
package/web/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>AIReady Visualizer (Dev)</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-dG0hWy9V.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-SEmv1C_v.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/web/package.json
CHANGED
package/web/src/App.tsx
CHANGED
|
@@ -1,256 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { GraphData, FileNode, SeverityLevel, EdgeType } from './types';
|
|
3
|
-
import { themeConfig } from './constants';
|
|
4
|
-
import { transformReportToGraph, loadReportData } from './utils';
|
|
5
|
-
import { useDimensions } from './hooks/useDimensions';
|
|
6
|
-
import { useTheme } from './hooks/useTheme';
|
|
7
|
-
import {
|
|
8
|
-
LoadingSpinner,
|
|
9
|
-
ErrorDisplay,
|
|
10
|
-
} from '@aiready/components';
|
|
11
|
-
import {
|
|
12
|
-
Navbar,
|
|
13
|
-
LegendPanel,
|
|
14
|
-
NodeDetails,
|
|
15
|
-
GraphCanvas,
|
|
16
|
-
} from './components';
|
|
1
|
+
import { LegendPanel } from './components';
|
|
17
2
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// All available edge types (excluding 'default' and 'reference' as they're filtered out in UI)
|
|
22
|
-
const ALL_EDGE_TYPES: EdgeType[] = ['similarity', 'dependency', 'related'];
|
|
23
|
-
|
|
24
|
-
function App() {
|
|
25
|
-
const [data, setData] = useState<GraphData | null>(null);
|
|
26
|
-
const [selectedNode, setSelectedNode] = useState<FileNode | null>(null);
|
|
27
|
-
const [loading, setLoading] = useState(true);
|
|
28
|
-
const [error, setError] = useState<string | null>(null);
|
|
29
|
-
const [truncatedWarning, setTruncatedWarning] = useState<{
|
|
30
|
-
nodes: boolean;
|
|
31
|
-
edges: boolean;
|
|
32
|
-
} | null>(null);
|
|
33
|
-
|
|
34
|
-
// Filter state - start with all visible
|
|
35
|
-
const [visibleSeverities, setVisibleSeverities] = useState<
|
|
36
|
-
Set<SeverityLevel>
|
|
37
|
-
>(new Set(ALL_SEVERITIES));
|
|
38
|
-
const [visibleEdgeTypes, setVisibleEdgeTypes] = useState<Set<EdgeType>>(
|
|
39
|
-
new Set(ALL_EDGE_TYPES)
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
const { containerRef, dimensions } = useDimensions();
|
|
43
|
-
const { theme, setTheme, effectiveTheme } = useTheme();
|
|
44
|
-
|
|
45
|
-
const colors = themeConfig[effectiveTheme];
|
|
46
|
-
|
|
47
|
-
// Load report data
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
const loadData = async () => {
|
|
50
|
-
try {
|
|
51
|
-
const reportData = await loadReportData();
|
|
52
|
-
|
|
53
|
-
if (!reportData) {
|
|
54
|
-
setError(
|
|
55
|
-
'No scan data found. Run "pnpm aiready scan ." then copy to public/report-data.json'
|
|
56
|
-
);
|
|
57
|
-
setLoading(false);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Extract optional visualizer config from report (injected by CLI)
|
|
62
|
-
const visualizerConfig = (reportData as any).visualizerConfig;
|
|
63
|
-
const graphData = transformReportToGraph(reportData, visualizerConfig);
|
|
64
|
-
setData(graphData);
|
|
65
|
-
|
|
66
|
-
// Show warning if graph was truncated
|
|
67
|
-
if (graphData.truncated?.nodes || graphData.truncated?.edges) {
|
|
68
|
-
setTruncatedWarning({
|
|
69
|
-
nodes: graphData.truncated.nodes,
|
|
70
|
-
edges: graphData.truncated.edges,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
} catch (err: any) {
|
|
74
|
-
console.error('Failed to load or transform report data:', err);
|
|
75
|
-
setError(`Failed to load visualization: ${err.message || 'Unknown error'}`);
|
|
76
|
-
} finally {
|
|
77
|
-
setLoading(false);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
loadData();
|
|
82
|
-
}, []);
|
|
83
|
-
|
|
84
|
-
// Toggle severity visibility
|
|
85
|
-
const handleToggleSeverity = (severity: SeverityLevel) => {
|
|
86
|
-
setVisibleSeverities((prev) => {
|
|
87
|
-
const next = new Set(prev);
|
|
88
|
-
if (next.has(severity)) {
|
|
89
|
-
next.delete(severity);
|
|
90
|
-
} else {
|
|
91
|
-
next.add(severity);
|
|
92
|
-
}
|
|
93
|
-
return next;
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// Toggle edge type visibility
|
|
98
|
-
const handleToggleEdgeType = (edgeType: EdgeType) => {
|
|
99
|
-
setVisibleEdgeTypes((prev) => {
|
|
100
|
-
const next = new Set(prev);
|
|
101
|
-
if (next.has(edgeType)) {
|
|
102
|
-
next.delete(edgeType);
|
|
103
|
-
} else {
|
|
104
|
-
next.add(edgeType);
|
|
105
|
-
}
|
|
106
|
-
return next;
|
|
107
|
-
});
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
// Filter data based on visible severities and edge types
|
|
111
|
-
// Also hides edges connected to hidden nodes
|
|
112
|
-
const filteredData = useMemo(() => {
|
|
113
|
-
if (!data) return null;
|
|
114
|
-
|
|
115
|
-
// Get set of visible node IDs
|
|
116
|
-
const visibleNodeIds = new Set(
|
|
117
|
-
data.nodes
|
|
118
|
-
.filter((node) => {
|
|
119
|
-
const severity = (node.severity || 'default') as SeverityLevel;
|
|
120
|
-
return visibleSeverities.has(severity);
|
|
121
|
-
})
|
|
122
|
-
.map((node) => node.id)
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
// Filter nodes: keep if severity is visible
|
|
126
|
-
const filteredNodes = data.nodes.filter((node) => {
|
|
127
|
-
const severity = (node.severity || 'default') as SeverityLevel;
|
|
128
|
-
return visibleSeverities.has(severity);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Filter edges: keep if:
|
|
132
|
-
// 1. Edge type is visible AND
|
|
133
|
-
// 2. Both source and target nodes are visible
|
|
134
|
-
const filteredEdges = data.edges.filter((edge) => {
|
|
135
|
-
// Check edge type visibility
|
|
136
|
-
const edgeType = (edge.type || 'default') as EdgeType;
|
|
137
|
-
if (!visibleEdgeTypes.has(edgeType)) {
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
// Check that both connected nodes are visible
|
|
141
|
-
const sourceId =
|
|
142
|
-
typeof edge.source === 'string' ? edge.source : (edge.source as any).id;
|
|
143
|
-
const targetId =
|
|
144
|
-
typeof edge.target === 'string' ? edge.target : (edge.target as any).id;
|
|
145
|
-
return visibleNodeIds.has(sourceId) && visibleNodeIds.has(targetId);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
nodes: filteredNodes,
|
|
150
|
-
edges: filteredEdges,
|
|
151
|
-
};
|
|
152
|
-
}, [data, visibleSeverities, visibleEdgeTypes]);
|
|
153
|
-
|
|
154
|
-
// Handle loading state
|
|
155
|
-
if (loading) {
|
|
156
|
-
return <LoadingSpinner />;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Handle error state
|
|
160
|
-
if (error) {
|
|
161
|
-
return <ErrorDisplay message={error} />;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return (
|
|
165
|
-
<div
|
|
166
|
-
className="flex flex-col h-screen font-sans"
|
|
167
|
-
style={{ backgroundColor: colors.bg, color: colors.text }}
|
|
168
|
-
>
|
|
169
|
-
<Navbar
|
|
170
|
-
colors={colors}
|
|
171
|
-
theme={theme}
|
|
172
|
-
setTheme={setTheme}
|
|
173
|
-
data={filteredData}
|
|
174
|
-
metadata={data?.metadata}
|
|
175
|
-
/>
|
|
176
|
-
|
|
177
|
-
{/* Truncation warning banner */}
|
|
178
|
-
{truncatedWarning &&
|
|
179
|
-
(truncatedWarning.nodes || truncatedWarning.edges) && (
|
|
180
|
-
<div
|
|
181
|
-
className="px-4 py-3 bg-yellow-50 border-b flex items-center justify-between"
|
|
182
|
-
style={{ borderColor: colors.panelBorder }}
|
|
183
|
-
>
|
|
184
|
-
<div className="flex-1">
|
|
185
|
-
<p className="text-sm text-yellow-800 font-medium">
|
|
186
|
-
⚠️ Graph visualization truncated at display limits.
|
|
187
|
-
{truncatedWarning.nodes && ' Too many nodes.'}
|
|
188
|
-
{truncatedWarning.edges && ' Too many edges.'}
|
|
189
|
-
</p>
|
|
190
|
-
<p className="text-xs text-yellow-700 mt-1">
|
|
191
|
-
To show more data, increase graph limits in aiready.json:
|
|
192
|
-
<code
|
|
193
|
-
className="bg-yellow-100 px-1 rounded"
|
|
194
|
-
style={{ marginLeft: '4px' }}
|
|
195
|
-
>
|
|
196
|
-
{
|
|
197
|
-
'"visualizer": { "graph": { "maxNodes": 2000, "maxEdges": 5000 } }'
|
|
198
|
-
}
|
|
199
|
-
</code>
|
|
200
|
-
</p>
|
|
201
|
-
</div>
|
|
202
|
-
<button
|
|
203
|
-
onClick={() => setTruncatedWarning(null)}
|
|
204
|
-
className="text-yellow-600 hover:text-yellow-800 ml-4 flex-shrink-0"
|
|
205
|
-
>
|
|
206
|
-
✕
|
|
207
|
-
</button>
|
|
208
|
-
</div>
|
|
209
|
-
)}
|
|
210
|
-
|
|
211
|
-
<div className="flex flex-1 overflow-hidden">
|
|
212
|
-
<div ref={containerRef} className="flex-1 relative">
|
|
213
|
-
{filteredData && (
|
|
214
|
-
<GraphCanvas
|
|
215
|
-
data={filteredData}
|
|
216
|
-
dimensions={dimensions}
|
|
217
|
-
colors={colors}
|
|
218
|
-
effectiveTheme={effectiveTheme}
|
|
219
|
-
onNodeClick={setSelectedNode}
|
|
220
|
-
/>
|
|
221
|
-
)}
|
|
222
|
-
</div>
|
|
223
|
-
|
|
224
|
-
{/* Right panel: Legend OR NodeDetails */}
|
|
225
|
-
<div
|
|
226
|
-
className="w-80 border-l flex flex-col h-full"
|
|
227
|
-
style={{
|
|
228
|
-
backgroundColor: colors.panel,
|
|
229
|
-
borderColor: colors.panelBorder,
|
|
230
|
-
}}
|
|
231
|
-
>
|
|
232
|
-
{selectedNode ? (
|
|
233
|
-
<NodeDetails
|
|
234
|
-
colors={colors}
|
|
235
|
-
selectedNode={selectedNode}
|
|
236
|
-
onClose={() => setSelectedNode(null)}
|
|
237
|
-
/>
|
|
238
|
-
) : (
|
|
239
|
-
<div className="flex-1 overflow-y-auto">
|
|
240
|
-
<LegendPanel
|
|
241
|
-
colors={colors}
|
|
242
|
-
visibleSeverities={visibleSeverities}
|
|
243
|
-
visibleEdgeTypes={visibleEdgeTypes}
|
|
244
|
-
onToggleSeverity={handleToggleSeverity}
|
|
245
|
-
onToggleEdgeType={handleToggleEdgeType}
|
|
246
|
-
metadata={data?.metadata}
|
|
247
|
-
/>
|
|
248
|
-
</div>
|
|
249
|
-
)}
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
253
|
-
);
|
|
3
|
+
export default function App() {
|
|
4
|
+
return <LegendPanel colors={{} as any} visibleSeverities={new Set()} visibleEdgeTypes={new Set()} onToggleSeverity={() => {}} onToggleEdgeType={() => {}} metadata={undefined} />;
|
|
254
5
|
}
|
|
255
|
-
|
|
256
|
-
export default App;
|
package/web/src/types.ts
CHANGED
|
@@ -1,45 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import type { GraphNode, GraphEdge, GraphData, BusinessMetrics } from '@aiready/core/client';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* Business impact metrics (v0.10+)
|
|
5
|
-
*/
|
|
6
|
-
export interface BusinessMetrics {
|
|
7
|
-
estimatedMonthlyCost?: number;
|
|
8
|
-
estimatedDeveloperHours?: number;
|
|
9
|
-
aiAcceptanceRate?: number;
|
|
10
|
-
aiReadinessScore?: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface FileNode {
|
|
14
|
-
id: string;
|
|
15
|
-
label: string;
|
|
16
|
-
value: number;
|
|
17
|
-
color: string;
|
|
18
|
-
title: string;
|
|
19
|
-
duplicates?: number;
|
|
20
|
-
tokenCost?: number;
|
|
21
|
-
severity?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface GraphEdge {
|
|
25
|
-
source: string;
|
|
26
|
-
target: string;
|
|
27
|
-
type: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface GraphData {
|
|
31
|
-
nodes: FileNode[];
|
|
32
|
-
edges: GraphEdge[];
|
|
33
|
-
truncated?: {
|
|
34
|
-
nodes: boolean;
|
|
35
|
-
edges: boolean;
|
|
36
|
-
nodeCount: number;
|
|
37
|
-
edgeCount: number;
|
|
38
|
-
nodeLimit: number;
|
|
39
|
-
edgeLimit: number;
|
|
40
|
-
};
|
|
41
|
-
metadata?: BusinessMetrics;
|
|
42
|
-
}
|
|
3
|
+
export type { GraphNode as FileNode, GraphEdge, GraphData, BusinessMetrics };
|
|
43
4
|
|
|
44
5
|
// Filter types
|
|
45
6
|
export type SeverityLevel = 'critical' | 'major' | 'minor' | 'info';
|
package/web/src/utils.ts
CHANGED
|
@@ -278,8 +278,8 @@ export function transformReportToGraph(
|
|
|
278
278
|
related: 3,
|
|
279
279
|
};
|
|
280
280
|
const sortedEdges = [...edges].sort((a, b) => {
|
|
281
|
-
const priorityA = edgePriority[a.type] || 99;
|
|
282
|
-
const priorityB = edgePriority[b.type] || 99;
|
|
281
|
+
const priorityA = a.type ? edgePriority[a.type] || 99 : 99;
|
|
282
|
+
const priorityB = b.type ? edgePriority[b.type] || 99 : 99;
|
|
283
283
|
return priorityA - priorityB;
|
|
284
284
|
});
|
|
285
285
|
|
|
@@ -310,10 +310,10 @@ export async function loadReportData(): Promise<ReportData | null> {
|
|
|
310
310
|
try {
|
|
311
311
|
const response = await fetch(path);
|
|
312
312
|
if (response.ok) {
|
|
313
|
-
const
|
|
313
|
+
const reportData = await response.json();
|
|
314
314
|
// If it's a unified report, it might be nested under 'results' if it was a deep clone
|
|
315
315
|
// but typically unified report IS the data.
|
|
316
|
-
return
|
|
316
|
+
return reportData;
|
|
317
317
|
}
|
|
318
318
|
} catch {
|
|
319
319
|
continue;
|
package/web/vite.config.ts
CHANGED
|
@@ -21,7 +21,7 @@ export default defineConfig(async ({ command }) => {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const plugins: any[] = [react()
|
|
24
|
+
const plugins: any[] = [react() /*, tailwindcss()*/];
|
|
25
25
|
// Dev-time middleware: if the CLI sets AIREADY_REPORT_PATH when spawning Vite,
|
|
26
26
|
// serve that file at /report-data.json so the client can fetch the report
|
|
27
27
|
// directly from the consumer working directory without copying into node_modules.
|
|
@@ -79,6 +79,8 @@ export default defineConfig(async ({ command }) => {
|
|
|
79
79
|
plugins,
|
|
80
80
|
build: {
|
|
81
81
|
outDir: 'dist',
|
|
82
|
+
minify: false,
|
|
83
|
+
sourcemap: true,
|
|
82
84
|
emptyOutDir: true,
|
|
83
85
|
rollupOptions: {
|
|
84
86
|
output: {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-100:oklch(97.3% .071 103.193);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-700:oklch(55.4% .135 66.442);--color-yellow-800:oklch(47.6% .114 61.907);--color-cyan-400:oklch(78.9% .154 211.53);--color-indigo-500:oklch(58.5% .233 277.117);--color-purple-400:oklch(71.4% .203 305.504);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-black:900;--tracking-wide:.025em;--tracking-wider:.05em;--tracking-widest:.1em;--leading-tight:1.25;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in:cubic-bezier(.4, 0, 1, 1);--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--blur-sm:8px;--blur-md:12px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}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;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.end{inset-inline-end:var(--spacing)}.bottom-6{bottom:calc(var(--spacing) * 6)}.left-6{left:calc(var(--spacing) * 6)}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.mt-1{margin-top:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.ml-4{margin-left:calc(var(--spacing) * 4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.h-1{height:calc(var(--spacing) * 1)}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-7{height:calc(var(--spacing) * 7)}.h-9{height:calc(var(--spacing) * 9)}.h-16{height:calc(var(--spacing) * 16)}.h-full{height:100%}.h-screen{height:100vh}.w-2{width:calc(var(--spacing) * 2)}.w-3{width:calc(var(--spacing) * 3)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-7{width:calc(var(--spacing) * 7)}.w-10{width:calc(var(--spacing) * 10)}.w-80{width:calc(var(--spacing) * 80)}.w-auto{width:auto}.w-full{width:100%}.w-px{width:1px}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.-space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing) * -2) * var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing) * -2) * calc(1 - var(--tw-space-x-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-indigo-500\/10{border-color:#625fff1a}@supports (color:color-mix(in lab,red,red)){.border-indigo-500\/10{border-color:color-mix(in oklab,var(--color-indigo-500) 10%,transparent)}}.bg-cyan-400{background-color:var(--color-cyan-400)}.bg-purple-400{background-color:var(--color-purple-400)}.bg-yellow-50{background-color:var(--color-yellow-50)}.bg-yellow-100{background-color:var(--color-yellow-100)}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.px-1{padding-inline:calc(var(--spacing) * 1)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-10{padding-block:calc(var(--spacing) * 10)}.text-center{text-align:center}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.tracking-widest{--tw-tracking:var(--tracking-widest);letter-spacing:var(--tracking-widest)}.break-all{word-break:break-all}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-400{color:var(--color-amber-400)}.text-amber-500{color:var(--color-amber-500)}.text-cyan-400{color:var(--color-cyan-400)}.text-purple-400{color:var(--color-purple-400)}.text-yellow-600{color:var(--color-yellow-600)}.text-yellow-700{color:var(--color-yellow-700)}.text-yellow-800{color:var(--color-yellow-800)}.uppercase{text-transform:uppercase}.opacity-40{opacity:.4}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.backdrop-blur-md{--tw-backdrop-blur:blur(var(--blur-md));-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(var(--blur-sm));-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:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}@media(hover:hover){.hover\:bg-white\/5:hover{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/5:hover{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.hover\:text-yellow-800:hover{color:var(--color-yellow-800)}}}*{box-sizing:border-box;margin:0;padding:0}html,body,#root{width:100%;height:100%;overflow:hidden}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;margin:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}#root{flex-direction:column;width:100%;height:100vh;display:flex}@keyframes fadeIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}
|