Deepfake Authenticator commited on
Commit ·
459d5a1
1
Parent(s): 91da8ab
feat: React + Three.js UI with 3D reactive orb
Browse files- Vite + React + TypeScript frontend
- Three.js / React Three Fiber: animated 3D orb that changes color/behavior
based on analysis state (idle=blue, loading=fast spin, FAKE=red, REAL=green)
- Framer Motion: spring animations, AnimatePresence transitions, shimmer button
- Zustand state management
- Animated confidence arc (SVG semicircle gauge)
- Frame timeline with per-bar Framer Motion animations
- Agent pipeline cards with sequential activation
- Audio analysis card with Wav2Vec2 + heuristic scores
- Sticky 3D orb panel on desktop, stacks on mobile
- Vite proxy for dev, FastAPI serves built dist in production
- backend/main.py +26 -6
- frontend/index.html +12 -411
- frontend/package-lock.json +697 -9
- frontend/package.json +6 -1
- frontend/src/App.tsx +183 -0
- frontend/src/api.ts +20 -0
- frontend/src/components/DeepfakeOrb.tsx +103 -0
- frontend/src/components/ResultPanel.tsx +346 -0
- frontend/src/components/UploadZone.tsx +190 -0
- frontend/src/index.css +32 -46
- frontend/src/main.tsx +10 -0
- frontend/src/store.ts +59 -0
- frontend/vite.config.js +11 -1
backend/main.py
CHANGED
|
@@ -135,23 +135,43 @@ async def analyze_video(file: UploadFile = File(...)):
|
|
| 135 |
|
| 136 |
|
| 137 |
# ── Serve frontend ────────────────────────────
|
| 138 |
-
|
|
|
|
|
|
|
| 139 |
|
| 140 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
@app.get("/script.js")
|
| 142 |
async def serve_script():
|
| 143 |
-
from fastapi.responses import FileResponse
|
| 144 |
return FileResponse(
|
| 145 |
-
str(
|
| 146 |
media_type="application/javascript",
|
| 147 |
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 148 |
)
|
| 149 |
|
| 150 |
@app.get("/")
|
| 151 |
async def serve_index():
|
| 152 |
-
from fastapi.responses import FileResponse
|
| 153 |
return FileResponse(
|
| 154 |
-
str(
|
| 155 |
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 156 |
)
|
| 157 |
|
|
|
|
| 135 |
|
| 136 |
|
| 137 |
# ── Serve frontend ────────────────────────────
|
| 138 |
+
# Prefer built React dist, fall back to vanilla HTML
|
| 139 |
+
_react_dist = Path(__file__).parent.parent / "frontend-dist"
|
| 140 |
+
_vanilla_dir = Path(__file__).parent.parent / "frontend-vanilla"
|
| 141 |
|
| 142 |
+
if _react_dist.exists():
|
| 143 |
+
# Serve React SPA
|
| 144 |
+
app.mount("/assets", StaticFiles(directory=str(_react_dist / "assets")), name="assets")
|
| 145 |
+
|
| 146 |
+
@app.get("/")
|
| 147 |
+
async def serve_index():
|
| 148 |
+
return FileResponse(
|
| 149 |
+
str(_react_dist / "index.html"),
|
| 150 |
+
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
# Catch-all for React Router (SPA fallback)
|
| 154 |
+
@app.get("/{full_path:path}")
|
| 155 |
+
async def spa_fallback(full_path: str):
|
| 156 |
+
index = _react_dist / "index.html"
|
| 157 |
+
if index.exists():
|
| 158 |
+
return FileResponse(str(index))
|
| 159 |
+
return {"detail": "Not found"}
|
| 160 |
+
|
| 161 |
+
elif _vanilla_dir.exists():
|
| 162 |
+
# Fallback: vanilla HTML
|
| 163 |
@app.get("/script.js")
|
| 164 |
async def serve_script():
|
|
|
|
| 165 |
return FileResponse(
|
| 166 |
+
str(_vanilla_dir / "script.js"),
|
| 167 |
media_type="application/javascript",
|
| 168 |
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 169 |
)
|
| 170 |
|
| 171 |
@app.get("/")
|
| 172 |
async def serve_index():
|
|
|
|
| 173 |
return FileResponse(
|
| 174 |
+
str(_vanilla_dir / "index.html"),
|
| 175 |
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 176 |
)
|
| 177 |
|
frontend/index.html
CHANGED
|
@@ -1,413 +1,14 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8"/>
|
| 5 |
-
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
| 6 |
-
<title>Deepfake Authenticator</title>
|
| 7 |
-
<
|
| 8 |
-
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet"/>
|
| 9 |
-
<
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
.hidden{display:none!important;}
|
| 16 |
-
</style>
|
| 17 |
-
</head>
|
| 18 |
-
<body>
|
| 19 |
-
|
| 20 |
-
<style>
|
| 21 |
-
/* ── Particle Canvas ── */
|
| 22 |
-
#particles{position:fixed;inset:0;z-index:0;pointer-events:none;}
|
| 23 |
-
/* ── Grid overlay ── */
|
| 24 |
-
.grid-bg{position:fixed;inset:0;z-index:0;pointer-events:none;
|
| 25 |
-
background-image:linear-gradient(rgba(0,255,136,0.03) 1px,transparent 1px),linear-gradient(90deg,rgba(0,255,136,0.03) 1px,transparent 1px);
|
| 26 |
-
background-size:60px 60px;}
|
| 27 |
-
/* ── Glass ── */
|
| 28 |
-
.glass{background:var(--card);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);border:1px solid var(--border);border-radius:16px;box-shadow:0 8px 40px rgba(0,0,0,0.5);}
|
| 29 |
-
.glass-sm{background:rgba(255,255,255,0.03);backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.07);border-radius:12px;}
|
| 30 |
-
/* ── Glow borders ── */
|
| 31 |
-
.glow-green{box-shadow:0 0 20px rgba(0,255,136,0.25),inset 0 0 20px rgba(0,255,136,0.04);}
|
| 32 |
-
.glow-red{box-shadow:0 0 20px rgba(255,51,85,0.25),inset 0 0 20px rgba(255,51,85,0.04);}
|
| 33 |
-
/* ── Header ── */
|
| 34 |
-
header{position:sticky;top:0;z-index:50;background:rgba(5,5,8,0.85);backdrop-filter:blur(24px);border-bottom:1px solid rgba(0,255,136,0.1);}
|
| 35 |
-
/* ── Eye SVG animation ── */
|
| 36 |
-
@keyframes scanPulse{0%,100%{opacity:1;transform:scale(1);}50%{opacity:0.5;transform:scale(1.15);}}
|
| 37 |
-
@keyframes eyeScan{0%{stroke-dashoffset:100;}100%{stroke-dashoffset:0;}}
|
| 38 |
-
.eye-scan{animation:scanPulse 2.5s ease-in-out infinite;}
|
| 39 |
-
/* ── Status dot ── */
|
| 40 |
-
@keyframes statusBlink{0%,100%{opacity:1;box-shadow:0 0 6px var(--g);}50%{opacity:0.4;box-shadow:none;}}
|
| 41 |
-
.status-dot{width:8px;height:8px;border-radius:50%;background:var(--g);animation:statusBlink 2s ease-in-out infinite;}
|
| 42 |
-
/* ── Drop zone border animation ── */
|
| 43 |
-
@keyframes borderDash{to{stroke-dashoffset:-20;}}
|
| 44 |
-
#dropZone{border:2px dashed rgba(0,255,136,0.25);border-radius:16px;cursor:pointer;min-height:200px;display:flex;align-items:center;justify-content:center;transition:all 0.35s ease;position:relative;overflow:hidden;}
|
| 45 |
-
#dropZone:hover,#dropZone.drag-over{border-color:var(--g);background:rgba(0,255,136,0.04);box-shadow:0 0 40px rgba(0,255,136,0.2),inset 0 0 30px rgba(0,255,136,0.06);transform:scale(1.01);}
|
| 46 |
-
/* ── Orbit ring ── */
|
| 47 |
-
@keyframes orbitRing{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}
|
| 48 |
-
.orbit{animation:orbitRing 3s linear infinite;transform-origin:center;}
|
| 49 |
-
/* ── Analyze button ── */
|
| 50 |
-
#analyzeBtn{width:100%;padding:16px;border-radius:12px;font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:14px;letter-spacing:0.12em;text-transform:uppercase;border:1px solid rgba(255,255,255,0.1);background:rgba(255,255,255,0.05);color:var(--muted);cursor:not-allowed;transition:all 0.3s ease;position:relative;overflow:hidden;}
|
| 51 |
-
#analyzeBtn.active{background:linear-gradient(135deg,#00ff88,#00cc66);color:#050508;border-color:transparent;cursor:pointer;box-shadow:0 0 30px rgba(0,255,136,0.4);}
|
| 52 |
-
#analyzeBtn.active:hover{transform:translateY(-2px) scale(1.01);box-shadow:0 0 50px rgba(0,255,136,0.6);}
|
| 53 |
-
#analyzeBtn.active::after{content:'';position:absolute;top:0;left:-100%;width:60%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,0.25),transparent);animation:shimmer 2s ease infinite;}
|
| 54 |
-
@keyframes shimmer{to{left:150%;}}
|
| 55 |
-
#analyzeBtn:disabled{opacity:0.45;cursor:not-allowed;transform:none!important;}
|
| 56 |
-
</style>
|
| 57 |
-
|
| 58 |
-
<style>
|
| 59 |
-
/* ── Loading ── */
|
| 60 |
-
@keyframes radarSpin{from{transform:rotate(0deg);}to{transform:rotate(360deg);}}
|
| 61 |
-
@keyframes radarPulse{0%,100%{opacity:0.8;transform:scale(1);}50%{opacity:0.3;transform:scale(1.05);}}
|
| 62 |
-
.radar-ring{border-radius:50%;border:1px solid rgba(0,255,136,0.2);position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}
|
| 63 |
-
.radar-sweep{position:absolute;top:50%;left:50%;width:50%;height:2px;transform-origin:left center;background:linear-gradient(90deg,transparent,var(--g));animation:radarSpin 2s linear infinite;}
|
| 64 |
-
/* ── Agent cards ── */
|
| 65 |
-
.agent-card{transition:all 0.4s ease;border:1px solid rgba(255,255,255,0.06);border-radius:12px;padding:12px 16px;background:rgba(255,255,255,0.02);}
|
| 66 |
-
.agent-card.active{border-color:var(--g);background:rgba(0,255,136,0.06);box-shadow:0 0 20px rgba(0,255,136,0.15);}
|
| 67 |
-
.agent-card.active .agent-dot{background:var(--g);box-shadow:0 0 8px var(--g);}
|
| 68 |
-
.agent-dot{width:8px;height:8px;border-radius:50%;background:rgba(255,255,255,0.15);transition:all 0.4s ease;flex-shrink:0;}
|
| 69 |
-
/* ── Progress bar ── */
|
| 70 |
-
.prog-track{height:4px;background:rgba(255,255,255,0.06);border-radius:99px;overflow:hidden;}
|
| 71 |
-
.prog-fill{height:100%;border-radius:99px;background:linear-gradient(90deg,#00aaff,var(--g));box-shadow:0 0 12px var(--g);transition:width 0.5s ease;}
|
| 72 |
-
/* ── Verdict ── */
|
| 73 |
-
@keyframes glitch{0%,100%{text-shadow:0 0 10px var(--r);}20%{text-shadow:-2px 0 #ff0,2px 0 #0ff,0 0 20px var(--r);}40%{text-shadow:2px 0 #ff0,-2px 0 #0ff,0 0 10px var(--r);}60%{text-shadow:0 0 30px var(--r);}}
|
| 74 |
-
.glitch-text{animation:glitch 3s ease-in-out infinite;}
|
| 75 |
-
@keyframes fadeUp{from{opacity:0;transform:translateY(24px);}to{opacity:1;transform:none;}}
|
| 76 |
-
.fade-up{animation:fadeUp 0.6s cubic-bezier(0.16,1,0.3,1) both;}
|
| 77 |
-
@keyframes slideInLeft{from{opacity:0;transform:translateX(-20px);}to{opacity:1;transform:none;}}
|
| 78 |
-
/* ── Verdict badge ── */
|
| 79 |
-
.verdict-badge{width:110px;height:110px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:2.8rem;transition:all 0.5s;border:2px solid var(--border);}
|
| 80 |
-
@keyframes pulseFake{0%,100%{box-shadow:0 0 20px rgba(255,51,85,0.3);}50%{box-shadow:0 0 50px rgba(255,51,85,0.7);}}
|
| 81 |
-
@keyframes pulseReal{0%,100%{box-shadow:0 0 20px rgba(0,255,136,0.3);}50%{box-shadow:0 0 50px rgba(0,255,136,0.7);}}
|
| 82 |
-
.verdict-badge.fake{border-color:var(--r);animation:pulseFake 2s infinite;}
|
| 83 |
-
.verdict-badge.real{border-color:var(--g);animation:pulseReal 2s infinite;}
|
| 84 |
-
/* ── Confidence arc (CSS semicircle gauge) ── */
|
| 85 |
-
.gauge-wrap{position:relative;width:200px;height:110px;overflow:hidden;margin:0 auto;}
|
| 86 |
-
.gauge-bg{width:200px;height:200px;border-radius:50%;border:12px solid rgba(255,255,255,0.06);position:absolute;top:0;left:0;clip-path:inset(0 0 50% 0);}
|
| 87 |
-
.gauge-fill{width:200px;height:200px;border-radius:50%;border:12px solid transparent;position:absolute;top:0;left:0;clip-path:inset(0 0 50% 0);transition:transform 1.2s cubic-bezier(0.22,1,0.36,1);}
|
| 88 |
-
.gauge-fill.fake{border-color:var(--r);box-shadow:0 0 20px rgba(255,51,85,0.5);}
|
| 89 |
-
.gauge-fill.real{border-color:var(--g);box-shadow:0 0 20px rgba(0,255,136,0.5);}
|
| 90 |
-
/* ── Conf bar ── */
|
| 91 |
-
.conf-track{height:10px;background:rgba(255,255,255,0.05);border-radius:99px;overflow:hidden;border:1px solid rgba(255,255,255,0.05);}
|
| 92 |
-
.conf-fill{height:100%;border-radius:99px;transition:width 1.2s cubic-bezier(0.22,1,0.36,1);}
|
| 93 |
-
.conf-fill.fake{background:linear-gradient(90deg,#880022,var(--r));box-shadow:0 0 15px rgba(255,51,85,0.6);}
|
| 94 |
-
.conf-fill.real{background:linear-gradient(90deg,#00aaff,var(--g));box-shadow:0 0 15px rgba(0,255,136,0.6);}
|
| 95 |
-
/* ── Risk needle ── */
|
| 96 |
-
#riskNeedle{display:inline-block;padding:4px 14px;border-radius:99px;font-size:11px;font-weight:700;letter-spacing:0.15em;border:1px solid;transition:all 0.5s;}
|
| 97 |
-
/* ── Insight bullets ── */
|
| 98 |
-
.insight-item{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;border-radius:10px;background:rgba(255,255,255,0.02);border:1px solid rgba(255,255,255,0.05);font-size:13px;line-height:1.6;color:#a0aec0;animation:slideInLeft 0.4s ease both;}
|
| 99 |
-
.insight-item:hover{background:rgba(255,255,255,0.04);}
|
| 100 |
-
.insight-dot{flex-shrink:0;width:8px;height:8px;border-radius:50%;margin-top:6px;}
|
| 101 |
-
/* ── Timeline bars ── */
|
| 102 |
-
.bar-wrap{display:flex;flex-direction:column;align-items:center;gap:4px;flex:1;min-width:0;position:relative;}
|
| 103 |
-
.bar-outer{width:100%;background:rgba(255,255,255,0.05);border-radius:4px 4px 0 0;overflow:hidden;position:relative;}
|
| 104 |
-
.bar-inner{width:100%;border-radius:4px 4px 0 0;transition:height 0.8s cubic-bezier(0.22,1,0.36,1);}
|
| 105 |
-
.bar-wrap:hover .bar-tooltip{opacity:1;transform:translateY(-4px);}
|
| 106 |
-
.bar-tooltip{position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);background:rgba(10,10,20,0.95);border:1px solid rgba(255,255,255,0.1);border-radius:6px;padding:4px 8px;font-size:10px;white-space:nowrap;pointer-events:none;opacity:0;transition:all 0.2s ease;z-index:10;font-family:'JetBrains Mono',monospace;}
|
| 107 |
-
/* ── Meta grid ── */
|
| 108 |
-
.meta-row{display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.05);}
|
| 109 |
-
.meta-row:last-child{border-bottom:none;}
|
| 110 |
-
/* ── Error ── */
|
| 111 |
-
#errorSection{border-color:rgba(255,51,85,0.3)!important;background:rgba(255,51,85,0.04)!important;}
|
| 112 |
-
/* ── Stat pills ── */
|
| 113 |
-
.stat-pill{display:inline-flex;align-items:center;gap:8px;padding:8px 18px;border-radius:99px;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);font-size:12px;font-weight:600;letter-spacing:0.05em;color:#a0aec0;}
|
| 114 |
-
.stat-pill .pip{width:6px;height:6px;border-radius:50%;background:var(--g);box-shadow:0 0 6px var(--g);}
|
| 115 |
-
/* ── Section label ── */
|
| 116 |
-
.sec-label{font-size:10px;font-weight:700;letter-spacing:0.2em;text-transform:uppercase;color:#00aaff;display:flex;align-items:center;gap:8px;margin-bottom:14px;}
|
| 117 |
-
.sec-label::before{content:'';display:inline-block;width:3px;height:14px;background:#00aaff;border-radius:2px;box-shadow:0 0 8px #00aaff;}
|
| 118 |
-
/* ── Model badge ── */
|
| 119 |
-
#modelBadge{display:inline-flex;align-items:center;gap:8px;padding:6px 14px;border-radius:99px;background:rgba(0,255,136,0.08);border:1px solid rgba(0,255,136,0.25);font-size:11px;font-weight:600;letter-spacing:0.1em;color:var(--g);}
|
| 120 |
-
</style>
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
<!-- Particle canvas + grid -->
|
| 124 |
-
<canvas id="particles"></canvas>
|
| 125 |
-
<div class="grid-bg"></div>
|
| 126 |
-
|
| 127 |
-
<!-- ══ HEADER ══ -->
|
| 128 |
-
<header>
|
| 129 |
-
<div style="max-width:1100px;margin:0 auto;padding:0 24px;height:64px;display:flex;align-items:center;justify-content:space-between;">
|
| 130 |
-
<div style="display:flex;align-items:center;gap:14px;">
|
| 131 |
-
<!-- Animated eye logo -->
|
| 132 |
-
<div class="eye-scan" style="width:36px;height:36px;display:flex;align-items:center;justify-content:center;">
|
| 133 |
-
<svg width="36" height="36" viewBox="0 0 36 36" fill="none">
|
| 134 |
-
<ellipse cx="18" cy="18" rx="14" ry="9" stroke="#00ff88" stroke-width="1.5" opacity="0.9"/>
|
| 135 |
-
<circle cx="18" cy="18" r="5" fill="#00ff88" opacity="0.9"/>
|
| 136 |
-
<circle cx="18" cy="18" r="2.5" fill="#050508"/>
|
| 137 |
-
<path d="M4 18 Q18 4 32 18" stroke="#00ff88" stroke-width="0.8" stroke-dasharray="4 3" opacity="0.4">
|
| 138 |
-
<animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="8s" repeatCount="indefinite"/>
|
| 139 |
-
</path>
|
| 140 |
-
</svg>
|
| 141 |
-
</div>
|
| 142 |
-
<div>
|
| 143 |
-
<div style="font-size:13px;font-weight:700;letter-spacing:0.18em;color:#fff;">DEEPFAKE AUTHENTICATOR</div>
|
| 144 |
-
<div style="font-size:9px;letter-spacing:0.25em;color:var(--muted);font-family:'JetBrains Mono',monospace;">AI-POWERED VIDEO FORENSICS</div>
|
| 145 |
-
</div>
|
| 146 |
-
</div>
|
| 147 |
-
<div id="modelBadge">
|
| 148 |
-
<div class="status-dot"></div>
|
| 149 |
-
<span>ViT ENSEMBLE</span>
|
| 150 |
-
</div>
|
| 151 |
-
</div>
|
| 152 |
-
</header>
|
| 153 |
-
|
| 154 |
-
<!-- ══ MAIN ══ -->
|
| 155 |
-
<div style="position:relative;z-index:1;max-width:860px;margin:0 auto;padding:48px 24px 80px;">
|
| 156 |
-
|
| 157 |
-
<!-- Hero -->
|
| 158 |
-
<div class="fade-up" style="text-align:center;margin-bottom:48px;animation-delay:0.1s;">
|
| 159 |
-
<h1 style="font-size:clamp(2rem,5vw,3.5rem);font-weight:700;line-height:1.1;margin-bottom:16px;background:linear-gradient(135deg,#fff 30%,var(--g));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;">Is This Video Real?</h1>
|
| 160 |
-
<p style="color:var(--muted);font-size:15px;max-width:480px;margin:0 auto 28px;line-height:1.7;">Advanced AI ensemble detects facial manipulation with 99%+ accuracy using dual Vision Transformer models.</p>
|
| 161 |
-
<div style="display:flex;flex-wrap:wrap;gap:10px;justify-content:center;">
|
| 162 |
-
<div class="stat-pill"><span class="pip"></span>2 ViT Models</div>
|
| 163 |
-
<div class="stat-pill"><span class="pip"></span>40 Frame Analysis</div>
|
| 164 |
-
<div class="stat-pill"><span class="pip"></span>< 15s Detection</div>
|
| 165 |
-
</div>
|
| 166 |
-
</div>
|
| 167 |
-
|
| 168 |
-
<!-- ══ UPLOAD SECTION ══ -->
|
| 169 |
-
<section id="uploadSection" class="glass fade-up" style="padding:32px;margin-bottom:24px;animation-delay:0.2s;">
|
| 170 |
-
<div id="dropZone">
|
| 171 |
-
<!-- Upload prompt -->
|
| 172 |
-
<div id="uploadPrompt" style="text-align:center;padding:40px 24px;">
|
| 173 |
-
<div style="position:relative;width:80px;height:80px;margin:0 auto 20px;">
|
| 174 |
-
<div style="position:absolute;inset:0;border-radius:50%;border:1px dashed rgba(0,255,136,0.3);" class="orbit"></div>
|
| 175 |
-
<div style="position:absolute;inset:8px;border-radius:50%;border:1px solid rgba(0,255,136,0.15);"></div>
|
| 176 |
-
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;">
|
| 177 |
-
<svg width="32" height="32" fill="none" stroke="#00ff88" stroke-width="1.5" viewBox="0 0 24 24" opacity="0.8">
|
| 178 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/>
|
| 179 |
-
</svg>
|
| 180 |
-
</div>
|
| 181 |
-
</div>
|
| 182 |
-
<h3 style="font-size:17px;font-weight:600;color:#fff;margin-bottom:8px;">Drop Video for Forensic Analysis</h3>
|
| 183 |
-
<p style="font-size:13px;color:var(--muted);">Drag & drop or click to browse</p>
|
| 184 |
-
<p style="font-size:11px;color:var(--muted);margin-top:12px;opacity:0.6;font-family:'JetBrains Mono',monospace;">MP4 · AVI · MOV · MKV · WebM — Max 100MB</p>
|
| 185 |
-
</div>
|
| 186 |
-
<!-- File chosen state -->
|
| 187 |
-
<div id="fileChosen" class="hidden" style="width:100%;padding:28px 32px;display:flex;align-items:center;gap:20px;">
|
| 188 |
-
<div style="width:56px;height:56px;border-radius:12px;background:rgba(0,255,136,0.1);border:1px solid rgba(0,255,136,0.3);display:flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:0 0 20px rgba(0,255,136,0.15);">
|
| 189 |
-
<svg width="28" height="28" fill="none" stroke="#00ff88" stroke-width="1.5" viewBox="0 0 24 24">
|
| 190 |
-
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"/>
|
| 191 |
-
</svg>
|
| 192 |
-
</div>
|
| 193 |
-
<div style="flex:1;min-width:0;">
|
| 194 |
-
<p id="chosenName" style="font-size:14px;font-weight:600;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;"></p>
|
| 195 |
-
<p id="chosenSize" style="font-size:12px;color:var(--muted);margin-top:4px;font-family:'JetBrains Mono',monospace;"></p>
|
| 196 |
-
</div>
|
| 197 |
-
<button id="clearBtn" style="padding:8px;color:var(--muted);background:none;border:none;cursor:pointer;border-radius:8px;transition:all 0.2s;" onmouseover="this.style.color='#ff3355';this.style.background='rgba(255,51,85,0.1)'" onmouseout="this.style.color='var(--muted)';this.style.background='none'">
|
| 198 |
-
<svg width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>
|
| 199 |
-
</button>
|
| 200 |
-
</div>
|
| 201 |
-
</div>
|
| 202 |
-
<input type="file" id="fileInput" accept="video/*" style="display:none;"/>
|
| 203 |
-
<div style="margin-top:20px;">
|
| 204 |
-
<button id="analyzeBtn" disabled>
|
| 205 |
-
<span id="analyzeBtnText">Analyze Video</span>
|
| 206 |
-
</button>
|
| 207 |
-
</div>
|
| 208 |
-
</section>
|
| 209 |
-
|
| 210 |
-
<!-- ══ LOADING SECTION ══ -->
|
| 211 |
-
<section id="loadingSection" class="hidden glass fade-up" style="padding:48px 32px;text-align:center;margin-bottom:24px;">
|
| 212 |
-
<!-- Radar -->
|
| 213 |
-
<div style="position:relative;width:140px;height:140px;margin:0 auto 36px;">
|
| 214 |
-
<div class="radar-ring" style="width:140px;height:140px;"></div>
|
| 215 |
-
<div class="radar-ring" style="width:100px;height:100px;"></div>
|
| 216 |
-
<div class="radar-ring" style="width:60px;height:60px;"></div>
|
| 217 |
-
<div class="radar-sweep" style="width:70px;top:50%;left:50%;transform-origin:left center;"></div>
|
| 218 |
-
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:12px;height:12px;border-radius:50%;background:var(--g);box-shadow:0 0 15px var(--g);"></div>
|
| 219 |
-
</div>
|
| 220 |
-
<!-- Status -->
|
| 221 |
-
<p id="loadingStatus" style="font-size:11px;font-weight:700;letter-spacing:0.2em;color:#00aaff;text-transform:uppercase;margin-bottom:20px;font-family:'JetBrains Mono',monospace;">Initializing sequence...</p>
|
| 222 |
-
<!-- Progress -->
|
| 223 |
-
<div style="max-width:400px;margin:0 auto 32px;">
|
| 224 |
-
<div class="prog-track">
|
| 225 |
-
<div id="progressBar" style="width:0%;" class="prog-fill"></div>
|
| 226 |
-
</div>
|
| 227 |
-
</div>
|
| 228 |
-
<!-- Agent cards -->
|
| 229 |
-
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:12px;max-width:500px;margin:0 auto;">
|
| 230 |
-
<div id="ag0" class="agent-card" style="display:flex;align-items:center;gap:10px;">
|
| 231 |
-
<div class="agent-dot"></div>
|
| 232 |
-
<div style="text-align:left;">
|
| 233 |
-
<div style="font-size:11px;font-weight:600;color:#fff;letter-spacing:0.05em;">Frame Extractor</div>
|
| 234 |
-
<div style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Sampling keyframes</div>
|
| 235 |
-
</div>
|
| 236 |
-
</div>
|
| 237 |
-
<div id="ag1" class="agent-card" style="display:flex;align-items:center;gap:10px;">
|
| 238 |
-
<div class="agent-dot"></div>
|
| 239 |
-
<div style="text-align:left;">
|
| 240 |
-
<div style="font-size:11px;font-weight:600;color:#fff;letter-spacing:0.05em;">Face Detector</div>
|
| 241 |
-
<div style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Isolating regions</div>
|
| 242 |
-
</div>
|
| 243 |
-
</div>
|
| 244 |
-
<div id="ag2" class="agent-card" style="display:flex;align-items:center;gap:10px;">
|
| 245 |
-
<div class="agent-dot"></div>
|
| 246 |
-
<div style="text-align:left;">
|
| 247 |
-
<div style="font-size:11px;font-weight:600;color:#fff;letter-spacing:0.05em;">ViT Ensemble</div>
|
| 248 |
-
<div style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Neural inference</div>
|
| 249 |
-
</div>
|
| 250 |
-
</div>
|
| 251 |
-
<div id="ag3" class="agent-card" style="display:flex;align-items:center;gap:10px;">
|
| 252 |
-
<div class="agent-dot"></div>
|
| 253 |
-
<div style="text-align:left;">
|
| 254 |
-
<div style="font-size:11px;font-weight:600;color:#fff;letter-spacing:0.05em;">Report Builder</div>
|
| 255 |
-
<div style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Compiling results</div>
|
| 256 |
-
</div>
|
| 257 |
-
</div>
|
| 258 |
-
</div>
|
| 259 |
-
</section>
|
| 260 |
-
|
| 261 |
-
<!-- ══ RESULT SECTION ══ -->
|
| 262 |
-
<section id="resultSection" class="hidden fade-up" style="display:flex;flex-direction:column;gap:20px;">
|
| 263 |
-
|
| 264 |
-
<!-- Verdict Card -->
|
| 265 |
-
<div id="verdictCard" class="glass" style="padding:40px;display:flex;flex-wrap:wrap;align-items:center;gap:36px;">
|
| 266 |
-
<!-- Badge + label -->
|
| 267 |
-
<div style="display:flex;flex-direction:column;align-items:center;flex-shrink:0;">
|
| 268 |
-
<div id="verdictBadge" class="verdict-badge" style="margin-bottom:14px;background:rgba(5,5,8,0.8);">
|
| 269 |
-
<span id="verdictEmoji" style="font-size:2.5rem;"></span>
|
| 270 |
-
</div>
|
| 271 |
-
<div id="verdictLabel" style="font-size:28px;font-weight:700;letter-spacing:0.15em;text-transform:uppercase;"></div>
|
| 272 |
-
<div style="font-size:9px;color:var(--muted);letter-spacing:0.25em;margin-top:4px;font-family:'JetBrains Mono',monospace;">VERDICT</div>
|
| 273 |
-
</div>
|
| 274 |
-
<!-- Confidence + risk -->
|
| 275 |
-
<div style="flex:1;min-width:220px;">
|
| 276 |
-
<div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:10px;">
|
| 277 |
-
<span style="font-size:10px;color:var(--muted);letter-spacing:0.15em;text-transform:uppercase;">Confidence Score</span>
|
| 278 |
-
<span id="confValue" style="font-size:36px;font-weight:700;font-family:'JetBrains Mono',monospace;"></span>
|
| 279 |
-
</div>
|
| 280 |
-
<div class="conf-track" style="margin-bottom:20px;">
|
| 281 |
-
<div id="confBar" class="conf-fill" style="width:0%;"></div>
|
| 282 |
-
</div>
|
| 283 |
-
<div class="glass-sm" style="padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">
|
| 284 |
-
<span style="font-size:10px;color:var(--muted);letter-spacing:0.15em;text-transform:uppercase;">Risk Assessment</span>
|
| 285 |
-
<span id="riskNeedle"></span>
|
| 286 |
-
</div>
|
| 287 |
-
<div style="margin-top:12px;text-align:right;">
|
| 288 |
-
<span id="riskLabel" style="font-size:11px;color:var(--muted);font-family:'JetBrains Mono',monospace;"></span>
|
| 289 |
-
</div>
|
| 290 |
-
</div>
|
| 291 |
-
</div>
|
| 292 |
-
|
| 293 |
-
<!-- Insights + Meta -->
|
| 294 |
-
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
|
| 295 |
-
<div class="glass" style="padding:24px;">
|
| 296 |
-
<div class="sec-label">Analysis Insights</div>
|
| 297 |
-
<div id="detailsList" style="display:flex;flex-direction:column;gap:10px;"></div>
|
| 298 |
-
</div>
|
| 299 |
-
<div class="glass" style="padding:24px;display:flex;flex-direction:column;justify-content:space-between;">
|
| 300 |
-
<div>
|
| 301 |
-
<div class="sec-label">Video Metadata</div>
|
| 302 |
-
<div id="metaGrid"></div>
|
| 303 |
-
</div>
|
| 304 |
-
<div style="margin-top:16px;padding-top:14px;border-top:1px solid rgba(255,255,255,0.05);display:flex;align-items:center;gap:8px;">
|
| 305 |
-
<div style="width:6px;height:6px;border-radius:50%;background:var(--g);box-shadow:0 0 6px var(--g);"></div>
|
| 306 |
-
<span style="font-size:10px;color:var(--muted);letter-spacing:0.15em;text-transform:uppercase;font-family:'JetBrains Mono',monospace;">Models: ViT Ensemble</span>
|
| 307 |
-
</div>
|
| 308 |
-
</div>
|
| 309 |
-
</div>
|
| 310 |
-
|
| 311 |
-
<!-- Frame Timeline -->
|
| 312 |
-
<div class="glass" style="padding:24px;">
|
| 313 |
-
<div class="sec-label">Frame Analysis Timeline</div>
|
| 314 |
-
<div id="timelineChart" style="display:flex;align-items:flex-end;gap:3px;height:80px;position:relative;padding-bottom:20px;"></div>
|
| 315 |
-
<div style="display:flex;justify-content:space-between;margin-top:8px;">
|
| 316 |
-
<span style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Frame 0</span>
|
| 317 |
-
<span style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;">Frame 40</span>
|
| 318 |
-
</div>
|
| 319 |
-
</div>
|
| 320 |
-
|
| 321 |
-
<!-- ══ AUDIO ANALYSIS CARD ══ -->
|
| 322 |
-
<div id="audioSection" class="hidden glass fade-up" style="padding:28px;">
|
| 323 |
-
<div class="sec-label" style="margin-bottom:18px;">
|
| 324 |
-
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="flex-shrink:0;"><path stroke-linecap="round" stroke-linejoin="round" d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z"/></svg>
|
| 325 |
-
VOICE AUTHENTICITY ANALYSIS
|
| 326 |
-
</div>
|
| 327 |
-
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:20px;margin-bottom:20px;">
|
| 328 |
-
<div id="audioBadge" style="padding:8px 18px;border-radius:99px;font-size:12px;font-weight:700;letter-spacing:0.1em;border:1px solid;transition:all .4s;"></div>
|
| 329 |
-
<div style="flex:1;min-width:160px;">
|
| 330 |
-
<div class="conf-track">
|
| 331 |
-
<div id="audioBar" class="conf-fill" style="width:0%;transition:width 1.2s cubic-bezier(0.22,1,0.36,1);"></div>
|
| 332 |
-
</div>
|
| 333 |
-
</div>
|
| 334 |
-
</div>
|
| 335 |
-
<div style="display:flex;gap:16px;margin-bottom:20px;flex-wrap:wrap;">
|
| 336 |
-
<div class="glass-sm" style="padding:12px 18px;flex:1;min-width:120px;text-align:center;">
|
| 337 |
-
<div style="font-size:10px;color:var(--muted);letter-spacing:.1em;margin-bottom:4px;">WAV2VEC2 MODEL</div>
|
| 338 |
-
<div id="audioModelScore" style="font-size:22px;font-weight:700;font-family:'JetBrains Mono',monospace;color:#fff;"></div>
|
| 339 |
-
</div>
|
| 340 |
-
<div class="glass-sm" style="padding:12px 18px;flex:1;min-width:120px;text-align:center;">
|
| 341 |
-
<div style="font-size:10px;color:var(--muted);letter-spacing:.1em;margin-bottom:4px;">SIGNAL HEURISTICS</div>
|
| 342 |
-
<div id="audioHeurScore" style="font-size:22px;font-weight:700;font-family:'JetBrains Mono',monospace;color:#fff;"></div>
|
| 343 |
-
</div>
|
| 344 |
-
</div>
|
| 345 |
-
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
| 346 |
-
<div>
|
| 347 |
-
<div style="font-size:10px;color:var(--muted);letter-spacing:.12em;margin-bottom:10px;">VOICE INSIGHTS</div>
|
| 348 |
-
<div id="audioDetailsList" style="display:flex;flex-direction:column;gap:8px;"></div>
|
| 349 |
-
</div>
|
| 350 |
-
<div>
|
| 351 |
-
<div style="font-size:10px;color:var(--muted);letter-spacing:.12em;margin-bottom:10px;">SIGNAL FEATURES</div>
|
| 352 |
-
<div id="audioFeatGrid"></div>
|
| 353 |
-
</div>
|
| 354 |
-
</div>
|
| 355 |
-
</div>
|
| 356 |
-
|
| 357 |
-
<!-- Reset -->
|
| 358 |
-
<div style="text-align:center;margin-top:8px;">
|
| 359 |
-
<button onclick="resetAll()" style="padding:12px 28px;font-size:11px;font-weight:700;letter-spacing:0.15em;text-transform:uppercase;color:var(--muted);background:none;border:1px solid rgba(255,255,255,0.1);border-radius:10px;cursor:pointer;transition:all 0.3s;font-family:'Space Grotesk',sans-serif;" onmouseover="this.style.color='var(--g)';this.style.borderColor='rgba(0,255,136,0.4)';this.style.background='rgba(0,255,136,0.05)'" onmouseout="this.style.color='var(--muted)';this.style.borderColor='rgba(255,255,255,0.1)';this.style.background='none'">
|
| 360 |
-
↻ Analyze Another Video
|
| 361 |
-
</button>
|
| 362 |
-
</div>
|
| 363 |
-
</section>
|
| 364 |
-
|
| 365 |
-
<!-- ══ ERROR SECTION ══ -->
|
| 366 |
-
<section id="errorSection" class="hidden glass fade-up" style="padding:28px 32px;margin-bottom:24px;">
|
| 367 |
-
<div style="display:flex;align-items:center;gap:12px;margin-bottom:10px;">
|
| 368 |
-
<svg width="22" height="22" fill="none" stroke="#ff3355" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
|
| 369 |
-
<span style="font-size:12px;font-weight:700;letter-spacing:0.2em;color:#ff3355;text-transform:uppercase;font-family:'JetBrains Mono',monospace;">Analysis Failed</span>
|
| 370 |
-
</div>
|
| 371 |
-
<p id="errorMsg" style="font-size:13px;color:var(--muted);margin-left:34px;line-height:1.6;"></p>
|
| 372 |
-
<div style="margin-left:34px;margin-top:16px;">
|
| 373 |
-
<button onclick="resetAll()" style="font-size:11px;color:rgba(255,255,255,0.4);background:none;border:none;cursor:pointer;letter-spacing:0.15em;text-transform:uppercase;font-family:'Space Grotesk',sans-serif;transition:color 0.2s;" onmouseover="this.style.color='#fff'" onmouseout="this.style.color='rgba(255,255,255,0.4)'">↻ Try Again</button>
|
| 374 |
-
</div>
|
| 375 |
-
</section>
|
| 376 |
-
|
| 377 |
-
</div><!-- /main -->
|
| 378 |
-
|
| 379 |
-
<footer style="position:relative;z-index:1;text-align:center;padding:24px;font-size:10px;color:rgba(90,106,122,0.5);letter-spacing:0.2em;text-transform:uppercase;font-family:'JetBrains Mono',monospace;">
|
| 380 |
-
Deepfake Authenticator · Secure Local Inference · ViT Ensemble v2
|
| 381 |
-
</footer>
|
| 382 |
-
|
| 383 |
-
<script>
|
| 384 |
-
// ── Particle background ──────────────────────────────────────────────────────
|
| 385 |
-
(function(){
|
| 386 |
-
const c=document.getElementById('particles');
|
| 387 |
-
const ctx=c.getContext('2d');
|
| 388 |
-
let W,H,pts=[];
|
| 389 |
-
function resize(){W=c.width=window.innerWidth;H=c.height=window.innerHeight;}
|
| 390 |
-
resize();window.addEventListener('resize',resize);
|
| 391 |
-
for(let i=0;i<60;i++)pts.push({x:Math.random()*W,y:Math.random()*H,vx:(Math.random()-0.5)*0.3,vy:(Math.random()-0.5)*0.3,r:Math.random()*1.5+0.5,a:Math.random()});
|
| 392 |
-
function draw(){
|
| 393 |
-
ctx.clearRect(0,0,W,H);
|
| 394 |
-
pts.forEach(p=>{
|
| 395 |
-
p.x+=p.vx;p.y+=p.vy;
|
| 396 |
-
if(p.x<0||p.x>W)p.vx*=-1;
|
| 397 |
-
if(p.y<0||p.y>H)p.vy*=-1;
|
| 398 |
-
ctx.beginPath();ctx.arc(p.x,p.y,p.r,0,Math.PI*2);
|
| 399 |
-
ctx.fillStyle=`rgba(0,255,136,${0.15+p.a*0.2})`;ctx.fill();
|
| 400 |
-
});
|
| 401 |
-
// draw connections
|
| 402 |
-
for(let i=0;i<pts.length;i++)for(let j=i+1;j<pts.length;j++){
|
| 403 |
-
const dx=pts[i].x-pts[j].x,dy=pts[i].y-pts[j].y,d=Math.sqrt(dx*dx+dy*dy);
|
| 404 |
-
if(d<120){ctx.beginPath();ctx.moveTo(pts[i].x,pts[i].y);ctx.lineTo(pts[j].x,pts[j].y);ctx.strokeStyle=`rgba(0,255,136,${0.06*(1-d/120)})`;ctx.lineWidth=0.5;ctx.stroke();}
|
| 405 |
-
}
|
| 406 |
-
requestAnimationFrame(draw);
|
| 407 |
-
}
|
| 408 |
-
draw();
|
| 409 |
-
})();
|
| 410 |
-
</script>
|
| 411 |
-
<script src="script.js?v=3"></script>
|
| 412 |
-
</body>
|
| 413 |
-
</html>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Deepfake Authenticator</title>
|
| 7 |
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<div id="root"></div>
|
| 12 |
+
<script type="module" src="/src/main.tsx"></script>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/package-lock.json
CHANGED
|
@@ -8,13 +8,18 @@
|
|
| 8 |
"name": "frontend",
|
| 9 |
"version": "0.0.0",
|
| 10 |
"dependencies": {
|
|
|
|
|
|
|
| 11 |
"@tailwindcss/vite": "^4.2.4",
|
| 12 |
"autoprefixer": "^10.5.0",
|
|
|
|
| 13 |
"postcss": "^8.5.10",
|
| 14 |
"react": "^19.2.5",
|
| 15 |
"react-dom": "^19.2.5",
|
| 16 |
"styled-components": "^6.4.1",
|
| 17 |
-
"tailwindcss": "^4.2.4"
|
|
|
|
|
|
|
| 18 |
},
|
| 19 |
"devDependencies": {
|
| 20 |
"@eslint/js": "^10.0.1",
|
|
@@ -220,6 +225,15 @@
|
|
| 220 |
"node": ">=6.0.0"
|
| 221 |
}
|
| 222 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
"node_modules/@babel/template": {
|
| 224 |
"version": "7.28.6",
|
| 225 |
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
|
@@ -268,6 +282,12 @@
|
|
| 268 |
"node": ">=6.9.0"
|
| 269 |
}
|
| 270 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
"node_modules/@emnapi/core": {
|
| 272 |
"version": "1.10.0",
|
| 273 |
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
|
@@ -553,6 +573,24 @@
|
|
| 553 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 554 |
}
|
| 555 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
"node_modules/@napi-rs/wasm-runtime": {
|
| 557 |
"version": "1.1.4",
|
| 558 |
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
|
@@ -575,11 +613,100 @@
|
|
| 575 |
"version": "0.127.0",
|
| 576 |
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
|
| 577 |
"integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
|
|
|
|
| 578 |
"license": "MIT",
|
| 579 |
"funding": {
|
| 580 |
"url": "https://github.com/sponsors/Boshen"
|
| 581 |
}
|
| 582 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 583 |
"node_modules/@rolldown/binding-android-arm64": {
|
| 584 |
"version": "1.0.0-rc.17",
|
| 585 |
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
|
|
@@ -587,6 +714,7 @@
|
|
| 587 |
"cpu": [
|
| 588 |
"arm64"
|
| 589 |
],
|
|
|
|
| 590 |
"license": "MIT",
|
| 591 |
"optional": true,
|
| 592 |
"os": [
|
|
@@ -603,6 +731,7 @@
|
|
| 603 |
"cpu": [
|
| 604 |
"arm64"
|
| 605 |
],
|
|
|
|
| 606 |
"license": "MIT",
|
| 607 |
"optional": true,
|
| 608 |
"os": [
|
|
@@ -619,6 +748,7 @@
|
|
| 619 |
"cpu": [
|
| 620 |
"x64"
|
| 621 |
],
|
|
|
|
| 622 |
"license": "MIT",
|
| 623 |
"optional": true,
|
| 624 |
"os": [
|
|
@@ -635,6 +765,7 @@
|
|
| 635 |
"cpu": [
|
| 636 |
"x64"
|
| 637 |
],
|
|
|
|
| 638 |
"license": "MIT",
|
| 639 |
"optional": true,
|
| 640 |
"os": [
|
|
@@ -651,6 +782,7 @@
|
|
| 651 |
"cpu": [
|
| 652 |
"arm"
|
| 653 |
],
|
|
|
|
| 654 |
"license": "MIT",
|
| 655 |
"optional": true,
|
| 656 |
"os": [
|
|
@@ -667,6 +799,7 @@
|
|
| 667 |
"cpu": [
|
| 668 |
"arm64"
|
| 669 |
],
|
|
|
|
| 670 |
"license": "MIT",
|
| 671 |
"optional": true,
|
| 672 |
"os": [
|
|
@@ -683,6 +816,7 @@
|
|
| 683 |
"cpu": [
|
| 684 |
"arm64"
|
| 685 |
],
|
|
|
|
| 686 |
"license": "MIT",
|
| 687 |
"optional": true,
|
| 688 |
"os": [
|
|
@@ -699,6 +833,7 @@
|
|
| 699 |
"cpu": [
|
| 700 |
"ppc64"
|
| 701 |
],
|
|
|
|
| 702 |
"license": "MIT",
|
| 703 |
"optional": true,
|
| 704 |
"os": [
|
|
@@ -715,6 +850,7 @@
|
|
| 715 |
"cpu": [
|
| 716 |
"s390x"
|
| 717 |
],
|
|
|
|
| 718 |
"license": "MIT",
|
| 719 |
"optional": true,
|
| 720 |
"os": [
|
|
@@ -731,6 +867,7 @@
|
|
| 731 |
"cpu": [
|
| 732 |
"x64"
|
| 733 |
],
|
|
|
|
| 734 |
"license": "MIT",
|
| 735 |
"optional": true,
|
| 736 |
"os": [
|
|
@@ -747,6 +884,7 @@
|
|
| 747 |
"cpu": [
|
| 748 |
"x64"
|
| 749 |
],
|
|
|
|
| 750 |
"license": "MIT",
|
| 751 |
"optional": true,
|
| 752 |
"os": [
|
|
@@ -763,6 +901,7 @@
|
|
| 763 |
"cpu": [
|
| 764 |
"arm64"
|
| 765 |
],
|
|
|
|
| 766 |
"license": "MIT",
|
| 767 |
"optional": true,
|
| 768 |
"os": [
|
|
@@ -779,6 +918,7 @@
|
|
| 779 |
"cpu": [
|
| 780 |
"wasm32"
|
| 781 |
],
|
|
|
|
| 782 |
"license": "MIT",
|
| 783 |
"optional": true,
|
| 784 |
"dependencies": {
|
|
@@ -797,6 +937,7 @@
|
|
| 797 |
"cpu": [
|
| 798 |
"arm64"
|
| 799 |
],
|
|
|
|
| 800 |
"license": "MIT",
|
| 801 |
"optional": true,
|
| 802 |
"os": [
|
|
@@ -813,6 +954,7 @@
|
|
| 813 |
"cpu": [
|
| 814 |
"x64"
|
| 815 |
],
|
|
|
|
| 816 |
"license": "MIT",
|
| 817 |
"optional": true,
|
| 818 |
"os": [
|
|
@@ -1086,6 +1228,12 @@
|
|
| 1086 |
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
| 1087 |
}
|
| 1088 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1089 |
"node_modules/@tybys/wasm-util": {
|
| 1090 |
"version": "0.10.1",
|
| 1091 |
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
|
@@ -1096,6 +1244,12 @@
|
|
| 1096 |
"tslib": "^2.4.0"
|
| 1097 |
}
|
| 1098 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1099 |
"node_modules/@types/esrecurse": {
|
| 1100 |
"version": "4.3.1",
|
| 1101 |
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
|
@@ -1117,6 +1271,12 @@
|
|
| 1117 |
"dev": true,
|
| 1118 |
"license": "MIT"
|
| 1119 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1120 |
"node_modules/@types/react": {
|
| 1121 |
"version": "19.2.14",
|
| 1122 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
|
@@ -1137,6 +1297,59 @@
|
|
| 1137 |
"@types/react": "^19.2.0"
|
| 1138 |
}
|
| 1139 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1140 |
"node_modules/@vitejs/plugin-react": {
|
| 1141 |
"version": "6.0.1",
|
| 1142 |
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
|
|
@@ -1249,6 +1462,26 @@
|
|
| 1249 |
"node": "18 || 20 || >=22"
|
| 1250 |
}
|
| 1251 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1252 |
"node_modules/baseline-browser-mapping": {
|
| 1253 |
"version": "2.10.21",
|
| 1254 |
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz",
|
|
@@ -1261,6 +1494,15 @@
|
|
| 1261 |
"node": ">=6.0.0"
|
| 1262 |
}
|
| 1263 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1264 |
"node_modules/brace-expansion": {
|
| 1265 |
"version": "5.0.5",
|
| 1266 |
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
|
@@ -1307,6 +1549,30 @@
|
|
| 1307 |
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1308 |
}
|
| 1309 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1310 |
"node_modules/camelize": {
|
| 1311 |
"version": "1.0.1",
|
| 1312 |
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
|
@@ -1316,6 +1582,19 @@
|
|
| 1316 |
"url": "https://github.com/sponsors/ljharb"
|
| 1317 |
}
|
| 1318 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1319 |
"node_modules/caniuse-lite": {
|
| 1320 |
"version": "1.0.30001790",
|
| 1321 |
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz",
|
|
@@ -1343,11 +1622,28 @@
|
|
| 1343 |
"dev": true,
|
| 1344 |
"license": "MIT"
|
| 1345 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1346 |
"node_modules/cross-spawn": {
|
| 1347 |
"version": "7.0.6",
|
| 1348 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
| 1349 |
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
| 1350 |
-
"dev": true,
|
| 1351 |
"license": "MIT",
|
| 1352 |
"dependencies": {
|
| 1353 |
"path-key": "^3.1.0",
|
|
@@ -1409,6 +1705,15 @@
|
|
| 1409 |
"dev": true,
|
| 1410 |
"license": "MIT"
|
| 1411 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1412 |
"node_modules/detect-libc": {
|
| 1413 |
"version": "2.1.2",
|
| 1414 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
|
@@ -1418,6 +1723,12 @@
|
|
| 1418 |
"node": ">=8"
|
| 1419 |
}
|
| 1420 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1421 |
"node_modules/electron-to-chromium": {
|
| 1422 |
"version": "1.5.344",
|
| 1423 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz",
|
|
@@ -1666,6 +1977,7 @@
|
|
| 1666 |
"version": "6.5.0",
|
| 1667 |
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1668 |
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
|
|
|
| 1669 |
"license": "MIT",
|
| 1670 |
"engines": {
|
| 1671 |
"node": ">=12.0.0"
|
|
@@ -1679,6 +1991,12 @@
|
|
| 1679 |
}
|
| 1680 |
}
|
| 1681 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1682 |
"node_modules/file-entry-cache": {
|
| 1683 |
"version": "8.0.0",
|
| 1684 |
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
|
@@ -1743,10 +2061,38 @@
|
|
| 1743 |
"url": "https://github.com/sponsors/rawify"
|
| 1744 |
}
|
| 1745 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1746 |
"node_modules/fsevents": {
|
| 1747 |
"version": "2.3.3",
|
| 1748 |
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1749 |
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
|
|
|
| 1750 |
"hasInstallScript": true,
|
| 1751 |
"license": "MIT",
|
| 1752 |
"optional": true,
|
|
@@ -1793,6 +2139,12 @@
|
|
| 1793 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 1794 |
}
|
| 1795 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1796 |
"node_modules/graceful-fs": {
|
| 1797 |
"version": "4.2.11",
|
| 1798 |
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
|
@@ -1816,6 +2168,32 @@
|
|
| 1816 |
"hermes-estree": "0.25.1"
|
| 1817 |
}
|
| 1818 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1819 |
"node_modules/ignore": {
|
| 1820 |
"version": "5.3.2",
|
| 1821 |
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
|
@@ -1826,6 +2204,12 @@
|
|
| 1826 |
"node": ">= 4"
|
| 1827 |
}
|
| 1828 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1829 |
"node_modules/imurmurhash": {
|
| 1830 |
"version": "0.1.4",
|
| 1831 |
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
|
@@ -1859,13 +2243,30 @@
|
|
| 1859 |
"node": ">=0.10.0"
|
| 1860 |
}
|
| 1861 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1862 |
"node_modules/isexe": {
|
| 1863 |
"version": "2.0.0",
|
| 1864 |
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
| 1865 |
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
| 1866 |
-
"dev": true,
|
| 1867 |
"license": "ISC"
|
| 1868 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1869 |
"node_modules/jiti": {
|
| 1870 |
"version": "2.6.1",
|
| 1871 |
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
|
@@ -1953,6 +2354,15 @@
|
|
| 1953 |
"node": ">= 0.8.0"
|
| 1954 |
}
|
| 1955 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1956 |
"node_modules/lightningcss": {
|
| 1957 |
"version": "1.32.0",
|
| 1958 |
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
|
@@ -2228,6 +2638,16 @@
|
|
| 2228 |
"yallist": "^3.0.2"
|
| 2229 |
}
|
| 2230 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2231 |
"node_modules/magic-string": {
|
| 2232 |
"version": "0.30.21",
|
| 2233 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
|
@@ -2237,6 +2657,21 @@
|
|
| 2237 |
"@jridgewell/sourcemap-codec": "^1.5.5"
|
| 2238 |
}
|
| 2239 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2240 |
"node_modules/minimatch": {
|
| 2241 |
"version": "10.2.5",
|
| 2242 |
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
|
@@ -2253,6 +2688,21 @@
|
|
| 2253 |
"url": "https://github.com/sponsors/isaacs"
|
| 2254 |
}
|
| 2255 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2256 |
"node_modules/ms": {
|
| 2257 |
"version": "2.1.3",
|
| 2258 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
|
@@ -2355,7 +2805,6 @@
|
|
| 2355 |
"version": "3.1.1",
|
| 2356 |
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
| 2357 |
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
| 2358 |
-
"dev": true,
|
| 2359 |
"license": "MIT",
|
| 2360 |
"engines": {
|
| 2361 |
"node": ">=8"
|
|
@@ -2371,6 +2820,7 @@
|
|
| 2371 |
"version": "4.0.4",
|
| 2372 |
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 2373 |
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
|
|
|
| 2374 |
"license": "MIT",
|
| 2375 |
"engines": {
|
| 2376 |
"node": ">=12"
|
|
@@ -2413,6 +2863,12 @@
|
|
| 2413 |
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
| 2414 |
"license": "MIT"
|
| 2415 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2416 |
"node_modules/prelude-ls": {
|
| 2417 |
"version": "1.2.1",
|
| 2418 |
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
|
@@ -2423,6 +2879,16 @@
|
|
| 2423 |
"node": ">= 0.8.0"
|
| 2424 |
}
|
| 2425 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2426 |
"node_modules/punycode": {
|
| 2427 |
"version": "2.3.1",
|
| 2428 |
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
|
@@ -2454,10 +2920,35 @@
|
|
| 2454 |
"react": "^19.2.5"
|
| 2455 |
}
|
| 2456 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2457 |
"node_modules/rolldown": {
|
| 2458 |
"version": "1.0.0-rc.17",
|
| 2459 |
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
|
| 2460 |
"integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
|
|
|
|
| 2461 |
"license": "MIT",
|
| 2462 |
"dependencies": {
|
| 2463 |
"@oxc-project/types": "=0.127.0",
|
|
@@ -2491,6 +2982,7 @@
|
|
| 2491 |
"version": "1.0.0-rc.17",
|
| 2492 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
|
| 2493 |
"integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
|
|
|
|
| 2494 |
"license": "MIT"
|
| 2495 |
},
|
| 2496 |
"node_modules/scheduler": {
|
|
@@ -2513,7 +3005,6 @@
|
|
| 2513 |
"version": "2.0.0",
|
| 2514 |
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
| 2515 |
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
| 2516 |
-
"dev": true,
|
| 2517 |
"license": "MIT",
|
| 2518 |
"dependencies": {
|
| 2519 |
"shebang-regex": "^3.0.0"
|
|
@@ -2526,7 +3017,6 @@
|
|
| 2526 |
"version": "3.0.0",
|
| 2527 |
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
| 2528 |
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
| 2529 |
-
"dev": true,
|
| 2530 |
"license": "MIT",
|
| 2531 |
"engines": {
|
| 2532 |
"node": ">=8"
|
|
@@ -2541,6 +3031,32 @@
|
|
| 2541 |
"node": ">=0.10.0"
|
| 2542 |
}
|
| 2543 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2544 |
"node_modules/styled-components": {
|
| 2545 |
"version": "6.4.1",
|
| 2546 |
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.4.1.tgz",
|
|
@@ -2583,6 +3099,15 @@
|
|
| 2583 |
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
|
| 2584 |
"license": "MIT"
|
| 2585 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2586 |
"node_modules/tailwindcss": {
|
| 2587 |
"version": "4.2.4",
|
| 2588 |
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
|
|
@@ -2602,10 +3127,49 @@
|
|
| 2602 |
"url": "https://opencollective.com/webpack"
|
| 2603 |
}
|
| 2604 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2605 |
"node_modules/tinyglobby": {
|
| 2606 |
"version": "0.2.16",
|
| 2607 |
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
| 2608 |
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
|
|
|
| 2609 |
"license": "MIT",
|
| 2610 |
"dependencies": {
|
| 2611 |
"fdir": "^6.5.0",
|
|
@@ -2618,12 +3182,78 @@
|
|
| 2618 |
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 2619 |
}
|
| 2620 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2621 |
"node_modules/tslib": {
|
| 2622 |
"version": "2.8.1",
|
| 2623 |
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2624 |
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 2625 |
-
"license": "0BSD"
|
| 2626 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2627 |
},
|
| 2628 |
"node_modules/type-check": {
|
| 2629 |
"version": "0.4.0",
|
|
@@ -2678,10 +3308,29 @@
|
|
| 2678 |
"punycode": "^2.1.0"
|
| 2679 |
}
|
| 2680 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2681 |
"node_modules/vite": {
|
| 2682 |
"version": "8.0.10",
|
| 2683 |
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
|
| 2684 |
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
|
|
|
|
| 2685 |
"license": "MIT",
|
| 2686 |
"dependencies": {
|
| 2687 |
"lightningcss": "^1.32.0",
|
|
@@ -2755,11 +3404,21 @@
|
|
| 2755 |
}
|
| 2756 |
}
|
| 2757 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2758 |
"node_modules/which": {
|
| 2759 |
"version": "2.0.2",
|
| 2760 |
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
| 2761 |
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
| 2762 |
-
"dev": true,
|
| 2763 |
"license": "ISC",
|
| 2764 |
"dependencies": {
|
| 2765 |
"isexe": "^2.0.0"
|
|
@@ -2823,6 +3482,35 @@
|
|
| 2823 |
"peerDependencies": {
|
| 2824 |
"zod": "^3.25.0 || ^4.0.0"
|
| 2825 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2826 |
}
|
| 2827 |
}
|
| 2828 |
}
|
|
|
|
| 8 |
"name": "frontend",
|
| 9 |
"version": "0.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
+
"@react-three/drei": "^10.7.7",
|
| 12 |
+
"@react-three/fiber": "^9.6.0",
|
| 13 |
"@tailwindcss/vite": "^4.2.4",
|
| 14 |
"autoprefixer": "^10.5.0",
|
| 15 |
+
"framer-motion": "^12.38.0",
|
| 16 |
"postcss": "^8.5.10",
|
| 17 |
"react": "^19.2.5",
|
| 18 |
"react-dom": "^19.2.5",
|
| 19 |
"styled-components": "^6.4.1",
|
| 20 |
+
"tailwindcss": "^4.2.4",
|
| 21 |
+
"three": "^0.184.0",
|
| 22 |
+
"zustand": "^5.0.12"
|
| 23 |
},
|
| 24 |
"devDependencies": {
|
| 25 |
"@eslint/js": "^10.0.1",
|
|
|
|
| 225 |
"node": ">=6.0.0"
|
| 226 |
}
|
| 227 |
},
|
| 228 |
+
"node_modules/@babel/runtime": {
|
| 229 |
+
"version": "7.29.2",
|
| 230 |
+
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
| 231 |
+
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
| 232 |
+
"license": "MIT",
|
| 233 |
+
"engines": {
|
| 234 |
+
"node": ">=6.9.0"
|
| 235 |
+
}
|
| 236 |
+
},
|
| 237 |
"node_modules/@babel/template": {
|
| 238 |
"version": "7.28.6",
|
| 239 |
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
|
|
|
|
| 282 |
"node": ">=6.9.0"
|
| 283 |
}
|
| 284 |
},
|
| 285 |
+
"node_modules/@dimforge/rapier3d-compat": {
|
| 286 |
+
"version": "0.12.0",
|
| 287 |
+
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
|
| 288 |
+
"integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==",
|
| 289 |
+
"license": "Apache-2.0"
|
| 290 |
+
},
|
| 291 |
"node_modules/@emnapi/core": {
|
| 292 |
"version": "1.10.0",
|
| 293 |
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
|
|
|
|
| 573 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 574 |
}
|
| 575 |
},
|
| 576 |
+
"node_modules/@mediapipe/tasks-vision": {
|
| 577 |
+
"version": "0.10.17",
|
| 578 |
+
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz",
|
| 579 |
+
"integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==",
|
| 580 |
+
"license": "Apache-2.0"
|
| 581 |
+
},
|
| 582 |
+
"node_modules/@monogrid/gainmap-js": {
|
| 583 |
+
"version": "3.4.0",
|
| 584 |
+
"resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz",
|
| 585 |
+
"integrity": "sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==",
|
| 586 |
+
"license": "MIT",
|
| 587 |
+
"dependencies": {
|
| 588 |
+
"promise-worker-transferable": "^1.0.4"
|
| 589 |
+
},
|
| 590 |
+
"peerDependencies": {
|
| 591 |
+
"three": ">= 0.159.0"
|
| 592 |
+
}
|
| 593 |
+
},
|
| 594 |
"node_modules/@napi-rs/wasm-runtime": {
|
| 595 |
"version": "1.1.4",
|
| 596 |
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
|
|
|
| 613 |
"version": "0.127.0",
|
| 614 |
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
|
| 615 |
"integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
|
| 616 |
+
"dev": true,
|
| 617 |
"license": "MIT",
|
| 618 |
"funding": {
|
| 619 |
"url": "https://github.com/sponsors/Boshen"
|
| 620 |
}
|
| 621 |
},
|
| 622 |
+
"node_modules/@react-three/drei": {
|
| 623 |
+
"version": "10.7.7",
|
| 624 |
+
"resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-10.7.7.tgz",
|
| 625 |
+
"integrity": "sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==",
|
| 626 |
+
"license": "MIT",
|
| 627 |
+
"dependencies": {
|
| 628 |
+
"@babel/runtime": "^7.26.0",
|
| 629 |
+
"@mediapipe/tasks-vision": "0.10.17",
|
| 630 |
+
"@monogrid/gainmap-js": "^3.0.6",
|
| 631 |
+
"@use-gesture/react": "^10.3.1",
|
| 632 |
+
"camera-controls": "^3.1.0",
|
| 633 |
+
"cross-env": "^7.0.3",
|
| 634 |
+
"detect-gpu": "^5.0.56",
|
| 635 |
+
"glsl-noise": "^0.0.0",
|
| 636 |
+
"hls.js": "^1.5.17",
|
| 637 |
+
"maath": "^0.10.8",
|
| 638 |
+
"meshline": "^3.3.1",
|
| 639 |
+
"stats-gl": "^2.2.8",
|
| 640 |
+
"stats.js": "^0.17.0",
|
| 641 |
+
"suspend-react": "^0.1.3",
|
| 642 |
+
"three-mesh-bvh": "^0.8.3",
|
| 643 |
+
"three-stdlib": "^2.35.6",
|
| 644 |
+
"troika-three-text": "^0.52.4",
|
| 645 |
+
"tunnel-rat": "^0.1.2",
|
| 646 |
+
"use-sync-external-store": "^1.4.0",
|
| 647 |
+
"utility-types": "^3.11.0",
|
| 648 |
+
"zustand": "^5.0.1"
|
| 649 |
+
},
|
| 650 |
+
"peerDependencies": {
|
| 651 |
+
"@react-three/fiber": "^9.0.0",
|
| 652 |
+
"react": "^19",
|
| 653 |
+
"react-dom": "^19",
|
| 654 |
+
"three": ">=0.159"
|
| 655 |
+
},
|
| 656 |
+
"peerDependenciesMeta": {
|
| 657 |
+
"react-dom": {
|
| 658 |
+
"optional": true
|
| 659 |
+
}
|
| 660 |
+
}
|
| 661 |
+
},
|
| 662 |
+
"node_modules/@react-three/fiber": {
|
| 663 |
+
"version": "9.6.0",
|
| 664 |
+
"resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.0.tgz",
|
| 665 |
+
"integrity": "sha512-90abYK2q5/qDM+GACs9zRvc5KhEEpEWqWlHSd64zTPNxg+9wCJvTfyD9x2so7hlQhjRYO1Fa6flR3BC/kpTFkA==",
|
| 666 |
+
"license": "MIT",
|
| 667 |
+
"dependencies": {
|
| 668 |
+
"@babel/runtime": "^7.17.8",
|
| 669 |
+
"@types/webxr": "*",
|
| 670 |
+
"base64-js": "^1.5.1",
|
| 671 |
+
"buffer": "^6.0.3",
|
| 672 |
+
"its-fine": "^2.0.0",
|
| 673 |
+
"react-use-measure": "^2.1.7",
|
| 674 |
+
"scheduler": "^0.27.0",
|
| 675 |
+
"suspend-react": "^0.1.3",
|
| 676 |
+
"use-sync-external-store": "^1.4.0",
|
| 677 |
+
"zustand": "^5.0.3"
|
| 678 |
+
},
|
| 679 |
+
"peerDependencies": {
|
| 680 |
+
"expo": ">=43.0",
|
| 681 |
+
"expo-asset": ">=8.4",
|
| 682 |
+
"expo-file-system": ">=11.0",
|
| 683 |
+
"expo-gl": ">=11.0",
|
| 684 |
+
"react": ">=19 <19.3",
|
| 685 |
+
"react-dom": ">=19 <19.3",
|
| 686 |
+
"react-native": ">=0.78",
|
| 687 |
+
"three": ">=0.156"
|
| 688 |
+
},
|
| 689 |
+
"peerDependenciesMeta": {
|
| 690 |
+
"expo": {
|
| 691 |
+
"optional": true
|
| 692 |
+
},
|
| 693 |
+
"expo-asset": {
|
| 694 |
+
"optional": true
|
| 695 |
+
},
|
| 696 |
+
"expo-file-system": {
|
| 697 |
+
"optional": true
|
| 698 |
+
},
|
| 699 |
+
"expo-gl": {
|
| 700 |
+
"optional": true
|
| 701 |
+
},
|
| 702 |
+
"react-dom": {
|
| 703 |
+
"optional": true
|
| 704 |
+
},
|
| 705 |
+
"react-native": {
|
| 706 |
+
"optional": true
|
| 707 |
+
}
|
| 708 |
+
}
|
| 709 |
+
},
|
| 710 |
"node_modules/@rolldown/binding-android-arm64": {
|
| 711 |
"version": "1.0.0-rc.17",
|
| 712 |
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
|
|
|
|
| 714 |
"cpu": [
|
| 715 |
"arm64"
|
| 716 |
],
|
| 717 |
+
"dev": true,
|
| 718 |
"license": "MIT",
|
| 719 |
"optional": true,
|
| 720 |
"os": [
|
|
|
|
| 731 |
"cpu": [
|
| 732 |
"arm64"
|
| 733 |
],
|
| 734 |
+
"dev": true,
|
| 735 |
"license": "MIT",
|
| 736 |
"optional": true,
|
| 737 |
"os": [
|
|
|
|
| 748 |
"cpu": [
|
| 749 |
"x64"
|
| 750 |
],
|
| 751 |
+
"dev": true,
|
| 752 |
"license": "MIT",
|
| 753 |
"optional": true,
|
| 754 |
"os": [
|
|
|
|
| 765 |
"cpu": [
|
| 766 |
"x64"
|
| 767 |
],
|
| 768 |
+
"dev": true,
|
| 769 |
"license": "MIT",
|
| 770 |
"optional": true,
|
| 771 |
"os": [
|
|
|
|
| 782 |
"cpu": [
|
| 783 |
"arm"
|
| 784 |
],
|
| 785 |
+
"dev": true,
|
| 786 |
"license": "MIT",
|
| 787 |
"optional": true,
|
| 788 |
"os": [
|
|
|
|
| 799 |
"cpu": [
|
| 800 |
"arm64"
|
| 801 |
],
|
| 802 |
+
"dev": true,
|
| 803 |
"license": "MIT",
|
| 804 |
"optional": true,
|
| 805 |
"os": [
|
|
|
|
| 816 |
"cpu": [
|
| 817 |
"arm64"
|
| 818 |
],
|
| 819 |
+
"dev": true,
|
| 820 |
"license": "MIT",
|
| 821 |
"optional": true,
|
| 822 |
"os": [
|
|
|
|
| 833 |
"cpu": [
|
| 834 |
"ppc64"
|
| 835 |
],
|
| 836 |
+
"dev": true,
|
| 837 |
"license": "MIT",
|
| 838 |
"optional": true,
|
| 839 |
"os": [
|
|
|
|
| 850 |
"cpu": [
|
| 851 |
"s390x"
|
| 852 |
],
|
| 853 |
+
"dev": true,
|
| 854 |
"license": "MIT",
|
| 855 |
"optional": true,
|
| 856 |
"os": [
|
|
|
|
| 867 |
"cpu": [
|
| 868 |
"x64"
|
| 869 |
],
|
| 870 |
+
"dev": true,
|
| 871 |
"license": "MIT",
|
| 872 |
"optional": true,
|
| 873 |
"os": [
|
|
|
|
| 884 |
"cpu": [
|
| 885 |
"x64"
|
| 886 |
],
|
| 887 |
+
"dev": true,
|
| 888 |
"license": "MIT",
|
| 889 |
"optional": true,
|
| 890 |
"os": [
|
|
|
|
| 901 |
"cpu": [
|
| 902 |
"arm64"
|
| 903 |
],
|
| 904 |
+
"dev": true,
|
| 905 |
"license": "MIT",
|
| 906 |
"optional": true,
|
| 907 |
"os": [
|
|
|
|
| 918 |
"cpu": [
|
| 919 |
"wasm32"
|
| 920 |
],
|
| 921 |
+
"dev": true,
|
| 922 |
"license": "MIT",
|
| 923 |
"optional": true,
|
| 924 |
"dependencies": {
|
|
|
|
| 937 |
"cpu": [
|
| 938 |
"arm64"
|
| 939 |
],
|
| 940 |
+
"dev": true,
|
| 941 |
"license": "MIT",
|
| 942 |
"optional": true,
|
| 943 |
"os": [
|
|
|
|
| 954 |
"cpu": [
|
| 955 |
"x64"
|
| 956 |
],
|
| 957 |
+
"dev": true,
|
| 958 |
"license": "MIT",
|
| 959 |
"optional": true,
|
| 960 |
"os": [
|
|
|
|
| 1228 |
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
| 1229 |
}
|
| 1230 |
},
|
| 1231 |
+
"node_modules/@tweenjs/tween.js": {
|
| 1232 |
+
"version": "23.1.3",
|
| 1233 |
+
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
| 1234 |
+
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
|
| 1235 |
+
"license": "MIT"
|
| 1236 |
+
},
|
| 1237 |
"node_modules/@tybys/wasm-util": {
|
| 1238 |
"version": "0.10.1",
|
| 1239 |
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
|
|
|
| 1244 |
"tslib": "^2.4.0"
|
| 1245 |
}
|
| 1246 |
},
|
| 1247 |
+
"node_modules/@types/draco3d": {
|
| 1248 |
+
"version": "1.4.10",
|
| 1249 |
+
"resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz",
|
| 1250 |
+
"integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==",
|
| 1251 |
+
"license": "MIT"
|
| 1252 |
+
},
|
| 1253 |
"node_modules/@types/esrecurse": {
|
| 1254 |
"version": "4.3.1",
|
| 1255 |
"resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
|
|
|
|
| 1271 |
"dev": true,
|
| 1272 |
"license": "MIT"
|
| 1273 |
},
|
| 1274 |
+
"node_modules/@types/offscreencanvas": {
|
| 1275 |
+
"version": "2019.7.3",
|
| 1276 |
+
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
|
| 1277 |
+
"integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
|
| 1278 |
+
"license": "MIT"
|
| 1279 |
+
},
|
| 1280 |
"node_modules/@types/react": {
|
| 1281 |
"version": "19.2.14",
|
| 1282 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
|
|
|
| 1297 |
"@types/react": "^19.2.0"
|
| 1298 |
}
|
| 1299 |
},
|
| 1300 |
+
"node_modules/@types/react-reconciler": {
|
| 1301 |
+
"version": "0.28.9",
|
| 1302 |
+
"resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
|
| 1303 |
+
"integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
|
| 1304 |
+
"license": "MIT",
|
| 1305 |
+
"peerDependencies": {
|
| 1306 |
+
"@types/react": "*"
|
| 1307 |
+
}
|
| 1308 |
+
},
|
| 1309 |
+
"node_modules/@types/stats.js": {
|
| 1310 |
+
"version": "0.17.4",
|
| 1311 |
+
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
|
| 1312 |
+
"integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==",
|
| 1313 |
+
"license": "MIT"
|
| 1314 |
+
},
|
| 1315 |
+
"node_modules/@types/three": {
|
| 1316 |
+
"version": "0.184.0",
|
| 1317 |
+
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.184.0.tgz",
|
| 1318 |
+
"integrity": "sha512-4mY2tZAu0y0B0567w7013BBXSpsP0+Z48NJvmNo4Y/Pf76yCyz6Jw4P3tUVs10WuYNXXZ+wmHyGWpCek3amJxA==",
|
| 1319 |
+
"license": "MIT",
|
| 1320 |
+
"dependencies": {
|
| 1321 |
+
"@dimforge/rapier3d-compat": "~0.12.0",
|
| 1322 |
+
"@tweenjs/tween.js": "~23.1.3",
|
| 1323 |
+
"@types/stats.js": "*",
|
| 1324 |
+
"@types/webxr": ">=0.5.17",
|
| 1325 |
+
"fflate": "~0.8.2",
|
| 1326 |
+
"meshoptimizer": "~1.1.1"
|
| 1327 |
+
}
|
| 1328 |
+
},
|
| 1329 |
+
"node_modules/@types/webxr": {
|
| 1330 |
+
"version": "0.5.24",
|
| 1331 |
+
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz",
|
| 1332 |
+
"integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==",
|
| 1333 |
+
"license": "MIT"
|
| 1334 |
+
},
|
| 1335 |
+
"node_modules/@use-gesture/core": {
|
| 1336 |
+
"version": "10.3.1",
|
| 1337 |
+
"resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz",
|
| 1338 |
+
"integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==",
|
| 1339 |
+
"license": "MIT"
|
| 1340 |
+
},
|
| 1341 |
+
"node_modules/@use-gesture/react": {
|
| 1342 |
+
"version": "10.3.1",
|
| 1343 |
+
"resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz",
|
| 1344 |
+
"integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==",
|
| 1345 |
+
"license": "MIT",
|
| 1346 |
+
"dependencies": {
|
| 1347 |
+
"@use-gesture/core": "10.3.1"
|
| 1348 |
+
},
|
| 1349 |
+
"peerDependencies": {
|
| 1350 |
+
"react": ">= 16.8.0"
|
| 1351 |
+
}
|
| 1352 |
+
},
|
| 1353 |
"node_modules/@vitejs/plugin-react": {
|
| 1354 |
"version": "6.0.1",
|
| 1355 |
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
|
|
|
|
| 1462 |
"node": "18 || 20 || >=22"
|
| 1463 |
}
|
| 1464 |
},
|
| 1465 |
+
"node_modules/base64-js": {
|
| 1466 |
+
"version": "1.5.1",
|
| 1467 |
+
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
| 1468 |
+
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
| 1469 |
+
"funding": [
|
| 1470 |
+
{
|
| 1471 |
+
"type": "github",
|
| 1472 |
+
"url": "https://github.com/sponsors/feross"
|
| 1473 |
+
},
|
| 1474 |
+
{
|
| 1475 |
+
"type": "patreon",
|
| 1476 |
+
"url": "https://www.patreon.com/feross"
|
| 1477 |
+
},
|
| 1478 |
+
{
|
| 1479 |
+
"type": "consulting",
|
| 1480 |
+
"url": "https://feross.org/support"
|
| 1481 |
+
}
|
| 1482 |
+
],
|
| 1483 |
+
"license": "MIT"
|
| 1484 |
+
},
|
| 1485 |
"node_modules/baseline-browser-mapping": {
|
| 1486 |
"version": "2.10.21",
|
| 1487 |
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.21.tgz",
|
|
|
|
| 1494 |
"node": ">=6.0.0"
|
| 1495 |
}
|
| 1496 |
},
|
| 1497 |
+
"node_modules/bidi-js": {
|
| 1498 |
+
"version": "1.0.3",
|
| 1499 |
+
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
|
| 1500 |
+
"integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
|
| 1501 |
+
"license": "MIT",
|
| 1502 |
+
"dependencies": {
|
| 1503 |
+
"require-from-string": "^2.0.2"
|
| 1504 |
+
}
|
| 1505 |
+
},
|
| 1506 |
"node_modules/brace-expansion": {
|
| 1507 |
"version": "5.0.5",
|
| 1508 |
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
|
|
|
| 1549 |
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
| 1550 |
}
|
| 1551 |
},
|
| 1552 |
+
"node_modules/buffer": {
|
| 1553 |
+
"version": "6.0.3",
|
| 1554 |
+
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
| 1555 |
+
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
| 1556 |
+
"funding": [
|
| 1557 |
+
{
|
| 1558 |
+
"type": "github",
|
| 1559 |
+
"url": "https://github.com/sponsors/feross"
|
| 1560 |
+
},
|
| 1561 |
+
{
|
| 1562 |
+
"type": "patreon",
|
| 1563 |
+
"url": "https://www.patreon.com/feross"
|
| 1564 |
+
},
|
| 1565 |
+
{
|
| 1566 |
+
"type": "consulting",
|
| 1567 |
+
"url": "https://feross.org/support"
|
| 1568 |
+
}
|
| 1569 |
+
],
|
| 1570 |
+
"license": "MIT",
|
| 1571 |
+
"dependencies": {
|
| 1572 |
+
"base64-js": "^1.3.1",
|
| 1573 |
+
"ieee754": "^1.2.1"
|
| 1574 |
+
}
|
| 1575 |
+
},
|
| 1576 |
"node_modules/camelize": {
|
| 1577 |
"version": "1.0.1",
|
| 1578 |
"resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
|
|
|
|
| 1582 |
"url": "https://github.com/sponsors/ljharb"
|
| 1583 |
}
|
| 1584 |
},
|
| 1585 |
+
"node_modules/camera-controls": {
|
| 1586 |
+
"version": "3.1.0",
|
| 1587 |
+
"resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-3.1.0.tgz",
|
| 1588 |
+
"integrity": "sha512-w5oULNpijgTRH0ARFJJ0R5ct1nUM3R3WP7/b8A6j9uTGpRfnsypc/RBMPQV8JQDPayUe37p/TZZY1PcUr4czOQ==",
|
| 1589 |
+
"license": "MIT",
|
| 1590 |
+
"engines": {
|
| 1591 |
+
"node": ">=20.11.0",
|
| 1592 |
+
"npm": ">=10.8.2"
|
| 1593 |
+
},
|
| 1594 |
+
"peerDependencies": {
|
| 1595 |
+
"three": ">=0.126.1"
|
| 1596 |
+
}
|
| 1597 |
+
},
|
| 1598 |
"node_modules/caniuse-lite": {
|
| 1599 |
"version": "1.0.30001790",
|
| 1600 |
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001790.tgz",
|
|
|
|
| 1622 |
"dev": true,
|
| 1623 |
"license": "MIT"
|
| 1624 |
},
|
| 1625 |
+
"node_modules/cross-env": {
|
| 1626 |
+
"version": "7.0.3",
|
| 1627 |
+
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
|
| 1628 |
+
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
|
| 1629 |
+
"license": "MIT",
|
| 1630 |
+
"dependencies": {
|
| 1631 |
+
"cross-spawn": "^7.0.1"
|
| 1632 |
+
},
|
| 1633 |
+
"bin": {
|
| 1634 |
+
"cross-env": "src/bin/cross-env.js",
|
| 1635 |
+
"cross-env-shell": "src/bin/cross-env-shell.js"
|
| 1636 |
+
},
|
| 1637 |
+
"engines": {
|
| 1638 |
+
"node": ">=10.14",
|
| 1639 |
+
"npm": ">=6",
|
| 1640 |
+
"yarn": ">=1"
|
| 1641 |
+
}
|
| 1642 |
+
},
|
| 1643 |
"node_modules/cross-spawn": {
|
| 1644 |
"version": "7.0.6",
|
| 1645 |
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
| 1646 |
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
|
|
|
| 1647 |
"license": "MIT",
|
| 1648 |
"dependencies": {
|
| 1649 |
"path-key": "^3.1.0",
|
|
|
|
| 1705 |
"dev": true,
|
| 1706 |
"license": "MIT"
|
| 1707 |
},
|
| 1708 |
+
"node_modules/detect-gpu": {
|
| 1709 |
+
"version": "5.0.70",
|
| 1710 |
+
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
|
| 1711 |
+
"integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==",
|
| 1712 |
+
"license": "MIT",
|
| 1713 |
+
"dependencies": {
|
| 1714 |
+
"webgl-constants": "^1.1.1"
|
| 1715 |
+
}
|
| 1716 |
+
},
|
| 1717 |
"node_modules/detect-libc": {
|
| 1718 |
"version": "2.1.2",
|
| 1719 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
|
|
|
| 1723 |
"node": ">=8"
|
| 1724 |
}
|
| 1725 |
},
|
| 1726 |
+
"node_modules/draco3d": {
|
| 1727 |
+
"version": "1.5.7",
|
| 1728 |
+
"resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz",
|
| 1729 |
+
"integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==",
|
| 1730 |
+
"license": "Apache-2.0"
|
| 1731 |
+
},
|
| 1732 |
"node_modules/electron-to-chromium": {
|
| 1733 |
"version": "1.5.344",
|
| 1734 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz",
|
|
|
|
| 1977 |
"version": "6.5.0",
|
| 1978 |
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1979 |
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1980 |
+
"dev": true,
|
| 1981 |
"license": "MIT",
|
| 1982 |
"engines": {
|
| 1983 |
"node": ">=12.0.0"
|
|
|
|
| 1991 |
}
|
| 1992 |
}
|
| 1993 |
},
|
| 1994 |
+
"node_modules/fflate": {
|
| 1995 |
+
"version": "0.8.2",
|
| 1996 |
+
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
| 1997 |
+
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
| 1998 |
+
"license": "MIT"
|
| 1999 |
+
},
|
| 2000 |
"node_modules/file-entry-cache": {
|
| 2001 |
"version": "8.0.0",
|
| 2002 |
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
|
|
|
| 2061 |
"url": "https://github.com/sponsors/rawify"
|
| 2062 |
}
|
| 2063 |
},
|
| 2064 |
+
"node_modules/framer-motion": {
|
| 2065 |
+
"version": "12.38.0",
|
| 2066 |
+
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
|
| 2067 |
+
"integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
|
| 2068 |
+
"license": "MIT",
|
| 2069 |
+
"dependencies": {
|
| 2070 |
+
"motion-dom": "^12.38.0",
|
| 2071 |
+
"motion-utils": "^12.36.0",
|
| 2072 |
+
"tslib": "^2.4.0"
|
| 2073 |
+
},
|
| 2074 |
+
"peerDependencies": {
|
| 2075 |
+
"@emotion/is-prop-valid": "*",
|
| 2076 |
+
"react": "^18.0.0 || ^19.0.0",
|
| 2077 |
+
"react-dom": "^18.0.0 || ^19.0.0"
|
| 2078 |
+
},
|
| 2079 |
+
"peerDependenciesMeta": {
|
| 2080 |
+
"@emotion/is-prop-valid": {
|
| 2081 |
+
"optional": true
|
| 2082 |
+
},
|
| 2083 |
+
"react": {
|
| 2084 |
+
"optional": true
|
| 2085 |
+
},
|
| 2086 |
+
"react-dom": {
|
| 2087 |
+
"optional": true
|
| 2088 |
+
}
|
| 2089 |
+
}
|
| 2090 |
+
},
|
| 2091 |
"node_modules/fsevents": {
|
| 2092 |
"version": "2.3.3",
|
| 2093 |
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 2094 |
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 2095 |
+
"dev": true,
|
| 2096 |
"hasInstallScript": true,
|
| 2097 |
"license": "MIT",
|
| 2098 |
"optional": true,
|
|
|
|
| 2139 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 2140 |
}
|
| 2141 |
},
|
| 2142 |
+
"node_modules/glsl-noise": {
|
| 2143 |
+
"version": "0.0.0",
|
| 2144 |
+
"resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz",
|
| 2145 |
+
"integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==",
|
| 2146 |
+
"license": "MIT"
|
| 2147 |
+
},
|
| 2148 |
"node_modules/graceful-fs": {
|
| 2149 |
"version": "4.2.11",
|
| 2150 |
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
|
|
|
| 2168 |
"hermes-estree": "0.25.1"
|
| 2169 |
}
|
| 2170 |
},
|
| 2171 |
+
"node_modules/hls.js": {
|
| 2172 |
+
"version": "1.6.16",
|
| 2173 |
+
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz",
|
| 2174 |
+
"integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==",
|
| 2175 |
+
"license": "Apache-2.0"
|
| 2176 |
+
},
|
| 2177 |
+
"node_modules/ieee754": {
|
| 2178 |
+
"version": "1.2.1",
|
| 2179 |
+
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
| 2180 |
+
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
| 2181 |
+
"funding": [
|
| 2182 |
+
{
|
| 2183 |
+
"type": "github",
|
| 2184 |
+
"url": "https://github.com/sponsors/feross"
|
| 2185 |
+
},
|
| 2186 |
+
{
|
| 2187 |
+
"type": "patreon",
|
| 2188 |
+
"url": "https://www.patreon.com/feross"
|
| 2189 |
+
},
|
| 2190 |
+
{
|
| 2191 |
+
"type": "consulting",
|
| 2192 |
+
"url": "https://feross.org/support"
|
| 2193 |
+
}
|
| 2194 |
+
],
|
| 2195 |
+
"license": "BSD-3-Clause"
|
| 2196 |
+
},
|
| 2197 |
"node_modules/ignore": {
|
| 2198 |
"version": "5.3.2",
|
| 2199 |
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
|
|
|
| 2204 |
"node": ">= 4"
|
| 2205 |
}
|
| 2206 |
},
|
| 2207 |
+
"node_modules/immediate": {
|
| 2208 |
+
"version": "3.0.6",
|
| 2209 |
+
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
|
| 2210 |
+
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
|
| 2211 |
+
"license": "MIT"
|
| 2212 |
+
},
|
| 2213 |
"node_modules/imurmurhash": {
|
| 2214 |
"version": "0.1.4",
|
| 2215 |
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
|
|
|
|
| 2243 |
"node": ">=0.10.0"
|
| 2244 |
}
|
| 2245 |
},
|
| 2246 |
+
"node_modules/is-promise": {
|
| 2247 |
+
"version": "2.2.2",
|
| 2248 |
+
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
|
| 2249 |
+
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
|
| 2250 |
+
"license": "MIT"
|
| 2251 |
+
},
|
| 2252 |
"node_modules/isexe": {
|
| 2253 |
"version": "2.0.0",
|
| 2254 |
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
| 2255 |
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
|
|
|
| 2256 |
"license": "ISC"
|
| 2257 |
},
|
| 2258 |
+
"node_modules/its-fine": {
|
| 2259 |
+
"version": "2.0.0",
|
| 2260 |
+
"resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz",
|
| 2261 |
+
"integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==",
|
| 2262 |
+
"license": "MIT",
|
| 2263 |
+
"dependencies": {
|
| 2264 |
+
"@types/react-reconciler": "^0.28.9"
|
| 2265 |
+
},
|
| 2266 |
+
"peerDependencies": {
|
| 2267 |
+
"react": "^19.0.0"
|
| 2268 |
+
}
|
| 2269 |
+
},
|
| 2270 |
"node_modules/jiti": {
|
| 2271 |
"version": "2.6.1",
|
| 2272 |
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
|
|
|
| 2354 |
"node": ">= 0.8.0"
|
| 2355 |
}
|
| 2356 |
},
|
| 2357 |
+
"node_modules/lie": {
|
| 2358 |
+
"version": "3.3.0",
|
| 2359 |
+
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
|
| 2360 |
+
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
|
| 2361 |
+
"license": "MIT",
|
| 2362 |
+
"dependencies": {
|
| 2363 |
+
"immediate": "~3.0.5"
|
| 2364 |
+
}
|
| 2365 |
+
},
|
| 2366 |
"node_modules/lightningcss": {
|
| 2367 |
"version": "1.32.0",
|
| 2368 |
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
|
|
|
| 2638 |
"yallist": "^3.0.2"
|
| 2639 |
}
|
| 2640 |
},
|
| 2641 |
+
"node_modules/maath": {
|
| 2642 |
+
"version": "0.10.8",
|
| 2643 |
+
"resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz",
|
| 2644 |
+
"integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==",
|
| 2645 |
+
"license": "MIT",
|
| 2646 |
+
"peerDependencies": {
|
| 2647 |
+
"@types/three": ">=0.134.0",
|
| 2648 |
+
"three": ">=0.134.0"
|
| 2649 |
+
}
|
| 2650 |
+
},
|
| 2651 |
"node_modules/magic-string": {
|
| 2652 |
"version": "0.30.21",
|
| 2653 |
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
|
|
|
| 2657 |
"@jridgewell/sourcemap-codec": "^1.5.5"
|
| 2658 |
}
|
| 2659 |
},
|
| 2660 |
+
"node_modules/meshline": {
|
| 2661 |
+
"version": "3.3.1",
|
| 2662 |
+
"resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz",
|
| 2663 |
+
"integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==",
|
| 2664 |
+
"license": "MIT",
|
| 2665 |
+
"peerDependencies": {
|
| 2666 |
+
"three": ">=0.137"
|
| 2667 |
+
}
|
| 2668 |
+
},
|
| 2669 |
+
"node_modules/meshoptimizer": {
|
| 2670 |
+
"version": "1.1.1",
|
| 2671 |
+
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.1.1.tgz",
|
| 2672 |
+
"integrity": "sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==",
|
| 2673 |
+
"license": "MIT"
|
| 2674 |
+
},
|
| 2675 |
"node_modules/minimatch": {
|
| 2676 |
"version": "10.2.5",
|
| 2677 |
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
|
|
|
| 2688 |
"url": "https://github.com/sponsors/isaacs"
|
| 2689 |
}
|
| 2690 |
},
|
| 2691 |
+
"node_modules/motion-dom": {
|
| 2692 |
+
"version": "12.38.0",
|
| 2693 |
+
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
|
| 2694 |
+
"integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
|
| 2695 |
+
"license": "MIT",
|
| 2696 |
+
"dependencies": {
|
| 2697 |
+
"motion-utils": "^12.36.0"
|
| 2698 |
+
}
|
| 2699 |
+
},
|
| 2700 |
+
"node_modules/motion-utils": {
|
| 2701 |
+
"version": "12.36.0",
|
| 2702 |
+
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
|
| 2703 |
+
"integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
|
| 2704 |
+
"license": "MIT"
|
| 2705 |
+
},
|
| 2706 |
"node_modules/ms": {
|
| 2707 |
"version": "2.1.3",
|
| 2708 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
|
|
|
| 2805 |
"version": "3.1.1",
|
| 2806 |
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
| 2807 |
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
|
|
|
| 2808 |
"license": "MIT",
|
| 2809 |
"engines": {
|
| 2810 |
"node": ">=8"
|
|
|
|
| 2820 |
"version": "4.0.4",
|
| 2821 |
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 2822 |
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 2823 |
+
"dev": true,
|
| 2824 |
"license": "MIT",
|
| 2825 |
"engines": {
|
| 2826 |
"node": ">=12"
|
|
|
|
| 2863 |
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
| 2864 |
"license": "MIT"
|
| 2865 |
},
|
| 2866 |
+
"node_modules/potpack": {
|
| 2867 |
+
"version": "1.0.2",
|
| 2868 |
+
"resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz",
|
| 2869 |
+
"integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==",
|
| 2870 |
+
"license": "ISC"
|
| 2871 |
+
},
|
| 2872 |
"node_modules/prelude-ls": {
|
| 2873 |
"version": "1.2.1",
|
| 2874 |
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
|
|
|
| 2879 |
"node": ">= 0.8.0"
|
| 2880 |
}
|
| 2881 |
},
|
| 2882 |
+
"node_modules/promise-worker-transferable": {
|
| 2883 |
+
"version": "1.0.4",
|
| 2884 |
+
"resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
|
| 2885 |
+
"integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
|
| 2886 |
+
"license": "Apache-2.0",
|
| 2887 |
+
"dependencies": {
|
| 2888 |
+
"is-promise": "^2.1.0",
|
| 2889 |
+
"lie": "^3.0.2"
|
| 2890 |
+
}
|
| 2891 |
+
},
|
| 2892 |
"node_modules/punycode": {
|
| 2893 |
"version": "2.3.1",
|
| 2894 |
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
|
|
|
| 2920 |
"react": "^19.2.5"
|
| 2921 |
}
|
| 2922 |
},
|
| 2923 |
+
"node_modules/react-use-measure": {
|
| 2924 |
+
"version": "2.1.7",
|
| 2925 |
+
"resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz",
|
| 2926 |
+
"integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==",
|
| 2927 |
+
"license": "MIT",
|
| 2928 |
+
"peerDependencies": {
|
| 2929 |
+
"react": ">=16.13",
|
| 2930 |
+
"react-dom": ">=16.13"
|
| 2931 |
+
},
|
| 2932 |
+
"peerDependenciesMeta": {
|
| 2933 |
+
"react-dom": {
|
| 2934 |
+
"optional": true
|
| 2935 |
+
}
|
| 2936 |
+
}
|
| 2937 |
+
},
|
| 2938 |
+
"node_modules/require-from-string": {
|
| 2939 |
+
"version": "2.0.2",
|
| 2940 |
+
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
| 2941 |
+
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
| 2942 |
+
"license": "MIT",
|
| 2943 |
+
"engines": {
|
| 2944 |
+
"node": ">=0.10.0"
|
| 2945 |
+
}
|
| 2946 |
+
},
|
| 2947 |
"node_modules/rolldown": {
|
| 2948 |
"version": "1.0.0-rc.17",
|
| 2949 |
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
|
| 2950 |
"integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
|
| 2951 |
+
"dev": true,
|
| 2952 |
"license": "MIT",
|
| 2953 |
"dependencies": {
|
| 2954 |
"@oxc-project/types": "=0.127.0",
|
|
|
|
| 2982 |
"version": "1.0.0-rc.17",
|
| 2983 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
|
| 2984 |
"integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
|
| 2985 |
+
"dev": true,
|
| 2986 |
"license": "MIT"
|
| 2987 |
},
|
| 2988 |
"node_modules/scheduler": {
|
|
|
|
| 3005 |
"version": "2.0.0",
|
| 3006 |
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
| 3007 |
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
|
|
|
| 3008 |
"license": "MIT",
|
| 3009 |
"dependencies": {
|
| 3010 |
"shebang-regex": "^3.0.0"
|
|
|
|
| 3017 |
"version": "3.0.0",
|
| 3018 |
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
| 3019 |
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
|
|
|
| 3020 |
"license": "MIT",
|
| 3021 |
"engines": {
|
| 3022 |
"node": ">=8"
|
|
|
|
| 3031 |
"node": ">=0.10.0"
|
| 3032 |
}
|
| 3033 |
},
|
| 3034 |
+
"node_modules/stats-gl": {
|
| 3035 |
+
"version": "2.4.2",
|
| 3036 |
+
"resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz",
|
| 3037 |
+
"integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==",
|
| 3038 |
+
"license": "MIT",
|
| 3039 |
+
"dependencies": {
|
| 3040 |
+
"@types/three": "*",
|
| 3041 |
+
"three": "^0.170.0"
|
| 3042 |
+
},
|
| 3043 |
+
"peerDependencies": {
|
| 3044 |
+
"@types/three": "*",
|
| 3045 |
+
"three": "*"
|
| 3046 |
+
}
|
| 3047 |
+
},
|
| 3048 |
+
"node_modules/stats-gl/node_modules/three": {
|
| 3049 |
+
"version": "0.170.0",
|
| 3050 |
+
"resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
|
| 3051 |
+
"integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
|
| 3052 |
+
"license": "MIT"
|
| 3053 |
+
},
|
| 3054 |
+
"node_modules/stats.js": {
|
| 3055 |
+
"version": "0.17.0",
|
| 3056 |
+
"resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz",
|
| 3057 |
+
"integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==",
|
| 3058 |
+
"license": "MIT"
|
| 3059 |
+
},
|
| 3060 |
"node_modules/styled-components": {
|
| 3061 |
"version": "6.4.1",
|
| 3062 |
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.4.1.tgz",
|
|
|
|
| 3099 |
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
|
| 3100 |
"license": "MIT"
|
| 3101 |
},
|
| 3102 |
+
"node_modules/suspend-react": {
|
| 3103 |
+
"version": "0.1.3",
|
| 3104 |
+
"resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz",
|
| 3105 |
+
"integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==",
|
| 3106 |
+
"license": "MIT",
|
| 3107 |
+
"peerDependencies": {
|
| 3108 |
+
"react": ">=17.0"
|
| 3109 |
+
}
|
| 3110 |
+
},
|
| 3111 |
"node_modules/tailwindcss": {
|
| 3112 |
"version": "4.2.4",
|
| 3113 |
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
|
|
|
|
| 3127 |
"url": "https://opencollective.com/webpack"
|
| 3128 |
}
|
| 3129 |
},
|
| 3130 |
+
"node_modules/three": {
|
| 3131 |
+
"version": "0.184.0",
|
| 3132 |
+
"resolved": "https://registry.npmjs.org/three/-/three-0.184.0.tgz",
|
| 3133 |
+
"integrity": "sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==",
|
| 3134 |
+
"license": "MIT"
|
| 3135 |
+
},
|
| 3136 |
+
"node_modules/three-mesh-bvh": {
|
| 3137 |
+
"version": "0.8.3",
|
| 3138 |
+
"resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.8.3.tgz",
|
| 3139 |
+
"integrity": "sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==",
|
| 3140 |
+
"license": "MIT",
|
| 3141 |
+
"peerDependencies": {
|
| 3142 |
+
"three": ">= 0.159.0"
|
| 3143 |
+
}
|
| 3144 |
+
},
|
| 3145 |
+
"node_modules/three-stdlib": {
|
| 3146 |
+
"version": "2.36.1",
|
| 3147 |
+
"resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz",
|
| 3148 |
+
"integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==",
|
| 3149 |
+
"license": "MIT",
|
| 3150 |
+
"dependencies": {
|
| 3151 |
+
"@types/draco3d": "^1.4.0",
|
| 3152 |
+
"@types/offscreencanvas": "^2019.6.4",
|
| 3153 |
+
"@types/webxr": "^0.5.2",
|
| 3154 |
+
"draco3d": "^1.4.1",
|
| 3155 |
+
"fflate": "^0.6.9",
|
| 3156 |
+
"potpack": "^1.0.1"
|
| 3157 |
+
},
|
| 3158 |
+
"peerDependencies": {
|
| 3159 |
+
"three": ">=0.128.0"
|
| 3160 |
+
}
|
| 3161 |
+
},
|
| 3162 |
+
"node_modules/three-stdlib/node_modules/fflate": {
|
| 3163 |
+
"version": "0.6.10",
|
| 3164 |
+
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
|
| 3165 |
+
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
|
| 3166 |
+
"license": "MIT"
|
| 3167 |
+
},
|
| 3168 |
"node_modules/tinyglobby": {
|
| 3169 |
"version": "0.2.16",
|
| 3170 |
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
| 3171 |
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
| 3172 |
+
"dev": true,
|
| 3173 |
"license": "MIT",
|
| 3174 |
"dependencies": {
|
| 3175 |
"fdir": "^6.5.0",
|
|
|
|
| 3182 |
"url": "https://github.com/sponsors/SuperchupuDev"
|
| 3183 |
}
|
| 3184 |
},
|
| 3185 |
+
"node_modules/troika-three-text": {
|
| 3186 |
+
"version": "0.52.4",
|
| 3187 |
+
"resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.52.4.tgz",
|
| 3188 |
+
"integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==",
|
| 3189 |
+
"license": "MIT",
|
| 3190 |
+
"dependencies": {
|
| 3191 |
+
"bidi-js": "^1.0.2",
|
| 3192 |
+
"troika-three-utils": "^0.52.4",
|
| 3193 |
+
"troika-worker-utils": "^0.52.0",
|
| 3194 |
+
"webgl-sdf-generator": "1.1.1"
|
| 3195 |
+
},
|
| 3196 |
+
"peerDependencies": {
|
| 3197 |
+
"three": ">=0.125.0"
|
| 3198 |
+
}
|
| 3199 |
+
},
|
| 3200 |
+
"node_modules/troika-three-utils": {
|
| 3201 |
+
"version": "0.52.4",
|
| 3202 |
+
"resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.52.4.tgz",
|
| 3203 |
+
"integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==",
|
| 3204 |
+
"license": "MIT",
|
| 3205 |
+
"peerDependencies": {
|
| 3206 |
+
"three": ">=0.125.0"
|
| 3207 |
+
}
|
| 3208 |
+
},
|
| 3209 |
+
"node_modules/troika-worker-utils": {
|
| 3210 |
+
"version": "0.52.0",
|
| 3211 |
+
"resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz",
|
| 3212 |
+
"integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==",
|
| 3213 |
+
"license": "MIT"
|
| 3214 |
+
},
|
| 3215 |
"node_modules/tslib": {
|
| 3216 |
"version": "2.8.1",
|
| 3217 |
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 3218 |
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 3219 |
+
"license": "0BSD"
|
| 3220 |
+
},
|
| 3221 |
+
"node_modules/tunnel-rat": {
|
| 3222 |
+
"version": "0.1.2",
|
| 3223 |
+
"resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz",
|
| 3224 |
+
"integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==",
|
| 3225 |
+
"license": "MIT",
|
| 3226 |
+
"dependencies": {
|
| 3227 |
+
"zustand": "^4.3.2"
|
| 3228 |
+
}
|
| 3229 |
+
},
|
| 3230 |
+
"node_modules/tunnel-rat/node_modules/zustand": {
|
| 3231 |
+
"version": "4.5.7",
|
| 3232 |
+
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
| 3233 |
+
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
| 3234 |
+
"license": "MIT",
|
| 3235 |
+
"dependencies": {
|
| 3236 |
+
"use-sync-external-store": "^1.2.2"
|
| 3237 |
+
},
|
| 3238 |
+
"engines": {
|
| 3239 |
+
"node": ">=12.7.0"
|
| 3240 |
+
},
|
| 3241 |
+
"peerDependencies": {
|
| 3242 |
+
"@types/react": ">=16.8",
|
| 3243 |
+
"immer": ">=9.0.6",
|
| 3244 |
+
"react": ">=16.8"
|
| 3245 |
+
},
|
| 3246 |
+
"peerDependenciesMeta": {
|
| 3247 |
+
"@types/react": {
|
| 3248 |
+
"optional": true
|
| 3249 |
+
},
|
| 3250 |
+
"immer": {
|
| 3251 |
+
"optional": true
|
| 3252 |
+
},
|
| 3253 |
+
"react": {
|
| 3254 |
+
"optional": true
|
| 3255 |
+
}
|
| 3256 |
+
}
|
| 3257 |
},
|
| 3258 |
"node_modules/type-check": {
|
| 3259 |
"version": "0.4.0",
|
|
|
|
| 3308 |
"punycode": "^2.1.0"
|
| 3309 |
}
|
| 3310 |
},
|
| 3311 |
+
"node_modules/use-sync-external-store": {
|
| 3312 |
+
"version": "1.6.0",
|
| 3313 |
+
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
| 3314 |
+
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
| 3315 |
+
"license": "MIT",
|
| 3316 |
+
"peerDependencies": {
|
| 3317 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 3318 |
+
}
|
| 3319 |
+
},
|
| 3320 |
+
"node_modules/utility-types": {
|
| 3321 |
+
"version": "3.11.0",
|
| 3322 |
+
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
|
| 3323 |
+
"integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==",
|
| 3324 |
+
"license": "MIT",
|
| 3325 |
+
"engines": {
|
| 3326 |
+
"node": ">= 4"
|
| 3327 |
+
}
|
| 3328 |
+
},
|
| 3329 |
"node_modules/vite": {
|
| 3330 |
"version": "8.0.10",
|
| 3331 |
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
|
| 3332 |
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
|
| 3333 |
+
"dev": true,
|
| 3334 |
"license": "MIT",
|
| 3335 |
"dependencies": {
|
| 3336 |
"lightningcss": "^1.32.0",
|
|
|
|
| 3404 |
}
|
| 3405 |
}
|
| 3406 |
},
|
| 3407 |
+
"node_modules/webgl-constants": {
|
| 3408 |
+
"version": "1.1.1",
|
| 3409 |
+
"resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz",
|
| 3410 |
+
"integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg=="
|
| 3411 |
+
},
|
| 3412 |
+
"node_modules/webgl-sdf-generator": {
|
| 3413 |
+
"version": "1.1.1",
|
| 3414 |
+
"resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz",
|
| 3415 |
+
"integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==",
|
| 3416 |
+
"license": "MIT"
|
| 3417 |
+
},
|
| 3418 |
"node_modules/which": {
|
| 3419 |
"version": "2.0.2",
|
| 3420 |
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
| 3421 |
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
|
|
|
| 3422 |
"license": "ISC",
|
| 3423 |
"dependencies": {
|
| 3424 |
"isexe": "^2.0.0"
|
|
|
|
| 3482 |
"peerDependencies": {
|
| 3483 |
"zod": "^3.25.0 || ^4.0.0"
|
| 3484 |
}
|
| 3485 |
+
},
|
| 3486 |
+
"node_modules/zustand": {
|
| 3487 |
+
"version": "5.0.12",
|
| 3488 |
+
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
|
| 3489 |
+
"integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
|
| 3490 |
+
"license": "MIT",
|
| 3491 |
+
"engines": {
|
| 3492 |
+
"node": ">=12.20.0"
|
| 3493 |
+
},
|
| 3494 |
+
"peerDependencies": {
|
| 3495 |
+
"@types/react": ">=18.0.0",
|
| 3496 |
+
"immer": ">=9.0.6",
|
| 3497 |
+
"react": ">=18.0.0",
|
| 3498 |
+
"use-sync-external-store": ">=1.2.0"
|
| 3499 |
+
},
|
| 3500 |
+
"peerDependenciesMeta": {
|
| 3501 |
+
"@types/react": {
|
| 3502 |
+
"optional": true
|
| 3503 |
+
},
|
| 3504 |
+
"immer": {
|
| 3505 |
+
"optional": true
|
| 3506 |
+
},
|
| 3507 |
+
"react": {
|
| 3508 |
+
"optional": true
|
| 3509 |
+
},
|
| 3510 |
+
"use-sync-external-store": {
|
| 3511 |
+
"optional": true
|
| 3512 |
+
}
|
| 3513 |
+
}
|
| 3514 |
}
|
| 3515 |
}
|
| 3516 |
}
|
frontend/package.json
CHANGED
|
@@ -10,13 +10,18 @@
|
|
| 10 |
"preview": "vite preview"
|
| 11 |
},
|
| 12 |
"dependencies": {
|
|
|
|
|
|
|
| 13 |
"@tailwindcss/vite": "^4.2.4",
|
| 14 |
"autoprefixer": "^10.5.0",
|
|
|
|
| 15 |
"postcss": "^8.5.10",
|
| 16 |
"react": "^19.2.5",
|
| 17 |
"react-dom": "^19.2.5",
|
| 18 |
"styled-components": "^6.4.1",
|
| 19 |
-
"tailwindcss": "^4.2.4"
|
|
|
|
|
|
|
| 20 |
},
|
| 21 |
"devDependencies": {
|
| 22 |
"@eslint/js": "^10.0.1",
|
|
|
|
| 10 |
"preview": "vite preview"
|
| 11 |
},
|
| 12 |
"dependencies": {
|
| 13 |
+
"@react-three/drei": "^10.7.7",
|
| 14 |
+
"@react-three/fiber": "^9.6.0",
|
| 15 |
"@tailwindcss/vite": "^4.2.4",
|
| 16 |
"autoprefixer": "^10.5.0",
|
| 17 |
+
"framer-motion": "^12.38.0",
|
| 18 |
"postcss": "^8.5.10",
|
| 19 |
"react": "^19.2.5",
|
| 20 |
"react-dom": "^19.2.5",
|
| 21 |
"styled-components": "^6.4.1",
|
| 22 |
+
"tailwindcss": "^4.2.4",
|
| 23 |
+
"three": "^0.184.0",
|
| 24 |
+
"zustand": "^5.0.12"
|
| 25 |
},
|
| 26 |
"devDependencies": {
|
| 27 |
"@eslint/js": "^10.0.1",
|
frontend/src/App.tsx
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, Suspense, lazy } from 'react'
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion'
|
| 3 |
+
import { useStore } from './store'
|
| 4 |
+
import { checkHealth } from './api'
|
| 5 |
+
import UploadZone from './components/UploadZone'
|
| 6 |
+
import ResultPanel from './components/ResultPanel'
|
| 7 |
+
|
| 8 |
+
// Lazy-load the heavy 3D component
|
| 9 |
+
const DeepfakeOrb = lazy(() => import('./components/DeepfakeOrb'))
|
| 10 |
+
|
| 11 |
+
export default function App() {
|
| 12 |
+
const { state, result, error, reset } = useStore()
|
| 13 |
+
const verdict = result?.result ?? null
|
| 14 |
+
|
| 15 |
+
// Health check on mount
|
| 16 |
+
useEffect(() => {
|
| 17 |
+
const badge = document.getElementById('modelBadge')
|
| 18 |
+
checkHealth()
|
| 19 |
+
.then((d) => {
|
| 20 |
+
if (badge) {
|
| 21 |
+
badge.textContent = d.model.toUpperCase()
|
| 22 |
+
badge.style.color = '#00ff88bb'
|
| 23 |
+
badge.style.borderColor = '#00ff8844'
|
| 24 |
+
}
|
| 25 |
+
})
|
| 26 |
+
.catch(() => {
|
| 27 |
+
if (badge) {
|
| 28 |
+
badge.textContent = 'SERVER OFFLINE'
|
| 29 |
+
badge.style.color = '#ff3355bb'
|
| 30 |
+
badge.style.borderColor = '#ff335544'
|
| 31 |
+
}
|
| 32 |
+
})
|
| 33 |
+
}, [])
|
| 34 |
+
|
| 35 |
+
const showUpload = state === 'idle' || state === 'loading'
|
| 36 |
+
const showResult = state === 'result'
|
| 37 |
+
const showError = state === 'error'
|
| 38 |
+
|
| 39 |
+
return (
|
| 40 |
+
<div className="min-h-screen text-white overflow-x-hidden" style={{ background: '#050508' }}>
|
| 41 |
+
|
| 42 |
+
{/* ── Sticky header ── */}
|
| 43 |
+
<header className="sticky top-0 z-50 border-b border-white/5"
|
| 44 |
+
style={{ background: 'rgba(5,5,8,0.85)', backdropFilter: 'blur(20px)' }}>
|
| 45 |
+
<div className="max-w-5xl mx-auto px-6 h-16 flex items-center justify-between">
|
| 46 |
+
<div className="flex items-center gap-3">
|
| 47 |
+
<motion.div
|
| 48 |
+
className="w-9 h-9 rounded-lg border border-[#00ff8855] flex items-center justify-center"
|
| 49 |
+
animate={{ boxShadow: ['0 0 8px #00ff8820', '0 0 20px #00ff8840', '0 0 8px #00ff8820'] }}
|
| 50 |
+
transition={{ duration: 2.5, repeat: Infinity }}
|
| 51 |
+
>
|
| 52 |
+
<svg width="18" height="18" fill="none" stroke="#00ff88" strokeWidth="1.5" viewBox="0 0 24 24">
|
| 53 |
+
<path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.964-7.178z"/>
|
| 54 |
+
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
|
| 55 |
+
</svg>
|
| 56 |
+
</motion.div>
|
| 57 |
+
<div>
|
| 58 |
+
<p className="text-sm font-bold tracking-widest" style={{ color: '#00ff88', textShadow: '0 0 12px #00ff8855' }}>
|
| 59 |
+
DEEPFAKE AUTHENTICATOR
|
| 60 |
+
</p>
|
| 61 |
+
<p className="text-[9px] tracking-widest text-white/30 font-mono">AI-POWERED VIDEO FORENSICS</p>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
<div
|
| 65 |
+
id="modelBadge"
|
| 66 |
+
className="text-[10px] px-3 py-1.5 rounded-full border font-mono tracking-wider transition-all duration-500"
|
| 67 |
+
style={{ borderColor: 'rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.3)' }}
|
| 68 |
+
>
|
| 69 |
+
CONNECTING...
|
| 70 |
+
</div>
|
| 71 |
+
</div>
|
| 72 |
+
</header>
|
| 73 |
+
|
| 74 |
+
{/* ── Main layout ── */}
|
| 75 |
+
<main className="max-w-5xl mx-auto px-6 py-10">
|
| 76 |
+
<div className="flex flex-col lg:flex-row gap-10 items-start">
|
| 77 |
+
|
| 78 |
+
{/* ── Left: 3D orb + hero ── */}
|
| 79 |
+
<div className="w-full lg:w-80 flex-shrink-0 flex flex-col items-center gap-6 lg:sticky lg:top-24">
|
| 80 |
+
{/* 3D Orb */}
|
| 81 |
+
<div className="w-64 h-64 relative">
|
| 82 |
+
<Suspense fallback={
|
| 83 |
+
<div className="w-full h-full rounded-full border border-[#00ff8830] flex items-center justify-center">
|
| 84 |
+
<div className="w-16 h-16 rounded-full border-2 border-[#00ff88] border-t-transparent animate-spin" />
|
| 85 |
+
</div>
|
| 86 |
+
}>
|
| 87 |
+
<DeepfakeOrb state={state} verdict={verdict} />
|
| 88 |
+
</Suspense>
|
| 89 |
+
</div>
|
| 90 |
+
|
| 91 |
+
{/* Hero text */}
|
| 92 |
+
<AnimatePresence mode="wait">
|
| 93 |
+
{showUpload && (
|
| 94 |
+
<motion.div
|
| 95 |
+
key="hero"
|
| 96 |
+
className="text-center"
|
| 97 |
+
initial={{ opacity: 0, y: 10 }}
|
| 98 |
+
animate={{ opacity: 1, y: 0 }}
|
| 99 |
+
exit={{ opacity: 0, y: -10 }}
|
| 100 |
+
>
|
| 101 |
+
<h1 className="text-2xl font-bold mb-2 leading-tight"
|
| 102 |
+
style={{ background: 'linear-gradient(135deg, #fff 30%, #00ff88)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
|
| 103 |
+
Is This Video Real?
|
| 104 |
+
</h1>
|
| 105 |
+
<p className="text-white/40 text-xs leading-relaxed max-w-xs">
|
| 106 |
+
Dual ViT ensemble + Wav2Vec2 audio analysis detects facial manipulation and AI-generated voices.
|
| 107 |
+
</p>
|
| 108 |
+
{/* Stat pills */}
|
| 109 |
+
<div className="flex flex-wrap gap-2 justify-center mt-4">
|
| 110 |
+
{['2 ViT Models', '40 Frames', 'Audio AI', '< 20s'].map((s) => (
|
| 111 |
+
<span key={s} className="text-[10px] px-2.5 py-1 rounded-full border border-white/8 text-white/40 bg-white/3">
|
| 112 |
+
{s}
|
| 113 |
+
</span>
|
| 114 |
+
))}
|
| 115 |
+
</div>
|
| 116 |
+
</motion.div>
|
| 117 |
+
)}
|
| 118 |
+
|
| 119 |
+
{showResult && verdict && (
|
| 120 |
+
<motion.div
|
| 121 |
+
key="verdict-summary"
|
| 122 |
+
className="text-center"
|
| 123 |
+
initial={{ opacity: 0, scale: 0.9 }}
|
| 124 |
+
animate={{ opacity: 1, scale: 1 }}
|
| 125 |
+
exit={{ opacity: 0 }}
|
| 126 |
+
>
|
| 127 |
+
<p className="text-3xl font-bold tracking-widest mb-1"
|
| 128 |
+
style={{ color: verdict === 'FAKE' ? '#ff3355' : '#00ff88', textShadow: `0 0 20px ${verdict === 'FAKE' ? '#ff335566' : '#00ff8866'}` }}>
|
| 129 |
+
{verdict === 'FAKE' ? 'DEEPFAKE' : 'AUTHENTIC'}
|
| 130 |
+
</p>
|
| 131 |
+
<p className="text-white/30 text-xs font-mono">{result?.confidence}% confidence</p>
|
| 132 |
+
</motion.div>
|
| 133 |
+
)}
|
| 134 |
+
</AnimatePresence>
|
| 135 |
+
</div>
|
| 136 |
+
|
| 137 |
+
{/* ── Right: Upload / Result / Error ── */}
|
| 138 |
+
<div className="flex-1 min-w-0">
|
| 139 |
+
<AnimatePresence mode="wait">
|
| 140 |
+
{showUpload && (
|
| 141 |
+
<motion.div key="upload" initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }}>
|
| 142 |
+
<UploadZone />
|
| 143 |
+
</motion.div>
|
| 144 |
+
)}
|
| 145 |
+
|
| 146 |
+
{showResult && (
|
| 147 |
+
<motion.div key="result" initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }}>
|
| 148 |
+
<ResultPanel />
|
| 149 |
+
</motion.div>
|
| 150 |
+
)}
|
| 151 |
+
|
| 152 |
+
{showError && (
|
| 153 |
+
<motion.div
|
| 154 |
+
key="error"
|
| 155 |
+
className="rounded-2xl border border-red-500/20 bg-red-500/5 p-6"
|
| 156 |
+
initial={{ opacity: 0, scale: 0.95 }}
|
| 157 |
+
animate={{ opacity: 1, scale: 1 }}
|
| 158 |
+
exit={{ opacity: 0 }}
|
| 159 |
+
>
|
| 160 |
+
<div className="flex items-center gap-3 mb-3">
|
| 161 |
+
<span className="text-xl">⚠️</span>
|
| 162 |
+
<span className="text-red-400 font-bold text-sm tracking-wider uppercase">Analysis Failed</span>
|
| 163 |
+
</div>
|
| 164 |
+
<p className="text-white/50 text-sm leading-relaxed">{error}</p>
|
| 165 |
+
<button
|
| 166 |
+
onClick={reset}
|
| 167 |
+
className="mt-4 text-xs text-white/30 hover:text-white transition-colors font-mono tracking-wider"
|
| 168 |
+
>
|
| 169 |
+
↻ Try again
|
| 170 |
+
</button>
|
| 171 |
+
</motion.div>
|
| 172 |
+
)}
|
| 173 |
+
</AnimatePresence>
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
</main>
|
| 177 |
+
|
| 178 |
+
<footer className="text-center py-8 text-[10px] text-white/15 font-mono tracking-widest border-t border-white/3">
|
| 179 |
+
DEEPFAKE AUTHENTICATOR · MEDIAPIPE + VIT ENSEMBLE + WAV2VEC2 · LOCAL INFERENCE
|
| 180 |
+
</footer>
|
| 181 |
+
</div>
|
| 182 |
+
)
|
| 183 |
+
}
|
frontend/src/api.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { AnalysisResult } from './store'
|
| 2 |
+
|
| 3 |
+
const BASE = import.meta.env.DEV ? 'http://localhost:8000' : window.location.origin
|
| 4 |
+
|
| 5 |
+
export async function analyzeVideo(file: File): Promise<AnalysisResult> {
|
| 6 |
+
const fd = new FormData()
|
| 7 |
+
fd.append('file', file)
|
| 8 |
+
const res = await fetch(`${BASE}/analyze`, { method: 'POST', body: fd })
|
| 9 |
+
if (!res.ok) {
|
| 10 |
+
const err = await res.json().catch(() => ({}))
|
| 11 |
+
throw new Error((err as any).detail || `Server error ${res.status}`)
|
| 12 |
+
}
|
| 13 |
+
return res.json()
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
export async function checkHealth(): Promise<{ status: string; model: string; ready: boolean }> {
|
| 17 |
+
const res = await fetch(`${BASE}/health`)
|
| 18 |
+
if (!res.ok) throw new Error('Server offline')
|
| 19 |
+
return res.json()
|
| 20 |
+
}
|
frontend/src/components/DeepfakeOrb.tsx
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useRef, useMemo } from 'react'
|
| 2 |
+
import { Canvas, useFrame } from '@react-three/fiber'
|
| 3 |
+
import { Sphere, MeshDistortMaterial, Float, Stars } from '@react-three/drei'
|
| 4 |
+
import * as THREE from 'three'
|
| 5 |
+
import type { AppState, Verdict } from '../store'
|
| 6 |
+
|
| 7 |
+
// ── Inner animated orb ────────────────────────────────────────────────────────
|
| 8 |
+
function Orb({ state, verdict }: { state: AppState; verdict: Verdict }) {
|
| 9 |
+
const meshRef = useRef<THREE.Mesh>(null)
|
| 10 |
+
const ringRef = useRef<THREE.Mesh>(null)
|
| 11 |
+
const ring2Ref = useRef<THREE.Mesh>(null)
|
| 12 |
+
|
| 13 |
+
const isFake = verdict === 'FAKE'
|
| 14 |
+
const isReal = verdict === 'REAL'
|
| 15 |
+
const isLoading = state === 'loading'
|
| 16 |
+
|
| 17 |
+
const color = isFake ? '#ff3355' : isReal ? '#00ff88' : '#00aaff'
|
| 18 |
+
const emissive = isFake ? '#ff0033' : isReal ? '#00cc66' : '#0066ff'
|
| 19 |
+
|
| 20 |
+
useFrame((_, delta) => {
|
| 21 |
+
if (!meshRef.current) return
|
| 22 |
+
meshRef.current.rotation.y += delta * (isLoading ? 1.5 : 0.4)
|
| 23 |
+
meshRef.current.rotation.x += delta * (isLoading ? 0.8 : 0.15)
|
| 24 |
+
if (ringRef.current) ringRef.current.rotation.z += delta * 0.6
|
| 25 |
+
if (ring2Ref.current) ring2Ref.current.rotation.x += delta * 0.4
|
| 26 |
+
})
|
| 27 |
+
|
| 28 |
+
// Particle ring
|
| 29 |
+
const particles = useMemo(() => {
|
| 30 |
+
const count = 120
|
| 31 |
+
const positions = new Float32Array(count * 3)
|
| 32 |
+
for (let i = 0; i < count; i++) {
|
| 33 |
+
const angle = (i / count) * Math.PI * 2
|
| 34 |
+
const radius = 1.8 + Math.random() * 0.4
|
| 35 |
+
positions[i * 3] = Math.cos(angle) * radius
|
| 36 |
+
positions[i * 3 + 1] = (Math.random() - 0.5) * 0.3
|
| 37 |
+
positions[i * 3 + 2] = Math.sin(angle) * radius
|
| 38 |
+
}
|
| 39 |
+
return positions
|
| 40 |
+
}, [])
|
| 41 |
+
|
| 42 |
+
return (
|
| 43 |
+
<group>
|
| 44 |
+
{/* Stars background */}
|
| 45 |
+
<Stars radius={8} depth={4} count={300} factor={1} fade speed={isLoading ? 3 : 0.5} />
|
| 46 |
+
|
| 47 |
+
{/* Main orb */}
|
| 48 |
+
<Float speed={isLoading ? 4 : 1.5} rotationIntensity={isLoading ? 1 : 0.3} floatIntensity={isLoading ? 1.5 : 0.5}>
|
| 49 |
+
<Sphere ref={meshRef} args={[1, 64, 64]}>
|
| 50 |
+
<MeshDistortMaterial
|
| 51 |
+
color={color}
|
| 52 |
+
emissive={emissive}
|
| 53 |
+
emissiveIntensity={isLoading ? 0.8 : 0.4}
|
| 54 |
+
distort={isLoading ? 0.6 : isFake ? 0.45 : 0.25}
|
| 55 |
+
speed={isLoading ? 4 : 2}
|
| 56 |
+
roughness={0.1}
|
| 57 |
+
metalness={0.8}
|
| 58 |
+
transparent
|
| 59 |
+
opacity={0.9}
|
| 60 |
+
/>
|
| 61 |
+
</Sphere>
|
| 62 |
+
</Float>
|
| 63 |
+
|
| 64 |
+
{/* Orbit ring 1 */}
|
| 65 |
+
<mesh ref={ringRef} rotation={[Math.PI / 2, 0, 0]}>
|
| 66 |
+
<torusGeometry args={[1.6, 0.015, 8, 100]} />
|
| 67 |
+
<meshBasicMaterial color={color} transparent opacity={0.5} />
|
| 68 |
+
</mesh>
|
| 69 |
+
|
| 70 |
+
{/* Orbit ring 2 */}
|
| 71 |
+
<mesh ref={ring2Ref} rotation={[Math.PI / 3, Math.PI / 4, 0]}>
|
| 72 |
+
<torusGeometry args={[1.9, 0.008, 8, 100]} />
|
| 73 |
+
<meshBasicMaterial color={color} transparent opacity={0.25} />
|
| 74 |
+
</mesh>
|
| 75 |
+
|
| 76 |
+
{/* Particle ring */}
|
| 77 |
+
<points>
|
| 78 |
+
<bufferGeometry>
|
| 79 |
+
<bufferAttribute attach="attributes-position" args={[particles, 3]} />
|
| 80 |
+
</bufferGeometry>
|
| 81 |
+
<pointsMaterial color={color} size={0.025} transparent opacity={0.6} />
|
| 82 |
+
</points>
|
| 83 |
+
|
| 84 |
+
{/* Ambient + point lights */}
|
| 85 |
+
<ambientLight intensity={0.2} />
|
| 86 |
+
<pointLight position={[3, 3, 3]} intensity={2} color={color} />
|
| 87 |
+
<pointLight position={[-3, -3, -3]} intensity={1} color={emissive} />
|
| 88 |
+
</group>
|
| 89 |
+
)
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// ── Exported canvas wrapper ───────────────────────────────────────────────────
|
| 93 |
+
export default function DeepfakeOrb({ state, verdict }: { state: AppState; verdict: Verdict }) {
|
| 94 |
+
return (
|
| 95 |
+
<Canvas
|
| 96 |
+
camera={{ position: [0, 0, 4], fov: 50 }}
|
| 97 |
+
style={{ width: '100%', height: '100%' }}
|
| 98 |
+
gl={{ antialias: true, alpha: true }}
|
| 99 |
+
>
|
| 100 |
+
<Orb state={state} verdict={verdict} />
|
| 101 |
+
</Canvas>
|
| 102 |
+
)
|
| 103 |
+
}
|
frontend/src/components/ResultPanel.tsx
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { motion, AnimatePresence } from 'framer-motion'
|
| 2 |
+
import { useStore } from '../store'
|
| 3 |
+
import type { FramePoint, AudioResult } from '../store'
|
| 4 |
+
|
| 5 |
+
// ── Animated confidence arc ───────────────────────────────────────────────────
|
| 6 |
+
function ConfidenceArc({ pct, isFake }: { pct: number; isFake: boolean }) {
|
| 7 |
+
const r = 54
|
| 8 |
+
const circ = 2 * Math.PI * r
|
| 9 |
+
const half = circ / 2
|
| 10 |
+
const fill = (pct / 100) * half
|
| 11 |
+
const color = isFake ? '#ff3355' : '#00ff88'
|
| 12 |
+
|
| 13 |
+
return (
|
| 14 |
+
<div className="relative w-40 h-20 mx-auto">
|
| 15 |
+
<svg width="160" height="80" viewBox="0 0 160 80">
|
| 16 |
+
{/* Track */}
|
| 17 |
+
<path
|
| 18 |
+
d={`M 16 80 A ${r} ${r} 0 0 1 144 80`}
|
| 19 |
+
fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="10" strokeLinecap="round"
|
| 20 |
+
/>
|
| 21 |
+
{/* Fill */}
|
| 22 |
+
<motion.path
|
| 23 |
+
d={`M 16 80 A ${r} ${r} 0 0 1 144 80`}
|
| 24 |
+
fill="none"
|
| 25 |
+
stroke={color}
|
| 26 |
+
strokeWidth="10"
|
| 27 |
+
strokeLinecap="round"
|
| 28 |
+
strokeDasharray={`${half} ${half}`}
|
| 29 |
+
initial={{ strokeDashoffset: half }}
|
| 30 |
+
animate={{ strokeDashoffset: half - fill }}
|
| 31 |
+
transition={{ duration: 1.4, ease: [0.22, 1, 0.36, 1] }}
|
| 32 |
+
style={{ filter: `drop-shadow(0 0 8px ${color})` }}
|
| 33 |
+
/>
|
| 34 |
+
</svg>
|
| 35 |
+
<div className="absolute inset-0 flex flex-col items-center justify-end pb-1">
|
| 36 |
+
<motion.span
|
| 37 |
+
className="text-2xl font-bold font-mono"
|
| 38 |
+
style={{ color }}
|
| 39 |
+
initial={{ opacity: 0 }}
|
| 40 |
+
animate={{ opacity: 1 }}
|
| 41 |
+
transition={{ delay: 0.5 }}
|
| 42 |
+
>
|
| 43 |
+
{pct}%
|
| 44 |
+
</motion.span>
|
| 45 |
+
</div>
|
| 46 |
+
</div>
|
| 47 |
+
)
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
// ── Frame timeline ────────────────────────────────────────────────────────────
|
| 51 |
+
function Timeline({ frames, isFake }: { frames: FramePoint[]; isFake: boolean }) {
|
| 52 |
+
if (!frames.length) return (
|
| 53 |
+
<p className="text-white/30 text-xs font-mono text-center py-4">No per-frame data</p>
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
const color = isFake ? '#ff3355' : '#00ff88'
|
| 57 |
+
const maxH = 60
|
| 58 |
+
|
| 59 |
+
return (
|
| 60 |
+
<div className="flex items-end gap-1 overflow-x-auto pb-1" style={{ height: maxH + 24 + 'px', position: 'relative' }}>
|
| 61 |
+
{/* 60% threshold line */}
|
| 62 |
+
<div
|
| 63 |
+
className="absolute left-0 right-0 pointer-events-none"
|
| 64 |
+
style={{ bottom: 24 + maxH * 0.6, height: 1, background: 'rgba(255,170,0,0.35)' }}
|
| 65 |
+
>
|
| 66 |
+
<span className="absolute right-0 -top-4 text-[9px] text-yellow-400/60 font-mono">60%</span>
|
| 67 |
+
</div>
|
| 68 |
+
|
| 69 |
+
{frames.map((pt, i) => {
|
| 70 |
+
const h = Math.max(3, (pt.fake_pct / 100) * maxH)
|
| 71 |
+
const hot = pt.fake_pct >= 60
|
| 72 |
+
return (
|
| 73 |
+
<div key={i} className="group relative flex-shrink-0" style={{ width: 10, height: maxH }}>
|
| 74 |
+
{/* Tooltip */}
|
| 75 |
+
<div className="absolute bottom-full mb-1 left-1/2 -translate-x-1/2 bg-[#0a0a14] border border-white/10 rounded px-1.5 py-0.5 text-[9px] font-mono whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity z-10 pointer-events-none">
|
| 76 |
+
F{pt.frame}: {pt.fake_pct}%
|
| 77 |
+
</div>
|
| 78 |
+
<motion.div
|
| 79 |
+
className="absolute bottom-0 w-full rounded-t"
|
| 80 |
+
style={{
|
| 81 |
+
background: hot
|
| 82 |
+
? `linear-gradient(to top, ${color}, rgba(255,255,255,0.3))`
|
| 83 |
+
: 'rgba(255,255,255,0.1)',
|
| 84 |
+
boxShadow: hot ? `0 0 6px ${color}66` : 'none',
|
| 85 |
+
}}
|
| 86 |
+
initial={{ height: 0 }}
|
| 87 |
+
animate={{ height: h }}
|
| 88 |
+
transition={{ duration: 0.6, delay: i * 0.02, ease: [0.22, 1, 0.36, 1] }}
|
| 89 |
+
/>
|
| 90 |
+
</div>
|
| 91 |
+
)
|
| 92 |
+
})}
|
| 93 |
+
</div>
|
| 94 |
+
)
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// ── Audio card ────────────────────────────────────────────────────────────────
|
| 98 |
+
function AudioCard({ audio }: { audio: AudioResult }) {
|
| 99 |
+
if (!audio?.available) return null
|
| 100 |
+
|
| 101 |
+
const isAI = audio.result === 'AI_VOICE'
|
| 102 |
+
const color = isAI ? '#ff3355' : '#00ff88'
|
| 103 |
+
const colorDim = isAI ? 'rgba(255,51,85,0.12)' : 'rgba(0,255,136,0.12)'
|
| 104 |
+
|
| 105 |
+
return (
|
| 106 |
+
<motion.div
|
| 107 |
+
className="rounded-2xl border p-5"
|
| 108 |
+
style={{ borderColor: `${color}33`, background: colorDim }}
|
| 109 |
+
initial={{ opacity: 0, y: 16 }}
|
| 110 |
+
animate={{ opacity: 1, y: 0 }}
|
| 111 |
+
transition={{ delay: 0.4 }}
|
| 112 |
+
>
|
| 113 |
+
<div className="flex items-center gap-3 mb-4">
|
| 114 |
+
<div className="w-1 h-5 rounded-full" style={{ background: '#00aaff', boxShadow: '0 0 8px #00aaff' }} />
|
| 115 |
+
<span className="text-[10px] font-bold tracking-widest text-[#00aaff] uppercase">Voice Authenticity</span>
|
| 116 |
+
</div>
|
| 117 |
+
|
| 118 |
+
<div className="flex items-center gap-3 mb-4">
|
| 119 |
+
<span
|
| 120 |
+
className="px-3 py-1.5 rounded-full text-xs font-bold tracking-wider border"
|
| 121 |
+
style={{ color, borderColor: `${color}55`, background: `${color}12` }}
|
| 122 |
+
>
|
| 123 |
+
{isAI ? '🤖 AI VOICE DETECTED' : '🎙️ HUMAN VOICE'}
|
| 124 |
+
</span>
|
| 125 |
+
<span className="text-2xl font-bold font-mono ml-auto" style={{ color }}>
|
| 126 |
+
{audio.confidence}%
|
| 127 |
+
</span>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
+
{/* Bar */}
|
| 131 |
+
<div className="h-2 rounded-full bg-white/5 overflow-hidden mb-4">
|
| 132 |
+
<motion.div
|
| 133 |
+
className="h-full rounded-full"
|
| 134 |
+
style={{
|
| 135 |
+
background: isAI ? 'linear-gradient(90deg,#880022,#ff3355)' : 'linear-gradient(90deg,#00aaff,#00ff88)',
|
| 136 |
+
boxShadow: `0 0 10px ${color}66`,
|
| 137 |
+
}}
|
| 138 |
+
initial={{ width: 0 }}
|
| 139 |
+
animate={{ width: `${audio.confidence}%` }}
|
| 140 |
+
transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
|
| 141 |
+
/>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
{/* Scores */}
|
| 145 |
+
<div className="grid grid-cols-2 gap-3 mb-4">
|
| 146 |
+
{[
|
| 147 |
+
{ label: 'Wav2Vec2 Model', val: `${audio.model_score}%` },
|
| 148 |
+
{ label: 'Signal Heuristics', val: `${audio.heuristic_score}%` },
|
| 149 |
+
].map(({ label, val }) => (
|
| 150 |
+
<div key={label} className="rounded-xl bg-white/3 border border-white/5 p-3 text-center">
|
| 151 |
+
<p className="text-[10px] text-white/40 tracking-wider mb-1">{label}</p>
|
| 152 |
+
<p className="text-lg font-bold font-mono text-white">{val}</p>
|
| 153 |
+
</div>
|
| 154 |
+
))}
|
| 155 |
+
</div>
|
| 156 |
+
|
| 157 |
+
{/* Details */}
|
| 158 |
+
<div className="space-y-2">
|
| 159 |
+
{audio.details.map((d, i) => (
|
| 160 |
+
<motion.div
|
| 161 |
+
key={i}
|
| 162 |
+
className="flex items-start gap-2.5 text-xs text-white/60 bg-white/2 rounded-lg px-3 py-2 border-l-2"
|
| 163 |
+
style={{ borderLeftColor: color }}
|
| 164 |
+
initial={{ opacity: 0, x: -8 }}
|
| 165 |
+
animate={{ opacity: 1, x: 0 }}
|
| 166 |
+
transition={{ delay: 0.5 + i * 0.07 }}
|
| 167 |
+
>
|
| 168 |
+
<span className="w-1.5 h-1.5 rounded-full flex-shrink-0 mt-1" style={{ background: color, boxShadow: `0 0 6px ${color}` }} />
|
| 169 |
+
{d}
|
| 170 |
+
</motion.div>
|
| 171 |
+
))}
|
| 172 |
+
</div>
|
| 173 |
+
|
| 174 |
+
{/* Features */}
|
| 175 |
+
{audio.features && Object.keys(audio.features).length > 0 && (
|
| 176 |
+
<div className="mt-4 pt-4 border-t border-white/5">
|
| 177 |
+
<p className="text-[10px] text-white/30 tracking-widest uppercase mb-2">Signal Features</p>
|
| 178 |
+
<div className="grid grid-cols-2 gap-2">
|
| 179 |
+
{Object.entries(audio.features).map(([k, v]) => (
|
| 180 |
+
<div key={k} className="flex justify-between text-[10px]">
|
| 181 |
+
<span className="text-white/30 capitalize">{k.replace(/_/g, ' ')}</span>
|
| 182 |
+
<span className="text-white/70 font-mono">{typeof v === 'number' ? v.toFixed(3) : v}</span>
|
| 183 |
+
</div>
|
| 184 |
+
))}
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
)}
|
| 188 |
+
</motion.div>
|
| 189 |
+
)
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
// ── Main result panel ─────────────────────────────────────────────────────────
|
| 193 |
+
export default function ResultPanel() {
|
| 194 |
+
const { result, reset } = useStore()
|
| 195 |
+
if (!result) return null
|
| 196 |
+
|
| 197 |
+
const isFake = result.result === 'FAKE'
|
| 198 |
+
const color = isFake ? '#ff3355' : '#00ff88'
|
| 199 |
+
const colorDim = isFake ? 'rgba(255,51,85,0.08)' : 'rgba(0,255,136,0.08)'
|
| 200 |
+
|
| 201 |
+
return (
|
| 202 |
+
<AnimatePresence>
|
| 203 |
+
<motion.div
|
| 204 |
+
className="w-full max-w-2xl mx-auto space-y-4"
|
| 205 |
+
initial={{ opacity: 0 }}
|
| 206 |
+
animate={{ opacity: 1 }}
|
| 207 |
+
exit={{ opacity: 0 }}
|
| 208 |
+
>
|
| 209 |
+
{/* Verdict card */}
|
| 210 |
+
<motion.div
|
| 211 |
+
className="rounded-2xl border-2 p-6"
|
| 212 |
+
style={{ borderColor: `${color}55`, background: colorDim, boxShadow: `0 0 50px ${color}15` }}
|
| 213 |
+
initial={{ scale: 0.95, opacity: 0 }}
|
| 214 |
+
animate={{ scale: 1, opacity: 1 }}
|
| 215 |
+
transition={{ type: 'spring', stiffness: 200, damping: 20 }}
|
| 216 |
+
>
|
| 217 |
+
<div className="flex flex-wrap items-center gap-6">
|
| 218 |
+
{/* Badge */}
|
| 219 |
+
<div className="text-center flex-shrink-0">
|
| 220 |
+
<motion.div
|
| 221 |
+
className="w-24 h-24 rounded-full border-2 flex items-center justify-center mx-auto mb-3 text-4xl"
|
| 222 |
+
style={{ borderColor: color, boxShadow: `0 0 30px ${color}44`, background: `${color}10` }}
|
| 223 |
+
animate={{ boxShadow: [`0 0 20px ${color}30`, `0 0 50px ${color}60`, `0 0 20px ${color}30`] }}
|
| 224 |
+
transition={{ duration: 2, repeat: Infinity }}
|
| 225 |
+
>
|
| 226 |
+
{isFake ? '⚠️' : '✅'}
|
| 227 |
+
</motion.div>
|
| 228 |
+
<motion.p
|
| 229 |
+
className="text-2xl font-bold tracking-widest"
|
| 230 |
+
style={{ color, textShadow: `0 0 20px ${color}66` }}
|
| 231 |
+
animate={isFake ? { textShadow: [`0 0 10px ${color}44`, `0 0 25px ${color}88`, `0 0 10px ${color}44`] } : {}}
|
| 232 |
+
transition={{ duration: 2.5, repeat: Infinity }}
|
| 233 |
+
>
|
| 234 |
+
{isFake ? 'DEEPFAKE' : 'AUTHENTIC'}
|
| 235 |
+
</motion.p>
|
| 236 |
+
<p className="text-[10px] text-white/30 tracking-widest mt-1">VERDICT</p>
|
| 237 |
+
</div>
|
| 238 |
+
|
| 239 |
+
{/* Confidence */}
|
| 240 |
+
<div className="flex-1 min-w-[200px]">
|
| 241 |
+
<p className="text-[10px] text-white/40 tracking-widest uppercase mb-3">Confidence Score</p>
|
| 242 |
+
<ConfidenceArc pct={result.confidence} isFake={isFake} />
|
| 243 |
+
|
| 244 |
+
{/* Risk pill */}
|
| 245 |
+
<div className="mt-4 flex items-center justify-between bg-white/3 rounded-xl px-4 py-3 border border-white/5">
|
| 246 |
+
<span className="text-[10px] text-white/40 tracking-wider uppercase">Risk Level</span>
|
| 247 |
+
<span
|
| 248 |
+
className="text-xs font-bold tracking-wider px-3 py-1 rounded-full border"
|
| 249 |
+
style={{
|
| 250 |
+
color: result.confidence < 35 ? '#00ff88' : result.confidence < 65 ? '#ffaa00' : '#ff3355',
|
| 251 |
+
borderColor: result.confidence < 35 ? '#00ff8844' : result.confidence < 65 ? '#ffaa0044' : '#ff335544',
|
| 252 |
+
background: result.confidence < 35 ? '#00ff8810' : result.confidence < 65 ? '#ffaa0010' : '#ff335510',
|
| 253 |
+
}}
|
| 254 |
+
>
|
| 255 |
+
{result.confidence < 35 ? 'LOW' : result.confidence < 65 ? 'MEDIUM' : result.confidence < 80 ? 'HIGH' : 'CRITICAL'}
|
| 256 |
+
</span>
|
| 257 |
+
</div>
|
| 258 |
+
</div>
|
| 259 |
+
</div>
|
| 260 |
+
</motion.div>
|
| 261 |
+
|
| 262 |
+
{/* Insights + Meta */}
|
| 263 |
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 264 |
+
{/* Insights */}
|
| 265 |
+
<div className="rounded-2xl border border-white/6 bg-white/2 p-5">
|
| 266 |
+
<div className="flex items-center gap-2 mb-4">
|
| 267 |
+
<div className="w-0.5 h-4 rounded-full bg-[#00aaff] shadow-[0_0_8px_#00aaff]" />
|
| 268 |
+
<span className="text-[10px] font-bold tracking-widest text-[#00aaff] uppercase">Analysis Insights</span>
|
| 269 |
+
</div>
|
| 270 |
+
<div className="space-y-2">
|
| 271 |
+
{result.details.map((d, i) => (
|
| 272 |
+
<motion.div
|
| 273 |
+
key={i}
|
| 274 |
+
className="flex items-start gap-2.5 text-xs text-white/60 bg-white/2 rounded-lg px-3 py-2.5 border-l-2"
|
| 275 |
+
style={{ borderLeftColor: color }}
|
| 276 |
+
initial={{ opacity: 0, x: -10 }}
|
| 277 |
+
animate={{ opacity: 1, x: 0 }}
|
| 278 |
+
transition={{ delay: 0.1 + i * 0.07 }}
|
| 279 |
+
>
|
| 280 |
+
<span className="w-1.5 h-1.5 rounded-full flex-shrink-0 mt-1" style={{ background: color }} />
|
| 281 |
+
{d}
|
| 282 |
+
</motion.div>
|
| 283 |
+
))}
|
| 284 |
+
</div>
|
| 285 |
+
</div>
|
| 286 |
+
|
| 287 |
+
{/* Metadata */}
|
| 288 |
+
<div className="rounded-2xl border border-white/6 bg-white/2 p-5">
|
| 289 |
+
<div className="flex items-center gap-2 mb-4">
|
| 290 |
+
<div className="w-0.5 h-4 rounded-full bg-[#00aaff] shadow-[0_0_8px_#00aaff]" />
|
| 291 |
+
<span className="text-[10px] font-bold tracking-widest text-[#00aaff] uppercase">Video Metadata</span>
|
| 292 |
+
</div>
|
| 293 |
+
<div className="space-y-2.5">
|
| 294 |
+
{[
|
| 295 |
+
['Frames Analyzed', result.metadata.frames_analyzed],
|
| 296 |
+
['Faces Detected', result.metadata.frames_with_faces],
|
| 297 |
+
['Duration', `${result.metadata.video_duration_sec}s`],
|
| 298 |
+
['FPS', result.metadata.video_fps],
|
| 299 |
+
['Resolution', result.metadata.resolution],
|
| 300 |
+
['Processing Time', `${result.processing_time_sec}s`],
|
| 301 |
+
].map(([k, v]) => (
|
| 302 |
+
<div key={String(k)} className="flex justify-between items-center border-b border-white/4 pb-2 last:border-0 last:pb-0">
|
| 303 |
+
<span className="text-[11px] text-white/40">{k}</span>
|
| 304 |
+
<span className="text-[12px] font-bold font-mono text-white">{v ?? '—'}</span>
|
| 305 |
+
</div>
|
| 306 |
+
))}
|
| 307 |
+
</div>
|
| 308 |
+
<div className="mt-4 pt-3 border-t border-white/5 flex items-center gap-2">
|
| 309 |
+
<div className="w-1.5 h-1.5 rounded-full bg-[#00ff88] shadow-[0_0_6px_#00ff88]" />
|
| 310 |
+
<span className="text-[10px] text-white/30 font-mono tracking-wider">ViT Ensemble · 2 Models</span>
|
| 311 |
+
</div>
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
|
| 315 |
+
{/* Frame Timeline */}
|
| 316 |
+
<div className="rounded-2xl border border-white/6 bg-white/2 p-5">
|
| 317 |
+
<div className="flex items-center gap-2 mb-4">
|
| 318 |
+
<div className="w-0.5 h-4 rounded-full bg-[#00aaff] shadow-[0_0_8px_#00aaff]" />
|
| 319 |
+
<span className="text-[10px] font-bold tracking-widest text-[#00aaff] uppercase">Frame Analysis Timeline</span>
|
| 320 |
+
<span className="text-[10px] text-white/20 font-mono ml-1">— {result.frame_timeline.length} frames</span>
|
| 321 |
+
</div>
|
| 322 |
+
<Timeline frames={result.frame_timeline} isFake={isFake} />
|
| 323 |
+
<div className="flex justify-between mt-2">
|
| 324 |
+
<span className="text-[10px] text-white/20 font-mono">Frame 0</span>
|
| 325 |
+
<span className="text-[10px] text-white/20 font-mono">Frame {result.frame_timeline.length}</span>
|
| 326 |
+
</div>
|
| 327 |
+
</div>
|
| 328 |
+
|
| 329 |
+
{/* Audio */}
|
| 330 |
+
{result.audio && <AudioCard audio={result.audio} />}
|
| 331 |
+
|
| 332 |
+
{/* Reset */}
|
| 333 |
+
<div className="text-center pt-2">
|
| 334 |
+
<motion.button
|
| 335 |
+
onClick={reset}
|
| 336 |
+
className="px-6 py-2.5 text-xs font-bold tracking-widest uppercase text-white/40 border border-white/10 rounded-xl hover:text-[#00ff88] hover:border-[#00ff8840] hover:bg-[#00ff8808] transition-all duration-200"
|
| 337 |
+
whileHover={{ scale: 1.02 }}
|
| 338 |
+
whileTap={{ scale: 0.98 }}
|
| 339 |
+
>
|
| 340 |
+
↻ Analyze Another Video
|
| 341 |
+
</motion.button>
|
| 342 |
+
</div>
|
| 343 |
+
</motion.div>
|
| 344 |
+
</AnimatePresence>
|
| 345 |
+
)
|
| 346 |
+
}
|
frontend/src/components/UploadZone.tsx
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useRef, useState } from 'react'
|
| 2 |
+
import { motion } from 'framer-motion'
|
| 3 |
+
import { useStore } from '../store'
|
| 4 |
+
import { analyzeVideo } from '../api'
|
| 5 |
+
|
| 6 |
+
const AGENTS = [
|
| 7 |
+
{ icon: '🎬', label: 'Frame Extractor', sub: 'Sampling keyframes' },
|
| 8 |
+
{ icon: '👤', label: 'Face Detector', sub: 'Isolating regions' },
|
| 9 |
+
{ icon: '🧠', label: 'ViT Ensemble', sub: 'Neural inference' },
|
| 10 |
+
{ icon: '📊', label: 'Report Builder', sub: 'Compiling results' },
|
| 11 |
+
]
|
| 12 |
+
|
| 13 |
+
function fmtBytes(b: number) {
|
| 14 |
+
if (b < 1024) return `${b} B`
|
| 15 |
+
if (b < 1048576) return `${(b / 1024).toFixed(1)} KB`
|
| 16 |
+
return `${(b / 1048576).toFixed(1)} MB`
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
export default function UploadZone() {
|
| 20 |
+
const { file, state, setFile, setState, setResult, setError, setAgentStep, reset } = useStore()
|
| 21 |
+
const inputRef = useRef<HTMLInputElement>(null)
|
| 22 |
+
const [dragging, setDragging] = useState(false)
|
| 23 |
+
|
| 24 |
+
const isLoading = state === 'loading'
|
| 25 |
+
|
| 26 |
+
function applyFile(f: File) {
|
| 27 |
+
if (f.type.startsWith('video/')) setFile(f)
|
| 28 |
+
else setError('Please select a valid video file (MP4, AVI, MOV, MKV, WebM).')
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
async function handleAnalyze() {
|
| 32 |
+
if (!file) return
|
| 33 |
+
setState('loading')
|
| 34 |
+
setAgentStep(0)
|
| 35 |
+
|
| 36 |
+
// Simulate agent steps
|
| 37 |
+
const delays = [0, 1800, 4200, 7500]
|
| 38 |
+
delays.forEach((d, i) => setTimeout(() => setAgentStep(i), d))
|
| 39 |
+
|
| 40 |
+
try {
|
| 41 |
+
const result = await analyzeVideo(file)
|
| 42 |
+
setResult(result)
|
| 43 |
+
} catch (e: any) {
|
| 44 |
+
setError(e.message || 'Analysis failed')
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
return (
|
| 49 |
+
<div className="w-full max-w-xl mx-auto flex flex-col gap-5">
|
| 50 |
+
{/* Drop zone */}
|
| 51 |
+
<motion.div
|
| 52 |
+
className={`relative rounded-2xl border-2 border-dashed cursor-pointer transition-all duration-300 overflow-hidden
|
| 53 |
+
${dragging ? 'border-[#00ff88] bg-[#00ff8808] shadow-[0_0_40px_#00ff8820]' : 'border-[#00ff8830] hover:border-[#00ff8866] hover:bg-[#00ff8806]'}
|
| 54 |
+
${file ? 'py-6 px-8' : 'py-12 px-8'}`}
|
| 55 |
+
onClick={() => !file && inputRef.current?.click()}
|
| 56 |
+
onDragOver={(e) => { e.preventDefault(); setDragging(true) }}
|
| 57 |
+
onDragLeave={() => setDragging(false)}
|
| 58 |
+
onDrop={(e) => {
|
| 59 |
+
e.preventDefault(); setDragging(false)
|
| 60 |
+
const f = e.dataTransfer.files[0]
|
| 61 |
+
if (f) applyFile(f)
|
| 62 |
+
}}
|
| 63 |
+
whileHover={{ scale: file ? 1 : 1.01 }}
|
| 64 |
+
whileTap={{ scale: 0.99 }}
|
| 65 |
+
>
|
| 66 |
+
<input
|
| 67 |
+
ref={inputRef}
|
| 68 |
+
type="file"
|
| 69 |
+
accept="video/*"
|
| 70 |
+
className="hidden"
|
| 71 |
+
onChange={(e) => e.target.files?.[0] && applyFile(e.target.files[0])}
|
| 72 |
+
/>
|
| 73 |
+
|
| 74 |
+
{!file ? (
|
| 75 |
+
<motion.div className="text-center" initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
|
| 76 |
+
{/* Orbit upload icon */}
|
| 77 |
+
<div className="relative w-20 h-20 mx-auto mb-5">
|
| 78 |
+
<motion.div
|
| 79 |
+
className="absolute inset-0 rounded-full border border-dashed border-[#00ff8840]"
|
| 80 |
+
animate={{ rotate: 360 }}
|
| 81 |
+
transition={{ duration: 8, repeat: Infinity, ease: 'linear' }}
|
| 82 |
+
/>
|
| 83 |
+
<motion.div
|
| 84 |
+
className="absolute inset-3 rounded-full border border-[#00ff8820]"
|
| 85 |
+
animate={{ rotate: -360 }}
|
| 86 |
+
transition={{ duration: 5, repeat: Infinity, ease: 'linear' }}
|
| 87 |
+
/>
|
| 88 |
+
<div className="absolute inset-0 flex items-center justify-center">
|
| 89 |
+
<svg width="28" height="28" fill="none" stroke="#00ff8899" strokeWidth="1.5" viewBox="0 0 24 24">
|
| 90 |
+
<path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/>
|
| 91 |
+
</svg>
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
<p className="text-white/80 font-semibold text-base mb-1">Drop video for forensic analysis</p>
|
| 95 |
+
<p className="text-white/40 text-sm">Drag & drop or click to browse</p>
|
| 96 |
+
<p className="text-white/20 text-xs mt-3 font-mono">MP4 · AVI · MOV · MKV · WebM — Max 100MB</p>
|
| 97 |
+
</motion.div>
|
| 98 |
+
) : (
|
| 99 |
+
<motion.div
|
| 100 |
+
className="flex items-center gap-4"
|
| 101 |
+
initial={{ opacity: 0, y: 8 }}
|
| 102 |
+
animate={{ opacity: 1, y: 0 }}
|
| 103 |
+
>
|
| 104 |
+
<div className="w-12 h-12 rounded-xl bg-[#00ff8812] border border-[#00ff8830] flex items-center justify-center flex-shrink-0 shadow-[0_0_20px_#00ff8820]">
|
| 105 |
+
<svg width="22" height="22" fill="none" stroke="#00ff88" strokeWidth="1.5" viewBox="0 0 24 24">
|
| 106 |
+
<path strokeLinecap="round" strokeLinejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"/>
|
| 107 |
+
</svg>
|
| 108 |
+
</div>
|
| 109 |
+
<div className="flex-1 min-w-0">
|
| 110 |
+
<p className="text-white font-semibold text-sm truncate">{file.name}</p>
|
| 111 |
+
<p className="text-white/40 text-xs mt-0.5 font-mono">{fmtBytes(file.size)}</p>
|
| 112 |
+
</div>
|
| 113 |
+
<button
|
| 114 |
+
onClick={(e) => { e.stopPropagation(); reset() }}
|
| 115 |
+
className="p-2 text-white/30 hover:text-red-400 transition-colors rounded-lg hover:bg-red-400/10"
|
| 116 |
+
>
|
| 117 |
+
<svg width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
|
| 118 |
+
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
| 119 |
+
</svg>
|
| 120 |
+
</button>
|
| 121 |
+
</motion.div>
|
| 122 |
+
)}
|
| 123 |
+
</motion.div>
|
| 124 |
+
|
| 125 |
+
{/* Analyze button */}
|
| 126 |
+
<motion.button
|
| 127 |
+
disabled={!file || isLoading}
|
| 128 |
+
onClick={handleAnalyze}
|
| 129 |
+
className={`w-full py-4 rounded-xl font-bold text-sm tracking-widest uppercase transition-all duration-300 relative overflow-hidden
|
| 130 |
+
${file && !isLoading
|
| 131 |
+
? 'bg-gradient-to-r from-[#00ff88] to-[#00cc66] text-[#050508] shadow-[0_0_30px_#00ff8840] cursor-pointer'
|
| 132 |
+
: 'bg-white/5 text-white/20 cursor-not-allowed border border-white/10'}`}
|
| 133 |
+
whileHover={file && !isLoading ? { scale: 1.02, boxShadow: '0 0 50px #00ff8860' } : {}}
|
| 134 |
+
whileTap={file && !isLoading ? { scale: 0.98 } : {}}
|
| 135 |
+
>
|
| 136 |
+
{/* Shimmer */}
|
| 137 |
+
{file && !isLoading && (
|
| 138 |
+
<motion.div
|
| 139 |
+
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent skew-x-12"
|
| 140 |
+
animate={{ x: ['-100%', '200%'] }}
|
| 141 |
+
transition={{ duration: 2.5, repeat: Infinity, ease: 'linear' }}
|
| 142 |
+
/>
|
| 143 |
+
)}
|
| 144 |
+
<span className="relative z-10">
|
| 145 |
+
{isLoading ? '⏳ Analyzing...' : '▶ Analyze Video'}
|
| 146 |
+
</span>
|
| 147 |
+
</motion.button>
|
| 148 |
+
|
| 149 |
+
{/* Agent pipeline (loading state) */}
|
| 150 |
+
{isLoading && <AgentPipeline />}
|
| 151 |
+
</div>
|
| 152 |
+
)
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
function AgentPipeline() {
|
| 156 |
+
const agentStep = useStore((s) => s.agentStep)
|
| 157 |
+
|
| 158 |
+
return (
|
| 159 |
+
<motion.div
|
| 160 |
+
initial={{ opacity: 0, y: 12 }}
|
| 161 |
+
animate={{ opacity: 1, y: 0 }}
|
| 162 |
+
className="grid grid-cols-2 gap-3"
|
| 163 |
+
>
|
| 164 |
+
{AGENTS.map((agent, i) => {
|
| 165 |
+
const active = i <= agentStep
|
| 166 |
+
const current = i === agentStep
|
| 167 |
+
return (
|
| 168 |
+
<motion.div
|
| 169 |
+
key={i}
|
| 170 |
+
className={`rounded-xl p-3 border transition-all duration-500 flex items-center gap-3
|
| 171 |
+
${active
|
| 172 |
+
? 'border-[#00ff8844] bg-[#00ff8808] shadow-[0_0_15px_#00ff8815]'
|
| 173 |
+
: 'border-white/5 bg-white/2'}`}
|
| 174 |
+
animate={current ? { scale: [1, 1.02, 1] } : {}}
|
| 175 |
+
transition={{ duration: 0.6, repeat: current ? Infinity : 0 }}
|
| 176 |
+
>
|
| 177 |
+
<div className={`w-2 h-2 rounded-full flex-shrink-0 transition-all duration-500
|
| 178 |
+
${active ? 'bg-[#00ff88] shadow-[0_0_8px_#00ff88]' : 'bg-white/15'}`} />
|
| 179 |
+
<div>
|
| 180 |
+
<p className="text-xs font-semibold text-white/80">{agent.label}</p>
|
| 181 |
+
<p className={`text-[10px] font-mono transition-colors duration-300 ${active ? 'text-[#00ff88]' : 'text-white/30'}`}>
|
| 182 |
+
{active ? (current ? agent.sub : 'Done ✓') : 'Waiting...'}
|
| 183 |
+
</p>
|
| 184 |
+
</div>
|
| 185 |
+
</motion.div>
|
| 186 |
+
)
|
| 187 |
+
})}
|
| 188 |
+
</motion.div>
|
| 189 |
+
)
|
| 190 |
+
}
|
frontend/src/index.css
CHANGED
|
@@ -1,57 +1,43 @@
|
|
| 1 |
@import "tailwindcss";
|
| 2 |
|
| 3 |
-
|
| 4 |
-
--primary: #00ff9c;
|
| 5 |
-
--primary-dim: #00cc7d;
|
| 6 |
-
--secondary: #00e5ff; /* soft cyan */
|
| 7 |
-
--red: #ff4444;
|
| 8 |
-
--red-dim: #cc2222;
|
| 9 |
-
--bg-dark: #040906;
|
| 10 |
-
--bg-gradient: radial-gradient(circle at top center, #001a11 0%, #040906 100%);
|
| 11 |
-
--card-bg: rgba(10, 20, 15, 0.6);
|
| 12 |
-
--card-bg-inner: rgba(15, 25, 20, 0.8);
|
| 13 |
-
--border: rgba(0, 255, 156, 0.15);
|
| 14 |
-
--border-hover: rgba(0, 255, 156, 0.4);
|
| 15 |
-
--text: #e2e8f0;
|
| 16 |
-
--muted: #849ca3;
|
| 17 |
-
}
|
| 18 |
|
| 19 |
body {
|
| 20 |
-
background:
|
| 21 |
-
|
| 22 |
-
font-family: 'Inter', sans-serif;
|
| 23 |
-
|
| 24 |
-
min-height: 100vh;
|
| 25 |
-
overflow-x: hidden;
|
| 26 |
-
letter-spacing: 0.02em;
|
| 27 |
}
|
| 28 |
|
| 29 |
-
|
| 30 |
-
::-webkit-scrollbar {
|
| 31 |
-
::-webkit-scrollbar-
|
| 32 |
-
::-webkit-scrollbar-thumb { background: rgba(0, 255, 156, 0.3); border-radius: 6px; }
|
| 33 |
|
| 34 |
-
/*
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
100% { background-position: 0 100vh; }
|
| 46 |
}
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
|
| 53 |
-
|
| 54 |
-
@keyframes fadeUp {
|
| 55 |
-
from { opacity: 0; transform: translateY(20px); }
|
| 56 |
-
to { opacity: 1; transform: none; }
|
| 57 |
-
}
|
|
|
|
| 1 |
@import "tailwindcss";
|
| 2 |
|
| 3 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
body {
|
| 6 |
+
background: #050508;
|
| 7 |
+
color: #e2e8f0;
|
| 8 |
+
font-family: 'Space Grotesk', 'Inter', sans-serif;
|
| 9 |
+
-webkit-font-smoothing: antialiased;
|
|
|
|
|
|
|
|
|
|
| 10 |
}
|
| 11 |
|
| 12 |
+
::-webkit-scrollbar { width: 4px; height: 4px; }
|
| 13 |
+
::-webkit-scrollbar-track { background: #050508; }
|
| 14 |
+
::-webkit-scrollbar-thumb { background: rgba(0,255,136,0.2); border-radius: 4px; }
|
|
|
|
| 15 |
|
| 16 |
+
/* Animated grid background */
|
| 17 |
+
body::before {
|
| 18 |
+
content: '';
|
| 19 |
+
position: fixed;
|
| 20 |
+
inset: 0;
|
| 21 |
+
z-index: 0;
|
| 22 |
+
pointer-events: none;
|
| 23 |
+
background-image:
|
| 24 |
+
linear-gradient(rgba(0,255,136,0.025) 1px, transparent 1px),
|
| 25 |
+
linear-gradient(90deg, rgba(0,255,136,0.025) 1px, transparent 1px);
|
| 26 |
+
background-size: 60px 60px;
|
|
|
|
| 27 |
}
|
| 28 |
|
| 29 |
+
/* Radial glow at top */
|
| 30 |
+
body::after {
|
| 31 |
+
content: '';
|
| 32 |
+
position: fixed;
|
| 33 |
+
top: -200px;
|
| 34 |
+
left: 50%;
|
| 35 |
+
transform: translateX(-50%);
|
| 36 |
+
width: 800px;
|
| 37 |
+
height: 600px;
|
| 38 |
+
background: radial-gradient(ellipse, rgba(0,255,136,0.06) 0%, transparent 70%);
|
| 39 |
+
pointer-events: none;
|
| 40 |
+
z-index: 0;
|
| 41 |
}
|
| 42 |
|
| 43 |
+
main, header, footer { position: relative; z-index: 1; }
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/main.tsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { StrictMode } from 'react'
|
| 2 |
+
import { createRoot } from 'react-dom/client'
|
| 3 |
+
import './index.css'
|
| 4 |
+
import App from './App.tsx'
|
| 5 |
+
|
| 6 |
+
createRoot(document.getElementById('root')!).render(
|
| 7 |
+
<StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</StrictMode>,
|
| 10 |
+
)
|
frontend/src/store.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { create } from 'zustand'
|
| 2 |
+
|
| 3 |
+
export type AppState = 'idle' | 'loading' | 'result' | 'error'
|
| 4 |
+
export type Verdict = 'FAKE' | 'REAL' | null
|
| 5 |
+
|
| 6 |
+
export interface FramePoint { frame: number; fake_pct: number }
|
| 7 |
+
export interface AudioResult {
|
| 8 |
+
available: boolean
|
| 9 |
+
result: string
|
| 10 |
+
confidence: number
|
| 11 |
+
fake_probability: number
|
| 12 |
+
model_score: number
|
| 13 |
+
heuristic_score: number
|
| 14 |
+
details: string[]
|
| 15 |
+
features: Record<string, number>
|
| 16 |
+
}
|
| 17 |
+
export interface AnalysisResult {
|
| 18 |
+
result: 'FAKE' | 'REAL'
|
| 19 |
+
confidence: number
|
| 20 |
+
details: string[]
|
| 21 |
+
frame_timeline: FramePoint[]
|
| 22 |
+
metadata: {
|
| 23 |
+
frames_analyzed: number
|
| 24 |
+
frames_with_faces: number
|
| 25 |
+
video_duration_sec: number
|
| 26 |
+
video_fps: number
|
| 27 |
+
resolution: string
|
| 28 |
+
}
|
| 29 |
+
processing_time_sec: number
|
| 30 |
+
audio: AudioResult
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
interface Store {
|
| 34 |
+
state: AppState
|
| 35 |
+
file: File | null
|
| 36 |
+
result: AnalysisResult | null
|
| 37 |
+
error: string | null
|
| 38 |
+
agentStep: number
|
| 39 |
+
setState: (s: AppState) => void
|
| 40 |
+
setFile: (f: File | null) => void
|
| 41 |
+
setResult: (r: AnalysisResult) => void
|
| 42 |
+
setError: (e: string) => void
|
| 43 |
+
setAgentStep: (n: number) => void
|
| 44 |
+
reset: () => void
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
export const useStore = create<Store>((set) => ({
|
| 48 |
+
state: 'idle',
|
| 49 |
+
file: null,
|
| 50 |
+
result: null,
|
| 51 |
+
error: null,
|
| 52 |
+
agentStep: -1,
|
| 53 |
+
setState: (state) => set({ state }),
|
| 54 |
+
setFile: (file) => set({ file }),
|
| 55 |
+
setResult: (result) => set({ result, state: 'result' }),
|
| 56 |
+
setError: (error) => set({ error, state: 'error' }),
|
| 57 |
+
setAgentStep: (agentStep) => set({ agentStep }),
|
| 58 |
+
reset: () => set({ state: 'idle', file: null, result: null, error: null, agentStep: -1 }),
|
| 59 |
+
}))
|
frontend/vite.config.js
CHANGED
|
@@ -2,7 +2,17 @@ import { defineConfig } from 'vite'
|
|
| 2 |
import react from '@vitejs/plugin-react'
|
| 3 |
import tailwindcss from '@tailwindcss/vite'
|
| 4 |
|
| 5 |
-
// https://vite.dev/config/
|
| 6 |
export default defineConfig({
|
| 7 |
plugins: [react(), tailwindcss()],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
})
|
|
|
|
| 2 |
import react from '@vitejs/plugin-react'
|
| 3 |
import tailwindcss from '@tailwindcss/vite'
|
| 4 |
|
|
|
|
| 5 |
export default defineConfig({
|
| 6 |
plugins: [react(), tailwindcss()],
|
| 7 |
+
server: {
|
| 8 |
+
port: 5173,
|
| 9 |
+
proxy: {
|
| 10 |
+
'/analyze': 'http://localhost:8000',
|
| 11 |
+
'/health': 'http://localhost:8000',
|
| 12 |
+
},
|
| 13 |
+
},
|
| 14 |
+
build: {
|
| 15 |
+
outDir: '../frontend-dist',
|
| 16 |
+
emptyOutDir: true,
|
| 17 |
+
},
|
| 18 |
})
|