Spaces:
Sleeping
Sleeping
shadow55gh commited on
Commit Β·
c5ec583
1
Parent(s): 6469706
fix: real ELA heatmap, real face boxes, real DCT freq, fix HF URLs
Browse files- backend/main.py +6 -1
- backend/models/deepfake_detector.py +87 -5
- frontend/index.html +262 -579
backend/main.py
CHANGED
|
@@ -187,10 +187,15 @@ async def analyze(file: UploadFile = File(...), modules: str = "all"):
|
|
| 187 |
results["module_scores"][46] = 1.0 - synth_result.get("confidence", 0.5)
|
| 188 |
|
| 189 |
metadata = extract_metadata(content, ct)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
module_scores = results["module_scores"]
|
| 191 |
custody = results["custody"]
|
| 192 |
key_findings = results.get("key_findings", [])
|
| 193 |
-
ai_summary = f"EfficientNet: {effnet_authentic:.1%} authentic | Combined: {combined:.1%} | {results.get('ai_summary','')}"
|
| 194 |
|
| 195 |
# Visualizations
|
| 196 |
try:
|
|
|
|
| 187 |
results["module_scores"][46] = 1.0 - synth_result.get("confidence", 0.5)
|
| 188 |
|
| 189 |
metadata = extract_metadata(content, ct)
|
| 190 |
+
# Inject face boxes and ELA data from ML models into metadata
|
| 191 |
+
metadata["faces"] = dl.get("face_boxes", [])
|
| 192 |
+
metadata["face_count"] = dl.get("face_count", 0)
|
| 193 |
+
metadata["ela_data"] = dl.get("ela_data", "")
|
| 194 |
+
metadata["freq_data"] = dl.get("freq_data", {})
|
| 195 |
module_scores = results["module_scores"]
|
| 196 |
custody = results["custody"]
|
| 197 |
key_findings = results.get("key_findings", [])
|
| 198 |
+
ai_summary = f"EfficientNet: {effnet_authentic:.1%} authentic | Combined: {combined:.1%} | faces: {dl.get('face_count',0)} | {results.get('ai_summary','')}"
|
| 199 |
|
| 200 |
# Visualizations
|
| 201 |
try:
|
backend/models/deepfake_detector.py
CHANGED
|
@@ -365,14 +365,25 @@ async def analyze_image(content: bytes) -> dict:
|
|
| 365 |
total_w = sum(weights_list)
|
| 366 |
fake_prob = sum(s * w for s, w in zip(scores_list, weights_list)) / total_w
|
| 367 |
|
| 368 |
-
# ββ Face
|
| 369 |
face_count = 0
|
|
|
|
| 370 |
if _mtcnn is not None:
|
| 371 |
try:
|
| 372 |
-
boxes,
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 376 |
|
| 377 |
logger.debug(
|
| 378 |
f"[DeepfakeDetector] fake={fake_prob:.3f} effnet={effnet_score:.3f} "
|
|
@@ -388,5 +399,76 @@ async def analyze_image(content: bytes) -> dict:
|
|
| 388 |
"gan_score": round(gan_score, 4),
|
| 389 |
"effnet_score": round(effnet_score, 4),
|
| 390 |
"face_count": face_count,
|
|
|
|
|
|
|
|
|
|
| 391 |
"method": "+".join(methods),
|
| 392 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
total_w = sum(weights_list)
|
| 366 |
fake_prob = sum(s * w for s, w in zip(scores_list, weights_list)) / total_w
|
| 367 |
|
| 368 |
+
# ββ Face detection via MTCNN β return boxes + count βββββ
|
| 369 |
face_count = 0
|
| 370 |
+
face_boxes = [] # list of [x, y, w, h] in pixel coords
|
| 371 |
if _mtcnn is not None:
|
| 372 |
try:
|
| 373 |
+
boxes, probs = _mtcnn.detect(img)
|
| 374 |
+
if boxes is not None:
|
| 375 |
+
face_count = len(boxes)
|
| 376 |
+
for box in boxes:
|
| 377 |
+
x1, y1, x2, y2 = [float(v) for v in box]
|
| 378 |
+
face_boxes.append([x1, y1, x2 - x1, y2 - y1]) # [x, y, w, h]
|
| 379 |
+
except Exception as e:
|
| 380 |
+
logger.debug(f"[MTCNN] detection failed: {e}")
|
| 381 |
+
|
| 382 |
+
# ββ Real ELA (Error Level Analysis) heatmap data βββββββββ
|
| 383 |
+
ela_data = _compute_ela(arr)
|
| 384 |
+
|
| 385 |
+
# ββ Frequency domain anomaly score βββββββββββββββββββββββ
|
| 386 |
+
freq_data = _compute_freq_anomaly(arr)
|
| 387 |
|
| 388 |
logger.debug(
|
| 389 |
f"[DeepfakeDetector] fake={fake_prob:.3f} effnet={effnet_score:.3f} "
|
|
|
|
| 399 |
"gan_score": round(gan_score, 4),
|
| 400 |
"effnet_score": round(effnet_score, 4),
|
| 401 |
"face_count": face_count,
|
| 402 |
+
"face_boxes": face_boxes, # [[x,y,w,h], ...] pixel coords
|
| 403 |
+
"ela_data": ela_data, # base64 PNG of ELA map
|
| 404 |
+
"freq_data": freq_data, # {bins: [...], magnitudes: [...]}
|
| 405 |
"method": "+".join(methods),
|
| 406 |
}
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
def _compute_ela(arr: np.ndarray) -> str:
|
| 410 |
+
"""
|
| 411 |
+
Real Error Level Analysis β compresses image at quality 75,
|
| 412 |
+
subtracts from original, amplifies difference, returns base64 PNG.
|
| 413 |
+
ELA highlights regions that have been re-compressed (edited/generated).
|
| 414 |
+
"""
|
| 415 |
+
try:
|
| 416 |
+
import io, base64
|
| 417 |
+
from PIL import Image
|
| 418 |
+
|
| 419 |
+
img_orig = Image.fromarray(arr)
|
| 420 |
+
|
| 421 |
+
# Save at reduced quality
|
| 422 |
+
buf = io.BytesIO()
|
| 423 |
+
img_orig.save(buf, format='JPEG', quality=75)
|
| 424 |
+
buf.seek(0)
|
| 425 |
+
img_compressed = Image.open(buf).convert('RGB')
|
| 426 |
+
|
| 427 |
+
orig_arr = np.array(img_orig, dtype=np.float32)
|
| 428 |
+
comp_arr = np.array(img_compressed, dtype=np.float32)
|
| 429 |
+
|
| 430 |
+
# Difference amplified
|
| 431 |
+
diff = np.abs(orig_arr - comp_arr)
|
| 432 |
+
diff_scaled = np.clip(diff * 15, 0, 255).astype(np.uint8)
|
| 433 |
+
|
| 434 |
+
ela_img = Image.fromarray(diff_scaled)
|
| 435 |
+
|
| 436 |
+
# Resize to reasonable size for frontend
|
| 437 |
+
ela_img = ela_img.resize((320, 240), Image.LANCZOS)
|
| 438 |
+
|
| 439 |
+
out = io.BytesIO()
|
| 440 |
+
ela_img.save(out, format='PNG')
|
| 441 |
+
return base64.b64encode(out.getvalue()).decode('utf-8')
|
| 442 |
+
except Exception as e:
|
| 443 |
+
logger.debug(f"[ELA] computation failed: {e}")
|
| 444 |
+
return ""
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
def _compute_freq_anomaly(arr: np.ndarray) -> dict:
|
| 448 |
+
"""
|
| 449 |
+
DCT frequency domain analysis.
|
| 450 |
+
Returns histogram of frequency magnitudes β AI images have characteristic
|
| 451 |
+
patterns in high-frequency bands.
|
| 452 |
+
"""
|
| 453 |
+
try:
|
| 454 |
+
from scipy.fftpack import dct
|
| 455 |
+
gray = arr.mean(axis=2).astype(np.float32)
|
| 456 |
+
# Sample a center patch
|
| 457 |
+
h, w = gray.shape
|
| 458 |
+
patch_size = min(256, h, w)
|
| 459 |
+
py, px = (h - patch_size) // 2, (w - patch_size) // 2
|
| 460 |
+
patch = gray[py:py+patch_size, px:px+patch_size]
|
| 461 |
+
|
| 462 |
+
# 2D DCT
|
| 463 |
+
dct_2d = dct(dct(patch, axis=0, norm='ortho'), axis=1, norm='ortho')
|
| 464 |
+
mag = np.abs(dct_2d).flatten()
|
| 465 |
+
|
| 466 |
+
# Log-scale histogram with 32 bins
|
| 467 |
+
mag_log = np.log1p(mag)
|
| 468 |
+
hist, _ = np.histogram(mag_log, bins=32)
|
| 469 |
+
hist_norm = (hist / (hist.max() + 1e-6)).tolist()
|
| 470 |
+
|
| 471 |
+
return {"bins": list(range(32)), "magnitudes": hist_norm}
|
| 472 |
+
except Exception as e:
|
| 473 |
+
logger.debug(f"[FreqAnalysis] computation failed: {e}")
|
| 474 |
+
return {"bins": list(range(32)), "magnitudes": [0.5] * 32}
|
frontend/index.html
CHANGED
|
@@ -63,52 +63,52 @@ input,textarea{font-family:inherit;cursor:none;user-select:text;-webkit-user-sel
|
|
| 63 |
/* BG */
|
| 64 |
.bg-layer{position:fixed;inset:0;z-index:0;pointer-events:none;
|
| 65 |
background:
|
| 66 |
-
radial-gradient(ellipse 70% 55% at 5% 0%,rgba(
|
| 67 |
-
radial-gradient(ellipse 60% 50% at 95% 100%,rgba(
|
| 68 |
-
radial-gradient(ellipse 50% 50% at 50% 50%,rgba(
|
| 69 |
-
repeating-linear-gradient(0deg,rgba(
|
| 70 |
-
repeating-linear-gradient(90deg,rgba(
|
| 71 |
-
linear-gradient(135deg,rgba(
|
| 72 |
.grain{position:fixed;inset:0;z-index:0;pointer-events:none;opacity:0.03;
|
| 73 |
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)'/%3E%3C/svg%3E");
|
| 74 |
background-size:200px;}
|
| 75 |
.blob{position:fixed;border-radius:50%;filter:blur(90px);pointer-events:none;z-index:0;animation:blobFloat 18s ease-in-out infinite;}
|
| 76 |
-
.b1{width:700px;height:700px;top:-200px;left:-200px;background:radial-gradient(circle,rgba(
|
| 77 |
-
.b2{width:500px;height:500px;bottom:-100px;right:-100px;background:radial-gradient(circle,rgba(
|
| 78 |
-
.b3{width:400px;height:400px;top:45%;left:40%;background:radial-gradient(circle,rgba(
|
| 79 |
@keyframes blobFloat{0%,100%{transform:translate(0,0)scale(1)}33%{transform:translate(50px,-40px)scale(1.06)}66%{transform:translate(-30px,30px)scale(0.94)}}
|
| 80 |
|
| 81 |
/* CURSOR */
|
| 82 |
#cur{position:fixed;width:10px;height:10px;background:var(--blue);border-radius:50%;pointer-events:none;z-index:99999;transform:translate(-50%,-50%);transition:width .2s var(--ease),height .2s var(--ease),opacity .2s;mix-blend-mode:normal;}
|
| 83 |
-
#cur-ring{position:fixed;width:30px;height:30px;border:1.5px solid rgba(
|
| 84 |
.h-active #cur{width:22px;height:22px;}
|
| 85 |
-
.c-active #cur{width:14px;height:14px;background:rgba(
|
| 86 |
|
| 87 |
/* LAYOUT */
|
| 88 |
.app{position:relative;z-index:2;min-height:100vh;}
|
| 89 |
.container{max-width:1280px;margin:0 auto;padding:0 40px;}
|
| 90 |
|
| 91 |
/* HEADER */
|
| 92 |
-
header{position:sticky;top:0;z-index:100;backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);background:rgba(
|
| 93 |
.header-inner{display:flex;align-items:center;justify-content:space-between;height:64px;}
|
| 94 |
.logo{display:flex;align-items:center;gap:10px;font-family:'Playfair Display',serif;font-weight:700;font-size:20px;letter-spacing:0.05em;color:var(--text-1);}
|
| 95 |
-
.logo-icon{width:34px;height:34px;border-radius:10px;background:linear-gradient(135deg,#
|
| 96 |
.logo-sub{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:400;color:var(--text-4);letter-spacing:0.15em;display:block;margin-top:1px;}
|
| 97 |
.header-center{display:flex;gap:4px;}
|
| 98 |
.nav-btn{padding:7px 16px;border-radius:100px;font-size:13px;font-weight:500;color:var(--text-3);letter-spacing:0.01em;transition:all 0.2s;cursor:none;border:1px solid transparent;}
|
| 99 |
.nav-btn:hover,.nav-btn.active{background:rgba(255,255,255,0.2);border-color:rgba(255,255,255,0.35);color:var(--text-1);box-shadow:var(--shadow-sm);}
|
| 100 |
.nav-btn.active{color:var(--blue);}
|
| 101 |
.header-right{display:flex;align-items:center;gap:10px;}
|
| 102 |
-
.status-badge{display:flex;align-items:center;gap:6px;padding:6px 12px;border-radius:100px;background:rgba(255,255,255,0.30);border:1px solid rgba(
|
| 103 |
.dot-live{width:7px;height:7px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green);animation:liveGlow 2s ease-in-out infinite;}
|
| 104 |
@keyframes liveGlow{0%,100%{opacity:1}50%{opacity:0.5}}
|
| 105 |
|
| 106 |
/* HERO */
|
| 107 |
.hero{padding:40px 0 20px;}
|
| 108 |
-
.hero-title{font-family:'Playfair Display',serif;font-size:42px;font-weight:900;letter-spacing:-0.02em;line-height:1.1;background:linear-gradient(135deg,#
|
| 109 |
.hero-sub{font-size:15px;color:var(--text-3);font-weight:400;line-height:1.5;}
|
| 110 |
.hero-badges{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px;}
|
| 111 |
-
.hero-badge{display:flex;align-items:center;gap:5px;padding:5px 12px;border-radius:100px;font-size:11px;font-weight:600;letter-spacing:0.04em;background:rgba(255,255,255,0.
|
| 112 |
.hero-badge:nth-child(1){animation-delay:.05s}
|
| 113 |
.hero-badge:nth-child(2){animation-delay:.1s}
|
| 114 |
.hero-badge:nth-child(3){animation-delay:.15s}
|
|
@@ -261,6 +261,8 @@ header{position:sticky;top:0;z-index:100;backdrop-filter:blur(24px) saturate(180
|
|
| 261 |
.mod-card.running .mod-status{background:var(--amber);box-shadow:0 0 8px var(--amber);animation:liveGlow 0.8s ease-in-out infinite;}
|
| 262 |
.mod-card.disabled-mod{opacity:0.4;filter:grayscale(0.6);}
|
| 263 |
.mod-card.disabled-mod .mod-status{background:rgba(0,0,0,0.1);}
|
|
|
|
|
|
|
| 264 |
.mod-icon{font-size:18px;margin-bottom:6px;display:block;}
|
| 265 |
.mod-name{font-size:10px;font-weight:700;color:var(--text-2);letter-spacing:0.04em;margin-bottom:2px;}
|
| 266 |
.mod-desc{font-size:9px;color:var(--text-4);line-height:1.4;}
|
|
@@ -426,51 +428,6 @@ canvas{width:100%;display:block;}
|
|
| 426 |
.fade-in:nth-child(1){animation-delay:.05s}
|
| 427 |
.fade-in:nth-child(2){animation-delay:.1s}
|
| 428 |
@keyframes fadeIn{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
|
| 429 |
-
.mod-card.mod-flagged{border-color:rgba(220,38,38,0.35)!important;background:rgba(220,38,38,0.06)!important;}
|
| 430 |
-
.mod-card.mod-flagged .mod-status{background:var(--red)!important;box-shadow:0 0 8px var(--red)!important;}
|
| 431 |
-
|
| 432 |
-
/* AI SUMMARY CARD */
|
| 433 |
-
.ai-summary-card{padding:16px 22px;border-top:1px solid rgba(255,255,255,0.2);display:none;}
|
| 434 |
-
.ai-summary-card.visible{display:block;}
|
| 435 |
-
.ai-summary-title{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;color:var(--text-4);letter-spacing:0.15em;text-transform:uppercase;margin-bottom:8px;}
|
| 436 |
-
.ai-summary-text{font-size:12px;color:var(--text-2);line-height:1.65;padding:12px 14px;border-radius:10px;background:rgba(255,255,255,0.22);border:1px solid rgba(255,255,255,0.4);}
|
| 437 |
-
|
| 438 |
-
/* BATCH SECTION */
|
| 439 |
-
.batch-drop-grid{display:flex;flex-direction:column;gap:10px;}
|
| 440 |
-
.batch-file-row{display:flex;align-items:center;gap:12px;padding:12px 16px;border-radius:12px;background:rgba(255,255,255,0.22);border:1px solid rgba(255,255,255,0.4);}
|
| 441 |
-
.batch-file-icon{font-size:20px;}
|
| 442 |
-
.batch-file-info{flex:1;}
|
| 443 |
-
.batch-file-name{font-size:13px;font-weight:600;color:var(--text-1);}
|
| 444 |
-
.batch-file-size{font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--text-4);}
|
| 445 |
-
.batch-file-status{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:700;}
|
| 446 |
-
.batch-file-remove{width:24px;height:24px;border-radius:50%;background:rgba(220,38,38,0.08);border:1px solid rgba(220,38,38,0.2);color:var(--red);font-size:12px;display:flex;align-items:center;justify-content:center;cursor:none;transition:all 0.2s;}
|
| 447 |
-
.batch-file-remove:hover{background:var(--red-pale);}
|
| 448 |
-
.batch-result-card{padding:16px;border-radius:14px;background:rgba(255,255,255,0.22);border:1px solid rgba(255,255,255,0.4);display:flex;align-items:center;gap:14px;cursor:none;transition:all 0.2s;}
|
| 449 |
-
.batch-result-card:hover{border-color:rgba(79,172,254,0.4);transform:translateX(3px);}
|
| 450 |
-
.batch-verdict-badge{padding:5px 12px;border-radius:100px;font-size:11px;font-weight:700;letter-spacing:0.06em;white-space:nowrap;}
|
| 451 |
-
.batch-summary-bar{display:flex;gap:8px;padding:12px 16px;border-radius:12px;background:rgba(255,255,255,0.22);border:1px solid rgba(255,255,255,0.3);margin-bottom:16px;}
|
| 452 |
-
.batch-stat{flex:1;text-align:center;}
|
| 453 |
-
.batch-stat-num{font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700;}
|
| 454 |
-
.batch-stat-lbl{font-size:10px;color:var(--text-4);letter-spacing:0.08em;}
|
| 455 |
-
|
| 456 |
-
/* HISTORY SECTION */
|
| 457 |
-
.history-filters{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px;}
|
| 458 |
-
.hist-filter-btn{padding:5px 14px;border-radius:100px;font-size:11px;font-weight:600;background:rgba(255,255,255,0.22);border:1px solid rgba(255,255,255,0.4);color:var(--text-3);cursor:none;transition:all 0.2s;}
|
| 459 |
-
.hist-filter-btn:hover,.hist-filter-btn.active{background:var(--blue-pale);border-color:rgba(79,172,254,0.5);color:var(--blue);}
|
| 460 |
-
.history-grid{display:flex;flex-direction:column;gap:10px;}
|
| 461 |
-
.history-card{display:flex;align-items:center;gap:14px;padding:14px 18px;border-radius:14px;background:rgba(255,255,255,0.18);border:1px solid rgba(255,255,255,0.4);cursor:none;transition:all 0.25s;}
|
| 462 |
-
.history-card:hover{background:rgba(255,255,255,0.28);border-color:rgba(79,172,254,0.35);transform:translateX(4px);}
|
| 463 |
-
.history-icon{font-size:22px;flex-shrink:0;}
|
| 464 |
-
.history-info{flex:1;min-width:0;}
|
| 465 |
-
.history-name{font-size:13px;font-weight:700;color:var(--text-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
| 466 |
-
.history-meta{font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--text-4);margin-top:2px;}
|
| 467 |
-
.history-verdict{padding:4px 12px;border-radius:100px;font-size:11px;font-weight:700;letter-spacing:0.06em;white-space:nowrap;flex-shrink:0;}
|
| 468 |
-
.history-risk{font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;min-width:48px;text-align:right;}
|
| 469 |
-
.history-empty{text-align:center;padding:48px 24px;color:var(--text-4);font-family:'JetBrains Mono',monospace;font-size:12px;}
|
| 470 |
-
.hist-stats-row{display:flex;gap:12px;margin-bottom:16px;}
|
| 471 |
-
.hist-stat-pill{flex:1;padding:12px;border-radius:12px;background:rgba(255,255,255,0.22);border:1px solid rgba(255,255,255,0.35);text-align:center;}
|
| 472 |
-
.hist-stat-pill-num{font-family:'JetBrains Mono',monospace;font-size:20px;font-weight:700;color:var(--text-1);}
|
| 473 |
-
.hist-stat-pill-lbl{font-size:10px;color:var(--text-4);letter-spacing:0.08em;margin-top:2px;}
|
| 474 |
</style>
|
| 475 |
</head>
|
| 476 |
<body>
|
|
@@ -498,17 +455,15 @@ canvas{width:100%;display:block;}
|
|
| 498 |
<div class="header-inner">
|
| 499 |
<div class="logo">
|
| 500 |
<div class="logo-icon">π¬</div>
|
| 501 |
-
<div>VERIDEX <span class="logo-sub">FORENSIC AI PLATFORM
|
| 502 |
</div>
|
| 503 |
<nav class="header-center">
|
| 504 |
<button class="nav-btn active" onclick="showSection('analyze')">Analyze</button>
|
| 505 |
-
<button class="nav-btn" onclick="showSection('batch')">β‘ Batch</button>
|
| 506 |
<button class="nav-btn" onclick="showSection('modules')">46 Modules</button>
|
| 507 |
-
<button class="nav-btn" onclick="showSection('history')">π History</button>
|
| 508 |
<button class="nav-btn" onclick="showSection('custody')">Chain of Custody</button>
|
| 509 |
</nav>
|
| 510 |
<div class="header-right">
|
| 511 |
-
<div class="status-badge"
|
| 512 |
</div>
|
| 513 |
</div>
|
| 514 |
</div>
|
|
@@ -649,16 +604,6 @@ canvas{width:100%;display:block;}
|
|
| 649 |
<button class="btn-report" id="btnReport" disabled onclick="openReport()">π GENERATE COURT REPORT</button>
|
| 650 |
</div>
|
| 651 |
|
| 652 |
-
<!-- AI SUMMARY CARD -->
|
| 653 |
-
<div class="card fade-in" id="aiSummaryCard" style="display:none;">
|
| 654 |
-
<div class="card-header"><div class="card-title"><div class="card-title-icon" style="background:rgba(124,58,237,0.08)">π§ </div>AI Forensic Summary</div></div>
|
| 655 |
-
<div class="ai-summary-card visible" id="aiSummaryWrap">
|
| 656 |
-
<div class="ai-summary-title">π Analysis Conclusion</div>
|
| 657 |
-
<div class="ai-summary-text" id="aiSummaryText">Awaiting analysis...</div>
|
| 658 |
-
</div>
|
| 659 |
-
<div style="padding:0 22px 16px;display:flex;flex-direction:column;gap:6px;" id="keyFindingsList"></div>
|
| 660 |
-
</div>
|
| 661 |
-
|
| 662 |
<!-- METADATA -->
|
| 663 |
<div class="card fade-in">
|
| 664 |
<div class="card-header"><div class="card-title"><div class="card-title-icon" style="background:rgba(13,148,136,0.08)">ποΈ</div>File Metadata</div></div>
|
|
@@ -677,96 +622,6 @@ canvas{width:100%;display:block;}
|
|
| 677 |
</div>
|
| 678 |
</div>
|
| 679 |
|
| 680 |
-
<!-- BATCH SECTION β NEW -->
|
| 681 |
-
<div id="sec-batch" style="display:none;padding-bottom:40px">
|
| 682 |
-
<div class="sec-lbl">Batch Forensic Analysis</div>
|
| 683 |
-
<div class="main-grid">
|
| 684 |
-
<div class="left-col">
|
| 685 |
-
<div class="card">
|
| 686 |
-
<div class="card-header">
|
| 687 |
-
<div class="card-title"><div class="card-title-icon" style="background:var(--blue-pale)">β‘</div>Upload Multiple Files</div>
|
| 688 |
-
<div class="status-badge" style="font-size:11px"><span id="batchCount">0</span>/10 files</div>
|
| 689 |
-
</div>
|
| 690 |
-
<div class="card-pad">
|
| 691 |
-
<div class="upload-zone" id="batchZone" onclick="document.getElementById('batchInput').click()">
|
| 692 |
-
<input type="file" id="batchInput" multiple accept="image/*,video/*,audio/*,.pdf" style="display:none">
|
| 693 |
-
<div class="upload-icon-wrap"><span>π</span></div>
|
| 694 |
-
<div class="upload-title">Drop up to 10 files</div>
|
| 695 |
-
<div class="upload-sub">All formats Β· Analyzed in parallel Β· One report per file</div>
|
| 696 |
-
<div class="upload-formats">
|
| 697 |
-
<span class="fmt-tag">Images</span><span class="fmt-tag">Videos</span>
|
| 698 |
-
<span class="fmt-tag">Audio</span><span class="fmt-tag">PDFs</span>
|
| 699 |
-
</div>
|
| 700 |
-
</div>
|
| 701 |
-
</div>
|
| 702 |
-
<div style="padding:0 22px 16px" id="batchFileList"></div>
|
| 703 |
-
<div style="padding:0 22px 16px">
|
| 704 |
-
<button class="btn-analyze" id="btnBatch" onclick="startBatch()" style="display:none">
|
| 705 |
-
<span class="btn-text">β‘ ANALYZE ALL FILES</span>
|
| 706 |
-
<div class="btn-spinner"></div>
|
| 707 |
-
</button>
|
| 708 |
-
</div>
|
| 709 |
-
</div>
|
| 710 |
-
</div>
|
| 711 |
-
<div class="right-col">
|
| 712 |
-
<div class="card" id="batchResultsCard" style="display:none;">
|
| 713 |
-
<div class="card-header"><div class="card-title"><div class="card-title-icon" style="background:var(--green-pale)">π</div>Batch Results</div></div>
|
| 714 |
-
<div style="padding:16px">
|
| 715 |
-
<div class="batch-summary-bar" id="batchSummaryBar"></div>
|
| 716 |
-
<div class="batch-drop-grid" id="batchResultsList"></div>
|
| 717 |
-
</div>
|
| 718 |
-
</div>
|
| 719 |
-
<div class="card">
|
| 720 |
-
<div class="card-header"><div class="card-title"><div class="card-title-icon" style="background:rgba(245,158,11,0.08)">βΉοΈ</div>Batch Info</div></div>
|
| 721 |
-
<div style="padding:16px;font-size:12px;color:var(--text-3);line-height:1.7">
|
| 722 |
-
<b style="color:var(--text-1)">How Batch Works:</b><br>
|
| 723 |
-
β’ Upload up to 10 files at once<br>
|
| 724 |
-
β’ Each file runs the full 46-module pipeline<br>
|
| 725 |
-
β’ Results appear as each file completes<br>
|
| 726 |
-
β’ Each case gets its own PDF report<br>
|
| 727 |
-
β’ All results saved to History
|
| 728 |
-
</div>
|
| 729 |
-
</div>
|
| 730 |
-
</div>
|
| 731 |
-
</div>
|
| 732 |
-
</div>
|
| 733 |
-
|
| 734 |
-
<!-- HISTORY SECTION β NEW -->
|
| 735 |
-
<div id="sec-history" style="display:none;padding-bottom:40px">
|
| 736 |
-
<div class="sec-lbl">Case History</div>
|
| 737 |
-
<div class="card">
|
| 738 |
-
<div class="card-header">
|
| 739 |
-
<div class="card-title"><div class="card-title-icon" style="background:rgba(99,102,241,0.08)">π</div>Past Analysis Cases</div>
|
| 740 |
-
<div style="display:flex;gap:8px">
|
| 741 |
-
<button class="mod-ctrl-btn" onclick="loadHistory()">π Refresh</button>
|
| 742 |
-
<button class="mod-ctrl-btn" onclick="clearHistory()" style="color:var(--red)">ποΈ Clear All</button>
|
| 743 |
-
</div>
|
| 744 |
-
</div>
|
| 745 |
-
<div style="padding:16px 22px">
|
| 746 |
-
<!-- Stats row -->
|
| 747 |
-
<div class="hist-stats-row" id="histStatsRow">
|
| 748 |
-
<div class="hist-stat-pill"><div class="hist-stat-pill-num" id="histTotal">0</div><div class="hist-stat-pill-lbl">TOTAL CASES</div></div>
|
| 749 |
-
<div class="hist-stat-pill"><div class="hist-stat-pill-num" style="color:var(--red)" id="histFakes">0</div><div class="hist-stat-pill-lbl">FAKES</div></div>
|
| 750 |
-
<div class="hist-stat-pill"><div class="hist-stat-pill-num" style="color:var(--green)" id="histAuthentic">0</div><div class="hist-stat-pill-lbl">AUTHENTIC</div></div>
|
| 751 |
-
<div class="hist-stat-pill"><div class="hist-stat-pill-num" style="color:var(--synth)" id="histSynth">0</div><div class="hist-stat-pill-lbl">SYNTHETIC</div></div>
|
| 752 |
-
</div>
|
| 753 |
-
<!-- Filters -->
|
| 754 |
-
<div class="history-filters">
|
| 755 |
-
<button class="hist-filter-btn active" onclick="filterHistory('all',this)">All</button>
|
| 756 |
-
<button class="hist-filter-btn" onclick="filterHistory('FAKE',this)">β οΈ Fake</button>
|
| 757 |
-
<button class="hist-filter-btn" onclick="filterHistory('AUTHENTIC',this)">β
Authentic</button>
|
| 758 |
-
<button class="hist-filter-btn" onclick="filterHistory('SYNTHETIC',this)">𧬠Synthetic</button>
|
| 759 |
-
<button class="hist-filter-btn" onclick="filterHistory('image',this)">πΌοΈ Images</button>
|
| 760 |
-
<button class="hist-filter-btn" onclick="filterHistory('video',this)">π₯ Videos</button>
|
| 761 |
-
<button class="hist-filter-btn" onclick="filterHistory('audio',this)">π΅ Audio</button>
|
| 762 |
-
</div>
|
| 763 |
-
<div class="history-grid" id="historyGrid">
|
| 764 |
-
<div class="history-empty">π No cases yet β run an analysis to see history here</div>
|
| 765 |
-
</div>
|
| 766 |
-
</div>
|
| 767 |
-
</div>
|
| 768 |
-
</div>
|
| 769 |
-
|
| 770 |
<!-- MODULES SECTION -->
|
| 771 |
<div id="sec-modules" style="display:none;padding-bottom:40px">
|
| 772 |
<div class="sec-lbl">All 46 Detection Modules</div>
|
|
@@ -1298,14 +1153,88 @@ function resetResults(){
|
|
| 1298 |
function resetFrameBars(){Array.from({length:80},(_,i)=>{const fb=document.getElementById('fb'+i);if(fb){fb.className='fb idle';fb.style.height=(20+Math.random()*15)+'px';}});}
|
| 1299 |
|
| 1300 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1301 |
-
//
|
| 1302 |
-
// Run: uvicorn main:app --reload --port 8000
|
| 1303 |
-
// Then open: http://localhost:8000
|
| 1304 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1305 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1306 |
|
| 1307 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1308 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1309 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1310 |
async function startAnalysis(){
|
| 1311 |
if(!state.file){toast('β οΈ Please upload a file first',2500);return;}
|
|
@@ -1335,87 +1264,89 @@ async function startAnalysis(){
|
|
| 1335 |
if(pct>15 && msgIdx<msgs.length){toast(msgs[msgIdx++],1200);}
|
| 1336 |
},400);
|
| 1337 |
|
|
|
|
|
|
|
|
|
|
| 1338 |
try {
|
| 1339 |
const formData=new FormData();
|
| 1340 |
formData.append('file', state.file);
|
| 1341 |
const enabledIds=active.map(m=>m.id).join(',');
|
| 1342 |
formData.append('modules', enabledIds);
|
| 1343 |
|
| 1344 |
-
const response = await fetch(`${API_BASE}/analyze`, {method:'POST',
|
| 1345 |
-
clearInterval(interval);
|
| 1346 |
|
|
|
|
| 1347 |
if(!response.ok){
|
| 1348 |
let errMsg='Analysis failed';
|
| 1349 |
try{const err=await response.json();errMsg=err.detail||errMsg;}catch(e){}
|
| 1350 |
throw new Error(`Server ${response.status}: ${errMsg}`);
|
| 1351 |
}
|
| 1352 |
-
|
| 1353 |
-
const data = await response.json();
|
| 1354 |
-
progBar.style.width='100%';
|
| 1355 |
-
|
| 1356 |
-
state.analysisId = data.case_id;
|
| 1357 |
-
state.timestamp = data.timestamp;
|
| 1358 |
-
state.apiData = data;
|
| 1359 |
-
|
| 1360 |
-
if(data.sha256){
|
| 1361 |
-
state.hash = data.sha256;
|
| 1362 |
-
state.md5 = data.md5||'';
|
| 1363 |
-
const md5Line = data.md5 ? `<br><strong>MD5:</strong><br>${data.md5}` : '';
|
| 1364 |
-
document.getElementById('hashDisplay').innerHTML=`<strong>SHA-256:</strong><br>${data.sha256}${md5Line}`;
|
| 1365 |
-
document.getElementById('hashStatus').textContent='β
';
|
| 1366 |
-
document.getElementById('hashStatusTxt').textContent='Server-verified integrity';
|
| 1367 |
-
document.getElementById('chash1').textContent=data.sha256.slice(0,16)+'...';
|
| 1368 |
-
markCustodyStep(1,data.sha256.slice(0,16)+'...');
|
| 1369 |
-
}
|
| 1370 |
-
|
| 1371 |
-
// Map ALL backend module scores to state (0.0-1.0 β 0-100%)
|
| 1372 |
-
const scores=data.scores||{};
|
| 1373 |
-
Object.entries(scores).forEach(([k,v])=>{
|
| 1374 |
-
const mid=parseInt(k);
|
| 1375 |
-
if(!isNaN(mid)) state.moduleResults[mid]=Math.round(parseFloat(v)*100);
|
| 1376 |
-
});
|
| 1377 |
-
|
| 1378 |
-
// Animate ALL 46 module cards with real scores
|
| 1379 |
-
active.forEach((m,i)=>{
|
| 1380 |
-
setTimeout(()=>{
|
| 1381 |
-
const el=document.getElementById('mod'+m.id);
|
| 1382 |
-
const fp=document.getElementById('fp'+m.id);
|
| 1383 |
-
const score=state.moduleResults[m.id]??70;
|
| 1384 |
-
if(el) el.className='mod-card on'+(score<40?' mod-flagged':'');
|
| 1385 |
-
if(fp) fp.className='feat-pip on';
|
| 1386 |
-
},i*40);
|
| 1387 |
-
});
|
| 1388 |
-
|
| 1389 |
-
document.getElementById('scanBeam').classList.remove('active');
|
| 1390 |
-
btn.classList.remove('loading');
|
| 1391 |
-
|
| 1392 |
-
const verdict = data.verdict||'UNKNOWN';
|
| 1393 |
-
const isFake = verdict==='FAKE'||verdict==='DEEPFAKE';
|
| 1394 |
-
const isSynth = data.is_synth||verdict==='SYNTHETIC';
|
| 1395 |
-
const score = Math.round(data.confidence*100);
|
| 1396 |
-
|
| 1397 |
-
finishAnalysisFromAPI(data, score, isFake, isSynth);
|
| 1398 |
|
| 1399 |
} catch(err) {
|
| 1400 |
clearInterval(interval);
|
| 1401 |
-
|
| 1402 |
-
document.getElementById('scanBeam').classList.remove('active');
|
| 1403 |
-
progBar.style.width='0%'; progWrap.classList.remove('active');
|
| 1404 |
-
active.forEach(m=>{const el=document.getElementById('mod'+m.id);if(el)el.className='mod-card';});
|
| 1405 |
-
|
| 1406 |
-
const isNetwork=err.message.toLowerCase().includes('fetch')||err.message.toLowerCase().includes('network')||err.message.toLowerCase().includes('failed');
|
| 1407 |
if(isNetwork){
|
| 1408 |
-
|
|
|
|
|
|
|
|
|
|
| 1409 |
} else {
|
| 1410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1411 |
}
|
| 1412 |
-
console.error('[VERIDEX] Analysis error:',err);
|
| 1413 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1414 |
}
|
| 1415 |
|
| 1416 |
-
//
|
| 1417 |
-
// FINISH β render real backend results
|
| 1418 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1419 |
function finishAnalysisFromAPI(data, score, isFake, isSynth){
|
| 1420 |
state.finalScore=score; state.isFake=isFake; state.isSynth=isSynth; state.analyzed=true;
|
| 1421 |
|
|
@@ -1432,7 +1363,7 @@ function finishAnalysisFromAPI(data, score, isFake, isSynth){
|
|
| 1432 |
else if(isSynth){chip.className='verdict-chip synth';chip.textContent='𧬠SYNTHETIC DETECTED';}
|
| 1433 |
else{chip.className='verdict-chip authentic';chip.textContent='β
AUTHENTIC MEDIA';}
|
| 1434 |
|
| 1435 |
-
// 8
|
| 1436 |
const scores=data.scores||{};
|
| 1437 |
const subMap=[
|
| 1438 |
['6','RGB Forensic'],['7','Frequency'],['8','Noise Residual'],['9','rPPG Signal'],
|
|
@@ -1452,12 +1383,24 @@ function finishAnalysisFromAPI(data, score, isFake, isSynth){
|
|
| 1452 |
},i*150);
|
| 1453 |
});
|
| 1454 |
|
| 1455 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1456 |
if(isSynth && data.synth_data && Object.keys(data.synth_data).length>0){
|
| 1457 |
const sd=data.synth_data;
|
| 1458 |
state.synthData={
|
| 1459 |
model:sd.generator||'Unknown',generator:sd.provider||'Unknown',
|
| 1460 |
-
confidence:Math.round((sd.confidence||0)*100),
|
| 1461 |
faceMatch:'GENERATED',watermark:sd.watermark_detected?'DETECTED':'NOT FOUND',
|
| 1462 |
signalType:sd.signal_type||'Unknown',
|
| 1463 |
};
|
|
@@ -1470,19 +1413,17 @@ function finishAnalysisFromAPI(data, score, isFake, isSynth){
|
|
| 1470 |
setTimeout(()=>{document.getElementById('synthConfFill').style.width=state.synthData.confidence+'%';},300);
|
| 1471 |
}
|
| 1472 |
|
| 1473 |
-
// Key findings
|
| 1474 |
if(data.key_findings && data.key_findings.length>0){
|
| 1475 |
const meta=document.getElementById('metaPanel');
|
| 1476 |
-
const
|
| 1477 |
-
|
| 1478 |
}
|
| 1479 |
|
| 1480 |
-
//
|
| 1481 |
-
|
| 1482 |
-
|
| 1483 |
-
|
| 1484 |
-
const results=isFake?['β οΈ ANOMALY','β οΈ MISMATCH','β οΈ BROKEN','β οΈ WARPED','β οΈ ABSENT','β οΈ LOW']:['β
STABLE','β
SYNCED','β
COHERENT','β
NATURAL','β
DETECTED','β
NORMAL'];
|
| 1485 |
-
vstf.forEach((c,i)=>{c.textContent=results[i];c.style.color=isFake?'var(--red)':'var(--green)';});
|
| 1486 |
}
|
| 1487 |
|
| 1488 |
animateFrameBars(isFake);
|
|
@@ -1492,48 +1433,86 @@ function finishAnalysisFromAPI(data, score, isFake, isSynth){
|
|
| 1492 |
document.getElementById('btnReport').disabled=false;
|
| 1493 |
document.getElementById('progWrap').classList.remove('active');
|
| 1494 |
|
| 1495 |
-
|
| 1496 |
-
|
| 1497 |
-
|
| 1498 |
-
|
| 1499 |
-
|
| 1500 |
-
|
| 1501 |
-
|
| 1502 |
-
|
| 1503 |
-
|
| 1504 |
-
|
| 1505 |
-
|
| 1506 |
-
|
| 1507 |
-
|
| 1508 |
-
|
| 1509 |
-
|
| 1510 |
-
|
| 1511 |
-
|
| 1512 |
-
|
| 1513 |
-
|
| 1514 |
-
}
|
| 1515 |
-
|
| 1516 |
-
|
| 1517 |
-
|
| 1518 |
-
|
| 1519 |
-
|
| 1520 |
-
|
| 1521 |
-
|
| 1522 |
-
|
| 1523 |
-
|
| 1524 |
-
|
| 1525 |
-
|
| 1526 |
-
|
| 1527 |
-
|
| 1528 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1529 |
}
|
| 1530 |
|
| 1531 |
-
|
| 1532 |
-
setTimeout(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1533 |
|
| 1534 |
-
toast(isFake?'β οΈ
|
| 1535 |
}
|
| 1536 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1537 |
|
| 1538 |
function animateFrameBars(isFake){
|
| 1539 |
Array.from({length:80},(_,i)=>setTimeout(()=>{
|
|
@@ -1626,18 +1605,9 @@ function updateCustodyLog(){
|
|
| 1626 |
// NAVIGATION
|
| 1627 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1628 |
function showSection(name){
|
| 1629 |
-
['analyze','
|
| 1630 |
-
|
| 1631 |
-
|
| 1632 |
-
});
|
| 1633 |
-
document.querySelectorAll('.nav-btn').forEach(b=>{
|
| 1634 |
-
b.classList.toggle('active',
|
| 1635 |
-
b.textContent.toLowerCase().includes(name) ||
|
| 1636 |
-
(name==='analyze' && b.textContent.toLowerCase().includes('analyze'))
|
| 1637 |
-
);
|
| 1638 |
-
});
|
| 1639 |
-
if(name==='custody') updateCustodyLog();
|
| 1640 |
-
if(name==='history') loadHistory();
|
| 1641 |
window.scrollTo({top:0,behavior:'smooth'});
|
| 1642 |
}
|
| 1643 |
|
|
@@ -1667,7 +1637,7 @@ function openReport(){
|
|
| 1667 |
<div class="rep-field"><div class="rep-f-lbl">Active Modules</div><div class="rep-f-val">${MODULES.length-state.disabledModules.size}/46</div></div>
|
| 1668 |
</div></div>
|
| 1669 |
<div class="report-section"><div class="rep-section-title">π Cryptographic Integrity</div>
|
| 1670 |
-
<div class="rep-hash"><strong>SHA-256:</strong><br>${state.hash||'N/A'}
|
| 1671 |
</div>
|
| 1672 |
<div class="report-section"><div class="rep-section-title">βοΈ Forensic Verdict</div>
|
| 1673 |
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px;border-radius:14px;background:${state.isFake?'var(--red-pale)':state.isSynth?'var(--synth-pale)':'var(--green-pale)'};border:1px solid ${state.isFake?'rgba(220,38,38,0.2)':state.isSynth?'rgba(139,92,246,0.2)':'rgba(22,163,74,0.2)'}">
|
|
@@ -1740,293 +1710,6 @@ function formatSize(b){if(b>1024*1024)return(b/1024/1024).toFixed(1)+' MB';if(b>
|
|
| 1740 |
function shortHash(){return Array.from({length:16},()=>'0123456789abcdef'[Math.floor(Math.random()*16)]).join('');}
|
| 1741 |
function toast(msg,dur=2500){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),dur);}
|
| 1742 |
|
| 1743 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1744 |
-
// AI SUMMARY DISPLAY
|
| 1745 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1746 |
-
function showAISummary(data){
|
| 1747 |
-
const card=document.getElementById('aiSummaryCard');
|
| 1748 |
-
const text=document.getElementById('aiSummaryText');
|
| 1749 |
-
const list=document.getElementById('keyFindingsList');
|
| 1750 |
-
if(!card||!text) return;
|
| 1751 |
-
card.style.display='block';
|
| 1752 |
-
text.textContent=data.ai_summary||'Analysis complete.';
|
| 1753 |
-
list.innerHTML=(data.key_findings||[]).slice(0,5).map((f,i)=>`
|
| 1754 |
-
<div style="display:flex;align-items:flex-start;gap:8px;padding:8px 12px;border-radius:8px;background:rgba(255,255,255,0.18);border:1px solid rgba(255,255,255,0.3)">
|
| 1755 |
-
<span style="font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;color:var(--blue);flex-shrink:0">${String(i+1).padStart(2,'0')}.</span>
|
| 1756 |
-
<span style="font-size:11px;color:var(--text-2);line-height:1.5">${f}</span>
|
| 1757 |
-
</div>`).join('');
|
| 1758 |
-
}
|
| 1759 |
-
|
| 1760 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1761 |
-
// BATCH ANALYSIS β NEW
|
| 1762 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1763 |
-
const _batchFiles=[];
|
| 1764 |
-
|
| 1765 |
-
const batchZone=document.getElementById('batchZone');
|
| 1766 |
-
const batchInput=document.getElementById('batchInput');
|
| 1767 |
-
if(batchZone){
|
| 1768 |
-
batchZone.addEventListener('dragover',e=>{e.preventDefault();batchZone.classList.add('drag-over');});
|
| 1769 |
-
batchZone.addEventListener('dragleave',()=>batchZone.classList.remove('drag-over'));
|
| 1770 |
-
batchZone.addEventListener('drop',e=>{e.preventDefault();batchZone.classList.remove('drag-over');Array.from(e.dataTransfer.files).forEach(addBatchFile);});
|
| 1771 |
-
}
|
| 1772 |
-
if(batchInput) batchInput.addEventListener('change',e=>Array.from(e.target.files).forEach(addBatchFile));
|
| 1773 |
-
|
| 1774 |
-
function addBatchFile(file){
|
| 1775 |
-
if(_batchFiles.length>=10){toast('β οΈ Max 10 files per batch',2500);return;}
|
| 1776 |
-
if(_batchFiles.some(f=>f.name===file.name&&f.size===file.size)){toast('β οΈ Duplicate file skipped',2000);return;}
|
| 1777 |
-
_batchFiles.push(file);
|
| 1778 |
-
renderBatchFileList();
|
| 1779 |
-
}
|
| 1780 |
-
|
| 1781 |
-
function removeBatchFile(idx){
|
| 1782 |
-
_batchFiles.splice(idx,1);
|
| 1783 |
-
renderBatchFileList();
|
| 1784 |
-
}
|
| 1785 |
-
|
| 1786 |
-
function renderBatchFileList(){
|
| 1787 |
-
const el=document.getElementById('batchFileList');
|
| 1788 |
-
const cnt=document.getElementById('batchCount');
|
| 1789 |
-
const btn=document.getElementById('btnBatch');
|
| 1790 |
-
if(!el) return;
|
| 1791 |
-
if(cnt) cnt.textContent=_batchFiles.length;
|
| 1792 |
-
if(btn) btn.style.display=_batchFiles.length>0?'block':'none';
|
| 1793 |
-
if(_batchFiles.length===0){el.innerHTML='';return;}
|
| 1794 |
-
const typeIcon=t=>t.startsWith('image')?'πΌοΈ':t.startsWith('video')?'π₯':t.startsWith('audio')?'π΅':'π';
|
| 1795 |
-
el.innerHTML='<div class="batch-drop-grid">'+_batchFiles.map((f,i)=>`
|
| 1796 |
-
<div class="batch-file-row">
|
| 1797 |
-
<span class="batch-file-icon">${typeIcon(f.type||'')}</span>
|
| 1798 |
-
<div class="batch-file-info">
|
| 1799 |
-
<div class="batch-file-name">${f.name}</div>
|
| 1800 |
-
<div class="batch-file-size">${formatSize(f.size)} Β· ${f.type||'Unknown'}</div>
|
| 1801 |
-
</div>
|
| 1802 |
-
<div class="batch-file-status" id="bfstatus${i}" style="color:var(--text-4)">β³ QUEUED</div>
|
| 1803 |
-
<button class="batch-file-remove" onclick="removeBatchFile(${i})">β</button>
|
| 1804 |
-
</div>`).join('')+'</div>';
|
| 1805 |
-
}
|
| 1806 |
-
|
| 1807 |
-
async function startBatch(){
|
| 1808 |
-
if(_batchFiles.length===0){toast('β οΈ Add files first',2000);return;}
|
| 1809 |
-
const btn=document.getElementById('btnBatch');
|
| 1810 |
-
btn.classList.add('loading');
|
| 1811 |
-
toast('β‘ Sending '+_batchFiles.length+' files for analysis...',3000);
|
| 1812 |
-
|
| 1813 |
-
// Update statuses to running
|
| 1814 |
-
_batchFiles.forEach((_,i)=>{
|
| 1815 |
-
const s=document.getElementById('bfstatus'+i);
|
| 1816 |
-
if(s){s.textContent='π RUNNING';s.style.color='var(--amber)';}
|
| 1817 |
-
});
|
| 1818 |
-
|
| 1819 |
-
try{
|
| 1820 |
-
const fd=new FormData();
|
| 1821 |
-
_batchFiles.forEach(f=>fd.append('files',f));
|
| 1822 |
-
fd.append('modules','all');
|
| 1823 |
-
|
| 1824 |
-
const resp=await fetch(`${API_BASE}/batch`,{method:'POST',body:fd});
|
| 1825 |
-
if(!resp.ok){
|
| 1826 |
-
const err=await resp.json().catch(()=>({}));
|
| 1827 |
-
throw new Error(err.detail||'Batch failed ('+resp.status+')');
|
| 1828 |
-
}
|
| 1829 |
-
const data=await resp.json();
|
| 1830 |
-
btn.classList.remove('loading');
|
| 1831 |
-
|
| 1832 |
-
// Update statuses
|
| 1833 |
-
(data.results||[]).forEach((r,i)=>{
|
| 1834 |
-
const s=document.getElementById('bfstatus'+i);
|
| 1835 |
-
if(!s)return;
|
| 1836 |
-
if(r.error){s.textContent='β ERROR';s.style.color='var(--red)';return;}
|
| 1837 |
-
const v=r.verdict||'UNKNOWN';
|
| 1838 |
-
const isFake=v==='FAKE'||v==='DEEPFAKE';
|
| 1839 |
-
const isSynth=v==='SYNTHETIC';
|
| 1840 |
-
s.textContent=isFake?'β οΈ FAKE':isSynth?'𧬠SYNTH':'β
AUTH';
|
| 1841 |
-
s.style.color=isFake?'var(--red)':isSynth?'var(--synth)':'var(--green)';
|
| 1842 |
-
});
|
| 1843 |
-
|
| 1844 |
-
renderBatchResults(data);
|
| 1845 |
-
loadHistory(); // refresh history
|
| 1846 |
-
toast(`β
Batch done: ${data.flagged} flagged / ${data.total} total`,4000);
|
| 1847 |
-
}catch(e){
|
| 1848 |
-
btn.classList.remove('loading');
|
| 1849 |
-
toast('β Batch error: '+e.message,5000);
|
| 1850 |
-
_batchFiles.forEach((_,i)=>{
|
| 1851 |
-
const s=document.getElementById('bfstatus'+i);
|
| 1852 |
-
if(s){s.textContent='β ERROR';s.style.color='var(--red)';}
|
| 1853 |
-
});
|
| 1854 |
-
}
|
| 1855 |
-
}
|
| 1856 |
-
|
| 1857 |
-
function renderBatchResults(data){
|
| 1858 |
-
const card=document.getElementById('batchResultsCard');
|
| 1859 |
-
const sumBar=document.getElementById('batchSummaryBar');
|
| 1860 |
-
const list=document.getElementById('batchResultsList');
|
| 1861 |
-
if(!card) return;
|
| 1862 |
-
card.style.display='block';
|
| 1863 |
-
|
| 1864 |
-
if(sumBar){
|
| 1865 |
-
sumBar.innerHTML=[
|
| 1866 |
-
{num:data.total,lbl:'TOTAL',c:'var(--text-1)'},
|
| 1867 |
-
{num:data.flagged,lbl:'FLAGGED',c:'var(--red)'},
|
| 1868 |
-
{num:data.authentic,lbl:'AUTHENTIC',c:'var(--green)'},
|
| 1869 |
-
].map(s=>`<div class="batch-stat"><div class="batch-stat-num" style="color:${s.c}">${s.num}</div><div class="batch-stat-lbl">${s.lbl}</div></div>`).join('');
|
| 1870 |
-
}
|
| 1871 |
-
|
| 1872 |
-
if(list){
|
| 1873 |
-
list.innerHTML=(data.results||[]).map(r=>{
|
| 1874 |
-
if(r.error) return `<div class="batch-result-card"><div style="font-size:13px;color:var(--red)">β ${r.file_name} β ${r.error}</div></div>`;
|
| 1875 |
-
const v=r.verdict||'UNKNOWN';
|
| 1876 |
-
const isFake=v==='FAKE'||v==='DEEPFAKE';
|
| 1877 |
-
const isSynth=v==='SYNTHETIC';
|
| 1878 |
-
const col=isFake?'var(--red)':isSynth?'var(--synth)':'var(--green)';
|
| 1879 |
-
const bg=isFake?'var(--red-pale)':isSynth?'var(--synth-pale)':'var(--green-pale)';
|
| 1880 |
-
const typeIcon=t=>t.startsWith('image')?'πΌοΈ':t.startsWith('video')?'π₯':t.startsWith('audio')?'π΅':'π';
|
| 1881 |
-
return `<div class="batch-result-card" onclick="openBatchDetail('${r.case_id}')">
|
| 1882 |
-
<span style="font-size:20px">${typeIcon(r.file_type||'')}</span>
|
| 1883 |
-
<div style="flex:1;min-width:0">
|
| 1884 |
-
<div style="font-size:13px;font-weight:700;color:var(--text-1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">${r.file_name||'Unknown'}</div>
|
| 1885 |
-
<div style="font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--text-4)">${r.case_id} Β· ${formatSize(r.file_size_bytes||0)}</div>
|
| 1886 |
-
</div>
|
| 1887 |
-
<div class="batch-verdict-badge" style="background:${bg};color:${col};border:1px solid ${col}33">${v}</div>
|
| 1888 |
-
<div style="font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;color:${col}">${Math.round((r.confidence||0.5)*100)}%</div>
|
| 1889 |
-
</div>`;
|
| 1890 |
-
}).join('');
|
| 1891 |
-
}
|
| 1892 |
-
}
|
| 1893 |
-
|
| 1894 |
-
function openBatchDetail(caseId){
|
| 1895 |
-
if(!caseId) return;
|
| 1896 |
-
// Download report if available
|
| 1897 |
-
toast('β¬οΈ Downloading report for '+caseId,2000);
|
| 1898 |
-
const a=document.createElement('a');
|
| 1899 |
-
a.href=`${API_BASE}/report/${caseId}`;
|
| 1900 |
-
a.target='_blank';
|
| 1901 |
-
a.click();
|
| 1902 |
-
}
|
| 1903 |
-
|
| 1904 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1905 |
-
// HISTORY β NEW
|
| 1906 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1907 |
-
let _historyData=[];
|
| 1908 |
-
let _histFilter='all';
|
| 1909 |
-
|
| 1910 |
-
async function loadHistory(){
|
| 1911 |
-
try{
|
| 1912 |
-
const resp=await fetch(`${API_BASE}/history?limit=100`);
|
| 1913 |
-
if(!resp.ok) throw new Error('No history endpoint');
|
| 1914 |
-
const data=await resp.json();
|
| 1915 |
-
_historyData=data.cases||[];
|
| 1916 |
-
updateHistoryStats(data);
|
| 1917 |
-
renderHistoryGrid(_historyData);
|
| 1918 |
-
}catch(e){
|
| 1919 |
-
// Fallback: show cases from current session state
|
| 1920 |
-
renderHistoryGrid([]);
|
| 1921 |
-
}
|
| 1922 |
-
}
|
| 1923 |
-
|
| 1924 |
-
function updateHistoryStats(data){
|
| 1925 |
-
const cases=data.cases||[];
|
| 1926 |
-
document.getElementById('histTotal').textContent=data.total||cases.length||0;
|
| 1927 |
-
document.getElementById('histFakes').textContent=cases.filter(c=>c.verdict==='FAKE'||c.verdict==='DEEPFAKE').length;
|
| 1928 |
-
document.getElementById('histAuthentic').textContent=cases.filter(c=>c.verdict==='AUTHENTIC').length;
|
| 1929 |
-
document.getElementById('histSynth').textContent=cases.filter(c=>c.verdict==='SYNTHETIC').length;
|
| 1930 |
-
}
|
| 1931 |
-
|
| 1932 |
-
function filterHistory(filter,btn){
|
| 1933 |
-
_histFilter=filter;
|
| 1934 |
-
document.querySelectorAll('.hist-filter-btn').forEach(b=>b.classList.remove('active'));
|
| 1935 |
-
if(btn) btn.classList.add('active');
|
| 1936 |
-
let filtered=_historyData;
|
| 1937 |
-
if(filter!=='all'){
|
| 1938 |
-
const f=filter.toLowerCase();
|
| 1939 |
-
filtered=_historyData.filter(c=>
|
| 1940 |
-
c.verdict.toLowerCase()===f ||
|
| 1941 |
-
(c.file_type||'').toLowerCase().includes(f)
|
| 1942 |
-
);
|
| 1943 |
-
}
|
| 1944 |
-
renderHistoryGrid(filtered);
|
| 1945 |
-
}
|
| 1946 |
-
|
| 1947 |
-
function renderHistoryGrid(cases){
|
| 1948 |
-
const grid=document.getElementById('historyGrid');
|
| 1949 |
-
if(!grid) return;
|
| 1950 |
-
if(!cases||cases.length===0){
|
| 1951 |
-
grid.innerHTML='<div class="history-empty">π No cases match this filter</div>';
|
| 1952 |
-
return;
|
| 1953 |
-
}
|
| 1954 |
-
const typeIcon=t=>(t||'').startsWith('image')?'πΌοΈ':(t||'').startsWith('video')?'π₯':(t||'').startsWith('audio')?'π΅':'π';
|
| 1955 |
-
grid.innerHTML=cases.map(c=>{
|
| 1956 |
-
const v=c.verdict||'UNKNOWN';
|
| 1957 |
-
const isFake=v==='FAKE'||v==='DEEPFAKE';
|
| 1958 |
-
const isSynth=v==='SYNTHETIC';
|
| 1959 |
-
const col=isFake?'var(--red)':isSynth?'var(--synth)':v==='AUTHENTIC'?'var(--green)':'var(--text-4)';
|
| 1960 |
-
const bg=isFake?'var(--red-pale)':isSynth?'var(--synth-pale)':v==='AUTHENTIC'?'var(--green-pale)':'rgba(0,0,0,0.06)';
|
| 1961 |
-
const ts=c.timestamp?new Date(c.timestamp).toLocaleString():'Unknown time';
|
| 1962 |
-
return `<div class="history-card" onclick="openHistoryCase('${c.case_id}')">
|
| 1963 |
-
<span class="history-icon">${typeIcon(c.file_type)}</span>
|
| 1964 |
-
<div class="history-info">
|
| 1965 |
-
<div class="history-name">${c.file_name||'Unknown file'}</div>
|
| 1966 |
-
<div class="history-meta">${c.case_id} Β· ${ts} Β· ${c.enabled_modules||46} modules</div>
|
| 1967 |
-
${c.ai_summary?`<div style="font-size:11px;color:var(--text-3);margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:400px">${c.ai_summary}</div>`:''}
|
| 1968 |
-
</div>
|
| 1969 |
-
<div class="history-verdict" style="background:${bg};color:${col};border:1px solid ${col}33">${v}</div>
|
| 1970 |
-
<div class="history-risk" style="color:${col}">${c.risk_score||0}%</div>
|
| 1971 |
-
<button onclick="event.stopPropagation();deleteHistoryCase('${c.case_id}')" style="padding:4px 8px;border-radius:6px;background:rgba(220,38,38,0.06);border:1px solid rgba(220,38,38,0.15);color:var(--red);font-size:10px;cursor:none;transition:all 0.2s" title="Delete case">ποΈ</button>
|
| 1972 |
-
</div>`;
|
| 1973 |
-
}).join('');
|
| 1974 |
-
}
|
| 1975 |
-
|
| 1976 |
-
function openHistoryCase(caseId){
|
| 1977 |
-
if(!caseId) return;
|
| 1978 |
-
const a=document.createElement('a');
|
| 1979 |
-
a.href=`${API_BASE}/report/${caseId}`;
|
| 1980 |
-
a.target='_blank';
|
| 1981 |
-
a.click();
|
| 1982 |
-
toast('π Opening report for '+caseId,2000);
|
| 1983 |
-
}
|
| 1984 |
-
|
| 1985 |
-
async function deleteHistoryCase(caseId){
|
| 1986 |
-
try{
|
| 1987 |
-
const resp=await fetch(`${API_BASE}/history/${caseId}`,{method:'DELETE'});
|
| 1988 |
-
if(resp.ok){
|
| 1989 |
-
toast('ποΈ Case deleted',1500);
|
| 1990 |
-
await loadHistory();
|
| 1991 |
-
}
|
| 1992 |
-
}catch(e){toast('β Could not delete case',2000);}
|
| 1993 |
-
}
|
| 1994 |
-
|
| 1995 |
-
async function clearHistory(){
|
| 1996 |
-
if(!confirm('Clear all case history?')) return;
|
| 1997 |
-
try{
|
| 1998 |
-
await fetch(`${API_BASE}/history`,{method:'DELETE'});
|
| 1999 |
-
_historyData=[];
|
| 2000 |
-
renderHistoryGrid([]);
|
| 2001 |
-
['histTotal','histFakes','histAuthentic','histSynth'].forEach(id=>{
|
| 2002 |
-
const el=document.getElementById(id);if(el)el.textContent='0';
|
| 2003 |
-
});
|
| 2004 |
-
toast('ποΈ History cleared',2000);
|
| 2005 |
-
}catch(e){toast('β Could not clear history',2000);}
|
| 2006 |
-
}
|
| 2007 |
-
|
| 2008 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2009 |
-
// HEALTH CHECK ON LOAD
|
| 2010 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2011 |
-
async function checkHealth(){
|
| 2012 |
-
try{
|
| 2013 |
-
const resp=await fetch(`${API_BASE}/health`);
|
| 2014 |
-
if(!resp.ok) return;
|
| 2015 |
-
const data=await resp.json();
|
| 2016 |
-
const badge=document.getElementById('healthBadge');
|
| 2017 |
-
if(badge){
|
| 2018 |
-
badge.innerHTML=`<div class="dot-live"></div><span>${data.gpu?'GPU':'CPU'} Β· v${data.version}</span>`;
|
| 2019 |
-
badge.title='VERIDEX backend online';
|
| 2020 |
-
}
|
| 2021 |
-
}catch(e){
|
| 2022 |
-
const badge=document.getElementById('healthBadge');
|
| 2023 |
-
if(badge){
|
| 2024 |
-
badge.innerHTML='<div style="width:7px;height:7px;border-radius:50%;background:var(--red)"></div><span style="color:var(--red)">Offline</span>';
|
| 2025 |
-
}
|
| 2026 |
-
}
|
| 2027 |
-
}
|
| 2028 |
-
|
| 2029 |
-
setTimeout(checkHealth,800);
|
| 2030 |
setTimeout(drawIdleCanvases,300);
|
| 2031 |
setTimeout(drawIdleCanvases,800);
|
| 2032 |
</script>
|
|
|
|
| 63 |
/* BG */
|
| 64 |
.bg-layer{position:fixed;inset:0;z-index:0;pointer-events:none;
|
| 65 |
background:
|
| 66 |
+
radial-gradient(ellipse 70% 55% at 5% 0%,rgba(79,172,254,0.22) 0%,transparent 60%),
|
| 67 |
+
radial-gradient(ellipse 60% 50% at 95% 100%,rgba(0,242,254,0.18) 0%,transparent 55%),
|
| 68 |
+
radial-gradient(ellipse 50% 50% at 50% 50%,rgba(37,99,235,0.08) 0%,transparent 65%),
|
| 69 |
+
repeating-linear-gradient(0deg,rgba(79,172,254,0.07) 0px,rgba(79,172,254,0.07) 1px,transparent 1px,transparent 24px),
|
| 70 |
+
repeating-linear-gradient(90deg,rgba(79,172,254,0.07) 0px,rgba(79,172,254,0.07) 1px,transparent 1px,transparent 24px),
|
| 71 |
+
linear-gradient(135deg,rgba(224,240,255,0.93) 0%,rgba(224,254,255,0.90) 50%,rgba(230,242,255,0.92) 100%);}
|
| 72 |
.grain{position:fixed;inset:0;z-index:0;pointer-events:none;opacity:0.03;
|
| 73 |
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)'/%3E%3C/svg%3E");
|
| 74 |
background-size:200px;}
|
| 75 |
.blob{position:fixed;border-radius:50%;filter:blur(90px);pointer-events:none;z-index:0;animation:blobFloat 18s ease-in-out infinite;}
|
| 76 |
+
.b1{width:700px;height:700px;top:-200px;left:-200px;background:radial-gradient(circle,rgba(79,172,254,0.28),transparent 65%);}
|
| 77 |
+
.b2{width:500px;height:500px;bottom:-100px;right:-100px;background:radial-gradient(circle,rgba(0,242,254,0.24),transparent 65%);animation-delay:-9s;}
|
| 78 |
+
.b3{width:400px;height:400px;top:45%;left:40%;background:radial-gradient(circle,rgba(37,99,235,0.18),transparent 65%);animation-delay:-15s;}
|
| 79 |
@keyframes blobFloat{0%,100%{transform:translate(0,0)scale(1)}33%{transform:translate(50px,-40px)scale(1.06)}66%{transform:translate(-30px,30px)scale(0.94)}}
|
| 80 |
|
| 81 |
/* CURSOR */
|
| 82 |
#cur{position:fixed;width:10px;height:10px;background:var(--blue);border-radius:50%;pointer-events:none;z-index:99999;transform:translate(-50%,-50%);transition:width .2s var(--ease),height .2s var(--ease),opacity .2s;mix-blend-mode:normal;}
|
| 83 |
+
#cur-ring{position:fixed;width:30px;height:30px;border:1.5px solid rgba(0,242,254,0.7);border-radius:50%;pointer-events:none;z-index:99998;transform:translate(-50%,-50%);transition:all .1s ease;}
|
| 84 |
.h-active #cur{width:22px;height:22px;}
|
| 85 |
+
.c-active #cur{width:14px;height:14px;background:rgba(0,242,254,0.8);}
|
| 86 |
|
| 87 |
/* LAYOUT */
|
| 88 |
.app{position:relative;z-index:2;min-height:100vh;}
|
| 89 |
.container{max-width:1280px;margin:0 auto;padding:0 40px;}
|
| 90 |
|
| 91 |
/* HEADER */
|
| 92 |
+
header{position:sticky;top:0;z-index:100;backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);background:rgba(224,240,255,0.55);border-bottom:1px solid rgba(255,255,255,0.3);box-shadow:0 1px 24px rgba(79,172,254,0.2),0 1px 0 rgba(0,242,254,0.1);backdrop-filter:blur(24px) saturate(200%);-webkit-backdrop-filter:blur(24px) saturate(200%);}
|
| 93 |
.header-inner{display:flex;align-items:center;justify-content:space-between;height:64px;}
|
| 94 |
.logo{display:flex;align-items:center;gap:10px;font-family:'Playfair Display',serif;font-weight:700;font-size:20px;letter-spacing:0.05em;color:var(--text-1);}
|
| 95 |
+
.logo-icon{width:34px;height:34px;border-radius:10px;background:linear-gradient(135deg,#4FACFE,#00F2FE);display:flex;align-items:center;justify-content:center;font-size:16px;box-shadow:0 4px 14px rgba(79,172,254,0.5);}
|
| 96 |
.logo-sub{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:400;color:var(--text-4);letter-spacing:0.15em;display:block;margin-top:1px;}
|
| 97 |
.header-center{display:flex;gap:4px;}
|
| 98 |
.nav-btn{padding:7px 16px;border-radius:100px;font-size:13px;font-weight:500;color:var(--text-3);letter-spacing:0.01em;transition:all 0.2s;cursor:none;border:1px solid transparent;}
|
| 99 |
.nav-btn:hover,.nav-btn.active{background:rgba(255,255,255,0.2);border-color:rgba(255,255,255,0.35);color:var(--text-1);box-shadow:var(--shadow-sm);}
|
| 100 |
.nav-btn.active{color:var(--blue);}
|
| 101 |
.header-right{display:flex;align-items:center;gap:10px;}
|
| 102 |
+
.status-badge{display:flex;align-items:center;gap:6px;padding:6px 12px;border-radius:100px;background:rgba(255,255,255,0.30);border:1px solid rgba(79,172,254,0.3);box-shadow:var(--shadow-sm);font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-3);}
|
| 103 |
.dot-live{width:7px;height:7px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green);animation:liveGlow 2s ease-in-out infinite;}
|
| 104 |
@keyframes liveGlow{0%,100%{opacity:1}50%{opacity:0.5}}
|
| 105 |
|
| 106 |
/* HERO */
|
| 107 |
.hero{padding:40px 0 20px;}
|
| 108 |
+
.hero-title{font-family:'Playfair Display',serif;font-size:42px;font-weight:900;letter-spacing:-0.02em;line-height:1.1;background:linear-gradient(135deg,#2563EB 0%,#4FACFE 50%,#00F2FE 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;margin-bottom:10px;}
|
| 109 |
.hero-sub{font-size:15px;color:var(--text-3);font-weight:400;line-height:1.5;}
|
| 110 |
.hero-badges{display:flex;gap:8px;flex-wrap:wrap;margin-top:14px;}
|
| 111 |
+
.hero-badge{display:flex;align-items:center;gap:5px;padding:5px 12px;border-radius:100px;font-size:11px;font-weight:600;letter-spacing:0.04em;background:rgba(255,255,255,0.28);border:1px solid rgba(79,172,254,0.25);box-shadow:var(--shadow-sm);color:var(--text-1);animation:badgeFade 0.5s var(--ease) backwards;}
|
| 112 |
.hero-badge:nth-child(1){animation-delay:.05s}
|
| 113 |
.hero-badge:nth-child(2){animation-delay:.1s}
|
| 114 |
.hero-badge:nth-child(3){animation-delay:.15s}
|
|
|
|
| 261 |
.mod-card.running .mod-status{background:var(--amber);box-shadow:0 0 8px var(--amber);animation:liveGlow 0.8s ease-in-out infinite;}
|
| 262 |
.mod-card.disabled-mod{opacity:0.4;filter:grayscale(0.6);}
|
| 263 |
.mod-card.disabled-mod .mod-status{background:rgba(0,0,0,0.1);}
|
| 264 |
+
.mod-card.mod-flagged{border-color:rgba(220,38,38,0.35)!important;background:rgba(220,38,38,0.06)!important;}
|
| 265 |
+
.mod-card.mod-flagged .mod-status{background:var(--red)!important;box-shadow:0 0 8px var(--red)!important;}
|
| 266 |
.mod-icon{font-size:18px;margin-bottom:6px;display:block;}
|
| 267 |
.mod-name{font-size:10px;font-weight:700;color:var(--text-2);letter-spacing:0.04em;margin-bottom:2px;}
|
| 268 |
.mod-desc{font-size:9px;color:var(--text-4);line-height:1.4;}
|
|
|
|
| 428 |
.fade-in:nth-child(1){animation-delay:.05s}
|
| 429 |
.fade-in:nth-child(2){animation-delay:.1s}
|
| 430 |
@keyframes fadeIn{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
</style>
|
| 432 |
</head>
|
| 433 |
<body>
|
|
|
|
| 455 |
<div class="header-inner">
|
| 456 |
<div class="logo">
|
| 457 |
<div class="logo-icon">π¬</div>
|
| 458 |
+
<div>VERIDEX <span class="logo-sub">FORENSIC AI PLATFORM v2</span></div>
|
| 459 |
</div>
|
| 460 |
<nav class="header-center">
|
| 461 |
<button class="nav-btn active" onclick="showSection('analyze')">Analyze</button>
|
|
|
|
| 462 |
<button class="nav-btn" onclick="showSection('modules')">46 Modules</button>
|
|
|
|
| 463 |
<button class="nav-btn" onclick="showSection('custody')">Chain of Custody</button>
|
| 464 |
</nav>
|
| 465 |
<div class="header-right">
|
| 466 |
+
<div class="status-badge"><div class="dot-live"></div><span id="activeModCount">46</span>/46 Active</div>
|
| 467 |
</div>
|
| 468 |
</div>
|
| 469 |
</div>
|
|
|
|
| 604 |
<button class="btn-report" id="btnReport" disabled onclick="openReport()">π GENERATE COURT REPORT</button>
|
| 605 |
</div>
|
| 606 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 607 |
<!-- METADATA -->
|
| 608 |
<div class="card fade-in">
|
| 609 |
<div class="card-header"><div class="card-title"><div class="card-title-icon" style="background:rgba(13,148,136,0.08)">ποΈ</div>File Metadata</div></div>
|
|
|
|
| 622 |
</div>
|
| 623 |
</div>
|
| 624 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
<!-- MODULES SECTION -->
|
| 626 |
<div id="sec-modules" style="display:none;padding-bottom:40px">
|
| 627 |
<div class="sec-lbl">All 46 Detection Modules</div>
|
|
|
|
| 1153 |
function resetFrameBars(){Array.from({length:80},(_,i)=>{const fb=document.getElementById('fb'+i);if(fb){fb.className='fb idle';fb.style.height=(20+Math.random()*15)+'px';}});}
|
| 1154 |
|
| 1155 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1156 |
+
// ANALYSIS
|
|
|
|
|
|
|
| 1157 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1158 |
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1159 |
+
// BACKEND API CONFIG β smart detection
|
| 1160 |
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1161 |
+
// If served from the FastAPI backend (port 8000), same-origin works.
|
| 1162 |
+
// If opened from file:// or python -m http.server, try localhost:8000.
|
| 1163 |
+
const API_BASE = (()=>{
|
| 1164 |
+
const loc = window.location;
|
| 1165 |
+
// If we're already on port 8000 or 8001 (uvicorn), use same origin
|
| 1166 |
+
if(loc.port==='8000'||loc.port==='8001'||loc.hostname==='veridex.up.railway.app') return '';
|
| 1167 |
+
// Otherwise target the FastAPI backend on 8000
|
| 1168 |
+
return 'http://localhost:8000';
|
| 1169 |
+
})();
|
| 1170 |
|
| 1171 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1172 |
+
// SEEDED SIMULATION β used when backend is offline
|
| 1173 |
+
// Deterministic per filename so same file = same result
|
| 1174 |
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1175 |
+
function seededRandom(seed){
|
| 1176 |
+
let s=seed;
|
| 1177 |
+
return function(){s=Math.sin(s)*10000;return s-Math.floor(s);};
|
| 1178 |
+
}
|
| 1179 |
+
function simulateAllModules(fileName,fileSize,fileType){
|
| 1180 |
+
// Generate a consistent seed from file properties
|
| 1181 |
+
let seed=0;for(let i=0;i<fileName.length;i++)seed+=fileName.charCodeAt(i);seed+=fileSize%997;
|
| 1182 |
+
const rng=seededRandom(seed);
|
| 1183 |
+
|
| 1184 |
+
// Generate per-module scores (0.0β1.0) seeded
|
| 1185 |
+
const moduleScores={};
|
| 1186 |
+
const base=rng(); // 0-1, determines authentic vs fake tendency
|
| 1187 |
+
const isFake=base<0.45;
|
| 1188 |
+
const isSynth=!isFake&&base<0.65;
|
| 1189 |
+
|
| 1190 |
+
for(let m=1;m<=46;m++){
|
| 1191 |
+
const r=rng();
|
| 1192 |
+
if(m===1||m===2) moduleScores[m]=1.0; // Legal/hash = always valid
|
| 1193 |
+
else if(isFake) moduleScores[m]=Math.max(0.05,Math.min(0.95,0.35+r*0.4-0.2));
|
| 1194 |
+
else if(isSynth&&m===46) moduleScores[m]=Math.max(0.05,Math.min(0.95,0.25+r*0.3));
|
| 1195 |
+
else moduleScores[m]=Math.max(0.05,Math.min(0.95,0.65+r*0.35-0.1));
|
| 1196 |
+
}
|
| 1197 |
+
|
| 1198 |
+
// Weighted confidence
|
| 1199 |
+
const weights={6:0.12,7:0.10,8:0.09,15:0.11,46:0.14,9:0.07};
|
| 1200 |
+
const defaultW=0.02;
|
| 1201 |
+
let tw=0,ws=0;
|
| 1202 |
+
for(let m=1;m<=46;m++){const w=weights[m]||defaultW;tw+=w;ws+=w*moduleScores[m];}
|
| 1203 |
+
const avg=ws/tw;
|
| 1204 |
+
const confidence=Math.round(avg*100)/100;
|
| 1205 |
+
const verdict=avg<0.40?'FAKE':isSynth?'SYNTHETIC':'AUTHENTIC';
|
| 1206 |
+
|
| 1207 |
+
const moduleNames={6:'RGB Forensic',7:'DCT/FFT Analysis',8:'Noise Residual',9:'rPPG Signal',15:'GAN Artifacts',11:'Lip Sync',16:'Metadata',46:'Synth ID'};
|
| 1208 |
+
const anomalies=Object.entries(moduleScores).filter(([,v])=>v<0.4).sort(([,a],[,b])=>a-b).slice(0,4);
|
| 1209 |
+
const findings=anomalies.length?anomalies.map(([m])=>`Module ${m} (${moduleNames[m]||'Forensic'}): anomaly flagged β ${Math.round((1-moduleScores[m])*100)}% confidence`):['All modules within authentic parameters'];
|
| 1210 |
+
|
| 1211 |
+
const scoresStr={};
|
| 1212 |
+
Object.entries(moduleScores).forEach(([k,v])=>scoresStr[k]=v.toFixed(4));
|
| 1213 |
+
|
| 1214 |
+
return {
|
| 1215 |
+
case_id:'VRX-SIM-'+Date.now(),
|
| 1216 |
+
sha256:null,
|
| 1217 |
+
timestamp:new Date().toLocaleString(),
|
| 1218 |
+
file_name:fileName,file_type:fileType,
|
| 1219 |
+
verdict,
|
| 1220 |
+
confidence,
|
| 1221 |
+
is_synth:isSynth,
|
| 1222 |
+
synth_data:isSynth?{
|
| 1223 |
+
generator:['Stable Diffusion XL','DALL-E 3','Midjourney v6','Adobe Firefly'][Math.floor(rng()*4)],
|
| 1224 |
+
provider:['Stability AI','OpenAI','Midjourney Inc','Adobe'][Math.floor(rng()*4)],
|
| 1225 |
+
confidence:0.7+rng()*0.25,
|
| 1226 |
+
watermark_detected:rng()>0.5,
|
| 1227 |
+
signal_type:['GAN fingerprint','Diffusion trace','VAE artifact','CLIP embedding'][Math.floor(rng()*4)]
|
| 1228 |
+
}:{},
|
| 1229 |
+
scores:scoresStr,
|
| 1230 |
+
key_findings:findings,
|
| 1231 |
+
ai_summary:`[OFFLINE MODE] Seeded forensic simulation β connect backend for real analysis`,
|
| 1232 |
+
_simulated:true
|
| 1233 |
+
};
|
| 1234 |
+
}
|
| 1235 |
+
|
| 1236 |
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1237 |
+
// REAL BACKEND ANALYSIS with auto-fallback
|
| 1238 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1239 |
async function startAnalysis(){
|
| 1240 |
if(!state.file){toast('β οΈ Please upload a file first',2500);return;}
|
|
|
|
| 1264 |
if(pct>15 && msgIdx<msgs.length){toast(msgs[msgIdx++],1200);}
|
| 1265 |
},400);
|
| 1266 |
|
| 1267 |
+
let data=null;
|
| 1268 |
+
let usedFallback=false;
|
| 1269 |
+
|
| 1270 |
try {
|
| 1271 |
const formData=new FormData();
|
| 1272 |
formData.append('file', state.file);
|
| 1273 |
const enabledIds=active.map(m=>m.id).join(',');
|
| 1274 |
formData.append('modules', enabledIds);
|
| 1275 |
|
| 1276 |
+
const response = await fetch(`${API_BASE}/analyze`, {method:'POST',body:formData});
|
|
|
|
| 1277 |
|
| 1278 |
+
clearInterval(interval);
|
| 1279 |
if(!response.ok){
|
| 1280 |
let errMsg='Analysis failed';
|
| 1281 |
try{const err=await response.json();errMsg=err.detail||errMsg;}catch(e){}
|
| 1282 |
throw new Error(`Server ${response.status}: ${errMsg}`);
|
| 1283 |
}
|
| 1284 |
+
data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1285 |
|
| 1286 |
} catch(err) {
|
| 1287 |
clearInterval(interval);
|
| 1288 |
+
const isNetwork=err.message.includes('Failed to fetch')||err.message.includes('NetworkError')||err.message.includes('fetch')||err.message.includes('Load failed');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1289 |
if(isNetwork){
|
| 1290 |
+
// AUTO FALLBACK: seeded simulation β no error shown
|
| 1291 |
+
toast('π‘ Backend offline β using seeded forensic simulation',3000);
|
| 1292 |
+
data = simulateAllModules(state.file.name, state.file.size, state.file.type||'unknown');
|
| 1293 |
+
usedFallback=true;
|
| 1294 |
} else {
|
| 1295 |
+
btn.classList.remove('loading');
|
| 1296 |
+
document.getElementById('scanBeam').classList.remove('active');
|
| 1297 |
+
progBar.style.width='0%'; progWrap.classList.remove('active');
|
| 1298 |
+
active.forEach(m=>{const el=document.getElementById('mod'+m.id);if(el)el.className='mod-card';});
|
| 1299 |
+
toast('β '+err.message,5000);
|
| 1300 |
+
console.error('[VERIDEX] Analysis error:',err);
|
| 1301 |
+
return;
|
| 1302 |
}
|
|
|
|
| 1303 |
}
|
| 1304 |
+
|
| 1305 |
+
progBar.style.width='100%';
|
| 1306 |
+
|
| 1307 |
+
state.analysisId = data.case_id;
|
| 1308 |
+
state.timestamp = data.timestamp;
|
| 1309 |
+
state.apiData = data;
|
| 1310 |
+
|
| 1311 |
+
if(data.sha256){
|
| 1312 |
+
state.hash = data.sha256;
|
| 1313 |
+
document.getElementById('hashDisplay').innerHTML=`<strong>SHA-256:</strong><br>${data.sha256}`;
|
| 1314 |
+
document.getElementById('hashStatus').textContent='β
';
|
| 1315 |
+
document.getElementById('hashStatusTxt').textContent=usedFallback?'Client-computed hash':'Server-verified integrity';
|
| 1316 |
+
document.getElementById('chash1').textContent=data.sha256.slice(0,16)+'...';
|
| 1317 |
+
markCustodyStep(1,data.sha256.slice(0,16)+'...');
|
| 1318 |
+
}
|
| 1319 |
+
|
| 1320 |
+
// Map ALL 46 module scores to UI
|
| 1321 |
+
const scores=data.scores||{};
|
| 1322 |
+
Object.entries(scores).forEach(([k,v])=>{
|
| 1323 |
+
const mid=parseInt(k);
|
| 1324 |
+
if(!isNaN(mid)) state.moduleResults[mid]=Math.round(parseFloat(v)*100);
|
| 1325 |
+
});
|
| 1326 |
+
|
| 1327 |
+
// Animate ALL active module cards with their real/seeded scores
|
| 1328 |
+
active.forEach((m,i)=>{
|
| 1329 |
+
setTimeout(()=>{
|
| 1330 |
+
const el=document.getElementById('mod'+m.id);
|
| 1331 |
+
const fp=document.getElementById('fp'+m.id);
|
| 1332 |
+
const score=state.moduleResults[m.id]||70;
|
| 1333 |
+
if(el) el.className='mod-card on'+(score<40?' mod-flagged':'');
|
| 1334 |
+
if(fp) fp.className='feat-pip on';
|
| 1335 |
+
},i*40);
|
| 1336 |
+
});
|
| 1337 |
+
|
| 1338 |
+
document.getElementById('scanBeam').classList.remove('active');
|
| 1339 |
+
btn.classList.remove('loading');
|
| 1340 |
+
|
| 1341 |
+
const verdict = data.verdict||'UNKNOWN';
|
| 1342 |
+
const isFake = verdict==='FAKE'||verdict==='DEEPFAKE';
|
| 1343 |
+
const isSynth = data.is_synth||verdict==='SYNTHETIC';
|
| 1344 |
+
const score = Math.round(data.confidence*100);
|
| 1345 |
+
|
| 1346 |
+
finishAnalysisFromAPI(data, score, isFake, isSynth);
|
| 1347 |
}
|
| 1348 |
|
| 1349 |
+
// Uses REAL or SIMULATED data β both use the same response shape
|
|
|
|
|
|
|
| 1350 |
function finishAnalysisFromAPI(data, score, isFake, isSynth){
|
| 1351 |
state.finalScore=score; state.isFake=isFake; state.isSynth=isSynth; state.analyzed=true;
|
| 1352 |
|
|
|
|
| 1363 |
else if(isSynth){chip.className='verdict-chip synth';chip.textContent='𧬠SYNTHETIC DETECTED';}
|
| 1364 |
else{chip.className='verdict-chip authentic';chip.textContent='β
AUTHENTIC MEDIA';}
|
| 1365 |
|
| 1366 |
+
// ββ 8 sub-score bars ββ
|
| 1367 |
const scores=data.scores||{};
|
| 1368 |
const subMap=[
|
| 1369 |
['6','RGB Forensic'],['7','Frequency'],['8','Noise Residual'],['9','rPPG Signal'],
|
|
|
|
| 1383 |
},i*150);
|
| 1384 |
});
|
| 1385 |
|
| 1386 |
+
// ββ All 46 module cards: update color based on score ββ
|
| 1387 |
+
Object.entries(scores).forEach(([k,v])=>{
|
| 1388 |
+
const mid=parseInt(k); if(isNaN(mid))return;
|
| 1389 |
+
const val=Math.round(parseFloat(v)*100);
|
| 1390 |
+
state.moduleResults[mid]=val;
|
| 1391 |
+
// Already animated in startAnalysis; update flagged class here
|
| 1392 |
+
const el=document.getElementById('mod'+mid);
|
| 1393 |
+
if(el && el.classList.contains('on')){
|
| 1394 |
+
if(val<40) el.classList.add('mod-flagged');
|
| 1395 |
+
}
|
| 1396 |
+
});
|
| 1397 |
+
|
| 1398 |
+
// ββ Synth ID card ββ
|
| 1399 |
if(isSynth && data.synth_data && Object.keys(data.synth_data).length>0){
|
| 1400 |
const sd=data.synth_data;
|
| 1401 |
state.synthData={
|
| 1402 |
model:sd.generator||'Unknown',generator:sd.provider||'Unknown',
|
| 1403 |
+
confidence:Math.round((sd.confidence||0)*(sd.confidence>1?1:100)),
|
| 1404 |
faceMatch:'GENERATED',watermark:sd.watermark_detected?'DETECTED':'NOT FOUND',
|
| 1405 |
signalType:sd.signal_type||'Unknown',
|
| 1406 |
};
|
|
|
|
| 1413 |
setTimeout(()=>{document.getElementById('synthConfFill').style.width=state.synthData.confidence+'%';},300);
|
| 1414 |
}
|
| 1415 |
|
| 1416 |
+
// ββ Key findings in metadata panel ββ
|
| 1417 |
if(data.key_findings && data.key_findings.length>0){
|
| 1418 |
const meta=document.getElementById('metaPanel');
|
| 1419 |
+
const findingsHtml=data.key_findings.map(f=>`<div class="rep-field" style="grid-column:1/-1"><div class="rep-f-lbl">π FINDING</div><div class="rep-f-val" style="font-size:11px;color:var(--text-2)">${f}</div></div>`).join('');
|
| 1420 |
+
meta.innerHTML=(meta.innerHTML||'')+findingsHtml;
|
| 1421 |
}
|
| 1422 |
|
| 1423 |
+
// ββ Simulation badge ββ
|
| 1424 |
+
if(data._simulated){
|
| 1425 |
+
const meta=document.getElementById('metaPanel');
|
| 1426 |
+
if(meta) meta.innerHTML=`<div class="rep-field" style="grid-column:1/-1;background:var(--amber-pale);border-color:rgba(217,119,6,0.25)"><div class="rep-f-lbl">β οΈ OFFLINE MODE</div><div class="rep-f-val" style="font-size:11px;color:var(--amber)">Backend offline β seeded forensic simulation. Start uvicorn for real AI analysis.</div></div>`+(meta.innerHTML||'');
|
|
|
|
|
|
|
| 1427 |
}
|
| 1428 |
|
| 1429 |
animateFrameBars(isFake);
|
|
|
|
| 1433 |
document.getElementById('btnReport').disabled=false;
|
| 1434 |
document.getElementById('progWrap').classList.remove('active');
|
| 1435 |
|
| 1436 |
+
const modeTag=data._simulated?' [SIMULATED]':'';
|
| 1437 |
+
toast(isFake?'β οΈ DEEPFAKE DETECTED'+modeTag:isSynth?'𧬠Synthetic media detected'+modeTag:'β
Authentic media confirmed'+modeTag,4000);
|
| 1438 |
+
}
|
| 1439 |
+
|
| 1440 |
+
function finishAnalysis(score,isFake,isSynth){
|
| 1441 |
+
state.finalScore=score;state.isFake=isFake;state.isSynth=isSynth;state.analyzed=true;
|
| 1442 |
+
state.analysisId='VRX-'+Date.now()+'-'+Math.random().toString(36).slice(2,8).toUpperCase();
|
| 1443 |
+
state.timestamp=new Date().toISOString();
|
| 1444 |
+
|
| 1445 |
+
const offset=326-(326*score/100);
|
| 1446 |
+
const fill=document.getElementById('ringFill');
|
| 1447 |
+
fill.style.strokeDashoffset=offset;
|
| 1448 |
+
fill.style.stroke=isFake?'var(--red)':isSynth?'var(--synth)':'var(--green)';
|
| 1449 |
+
|
| 1450 |
+
document.getElementById('scoreNum').textContent=score+'%';
|
| 1451 |
+
document.getElementById('scoreNum').style.color=isFake?'var(--red)':isSynth?'var(--synth)':'var(--green)';
|
| 1452 |
+
|
| 1453 |
+
const chip=document.getElementById('verdictChip');
|
| 1454 |
+
if(isFake){chip.className='verdict-chip fake';chip.textContent='β οΈ DEEPFAKE DETECTED';}
|
| 1455 |
+
else if(isSynth){chip.className='verdict-chip synth';chip.textContent='𧬠SYNTHETIC ID DETECTED';}
|
| 1456 |
+
else{chip.className='verdict-chip authentic';chip.textContent='β
AUTHENTIC MEDIA';}
|
| 1457 |
+
|
| 1458 |
+
const sData=generateSubScores(isFake,isSynth);
|
| 1459 |
+
state.scores=sData;
|
| 1460 |
+
Object.entries(sData).forEach(([key,val],i)=>{
|
| 1461 |
+
const idx=i+1;const el=document.getElementById('ss'+idx);const valEl=document.getElementById('sv'+idx);if(!el||!valEl)return;
|
| 1462 |
+
setTimeout(()=>{
|
| 1463 |
+
el.style.width=val+'%';valEl.textContent=val+'%';
|
| 1464 |
+
const cls=idx===8?'synth-fill':val<40?'danger':val<65?'warn':'safe';
|
| 1465 |
+
el.className='ss-fill '+cls;
|
| 1466 |
+
},i*150);
|
| 1467 |
+
});
|
| 1468 |
+
|
| 1469 |
+
// SYNTH ID Card
|
| 1470 |
+
if(isSynth){
|
| 1471 |
+
const synthScore=Math.floor(65+Math.random()*30);
|
| 1472 |
+
state.synthData={
|
| 1473 |
+
model:['DALL-E 3','Midjourney v6','Stable Diffusion XL','Adobe Firefly'][Math.floor(Math.random()*4)],
|
| 1474 |
+
confidence:synthScore,
|
| 1475 |
+
faceMatch:'GENERATED',
|
| 1476 |
+
watermark:Math.random()>0.5?'DETECTED':'NOT FOUND',
|
| 1477 |
+
signalType:['GAN fingerprint','Diffusion trace','VAE artifact','CLIP embedding'][Math.floor(Math.random()*4)],
|
| 1478 |
+
generator:['OpenAI','Stability AI','Adobe','Midjourney'][Math.floor(Math.random()*4)],
|
| 1479 |
+
};
|
| 1480 |
+
const sc=document.getElementById('synthCard');sc.classList.add('visible');
|
| 1481 |
+
document.getElementById('synthGrid').innerHTML=Object.entries({
|
| 1482 |
+
'Generator Model':state.synthData.model,'Confidence':state.synthData.confidence+'%',
|
| 1483 |
+
'Face Status':state.synthData.faceMatch,'Watermark':state.synthData.watermark,
|
| 1484 |
+
'Signal Type':state.synthData.signalType,'Provider':state.synthData.generator,
|
| 1485 |
+
}).map(([k,v])=>`<div class="synth-item"><div class="synth-lbl">${k}</div><div class="synth-val">${v}</div></div>`).join('');
|
| 1486 |
+
setTimeout(()=>{document.getElementById('synthConfFill').style.width=state.synthData.confidence+'%';},300);
|
| 1487 |
}
|
| 1488 |
|
| 1489 |
+
animateFrameBars(isFake);
|
| 1490 |
+
if(state.fileType.startsWith('image')) setTimeout(()=>addDetectionBoxes(isFake),500);
|
| 1491 |
+
setTimeout(()=>drawAnalysisCanvases(isFake,isSynth),300);
|
| 1492 |
+
markCustodyStep(2,'Completed');markCustodyStep(3,score+'%');
|
| 1493 |
+
document.getElementById('btnReport').disabled=false;
|
| 1494 |
+
|
| 1495 |
+
// Update video forensic panel if visible
|
| 1496 |
+
const vfg=document.getElementById('vForensicGrid');
|
| 1497 |
+
if(vfg){
|
| 1498 |
+
const vstf=vfg.querySelectorAll('.vstf');
|
| 1499 |
+
const results=isFake?['β οΈ ANOMALY','β οΈ MISMATCH','β οΈ BROKEN','β οΈ WARPED','β οΈ ABSENT','β οΈ LOW']:['β
STABLE','β
SYNCED','β
COHERENT','β
NATURAL','β
DETECTED','β
NORMAL'];
|
| 1500 |
+
vstf.forEach((c,i)=>{c.textContent=results[i];c.style.color=isFake?'var(--red)':'var(--green)';});
|
| 1501 |
+
}
|
| 1502 |
|
| 1503 |
+
toast(isFake?'β οΈ Deepfake indicators found!':isSynth?'𧬠Synthetic ID detected!':'β
Media appears authentic',3500);
|
| 1504 |
}
|
| 1505 |
|
| 1506 |
+
function generateSubScores(isFake,isSynth){
|
| 1507 |
+
const base=isFake?25:75;
|
| 1508 |
+
const keys=['RGB Forensic','Frequency','Noise Residual','rPPG Signal','GAN Artifacts','Lip Sync','Metadata','Synth ID'];
|
| 1509 |
+
const out={};
|
| 1510 |
+
keys.forEach((k,i)=>{
|
| 1511 |
+
if(k==='Synth ID') out[k]=isSynth?Math.max(60,Math.min(98,75+Math.floor(Math.random()*20))):Math.max(5,Math.min(35,15+Math.floor(Math.random()*20)));
|
| 1512 |
+
else out[k]=Math.max(5,Math.min(98,base+Math.floor((Math.random()-0.5)*40)));
|
| 1513 |
+
});
|
| 1514 |
+
return out;
|
| 1515 |
+
}
|
| 1516 |
|
| 1517 |
function animateFrameBars(isFake){
|
| 1518 |
Array.from({length:80},(_,i)=>setTimeout(()=>{
|
|
|
|
| 1605 |
// NAVIGATION
|
| 1606 |
// βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1607 |
function showSection(name){
|
| 1608 |
+
['analyze','modules','custody'].forEach(s=>{document.getElementById('sec-'+s).style.display=s===name?'block':'none';});
|
| 1609 |
+
document.querySelectorAll('.nav-btn').forEach((b,i)=>{b.classList.toggle('active',b.textContent.toLowerCase().includes(name)||(i===0&&name==='analyze'));});
|
| 1610 |
+
if(name==='custody')updateCustodyLog();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1611 |
window.scrollTo({top:0,behavior:'smooth'});
|
| 1612 |
}
|
| 1613 |
|
|
|
|
| 1637 |
<div class="rep-field"><div class="rep-f-lbl">Active Modules</div><div class="rep-f-val">${MODULES.length-state.disabledModules.size}/46</div></div>
|
| 1638 |
</div></div>
|
| 1639 |
<div class="report-section"><div class="rep-section-title">π Cryptographic Integrity</div>
|
| 1640 |
+
<div class="rep-hash"><strong>SHA-256:</strong><br>${state.hash||'N/A'}<br><br><strong>CHAIN OF CUSTODY:</strong> File β Hash β Analysis β Verification β Report<br><strong>TAMPER STATUS:</strong> β
UNMODIFIED</div>
|
| 1641 |
</div>
|
| 1642 |
<div class="report-section"><div class="rep-section-title">βοΈ Forensic Verdict</div>
|
| 1643 |
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px;border-radius:14px;background:${state.isFake?'var(--red-pale)':state.isSynth?'var(--synth-pale)':'var(--green-pale)'};border:1px solid ${state.isFake?'rgba(220,38,38,0.2)':state.isSynth?'rgba(139,92,246,0.2)':'rgba(22,163,74,0.2)'}">
|
|
|
|
| 1710 |
function shortHash(){return Array.from({length:16},()=>'0123456789abcdef'[Math.floor(Math.random()*16)]).join('');}
|
| 1711 |
function toast(msg,dur=2500){const t=document.getElementById('toast');t.textContent=msg;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),dur);}
|
| 1712 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1713 |
setTimeout(drawIdleCanvases,300);
|
| 1714 |
setTimeout(drawIdleCanvases,800);
|
| 1715 |
</script>
|