| | <!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> |