academic-refchecker 1.2.66__tar.gz → 1.2.68__tar.gz

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.
Files changed (75) hide show
  1. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/MANIFEST.in +3 -0
  2. {academic_refchecker-1.2.66/academic_refchecker.egg-info → academic_refchecker-1.2.68}/PKG-INFO +3 -1
  3. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/README.md +2 -0
  4. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68/academic_refchecker.egg-info}/PKG-INFO +3 -1
  5. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/academic_refchecker.egg-info/SOURCES.txt +5 -0
  6. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/cli.py +11 -3
  7. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/database.py +27 -2
  8. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/main.py +105 -4
  9. academic_refchecker-1.2.68/backend/static/assets/index-2P6L_39v.css +1 -0
  10. academic_refchecker-1.2.68/backend/static/assets/index-hk21nqxR.js +25 -0
  11. academic_refchecker-1.2.68/backend/static/favicon.svg +6 -0
  12. academic_refchecker-1.2.68/backend/static/index.html +15 -0
  13. academic_refchecker-1.2.68/backend/static/vite.svg +1 -0
  14. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/pyproject.toml +1 -0
  15. academic_refchecker-1.2.68/src/refchecker/__version__.py +5 -0
  16. academic_refchecker-1.2.66/src/refchecker/__version__.py +0 -5
  17. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/LICENSE +0 -0
  18. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/academic_refchecker.egg-info/dependency_links.txt +0 -0
  19. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/academic_refchecker.egg-info/entry_points.txt +0 -0
  20. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/academic_refchecker.egg-info/requires.txt +0 -0
  21. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/academic_refchecker.egg-info/top_level.txt +0 -0
  22. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/__init__.py +0 -0
  23. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/__main__.py +0 -0
  24. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/concurrency.py +0 -0
  25. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/models.py +0 -0
  26. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/refchecker_wrapper.py +0 -0
  27. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/thumbnail.py +0 -0
  28. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/backend/websocket_manager.py +0 -0
  29. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/requirements.txt +0 -0
  30. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/scripts/download_db.py +0 -0
  31. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/scripts/run_tests.py +0 -0
  32. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/scripts/start_vllm_server.py +0 -0
  33. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/setup.cfg +0 -0
  34. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/__init__.py +0 -0
  35. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/__main__.py +0 -0
  36. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/__init__.py +0 -0
  37. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/crossref.py +0 -0
  38. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/enhanced_hybrid_checker.py +0 -0
  39. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/github_checker.py +0 -0
  40. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/local_semantic_scholar.py +0 -0
  41. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/openalex.py +0 -0
  42. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/openreview_checker.py +0 -0
  43. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/pdf_paper_checker.py +0 -0
  44. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/semantic_scholar.py +0 -0
  45. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/checkers/webpage_checker.py +0 -0
  46. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/config/__init__.py +0 -0
  47. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/config/logging.conf +0 -0
  48. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/config/settings.py +0 -0
  49. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/core/__init__.py +0 -0
  50. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/core/db_connection_pool.py +0 -0
  51. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/core/parallel_processor.py +0 -0
  52. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/core/refchecker.py +0 -0
  53. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/database/__init__.py +0 -0
  54. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/database/download_semantic_scholar_db.py +0 -0
  55. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/llm/__init__.py +0 -0
  56. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/llm/base.py +0 -0
  57. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/llm/providers.py +0 -0
  58. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/scripts/__init__.py +0 -0
  59. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/scripts/start_vllm_server.py +0 -0
  60. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/services/__init__.py +0 -0
  61. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/services/pdf_processor.py +0 -0
  62. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/__init__.py +0 -0
  63. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/arxiv_utils.py +0 -0
  64. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/author_utils.py +0 -0
  65. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/biblatex_parser.py +0 -0
  66. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/bibliography_utils.py +0 -0
  67. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/bibtex_parser.py +0 -0
  68. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/config_validator.py +0 -0
  69. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/db_utils.py +0 -0
  70. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/doi_utils.py +0 -0
  71. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/error_utils.py +0 -0
  72. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/mock_objects.py +0 -0
  73. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/text_utils.py +0 -0
  74. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/unicode_utils.py +0 -0
  75. {academic_refchecker-1.2.66 → academic_refchecker-1.2.68}/src/refchecker/utils/url_utils.py +0 -0
@@ -13,6 +13,9 @@ recursive-include src *.py
13
13
  # Include backend code
14
14
  recursive-include backend *.py
15
15
 
16
+ # Include web UI frontend (built assets)
17
+ recursive-include backend/static *
18
+
16
19
  # Include scripts
17
20
  recursive-include scripts *.py
18
21
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: academic-refchecker
3
- Version: 1.2.66
3
+ Version: 1.2.68
4
4
  Summary: A comprehensive tool for validating reference accuracy in academic papers
5
5
  Author-email: Mark Russinovich <markrussinovich@hotmail.com>
6
6
  License-Expression: MIT
@@ -186,6 +186,8 @@ Learn about RefChecker's design philosophy and development process in this detai
186
186
 
187
187
  RefChecker also includes a modern web interface with real-time progress updates, check history, and export options.
188
188
 
189
+ ![RefChecker Web UI](assets/webui.png)
190
+
189
191
  ### Prerequisites
190
192
 
191
193
  - **Python 3.8+** with RefChecker installed (`pip install academic-refchecker[webui]`)
@@ -115,6 +115,8 @@ Learn about RefChecker's design philosophy and development process in this detai
115
115
 
116
116
  RefChecker also includes a modern web interface with real-time progress updates, check history, and export options.
117
117
 
118
+ ![RefChecker Web UI](assets/webui.png)
119
+
118
120
  ### Prerequisites
119
121
 
120
122
  - **Python 3.8+** with RefChecker installed (`pip install academic-refchecker[webui]`)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: academic-refchecker
3
- Version: 1.2.66
3
+ Version: 1.2.68
4
4
  Summary: A comprehensive tool for validating reference accuracy in academic papers
5
5
  Author-email: Mark Russinovich <markrussinovich@hotmail.com>
6
6
  License-Expression: MIT
@@ -186,6 +186,8 @@ Learn about RefChecker's design philosophy and development process in this detai
186
186
 
187
187
  RefChecker also includes a modern web interface with real-time progress updates, check history, and export options.
188
188
 
189
+ ![RefChecker Web UI](assets/webui.png)
190
+
189
191
  ### Prerequisites
190
192
 
191
193
  - **Python 3.8+** with RefChecker installed (`pip install academic-refchecker[webui]`)
@@ -19,6 +19,11 @@ backend/models.py
19
19
  backend/refchecker_wrapper.py
20
20
  backend/thumbnail.py
21
21
  backend/websocket_manager.py
22
+ backend/static/favicon.svg
23
+ backend/static/index.html
24
+ backend/static/vite.svg
25
+ backend/static/assets/index-2P6L_39v.css
26
+ backend/static/assets/index-hk21nqxR.js
22
27
  scripts/download_db.py
23
28
  scripts/run_tests.py
24
29
  scripts/start_vllm_server.py
@@ -7,12 +7,13 @@ This module provides the console script entry point for the refchecker-webui com
7
7
 
8
8
  import sys
9
9
  import argparse
10
+ from pathlib import Path
10
11
 
11
12
 
12
13
  def main():
13
14
  """Main entry point for the refchecker-webui command."""
14
15
  parser = argparse.ArgumentParser(
15
- description="Start the RefChecker Web UI backend server"
16
+ description="Start the RefChecker Web UI server"
16
17
  )
17
18
  parser.add_argument(
18
19
  "--host",
@@ -40,8 +41,15 @@ def main():
40
41
  print("Install it with: pip install 'academic-refchecker[webui]'")
41
42
  sys.exit(1)
42
43
 
43
- print(f"Starting RefChecker Web UI backend on http://{args.host}:{args.port}")
44
- print("Make sure to start the frontend separately (cd web-ui && npm run dev)")
44
+ # Check if static frontend is bundled
45
+ static_dir = Path(__file__).parent / "static"
46
+ has_frontend = static_dir.exists() and (static_dir / "index.html").exists()
47
+
48
+ print(f"Starting RefChecker Web UI on http://{args.host}:{args.port}")
49
+ if has_frontend:
50
+ print(f"Open http://localhost:{args.port} in your browser")
51
+ else:
52
+ print("Note: Frontend not bundled. Start it separately: cd web-ui && npm run dev")
45
53
  print()
46
54
 
47
55
  uvicorn.run(
@@ -4,15 +4,38 @@ Database module for storing check history and LLM configurations
4
4
  import aiosqlite
5
5
  import json
6
6
  import os
7
+ import sys
7
8
  from datetime import datetime
8
9
  from typing import List, Optional, Dict, Any
9
10
  from pathlib import Path
10
11
  from cryptography.fernet import Fernet
11
12
 
12
13
 
14
+ def get_data_dir() -> Path:
15
+ """Get platform-appropriate user data directory for refchecker.
16
+
17
+ Windows: %LOCALAPPDATA%\refchecker
18
+ macOS: ~/Library/Application Support/refchecker
19
+ Linux: ~/.local/share/refchecker
20
+ """
21
+ if sys.platform == "win32":
22
+ # Windows: use LOCALAPPDATA
23
+ base = Path(os.environ.get("LOCALAPPDATA", Path.home() / "AppData" / "Local"))
24
+ elif sys.platform == "darwin":
25
+ # macOS: use Application Support
26
+ base = Path.home() / "Library" / "Application Support"
27
+ else:
28
+ # Linux/Unix: use XDG_DATA_HOME or ~/.local/share
29
+ base = Path(os.environ.get("XDG_DATA_HOME", Path.home() / ".local" / "share"))
30
+
31
+ data_dir = base / "refchecker"
32
+ data_dir.mkdir(parents=True, exist_ok=True)
33
+ return data_dir
34
+
35
+
13
36
  def get_encryption_key() -> bytes:
14
37
  """Get or create encryption key for API keys"""
15
- key_file = Path(__file__).parent / ".encryption_key"
38
+ key_file = get_data_dir() / ".encryption_key"
16
39
  if key_file.exists():
17
40
  return key_file.read_bytes()
18
41
  else:
@@ -24,7 +47,9 @@ def get_encryption_key() -> bytes:
24
47
  class Database:
25
48
  """Handles SQLite database operations for check history and LLM configs"""
26
49
 
27
- def __init__(self, db_path: str = "refchecker_history.db"):
50
+ def __init__(self, db_path: Optional[str] = None):
51
+ if db_path is None:
52
+ db_path = str(get_data_dir() / "refchecker_history.db")
28
53
  self.db_path = db_path
29
54
  self._fernet = Fernet(get_encryption_key())
30
55
 
@@ -7,9 +7,10 @@ import os
7
7
  import tempfile
8
8
  from pathlib import Path
9
9
  from typing import Optional
10
- from fastapi import FastAPI, WebSocket, WebSocketDisconnect, UploadFile, File, Form, HTTPException, Body
10
+ from fastapi import FastAPI, WebSocket, WebSocketDisconnect, UploadFile, File, Form, HTTPException, Body, Request
11
11
  from fastapi.middleware.cors import CORSMiddleware
12
- from fastapi.responses import FileResponse
12
+ from fastapi.responses import FileResponse, HTMLResponse
13
+ from fastapi.staticfiles import StaticFiles
13
14
  from pydantic import BaseModel
14
15
  import logging
15
16
 
@@ -69,10 +70,13 @@ class CheckLabelUpdate(BaseModel):
69
70
  # Create FastAPI app
70
71
  app = FastAPI(title="RefChecker Web UI API", version="1.0.0")
71
72
 
73
+ # Static files directory for bundled frontend
74
+ STATIC_DIR = Path(__file__).parent / "static"
75
+
72
76
  # Configure CORS for local development
73
77
  app.add_middleware(
74
78
  CORSMiddleware,
75
- allow_origins=["http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:5174", "http://localhost:5175", "http://127.0.0.1:5174", "http://127.0.0.1:5175"],
79
+ allow_origins=["http://localhost:5173", "http://127.0.0.1:5173", "http://localhost:5174", "http://localhost:5175", "http://127.0.0.1:5174", "http://127.0.0.1:5175", "http://localhost:8000", "http://127.0.0.1:8000"],
76
80
  allow_credentials=True,
77
81
  allow_methods=["*"],
78
82
  allow_headers=["*"],
@@ -117,7 +121,12 @@ async def startup_event():
117
121
 
118
122
  @app.get("/")
119
123
  async def root():
120
- """Health check endpoint"""
124
+ """Serve frontend if available, otherwise return API health check"""
125
+ # If static frontend is bundled, serve it
126
+ index_path = STATIC_DIR / "index.html"
127
+ if index_path.exists():
128
+ return FileResponse(str(index_path), media_type="text/html")
129
+ # Otherwise return API health check
121
130
  return {"status": "ok", "message": "RefChecker Web UI API"}
122
131
 
123
132
 
@@ -985,6 +994,27 @@ async def validate_llm_config(config: LLMConfigValidate):
985
994
  Validate an LLM configuration by making a test API call.
986
995
  Returns success or error message.
987
996
  """
997
+ # Map providers to their required packages
998
+ PROVIDER_PACKAGES = {
999
+ "anthropic": ("anthropic", "pip install anthropic"),
1000
+ "openai": ("openai", "pip install openai"),
1001
+ "google": ("google.generativeai", "pip install google-generativeai"),
1002
+ "gemini": ("google.generativeai", "pip install google-generativeai"),
1003
+ }
1004
+
1005
+ # Check if required package is installed for this provider
1006
+ provider_lower = config.provider.lower()
1007
+ if provider_lower in PROVIDER_PACKAGES:
1008
+ module_name, install_cmd = PROVIDER_PACKAGES[provider_lower]
1009
+ try:
1010
+ __import__(module_name.split('.')[0])
1011
+ except ImportError:
1012
+ raise HTTPException(
1013
+ status_code=400,
1014
+ detail=f"The '{config.provider}' provider requires the '{module_name.split('.')[0]}' package. "
1015
+ f"Please install it with: {install_cmd}"
1016
+ )
1017
+
988
1018
  try:
989
1019
  import sys
990
1020
  from pathlib import Path
@@ -1005,6 +1035,18 @@ async def validate_llm_config(config: LLMConfigValidate):
1005
1035
  if not provider:
1006
1036
  raise HTTPException(status_code=400, detail=f"Failed to create {config.provider} provider")
1007
1037
 
1038
+ # Check if provider is available (has required client initialized)
1039
+ if hasattr(provider, 'is_available') and not provider.is_available():
1040
+ # Provider was created but client failed to initialize
1041
+ if provider_lower in PROVIDER_PACKAGES:
1042
+ _, install_cmd = PROVIDER_PACKAGES[provider_lower]
1043
+ raise HTTPException(
1044
+ status_code=400,
1045
+ detail=f"Provider '{config.provider}' is not available. "
1046
+ f"Make sure the required package is installed: {install_cmd}"
1047
+ )
1048
+ raise HTTPException(status_code=400, detail=f"Provider '{config.provider}' is not available")
1049
+
1008
1050
  # Make a simple test call using _call_llm
1009
1051
  test_response = provider._call_llm("Say 'ok' if you can hear me.")
1010
1052
 
@@ -1025,6 +1067,16 @@ async def validate_llm_config(config: LLMConfigValidate):
1025
1067
  raise HTTPException(status_code=400, detail="Invalid API key")
1026
1068
  elif "rate" in error_msg.lower():
1027
1069
  raise HTTPException(status_code=400, detail="Rate limited - but API key is valid")
1070
+ elif "'NoneType'" in error_msg:
1071
+ # This usually means the provider library isn't installed
1072
+ if provider_lower in PROVIDER_PACKAGES:
1073
+ _, install_cmd = PROVIDER_PACKAGES[provider_lower]
1074
+ raise HTTPException(
1075
+ status_code=400,
1076
+ detail=f"The '{config.provider}' provider requires additional packages. "
1077
+ f"Please install with: {install_cmd}"
1078
+ )
1079
+ raise HTTPException(status_code=400, detail=f"Provider initialization failed. Check that required packages are installed.")
1028
1080
  else:
1029
1081
  raise HTTPException(status_code=400, detail=f"Validation failed: {error_msg}")
1030
1082
 
@@ -1261,6 +1313,55 @@ async def clear_database():
1261
1313
  raise HTTPException(status_code=500, detail=str(e))
1262
1314
 
1263
1315
 
1316
+ # Mount static files for bundled frontend (if available)
1317
+ # This must be after all API routes to avoid conflicts
1318
+ if STATIC_DIR.exists() and (STATIC_DIR / "index.html").exists():
1319
+ # Mount assets directory for JS/CSS files
1320
+ if (STATIC_DIR / "assets").exists():
1321
+ app.mount("/assets", StaticFiles(directory=str(STATIC_DIR / "assets")), name="assets")
1322
+
1323
+ @app.get("/favicon.svg")
1324
+ async def favicon():
1325
+ """Serve favicon"""
1326
+ favicon_path = STATIC_DIR / "favicon.svg"
1327
+ if favicon_path.exists():
1328
+ return FileResponse(str(favicon_path), media_type="image/svg+xml")
1329
+ raise HTTPException(status_code=404)
1330
+
1331
+ @app.get("/{full_path:path}")
1332
+ async def serve_spa(request: Request, full_path: str):
1333
+ """
1334
+ Serve the SPA frontend for all non-API routes.
1335
+ This enables client-side routing.
1336
+ """
1337
+ # Don't serve SPA for API routes (they're handled above)
1338
+ if full_path.startswith("api/"):
1339
+ raise HTTPException(status_code=404, detail="API endpoint not found")
1340
+
1341
+ # Try to serve the exact file if it exists
1342
+ file_path = STATIC_DIR / full_path
1343
+ if file_path.exists() and file_path.is_file():
1344
+ # Determine content type
1345
+ suffix = file_path.suffix.lower()
1346
+ media_types = {
1347
+ ".html": "text/html",
1348
+ ".css": "text/css",
1349
+ ".js": "application/javascript",
1350
+ ".json": "application/json",
1351
+ ".png": "image/png",
1352
+ ".jpg": "image/jpeg",
1353
+ ".jpeg": "image/jpeg",
1354
+ ".svg": "image/svg+xml",
1355
+ ".ico": "image/x-icon",
1356
+ }
1357
+ media_type = media_types.get(suffix, "application/octet-stream")
1358
+ return FileResponse(str(file_path), media_type=media_type)
1359
+
1360
+ # For all other paths, serve index.html (SPA routing)
1361
+ index_path = STATIC_DIR / "index.html"
1362
+ return FileResponse(str(index_path), media_type="text/html")
1363
+
1364
+
1264
1365
  if __name__ == "__main__":
1265
1366
  import uvicorn
1266
1367
  uvicorn.run(app, host="0.0.0.0", port=8000)
@@ -0,0 +1 @@
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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-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-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration: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-red-400:oklch(70.4% .191 22.216);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-purple-400:oklch(71.4% .203 305.504);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-800:oklch(27.8% .033 256.848);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-xl:36rem;--container-4xl:56rem;--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 ;--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-wide:.025em;--leading-tight:1.25;--radius-md:.375rem;--radius-lg:.5rem;--radius-2xl:1rem;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--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%;-moz-tab-size:4;-o-tab-size:4;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}::-moz-placeholder{opacity:1}::placeholder{opacity:1}@supports (not (-webkit-appearance:-apple-pay-button)) or (contain-intrinsic-size:1px){::-moz-placeholder{color:currentColor}::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::-moz-placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}::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]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance: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}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing)*0)}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.top-2{top:calc(var(--spacing)*2)}.top-4{top:calc(var(--spacing)*4)}.top-full{top:100%}.right-0{right:calc(var(--spacing)*0)}.right-2{right:calc(var(--spacing)*2)}.right-4{right:calc(var(--spacing)*4)}.bottom-4{bottom:calc(var(--spacing)*4)}.bottom-16{bottom:calc(var(--spacing)*16)}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.float-left{float:left}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.m-1{margin:calc(var(--spacing)*1)}.m-2{margin:calc(var(--spacing)*2)}.m-3{margin:calc(var(--spacing)*3)}.m-4{margin:calc(var(--spacing)*4)}.m-5{margin:calc(var(--spacing)*5)}.mx-1{margin-inline:calc(var(--spacing)*1)}.mx-2{margin-inline:calc(var(--spacing)*2)}.mx-3{margin-inline:calc(var(--spacing)*3)}.mx-4{margin-inline:calc(var(--spacing)*4)}.mx-5{margin-inline:calc(var(--spacing)*5)}.mx-auto{margin-inline:auto}.my-0\.5{margin-block:calc(var(--spacing)*.5)}.my-1{margin-block:calc(var(--spacing)*1)}.my-2{margin-block:calc(var(--spacing)*2)}.my-3{margin-block:calc(var(--spacing)*3)}.my-4{margin-block:calc(var(--spacing)*4)}.my-5{margin-block:calc(var(--spacing)*5)}.mt-0\.5{margin-top:calc(var(--spacing)*.5)}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-5{margin-top:calc(var(--spacing)*5)}.mr-1{margin-right:calc(var(--spacing)*1)}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mr-4{margin-right:calc(var(--spacing)*4)}.mr-5{margin-right:calc(var(--spacing)*5)}.mb-1{margin-bottom:calc(var(--spacing)*1)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-5{margin-bottom:calc(var(--spacing)*5)}.-ml-1{margin-left:calc(var(--spacing)*-1)}.ml-0\.5{margin-left:calc(var(--spacing)*.5)}.ml-1{margin-left:calc(var(--spacing)*1)}.ml-2{margin-left:calc(var(--spacing)*2)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-4{margin-left:calc(var(--spacing)*4)}.ml-5{margin-left:calc(var(--spacing)*5)}.ml-6{margin-left:calc(var(--spacing)*6)}.ml-24{margin-left:calc(var(--spacing)*24)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.hidden{display:none}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.h-1{height:calc(var(--spacing)*1)}.h-2{height:calc(var(--spacing)*2)}.h-3{height:calc(var(--spacing)*3)}.h-3\.5{height:calc(var(--spacing)*3.5)}.h-4{height:calc(var(--spacing)*4)}.h-5{height:calc(var(--spacing)*5)}.h-6{height:calc(var(--spacing)*6)}.h-7{height:calc(var(--spacing)*7)}.h-8{height:calc(var(--spacing)*8)}.h-9{height:calc(var(--spacing)*9)}.h-10{height:calc(var(--spacing)*10)}.h-12{height:calc(var(--spacing)*12)}.h-14{height:calc(var(--spacing)*14)}.h-full{height:100%}.h-screen{height:100vh}.max-h-48{max-height:calc(var(--spacing)*48)}.min-h-0{min-height:calc(var(--spacing)*0)}.w-1{width:calc(var(--spacing)*1)}.w-3{width:calc(var(--spacing)*3)}.w-3\.5{width:calc(var(--spacing)*3.5)}.w-4{width:calc(var(--spacing)*4)}.w-5{width:calc(var(--spacing)*5)}.w-6{width:calc(var(--spacing)*6)}.w-7{width:calc(var(--spacing)*7)}.w-8{width:calc(var(--spacing)*8)}.w-9{width:calc(var(--spacing)*9)}.w-10{width:calc(var(--spacing)*10)}.w-12{width:calc(var(--spacing)*12)}.w-48{width:calc(var(--spacing)*48)}.w-64{width:calc(var(--spacing)*64)}.w-\[600px\]{width:600px}.w-full{width:100%}.w-px{width:1px}.max-w-4xl{max-width:var(--container-4xl)}.max-w-\[90vw\]{max-width:90vw}.max-w-lg{max-width:var(--container-lg)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:calc(var(--spacing)*0)}.min-w-\[140px\]{min-width:140px}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.-translate-y-1\/2{--tw-translate-y: -50% ;translate:var(--tw-translate-x)var(--tw-translate-y)}.rotate-180{rotate:180deg}.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)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-y{resize:vertical}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:calc(var(--spacing)*1)}.gap-1\.5{gap:calc(var(--spacing)*1.5)}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px*var(--tw-divide-y-reverse));border-bottom-width:calc(1px*calc(1 - var(--tw-divide-y-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-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.border{border-style:var(--tw-border-style);border-width:1px}.border-0{border-style:var(--tw-border-style);border-width:0}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.bg-gray-600{background-color:var(--color-gray-600)}.bg-white\/10{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.bg-white\/10{background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.p-1{padding:calc(var(--spacing)*1)}.p-1\.5{padding:calc(var(--spacing)*1.5)}.p-2{padding:calc(var(--spacing)*2)}.p-3{padding:calc(var(--spacing)*3)}.p-4{padding:calc(var(--spacing)*4)}.p-5{padding:calc(var(--spacing)*5)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.px-1{padding-inline:calc(var(--spacing)*1)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-2\.5{padding-inline:calc(var(--spacing)*2.5)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.py-0\.5{padding-block:calc(var(--spacing)*.5)}.py-1{padding-block:calc(var(--spacing)*1)}.py-1\.5{padding-block:calc(var(--spacing)*1.5)}.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-4{padding-block:calc(var(--spacing)*4)}.py-5{padding-block:calc(var(--spacing)*5)}.py-8{padding-block:calc(var(--spacing)*8)}.pt-0\.5{padding-top:calc(var(--spacing)*.5)}.pt-1{padding-top:calc(var(--spacing)*1)}.pt-2{padding-top:calc(var(--spacing)*2)}.pt-3{padding-top:calc(var(--spacing)*3)}.pt-4{padding-top:calc(var(--spacing)*4)}.pt-5{padding-top:calc(var(--spacing)*5)}.pr-1{padding-right:calc(var(--spacing)*1)}.pr-2{padding-right:calc(var(--spacing)*2)}.pr-3{padding-right:calc(var(--spacing)*3)}.pr-4{padding-right:calc(var(--spacing)*4)}.pr-5{padding-right:calc(var(--spacing)*5)}.pr-8{padding-right:calc(var(--spacing)*8)}.pb-1{padding-bottom:calc(var(--spacing)*1)}.pb-2{padding-bottom:calc(var(--spacing)*2)}.pb-3{padding-bottom:calc(var(--spacing)*3)}.pb-4{padding-bottom:calc(var(--spacing)*4)}.pb-5{padding-bottom:calc(var(--spacing)*5)}.pl-1{padding-left:calc(var(--spacing)*1)}.pl-2{padding-left:calc(var(--spacing)*2)}.pl-3{padding-left:calc(var(--spacing)*3)}.pl-4{padding-left:calc(var(--spacing)*4)}.pl-5{padding-left:calc(var(--spacing)*5)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.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-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.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)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-gray-200{color:var(--color-gray-200)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-purple-400{color:var(--color-purple-400)}.text-red-400{color:var(--color-red-400)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.line-through{text-decoration-line:line-through}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.opacity-25{opacity:.25}.opacity-40{opacity:.4}.opacity-50{opacity:.5}.opacity-75{opacity:.75}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.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)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px 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)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.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,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.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-opacity{transition-property:opacity;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-300{--tw-duration:.3s;transition-duration:.3s}@media(hover:hover){.hover\:border-blue-400:hover{border-color:var(--color-blue-400)}.hover\:bg-\[var\(--color-bg-tertiary\)\]:hover{background-color:var(--color-bg-tertiary)}.hover\:bg-black\/10:hover{background-color:#0000001a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-black\/10:hover{background-color:color-mix(in oklab,var(--color-black)10%,transparent)}}.hover\:bg-blue-500:hover{background-color:var(--color-blue-500)}.hover\:bg-gray-800:hover{background-color:var(--color-gray-800)}.hover\:bg-white\/20:hover{background-color:#fff3}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/20:hover{background-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.hover\:text-gray-200:hover{color:var(--color-gray-200)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-80:hover{opacity:.8}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-md:hover{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px 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)}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px;--tw-ring-offset-shadow:var(--tw-ring-inset,)0 0 0 var(--tw-ring-offset-width)var(--tw-ring-offset-color)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}@media(prefers-color-scheme:dark){@media(hover:hover){.dark\:hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.dark\:hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}}}}:root{--color-bg-primary:#fff;--color-bg-secondary:#f7f7f8;--color-bg-tertiary:#ececf1;--color-bg-hover:#e0e0e5;--color-text-primary:#0d0d0d;--color-text-secondary:#676767;--color-text-muted:#8e8ea0;--color-border:#e5e5e5;--color-accent:#10a37f;--color-accent-hover:#0d8a6f;--color-error:#ef4146;--color-error-bg:#fef2f2;--color-warning:#f59e0b;--color-warning-bg:#fffbeb;--color-success:#10a37f;--color-success-bg:#ecfdf5;--color-info:#6366f1;--color-info-bg:#eef2ff;--color-link:#2563eb;--color-suggestion:#8b5cf6;--color-suggestion-bg:#f5f3ff;--sidebar-width:280px;--sidebar-bg:#f9f9f9}.dark{--color-bg-primary:#212121;--color-bg-secondary:#2f2f2f;--color-bg-tertiary:#424242;--color-bg-hover:#525252;--color-text-primary:#ececec;--color-text-secondary:#b4b4b4;--color-text-muted:#8e8e8e;--color-border:#444;--color-accent:#10a37f;--color-accent-hover:#1a7f64;--color-error:#f87171;--color-error-bg:#3b1818;--color-warning:#fbbf24;--color-warning-bg:#3b2f05;--color-success:#10a37f;--color-success-bg:#052e22;--color-info:#818cf8;--color-info-bg:#1e1b4b;--color-link:#60a5fa;--color-suggestion:#a78bfa;--color-suggestion-bg:#2e1f4d;--sidebar-bg:#171717}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}body{background-color:var(--color-bg-primary);color:var(--color-text-primary);min-height:100vh;margin:0}#root{min-height:100vh}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--color-bg-secondary)}::-webkit-scrollbar-thumb{background:var(--color-border);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--color-text-muted)}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:1s linear infinite spin}.drop-zone{border:2px dashed var(--color-border);transition:all .2s}.drop-zone.active{border-color:var(--color-accent);background-color:var(--color-info-bg)}.progress-bar{background:linear-gradient(90deg,var(--color-accent)0%,var(--color-info)100%)}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value: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-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-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-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}