Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>PhishGuard AI</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| :root { | |
| --bg-primary: #0F0F14; | |
| --bg-secondary: #1A1A24; | |
| --bg-card: #22222E; | |
| --bg-hover: #2A2A38; | |
| --text-primary: #EAEAF0; | |
| --text-secondary: #8888A0; | |
| --text-muted: #5A5A72; | |
| --accent: #534AB7; | |
| --accent-glow: rgba(83, 74, 183, 0.35); | |
| --safe: #22C55E; | |
| --safe-glow: rgba(34, 197, 94, 0.25); | |
| --danger: #EF4444; | |
| --danger-glow: rgba(239, 68, 68, 0.25); | |
| --warning: #F59E0B; | |
| --warning-glow: rgba(245, 158, 11, 0.25); | |
| --border: rgba(255,255,255,0.06); | |
| --radius: 12px; | |
| --radius-sm: 8px; | |
| } | |
| body { | |
| width: 380px; | |
| min-height: 480px; | |
| max-height: 640px; | |
| font-family: 'Inter', -apple-system, sans-serif; | |
| background: var(--bg-primary); | |
| color: var(--text-primary); | |
| overflow-y: auto; | |
| scrollbar-width: thin; | |
| scrollbar-color: var(--bg-hover) transparent; | |
| } | |
| body::-webkit-scrollbar { width: 4px; } | |
| body::-webkit-scrollbar-thumb { background: var(--bg-hover); border-radius: 4px; } | |
| /* ββ Header ββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .header { | |
| display: flex; align-items: center; gap: 10px; | |
| padding: 14px 20px 10px; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .header-logo { | |
| width: 28px; height: 28px; | |
| background: linear-gradient(135deg, var(--accent), #7C6BDB); | |
| border-radius: var(--radius-sm); | |
| display: flex; align-items: center; justify-content: center; | |
| font-size: 14px; | |
| } | |
| .header h1 { font-size: 15px; font-weight: 700; letter-spacing: -0.3px; } | |
| .header h1 span { color: var(--accent); } | |
| .header-badge { | |
| margin-left: auto; | |
| font-size: 10px; padding: 3px 8px; | |
| background: var(--bg-card); border: 1px solid var(--border); | |
| border-radius: 20px; color: var(--text-secondary); font-weight: 500; | |
| } | |
| /* ββ URL Bar ββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .url-bar { | |
| padding: 8px 20px; | |
| background: var(--bg-secondary); | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .url-text { | |
| font-size: 11px; color: var(--text-muted); | |
| overflow: hidden; text-overflow: ellipsis; white-space: nowrap; | |
| font-family: 'SF Mono', 'Fira Code', monospace; | |
| } | |
| /* ββ Loading ββββββββββββββββββββββββββββββββββββββββββββ */ | |
| .loading-container { | |
| display: flex; flex-direction: column; align-items: center; | |
| justify-content: center; padding: 40px 20px; gap: 14px; | |
| } | |
| .spinner { | |
| width: 40px; height: 40px; | |
| border: 3px solid var(--bg-hover); | |
| border-top-color: var(--accent); | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| .loading-text { | |
| font-size: 13px; color: var(--text-secondary); | |
| animation: pulse 1.5s ease-in-out infinite; | |
| } | |
| @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.5; } } | |
| /* ββ Result Panel ββββββββββββββββββββββββββββββββββββββ */ | |
| .result-panel { padding: 16px 20px; } | |
| .result-hero { | |
| display: flex; align-items: center; gap: 16px; | |
| margin-bottom: 16px; | |
| } | |
| .score-ring-wrap { | |
| position: relative; width: 80px; height: 80px; flex-shrink: 0; | |
| } | |
| .score-ring-bg, .score-ring-fg { | |
| fill: none; stroke-width: 6; | |
| } | |
| .score-ring-bg { stroke: var(--bg-hover); } | |
| .score-ring-fg { | |
| stroke-linecap: round; | |
| transform: rotate(-90deg); transform-origin: center; | |
| transition: stroke-dashoffset 1s ease, stroke 0.5s; | |
| stroke-dasharray: 213; stroke-dashoffset: 213; | |
| } | |
| .score-label { | |
| position: absolute; inset: 0; | |
| display: flex; flex-direction: column; | |
| align-items: center; justify-content: center; | |
| } | |
| .score-pct { font-size: 20px; font-weight: 700; line-height: 1; } | |
| .score-sub { | |
| font-size: 9px; color: var(--text-muted); | |
| margin-top: 2px; text-transform: uppercase; letter-spacing: 0.5px; | |
| } | |
| .shield-icon { | |
| font-size: 28px; | |
| animation: shieldPop 0.6s cubic-bezier(0.34, 1.56, 0.64, 1); | |
| } | |
| @keyframes shieldPop { | |
| 0% { transform: scale(0.3) rotate(-15deg); opacity: 0; } | |
| 60% { transform: scale(1.15) rotate(3deg); } | |
| 100% { transform: scale(1) rotate(0); opacity: 1; } | |
| } | |
| .result-verdict { flex: 1; } | |
| .verdict-label { font-size: 16px; font-weight: 700; line-height: 1.2; } | |
| .verdict-detail { font-size: 11px; color: var(--text-secondary); margin-top: 3px; } | |
| .status-safe { color: var(--safe); } | |
| .status-danger { color: var(--danger); } | |
| .status-warn { color: var(--warning); } | |
| /* ββ Tier Rows βββββββββββββββββββββββββββββββββββββββββ */ | |
| .tier-section { margin-top: 4px; } | |
| .tier-row { | |
| background: var(--bg-card); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius-sm); | |
| margin-bottom: 5px; overflow: hidden; | |
| transition: border-color 0.2s; | |
| } | |
| .tier-row:hover { border-color: rgba(255,255,255,0.1); } | |
| .tier-header { | |
| display: flex; align-items: center; | |
| padding: 8px 12px; cursor: pointer; | |
| user-select: none; gap: 8px; | |
| } | |
| .tier-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } | |
| .tier-name { font-size: 11px; font-weight: 600; flex: 1; } | |
| .tier-score { | |
| font-size: 11px; font-weight: 600; | |
| font-family: 'SF Mono', 'Fira Code', monospace; | |
| } | |
| .tier-chevron { | |
| font-size: 9px; color: var(--text-muted); | |
| transition: transform 0.2s; | |
| } | |
| .tier-row.open .tier-chevron { transform: rotate(180deg); } | |
| .tier-body { | |
| max-height: 0; overflow: hidden; | |
| transition: max-height 0.3s ease; padding: 0 12px; | |
| } | |
| .tier-row.open .tier-body { max-height: 200px; padding: 4px 12px 10px; } | |
| .tier-detail { font-size: 10px; color: var(--text-secondary); line-height: 1.6; } | |
| .flag-badge { | |
| display: inline-block; padding: 1px 6px; | |
| background: rgba(239,68,68,0.12); color: var(--danger); | |
| border-radius: 4px; font-size: 10px; margin: 2px 2px 2px 0; | |
| } | |
| /* ββ Feedback Section ββββββββββββββββββββββββββββββββββ */ | |
| .feedback-section { | |
| padding: 0 20px 12px; margin-top: 8px; | |
| } | |
| .feedback-prompt { | |
| font-size: 12px; color: var(--text-secondary); | |
| margin-bottom: 8px; text-align: center; | |
| } | |
| .feedback-buttons { display: flex; gap: 8px; } | |
| .fb-btn { | |
| flex: 1; padding: 8px 0; | |
| border: 1px solid var(--border); border-radius: var(--radius-sm); | |
| background: var(--bg-card); color: var(--text-primary); | |
| font-size: 13px; font-weight: 600; cursor: pointer; | |
| transition: all 0.2s; font-family: inherit; | |
| } | |
| .fb-btn:hover { background: var(--bg-hover); } | |
| .fb-btn-correct:hover { border-color: var(--safe); background: rgba(34,197,94,0.08); } | |
| .fb-btn-wrong:hover { border-color: var(--danger); background: rgba(239,68,68,0.08); } | |
| .fb-btn.selected { | |
| opacity: 1 ; | |
| } | |
| .fb-btn.dimmed { | |
| opacity: 0.3; pointer-events: none; | |
| } | |
| .fb-btn-correct.selected { | |
| border-color: var(--safe); background: rgba(34,197,94,0.15); | |
| color: var(--safe); | |
| } | |
| .fb-btn-wrong.selected { | |
| border-color: var(--danger); background: rgba(239,68,68,0.15); | |
| color: var(--danger); | |
| } | |
| .thank-you { | |
| display: none; text-align: center; padding: 10px; | |
| font-size: 12px; color: var(--safe); | |
| animation: slideDown 0.3s ease; | |
| } | |
| .thank-you.show { display: block; } | |
| @keyframes slideDown { | |
| from { opacity: 0; transform: translateY(-6px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* ββ Retraining Status βββββββββββββββββββββββββββββββββ */ | |
| .retrain-section { | |
| padding: 8px 20px 12px; | |
| border-top: 1px solid var(--border); | |
| margin-top: 4px; | |
| } | |
| .retrain-row { | |
| display: flex; align-items: center; gap: 6px; | |
| font-size: 11px; color: var(--text-muted); | |
| margin-bottom: 4px; | |
| } | |
| .retrain-row .icon { font-size: 12px; } | |
| .retrain-progress { | |
| height: 3px; background: var(--bg-hover); | |
| border-radius: 2px; margin: 6px 0 4px; | |
| overflow: hidden; | |
| } | |
| .retrain-progress-bar { | |
| height: 100%; background: linear-gradient(90deg, var(--accent), #7C6BDB); | |
| border-radius: 2px; transition: width 0.5s ease; | |
| } | |
| /* ββ Session Stats βββββββββββββββββββββββββββββββββββββ */ | |
| .stats-row { | |
| display: flex; justify-content: space-between; | |
| padding: 6px 20px; | |
| border-top: 1px solid var(--border); | |
| font-size: 11px; color: var(--text-muted); | |
| } | |
| /* ββ Blocked Overlay βββββββββββββββββββββββββββββββββββ */ | |
| .blocked-overlay { | |
| display: none; padding: 28px 24px; text-align: center; | |
| } | |
| .blocked-overlay.show { | |
| display: flex; flex-direction: column; | |
| align-items: center; gap: 10px; | |
| } | |
| .blocked-shield { font-size: 48px; animation: shieldPop 0.6s ease; } | |
| .blocked-title { font-size: 18px; font-weight: 700; color: var(--danger); } | |
| .blocked-url { | |
| font-size: 11px; color: var(--text-muted); | |
| word-break: break-all; max-width: 300px; | |
| } | |
| .blocked-method { font-size: 12px; color: var(--text-secondary); } | |
| .proceed-btn { | |
| margin-top: 8px; padding: 8px 20px; | |
| background: transparent; border: 1px solid rgba(239,68,68,0.3); | |
| border-radius: var(--radius-sm); color: var(--text-secondary); | |
| font-size: 12px; cursor: pointer; font-family: inherit; | |
| transition: all 0.2s; | |
| } | |
| .proceed-btn:hover { | |
| background: rgba(239,68,68,0.08); | |
| border-color: var(--danger); color: var(--danger); | |
| } | |
| .offline-banner { | |
| display: none; padding: 8px 16px; | |
| background: rgba(245, 158, 11, 0.08); | |
| border: 1px solid rgba(245, 158, 11, 0.2); | |
| border-radius: var(--radius-sm); | |
| margin: 8px 20px 0; font-size: 11px; | |
| color: var(--warning); text-align: center; | |
| } | |
| .offline-banner.show { display: block; } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header --> | |
| <div class="header"> | |
| <div class="header-logo">π‘οΈ</div> | |
| <h1>Phish<span>Guard</span> AI</h1> | |
| <span class="header-badge" id="versionBadge">v3.0</span> | |
| </div> | |
| <!-- URL Bar --> | |
| <div class="url-bar"> | |
| <div class="url-text" id="currentUrl">Analyzing...</div> | |
| </div> | |
| <!-- Server offline banner --> | |
| <div class="offline-banner" id="offlineBanner"> | |
| β οΈ Server offline β local heuristic only | |
| </div> | |
| <!-- Loading State --> | |
| <div class="loading-container" id="loadingState"> | |
| <div class="spinner"></div> | |
| <div class="loading-text">Analyzing with AI ensemble...</div> | |
| </div> | |
| <!-- Result Panel --> | |
| <div class="result-panel" id="resultPanel" style="display:none;"> | |
| <div class="result-hero"> | |
| <div class="score-ring-wrap"> | |
| <svg width="80" height="80" viewBox="0 0 80 80"> | |
| <circle class="score-ring-bg" cx="40" cy="40" r="34" /> | |
| <circle class="score-ring-fg" id="scoreRing" cx="40" cy="40" r="34" /> | |
| </svg> | |
| <div class="score-label"> | |
| <div class="score-pct" id="scorePct">0%</div> | |
| <div class="score-sub" id="scoreSub">RISK</div> | |
| </div> | |
| </div> | |
| <div style="display:flex; flex-direction:column; align-items:center; gap:4px"> | |
| <div class="shield-icon" id="shieldIcon">π‘οΈ</div> | |
| </div> | |
| <div class="result-verdict"> | |
| <div class="verdict-label" id="verdictLabel">Analyzing</div> | |
| <div class="verdict-detail" id="verdictDetail">Please wait...</div> | |
| </div> | |
| </div> | |
| <!-- Tier Rows --> | |
| <div class="tier-section" id="tierSection"> | |
| <div class="tier-row" data-tier="1"> | |
| <div class="tier-header" onclick="toggleTier(this)"> | |
| <div class="tier-dot" id="t1Dot"></div> | |
| <div class="tier-name">Tier 1 Β· Whitelist</div> | |
| <div class="tier-score" id="t1Score">β</div> | |
| <div class="tier-chevron">βΌ</div> | |
| </div> | |
| <div class="tier-body"><div class="tier-detail" id="t1Detail">O(1) domain lookup</div></div> | |
| </div> | |
| <div class="tier-row" data-tier="2"> | |
| <div class="tier-header" onclick="toggleTier(this)"> | |
| <div class="tier-dot" id="t2Dot"></div> | |
| <div class="tier-name">Tier 2 Β· Heuristics</div> | |
| <div class="tier-score" id="t2Score">β</div> | |
| <div class="tier-chevron">βΌ</div> | |
| </div> | |
| <div class="tier-body"><div class="tier-detail" id="t2Detail">15 regex/math signals</div></div> | |
| </div> | |
| <div class="tier-row" data-tier="3"> | |
| <div class="tier-header" onclick="toggleTier(this)"> | |
| <div class="tier-dot" id="t3Dot"></div> | |
| <div class="tier-name">Tier 3 Β· BERT + GNN</div> | |
| <div class="tier-score" id="t3Score">β</div> | |
| <div class="tier-chevron">βΌ</div> | |
| </div> | |
| <div class="tier-body"><div class="tier-detail" id="t3Detail">Parallel NLP + graph analysis</div></div> | |
| </div> | |
| <div class="tier-row" data-tier="4"> | |
| <div class="tier-header" onclick="toggleTier(this)"> | |
| <div class="tier-dot" id="t4Dot"></div> | |
| <div class="tier-name">Tier 4 Β· CNN Visual</div> | |
| <div class="tier-score" id="t4Score">β</div> | |
| <div class="tier-chevron">βΌ</div> | |
| </div> | |
| <div class="tier-body"><div class="tier-detail" id="t4Detail">Screenshot + brand detection</div></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Feedback Section --> | |
| <div class="feedback-section" id="feedbackSection" style="display:none;"> | |
| <div class="feedback-prompt">Was this correct?</div> | |
| <div class="feedback-buttons"> | |
| <button class="fb-btn fb-btn-correct" id="btnCorrect">π Correct</button> | |
| <button class="fb-btn fb-btn-wrong" id="btnWrong">π Incorrect</button> | |
| </div> | |
| <div class="thank-you" id="thankYou">β Thanks! Helps us improve π―</div> | |
| </div> | |
| <!-- Retraining Status --> | |
| <div class="retrain-section" id="retrainSection" style="display:none;"> | |
| <div class="retrain-row"> | |
| <span class="icon">π</span> | |
| <span id="retrainStatus">Next retrain: calculating...</span> | |
| </div> | |
| <div class="retrain-progress"> | |
| <div class="retrain-progress-bar" id="retrainProgressBar" style="width: 0%"></div> | |
| </div> | |
| <div class="retrain-row"> | |
| <span class="icon">π</span> | |
| <span id="retrainLast">No retraining yet</span> | |
| </div> | |
| </div> | |
| <!-- Session Stats --> | |
| <div class="stats-row" id="statsRow" style="display:none;"> | |
| <span id="statScanned">π 0 scanned</span> | |
| <span id="statFeedback">π¬ 0 feedback</span> | |
| <span id="statVersion">π·οΈ v0</span> | |
| </div> | |
| <!-- Blocked Page Overlay --> | |
| <div class="blocked-overlay" id="blockedOverlay"> | |
| <div class="blocked-shield">π¨</div> | |
| <div class="blocked-title">Phishing Detected!</div> | |
| <div class="blocked-url" id="blockedUrl"></div> | |
| <div class="blocked-method" id="blockedMethod"></div> | |
| <button class="proceed-btn" id="proceedBtn">Proceed Anyway (Unsafe)</button> | |
| </div> | |
| <script src="popup.js"></script> | |
| </body> | |
| </html> | |