Spaces:
Sleeping
Sleeping
Commit ·
a04ea44
1
Parent(s): 51a7cc4
Drop taxonomy label 11; renumber 'None of the above' from 12 to 11
Browse files- api.py +12 -557
- frontend_build/asset-manifest.json +4 -4
- frontend_build/index.html +1 -1
- frontend_build/static/css/{main.78d0a2b0.css → main.fb799031.css} +1 -1
- frontend_build/static/js/{main.ec549950.js → main.0f49f162.js} +0 -0
- frontend_build/static/js/{main.ec549950.js.LICENSE.txt → main.0f49f162.js.LICENSE.txt} +0 -0
api.py
CHANGED
|
@@ -2,13 +2,11 @@ import json
|
|
| 2 |
import os
|
| 3 |
import threading
|
| 4 |
import time
|
| 5 |
-
from typing import Any, Dict,
|
| 6 |
|
| 7 |
import pandas as pd
|
| 8 |
from fastapi import BackgroundTasks, FastAPI, HTTPException, Request
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
| 10 |
-
from fastapi.middleware.gzip import GZipMiddleware
|
| 11 |
-
from fastapi.responses import FileResponse
|
| 12 |
from fastapi.staticfiles import StaticFiles
|
| 13 |
from pydantic import BaseModel
|
| 14 |
|
|
@@ -30,10 +28,6 @@ progress_lock = threading.Lock()
|
|
| 30 |
# Initialize FastAPI app
|
| 31 |
app = FastAPI(title="Wiki Revision Classifier API")
|
| 32 |
|
| 33 |
-
# Compress JSON responses larger than 1 KB. Big win for /demo and /results
|
| 34 |
-
# (multi-MB JSON payloads shrink ~70% on the wire).
|
| 35 |
-
app.add_middleware(GZipMiddleware, minimum_size=1024)
|
| 36 |
-
|
| 37 |
# Enable CORS
|
| 38 |
app.add_middleware(
|
| 39 |
CORSMiddleware,
|
|
@@ -52,13 +46,12 @@ async def add_cache_control_headers(request: Request, call_next):
|
|
| 52 |
response.headers["Expires"] = "0"
|
| 53 |
return response
|
| 54 |
|
| 55 |
-
# Prepare
|
| 56 |
-
# Mounted at /files so it doesn't collide with CRA's /static asset paths.
|
| 57 |
os.makedirs("visualizations", exist_ok=True)
|
| 58 |
app.mount(
|
| 59 |
-
"/
|
| 60 |
StaticFiles(directory="visualizations"),
|
| 61 |
-
name="
|
| 62 |
)
|
| 63 |
|
| 64 |
# Pydantic models
|
|
@@ -115,7 +108,7 @@ async def prepare_article(payload: PreparePayload) -> Dict[str, Any]:
|
|
| 115 |
|
| 116 |
return {
|
| 117 |
"previewRows": preview,
|
| 118 |
-
"csvUrl": f"/
|
| 119 |
"diffs": diffs,
|
| 120 |
"revisionCount": revision_count,
|
| 121 |
}
|
|
@@ -126,7 +119,7 @@ async def classify_article(payload: ClassifyPayload, background_tasks: Backgroun
|
|
| 126 |
Start classification in the background and return immediately.
|
| 127 |
Uses the flexible-linker pipeline + OpenAI Batch API (single batch).
|
| 128 |
"""
|
| 129 |
-
|
| 130 |
model_name = payload.model or "gpt-5-mini"
|
| 131 |
|
| 132 |
df, title = fetch_revision_history(payload.article)
|
|
@@ -165,7 +158,6 @@ async def classify_article(payload: ClassifyPayload, background_tasks: Backgroun
|
|
| 165 |
background_tasks.add_task(
|
| 166 |
process_classification,
|
| 167 |
model_name=model_name,
|
| 168 |
-
api_key=api_key,
|
| 169 |
prompts=prompts,
|
| 170 |
df=prompted_df,
|
| 171 |
title=title,
|
|
@@ -224,7 +216,7 @@ def compute_grouped_idx(results_df: pd.DataFrame) -> pd.Series:
|
|
| 224 |
return pd.Series(out, index=results_df.index, dtype=int)
|
| 225 |
|
| 226 |
|
| 227 |
-
def process_classification(model_name,
|
| 228 |
"""Run the OpenAI batch classification in the background."""
|
| 229 |
safe_title = title.replace(" ", "_")
|
| 230 |
total = len(prompts)
|
|
@@ -247,7 +239,7 @@ def process_classification(model_name, api_key, prompts, df, title, taxonomy):
|
|
| 247 |
with progress_lock:
|
| 248 |
progress_map.setdefault(safe_title, {})["stage"] = "submitting"
|
| 249 |
|
| 250 |
-
model = GPT(model_name=model_name
|
| 251 |
raw_responses = model.predict(prompts, status_callback=_update_batch_status)
|
| 252 |
|
| 253 |
with progress_lock:
|
|
@@ -283,29 +275,8 @@ def process_classification(model_name, api_key, prompts, df, title, taxonomy):
|
|
| 283 |
result_path = os.path.join("visualizations", result_filename)
|
| 284 |
results_df.to_csv(result_path, index=False)
|
| 285 |
|
| 286 |
-
# Stash the full DataFrame so /summarize can look up Explanations
|
| 287 |
-
# for any (label, section, period) cell the user clicks.
|
| 288 |
-
_store_dataset(title, results_df)
|
| 289 |
-
|
| 290 |
-
# Generate the page-evolution summary now while we still have the
|
| 291 |
-
# user's API key in scope. Persist alongside the results CSV.
|
| 292 |
-
evolution_filename = f"{safe_title}_evolution.txt"
|
| 293 |
-
evolution_path = os.path.join("visualizations", evolution_filename)
|
| 294 |
-
try:
|
| 295 |
-
with progress_lock:
|
| 296 |
-
progress_map.setdefault(safe_title, {})["stage"] = "summarizing"
|
| 297 |
-
evolution_text = generate_evolution_summary(
|
| 298 |
-
results_df, api_key=api_key, model=model_name
|
| 299 |
-
)
|
| 300 |
-
if evolution_text:
|
| 301 |
-
with open(evolution_path, "w", encoding="utf-8") as f:
|
| 302 |
-
f.write(evolution_text)
|
| 303 |
-
except Exception as e:
|
| 304 |
-
# Don't fail the whole classification if the summary errors.
|
| 305 |
-
print(f"[warn] evolution summary failed: {e}")
|
| 306 |
-
|
| 307 |
print("Creating visualizations...")
|
| 308 |
-
taxonomy[
|
| 309 |
"category": "None of the above",
|
| 310 |
"description": "No meaningful scientific changes were detected between the revisions; applies to non-scientific, formatting, or trivial edits."
|
| 311 |
}
|
|
@@ -359,24 +330,12 @@ async def get_results(article: str) -> Dict[str, Any]:
|
|
| 359 |
|
| 360 |
# Load results
|
| 361 |
results_df = pd.read_csv(result_path)
|
| 362 |
-
_store_dataset(article, results_df)
|
| 363 |
preview_rows = results_df.head(5).to_dict(orient="records")
|
| 364 |
|
| 365 |
# URL paths for frontend
|
| 366 |
-
csv_url = f"/
|
| 367 |
-
distribution_url = f"/
|
| 368 |
-
cooccurrence_url = f"/
|
| 369 |
-
|
| 370 |
-
# Optional: page-evolution summary (label 1 + 6 across years).
|
| 371 |
-
evolution_filename = f"{safe_title}_evolution.txt"
|
| 372 |
-
evolution_path = os.path.join("visualizations", evolution_filename)
|
| 373 |
-
evolution_text: Optional[str] = None
|
| 374 |
-
if os.path.exists(evolution_path):
|
| 375 |
-
try:
|
| 376 |
-
with open(evolution_path, "r", encoding="utf-8") as f:
|
| 377 |
-
evolution_text = f.read().strip() or None
|
| 378 |
-
except Exception:
|
| 379 |
-
evolution_text = None
|
| 380 |
|
| 381 |
return {
|
| 382 |
"status": "complete",
|
|
@@ -385,484 +344,8 @@ async def get_results(article: str) -> Dict[str, Any]:
|
|
| 385 |
"results": results_df.to_dict(orient="records"),
|
| 386 |
"distributionUrl": distribution_url,
|
| 387 |
"cooccurrenceUrl": cooccurrence_url,
|
| 388 |
-
"evolutionSummary": evolution_text,
|
| 389 |
}
|
| 390 |
|
| 391 |
-
DEMOS = {
|
| 392 |
-
"CRISPR": {
|
| 393 |
-
"csv": "demo/crispr_results.csv",
|
| 394 |
-
"title": "CRISPR",
|
| 395 |
-
"evolution": "demo/crispr_evolution.txt",
|
| 396 |
-
},
|
| 397 |
-
}
|
| 398 |
-
|
| 399 |
-
# Full per-article DataFrames retained in memory so the summarize endpoint
|
| 400 |
-
# can look up Explanations for any (label, section, period) cell. Distinct
|
| 401 |
-
# from `_demo_cache`, which holds the slim JSON payload sent to the browser.
|
| 402 |
-
_dataset_cache: Dict[str, "pd.DataFrame"] = {}
|
| 403 |
-
_dataset_cache_lock = threading.Lock()
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
def _store_dataset(article: str, df: "pd.DataFrame") -> None:
|
| 407 |
-
with _dataset_cache_lock:
|
| 408 |
-
_dataset_cache[article] = df
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
def _get_dataset(article: str) -> Optional["pd.DataFrame"]:
|
| 412 |
-
with _dataset_cache_lock:
|
| 413 |
-
return _dataset_cache.get(article)
|
| 414 |
-
|
| 415 |
-
# Columns the frontend's interactive charts actually consume. We strip the
|
| 416 |
-
# large free-text columns (Prompt/Explanation) from the bulk `results` array
|
| 417 |
-
# to keep the JSON payload small; the first few preview rows keep them.
|
| 418 |
-
CHART_COLUMNS = [
|
| 419 |
-
"Section",
|
| 420 |
-
"Timestamp",
|
| 421 |
-
"Labels",
|
| 422 |
-
"grouped_idx",
|
| 423 |
-
"Level",
|
| 424 |
-
"Flexible_Previous_Section",
|
| 425 |
-
]
|
| 426 |
-
|
| 427 |
-
# In-memory cache for prepared demo payloads. Keyed by article name; cleared
|
| 428 |
-
# on container restart. Reading + serializing the CSV is the slow part on
|
| 429 |
-
# repeat clicks, so caching the final dict is the biggest win.
|
| 430 |
-
_demo_cache: Dict[str, Dict[str, Any]] = {}
|
| 431 |
-
_demo_cache_lock = threading.Lock()
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
def _build_demo_payload(demo: Dict[str, str]) -> Dict[str, Any]:
|
| 435 |
-
results_df = pd.read_csv(demo["csv"])
|
| 436 |
-
# The bundled demo CSVs may lack grouped_idx (e.g. older offline pipelines);
|
| 437 |
-
# compute it if absent so the frontend charts work uniformly.
|
| 438 |
-
if "grouped_idx" not in results_df.columns:
|
| 439 |
-
results_df["grouped_idx"] = compute_grouped_idx(results_df)
|
| 440 |
-
|
| 441 |
-
# Stash the full DataFrame so /summarize can look up Explanations.
|
| 442 |
-
_store_dataset(demo["title"], results_df)
|
| 443 |
-
|
| 444 |
-
preview_rows = results_df.head(5).to_dict(orient="records")
|
| 445 |
-
|
| 446 |
-
# Slim the bulk array: keep only columns the charts actually read.
|
| 447 |
-
chart_cols = [c for c in CHART_COLUMNS if c in results_df.columns]
|
| 448 |
-
slim_df = results_df[chart_cols]
|
| 449 |
-
|
| 450 |
-
# Static, pre-generated evolution summary if the demo entry provides one.
|
| 451 |
-
evolution_text: Optional[str] = None
|
| 452 |
-
evo_path = demo.get("evolution")
|
| 453 |
-
if evo_path and os.path.exists(evo_path):
|
| 454 |
-
try:
|
| 455 |
-
with open(evo_path, "r", encoding="utf-8") as f:
|
| 456 |
-
evolution_text = f.read().strip() or None
|
| 457 |
-
except Exception:
|
| 458 |
-
evolution_text = None
|
| 459 |
-
|
| 460 |
-
return {
|
| 461 |
-
"status": "complete",
|
| 462 |
-
"title": demo["title"],
|
| 463 |
-
"previewRows": preview_rows,
|
| 464 |
-
"results": slim_df.to_dict(orient="records"),
|
| 465 |
-
"evolutionSummary": evolution_text,
|
| 466 |
-
"csvUrl": f"/demo/download?article={demo['title']}",
|
| 467 |
-
}
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
@app.get("/demo")
|
| 471 |
-
async def get_demo(article: str) -> Dict[str, Any]:
|
| 472 |
-
"""
|
| 473 |
-
Return a pre-classified results dataset packaged with the Space.
|
| 474 |
-
Lets users see the interactive visualizations without running their own
|
| 475 |
-
classification.
|
| 476 |
-
"""
|
| 477 |
-
demo = DEMOS.get(article)
|
| 478 |
-
if demo is None:
|
| 479 |
-
raise HTTPException(status_code=404, detail=f"No demo available for '{article}'")
|
| 480 |
-
if not os.path.exists(demo["csv"]):
|
| 481 |
-
raise HTTPException(status_code=500, detail=f"Demo file missing: {demo['csv']}")
|
| 482 |
-
|
| 483 |
-
with _demo_cache_lock:
|
| 484 |
-
cached = _demo_cache.get(article)
|
| 485 |
-
if cached is not None:
|
| 486 |
-
return cached
|
| 487 |
-
|
| 488 |
-
payload = _build_demo_payload(demo)
|
| 489 |
-
with _demo_cache_lock:
|
| 490 |
-
_demo_cache[article] = payload
|
| 491 |
-
return payload
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
@app.get("/demo/download")
|
| 495 |
-
async def download_demo_csv(article: str) -> FileResponse:
|
| 496 |
-
"""Serve the bundled demo CSV so users can save and re-load it later."""
|
| 497 |
-
demo = DEMOS.get(article)
|
| 498 |
-
if demo is None:
|
| 499 |
-
raise HTTPException(status_code=404, detail=f"No demo available for '{article}'")
|
| 500 |
-
if not os.path.exists(demo["csv"]):
|
| 501 |
-
raise HTTPException(status_code=500, detail=f"Demo file missing: {demo['csv']}")
|
| 502 |
-
return FileResponse(
|
| 503 |
-
demo["csv"],
|
| 504 |
-
media_type="text/csv",
|
| 505 |
-
filename=f"{demo['title']}_classified_revisions.csv",
|
| 506 |
-
)
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
# ---------------------------------------------------------------------------
|
| 510 |
-
# Summarize endpoint: small OpenAI call over the Explanation column for the
|
| 511 |
-
# rows behind a clicked chart cell or line-chart point.
|
| 512 |
-
# ---------------------------------------------------------------------------
|
| 513 |
-
|
| 514 |
-
class SummarizePayload(BaseModel):
|
| 515 |
-
article: str
|
| 516 |
-
label: int
|
| 517 |
-
period: str # one of "Y" | "2Q" | "Q" — matches the frontend Period type
|
| 518 |
-
period_key: str # e.g. "2018", "2018Q3", "2018H1"
|
| 519 |
-
section_ids: Optional[List[int]] = None # grouped_idx values; None = all sections
|
| 520 |
-
api_key: str
|
| 521 |
-
model: Optional[str] = "gpt-4o-mini"
|
| 522 |
-
# When the dataset lives only in the user's browser (CSV they uploaded),
|
| 523 |
-
# the frontend filters rows itself and sends just the matching explanation
|
| 524 |
-
# snippets here. If provided, the server skips its own dataset lookup.
|
| 525 |
-
explanations: Optional[List[str]] = None
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
def _period_key(timestamp: pd.Timestamp, period: str) -> str:
|
| 529 |
-
"""Mirror of the frontend's periodKey() in taxonomy.ts."""
|
| 530 |
-
if pd.isna(timestamp):
|
| 531 |
-
return ""
|
| 532 |
-
if not isinstance(timestamp, pd.Timestamp):
|
| 533 |
-
timestamp = pd.Timestamp(timestamp)
|
| 534 |
-
if timestamp.tzinfo is None:
|
| 535 |
-
timestamp = timestamp.tz_localize("UTC")
|
| 536 |
-
else:
|
| 537 |
-
timestamp = timestamp.tz_convert("UTC")
|
| 538 |
-
y = timestamp.year
|
| 539 |
-
m = timestamp.month - 1
|
| 540 |
-
if period == "Y":
|
| 541 |
-
return str(y)
|
| 542 |
-
q = m // 3
|
| 543 |
-
if period == "Q":
|
| 544 |
-
return f"{y}Q{q + 1}"
|
| 545 |
-
# half-year ("2Q")
|
| 546 |
-
return f"{y}H1" if q < 2 else f"{y}H2"
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
def _parse_labels_field(raw: Any) -> List[int]:
|
| 550 |
-
if raw is None or (isinstance(raw, float) and pd.isna(raw)) or raw == "":
|
| 551 |
-
return []
|
| 552 |
-
if isinstance(raw, list):
|
| 553 |
-
return [int(x) for x in raw if str(x).strip().lstrip("-").isdigit()]
|
| 554 |
-
s = str(raw).strip()
|
| 555 |
-
if not s:
|
| 556 |
-
return []
|
| 557 |
-
try:
|
| 558 |
-
import ast as _ast
|
| 559 |
-
parsed = _ast.literal_eval(s.replace("'", '"'))
|
| 560 |
-
if isinstance(parsed, list):
|
| 561 |
-
return [int(x) for x in parsed if str(x).strip().lstrip("-").isdigit()]
|
| 562 |
-
except Exception:
|
| 563 |
-
pass
|
| 564 |
-
inner = s.strip("[]")
|
| 565 |
-
out: List[int] = []
|
| 566 |
-
for tok in inner.split(","):
|
| 567 |
-
tok = tok.strip()
|
| 568 |
-
if tok and tok.lstrip("-").isdigit():
|
| 569 |
-
out.append(int(tok))
|
| 570 |
-
return out
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
def _explanation_for_label(raw: Any, label: int) -> Optional[str]:
|
| 574 |
-
"""Explanation column stores a dict-like JSON string keyed by label."""
|
| 575 |
-
if raw is None or (isinstance(raw, float) and pd.isna(raw)) or raw == "":
|
| 576 |
-
return None
|
| 577 |
-
s = str(raw).strip()
|
| 578 |
-
if not s:
|
| 579 |
-
return None
|
| 580 |
-
try:
|
| 581 |
-
import ast as _ast
|
| 582 |
-
parsed = _ast.literal_eval(s)
|
| 583 |
-
if isinstance(parsed, dict):
|
| 584 |
-
# Keys may be int or str depending on how it was serialized.
|
| 585 |
-
for k in (label, str(label)):
|
| 586 |
-
if k in parsed:
|
| 587 |
-
return str(parsed[k])
|
| 588 |
-
except Exception:
|
| 589 |
-
pass
|
| 590 |
-
return None
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
# Labels considered "scientifically significant" for the evolution summary.
|
| 594 |
-
EVOLUTION_LABELS = {1, 6}
|
| 595 |
-
# Years with fewer than this many matching edits are dropped from the prompt.
|
| 596 |
-
EVOLUTION_MIN_PER_YEAR = 5
|
| 597 |
-
# Cap explanations sent per year to keep the prompt bounded on huge corpora.
|
| 598 |
-
EVOLUTION_MAX_PER_YEAR = 60
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
def generate_evolution_summary(
|
| 602 |
-
df: "pd.DataFrame", api_key: str, model: str = "gpt-4o-mini"
|
| 603 |
-
) -> Optional[str]:
|
| 604 |
-
"""
|
| 605 |
-
Build a prose paragraph describing scientifically significant edits across
|
| 606 |
-
years, grouped by year. Uses rows whose Labels include 1 (New Scientific
|
| 607 |
-
Information) or 6 (Change in Scientific Narrative). Drops sparse years
|
| 608 |
-
(< EVOLUTION_MIN_PER_YEAR matches) and caps very dense years.
|
| 609 |
-
|
| 610 |
-
Returns None if there's nothing to summarize, or raises on OpenAI errors.
|
| 611 |
-
"""
|
| 612 |
-
if "Labels" not in df.columns or "Timestamp" not in df.columns:
|
| 613 |
-
return None
|
| 614 |
-
if "Explanation" not in df.columns:
|
| 615 |
-
return None
|
| 616 |
-
|
| 617 |
-
# Pre-parse once.
|
| 618 |
-
timestamps = pd.to_datetime(df["Timestamp"], utc=True, errors="coerce")
|
| 619 |
-
parsed_labels = df["Labels"].apply(_parse_labels_field)
|
| 620 |
-
|
| 621 |
-
# Group explanations by year, only for rows touching label 1 or 6.
|
| 622 |
-
by_year: Dict[int, List[str]] = {}
|
| 623 |
-
for idx, ts in timestamps.items():
|
| 624 |
-
if pd.isna(ts):
|
| 625 |
-
continue
|
| 626 |
-
labels = parsed_labels.iloc[idx] if hasattr(parsed_labels, "iloc") else parsed_labels[idx]
|
| 627 |
-
relevant = EVOLUTION_LABELS.intersection(labels)
|
| 628 |
-
if not relevant:
|
| 629 |
-
continue
|
| 630 |
-
raw = df.at[idx, "Explanation"]
|
| 631 |
-
snippets: List[str] = []
|
| 632 |
-
for label in relevant:
|
| 633 |
-
snippet = _explanation_for_label(raw, label)
|
| 634 |
-
if snippet:
|
| 635 |
-
snippets.append(f"(label {label}) {snippet}")
|
| 636 |
-
if not snippets:
|
| 637 |
-
continue
|
| 638 |
-
year = int(ts.year)
|
| 639 |
-
by_year.setdefault(year, []).extend(snippets)
|
| 640 |
-
|
| 641 |
-
if not by_year:
|
| 642 |
-
return None
|
| 643 |
-
|
| 644 |
-
# Drop sparse years and cap dense ones.
|
| 645 |
-
kept_years = sorted(y for y, items in by_year.items() if len(items) >= EVOLUTION_MIN_PER_YEAR)
|
| 646 |
-
if not kept_years:
|
| 647 |
-
return None
|
| 648 |
-
|
| 649 |
-
# Build a year-headered prompt.
|
| 650 |
-
lines: List[str] = []
|
| 651 |
-
total_used = 0
|
| 652 |
-
for year in kept_years:
|
| 653 |
-
items = by_year[year][:EVOLUTION_MAX_PER_YEAR]
|
| 654 |
-
total_used += len(items)
|
| 655 |
-
lines.append(f"\n## {year} ({len(by_year[year])} matching edits)")
|
| 656 |
-
for item in items:
|
| 657 |
-
lines.append(f"- {item}")
|
| 658 |
-
grouped_text = "\n".join(lines)
|
| 659 |
-
|
| 660 |
-
user_prompt = (
|
| 661 |
-
"You are summarizing the scientific evolution of a Wikipedia article "
|
| 662 |
-
"based on per-revision edit explanations. The explanations below are "
|
| 663 |
-
"grouped by year, drawn only from edits classified as introducing "
|
| 664 |
-
"new scientific information (label 1) or changing the scientific "
|
| 665 |
-
"narrative (label 6).\n\n"
|
| 666 |
-
"Write ONE flowing paragraph (about 8–12 sentences) that traces "
|
| 667 |
-
"how the article's scientific content evolved across years. "
|
| 668 |
-
"Highlight the most dominant scientific changes per year, weaving in "
|
| 669 |
-
"concrete topics (e.g., specific discoveries, mechanisms, applications, "
|
| 670 |
-
"or controversies) by year (e.g., \"In 2017, findings about X were added\"). "
|
| 671 |
-
"Mention years explicitly. Do not list every edit — synthesize the "
|
| 672 |
-
"patterns. Do not invent details that aren't in the explanations. Do "
|
| 673 |
-
"not use bullet points or section headers; output plain prose only.\n\n"
|
| 674 |
-
f"Explanations grouped by year ({total_used} total snippets):\n"
|
| 675 |
-
f"{grouped_text}"
|
| 676 |
-
)
|
| 677 |
-
|
| 678 |
-
import openai
|
| 679 |
-
client = openai.OpenAI(api_key=api_key)
|
| 680 |
-
completion = client.chat.completions.create(
|
| 681 |
-
model=model or "gpt-4o-mini",
|
| 682 |
-
messages=[
|
| 683 |
-
{
|
| 684 |
-
"role": "system",
|
| 685 |
-
"content": (
|
| 686 |
-
"You are a careful research assistant that writes compact, "
|
| 687 |
-
"evidence-grounded prose summaries of how Wikipedia articles "
|
| 688 |
-
"evolved scientifically over time."
|
| 689 |
-
),
|
| 690 |
-
},
|
| 691 |
-
{"role": "user", "content": user_prompt},
|
| 692 |
-
],
|
| 693 |
-
)
|
| 694 |
-
text = completion.choices[0].message.content or ""
|
| 695 |
-
return text.strip() or None
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
@app.post("/summarize")
|
| 699 |
-
async def summarize_edits(payload: SummarizePayload) -> Dict[str, Any]:
|
| 700 |
-
"""
|
| 701 |
-
Summarize the Explanation text for all rows that share a (label, period,
|
| 702 |
-
section-group) cell using a fast OpenAI model. Used by interactive chart
|
| 703 |
-
drill-downs.
|
| 704 |
-
"""
|
| 705 |
-
# Client-supplied path: the dataset is in the user's browser, not cached
|
| 706 |
-
# on the server. The frontend has already filtered rows by (label, period,
|
| 707 |
-
# sections) and sent the relevant explanation snippets. Skip the server
|
| 708 |
-
# dataset lookup entirely.
|
| 709 |
-
client_snippets: Optional[List[str]] = None
|
| 710 |
-
matched_count: Optional[int] = None
|
| 711 |
-
if payload.explanations is not None:
|
| 712 |
-
client_snippets = [s for s in payload.explanations if s and s.strip()]
|
| 713 |
-
matched_count = len(payload.explanations)
|
| 714 |
-
if not client_snippets:
|
| 715 |
-
return {
|
| 716 |
-
"summary": None,
|
| 717 |
-
"edit_count": matched_count,
|
| 718 |
-
"explanation_count": 0,
|
| 719 |
-
"message": "Matched edits had no explanation text to summarize.",
|
| 720 |
-
}
|
| 721 |
-
|
| 722 |
-
df = None
|
| 723 |
-
if client_snippets is None:
|
| 724 |
-
df = _get_dataset(payload.article)
|
| 725 |
-
if df is None:
|
| 726 |
-
# Lazy-load the demo dataset if its cache got cleared (e.g. container restart).
|
| 727 |
-
demo = DEMOS.get(payload.article)
|
| 728 |
-
if demo is not None and os.path.exists(demo["csv"]):
|
| 729 |
-
_build_demo_payload(demo)
|
| 730 |
-
df = _get_dataset(payload.article)
|
| 731 |
-
if df is None:
|
| 732 |
-
raise HTTPException(
|
| 733 |
-
status_code=404,
|
| 734 |
-
detail=f"No dataset loaded for '{payload.article}'.",
|
| 735 |
-
)
|
| 736 |
-
if "Explanation" not in df.columns:
|
| 737 |
-
raise HTTPException(
|
| 738 |
-
status_code=400,
|
| 739 |
-
detail="This dataset has no Explanation column to summarize.",
|
| 740 |
-
)
|
| 741 |
-
|
| 742 |
-
if client_snippets is not None:
|
| 743 |
-
snippets = client_snippets
|
| 744 |
-
# We don't know the unmatched count on the client path; use the count
|
| 745 |
-
# the client reported.
|
| 746 |
-
match_count_for_response = matched_count or len(snippets)
|
| 747 |
-
else:
|
| 748 |
-
# Filter to rows that contain the requested label.
|
| 749 |
-
mask = df["Labels"].apply(lambda v: payload.label in _parse_labels_field(v))
|
| 750 |
-
|
| 751 |
-
# Filter by period bucket.
|
| 752 |
-
timestamps = pd.to_datetime(df["Timestamp"], utc=True, errors="coerce")
|
| 753 |
-
period_mask = timestamps.apply(lambda t: _period_key(t, payload.period) == payload.period_key)
|
| 754 |
-
mask = mask & period_mask
|
| 755 |
-
|
| 756 |
-
# Filter by section group(s) if provided.
|
| 757 |
-
if payload.section_ids is not None and "grouped_idx" in df.columns:
|
| 758 |
-
section_set = set(int(s) for s in payload.section_ids)
|
| 759 |
-
mask = mask & df["grouped_idx"].apply(
|
| 760 |
-
lambda v: int(v) in section_set if pd.notna(v) else False
|
| 761 |
-
)
|
| 762 |
-
|
| 763 |
-
matched = df.loc[mask]
|
| 764 |
-
if matched.empty:
|
| 765 |
-
return {
|
| 766 |
-
"summary": None,
|
| 767 |
-
"edit_count": 0,
|
| 768 |
-
"message": "No edits found for this cell.",
|
| 769 |
-
}
|
| 770 |
-
|
| 771 |
-
# Pull the relevant Explanation snippet for each matched row.
|
| 772 |
-
snippets = []
|
| 773 |
-
for _, row in matched.iterrows():
|
| 774 |
-
text = _explanation_for_label(row.get("Explanation"), payload.label)
|
| 775 |
-
if text:
|
| 776 |
-
snippets.append(text)
|
| 777 |
-
|
| 778 |
-
if not snippets:
|
| 779 |
-
return {
|
| 780 |
-
"summary": None,
|
| 781 |
-
"edit_count": int(len(matched)),
|
| 782 |
-
"message": "Matched edits had no explanation text to summarize.",
|
| 783 |
-
}
|
| 784 |
-
match_count_for_response = int(len(matched))
|
| 785 |
-
|
| 786 |
-
# Cap to keep the prompt comfortably within the model's context.
|
| 787 |
-
MAX_SNIPPETS = 100
|
| 788 |
-
truncated = len(snippets) > MAX_SNIPPETS
|
| 789 |
-
snippets_for_prompt = snippets[:MAX_SNIPPETS]
|
| 790 |
-
|
| 791 |
-
# Compose the prompt.
|
| 792 |
-
label_name_map = {
|
| 793 |
-
1: "New Scientific Information",
|
| 794 |
-
2: "Scientific Information Removed",
|
| 795 |
-
3: "Scientific Clarification Added",
|
| 796 |
-
4: "Scientific Technical Terms Added",
|
| 797 |
-
5: "Researcher Names Added",
|
| 798 |
-
6: "Change in Scientific Narrative",
|
| 799 |
-
7: "Academic References Added",
|
| 800 |
-
8: "Academic References Removed",
|
| 801 |
-
9: "Wikilink Added",
|
| 802 |
-
10: "Addition or Modification of Quantitative Information",
|
| 803 |
-
11: "Other Scientifically Relevant Change",
|
| 804 |
-
12: "None of the above",
|
| 805 |
-
}
|
| 806 |
-
label_name = label_name_map.get(payload.label, f"Label {payload.label}")
|
| 807 |
-
|
| 808 |
-
section_note = (
|
| 809 |
-
"across all sections"
|
| 810 |
-
if not payload.section_ids
|
| 811 |
-
else f"across {len(payload.section_ids)} section group(s)"
|
| 812 |
-
)
|
| 813 |
-
|
| 814 |
-
user_prompt = (
|
| 815 |
-
f"You are summarizing Wikipedia revision edits for the article "
|
| 816 |
-
f"'{payload.article}'.\n\n"
|
| 817 |
-
f"Below are explanations from {len(snippets_for_prompt)} edits "
|
| 818 |
-
f"classified as '{label_name}' (label {payload.label}) during "
|
| 819 |
-
f"period {payload.period_key}, {section_note}.\n\n"
|
| 820 |
-
f"Write a concise (4–6 sentences) summary describing what kinds of "
|
| 821 |
-
f"changes happened, common themes, and notable additions or removals. "
|
| 822 |
-
f"Be specific and cite concrete examples where possible. Do not "
|
| 823 |
-
f"enumerate every edit; synthesize patterns.\n\n"
|
| 824 |
-
f"Explanations:\n"
|
| 825 |
-
+ "\n\n".join(f"- {s}" for s in snippets_for_prompt)
|
| 826 |
-
)
|
| 827 |
-
|
| 828 |
-
if truncated:
|
| 829 |
-
user_prompt += (
|
| 830 |
-
f"\n\n(Note: {len(snippets) - MAX_SNIPPETS} additional explanations "
|
| 831 |
-
f"were omitted to fit context.)"
|
| 832 |
-
)
|
| 833 |
-
|
| 834 |
-
try:
|
| 835 |
-
import openai
|
| 836 |
-
client = openai.OpenAI(api_key=payload.api_key)
|
| 837 |
-
completion = client.chat.completions.create(
|
| 838 |
-
model=payload.model or "gpt-4o-mini",
|
| 839 |
-
messages=[
|
| 840 |
-
{
|
| 841 |
-
"role": "system",
|
| 842 |
-
"content": (
|
| 843 |
-
"You are a careful research assistant that summarizes "
|
| 844 |
-
"lists of Wikipedia edit explanations into compact, "
|
| 845 |
-
"high-signal prose."
|
| 846 |
-
),
|
| 847 |
-
},
|
| 848 |
-
{"role": "user", "content": user_prompt},
|
| 849 |
-
],
|
| 850 |
-
)
|
| 851 |
-
summary_text = completion.choices[0].message.content or ""
|
| 852 |
-
except Exception as e:
|
| 853 |
-
raise HTTPException(status_code=502, detail=f"OpenAI request failed: {e}")
|
| 854 |
-
|
| 855 |
-
return {
|
| 856 |
-
"summary": summary_text.strip(),
|
| 857 |
-
"edit_count": match_count_for_response,
|
| 858 |
-
"explanation_count": len(snippets),
|
| 859 |
-
"truncated": truncated,
|
| 860 |
-
"label": payload.label,
|
| 861 |
-
"label_name": label_name,
|
| 862 |
-
"period_key": payload.period_key,
|
| 863 |
-
}
|
| 864 |
-
|
| 865 |
-
|
| 866 |
@app.get("/progress")
|
| 867 |
async def get_progress(article: str) -> Dict[str, Any]:
|
| 868 |
"""
|
|
@@ -882,31 +365,3 @@ async def get_progress(article: str) -> Dict[str, Any]:
|
|
| 882 |
"batch": article_progress.get("batch"),
|
| 883 |
"error": article_progress.get("error"),
|
| 884 |
}
|
| 885 |
-
|
| 886 |
-
# Serve the built React app at "/".
|
| 887 |
-
# This must be registered AFTER all API routes so /prepare, /classify, /progress, /results,
|
| 888 |
-
# /files still take precedence.
|
| 889 |
-
from fastapi.responses import FileResponse # noqa: E402
|
| 890 |
-
|
| 891 |
-
FRONTEND_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "frontend_build")
|
| 892 |
-
|
| 893 |
-
if os.path.isdir(FRONTEND_DIR):
|
| 894 |
-
# CRA's compiled JS/CSS lives under build/static/...
|
| 895 |
-
app.mount(
|
| 896 |
-
"/static",
|
| 897 |
-
StaticFiles(directory=os.path.join(FRONTEND_DIR, "static")),
|
| 898 |
-
name="frontend-assets",
|
| 899 |
-
)
|
| 900 |
-
|
| 901 |
-
@app.get("/")
|
| 902 |
-
async def serve_index() -> FileResponse:
|
| 903 |
-
return FileResponse(os.path.join(FRONTEND_DIR, "index.html"))
|
| 904 |
-
|
| 905 |
-
@app.get("/{full_path:path}")
|
| 906 |
-
async def spa_fallback(full_path: str) -> FileResponse:
|
| 907 |
-
# Try a real file under frontend_build first (favicon.ico, manifest.json, etc.).
|
| 908 |
-
candidate = os.path.join(FRONTEND_DIR, full_path)
|
| 909 |
-
if os.path.isfile(candidate):
|
| 910 |
-
return FileResponse(candidate)
|
| 911 |
-
# Otherwise fall back to index.html (no client-side router today, but harmless).
|
| 912 |
-
return FileResponse(os.path.join(FRONTEND_DIR, "index.html"))
|
|
|
|
| 2 |
import os
|
| 3 |
import threading
|
| 4 |
import time
|
| 5 |
+
from typing import Any, Dict, Optional
|
| 6 |
|
| 7 |
import pandas as pd
|
| 8 |
from fastapi import BackgroundTasks, FastAPI, HTTPException, Request
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
|
| 10 |
from fastapi.staticfiles import StaticFiles
|
| 11 |
from pydantic import BaseModel
|
| 12 |
|
|
|
|
| 28 |
# Initialize FastAPI app
|
| 29 |
app = FastAPI(title="Wiki Revision Classifier API")
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
# Enable CORS
|
| 32 |
app.add_middleware(
|
| 33 |
CORSMiddleware,
|
|
|
|
| 46 |
response.headers["Expires"] = "0"
|
| 47 |
return response
|
| 48 |
|
| 49 |
+
# Prepare static directory
|
|
|
|
| 50 |
os.makedirs("visualizations", exist_ok=True)
|
| 51 |
app.mount(
|
| 52 |
+
"/static",
|
| 53 |
StaticFiles(directory="visualizations"),
|
| 54 |
+
name="static"
|
| 55 |
)
|
| 56 |
|
| 57 |
# Pydantic models
|
|
|
|
| 108 |
|
| 109 |
return {
|
| 110 |
"previewRows": preview,
|
| 111 |
+
"csvUrl": f"/static/{csv_filename}",
|
| 112 |
"diffs": diffs,
|
| 113 |
"revisionCount": revision_count,
|
| 114 |
}
|
|
|
|
| 119 |
Start classification in the background and return immediately.
|
| 120 |
Uses the flexible-linker pipeline + OpenAI Batch API (single batch).
|
| 121 |
"""
|
| 122 |
+
os.environ["OPENAI_API_KEY"] = payload.api_key
|
| 123 |
model_name = payload.model or "gpt-5-mini"
|
| 124 |
|
| 125 |
df, title = fetch_revision_history(payload.article)
|
|
|
|
| 158 |
background_tasks.add_task(
|
| 159 |
process_classification,
|
| 160 |
model_name=model_name,
|
|
|
|
| 161 |
prompts=prompts,
|
| 162 |
df=prompted_df,
|
| 163 |
title=title,
|
|
|
|
| 216 |
return pd.Series(out, index=results_df.index, dtype=int)
|
| 217 |
|
| 218 |
|
| 219 |
+
def process_classification(model_name, prompts, df, title, taxonomy):
|
| 220 |
"""Run the OpenAI batch classification in the background."""
|
| 221 |
safe_title = title.replace(" ", "_")
|
| 222 |
total = len(prompts)
|
|
|
|
| 239 |
with progress_lock:
|
| 240 |
progress_map.setdefault(safe_title, {})["stage"] = "submitting"
|
| 241 |
|
| 242 |
+
model = GPT(model_name=model_name)
|
| 243 |
raw_responses = model.predict(prompts, status_callback=_update_batch_status)
|
| 244 |
|
| 245 |
with progress_lock:
|
|
|
|
| 275 |
result_path = os.path.join("visualizations", result_filename)
|
| 276 |
results_df.to_csv(result_path, index=False)
|
| 277 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 278 |
print("Creating visualizations...")
|
| 279 |
+
taxonomy[11] = {
|
| 280 |
"category": "None of the above",
|
| 281 |
"description": "No meaningful scientific changes were detected between the revisions; applies to non-scientific, formatting, or trivial edits."
|
| 282 |
}
|
|
|
|
| 330 |
|
| 331 |
# Load results
|
| 332 |
results_df = pd.read_csv(result_path)
|
|
|
|
| 333 |
preview_rows = results_df.head(5).to_dict(orient="records")
|
| 334 |
|
| 335 |
# URL paths for frontend
|
| 336 |
+
csv_url = f"/static/{result_filename}"
|
| 337 |
+
distribution_url = f"/static/{dist_filename}"
|
| 338 |
+
cooccurrence_url = f"/static/{cooc_filename}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 339 |
|
| 340 |
return {
|
| 341 |
"status": "complete",
|
|
|
|
| 344 |
"results": results_df.to_dict(orient="records"),
|
| 345 |
"distributionUrl": distribution_url,
|
| 346 |
"cooccurrenceUrl": cooccurrence_url,
|
|
|
|
| 347 |
}
|
| 348 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 349 |
@app.get("/progress")
|
| 350 |
async def get_progress(article: str) -> Dict[str, Any]:
|
| 351 |
"""
|
|
|
|
| 365 |
"batch": article_progress.get("batch"),
|
| 366 |
"error": article_progress.get("error"),
|
| 367 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend_build/asset-manifest.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
{
|
| 2 |
"files": {
|
| 3 |
-
"main.css": "/static/css/main.
|
| 4 |
-
"main.js": "/static/js/main.
|
| 5 |
"static/js/206.b8423ec3.chunk.js": "/static/js/206.b8423ec3.chunk.js",
|
| 6 |
"index.html": "/index.html"
|
| 7 |
},
|
| 8 |
"entrypoints": [
|
| 9 |
-
"static/css/main.
|
| 10 |
-
"static/js/main.
|
| 11 |
]
|
| 12 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"files": {
|
| 3 |
+
"main.css": "/static/css/main.fb799031.css",
|
| 4 |
+
"main.js": "/static/js/main.0f49f162.js",
|
| 5 |
"static/js/206.b8423ec3.chunk.js": "/static/js/206.b8423ec3.chunk.js",
|
| 6 |
"index.html": "/index.html"
|
| 7 |
},
|
| 8 |
"entrypoints": [
|
| 9 |
+
"static/css/main.fb799031.css",
|
| 10 |
+
"static/js/main.0f49f162.js"
|
| 11 |
]
|
| 12 |
}
|
frontend_build/index.html
CHANGED
|
@@ -1 +1 @@
|
|
| 1 |
-
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.
|
|
|
|
| 1 |
+
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.0f49f162.js"></script><link href="/static/css/main.fb799031.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
frontend_build/static/css/{main.78d0a2b0.css → main.fb799031.css}
RENAMED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
*,:after,:before{--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:#3b82f680;--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:#3b82f680;--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: }/*
|
| 2 |
! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com
|
| 3 |
-
*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;font-variation-settings:normal;line-height:1.5;tab-size:4}body{line-height:inherit}hr{border-top-width:1px;color:inherit;height:0}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,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height: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:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-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,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}html{font-feature-settings:"cv11","ss01";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1));color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1));font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;margin:0}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}h1,h2,h3{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.card{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-shadow:0 1px 2px 0 #0f172a0a,0 1px 3px 0 #0f172a0f;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color),0 1px 3px 0 var(--tw-shadow-color);background-color:#fff;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1));border-radius:.75rem;border-width:1px}.btn-primary,.card{box-shadow:0 0 #0000,0 0 #0000,var(--tw-shadow);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.btn-primary{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);align-items:center;background-color:#0891b2;background-color:rgb(8 145 178/var(--tw-bg-opacity,1));border-radius:.5rem;color:#fff;color:rgb(255 255 255/var(--tw-text-opacity,1));display:inline-flex;font-weight:500;gap:.5rem;justify-content:center;padding:.625rem 1.25rem;transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.btn-primary:hover{--tw-bg-opacity:1;background-color:#0e7490;background-color:rgb(14 116 144/var(--tw-bg-opacity,1))}.btn-primary:disabled{--tw-bg-opacity:1;background-color:#94a3b8;background-color:rgb(148 163 184/var(--tw-bg-opacity,1));cursor:not-allowed}.btn-secondary{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:#fff;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1));border-radius:.5rem;border-width:1px;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1));display:inline-flex;font-weight:500;gap:.5rem;justify-content:center;padding:.5rem 1rem;transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.btn-secondary:hover{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.input-base{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;background-color:#fff;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1));border-radius:.5rem;border-width:1px;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1));padding:.5rem .75rem;width:100%}.input-base::placeholder{--tw-placeholder-opacity:1;color:#94a3b8;color:rgb(148 163 184/var(--tw-placeholder-opacity,1))}.input-base:focus{--tw-border-opacity: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(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-color:#0891b233;border-color:#0891b2;border-color:rgb(8 145 178/var(--tw-border-opacity,1));box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),0 0 #0000;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000;outline-offset:2px}.input-base:disabled{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1));color:#475569;color:rgb(71 85 105/var(--tw-text-opacity,1))}.label{display:block;margin-bottom:.25rem}.label,.tab{--tw-text-opacity:1;color:#475569;color:rgb(71 85 105/var(--tw-text-opacity,1));font-size:.875rem;font-weight:500;line-height:1.25rem}.tab{border-bottom-width:2px;border-color:#0000;cursor:pointer;padding:.5rem 1rem;transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.tab:hover{border-color:#94a3b8;border-color:rgb(148 163 184/var(--tw-border-opacity,1));color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.tab-active,.tab:hover{--tw-border-opacity:1;--tw-text-opacity:1}.tab-active{border-color:#0891b2;border-color:rgb(8 145 178/var(--tw-border-opacity,1));color:#0e7490;color:rgb(14 116 144/var(--tw-text-opacity,1))}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.sticky{position:-webkit-sticky;position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.top-0{top:0}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-auto{margin-left:auto;margin-right:auto}.\!mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-5{margin-left:1.25rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-48{max-height:12rem}.max-h-72{max-height:18rem}.min-h-\[22px\]{min-height:22px}.min-h-screen{min-height:100vh}.\!w-auto{width:auto!important}.w-1\/2{width:50%}.w-3{width:.75rem}.w-4{width:1rem}.w-6{width:1.5rem}.w-9{width:2.25rem}.w-full{width:100%}.min-w-\[180px\]{min-width:180px}.min-w-\[78px\]{min-width:78px}.min-w-full{min-width:100%}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-\[260px\]{max-width:260px}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1}.flex-shrink-0,.shrink-0{flex-shrink:0}.border-separate{border-collapse:initial}.border-spacing-0{--tw-border-spacing-x:0px;--tw-border-spacing-y:0px;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.rotate-90{--tw-rotate:90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.list-decimal{list-style-type:decimal}.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-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-x-3{column-gap:.75rem}.gap-x-4{column-gap:1rem}.gap-y-1{row-gap:.25rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.125rem*var(--tw-space-y-reverse));margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.375rem*var(--tw-space-y-reverse));margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.75rem*var(--tw-space-y-reverse));margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(2rem*var(--tw-space-y-reverse));margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-200{--tw-border-opacity:1;border-color:#fde68a;border-color:rgb(253 230 138/var(--tw-border-opacity,1))}.border-amber-400{--tw-border-opacity:1;border-color:#fbbf24;border-color:rgb(251 191 36/var(--tw-border-opacity,1))}.border-brand-200{--tw-border-opacity:1;border-color:#a5f3fc;border-color:rgb(165 243 252/var(--tw-border-opacity,1))}.border-brand-300{--tw-border-opacity:1;border-color:#67e8f9;border-color:rgb(103 232 249/var(--tw-border-opacity,1))}.border-brand-500{--tw-border-opacity:1;border-color:#0891b2;border-color:rgb(8 145 178/var(--tw-border-opacity,1))}.border-emerald-400{--tw-border-opacity:1;border-color:#34d399;border-color:rgb(52 211 153/var(--tw-border-opacity,1))}.border-red-200{--tw-border-opacity:1;border-color:#fecaca;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.border-sky-300{--tw-border-opacity:1;border-color:#7dd3fc;border-color:rgb(125 211 252/var(--tw-border-opacity,1))}.border-slate-200{--tw-border-opacity:1;border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1))}.border-slate-300{--tw-border-opacity:1;border-color:#cbd5e1;border-color:rgb(203 213 225/var(--tw-border-opacity,1))}.border-surface-border{--tw-border-opacity:1;border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1))}.bg-amber-50{--tw-bg-opacity:1;background-color:#fffbeb;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.bg-blue-500{--tw-bg-opacity:1;background-color:#3b82f6;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-600{--tw-bg-opacity:1;background-color:#2563eb;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-brand-50{--tw-bg-opacity:1;background-color:#ecfeff;background-color:rgb(236 254 255/var(--tw-bg-opacity,1))}.bg-brand-50\/40{background-color:#ecfeff66}.bg-brand-500{--tw-bg-opacity:1;background-color:#0891b2;background-color:rgb(8 145 178/var(--tw-bg-opacity,1))}.bg-emerald-100{--tw-bg-opacity:1;background-color:#d1fae5;background-color:rgb(209 250 229/var(--tw-bg-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:#f3f4f6;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:#e5e7eb;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-ink\/30{background-color:#0f172a4d}.bg-red-50{--tw-bg-opacity:1;background-color:#fef2f2;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-sky-50{--tw-bg-opacity:1;background-color:#f0f9ff;background-color:rgb(240 249 255/var(--tw-bg-opacity,1))}.bg-slate-50{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.bg-surface-border{--tw-bg-opacity:1;background-color:#e2e8f0;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.bg-surface-subtle{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.bg-surface-subtle\/60{background-color:#f8fafc99}.bg-transparent{background-color:initial}.bg-white{--tw-bg-opacity:1;background-color:#fff;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.from-brand-500{--tw-gradient-from:#0891b2 var(--tw-gradient-from-position);--tw-gradient-to:#0891b200 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-accent{--tw-gradient-to:#4338ca var(--tw-gradient-to-position)}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.\!px-2{padding-left:.5rem!important;padding-right:.5rem!important}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!important}.\!py-1{padding-bottom:.25rem!important;padding-top:.25rem!important}.\!py-1\.5{padding-bottom:.375rem!important;padding-top:.375rem!important}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.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-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-5{padding-bottom:1.25rem;padding-top:1.25rem}.py-8{padding-bottom:2rem;padding-top:2rem}.pb-1{padding-bottom:.25rem}.pb-1\.5{padding-bottom:.375rem}.pb-2{padding-bottom:.5rem}.pr-1{padding-right:.25rem}.pt-5{padding-top:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.\!text-xs{font-size:.75rem!important;line-height:1rem!important}.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-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.text-amber-700{--tw-text-opacity:1;color:#b45309;color:rgb(180 83 9/var(--tw-text-opacity,1))}.text-amber-900{--tw-text-opacity:1;color:#78350f;color:rgb(120 53 15/var(--tw-text-opacity,1))}.text-black{--tw-text-opacity:1;color:#000;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-blue-600{--tw-text-opacity:1;color:#2563eb;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-brand-500{--tw-text-opacity:1;color:#0891b2;color:rgb(8 145 178/var(--tw-text-opacity,1))}.text-brand-600{--tw-text-opacity:1;color:#0e7490;color:rgb(14 116 144/var(--tw-text-opacity,1))}.text-brand-700{--tw-text-opacity:1;color:#155e75;color:rgb(21 94 117/var(--tw-text-opacity,1))}.text-emerald-900{--tw-text-opacity:1;color:#064e3b;color:rgb(6 78 59/var(--tw-text-opacity,1))}.text-ink{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.text-ink-muted{--tw-text-opacity:1;color:#475569;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-ink-soft{--tw-text-opacity:1;color:#94a3b8;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:#dc2626;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:#b91c1c;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:#991b1b;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-sky-800{--tw-text-opacity:1;color:#075985;color:rgb(7 89 133/var(--tw-text-opacity,1))}.text-slate-300{--tw-text-opacity:1;color:#cbd5e1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:#94a3b8;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:#fff;color:rgb(255 255 255/var(--tw-text-opacity,1))}.underline{-webkit-text-decoration-line:underline;text-decoration-line:underline}.line-through{-webkit-text-decoration-line:line-through;text-decoration-line:line-through}.opacity-70{opacity:.7}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:0 0 #0000,0 0 #0000,var(--tw-shadow);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 #0000;outline-offset:2px}.blur{--tw-blur:blur(8px)}.blur,.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-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-\[width\]{transition-duration:.15s;transition-property:width;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-duration:.15s;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-300{transition-duration:.3s}.file\:mr-3::-webkit-file-upload-button{margin-right:.75rem}.file\:mr-3::file-selector-button{margin-right:.75rem}.file\:cursor-pointer::-webkit-file-upload-button{cursor:pointer}.file\:cursor-pointer::file-selector-button{cursor:pointer}.file\:rounded-md::-webkit-file-upload-button{border-radius:.375rem}.file\:rounded-md::file-selector-button{border-radius:.375rem}.file\:border-0::-webkit-file-upload-button{border-width:0}.file\:border-0::file-selector-button{border-width:0}.file\:bg-brand-50::-webkit-file-upload-button{--tw-bg-opacity:1;background-color:#ecfeff;background-color:rgb(236 254 255/var(--tw-bg-opacity,1))}.file\:bg-brand-50::file-selector-button{--tw-bg-opacity:1;background-color:#ecfeff;background-color:rgb(236 254 255/var(--tw-bg-opacity,1))}.file\:bg-surface-subtle::-webkit-file-upload-button{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.file\:bg-surface-subtle::file-selector-button{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.file\:px-3::-webkit-file-upload-button{padding-left:.75rem;padding-right:.75rem}.file\:px-3::file-selector-button{padding-left:.75rem;padding-right:.75rem}.file\:py-1\.5::-webkit-file-upload-button{padding-bottom:.375rem;padding-top:.375rem}.file\:py-1\.5::file-selector-button{padding-bottom:.375rem;padding-top:.375rem}.file\:text-brand-700::-webkit-file-upload-button{--tw-text-opacity:1;color:#155e75;color:rgb(21 94 117/var(--tw-text-opacity,1))}.file\:text-brand-700::file-selector-button{--tw-text-opacity:1;color:#155e75;color:rgb(21 94 117/var(--tw-text-opacity,1))}.file\:text-ink::-webkit-file-upload-button{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.file\:text-ink::file-selector-button{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.last\:border-0:last-child{border-width:0}.hover\:border-ink-muted:hover{--tw-border-opacity:1;border-color:#475569;border-color:rgb(71 85 105/var(--tw-border-opacity,1))}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:#1d4ed8;background-color:rgb(29 78 216/var(--tw-bg-opacity,1))}.hover\:bg-surface-subtle:hover{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.hover\:bg-surface-subtle\/40:hover{background-color:#f8fafc66}.hover\:text-brand-600:hover{--tw-text-opacity:1;color:#0e7490;color:rgb(14 116 144/var(--tw-text-opacity,1))}.hover\:text-brand-700:hover{--tw-text-opacity:1;color:#155e75;color:rgb(21 94 117/var(--tw-text-opacity,1))}.hover\:text-ink:hover{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.hover\:text-red-900:hover{--tw-text-opacity:1;color:#7f1d1d;color:rgb(127 29 29/var(--tw-text-opacity,1))}.hover\:file\:bg-brand-100::-webkit-file-upload-button:hover{--tw-bg-opacity:1;background-color:#cffafe;background-color:rgb(207 250 254/var(--tw-bg-opacity,1))}.hover\:file\:bg-brand-100::file-selector-button:hover{--tw-bg-opacity:1;background-color:#cffafe;background-color:rgb(207 250 254/var(--tw-bg-opacity,1))}.hover\:file\:bg-surface-border::-webkit-file-upload-button:hover{--tw-bg-opacity:1;background-color:#e2e8f0;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.hover\:file\:bg-surface-border::file-selector-button:hover{--tw-bg-opacity:1;background-color:#e2e8f0;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.focus\:ring-2:focus{--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),0 0 #0000;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}.focus\:ring-brand-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(8 145 178/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:w-\[420px\]{width:420px}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:text-xl{font-size:1.25rem;line-height:1.75rem}}@media (min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
|
|
|
| 1 |
*,:after,:before{--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:#3b82f680;--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:#3b82f680;--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: }/*
|
| 2 |
! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com
|
| 3 |
+
*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;font-variation-settings:normal;line-height:1.5;tab-size:4}body{line-height:inherit}hr{border-top-width:1px;color:inherit;height:0}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,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height: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:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-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,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]:where(:not([hidden=until-found])){display:none}html{font-feature-settings:"cv11","ss01";-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1));color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1));font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,sans-serif;margin:0}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}h1,h2,h3{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.card{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-shadow:0 1px 2px 0 #0f172a0a,0 1px 3px 0 #0f172a0f;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color),0 1px 3px 0 var(--tw-shadow-color);background-color:#fff;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1));border-radius:.75rem;border-width:1px}.btn-primary,.card{box-shadow:0 0 #0000,0 0 #0000,var(--tw-shadow);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.btn-primary{--tw-bg-opacity:1;--tw-text-opacity:1;--tw-shadow:0 1px 2px 0 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);align-items:center;background-color:#0891b2;background-color:rgb(8 145 178/var(--tw-bg-opacity,1));border-radius:.5rem;color:#fff;color:rgb(255 255 255/var(--tw-text-opacity,1));display:inline-flex;font-weight:500;gap:.5rem;justify-content:center;padding:.625rem 1.25rem;transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.btn-primary:hover{--tw-bg-opacity:1;background-color:#0e7490;background-color:rgb(14 116 144/var(--tw-bg-opacity,1))}.btn-primary:disabled{--tw-bg-opacity:1;background-color:#94a3b8;background-color:rgb(148 163 184/var(--tw-bg-opacity,1));cursor:not-allowed}.btn-secondary{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;align-items:center;background-color:#fff;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1));border-radius:.5rem;border-width:1px;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1));display:inline-flex;font-weight:500;gap:.5rem;justify-content:center;padding:.5rem 1rem;transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.btn-secondary:hover{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.input-base{--tw-border-opacity:1;--tw-bg-opacity:1;--tw-text-opacity:1;background-color:#fff;background-color:rgb(255 255 255/var(--tw-bg-opacity,1));border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1));border-radius:.5rem;border-width:1px;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1));padding:.5rem .75rem;width:100%}.input-base::placeholder{--tw-placeholder-opacity:1;color:#94a3b8;color:rgb(148 163 184/var(--tw-placeholder-opacity,1))}.input-base:focus{--tw-border-opacity: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(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);--tw-ring-color:#0891b233;border-color:#0891b2;border-color:rgb(8 145 178/var(--tw-border-opacity,1));box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),0 0 #0000;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000);outline:2px solid #0000;outline-offset:2px}.input-base:disabled{--tw-bg-opacity:1;--tw-text-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1));color:#475569;color:rgb(71 85 105/var(--tw-text-opacity,1))}.label{display:block;margin-bottom:.25rem}.label,.tab{--tw-text-opacity:1;color:#475569;color:rgb(71 85 105/var(--tw-text-opacity,1));font-size:.875rem;font-weight:500;line-height:1.25rem}.tab{border-bottom-width:2px;border-color:#0000;cursor:pointer;padding:.5rem 1rem;transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.tab:hover{border-color:#94a3b8;border-color:rgb(148 163 184/var(--tw-border-opacity,1));color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.tab-active,.tab:hover{--tw-border-opacity:1;--tw-text-opacity:1}.tab-active{border-color:#0891b2;border-color:rgb(8 145 178/var(--tw-border-opacity,1));color:#0e7490;color:rgb(14 116 144/var(--tw-text-opacity,1))}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:-webkit-sticky;position:sticky}.inset-0{inset:0}.bottom-0{bottom:0}.left-0{left:0}.right-0{right:0}.top-0{top:0}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.mx-1{margin-left:.25rem;margin-right:.25rem}.mx-auto{margin-left:auto;margin-right:auto}.\!mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-5{margin-left:1.25rem}.ml-auto{margin-left:auto}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-6{margin-top:1.5rem}.line-clamp-5{-webkit-box-orient:vertical;-webkit-line-clamp:5;display:-webkit-box;overflow:hidden}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-4{height:1rem}.h-6{height:1.5rem}.h-9{height:2.25rem}.h-full{height:100%}.max-h-48{max-height:12rem}.max-h-72{max-height:18rem}.min-h-\[22px\]{min-height:22px}.min-h-screen{min-height:100vh}.\!w-auto{width:auto!important}.w-1\/2{width:50%}.w-2{width:.5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-6{width:1.5rem}.w-9{width:2.25rem}.w-full{width:100%}.min-w-\[180px\]{min-width:180px}.min-w-\[78px\]{min-width:78px}.min-w-full{min-width:100%}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-\[200px\]{max-width:200px}.max-w-\[260px\]{max-width:260px}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1}.flex-shrink-0,.shrink-0{flex-shrink:0}.border-separate{border-collapse:initial}.border-spacing-0{--tw-border-spacing-x:0px;--tw-border-spacing-y:0px;border-spacing:var(--tw-border-spacing-x) var(--tw-border-spacing-y)}.rotate-90{--tw-rotate:90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.list-decimal{list-style-type:decimal}.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-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-x-3{column-gap:.75rem}.gap-x-4{column-gap:1rem}.gap-y-1{row-gap:.25rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.125rem*var(--tw-space-y-reverse));margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.375rem*var(--tw-space-y-reverse));margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.75rem*var(--tw-space-y-reverse));margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(2rem*var(--tw-space-y-reverse));margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-200{--tw-border-opacity:1;border-color:#fde68a;border-color:rgb(253 230 138/var(--tw-border-opacity,1))}.border-amber-400{--tw-border-opacity:1;border-color:#fbbf24;border-color:rgb(251 191 36/var(--tw-border-opacity,1))}.border-brand-200{--tw-border-opacity:1;border-color:#a5f3fc;border-color:rgb(165 243 252/var(--tw-border-opacity,1))}.border-brand-300{--tw-border-opacity:1;border-color:#67e8f9;border-color:rgb(103 232 249/var(--tw-border-opacity,1))}.border-brand-500{--tw-border-opacity:1;border-color:#0891b2;border-color:rgb(8 145 178/var(--tw-border-opacity,1))}.border-emerald-400{--tw-border-opacity:1;border-color:#34d399;border-color:rgb(52 211 153/var(--tw-border-opacity,1))}.border-red-200{--tw-border-opacity:1;border-color:#fecaca;border-color:rgb(254 202 202/var(--tw-border-opacity,1))}.border-sky-300{--tw-border-opacity:1;border-color:#7dd3fc;border-color:rgb(125 211 252/var(--tw-border-opacity,1))}.border-slate-200{--tw-border-opacity:1;border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1))}.border-slate-300{--tw-border-opacity:1;border-color:#cbd5e1;border-color:rgb(203 213 225/var(--tw-border-opacity,1))}.border-surface-border{--tw-border-opacity:1;border-color:#e2e8f0;border-color:rgb(226 232 240/var(--tw-border-opacity,1))}.bg-amber-50{--tw-bg-opacity:1;background-color:#fffbeb;background-color:rgb(255 251 235/var(--tw-bg-opacity,1))}.bg-blue-500{--tw-bg-opacity:1;background-color:#3b82f6;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-blue-600{--tw-bg-opacity:1;background-color:#2563eb;background-color:rgb(37 99 235/var(--tw-bg-opacity,1))}.bg-brand-50{--tw-bg-opacity:1;background-color:#ecfeff;background-color:rgb(236 254 255/var(--tw-bg-opacity,1))}.bg-brand-50\/40{background-color:#ecfeff66}.bg-brand-500{--tw-bg-opacity:1;background-color:#0891b2;background-color:rgb(8 145 178/var(--tw-bg-opacity,1))}.bg-emerald-100{--tw-bg-opacity:1;background-color:#d1fae5;background-color:rgb(209 250 229/var(--tw-bg-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:#f3f4f6;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:#e5e7eb;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-ink\/30{background-color:#0f172a4d}.bg-red-50{--tw-bg-opacity:1;background-color:#fef2f2;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-sky-50{--tw-bg-opacity:1;background-color:#f0f9ff;background-color:rgb(240 249 255/var(--tw-bg-opacity,1))}.bg-slate-50{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.bg-surface-border{--tw-bg-opacity:1;background-color:#e2e8f0;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.bg-surface-subtle{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.bg-surface-subtle\/60{background-color:#f8fafc99}.bg-transparent{background-color:initial}.bg-white{--tw-bg-opacity:1;background-color:#fff;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.from-brand-500{--tw-gradient-from:#0891b2 var(--tw-gradient-from-position);--tw-gradient-to:#0891b200 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-accent{--tw-gradient-to:#4338ca var(--tw-gradient-to-position)}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.\!px-2{padding-left:.5rem!important;padding-right:.5rem!important}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!important}.\!py-1{padding-bottom:.25rem!important;padding-top:.25rem!important}.\!py-1\.5{padding-bottom:.375rem!important;padding-top:.375rem!important}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.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-0\.5{padding-bottom:.125rem;padding-top:.125rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-5{padding-bottom:1.25rem;padding-top:1.25rem}.py-8{padding-bottom:2rem;padding-top:2rem}.pb-1{padding-bottom:.25rem}.pb-1\.5{padding-bottom:.375rem}.pb-2{padding-bottom:.5rem}.pr-1{padding-right:.25rem}.pt-5{padding-top:1.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.\!text-sm{font-size:.875rem!important;line-height:1.25rem!important}.\!text-xs{font-size:.75rem!important;line-height:1rem!important}.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-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.leading-snug{line-height:1.375}.tracking-wide{letter-spacing:.025em}.text-amber-700{--tw-text-opacity:1;color:#b45309;color:rgb(180 83 9/var(--tw-text-opacity,1))}.text-amber-900{--tw-text-opacity:1;color:#78350f;color:rgb(120 53 15/var(--tw-text-opacity,1))}.text-black{--tw-text-opacity:1;color:#000;color:rgb(0 0 0/var(--tw-text-opacity,1))}.text-blue-600{--tw-text-opacity:1;color:#2563eb;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-brand-500{--tw-text-opacity:1;color:#0891b2;color:rgb(8 145 178/var(--tw-text-opacity,1))}.text-brand-600{--tw-text-opacity:1;color:#0e7490;color:rgb(14 116 144/var(--tw-text-opacity,1))}.text-brand-700{--tw-text-opacity:1;color:#155e75;color:rgb(21 94 117/var(--tw-text-opacity,1))}.text-emerald-900{--tw-text-opacity:1;color:#064e3b;color:rgb(6 78 59/var(--tw-text-opacity,1))}.text-ink{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.text-ink-muted{--tw-text-opacity:1;color:#475569;color:rgb(71 85 105/var(--tw-text-opacity,1))}.text-ink-soft{--tw-text-opacity:1;color:#94a3b8;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-red-600{--tw-text-opacity:1;color:#dc2626;color:rgb(220 38 38/var(--tw-text-opacity,1))}.text-red-700{--tw-text-opacity:1;color:#b91c1c;color:rgb(185 28 28/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:#991b1b;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-sky-800{--tw-text-opacity:1;color:#075985;color:rgb(7 89 133/var(--tw-text-opacity,1))}.text-slate-300{--tw-text-opacity:1;color:#cbd5e1;color:rgb(203 213 225/var(--tw-text-opacity,1))}.text-slate-400{--tw-text-opacity:1;color:#94a3b8;color:rgb(148 163 184/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:#fff;color:rgb(255 255 255/var(--tw-text-opacity,1))}.underline{-webkit-text-decoration-line:underline;text-decoration-line:underline}.line-through{-webkit-text-decoration-line:line-through;text-decoration-line:line-through}.opacity-70{opacity:.7}.shadow-xl{--tw-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a;--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:0 0 #0000,0 0 #0000,var(--tw-shadow);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 #0000;outline-offset:2px}.blur{--tw-blur:blur(8px)}.blur,.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-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-text-decoration-color,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-\[width\]{transition-duration:.15s;transition-property:width;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,fill,stroke,-webkit-text-decoration-color;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,-webkit-text-decoration-color;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-duration:.15s;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-300{transition-duration:.3s}.file\:mr-3::-webkit-file-upload-button{margin-right:.75rem}.file\:mr-3::file-selector-button{margin-right:.75rem}.file\:cursor-pointer::-webkit-file-upload-button{cursor:pointer}.file\:cursor-pointer::file-selector-button{cursor:pointer}.file\:rounded-md::-webkit-file-upload-button{border-radius:.375rem}.file\:rounded-md::file-selector-button{border-radius:.375rem}.file\:border-0::-webkit-file-upload-button{border-width:0}.file\:border-0::file-selector-button{border-width:0}.file\:bg-brand-50::-webkit-file-upload-button{--tw-bg-opacity:1;background-color:#ecfeff;background-color:rgb(236 254 255/var(--tw-bg-opacity,1))}.file\:bg-brand-50::file-selector-button{--tw-bg-opacity:1;background-color:#ecfeff;background-color:rgb(236 254 255/var(--tw-bg-opacity,1))}.file\:bg-surface-subtle::-webkit-file-upload-button{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.file\:bg-surface-subtle::file-selector-button{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.file\:px-3::-webkit-file-upload-button{padding-left:.75rem;padding-right:.75rem}.file\:px-3::file-selector-button{padding-left:.75rem;padding-right:.75rem}.file\:py-1\.5::-webkit-file-upload-button{padding-bottom:.375rem;padding-top:.375rem}.file\:py-1\.5::file-selector-button{padding-bottom:.375rem;padding-top:.375rem}.file\:text-brand-700::-webkit-file-upload-button{--tw-text-opacity:1;color:#155e75;color:rgb(21 94 117/var(--tw-text-opacity,1))}.file\:text-brand-700::file-selector-button{--tw-text-opacity:1;color:#155e75;color:rgb(21 94 117/var(--tw-text-opacity,1))}.file\:text-ink::-webkit-file-upload-button{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.file\:text-ink::file-selector-button{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.last\:border-0:last-child{border-width:0}.hover\:border-ink-muted:hover{--tw-border-opacity:1;border-color:#475569;border-color:rgb(71 85 105/var(--tw-border-opacity,1))}.hover\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:#1d4ed8;background-color:rgb(29 78 216/var(--tw-bg-opacity,1))}.hover\:bg-surface-subtle:hover{--tw-bg-opacity:1;background-color:#f8fafc;background-color:rgb(248 250 252/var(--tw-bg-opacity,1))}.hover\:bg-surface-subtle\/40:hover{background-color:#f8fafc66}.hover\:text-brand-600:hover{--tw-text-opacity:1;color:#0e7490;color:rgb(14 116 144/var(--tw-text-opacity,1))}.hover\:text-brand-700:hover{--tw-text-opacity:1;color:#155e75;color:rgb(21 94 117/var(--tw-text-opacity,1))}.hover\:text-ink:hover{--tw-text-opacity:1;color:#0f172a;color:rgb(15 23 42/var(--tw-text-opacity,1))}.hover\:text-red-900:hover{--tw-text-opacity:1;color:#7f1d1d;color:rgb(127 29 29/var(--tw-text-opacity,1))}.hover\:file\:bg-brand-100::-webkit-file-upload-button:hover{--tw-bg-opacity:1;background-color:#cffafe;background-color:rgb(207 250 254/var(--tw-bg-opacity,1))}.hover\:file\:bg-brand-100::file-selector-button:hover{--tw-bg-opacity:1;background-color:#cffafe;background-color:rgb(207 250 254/var(--tw-bg-opacity,1))}.hover\:file\:bg-surface-border::-webkit-file-upload-button:hover{--tw-bg-opacity:1;background-color:#e2e8f0;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.hover\:file\:bg-surface-border::file-selector-button:hover{--tw-bg-opacity:1;background-color:#e2e8f0;background-color:rgb(226 232 240/var(--tw-bg-opacity,1))}.focus\:ring-2:focus{--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),0 0 #0000;box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-blue-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(59 130 246/var(--tw-ring-opacity,1))}.focus\:ring-brand-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(8 145 178/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:w-\[420px\]{width:420px}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:text-xl{font-size:1.25rem;line-height:1.75rem}}@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))}}
|
frontend_build/static/js/{main.ec549950.js → main.0f49f162.js}
RENAMED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend_build/static/js/{main.ec549950.js.LICENSE.txt → main.0f49f162.js.LICENSE.txt}
RENAMED
|
File without changes
|