ly0303521

添加IP过滤策略

@@ -16,6 +16,16 @@ from pydantic import BaseModel, Field, ConfigDict @@ -16,6 +16,16 @@ from pydantic import BaseModel, Field, ConfigDict
16 import logging 16 import logging
17 from PIL import Image 17 from PIL import Image
18 import io 18 import io
  19 +import sys
  20 +
  21 +# Add parent directory to path to import middleware
  22 +sys.path.append(str(Path(__file__).parent.parent))
  23 +try:
  24 + from middleware import IPFilterMiddleware
  25 +except ImportError:
  26 + # Fallback/Dummy if not found (should be found)
  27 + IPFilterMiddleware = None
  28 +
19 29
20 # --- Constants --- 30 # --- Constants ---
21 logger = logging.getLogger("uvicorn.error") 31 logger = logging.getLogger("uvicorn.error")
@@ -313,6 +323,11 @@ usage_store = UsageStore(USAGE_PATH) @@ -313,6 +323,11 @@ usage_store = UsageStore(USAGE_PATH)
313 323
314 # --- App Setup --- 324 # --- App Setup ---
315 app = FastAPI(title="Z-Image Proxy", version="1.0.0") 325 app = FastAPI(title="Z-Image Proxy", version="1.0.0")
  326 +
  327 +# IP Filter Middleware (Add BEFORE CORS to block early)
  328 +if IPFilterMiddleware:
  329 + app.add_middleware(IPFilterMiddleware)
  330 +
316 from fastapi.staticfiles import StaticFiles 331 from fastapi.staticfiles import StaticFiles
317 # Mount public directory to serve thumbnails and frontend config 332 # Mount public directory to serve thumbnails and frontend config
318 app.mount("/thumbnails", StaticFiles(directory=str(Path(__file__).parent.parent / "public" / "thumbnails")), name="thumbnails") 333 app.mount("/thumbnails", StaticFiles(directory=str(Path(__file__).parent.parent / "public" / "thumbnails")), name="thumbnails")
@@ -15,13 +15,15 @@ module.exports = { @@ -15,13 +15,15 @@ module.exports = {
15 }, 15 },
16 { 16 {
17 name: process.env.FRONTEND_NAME || "z-image-frontend", 17 name: process.env.FRONTEND_NAME || "z-image-frontend",
18 - script: "serve", 18 + script: "./venv/bin/python",
  19 + args: "frontend_server.py",
  20 + cwd: __dirname,
  21 + interpreter: "none",
  22 + autorestart: true,
  23 + watch: false,
19 env: { 24 env: {
20 - // Use LOCAL_FRONTEND_PORT from environment, default to 7001  
21 - PM2_SERVE_PATH: path.join(__dirname, 'z-image-generator/dist'),  
22 - PM2_SERVE_PORT: process.env.LOCAL_FRONTEND_PORT || 7001,  
23 - PM2_SERVE_SPA: 'true',  
24 - PM2_SERVE_HOMEPAGE: '/index.html' 25 + LOCAL_FRONTEND_PORT: process.env.LOCAL_FRONTEND_PORT || 7001,
  26 + ENABLE_IP_FILTER: process.env.ENABLE_IP_FILTER
25 } 27 }
26 } 28 }
27 ] 29 ]
  1 +import os
  2 +from fastapi import FastAPI
  3 +from fastapi.staticfiles import StaticFiles
  4 +from fastapi.responses import FileResponse
  5 +from middleware import IPFilterMiddleware
  6 +
  7 +app = FastAPI()
  8 +
  9 +# Add IP Filter Middleware
  10 +app.add_middleware(IPFilterMiddleware)
  11 +
  12 +# Frontend Dist Path
  13 +DIST_DIR = os.path.join(os.path.dirname(__file__), "z-image-generator", "dist")
  14 +
  15 +# Ensure dist exists
  16 +if not os.path.exists(DIST_DIR):
  17 + os.makedirs(DIST_DIR, exist_ok=True)
  18 + # Create a dummy index.html if not exists for testing
  19 + with open(os.path.join(DIST_DIR, "index.html"), "w") as f:
  20 + f.write("<h1>Frontend Build Not Found</h1>")
  21 +
  22 +# Serve Static Files
  23 +# We mount root to serve static files, BUT we need SPA fallback.
  24 +# Solution: Mount specific assets folder, and use a catch-all for index.html
  25 +
  26 +# 1. Mount /assets if it exists in dist
  27 +assets_path = os.path.join(DIST_DIR, "assets")
  28 +if os.path.exists(assets_path):
  29 + app.mount("/assets", StaticFiles(directory=assets_path), name="assets")
  30 +
  31 +# 2. Serve other static files (like favicon, etc) if needed?
  32 +# For simplicity, we just use a catch-all route to serve index.html or the file if it exists
  33 +@app.get("/{full_path:path}")
  34 +async def serve_spa(full_path: str):
  35 + # Check if file exists in dist
  36 + file_path = os.path.join(DIST_DIR, full_path)
  37 + if os.path.exists(file_path) and os.path.isfile(file_path):
  38 + return FileResponse(file_path)
  39 +
  40 + # Fallback to index.html for SPA
  41 + # Disable cache for index.html to ensure updates are seen immediately
  42 + response = FileResponse(os.path.join(DIST_DIR, "index.html"))
  43 + response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
  44 + response.headers["Pragma"] = "no-cache"
  45 + response.headers["Expires"] = "0"
  46 + return response
  47 +
  48 +if __name__ == "__main__":
  49 + import uvicorn
  50 + # Read port from env or default
  51 + port = int(os.getenv("LOCAL_FRONTEND_PORT", "7001"))
  52 + uvicorn.run(app, host="0.0.0.0", port=port)
  1 +import os
  2 +import time
  3 +import logging
  4 +from logging.handlers import RotatingFileHandler
  5 +from datetime import datetime
  6 +from typing import Optional, Tuple
  7 +from fastapi import Request, HTTPException, Response
  8 +from starlette.middleware.base import BaseHTTPMiddleware
  9 +from starlette.responses import JSONResponse, PlainTextResponse
  10 +import httpx
  11 +
  12 +# Configuration
  13 +ENABLE_IP_FILTER = os.getenv("ENABLE_IP_FILTER", "false").lower() == "true"
  14 +LOG_FILE = os.path.join(os.path.expanduser("~"), ".pm2", "logs", "access_monitor.log")
  15 +
  16 +# Setup Logger
  17 +logger = logging.getLogger("access_monitor")
  18 +logger.setLevel(logging.INFO)
  19 +formatter = logging.Formatter('%(message)s')
  20 +
  21 +# Use RotatingFileHandler instead of FileHandler
  22 +# maxBytes=10MB, backupCount=10 (Max storage ~100MB)
  23 +file_handler = RotatingFileHandler(LOG_FILE, maxBytes=10*1024*1024, backupCount=10, encoding='utf-8')
  24 +file_handler.setFormatter(formatter)
  25 +logger.addHandler(file_handler)
  26 +
  27 +# IP Cache: {ip: {"is_cn": bool, "location": str, "timestamp": float}}
  28 +IP_CACHE = {}
  29 +
  30 +async def get_ip_info(ip: str) -> Tuple[bool, str]:
  31 + """
  32 + Check if IP is from China and get location.
  33 + Returns (is_cn, location_string)
  34 + """
  35 + # Localhost / Private IP checks
  36 + if ip == "127.0.0.1" or ip == "::1" or ip.startswith("192.168.") or ip.startswith("10."):
  37 + return True, "Local Network"
  38 +
  39 + # Check Cache
  40 + if ip in IP_CACHE:
  41 + # Cache for 24 hours
  42 + if time.time() - IP_CACHE[ip]["timestamp"] < 86400:
  43 + return IP_CACHE[ip]["is_cn"], IP_CACHE[ip]["location"]
  44 +
  45 + # Query API
  46 + # Using ip-api.com (free, non-commercial use, 45 req/min)
  47 + # Lang=zh-CN for Chinese output
  48 + url = f"http://ip-api.com/json/{ip}?lang=zh-CN&fields=status,countryCode,country,regionName,city"
  49 +
  50 + try:
  51 + async with httpx.AsyncClient(timeout=3.0) as client:
  52 + resp = await client.get(url)
  53 + data = resp.json()
  54 +
  55 + if data.get("status") == "success":
  56 + is_cn = data.get("countryCode") == "CN"
  57 + location = f"{data.get('country')}-{data.get('regionName')}-{data.get('city')}"
  58 +
  59 + # Update Cache
  60 + IP_CACHE[ip] = {
  61 + "is_cn": is_cn,
  62 + "location": location,
  63 + "timestamp": time.time()
  64 + }
  65 + return is_cn, location
  66 + else:
  67 + # API failed or private IP
  68 + return True, "Unknown/Private"
  69 +
  70 + except Exception as e:
  71 + print(f"IP Lookup failed for {ip}: {e}")
  72 + # Fail open (allow access) if API fails
  73 + return True, "Lookup Failed"
  74 +
  75 +class IPFilterMiddleware(BaseHTTPMiddleware):
  76 + async def dispatch(self, request: Request, call_next):
  77 + start_time = time.time()
  78 +
  79 + # Get Client IP
  80 + # X-Forwarded-For is preferred if behind proxy (like Nginx), but here we might be direct or behind pm2
  81 + forwarded = request.headers.get("X-Forwarded-For")
  82 + if forwarded:
  83 + client_ip = forwarded.split(",")[0]
  84 + else:
  85 + client_ip = request.client.host if request.client else "unknown"
  86 +
  87 + # Determine Port (Local port server is listening on)
  88 + server_port = request.scope.get("server", ("", "unknown"))[1]
  89 +
  90 + # IP Lookup
  91 + is_allowed = True
  92 + location = "Unknown"
  93 +
  94 + # Optimize: Skip static assets logs to reduce noise?
  95 + # Requirement says "Add record access IP log", implies all or relevant ones.
  96 + # We will log everything for now or filter ext.
  97 +
  98 + is_cn, location = await get_ip_info(client_ip)
  99 +
  100 + if ENABLE_IP_FILTER and not is_cn:
  101 + is_allowed = False
  102 +
  103 + # Explicit check for HK/TW/MO if they somehow pass as CN (though API shouldn't do that)
  104 + # or if user wants double security.
  105 + # Based on ip-api.com, 'HK', 'TW', 'MO' are country codes, not 'CN'.
  106 + # So 'is_cn' check above is sufficient.
  107 +
  108 + # DEBUG: Log if we are blocking to ensure config works
  109 + if not is_allowed:
  110 + print(f"[BLOCK] Blocked {client_ip} ({location})")
  111 +
  112 + # Access Time
  113 + access_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  114 +
  115 + # Log Format: IP | Location | Time | Port | Path | Allowed
  116 + log_msg = f"{client_ip} | {location} | {access_time} | {server_port} | {request.url.path} | {'ALLOWED' if is_allowed else 'BLOCKED'}"
  117 + logger.info(log_msg)
  118 +
  119 + if not is_allowed:
  120 + return PlainTextResponse("Access Denied: Mainland China IP Required", status_code=403)
  121 +
  122 + response = await call_next(request)
  123 + return response
@@ -10,6 +10,9 @@ else @@ -10,6 +10,9 @@ else
10 exit 1 10 exit 1
11 fi 11 fi
12 12
  13 +# IP Filter Switch (Default to true for security)
  14 +export ENABLE_IP_FILTER="${ENABLE_IP_FILTER:-true}"
  15 +
13 # Bypass proxy for local connections 16 # Bypass proxy for local connections
14 export no_proxy="localhost,127.0.0.1,0.0.0.0,::1" 17 export no_proxy="localhost,127.0.0.1,0.0.0.0,::1"
15 export NO_PROXY="localhost,127.0.0.1,0.0.0.0,::1" 18 export NO_PROXY="localhost,127.0.0.1,0.0.0.0,::1"
1 -@tailwind base;  
2 -@tailwind components;  
3 -@tailwind utilities; 1 +@import "tailwindcss";
4 2
5 /* Hide scrollbar for Chrome, Safari and Opera */ 3 /* Hide scrollbar for Chrome, Safari and Opera */
6 .no-scrollbar::-webkit-scrollbar { 4 .no-scrollbar::-webkit-scrollbar {
@@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
13 "react-dom": "^19.2.3" 13 "react-dom": "^19.2.3"
14 }, 14 },
15 "devDependencies": { 15 "devDependencies": {
  16 + "@tailwindcss/postcss": "^4.1.18",
16 "@types/node": "^22.14.0", 17 "@types/node": "^22.14.0",
17 "@vitejs/plugin-react": "^5.0.0", 18 "@vitejs/plugin-react": "^5.0.0",
18 "autoprefixer": "^10.4.24", 19 "autoprefixer": "^10.4.24",
@@ -22,6 +23,19 @@ @@ -22,6 +23,19 @@
22 "vite": "^6.2.0" 23 "vite": "^6.2.0"
23 } 24 }
24 }, 25 },
  26 + "node_modules/@alloc/quick-lru": {
  27 + "version": "5.2.0",
  28 + "resolved": "https://repo.huaweicloud.com/repository/npm/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
  29 + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
  30 + "dev": true,
  31 + "license": "MIT",
  32 + "engines": {
  33 + "node": ">=10"
  34 + },
  35 + "funding": {
  36 + "url": "https://github.com/sponsors/sindresorhus"
  37 + }
  38 + },
25 "node_modules/@babel/code-frame": { 39 "node_modules/@babel/code-frame": {
26 "version": "7.27.1", 40 "version": "7.27.1",
27 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", 41 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -1111,6 +1125,277 @@ @@ -1111,6 +1125,277 @@
1111 "win32" 1125 "win32"
1112 ] 1126 ]
1113 }, 1127 },
  1128 + "node_modules/@tailwindcss/node": {
  1129 + "version": "4.1.18",
  1130 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/node/-/node-4.1.18.tgz",
  1131 + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
  1132 + "dev": true,
  1133 + "license": "MIT",
  1134 + "dependencies": {
  1135 + "@jridgewell/remapping": "^2.3.4",
  1136 + "enhanced-resolve": "^5.18.3",
  1137 + "jiti": "^2.6.1",
  1138 + "lightningcss": "1.30.2",
  1139 + "magic-string": "^0.30.21",
  1140 + "source-map-js": "^1.2.1",
  1141 + "tailwindcss": "4.1.18"
  1142 + }
  1143 + },
  1144 + "node_modules/@tailwindcss/oxide": {
  1145 + "version": "4.1.18",
  1146 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
  1147 + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
  1148 + "dev": true,
  1149 + "license": "MIT",
  1150 + "engines": {
  1151 + "node": ">= 10"
  1152 + },
  1153 + "optionalDependencies": {
  1154 + "@tailwindcss/oxide-android-arm64": "4.1.18",
  1155 + "@tailwindcss/oxide-darwin-arm64": "4.1.18",
  1156 + "@tailwindcss/oxide-darwin-x64": "4.1.18",
  1157 + "@tailwindcss/oxide-freebsd-x64": "4.1.18",
  1158 + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
  1159 + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
  1160 + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
  1161 + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
  1162 + "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
  1163 + "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
  1164 + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
  1165 + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
  1166 + }
  1167 + },
  1168 + "node_modules/@tailwindcss/oxide-android-arm64": {
  1169 + "version": "4.1.18",
  1170 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
  1171 + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
  1172 + "cpu": [
  1173 + "arm64"
  1174 + ],
  1175 + "dev": true,
  1176 + "license": "MIT",
  1177 + "optional": true,
  1178 + "os": [
  1179 + "android"
  1180 + ],
  1181 + "engines": {
  1182 + "node": ">= 10"
  1183 + }
  1184 + },
  1185 + "node_modules/@tailwindcss/oxide-darwin-arm64": {
  1186 + "version": "4.1.18",
  1187 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
  1188 + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
  1189 + "cpu": [
  1190 + "arm64"
  1191 + ],
  1192 + "dev": true,
  1193 + "license": "MIT",
  1194 + "optional": true,
  1195 + "os": [
  1196 + "darwin"
  1197 + ],
  1198 + "engines": {
  1199 + "node": ">= 10"
  1200 + }
  1201 + },
  1202 + "node_modules/@tailwindcss/oxide-darwin-x64": {
  1203 + "version": "4.1.18",
  1204 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
  1205 + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
  1206 + "cpu": [
  1207 + "x64"
  1208 + ],
  1209 + "dev": true,
  1210 + "license": "MIT",
  1211 + "optional": true,
  1212 + "os": [
  1213 + "darwin"
  1214 + ],
  1215 + "engines": {
  1216 + "node": ">= 10"
  1217 + }
  1218 + },
  1219 + "node_modules/@tailwindcss/oxide-freebsd-x64": {
  1220 + "version": "4.1.18",
  1221 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
  1222 + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
  1223 + "cpu": [
  1224 + "x64"
  1225 + ],
  1226 + "dev": true,
  1227 + "license": "MIT",
  1228 + "optional": true,
  1229 + "os": [
  1230 + "freebsd"
  1231 + ],
  1232 + "engines": {
  1233 + "node": ">= 10"
  1234 + }
  1235 + },
  1236 + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
  1237 + "version": "4.1.18",
  1238 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
  1239 + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
  1240 + "cpu": [
  1241 + "arm"
  1242 + ],
  1243 + "dev": true,
  1244 + "license": "MIT",
  1245 + "optional": true,
  1246 + "os": [
  1247 + "linux"
  1248 + ],
  1249 + "engines": {
  1250 + "node": ">= 10"
  1251 + }
  1252 + },
  1253 + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
  1254 + "version": "4.1.18",
  1255 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
  1256 + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
  1257 + "cpu": [
  1258 + "arm64"
  1259 + ],
  1260 + "dev": true,
  1261 + "license": "MIT",
  1262 + "optional": true,
  1263 + "os": [
  1264 + "linux"
  1265 + ],
  1266 + "engines": {
  1267 + "node": ">= 10"
  1268 + }
  1269 + },
  1270 + "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
  1271 + "version": "4.1.18",
  1272 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
  1273 + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
  1274 + "cpu": [
  1275 + "arm64"
  1276 + ],
  1277 + "dev": true,
  1278 + "license": "MIT",
  1279 + "optional": true,
  1280 + "os": [
  1281 + "linux"
  1282 + ],
  1283 + "engines": {
  1284 + "node": ">= 10"
  1285 + }
  1286 + },
  1287 + "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
  1288 + "version": "4.1.18",
  1289 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
  1290 + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
  1291 + "cpu": [
  1292 + "x64"
  1293 + ],
  1294 + "dev": true,
  1295 + "license": "MIT",
  1296 + "optional": true,
  1297 + "os": [
  1298 + "linux"
  1299 + ],
  1300 + "engines": {
  1301 + "node": ">= 10"
  1302 + }
  1303 + },
  1304 + "node_modules/@tailwindcss/oxide-linux-x64-musl": {
  1305 + "version": "4.1.18",
  1306 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
  1307 + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
  1308 + "cpu": [
  1309 + "x64"
  1310 + ],
  1311 + "dev": true,
  1312 + "license": "MIT",
  1313 + "optional": true,
  1314 + "os": [
  1315 + "linux"
  1316 + ],
  1317 + "engines": {
  1318 + "node": ">= 10"
  1319 + }
  1320 + },
  1321 + "node_modules/@tailwindcss/oxide-wasm32-wasi": {
  1322 + "version": "4.1.18",
  1323 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
  1324 + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
  1325 + "bundleDependencies": [
  1326 + "@napi-rs/wasm-runtime",
  1327 + "@emnapi/core",
  1328 + "@emnapi/runtime",
  1329 + "@tybys/wasm-util",
  1330 + "@emnapi/wasi-threads",
  1331 + "tslib"
  1332 + ],
  1333 + "cpu": [
  1334 + "wasm32"
  1335 + ],
  1336 + "dev": true,
  1337 + "license": "MIT",
  1338 + "optional": true,
  1339 + "dependencies": {
  1340 + "@emnapi/core": "^1.7.1",
  1341 + "@emnapi/runtime": "^1.7.1",
  1342 + "@emnapi/wasi-threads": "^1.1.0",
  1343 + "@napi-rs/wasm-runtime": "^1.1.0",
  1344 + "@tybys/wasm-util": "^0.10.1",
  1345 + "tslib": "^2.4.0"
  1346 + },
  1347 + "engines": {
  1348 + "node": ">=14.0.0"
  1349 + }
  1350 + },
  1351 + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
  1352 + "version": "4.1.18",
  1353 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
  1354 + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
  1355 + "cpu": [
  1356 + "arm64"
  1357 + ],
  1358 + "dev": true,
  1359 + "license": "MIT",
  1360 + "optional": true,
  1361 + "os": [
  1362 + "win32"
  1363 + ],
  1364 + "engines": {
  1365 + "node": ">= 10"
  1366 + }
  1367 + },
  1368 + "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
  1369 + "version": "4.1.18",
  1370 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
  1371 + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
  1372 + "cpu": [
  1373 + "x64"
  1374 + ],
  1375 + "dev": true,
  1376 + "license": "MIT",
  1377 + "optional": true,
  1378 + "os": [
  1379 + "win32"
  1380 + ],
  1381 + "engines": {
  1382 + "node": ">= 10"
  1383 + }
  1384 + },
  1385 + "node_modules/@tailwindcss/postcss": {
  1386 + "version": "4.1.18",
  1387 + "resolved": "https://repo.huaweicloud.com/repository/npm/@tailwindcss/postcss/-/postcss-4.1.18.tgz",
  1388 + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==",
  1389 + "dev": true,
  1390 + "license": "MIT",
  1391 + "dependencies": {
  1392 + "@alloc/quick-lru": "^5.2.0",
  1393 + "@tailwindcss/node": "4.1.18",
  1394 + "@tailwindcss/oxide": "4.1.18",
  1395 + "postcss": "^8.4.41",
  1396 + "tailwindcss": "4.1.18"
  1397 + }
  1398 + },
1114 "node_modules/@types/babel__core": { 1399 "node_modules/@types/babel__core": {
1115 "version": "7.20.5", 1400 "version": "7.20.5",
1116 "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", 1401 "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1321,6 +1606,16 @@ @@ -1321,6 +1606,16 @@
1321 } 1606 }
1322 } 1607 }
1323 }, 1608 },
  1609 + "node_modules/detect-libc": {
  1610 + "version": "2.1.2",
  1611 + "resolved": "https://repo.huaweicloud.com/repository/npm/detect-libc/-/detect-libc-2.1.2.tgz",
  1612 + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
  1613 + "dev": true,
  1614 + "license": "Apache-2.0",
  1615 + "engines": {
  1616 + "node": ">=8"
  1617 + }
  1618 + },
1324 "node_modules/electron-to-chromium": { 1619 "node_modules/electron-to-chromium": {
1325 "version": "1.5.267", 1620 "version": "1.5.267",
1326 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", 1621 "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
@@ -1328,6 +1623,20 @@ @@ -1328,6 +1623,20 @@
1328 "dev": true, 1623 "dev": true,
1329 "license": "ISC" 1624 "license": "ISC"
1330 }, 1625 },
  1626 + "node_modules/enhanced-resolve": {
  1627 + "version": "5.19.0",
  1628 + "resolved": "https://repo.huaweicloud.com/repository/npm/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
  1629 + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==",
  1630 + "dev": true,
  1631 + "license": "MIT",
  1632 + "dependencies": {
  1633 + "graceful-fs": "^4.2.4",
  1634 + "tapable": "^2.3.0"
  1635 + },
  1636 + "engines": {
  1637 + "node": ">=10.13.0"
  1638 + }
  1639 + },
1331 "node_modules/esbuild": { 1640 "node_modules/esbuild": {
1332 "version": "0.25.12", 1641 "version": "0.25.12",
1333 "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", 1642 "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
@@ -1437,6 +1746,23 @@ @@ -1437,6 +1746,23 @@
1437 "node": ">=6.9.0" 1746 "node": ">=6.9.0"
1438 } 1747 }
1439 }, 1748 },
  1749 + "node_modules/graceful-fs": {
  1750 + "version": "4.2.11",
  1751 + "resolved": "https://repo.huaweicloud.com/repository/npm/graceful-fs/-/graceful-fs-4.2.11.tgz",
  1752 + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
  1753 + "dev": true,
  1754 + "license": "ISC"
  1755 + },
  1756 + "node_modules/jiti": {
  1757 + "version": "2.6.1",
  1758 + "resolved": "https://repo.huaweicloud.com/repository/npm/jiti/-/jiti-2.6.1.tgz",
  1759 + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
  1760 + "dev": true,
  1761 + "license": "MIT",
  1762 + "bin": {
  1763 + "jiti": "lib/jiti-cli.mjs"
  1764 + }
  1765 + },
1440 "node_modules/js-tokens": { 1766 "node_modules/js-tokens": {
1441 "version": "4.0.0", 1767 "version": "4.0.0",
1442 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 1768 "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -1470,6 +1796,267 @@ @@ -1470,6 +1796,267 @@
1470 "node": ">=6" 1796 "node": ">=6"
1471 } 1797 }
1472 }, 1798 },
  1799 + "node_modules/lightningcss": {
  1800 + "version": "1.30.2",
  1801 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss/-/lightningcss-1.30.2.tgz",
  1802 + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
  1803 + "dev": true,
  1804 + "license": "MPL-2.0",
  1805 + "dependencies": {
  1806 + "detect-libc": "^2.0.3"
  1807 + },
  1808 + "engines": {
  1809 + "node": ">= 12.0.0"
  1810 + },
  1811 + "funding": {
  1812 + "type": "opencollective",
  1813 + "url": "https://opencollective.com/parcel"
  1814 + },
  1815 + "optionalDependencies": {
  1816 + "lightningcss-android-arm64": "1.30.2",
  1817 + "lightningcss-darwin-arm64": "1.30.2",
  1818 + "lightningcss-darwin-x64": "1.30.2",
  1819 + "lightningcss-freebsd-x64": "1.30.2",
  1820 + "lightningcss-linux-arm-gnueabihf": "1.30.2",
  1821 + "lightningcss-linux-arm64-gnu": "1.30.2",
  1822 + "lightningcss-linux-arm64-musl": "1.30.2",
  1823 + "lightningcss-linux-x64-gnu": "1.30.2",
  1824 + "lightningcss-linux-x64-musl": "1.30.2",
  1825 + "lightningcss-win32-arm64-msvc": "1.30.2",
  1826 + "lightningcss-win32-x64-msvc": "1.30.2"
  1827 + }
  1828 + },
  1829 + "node_modules/lightningcss-android-arm64": {
  1830 + "version": "1.30.2",
  1831 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
  1832 + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
  1833 + "cpu": [
  1834 + "arm64"
  1835 + ],
  1836 + "dev": true,
  1837 + "license": "MPL-2.0",
  1838 + "optional": true,
  1839 + "os": [
  1840 + "android"
  1841 + ],
  1842 + "engines": {
  1843 + "node": ">= 12.0.0"
  1844 + },
  1845 + "funding": {
  1846 + "type": "opencollective",
  1847 + "url": "https://opencollective.com/parcel"
  1848 + }
  1849 + },
  1850 + "node_modules/lightningcss-darwin-arm64": {
  1851 + "version": "1.30.2",
  1852 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
  1853 + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
  1854 + "cpu": [
  1855 + "arm64"
  1856 + ],
  1857 + "dev": true,
  1858 + "license": "MPL-2.0",
  1859 + "optional": true,
  1860 + "os": [
  1861 + "darwin"
  1862 + ],
  1863 + "engines": {
  1864 + "node": ">= 12.0.0"
  1865 + },
  1866 + "funding": {
  1867 + "type": "opencollective",
  1868 + "url": "https://opencollective.com/parcel"
  1869 + }
  1870 + },
  1871 + "node_modules/lightningcss-darwin-x64": {
  1872 + "version": "1.30.2",
  1873 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
  1874 + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
  1875 + "cpu": [
  1876 + "x64"
  1877 + ],
  1878 + "dev": true,
  1879 + "license": "MPL-2.0",
  1880 + "optional": true,
  1881 + "os": [
  1882 + "darwin"
  1883 + ],
  1884 + "engines": {
  1885 + "node": ">= 12.0.0"
  1886 + },
  1887 + "funding": {
  1888 + "type": "opencollective",
  1889 + "url": "https://opencollective.com/parcel"
  1890 + }
  1891 + },
  1892 + "node_modules/lightningcss-freebsd-x64": {
  1893 + "version": "1.30.2",
  1894 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
  1895 + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
  1896 + "cpu": [
  1897 + "x64"
  1898 + ],
  1899 + "dev": true,
  1900 + "license": "MPL-2.0",
  1901 + "optional": true,
  1902 + "os": [
  1903 + "freebsd"
  1904 + ],
  1905 + "engines": {
  1906 + "node": ">= 12.0.0"
  1907 + },
  1908 + "funding": {
  1909 + "type": "opencollective",
  1910 + "url": "https://opencollective.com/parcel"
  1911 + }
  1912 + },
  1913 + "node_modules/lightningcss-linux-arm-gnueabihf": {
  1914 + "version": "1.30.2",
  1915 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
  1916 + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
  1917 + "cpu": [
  1918 + "arm"
  1919 + ],
  1920 + "dev": true,
  1921 + "license": "MPL-2.0",
  1922 + "optional": true,
  1923 + "os": [
  1924 + "linux"
  1925 + ],
  1926 + "engines": {
  1927 + "node": ">= 12.0.0"
  1928 + },
  1929 + "funding": {
  1930 + "type": "opencollective",
  1931 + "url": "https://opencollective.com/parcel"
  1932 + }
  1933 + },
  1934 + "node_modules/lightningcss-linux-arm64-gnu": {
  1935 + "version": "1.30.2",
  1936 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
  1937 + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
  1938 + "cpu": [
  1939 + "arm64"
  1940 + ],
  1941 + "dev": true,
  1942 + "license": "MPL-2.0",
  1943 + "optional": true,
  1944 + "os": [
  1945 + "linux"
  1946 + ],
  1947 + "engines": {
  1948 + "node": ">= 12.0.0"
  1949 + },
  1950 + "funding": {
  1951 + "type": "opencollective",
  1952 + "url": "https://opencollective.com/parcel"
  1953 + }
  1954 + },
  1955 + "node_modules/lightningcss-linux-arm64-musl": {
  1956 + "version": "1.30.2",
  1957 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
  1958 + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
  1959 + "cpu": [
  1960 + "arm64"
  1961 + ],
  1962 + "dev": true,
  1963 + "license": "MPL-2.0",
  1964 + "optional": true,
  1965 + "os": [
  1966 + "linux"
  1967 + ],
  1968 + "engines": {
  1969 + "node": ">= 12.0.0"
  1970 + },
  1971 + "funding": {
  1972 + "type": "opencollective",
  1973 + "url": "https://opencollective.com/parcel"
  1974 + }
  1975 + },
  1976 + "node_modules/lightningcss-linux-x64-gnu": {
  1977 + "version": "1.30.2",
  1978 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
  1979 + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
  1980 + "cpu": [
  1981 + "x64"
  1982 + ],
  1983 + "dev": true,
  1984 + "license": "MPL-2.0",
  1985 + "optional": true,
  1986 + "os": [
  1987 + "linux"
  1988 + ],
  1989 + "engines": {
  1990 + "node": ">= 12.0.0"
  1991 + },
  1992 + "funding": {
  1993 + "type": "opencollective",
  1994 + "url": "https://opencollective.com/parcel"
  1995 + }
  1996 + },
  1997 + "node_modules/lightningcss-linux-x64-musl": {
  1998 + "version": "1.30.2",
  1999 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
  2000 + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
  2001 + "cpu": [
  2002 + "x64"
  2003 + ],
  2004 + "dev": true,
  2005 + "license": "MPL-2.0",
  2006 + "optional": true,
  2007 + "os": [
  2008 + "linux"
  2009 + ],
  2010 + "engines": {
  2011 + "node": ">= 12.0.0"
  2012 + },
  2013 + "funding": {
  2014 + "type": "opencollective",
  2015 + "url": "https://opencollective.com/parcel"
  2016 + }
  2017 + },
  2018 + "node_modules/lightningcss-win32-arm64-msvc": {
  2019 + "version": "1.30.2",
  2020 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
  2021 + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
  2022 + "cpu": [
  2023 + "arm64"
  2024 + ],
  2025 + "dev": true,
  2026 + "license": "MPL-2.0",
  2027 + "optional": true,
  2028 + "os": [
  2029 + "win32"
  2030 + ],
  2031 + "engines": {
  2032 + "node": ">= 12.0.0"
  2033 + },
  2034 + "funding": {
  2035 + "type": "opencollective",
  2036 + "url": "https://opencollective.com/parcel"
  2037 + }
  2038 + },
  2039 + "node_modules/lightningcss-win32-x64-msvc": {
  2040 + "version": "1.30.2",
  2041 + "resolved": "https://repo.huaweicloud.com/repository/npm/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
  2042 + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
  2043 + "cpu": [
  2044 + "x64"
  2045 + ],
  2046 + "dev": true,
  2047 + "license": "MPL-2.0",
  2048 + "optional": true,
  2049 + "os": [
  2050 + "win32"
  2051 + ],
  2052 + "engines": {
  2053 + "node": ">= 12.0.0"
  2054 + },
  2055 + "funding": {
  2056 + "type": "opencollective",
  2057 + "url": "https://opencollective.com/parcel"
  2058 + }
  2059 + },
1473 "node_modules/lru-cache": { 2060 "node_modules/lru-cache": {
1474 "version": "5.1.1", 2061 "version": "5.1.1",
1475 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 2062 "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -1489,6 +2076,16 @@ @@ -1489,6 +2076,16 @@
1489 "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" 2076 "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
1490 } 2077 }
1491 }, 2078 },
  2079 + "node_modules/magic-string": {
  2080 + "version": "0.30.21",
  2081 + "resolved": "https://repo.huaweicloud.com/repository/npm/magic-string/-/magic-string-0.30.21.tgz",
  2082 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
  2083 + "dev": true,
  2084 + "license": "MIT",
  2085 + "dependencies": {
  2086 + "@jridgewell/sourcemap-codec": "^1.5.5"
  2087 + }
  2088 + },
1492 "node_modules/ms": { 2089 "node_modules/ms": {
1493 "version": "2.1.3", 2090 "version": "2.1.3",
1494 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2091 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -1684,6 +2281,20 @@ @@ -1684,6 +2281,20 @@
1684 "dev": true, 2281 "dev": true,
1685 "license": "MIT" 2282 "license": "MIT"
1686 }, 2283 },
  2284 + "node_modules/tapable": {
  2285 + "version": "2.3.0",
  2286 + "resolved": "https://repo.huaweicloud.com/repository/npm/tapable/-/tapable-2.3.0.tgz",
  2287 + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
  2288 + "dev": true,
  2289 + "license": "MIT",
  2290 + "engines": {
  2291 + "node": ">=6"
  2292 + },
  2293 + "funding": {
  2294 + "type": "opencollective",
  2295 + "url": "https://opencollective.com/webpack"
  2296 + }
  2297 + },
1687 "node_modules/tinyglobby": { 2298 "node_modules/tinyglobby": {
1688 "version": "0.2.15", 2299 "version": "0.2.15",
1689 "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 2300 "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
14 "react-dom": "^19.2.3" 14 "react-dom": "^19.2.3"
15 }, 15 },
16 "devDependencies": { 16 "devDependencies": {
  17 + "@tailwindcss/postcss": "^4.1.18",
17 "@types/node": "^22.14.0", 18 "@types/node": "^22.14.0",
18 "@vitejs/plugin-react": "^5.0.0", 19 "@vitejs/plugin-react": "^5.0.0",
19 "autoprefixer": "^10.4.24", 20 "autoprefixer": "^10.4.24",
1 export default { 1 export default {
2 plugins: { 2 plugins: {
3 - tailwindcss: {}, 3 + '@tailwindcss/postcss': {},
4 autoprefixer: {}, 4 autoprefixer: {},
5 }, 5 },
6 } 6 }