Deepfake Authenticator commited on
Commit Β·
0f27de7
1
Parent(s): bd97ad3
feat: Chrome/Edge browser extension
Browse files- Manifest V3 extension with context menu, toolbar popup, content script
- Right-click any video β Analyze with Authrix
- Toolbar popup: auto-detects page videos, manual URL input, last result
- Content script overlay: radar loading animation, verdict display,
confidence bar, insight cards, audio result, re-analyze button
- Background service worker: fetches video URL, sends to localhost:8000
- AV_MISMATCH shown as DEEPFAKE in overlay
- Error state with server offline detection and instructions
- Icons generated (16/48/128px)
- extension/README.md +47 -0
- extension/background.js +129 -0
- extension/content.js +276 -0
- extension/generate_icons.html +58 -0
- extension/manifest.json +47 -0
- extension/overlay.css +347 -0
- extension/popup.html +227 -0
- extension/popup.js +165 -0
extension/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Authrix Browser Extension
|
| 2 |
+
|
| 3 |
+
Detect deepfake videos on any webpage without downloading them.
|
| 4 |
+
|
| 5 |
+
## Install (Chrome / Edge / Brave)
|
| 6 |
+
|
| 7 |
+
1. Open `chrome://extensions`
|
| 8 |
+
2. Enable **Developer Mode** (top right toggle)
|
| 9 |
+
3. Click **Load unpacked**
|
| 10 |
+
4. Select this `extension/` folder
|
| 11 |
+
5. The Authrix icon appears in your toolbar
|
| 12 |
+
|
| 13 |
+
## Requirements
|
| 14 |
+
|
| 15 |
+
The Authrix backend must be running locally:
|
| 16 |
+
```bash
|
| 17 |
+
cd backend
|
| 18 |
+
python -m uvicorn main:app --host 0.0.0.0 --port 8000
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
## Usage
|
| 22 |
+
|
| 23 |
+
### Method 1 β Right-click any video
|
| 24 |
+
Right-click on a video element on any webpage β **"π Analyze with Authrix"**
|
| 25 |
+
|
| 26 |
+
### Method 2 β Toolbar popup
|
| 27 |
+
Click the Authrix icon in the toolbar:
|
| 28 |
+
- Auto-detects videos on the current page
|
| 29 |
+
- Paste any direct video URL manually
|
| 30 |
+
- Shows last analysis result
|
| 31 |
+
|
| 32 |
+
### Method 3 β Right-click a link
|
| 33 |
+
Right-click any link to a video file β **"π Analyze video URL with Authrix"**
|
| 34 |
+
|
| 35 |
+
## How it works
|
| 36 |
+
|
| 37 |
+
1. Extension captures the video URL from the page
|
| 38 |
+
2. Background service worker fetches the video and sends it to `localhost:8000/analyze`
|
| 39 |
+
3. FastAPI backend runs the full 5-agent pipeline (visual + audio)
|
| 40 |
+
4. Result overlay appears on the page with verdict, confidence, and insights
|
| 41 |
+
|
| 42 |
+
## Limitations
|
| 43 |
+
|
| 44 |
+
- **DRM-protected videos** (Netflix, Disney+) cannot be analyzed
|
| 45 |
+
- **YouTube** β the extension detects the page URL but YouTube's video streams require yt-dlp; direct URL analysis works for non-DRM videos
|
| 46 |
+
- Backend must be running on `localhost:8000`
|
| 47 |
+
- Large videos (>100MB) may time out
|
extension/background.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Authrix Extension β Background Service Worker
|
| 3 |
+
* Handles context menu, coordinates analysis requests
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
const API_BASE = 'http://localhost:8000';
|
| 7 |
+
|
| 8 |
+
// ββ Create context menu on install ββββββββββββββββββββββββββββββββββββββββββββ
|
| 9 |
+
chrome.runtime.onInstalled.addListener(() => {
|
| 10 |
+
chrome.contextMenus.create({
|
| 11 |
+
id: 'authrix-analyze',
|
| 12 |
+
title: 'π Analyze with Authrix',
|
| 13 |
+
contexts: ['video', 'link', 'page'],
|
| 14 |
+
});
|
| 15 |
+
chrome.contextMenus.create({
|
| 16 |
+
id: 'authrix-analyze-url',
|
| 17 |
+
title: 'π Analyze video URL with Authrix',
|
| 18 |
+
contexts: ['link'],
|
| 19 |
+
});
|
| 20 |
+
console.log('[Authrix] Extension installed, context menu created');
|
| 21 |
+
});
|
| 22 |
+
|
| 23 |
+
// ββ Context menu click ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 24 |
+
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
|
| 25 |
+
if (!tab?.id) return;
|
| 26 |
+
|
| 27 |
+
if (info.menuItemId === 'authrix-analyze') {
|
| 28 |
+
// Inject content script to find and analyze the video on the page
|
| 29 |
+
chrome.scripting.executeScript({
|
| 30 |
+
target: { tabId: tab.id },
|
| 31 |
+
func: triggerPageAnalysis,
|
| 32 |
+
args: [info.srcUrl || info.pageUrl || null],
|
| 33 |
+
});
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
if (info.menuItemId === 'authrix-analyze-url') {
|
| 37 |
+
const url = info.linkUrl || info.srcUrl;
|
| 38 |
+
if (url) {
|
| 39 |
+
chrome.scripting.executeScript({
|
| 40 |
+
target: { tabId: tab.id },
|
| 41 |
+
func: showAuthrixOverlay,
|
| 42 |
+
args: [url],
|
| 43 |
+
});
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
});
|
| 47 |
+
|
| 48 |
+
// ββ Message handler (from content script / popup) βββββββββββββββββββββββββββββ
|
| 49 |
+
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
| 50 |
+
if (msg.type === 'ANALYZE_URL') {
|
| 51 |
+
analyzeVideoUrl(msg.url)
|
| 52 |
+
.then(result => sendResponse({ ok: true, result }))
|
| 53 |
+
.catch(err => sendResponse({ ok: false, error: err.message }));
|
| 54 |
+
return true; // keep channel open for async
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
if (msg.type === 'CHECK_HEALTH') {
|
| 58 |
+
fetch(`${API_BASE}/health`)
|
| 59 |
+
.then(r => r.json())
|
| 60 |
+
.then(d => sendResponse({ ok: true, data: d }))
|
| 61 |
+
.catch(() => sendResponse({ ok: false }));
|
| 62 |
+
return true;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
if (msg.type === 'ANALYZE_FILE_BYTES') {
|
| 66 |
+
// Receive base64 video bytes from content script, send to backend
|
| 67 |
+
analyzeBase64Video(msg.data, msg.filename, msg.mimeType)
|
| 68 |
+
.then(result => sendResponse({ ok: true, result }))
|
| 69 |
+
.catch(err => sendResponse({ ok: false, error: err.message }));
|
| 70 |
+
return true;
|
| 71 |
+
}
|
| 72 |
+
});
|
| 73 |
+
|
| 74 |
+
// ββ Analyze a video by URL ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 75 |
+
async function analyzeVideoUrl(videoUrl) {
|
| 76 |
+
// First fetch the video as a blob
|
| 77 |
+
const response = await fetch(videoUrl);
|
| 78 |
+
if (!response.ok) throw new Error(`Failed to fetch video: ${response.status}`);
|
| 79 |
+
|
| 80 |
+
const blob = await response.blob();
|
| 81 |
+
const filename = videoUrl.split('/').pop().split('?')[0] || 'video.mp4';
|
| 82 |
+
|
| 83 |
+
return sendToBackend(blob, filename);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
// ββ Analyze base64 video data βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 87 |
+
async function analyzeBase64Video(base64Data, filename, mimeType) {
|
| 88 |
+
const byteString = atob(base64Data);
|
| 89 |
+
const bytes = new Uint8Array(byteString.length);
|
| 90 |
+
for (let i = 0; i < byteString.length; i++) {
|
| 91 |
+
bytes[i] = byteString.charCodeAt(i);
|
| 92 |
+
}
|
| 93 |
+
const blob = new Blob([bytes], { type: mimeType || 'video/mp4' });
|
| 94 |
+
return sendToBackend(blob, filename || 'video.mp4');
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
// ββ Send blob to FastAPI backend ββββββββββββββββββββββββββββββββββββββββββββββ
|
| 98 |
+
async function sendToBackend(blob, filename) {
|
| 99 |
+
const fd = new FormData();
|
| 100 |
+
fd.append('file', blob, filename);
|
| 101 |
+
|
| 102 |
+
const res = await fetch(`${API_BASE}/analyze`, {
|
| 103 |
+
method: 'POST',
|
| 104 |
+
body: fd,
|
| 105 |
+
});
|
| 106 |
+
|
| 107 |
+
if (!res.ok) {
|
| 108 |
+
const err = await res.json().catch(() => ({}));
|
| 109 |
+
throw new Error(err.detail || `Backend error ${res.status}`);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
return res.json();
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
// ββ Injected functions (run in page context) ββββββββββββββββββββββββββββββββββ
|
| 116 |
+
function triggerPageAnalysis(srcUrl) {
|
| 117 |
+
// This runs in the page β calls showAuthrixOverlay which is defined in content.js
|
| 118 |
+
if (typeof window.__authrixAnalyze === 'function') {
|
| 119 |
+
window.__authrixAnalyze(srcUrl);
|
| 120 |
+
} else {
|
| 121 |
+
console.warn('[Authrix] Content script not ready');
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
function showAuthrixOverlay(url) {
|
| 126 |
+
if (typeof window.__authrixAnalyze === 'function') {
|
| 127 |
+
window.__authrixAnalyze(url);
|
| 128 |
+
}
|
| 129 |
+
}
|
extension/content.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Authrix Extension β Content Script
|
| 3 |
+
* Injected into every page. Handles overlay UI and video detection.
|
| 4 |
+
*/
|
| 5 |
+
|
| 6 |
+
// ββ Register global trigger βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 7 |
+
window.__authrixAnalyze = function(hintUrl) {
|
| 8 |
+
const url = hintUrl || findBestVideoUrl();
|
| 9 |
+
if (!url) {
|
| 10 |
+
showToast('β No video found on this page', 'warn');
|
| 11 |
+
return;
|
| 12 |
+
}
|
| 13 |
+
showAnalysisOverlay(url);
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
// ββ Find best video URL on the page ββββββββββββββββββββββββββββββββββββββββββ
|
| 17 |
+
function findBestVideoUrl() {
|
| 18 |
+
// 1. <video> element with src
|
| 19 |
+
const videos = Array.from(document.querySelectorAll('video'));
|
| 20 |
+
for (const v of videos) {
|
| 21 |
+
if (v.src && v.src.startsWith('http')) return v.src;
|
| 22 |
+
const src = v.querySelector('source');
|
| 23 |
+
if (src?.src) return src.src;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
// 2. YouTube β extract video ID and use yt-dlp-style URL
|
| 27 |
+
const ytMatch = location.href.match(/[?&]v=([^&]+)/);
|
| 28 |
+
if (ytMatch) return `https://www.youtube.com/watch?v=${ytMatch[1]}`;
|
| 29 |
+
|
| 30 |
+
// 3. Twitter/X video
|
| 31 |
+
const twitterVideo = document.querySelector('video[src*="video.twimg.com"]');
|
| 32 |
+
if (twitterVideo?.src) return twitterVideo.src;
|
| 33 |
+
|
| 34 |
+
return null;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
// ββ Main overlay ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 38 |
+
function showAnalysisOverlay(videoUrl) {
|
| 39 |
+
// Remove existing overlay
|
| 40 |
+
document.getElementById('authrix-overlay')?.remove();
|
| 41 |
+
|
| 42 |
+
const overlay = document.createElement('div');
|
| 43 |
+
overlay.id = 'authrix-overlay';
|
| 44 |
+
overlay.innerHTML = `
|
| 45 |
+
<div id="authrix-panel">
|
| 46 |
+
<div id="authrix-header">
|
| 47 |
+
<div id="authrix-logo">
|
| 48 |
+
<span id="authrix-logo-icon">β</span>
|
| 49 |
+
<span>AUTHRIX AI</span>
|
| 50 |
+
</div>
|
| 51 |
+
<button id="authrix-close">β</button>
|
| 52 |
+
</div>
|
| 53 |
+
|
| 54 |
+
<div id="authrix-url-bar">
|
| 55 |
+
<span id="authrix-url-text">${truncateUrl(videoUrl)}</span>
|
| 56 |
+
</div>
|
| 57 |
+
|
| 58 |
+
<!-- Loading state -->
|
| 59 |
+
<div id="authrix-loading">
|
| 60 |
+
<div id="authrix-radar">
|
| 61 |
+
<div class="authrix-ring r1"></div>
|
| 62 |
+
<div class="authrix-ring r2"></div>
|
| 63 |
+
<div class="authrix-ring r3"></div>
|
| 64 |
+
<div id="authrix-radar-dot"></div>
|
| 65 |
+
</div>
|
| 66 |
+
<div id="authrix-loading-text">Initializing analysis...</div>
|
| 67 |
+
<div id="authrix-steps">
|
| 68 |
+
<div class="authrix-step" id="as0">π¬ Extracting frames</div>
|
| 69 |
+
<div class="authrix-step" id="as1">π€ Detecting faces</div>
|
| 70 |
+
<div class="authrix-step" id="as2">π§ Running AI models</div>
|
| 71 |
+
<div class="authrix-step" id="as3">π Generating report</div>
|
| 72 |
+
</div>
|
| 73 |
+
<div id="authrix-note">β Large videos may take 15-30s</div>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<!-- Result state -->
|
| 77 |
+
<div id="authrix-result" style="display:none;">
|
| 78 |
+
<div id="authrix-verdict-row">
|
| 79 |
+
<div id="authrix-badge"></div>
|
| 80 |
+
<div id="authrix-verdict-text"></div>
|
| 81 |
+
<div id="authrix-conf"></div>
|
| 82 |
+
</div>
|
| 83 |
+
<div id="authrix-conf-bar-wrap">
|
| 84 |
+
<div id="authrix-conf-bar"></div>
|
| 85 |
+
</div>
|
| 86 |
+
<div id="authrix-details"></div>
|
| 87 |
+
<div id="authrix-audio-row" style="display:none;">
|
| 88 |
+
<span id="authrix-audio-icon">ποΈ</span>
|
| 89 |
+
<span id="authrix-audio-label"></span>
|
| 90 |
+
</div>
|
| 91 |
+
<div id="authrix-actions">
|
| 92 |
+
<button id="authrix-reanalyze">βΊ Re-analyze</button>
|
| 93 |
+
<button id="authrix-open-app">Open Authrix App β</button>
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
|
| 97 |
+
<!-- Error state -->
|
| 98 |
+
<div id="authrix-error" style="display:none;">
|
| 99 |
+
<div id="authrix-error-icon">β </div>
|
| 100 |
+
<div id="authrix-error-msg"></div>
|
| 101 |
+
<div id="authrix-error-hint"></div>
|
| 102 |
+
<button id="authrix-retry">βΊ Retry</button>
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
`;
|
| 106 |
+
|
| 107 |
+
document.body.appendChild(overlay);
|
| 108 |
+
|
| 109 |
+
// Wire close
|
| 110 |
+
document.getElementById('authrix-close').onclick = () => overlay.remove();
|
| 111 |
+
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
|
| 112 |
+
|
| 113 |
+
// Start analysis
|
| 114 |
+
startAnalysis(videoUrl);
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// ββ Run analysis ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 118 |
+
async function startAnalysis(videoUrl) {
|
| 119 |
+
showState('loading');
|
| 120 |
+
animateSteps();
|
| 121 |
+
|
| 122 |
+
try {
|
| 123 |
+
// Check if server is running first
|
| 124 |
+
const health = await fetch('http://localhost:8000/health').catch(() => null);
|
| 125 |
+
if (!health?.ok) {
|
| 126 |
+
throw new Error('SERVER_OFFLINE');
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
// Send URL to background for fetching + analysis
|
| 130 |
+
const response = await chrome.runtime.sendMessage({
|
| 131 |
+
type: 'ANALYZE_URL',
|
| 132 |
+
url: videoUrl,
|
| 133 |
+
});
|
| 134 |
+
|
| 135 |
+
stopStepAnimation();
|
| 136 |
+
|
| 137 |
+
if (!response.ok) throw new Error(response.error || 'Analysis failed');
|
| 138 |
+
|
| 139 |
+
renderResult(response.result, videoUrl);
|
| 140 |
+
} catch (err) {
|
| 141 |
+
stopStepAnimation();
|
| 142 |
+
showError(err.message, videoUrl);
|
| 143 |
+
}
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
// ββ Render result βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 147 |
+
function renderResult(data, videoUrl) {
|
| 148 |
+
const isFake = data.result === 'FAKE';
|
| 149 |
+
const conf = data.confidence || 0;
|
| 150 |
+
const color = isFake ? '#ff4466' : '#00ff9c';
|
| 151 |
+
|
| 152 |
+
// Badge
|
| 153 |
+
const badge = document.getElementById('authrix-badge');
|
| 154 |
+
badge.textContent = isFake ? 'β ' : 'β';
|
| 155 |
+
badge.style.color = color;
|
| 156 |
+
badge.style.borderColor = color;
|
| 157 |
+
badge.style.boxShadow = `0 0 16px ${color}44`;
|
| 158 |
+
|
| 159 |
+
// Verdict
|
| 160 |
+
const vt = document.getElementById('authrix-verdict-text');
|
| 161 |
+
vt.textContent = isFake ? 'DEEPFAKE DETECTED' : 'AUTHENTIC VIDEO';
|
| 162 |
+
vt.style.color = color;
|
| 163 |
+
|
| 164 |
+
// Confidence
|
| 165 |
+
document.getElementById('authrix-conf').textContent = conf + '%';
|
| 166 |
+
document.getElementById('authrix-conf').style.color = color;
|
| 167 |
+
|
| 168 |
+
// Bar
|
| 169 |
+
const bar = document.getElementById('authrix-conf-bar');
|
| 170 |
+
bar.style.background = isFake
|
| 171 |
+
? 'linear-gradient(90deg,#880022,#ff4466)'
|
| 172 |
+
: 'linear-gradient(90deg,#006633,#00ff9c)';
|
| 173 |
+
bar.style.boxShadow = `0 0 8px ${color}66`;
|
| 174 |
+
setTimeout(() => { bar.style.width = conf + '%'; }, 80);
|
| 175 |
+
|
| 176 |
+
// Details (first 3)
|
| 177 |
+
const dl = document.getElementById('authrix-details');
|
| 178 |
+
dl.innerHTML = (data.details || []).slice(0, 3).map(d =>
|
| 179 |
+
`<div class="authrix-detail" style="border-left-color:${color};">${escHtml(d)}</div>`
|
| 180 |
+
).join('');
|
| 181 |
+
|
| 182 |
+
// Audio
|
| 183 |
+
const audioRow = document.getElementById('authrix-audio-row');
|
| 184 |
+
if (data.audio?.available) {
|
| 185 |
+
const isAI = data.audio.result === 'AI_VOICE';
|
| 186 |
+
const isMismatch = data.audio.result === 'AV_MISMATCH';
|
| 187 |
+
const aColor = (isAI || isMismatch) ? '#ff4466' : '#00ff9c';
|
| 188 |
+
document.getElementById('authrix-audio-icon').textContent =
|
| 189 |
+
(isAI || isMismatch) ? 'π€' : 'ποΈ';
|
| 190 |
+
document.getElementById('authrix-audio-label').textContent =
|
| 191 |
+
isMismatch ? 'AV Mismatch β face-swap detected' :
|
| 192 |
+
isAI ? `AI Voice (${data.audio.confidence}%)` :
|
| 193 |
+
`Human Voice (${data.audio.confidence}%)`;
|
| 194 |
+
document.getElementById('authrix-audio-label').style.color = aColor;
|
| 195 |
+
audioRow.style.display = 'flex';
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
// Actions
|
| 199 |
+
document.getElementById('authrix-reanalyze').onclick = () => startAnalysis(videoUrl);
|
| 200 |
+
document.getElementById('authrix-open-app').onclick = () => window.open('http://localhost:8000', '_blank');
|
| 201 |
+
|
| 202 |
+
showState('result');
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
// ββ Show error ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 206 |
+
function showError(message, videoUrl) {
|
| 207 |
+
const isOffline = message === 'SERVER_OFFLINE';
|
| 208 |
+
document.getElementById('authrix-error-msg').textContent =
|
| 209 |
+
isOffline ? 'Authrix server is not running' : message;
|
| 210 |
+
document.getElementById('authrix-error-hint').textContent =
|
| 211 |
+
isOffline
|
| 212 |
+
? 'Start the server: cd backend && python -m uvicorn main:app --port 8000'
|
| 213 |
+
: 'The video may be DRM-protected or require authentication.';
|
| 214 |
+
document.getElementById('authrix-retry').onclick = () => startAnalysis(videoUrl);
|
| 215 |
+
showState('error');
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
// ββ State management ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 219 |
+
function showState(state) {
|
| 220 |
+
document.getElementById('authrix-loading').style.display = state === 'loading' ? 'flex' : 'none';
|
| 221 |
+
document.getElementById('authrix-result').style.display = state === 'result' ? 'block' : 'none';
|
| 222 |
+
document.getElementById('authrix-error').style.display = state === 'error' ? 'flex' : 'none';
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
// ββ Step animation ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 226 |
+
let _stepTimers = [];
|
| 227 |
+
const _stepDelays = [0, 2000, 5000, 9000];
|
| 228 |
+
const _stepMsgs = [
|
| 229 |
+
'Downloading video frames...',
|
| 230 |
+
'Detecting facial regions...',
|
| 231 |
+
'Running ViT ensemble inference...',
|
| 232 |
+
'Compiling authenticity report...',
|
| 233 |
+
];
|
| 234 |
+
|
| 235 |
+
function animateSteps() {
|
| 236 |
+
_stepTimers = [];
|
| 237 |
+
_stepDelays.forEach((delay, i) => {
|
| 238 |
+
const t = setTimeout(() => {
|
| 239 |
+
document.querySelectorAll('.authrix-step').forEach((el, j) => {
|
| 240 |
+
el.classList.toggle('active', j === i);
|
| 241 |
+
el.classList.toggle('done', j < i);
|
| 242 |
+
});
|
| 243 |
+
document.getElementById('authrix-loading-text').textContent = _stepMsgs[i];
|
| 244 |
+
}, delay);
|
| 245 |
+
_stepTimers.push(t);
|
| 246 |
+
});
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
function stopStepAnimation() {
|
| 250 |
+
_stepTimers.forEach(clearTimeout);
|
| 251 |
+
_stepTimers = [];
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
// ββ Toast notification ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 255 |
+
function showToast(msg, type = 'info') {
|
| 256 |
+
const t = document.createElement('div');
|
| 257 |
+
t.className = 'authrix-toast authrix-toast-' + type;
|
| 258 |
+
t.textContent = msg;
|
| 259 |
+
document.body.appendChild(t);
|
| 260 |
+
setTimeout(() => t.remove(), 3500);
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
// ββ Utilities βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 264 |
+
function truncateUrl(url) {
|
| 265 |
+
try {
|
| 266 |
+
const u = new URL(url);
|
| 267 |
+
const path = u.pathname.split('/').pop() || u.hostname;
|
| 268 |
+
return u.hostname + '/β¦/' + path.slice(0, 30);
|
| 269 |
+
} catch {
|
| 270 |
+
return url.slice(0, 50);
|
| 271 |
+
}
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
function escHtml(s) {
|
| 275 |
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
| 276 |
+
}
|
extension/generate_icons.html
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<!-- Run this file in a browser to generate icons, then save the PNGs -->
|
| 3 |
+
<html><body>
|
| 4 |
+
<canvas id="c16" width="16" height="16"></canvas>
|
| 5 |
+
<canvas id="c48" width="48" height="48"></canvas>
|
| 6 |
+
<canvas id="c128" width="128" height="128"></canvas>
|
| 7 |
+
<script>
|
| 8 |
+
function drawIcon(canvas) {
|
| 9 |
+
const ctx = canvas.getContext('2d');
|
| 10 |
+
const s = canvas.width;
|
| 11 |
+
const cx = s/2, cy = s/2, r = s*0.42;
|
| 12 |
+
|
| 13 |
+
// Background
|
| 14 |
+
ctx.fillStyle = '#0c150f';
|
| 15 |
+
ctx.beginPath();
|
| 16 |
+
ctx.roundRect(0, 0, s, s, s*0.18);
|
| 17 |
+
ctx.fill();
|
| 18 |
+
|
| 19 |
+
// Outer ring
|
| 20 |
+
ctx.strokeStyle = 'rgba(0,255,156,0.4)';
|
| 21 |
+
ctx.lineWidth = s*0.04;
|
| 22 |
+
ctx.beginPath();
|
| 23 |
+
ctx.arc(cx, cy, r, 0, Math.PI*2);
|
| 24 |
+
ctx.stroke();
|
| 25 |
+
|
| 26 |
+
// Inner dot
|
| 27 |
+
ctx.fillStyle = '#00ff9c';
|
| 28 |
+
ctx.shadowColor = '#00ff9c';
|
| 29 |
+
ctx.shadowBlur = s*0.15;
|
| 30 |
+
ctx.beginPath();
|
| 31 |
+
ctx.arc(cx, cy, r*0.35, 0, Math.PI*2);
|
| 32 |
+
ctx.fill();
|
| 33 |
+
|
| 34 |
+
// Cross-hair lines
|
| 35 |
+
ctx.strokeStyle = 'rgba(0,255,156,0.6)';
|
| 36 |
+
ctx.lineWidth = s*0.03;
|
| 37 |
+
ctx.shadowBlur = 0;
|
| 38 |
+
ctx.beginPath();
|
| 39 |
+
ctx.moveTo(cx - r*0.7, cy); ctx.lineTo(cx - r*0.45, cy);
|
| 40 |
+
ctx.moveTo(cx + r*0.45, cy); ctx.lineTo(cx + r*0.7, cy);
|
| 41 |
+
ctx.moveTo(cx, cy - r*0.7); ctx.lineTo(cx, cy - r*0.45);
|
| 42 |
+
ctx.moveTo(cx, cy + r*0.45); ctx.lineTo(cx, cy + r*0.7);
|
| 43 |
+
ctx.stroke();
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
['c16','c48','c128'].forEach(id => {
|
| 47 |
+
const c = document.getElementById(id);
|
| 48 |
+
drawIcon(c);
|
| 49 |
+
const link = document.createElement('a');
|
| 50 |
+
link.download = `icon${c.width}.png`;
|
| 51 |
+
link.href = c.toDataURL();
|
| 52 |
+
link.textContent = `Download icon${c.width}.png`;
|
| 53 |
+
link.style.display = 'block';
|
| 54 |
+
link.style.margin = '8px';
|
| 55 |
+
document.body.appendChild(link);
|
| 56 |
+
});
|
| 57 |
+
</script>
|
| 58 |
+
</body></html>
|
extension/manifest.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"manifest_version": 3,
|
| 3 |
+
"name": "Authrix β Deepfake Detector",
|
| 4 |
+
"version": "1.0.0",
|
| 5 |
+
"description": "Detect deepfake videos on any webpage using AI. Right-click any video or use the toolbar popup.",
|
| 6 |
+
|
| 7 |
+
"permissions": [
|
| 8 |
+
"contextMenus",
|
| 9 |
+
"activeTab",
|
| 10 |
+
"scripting",
|
| 11 |
+
"storage",
|
| 12 |
+
"notifications"
|
| 13 |
+
],
|
| 14 |
+
|
| 15 |
+
"host_permissions": [
|
| 16 |
+
"http://localhost:8000/*",
|
| 17 |
+
"<all_urls>"
|
| 18 |
+
],
|
| 19 |
+
|
| 20 |
+
"background": {
|
| 21 |
+
"service_worker": "background.js"
|
| 22 |
+
},
|
| 23 |
+
|
| 24 |
+
"content_scripts": [
|
| 25 |
+
{
|
| 26 |
+
"matches": ["<all_urls>"],
|
| 27 |
+
"js": ["content.js"],
|
| 28 |
+
"css": ["overlay.css"]
|
| 29 |
+
}
|
| 30 |
+
],
|
| 31 |
+
|
| 32 |
+
"action": {
|
| 33 |
+
"default_popup": "popup.html",
|
| 34 |
+
"default_title": "Authrix Deepfake Detector",
|
| 35 |
+
"default_icon": {
|
| 36 |
+
"16": "icons/icon16.png",
|
| 37 |
+
"48": "icons/icon48.png",
|
| 38 |
+
"128": "icons/icon128.png"
|
| 39 |
+
}
|
| 40 |
+
},
|
| 41 |
+
|
| 42 |
+
"icons": {
|
| 43 |
+
"16": "icons/icon16.png",
|
| 44 |
+
"48": "icons/icon48.png",
|
| 45 |
+
"128": "icons/icon128.png"
|
| 46 |
+
}
|
| 47 |
+
}
|
extension/overlay.css
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Authrix Extension β Overlay Styles */
|
| 2 |
+
|
| 3 |
+
#authrix-overlay {
|
| 4 |
+
position: fixed;
|
| 5 |
+
inset: 0;
|
| 6 |
+
z-index: 2147483647;
|
| 7 |
+
background: rgba(0, 0, 0, 0.65);
|
| 8 |
+
backdrop-filter: blur(4px);
|
| 9 |
+
display: flex;
|
| 10 |
+
align-items: center;
|
| 11 |
+
justify-content: center;
|
| 12 |
+
font-family: 'Segoe UI', system-ui, sans-serif;
|
| 13 |
+
animation: authrix-fade-in 0.2s ease;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
@keyframes authrix-fade-in {
|
| 17 |
+
from { opacity: 0; }
|
| 18 |
+
to { opacity: 1; }
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
#authrix-panel {
|
| 22 |
+
background: #0c150f;
|
| 23 |
+
border: 1px solid rgba(0, 255, 156, 0.25);
|
| 24 |
+
border-radius: 14px;
|
| 25 |
+
width: 420px;
|
| 26 |
+
max-width: calc(100vw - 32px);
|
| 27 |
+
box-shadow: 0 0 60px rgba(0, 255, 156, 0.12), 0 24px 48px rgba(0,0,0,0.6);
|
| 28 |
+
overflow: hidden;
|
| 29 |
+
animation: authrix-slide-up 0.25s ease;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
@keyframes authrix-slide-up {
|
| 33 |
+
from { opacity: 0; transform: translateY(16px); }
|
| 34 |
+
to { opacity: 1; transform: none; }
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
/* ββ Header ββ */
|
| 38 |
+
#authrix-header {
|
| 39 |
+
display: flex;
|
| 40 |
+
align-items: center;
|
| 41 |
+
justify-content: space-between;
|
| 42 |
+
padding: 14px 18px;
|
| 43 |
+
border-bottom: 1px solid rgba(0, 255, 156, 0.1);
|
| 44 |
+
background: rgba(0, 255, 156, 0.04);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
#authrix-logo {
|
| 48 |
+
display: flex;
|
| 49 |
+
align-items: center;
|
| 50 |
+
gap: 8px;
|
| 51 |
+
font-size: 13px;
|
| 52 |
+
font-weight: 700;
|
| 53 |
+
letter-spacing: 0.12em;
|
| 54 |
+
color: #00ff9c;
|
| 55 |
+
text-shadow: 0 0 10px rgba(0,255,156,0.5);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
#authrix-logo-icon {
|
| 59 |
+
font-size: 18px;
|
| 60 |
+
animation: authrix-pulse 2s ease-in-out infinite;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
@keyframes authrix-pulse {
|
| 64 |
+
0%,100% { opacity: 1; text-shadow: 0 0 8px #00ff9c; }
|
| 65 |
+
50% { opacity: 0.5; text-shadow: none; }
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
#authrix-close {
|
| 69 |
+
background: none;
|
| 70 |
+
border: none;
|
| 71 |
+
color: rgba(255,255,255,0.3);
|
| 72 |
+
font-size: 16px;
|
| 73 |
+
cursor: pointer;
|
| 74 |
+
padding: 4px 8px;
|
| 75 |
+
border-radius: 4px;
|
| 76 |
+
transition: color 0.2s, background 0.2s;
|
| 77 |
+
line-height: 1;
|
| 78 |
+
}
|
| 79 |
+
#authrix-close:hover { color: #ff4466; background: rgba(255,68,102,0.1); }
|
| 80 |
+
|
| 81 |
+
/* ββ URL bar ββ */
|
| 82 |
+
#authrix-url-bar {
|
| 83 |
+
padding: 8px 18px;
|
| 84 |
+
background: rgba(255,255,255,0.03);
|
| 85 |
+
border-bottom: 1px solid rgba(255,255,255,0.05);
|
| 86 |
+
}
|
| 87 |
+
#authrix-url-text {
|
| 88 |
+
font-size: 10px;
|
| 89 |
+
color: rgba(255,255,255,0.3);
|
| 90 |
+
font-family: monospace;
|
| 91 |
+
letter-spacing: 0.03em;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
/* ββ Loading ββ */
|
| 95 |
+
#authrix-loading {
|
| 96 |
+
display: flex;
|
| 97 |
+
flex-direction: column;
|
| 98 |
+
align-items: center;
|
| 99 |
+
padding: 28px 20px 24px;
|
| 100 |
+
gap: 16px;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
#authrix-radar {
|
| 104 |
+
position: relative;
|
| 105 |
+
width: 80px;
|
| 106 |
+
height: 80px;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.authrix-ring {
|
| 110 |
+
position: absolute;
|
| 111 |
+
border-radius: 50%;
|
| 112 |
+
border: 1px solid rgba(0,255,156,0.2);
|
| 113 |
+
top: 50%; left: 50%;
|
| 114 |
+
transform: translate(-50%, -50%);
|
| 115 |
+
}
|
| 116 |
+
.r1 { width: 80px; height: 80px; }
|
| 117 |
+
.r2 { width: 56px; height: 56px; border-color: rgba(0,255,156,0.3); }
|
| 118 |
+
.r3 { width: 32px; height: 32px; border-color: rgba(0,255,156,0.5); }
|
| 119 |
+
|
| 120 |
+
#authrix-radar-dot {
|
| 121 |
+
position: absolute;
|
| 122 |
+
top: 50%; left: 50%;
|
| 123 |
+
transform: translate(-50%, -50%);
|
| 124 |
+
width: 10px; height: 10px;
|
| 125 |
+
border-radius: 50%;
|
| 126 |
+
background: #00ff9c;
|
| 127 |
+
box-shadow: 0 0 12px #00ff9c;
|
| 128 |
+
animation: authrix-radar-spin 2s linear infinite;
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
@keyframes authrix-radar-spin {
|
| 132 |
+
0% { box-shadow: 0 0 12px #00ff9c; transform: translate(-50%,-50%) scale(1); }
|
| 133 |
+
50% { box-shadow: 0 0 24px #00ff9c; transform: translate(-50%,-50%) scale(1.3); }
|
| 134 |
+
100% { box-shadow: 0 0 12px #00ff9c; transform: translate(-50%,-50%) scale(1); }
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
#authrix-loading-text {
|
| 138 |
+
font-size: 12px;
|
| 139 |
+
color: #00ff9c;
|
| 140 |
+
font-weight: 600;
|
| 141 |
+
letter-spacing: 0.08em;
|
| 142 |
+
text-align: center;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
#authrix-steps {
|
| 146 |
+
display: flex;
|
| 147 |
+
flex-direction: column;
|
| 148 |
+
gap: 6px;
|
| 149 |
+
width: 100%;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.authrix-step {
|
| 153 |
+
font-size: 11px;
|
| 154 |
+
color: rgba(255,255,255,0.2);
|
| 155 |
+
padding: 6px 12px;
|
| 156 |
+
border-radius: 6px;
|
| 157 |
+
border: 1px solid transparent;
|
| 158 |
+
transition: all 0.3s ease;
|
| 159 |
+
letter-spacing: 0.03em;
|
| 160 |
+
}
|
| 161 |
+
.authrix-step.active {
|
| 162 |
+
color: #00ff9c;
|
| 163 |
+
background: rgba(0,255,156,0.06);
|
| 164 |
+
border-color: rgba(0,255,156,0.2);
|
| 165 |
+
}
|
| 166 |
+
.authrix-step.done {
|
| 167 |
+
color: rgba(0,255,156,0.4);
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
#authrix-note {
|
| 171 |
+
font-size: 10px;
|
| 172 |
+
color: rgba(255,255,255,0.2);
|
| 173 |
+
text-align: center;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
/* ββ Result ββ */
|
| 177 |
+
#authrix-result {
|
| 178 |
+
padding: 20px 18px;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
#authrix-verdict-row {
|
| 182 |
+
display: flex;
|
| 183 |
+
align-items: center;
|
| 184 |
+
gap: 12px;
|
| 185 |
+
margin-bottom: 14px;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
#authrix-badge {
|
| 189 |
+
width: 44px; height: 44px;
|
| 190 |
+
border-radius: 50%;
|
| 191 |
+
border: 2px solid;
|
| 192 |
+
display: flex; align-items: center; justify-content: center;
|
| 193 |
+
font-size: 20px;
|
| 194 |
+
flex-shrink: 0;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
#authrix-verdict-text {
|
| 198 |
+
font-size: 18px;
|
| 199 |
+
font-weight: 700;
|
| 200 |
+
letter-spacing: 0.08em;
|
| 201 |
+
flex: 1;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
#authrix-conf {
|
| 205 |
+
font-size: 22px;
|
| 206 |
+
font-weight: 700;
|
| 207 |
+
font-family: monospace;
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
#authrix-conf-bar-wrap {
|
| 211 |
+
height: 6px;
|
| 212 |
+
background: rgba(255,255,255,0.06);
|
| 213 |
+
border-radius: 99px;
|
| 214 |
+
overflow: hidden;
|
| 215 |
+
margin-bottom: 14px;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
#authrix-conf-bar {
|
| 219 |
+
height: 100%;
|
| 220 |
+
border-radius: 99px;
|
| 221 |
+
width: 0%;
|
| 222 |
+
transition: width 1s cubic-bezier(0.22,1,0.36,1);
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
#authrix-details {
|
| 226 |
+
display: flex;
|
| 227 |
+
flex-direction: column;
|
| 228 |
+
gap: 6px;
|
| 229 |
+
margin-bottom: 12px;
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
.authrix-detail {
|
| 233 |
+
font-size: 11px;
|
| 234 |
+
color: rgba(255,255,255,0.55);
|
| 235 |
+
padding: 7px 10px;
|
| 236 |
+
background: rgba(255,255,255,0.03);
|
| 237 |
+
border-left: 2px solid;
|
| 238 |
+
border-radius: 0 6px 6px 0;
|
| 239 |
+
line-height: 1.5;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
#authrix-audio-row {
|
| 243 |
+
display: flex;
|
| 244 |
+
align-items: center;
|
| 245 |
+
gap: 8px;
|
| 246 |
+
padding: 8px 10px;
|
| 247 |
+
background: rgba(255,255,255,0.03);
|
| 248 |
+
border-radius: 6px;
|
| 249 |
+
margin-bottom: 14px;
|
| 250 |
+
font-size: 11px;
|
| 251 |
+
border: 1px solid rgba(255,255,255,0.06);
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
#authrix-actions {
|
| 255 |
+
display: flex;
|
| 256 |
+
gap: 8px;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
#authrix-actions button {
|
| 260 |
+
flex: 1;
|
| 261 |
+
padding: 9px;
|
| 262 |
+
border-radius: 7px;
|
| 263 |
+
font-size: 11px;
|
| 264 |
+
font-weight: 600;
|
| 265 |
+
letter-spacing: 0.06em;
|
| 266 |
+
cursor: pointer;
|
| 267 |
+
transition: all 0.2s;
|
| 268 |
+
font-family: inherit;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
#authrix-reanalyze {
|
| 272 |
+
background: none;
|
| 273 |
+
border: 1px solid rgba(255,255,255,0.12);
|
| 274 |
+
color: rgba(255,255,255,0.5);
|
| 275 |
+
}
|
| 276 |
+
#authrix-reanalyze:hover {
|
| 277 |
+
border-color: rgba(0,255,156,0.4);
|
| 278 |
+
color: #00ff9c;
|
| 279 |
+
background: rgba(0,255,156,0.06);
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
#authrix-open-app {
|
| 283 |
+
background: rgba(0,255,156,0.1);
|
| 284 |
+
border: 1px solid rgba(0,255,156,0.3);
|
| 285 |
+
color: #00ff9c;
|
| 286 |
+
}
|
| 287 |
+
#authrix-open-app:hover {
|
| 288 |
+
background: rgba(0,255,156,0.18);
|
| 289 |
+
box-shadow: 0 0 16px rgba(0,255,156,0.2);
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
/* ββ Error ββ */
|
| 293 |
+
#authrix-error {
|
| 294 |
+
flex-direction: column;
|
| 295 |
+
align-items: center;
|
| 296 |
+
padding: 28px 20px 24px;
|
| 297 |
+
gap: 10px;
|
| 298 |
+
text-align: center;
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
#authrix-error-icon { font-size: 32px; }
|
| 302 |
+
|
| 303 |
+
#authrix-error-msg {
|
| 304 |
+
font-size: 13px;
|
| 305 |
+
font-weight: 600;
|
| 306 |
+
color: #ff4466;
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
#authrix-error-hint {
|
| 310 |
+
font-size: 11px;
|
| 311 |
+
color: rgba(255,255,255,0.35);
|
| 312 |
+
line-height: 1.5;
|
| 313 |
+
max-width: 300px;
|
| 314 |
+
font-family: monospace;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
#authrix-retry {
|
| 318 |
+
background: rgba(255,68,102,0.1);
|
| 319 |
+
border: 1px solid rgba(255,68,102,0.3);
|
| 320 |
+
color: #ff4466;
|
| 321 |
+
padding: 8px 20px;
|
| 322 |
+
border-radius: 7px;
|
| 323 |
+
font-size: 11px;
|
| 324 |
+
font-weight: 600;
|
| 325 |
+
cursor: pointer;
|
| 326 |
+
transition: all 0.2s;
|
| 327 |
+
font-family: inherit;
|
| 328 |
+
margin-top: 4px;
|
| 329 |
+
}
|
| 330 |
+
#authrix-retry:hover { background: rgba(255,68,102,0.2); }
|
| 331 |
+
|
| 332 |
+
/* ββ Toast ββ */
|
| 333 |
+
.authrix-toast {
|
| 334 |
+
position: fixed;
|
| 335 |
+
bottom: 24px;
|
| 336 |
+
right: 24px;
|
| 337 |
+
z-index: 2147483647;
|
| 338 |
+
padding: 10px 16px;
|
| 339 |
+
border-radius: 8px;
|
| 340 |
+
font-size: 12px;
|
| 341 |
+
font-weight: 600;
|
| 342 |
+
animation: authrix-fade-in 0.2s ease;
|
| 343 |
+
pointer-events: none;
|
| 344 |
+
}
|
| 345 |
+
.authrix-toast-info { background: #0c150f; border: 1px solid rgba(0,255,156,0.3); color: #00ff9c; }
|
| 346 |
+
.authrix-toast-warn { background: #1a0f00; border: 1px solid rgba(255,170,0,0.3); color: #ffaa00; }
|
| 347 |
+
.authrix-toast-error { background: #1a0005; border: 1px solid rgba(255,68,102,0.3); color: #ff4466; }
|
extension/popup.html
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8"/>
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
| 6 |
+
<title>Authrix</title>
|
| 7 |
+
<style>
|
| 8 |
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
| 9 |
+
body {
|
| 10 |
+
width: 340px;
|
| 11 |
+
background: #0c150f;
|
| 12 |
+
color: #dae5da;
|
| 13 |
+
font-family: 'Segoe UI', system-ui, sans-serif;
|
| 14 |
+
font-size: 13px;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
/* Header */
|
| 18 |
+
.header {
|
| 19 |
+
padding: 14px 16px 12px;
|
| 20 |
+
border-bottom: 1px solid rgba(0,255,156,0.12);
|
| 21 |
+
background: rgba(0,255,156,0.04);
|
| 22 |
+
display: flex; align-items: center; justify-content: space-between;
|
| 23 |
+
}
|
| 24 |
+
.logo {
|
| 25 |
+
display: flex; align-items: center; gap: 8px;
|
| 26 |
+
font-size: 14px; font-weight: 700; letter-spacing: 0.12em;
|
| 27 |
+
color: #00ff9c; text-shadow: 0 0 10px rgba(0,255,156,0.4);
|
| 28 |
+
}
|
| 29 |
+
.logo-dot {
|
| 30 |
+
width: 8px; height: 8px; border-radius: 50%;
|
| 31 |
+
background: #00ff9c; box-shadow: 0 0 8px #00ff9c;
|
| 32 |
+
animation: pulse 2s ease-in-out infinite;
|
| 33 |
+
}
|
| 34 |
+
@keyframes pulse {
|
| 35 |
+
0%,100% { opacity:1; box-shadow: 0 0 8px #00ff9c; }
|
| 36 |
+
50% { opacity:0.4; box-shadow: none; }
|
| 37 |
+
}
|
| 38 |
+
.status-badge {
|
| 39 |
+
font-size: 9px; font-weight: 700; letter-spacing: 0.1em;
|
| 40 |
+
padding: 3px 8px; border-radius: 99px;
|
| 41 |
+
border: 1px solid rgba(0,255,156,0.3);
|
| 42 |
+
color: #00ff9c; background: rgba(0,255,156,0.08);
|
| 43 |
+
}
|
| 44 |
+
.status-badge.offline {
|
| 45 |
+
border-color: rgba(255,68,102,0.3);
|
| 46 |
+
color: #ff4466; background: rgba(255,68,102,0.08);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
/* Body */
|
| 50 |
+
.body { padding: 16px; display: flex; flex-direction: column; gap: 12px; }
|
| 51 |
+
|
| 52 |
+
/* Current page section */
|
| 53 |
+
.section-label {
|
| 54 |
+
font-size: 9px; font-weight: 700; letter-spacing: 0.15em;
|
| 55 |
+
color: rgba(255,255,255,0.3); text-transform: uppercase; margin-bottom: 8px;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.page-video-card {
|
| 59 |
+
background: rgba(255,255,255,0.03);
|
| 60 |
+
border: 1px solid rgba(255,255,255,0.07);
|
| 61 |
+
border-radius: 8px; padding: 12px;
|
| 62 |
+
display: flex; align-items: center; gap: 10px;
|
| 63 |
+
}
|
| 64 |
+
.page-video-icon { font-size: 20px; flex-shrink: 0; }
|
| 65 |
+
.page-video-info { flex: 1; min-width: 0; }
|
| 66 |
+
.page-video-title {
|
| 67 |
+
font-size: 11px; font-weight: 600; color: #dae5da;
|
| 68 |
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
| 69 |
+
}
|
| 70 |
+
.page-video-sub { font-size: 10px; color: rgba(255,255,255,0.3); margin-top: 2px; }
|
| 71 |
+
|
| 72 |
+
.btn-analyze-page {
|
| 73 |
+
width: 100%; padding: 11px;
|
| 74 |
+
background: linear-gradient(135deg, #00ff9c, #00cc6a);
|
| 75 |
+
color: #002110; border: none; border-radius: 8px;
|
| 76 |
+
font-size: 12px; font-weight: 700; letter-spacing: 0.1em;
|
| 77 |
+
cursor: pointer; transition: all 0.2s;
|
| 78 |
+
box-shadow: 0 0 20px rgba(0,255,156,0.25);
|
| 79 |
+
}
|
| 80 |
+
.btn-analyze-page:hover {
|
| 81 |
+
transform: translateY(-1px);
|
| 82 |
+
box-shadow: 0 0 30px rgba(0,255,156,0.4);
|
| 83 |
+
}
|
| 84 |
+
.btn-analyze-page:disabled {
|
| 85 |
+
background: rgba(255,255,255,0.08);
|
| 86 |
+
color: rgba(255,255,255,0.2);
|
| 87 |
+
cursor: not-allowed; transform: none;
|
| 88 |
+
box-shadow: none;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
/* URL input */
|
| 92 |
+
.url-input-wrap {
|
| 93 |
+
display: flex; gap: 6px;
|
| 94 |
+
}
|
| 95 |
+
.url-input {
|
| 96 |
+
flex: 1; padding: 9px 12px;
|
| 97 |
+
background: rgba(255,255,255,0.05);
|
| 98 |
+
border: 1px solid rgba(255,255,255,0.1);
|
| 99 |
+
border-radius: 7px; color: #dae5da;
|
| 100 |
+
font-size: 11px; font-family: monospace;
|
| 101 |
+
outline: none; transition: border-color 0.2s;
|
| 102 |
+
}
|
| 103 |
+
.url-input:focus { border-color: rgba(0,255,156,0.4); }
|
| 104 |
+
.url-input::placeholder { color: rgba(255,255,255,0.2); }
|
| 105 |
+
|
| 106 |
+
.btn-go {
|
| 107 |
+
padding: 9px 14px;
|
| 108 |
+
background: rgba(0,255,156,0.1);
|
| 109 |
+
border: 1px solid rgba(0,255,156,0.3);
|
| 110 |
+
color: #00ff9c; border-radius: 7px;
|
| 111 |
+
font-size: 11px; font-weight: 700;
|
| 112 |
+
cursor: pointer; transition: all 0.2s; white-space: nowrap;
|
| 113 |
+
}
|
| 114 |
+
.btn-go:hover { background: rgba(0,255,156,0.18); }
|
| 115 |
+
|
| 116 |
+
/* Divider */
|
| 117 |
+
.divider {
|
| 118 |
+
display: flex; align-items: center; gap: 8px;
|
| 119 |
+
color: rgba(255,255,255,0.15); font-size: 10px;
|
| 120 |
+
}
|
| 121 |
+
.divider::before, .divider::after {
|
| 122 |
+
content: ''; flex: 1; height: 1px;
|
| 123 |
+
background: rgba(255,255,255,0.08);
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
/* Last result */
|
| 127 |
+
#last-result {
|
| 128 |
+
background: rgba(255,255,255,0.03);
|
| 129 |
+
border: 1px solid rgba(255,255,255,0.07);
|
| 130 |
+
border-radius: 8px; padding: 12px;
|
| 131 |
+
display: none;
|
| 132 |
+
}
|
| 133 |
+
.result-row {
|
| 134 |
+
display: flex; align-items: center; justify-content: space-between;
|
| 135 |
+
}
|
| 136 |
+
.result-verdict {
|
| 137 |
+
font-size: 14px; font-weight: 700; letter-spacing: 0.08em;
|
| 138 |
+
}
|
| 139 |
+
.result-conf { font-size: 18px; font-weight: 700; font-family: monospace; }
|
| 140 |
+
.result-bar-wrap {
|
| 141 |
+
height: 4px; background: rgba(255,255,255,0.06);
|
| 142 |
+
border-radius: 99px; overflow: hidden; margin: 8px 0;
|
| 143 |
+
}
|
| 144 |
+
.result-bar { height: 100%; border-radius: 99px; transition: width 0.8s ease; }
|
| 145 |
+
.result-file { font-size: 10px; color: rgba(255,255,255,0.25); font-family: monospace; }
|
| 146 |
+
|
| 147 |
+
/* Footer */
|
| 148 |
+
.footer {
|
| 149 |
+
padding: 10px 16px;
|
| 150 |
+
border-top: 1px solid rgba(255,255,255,0.05);
|
| 151 |
+
display: flex; align-items: center; justify-content: space-between;
|
| 152 |
+
}
|
| 153 |
+
.footer-link {
|
| 154 |
+
font-size: 10px; color: rgba(0,255,156,0.5);
|
| 155 |
+
text-decoration: none; letter-spacing: 0.08em;
|
| 156 |
+
transition: color 0.2s;
|
| 157 |
+
}
|
| 158 |
+
.footer-link:hover { color: #00ff9c; }
|
| 159 |
+
.model-info { font-size: 9px; color: rgba(255,255,255,0.2); letter-spacing: 0.06em; }
|
| 160 |
+
</style>
|
| 161 |
+
</head>
|
| 162 |
+
<body>
|
| 163 |
+
|
| 164 |
+
<!-- Header -->
|
| 165 |
+
<div class="header">
|
| 166 |
+
<div class="logo">
|
| 167 |
+
<div class="logo-dot"></div>
|
| 168 |
+
AUTHRIX AI
|
| 169 |
+
</div>
|
| 170 |
+
<div id="statusBadge" class="status-badge offline">OFFLINE</div>
|
| 171 |
+
</div>
|
| 172 |
+
|
| 173 |
+
<!-- Body -->
|
| 174 |
+
<div class="body">
|
| 175 |
+
|
| 176 |
+
<!-- Analyze current page -->
|
| 177 |
+
<div>
|
| 178 |
+
<div class="section-label">Current Page</div>
|
| 179 |
+
<div class="page-video-card">
|
| 180 |
+
<div class="page-video-icon">π¬</div>
|
| 181 |
+
<div class="page-video-info">
|
| 182 |
+
<div class="page-video-title" id="pageTitle">Detecting videos...</div>
|
| 183 |
+
<div class="page-video-sub" id="pageSub">Scanning page</div>
|
| 184 |
+
</div>
|
| 185 |
+
</div>
|
| 186 |
+
</div>
|
| 187 |
+
|
| 188 |
+
<button class="btn-analyze-page" id="btnAnalyzePage" disabled>
|
| 189 |
+
βΆ Analyze Video on This Page
|
| 190 |
+
</button>
|
| 191 |
+
|
| 192 |
+
<div class="divider">OR</div>
|
| 193 |
+
|
| 194 |
+
<!-- Manual URL -->
|
| 195 |
+
<div>
|
| 196 |
+
<div class="section-label">Paste Video URL</div>
|
| 197 |
+
<div class="url-input-wrap">
|
| 198 |
+
<input class="url-input" id="urlInput" type="url"
|
| 199 |
+
placeholder="https://example.com/video.mp4"/>
|
| 200 |
+
<button class="btn-go" id="btnGo">Analyze</button>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
<!-- Last result -->
|
| 205 |
+
<div id="last-result">
|
| 206 |
+
<div class="section-label" style="margin-bottom:8px;">Last Result</div>
|
| 207 |
+
<div class="result-row">
|
| 208 |
+
<div id="lastVerdict" class="result-verdict">β</div>
|
| 209 |
+
<div id="lastConf" class="result-conf">β</div>
|
| 210 |
+
</div>
|
| 211 |
+
<div class="result-bar-wrap">
|
| 212 |
+
<div id="lastBar" class="result-bar" style="width:0%"></div>
|
| 213 |
+
</div>
|
| 214 |
+
<div id="lastFile" class="result-file">β</div>
|
| 215 |
+
</div>
|
| 216 |
+
|
| 217 |
+
</div>
|
| 218 |
+
|
| 219 |
+
<!-- Footer -->
|
| 220 |
+
<div class="footer">
|
| 221 |
+
<a class="footer-link" href="http://localhost:8000" target="_blank">Open Full App β</a>
|
| 222 |
+
<div class="model-info" id="modelInfo">VIT ENSEMBLE</div>
|
| 223 |
+
</div>
|
| 224 |
+
|
| 225 |
+
<script src="popup.js"></script>
|
| 226 |
+
</body>
|
| 227 |
+
</html>
|
extension/popup.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Authrix Extension β Popup Script
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
const API_BASE = 'http://localhost:8000';
|
| 6 |
+
|
| 7 |
+
// ββ Init ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 8 |
+
document.addEventListener('DOMContentLoaded', async () => {
|
| 9 |
+
await checkHealth();
|
| 10 |
+
await detectPageVideo();
|
| 11 |
+
loadLastResult();
|
| 12 |
+
wireButtons();
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
+
// ββ Health check ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 16 |
+
async function checkHealth() {
|
| 17 |
+
const badge = document.getElementById('statusBadge');
|
| 18 |
+
const modelInfo = document.getElementById('modelInfo');
|
| 19 |
+
try {
|
| 20 |
+
const res = await fetch(`${API_BASE}/health`);
|
| 21 |
+
const d = await res.json();
|
| 22 |
+
if (d.ready) {
|
| 23 |
+
badge.textContent = 'ONLINE';
|
| 24 |
+
badge.classList.remove('offline');
|
| 25 |
+
modelInfo.textContent = (d.model || 'VIT ENSEMBLE').toUpperCase().slice(0, 22);
|
| 26 |
+
} else {
|
| 27 |
+
badge.textContent = 'LOADING';
|
| 28 |
+
}
|
| 29 |
+
} catch {
|
| 30 |
+
badge.textContent = 'OFFLINE';
|
| 31 |
+
badge.classList.add('offline');
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
// ββ Detect video on current tab βββββββββββββββββββββββββββββββββββββββββββββββ
|
| 36 |
+
async function detectPageVideo() {
|
| 37 |
+
const btn = document.getElementById('btnAnalyzePage');
|
| 38 |
+
const title = document.getElementById('pageTitle');
|
| 39 |
+
const sub = document.getElementById('pageSub');
|
| 40 |
+
|
| 41 |
+
try {
|
| 42 |
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
| 43 |
+
if (!tab?.id) return;
|
| 44 |
+
|
| 45 |
+
const results = await chrome.scripting.executeScript({
|
| 46 |
+
target: { tabId: tab.id },
|
| 47 |
+
func: detectVideosOnPage,
|
| 48 |
+
});
|
| 49 |
+
|
| 50 |
+
const found = results?.[0]?.result;
|
| 51 |
+
if (found?.url) {
|
| 52 |
+
title.textContent = found.title || 'Video detected';
|
| 53 |
+
sub.textContent = truncateUrl(found.url);
|
| 54 |
+
btn.disabled = false;
|
| 55 |
+
btn.dataset.url = found.url;
|
| 56 |
+
} else {
|
| 57 |
+
title.textContent = 'No video detected';
|
| 58 |
+
sub.textContent = 'Try right-clicking a video element';
|
| 59 |
+
btn.disabled = true;
|
| 60 |
+
}
|
| 61 |
+
} catch {
|
| 62 |
+
title.textContent = 'Cannot access this page';
|
| 63 |
+
sub.textContent = 'Extension pages are restricted';
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
// ββ Runs in page context ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 68 |
+
function detectVideosOnPage() {
|
| 69 |
+
// Check <video> elements
|
| 70 |
+
const videos = Array.from(document.querySelectorAll('video'));
|
| 71 |
+
for (const v of videos) {
|
| 72 |
+
const src = v.src || v.querySelector('source')?.src;
|
| 73 |
+
if (src && src.startsWith('http')) {
|
| 74 |
+
return { url: src, title: document.title };
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
// YouTube
|
| 78 |
+
const ytMatch = location.href.match(/[?&]v=([^&]+)/);
|
| 79 |
+
if (ytMatch) {
|
| 80 |
+
return { url: location.href, title: document.title };
|
| 81 |
+
}
|
| 82 |
+
return null;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// ββ Wire buttons ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 86 |
+
function wireButtons() {
|
| 87 |
+
// Analyze page video
|
| 88 |
+
document.getElementById('btnAnalyzePage').addEventListener('click', async () => {
|
| 89 |
+
const btn = document.getElementById('btnAnalyzePage');
|
| 90 |
+
const url = btn.dataset.url;
|
| 91 |
+
if (!url) return;
|
| 92 |
+
await injectAndAnalyze(url);
|
| 93 |
+
});
|
| 94 |
+
|
| 95 |
+
// Manual URL
|
| 96 |
+
document.getElementById('btnGo').addEventListener('click', async () => {
|
| 97 |
+
const url = document.getElementById('urlInput').value.trim();
|
| 98 |
+
if (!url) return;
|
| 99 |
+
await injectAndAnalyze(url);
|
| 100 |
+
});
|
| 101 |
+
|
| 102 |
+
document.getElementById('urlInput').addEventListener('keydown', async (e) => {
|
| 103 |
+
if (e.key === 'Enter') {
|
| 104 |
+
const url = e.target.value.trim();
|
| 105 |
+
if (url) await injectAndAnalyze(url);
|
| 106 |
+
}
|
| 107 |
+
});
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
// ββ Inject overlay into active tab and trigger analysis βββββββββββββββββββββββ
|
| 111 |
+
async function injectAndAnalyze(url) {
|
| 112 |
+
try {
|
| 113 |
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
| 114 |
+
if (!tab?.id) return;
|
| 115 |
+
|
| 116 |
+
await chrome.scripting.executeScript({
|
| 117 |
+
target: { tabId: tab.id },
|
| 118 |
+
func: (videoUrl) => {
|
| 119 |
+
if (typeof window.__authrixAnalyze === 'function') {
|
| 120 |
+
window.__authrixAnalyze(videoUrl);
|
| 121 |
+
}
|
| 122 |
+
},
|
| 123 |
+
args: [url],
|
| 124 |
+
});
|
| 125 |
+
|
| 126 |
+
// Close popup so user can see the overlay
|
| 127 |
+
window.close();
|
| 128 |
+
} catch (err) {
|
| 129 |
+
console.error('[Authrix popup]', err);
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
// ββ Load last result from storage ββββββββββββββββββββββββββββββββββββββββββββ
|
| 134 |
+
function loadLastResult() {
|
| 135 |
+
chrome.storage.local.get('lastResult', ({ lastResult }) => {
|
| 136 |
+
if (!lastResult) return;
|
| 137 |
+
const el = document.getElementById('last-result');
|
| 138 |
+
el.style.display = 'block';
|
| 139 |
+
|
| 140 |
+
const isFake = lastResult.result === 'FAKE';
|
| 141 |
+
const color = isFake ? '#ff4466' : '#00ff9c';
|
| 142 |
+
|
| 143 |
+
document.getElementById('lastVerdict').textContent = lastResult.result;
|
| 144 |
+
document.getElementById('lastVerdict').style.color = color;
|
| 145 |
+
document.getElementById('lastConf').textContent = lastResult.confidence + '%';
|
| 146 |
+
document.getElementById('lastConf').style.color = color;
|
| 147 |
+
document.getElementById('lastFile').textContent = lastResult.file || '';
|
| 148 |
+
|
| 149 |
+
const bar = document.getElementById('lastBar');
|
| 150 |
+
bar.style.background = isFake
|
| 151 |
+
? 'linear-gradient(90deg,#880022,#ff4466)'
|
| 152 |
+
: 'linear-gradient(90deg,#006633,#00ff9c)';
|
| 153 |
+
setTimeout(() => { bar.style.width = lastResult.confidence + '%'; }, 80);
|
| 154 |
+
});
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
// ββ Utilities βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 158 |
+
function truncateUrl(url) {
|
| 159 |
+
try {
|
| 160 |
+
const u = new URL(url);
|
| 161 |
+
return u.hostname + u.pathname.slice(0, 25);
|
| 162 |
+
} catch {
|
| 163 |
+
return url.slice(0, 40);
|
| 164 |
+
}
|
| 165 |
+
}
|