@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.
@@ -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-tkacLlNv.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-Caal1pcl.css">
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
@@ -11,6 +11,7 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "@aiready/components": "workspace:*",
14
+ "@aiready/core": "workspace:*",
14
15
  "d3": "^7.9.0",
15
16
  "react": "^19.0.0",
16
17
  "react-dom": "^19.0.0"
package/web/src/App.tsx CHANGED
@@ -1,256 +1,5 @@
1
- import { useEffect, useState, useMemo } from 'react';
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
- // All available severity levels
19
- const ALL_SEVERITIES: SeverityLevel[] = ['critical', 'major', 'minor', 'info'];
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
- // Types for the visualization
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 data = await response.json();
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 data;
316
+ return reportData;
317
317
  }
318
318
  } catch {
319
319
  continue;
@@ -21,7 +21,7 @@ export default defineConfig(async ({ command }) => {
21
21
  }
22
22
  }
23
23
 
24
- const plugins: any[] = [react(), tailwindcss()];
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}}