Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>History – Proofly</title> | |
| <meta name="description" content="Your fact-checking history"> | |
| <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <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> | |
| <script>if (localStorage.getItem('proofly-theme') === 'dark') document.documentElement.setAttribute('data-theme', 'dark');</script> | |
| <style> | |
| .history-scroll { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 0 3rem 3rem; | |
| } | |
| .history-scroll::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| .history-scroll::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| .history-scroll::-webkit-scrollbar-thumb { | |
| background: var(--text-light); | |
| border-radius: 4px; | |
| } | |
| .history-scroll::-webkit-scrollbar-thumb:hover { | |
| background: var(--text-muted); | |
| } | |
| .page-header-row { | |
| display: flex; | |
| align-items: baseline; | |
| justify-content: space-between; | |
| margin-bottom: 0.25rem; | |
| } | |
| .page-title { | |
| font-size: 1.4rem; | |
| font-weight: 700; | |
| color: var(--text-main); | |
| } | |
| .page-subtitle { | |
| font-size: 0.9rem; | |
| color: var(--text-muted); | |
| margin-bottom: 2rem; | |
| } | |
| /* Clear All button */ | |
| .clear-all-btn { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.4rem; | |
| padding: 0.45rem 1rem; | |
| border: 1.5px solid rgba(239, 68, 68, 0.35); | |
| border-radius: 50px; | |
| background: transparent; | |
| color: #ef4444; | |
| font-family: inherit; | |
| font-size: 0.82rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| .clear-all-btn:hover { | |
| background: rgba(239, 68, 68, 0.08); | |
| border-color: #ef4444; | |
| } | |
| .history-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| } | |
| .history-table th { | |
| text-align: left; | |
| font-size: 0.78rem; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| color: var(--text-light); | |
| padding: 0 1rem 0.75rem; | |
| border-bottom: 1px solid var(--border-color); | |
| } | |
| .history-row { | |
| background: var(--bg-card); | |
| border-bottom: 1px solid var(--border-color); | |
| transition: background 0.15s ease; | |
| } | |
| .history-row:last-of-type { | |
| border-bottom: none; | |
| } | |
| .history-row:hover { | |
| background: var(--bg-input); | |
| } | |
| .history-row td { | |
| padding: 1rem; | |
| font-size: 0.9rem; | |
| color: var(--text-main); | |
| vertical-align: middle; | |
| } | |
| .claim-cell { | |
| max-width: 350px; | |
| } | |
| .claim-text { | |
| font-weight: 500; | |
| line-height: 1.4; | |
| } | |
| .verdict-pill { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.3rem; | |
| padding: 0.3rem 0.8rem; | |
| border-radius: 50px; | |
| font-size: 0.78rem; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| white-space: nowrap; | |
| } | |
| .verdict-pill.true { | |
| color: #10b981; | |
| background: rgba(16, 185, 129, 0.12); | |
| } | |
| .verdict-pill.false { | |
| color: #ef4444; | |
| background: rgba(239, 68, 68, 0.12); | |
| } | |
| .verdict-pill.uncertain, | |
| .verdict-pill.mixture-uncertain { | |
| color: #f59e0b; | |
| background: rgba(245, 158, 11, 0.12); | |
| } | |
| .conf-bar { | |
| width: 80px; | |
| height: 5px; | |
| background: var(--border-color); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| margin-top: 4px; | |
| } | |
| .conf-fill { | |
| height: 100%; | |
| border-radius: 3px; | |
| background: var(--primary); | |
| } | |
| .date-cell { | |
| font-size: 0.82rem; | |
| color: var(--text-muted); | |
| white-space: nowrap; | |
| } | |
| /* Delete button (per row) */ | |
| .delete-btn { | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 50%; | |
| border: none; | |
| background: transparent; | |
| color: var(--text-light); | |
| font-size: 1rem; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: var(--transition); | |
| } | |
| .delete-btn:hover { | |
| background: rgba(239, 68, 68, 0.1); | |
| color: #ef4444; | |
| } | |
| .empty-state { | |
| text-align: center; | |
| padding: 5rem 2rem; | |
| color: var(--text-muted); | |
| } | |
| .empty-state i { | |
| font-size: 3.5rem; | |
| margin-bottom: 1rem; | |
| color: var(--text-light); | |
| } | |
| .empty-state h3 { | |
| font-size: 1.1rem; | |
| font-weight: 600; | |
| margin-bottom: 0.5rem; | |
| color: var(--text-main); | |
| } | |
| .empty-state a { | |
| display: inline-block; | |
| margin-top: 1.25rem; | |
| background: var(--primary); | |
| color: white; | |
| padding: 0.65rem 1.5rem; | |
| border-radius: 50px; | |
| text-decoration: none; | |
| font-weight: 600; | |
| font-size: 0.9rem; | |
| transition: var(--transition); | |
| } | |
| .empty-state a:hover { | |
| opacity: 0.85; | |
| transform: translateY(-1px); | |
| } | |
| .count-badge { | |
| display: inline-block; | |
| background: var(--bg-input); | |
| border: 1px solid var(--border-color); | |
| color: var(--text-muted); | |
| border-radius: 50px; | |
| padding: 0.2rem 0.7rem; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| margin-left: 0.5rem; | |
| vertical-align: middle; | |
| } | |
| /* ── Confirmation Modal ── */ | |
| .modal-backdrop { | |
| display: none; | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0, 0, 0, 0.5); | |
| backdrop-filter: blur(4px); | |
| z-index: 100; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .modal-backdrop.open { | |
| display: flex; | |
| } | |
| .modal { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-lg); | |
| padding: 2rem; | |
| width: 100%; | |
| max-width: 380px; | |
| box-shadow: var(--app-shadow); | |
| animation: slideDown 0.2s ease; | |
| } | |
| @keyframes slideDown { | |
| from { | |
| opacity: 0; | |
| transform: translateY(-12px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .modal h3 { | |
| font-size: 1.1rem; | |
| font-weight: 700; | |
| color: var(--text-main); | |
| margin-bottom: 0.5rem; | |
| } | |
| .modal p { | |
| font-size: 0.9rem; | |
| color: var(--text-muted); | |
| margin-bottom: 1.5rem; | |
| line-height: 1.5; | |
| } | |
| .modal-actions { | |
| display: flex; | |
| gap: 0.75rem; | |
| justify-content: flex-end; | |
| } | |
| .modal-cancel { | |
| padding: 0.55rem 1.2rem; | |
| border: 1px solid var(--border-color); | |
| border-radius: var(--radius-sm); | |
| background: var(--bg-input); | |
| color: var(--text-main); | |
| font-family: inherit; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| .modal-cancel:hover { | |
| background: var(--border-color); | |
| } | |
| .modal-confirm { | |
| padding: 0.55rem 1.2rem; | |
| border: none; | |
| border-radius: var(--radius-sm); | |
| background: #ef4444; | |
| color: white; | |
| font-family: inherit; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| } | |
| .modal-confirm:hover { | |
| background: #dc2626; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app-container"> | |
| <!-- Sidebar --> | |
| <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 active-icon" title="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 --> | |
| <main class="main-content"> | |
| <header class="top-header"> | |
| <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">Claim History</span> | |
| </div> | |
| <div class="header-right" style="display:flex; align-items:center; gap:1rem;"> | |
| </div> | |
| </header> | |
| <div class="history-scroll"> | |
| <div class="page-header-row"> | |
| <div> | |
| <span class="page-title"> | |
| Your Searches | |
| {% if records %} | |
| <span class="count-badge">{{ records|length }}</span> | |
| {% endif %} | |
| </span> | |
| </div> | |
| {% if records %} | |
| <button class="clear-all-btn" onclick="openClearModal()"> | |
| <i class="ph ph-trash"></i> Clear All | |
| </button> | |
| {% endif %} | |
| </div> | |
| <div class="page-subtitle">Every claim you've checked with Proofly</div> | |
| {% if records %} | |
| <table class="history-table"> | |
| <thead> | |
| <tr> | |
| <th>Claim</th> | |
| <th>Verdict</th> | |
| <th>Confidence</th> | |
| <th>Evidence</th> | |
| <th>Date</th> | |
| <th></th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {% for r in records %} | |
| <tr class="history-row" id="row-{{ r._id }}"> | |
| <td class="claim-cell"> | |
| <div class="claim-text">{{ r.claim }}</div> | |
| </td> | |
| <td data-label="Verdict"> | |
| <span class="verdict-pill {{ r.verdict|lower|replace('/', '-') }}"> | |
| {% if r.verdict|lower == 'true' %}<i class="ph-fill ph-check-circle"></i> | |
| {% elif r.verdict|lower == 'false' %}<i class="ph-fill ph-x-circle"></i> | |
| {% else %}<i class="ph-fill ph-minus-circle"></i>{% endif %} | |
| {{ r.verdict }} | |
| </span> | |
| </td> | |
| <td data-label="Confidence"> | |
| <span style="font-weight:600; font-size:0.9rem;">{{ (r.confidence * 100)|round(0)|int | |
| }}%</span> | |
| <div class="conf-bar"> | |
| <div class="conf-fill" style="width:{{ (r.confidence * 100)|round(0)|int }}%;"> | |
| </div> | |
| </div> | |
| </td> | |
| <td data-label="Evidence" style="color:var(--text-muted);">{{ r.evidence_count }} sources | |
| </td> | |
| <td data-label="Date" class="date-cell"> | |
| {{ r.created_at.strftime('%b %d, %Y') }}<br> | |
| <span style="font-size:0.78rem;">{{ r.created_at.strftime('%I:%M %p') }}</span> | |
| </td> | |
| <td data-label=""> | |
| <button class="delete-btn" title="Delete this entry" | |
| onclick="openDeleteModal('{{ r._id }}', `{{ r.claim[:60] }}`)"> | |
| <i class="ph ph-trash"></i> | |
| </button> | |
| </td> | |
| </tr> | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| {% else %} | |
| <div class="empty-state"> | |
| <i class="ph ph-clock-counter-clockwise"></i> | |
| <h3>No searches yet</h3> | |
| <p>Once you fact-check a claim, it will appear here.</p> | |
| <a href="/">Check your first claim</a> | |
| </div> | |
| {% endif %} | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Delete single item modal --> | |
| <div class="modal-backdrop" id="deleteModal"> | |
| <div class="modal"> | |
| <h3><i class="ph ph-trash" style="color:#ef4444;"></i> Delete Entry</h3> | |
| <p id="deleteModalMsg">Are you sure you want to delete this entry? This action cannot be undone.</p> | |
| <div class="modal-actions"> | |
| <button class="modal-cancel" onclick="closeModals()">Cancel</button> | |
| <form id="deleteForm" method="POST" style="margin:0;"> | |
| <button type="submit" class="modal-confirm">Delete</button> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Clear All modal --> | |
| <div class="modal-backdrop" id="clearModal"> | |
| <div class="modal"> | |
| <h3><i class="ph ph-warning" style="color:#f59e0b;"></i> Clear All History</h3> | |
| <p>This will permanently delete <strong>all {{ records|length if records else 0 }} entries</strong> from | |
| your history. This cannot be undone.</p> | |
| <div class="modal-actions"> | |
| <button class="modal-cancel" onclick="closeModals()">Cancel</button> | |
| <form action="{{ url_for('clear_history') }}" method="POST" style="margin:0;"> | |
| <button type="submit" class="modal-confirm">Clear All</button> | |
| </form> | |
| </div> | |
| </div> | |
| </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 openDeleteModal(itemId, claimPreview) { | |
| const msg = document.getElementById('deleteModalMsg'); | |
| msg.textContent = `Delete "${claimPreview}${claimPreview.length >= 60 ? '…' : ''}"? This cannot be undone.`; | |
| document.getElementById('deleteForm').action = `/history/delete/${itemId}`; | |
| document.getElementById('deleteModal').classList.add('open'); | |
| } | |
| function openClearModal() { | |
| document.getElementById('clearModal').classList.add('open'); | |
| } | |
| function closeModals() { | |
| document.querySelectorAll('.modal-backdrop').forEach(m => m.classList.remove('open')); | |
| } | |
| // Close on backdrop click | |
| document.querySelectorAll('.modal-backdrop').forEach(backdrop => { | |
| backdrop.addEventListener('click', e => { | |
| if (e.target === backdrop) closeModals(); | |
| }); | |
| }); | |
| // Close on Escape key | |
| document.addEventListener('keydown', e => { | |
| if (e.key === 'Escape') closeModals(); | |
| }); | |
| 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> |