proofly / templates /results.html
Pragthedon's picture
Initial backend API deployment
4f48a4e
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Results - Proofly</title>
<meta name="description" content="Fact-checking results with evidence and NLI analysis">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap"
rel="stylesheet">
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<!-- Apply saved theme immediately to prevent flash -->
<script>if (localStorage.getItem('proofly-theme') === 'dark') document.documentElement.setAttribute('data-theme', 'dark');</script>
<style>
.results-scroll-area {
flex: 1;
overflow-y: auto;
padding: 0 3rem 3rem 3rem;
}
.results-header {
margin-bottom: 2rem;
}
/* Verdict Hero Card */
.verdict-card {
background: var(--bg-card);
border-radius: var(--radius-lg);
padding: 2.5rem;
box-shadow: var(--shadow-sm);
border: 1px solid var(--border-color);
margin-bottom: 2rem;
text-align: center;
position: relative;
overflow: hidden;
transition: background 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
}
/* Coloured top accent bar */
.verdict-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
}
.verdict-card.true::before {
background: #10b981;
}
.verdict-card.false::before {
background: #ef4444;
}
.verdict-card.uncertain::before {
background: #f59e0b;
}
/* Coloured glow border around the whole card */
.verdict-card.true {
border-color: rgba(16, 185, 129, 0.45);
box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.25),
0 8px 32px rgba(16, 185, 129, 0.18);
}
.verdict-card.false {
border-color: rgba(239, 68, 68, 0.45);
box-shadow: 0 0 0 1px rgba(239, 68, 68, 0.25),
0 8px 32px rgba(239, 68, 68, 0.18);
}
.verdict-card.uncertain {
border-color: rgba(245, 158, 11, 0.45);
box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.25),
0 8px 32px rgba(245, 158, 11, 0.18);
}
.verdict-badge {
display: inline-block;
padding: 0.5rem 1.5rem;
border-radius: 50px;
font-weight: 700;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 1rem;
}
.verdict-badge.true {
color: #10b981;
background: rgba(16, 185, 129, 0.1);
}
.verdict-badge.false {
color: #ef4444;
background: rgba(239, 68, 68, 0.1);
}
.verdict-badge.uncertain {
color: #f59e0b;
background: rgba(245, 158, 11, 0.1);
}
.verdict-title {
font-size: 2rem;
font-weight: 500;
color: var(--text-main);
margin-bottom: 1.5rem;
line-height: 1.2;
}
.verdict-confidence-bar {
width: 260px;
height: 8px;
background: var(--border-color);
border-radius: 99px;
margin: 12px auto 0;
position: relative;
overflow: hidden;
}
.verdict-confidence-fill {
position: absolute;
left: 0;
top: 0;
height: 100%;
border-radius: 99px;
transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Fill colours driven by the parent verdict-card class */
.verdict-card.true .verdict-confidence-fill {
background: linear-gradient(90deg, #059669, #10b981);
}
.verdict-card.false .verdict-confidence-fill {
background: linear-gradient(90deg, #b91c1c, #ef4444);
}
.verdict-card.uncertain .verdict-confidence-fill {
background: linear-gradient(90deg, #b45309, #f59e0b);
}
/* Evidence full-width */
.evidence-section {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 2rem;
margin-bottom: 1.5rem;
box-shadow: var(--shadow-sm);
transition: background 0.3s ease, border-color 0.3s ease;
}
.section-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
}
.section-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-main);
}
/* Evidence/NLI Items */
.detail-item {
background: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: 1.5rem;
margin-bottom: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.04);
transition: var(--transition);
}
.detail-item:hover {
box-shadow: var(--shadow-soft);
transform: translateY(-2px);
}
.source-tag {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
color: var(--primary);
background: rgba(37, 99, 235, 0.1);
padding: 0.25rem 0.75rem;
border-radius: 50px;
letter-spacing: 0.05em;
}
.nli-status {
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem;
align-items: center;
}
.status-entailment {
color: #10b981;
}
.status-contradiction {
color: #ef4444;
}
.status-neutral {
color: #64748b;
}
.nli-confidence-meter {
height: 4px;
background: var(--border-color);
border-radius: 2px;
margin-top: 1rem;
overflow: hidden;
}
.nli-meter-fill {
height: 100%;
border-radius: 2px;
background: currentColor;
}
/* Empty/Error State */
.empty-glass {
background: var(--bg-input);
border: 1px dashed var(--text-light);
border-radius: var(--radius-md);
text-align: center;
padding: 3rem 2rem;
color: var(--text-muted);
}
/* AI Analysis Toggle Button */
.ai-toggle-btn {
display: inline-flex;
align-items: center;
gap: 0.6rem;
background: transparent;
color: var(--primary);
border: 1.5px solid var(--primary);
border-radius: 10px;
padding: 0.65rem 1.4rem;
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
font-family: inherit;
transition: all 0.2s ease;
margin-bottom: 1.5rem;
}
.ai-toggle-btn:hover {
background: var(--primary);
color: white;
}
.ai-toggle-btn i {
font-size: 1.1rem;
transition: transform 0.3s ease;
}
.ai-toggle-btn.open i.toggle-chevron {
transform: rotate(180deg);
}
/* AI Analysis collapsible panel */
.ai-analysis-panel {
display: none;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: 2rem;
margin-bottom: 1.5rem;
box-shadow: var(--shadow-sm);
animation: slideDown 0.25s ease;
transition: background 0.3s ease, border-color 0.3s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.ai-analysis-panel.visible {
display: block;
}
/* Hide scrollbar cleanly */
.results-scroll-area::-webkit-scrollbar {
width: 8px;
}
.results-scroll-area::-webkit-scrollbar-track {
background: transparent;
}
.results-scroll-area::-webkit-scrollbar-thumb {
background: var(--text-light);
border-radius: 4px;
}
.results-scroll-area::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
</style>
</head>
<body>
<div class="app-container">
<!-- Sidebar Navigation -->
<aside class="sidebar">
<div class="sidebar-top">
<a href="/" class="icon-btn" title="New Check" style="text-decoration:none;"><i
class="ph ph-plus"></i></a>
<div class="spacer"></div>
<a href="/history" class="nav-btn" title="My History" style="text-decoration:none;"><i
class="ph ph-clock-counter-clockwise"></i></a>
{% if g.is_admin %}
<a href="/admin" class="nav-btn" title="God Mode"
style="text-decoration:none; color: var(--primary);"><i class="ph ph-shield-check"></i></a>
{% endif %}
</div>
<div class="sidebar-bottom">
<button class="theme-toggle-btn" title="Toggle dark / light mode" onclick="toggleTheme()">
<i class="ph ph-moon icon-moon"></i>
<i class="ph ph-sun icon-sun"></i>
</button>
<div class="profile-menu-container">
<div class="profile-btn" onclick="toggleProfileMenu()" title="{{ g.username }}"
style="background: transparent; padding: 0; width: 44px; height: 44px;">
<img src="{{ url_for('static', filename='default_profile.svg') }}" alt="Profile"
style="width: 100%; height: 100%; border-radius: 50%; object-fit: cover; border: 2px solid var(--border-color); background: var(--bg-input);">
</div>
<div class="profile-dropdown" id="profileDropdown">
<div class="dropdown-header">
<span class="dropdown-username">{{ g.username }}</span>
</div>
<a href="{{ url_for('auth.logout') }}" class="dropdown-item danger">
<i class="ph ph-sign-out"></i> Logout
</a>
</div>
</div>
</div>
</aside>
<!-- Main Content Area -->
<main class="main-content">
<!-- Top Header -->
<header class="top-header" style="opacity: 0.9;">
<div class="header-left">
<a href="/" style="text-decoration: none; color: inherit;">
<button class="assistant-selector">
<i class="ph ph-arrow-left"></i> Back to Search
</button>
</a>
</div>
<div class="header-center">
<span class="daily-text">Analysis Results</span>
</div>
<div class="header-right" style="display:flex; align-items:center; gap:1rem;">
</div>
</header>
<div class="results-scroll-area">
{% if result.success %}
{% if result.verdict %}
<div class="verdict-card {{ result.verdict|lower }}">
<span class="verdict-badge {{ result.verdict|lower }}">{{ result.verdict }}</span>
<h2 class="verdict-title">"{{ result.claim }}"</h2>
<div class="verdict-confidence" style="color: var(--text-muted); font-size: 0.95rem;">
Confidence: <strong style="color: inherit;">{{ (result.confidence * 100)|round(0) }}%</strong>
<div class="verdict-confidence-bar">
<div class="verdict-confidence-fill"
style="width: {{ (result.confidence * 100)|round(0) }}%;"></div>
</div>
</div>
</div>
{% endif %}
<!-- Evidence Section (always visible, full-width) -->
<section class="evidence-section">
<div class="section-header">
<i class="ph-fill ph-magnifying-glass" style="font-size: 1.5rem; color: var(--primary);"></i>
<h3 class="section-title">Evidence Sources</h3>
<span
style="margin-left: auto; font-size: 0.85rem; color: var(--text-light); font-weight: 500;">
{{ result.evidence|length }} found
</span>
</div>
{% if result.evidence %}
{% for evidence in result.evidence %}
<div class="detail-item">
<div
style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<span class="source-tag">{{ evidence.source }}</span>
<span style="font-size: 0.8rem; font-weight: 600; color: var(--text-light);">
<i class="ph-fill ph-target"></i> {{ (evidence.similarity * 100)|round(0) }}% match
</span>
</div>
<p style="font-size: 0.95rem; color: var(--text-main); line-height: 1.6;">{{ evidence.text
}}</p>
</div>
{% endfor %}
{% else %}
<div class="empty-glass">
<i class="ph ph-mask-sad"
style="font-size: 3rem; margin-bottom: 1rem; color: var(--text-light);"></i>
<p>No direct evidence found for this claim.</p>
</div>
{% endif %}
</section>
<!-- AI Analysis Toggle -->
{% if result.nli_results %}
<button class="ai-toggle-btn" id="aiToggleBtn" onclick="toggleAIAnalysis()">
<i class="ph-fill ph-brain"></i>
<span id="aiToggleBtnText">Show AI Analysis</span>
<i class="ph ph-caret-down toggle-chevron"></i>
</button>
<!-- AI Analysis Panel (hidden by default) -->
<div class="ai-analysis-panel" id="aiAnalysisPanel">
<div class="section-header">
<i class="ph-fill ph-brain" style="font-size: 1.5rem; color: var(--primary);"></i>
<h3 class="section-title">AI Analysis</h3>
<span
style="margin-left: auto; font-size: 0.85rem; color: var(--text-light); font-weight: 500;">
{{ result.nli_results|length }} analyses
</span>
</div>
{% for nli in result.nli_results %}
<div class="detail-item">
<div class="nli-status">
<span style="font-weight: 600; font-size: 0.9rem;" class="status-{{ nli.label|lower }}">
{% if 'entail' in nli.label|lower %}
<i class="ph-fill ph-check-circle"></i> Supports
{% elif 'contradict' in nli.label|lower %}
<i class="ph-fill ph-x-circle"></i> Contradicts
{% else %}
<i class="ph-fill ph-minus-circle"></i> Neutral
{% endif %}
</span>
<span style="font-size: 0.85rem; font-weight: 600; color: var(--text-light);">
{{ (nli.score * 100)|round(1) }}%
</span>
</div>
<p style="font-size: 0.9rem; color: var(--text-muted); line-height: 1.5; margin-bottom: 0;">
{{ nli.evidence }}
</p>
<div class="nli-confidence-meter status-{{ nli.label|lower }}">
<div class="nli-meter-fill" style="width: {{ (nli.score * 100)|round(1) }}%;"></div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% else %}
<!-- Error State -->
<div class="verdict-card false" style="max-width: 600px; margin: 4rem auto;">
<i class="ph-fill ph-warning-circle"
style="font-size: 4rem; color: #ef4444; margin-bottom: 1rem;"></i>
<h2 style="margin-bottom: 1rem; color: var(--text-main);">Analysis Failed</h2>
<p style="color: var(--text-muted); margin-bottom: 2rem;">{{ result.error }}</p>
<a href="/"
style="display: inline-block; background: #0f172a; color: white; padding: 0.75rem 1.5rem; text-decoration: none; border-radius: var(--radius-sm); font-weight: 500;">
Try Again
</a>
</div>
{% endif %}
</div>
</main>
</div>
<script>
function toggleTheme() {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
if (isDark) {
document.documentElement.removeAttribute('data-theme');
localStorage.setItem('proofly-theme', 'light');
} else {
document.documentElement.setAttribute('data-theme', 'dark');
localStorage.setItem('proofly-theme', 'dark');
}
}
function toggleAIAnalysis() {
const panel = document.getElementById('aiAnalysisPanel');
const btn = document.getElementById('aiToggleBtn');
const btnText = document.getElementById('aiToggleBtnText');
const isOpen = panel.classList.contains('visible');
if (isOpen) {
panel.classList.remove('visible');
btn.classList.remove('open');
btnText.textContent = 'Show AI Analysis';
} else {
panel.classList.add('visible');
btn.classList.add('open');
btnText.textContent = 'Hide AI Analysis';
}
}
function toggleProfileMenu() {
const menu = document.getElementById('profileDropdown');
if (menu) menu.classList.toggle('open');
}
document.addEventListener('click', (e) => {
const container = document.querySelector('.profile-menu-container');
const menu = document.getElementById('profileDropdown');
if (container && menu && !container.contains(e.target)) {
menu.classList.remove('open');
}
});
</script>
</body>
</html>