polymo 0.1.0__py3-none-any.whl

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.
polymo/__init__.py ADDED
@@ -0,0 +1,32 @@
1
+ """Public entrypoints for the polymo REST data source."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pyspark.sql import SparkSession
6
+
7
+ from .config import RestSourceConfig, config_to_dict, dump_config, load_config, parse_config
8
+ from .datasource import ApiReader
9
+
10
+ __all__ = [
11
+ "register",
12
+ "ApiReader",
13
+ "RestSourceConfig",
14
+ "config_to_dict",
15
+ "dump_config",
16
+ "load_config",
17
+ "parse_config",
18
+ ]
19
+
20
+
21
+ def _alias_datasource(name: str) -> type[ApiReader]:
22
+ alias_name = name
23
+
24
+ class AliasRestDataSource(ApiReader):
25
+ @classmethod
26
+ def name(cls) -> str: # type: ignore[override]
27
+ return alias_name
28
+
29
+ AliasRestDataSource.__name__ = f"RestDataSource_{name.replace('.', '_')}"
30
+ AliasRestDataSource.__qualname__ = AliasRestDataSource.__name__
31
+ AliasRestDataSource.__module__ = ApiReader.__module__
32
+ return AliasRestDataSource
@@ -0,0 +1,5 @@
1
+ """Web interface for building polymo REST configurations."""
2
+
3
+ from .app import create_app
4
+
5
+ __all__ = ["create_app"]
polymo/builder/app.py ADDED
@@ -0,0 +1,339 @@
1
+ """FastAPI application powering the polymo web builder."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from functools import partial
6
+ from importlib import resources
7
+ from typing import Any, Dict, List, Optional, Tuple
8
+
9
+ import yaml
10
+ from fastapi import FastAPI, HTTPException, Request
11
+ from fastapi.responses import FileResponse
12
+ from fastapi.staticfiles import StaticFiles
13
+ from fastapi.templating import Jinja2Templates
14
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
15
+ from starlette.concurrency import run_in_threadpool
16
+
17
+ from ..config import (
18
+ ConfigError,
19
+ RestSourceConfig,
20
+ config_to_dict,
21
+ dump_config,
22
+ parse_config,
23
+ )
24
+ from ..rest_client import RestClient
25
+
26
+ PACKAGE_ROOT = resources.files(__package__)
27
+ TEMPLATES = Jinja2Templates(directory=str(PACKAGE_ROOT.joinpath("templates")))
28
+ STATIC_PATH = PACKAGE_ROOT.joinpath("static")
29
+
30
+ SAMPLE_CONFIG = """\
31
+ version: 0.1
32
+ source:
33
+ type: rest
34
+ base_url: https://jsonplaceholder.typicode.com
35
+ stream:
36
+ name: posts
37
+ path: /posts
38
+ params:
39
+ _limit: 25
40
+ pagination:
41
+ type: none
42
+ incremental:
43
+ mode: null
44
+ cursor_param: null
45
+ cursor_field: null
46
+ infer_schema: true
47
+ schema: null
48
+ """
49
+
50
+ SAMPLE_CONFIG_OBJECT = parse_config(yaml.safe_load(SAMPLE_CONFIG))
51
+ SAMPLE_CONFIG_DICT = config_to_dict(SAMPLE_CONFIG_OBJECT)
52
+ SAMPLE_CONFIG_YAML = dump_config(SAMPLE_CONFIG_OBJECT)
53
+
54
+
55
+ class ValidationRequest(BaseModel):
56
+ config: Optional[str] = Field(None, description="YAML configuration text")
57
+ config_dict: Optional[Dict[str, Any]] = Field(
58
+ None, description="Configuration provided as a dictionary"
59
+ )
60
+ token: Optional[str] = Field(None, description="Bearer token supplied separately (not stored)")
61
+ options: Optional[Dict[str, Any]] = Field(
62
+ default=None, description="Spark reader options provided alongside the config"
63
+ )
64
+
65
+ model_config = ConfigDict(extra="ignore")
66
+
67
+ @model_validator(mode="after")
68
+ def _ensure_payload(self) -> "ValidationRequest":
69
+ if self.config is None and self.config_dict is None:
70
+ raise ValueError("Either 'config' or 'config_dict' must be provided")
71
+ return self
72
+
73
+
74
+ class ValidationResponse(BaseModel):
75
+ valid: bool
76
+ stream: str | None = None
77
+ message: Optional[str] = None
78
+ config: Optional[Dict[str, Any]] = None
79
+ yaml: Optional[str] = None
80
+
81
+
82
+ class SampleRequest(BaseModel):
83
+ config: Optional[str] = None
84
+ config_dict: Optional[Dict[str, Any]] = None
85
+ token: Optional[str] = None
86
+ limit: int = Field(20, ge=1, le=500, description="Maximum records to preview")
87
+ options: Optional[Dict[str, Any]] = Field(
88
+ default=None, description="Spark reader options provided alongside the config"
89
+ )
90
+
91
+ model_config = ConfigDict(extra="ignore")
92
+
93
+ @model_validator(mode="after")
94
+ def _ensure_payload(self) -> "SampleRequest":
95
+ if self.config is None and self.config_dict is None:
96
+ raise ValueError("Either 'config' or 'config_dict' must be provided")
97
+ return self
98
+
99
+
100
+ class SampleResponse(BaseModel):
101
+ stream: str
102
+ records: List[Dict[str, Any]]
103
+ dtypes: List[Dict[str, str]] = Field(default_factory=list, description="Spark column data types")
104
+ raw_pages: List[Dict[str, Any]] = Field(
105
+ default_factory=list, description="Raw REST API responses captured per page"
106
+ )
107
+ rest_error: Optional[str] = None
108
+
109
+
110
+ class FormatRequest(BaseModel):
111
+ config_dict: Dict[str, Any]
112
+
113
+
114
+ class FormatResponse(BaseModel):
115
+ yaml: str
116
+
117
+
118
+ def create_app() -> FastAPI:
119
+ app = FastAPI(title="polymo builder", version="0.1.0")
120
+
121
+ app.mount("/static", StaticFiles(directory=str(STATIC_PATH)), name="static")
122
+
123
+ @app.get("/favicon.ico", include_in_schema=False)
124
+ async def favicon() -> FileResponse: # pragma: no cover - static convenience endpoint
125
+ return FileResponse(STATIC_PATH / "favicon.ico")
126
+
127
+ @app.get("/apple-touch-icon.png", include_in_schema=False)
128
+ @app.get("/apple-touch-icon-precomposed.png", include_in_schema=False)
129
+ async def apple_touch_icon() -> FileResponse: # pragma: no cover - static convenience endpoint
130
+ # Re-use existing high-res logo as apple touch icon
131
+ return FileResponse(STATIC_PATH / "logo192.png")
132
+
133
+ @app.get("/")
134
+ async def index(request: Request) -> Any:
135
+ return TEMPLATES.TemplateResponse(
136
+ "index.html",
137
+ {
138
+ "request": request,
139
+ "sample_config": SAMPLE_CONFIG_YAML,
140
+ "sample_config_dict": SAMPLE_CONFIG_DICT,
141
+ },
142
+ )
143
+
144
+ @app.post("/api/validate", response_model=ValidationResponse)
145
+ async def validate_config(payload: ValidationRequest) -> ValidationResponse:
146
+ try:
147
+ config = _load_config_payload(
148
+ payload.config, payload.config_dict, payload.token, payload.options
149
+ )
150
+ except ConfigError as exc:
151
+ return ValidationResponse(valid=False, stream=None, message=str(exc))
152
+ except ValueError as exc:
153
+ return ValidationResponse(valid=False, stream=None, message=str(exc))
154
+
155
+ config_dict = config_to_dict(config)
156
+ return ValidationResponse(
157
+ valid=True,
158
+ stream=config.stream.name,
159
+ message="Configuration is valid",
160
+ config=config_dict,
161
+ yaml=dump_config(config),
162
+ )
163
+
164
+ @app.post("/api/sample", response_model=SampleResponse)
165
+ async def sample_records(payload: SampleRequest) -> SampleResponse:
166
+ try:
167
+ config = _load_config_payload(
168
+ payload.config, payload.config_dict, payload.token, payload.options
169
+ )
170
+ except ConfigError as exc:
171
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
172
+ except ValueError as exc:
173
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
174
+
175
+ stream_config = config.stream
176
+
177
+ raw_pages, rest_error = await run_in_threadpool(
178
+ partial(_collect_rest_preview, config, payload.limit)
179
+ )
180
+
181
+ if rest_error:
182
+ return SampleResponse(
183
+ stream=stream_config.name,
184
+ records=[],
185
+ dtypes=[],
186
+ raw_pages=raw_pages,
187
+ rest_error=rest_error,
188
+ )
189
+
190
+ try:
191
+ records, dtypes = await run_in_threadpool(
192
+ partial(_collect_records, config, payload.token, payload.limit)
193
+ )
194
+ except Exception as exc: # pragma: no cover - surfaced to UI
195
+ raise HTTPException(status_code=502, detail=str(exc)) from exc
196
+
197
+ return SampleResponse(
198
+ stream=stream_config.name,
199
+ records=records,
200
+ dtypes=dtypes,
201
+ raw_pages=raw_pages,
202
+ rest_error=None,
203
+ )
204
+
205
+ @app.post("/api/format", response_model=FormatResponse)
206
+ async def format_config(payload: FormatRequest) -> FormatResponse:
207
+ try:
208
+ config = parse_config(payload.config_dict)
209
+ except ConfigError as exc:
210
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
211
+ return FormatResponse(yaml=dump_config(config))
212
+
213
+ return app
214
+
215
+
216
+ def _load_config_payload(
217
+ config_text: Optional[str],
218
+ config_dict: Optional[Dict[str, Any]],
219
+ token: Optional[str] = None,
220
+ options: Optional[Dict[str, Any]] = None,
221
+ ) -> RestSourceConfig:
222
+ if config_dict is not None:
223
+ return parse_config(config_dict, token=token, options=options)
224
+ if config_text is None:
225
+ raise ConfigError("Configuration payload is missing")
226
+ return _parse_yaml(config_text, token=token, options=options)
227
+
228
+
229
+ def _parse_yaml(
230
+ text: str,
231
+ token: Optional[str] = None,
232
+ options: Optional[Dict[str, Any]] = None,
233
+ ) -> RestSourceConfig:
234
+ try:
235
+ parsed = yaml.safe_load(text)
236
+ except yaml.YAMLError as exc:
237
+ raise ConfigError(f"Invalid YAML: {exc}") from exc
238
+ return parse_config(parsed, token=token, options=options)
239
+
240
+
241
+ def _collect_rest_preview(config: RestSourceConfig, limit: int) -> Tuple[List[Dict[str, Any]], Optional[str]]:
242
+ pages: List[Dict[str, Any]] = []
243
+ total_records = 0
244
+
245
+ try:
246
+ with RestClient(base_url=config.base_url, auth=config.auth, options=config.options) as client:
247
+ for index, page in enumerate(client.fetch_pages(config.stream), start=1):
248
+ remaining = max(0, limit - total_records)
249
+ if remaining <= 0:
250
+ break
251
+
252
+ page_records = list(page.records)
253
+ if remaining < len(page_records):
254
+ page_records = page_records[:remaining]
255
+
256
+ total_records += len(page_records)
257
+
258
+ pages.append(
259
+ {
260
+ "page": index,
261
+ "url": page.url,
262
+ "status_code": page.status_code,
263
+ "headers": dict(page.headers),
264
+ "records": page_records,
265
+ "payload": page.payload,
266
+ }
267
+ )
268
+
269
+ if total_records >= limit:
270
+ break
271
+ return pages, None
272
+ except Exception as exc:
273
+ return pages, str(exc)
274
+
275
+
276
+ def _collect_records(config: RestSourceConfig, token: str | None, limit: int) -> Tuple[List[Dict[str, Any]], List[Dict[str, str]]]:
277
+ """Collect processed records and dtypes using PySpark DataSource."""
278
+ import tempfile
279
+ import os
280
+ from ..config import dump_config
281
+
282
+ # Create a temporary config file for Spark to use
283
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.yml', delete=False) as f:
284
+ config_yaml = dump_config(config)
285
+ f.write(config_yaml)
286
+ config_path = f.name
287
+
288
+ spark = _get_or_create_spark()
289
+ try:
290
+ df = _get_preview_df(config_path, token, spark, config.options)
291
+ records = df.limit(limit).collect()
292
+ dtypes = df.dtypes
293
+ record_dicts = [row.asDict(recursive=True) for row in records]
294
+ dtype_dicts: List[Dict[str, str]] = []
295
+ sample_row = record_dicts[0] if record_dicts else {}
296
+ for column, dtype in dtypes:
297
+ if sample_row and column in sample_row:
298
+ dtype_dicts.append({"column": column, "type": str(dtype)})
299
+ return record_dicts, dtype_dicts
300
+ finally:
301
+ spark.stop()
302
+ os.unlink(config_path)
303
+
304
+ def _get_preview_df(
305
+ config_path: str,
306
+ token: str | None,
307
+ spark: "SparkSession",
308
+ reader_options: Dict[str, Any],
309
+ ):
310
+ """Get a Spark DataFrame for previewing data from the specified stream."""
311
+
312
+ from polymo import ApiReader
313
+
314
+ _get_or_create_spark()
315
+ spark.dataSource.register(ApiReader)
316
+
317
+ # Alternative approach: Use the DataSource directly without format registration
318
+ # Create the DataSource instance manually
319
+ options = {
320
+ "config_path": config_path,
321
+ }
322
+ if token is not None:
323
+ options["token"] = token
324
+ for key, value in reader_options.items():
325
+ if key in {"config_path", "token"}:
326
+ continue
327
+ options[key] = value
328
+ return spark.read.format("polymo").options(**options).load()
329
+
330
+ def _get_or_create_spark() -> Any:
331
+ """Get or create a Spark session."""
332
+ from pyspark.sql import SparkSession
333
+
334
+ spark = SparkSession.builder \
335
+ .appName("polymo-builder") \
336
+ .config("spark.sql.adaptive.enabled", "false") \
337
+ .config("spark.sql.adaptive.coalescePartitions.enabled", "false") \
338
+ .getOrCreate()
339
+ return spark
@@ -0,0 +1,15 @@
1
+ version: 0.1
2
+ source:
3
+ type: rest
4
+ base_url: https://api.github.com/orgs/godatadriven
5
+ stream:
6
+ path: /repos
7
+ infer_schema: true
8
+ schema: null
9
+ pagination:
10
+ type: none
11
+ record_selector:
12
+ field_path:
13
+ - owner
14
+ record_filter: null
15
+ cast_to_schema_types: false
@@ -0,0 +1,9 @@
1
+ version: 0.1
2
+ source:
3
+ type: rest
4
+ base_url: https://jsonplaceholder.typicode.com
5
+ stream:
6
+ path: /posts
7
+ params:
8
+ _limit: 20
9
+ infer_schema: true
Binary file
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" href="/favicon.ico" sizes="any" />
6
+ <link rel="icon" type="image/png" sizes="32x32" href="/favicon.ico" />
7
+ <link rel="icon" type="image/png" sizes="192x192" href="/favicon.ico" />
8
+ <link rel="apple-touch-icon" href="/favicon.ico" />
9
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
+ <title>polymo Connector Builder</title>
11
+ <script type="module" crossorigin src="/main.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/main.css">
13
+ </head>
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+ </html>
Binary file
Binary file
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap";*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,system-ui,sans-serif;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:JetBrains Mono,ui-monospace,SFMono-Regular,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width: 640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width: 768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width: 1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width: 1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width: 1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bottom:0;left:0}.-right-2{right:-.5rem}.-top-2{top:-.5rem}.left-1\/2{left:50%}.right-0{right:0}.right-3{right:.75rem}.top-0{top:0}.top-1\/2{top:50%}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-50{z-index:50}.mx-auto{margin-left:auto;margin-right:auto}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-4{margin-top:1rem}.mt-auto{margin-top:auto}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-10{height:2.5rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-3{height:.75rem}.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-\[520px\]{height:520px}.h-full{height:100%}.min-h-\[260px\]{min-height:260px}.min-h-\[96px\]{min-height:96px}.min-h-screen{min-height:100vh}.w-10{width:2.5rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-60{width:15rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.min-w-0{min-width:0px}.min-w-full{min-width:100%}.min-w-max{min-width:-moz-max-content;min-width:max-content}.max-w-2xl{max-width:42rem}.max-w-4xl{max-width:56rem}.max-w-7xl{max-width:80rem}.max-w-\[140px\]{max-width:140px}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--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-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-y-2{--tw-translate-y: .5rem;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))}.rotate-90{--tw-rotate: 90deg;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))}.transform{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))}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.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-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-stretch{align-items:stretch}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-12{gap:3rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.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-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.space-y-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-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-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-border{--tw-border-opacity: 1;border-color:rgb(219 216 224 / var(--tw-border-opacity, 1))}.border-border\/60{border-color:#dbd8e099}.border-border\/70{border-color:#dbd8e0b3}.border-error\/40{border-color:#e5484d66}.border-red-7{--tw-border-opacity: 1;border-color:rgb(244 169 170 / var(--tw-border-opacity, 1))}.border-red-7\/50{border-color:#f4a9aa80}.bg-background{--tw-bg-opacity: 1;background-color:rgb(250 249 251 / var(--tw-bg-opacity, 1))}.bg-background\/50{background-color:#faf9fb80}.bg-background\/60{background-color:#faf9fb99}.bg-background\/70{background-color:#faf9fbb3}.bg-background\/80{background-color:#faf9fbcc}.bg-black\/50{background-color:#00000080}.bg-blue-4\/50{background-color:#d5efff80}.bg-blue-5{--tw-bg-opacity: 1;background-color:rgb(194 229 255 / var(--tw-bg-opacity, 1))}.bg-blue-9{--tw-bg-opacity: 1;background-color:rgb(0 144 255 / var(--tw-bg-opacity, 1))}.bg-current{background-color:currentColor}.bg-error{--tw-bg-opacity: 1;background-color:rgb(229 72 77 / var(--tw-bg-opacity, 1))}.bg-red-3{--tw-bg-opacity: 1;background-color:rgb(254 235 236 / var(--tw-bg-opacity, 1))}.bg-red-3\/50{background-color:#feebec80}.bg-red-3\/60{background-color:#feebec99}.bg-red-9{--tw-bg-opacity: 1;background-color:rgb(229 72 77 / var(--tw-bg-opacity, 1))}.bg-slate-12{--tw-bg-opacity: 1;background-color:rgb(28 32 36 / var(--tw-bg-opacity, 1))}.bg-slate-2{--tw-bg-opacity: 1;background-color:rgb(249 249 251 / var(--tw-bg-opacity, 1))}.bg-surface{--tw-bg-opacity: 1;background-color:rgb(242 239 243 / var(--tw-bg-opacity, 1))}.bg-surface\/70{background-color:#f2eff3b3}.bg-surface\/80{background-color:#f2eff3cc}.bg-surface\/90{background-color:#f2eff3e6}.bg-surface\/95{background-color:#f2eff3f2}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pr-9{padding-right:2.25rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.align-top{vertical-align:top}.align-bottom{vertical-align:bottom}.font-mono{font-family:JetBrains Mono,ui-monospace,SFMono-Regular,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.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-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-5{line-height:1.25rem}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.leading-tight{line-height:1.25}.tracking-wide{letter-spacing:.025em}.text-background-foreground{--tw-text-opacity: 1;color:rgb(33 31 38 / var(--tw-text-opacity, 1))}.text-blue-9{--tw-text-opacity: 1;color:rgb(0 144 255 / var(--tw-text-opacity, 1))}.text-error{--tw-text-opacity: 1;color:rgb(229 72 77 / var(--tw-text-opacity, 1))}.text-muted{--tw-text-opacity: 1;color:rgb(188 186 199 / var(--tw-text-opacity, 1))}.text-red-11{--tw-text-opacity: 1;color:rgb(206 44 49 / var(--tw-text-opacity, 1))}.text-slate-10{--tw-text-opacity: 1;color:rgb(128 131 141 / var(--tw-text-opacity, 1))}.text-slate-11{--tw-text-opacity: 1;color:rgb(96 100 108 / var(--tw-text-opacity, 1))}.text-slate-12{--tw-text-opacity: 1;color:rgb(28 32 36 / var(--tw-text-opacity, 1))}.text-slate-9{--tw-text-opacity: 1;color:rgb(139 141 152 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.accent-blue-9{accent-color:#0090ff}.opacity-0{opacity:0}.shadow-inner{--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / .05);--tw-shadow-colored: inset 0 2px 4px 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-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-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-soft{--tw-shadow: 0 10px 40px -15px rgba(15, 23, 42, .25);--tw-shadow-colored: 0 10px 40px -15px 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)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--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(1px + 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-border\/40{--tw-ring-color: rgb(219 216 224 / .4)}.blur{--tw-blur: blur(8px);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)}.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{--tw-backdrop-blur: blur(8px);-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)}.backdrop-filter{-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{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.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-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}:root{color-scheme:light dark}body{--tw-bg-opacity: 1;background-color:rgb(250 249 251 / var(--tw-bg-opacity, 1));font-family:Inter,system-ui,sans-serif;--tw-text-opacity: 1;color:rgb(33 31 38 / var(--tw-text-opacity, 1));-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.dark body{background-color:#282a36}.theme-fade{animation:theme-fade .24s ease-out}@keyframes theme-fade{0%{opacity:.4;filter:saturate(.6) brightness(.9)}to{opacity:1;filter:none}}.animate-param-enter{animation:param-enter .22s ease-out}@keyframes param-enter{0%{opacity:0;transform:scale(.97) translateY(4px)}to{opacity:1;transform:scale(1) translateY(0)}}.modern-select{width:100%;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.5rem;border-width:1px;--tw-border-opacity: 1;border-color:rgb(219 216 224 / var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgb(250 249 251 / var(--tw-bg-opacity, 1));padding:.5rem 2.25rem .5rem .75rem;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(28 32 36 / var(--tw-text-opacity, 1));--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);transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.dark .modern-select{background-color:#282a36;border-color:#44475a;color:#f8f8f2}.modern-select:focus-visible{--tw-border-opacity: 1;border-color:rgb(142 200 246 / var(--tw-border-opacity, 1));outline:2px solid transparent;outline-offset:2px}.modern-select:is(.dark *){--tw-border-opacity: 1;border-color:rgb(68 71 90 / var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgb(48 50 65 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(248 248 242 / var(--tw-text-opacity, 1))}.modern-select:focus-visible:is(.dark *){--tw-border-opacity: 1;border-color:rgb(189 147 249 / var(--tw-border-opacity, 1))}.dark input:not([type=checkbox]),.dark textarea,.dark select{--tw-border-opacity: 1;border-color:rgb(68 71 90 / var(--tw-border-opacity, 1));--tw-bg-opacity: 1;background-color:rgb(48 50 65 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(248 248 242 / var(--tw-text-opacity, 1))}.dark input:not([type=checkbox])::-moz-placeholder,.dark textarea::-moz-placeholder,.dark select::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(98 114 164 / var(--tw-placeholder-opacity, 1))}.dark input:not([type=checkbox])::placeholder,.dark textarea::placeholder,.dark select::placeholder{--tw-placeholder-opacity: 1;color:rgb(98 114 164 / var(--tw-placeholder-opacity, 1))}.dark ::-moz-placeholder{--tw-text-opacity: 1;color:rgb(98 114 164 / var(--tw-text-opacity, 1))}.dark ::placeholder{--tw-text-opacity: 1;color:rgb(98 114 164 / var(--tw-text-opacity, 1))}.dark .bg-background{background-color:#282a36!important}.dark .bg-surface{background-color:#303241!important}.dark .border-border{border-color:#44475a!important}.dark .text-muted{color:#6272a4!important}.dark .shadow-soft{box-shadow:0 10px 40px -15px #000000a6}.dark .text-slate-12,.dark .text-slate-11,.dark .text-slate-10{color:#f8f8f2!important}.dark .text-slate-9,.dark .text-slate-8{color:#6272a4!important}.dark .group:hover.bg-background\/60{background-color:#484c5e8c!important}.radix-scroll-area-scrollbar{display:flex;touch-action:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;padding:.125rem;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-timing-function:cubic-bezier(0,0,.2,1)}.radix-scroll-area-thumb{flex:1 1 0%;border-radius:9999px;--tw-bg-opacity: 1;background-color:rgb(188 186 199 / var(--tw-bg-opacity, 1))}.radix-scroll-area-thumb:hover{--tw-bg-opacity: 1;background-color:rgb(142 140 153 / var(--tw-bg-opacity, 1))}.radix-scroll-area-thumb:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(68 71 90 / var(--tw-bg-opacity, 1))}.radix-scroll-area-thumb:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(98 114 164 / var(--tw-bg-opacity, 1))}button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible{outline:2px solid transparent;outline-offset:2px;--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);--tw-ring-opacity: 1;--tw-ring-color: rgb(142 200 246 / var(--tw-ring-opacity, 1));--tw-ring-offset-width: 2px;--tw-ring-offset-color: #f2eff3}button:focus-visible:is(.dark *),input:focus-visible:is(.dark *),textarea:focus-visible:is(.dark *),select:focus-visible:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(189 147 249 / var(--tw-ring-opacity, 1));--tw-ring-offset-color: #303241}[data-status=success]{--tw-text-opacity: 1;color:rgb(48 164 108 / var(--tw-text-opacity, 1))}[data-status=warn],[data-status=warning]{--tw-text-opacity: 1;color:rgb(255 197 61 / var(--tw-text-opacity, 1))}[data-status=error]{--tw-text-opacity: 1;color:rgb(229 72 77 / var(--tw-text-opacity, 1))}[data-status=info]{--tw-text-opacity: 1;color:rgb(13 116 206 / var(--tw-text-opacity, 1))}[data-status=info]:is(.dark *){--tw-text-opacity: 1;color:rgb(189 147 249 / var(--tw-text-opacity, 1))}pre{overflow-x:auto;border-radius:.5rem;--tw-bg-opacity: 1;background-color:rgb(249 249 251 / var(--tw-bg-opacity, 1));padding:1rem;font-size:.875rem;line-height:1.25rem;--tw-text-opacity: 1;color:rgb(28 32 36 / var(--tw-text-opacity, 1))}.dark pre{color:#f8f8f2}pre:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(48 50 65 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(248 248 242 / var(--tw-text-opacity, 1))}.dark table thead tr{background-color:#ffffff0a!important}.dark table tbody tr.even\:bg-slate-2\/40{background-color:#ffffff05!important}.dark table tbody tr:hover{background-color:#bd93f91f!important}.prism-container{position:relative;height:100%;font-family:JetBrains Mono,ui-monospace,SFMono-Regular,monospace;font-size:.875rem;line-height:1.25rem}.prism-editor{position:absolute;top:0;right:0;bottom:0;left:0;height:100%;width:100%;resize:none;background-color:transparent;line-height:1.25rem;color:transparent;caret-color:#fff;outline:2px solid transparent;outline-offset:2px}.prism-highlight{pointer-events:none;white-space:pre-wrap;overflow-wrap:break-word;text-align:left;font-family:JetBrains Mono,ui-monospace,SFMono-Regular,monospace;line-height:1.25rem}.dark .prism-highlight{color:#f8f8f2}.prism-highlight .token.comment{color:#6272a4}.prism-highlight .token.key{color:#ff79c6}.prism-highlight .token.punctuation{color:#f8f8f2}.prism-highlight .token.string{color:#f1fa8c}.prism-highlight .token.number{color:#bd93f9}.prism-highlight .token.boolean{color:#ffb86c}.prism-highlight .token.null{color:#f55}.prism-highlight .token.directive{color:#8be9fd}.prism-highlight{position:relative;--ch: .62ch}.prism-highlight .editor-line{position:relative;padding-left:0}.prism-highlight .editor-line:before{content:attr(data-line);position:absolute;left:-.75rem;transform:translate(-100%);width:3rem;text-align:right;padding-right:.75rem;color:#64748b;font-size:.75rem;line-height:1.25rem;font-variant-numeric:tabular-nums;opacity:.65}.dark .prism-highlight .editor-line:before{color:#6272a4;opacity:.55}.prism-highlight .editor-line.error{background:#ff55551a;box-shadow:inset 0 0 0 1px #ff555559}.dark .prism-highlight .editor-line.error{background:#ff55552b;box-shadow:inset 0 0 0 1px #ff555573}.prism-highlight .editor-line .col-marker{position:absolute;top:0;bottom:0;width:2px;background:#f55;left:calc(var(--col) * var(--ch));pointer-events:none;opacity:.4}.dark .prism-highlight .editor-line .col-marker{background:#f55;opacity:.55}.prism-highlight .editor-line .err-char{background:linear-gradient(transparent 70%,#ff55558c 70%);border-radius:2px}.dark .prism-highlight .editor-line .err-char{background:linear-gradient(transparent 65%,#ff5555bf 65%)}.prism-highlight::-webkit-scrollbar{width:10px;height:10px}.prism-highlight::-webkit-scrollbar-track{background:transparent}.prism-highlight::-webkit-scrollbar-thumb{background:#64748b59;border-radius:6px}.dark .prism-highlight::-webkit-scrollbar-thumb{background:#f8f8f233}.prism-highlight::-webkit-scrollbar-thumb:hover{background:#64748b8c}.dark .prism-highlight::-webkit-scrollbar-thumb:hover{background:#f8f8f259}.prism-highlight,.prism-highlight .editor-line,.prism-highlight .line-content{pointer-events:none}.prism-editor{color:transparent;text-shadow:none}.dark .prism-editor{color:transparent}.prism-editor{caret-color:#111}.dark .prism-editor{caret-color:#f8f8f2}[role=tooltip]{animation:fadeIn .12s ease-out}@keyframes fadeIn{0%{opacity:0;transform:translate(-50%,4px)}to{opacity:1;transform:translate(-50%)}}.dark .btn-accent{--tw-bg-opacity: 1;background-color:rgb(189 147 249 / var(--tw-bg-opacity, 1));--tw-text-opacity: 1;color:rgb(248 248 242 / var(--tw-text-opacity, 1))}.dark .btn-accent:hover{--tw-bg-opacity: 1;background-color:rgb(255 121 198 / var(--tw-bg-opacity, 1))}.dark .border-accent{--tw-border-opacity: 1;border-color:rgb(189 147 249 / var(--tw-border-opacity, 1))}.validate-flash-success{animation:validateFlashSuccess 4s ease-in-out forwards}.validate-flash-error{animation:validateFlashError 4s ease-in-out forwards}@keyframes validateFlashSuccess{0%,85%{background:#30a46c;color:#fff}to{background:transparent;color:inherit}}@keyframes validateFlashError{0%,85%{background:#e5484d;color:#fff}to{background:transparent;color:inherit}}.dark button.validate-flash-success,.dark .validate-flash-success{animation:darkValidateFlashSuccess 4s ease-in-out forwards;position:relative}.dark button.validate-flash-error,.dark .validate-flash-error{animation:darkValidateFlashError 4s ease-in-out forwards;position:relative}.dark button.validate-flash-success:after,.dark .validate-flash-success:after,.dark button.validate-flash-error:after,.dark .validate-flash-error:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border-radius:inherit;pointer-events:none}.dark button.validate-flash-success:after,.dark .validate-flash-success:after{background:radial-gradient(circle at 30% 30%,#50fa7b59,#50fa7b0d)}.dark button.validate-flash-error:after,.dark .validate-flash-error:after{background:radial-gradient(circle at 30% 30%,#f556,#ff55550f)}@keyframes darkValidateFlashSuccess{0%,85%{background-color:#50fa7b40!important;color:#fff!important;border-color:#50fa7b8c!important;box-shadow:0 0 #50fa7b99,0 0 16px 4px #50fa7b59}to{background-color:transparent!important;color:inherit!important;border-color:var(--border-color,rgba(100,100,120,.4))!important;box-shadow:none}}@keyframes darkValidateFlashError{0%,85%{background-color:#ff555547!important;color:#fff!important;border-color:#ff55558c!important;box-shadow:0 0 #f559,0 0 16px 4px #ff555561}to{background-color:transparent!important;color:inherit!important;border-color:var(--border-color,rgba(100,100,120,.4))!important;box-shadow:none}}.dark .bg-blue-9{transition:background-color .3s ease,box-shadow .4s ease}.dark .bg-blue-9:hover{box-shadow:0 0 #bd93f900,0 0 12px 2px #bd93f959}.even\:bg-slate-2\/40:nth-child(2n){background-color:#f9f9fb66}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;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-blue-7:hover{--tw-border-opacity: 1;border-color:rgb(142 200 246 / var(--tw-border-opacity, 1))}.hover\:border-blue-7\/60:hover{border-color:#8ec8f699}.hover\:border-red-7:hover{--tw-border-opacity: 1;border-color:rgb(244 169 170 / var(--tw-border-opacity, 1))}.hover\:bg-blue-10:hover{--tw-bg-opacity: 1;background-color:rgb(5 136 240 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-3\/20:hover{background-color:#e6f4fe33}.hover\:bg-blue-3\/40:hover{background-color:#e6f4fe66}.hover\:bg-blue-3\/50:hover{background-color:#e6f4fe80}.hover\:bg-blue-3\/60:hover{background-color:#e6f4fe99}.hover\:bg-red-10:hover{--tw-bg-opacity: 1;background-color:rgb(220 62 66 / var(--tw-bg-opacity, 1))}.hover\:bg-red-3\/60:hover{background-color:#feebec99}.hover\:bg-slate-3:hover{--tw-bg-opacity: 1;background-color:rgb(240 240 243 / var(--tw-bg-opacity, 1))}.hover\:text-blue-11:hover{--tw-text-opacity: 1;color:rgb(13 116 206 / var(--tw-text-opacity, 1))}.hover\:text-red-10:hover{--tw-text-opacity: 1;color:rgb(220 62 66 / var(--tw-text-opacity, 1))}.hover\:text-slate-12:hover{--tw-text-opacity: 1;color:rgb(28 32 36 / var(--tw-text-opacity, 1))}.hover\:shadow-md:hover{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.hover\:shadow-sm:hover{--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)}.focus\:border-blue-7:focus{--tw-border-opacity: 1;border-color:rgb(142 200 246 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-blue-7:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(142 200 246 / var(--tw-ring-opacity, 1))}.focus-visible\:border-blue-7:focus-visible{--tw-border-opacity: 1;border-color:rgb(142 200 246 / var(--tw-border-opacity, 1))}.focus-visible\:bg-blue-9:focus-visible{--tw-bg-opacity: 1;background-color:rgb(0 144 255 / var(--tw-bg-opacity, 1))}.focus-visible\:text-white:focus-visible{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.focus-visible\:opacity-100:focus-visible{opacity:1}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-1: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(1px + 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-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-blue-5:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(194 229 255 / var(--tw-ring-opacity, 1))}.focus-visible\:ring-blue-7:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(142 200 246 / var(--tw-ring-opacity, 1))}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:border-blue-7\/50{border-color:#8ec8f680}.group:hover .group-hover\:bg-blue-5\/80{background-color:#c2e5ffcc}.group:hover .group-hover\:opacity-100{opacity:1}.data-\[state\=active\]\:border-b-2[data-state=active]{border-bottom-width:2px}.data-\[state\=active\]\:border-blue-9[data-state=active]{--tw-border-opacity: 1;border-color:rgb(0 144 255 / var(--tw-border-opacity, 1))}.data-\[state\=active\]\:bg-blue-9[data-state=active]{--tw-bg-opacity: 1;background-color:rgb(0 144 255 / var(--tw-bg-opacity, 1))}.data-\[state\=checked\]\:bg-blue-4[data-state=checked]{--tw-bg-opacity: 1;background-color:rgb(213 239 255 / var(--tw-bg-opacity, 1))}.data-\[state\=active\]\:text-blue-11[data-state=active]{--tw-text-opacity: 1;color:rgb(13 116 206 / var(--tw-text-opacity, 1))}.data-\[state\=active\]\:text-white[data-state=active]{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.data-\[state\=active\]\:shadow-sm[data-state=active]{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}@supports (backdrop-filter: var(--tw)){.supports-\[backdrop-filter\]\:bg-slate-2\/75{background-color:#f9f9fbbf}}.dark\:border-\[\#2c313a\]:is(.dark *){--tw-border-opacity: 1;border-color:rgb(44 49 58 / var(--tw-border-opacity, 1))}.dark\:border-drac-border:is(.dark *){--tw-border-opacity: 1;border-color:rgb(68 71 90 / var(--tw-border-opacity, 1))}.dark\:border-drac-border\/40:is(.dark *){border-color:#44475a66}.dark\:border-drac-border\/50:is(.dark *){border-color:#44475a80}.dark\:border-drac-border\/60:is(.dark *){border-color:#44475a99}.dark\:border-drac-red\/40:is(.dark *){border-color:#f556}.dark\:border-red-9\/30:is(.dark *){border-color:#e5484d4d}.dark\:border-slate-6:is(.dark *){--tw-border-opacity: 1;border-color:rgb(217 217 224 / var(--tw-border-opacity, 1))}.dark\:bg-\[\#1d2026\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(29 32 38 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#1f232b\]\/60:is(.dark *){background-color:#1f232b99}.dark\:bg-\[\#1f232b\]\/80:is(.dark *){background-color:#1f232bcc}.dark\:bg-\[\#242a33\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(36 42 51 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#262c35\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(38 44 53 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#272d38\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(39 45 56 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#282a36\]\/70:is(.dark *){background-color:#282a36b3}.dark\:bg-\[\#2d3541\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(45 53 65 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#303745\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(48 55 69 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#313341\]\/50:is(.dark *){background-color:#31334180}.dark\:bg-blue-7\/40:is(.dark *){background-color:#8ec8f666}.dark\:bg-blue-9\/20:is(.dark *){background-color:#0090ff33}.dark\:bg-drac-accent:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(189 147 249 / var(--tw-bg-opacity, 1))}.dark\:bg-drac-base:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(40 42 54 / var(--tw-bg-opacity, 1))}.dark\:bg-drac-red\/25:is(.dark *){background-color:#ff555540}.dark\:bg-drac-surface:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(48 50 65 / var(--tw-bg-opacity, 1))}.dark\:bg-drac-surface\/60:is(.dark *){background-color:#30324199}.dark\:bg-drac-surface\/80:is(.dark *){background-color:#303241cc}.dark\:bg-red-9\/15:is(.dark *){background-color:#e5484d26}.dark\:bg-red-9\/20:is(.dark *){background-color:#e5484d33}.dark\:bg-slate-1:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(252 252 253 / var(--tw-bg-opacity, 1))}.dark\:bg-slate-2:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(249 249 251 / var(--tw-bg-opacity, 1))}.dark\:text-blue-9:is(.dark *){--tw-text-opacity: 1;color:rgb(0 144 255 / var(--tw-text-opacity, 1))}.dark\:text-drac-foreground:is(.dark *){--tw-text-opacity: 1;color:rgb(248 248 242 / var(--tw-text-opacity, 1))}.dark\:text-drac-foreground\/80:is(.dark *){color:#f8f8f2cc}.dark\:text-drac-foreground\/90:is(.dark *){color:#f8f8f2e6}.dark\:text-drac-muted:is(.dark *){--tw-text-opacity: 1;color:rgb(98 114 164 / var(--tw-text-opacity, 1))}.dark\:text-red-9:is(.dark *){--tw-text-opacity: 1;color:rgb(229 72 77 / var(--tw-text-opacity, 1))}.dark\:text-slate-12:is(.dark *){--tw-text-opacity: 1;color:rgb(28 32 36 / var(--tw-text-opacity, 1))}.dark\:accent-drac-accent:is(.dark *){accent-color:#bd93f9}.dark\:shadow-\[0_8px_24px_-6px_rgba\(0\,0\,0\,0\.7\)\]:is(.dark *){--tw-shadow: 0 8px 24px -6px rgba(0,0,0,.7);--tw-shadow-colored: 0 8px 24px -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)}.dark\:ring-drac-border\/30:is(.dark *){--tw-ring-color: rgb(68 71 90 / .3)}.dark\:hover\:border-drac-accent:hover:is(.dark *){--tw-border-opacity: 1;border-color:rgb(189 147 249 / var(--tw-border-opacity, 1))}.dark\:hover\:border-drac-accent\/60:hover:is(.dark *){border-color:#bd93f999}.dark\:hover\:border-drac-red\/80:hover:is(.dark *){border-color:#f55c}.dark\:hover\:bg-\[\#273242\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(39 50 66 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-\[\#383f4c\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(56 63 76 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-blue-9\/20:hover:is(.dark *){background-color:#0090ff33}.dark\:hover\:bg-blue-9\/25:hover:is(.dark *){background-color:#0090ff40}.dark\:hover\:bg-blue-9\/30:hover:is(.dark *){background-color:#0090ff4d}.dark\:hover\:bg-drac-base\/60:hover:is(.dark *){background-color:#282a3699}.dark\:hover\:text-drac-accent:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(189 147 249 / var(--tw-text-opacity, 1))}.dark\:hover\:text-drac-foreground:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(248 248 242 / var(--tw-text-opacity, 1))}.dark\:hover\:text-drac-red:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(255 85 85 / var(--tw-text-opacity, 1))}.dark\:hover\:shadow-\[0_0_8px_rgba\(80\,250\,123\,0\.15\)\]:hover:is(.dark *){--tw-shadow: 0 0 8px rgba(80,250,123,.15);--tw-shadow-colored: 0 0 8px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark\:focus\:border-drac-accent:focus:is(.dark *){--tw-border-opacity: 1;border-color:rgb(189 147 249 / var(--tw-border-opacity, 1))}.dark\:focus\:ring-drac-accent:focus:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(189 147 249 / var(--tw-ring-opacity, 1))}.dark\:focus-visible\:border-drac-accent:focus-visible:is(.dark *){--tw-border-opacity: 1;border-color:rgb(189 147 249 / var(--tw-border-opacity, 1))}.dark\:focus-visible\:bg-drac-accent:focus-visible:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(189 147 249 / var(--tw-bg-opacity, 1))}.dark\:focus-visible\:ring-drac-accent:focus-visible:is(.dark *){--tw-ring-opacity: 1;--tw-ring-color: rgb(189 147 249 / var(--tw-ring-opacity, 1))}.dark\:focus-visible\:ring-drac-accent\/40:focus-visible:is(.dark *){--tw-ring-color: rgb(189 147 249 / .4)}.group:hover .dark\:group-hover\:bg-blue-9\/30:is(.dark *){background-color:#0090ff4d}.dark\:data-\[state\=active\]\:border-drac-accent[data-state=active]:is(.dark *){--tw-border-opacity: 1;border-color:rgb(189 147 249 / var(--tw-border-opacity, 1))}.dark\:data-\[state\=active\]\:text-drac-accent[data-state=active]:is(.dark *){--tw-text-opacity: 1;color:rgb(189 147 249 / var(--tw-text-opacity, 1))}@supports (backdrop-filter: var(--tw)){.dark\:supports-\[backdrop-filter\]\:bg-drac-surface\/70:is(.dark *){background-color:#303241b3}}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-\[minmax\(0\,1fr\)_minmax\(200px\,auto\)\]{grid-template-columns:minmax(0,1fr) minmax(200px,auto)}.lg\:items-end{align-items:flex-end}.lg\:px-6{padding-left:1.5rem;padding-right:1.5rem}}.\[\&_td\]\:whitespace-nowrap td{white-space:nowrap}.\[\&_td\]\:whitespace-pre-wrap td{white-space:pre-wrap}