Spaces:
Running
Running
| <html lang="en" data-theme="animated"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>VeriFile-X | Unmask AI Content with Confidence</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> | |
| <style> | |
| /* βββ RESET βββββββββββββββββββββββββββββββββββββββββ */ | |
| *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } | |
| /* βββ THEME VARIABLES βββββββββββββββββββββββββββββββ */ | |
| [data-theme="light"] { | |
| --bg: #f4f6f8; | |
| --bg2: #ffffff; | |
| --bg3: #eaecf0; | |
| --card: #ffffff; | |
| --text: #2b2f33; | |
| --subtext: #6b7280; | |
| --primary: #2f4f63; | |
| --primary2: #3a6f8f; | |
| --border: #e6eaee; | |
| --border2: rgba(47,79,99,0.18); | |
| --shadow: 0 8px 28px rgba(0,0,0,0.08); | |
| --shadow-lg: 0 16px 50px rgba(0,0,0,0.12); | |
| --nav-bg: rgba(255,255,255,0.97); | |
| --accent: #2f4f63; | |
| --red: #dc2626; | |
| --orange: #ea580c; | |
| --yellow: #ca8a04; | |
| --green: #16a34a; | |
| --gradient: linear-gradient(135deg, #2f4f63, #3a6f8f); | |
| } | |
| [data-theme="dark"] { | |
| --bg: #0f1720; | |
| --bg2: #18222c; | |
| --bg3: #1e2d3a; | |
| --card: #18222c; | |
| --text: #e6edf3; | |
| --subtext: #9aa6b2; | |
| --primary: #6fa3c8; | |
| --primary2: #5fa4c9; | |
| --border: #263341; | |
| --border2: rgba(111,163,200,0.22); | |
| --shadow: 0 8px 28px rgba(0,0,0,0.35); | |
| --shadow-lg: 0 16px 50px rgba(0,0,0,0.55); | |
| --nav-bg: rgba(15,23,32,0.97); | |
| --accent: #6fa3c8; | |
| --red: #ef4444; | |
| --orange: #f97316; | |
| --yellow: #eab308; | |
| --green: #10b981; | |
| --gradient: linear-gradient(135deg, #5fa4c9, #6fa3c8); | |
| } | |
| [data-theme="animated"] { | |
| --bg: #0b0f1a; | |
| --bg2: #111827; | |
| --bg3: #1a2236; | |
| --card: rgba(17,24,39,0.85); | |
| --text: #e6edf7; | |
| --subtext: #6b7a99; | |
| --primary: #00ffe7; | |
| --primary2: #7a7aff; | |
| --border: rgba(0,255,238,0.12); | |
| --border2: rgba(0,255,238,0.22); | |
| --shadow: 0 8px 28px rgba(0,0,0,0.5); | |
| --shadow-lg: 0 16px 50px rgba(0,0,0,0.7); | |
| --nav-bg: rgba(11,15,26,0.88); | |
| --accent: #00ffe7; | |
| --red: #ef4444; | |
| --orange: #f97316; | |
| --yellow: #eab308; | |
| --green: #10b981; | |
| --gradient: linear-gradient(135deg, #00ffe7, #7a7aff); | |
| } | |
| /* βββ BASE ββββββββββββββββββββββββββββββββββββββββββ */ | |
| html { scroll-behavior: smooth; } | |
| body { | |
| font-family: 'Space Grotesk', sans-serif; | |
| background: var(--bg); | |
| color: var(--text); | |
| min-height: 100vh; | |
| overflow-x: hidden; | |
| transition: background 0.3s ease, color 0.3s ease; | |
| } | |
| /* βββ GRID TEXTURE (light & dark only) ββββββββββββββ */ | |
| [data-theme="light"] body::before, | |
| [data-theme="dark"] body::before { | |
| content: ""; | |
| position: fixed; | |
| inset: 0; | |
| background: | |
| linear-gradient(rgba(120,160,190,0.06) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(120,160,190,0.06) 1px, transparent 1px); | |
| background-size: 42px 42px; | |
| pointer-events: none; | |
| z-index: 0; | |
| } | |
| /* βββ THREE.JS CANVAS βββββββββββββββββββββββββββββββ */ | |
| #bg-canvas { | |
| position: fixed; | |
| inset: 0; | |
| z-index: 0; | |
| pointer-events: none; | |
| opacity: 0; | |
| transition: opacity 0.6s ease; | |
| } | |
| [data-theme="animated"] #bg-canvas { opacity: 1; } | |
| /* βββ PAGE WRAPPER ββββββββββββββββββββββββββββββββββ */ | |
| .page { position: relative; z-index: 2; } | |
| /* βββ NAVBAR ββββββββββββββββββββββββββββββββββββββββ */ | |
| nav { | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 18px 60px; | |
| background: var(--nav-bg); | |
| border-bottom: 1px solid var(--border); | |
| backdrop-filter: blur(18px); | |
| transition: background 0.3s ease, border-color 0.3s ease; | |
| } | |
| .nav-logo-text { | |
| font-size: 22px; | |
| font-weight: 700; | |
| letter-spacing: 2px; | |
| color: var(--primary); | |
| text-decoration: none; | |
| } | |
| .nav-right { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| .theme-switcher { | |
| display: flex; | |
| align-items: center; | |
| gap: 3px; | |
| background: var(--bg3); | |
| border: 1px solid var(--border); | |
| border-radius: 10px; | |
| padding: 3px; | |
| } | |
| .theme-btn { | |
| padding: 6px 14px; | |
| border: none; | |
| border-radius: 8px; | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 12px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| background: transparent; | |
| color: var(--subtext); | |
| transition: all 0.2s; | |
| letter-spacing: 0.03em; | |
| } | |
| .theme-btn:hover { color: var(--text); } | |
| .theme-btn.active { | |
| background: var(--primary); | |
| color: #fff; | |
| } | |
| [data-theme="light"] .theme-btn.active { color: #fff; } | |
| .live-indicator { | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 12px; | |
| font-weight: 600; | |
| color: var(--green); | |
| letter-spacing: 0.05em; | |
| } | |
| .live-dot { | |
| width: 7px; | |
| height: 7px; | |
| border-radius: 50%; | |
| background: var(--green); | |
| animation: blink 2.2s infinite; | |
| } | |
| @keyframes blink { 0%,100% { opacity:1; } 50% { opacity:0.25; } } | |
| /* βββ HERO ββββββββββββββββββββββββββββββββββββββββββ */ | |
| .hero { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 88vh; | |
| padding: 60px 40px; | |
| } | |
| .hero-inner { | |
| background: var(--card); | |
| border-radius: 22px; | |
| padding: 70px 80px; | |
| max-width: 1020px; | |
| width: 100%; | |
| display: flex; | |
| align-items: center; | |
| gap: 70px; | |
| box-shadow: var(--shadow-lg); | |
| border: 1px solid var(--border); | |
| transition: background 0.3s ease, border-color 0.3s ease; | |
| } | |
| .hero-logo-col { | |
| flex-shrink: 0; | |
| } | |
| .hero-logo-img { | |
| width: 320px; | |
| max-width: 100%; | |
| object-fit: contain; | |
| filter: drop-shadow(0 8px 24px rgba(0,0,0,0.2)); | |
| } | |
| .hero-text-col { flex: 1; } | |
| .hero-badge { | |
| display: inline-block; | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| color: var(--primary); | |
| background: var(--bg3); | |
| border: 1px solid var(--border2); | |
| padding: 5px 14px; | |
| border-radius: 20px; | |
| margin-bottom: 18px; | |
| } | |
| .hero-text-col h1 { | |
| font-size: 44px; | |
| font-weight: 700; | |
| color: var(--primary); | |
| line-height: 1.15; | |
| margin-bottom: 16px; | |
| letter-spacing: -0.02em; | |
| } | |
| .hero-text-col p { | |
| font-size: 17px; | |
| color: var(--subtext); | |
| line-height: 1.7; | |
| margin-bottom: 32px; | |
| } | |
| .hero-cta { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| background: var(--gradient); | |
| color: #fff; | |
| padding: 15px 32px; | |
| border-radius: 12px; | |
| border: none; | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 15px; | |
| font-weight: 700; | |
| cursor: pointer; | |
| box-shadow: var(--shadow); | |
| transition: all 0.25s; | |
| text-decoration: none; | |
| } | |
| .hero-cta:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); } | |
| .hero-cta-arrow { | |
| width: 18px; | |
| height: 18px; | |
| stroke: currentColor; | |
| stroke-width: 2.2; | |
| fill: none; | |
| stroke-linecap: round; | |
| stroke-linejoin: round; | |
| transition: transform 0.2s; | |
| } | |
| .hero-cta:hover .hero-cta-arrow { transform: translateX(3px); } | |
| /* Animated hero (no logo, text centered) */ | |
| [data-theme="animated"] .hero { min-height: 80vh; } | |
| [data-theme="animated"] .hero-inner { | |
| flex-direction: column; | |
| text-align: center; | |
| padding: 80px 60px; | |
| gap: 0; | |
| background: rgba(17,24,39,0.7); | |
| backdrop-filter: blur(20px); | |
| } | |
| [data-theme="animated"] .hero-logo-col { display: none; } | |
| [data-theme="animated"] .hero-text-col { width: 100%; max-width: 680px; margin: 0 auto; } | |
| [data-theme="animated"] .hero-text-col h1 { font-size: 52px; color: var(--primary); } | |
| [data-theme="animated"] .hero-badge { display: none; } | |
| [data-theme="animated"] .hero-text-col p { font-size: 18px; } | |
| /* βββ STATS BAR βββββββββββββββββββββββββββββββββββββ */ | |
| .stats-bar { | |
| padding: 0 60px 0; | |
| display: flex; | |
| justify-content: center; | |
| } | |
| .stats-inner { | |
| max-width: 1020px; | |
| width: 100%; | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 0; | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| overflow: hidden; | |
| box-shadow: var(--shadow); | |
| } | |
| .stat-item { | |
| padding: 28px 24px; | |
| text-align: center; | |
| border-right: 1px solid var(--border); | |
| transition: background 0.2s; | |
| } | |
| .stat-item:last-child { border-right: none; } | |
| .stat-item:hover { background: var(--bg3); } | |
| .stat-number { | |
| font-size: 36px; | |
| font-weight: 700; | |
| color: var(--primary); | |
| line-height: 1; | |
| margin-bottom: 6px; | |
| } | |
| .stat-label { | |
| font-size: 13px; | |
| color: var(--subtext); | |
| font-weight: 500; | |
| } | |
| /* βββ SECTION LAYOUT ββββββββββββββββββββββββββββββββ */ | |
| .section { | |
| padding: 80px 60px; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| .section-header { | |
| text-align: center; | |
| margin-bottom: 52px; | |
| } | |
| .section-label { | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| color: var(--primary); | |
| margin-bottom: 12px; | |
| } | |
| .section-title { | |
| font-size: 34px; | |
| font-weight: 700; | |
| color: var(--text); | |
| letter-spacing: -0.02em; | |
| margin-bottom: 14px; | |
| } | |
| .section-sub { | |
| font-size: 16px; | |
| color: var(--subtext); | |
| max-width: 520px; | |
| margin: 0 auto; | |
| line-height: 1.7; | |
| } | |
| /* βββ FEATURE CARDS βββββββββββββββββββββββββββββββββ */ | |
| .features-grid { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 28px; | |
| } | |
| .feature-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 18px; | |
| padding: 36px 30px; | |
| box-shadow: var(--shadow); | |
| transition: all 0.3s; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .feature-card::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| background: radial-gradient(circle at top left, rgba(95,164,201,0.12), transparent 65%); | |
| opacity: 0; | |
| transition: opacity 0.4s; | |
| } | |
| [data-theme="animated"] .feature-card::after { | |
| background: radial-gradient(circle at top left, rgba(0,255,238,0.1), transparent 65%); | |
| } | |
| .feature-card:hover { transform: translateY(-8px); box-shadow: var(--shadow-lg); } | |
| .feature-card:hover::after { opacity: 1; } | |
| .feature-icon { | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 14px; | |
| background: var(--bg3); | |
| border: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 20px; | |
| } | |
| .feature-icon svg { | |
| width: 24px; | |
| height: 24px; | |
| stroke: var(--primary); | |
| stroke-width: 1.8; | |
| fill: none; | |
| stroke-linecap: round; | |
| stroke-linejoin: round; | |
| } | |
| .feature-card h3 { | |
| font-size: 18px; | |
| font-weight: 700; | |
| color: var(--text); | |
| margin-bottom: 10px; | |
| } | |
| .feature-card p { | |
| font-size: 14px; | |
| color: var(--subtext); | |
| line-height: 1.7; | |
| } | |
| /* βββ HOW IT WORKS ββββββββββββββββββββββββββββββββββ */ | |
| .how-grid { | |
| display: grid; | |
| grid-template-columns: repeat(4, 1fr); | |
| gap: 24px; | |
| position: relative; | |
| } | |
| .how-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 28px 24px; | |
| text-align: center; | |
| box-shadow: var(--shadow); | |
| position: relative; | |
| } | |
| .how-step { | |
| display: inline-block; | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 10px; | |
| background: var(--gradient); | |
| color: #fff; | |
| font-size: 14px; | |
| font-weight: 700; | |
| line-height: 36px; | |
| text-align: center; | |
| margin-bottom: 14px; | |
| } | |
| .how-card h4 { | |
| font-size: 15px; | |
| font-weight: 700; | |
| color: var(--text); | |
| margin-bottom: 8px; | |
| } | |
| .how-card p { | |
| font-size: 13px; | |
| color: var(--subtext); | |
| line-height: 1.6; | |
| } | |
| /* βββ DIVIDER βββββββββββββββββββββββββββββββββββββββ */ | |
| .divider { | |
| border: none; | |
| border-top: 1px solid var(--border); | |
| margin: 0 60px; | |
| } | |
| /* βββ UPLOAD AREA βββββββββββββββββββββββββββββββββββ */ | |
| .upload-section { | |
| padding: 80px 60px; | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| } | |
| .upload-section-header { | |
| text-align: center; | |
| margin-bottom: 40px; | |
| } | |
| .upload-zone { | |
| max-width: 680px; | |
| margin: 0 auto; | |
| padding: 56px 40px; | |
| text-align: center; | |
| cursor: pointer; | |
| background: var(--card); | |
| border: 2px dashed var(--border2); | |
| border-radius: 18px; | |
| box-shadow: var(--shadow); | |
| transition: all 0.25s; | |
| } | |
| .upload-zone:hover, | |
| .upload-zone.dragover { | |
| border-color: var(--primary); | |
| background: var(--bg3); | |
| transform: translateY(-2px); | |
| } | |
| .upload-zone-icon { | |
| width: 52px; | |
| height: 52px; | |
| margin: 0 auto 16px; | |
| display: block; | |
| stroke: var(--primary); | |
| stroke-width: 1.5; | |
| fill: none; | |
| stroke-linecap: round; | |
| stroke-linejoin: round; | |
| } | |
| .upload-zone h2 { | |
| font-size: 20px; | |
| font-weight: 700; | |
| color: var(--text); | |
| margin-bottom: 8px; | |
| } | |
| .upload-zone p { | |
| font-size: 14px; | |
| color: var(--subtext); | |
| margin-bottom: 24px; | |
| } | |
| .btn-upload { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 8px; | |
| background: var(--gradient); | |
| color: #fff; | |
| padding: 13px 28px; | |
| border-radius: 10px; | |
| border: none; | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 14px; | |
| font-weight: 700; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| box-shadow: var(--shadow); | |
| } | |
| .btn-upload:hover { opacity: 0.9; transform: translateY(-1px); } | |
| #fileInput { display: none; } | |
| /* βββ LOADING βββββββββββββββββββββββββββββββββββββββ */ | |
| .loading { | |
| display: none; | |
| text-align: center; | |
| padding: 60px 20px; | |
| } | |
| .loading.active { display: block; } | |
| .spinner { | |
| width: 52px; | |
| height: 52px; | |
| border: 3px solid var(--border); | |
| border-top-color: var(--primary); | |
| border-radius: 50%; | |
| animation: spin 0.9s linear infinite; | |
| margin: 0 auto 20px; | |
| } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| .loading h3 { font-size: 20px; font-weight: 700; color: var(--text); margin-bottom: 8px; } | |
| .loading p { font-size: 14px; color: var(--subtext); } | |
| /* βββ RESULTS βββββββββββββββββββββββββββββββββββββββ */ | |
| .results { | |
| display: none; | |
| max-width: 1200px; | |
| margin: 0 auto 80px; | |
| padding: 0 60px; | |
| } | |
| .results.active { display: block; } | |
| .verdict-card { | |
| background: var(--card); | |
| border: 1px solid var(--border2); | |
| border-radius: 18px; | |
| padding: 40px; | |
| margin-bottom: 20px; | |
| text-align: center; | |
| box-shadow: var(--shadow); | |
| } | |
| .verdict-card-label { | |
| font-size: 11px; | |
| font-weight: 600; | |
| letter-spacing: 0.15em; | |
| text-transform: uppercase; | |
| color: var(--subtext); | |
| margin-bottom: 8px; | |
| } | |
| .evidence-tag { | |
| font-family: 'JetBrains Mono', monospace; | |
| font-size: 11px; | |
| color: var(--subtext); | |
| margin-bottom: 16px; | |
| word-break: break-all; | |
| } | |
| .probability-number { | |
| font-size: 80px; | |
| font-weight: 700; | |
| line-height: 1; | |
| margin-bottom: 10px; | |
| letter-spacing: -0.04em; | |
| } | |
| .prob-ai { color: var(--red); } | |
| .prob-maybe { color: var(--orange); } | |
| .prob-human { color: var(--green); } | |
| .classification-badge { | |
| display: inline-block; | |
| padding: 6px 20px; | |
| border-radius: 20px; | |
| font-size: 13px; | |
| font-weight: 700; | |
| letter-spacing: 0.05em; | |
| text-transform: uppercase; | |
| margin-bottom: 10px; | |
| } | |
| .badge-ai { background: rgba(220,38,38,0.1); color: var(--red); border: 1px solid rgba(220,38,38,0.2); } | |
| .badge-maybe { background: rgba(234,88,12,0.1); color: var(--orange); border: 1px solid rgba(234,88,12,0.2); } | |
| .badge-human { background: rgba(22,163,74,0.1); color: var(--green); border: 1px solid rgba(22,163,74,0.2); } | |
| .methods-used { font-size: 13px; color: var(--subtext); margin-bottom: 16px; } | |
| .confidence-row { | |
| display: flex; | |
| justify-content: center; | |
| gap: 40px; | |
| flex-wrap: wrap; | |
| margin-top: 12px; | |
| } | |
| .confidence-item { text-align: center; } | |
| .confidence-value { font-size: 22px; font-weight: 700; color: var(--primary); } | |
| .confidence-label { font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--subtext); margin-top: 4px; } | |
| .grid-2 { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 18px; | |
| margin-bottom: 18px; | |
| } | |
| .result-card { | |
| background: var(--card); | |
| border: 1px solid var(--border); | |
| border-radius: 14px; | |
| padding: 24px; | |
| box-shadow: var(--shadow); | |
| } | |
| .result-card h3 { | |
| font-size: 11px; | |
| font-weight: 700; | |
| letter-spacing: 0.12em; | |
| text-transform: uppercase; | |
| color: var(--primary); | |
| margin-bottom: 16px; | |
| } | |
| .info-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 8px 0; | |
| border-bottom: 1px solid var(--border); | |
| font-size: 13px; | |
| } | |
| .info-row:last-child { border-bottom: none; } | |
| .info-label { color: var(--subtext); } | |
| .info-value { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--text); } | |
| .hash-item { margin-bottom: 12px; } | |
| .hash-label { font-size: 10px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--primary); margin-bottom: 4px; } | |
| .hash-value { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: var(--text); word-break: break-all; background: var(--bg3); padding: 6px 10px; border-radius: 6px; } | |
| .flag-item { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 13px; color: var(--text); } | |
| .flag-item:last-child { border-bottom: none; } | |
| .flag-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--orange); flex-shrink: 0; } | |
| .no-flags { font-size: 13px; color: var(--green); } | |
| .exif-empty { font-size: 13px; color: var(--subtext); font-style: italic; } | |
| .card-full { margin-bottom: 18px; } | |
| .signal-item { margin-bottom: 12px; break-inside: avoid; } | |
| .signal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; } | |
| .signal-name { font-size: 13px; font-weight: 500; color: var(--text); } | |
| .signal-score { font-size: 13px; font-weight: 700; } | |
| .score-high { color: var(--red); } | |
| .score-mid { color: var(--orange); } | |
| .score-low { color: var(--green); } | |
| .signal-bar { height: 5px; background: var(--bg3); border-radius: 4px; overflow: hidden; } | |
| .signal-fill { height: 100%; border-radius: 4px; transition: width 0.5s ease; } | |
| .fill-high { background: linear-gradient(90deg, var(--orange), var(--red)); } | |
| .fill-mid { background: linear-gradient(90deg, var(--yellow), var(--orange)); } | |
| .fill-low { background: linear-gradient(90deg, var(--green), #34d399); } | |
| .analyze-again { | |
| text-align: center; | |
| margin-top: 28px; | |
| display: flex; | |
| justify-content: center; | |
| gap: 14px; | |
| flex-wrap: wrap; | |
| } | |
| .btn-primary { | |
| display: inline-flex; | |
| align-items: center; | |
| background: var(--gradient); | |
| color: #fff; | |
| padding: 13px 28px; | |
| border-radius: 10px; | |
| border: none; | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 14px; | |
| font-weight: 700; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| box-shadow: var(--shadow); | |
| } | |
| .btn-primary:hover { opacity: 0.9; transform: translateY(-1px); } | |
| .btn-secondary { | |
| display: inline-flex; | |
| align-items: center; | |
| background: var(--card); | |
| color: var(--text); | |
| padding: 13px 28px; | |
| border-radius: 10px; | |
| border: 1px solid var(--border2); | |
| font-family: 'Space Grotesk', sans-serif; | |
| font-size: 14px; | |
| font-weight: 700; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| box-shadow: var(--shadow); | |
| } | |
| .btn-secondary:hover { background: var(--bg3); transform: translateY(-1px); } | |
| /* βββ FOOTER ββββββββββββββββββββββββββββββββββββββββ */ | |
| footer { | |
| background: var(--card); | |
| border-top: 1px solid var(--border); | |
| margin-top: 40px; | |
| } | |
| .footer-inner { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 52px 60px 36px; | |
| display: grid; | |
| grid-template-columns: 1.6fr 1fr 1fr 1fr; | |
| gap: 48px; | |
| } | |
| .footer-brand h3 { | |
| font-size: 20px; | |
| font-weight: 700; | |
| color: var(--primary); | |
| letter-spacing: 1px; | |
| margin-bottom: 12px; | |
| } | |
| .footer-brand p { | |
| font-size: 14px; | |
| color: var(--subtext); | |
| line-height: 1.7; | |
| max-width: 280px; | |
| } | |
| .footer-col h4 { | |
| font-size: 13px; | |
| font-weight: 700; | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| color: var(--text); | |
| margin-bottom: 16px; | |
| } | |
| .footer-col ul { | |
| list-style: none; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .footer-col ul li a { | |
| font-size: 14px; | |
| color: var(--subtext); | |
| text-decoration: none; | |
| transition: color 0.2s; | |
| } | |
| .footer-col ul li a:hover { color: var(--primary); } | |
| .footer-bottom { | |
| max-width: 1200px; | |
| margin: 0 auto; | |
| padding: 18px 60px; | |
| border-top: 1px solid var(--border); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-size: 13px; | |
| color: var(--subtext); | |
| } | |
| .footer-badges { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .footer-badge { | |
| padding: 4px 12px; | |
| border-radius: 20px; | |
| border: 1px solid var(--border); | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: var(--primary); | |
| background: var(--bg3); | |
| letter-spacing: 0.05em; | |
| } | |
| /* βββ RESPONSIVE ββββββββββββββββββββββββββββββββββββ */ | |
| @media (max-width: 900px) { | |
| nav { padding: 16px 20px; } | |
| .hero { padding: 40px 20px; } | |
| .hero-inner { flex-direction: column; padding: 40px 30px; gap: 32px; text-align: center; } | |
| .hero-logo-img { width: 220px; } | |
| .hero-text-col h1 { font-size: 32px; } | |
| .stats-bar { padding: 0 20px; } | |
| .stats-inner { grid-template-columns: 1fr; } | |
| .stat-item { border-right: none; border-bottom: 1px solid var(--border); } | |
| .stat-item:last-child { border-bottom: none; } | |
| .section { padding: 60px 20px; } | |
| .features-grid { grid-template-columns: 1fr; } | |
| .how-grid { grid-template-columns: 1fr 1fr; } | |
| .divider { margin: 0 20px; } | |
| .upload-section { padding: 60px 20px; } | |
| .results { padding: 0 20px; } | |
| .grid-2 { grid-template-columns: 1fr; } | |
| .footer-inner { grid-template-columns: 1fr; gap: 32px; padding: 40px 20px; } | |
| .footer-bottom { flex-direction: column; gap: 12px; padding: 18px 20px; } | |
| [data-theme="animated"] .hero-text-col h1 { font-size: 38px; } | |
| } | |
| @media (max-width: 600px) { | |
| .how-grid { grid-template-columns: 1fr; } | |
| .theme-btn { font-size: 11px; padding: 5px 10px; } | |
| } | |
| </style> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> | |
| </head> | |
| <body> | |
| <canvas id="bg-canvas"></canvas> | |
| <div class="page"> | |
| <!-- NAV --> | |
| <nav> | |
| <a href="#" class="nav-logo-text">VeriFile-X</a> | |
| <div class="nav-right"> | |
| <div class="live-indicator"> | |
| <div class="live-dot"></div> | |
| System Active | |
| </div> | |
| <div class="theme-switcher"> | |
| <button class="theme-btn" id="btn-animated" onclick="setTheme('animated')">Animated</button> | |
| <button class="theme-btn" id="btn-dark" onclick="setTheme('dark')">Dark</button> | |
| <button class="theme-btn" id="btn-light" onclick="setTheme('light')">Light</button> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- HERO (light/dark: logo + text side by side | animated: text only centered) --> | |
| <section class="hero"> | |
| <div class="hero-inner"> | |
| <div class="hero-logo-col"> | |
| <img class="hero-logo-img" src="https://raw.githubusercontent.com/abinaze/VeriFile-X/main/frontend/logo2.png" onerror="this.style.display='none'" alt="VeriFile-X"> | |
| </div> | |
| <div class="hero-text-col"> | |
| <div class="hero-badge">DIRE + CLIP + Statistical Analysis</div> | |
| <h1>Unmask AI Content with Confidence</h1> | |
| <p>VeriFile-X is an advanced AI image verification platform that analyses any image using 26 independent forensic signals. Instantly determine whether an image is AI-generated or authentic β with cryptographic hashes, EXIF forensics, generator attribution, platform detection, and a downloadable evidence report.</p> | |
| <a class="hero-cta" href="#upload-section"> | |
| Start Analysis | |
| <svg class="hero-cta-arrow" viewBox="0 0 20 20"> | |
| <path d="M4 10h12M10 4l6 6-6 6"/> | |
| </svg> | |
| </a> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- STATS BAR --> | |
| <div class="stats-bar"> | |
| <div class="stats-inner"> | |
| <div class="stat-item"> | |
| <div class="stat-number">26</div> | |
| <div class="stat-label">Detection Signals</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-number">8</div> | |
| <div class="stat-label">AI Methods</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-number">85β92%</div> | |
| <div class="stat-label">Validated Accuracy</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- WHY VERIFILE-X --> | |
| <section class="section"> | |
| <div class="section-header"> | |
| <div class="section-label">Capabilities</div> | |
| <h2 class="section-title">Why VeriFile-X</h2> | |
| <p class="section-sub">Built for journalists, legal professionals, researchers, and anyone who needs to verify the authenticity of digital images.</p> | |
| </div> | |
| <div class="features-grid"> | |
| <div class="feature-card"> | |
| <div class="feature-icon"> | |
| <svg viewBox="0 0 24 24"> | |
| <circle cx="11" cy="11" r="7"/> | |
| <path d="M21 21l-4.35-4.35"/> | |
| <path d="M11 8v6M8 11h6"/> | |
| </svg> | |
| </div> | |
| <h3>AI Detection</h3> | |
| <p>Detect AI-generated content using 26 independent signals spanning deep learning, frequency analysis, EXIF forensics, and statistical modelling. Each signal explains its reasoning.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <div class="feature-icon"> | |
| <svg viewBox="0 0 24 24"> | |
| <rect x="3" y="11" width="18" height="11" rx="2"/> | |
| <path d="M7 11V7a5 5 0 0 1 10 0v4"/> | |
| </svg> | |
| </div> | |
| <h3>Secure Verification</h3> | |
| <p>Every image is analysed with SHA-256 and MD5 cryptographic hashing, perceptual fingerprinting, and tamper-indicator detection β producing a full forensic evidence report.</p> | |
| </div> | |
| <div class="feature-card"> | |
| <div class="feature-icon"> | |
| <svg viewBox="0 0 24 24"> | |
| <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/> | |
| </svg> | |
| </div> | |
| <h3>Fast Processing</h3> | |
| <p>Results in seconds. All processing is done in-memory β your image is never stored to disk. Download a full PDF forensic report with a single click after analysis.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <hr class="divider"> | |
| <!-- HOW IT WORKS --> | |
| <section class="section"> | |
| <div class="section-header"> | |
| <div class="section-label">Process</div> | |
| <h2 class="section-title">How It Works</h2> | |
| <p class="section-sub">Four steps from upload to forensic-grade evidence report.</p> | |
| </div> | |
| <div class="how-grid"> | |
| <div class="how-card"> | |
| <div class="how-step">01</div> | |
| <h4>Upload Image</h4> | |
| <p>Drag and drop or select a JPEG, PNG, or WebP file up to 10 MB.</p> | |
| </div> | |
| <div class="how-card"> | |
| <div class="how-step">02</div> | |
| <h4>Forensic Analysis</h4> | |
| <p>26 detection signals run in parallel across 8 independent methods.</p> | |
| </div> | |
| <div class="how-card"> | |
| <div class="how-step">03</div> | |
| <h4>Verdict Delivered</h4> | |
| <p>An AI generation probability score and classification are returned with full signal breakdown.</p> | |
| </div> | |
| <div class="how-card"> | |
| <div class="how-step">04</div> | |
| <h4>Download Report</h4> | |
| <p>Export a complete PDF forensic report including hashes, EXIF data, and all signal results.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <hr class="divider"> | |
| <!-- UPLOAD --> | |
| <div id="upload-section" class="upload-section"> | |
| <div class="upload-section-header"> | |
| <div class="section-label">Analysis Engine</div> | |
| <h2 class="section-title">Analyse an Image</h2> | |
| <p class="section-sub" style="margin-top:12px;">Drop any image below. Results appear within seconds.</p> | |
| </div> | |
| <div class="upload-zone" id="uploadZone" onclick="document.getElementById('fileInput').click()"> | |
| <svg class="upload-zone-icon" viewBox="0 0 52 52"> | |
| <path d="M26 36V16M16 26l10-10 10 10"/> | |
| <rect x="6" y="6" width="40" height="40" rx="10"/> | |
| <path d="M14 40h24"/> | |
| </svg> | |
| <h2>Drop image here or click to upload</h2> | |
| <p>JPEG, PNG, WebP • Max 10 MB</p> | |
| <button class="btn-upload" onclick="event.stopPropagation(); document.getElementById('fileInput').click()"> | |
| Select Image | |
| </button> | |
| <input type="file" id="fileInput" accept="image/jpeg,image/png,image/webp"> | |
| </div> | |
| </div> | |
| <!-- LOADING --> | |
| <div class="loading" id="loading"> | |
| <div class="spinner"></div> | |
| <h3>Analysing with 26 signals</h3> | |
| <p>Running DIRE reconstruction, CLIP embeddings, and statistical analysis</p> | |
| </div> | |
| <!-- RESULTS --> | |
| <div class="results" id="results"> | |
| <div class="verdict-card"> | |
| <div class="verdict-card-label">AI Generation Probability</div> | |
| <p class="evidence-tag" id="evidenceId">Evidence ID: β</p> | |
| <div class="probability-number" id="aiProb">0%</div> | |
| <div><span class="classification-badge" id="classificationBadge"></span></div> | |
| <p class="methods-used" id="methods"></p> | |
| <div class="confidence-row"> | |
| <div class="confidence-item"> | |
| <div class="confidence-value" id="suspiciousCount">β</div> | |
| <div class="confidence-label">Suspicious Signals</div> | |
| </div> | |
| <div class="confidence-item"> | |
| <div class="confidence-value" id="totalSignals">β</div> | |
| <div class="confidence-label">Total Signals</div> | |
| </div> | |
| <div class="confidence-item"> | |
| <div class="confidence-value" id="confidenceLevel">β</div> | |
| <div class="confidence-label">Confidence</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="grid-2"> | |
| <div class="result-card"> | |
| <h3>File Information</h3> | |
| <div id="fileInfo"></div> | |
| </div> | |
| <div class="result-card"> | |
| <h3>Cryptographic Hashes</h3> | |
| <div id="hashInfo"></div> | |
| </div> | |
| </div> | |
| <div class="grid-2"> | |
| <div class="result-card"> | |
| <h3>Tampering Indicators</h3> | |
| <div id="tamperingInfo"></div> | |
| </div> | |
| <div class="result-card"> | |
| <h3>EXIF Metadata</h3> | |
| <div id="exifInfo"></div> | |
| </div> | |
| </div> | |
| <div class="result-card card-full"> | |
| <h3>Detection Signals (sorted by AI likelihood)</h3> | |
| <div id="signalsGrid" style="columns: 2; column-gap: 2rem; margin-top: 8px;"></div> | |
| </div> | |
| <div class="analyze-again"> | |
| <button class="btn-primary" onclick="resetUI()">Analyse Another Image</button> | |
| <button class="btn-secondary" onclick="downloadPDF()">Download PDF Report</button> | |
| </div> | |
| <p style="text-align:center;font-size:12px;color:var(--subtext);margin-top:12px;"> | |
| Accuracy ~85-92% with validated CLIP database. Not intended as sole evidence in legal proceedings. | |
| </p> | |
| </div> | |
| <!-- FOOTER --> | |
| <footer> | |
| <div class="footer-inner"> | |
| <div class="footer-brand"> | |
| <h3>VeriFile-X</h3> | |
| <p>Advanced AI image verification platform. 26 forensic signals. Cryptographic evidence reports. Built for accuracy, transparency, and trust.</p> | |
| </div> | |
| <div class="footer-col"> | |
| <h4>Product</h4> | |
| <ul> | |
| <li><a href="#">Image Analysis</a></li> | |
| <li><a href="#">PDF Reports</a></li> | |
| <li><a href="#">Detection Signals</a></li> | |
| <li><a href="#">API Access</a></li> | |
| </ul> | |
| </div> | |
| <div class="footer-col"> | |
| <h4>Technology</h4> | |
| <ul> | |
| <li><a href="#">DIRE Detection</a></li> | |
| <li><a href="#">CLIP Embeddings</a></li> | |
| <li><a href="#">Statistical Analysis</a></li> | |
| <li><a href="#">EXIF Forensics</a></li> | |
| </ul> | |
| </div> | |
| <div class="footer-col"> | |
| <h4>Resources</h4> | |
| <ul> | |
| <li><a href="#">Documentation</a></li> | |
| <li><a href="#">GitHub</a></li> | |
| <li><a href="#">Security Policy</a></li> | |
| <li><a href="#">Contributing</a></li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="footer-bottom"> | |
| <span>Β© 2026 VeriFile-X β Built for Trust & Transparency</span> | |
| <div class="footer-badges"> | |
| <span class="footer-badge">26 Signals</span> | |
| <span class="footer-badge">Privacy First</span> | |
| <span class="footer-badge">Open Source</span> | |
| </div> | |
| </div> | |
| </footer> | |
| </div> | |
| <!-- THREE.JS --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script> | |
| var _threeReady = false; | |
| var _tScene, _tCamera, _tRenderer, _tCubes, _tNodes, _tParticles; | |
| var _tMouseX = 0, _tMouseY = 0; | |
| function initThree() { | |
| if (_threeReady) return; | |
| _threeReady = true; | |
| var canvas = document.getElementById('bg-canvas'); | |
| _tScene = new THREE.Scene(); | |
| _tCamera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000); | |
| _tCamera.position.z = 18; | |
| _tRenderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: true }); | |
| _tRenderer.setSize(innerWidth, innerHeight); | |
| _tRenderer.setPixelRatio(Math.min(devicePixelRatio, 2)); | |
| _tScene.add(new THREE.AmbientLight(0x00ffff, 0.6)); | |
| var pl = new THREE.PointLight(0x7a7aff, 2); | |
| pl.position.set(10, 10, 10); | |
| _tScene.add(pl); | |
| var grid = new THREE.GridHelper(200, 60, 0x00ffee, 0x003344); | |
| grid.position.y = -8; | |
| _tScene.add(grid); | |
| _tCubes = []; | |
| for (var i = 0; i < 40; i++) { | |
| var c = new THREE.Mesh( | |
| new THREE.BoxGeometry(1.4, 1.4, 0.2), | |
| new THREE.MeshStandardMaterial({ color: 0x00ffee, emissive: 0x002222, metalness: 0.7, roughness: 0.2 }) | |
| ); | |
| c.position.x = (Math.random() - 0.5) * 40; | |
| c.position.y = (Math.random() - 0.5) * 20; | |
| c.position.z = (Math.random() - 0.5) * 30; | |
| c.rs = Math.random() * 0.02; | |
| _tScene.add(c); | |
| _tCubes.push(c); | |
| } | |
| _tNodes = []; | |
| for (var j = 0; j < 25; j++) { | |
| var n = new THREE.Mesh( | |
| new THREE.SphereGeometry(0.35, 32, 32), | |
| new THREE.MeshStandardMaterial({ color: 0x7a7aff, emissive: 0x2222ff }) | |
| ); | |
| n.position.x = (Math.random() - 0.5) * 30; | |
| n.position.y = (Math.random() - 0.5) * 15; | |
| n.position.z = (Math.random() - 0.5) * 30; | |
| n.vel = Math.random() * 0.01; | |
| _tScene.add(n); | |
| _tNodes.push(n); | |
| } | |
| var pg = new THREE.BufferGeometry(); | |
| var pa = new Float32Array(1200 * 3); | |
| for (var k = 0; k < 3600; k++) pa[k] = (Math.random() - 0.5) * 80; | |
| pg.setAttribute('position', new THREE.BufferAttribute(pa, 3)); | |
| _tParticles = new THREE.Points(pg, new THREE.PointsMaterial({ size: 0.05, color: 0x00ffff })); | |
| _tScene.add(_tParticles); | |
| document.addEventListener('mousemove', function(e) { | |
| _tMouseX = (e.clientX / innerWidth - 0.5) * 2; | |
| _tMouseY = (e.clientY / innerHeight - 0.5) * 2; | |
| }); | |
| window.addEventListener('resize', function() { | |
| _tCamera.aspect = innerWidth / innerHeight; | |
| _tCamera.updateProjectionMatrix(); | |
| _tRenderer.setSize(innerWidth, innerHeight); | |
| }); | |
| (function loop() { | |
| requestAnimationFrame(loop); | |
| var t = Date.now(); | |
| _tCubes.forEach(function(c) { | |
| c.rotation.x += c.rs; | |
| c.rotation.y += c.rs; | |
| c.position.y += Math.sin(t * 0.001 + c.position.x) * 0.002; | |
| }); | |
| _tNodes.forEach(function(n) { n.position.y += Math.sin(t * 0.002) * n.vel; }); | |
| _tParticles.rotation.y += 0.0008; | |
| _tCamera.position.x += (_tMouseX * 3 - _tCamera.position.x) * 0.05; | |
| _tCamera.position.y += (-_tMouseY * 2 - _tCamera.position.y) * 0.05; | |
| _tCamera.lookAt(_tScene.position); | |
| _tRenderer.render(_tScene, _tCamera); | |
| })(); | |
| } | |
| function setTheme(theme) { | |
| document.documentElement.setAttribute('data-theme', theme); | |
| ['animated','dark','light'].forEach(function(t) { | |
| document.getElementById('btn-' + t).classList.toggle('active', t === theme); | |
| }); | |
| localStorage.setItem('vfx-theme', theme); | |
| if (theme === 'animated') initThree(); | |
| } | |
| (function() { | |
| var saved = localStorage.getItem('vfx-theme') || 'animated'; | |
| setTheme(saved); | |
| })(); | |
| /* ββ DETECTION LOGIC βββββββββββββββββββββββββββββββ */ | |
| const API_URL = window.location.hostname.includes('github.io') || window.location.hostname.includes('abinaze') | |
| ? 'https://abinazebinoy-verifile-x-api.hf.space' | |
| : window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' | |
| ? 'http://localhost:8000' | |
| : 'https://abinazebinoy-verifile-x-api.hf.space'; | |
| const fileInput = document.getElementById('fileInput'); | |
| const uploadZone = document.getElementById('uploadZone'); | |
| const loading = document.getElementById('loading'); | |
| const results = document.getElementById('results'); | |
| uploadZone.addEventListener('dragover', (e) => { e.preventDefault(); uploadZone.classList.add('dragover'); }); | |
| uploadZone.addEventListener('dragleave', () => uploadZone.classList.remove('dragover')); | |
| uploadZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| uploadZone.classList.remove('dragover'); | |
| const file = e.dataTransfer.files[0]; | |
| if (file) analyzeFile(file); | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| if (e.target.files[0]) analyzeFile(e.target.files[0]); | |
| }); | |
| function resetUI() { | |
| results.classList.remove('active'); | |
| uploadZone.style.display = 'block'; | |
| fileInput.value = ''; | |
| document.getElementById('upload-section').scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| async function analyzeFile(file) { | |
| uploadZone.style.display = 'none'; | |
| loading.classList.add('active'); | |
| results.classList.remove('active'); | |
| const formData = new FormData(); | |
| formData.append('file', file); | |
| // Client-side validation | |
| if (file.size > 10 * 1024 * 1024) { alert('File too large. Maximum 10MB.'); uploadZone.style.display = 'block'; loading.classList.remove('active'); return; } | |
| const allowed = ['image/jpeg', 'image/png', 'image/webp']; | |
| if (!allowed.includes(file.type)) { alert('Unsupported type. Use JPEG, PNG or WebP.'); uploadZone.style.display = 'block'; loading.classList.remove('active'); return; } | |
| try { | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), 30000); | |
| const response = await fetch(`${API_URL}/api/v1/analyze/image`, { | |
| signal: controller.signal, method: 'POST', body: formData | |
| }); | |
| clearTimeout(timeoutId); | |
| if (!response.ok) { | |
| const err = await response.json().catch(() => ({})); | |
| throw new Error(err.detail || `Server error ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| window._lastReport = data; | |
| renderResults(data); | |
| loading.classList.remove('active'); | |
| results.classList.add('active'); | |
| } catch (error) { | |
| loading.classList.remove('active'); | |
| uploadZone.style.display = 'block'; | |
| if (error.name === 'AbortError') { | |
| alert('Analysis timed out (30s). Please try again.'); | |
| } else { | |
| alert('Analysis failed: ' + error.message); | |
| } | |
| } | |
| } | |
| function renderResults(data) { | |
| const ai = data.ai_detection; | |
| const prob = Math.round(ai.ai_probability * 100); | |
| const probEl = document.getElementById('aiProb'); | |
| document.getElementById('evidenceId').textContent = 'Evidence ID: ' + (data.evidence_id || 'N/A'); | |
| probEl.textContent = prob + '%'; | |
| probEl.className = 'probability-number ' + (prob >= 70 ? 'prob-ai' : prob >= 40 ? 'prob-maybe' : 'prob-human'); | |
| const badge = document.getElementById('classificationBadge'); | |
| const cls = ai.classification.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); | |
| badge.textContent = cls; | |
| badge.className = 'classification-badge ' + (prob >= 70 ? 'badge-ai' : prob >= 40 ? 'badge-maybe' : 'badge-human'); | |
| document.getElementById('methods').textContent = 'Detection methods: ' + ai.methods_used.join(', '); | |
| document.getElementById('suspiciousCount').textContent = ai.suspicious_signals_count + '/' + ai.total_signals; | |
| document.getElementById('totalSignals').textContent = ai.total_signals; | |
| document.getElementById('confidenceLevel').textContent = (ai.confidence || 'N/A').replace(/_/g, ' '); | |
| const fi = data.file_info; | |
| document.getElementById('fileInfo').innerHTML = ` | |
| <div class="info-row"><span class="info-label">Filename</span><span class="info-value">${fi.filename}</span></div> | |
| <div class="info-row"><span class="info-label">Format</span><span class="info-value">${fi.format || 'Unknown'}</span></div> | |
| <div class="info-row"><span class="info-label">Dimensions</span><span class="info-value">${fi.width} x ${fi.height}px</span></div> | |
| <div class="info-row"><span class="info-label">Mode</span><span class="info-value">${fi.mode}</span></div> | |
| <div class="info-row"><span class="info-label">File Size</span><span class="info-value">${(fi.file_size_bytes / 1024).toFixed(1)} KB</span></div> | |
| <div class="info-row"><span class="info-label">Analyzer Version</span><span class="info-value">${data.metadata.analyzer_version}</span></div> | |
| `; | |
| const h = data.hashes; | |
| document.getElementById('hashInfo').innerHTML = ` | |
| <div class="hash-item"><div class="hash-label">SHA-256</div><div class="hash-value">${h.sha256}</div></div> | |
| <div class="hash-item"><div class="hash-label">MD5</div><div class="hash-value">${h.md5}</div></div> | |
| <div class="hash-item"><div class="hash-label">Perceptual Hash</div><div class="hash-value">${h.perceptual_hash}</div></div> | |
| <div class="hash-item"><div class="hash-label">Average Hash</div><div class="hash-value">${h.average_hash}</div></div> | |
| <div class="hash-item"><div class="hash-label">Difference Hash</div><div class="hash-value">${h.difference_hash}</div></div> | |
| `; | |
| const t = data.tampering_analysis; | |
| const flags = t.suspicious_flags || []; | |
| document.getElementById('tamperingInfo').innerHTML = flags.length === 0 | |
| ? `<div class="no-flags">No tampering indicators found</div> | |
| <div class="info-row" style="margin-top:12px"><span class="info-label">Authenticity Confidence</span><span class="info-value" style="color:var(--green)">${t.confidence}</span></div>` | |
| : flags.map(f => `<div class="flag-item"><div class="flag-dot"></div>${f}</div>`).join('') | |
| + `<div class="info-row" style="margin-top:8px"><span class="info-label">Confidence</span><span class="info-value" style="color:var(--orange)">${t.confidence}</span></div>`; | |
| const exif = data.exif_data || {}; | |
| if (!exif.has_exif || Object.keys(exif).length <= 1) { | |
| document.getElementById('exifInfo').innerHTML = '<p class="exif-empty">No EXIF metadata found in this image.</p>'; | |
| } else { | |
| const skip = ['has_exif', 'gps']; | |
| const rows = Object.entries(exif).filter(([k]) => !skip.includes(k)).slice(0, 10) | |
| .map(([k, v]) => `<div class="info-row"><span class="info-label">${k}</span><span class="info-value" style="max-width:55%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${v}</span></div>`).join(''); | |
| document.getElementById('exifInfo').innerHTML = rows || '<p class="exif-empty">No readable EXIF fields.</p>'; | |
| } | |
| const signals = (ai.all_signals || []).sort((a, b) => b.score - a.score); | |
| document.getElementById('signalsGrid').innerHTML = signals.map(s => { | |
| const pct = Math.round(s.score * 100); | |
| const cls = pct >= 70 ? 'score-high fill-high' : pct >= 40 ? 'score-mid fill-mid' : 'score-low fill-low'; | |
| const [scoreCls, fillCls] = cls.split(' '); | |
| return `<div class="signal-item"> | |
| <div class="signal-header"> | |
| <span class="signal-name">${s.signal_name || s.name || 'Signal'}</span> | |
| <span class="signal-score ${scoreCls}">${pct}%</span> | |
| </div> | |
| <div class="signal-bar"><div class="signal-fill ${fillCls}" style="width:${pct}%"></div></div> | |
| </div>`; | |
| }).join(''); | |
| } | |
| function downloadPDF() { | |
| const { jsPDF } = window.jspdf; | |
| const doc = new jsPDF(); | |
| const data = window._lastReport; | |
| if (!data) { alert('No report to download'); return; } | |
| const ai = data.ai_detection; | |
| const prob = Math.round(ai.ai_probability * 100); | |
| const ts = data.metadata.analysis_timestamp; | |
| doc.setFillColor(10, 22, 40); | |
| doc.rect(0, 0, 210, 30, 'F'); | |
| doc.setTextColor(255, 255, 255); | |
| doc.setFontSize(18); | |
| doc.setFont('helvetica', 'bold'); | |
| // Guard against missing fields | |
| const safeGet = (obj, path, fallback = 'N/A') => { | |
| try { | |
| return path.split('.').reduce((o, k) => o[k], obj) ?? fallback; | |
| } catch { return fallback; } | |
| }; | |
| doc.text('VeriFile-X Forensic Analysis Report', 105, 14, { align: 'center' }); | |
| doc.setFontSize(9); | |
| doc.text('AI-Generated Image Detection Platform v6.6.0', 105, 22, { align: 'center' }); | |
| doc.setTextColor(0, 0, 0); | |
| let y = 38; | |
| doc.setFontSize(9); | |
| doc.setFont('helvetica', 'normal'); | |
| doc.setTextColor(100, 100, 100); | |
| doc.text('Evidence ID:', 14, y); | |
| doc.setTextColor(0, 0, 0); | |
| doc.setFont('courier', 'normal'); | |
| doc.text(data.evidence_id || 'N/A', 14, y + 5); | |
| doc.setFont('helvetica', 'normal'); | |
| doc.text('Analysis Timestamp: ' + ts, 14, y + 12); | |
| doc.text('File: ' + data.file_info.filename + ' | ' + data.file_info.width + 'x' + data.file_info.height + 'px | ' + (data.file_info.file_size_bytes / 1024).toFixed(1) + ' KB', 14, y + 19); | |
| y += 30; | |
| const verdictColor = prob >= 70 ? [220, 38, 38] : prob >= 40 ? [234, 88, 12] : [16, 185, 129]; | |
| doc.setFillColor(...verdictColor); | |
| doc.roundedRect(14, y, 182, 22, 3, 3, 'F'); | |
| doc.setTextColor(255, 255, 255); | |
| doc.setFontSize(14); | |
| doc.setFont('helvetica', 'bold'); | |
| doc.text('VERDICT: ' + ai.classification.replace(/_/g, ' ').toUpperCase() + ' β ' + prob + '%', 105, y + 10, { align: 'center' }); | |
| doc.setFontSize(9); | |
| doc.text('Confidence: ' + (ai.confidence || 'N/A') + ' | Methods: ' + ai.methods_used.join(', ') + ' | Signals: ' + ai.suspicious_signals_count + '/' + ai.total_signals, 105, y + 17, { align: 'center' }); | |
| y += 30; | |
| doc.setTextColor(0, 0, 0); | |
| doc.setFontSize(11); | |
| doc.setFont('helvetica', 'bold'); | |
| doc.text('Cryptographic Hashes', 14, y); | |
| y += 6; | |
| doc.setFontSize(8); | |
| doc.setFont('courier', 'normal'); | |
| const h = data.hashes; | |
| doc.text('SHA-256: ' + h.sha256, 14, y); y += 5; | |
| doc.text('MD5: ' + h.md5, 14, y); y += 5; | |
| doc.text('pHash: ' + h.perceptual_hash + ' aHash: ' + h.average_hash + ' dHash: ' + h.difference_hash, 14, y); y += 10; | |
| doc.setFont('helvetica', 'bold'); | |
| doc.setFontSize(11); | |
| doc.text('Tampering Analysis', 14, y); y += 6; | |
| doc.setFontSize(9); | |
| doc.setFont('helvetica', 'normal'); | |
| const flags = data.tampering_analysis.suspicious_flags; | |
| if (flags.length === 0) { | |
| doc.setTextColor(16, 185, 129); | |
| doc.text('No tampering indicators detected. Authenticity confidence: ' + data.tampering_analysis.confidence, 14, y); | |
| } else { | |
| doc.setTextColor(220, 38, 38); | |
| flags.forEach(f => { doc.text('- ' + f, 14, y); y += 5; }); | |
| } | |
| doc.setTextColor(0, 0, 0); | |
| y += 8; | |
| doc.setFont('helvetica', 'bold'); | |
| doc.setFontSize(11); | |
| doc.text('Detection Signals (' + ai.total_signals + ' total)', 14, y); y += 6; | |
| doc.setFontSize(8); | |
| const signals = (ai.all_signals || []).sort((a, b) => b.score - a.score); | |
| signals.forEach((s) => { | |
| if (y > 270) { doc.addPage(); y = 20; } | |
| const pct = Math.round(s.score * 100); | |
| const col = pct >= 70 ? [220, 38, 38] : pct >= 40 ? [234, 88, 12] : [16, 185, 129]; | |
| doc.setFont('helvetica', 'normal'); | |
| doc.setTextColor(0, 0, 0); | |
| doc.text((s.signal_name || s.name || 'Signal'), 14, y); | |
| doc.setTextColor(...col); | |
| doc.text(pct + '%', 195, y, { align: 'right' }); | |
| doc.setTextColor(0, 0, 0); | |
| y += 5; | |
| }); | |
| const pageCount = doc.internal.getNumberOfPages(); | |
| for (let i = 1; i <= pageCount; i++) { | |
| doc.setPage(i); | |
| doc.setFontSize(8); | |
| doc.setTextColor(150, 150, 150); | |
| doc.text('VeriFile-X | Evidence ID: ' + (data.evidence_id || 'N/A') + ' | Page ' + i + ' of ' + pageCount, 105, 290, { align: 'center' }); | |
| } | |
| doc.save('VeriFileX-Report-' + (data.evidence_id || 'report').substring(0, 8) + '.pdf'); | |
| } | |
| </script> | |
| </body> | |
| </html> | |