| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, user-scalable=yes"> |
| <meta name="apple-mobile-web-app-capable" content="yes"> |
| <meta name="mobile-web-app-capable" content="yes"> |
| <title>TextAuth Forensics — Evidence-Based Text Authenticity Analysis</title> |
| <style> |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| -webkit-tap-highlight-color: transparent; |
| -webkit-font-smoothing: antialiased; |
| -moz-osx-font-smoothing: grayscale; |
| } |
| |
| :root { |
| --primary: #06b6d4; |
| --primary-dark: #0891b2; |
| --secondary: #3b82f6; |
| --success: #10b981; |
| --warning: #f59e0b; |
| --danger: #ef4444; |
| --bg-dark: #0f172a; |
| --bg-darker: #020617; |
| --bg-panel: rgba(30, 41, 59, 0.95); |
| --text-primary: #f1f5f9; |
| --text-secondary: #94a3b8; |
| --text-muted: #64748b; |
| --border: rgba(71, 85, 105, 0.5); |
| } |
| |
| html { |
| font-size: 16px; |
| scroll-behavior: smooth; |
| -webkit-text-size-adjust: 100%; |
| text-size-adjust: 100%; |
| } |
| |
| body { |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; |
| background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%); |
| color: var(--text-primary); |
| line-height: 1.6; |
| min-height: 100vh; |
| overflow-x: hidden; |
| position: relative; |
| width: 100%; |
| } |
| |
| |
| body, .landing-page, .analysis-interface { |
| max-width: 100vw; |
| overflow-x: hidden; |
| } |
| |
| |
| h1, h2, h3, h4 { |
| font-weight: 600; |
| line-height: 1.3; |
| word-wrap: break-word; |
| overflow-wrap: break-word; |
| } |
| |
| p, span, div { |
| word-wrap: break-word; |
| overflow-wrap: break-word; |
| } |
| |
| |
| .header { |
| background: rgba(15, 23, 42, 0.98); |
| backdrop-filter: blur(10px); |
| padding: 0.75rem 1rem; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| border-bottom: 1px solid var(--border); |
| position: sticky; |
| top: 0; |
| z-index: 1000; |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
| min-height: 60px; |
| } |
| |
| .logo { |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| font-size: 1.1rem; |
| font-weight: 700; |
| color: #fff; |
| text-decoration: none; |
| flex-shrink: 1; |
| min-width: 0; |
| } |
| |
| .logo-icon { |
| width: 32px; |
| height: 32px; |
| min-width: 32px; |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); |
| border-radius: 8px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 1.1rem; |
| box-shadow: 0 4px 12px rgba(6, 182, 212, 0.3); |
| } |
| |
| .logo span { |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .nav-links { |
| display: flex; |
| gap: 1rem; |
| align-items: center; |
| } |
| |
| .nav-link { |
| color: var(--text-secondary); |
| text-decoration: none; |
| font-weight: 500; |
| transition: color 0.3s; |
| cursor: pointer; |
| font-size: 0.9rem; |
| white-space: nowrap; |
| padding: 0.5rem 0.25rem; |
| } |
| |
| .nav-link:hover { |
| color: var(--primary); |
| } |
| |
| .try-btn { |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); |
| color: #fff; |
| padding: 0.625rem 1.25rem; |
| border-radius: 8px; |
| font-weight: 600; |
| border: none; |
| cursor: pointer; |
| transition: transform 0.3s, box-shadow 0.3s; |
| text-decoration: none; |
| display: inline-block; |
| font-size: 0.95rem; |
| white-space: nowrap; |
| touch-action: manipulation; |
| } |
| |
| .try-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 8px 20px rgba(6, 182, 212, 0.4); |
| } |
| |
| |
| .landing-page { |
| display: block; |
| width: 100%; |
| padding-bottom: 2rem; |
| } |
| |
| .hero { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 3rem 1rem 2rem; |
| text-align: center; |
| } |
| |
| .hero-title { |
| font-size: 2.25rem; |
| font-weight: 800; |
| margin-bottom: 1rem; |
| background: linear-gradient(135deg, #fff 0%, var(--primary) 100%); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| background-clip: text; |
| line-height: 1.2; |
| padding: 0 0.5rem; |
| } |
| |
| .hero-subtitle { |
| font-size: 1.1rem; |
| color: var(--text-secondary); |
| margin-bottom: 1rem; |
| padding: 0 0.5rem; |
| } |
| |
| .hero-description { |
| font-size: 1rem; |
| color: var(--text-muted); |
| max-width: 700px; |
| margin: 0 auto 2rem; |
| padding: 0 1rem; |
| } |
| |
| .accuracy-badge { |
| display: inline-block; |
| background: linear-gradient(135deg, rgba(16, 185, 129, 0.2) 0%, rgba(6, 182, 212, 0.2) 100%); |
| border: 2px solid var(--success); |
| padding: 0.75rem 1.5rem; |
| border-radius: 12px; |
| font-size: 1.1rem; |
| font-weight: 700; |
| color: var(--success); |
| margin-bottom: 2rem; |
| } |
| |
| .stats-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); |
| gap: 1rem; |
| max-width: 1000px; |
| margin: 3rem auto; |
| padding: 0 1rem; |
| } |
| |
| .stat-card { |
| background: var(--bg-panel); |
| padding: 1.5rem; |
| border-radius: 16px; |
| border: 1px solid var(--border); |
| text-align: center; |
| } |
| |
| .stat-value { |
| font-size: 2rem; |
| font-weight: 800; |
| color: var(--primary); |
| margin-bottom: 0.5rem; |
| } |
| |
| .stat-label { |
| color: var(--text-secondary); |
| font-size: 0.85rem; |
| line-height: 1.4; |
| } |
| |
| |
| .features-section { |
| max-width: 1200px; |
| margin: 4rem auto; |
| padding: 0 1rem; |
| } |
| |
| .section-title { |
| font-size: 1.75rem; |
| font-weight: 700; |
| text-align: center; |
| margin-bottom: 1rem; |
| padding: 0 0.5rem; |
| } |
| |
| .section-subtitle { |
| text-align: center; |
| color: var(--text-secondary); |
| font-size: 1rem; |
| margin-bottom: 3rem; |
| padding: 0 0.5rem; |
| } |
| |
| .features-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); |
| gap: 1.5rem; |
| } |
| |
| .feature-card { |
| background: var(--bg-panel); |
| padding: 2rem; |
| border-radius: 16px; |
| border: 1px solid var(--border); |
| transition: transform 0.3s, box-shadow 0.3s; |
| } |
| |
| .feature-card:hover { |
| transform: translateY(-5px); |
| box-shadow: 0 10px 30px rgba(6, 182, 212, 0.2); |
| } |
| |
| .feature-icon { |
| font-size: 2rem; |
| margin-bottom: 1rem; |
| } |
| |
| .feature-title { |
| font-size: 1.25rem; |
| font-weight: 700; |
| margin-bottom: 1rem; |
| color: #fff; |
| } |
| |
| .feature-description { |
| color: var(--text-secondary); |
| line-height: 1.6; |
| font-size: 0.95rem; |
| } |
| |
| |
| .metrics-info { |
| max-width: 1200px; |
| margin: 4rem auto; |
| padding: 0 1rem; |
| } |
| |
| .metric-card { |
| background: var(--bg-panel); |
| padding: 1.5rem; |
| border-radius: 12px; |
| border: 1px solid var(--border); |
| margin-bottom: 1rem; |
| display: grid; |
| grid-template-columns: auto 1fr; |
| gap: 1.5rem; |
| align-items: center; |
| } |
| |
| .metric-icon-box { |
| width: 60px; |
| height: 60px; |
| min-width: 60px; |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); |
| border-radius: 10px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 1.5rem; |
| } |
| |
| .metric-content h3 { |
| font-size: 1.1rem; |
| margin-bottom: 0.5rem; |
| color: #fff; |
| display: flex; |
| flex-wrap: wrap; |
| align-items: center; |
| gap: 0.5rem; |
| } |
| |
| .metric-weight { |
| display: inline-block; |
| background: rgba(6, 182, 212, 0.2); |
| padding: 0.25rem 0.5rem; |
| border-radius: 6px; |
| font-size: 0.8rem; |
| color: var(--primary); |
| font-weight: 600; |
| white-space: nowrap; |
| } |
| |
| .metric-content p { |
| color: var(--text-secondary); |
| font-size: 0.9rem; |
| line-height: 1.5; |
| } |
| |
| |
| .analysis-interface { |
| display: none; |
| max-width: 100%; |
| margin: 1rem auto; |
| padding: 0 1rem 1rem; |
| width: 100%; |
| } |
| |
| .interface-grid { |
| display: grid; |
| grid-template-columns: 1fr; |
| gap: 1.5rem; |
| align-items: start; |
| width: 100%; |
| } |
| |
| .panel { |
| background: var(--bg-panel); |
| border-radius: 16px; |
| padding: 1.5rem; |
| border: 1px solid var(--border); |
| backdrop-filter: blur(10px); |
| overflow: hidden; |
| display: flex; |
| flex-direction: column; |
| width: 100%; |
| min-height: 600px; |
| max-height: 90vh; |
| } |
| |
| .panel-content { |
| flex: 1; |
| overflow-y: auto; |
| padding: 0.5rem 0; |
| -webkit-overflow-scrolling: touch; |
| } |
| |
| .panel-title { |
| font-size: 1.25rem; |
| font-weight: 700; |
| margin-bottom: 1.25rem; |
| color: #fff; |
| } |
| |
| |
| .input-tabs { |
| display: flex; |
| gap: 0.75rem; |
| margin-bottom: 1.25rem; |
| flex-wrap: wrap; |
| } |
| |
| .input-tab { |
| flex: 1; |
| min-width: 140px; |
| padding: 0.75rem 0.5rem; |
| background: rgba(51, 65, 85, 0.6); |
| border: none; |
| border-radius: 8px; |
| color: var(--text-secondary); |
| cursor: pointer; |
| font-size: 0.9rem; |
| font-weight: 600; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 0.5rem; |
| transition: all 0.3s; |
| text-align: center; |
| touch-action: manipulation; |
| } |
| |
| .input-tab.active { |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); |
| color: #fff; |
| } |
| |
| .input-tab:hover:not(.active) { |
| background: rgba(71, 85, 105, 0.8); |
| } |
| |
| .tab-content { |
| display: none; |
| } |
| |
| .tab-content.active { |
| display: block; |
| } |
| |
| |
| .text-input { |
| width: 100%; |
| min-height: 300px; |
| max-height: 50vh; |
| padding: 1rem; |
| background: rgba(15, 23, 42, 0.8); |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| color: var(--text-primary); |
| font-size: 0.95rem; |
| line-height: 1.6; |
| resize: vertical; |
| font-family: inherit; |
| -webkit-appearance: none; |
| -moz-appearance: none; |
| appearance: none; |
| } |
| |
| .text-input::placeholder { |
| color: var(--text-muted); |
| } |
| |
| .text-input:focus { |
| outline: none; |
| border-color: var(--primary); |
| } |
| |
| |
| .file-upload-area { |
| border: 2px dashed var(--border); |
| border-radius: 8px; |
| padding: 2rem 1rem; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.3s; |
| background: rgba(15, 23, 42, 0.5); |
| touch-action: manipulation; |
| } |
| |
| .file-upload-area:hover { |
| border-color: var(--primary); |
| background: rgba(6, 182, 212, 0.05); |
| } |
| |
| .file-upload-area.drag-over { |
| border-color: var(--primary); |
| background: rgba(6, 182, 212, 0.1); |
| } |
| |
| .file-upload-icon { |
| font-size: 2.5rem; |
| margin-bottom: 0.75rem; |
| } |
| |
| .file-input { |
| display: none; |
| } |
| |
| .file-name-display { |
| margin-top: 1rem; |
| padding: 0.75rem; |
| background: rgba(6, 182, 212, 0.1); |
| border-radius: 6px; |
| color: var(--primary); |
| display: none; |
| font-size: 0.9rem; |
| word-break: break-all; |
| } |
| |
| |
| .options-section { |
| margin: 1.25rem 0; |
| padding: 1rem; |
| background: rgba(51, 65, 85, 0.3); |
| border-radius: 8px; |
| } |
| |
| .option-row { |
| display: flex; |
| flex-direction: column; |
| gap: 0.5rem; |
| margin-bottom: 1rem; |
| } |
| |
| .option-row:last-child { |
| margin-bottom: 0; |
| } |
| |
| .option-label { |
| font-size: 0.9rem; |
| color: var(--text-secondary); |
| flex: 1; |
| margin-bottom: 0.25rem; |
| } |
| |
| select { |
| background: rgba(15, 23, 42, 0.8); |
| border: 1px solid var(--border); |
| padding: 0.75rem; |
| border-radius: 6px; |
| color: var(--text-primary); |
| font-size: 0.9rem; |
| cursor: pointer; |
| width: 100%; |
| max-width: 100%; |
| -webkit-appearance: none; |
| -moz-appearance: none; |
| appearance: none; |
| background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); |
| background-repeat: no-repeat; |
| background-position: right 0.75rem center; |
| background-size: 1rem; |
| padding-right: 2.5rem; |
| } |
| |
| select:focus { |
| outline: none; |
| border-color: var(--primary); |
| } |
| |
| .checkbox-wrapper { |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| padding: 0.25rem 0; |
| } |
| |
| input[type="checkbox"] { |
| width: 20px; |
| height: 20px; |
| min-width: 20px; |
| cursor: pointer; |
| -webkit-appearance: none; |
| -moz-appearance: none; |
| appearance: none; |
| background: rgba(15, 23, 42, 0.8); |
| border: 1px solid var(--border); |
| border-radius: 4px; |
| position: relative; |
| } |
| |
| input[type="checkbox"]:checked { |
| background: var(--primary); |
| border-color: var(--primary); |
| } |
| |
| input[type="checkbox"]:checked::after { |
| content: "✓"; |
| position: absolute; |
| color: white; |
| font-size: 14px; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| } |
| |
| .checkbox-wrapper span { |
| font-size: 0.85rem; |
| color: var(--text-muted); |
| line-height: 1.4; |
| } |
| |
| |
| .analyze-btn { |
| width: 100%; |
| padding: 1rem; |
| margin-top: 1.25rem; |
| background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); |
| color: #fff; |
| border: none; |
| border-radius: 8px; |
| font-size: 1rem; |
| font-weight: 700; |
| cursor: pointer; |
| transition: all 0.3s; |
| touch-action: manipulation; |
| } |
| |
| .analyze-btn:hover:not(:disabled) { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 25px rgba(6, 182, 212, 0.3); |
| } |
| |
| .analyze-btn:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| transform: none; |
| } |
| |
| .action-buttons { |
| display: flex; |
| gap: 0.75rem; |
| margin-top: 1.25rem; |
| flex-wrap: wrap; |
| } |
| |
| .action-btn { |
| flex: 1; |
| min-width: 120px; |
| padding: 0.75rem; |
| background: rgba(51, 65, 85, 0.6); |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| color: var(--text-primary); |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.3s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 0.5rem; |
| font-size: 0.9rem; |
| touch-action: manipulation; |
| } |
| |
| .action-btn:hover { |
| background: var(--primary); |
| border-color: var(--primary); |
| transform: translateY(-2px); |
| } |
| |
| .action-btn.refresh { |
| background: rgba(245, 158, 11, 0.2); |
| border-color: var(--warning); |
| color: var(--warning); |
| } |
| |
| .action-btn.refresh:hover { |
| background: var(--warning); |
| color: var(--bg-darker); |
| } |
| |
| |
| .report-tabs { |
| display: flex; |
| gap: 0.5rem; |
| margin-bottom: 1.25rem; |
| border-bottom: 1px solid var(--border); |
| padding-bottom: 0.5rem; |
| overflow-x: auto; |
| -webkit-overflow-scrolling: touch; |
| scrollbar-width: none; |
| } |
| |
| .report-tabs::-webkit-scrollbar { |
| display: none; |
| } |
| |
| .report-tab { |
| padding: 0.75rem 0.5rem; |
| background: none; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| font-size: 0.9rem; |
| font-weight: 600; |
| border-bottom: 3px solid transparent; |
| transition: all 0.3s; |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| white-space: nowrap; |
| flex-shrink: 0; |
| touch-action: manipulation; |
| } |
| |
| .report-tab.active { |
| color: var(--primary); |
| border-bottom-color: var(--primary); |
| } |
| |
| .report-content { |
| display: none; |
| } |
| |
| .report-content.active { |
| display: block; |
| } |
| |
| |
| .empty-state { |
| text-align: center; |
| padding: 3rem 1rem; |
| } |
| |
| .empty-icon { |
| width: 60px; |
| height: 60px; |
| margin: 0 auto 1.25rem; |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); |
| border-radius: 50%; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 2rem; |
| } |
| |
| .empty-title { |
| font-size: 1.25rem; |
| font-weight: 700; |
| margin-bottom: 0.75rem; |
| color: #fff; |
| } |
| |
| .empty-description { |
| color: var(--text-secondary); |
| line-height: 1.6; |
| font-size: 0.95rem; |
| } |
| |
| |
| .loading { |
| text-align: center; |
| padding: 2rem 1rem; |
| } |
| |
| .spinner { |
| width: 50px; |
| height: 50px; |
| border: 4px solid rgba(71, 85, 105, 0.3); |
| border-top-color: var(--primary); |
| border-radius: 50%; |
| animation: spin 1s linear infinite; |
| margin: 0 auto 1rem; |
| } |
| |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| |
| |
| .result-summary { |
| text-align: center; |
| padding: 1.5rem 0; |
| } |
| |
| .gauge-container { |
| width: 220px; |
| height: 220px; |
| margin: 0 auto 1.5rem; |
| position: relative; |
| max-width: 100%; |
| } |
| |
| .gauge-circle { |
| width: 100%; |
| height: 100%; |
| border-radius: 50%; |
| background: conic-gradient(var(--gauge-color) 0deg, var(--gauge-color) var(--gauge-degree), rgba(51, 65, 85, 0.3) var(--gauge-degree)); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); |
| } |
| |
| .gauge-inner { |
| width: 170px; |
| height: 170px; |
| background: var(--bg-panel); |
| border-radius: 50%; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .gauge-value { |
| font-size: 2.5rem; |
| font-weight: 800; |
| color: var(--gauge-color); |
| } |
| |
| .gauge-label { |
| font-size: 0.85rem; |
| color: var(--text-secondary); |
| margin-top: 0.25rem; |
| } |
| |
| .result-info-grid { |
| display: grid; |
| grid-template-columns: 1fr; |
| gap: 1rem; |
| margin: 1.5rem 0; |
| } |
| |
| .info-card { |
| background: rgba(51, 65, 85, 0.3); |
| padding: 1.25rem; |
| border-radius: 10px; |
| border: 1px solid var(--border); |
| } |
| |
| .info-label { |
| font-size: 0.8rem; |
| color: var(--text-secondary); |
| margin-bottom: 0.5rem; |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| } |
| |
| .info-value { |
| font-size: 1.2rem; |
| font-weight: 700; |
| color: #fff; |
| } |
| |
| .confidence-badge { |
| display: inline-block; |
| padding: 0.4rem 1rem; |
| border-radius: 6px; |
| font-size: 0.85rem; |
| font-weight: 600; |
| } |
| |
| .confidence-high { |
| background: rgba(16, 185, 129, 0.2); |
| color: var(--success); |
| } |
| |
| .confidence-medium { |
| background: rgba(245, 158, 11, 0.2); |
| color: var(--warning); |
| } |
| |
| .confidence-low { |
| background: rgba(239, 68, 68, 0.2); |
| color: var(--danger); |
| } |
| |
| |
| .reasoning-box { |
| background: rgba(51, 65, 85, 0.4); |
| padding: 1.25rem; |
| border-radius: 10px; |
| border-left: 4px solid var(--primary); |
| margin-top: 1.5rem; |
| } |
| |
| .reasoning-title { |
| font-weight: 700; |
| margin-bottom: 1rem; |
| color: var(--primary); |
| font-size: 1rem; |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| } |
| |
| .reasoning-text { |
| color: var(--text-secondary); |
| line-height: 1.7; |
| font-size: 0.9rem; |
| } |
| |
| |
| .reasoning-box.enhanced { |
| background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.95) 100%); |
| border: 1px solid rgba(71, 85, 105, 0.5); |
| border-radius: 12px; |
| padding: 1.25rem; |
| margin-top: 1.5rem; |
| backdrop-filter: blur(10px); |
| } |
| |
| .reasoning-header { |
| display: flex; |
| align-items: center; |
| gap: 0.75rem; |
| margin-bottom: 1rem; |
| flex-wrap: wrap; |
| } |
| |
| .reasoning-icon { |
| font-size: 1.25rem; |
| } |
| |
| .reasoning-title { |
| font-size: 1rem; |
| font-weight: 700; |
| color: var(--primary); |
| flex: 1; |
| } |
| |
| .confidence-tag { |
| padding: 0.25rem 0.75rem; |
| border-radius: 20px; |
| font-size: 0.75rem; |
| font-weight: 600; |
| text-transform: uppercase; |
| white-space: nowrap; |
| } |
| |
| .high-confidence { |
| background: rgba(16, 185, 129, 0.2); |
| color: var(--success); |
| border: 1px solid rgba(16, 185, 129, 0.3); |
| } |
| |
| .medium-confidence { |
| background: rgba(245, 158, 11, 0.2); |
| color: var(--warning); |
| border: 1px solid rgba(245, 158, 11, 0.3); |
| } |
| |
| .low-confidence { |
| background: rgba(239, 68, 68, 0.2); |
| color: var(--danger); |
| border: 1px solid rgba(239, 68, 68, 0.3); |
| } |
| |
| .verdict-summary { |
| display: flex; |
| flex-direction: column; |
| gap: 0.75rem; |
| margin-bottom: 1.25rem; |
| padding: 1rem; |
| background: rgba(51, 65, 85, 0.3); |
| border-radius: 8px; |
| } |
| |
| .verdict-text { |
| font-size: 1.1rem; |
| font-weight: 800; |
| color: var(--warning); |
| text-align: center; |
| } |
| |
| .probability { |
| color: var(--text-secondary); |
| font-size: 0.9rem; |
| text-align: center; |
| } |
| |
| .probability-value { |
| color: var(--text-primary); |
| font-weight: 700; |
| } |
| |
| .metrics-breakdown { |
| margin-bottom: 1.25rem; |
| } |
| |
| .breakdown-header { |
| font-size: 0.85rem; |
| font-weight: 600; |
| color: var(--text-secondary); |
| margin-bottom: 1rem; |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| text-align: center; |
| } |
| |
| .metric-indicator { |
| display: flex; |
| flex-direction: column; |
| gap: 0.5rem; |
| padding: 0.75rem; |
| margin-bottom: 0.5rem; |
| border-radius: 8px; |
| transition: all 0.2s ease; |
| } |
| |
| .metric-indicator:hover { |
| background: rgba(51, 65, 85, 0.4); |
| } |
| |
| .metric-name { |
| font-weight: 400; |
| color: var(--text-primary); |
| font-size: 0.9rem; |
| } |
| |
| .metric-details { |
| display: flex; |
| gap: 0.75rem; |
| align-items: center; |
| flex-wrap: wrap; |
| } |
| |
| .verdict-badge { |
| padding: 0.2rem 0.6rem; |
| border-radius: 6px; |
| font-size: 0.7rem; |
| font-weight: 700; |
| text-transform: uppercase; |
| min-width: 60px; |
| text-align: center; |
| } |
| |
| .synthetic-badge { |
| background: rgba(239, 68, 68, 0.2); |
| color: var(--danger); |
| border: 1px solid rgba(239, 68, 68, 0.3); |
| } |
| |
| .authentic-badge { |
| background: rgba(16, 185, 129, 0.2); |
| color: var(--success); |
| border: 1px solid rgba(16, 185, 129, 0.3); |
| } |
| |
| .confidence, .weight { |
| font-size: 0.75rem; |
| color: var(--text-muted); |
| } |
| |
| .agreement-indicator { |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| padding: 0.75rem; |
| background: rgba(16, 185, 129, 0.1); |
| border: 1px solid rgba(16, 185, 129, 0.2); |
| border-radius: 8px; |
| color: var(--success); |
| font-size: 0.9rem; |
| } |
| |
| .agreement-icon { |
| font-weight: 700; |
| } |
| |
| .agreement-text { |
| font-size: 0.85rem; |
| font-weight: 600; |
| } |
| |
| |
| .download-actions { |
| display: flex; |
| flex-direction: column; |
| gap: 0.75rem; |
| margin-top: 1.5rem; |
| } |
| |
| .download-btn { |
| width: 100%; |
| padding: 0.75rem; |
| background: rgba(51, 65, 85, 0.6); |
| border: 1px solid var(--border); |
| border-radius: 8px; |
| color: var(--text-primary); |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.3s; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 0.5rem; |
| font-size: 0.9rem; |
| touch-action: manipulation; |
| } |
| |
| .download-btn:hover { |
| background: var(--primary); |
| border-color: var(--primary); |
| transform: translateY(-2px); |
| } |
| |
| |
| .metrics-grid { |
| display: grid; |
| grid-template-columns: 1fr; |
| gap: 1rem; |
| } |
| |
| .metric-result-card { |
| background: rgba(51, 65, 85, 0.4); |
| padding: 1.25rem; |
| border-radius: 10px; |
| border: 1px solid var(--border); |
| } |
| |
| .metric-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 0.75rem; |
| flex-wrap: wrap; |
| gap: 0.5rem; |
| } |
| |
| .metric-name { |
| font-weight: 700; |
| color: #fff; |
| font-size: 1rem; |
| } |
| |
| .metric-score { |
| font-size: 1.5rem; |
| font-weight: 800; |
| } |
| |
| .metric-verdict { |
| display: inline-block; |
| padding: 0.25rem 0.75rem; |
| border-radius: 6px; |
| font-size: 0.7rem; |
| font-weight: 600; |
| text-transform: uppercase; |
| margin-top: 0.5rem; |
| } |
| |
| .verdict-synthetic { |
| background: rgba(239, 68, 68, 0.2); |
| color: var(--danger); |
| } |
| |
| .verdict-authentic { |
| background: rgba(16, 185, 129, 0.2); |
| color: var(--success); |
| } |
| |
| .verdict-uncertain { |
| background: rgba(245, 158, 11, 0.2); |
| color: var(--warning); |
| } |
| |
| .metric-description { |
| font-size: 0.8rem; |
| color: var(--text-secondary); |
| line-height: 1.5; |
| margin-top: 0.75rem; |
| } |
| |
| |
| .highlight-legend { |
| display: flex; |
| gap: 1rem; |
| margin-bottom: 1.25rem; |
| padding: 1rem; |
| background: rgba(51, 65, 85, 0.4); |
| border-radius: 8px; |
| flex-wrap: wrap; |
| justify-content: center; |
| } |
| |
| .legend-item { |
| display: flex; |
| align-items: center; |
| gap: 0.5rem; |
| } |
| |
| .legend-color { |
| width: 20px; |
| height: 20px; |
| min-width: 20px; |
| border-radius: 4px; |
| } |
| |
| .legend-label { |
| font-size: 0.85rem; |
| color: var(--text-secondary); |
| } |
| |
| .highlighted-text { |
| background: rgba(15, 23, 42, 0.8); |
| padding: 1.25rem; |
| border-radius: 10px; |
| border: 1px solid var(--border); |
| line-height: 1.7; |
| font-size: 0.9rem; |
| max-height: 60vh; |
| overflow-y: auto; |
| -webkit-overflow-scrolling: touch; |
| } |
| |
| |
| .footer { |
| max-width: 1200px; |
| margin: 4rem auto 0; |
| padding: 2rem 1rem; |
| border-top: 1px solid var(--border); |
| text-align: center; |
| color: var(--text-muted); |
| font-size: 0.9rem; |
| } |
| |
| |
| .metrics-carousel-container { |
| display: flex; |
| flex-direction: column; |
| height: 100%; |
| } |
| |
| .metrics-carousel-content { |
| flex: 1; |
| padding: 0; |
| display: flex; |
| align-items: flex-start; |
| justify-content: flex-start; |
| overflow-y: auto; |
| -webkit-overflow-scrolling: touch; |
| padding: 0.5rem; |
| } |
| |
| .metric-slide { |
| display: none; |
| width: 100%; |
| padding: 0.5rem; |
| } |
| |
| .metric-slide.active { |
| display: block; |
| } |
| |
| .metrics-carousel-nav { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 1rem 0.5rem; |
| border-top: 1px solid var(--border); |
| background: rgba(15, 23, 42, 0.8); |
| flex-wrap: wrap; |
| gap: 0.5rem; |
| } |
| |
| .carousel-btn { |
| padding: 0.75rem 1rem; |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); |
| color: #fff; |
| border: none; |
| border-radius: 8px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: transform 0.3s, box-shadow 0.3s; |
| font-size: 0.9rem; |
| touch-action: manipulation; |
| flex: 1; |
| min-width: 120px; |
| } |
| |
| .carousel-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 8px 20px rgba(6, 182, 212, 0.4); |
| } |
| |
| .carousel-btn:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| transform: none; |
| } |
| |
| .carousel-position { |
| font-size: 0.85rem; |
| color: var(--text-secondary); |
| font-weight: 600; |
| text-align: center; |
| flex-basis: 100%; |
| order: -1; |
| } |
| |
| |
| .verdict-text { |
| font-size: 1.1rem !important; |
| } |
| |
| .domain-text { |
| font-size: 1rem !important; |
| } |
| |
| .verdict-hybrid { |
| background: rgba(168, 85, 247, 0.2); |
| color: #a855f7; |
| border: 1px solid rgba(168, 85, 247, 0.3); |
| } |
| |
| |
| .reasoning-bullet-points { |
| margin: 1.25rem 0; |
| line-height: 1.6; |
| text-align: left; |
| } |
| |
| .bullet-point { |
| margin-bottom: 0.75rem; |
| padding-left: 0.5rem; |
| color: var(--text-secondary); |
| font-size: 0.9rem; |
| text-align: left; |
| line-height: 1.5; |
| } |
| |
| .bullet-point:last-child { |
| margin-bottom: 0; |
| } |
| |
| .bullet-point strong { |
| color: var(--text-primary); |
| } |
| |
| |
| .single-progress-gauge { |
| width: 200px; |
| height: 200px; |
| margin: 0 auto 1.5rem; |
| position: relative; |
| border-radius: 50%; |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); |
| } |
| |
| .single-progress-gauge .gauge-inner { |
| position: absolute; |
| width: 150px; |
| height: 150px; |
| background: var(--bg-panel); |
| border-radius: 50%; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .single-progress-gauge .gauge-value { |
| font-size: 2.5rem; |
| font-weight: 800; |
| } |
| |
| .single-progress-gauge .gauge-label { |
| font-size: 0.8rem; |
| color: var(--text-secondary); |
| margin-top: 0.25rem; |
| } |
| |
| |
| |
| |
| @media (min-width: 768px) { |
| .header { |
| padding: 1rem 1.5rem; |
| } |
| |
| .logo { |
| font-size: 1.25rem; |
| gap: 0.75rem; |
| } |
| |
| .logo-icon { |
| width: 36px; |
| height: 36px; |
| min-width: 36px; |
| font-size: 1.25rem; |
| } |
| |
| .nav-link { |
| font-size: 0.95rem; |
| padding: 0.5rem; |
| } |
| |
| .hero { |
| padding: 4rem 1.5rem 3rem; |
| } |
| |
| .hero-title { |
| font-size: 2.75rem; |
| } |
| |
| .hero-subtitle { |
| font-size: 1.25rem; |
| } |
| |
| .stats-grid { |
| grid-template-columns: repeat(3, 1fr); |
| gap: 1.5rem; |
| } |
| |
| .section-title { |
| font-size: 2rem; |
| } |
| |
| .section-subtitle { |
| font-size: 1.1rem; |
| } |
| |
| .features-grid { |
| grid-template-columns: repeat(2, 1fr); |
| gap: 2rem; |
| } |
| |
| .metric-card { |
| padding: 1.75rem; |
| gap: 1.75rem; |
| } |
| |
| .metric-icon-box { |
| width: 70px; |
| height: 70px; |
| min-width: 70px; |
| font-size: 1.75rem; |
| } |
| |
| .analysis-interface { |
| padding: 0 1.5rem 1.5rem; |
| } |
| |
| .panel { |
| padding: 1.75rem; |
| min-height: 650px; |
| } |
| |
| .interface-grid { |
| gap: 2rem; |
| } |
| |
| .result-info-grid { |
| grid-template-columns: repeat(3, 1fr); |
| } |
| |
| .option-row { |
| flex-direction: row; |
| align-items: center; |
| gap: 1rem; |
| } |
| |
| .option-label { |
| margin-bottom: 0; |
| min-width: 160px; |
| } |
| |
| select { |
| max-width: 200px; |
| } |
| |
| .download-actions { |
| flex-direction: row; |
| } |
| |
| .action-buttons { |
| flex-wrap: nowrap; |
| } |
| |
| .metrics-grid { |
| grid-template-columns: repeat(2, 1fr); |
| } |
| |
| .carousel-position { |
| flex-basis: auto; |
| order: 0; |
| } |
| |
| .highlight-legend { |
| justify-content: flex-start; |
| } |
| } |
| |
| |
| @media (min-width: 1024px) { |
| .interface-grid { |
| grid-template-columns: 1fr 1fr; |
| } |
| |
| .hero { |
| padding: 5rem 2rem 4rem; |
| } |
| |
| .hero-title { |
| font-size: 3.25rem; |
| } |
| |
| .features-grid { |
| grid-template-columns: repeat(3, 1fr); |
| } |
| |
| .metric-card { |
| grid-template-columns: 80px 1fr; |
| gap: 2rem; |
| } |
| |
| .panel { |
| height: 750px; |
| } |
| |
| .download-actions { |
| flex-direction: row; |
| } |
| |
| .nav-links { |
| gap: 2rem; |
| } |
| } |
| |
| |
| @media (min-width: 1200px) { |
| .hero-title { |
| font-size: 3.5rem; |
| } |
| |
| .features-grid { |
| grid-template-columns: repeat(3, 1fr); |
| gap: 2.5rem; |
| } |
| |
| .panel { |
| height: 800px; |
| } |
| |
| .single-progress-gauge { |
| width: 220px; |
| height: 220px; |
| } |
| |
| .single-progress-gauge .gauge-inner { |
| width: 170px; |
| height: 170px; |
| } |
| } |
| |
| |
| @media (max-width: 480px) { |
| .logo span { |
| display: none; |
| } |
| |
| .logo-icon { |
| margin-right: 0; |
| } |
| |
| .nav-links { |
| gap: 0.5rem; |
| } |
| |
| .nav-link { |
| font-size: 0.8rem; |
| padding: 0.5rem 0.25rem; |
| } |
| |
| .try-btn { |
| padding: 0.5rem 1rem; |
| font-size: 0.85rem; |
| } |
| |
| .hero-title { |
| font-size: 1.75rem; |
| } |
| |
| .hero-subtitle { |
| font-size: 1rem; |
| } |
| |
| .stats-grid { |
| grid-template-columns: 1fr; |
| } |
| |
| .feature-card { |
| padding: 1.5rem; |
| } |
| |
| .metric-card { |
| grid-template-columns: 1fr; |
| text-align: center; |
| gap: 1rem; |
| } |
| |
| .metric-icon-box { |
| margin: 0 auto; |
| } |
| |
| .panel { |
| padding: 1.25rem; |
| } |
| |
| .input-tab { |
| min-width: 120px; |
| font-size: 0.85rem; |
| } |
| |
| .text-input { |
| min-height: 250px; |
| } |
| |
| .file-upload-area { |
| padding: 1.5rem 1rem; |
| } |
| |
| .file-upload-icon { |
| font-size: 2rem; |
| } |
| |
| .action-btn { |
| min-width: 100%; |
| } |
| |
| .carousel-btn { |
| min-width: 100%; |
| margin-bottom: 0.5rem; |
| } |
| |
| .carousel-position { |
| order: -1; |
| margin-bottom: 0.5rem; |
| } |
| |
| .metrics-carousel-nav { |
| flex-direction: column; |
| } |
| } |
| |
| |
| @media (orientation: landscape) and (max-height: 500px) { |
| .panel { |
| min-height: 400px; |
| max-height: 85vh; |
| } |
| |
| .text-input { |
| min-height: 200px; |
| max-height: 40vh; |
| } |
| |
| .highlighted-text { |
| max-height: 40vh; |
| } |
| |
| .hero { |
| padding: 2rem 1rem 1.5rem; |
| } |
| |
| .hero-title { |
| font-size: 2rem; |
| margin-bottom: 0.75rem; |
| } |
| |
| .stats-grid { |
| margin: 2rem auto; |
| } |
| } |
| |
| |
| @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { |
| .logo-icon, .metric-icon-box, .empty-icon, .gauge-circle, .single-progress-gauge { |
| -webkit-backface-visibility: hidden; |
| backface-visibility: hidden; |
| transform: translateZ(0); |
| } |
| } |
| |
| |
| @media print { |
| .header, .footer, .try-btn, .action-buttons, .download-actions, .report-tabs { |
| display: none !important; |
| } |
| |
| .panel { |
| border: none; |
| box-shadow: none; |
| height: auto; |
| min-height: auto; |
| max-height: none; |
| } |
| |
| body { |
| background: white; |
| color: black; |
| } |
| |
| .highlighted-text { |
| background: white; |
| border: 1px solid #ccc; |
| } |
| } |
| |
| |
| @media (prefers-reduced-motion: reduce) { |
| * { |
| animation-duration: 0.01ms !important; |
| animation-iteration-count: 1 !important; |
| transition-duration: 0.01ms !important; |
| } |
| |
| .spinner { |
| animation: none !important; |
| } |
| } |
| |
| |
| @media (prefers-color-scheme: dark) { |
| :root { |
| --text-primary: #f1f5f9; |
| --text-secondary: #94a3b8; |
| --text-muted: #64748b; |
| } |
| } |
| |
| </style> |
| </head> |
| <body> |
| |
| <div class="header"> |
| <a href="#" class="logo" onclick="showLanding(); return false;"> |
| <div class="logo-icon">🔍</div> |
| <span>TextAuth Forensics</span> |
| </a> |
| <div class="nav-links"> |
| <a href="#features" class="nav-link">Features</a> |
| <a href="#metrics" class="nav-link">Forensic Signals</a> |
| <a href="#" class="nav-link" onclick="showAnalysis(); return false;">Try It Now</a> |
| </div> |
| </div> |
|
|
| |
| <div class="landing-page" id="landing-page"> |
| |
| <section class="hero"> |
| <h1 class="hero-title">Evidence-Based Text Forensics Platform</h1> |
| <p class="hero-subtitle">Analyzing Content Authenticity Through Linguistic & Statistical Evidence</p> |
| <p class="hero-description"> |
| A forensic analysis system that evaluates textual evidence using multiple statistical, |
| linguistic, and semantic signals to assess content authenticity across education, |
| publishing, hiring, and research domains. |
| </p> |
| <button class="try-btn" onclick="showAnalysis()"> Try It Now → </button> |
| </section> |
|
|
| |
| <div class="stats-grid"> |
| <div class="stat-card"> |
| <div class="stat-value">Low</div> |
| <div class="stat-label">False-Positive Bias (Domain-Calibrated)</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-value">6</div> |
| <div class="stat-label">Total Forensic Signals</div> |
| </div> |
| <div class="stat-card"> |
| <div class="stat-value">10s</div> |
| <div class="stat-label">Average Processing Time</div> |
| </div> |
| </div> |
|
|
| |
| <section class="features-section" id="features"> |
| <h2 class="section-title">Why Choose Our Platform?</h2> |
| <p class="section-subtitle"> |
| Advanced technology meets practical application |
| </p> |
| <div class="features-grid"> |
| <div class="feature-card"> |
| <div class="feature-icon">🎯</div> |
| <h3 class="feature-title">Domain-Aware Analysis</h3> |
| <p class="feature-description"> |
| Calibrated thresholds for Academic, Technical, Creative, and Casual content types with specialized analysis algorithms for each domain. |
| </p> |
| </div> |
| <div class="feature-card"> |
| <div class="feature-icon">🔬</div> |
| <h3 class="feature-title">6-Signal Evidence Ensemble</h3> |
| <p class="feature-description"> |
| Combines perplexity, entropy, structural, linguistic, semantic, and perturbation-stability signals to form a multi-angle forensic evidence profile |
| </p> |
| </div> |
| <div class="feature-card"> |
| <div class="feature-icon">💡</div> |
| <h3 class="feature-title">Explainable Results</h3> |
| <p class="feature-description"> |
| Sentence-level highlighting with confidence scores and detailed forensic reasoning for each assessment. |
| </p> |
| </div> |
| <div class="feature-card"> |
| <div class="feature-icon">🚀</div> |
| <h3 class="feature-title">Fast Processing</h3> |
| <p class="feature-description"> |
| Analyze short texts in 1.2 seconds, medium documents in 3.5 seconds with parallel metric computation. |
| </p> |
| </div> |
| <div class="feature-card"> |
| <div class="feature-icon">📄</div> |
| <h3 class="feature-title">Multi-Format Support</h3> |
| <p class="feature-description"> |
| Upload and analyze TXT, PDF, DOCX, DOC, and Markdown files with automatic text extraction. |
| </p> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <section class="metrics-info" id="metrics"> |
| <h2 class="section-title">Forensic Signals Explained</h2> |
| <p class="section-subtitle"> |
| Understanding the science behind the forensic evaluation |
| </p> |
| <div class="metric-card"> |
| <div class="metric-icon-box">📊</div> |
| <div class="metric-content"> |
| <h3>Perplexity <span class="metric-weight">Weight: 25%</span></h3> |
| <p>Measures how predictable the text is using reference language model. Model-generated or algorithmically assisted text typically exhibits lower perplexity (more predictable) than human writing, which tends to be more varied and surprising.</p> |
| </div> |
| </div> |
| <div class="metric-card"> |
| <div class="metric-icon-box">🎲</div> |
| <div class="metric-content"> |
| <h3>Entropy <span class="metric-weight">Weight: 20%</span></h3> |
| <p>Calculates token-level diversity and unpredictability in text sequences. Human writing shows higher entropy with more varied word choices, while algorithmically generated text tends toward more uniform token distributions.</p> |
| </div> |
| </div> |
| <div class="metric-card"> |
| <div class="metric-icon-box">📈</div> |
| <div class="metric-content"> |
| <h3>Structural Analysis <span class="metric-weight">Weight: 15%</span></h3> |
| <p>Analyzes sentence length variance, punctuation patterns, and lexical burstiness. Human writing exhibits more variation in sentence structure and rhythm compared to algorithmically generated text, which often shows more uniform patterns.</p> |
| </div> |
| </div> |
| <div class="metric-card"> |
| <div class="metric-icon-box">📝</div> |
| <div class="metric-content"> |
| <h3>Linguistic Analysis <span class="metric-weight">Weight: 15%</span></h3> |
| <p>Evaluates POS tag diversity, syntactic complexity, and grammatical patterns. Examines the richness of language structures and whether they match natural human linguistic variation.</p> |
| </div> |
| </div> |
| <div class="metric-card"> |
| <div class="metric-icon-box">🧠</div> |
| <div class="metric-content"> |
| <h3>Semantic Analysis <span class="metric-weight">Weight: 15%</span></h3> |
| <p>Assesses semantic coherence, repetition patterns, and contextual consistency. Identifies semantic consistency patterns that often differ between human-authored and algorithmically generated text.</p> |
| </div> |
| </div> |
| <div class="metric-card"> |
| <div class="metric-icon-box">🔍</div> |
| <div class="metric-content"> |
| <h3>Multi-Perturbation Stability <span class="metric-weight">Weight: 10%</span></h3> |
| <p>Tests text stability under random perturbations. Algorithmically generated text tends to maintain higher likelihood scores even when slightly modified, while human text shows more variation.</p> |
| </div> |
| </div> |
| </section> |
|
|
| |
| <footer class="footer"> |
| <p>© 2025 Evidence-Based Text Forensics Platform</p> |
| <p style="margin-top: 1rem;">Evidence-first text forensics with explainable decisions.</p> |
| </footer> |
| </div> |
|
|
| |
| <div class="analysis-interface" id="analysis-interface"> |
| <div class="interface-grid"> |
| |
| <div class="panel"> |
| <h2 class="panel-title">Submit Content for Analysis</h2> |
| <div class="panel-content"> |
| <div class="input-tabs"> |
| <button class="input-tab active" data-tab="paste"> |
| 📋 Paste Text |
| </button> |
| <button class="input-tab" data-tab="upload"> |
| 📁 Upload File |
| </button> |
| </div> |
| <div id="paste-tab" class="tab-content active"> |
| <textarea |
| id="text-input" |
| class="text-input" |
| placeholder="Paste your text here for analysis... |
| The more text you provide (minimum 50 characters), the more reliable the forensic evaluation will be." |
| ></textarea> |
| </div> |
| <div id="upload-tab" class="tab-content"> |
| <div class="file-upload-area" id="file-upload-area"> |
| <input type="file" id="file-input" class="file-input" accept=".txt,.pdf,.docx,.doc,.md"> |
| <div class="file-upload-icon">📄</div> |
| <div style="font-size: 1.1rem; font-weight: 600; margin-bottom: 0.5rem;"> |
| Click to upload or drag and drop |
| </div> |
| <div style="color: var(--text-muted); font-size: 0.9rem;"> |
| Supported formats: TXT, PDF, DOCX, DOC, MD |
| </div> |
| <div style="color: var(--text-muted); font-size: 0.85rem; margin-top: 0.5rem;"> |
| Maximum file size: 10MB |
| </div> |
| </div> |
| <div id="file-name-display" class="file-name-display"></div> |
| </div> |
| <div class="options-section"> |
| <div class="option-row"> |
| <label class="option-label">Content Domain:</label> |
| <select id="domain-select"> |
| <option value="">Auto-detect</option> |
| <option value="academic">Academic</option> |
| <option value="technical_doc">Technical/Medical</option> |
| <option value="creative">Creative Writing</option> |
| <option value="social_media">Social Media</option> |
| </select> |
| </div> |
| <div class="option-row"> |
| <label class="option-label">Enable Sentence Highlighting:</label> |
| <div class="checkbox-wrapper"> |
| <input type="checkbox" id="enable-highlighting" checked> |
| <span style="font-size: 0.85rem; color: var(--text-muted);">Show suspicious sentences</span> |
| </div> |
| </div> |
| |
| <div class="option-row"> |
| <label class="option-label">Sentence-Level Analysis:</label> |
| <div class="checkbox-wrapper"> |
| <input type="checkbox" id="use-sentence-level" checked> |
| <span style="font-size: 0.85rem; color: var(--text-muted);">More accurate but slower analysis</span> |
| </div> |
| </div> |
| <div class="option-row"> |
| <label class="option-label">Include Metrics Summary:</label> |
| <div class="checkbox-wrapper"> |
| <input type="checkbox" id="include-metrics-summary" checked> |
| <span style="font-size: 0.85rem; color: var(--text-muted);">Show text analysis statistics</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| <div style="display: flex; flex-direction: column; gap: 1rem;"> |
| <button id="analyze-btn" class="analyze-btn"> |
| 🔍 Analyze Text |
| </button> |
| <div class="action-buttons"> |
| <button id="refresh-btn" class="action-btn refresh"> |
| 🔄 Refresh |
| </button> |
| <button id="try-next-btn" class="action-btn"> |
| ➕ Try Next |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| |
| <div class="panel"> |
| <h2 class="panel-title">Analysis Report</h2> |
| <div class="panel-content"> |
| <div class="report-tabs"> |
| <button class="report-tab active" data-report="summary"> |
| 📊 Summary |
| </button> |
| <button class="report-tab" data-report="highlighted"> |
| 📝 Highlighted Text |
| </button> |
| <button class="report-tab" data-report="metrics"> |
| ℹ️ Detailed Metrics |
| </button> |
| </div> |
| |
| |
| <div id="summary-report" class="report-content active"> |
| <div class="empty-state"> |
| <div class="empty-icon">✓</div> |
| <h3 class="empty-title">Ready for Analysis</h3> |
| <p class="empty-description"> |
| Paste text or upload a document to begin evidence-based forensic analysis. |
| Our multi-signal ensemble will provide detailed, explainable insights. |
| </p> |
| </div> |
| </div> |
| |
| |
| <div id="highlighted-report" class="report-content"> |
| <div class="empty-state"> |
| <div class="empty-icon">📝</div> |
| <p class="empty-description"> |
| Run an analysis to see sentence-level highlighting |
| </p> |
| </div> |
| </div> |
| |
| |
| <div id="metrics-report" class="report-content"> |
| <div class="empty-state"> |
| <div class="empty-icon">📊</div> |
| <p class="empty-description"> |
| Run an analysis to see detailed metric breakdowns |
| </p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| const API_BASE = ''; |
| let currentAnalysisData = null; |
| let currentMetricIndex = 0; |
| let totalMetrics = 0; |
| |
| |
| function showLanding() { |
| document.getElementById('landing-page').style.display = 'block'; |
| document.getElementById('analysis-interface').style.display = 'none'; |
| window.scrollTo(0, 0); |
| } |
| |
| function showAnalysis() { |
| document.getElementById('landing-page').style.display = 'none'; |
| document.getElementById('analysis-interface').style.display = 'block'; |
| window.scrollTo(0, 0); |
| resetAnalysisInterface(); |
| } |
| |
| |
| function resetAnalysisInterface() { |
| |
| document.getElementById('text-input').value = ''; |
| |
| document.getElementById('file-input').value = ''; |
| document.getElementById('file-name-display').style.display = 'none'; |
| document.getElementById('file-name-display').innerHTML = ''; |
| |
| document.querySelectorAll('.input-tab').forEach(t => t.classList.remove('active')); |
| document.querySelector('.input-tab[data-tab="paste"]').classList.add('active'); |
| document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); |
| document.getElementById('paste-tab').classList.add('active'); |
| |
| document.getElementById('domain-select').value = ''; |
| document.getElementById('enable-highlighting').checked = true; |
| document.getElementById('use-sentence-level').checked = true; |
| document.getElementById('include-metrics-summary').checked = true; |
| |
| document.querySelectorAll('.report-tab').forEach(t => t.classList.remove('active')); |
| document.querySelector('.report-tab[data-report="summary"]').classList.add('active'); |
| document.querySelectorAll('.report-content').forEach(content => content.classList.remove('active')); |
| document.getElementById('summary-report').classList.add('active'); |
| |
| document.getElementById('summary-report').innerHTML = ` |
| <div class="empty-state"> |
| <div class="empty-icon">✓</div> |
| <h3 class="empty-title">Ready for Analysis</h3> |
| <p class="empty-description"> |
| Paste text or upload a document to begin evidence-based forensic analysis. |
| Our multi-signal ensemble will provide detailed, explainable insights. |
| </p> |
| </div> |
| `; |
| document.getElementById('highlighted-report').innerHTML = ` |
| <div class="empty-state"> |
| <div class="empty-icon">📝</div> |
| <p class="empty-description"> |
| Run an analysis to see sentence-level highlighting |
| </p> |
| </div> |
| `; |
| document.getElementById('metrics-report').innerHTML = ` |
| <div class="empty-state"> |
| <div class="empty-icon">📊</div> |
| <p class="empty-description"> |
| Run an analysis to see detailed metric breakdowns |
| </p> |
| </div> |
| `; |
| |
| currentAnalysisData = null; |
| currentMetricIndex = 0; |
| totalMetrics = 0; |
| } |
| |
| |
| document.querySelectorAll('.input-tab').forEach(tab => { |
| tab.addEventListener('click', () => { |
| const tabName = tab.dataset.tab; |
| document.querySelectorAll('.input-tab').forEach(t => t.classList.remove('active')); |
| tab.classList.add('active'); |
| document.querySelectorAll('#paste-tab, #upload-tab').forEach(content => { |
| content.classList.remove('active'); |
| }); |
| document.getElementById(`${tabName}-tab`).classList.add('active'); |
| }); |
| }); |
| |
| |
| document.querySelectorAll('.report-tab').forEach(tab => { |
| tab.addEventListener('click', () => { |
| const reportName = tab.dataset.report; |
| document.querySelectorAll('.report-tab').forEach(t => t.classList.remove('active')); |
| tab.classList.add('active'); |
| document.querySelectorAll('.report-content').forEach(content => { |
| content.classList.remove('active'); |
| }); |
| document.getElementById(`${reportName}-report`).classList.add('active'); |
| }); |
| }); |
| |
| |
| const fileInput = document.getElementById('file-input'); |
| const fileUploadArea = document.getElementById('file-upload-area'); |
| const fileNameDisplay = document.getElementById('file-name-display'); |
| |
| fileUploadArea.addEventListener('click', () => { |
| fileInput.click(); |
| }); |
| |
| fileInput.addEventListener('change', (e) => { |
| handleFileSelect(e.target.files[0]); |
| }); |
| |
| |
| fileUploadArea.addEventListener('dragover', (e) => { |
| e.preventDefault(); |
| fileUploadArea.classList.add('drag-over'); |
| }); |
| |
| fileUploadArea.addEventListener('dragleave', () => { |
| fileUploadArea.classList.remove('drag-over'); |
| }); |
| |
| fileUploadArea.addEventListener('drop', (e) => { |
| e.preventDefault(); |
| fileUploadArea.classList.remove('drag-over'); |
| const file = e.dataTransfer.files[0]; |
| if (file) { |
| fileInput.files = e.dataTransfer.files; |
| handleFileSelect(file); |
| } |
| }); |
| |
| function handleFileSelect(file) { |
| if (!file) return; |
| const allowedTypes = ['.txt', '.pdf', '.docx', '.doc', '.md']; |
| const fileExt = '.' + file.name.split('.').pop().toLowerCase(); |
| if (!allowedTypes.includes(fileExt)) { |
| alert('Unsupported file type. Please upload: TXT, PDF, DOCX, DOC, or MD files.'); |
| return; |
| } |
| if (file.size > 10 * 1024 * 1024) { |
| alert('File size exceeds 10MB limit.'); |
| return; |
| } |
| fileNameDisplay.style.display = 'block'; |
| fileNameDisplay.innerHTML = ` |
| <strong>Selected file:</strong> ${file.name} |
| <span style="color: var(--text-muted);">(${formatFileSize(file.size)})</span> |
| `; |
| } |
| |
| function formatFileSize(bytes) { |
| if (bytes < 1024) return bytes + ' B'; |
| if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'; |
| return (bytes / (1024 * 1024)).toFixed(1) + ' MB'; |
| } |
| |
| |
| document.getElementById('analyze-btn').addEventListener('click', async () => { |
| const activeTab = document.querySelector('.input-tab.active').dataset.tab; |
| const textInput = document.getElementById('text-input').value.trim(); |
| const fileInput = document.getElementById('file-input').files[0]; |
| |
| if (activeTab === 'paste' && !textInput) { |
| alert('Please paste some text to analyze (minimum 50 characters).'); |
| return; |
| } |
| if (activeTab === 'paste' && textInput.length < 50) { |
| alert('Text must be at least 50 characters long for accurate analysis.'); |
| return; |
| } |
| if (activeTab === 'upload' && !fileInput) { |
| alert('Please select a file to upload.'); |
| return; |
| } |
| |
| await performAnalysis(activeTab, textInput, fileInput); |
| }); |
| |
| |
| document.getElementById('refresh-btn').addEventListener('click', () => { |
| resetAnalysisInterface(); |
| }); |
| |
| |
| document.getElementById('try-next-btn').addEventListener('click', () => { |
| resetAnalysisInterface(); |
| }); |
| |
| async function performAnalysis(mode, text, file) { |
| const analyzeBtn = document.getElementById('analyze-btn'); |
| analyzeBtn.disabled = true; |
| analyzeBtn.innerHTML = '⏳ Analyzing...'; |
| showLoading(); |
| |
| try { |
| let response; |
| if (mode === 'paste') { |
| response = await analyzeText(text); |
| } else { |
| response = await analyzeFile(file); |
| } |
| currentAnalysisData = response; |
| displayResults(response); |
| } catch (error) { |
| console.error('Analysis error:', error); |
| showError(error.message || 'Analysis failed. Please try again.'); |
| } finally { |
| analyzeBtn.disabled = false; |
| analyzeBtn.innerHTML = '🔍 Analyze Text'; |
| } |
| } |
| |
| async function analyzeText(text) { |
| const domain = document.getElementById('domain-select').value || null; |
| const enableHighlighting = document.getElementById('enable-highlighting').checked; |
| const useSentenceLevel = document.getElementById('use-sentence-level').checked; |
| const includeMetricsSummary = document.getElementById('include-metrics-summary').checked; |
| |
| const response = await fetch(`${API_BASE}/api/analyze`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| text: text, |
| domain: domain, |
| enable_highlighting: enableHighlighting, |
| use_sentence_level: useSentenceLevel, |
| include_metrics_summary: includeMetricsSummary, |
| skip_expensive_metrics: false |
| }) |
| }); |
| |
| if (!response.ok) { |
| const error = await response.json(); |
| throw new Error(error.error || 'Analysis failed'); |
| } |
| return await response.json(); |
| } |
| |
| async function analyzeFile(file) { |
| const domain = document.getElementById('domain-select').value || null; |
| const useSentenceLevel = document.getElementById('use-sentence-level').checked; |
| const includeMetricsSummary = document.getElementById('include-metrics-summary').checked; |
| |
| const formData = new FormData(); |
| formData.append('file', file); |
| if (domain) formData.append('domain', domain); |
| formData.append('use_sentence_level', useSentenceLevel.toString()); |
| formData.append('include_metrics_summary', includeMetricsSummary.toString()); |
| formData.append('skip_expensive_metrics', 'false'); |
| |
| const response = await fetch(`${API_BASE}/api/analyze/file`, { |
| method: 'POST', |
| body: formData |
| }); |
| |
| if (!response.ok) { |
| const error = await response.json(); |
| throw new Error(error.error || 'File analysis failed'); |
| } |
| return await response.json(); |
| } |
| |
| function showLoading() { |
| document.getElementById('summary-report').innerHTML = ` |
| <div class="loading"> |
| <div class="spinner"></div> |
| <p style="color: var(--text-secondary);">Analyzing content using multi-signal forensic evaluation...</p> |
| <p style="color: var(--text-muted); font-size: 0.9rem; margin-top: 0.5rem;"> |
| This may take a few seconds |
| </p> |
| </div> |
| `; |
| } |
| |
| function showError(message) { |
| document.getElementById('summary-report').innerHTML = ` |
| <div class="empty-state"> |
| <div class="empty-icon" style="background: linear-gradient(135deg, var(--danger) 0%, #dc2626 100%);">⚠️</div> |
| <h3 class="empty-title">Analysis Failed</h3> |
| <p class="empty-description">${message}</p> |
| </div> |
| `; |
| } |
| |
| function displayResults(data) { |
| console.log('Response data:', data); |
| |
| const detection = data.detection_result; |
| if (!detection) { |
| showError('Invalid response structure. Please check the API response format.'); |
| console.error('Full response:', data); |
| return; |
| } |
| |
| |
| const ensemble = detection.ensemble_result || detection.ensemble; |
| const prediction = detection.prediction || {}; |
| const metrics = detection.metric_results || detection.metrics; |
| const analysis = detection.analysis || {}; |
| |
| |
| displaySummary(ensemble, prediction, analysis, data.reasoning); |
| |
| |
| if (data.highlighted_html) { |
| displayHighlightedText(data.highlighted_html); |
| } else { |
| document.getElementById('highlighted-report').innerHTML = ` |
| <div class="empty-state"> |
| <p class="empty-description">Highlighting not available for this analysis</p> |
| </div> |
| `; |
| } |
| |
| |
| if (metrics && Object.keys(metrics).length > 0) { |
| displayMetricsCarousel(metrics, analysis, ensemble); |
| } else { |
| document.getElementById('metrics-report').innerHTML = ` |
| <div class="empty-state"> |
| <p class="empty-description">Metric details not available</p> |
| </div> |
| `; |
| } |
| } |
| |
| function displaySummary(ensemble, prediction, analysis, reasoning) { |
| |
| const { |
| syntheticProbability, |
| authenticProbability, |
| hybridProbability, |
| verdict, |
| confidence, |
| domain, |
| isSynthetic, |
| gaugeColor, |
| gaugeDegree, |
| confidenceLevel, |
| confidenceClass |
| } = extractSummaryData(ensemble, analysis); |
| |
| document.getElementById('summary-report').innerHTML = ` |
| <div class="result-summary"> |
| ${createGaugeSection(syntheticProbability, authenticProbability, hybridProbability, gaugeColor, gaugeDegree)} |
| ${createInfoGrid(verdict, confidence, confidenceClass, domain, hybridProbability)} |
| ${createEnhancedReasoningHTML(ensemble, analysis, reasoning)} |
| ${createDownloadActions()} |
| </div> |
| `; |
| } |
| |
| |
| function extractSummaryData(ensemble, analysis) { |
| const syntheticProbability = ensemble.synthetic_probability !== undefined |
| ? (ensemble.synthetic_probability * 100).toFixed(0) |
| : '0'; |
| |
| const authenticProbability = ensemble.authentic_probability !== undefined |
| ? (ensemble.authentic_probability * 100).toFixed(0) |
| : '0'; |
| |
| const hybridProbability = ensemble.hybrid_probability !== undefined |
| ? (ensemble.hybrid_probability * 100).toFixed(0) |
| : '0'; |
| |
| const verdict = ensemble.final_verdict || 'Unknown'; |
| |
| const confidence = ensemble.overall_confidence !== undefined |
| ? (ensemble.overall_confidence * 100).toFixed(1) |
| : '0'; |
| |
| const domain = analysis.domain || 'general'; |
| |
| const isSynthetic = verdict.toLowerCase().includes('synthetic'); |
| |
| const gaugeColor = isSynthetic |
| ? 'var(--danger)' |
| : 'var(--success)'; |
| |
| const gaugeDegree = parseFloat(syntheticProbability) * 3.6; |
| |
| const confidenceLevel = getConfidenceLevel(parseFloat(confidence)); |
| const confidenceClass = getConfidenceClass(confidenceLevel); |
| |
| return { |
| syntheticProbability, |
| authenticProbability, |
| hybridProbability, |
| verdict, |
| confidence, |
| domain, |
| isSynthetic, |
| gaugeColor, |
| gaugeDegree, |
| confidenceLevel, |
| confidenceClass |
| }; |
| } |
| |
| |
| function getConfidenceLevel(confidence) { |
| if (confidence >= 70) return 'HIGH'; |
| if (confidence >= 40) return 'MEDIUM'; |
| return 'LOW'; |
| } |
| |
| |
| function getConfidenceClass(confidenceLevel) { |
| const classMap = { |
| 'HIGH': 'confidence-high', |
| 'MEDIUM': 'confidence-medium', |
| 'LOW': 'confidence-low' |
| }; |
| return classMap[confidenceLevel] || 'confidence-low'; |
| } |
| |
| |
| function formatModelName(modelName) { |
| return modelName.replace(/_/g, ' ').replace(/-/g, ' ').toUpperCase(); |
| } |
| |
| |
| function createGaugeSection(syntheticProbability, authenticProbability, hybridProbability, gaugeColor, gaugeDegree) { |
| |
| const synthetic = parseFloat(syntheticProbability); |
| const authentic = parseFloat(authenticProbability); |
| const hybrid = parseFloat(hybridProbability); |
| |
| |
| let maxValue, maxColor, maxLabel; |
| |
| if (synthetic >= authentic && synthetic >= hybrid) { |
| maxValue = synthetic; |
| maxColor = 'var(--danger)'; |
| maxLabel = 'Synthetic Probability'; |
| } else if (authentic >= synthetic && authentic >= hybrid) { |
| maxValue = authentic; |
| maxColor = 'var(--success)'; |
| maxLabel = 'Authentic Probability'; |
| } else { |
| maxValue = hybrid; |
| maxColor = 'var(--primary)'; |
| maxLabel = 'Hybrid Probability'; |
| } |
| |
| console.log('Selected:', { maxValue, maxLabel }); |
| |
| |
| const progressDegree = (maxValue / 100) * 360; |
| |
| return ` |
| <div class="gauge-container"> |
| <div class="single-progress-gauge" style=" |
| background: conic-gradient( |
| ${maxColor} 0deg, |
| ${maxColor} ${progressDegree}deg, |
| rgba(51, 65, 85, 0.3) ${progressDegree}deg, |
| rgba(51, 65, 85, 0.3) 360deg |
| ); |
| "> |
| <div class="gauge-inner"> |
| <div class="gauge-value" style="color: ${maxColor}">${maxValue}%</div> |
| <div class="gauge-label">${maxLabel}</div> |
| </div> |
| </div> |
| </div> |
| <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin: 1.5rem 0;"> |
| <div style="text-align: center; padding: 1rem; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border: 1px solid rgba(239, 68, 68, 0.3);"> |
| <div style="font-size: 0.85rem; color: var(--danger); margin-bottom: 0.25rem; font-weight: 600;">Synthetic</div> |
| <div style="font-size: 1.4rem; font-weight: 700; color: var(--danger);">${syntheticProbability}%</div> |
| </div> |
| <div style="text-align: center; padding: 1rem; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border: 1px solid rgba(16, 185, 129, 0.3);"> |
| <div style="font-size: 0.85rem; color: var(--success); margin-bottom: 0.25rem; font-weight: 600;">Authentic</div> |
| <div style="font-size: 1.4rem; font-weight: 700; color: var(--success);">${authenticProbability}%</div> |
| </div> |
| <div style="text-align: center; padding: 1rem; background: rgba(6, 182, 212, 0.1); border-radius: 8px; border: 1px solid rgba(6, 182, 212, 0.3);"> |
| <div style="font-size: 0.85rem; color: var(--primary); margin-bottom: 0.25rem; font-weight: 600;">Hybrid</div> |
| <div style="font-size: 1.4rem; font-weight: 700; color: var(--primary);">${hybridProbability}%</div> |
| </div> |
| </div> |
| <style> |
| .single-progress-gauge { |
| width: 220px; |
| height: 220px; |
| margin: 0 auto 2rem; |
| position: relative; |
| border-radius: 50%; |
| box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); |
| } |
| |
| .gauge-inner { |
| position: absolute; |
| width: 170px; |
| height: 170px; |
| background: var(--bg-panel); |
| border-radius: 50%; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .gauge-value { |
| font-size: 3rem; |
| font-weight: 800; |
| } |
| |
| .gauge-label { |
| font-size: 0.9rem; |
| color: var(--text-secondary); |
| margin-top: 0.25rem; |
| } |
| </style> |
| `; |
| } |
| |
| |
| |
| function createInfoGrid(verdict, confidence, confidenceClass, domain, hybridProbability) { |
| const hybridContentInfo = hybridProbability > 10 ? |
| `<div style="margin-top: 0.5rem; font-size: 0.85rem; color: var(--primary);"> |
| 🔀 ${hybridProbability}% Hybrid Content Detected |
| </div>` : ''; |
| |
| return ` |
| <div class="result-info-grid"> |
| <div class="info-card"> |
| <div class="info-label">Verdict</div> |
| <div class="info-value verdict-text">${verdict}</div> |
| ${hybridContentInfo} |
| </div> |
| <div class="info-card"> |
| <div class="info-label">Confidence Level</div> |
| <div class="info-value"> |
| <span class="confidence-badge ${confidenceClass}">${confidence}%</span> |
| </div> |
| </div> |
| <div class="info-card"> |
| <div class="info-label">Content Domain</div> |
| <div class="info-value domain-text">${formatDomainName(domain)}</div> |
| </div> |
| </div> |
| `; |
| } |
| |
| |
| function createDownloadActions() { |
| return ` |
| <div class="download-actions"> |
| <button class="download-btn" onclick="downloadReport('json')"> |
| 📄 Download JSON |
| </button> |
| <button class="download-btn" onclick="downloadReport('pdf')"> |
| 📑 Download PDF Report |
| </button> |
| </div> |
| `; |
| } |
| |
| |
| function formatDomainName(domain) { |
| return domain.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); |
| } |
| |
| function createEnhancedReasoningHTML(ensemble, analysis, reasoning) { |
| |
| if (reasoning && reasoning.summary) { |
| |
| const bulletPoints = formatSummaryAsBulletPoints(reasoning.summary, ensemble, analysis); |
| |
| const dominantLabel = |
| ensemble.hybrid_probability > ensemble.synthetic_probability && |
| ensemble.hybrid_probability > ensemble.authentic_probability |
| ? 'Hybrid Probability' |
| : ensemble.synthetic_probability > ensemble.authentic_probability |
| ? 'Synthetic Probability' |
| : 'Authentic Probability'; |
| |
| const dominantValue = |
| Math.max( |
| ensemble.synthetic_probability, |
| ensemble.authentic_probability, |
| ensemble.hybrid_probability |
| ); |
| |
| |
| let processedIndicators = []; |
| if (reasoning.key_indicators && reasoning.key_indicators.length > 0) { |
| processedIndicators = reasoning.key_indicators.map(indicator => { |
| let processedIndicator = indicator; |
| |
| |
| processedIndicator = processedIndicator.replace(/*/g, '*') |
| .replace(/*/g, '*'); |
| |
| |
| processedIndicator = processedIndicator.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>') |
| .replace(/\*([^*]+)\*/g, '<strong>$1</strong>'); |
| |
| |
| processedIndicator = processedIndicator.replace(/\*\*/g, '') |
| .replace(/\*(?![^<]*>)/g, ''); |
| |
| |
| processedIndicator = processedIndicator.replace(/_/g, ' '); |
| |
| |
| return processedIndicator; |
| }); |
| } |
| |
| return ` |
| <div class="reasoning-box enhanced"> |
| <div class="reasoning-header"> |
| <div class="reasoning-icon">💡</div> |
| <div class="reasoning-title">Forensic Reasoning</div> |
| <div class="confidence-tag ${ensemble.overall_confidence >= 0.7 ? 'high-confidence' : ensemble.overall_confidence >= 0.4 ? 'medium-confidence' : 'low-confidence'}"> |
| ${ensemble.overall_confidence >= 0.7 ? 'High Confidence' : ensemble.overall_confidence >= 0.4 ? 'Medium Confidence' : 'Low Confidence'} |
| </div> |
| </div> |
| <div class="verdict-summary"> |
| <div class="verdict-text">${ensemble.final_verdict}</div> |
| ${dominantLabel}: |
| <span class="probability-value">${(dominantValue * 100).toFixed(2)}%</span> |
| </div> |
| <div class="reasoning-bullet-points"> |
| ${bulletPoints} |
| </div> |
| ${processedIndicators.length > 0 ? ` |
| <div class="metrics-breakdown"> |
| <div class="breakdown-header" style="text-align: center; font-weight: 700; color: var(--text-secondary); margin-bottom: 1rem;"> |
| KEY INDICATORS |
| </div> |
| ${processedIndicators.map(indicator => { |
| // Split indicator into metric name and sub-metric details |
| const colonIndex = indicator.indexOf(':'); |
| if (colonIndex !== -1) { |
| const metricName = indicator.substring(0, colonIndex).trim(); |
| const metricDetails = indicator.substring(colonIndex + 1).trim(); |
| |
| return ` |
| <div style="margin-bottom: 1rem; text-align: left;"> |
| <div style="font-weight: 700; color: #fff; text-align: center; margin-bottom: 0.5rem; font-size: 1rem;"> |
| ${metricName} |
| </div> |
| <div style="color: var(--text-secondary); font-size: 0.9rem; line-height: 1.4; text-align: left;"> |
| ${metricDetails} |
| </div> |
| </div> |
| `; |
| } else { |
| // If no colon, treat as general indicator |
| return ` |
| <div style="margin-bottom: 1rem; text-align: left;"> |
| <div style="color: var(--text-secondary); font-size: 0.9rem; line-height: 1.4;"> |
| ${indicator} |
| </div> |
| </div> |
| `; |
| } |
| }).join('')} |
| </div> |
| ` : ''} |
| ${ensemble.consensus_level > 0.7 ? ` |
| <div class="agreement-indicator"> |
| <div class="agreement-icon">✓</div> |
| <div class="agreement-text">Strong metric consensus (${(ensemble.consensus_level * 100).toFixed(1)}%)</div> |
| </div> |
| ` : ''} |
| </div> |
| `; |
| } |
| |
| return ` |
| <div class="reasoning-box"> |
| <div class="reasoning-title">💡 Forensic Reasoning</div> |
| <p class="reasoning-text" style="text-align: left;"> |
| Analysis based on a multi-signal forensic ensemble with domain-aware calibration. |
| The system evaluated linguistic, statistical, and semantic evidence patterns |
| to assess content authenticity with ${(ensemble.overall_confidence * 100).toFixed(1)}% confidence. |
| </p> |
| </div> |
| `; |
| } |
| |
| |
| function formatSummaryAsBulletPoints(summary, ensemble, analysis) { |
| let processedSummary = summary; |
| |
| |
| processedSummary = processedSummary.replace(/*/g, '*') |
| .replace(/*/g, '*'); |
| |
| |
| processedSummary = processedSummary.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>') |
| .replace(/\*([^*]+)\*/g, '<strong>$1</strong>'); |
| |
| |
| processedSummary = processedSummary.replace(/\*\*/g, '') |
| .replace(/\*(?![^<]*>)/g, ''); |
| |
| |
| const sentences = processedSummary.split(/\.\s+/); |
| |
| |
| const bulletPoints = []; |
| |
| |
| const confidenceLevel = ensemble.overall_confidence >= 0.7 ? 'High Confidence' : |
| ensemble.overall_confidence >= 0.4 ? 'Medium Confidence' : 'Low Confidence'; |
| bulletPoints.push(`<div class="bullet-point">• ${confidenceLevel}</div>`); |
| |
| |
| bulletPoints.push(`<div class="bullet-point">• ${ensemble.final_verdict}</div>`); |
| |
| |
| bulletPoints.push(`<div class="bullet-point">• Synthetic Probability: ${(ensemble.synthetic_probability * 100).toFixed(2)}%</div>`); |
| |
| |
| sentences.forEach(sentence => { |
| if (sentence.trim() && |
| !sentence.includes('confidence') && |
| !sentence.includes(ensemble.final_verdict) && |
| !sentence.includes('Synthetic probability')) { |
| |
| let cleanSentence = sentence.trim(); |
| if (!cleanSentence.endsWith('.')) { |
| cleanSentence += '.'; |
| } |
| bulletPoints.push(`<div class="bullet-point">• ${cleanSentence}</div>`); |
| } |
| }); |
| |
| return bulletPoints.join(''); |
| } |
| |
| function displayHighlightedText(html) { |
| document.getElementById('highlighted-report').innerHTML = ` |
| ${createDefaultLegend()} |
| <div class="highlighted-text"> |
| ${html} |
| </div> |
| ${getHighlightStyles()} |
| `; |
| } |
| |
| function createDefaultLegend() { |
| return ` |
| <div class="highlight-legend"> |
| <div class="legend-item"> |
| <div class="legend-color" style="background: #dcfce7;"></div> |
| <div class="legend-label">Authentic</div> |
| </div> |
| <div class="legend-item"> |
| <div class="legend-color" style="background: #fef9c3;"></div> |
| <div class="legend-label">Uncertain</div> |
| </div> |
| <div class="legend-item"> |
| <div class="legend-color" style="background: #fee2e2;"></div> |
| <div class="legend-label">Synthetic</div> |
| </div> |
| <div class="legend-item"> |
| <div class="legend-color" style="background: #e9d5ff;"></div> |
| <div class="legend-label">Hybrid</div> |
| </div> |
| </div> |
| `; |
| } |
| |
| function getHighlightStyles() { |
| return ` |
| <style> |
| #highlighted-report .highlight { |
| padding: 2px 4px; |
| margin: 0 1px; |
| border-radius: 3px; |
| cursor: help; |
| transition: all 0.2s; |
| border-bottom: 2px solid transparent; |
| color: #000000 !important; |
| font-weight: 500; |
| } |
| #highlighted-report .highlight:hover { |
| transform: translateY(-1px); |
| box-shadow: 0 4px 12px rgba(0,0,0,0.15); |
| z-index: 10; |
| text-shadow: 0 1px 1px rgba(255,255,255,0.8); |
| } |
| #highlighted-report .very-high-synthetic { |
| background-color: #fee2e2 !important; |
| border-bottom-color: #ef4444 !important; |
| } |
| #highlighted-report .high-synthetic { |
| background-color: #fed7aa !important; |
| border-bottom-color: #f97316 !important; |
| } |
| #highlighted-report .medium-synthetic { |
| background-color: #fef3c7 !important; |
| border-bottom-color: #f59e0b !important; |
| } |
| #highlighted-report .uncertain { |
| background-color: #fef9c3 !important; |
| border-bottom-color: #fbbf24 !important; |
| } |
| #highlighted-report .medium-authentic { |
| background-color: #ecfccb !important; |
| border-bottom-color: #a3e635 !important; |
| } |
| #highlighted-report .high-authentic { |
| background-color: #bbf7d0 !important; |
| border-bottom-color: #4ade80 !important; |
| } |
| #highlighted-report .very-high-authentic { |
| background-color: #dcfce7 !important; |
| border-bottom-color: #22c55e !important; |
| } |
| #highlighted-report .hybrid-content { |
| background-color: #e9d5ff !important; |
| border-bottom-color: #a855f7 !important; |
| background-image: repeating-linear-gradient(45deg, transparent, transparent 5px, rgba(168, 85, 247, 0.1) 5px, rgba(168, 85, 247, 0.1) 10px) !important; |
| } |
| </style> |
| `; |
| } |
| |
| function displayMetricsCarousel(metrics, analysis, ensemble) { |
| const metricOrder = ['structural', 'perplexity', 'entropy', 'semantic_analysis', 'linguistic', 'multi_perturbation_stability']; |
| const availableMetrics = metricOrder.filter(key => metrics[key]); |
| totalMetrics = availableMetrics.length; |
| |
| if (totalMetrics === 0) { |
| document.getElementById('metrics-report').innerHTML = ` |
| <div class="empty-state"> |
| <p class="empty-description">No metric details available</p> |
| </div> |
| `; |
| return; |
| } |
| |
| let carouselHTML = ` |
| <div class="metrics-carousel-container"> |
| <div class="metrics-carousel-content"> |
| `; |
| |
| availableMetrics.forEach((metricKey, index) => { |
| const metric = metrics[metricKey]; |
| if (!metric) return; |
| |
| const syntheticProb = (metric.synthetic_probability * 100).toFixed(1); |
| const authenticProb = (metric.authentic_probability * 100).toFixed(1); |
| const hybridProb = (metric.hybrid_probability * 100).toFixed(1); |
| const confidence = (metric.confidence * 100).toFixed(1); |
| const weight = ensemble.metric_contributions && ensemble.metric_contributions[metricKey] ? |
| (ensemble.metric_contributions[metricKey].weight * 100).toFixed(1) : '0.0'; |
| |
| |
| let verdictText, verdictClass; |
| if (metric.hybrid_probability > 0.3) { |
| verdictText = 'Hybrid'; |
| verdictClass = 'verdict-hybrid'; |
| } else if (metric.synthetic_probability >= 0.6) { |
| verdictText = 'Synthetic'; |
| verdictClass = 'verdict-synthetic'; |
| } else if (metric.synthetic_probability >= 0.4) { |
| verdictText = 'Uncertain'; |
| verdictClass = 'verdict-uncertain'; |
| } else { |
| verdictText = 'Authentic'; |
| verdictClass = 'verdict-authentic'; |
| } |
| |
| carouselHTML += ` |
| <div class="metric-slide ${index === 0 ? 'active' : ''}" data-metric-index="${index}"> |
| <div class="metric-result-card"> |
| <div class="metric-header"> |
| <div class="metric-name">${formatMetricName(metricKey)}</div> |
| </div> |
| <div class="metric-description"> |
| ${getMetricDescription(metricKey)} |
| </div> |
| |
| <!-- Probability Display with Hybrid --> |
| <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1rem; margin: 1rem 0;"> |
| <div style="text-align: center;"> |
| <div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">Synthetic</div> |
| <div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;"> |
| <div style="background: var(--danger); height: 100%; width: ${syntheticProb}%; transition: width 0.5s;"></div> |
| </div> |
| <div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${syntheticProb}%</div> |
| </div> |
| <div style="text-align: center;"> |
| <div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">Authentic</div> |
| <div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;"> |
| <div style="background: var(--success); height: 100%; width: ${authenticProb}%; transition: width 0.5s;"></div> |
| </div> |
| <div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${authenticProb}%</div> |
| </div> |
| <div style="text-align: center;"> |
| <div style="font-size: 0.75rem; color: var(--text-muted); margin-bottom: 0.25rem;">Hybrid</div> |
| <div style="background: rgba(51, 65, 85, 0.5); height: 8px; border-radius: 4px; overflow: hidden;"> |
| <div style="background: var(--primary); height: 100%; width: ${hybridProb}%; transition: width 0.5s;"></div> |
| </div> |
| <div style="font-size: 0.85rem; font-weight: 600; margin-top: 0.25rem;">${hybridProb}%</div> |
| </div> |
| </div> |
| |
| <div style="display: flex; justify-content: space-between; align-items: center; margin: 0.75rem 0;"> |
| <span class="metric-verdict ${verdictClass}">${verdictText}</span> |
| <span style="font-size: 0.85rem; color: var(--text-secondary);">Confidence: ${confidence}% | Weight: ${weight}%</span> |
| </div> |
| |
| ${metric.details && Object.keys(metric.details).length > 0 ? renderMetricDetails(metricKey, metric.details) : '<div style="color: var(--text-muted); font-size: 0.85rem; margin-top: 1rem; text-align: center; padding: 1rem;">No detailed metrics available for this metric</div>'} |
| </div> |
| </div> |
| `; |
| }); |
| |
| carouselHTML += ` |
| </div> |
| <div class="metrics-carousel-nav"> |
| <button class="carousel-btn prev-btn" onclick="navigateMetrics(-1)" ${currentMetricIndex === 0 ? 'disabled' : ''}>← Previous</button> |
| <div class="carousel-position">${currentMetricIndex + 1} / ${totalMetrics}</div> |
| <button class="carousel-btn next-btn" onclick="navigateMetrics(1)" ${currentMetricIndex === totalMetrics - 1 ? 'disabled' : ''}>Next →</button> |
| </div> |
| </div> |
| `; |
| |
| document.getElementById('metrics-report').innerHTML = carouselHTML; |
| updateCarouselButtons(); |
| } |
| |
| function navigateMetrics(direction) { |
| const newMetricIndex = currentMetricIndex + direction; |
| if (newMetricIndex >= 0 && newMetricIndex < totalMetrics) { |
| currentMetricIndex = newMetricIndex; |
| updateMetricCarousel(); |
| } |
| } |
| |
| function updateMetricCarousel() { |
| const slides = document.querySelectorAll('.metric-slide'); |
| slides.forEach((slide, index) => { |
| if (index === currentMetricIndex) { |
| slide.classList.add('active'); |
| } else { |
| slide.classList.remove('active'); |
| } |
| }); |
| updateCarouselButtons(); |
| |
| const positionElement = document.querySelector('.carousel-position'); |
| if (positionElement) { |
| positionElement.textContent = `${currentMetricIndex + 1} / ${totalMetrics}`; |
| } |
| } |
| |
| function updateCarouselButtons() { |
| const prevBtn = document.querySelector('.prev-btn'); |
| const nextBtn = document.querySelector('.next-btn'); |
| if (prevBtn) { |
| prevBtn.disabled = currentMetricIndex === 0; |
| } |
| if (nextBtn) { |
| nextBtn.disabled = currentMetricIndex === totalMetrics - 1; |
| } |
| } |
| |
| function renderMetricDetails(metricName, details) { |
| |
| if (!details || Object.keys(details).length === 0) { |
| return ''; |
| } |
| |
| let detailsHTML = '<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--border);">'; |
| detailsHTML += '<div style="font-size: 0.9rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.75rem;">📈 Detailed Metrics:</div>'; |
| detailsHTML += '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 0.75rem; font-size: 0.85rem;">'; |
| |
| |
| Object.keys(details).forEach(key => { |
| if (details[key] !== undefined && details[key] !== null) { |
| let value = details[key]; |
| let displayValue; |
| |
| |
| if (typeof value === 'number') { |
| |
| if (value >= 0 && value <= 1 && !Number.isInteger(value)) { |
| |
| displayValue = (value * 100).toFixed(2) + '%'; |
| } else if (Number.isInteger(value)) { |
| |
| displayValue = value; |
| } else { |
| |
| displayValue = value.toFixed(2); |
| } |
| } else if (typeof value === 'boolean') { |
| displayValue = value ? 'Yes' : 'No'; |
| } else { |
| displayValue = String(value); |
| } |
| |
| |
| const label = key |
| .replace(/_/g, ' ') |
| .replace(/\b\w/g, c => c.toUpperCase()); |
| |
| detailsHTML += ` |
| <div style="background: rgba(15, 23, 42, 0.6); padding: 0.5rem; border-radius: 6px;"> |
| <div style="color: var(--text-muted); font-size: 0.75rem; margin-bottom: 0.25rem;">${label}</div> |
| <div style="color: var(--primary); font-weight: 700;">${displayValue}</div> |
| </div> |
| `; |
| } |
| }); |
| |
| detailsHTML += '</div></div>'; |
| return detailsHTML; |
| } |
| |
| function getMetricDescription(metricName) { |
| const descriptions = { |
| structural: 'Analyzes sentence structure, length patterns, and statistical features.', |
| perplexity: 'Measures text predictability using language model cross-entropy.', |
| entropy: 'Evaluates token diversity and sequence unpredictability.', |
| semantic_analysis: 'Examines semantic coherence, topic consistency, and logical flow.', |
| linguistic: 'Assesses grammatical patterns, syntactic complexity, and style markers.', |
| multi_perturbation_stability: 'Tests text stability under perturbation using curvature analysis.' |
| }; |
| return descriptions[metricName] || 'Metric analysis complete.'; |
| } |
| |
| function formatMetricName(name) { |
| const names = { |
| structural: 'Structural Analysis', |
| perplexity: 'Perplexity', |
| entropy: 'Entropy', |
| semantic_analysis: 'Semantic Analysis', |
| linguistic: 'Linguistic Analysis', |
| multi_perturbation_stability: 'Multi-Perturbation Stability' |
| }; |
| return names[name] || name.split('_').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); |
| } |
| |
| async function downloadReport(format) { |
| if (!currentAnalysisData) { |
| alert('No analysis data available'); |
| return; |
| } |
| |
| try { |
| const analysisId = currentAnalysisData.analysis_id; |
| const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); |
| |
| |
| if (format === 'json') { |
| const data = { |
| ...currentAnalysisData, |
| download_timestamp: new Date().toISOString(), |
| report_version: '2.0.0' |
| }; |
| const blob = new Blob([JSON.stringify(data, null, 2)], { |
| type: 'application/json' |
| }); |
| const filename = `text-forensics-report-${analysisId}-${timestamp}.json`; |
| await downloadBlob(blob, filename); |
| return; |
| } |
| |
| |
| const activeTab = document.querySelector('.input-tab.active').dataset.tab; |
| let textToSend = ''; |
| if (activeTab === 'paste') { |
| textToSend = document.getElementById('text-input').value; |
| } else { |
| textToSend = currentAnalysisData.detection_result?.processed_text?.text || |
| 'Uploaded file content - see analysis for details'; |
| } |
| |
| |
| const formData = new FormData(); |
| formData.append('analysis_id', analysisId); |
| formData.append('text', textToSend); |
| formData.append('formats', format); |
| formData.append('include_highlights', document.getElementById('enable-highlighting').checked.toString()); |
| |
| const response = await fetch(`${API_BASE}/api/report/generate`, { |
| method: 'POST', |
| body: formData |
| }); |
| |
| if (!response.ok) { |
| throw new Error('Report generation failed'); |
| } |
| |
| const result = await response.json(); |
| if (result.reports && result.reports[format]) { |
| const filename = result.reports[format]; |
| const downloadResponse = await fetch(`${API_BASE}/api/report/download/${filename}`); |
| if (!downloadResponse.ok) { |
| throw new Error('Failed to download file'); |
| } |
| const blob = await downloadResponse.blob(); |
| const downloadFilename = `text-forensics-report-${format}-report-${analysisId}-${timestamp}.${format}`; |
| await downloadBlob(blob, downloadFilename); |
| } else { |
| alert('Report file not available'); |
| } |
| } catch (error) { |
| console.error('Download error:', error); |
| alert('Failed to download report. Please try again.'); |
| } |
| } |
| |
| async function downloadBlob(blob, filename) { |
| try { |
| const url = URL.createObjectURL(blob); |
| const a = document.createElement('a'); |
| a.href = url; |
| a.download = filename; |
| a.style.display = 'none'; |
| document.body.appendChild(a); |
| a.click(); |
| setTimeout(() => { |
| document.body.removeChild(a); |
| URL.revokeObjectURL(url); |
| showDownloadSuccess(filename); |
| }, 100); |
| } catch (error) { |
| console.error('Download failed:', error); |
| alert('Download failed. Please try again.'); |
| } |
| } |
| |
| function showDownloadSuccess(filename) { |
| const notification = document.createElement('div'); |
| notification.style.cssText = ` |
| position: fixed; |
| top: 20px; |
| right: 20px; |
| background: var(--success); |
| color: white; |
| padding: 1rem 1.5rem; |
| border-radius: 8px; |
| font-weight: 600; |
| box-shadow: 0 4px 12px rgba(0,0,0,0.3); |
| z-index: 10000; |
| animation: slideIn 0.3s ease; |
| `; |
| notification.innerHTML = ` |
| <div style="display: flex; align-items: center; gap: 0.5rem;"> |
| <span>✓</span> |
| <span>Downloaded: ${filename}</span> |
| </div> |
| `; |
| document.body.appendChild(notification); |
| |
| if (!document.querySelector('#download-animation')) { |
| const style = document.createElement('style'); |
| style.id = 'download-animation'; |
| style.textContent = ` |
| @keyframes slideIn { |
| from { transform: translateX(100%); opacity: 0; } |
| to { transform: translateX(0); opacity: 1; } |
| } |
| `; |
| document.head.appendChild(style); |
| } |
| |
| setTimeout(() => { |
| if (notification.parentNode) { |
| notification.parentNode.removeChild(notification); |
| } |
| }, 3000); |
| } |
| |
| |
| document.querySelectorAll('a[href^="#"]').forEach(anchor => { |
| anchor.addEventListener('click', function (e) { |
| const href = this.getAttribute('href'); |
| if (href !== '#') { |
| e.preventDefault(); |
| const target = document.querySelector(href); |
| if (target) { |
| target.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
| } |
| } |
| }); |
| }); |
| |
| |
| showLanding(); |
| </script> |
| </body> |
| </html> |