Deepfake Authenticator commited on
Commit
0424077
·
1 Parent(s): 3df7b7b

chore: remove unused files — frontend React, stitch designs, stripe scripts, setup scripts

Browse files
.gitignore CHANGED
@@ -10,18 +10,31 @@ dist/
10
  build/
11
  .eggs/
12
 
13
- # Uploads (temp video files)
14
  backend/uploads/
15
 
16
- # HuggingFace model cache (large, re-downloads automatically)
17
- # Uncomment below if you want to exclude the cache too
18
- # ~/.cache/huggingface/
19
-
20
- # Environment
21
  .env
22
  .env.local
23
  *.env
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  # IDE
26
  .vscode/settings.json
27
  .idea/
@@ -33,19 +46,23 @@ backend/uploads/
33
  Thumbs.db
34
  desktop.ini
35
 
36
- # Stitch design files (not needed for deployment)
37
- stitch_authrix_deepfake_detection_engine/
 
 
 
 
 
 
 
 
38
 
39
- # PNG/binary files (use Git LFS or HF Xet for these)
40
  *.png
41
  *.jpg
42
  *.jpeg
43
  *.gif
44
  *.ico
45
 
46
- # Test artifacts
47
- *.mp4
48
- *.avi
49
- *.mov
50
- *.mkv
51
- *.webm
 
10
  build/
11
  .eggs/
12
 
13
+ # Uploads
14
  backend/uploads/
15
 
16
+ # Sensitive files
17
+ backend/api_keys.json
18
+ backend/usage.json
 
 
19
  .env
20
  .env.local
21
  *.env
22
 
23
+ # Unused folders
24
+ frontend/
25
+ stitch_authrix_deepfake_detection_engine/
26
+
27
+ # One-time / internal scripts
28
+ backend/create_owner_key.py
29
+ backend/stripe_integration.py
30
+ STRIPE_SETUP.md
31
+ DEPLOY_HF.md
32
+ render.yaml
33
+ setup.bat
34
+ setup.sh
35
+ start.bat
36
+ start.sh
37
+
38
  # IDE
39
  .vscode/settings.json
40
  .idea/
 
46
  Thumbs.db
47
  desktop.ini
48
 
49
+ # Logs
50
+ *.log
51
+ logs/
52
+
53
+ # Video test artifacts
54
+ *.mp4
55
+ *.avi
56
+ *.mov
57
+ *.mkv
58
+ *.webm
59
 
60
+ # Binary / image files (force-add icons explicitly with git add -f)
61
  *.png
62
  *.jpg
63
  *.jpeg
64
  *.gif
65
  *.ico
66
 
67
+ # Node
68
+ node_modules/
 
 
 
 
DEPLOY_HF.md DELETED
@@ -1,101 +0,0 @@
1
- # Deploy Authrix to Hugging Face Spaces (Free)
2
-
3
- ## Why HF Spaces?
4
- - FREE 16GB RAM
5
- - FREE CPU (and optional GPU)
6
- - Always-on (no sleep on free tier for public spaces)
7
- - Built for AI apps exactly like this
8
-
9
- ---
10
-
11
- ## Step-by-Step Deployment
12
-
13
- ### 1. Create HuggingFace Account
14
- Go to https://huggingface.co/join and sign up (free)
15
-
16
- ### 2. Create a New Space
17
- 1. Go to https://huggingface.co/new-space
18
- 2. Fill in:
19
- - **Space name:** authrix (or authrix-deepfake-detector)
20
- - **License:** MIT
21
- - **SDK:** Docker
22
- - **Visibility:** Public (required for free tier)
23
- 3. Click **Create Space**
24
-
25
- ### 3. Push Your Code
26
-
27
- Install Git LFS first:
28
- ```bash
29
- # Windows
30
- winget install Git.Git
31
- git lfs install
32
- ```
33
-
34
- Then push:
35
- ```bash
36
- # In your project root (E:\DeepFake Detect)
37
- git init
38
- git add .
39
- git commit -m "Initial deployment"
40
-
41
- # Add HuggingFace remote (replace YOUR_USERNAME)
42
- git remote add hf https://huggingface.co/spaces/YOUR_USERNAME/authrix
43
-
44
- # Push (will ask for HF username + token)
45
- git push hf main
46
- ```
47
-
48
- Get your HF token at: https://huggingface.co/settings/tokens
49
- (Create a token with "write" permission)
50
-
51
- ### 4. Wait for Build
52
- - Build takes 10-15 minutes (downloading models)
53
- - Watch progress at: https://huggingface.co/spaces/YOUR_USERNAME/authrix
54
-
55
- ### 5. Update Extension API URL
56
- Once deployed, your app will be at:
57
- `https://YOUR_USERNAME-authrix.hf.space`
58
-
59
- Update `extension/background.js`:
60
- ```javascript
61
- const API_BASE = 'https://YOUR_USERNAME-authrix.hf.space';
62
- ```
63
-
64
- Update `extension/popup.js`:
65
- ```javascript
66
- const API_BASE = 'https://YOUR_USERNAME-authrix.hf.space';
67
- ```
68
-
69
- Update `extension/content.js` (Open Authrix App button):
70
- ```javascript
71
- window.open('https://YOUR_USERNAME-authrix.hf.space', '_blank');
72
- ```
73
-
74
- ---
75
-
76
- ## Limitations of Free HF Spaces
77
-
78
- | Feature | Free Tier |
79
- |---------|-----------|
80
- | RAM | 16GB ✅ |
81
- | CPU | 2 vCPUs ✅ |
82
- | GPU | ❌ (CPU only) |
83
- | Storage | 50GB ✅ |
84
- | Sleep | Never (public spaces) ✅ |
85
- | Custom domain | ❌ (need Pro $9/mo) |
86
- | Private space | ❌ (need Pro) |
87
-
88
- ## Upgrade Options
89
- - **HF Pro ($9/mo):** Custom domain, private spaces, more storage
90
- - **HF GPU Space ($0.60/hr):** 10x faster inference with T4 GPU
91
-
92
- ---
93
-
94
- ## After Deployment
95
-
96
- Your app will be live at:
97
- `https://YOUR_USERNAME-authrix.hf.space`
98
-
99
- Share this URL with anyone — no installation needed!
100
-
101
- The browser extension will also work with this URL once you update `API_BASE`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
STRIPE_SETUP.md DELETED
@@ -1,315 +0,0 @@
1
- # 💳 Stripe Subscription Setup Guide
2
-
3
- ## Your Owner API Key
4
-
5
- ```
6
- 🔑 API Key: authrix_vx5b5HqXIEtAuhUJw92p-aU7Ucz34RtWHzpBCzbKqKE
7
- ⭐ Tier: Owner (Unlimited Access)
8
- ```
9
-
10
- **Save this key securely!** You have unlimited analyses.
11
-
12
- ---
13
-
14
- ## Quick Start: Accept Payments in 30 Minutes
15
-
16
- ### Step 1: Create Stripe Account (5 min)
17
-
18
- 1. Go to https://dashboard.stripe.com/register
19
- 2. Sign up with your email
20
- 3. Complete business verification
21
-
22
- ### Step 2: Get API Keys (2 min)
23
-
24
- 1. Go to https://dashboard.stripe.com/apikeys
25
- 2. Copy your **Secret Key** (starts with `sk_test_` or `sk_live_`)
26
- 3. Add to your environment:
27
-
28
- ```bash
29
- # Windows
30
- set STRIPE_SECRET_KEY=sk_test_YOUR_KEY_HERE
31
-
32
- # Linux/Mac
33
- export STRIPE_SECRET_KEY=sk_test_YOUR_KEY_HERE
34
- ```
35
-
36
- ### Step 3: Create Products & Prices (10 min)
37
-
38
- 1. Go to https://dashboard.stripe.com/products
39
- 2. Click **+ Add product**
40
-
41
- **Create these products:**
42
-
43
- #### Product 1: Authrix Pro
44
- - **Name:** Authrix Pro
45
- - **Description:** 100 video analyses per month
46
- - **Pricing:**
47
- - Monthly: $9.99/month (recurring)
48
- - Yearly: $99/year (recurring)
49
- - Copy the **Price ID** (starts with `price_`)
50
-
51
- #### Product 2: Authrix Business
52
- - **Name:** Authrix Business
53
- - **Description:** 1,000 video analyses per month
54
- - **Pricing:**
55
- - Monthly: $49/month (recurring)
56
- - Yearly: $490/year (recurring)
57
- - Copy the **Price ID**
58
-
59
- ### Step 4: Update Price IDs (2 min)
60
-
61
- Edit `backend/stripe_integration.py`:
62
-
63
- ```python
64
- PRICE_IDS = {
65
- "pro_monthly": "price_YOUR_PRO_MONTHLY_ID",
66
- "pro_yearly": "price_YOUR_PRO_YEARLY_ID",
67
- "business_monthly": "price_YOUR_BUSINESS_MONTHLY_ID",
68
- "business_yearly": "price_YOUR_BUSINESS_YEARLY_ID",
69
- }
70
- ```
71
-
72
- ### Step 5: Set Up Webhooks (5 min)
73
-
74
- 1. Go to https://dashboard.stripe.com/webhooks
75
- 2. Click **+ Add endpoint**
76
- 3. **Endpoint URL:** `https://your-domain.com/api/stripe/webhook`
77
- 4. **Events to send:**
78
- - `checkout.session.completed`
79
- - `customer.subscription.updated`
80
- - `customer.subscription.deleted`
81
- 5. Copy the **Signing secret** (starts with `whsec_`)
82
- 6. Add to environment:
83
-
84
- ```bash
85
- set STRIPE_WEBHOOK_SECRET=whsec_YOUR_SECRET_HERE
86
- ```
87
-
88
- ### Step 6: Test Payment (5 min)
89
-
90
- ```bash
91
- # Start backend
92
- python -m uvicorn main:app --port 8000
93
-
94
- # Test checkout (in another terminal)
95
- curl -X POST http://localhost:8000/api/stripe/create-checkout-session \
96
- -H "Content-Type: application/json" \
97
- -d '{
98
- "email": "test@example.com",
99
- "plan": "pro_monthly",
100
- "success_url": "http://localhost:8000/success",
101
- "cancel_url": "http://localhost:8000/pricing"
102
- }'
103
- ```
104
-
105
- You'll get a `checkout_url` - open it in your browser!
106
-
107
- **Test Card Numbers:**
108
- - Success: `4242 4242 4242 4242`
109
- - Decline: `4000 0000 0000 0002`
110
- - Any future expiry date, any CVC
111
-
112
- ---
113
-
114
- ## Integration Examples
115
-
116
- ### Frontend: Create Checkout
117
-
118
- ```javascript
119
- // When user clicks "Subscribe to Pro"
120
- async function subscribeToPro() {
121
- const response = await fetch('http://localhost:8000/api/stripe/create-checkout-session', {
122
- method: 'POST',
123
- headers: { 'Content-Type': 'application/json' },
124
- body: JSON.stringify({
125
- email: 'user@example.com',
126
- plan: 'pro_monthly',
127
- success_url: window.location.origin + '/success',
128
- cancel_url: window.location.origin + '/pricing'
129
- })
130
- });
131
-
132
- const { checkout_url } = await response.json();
133
- window.location.href = checkout_url; // Redirect to Stripe
134
- }
135
- ```
136
-
137
- ### Backend: Use API with Key
138
-
139
- ```python
140
- import requests
141
-
142
- response = requests.post(
143
- 'http://localhost:8000/analyze',
144
- headers={'X-API-Key': 'authrix_YOUR_KEY_HERE'},
145
- files={'file': open('video.mp4', 'rb')}
146
- )
147
-
148
- print(response.json())
149
- ```
150
-
151
- ### Extension: Add API Key
152
-
153
- Edit `extension/background.js`:
154
-
155
- ```javascript
156
- const API_KEY = 'authrix_vx5b5HqXIEtAuhUJw92p-aU7Ucz34RtWHzpBCzbKqKE';
157
-
158
- async function submitBlob(chunks, mimeType, totalSize) {
159
- // ... existing code ...
160
-
161
- const res = await fetch(`${API_BASE}/analyze`, {
162
- method: 'POST',
163
- headers: {
164
- 'X-API-Key': API_KEY // Add this line
165
- },
166
- body: fd
167
- });
168
-
169
- // ... rest of code ...
170
- }
171
- ```
172
-
173
- ---
174
-
175
- ## Pricing Page HTML
176
-
177
- Create `pricing.html`:
178
-
179
- ```html
180
- <!DOCTYPE html>
181
- <html>
182
- <head>
183
- <title>Authrix Pricing</title>
184
- <script src="https://js.stripe.com/v3/"></script>
185
- </head>
186
- <body>
187
- <h1>Choose Your Plan</h1>
188
-
189
- <div class="pricing-cards">
190
- <!-- Free -->
191
- <div class="card">
192
- <h2>Free</h2>
193
- <p class="price">$0<span>/month</span></p>
194
- <ul>
195
- <li>10 analyses/month</li>
196
- <li>Browser extension</li>
197
- <li>2-min videos</li>
198
- </ul>
199
- <button onclick="window.location.href='/signup'">Get Started</button>
200
- </div>
201
-
202
- <!-- Pro -->
203
- <div class="card popular">
204
- <h2>Pro</h2>
205
- <p class="price">$9.99<span>/month</span></p>
206
- <ul>
207
- <li>100 analyses/month</li>
208
- <li>10-min videos</li>
209
- <li>API access</li>
210
- <li>Email support</li>
211
- </ul>
212
- <button onclick="subscribe('pro_monthly')">Subscribe</button>
213
- </div>
214
-
215
- <!-- Business -->
216
- <div class="card">
217
- <h2>Business</h2>
218
- <p class="price">$49<span>/month</span></p>
219
- <ul>
220
- <li>1,000 analyses/month</li>
221
- <li>Unlimited length</li>
222
- <li>White-label reports</li>
223
- <li>Priority support</li>
224
- </ul>
225
- <button onclick="subscribe('business_monthly')">Subscribe</button>
226
- </div>
227
- </div>
228
-
229
- <script>
230
- async function subscribe(plan) {
231
- const email = prompt('Enter your email:');
232
- if (!email) return;
233
-
234
- const response = await fetch('http://localhost:8000/api/stripe/create-checkout-session', {
235
- method: 'POST',
236
- headers: { 'Content-Type': 'application/json' },
237
- body: JSON.stringify({
238
- email,
239
- plan,
240
- success_url: window.location.origin + '/success',
241
- cancel_url: window.location.origin + '/pricing'
242
- })
243
- });
244
-
245
- const { checkout_url } = await response.json();
246
- window.location.href = checkout_url;
247
- }
248
- </script>
249
- </body>
250
- </html>
251
- ```
252
-
253
- ---
254
-
255
- ## Go Live Checklist
256
-
257
- ### Before Launch:
258
- - [ ] Switch to **Live mode** in Stripe Dashboard
259
- - [ ] Update `STRIPE_SECRET_KEY` with live key (`sk_live_...`)
260
- - [ ] Update webhook endpoint to production URL
261
- - [ ] Test with real card (will charge!)
262
- - [ ] Set up email notifications (welcome, payment failed, etc.)
263
- - [ ] Add terms of service & privacy policy
264
- - [ ] Set up customer support email
265
-
266
- ### After Launch:
267
- - [ ] Monitor Stripe Dashboard for payments
268
- - [ ] Set up Stripe Radar for fraud prevention
269
- - [ ] Enable 3D Secure for EU customers
270
- - [ ] Set up tax collection (Stripe Tax)
271
- - [ ] Create refund policy
272
-
273
- ---
274
-
275
- ## Revenue Tracking
276
-
277
- ### Check Earnings:
278
- ```bash
279
- # Get all API keys and their tiers
280
- python -c "from auth import load_api_keys; import json; print(json.dumps(load_api_keys(), indent=2))"
281
-
282
- # Check usage for a key
283
- python -c "from auth import check_usage_limit; print(check_usage_limit('authrix_YOUR_KEY'))"
284
- ```
285
-
286
- ### Stripe Dashboard:
287
- - **Revenue:** https://dashboard.stripe.com/revenue
288
- - **Customers:** https://dashboard.stripe.com/customers
289
- - **Subscriptions:** https://dashboard.stripe.com/subscriptions
290
-
291
- ---
292
-
293
- ## Support & Resources
294
-
295
- - **Stripe Docs:** https://stripe.com/docs
296
- - **Stripe Testing:** https://stripe.com/docs/testing
297
- - **Webhook Testing:** https://stripe.com/docs/webhooks/test
298
- - **Stripe CLI:** https://stripe.com/docs/stripe-cli
299
-
300
- ---
301
-
302
- ## Next Steps
303
-
304
- 1. **Deploy to production** (Heroku, AWS, DigitalOcean)
305
- 2. **Get a domain** (authrix.ai, authrix.com)
306
- 3. **Set up SSL** (Let's Encrypt, Cloudflare)
307
- 4. **Create landing page** (Webflow, Carrd, custom)
308
- 5. **Launch on Product Hunt**
309
- 6. **Start marketing!**
310
-
311
- ---
312
-
313
- **Questions?** Open an issue or email support@authrix.ai
314
-
315
- **Built with ❤️ by the Authrix Team**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/api_keys.json DELETED
@@ -1,15 +0,0 @@
1
- {
2
- "c522f03ba084fc1150f9ab5bd78bf23e295dbf15cf75d232f3cf0dc3afad3641": {
3
- "email": "demo@authrix.ai",
4
- "tier": "pro",
5
- "created_at": "2026-04-25T20:13:27.568274",
6
- "active": true
7
- },
8
- "b35e8f8fb3db9ee0f6c4bc3b5a01112861de233b93610e5eeca87b75563ab030": {
9
- "email": "owner@authrix.ai",
10
- "tier": "owner",
11
- "created_at": "2026-04-25T20:16:53.342074",
12
- "active": true,
13
- "unlimited": true
14
- }
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/create_owner_key.py DELETED
@@ -1,27 +0,0 @@
1
- """
2
- Create owner API key with unlimited access
3
- """
4
-
5
- from auth import create_api_key, load_api_keys, save_api_keys, hash_key
6
-
7
- # Create owner key
8
- owner_email = "owner@authrix.ai"
9
- owner_key = create_api_key(owner_email, "owner")
10
-
11
- # Update to unlimited tier
12
- keys = load_api_keys()
13
- key_hash = hash_key(owner_key)
14
- keys[key_hash]["tier"] = "owner"
15
- keys[key_hash]["unlimited"] = True
16
- save_api_keys(keys)
17
-
18
- print("\n" + "="*60)
19
- print("🎉 OWNER API KEY CREATED")
20
- print("="*60)
21
- print(f"\n📧 Email: {owner_email}")
22
- print(f"🔑 API Key: {owner_key}")
23
- print(f"⭐ Tier: Owner (Unlimited)")
24
- print(f"\n💡 Add this to your requests:")
25
- print(f' X-API-Key: {owner_key}')
26
- print(f"\n📝 Save this key securely - it won't be shown again!")
27
- print("="*60 + "\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/stripe_integration.py DELETED
@@ -1,264 +0,0 @@
1
- """
2
- Authrix - Stripe Subscription Integration
3
- """
4
-
5
- import os
6
- from typing import Optional
7
- import stripe
8
- from fastapi import APIRouter, HTTPException, Request
9
- from pydantic import BaseModel
10
- import logging
11
-
12
- from auth import create_api_key, load_api_keys, save_api_keys, hash_key
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
- # Set your Stripe secret key (get from https://dashboard.stripe.com/apikeys)
17
- stripe.api_key = os.getenv("STRIPE_SECRET_KEY", "sk_test_YOUR_KEY_HERE")
18
- STRIPE_WEBHOOK_SECRET = os.getenv("STRIPE_WEBHOOK_SECRET", "whsec_YOUR_WEBHOOK_SECRET")
19
-
20
- router = APIRouter(prefix="/api/stripe", tags=["stripe"])
21
-
22
- # Stripe Price IDs (create these in Stripe Dashboard)
23
- PRICE_IDS = {
24
- "pro_monthly": "price_PRO_MONTHLY_ID", # $9.99/month
25
- "pro_yearly": "price_PRO_YEARLY_ID", # $99/year (2 months free)
26
- "business_monthly": "price_BUSINESS_MONTHLY_ID", # $49/month
27
- "business_yearly": "price_BUSINESS_YEARLY_ID", # $490/year
28
- }
29
-
30
- class CheckoutRequest(BaseModel):
31
- email: str
32
- plan: str # "pro_monthly", "pro_yearly", "business_monthly", "business_yearly"
33
- success_url: str
34
- cancel_url: str
35
-
36
- class PortalRequest(BaseModel):
37
- customer_id: str
38
- return_url: str
39
-
40
- @router.post("/create-checkout-session")
41
- async def create_checkout_session(request: CheckoutRequest):
42
- """
43
- Create a Stripe Checkout session for subscription.
44
-
45
- Usage:
46
- POST /api/stripe/create-checkout-session
47
- {
48
- "email": "user@example.com",
49
- "plan": "pro_monthly",
50
- "success_url": "https://authrix.ai/success",
51
- "cancel_url": "https://authrix.ai/pricing"
52
- }
53
- """
54
- try:
55
- price_id = PRICE_IDS.get(request.plan)
56
- if not price_id:
57
- raise HTTPException(status_code=400, detail=f"Invalid plan: {request.plan}")
58
-
59
- # Create Stripe Checkout Session
60
- session = stripe.checkout.Session.create(
61
- customer_email=request.email,
62
- payment_method_types=["card"],
63
- line_items=[{
64
- "price": price_id,
65
- "quantity": 1,
66
- }],
67
- mode="subscription",
68
- success_url=request.success_url + "?session_id={CHECKOUT_SESSION_ID}",
69
- cancel_url=request.cancel_url,
70
- metadata={
71
- "email": request.email,
72
- "plan": request.plan,
73
- },
74
- )
75
-
76
- return {"checkout_url": session.url, "session_id": session.id}
77
-
78
- except stripe.error.StripeError as e:
79
- logger.error(f"Stripe error: {e}")
80
- raise HTTPException(status_code=400, detail=str(e))
81
-
82
- @router.post("/create-portal-session")
83
- async def create_portal_session(request: PortalRequest):
84
- """
85
- Create a Stripe Customer Portal session for managing subscription.
86
-
87
- Usage:
88
- POST /api/stripe/create-portal-session
89
- {
90
- "customer_id": "cus_XXXXX",
91
- "return_url": "https://authrix.ai/account"
92
- }
93
- """
94
- try:
95
- session = stripe.billing_portal.Session.create(
96
- customer=request.customer_id,
97
- return_url=request.return_url,
98
- )
99
- return {"portal_url": session.url}
100
-
101
- except stripe.error.StripeError as e:
102
- logger.error(f"Stripe error: {e}")
103
- raise HTTPException(status_code=400, detail=str(e))
104
-
105
- @router.post("/webhook")
106
- async def stripe_webhook(request: Request):
107
- """
108
- Handle Stripe webhook events.
109
-
110
- Events handled:
111
- - checkout.session.completed: Create API key when subscription starts
112
- - customer.subscription.updated: Update tier when subscription changes
113
- - customer.subscription.deleted: Downgrade to free when subscription cancels
114
- """
115
- payload = await request.body()
116
- sig_header = request.headers.get("stripe-signature")
117
-
118
- try:
119
- event = stripe.Webhook.construct_event(
120
- payload, sig_header, STRIPE_WEBHOOK_SECRET
121
- )
122
- except ValueError:
123
- raise HTTPException(status_code=400, detail="Invalid payload")
124
- except stripe.error.SignatureVerificationError:
125
- raise HTTPException(status_code=400, detail="Invalid signature")
126
-
127
- # Handle the event
128
- if event["type"] == "checkout.session.completed":
129
- session = event["data"]["object"]
130
- await handle_checkout_completed(session)
131
-
132
- elif event["type"] == "customer.subscription.updated":
133
- subscription = event["data"]["object"]
134
- await handle_subscription_updated(subscription)
135
-
136
- elif event["type"] == "customer.subscription.deleted":
137
- subscription = event["data"]["object"]
138
- await handle_subscription_deleted(subscription)
139
-
140
- return {"status": "success"}
141
-
142
- async def handle_checkout_completed(session: dict):
143
- """Create API key when checkout completes."""
144
- email = session["metadata"]["email"]
145
- plan = session["metadata"]["plan"]
146
- customer_id = session["customer"]
147
- subscription_id = session["subscription"]
148
-
149
- # Determine tier from plan
150
- tier = "pro" if "pro" in plan else "business"
151
-
152
- # Create API key
153
- api_key = create_api_key(email, tier)
154
-
155
- # Store Stripe customer ID and subscription ID
156
- keys = load_api_keys()
157
- key_hash = hash_key(api_key)
158
- keys[key_hash]["stripe_customer_id"] = customer_id
159
- keys[key_hash]["stripe_subscription_id"] = subscription_id
160
- save_api_keys(keys)
161
-
162
- logger.info(f"Created {tier} API key for {email} (customer: {customer_id})")
163
-
164
- # TODO: Send welcome email with API key
165
- # send_email(email, "Welcome to Authrix!", f"Your API key: {api_key}")
166
-
167
- async def handle_subscription_updated(subscription: dict):
168
- """Update tier when subscription changes."""
169
- customer_id = subscription["customer"]
170
- status = subscription["status"]
171
-
172
- # Find API key by customer ID
173
- keys = load_api_keys()
174
- for key_hash, key_data in keys.items():
175
- if key_data.get("stripe_customer_id") == customer_id:
176
- if status == "active":
177
- # Subscription is active - ensure tier is correct
178
- logger.info(f"Subscription active for {key_data['email']}")
179
- elif status in ("past_due", "unpaid"):
180
- # Payment failed - send warning
181
- logger.warning(f"Payment issue for {key_data['email']}")
182
- break
183
-
184
- async def handle_subscription_deleted(subscription: dict):
185
- """Downgrade to free when subscription cancels."""
186
- customer_id = subscription["customer"]
187
-
188
- # Find API key and downgrade to free
189
- keys = load_api_keys()
190
- for key_hash, key_data in keys.items():
191
- if key_data.get("stripe_customer_id") == customer_id:
192
- keys[key_hash]["tier"] = "free"
193
- keys[key_hash]["active"] = False # Deactivate key
194
- save_api_keys(keys)
195
- logger.info(f"Downgraded {key_data['email']} to free tier")
196
- break
197
-
198
- # Pricing plans for frontend
199
- PRICING_PLANS = {
200
- "free": {
201
- "name": "Free",
202
- "price": 0,
203
- "interval": "month",
204
- "analyses": 10,
205
- "features": [
206
- "10 video analyses per month",
207
- "Browser extension",
208
- "Max 2-minute videos",
209
- "Community support",
210
- ],
211
- },
212
- "pro": {
213
- "name": "Pro",
214
- "price_monthly": 9.99,
215
- "price_yearly": 99,
216
- "interval": "month",
217
- "analyses": 100,
218
- "features": [
219
- "100 analyses per month",
220
- "Up to 10-minute videos",
221
- "API access (100 calls/month)",
222
- "Priority processing",
223
- "Email support",
224
- "Batch upload",
225
- ],
226
- "popular": True,
227
- },
228
- "business": {
229
- "name": "Business",
230
- "price_monthly": 49,
231
- "price_yearly": 490,
232
- "interval": "month",
233
- "analyses": 1000,
234
- "features": [
235
- "1,000 analyses per month",
236
- "Unlimited video length",
237
- "API access (5,000 calls/month)",
238
- "White-label reports",
239
- "Slack/Teams integration",
240
- "Priority support",
241
- "Custom branding",
242
- ],
243
- },
244
- "enterprise": {
245
- "name": "Enterprise",
246
- "price": "Custom",
247
- "interval": "month",
248
- "analyses": "Unlimited",
249
- "features": [
250
- "Unlimited analyses",
251
- "On-premise deployment",
252
- "Custom model training",
253
- "SLA guarantees",
254
- "Dedicated support",
255
- "Multi-user accounts",
256
- ],
257
- "contact": True,
258
- },
259
- }
260
-
261
- @router.get("/pricing")
262
- async def get_pricing():
263
- """Get pricing plans."""
264
- return PRICING_PLANS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/.gitignore DELETED
@@ -1,24 +0,0 @@
1
- # Logs
2
- logs
3
- *.log
4
- npm-debug.log*
5
- yarn-debug.log*
6
- yarn-error.log*
7
- pnpm-debug.log*
8
- lerna-debug.log*
9
-
10
- node_modules
11
- dist
12
- dist-ssr
13
- *.local
14
-
15
- # Editor directories and files
16
- .vscode/*
17
- !.vscode/extensions.json
18
- .idea
19
- .DS_Store
20
- *.suo
21
- *.ntvs*
22
- *.njsproj
23
- *.sln
24
- *.sw?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/README.md DELETED
@@ -1,16 +0,0 @@
1
- # React + Vite
2
-
3
- This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
-
5
- Currently, two official plugins are available:
6
-
7
- - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8
- - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
9
-
10
- ## React Compiler
11
-
12
- The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
-
14
- ## Expanding the ESLint configuration
15
-
16
- If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/eslint.config.js DELETED
@@ -1,21 +0,0 @@
1
- import js from '@eslint/js'
2
- import globals from 'globals'
3
- import reactHooks from 'eslint-plugin-react-hooks'
4
- import reactRefresh from 'eslint-plugin-react-refresh'
5
- import { defineConfig, globalIgnores } from 'eslint/config'
6
-
7
- export default defineConfig([
8
- globalIgnores(['dist']),
9
- {
10
- files: ['**/*.{js,jsx}'],
11
- extends: [
12
- js.configs.recommended,
13
- reactHooks.configs.flat.recommended,
14
- reactRefresh.configs.vite,
15
- ],
16
- languageOptions: {
17
- globals: globals.browser,
18
- parserOptions: { ecmaFeatures: { jsx: true } },
19
- },
20
- },
21
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/index.html DELETED
@@ -1,14 +0,0 @@
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>Deepfake Authenticator</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
9
- </head>
10
- <body>
11
- <div id="root"></div>
12
- <script type="module" src="/src/main.tsx"></script>
13
- </body>
14
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/package-lock.json DELETED
The diff for this file is too large to render. See raw diff
 
frontend/package.json DELETED
@@ -1,37 +0,0 @@
1
- {
2
- "name": "frontend",
3
- "private": true,
4
- "version": "0.0.0",
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "vite build",
9
- "lint": "eslint .",
10
- "preview": "vite preview"
11
- },
12
- "dependencies": {
13
- "@react-three/drei": "^10.7.7",
14
- "@react-three/fiber": "^9.6.0",
15
- "@tailwindcss/vite": "^4.2.4",
16
- "autoprefixer": "^10.5.0",
17
- "framer-motion": "^12.38.0",
18
- "postcss": "^8.5.10",
19
- "react": "^19.2.5",
20
- "react-dom": "^19.2.5",
21
- "styled-components": "^6.4.1",
22
- "tailwindcss": "^4.2.4",
23
- "three": "^0.184.0",
24
- "zustand": "^5.0.12"
25
- },
26
- "devDependencies": {
27
- "@eslint/js": "^10.0.1",
28
- "@types/react": "^19.2.14",
29
- "@types/react-dom": "^19.2.3",
30
- "@vitejs/plugin-react": "^6.0.1",
31
- "eslint": "^10.2.1",
32
- "eslint-plugin-react-hooks": "^7.1.1",
33
- "eslint-plugin-react-refresh": "^0.5.2",
34
- "globals": "^17.5.0",
35
- "vite": "^8.0.10"
36
- }
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/public/favicon.svg DELETED
frontend/public/icons.svg DELETED
frontend/script.js DELETED
@@ -1,438 +0,0 @@
1
- /**
2
- * Deepfake Authenticator — Frontend Logic (Cinematic Dark UI)
3
- */
4
-
5
- const API_BASE = (window.location.protocol === 'file:')
6
- ? 'http://localhost:8000'
7
- : window.location.origin;
8
-
9
- let selectedFile = null;
10
-
11
- // ── Boot ──────────────────────────────────────
12
- window.addEventListener('load', () => {
13
- initUpload();
14
- });
15
-
16
- // ── Upload wiring ─────────────────────────────
17
- function initUpload() {
18
- const zone = document.getElementById('dropZone');
19
- const input = document.getElementById('fileInput');
20
- const clear = document.getElementById('clearBtn');
21
- const btn = document.getElementById('analyzeBtn');
22
-
23
- zone.addEventListener('click', e => {
24
- if (!e.target.closest('#clearBtn')) input.click();
25
- });
26
-
27
- input.addEventListener('change', () => {
28
- if (input.files?.[0]) applyFile(input.files[0]);
29
- });
30
-
31
- clear.addEventListener('click', e => {
32
- e.stopPropagation();
33
- clearFile();
34
- });
35
-
36
- zone.addEventListener('dragover', e => {
37
- e.preventDefault(); e.stopPropagation();
38
- zone.classList.add('drag-over');
39
- });
40
-
41
- zone.addEventListener('dragleave', e => {
42
- e.preventDefault();
43
- zone.classList.remove('drag-over');
44
- });
45
-
46
- zone.addEventListener('drop', e => {
47
- e.preventDefault(); e.stopPropagation();
48
- zone.classList.remove('drag-over');
49
- const f = e.dataTransfer.files[0];
50
- if (f?.type.startsWith('video/')) applyFile(f);
51
- else showError('Please drop a valid video file (MP4, AVI, MOV, MKV, WebM).');
52
- });
53
-
54
- btn.addEventListener('click', analyzeVideo);
55
- }
56
-
57
- function applyFile(file) {
58
- selectedFile = file;
59
- document.getElementById('uploadPrompt').classList.add('hidden');
60
- const fc = document.getElementById('fileChosen');
61
- fc.classList.remove('hidden');
62
- document.getElementById('chosenName').textContent = file.name;
63
- document.getElementById('chosenSize').textContent = fmtBytes(file.size);
64
- const btn = document.getElementById('analyzeBtn');
65
- btn.disabled = false;
66
- btn.classList.add('active');
67
- }
68
-
69
- function clearFile() {
70
- selectedFile = null;
71
- document.getElementById('fileInput').value = '';
72
- document.getElementById('fileChosen').classList.add('hidden');
73
- document.getElementById('uploadPrompt').classList.remove('hidden');
74
- const btn = document.getElementById('analyzeBtn');
75
- btn.disabled = true;
76
- btn.classList.remove('active');
77
- }
78
-
79
- function resetAll() {
80
- clearFile();
81
- ['loadingSection', 'resultSection', 'errorSection'].forEach(hide);
82
- show('uploadSection');
83
- }
84
-
85
- // ── Analyze ───────────────────────────────────
86
- async function analyzeVideo() {
87
- if (!selectedFile) return;
88
-
89
- hide('uploadSection');
90
- show('loadingSection');
91
- ['resultSection', 'errorSection'].forEach(hide);
92
-
93
- startAgentAnim();
94
-
95
- const fd = new FormData();
96
- fd.append('file', selectedFile);
97
-
98
- try {
99
- const res = await fetch(`${API_BASE}/analyze`, { method: 'POST', body: fd });
100
- if (!res.ok) {
101
- const e = await res.json().catch(() => ({}));
102
- throw new Error(e.detail || `Server error ${res.status}`);
103
- }
104
- const data = await res.json();
105
- console.log('[API Response] keys:', Object.keys(data), '| frame_timeline length:', data.frame_timeline?.length);
106
- renderResult(data);
107
- } catch (err) {
108
- showError(err.message || 'Connection to analysis engine failed.');
109
- } finally {
110
- hide('loadingSection');
111
- stopAgentAnim();
112
- }
113
- }
114
-
115
- // ── Loading Animation ─────────────────────────
116
- let _simTimer = null;
117
- let _agentTimer = null;
118
-
119
- function startAgentAnim() {
120
- const statusEl = document.getElementById('loadingStatus');
121
- const progBar = document.getElementById('progressBar');
122
-
123
- const phases = [
124
- { p: 12, msg: 'Extracting keyframes...', ag: 0 },
125
- { p: 35, msg: 'Isolating facial regions...', ag: 1 },
126
- { p: 65, msg: 'Running ViT neural inference...', ag: 2 },
127
- { p: 85, msg: 'Cross-referencing metadata...', ag: 2 },
128
- { p: 95, msg: 'Compiling authenticity report...', ag: 3 },
129
- ];
130
-
131
- // Reset agents
132
- [0,1,2,3].forEach(i => {
133
- const card = document.getElementById('ag' + i);
134
- if (card) card.classList.remove('active');
135
- });
136
-
137
- statusEl.textContent = 'Initializing sequence...';
138
- progBar.style.width = '0%';
139
-
140
- let idx = 0;
141
- _simTimer = setInterval(() => {
142
- if (idx < phases.length) {
143
- const ph = phases[idx];
144
- statusEl.textContent = ph.msg;
145
- progBar.style.width = ph.p + '%';
146
- const card = document.getElementById('ag' + ph.ag);
147
- if (card) card.classList.add('active');
148
- idx++;
149
- }
150
- }, 1100);
151
- }
152
-
153
- function stopAgentAnim() {
154
- if (_simTimer) { clearInterval(_simTimer); _simTimer = null; }
155
- document.getElementById('progressBar').style.width = '100%';
156
- [0,1,2,3].forEach(i => {
157
- const card = document.getElementById('ag' + i);
158
- if (card) card.classList.add('active');
159
- });
160
- }
161
-
162
- // ── Render Result ─────────────────────────────
163
- function renderResult(data) {
164
- const isFake = data.result === 'FAKE';
165
- const pct = data.confidence;
166
-
167
- // Verdict card border glow
168
- const vc = document.getElementById('verdictCard');
169
- vc.style.borderColor = isFake ? 'rgba(255,51,85,0.5)' : 'rgba(0,255,136,0.5)';
170
- vc.style.boxShadow = isFake
171
- ? '0 0 50px rgba(255,51,85,0.15), inset 0 0 30px rgba(255,51,85,0.05)'
172
- : '0 0 50px rgba(0,255,136,0.15), inset 0 0 30px rgba(0,255,136,0.05)';
173
-
174
- // Badge
175
- const badge = document.getElementById('verdictBadge');
176
- badge.className = 'verdict-badge ' + (isFake ? 'fake' : 'real');
177
- badge.style.background = isFake ? 'rgba(255,51,85,0.08)' : 'rgba(0,255,136,0.08)';
178
-
179
- // Emoji
180
- document.getElementById('verdictEmoji').textContent = isFake ? '⚠' : '✓';
181
-
182
- // Label
183
- const lbl = document.getElementById('verdictLabel');
184
- lbl.textContent = isFake ? 'DEEPFAKE' : 'AUTHENTIC';
185
- lbl.style.color = isFake ? '#ff3355' : '#00ff88';
186
- lbl.style.textShadow = isFake
187
- ? '0 0 20px rgba(255,51,85,0.7)'
188
- : '0 0 20px rgba(0,255,136,0.7)';
189
- if (isFake) lbl.classList.add('glitch-text');
190
- else lbl.classList.remove('glitch-text');
191
-
192
- // Confidence value
193
- const cv = document.getElementById('confValue');
194
- cv.textContent = pct + '%';
195
- cv.style.color = isFake ? '#ff3355' : '#00ff88';
196
-
197
- // Confidence bar
198
- const bar = document.getElementById('confBar');
199
- bar.className = 'conf-fill ' + (isFake ? 'fake' : 'real');
200
- setTimeout(() => { bar.style.width = pct + '%'; }, 80);
201
-
202
- // Risk needle
203
- const needle = document.getElementById('riskNeedle');
204
- const riskLbl = document.getElementById('riskLabel');
205
- if (pct < 35) {
206
- needle.textContent = 'LOW RISK';
207
- needle.style.color = '#00ff88';
208
- needle.style.borderColor = 'rgba(0,255,136,0.35)';
209
- needle.style.background = 'rgba(0,255,136,0.08)';
210
- if (riskLbl) riskLbl.textContent = 'Minimal manipulation indicators detected';
211
- } else if (pct < 65) {
212
- needle.textContent = 'MEDIUM RISK';
213
- needle.style.color = '#ffaa00';
214
- needle.style.borderColor = 'rgba(255,170,0,0.35)';
215
- needle.style.background = 'rgba(255,170,0,0.08)';
216
- if (riskLbl) riskLbl.textContent = 'Moderate anomalies detected — review advised';
217
- } else {
218
- needle.textContent = 'CRITICAL RISK';
219
- needle.style.color = '#ff3355';
220
- needle.style.borderColor = 'rgba(255,51,85,0.35)';
221
- needle.style.background = 'rgba(255,51,85,0.08)';
222
- if (riskLbl) riskLbl.textContent = 'High-confidence manipulation signatures found';
223
- }
224
-
225
- // Insights
226
- const dl = document.getElementById('detailsList');
227
- dl.innerHTML = '';
228
- const details = data.details || ['Analysis completed successfully.'];
229
- const dotColor = isFake ? '#ff3355' : '#00ff88';
230
- const dotGlow = isFake ? 'rgba(255,51,85,0.6)' : 'rgba(0,255,136,0.6)';
231
- details.forEach((txt, i) => {
232
- const div = document.createElement('div');
233
- div.className = 'insight-item';
234
- div.style.animationDelay = (i * 0.08) + 's';
235
- div.style.borderLeftColor = dotColor;
236
- div.style.borderLeft = `2px solid ${dotColor}`;
237
- div.innerHTML = `<span class="insight-dot" style="background:${dotColor};box-shadow:0 0 8px ${dotGlow};"></span><span>${esc(txt)}</span>`;
238
- dl.appendChild(div);
239
- });
240
-
241
- // Metadata
242
- const meta = data.metadata || {};
243
- const mg = document.getElementById('metaGrid');
244
- mg.innerHTML = '';
245
- const metaItems = [
246
- ['Frames Analyzed', meta.frames_analyzed ?? '—'],
247
- ['Duration', meta.video_duration_sec ? meta.video_duration_sec + 's' : '—'],
248
- ['FPS', meta.video_fps ?? '—'],
249
- ['Resolution', meta.resolution ?? '—'],
250
- ['Processing Time', data.processing_time_sec ? data.processing_time_sec + 's' : '—'],
251
- ];
252
- metaItems.forEach(([k, v]) => {
253
- const row = document.createElement('div');
254
- row.className = 'meta-row';
255
- row.innerHTML = `<span style="font-size:11px;color:var(--muted);letter-spacing:0.08em;">${k}</span><span style="font-size:13px;font-weight:600;color:#fff;font-family:'JetBrains Mono',monospace;">${v}</span>`;
256
- mg.appendChild(row);
257
- });
258
-
259
- // Frame timeline
260
- renderTimeline(data, isFake);
261
-
262
- // Audio result
263
- renderAudio(data.audio || null);
264
-
265
- show('resultSection');
266
- }
267
-
268
- function renderTimeline(data, isFake) {
269
- const chart = document.getElementById('timelineChart');
270
- if (!chart) return;
271
- chart.innerHTML = '';
272
-
273
- // Backend sends frame_timeline: [{frame, fake_pct}, ...]
274
- const frames = data.frame_timeline || data.frame_scores || [];
275
- console.log('[Timeline] frame_timeline:', data.frame_timeline?.length, 'frames:', frames.length, 'sample:', frames[0]);
276
- if (!frames.length) {
277
- chart.innerHTML = '<span style="font-size:11px;color:var(--muted);font-family:\'JetBrains Mono\',monospace;margin:auto;">No per-frame data available</span>';
278
- return;
279
- }
280
-
281
- const maxH = 60; // px
282
- const barColor = isFake ? '#ff3355' : '#00ff88';
283
- const barGlow = isFake ? 'rgba(255,51,85,0.5)' : 'rgba(0,255,136,0.5)';
284
-
285
- frames.forEach((point, i) => {
286
- // Support both {fake_pct: 72.1} and {fake_probability: 0.721}
287
- const pct = point.fake_pct != null ? point.fake_pct : (point.fake_probability * 100);
288
- const score = pct / 100;
289
- const h = Math.max(4, Math.round(score * maxH));
290
- const hot = pct >= 60;
291
-
292
- const wrap = document.createElement('div');
293
- wrap.className = 'bar-wrap';
294
- wrap.style.height = maxH + 'px';
295
-
296
- const outer = document.createElement('div');
297
- outer.className = 'bar-outer';
298
- outer.style.height = maxH + 'px';
299
-
300
- const inner = document.createElement('div');
301
- inner.className = 'bar-inner';
302
- inner.style.height = '0px';
303
- inner.style.background = hot
304
- ? `linear-gradient(to top, ${barColor}, rgba(255,255,255,0.3))`
305
- : 'rgba(255,255,255,0.12)';
306
- if (hot) inner.style.boxShadow = `0 0 8px ${barGlow}`;
307
-
308
- outer.appendChild(inner);
309
-
310
- const tip = document.createElement('div');
311
- tip.className = 'bar-tooltip';
312
- tip.textContent = `Frame ${point.frame != null ? point.frame : i}: ${pct.toFixed(1)}%`;
313
-
314
- wrap.appendChild(outer);
315
- wrap.appendChild(tip);
316
- chart.appendChild(wrap);
317
-
318
- setTimeout(() => { inner.style.height = h + 'px'; }, 50 + i * 20);
319
- });
320
-
321
- // Threshold line at 50%
322
- const line = document.createElement('div');
323
- line.style.cssText = `position:absolute;left:0;right:0;bottom:${maxH*0.5}px;height:1px;background:rgba(255,170,0,0.4);pointer-events:none;`;
324
- const lineLbl = document.createElement('span');
325
- lineLbl.style.cssText = 'position:absolute;right:4px;top:-14px;font-size:9px;color:#ffaa00;font-family:\'JetBrains Mono\',monospace;letter-spacing:0.1em;';
326
- lineLbl.textContent = '50%';
327
- line.appendChild(lineLbl);
328
- chart.appendChild(line);
329
- }
330
-
331
- // ── Helpers ───────────────────────────────────
332
- function show(id) {
333
- const el = document.getElementById(id);
334
- if (!el) return;
335
- el.classList.remove('hidden');
336
- if (el.style.display === 'none') el.style.display = '';
337
- }
338
-
339
- function hide(id) {
340
- const el = document.getElementById(id);
341
- if (el) el.classList.add('hidden');
342
- }
343
-
344
- function fmtBytes(b) {
345
- if (b < 1024) return b + ' B';
346
- if (b < 1048576) return (b / 1024).toFixed(1) + ' KB';
347
- return (b / 1048576).toFixed(1) + ' MB';
348
- }
349
-
350
- function esc(s) {
351
- return String(s)
352
- .replace(/&/g, '&amp;')
353
- .replace(/</g, '&lt;')
354
- .replace(/>/g, '&gt;');
355
- }
356
-
357
- function showError(msg) {
358
- hide('uploadSection');
359
- hide('loadingSection');
360
- document.getElementById('errorMsg').textContent = msg;
361
- show('errorSection');
362
- }
363
-
364
- // ── Audio Result ──────────────────────────────
365
- function renderAudio(audio) {
366
- const section = document.getElementById('audioSection');
367
- if (!section) return;
368
-
369
- if (!audio || !audio.available) {
370
- section.classList.add('hidden');
371
- return;
372
- }
373
-
374
- section.classList.remove('hidden');
375
-
376
- const isAI = audio.result === 'AI_VOICE';
377
- const pct = audio.confidence;
378
- const color = isAI ? '#ff3355' : '#00ff88';
379
- const colorDim = isAI ? 'rgba(255,51,85,0.15)' : 'rgba(0,255,136,0.15)';
380
-
381
- // Header badge
382
- const badge = document.getElementById('audioBadge');
383
- if (badge) {
384
- badge.textContent = isAI ? '🤖 AI VOICE DETECTED' : '🎙️ HUMAN VOICE';
385
- badge.style.color = color;
386
- badge.style.borderColor = color + '55';
387
- badge.style.background = colorDim;
388
- }
389
-
390
- // Scores
391
- const modelScore = document.getElementById('audioModelScore');
392
- const heurScore = document.getElementById('audioHeurScore');
393
- if (modelScore) modelScore.textContent = audio.model_score + '%';
394
- if (heurScore) heurScore.textContent = audio.heuristic_score + '%';
395
-
396
- // Bar
397
- const bar = document.getElementById('audioBar');
398
- if (bar) {
399
- bar.style.background = isAI
400
- ? 'linear-gradient(90deg,#880022,#ff3355)'
401
- : 'linear-gradient(90deg,#00aaff,#00ff88)';
402
- bar.style.boxShadow = `0 0 12px ${color}66`;
403
- setTimeout(() => { bar.style.width = pct + '%'; }, 80);
404
- }
405
-
406
- // Details
407
- const dl = document.getElementById('audioDetailsList');
408
- if (dl) {
409
- dl.innerHTML = '';
410
- (audio.details || []).forEach((txt, i) => {
411
- const div = document.createElement('div');
412
- div.className = 'insight-item';
413
- div.style.animationDelay = (i * 0.07) + 's';
414
- div.style.borderLeft = `2px solid ${color}`;
415
- div.innerHTML = `<span class="insight-dot" style="background:${color};box-shadow:0 0 6px ${color}88;"></span><span>${esc(txt)}</span>`;
416
- dl.appendChild(div);
417
- });
418
- }
419
-
420
- // Features
421
- const feat = audio.features || {};
422
- const featGrid = document.getElementById('audioFeatGrid');
423
- if (featGrid) {
424
- featGrid.innerHTML = '';
425
- const items = [
426
- ['Pitch Std Dev', feat.pitch_std_hz != null ? feat.pitch_std_hz + ' Hz' : '—'],
427
- ['MFCC Δ Variance', feat.mfcc_delta_var != null ? feat.mfcc_delta_var : '—'],
428
- ['Spectral Flatness', feat.spectral_flatness != null ? feat.spectral_flatness : '—'],
429
- ['Silence Ratio', feat.silence_ratio != null ? (feat.silence_ratio * 100).toFixed(1) + '%' : '—'],
430
- ];
431
- items.forEach(([k, v]) => {
432
- const row = document.createElement('div');
433
- row.className = 'meta-row';
434
- row.innerHTML = `<span style="font-size:11px;color:var(--muted);">${k}</span><span style="font-size:12px;font-weight:600;color:#fff;font-family:'JetBrains Mono',monospace;">${v}</span>`;
435
- featGrid.appendChild(row);
436
- });
437
- }
438
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/App.css DELETED
@@ -1,184 +0,0 @@
1
- .counter {
2
- font-size: 16px;
3
- padding: 5px 10px;
4
- border-radius: 5px;
5
- color: var(--accent);
6
- background: var(--accent-bg);
7
- border: 2px solid transparent;
8
- transition: border-color 0.3s;
9
- margin-bottom: 24px;
10
-
11
- &:hover {
12
- border-color: var(--accent-border);
13
- }
14
- &:focus-visible {
15
- outline: 2px solid var(--accent);
16
- outline-offset: 2px;
17
- }
18
- }
19
-
20
- .hero {
21
- position: relative;
22
-
23
- .base,
24
- .framework,
25
- .vite {
26
- inset-inline: 0;
27
- margin: 0 auto;
28
- }
29
-
30
- .base {
31
- width: 170px;
32
- position: relative;
33
- z-index: 0;
34
- }
35
-
36
- .framework,
37
- .vite {
38
- position: absolute;
39
- }
40
-
41
- .framework {
42
- z-index: 1;
43
- top: 34px;
44
- height: 28px;
45
- transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
46
- scale(1.4);
47
- }
48
-
49
- .vite {
50
- z-index: 0;
51
- top: 107px;
52
- height: 26px;
53
- width: auto;
54
- transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
55
- scale(0.8);
56
- }
57
- }
58
-
59
- #center {
60
- display: flex;
61
- flex-direction: column;
62
- gap: 25px;
63
- place-content: center;
64
- place-items: center;
65
- flex-grow: 1;
66
-
67
- @media (max-width: 1024px) {
68
- padding: 32px 20px 24px;
69
- gap: 18px;
70
- }
71
- }
72
-
73
- #next-steps {
74
- display: flex;
75
- border-top: 1px solid var(--border);
76
- text-align: left;
77
-
78
- & > div {
79
- flex: 1 1 0;
80
- padding: 32px;
81
- @media (max-width: 1024px) {
82
- padding: 24px 20px;
83
- }
84
- }
85
-
86
- .icon {
87
- margin-bottom: 16px;
88
- width: 22px;
89
- height: 22px;
90
- }
91
-
92
- @media (max-width: 1024px) {
93
- flex-direction: column;
94
- text-align: center;
95
- }
96
- }
97
-
98
- #docs {
99
- border-right: 1px solid var(--border);
100
-
101
- @media (max-width: 1024px) {
102
- border-right: none;
103
- border-bottom: 1px solid var(--border);
104
- }
105
- }
106
-
107
- #next-steps ul {
108
- list-style: none;
109
- padding: 0;
110
- display: flex;
111
- gap: 8px;
112
- margin: 32px 0 0;
113
-
114
- .logo {
115
- height: 18px;
116
- }
117
-
118
- a {
119
- color: var(--text-h);
120
- font-size: 16px;
121
- border-radius: 6px;
122
- background: var(--social-bg);
123
- display: flex;
124
- padding: 6px 12px;
125
- align-items: center;
126
- gap: 8px;
127
- text-decoration: none;
128
- transition: box-shadow 0.3s;
129
-
130
- &:hover {
131
- box-shadow: var(--shadow);
132
- }
133
- .button-icon {
134
- height: 18px;
135
- width: 18px;
136
- }
137
- }
138
-
139
- @media (max-width: 1024px) {
140
- margin-top: 20px;
141
- flex-wrap: wrap;
142
- justify-content: center;
143
-
144
- li {
145
- flex: 1 1 calc(50% - 8px);
146
- }
147
-
148
- a {
149
- width: 100%;
150
- justify-content: center;
151
- box-sizing: border-box;
152
- }
153
- }
154
- }
155
-
156
- #spacer {
157
- height: 88px;
158
- border-top: 1px solid var(--border);
159
- @media (max-width: 1024px) {
160
- height: 48px;
161
- }
162
- }
163
-
164
- .ticks {
165
- position: relative;
166
- width: 100%;
167
-
168
- &::before,
169
- &::after {
170
- content: '';
171
- position: absolute;
172
- top: -4.5px;
173
- border: 5px solid transparent;
174
- }
175
-
176
- &::before {
177
- left: 0;
178
- border-left-color: var(--border);
179
- }
180
- &::after {
181
- right: 0;
182
- border-right-color: var(--border);
183
- }
184
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/App.jsx DELETED
@@ -1,225 +0,0 @@
1
- import React, { useState, useRef } from 'react';
2
- import Loader from './components/Loader';
3
- const API_BASE = 'http://localhost:8000';
4
-
5
- function App() {
6
- const [file, setFile] = useState(null);
7
- const [status, setStatus] = useState('idle'); // 'idle', 'analyzing', 'result', 'error'
8
- const [resultData, setResultData] = useState(null);
9
- const [errorMsg, setErrorMsg] = useState('');
10
-
11
- const fileInputRef = useRef(null);
12
-
13
- const handleDrop = (e) => {
14
- e.preventDefault();
15
- const droppedFile = e.dataTransfer.files[0];
16
- if (droppedFile && droppedFile.type.startsWith('video/')) {
17
- setFile(droppedFile);
18
- } else {
19
- setErrorMsg('Please drop a valid video file (MP4, AVI, MOV, MKV, WebM).');
20
- setStatus('error');
21
- }
22
- };
23
-
24
- const handleFileChange = (e) => {
25
- if (e.target.files && e.target.files[0]) {
26
- setFile(e.target.files[0]);
27
- }
28
- };
29
-
30
- const handleAnalyze = async () => {
31
- if (!file) return;
32
- setStatus('analyzing');
33
-
34
- const formData = new FormData();
35
- formData.append('file', file);
36
-
37
- try {
38
- const res = await fetch(`${API_BASE}/analyze`, { method: 'POST', body: formData });
39
- if (!res.ok) {
40
- const e = await res.json().catch(() => ({}));
41
- throw new Error(e.detail || `Server error ${res.status}`);
42
- }
43
- const data = await res.json();
44
- setResultData(data);
45
- setStatus('result');
46
- } catch (err) {
47
- setErrorMsg(err.message || 'Connection to analysis engine failed.');
48
- setStatus('error');
49
- }
50
- };
51
-
52
- const resetAll = () => {
53
- setFile(null);
54
- setResultData(null);
55
- setErrorMsg('');
56
- setStatus('idle');
57
- };
58
-
59
- const formatBytes = (bytes) => {
60
- if (bytes < 1024) return bytes + ' B';
61
- if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
62
- return (bytes / 1048576).toFixed(1) + ' MB';
63
- };
64
-
65
- return (
66
- <>
67
- <div className="scanner"></div>
68
-
69
- <div className="max-w-5xl mx-auto px-6 py-12 min-h-screen flex flex-col relative z-10">
70
-
71
- {/* Header */}
72
- <header className="text-center mb-12 fade-up" style={{ animationDelay: '0.1s' }}>
73
- <div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-[rgba(0,255,156,0.3)] bg-[rgba(0,255,156,0.05)] mb-6 shadow-[0_0_15px_rgba(0,255,156,0.1)]">
74
- <div className="w-2 h-2 rounded-full bg-[#00ff9c] shadow-[0_0_8px_#00ff9c] animate-pulse"></div>
75
- <span className="text-[10px] font-semibold tracking-[0.2em] text-[#00ff9c] uppercase">AI & Machine Learning</span>
76
- </div>
77
- <h1 className="text-4xl md:text-5xl font-bold tracking-tight text-white mb-2">Deepfake <span className="text-glow">Authenticator</span></h1>
78
- <p className="text-[#849ca3] text-sm md:text-base font-light tracking-wide">Advanced video forensics and digital truth verification.</p>
79
- </header>
80
-
81
- {/* Main Content */}
82
- <main className="flex-1 flex flex-col items-center gap-8 w-full">
83
-
84
- {status === 'idle' && (
85
- <section className="w-full max-w-3xl glass p-8 fade-up" style={{ animationDelay: '0.2s' }}>
86
- <div
87
- className="glass-inner rounded-xl border-2 border-dashed border-[rgba(0,255,156,0.15)] hover:border-[#00ff9c] transition-all cursor-pointer min-h-[200px] flex items-center justify-center relative overflow-hidden"
88
- onDragOver={(e) => e.preventDefault()}
89
- onDrop={handleDrop}
90
- onClick={() => fileInputRef.current?.click()}
91
- >
92
- {!file ? (
93
- <div className="text-center p-8">
94
- <div className="w-16 h-16 mx-auto mb-4 rounded-full border border-[rgba(0,255,156,0.2)] flex items-center justify-center bg-[rgba(0,255,156,0.02)] shadow-[0_0_20px_rgba(0,255,156,0.05)]">
95
- <svg className="w-8 h-8 text-[#00ff9c] opacity-70" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24">
96
- <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
97
- </svg>
98
- </div>
99
- <h3 className="text-lg font-medium text-white mb-1">Upload Video for Analysis</h3>
100
- <p className="text-sm text-[#849ca3]">Drag & drop or click to browse</p>
101
- <p className="text-[11px] text-[#849ca3] mt-4 opacity-60">MP4, AVI, MOV, MKV, WebM — Max 100MB</p>
102
- </div>
103
- ) : (
104
- <div className="w-full p-8 flex items-center gap-6" onClick={(e) => e.stopPropagation()}>
105
- <div className="w-14 h-14 rounded-lg bg-[rgba(0,255,156,0.1)] border border-[rgba(0,255,156,0.3)] flex items-center justify-center flex-shrink-0 shadow-[0_0_15px_rgba(0,255,156,0.15)]">
106
- <svg className="w-7 h-7 text-[#00ff9c]" fill="none" stroke="currentColor" strokeWidth="1.5" viewBox="0 0 24 24">
107
- <path strokeLinecap="round" strokeLinejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" />
108
- </svg>
109
- </div>
110
- <div className="flex-1 min-w-0">
111
- <p className="text-sm font-medium text-white truncate">{file.name}</p>
112
- <p className="text-xs text-[#849ca3] mt-1">{formatBytes(file.size)}</p>
113
- </div>
114
- <button onClick={() => setFile(null)} className="p-2 text-[#849ca3] hover:text-[#ff4444] transition-colors rounded-lg hover:bg-[rgba(255,68,68,0.1)]">
115
- <svg className="w-5 h-5" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
116
- <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
117
- </svg>
118
- </button>
119
- </div>
120
- )}
121
- </div>
122
- <input type="file" ref={fileInputRef} onChange={handleFileChange} accept="video/*" className="hidden" />
123
-
124
- <div className="mt-8 text-center">
125
- <button
126
- onClick={handleAnalyze}
127
- disabled={!file}
128
- className={`border border-[rgba(0,255,156,0.15)] rounded-lg px-10 py-3 font-semibold text-sm tracking-widest uppercase transition-all ${
129
- file
130
- ? 'bg-[#00ff9c] text-[#040906] shadow-[0_0_20px_rgba(0,255,156,0.4)] hover:bg-[#00cc7d] hover:-translate-y-1'
131
- : 'text-[#849ca3] opacity-50 cursor-not-allowed'
132
- }`}
133
- >
134
- Analyze Video
135
- </button>
136
- </div>
137
- </section>
138
- )}
139
-
140
- {status === 'analyzing' && (
141
- <section className="fade-up w-full flex justify-center py-20">
142
- <Loader />
143
- </section>
144
- )}
145
-
146
- {status === 'result' && resultData && (
147
- <section className="w-full flex flex-col gap-10 fade-up">
148
-
149
- <div className="flex flex-col md:flex-row items-stretch justify-center gap-6 w-full max-w-3xl mx-auto">
150
- <div className={`flex-1 glass p-8 text-center border-t-4 flex flex-col justify-center ${resultData.result === 'FAKE' ? 'border-t-[#ff4444] shadow-[0_-5px_20px_rgba(255,68,68,0.15)]' : 'border-t-[#00ff9c] shadow-[0_-5px_20px_rgba(0,255,156,0.15)]'}`}>
151
- <p className="text-[#849ca3] text-xs font-semibold tracking-[0.2em] uppercase mb-2">Verdict</p>
152
- <h2 className={`text-4xl font-bold tracking-widest ${resultData.result === 'FAKE' ? 'text-[#ff4444] drop-shadow-[0_0_10px_rgba(255,68,68,0.5)]' : 'text-[#00ff9c] drop-shadow-[0_0_10px_rgba(0,255,156,0.5)]'}`}>
153
- {resultData.result}
154
- </h2>
155
- <div className="mt-4 inline-block bg-[rgba(255,255,255,0.05)] px-4 py-1.5 rounded-full border border-[rgba(255,255,255,0.1)]">
156
- <span className="text-[#849ca3] text-xs uppercase tracking-wider mr-2">Confidence:</span>
157
- <span className="text-white font-bold">{resultData.confidence}%</span>
158
- </div>
159
- </div>
160
-
161
- <div className="flex-1 glass p-8 text-center border-t-4 border-t-[#00e5ff] shadow-[0_-5px_20px_rgba(0,229,255,0.15)] flex flex-col justify-center">
162
- <p className="text-[#849ca3] text-xs font-semibold tracking-[0.2em] uppercase mb-2">Metrics</p>
163
- <div className="flex flex-col gap-3 mt-2">
164
- <div className="flex justify-between items-center bg-[rgba(255,255,255,0.03)] p-3 rounded border border-[rgba(255,255,255,0.05)]">
165
- <span className="text-xs text-[#849ca3] uppercase tracking-wider">Frames</span>
166
- <span className="text-white font-mono font-medium">{resultData.metadata?.frames_analyzed || 0}</span>
167
- </div>
168
- <div className="flex justify-between items-center bg-[rgba(255,255,255,0.03)] p-3 rounded border border-[rgba(255,255,255,0.05)]">
169
- <span className="text-xs text-[#849ca3] uppercase tracking-wider">Time</span>
170
- <span className="text-[#00e5ff] font-mono font-medium">{resultData.processing_time_sec || 0}s</span>
171
- </div>
172
- </div>
173
- </div>
174
- </div>
175
-
176
- <div className="w-full max-w-3xl mx-auto glass p-8 mt-4">
177
- <h3 className="text-xs font-semibold tracking-[0.15em] text-[#00e5ff] uppercase flex items-center gap-2 mb-4">
178
- <div className="w-1 h-3 rounded-sm bg-[#00e5ff] shadow-[0_0_8px_#00e5ff]"></div>
179
- Analysis Insights
180
- </h3>
181
- <div className="flex flex-col gap-3">
182
- {(resultData.details || ['Analysis completed successfully.']).map((txt, i) => (
183
- <div key={i} className="flex items-start gap-3 p-3 rounded-lg bg-[rgba(255,255,255,0.02)] border border-[rgba(255,255,255,0.05)]">
184
- <span className={`flex-shrink-0 w-2 h-2 rounded-full mt-1.5 ${resultData.result === 'FAKE' ? 'bg-[#ff4444] shadow-[0_0_8px_rgba(255,68,68,0.6)]' : 'bg-[#00ff9c] shadow-[0_0_8px_rgba(0,255,156,0.6)]'}`}></span>
185
- <span className="text-sm text-[#a0aec0]">{txt}</span>
186
- </div>
187
- ))}
188
- </div>
189
- </div>
190
-
191
- <div className="text-center mt-4">
192
- <button onClick={resetAll} className="px-6 py-3 text-xs font-semibold tracking-widest uppercase text-[#849ca3] border border-[#849ca3]/30 rounded-lg hover:text-[#00ff9c] hover:border-[#00ff9c]/50 hover:bg-[#00ff9c]/5 transition-all">
193
- <span className="mr-2">↻</span> Analyze Another Video
194
- </button>
195
- </div>
196
- </section>
197
- )}
198
-
199
- {status === 'error' && (
200
- <section className="glass p-6 border-[#ff4444]/30 bg-[#ff4444]/5 fade-up w-full max-w-3xl">
201
- <div className="flex items-center gap-3 mb-2">
202
- <svg className="w-6 h-6 text-[#ff4444]" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
203
- <span className="text-sm font-bold tracking-widest text-[#ff4444] uppercase">Analysis Failed</span>
204
- </div>
205
- <p className="text-sm text-[#849ca3] ml-9">{errorMsg}</p>
206
- <div className="ml-9 mt-4">
207
- <button onClick={resetAll} className="text-xs text-white/50 hover:text-white uppercase tracking-wider transition-colors">
208
- ↻ Try Again
209
- </button>
210
- </div>
211
- </section>
212
- )}
213
-
214
- </main>
215
-
216
- <footer className="mt-16 text-center text-[10px] text-[#849ca3]/50 tracking-[0.2em] uppercase">
217
- Deepfake Authenticator • Secure Cybernetic Analysis • Local Inference
218
- </footer>
219
-
220
- </div>
221
- </>
222
- );
223
- }
224
-
225
- export default App;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/App.tsx DELETED
@@ -1,183 +0,0 @@
1
- import { useEffect, Suspense, lazy } from 'react'
2
- import { motion, AnimatePresence } from 'framer-motion'
3
- import { useStore } from './store'
4
- import { checkHealth } from './api'
5
- import UploadZone from './components/UploadZone'
6
- import ResultPanel from './components/ResultPanel'
7
-
8
- // Lazy-load the heavy 3D component
9
- const DeepfakeOrb = lazy(() => import('./components/DeepfakeOrb'))
10
-
11
- export default function App() {
12
- const { state, result, error, reset } = useStore()
13
- const verdict = result?.result ?? null
14
-
15
- // Health check on mount
16
- useEffect(() => {
17
- const badge = document.getElementById('modelBadge')
18
- checkHealth()
19
- .then((d) => {
20
- if (badge) {
21
- badge.textContent = d.model.toUpperCase()
22
- badge.style.color = '#00ff88bb'
23
- badge.style.borderColor = '#00ff8844'
24
- }
25
- })
26
- .catch(() => {
27
- if (badge) {
28
- badge.textContent = 'SERVER OFFLINE'
29
- badge.style.color = '#ff3355bb'
30
- badge.style.borderColor = '#ff335544'
31
- }
32
- })
33
- }, [])
34
-
35
- const showUpload = state === 'idle' || state === 'loading'
36
- const showResult = state === 'result'
37
- const showError = state === 'error'
38
-
39
- return (
40
- <div className="min-h-screen text-white overflow-x-hidden" style={{ background: '#050508' }}>
41
-
42
- {/* ── Sticky header ── */}
43
- <header className="sticky top-0 z-50 border-b border-white/5"
44
- style={{ background: 'rgba(5,5,8,0.85)', backdropFilter: 'blur(20px)' }}>
45
- <div className="max-w-5xl mx-auto px-6 h-16 flex items-center justify-between">
46
- <div className="flex items-center gap-3">
47
- <motion.div
48
- className="w-9 h-9 rounded-lg border border-[#00ff8855] flex items-center justify-center"
49
- animate={{ boxShadow: ['0 0 8px #00ff8820', '0 0 20px #00ff8840', '0 0 8px #00ff8820'] }}
50
- transition={{ duration: 2.5, repeat: Infinity }}
51
- >
52
- <svg width="18" height="18" fill="none" stroke="#00ff88" strokeWidth="1.5" viewBox="0 0 24 24">
53
- <path strokeLinecap="round" strokeLinejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.964-7.178z"/>
54
- <path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
55
- </svg>
56
- </motion.div>
57
- <div>
58
- <p className="text-sm font-bold tracking-widest" style={{ color: '#00ff88', textShadow: '0 0 12px #00ff8855' }}>
59
- DEEPFAKE AUTHENTICATOR
60
- </p>
61
- <p className="text-[9px] tracking-widest text-white/30 font-mono">AI-POWERED VIDEO FORENSICS</p>
62
- </div>
63
- </div>
64
- <div
65
- id="modelBadge"
66
- className="text-[10px] px-3 py-1.5 rounded-full border font-mono tracking-wider transition-all duration-500"
67
- style={{ borderColor: 'rgba(255,255,255,0.1)', color: 'rgba(255,255,255,0.3)' }}
68
- >
69
- CONNECTING...
70
- </div>
71
- </div>
72
- </header>
73
-
74
- {/* ── Main layout ── */}
75
- <main className="max-w-5xl mx-auto px-6 py-10">
76
- <div className="flex flex-col lg:flex-row gap-10 items-start">
77
-
78
- {/* ── Left: 3D orb + hero ── */}
79
- <div className="w-full lg:w-80 flex-shrink-0 flex flex-col items-center gap-6 lg:sticky lg:top-24">
80
- {/* 3D Orb */}
81
- <div className="w-64 h-64 relative">
82
- <Suspense fallback={
83
- <div className="w-full h-full rounded-full border border-[#00ff8830] flex items-center justify-center">
84
- <div className="w-16 h-16 rounded-full border-2 border-[#00ff88] border-t-transparent animate-spin" />
85
- </div>
86
- }>
87
- <DeepfakeOrb state={state} verdict={verdict} />
88
- </Suspense>
89
- </div>
90
-
91
- {/* Hero text */}
92
- <AnimatePresence mode="wait">
93
- {showUpload && (
94
- <motion.div
95
- key="hero"
96
- className="text-center"
97
- initial={{ opacity: 0, y: 10 }}
98
- animate={{ opacity: 1, y: 0 }}
99
- exit={{ opacity: 0, y: -10 }}
100
- >
101
- <h1 className="text-2xl font-bold mb-2 leading-tight"
102
- style={{ background: 'linear-gradient(135deg, #fff 30%, #00ff88)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' }}>
103
- Is This Video Real?
104
- </h1>
105
- <p className="text-white/40 text-xs leading-relaxed max-w-xs">
106
- Dual ViT ensemble + Wav2Vec2 audio analysis detects facial manipulation and AI-generated voices.
107
- </p>
108
- {/* Stat pills */}
109
- <div className="flex flex-wrap gap-2 justify-center mt-4">
110
- {['2 ViT Models', '40 Frames', 'Audio AI', '< 20s'].map((s) => (
111
- <span key={s} className="text-[10px] px-2.5 py-1 rounded-full border border-white/8 text-white/40 bg-white/3">
112
- {s}
113
- </span>
114
- ))}
115
- </div>
116
- </motion.div>
117
- )}
118
-
119
- {showResult && verdict && (
120
- <motion.div
121
- key="verdict-summary"
122
- className="text-center"
123
- initial={{ opacity: 0, scale: 0.9 }}
124
- animate={{ opacity: 1, scale: 1 }}
125
- exit={{ opacity: 0 }}
126
- >
127
- <p className="text-3xl font-bold tracking-widest mb-1"
128
- style={{ color: verdict === 'FAKE' ? '#ff3355' : '#00ff88', textShadow: `0 0 20px ${verdict === 'FAKE' ? '#ff335566' : '#00ff8866'}` }}>
129
- {verdict === 'FAKE' ? 'DEEPFAKE' : 'AUTHENTIC'}
130
- </p>
131
- <p className="text-white/30 text-xs font-mono">{result?.confidence}% confidence</p>
132
- </motion.div>
133
- )}
134
- </AnimatePresence>
135
- </div>
136
-
137
- {/* ── Right: Upload / Result / Error ── */}
138
- <div className="flex-1 min-w-0">
139
- <AnimatePresence mode="wait">
140
- {showUpload && (
141
- <motion.div key="upload" initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }}>
142
- <UploadZone />
143
- </motion.div>
144
- )}
145
-
146
- {showResult && (
147
- <motion.div key="result" initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -20 }}>
148
- <ResultPanel />
149
- </motion.div>
150
- )}
151
-
152
- {showError && (
153
- <motion.div
154
- key="error"
155
- className="rounded-2xl border border-red-500/20 bg-red-500/5 p-6"
156
- initial={{ opacity: 0, scale: 0.95 }}
157
- animate={{ opacity: 1, scale: 1 }}
158
- exit={{ opacity: 0 }}
159
- >
160
- <div className="flex items-center gap-3 mb-3">
161
- <span className="text-xl">⚠️</span>
162
- <span className="text-red-400 font-bold text-sm tracking-wider uppercase">Analysis Failed</span>
163
- </div>
164
- <p className="text-white/50 text-sm leading-relaxed">{error}</p>
165
- <button
166
- onClick={reset}
167
- className="mt-4 text-xs text-white/30 hover:text-white transition-colors font-mono tracking-wider"
168
- >
169
- ↻ Try again
170
- </button>
171
- </motion.div>
172
- )}
173
- </AnimatePresence>
174
- </div>
175
- </div>
176
- </main>
177
-
178
- <footer className="text-center py-8 text-[10px] text-white/15 font-mono tracking-widest border-t border-white/3">
179
- DEEPFAKE AUTHENTICATOR · MEDIAPIPE + VIT ENSEMBLE + WAV2VEC2 · LOCAL INFERENCE
180
- </footer>
181
- </div>
182
- )
183
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/api.ts DELETED
@@ -1,20 +0,0 @@
1
- import type { AnalysisResult } from './store'
2
-
3
- const BASE = import.meta.env.DEV ? 'http://localhost:8000' : window.location.origin
4
-
5
- export async function analyzeVideo(file: File): Promise<AnalysisResult> {
6
- const fd = new FormData()
7
- fd.append('file', file)
8
- const res = await fetch(`${BASE}/analyze`, { method: 'POST', body: fd })
9
- if (!res.ok) {
10
- const err = await res.json().catch(() => ({}))
11
- throw new Error((err as any).detail || `Server error ${res.status}`)
12
- }
13
- return res.json()
14
- }
15
-
16
- export async function checkHealth(): Promise<{ status: string; model: string; ready: boolean }> {
17
- const res = await fetch(`${BASE}/health`)
18
- if (!res.ok) throw new Error('Server offline')
19
- return res.json()
20
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/assets/hero.png DELETED
Binary file (13.1 kB)
 
frontend/src/assets/react.svg DELETED
frontend/src/assets/vite.svg DELETED
frontend/src/components/CyberCard.jsx DELETED
@@ -1,364 +0,0 @@
1
- import React from 'react';
2
- import styled from 'styled-components';
3
-
4
- const CyberCard = ({ title, subtitle, highlight, hoverText, isFake }) => {
5
- return (
6
- <StyledWrapper $isFake={isFake}>
7
- <div className="container noselect">
8
- <div className="canvas">
9
- {Array.from({ length: 25 }).map((_, i) => (
10
- <div key={i} className={`tracker tr-${i + 1}`} />
11
- ))}
12
- <div id="card">
13
- <div className="card-content">
14
- <div className="card-glare" />
15
- <div className="cyber-lines">
16
- <span /><span /><span /><span />
17
- </div>
18
- <p id="prompt">{hoverText || "HOVER ME"}</p>
19
- <div className="title" dangerouslySetInnerHTML={{ __html: title || "CYBER<br />CARD" }} />
20
- <div className="glowing-elements">
21
- <div className="glow-1" />
22
- <div className="glow-2" />
23
- <div className="glow-3" />
24
- </div>
25
- <div className="subtitle">
26
- <span>{subtitle || "INTERACTIVE"}</span>
27
- <span className="highlight">{highlight || "3D EFFECT"}</span>
28
- </div>
29
- <div className="card-particles">
30
- <span /><span /><span /> <span /><span /><span />
31
- </div>
32
- <div className="corner-elements">
33
- <span /><span /><span /><span />
34
- </div>
35
- <div className="scan-line" />
36
- </div>
37
- </div>
38
- </div>
39
- </div>
40
- </StyledWrapper>
41
- );
42
- }
43
-
44
- const StyledWrapper = styled.div`
45
- .container {
46
- position: relative;
47
- width: 260px;
48
- height: 340px;
49
- transition: 200ms;
50
- margin: 0 auto;
51
- }
52
-
53
- .container:active {
54
- width: 250px;
55
- height: 330px;
56
- }
57
-
58
- #card {
59
- position: absolute;
60
- inset: 0;
61
- z-index: 0;
62
- display: flex;
63
- justify-content: center;
64
- align-items: center;
65
- border-radius: 20px;
66
- transition: 700ms;
67
- background: linear-gradient(45deg, #1a1a1a, #262626);
68
- border: 2px solid ${props => props.$isFake ? 'rgba(255, 68, 68, 0.4)' : 'rgba(0, 255, 170, 0.4)'};
69
- overflow: hidden;
70
- box-shadow:
71
- 0 0 20px ${props => props.$isFake ? 'rgba(255, 68, 68, 0.3)' : 'rgba(0, 255, 170, 0.3)'},
72
- inset 0 0 20px rgba(0, 0, 0, 0.2);
73
- }
74
-
75
- .card-content {
76
- position: relative;
77
- width: 100%;
78
- height: 100%;
79
- }
80
-
81
- #prompt {
82
- bottom: 120px;
83
- left: 50%;
84
- transform: translateX(-50%);
85
- z-index: 20;
86
- font-size: 16px;
87
- font-weight: 600;
88
- letter-spacing: 2px;
89
- transition: 300ms ease-in-out;
90
- position: absolute;
91
- text-align: center;
92
- color: rgba(255, 255, 255, 0.7);
93
- text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
94
- }
95
-
96
- .title {
97
- opacity: 0;
98
- transition: 300ms ease-in-out;
99
- position: absolute;
100
- font-size: 42px;
101
- font-weight: 800;
102
- letter-spacing: 6px;
103
- text-align: center;
104
- width: 100%;
105
- padding-top: 40px;
106
- background: ${props => props.$isFake ? 'linear-gradient(45deg, #ff4444, #ff8888)' : 'linear-gradient(45deg, #00ffaa, #00a2ff)'};
107
- -webkit-background-clip: text;
108
- -webkit-text-fill-color: transparent;
109
- filter: drop-shadow(0 0 15px ${props => props.$isFake ? 'rgba(255, 68, 68, 0.3)' : 'rgba(0, 255, 170, 0.3)'});
110
- text-shadow:
111
- 0 0 10px ${props => props.$isFake ? 'rgba(255, 68, 68, 0.5)' : 'rgba(92, 103, 255, 0.5)'},
112
- 0 0 20px ${props => props.$isFake ? 'rgba(255, 68, 68, 0.3)' : 'rgba(92, 103, 255, 0.3)'};
113
- }
114
-
115
- .subtitle {
116
- position: absolute;
117
- bottom: 40px;
118
- width: 100%;
119
- text-align: center;
120
- font-size: 14px;
121
- letter-spacing: 2px;
122
- transform: translateY(30px);
123
- color: rgba(255, 255, 255, 0.6);
124
- }
125
-
126
- .highlight {
127
- color: ${props => props.$isFake ? '#ff4444' : '#00ffaa'};
128
- margin-left: 5px;
129
- background: ${props => props.$isFake ? 'linear-gradient(90deg, #ff4444, #ff8888)' : 'linear-gradient(90deg, #5c67ff, #ad51ff)'};
130
- -webkit-background-clip: text;
131
- -webkit-text-fill-color: transparent;
132
- font-weight: bold;
133
- }
134
-
135
- .glowing-elements {
136
- position: absolute;
137
- inset: 0;
138
- pointer-events: none;
139
- }
140
-
141
- .glow-1,
142
- .glow-2,
143
- .glow-3 {
144
- position: absolute;
145
- width: 120px;
146
- height: 120px;
147
- border-radius: 50%;
148
- background: radial-gradient(
149
- circle at center,
150
- ${props => props.$isFake ? 'rgba(255, 68, 68, 0.3)' : 'rgba(0, 255, 170, 0.3)'} 0%,
151
- transparent 70%
152
- );
153
- filter: blur(15px);
154
- opacity: 0;
155
- transition: opacity 0.3s ease;
156
- }
157
-
158
- .glow-1 { top: -20px; left: -20px; }
159
- .glow-2 { top: 50%; right: -30px; transform: translateY(-50%); }
160
- .glow-3 { bottom: -20px; left: 30%; }
161
-
162
- .card-particles span {
163
- position: absolute;
164
- width: 4px;
165
- height: 4px;
166
- background: ${props => props.$isFake ? '#ff4444' : '#00ffaa'};
167
- border-radius: 50%;
168
- opacity: 0;
169
- transition: opacity 0.3s ease;
170
- }
171
-
172
- /* Hover effects */
173
- .tracker:hover ~ #card .title {
174
- opacity: 1;
175
- transform: translateY(-10px);
176
- }
177
-
178
- .tracker:hover ~ #card .glowing-elements div {
179
- opacity: 1;
180
- }
181
-
182
- .tracker:hover ~ #card .card-particles span {
183
- animation: particleFloat 2s infinite;
184
- }
185
-
186
- @keyframes particleFloat {
187
- 0% { transform: translate(0, 0); opacity: 0; }
188
- 50% { opacity: 1; }
189
- 100% { transform: translate(calc(var(--x, 0) * 30px), calc(var(--y, 0) * 30px)); opacity: 0; }
190
- }
191
-
192
- /* Particle positions */
193
- .card-particles span:nth-child(1) { --x: 1; --y: -1; top: 40%; left: 20%; }
194
- .card-particles span:nth-child(2) { --x: -1; --y: -1; top: 60%; right: 20%; }
195
- .card-particles span:nth-child(3) { --x: 0.5; --y: 1; top: 20%; left: 40%; }
196
- .card-particles span:nth-child(4) { --x: -0.5; --y: 1; top: 80%; right: 40%; }
197
- .card-particles span:nth-child(5) { --x: 1; --y: 0.5; top: 30%; left: 60%; }
198
- .card-particles span:nth-child(6) { --x: -1; --y: 0.5; top: 70%; right: 60%; }
199
-
200
- #card::before {
201
- content: "";
202
- background: radial-gradient(
203
- circle at center,
204
- ${props => props.$isFake ? 'rgba(255, 68, 68, 0.1)' : 'rgba(0, 255, 170, 0.1)'} 0%,
205
- ${props => props.$isFake ? 'rgba(255, 136, 136, 0.05)' : 'rgba(0, 162, 255, 0.05)'} 50%,
206
- transparent 100%
207
- );
208
- filter: blur(20px);
209
- opacity: 0;
210
- width: 150%;
211
- height: 150%;
212
- position: absolute;
213
- left: 50%;
214
- top: 50%;
215
- transform: translate(-50%, -50%);
216
- transition: opacity 0.3s ease;
217
- }
218
-
219
- .tracker:hover ~ #card::before { opacity: 1; }
220
-
221
- .tracker {
222
- position: absolute;
223
- z-index: 200;
224
- width: 100%;
225
- height: 100%;
226
- }
227
-
228
- .tracker:hover { cursor: pointer; }
229
- .tracker:hover ~ #card #prompt { opacity: 0; }
230
- .tracker:hover ~ #card { transition: 300ms; filter: brightness(1.1); }
231
- .container:hover #card::before { transition: 200ms; content: ""; opacity: 80%; }
232
-
233
- .canvas {
234
- perspective: 800px;
235
- inset: 0;
236
- z-index: 200;
237
- position: absolute;
238
- display: grid;
239
- grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
240
- grid-template-rows: 1fr 1fr 1fr 1fr 1fr;
241
- gap: 0px 0px;
242
- grid-template-areas:
243
- "tr-1 tr-2 tr-3 tr-4 tr-5"
244
- "tr-6 tr-7 tr-8 tr-9 tr-10"
245
- "tr-11 tr-12 tr-13 tr-14 tr-15"
246
- "tr-16 tr-17 tr-18 tr-19 tr-20"
247
- "tr-21 tr-22 tr-23 tr-24 tr-25";
248
- }
249
-
250
- .tr-1 { grid-area: tr-1; } .tr-2 { grid-area: tr-2; } .tr-3 { grid-area: tr-3; } .tr-4 { grid-area: tr-4; } .tr-5 { grid-area: tr-5; }
251
- .tr-6 { grid-area: tr-6; } .tr-7 { grid-area: tr-7; } .tr-8 { grid-area: tr-8; } .tr-9 { grid-area: tr-9; } .tr-10 { grid-area: tr-10; }
252
- .tr-11 { grid-area: tr-11; } .tr-12 { grid-area: tr-12; } .tr-13 { grid-area: tr-13; } .tr-14 { grid-area: tr-14; } .tr-15 { grid-area: tr-15; }
253
- .tr-16 { grid-area: tr-16; } .tr-17 { grid-area: tr-17; } .tr-18 { grid-area: tr-18; } .tr-19 { grid-area: tr-19; } .tr-20 { grid-area: tr-20; }
254
- .tr-21 { grid-area: tr-21; } .tr-22 { grid-area: tr-22; } .tr-23 { grid-area: tr-23; } .tr-24 { grid-area: tr-24; } .tr-25 { grid-area: tr-25; }
255
-
256
- .tr-1:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(20deg) rotateY(-10deg) rotateZ(0deg); }
257
- .tr-2:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(20deg) rotateY(-5deg) rotateZ(0deg); }
258
- .tr-3:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(20deg) rotateY(0deg) rotateZ(0deg); }
259
- .tr-4:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(20deg) rotateY(5deg) rotateZ(0deg); }
260
- .tr-5:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(20deg) rotateY(10deg) rotateZ(0deg); }
261
- .tr-6:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(10deg) rotateY(-10deg) rotateZ(0deg); }
262
- .tr-7:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(10deg) rotateY(-5deg) rotateZ(0deg); }
263
- .tr-8:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(10deg) rotateY(0deg) rotateZ(0deg); }
264
- .tr-9:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(10deg) rotateY(5deg) rotateZ(0deg); }
265
- .tr-10:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(10deg) rotateY(10deg) rotateZ(0deg); }
266
- .tr-11:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(0deg) rotateY(-10deg) rotateZ(0deg); }
267
- .tr-12:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(0deg) rotateY(-5deg) rotateZ(0deg); }
268
- .tr-13:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg); }
269
- .tr-14:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(0deg) rotateY(5deg) rotateZ(0deg); }
270
- .tr-15:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(0deg) rotateY(10deg) rotateZ(0deg); }
271
- .tr-16:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-10deg) rotateY(-10deg) rotateZ(0deg); }
272
- .tr-17:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-10deg) rotateY(-5deg) rotateZ(0deg); }
273
- .tr-18:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-10deg) rotateY(0deg) rotateZ(0deg); }
274
- .tr-19:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-10deg) rotateY(5deg) rotateZ(0deg); }
275
- .tr-20:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-10deg) rotateY(10deg) rotateZ(0deg); }
276
- .tr-21:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-20deg) rotateY(-10deg) rotateZ(0deg); }
277
- .tr-22:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-20deg) rotateY(-5deg) rotateZ(0deg); }
278
- .tr-23:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-20deg) rotateY(0deg) rotateZ(0deg); }
279
- .tr-24:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-20deg) rotateY(5deg) rotateZ(0deg); }
280
- .tr-25:hover ~ #card { transition: 125ms ease-in-out; transform: rotateX(-20deg) rotateY(10deg) rotateZ(0deg); }
281
-
282
- .noselect {
283
- -webkit-touch-callout: none;
284
- -webkit-user-select: none;
285
- -moz-user-select: none;
286
- -ms-user-select: none;
287
- user-select: none;
288
- }
289
-
290
- .card-glare {
291
- position: absolute;
292
- inset: 0;
293
- background: linear-gradient(
294
- 125deg,
295
- rgba(255, 255, 255, 0) 0%,
296
- rgba(255, 255, 255, 0.05) 45%,
297
- rgba(255, 255, 255, 0.1) 50%,
298
- rgba(255, 255, 255, 0.05) 55%,
299
- rgba(255, 255, 255, 0) 100%
300
- );
301
- opacity: 0;
302
- transition: opacity 300ms;
303
- }
304
-
305
- .cyber-lines span {
306
- position: absolute;
307
- background: linear-gradient(
308
- 90deg,
309
- transparent,
310
- ${props => props.$isFake ? 'rgba(255, 68, 68, 0.4)' : 'rgba(92, 103, 255, 0.2)'},
311
- transparent
312
- );
313
- }
314
-
315
- .cyber-lines span:nth-child(1) { top: 20%; left: 0; width: 100%; height: 1px; transform: scaleX(0); transform-origin: left; animation: lineGrow 3s linear infinite; }
316
- .cyber-lines span:nth-child(2) { top: 40%; right: 0; width: 100%; height: 1px; transform: scaleX(0); transform-origin: right; animation: lineGrow 3s linear infinite 1s; }
317
- .cyber-lines span:nth-child(3) { top: 60%; left: 0; width: 100%; height: 1px; transform: scaleX(0); transform-origin: left; animation: lineGrow 3s linear infinite 2s; }
318
- .cyber-lines span:nth-child(4) { top: 80%; right: 0; width: 100%; height: 1px; transform: scaleX(0); transform-origin: right; animation: lineGrow 3s linear infinite 1.5s; }
319
-
320
- .corner-elements span {
321
- position: absolute;
322
- width: 15px;
323
- height: 15px;
324
- border: 2px solid ${props => props.$isFake ? 'rgba(255, 68, 68, 0.5)' : 'rgba(92, 103, 255, 0.3)'};
325
- transition: all 0.3s ease;
326
- }
327
-
328
- .corner-elements span:nth-child(1) { top: 10px; left: 10px; border-right: 0; border-bottom: 0; }
329
- .corner-elements span:nth-child(2) { top: 10px; right: 10px; border-left: 0; border-bottom: 0; }
330
- .corner-elements span:nth-child(3) { bottom: 10px; left: 10px; border-right: 0; border-top: 0; }
331
- .corner-elements span:nth-child(4) { bottom: 10px; right: 10px; border-left: 0; border-top: 0; }
332
-
333
- .scan-line {
334
- position: absolute;
335
- inset: 0;
336
- background: linear-gradient(
337
- to bottom,
338
- transparent,
339
- ${props => props.$isFake ? 'rgba(255, 68, 68, 0.2)' : 'rgba(92, 103, 255, 0.1)'},
340
- transparent
341
- );
342
- transform: translateY(-100%);
343
- animation: scanMove 2s linear infinite;
344
- }
345
-
346
- @keyframes lineGrow {
347
- 0% { transform: scaleX(0); opacity: 0; }
348
- 50% { transform: scaleX(1); opacity: 1; }
349
- 100% { transform: scaleX(0); opacity: 0; }
350
- }
351
-
352
- @keyframes scanMove {
353
- 0% { transform: translateY(-100%); }
354
- 100% { transform: translateY(100%); }
355
- }
356
-
357
- #card:hover .card-glare { opacity: 1; }
358
- #card:hover .corner-elements span {
359
- border-color: ${props => props.$isFake ? 'rgba(255, 68, 68, 0.8)' : 'rgba(92, 103, 255, 0.8)'};
360
- box-shadow: 0 0 10px ${props => props.$isFake ? 'rgba(255, 68, 68, 0.5)' : 'rgba(92, 103, 255, 0.5)'};
361
- }
362
- `;
363
-
364
- export default CyberCard;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/DeepfakeOrb.tsx DELETED
@@ -1,103 +0,0 @@
1
- import { useRef, useMemo } from 'react'
2
- import { Canvas, useFrame } from '@react-three/fiber'
3
- import { Sphere, MeshDistortMaterial, Float, Stars } from '@react-three/drei'
4
- import * as THREE from 'three'
5
- import type { AppState, Verdict } from '../store'
6
-
7
- // ── Inner animated orb ────────────────────────────────────────────────────────
8
- function Orb({ state, verdict }: { state: AppState; verdict: Verdict }) {
9
- const meshRef = useRef<THREE.Mesh>(null)
10
- const ringRef = useRef<THREE.Mesh>(null)
11
- const ring2Ref = useRef<THREE.Mesh>(null)
12
-
13
- const isFake = verdict === 'FAKE'
14
- const isReal = verdict === 'REAL'
15
- const isLoading = state === 'loading'
16
-
17
- const color = isFake ? '#ff3355' : isReal ? '#00ff88' : '#00aaff'
18
- const emissive = isFake ? '#ff0033' : isReal ? '#00cc66' : '#0066ff'
19
-
20
- useFrame((_, delta) => {
21
- if (!meshRef.current) return
22
- meshRef.current.rotation.y += delta * (isLoading ? 1.5 : 0.4)
23
- meshRef.current.rotation.x += delta * (isLoading ? 0.8 : 0.15)
24
- if (ringRef.current) ringRef.current.rotation.z += delta * 0.6
25
- if (ring2Ref.current) ring2Ref.current.rotation.x += delta * 0.4
26
- })
27
-
28
- // Particle ring
29
- const particles = useMemo(() => {
30
- const count = 120
31
- const positions = new Float32Array(count * 3)
32
- for (let i = 0; i < count; i++) {
33
- const angle = (i / count) * Math.PI * 2
34
- const radius = 1.8 + Math.random() * 0.4
35
- positions[i * 3] = Math.cos(angle) * radius
36
- positions[i * 3 + 1] = (Math.random() - 0.5) * 0.3
37
- positions[i * 3 + 2] = Math.sin(angle) * radius
38
- }
39
- return positions
40
- }, [])
41
-
42
- return (
43
- <group>
44
- {/* Stars background */}
45
- <Stars radius={8} depth={4} count={300} factor={1} fade speed={isLoading ? 3 : 0.5} />
46
-
47
- {/* Main orb */}
48
- <Float speed={isLoading ? 4 : 1.5} rotationIntensity={isLoading ? 1 : 0.3} floatIntensity={isLoading ? 1.5 : 0.5}>
49
- <Sphere ref={meshRef} args={[1, 64, 64]}>
50
- <MeshDistortMaterial
51
- color={color}
52
- emissive={emissive}
53
- emissiveIntensity={isLoading ? 0.8 : 0.4}
54
- distort={isLoading ? 0.6 : isFake ? 0.45 : 0.25}
55
- speed={isLoading ? 4 : 2}
56
- roughness={0.1}
57
- metalness={0.8}
58
- transparent
59
- opacity={0.9}
60
- />
61
- </Sphere>
62
- </Float>
63
-
64
- {/* Orbit ring 1 */}
65
- <mesh ref={ringRef} rotation={[Math.PI / 2, 0, 0]}>
66
- <torusGeometry args={[1.6, 0.015, 8, 100]} />
67
- <meshBasicMaterial color={color} transparent opacity={0.5} />
68
- </mesh>
69
-
70
- {/* Orbit ring 2 */}
71
- <mesh ref={ring2Ref} rotation={[Math.PI / 3, Math.PI / 4, 0]}>
72
- <torusGeometry args={[1.9, 0.008, 8, 100]} />
73
- <meshBasicMaterial color={color} transparent opacity={0.25} />
74
- </mesh>
75
-
76
- {/* Particle ring */}
77
- <points>
78
- <bufferGeometry>
79
- <bufferAttribute attach="attributes-position" args={[particles, 3]} />
80
- </bufferGeometry>
81
- <pointsMaterial color={color} size={0.025} transparent opacity={0.6} />
82
- </points>
83
-
84
- {/* Ambient + point lights */}
85
- <ambientLight intensity={0.2} />
86
- <pointLight position={[3, 3, 3]} intensity={2} color={color} />
87
- <pointLight position={[-3, -3, -3]} intensity={1} color={emissive} />
88
- </group>
89
- )
90
- }
91
-
92
- // ── Exported canvas wrapper ───────────────────────────────────────────────────
93
- export default function DeepfakeOrb({ state, verdict }: { state: AppState; verdict: Verdict }) {
94
- return (
95
- <Canvas
96
- camera={{ position: [0, 0, 4], fov: 50 }}
97
- style={{ width: '100%', height: '100%' }}
98
- gl={{ antialias: true, alpha: true }}
99
- >
100
- <Orb state={state} verdict={verdict} />
101
- </Canvas>
102
- )
103
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/Loader.jsx DELETED
@@ -1,97 +0,0 @@
1
- import React from 'react';
2
- import styled from 'styled-components';
3
-
4
- const Loader = () => {
5
- return (
6
- <StyledWrapper>
7
- <div className="liquid-loader">
8
- <div className="loading-text">
9
- Analyzing<span className="dot">.</span><span className="dot">.</span><span className="dot">.</span>
10
- </div>
11
- <div className="loader-track">
12
- <div className="liquid-fill" />
13
- </div>
14
- </div>
15
- </StyledWrapper>
16
- );
17
- }
18
-
19
- const StyledWrapper = styled.div`
20
- .liquid-loader {
21
- display: flex;
22
- flex-direction: column;
23
- align-items: center;
24
- gap: 15px;
25
- padding: 20px;
26
- font-family: system-ui, sans-serif;
27
- }
28
-
29
- .loader-track {
30
- position: relative;
31
- width: 250px;
32
- height: 32px;
33
- background: linear-gradient(135deg, #2a2a2a, #1a1a1a);
34
- border-radius: 16px;
35
- overflow: hidden;
36
- box-shadow:
37
- inset 0 2px 4px rgba(0, 0, 0, 0.6),
38
- 0 1px 3px rgba(255, 255, 255, 0.1);
39
- }
40
-
41
- .liquid-fill {
42
- position: absolute;
43
- top: 2px;
44
- left: 2px;
45
- height: calc(100% - 4px);
46
- background: linear-gradient(90deg, #00ffaa, #00a2ff, #5c67ff, #00ffaa);
47
- border-radius: 14px;
48
- animation:
49
- fillProgress 8s ease-out forwards,
50
- colorShift 3s linear infinite;
51
- box-shadow:
52
- 0 0 12px rgba(0, 255, 170, 0.4),
53
- inset 0 1px 2px rgba(255, 255, 255, 0.2);
54
- }
55
-
56
- .loading-text {
57
- color: white;
58
- font-size: 18px;
59
- font-weight: 600;
60
- letter-spacing: 2px;
61
- text-transform: uppercase;
62
- animation: textGlow 1s ease-in-out infinite;
63
- }
64
-
65
- .dot {
66
- margin-left: 3px;
67
- animation: blink 1.5s infinite;
68
- }
69
- .dot:nth-of-type(1) { animation-delay: 0s; }
70
- .dot:nth-of-type(2) { animation-delay: 0.3s; }
71
- .dot:nth-of-type(3) { animation-delay: 0.6s; }
72
-
73
- @keyframes fillProgress {
74
- 0% { width: 4px; }
75
- 25% { width: 35%; }
76
- 50% { width: 65%; }
77
- 75% { width: 85%; }
78
- 100% { width: calc(100% - 4px); }
79
- }
80
-
81
- @keyframes colorShift {
82
- 0% { filter: hue-rotate(0deg) brightness(1); }
83
- 100% { filter: hue-rotate(360deg) brightness(1); }
84
- }
85
-
86
- @keyframes textGlow {
87
- 0%, 100% { opacity: 0.7; text-shadow: 0 0 8px rgba(0, 255, 170, 0.3); }
88
- 50% { opacity: 1; text-shadow: 0 0 16px rgba(0, 255, 170, 0.6); }
89
- }
90
-
91
- @keyframes blink {
92
- 0%, 50% { opacity: 1; }
93
- 51%, 100% { opacity: 0; }
94
- }
95
- `;
96
-
97
- export default Loader;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/ResultPanel.tsx DELETED
@@ -1,346 +0,0 @@
1
- import { motion, AnimatePresence } from 'framer-motion'
2
- import { useStore } from '../store'
3
- import type { FramePoint, AudioResult } from '../store'
4
-
5
- // ── Animated confidence arc ───────────────────────────────────────────────────
6
- function ConfidenceArc({ pct, isFake }: { pct: number; isFake: boolean }) {
7
- const r = 54
8
- const circ = 2 * Math.PI * r
9
- const half = circ / 2
10
- const fill = (pct / 100) * half
11
- const color = isFake ? '#ff3355' : '#00ff88'
12
-
13
- return (
14
- <div className="relative w-40 h-20 mx-auto">
15
- <svg width="160" height="80" viewBox="0 0 160 80">
16
- {/* Track */}
17
- <path
18
- d={`M 16 80 A ${r} ${r} 0 0 1 144 80`}
19
- fill="none" stroke="rgba(255,255,255,0.06)" strokeWidth="10" strokeLinecap="round"
20
- />
21
- {/* Fill */}
22
- <motion.path
23
- d={`M 16 80 A ${r} ${r} 0 0 1 144 80`}
24
- fill="none"
25
- stroke={color}
26
- strokeWidth="10"
27
- strokeLinecap="round"
28
- strokeDasharray={`${half} ${half}`}
29
- initial={{ strokeDashoffset: half }}
30
- animate={{ strokeDashoffset: half - fill }}
31
- transition={{ duration: 1.4, ease: [0.22, 1, 0.36, 1] }}
32
- style={{ filter: `drop-shadow(0 0 8px ${color})` }}
33
- />
34
- </svg>
35
- <div className="absolute inset-0 flex flex-col items-center justify-end pb-1">
36
- <motion.span
37
- className="text-2xl font-bold font-mono"
38
- style={{ color }}
39
- initial={{ opacity: 0 }}
40
- animate={{ opacity: 1 }}
41
- transition={{ delay: 0.5 }}
42
- >
43
- {pct}%
44
- </motion.span>
45
- </div>
46
- </div>
47
- )
48
- }
49
-
50
- // ── Frame timeline ────────────────────────────────────────────────────────────
51
- function Timeline({ frames, isFake }: { frames: FramePoint[]; isFake: boolean }) {
52
- if (!frames.length) return (
53
- <p className="text-white/30 text-xs font-mono text-center py-4">No per-frame data</p>
54
- )
55
-
56
- const color = isFake ? '#ff3355' : '#00ff88'
57
- const maxH = 60
58
-
59
- return (
60
- <div className="flex items-end gap-1 overflow-x-auto pb-1" style={{ height: maxH + 24 + 'px', position: 'relative' }}>
61
- {/* 60% threshold line */}
62
- <div
63
- className="absolute left-0 right-0 pointer-events-none"
64
- style={{ bottom: 24 + maxH * 0.6, height: 1, background: 'rgba(255,170,0,0.35)' }}
65
- >
66
- <span className="absolute right-0 -top-4 text-[9px] text-yellow-400/60 font-mono">60%</span>
67
- </div>
68
-
69
- {frames.map((pt, i) => {
70
- const h = Math.max(3, (pt.fake_pct / 100) * maxH)
71
- const hot = pt.fake_pct >= 60
72
- return (
73
- <div key={i} className="group relative flex-shrink-0" style={{ width: 10, height: maxH }}>
74
- {/* Tooltip */}
75
- <div className="absolute bottom-full mb-1 left-1/2 -translate-x-1/2 bg-[#0a0a14] border border-white/10 rounded px-1.5 py-0.5 text-[9px] font-mono whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity z-10 pointer-events-none">
76
- F{pt.frame}: {pt.fake_pct}%
77
- </div>
78
- <motion.div
79
- className="absolute bottom-0 w-full rounded-t"
80
- style={{
81
- background: hot
82
- ? `linear-gradient(to top, ${color}, rgba(255,255,255,0.3))`
83
- : 'rgba(255,255,255,0.1)',
84
- boxShadow: hot ? `0 0 6px ${color}66` : 'none',
85
- }}
86
- initial={{ height: 0 }}
87
- animate={{ height: h }}
88
- transition={{ duration: 0.6, delay: i * 0.02, ease: [0.22, 1, 0.36, 1] }}
89
- />
90
- </div>
91
- )
92
- })}
93
- </div>
94
- )
95
- }
96
-
97
- // ── Audio card ────────────────────────────────────────────────────────────────
98
- function AudioCard({ audio }: { audio: AudioResult }) {
99
- if (!audio?.available) return null
100
-
101
- const isAI = audio.result === 'AI_VOICE'
102
- const color = isAI ? '#ff3355' : '#00ff88'
103
- const colorDim = isAI ? 'rgba(255,51,85,0.12)' : 'rgba(0,255,136,0.12)'
104
-
105
- return (
106
- <motion.div
107
- className="rounded-2xl border p-5"
108
- style={{ borderColor: `${color}33`, background: colorDim }}
109
- initial={{ opacity: 0, y: 16 }}
110
- animate={{ opacity: 1, y: 0 }}
111
- transition={{ delay: 0.4 }}
112
- >
113
- <div className="flex items-center gap-3 mb-4">
114
- <div className="w-1 h-5 rounded-full" style={{ background: '#00aaff', boxShadow: '0 0 8px #00aaff' }} />
115
- <span className="text-[10px] font-bold tracking-widest text-[#00aaff] uppercase">Voice Authenticity</span>
116
- </div>
117
-
118
- <div className="flex items-center gap-3 mb-4">
119
- <span
120
- className="px-3 py-1.5 rounded-full text-xs font-bold tracking-wider border"
121
- style={{ color, borderColor: `${color}55`, background: `${color}12` }}
122
- >
123
- {isAI ? '🤖 AI VOICE DETECTED' : '🎙️ HUMAN VOICE'}
124
- </span>
125
- <span className="text-2xl font-bold font-mono ml-auto" style={{ color }}>
126
- {audio.confidence}%
127
- </span>
128
- </div>
129
-
130
- {/* Bar */}
131
- <div className="h-2 rounded-full bg-white/5 overflow-hidden mb-4">
132
- <motion.div
133
- className="h-full rounded-full"
134
- style={{
135
- background: isAI ? 'linear-gradient(90deg,#880022,#ff3355)' : 'linear-gradient(90deg,#00aaff,#00ff88)',
136
- boxShadow: `0 0 10px ${color}66`,
137
- }}
138
- initial={{ width: 0 }}
139
- animate={{ width: `${audio.confidence}%` }}
140
- transition={{ duration: 1.2, ease: [0.22, 1, 0.36, 1] }}
141
- />
142
- </div>
143
-
144
- {/* Scores */}
145
- <div className="grid grid-cols-2 gap-3 mb-4">
146
- {[
147
- { label: 'Wav2Vec2 Model', val: `${audio.model_score}%` },
148
- { label: 'Signal Heuristics', val: `${audio.heuristic_score}%` },
149
- ].map(({ label, val }) => (
150
- <div key={label} className="rounded-xl bg-white/3 border border-white/5 p-3 text-center">
151
- <p className="text-[10px] text-white/40 tracking-wider mb-1">{label}</p>
152
- <p className="text-lg font-bold font-mono text-white">{val}</p>
153
- </div>
154
- ))}
155
- </div>
156
-
157
- {/* Details */}
158
- <div className="space-y-2">
159
- {audio.details.map((d, i) => (
160
- <motion.div
161
- key={i}
162
- className="flex items-start gap-2.5 text-xs text-white/60 bg-white/2 rounded-lg px-3 py-2 border-l-2"
163
- style={{ borderLeftColor: color }}
164
- initial={{ opacity: 0, x: -8 }}
165
- animate={{ opacity: 1, x: 0 }}
166
- transition={{ delay: 0.5 + i * 0.07 }}
167
- >
168
- <span className="w-1.5 h-1.5 rounded-full flex-shrink-0 mt-1" style={{ background: color, boxShadow: `0 0 6px ${color}` }} />
169
- {d}
170
- </motion.div>
171
- ))}
172
- </div>
173
-
174
- {/* Features */}
175
- {audio.features && Object.keys(audio.features).length > 0 && (
176
- <div className="mt-4 pt-4 border-t border-white/5">
177
- <p className="text-[10px] text-white/30 tracking-widest uppercase mb-2">Signal Features</p>
178
- <div className="grid grid-cols-2 gap-2">
179
- {Object.entries(audio.features).map(([k, v]) => (
180
- <div key={k} className="flex justify-between text-[10px]">
181
- <span className="text-white/30 capitalize">{k.replace(/_/g, ' ')}</span>
182
- <span className="text-white/70 font-mono">{typeof v === 'number' ? v.toFixed(3) : v}</span>
183
- </div>
184
- ))}
185
- </div>
186
- </div>
187
- )}
188
- </motion.div>
189
- )
190
- }
191
-
192
- // ── Main result panel ─────────────────────────────────────────────────────────
193
- export default function ResultPanel() {
194
- const { result, reset } = useStore()
195
- if (!result) return null
196
-
197
- const isFake = result.result === 'FAKE'
198
- const color = isFake ? '#ff3355' : '#00ff88'
199
- const colorDim = isFake ? 'rgba(255,51,85,0.08)' : 'rgba(0,255,136,0.08)'
200
-
201
- return (
202
- <AnimatePresence>
203
- <motion.div
204
- className="w-full max-w-2xl mx-auto space-y-4"
205
- initial={{ opacity: 0 }}
206
- animate={{ opacity: 1 }}
207
- exit={{ opacity: 0 }}
208
- >
209
- {/* Verdict card */}
210
- <motion.div
211
- className="rounded-2xl border-2 p-6"
212
- style={{ borderColor: `${color}55`, background: colorDim, boxShadow: `0 0 50px ${color}15` }}
213
- initial={{ scale: 0.95, opacity: 0 }}
214
- animate={{ scale: 1, opacity: 1 }}
215
- transition={{ type: 'spring', stiffness: 200, damping: 20 }}
216
- >
217
- <div className="flex flex-wrap items-center gap-6">
218
- {/* Badge */}
219
- <div className="text-center flex-shrink-0">
220
- <motion.div
221
- className="w-24 h-24 rounded-full border-2 flex items-center justify-center mx-auto mb-3 text-4xl"
222
- style={{ borderColor: color, boxShadow: `0 0 30px ${color}44`, background: `${color}10` }}
223
- animate={{ boxShadow: [`0 0 20px ${color}30`, `0 0 50px ${color}60`, `0 0 20px ${color}30`] }}
224
- transition={{ duration: 2, repeat: Infinity }}
225
- >
226
- {isFake ? '⚠️' : '✅'}
227
- </motion.div>
228
- <motion.p
229
- className="text-2xl font-bold tracking-widest"
230
- style={{ color, textShadow: `0 0 20px ${color}66` }}
231
- animate={isFake ? { textShadow: [`0 0 10px ${color}44`, `0 0 25px ${color}88`, `0 0 10px ${color}44`] } : {}}
232
- transition={{ duration: 2.5, repeat: Infinity }}
233
- >
234
- {isFake ? 'DEEPFAKE' : 'AUTHENTIC'}
235
- </motion.p>
236
- <p className="text-[10px] text-white/30 tracking-widest mt-1">VERDICT</p>
237
- </div>
238
-
239
- {/* Confidence */}
240
- <div className="flex-1 min-w-[200px]">
241
- <p className="text-[10px] text-white/40 tracking-widest uppercase mb-3">Confidence Score</p>
242
- <ConfidenceArc pct={result.confidence} isFake={isFake} />
243
-
244
- {/* Risk pill */}
245
- <div className="mt-4 flex items-center justify-between bg-white/3 rounded-xl px-4 py-3 border border-white/5">
246
- <span className="text-[10px] text-white/40 tracking-wider uppercase">Risk Level</span>
247
- <span
248
- className="text-xs font-bold tracking-wider px-3 py-1 rounded-full border"
249
- style={{
250
- color: result.confidence < 35 ? '#00ff88' : result.confidence < 65 ? '#ffaa00' : '#ff3355',
251
- borderColor: result.confidence < 35 ? '#00ff8844' : result.confidence < 65 ? '#ffaa0044' : '#ff335544',
252
- background: result.confidence < 35 ? '#00ff8810' : result.confidence < 65 ? '#ffaa0010' : '#ff335510',
253
- }}
254
- >
255
- {result.confidence < 35 ? 'LOW' : result.confidence < 65 ? 'MEDIUM' : result.confidence < 80 ? 'HIGH' : 'CRITICAL'}
256
- </span>
257
- </div>
258
- </div>
259
- </div>
260
- </motion.div>
261
-
262
- {/* Insights + Meta */}
263
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
264
- {/* Insights */}
265
- <div className="rounded-2xl border border-white/6 bg-white/2 p-5">
266
- <div className="flex items-center gap-2 mb-4">
267
- <div className="w-0.5 h-4 rounded-full bg-[#00aaff] shadow-[0_0_8px_#00aaff]" />
268
- <span className="text-[10px] font-bold tracking-widest text-[#00aaff] uppercase">Analysis Insights</span>
269
- </div>
270
- <div className="space-y-2">
271
- {result.details.map((d, i) => (
272
- <motion.div
273
- key={i}
274
- className="flex items-start gap-2.5 text-xs text-white/60 bg-white/2 rounded-lg px-3 py-2.5 border-l-2"
275
- style={{ borderLeftColor: color }}
276
- initial={{ opacity: 0, x: -10 }}
277
- animate={{ opacity: 1, x: 0 }}
278
- transition={{ delay: 0.1 + i * 0.07 }}
279
- >
280
- <span className="w-1.5 h-1.5 rounded-full flex-shrink-0 mt-1" style={{ background: color }} />
281
- {d}
282
- </motion.div>
283
- ))}
284
- </div>
285
- </div>
286
-
287
- {/* Metadata */}
288
- <div className="rounded-2xl border border-white/6 bg-white/2 p-5">
289
- <div className="flex items-center gap-2 mb-4">
290
- <div className="w-0.5 h-4 rounded-full bg-[#00aaff] shadow-[0_0_8px_#00aaff]" />
291
- <span className="text-[10px] font-bold tracking-widest text-[#00aaff] uppercase">Video Metadata</span>
292
- </div>
293
- <div className="space-y-2.5">
294
- {[
295
- ['Frames Analyzed', result.metadata.frames_analyzed],
296
- ['Faces Detected', result.metadata.frames_with_faces],
297
- ['Duration', `${result.metadata.video_duration_sec}s`],
298
- ['FPS', result.metadata.video_fps],
299
- ['Resolution', result.metadata.resolution],
300
- ['Processing Time', `${result.processing_time_sec}s`],
301
- ].map(([k, v]) => (
302
- <div key={String(k)} className="flex justify-between items-center border-b border-white/4 pb-2 last:border-0 last:pb-0">
303
- <span className="text-[11px] text-white/40">{k}</span>
304
- <span className="text-[12px] font-bold font-mono text-white">{v ?? '—'}</span>
305
- </div>
306
- ))}
307
- </div>
308
- <div className="mt-4 pt-3 border-t border-white/5 flex items-center gap-2">
309
- <div className="w-1.5 h-1.5 rounded-full bg-[#00ff88] shadow-[0_0_6px_#00ff88]" />
310
- <span className="text-[10px] text-white/30 font-mono tracking-wider">ViT Ensemble · 2 Models</span>
311
- </div>
312
- </div>
313
- </div>
314
-
315
- {/* Frame Timeline */}
316
- <div className="rounded-2xl border border-white/6 bg-white/2 p-5">
317
- <div className="flex items-center gap-2 mb-4">
318
- <div className="w-0.5 h-4 rounded-full bg-[#00aaff] shadow-[0_0_8px_#00aaff]" />
319
- <span className="text-[10px] font-bold tracking-widest text-[#00aaff] uppercase">Frame Analysis Timeline</span>
320
- <span className="text-[10px] text-white/20 font-mono ml-1">— {result.frame_timeline.length} frames</span>
321
- </div>
322
- <Timeline frames={result.frame_timeline} isFake={isFake} />
323
- <div className="flex justify-between mt-2">
324
- <span className="text-[10px] text-white/20 font-mono">Frame 0</span>
325
- <span className="text-[10px] text-white/20 font-mono">Frame {result.frame_timeline.length}</span>
326
- </div>
327
- </div>
328
-
329
- {/* Audio */}
330
- {result.audio && <AudioCard audio={result.audio} />}
331
-
332
- {/* Reset */}
333
- <div className="text-center pt-2">
334
- <motion.button
335
- onClick={reset}
336
- className="px-6 py-2.5 text-xs font-bold tracking-widest uppercase text-white/40 border border-white/10 rounded-xl hover:text-[#00ff88] hover:border-[#00ff8840] hover:bg-[#00ff8808] transition-all duration-200"
337
- whileHover={{ scale: 1.02 }}
338
- whileTap={{ scale: 0.98 }}
339
- >
340
- ↻ &nbsp;Analyze Another Video
341
- </motion.button>
342
- </div>
343
- </motion.div>
344
- </AnimatePresence>
345
- )
346
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/components/UploadZone.tsx DELETED
@@ -1,190 +0,0 @@
1
- import { useRef, useState } from 'react'
2
- import { motion } from 'framer-motion'
3
- import { useStore } from '../store'
4
- import { analyzeVideo } from '../api'
5
-
6
- const AGENTS = [
7
- { icon: '🎬', label: 'Frame Extractor', sub: 'Sampling keyframes' },
8
- { icon: '👤', label: 'Face Detector', sub: 'Isolating regions' },
9
- { icon: '🧠', label: 'ViT Ensemble', sub: 'Neural inference' },
10
- { icon: '📊', label: 'Report Builder', sub: 'Compiling results' },
11
- ]
12
-
13
- function fmtBytes(b: number) {
14
- if (b < 1024) return `${b} B`
15
- if (b < 1048576) return `${(b / 1024).toFixed(1)} KB`
16
- return `${(b / 1048576).toFixed(1)} MB`
17
- }
18
-
19
- export default function UploadZone() {
20
- const { file, state, setFile, setState, setResult, setError, setAgentStep, reset } = useStore()
21
- const inputRef = useRef<HTMLInputElement>(null)
22
- const [dragging, setDragging] = useState(false)
23
-
24
- const isLoading = state === 'loading'
25
-
26
- function applyFile(f: File) {
27
- if (f.type.startsWith('video/')) setFile(f)
28
- else setError('Please select a valid video file (MP4, AVI, MOV, MKV, WebM).')
29
- }
30
-
31
- async function handleAnalyze() {
32
- if (!file) return
33
- setState('loading')
34
- setAgentStep(0)
35
-
36
- // Simulate agent steps
37
- const delays = [0, 1800, 4200, 7500]
38
- delays.forEach((d, i) => setTimeout(() => setAgentStep(i), d))
39
-
40
- try {
41
- const result = await analyzeVideo(file)
42
- setResult(result)
43
- } catch (e: any) {
44
- setError(e.message || 'Analysis failed')
45
- }
46
- }
47
-
48
- return (
49
- <div className="w-full max-w-xl mx-auto flex flex-col gap-5">
50
- {/* Drop zone */}
51
- <motion.div
52
- className={`relative rounded-2xl border-2 border-dashed cursor-pointer transition-all duration-300 overflow-hidden
53
- ${dragging ? 'border-[#00ff88] bg-[#00ff8808] shadow-[0_0_40px_#00ff8820]' : 'border-[#00ff8830] hover:border-[#00ff8866] hover:bg-[#00ff8806]'}
54
- ${file ? 'py-6 px-8' : 'py-12 px-8'}`}
55
- onClick={() => !file && inputRef.current?.click()}
56
- onDragOver={(e) => { e.preventDefault(); setDragging(true) }}
57
- onDragLeave={() => setDragging(false)}
58
- onDrop={(e) => {
59
- e.preventDefault(); setDragging(false)
60
- const f = e.dataTransfer.files[0]
61
- if (f) applyFile(f)
62
- }}
63
- whileHover={{ scale: file ? 1 : 1.01 }}
64
- whileTap={{ scale: 0.99 }}
65
- >
66
- <input
67
- ref={inputRef}
68
- type="file"
69
- accept="video/*"
70
- className="hidden"
71
- onChange={(e) => e.target.files?.[0] && applyFile(e.target.files[0])}
72
- />
73
-
74
- {!file ? (
75
- <motion.div className="text-center" initial={{ opacity: 0 }} animate={{ opacity: 1 }}>
76
- {/* Orbit upload icon */}
77
- <div className="relative w-20 h-20 mx-auto mb-5">
78
- <motion.div
79
- className="absolute inset-0 rounded-full border border-dashed border-[#00ff8840]"
80
- animate={{ rotate: 360 }}
81
- transition={{ duration: 8, repeat: Infinity, ease: 'linear' }}
82
- />
83
- <motion.div
84
- className="absolute inset-3 rounded-full border border-[#00ff8820]"
85
- animate={{ rotate: -360 }}
86
- transition={{ duration: 5, repeat: Infinity, ease: 'linear' }}
87
- />
88
- <div className="absolute inset-0 flex items-center justify-center">
89
- <svg width="28" height="28" fill="none" stroke="#00ff8899" strokeWidth="1.5" viewBox="0 0 24 24">
90
- <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"/>
91
- </svg>
92
- </div>
93
- </div>
94
- <p className="text-white/80 font-semibold text-base mb-1">Drop video for forensic analysis</p>
95
- <p className="text-white/40 text-sm">Drag & drop or click to browse</p>
96
- <p className="text-white/20 text-xs mt-3 font-mono">MP4 · AVI · MOV · MKV · WebM — Max 100MB</p>
97
- </motion.div>
98
- ) : (
99
- <motion.div
100
- className="flex items-center gap-4"
101
- initial={{ opacity: 0, y: 8 }}
102
- animate={{ opacity: 1, y: 0 }}
103
- >
104
- <div className="w-12 h-12 rounded-xl bg-[#00ff8812] border border-[#00ff8830] flex items-center justify-center flex-shrink-0 shadow-[0_0_20px_#00ff8820]">
105
- <svg width="22" height="22" fill="none" stroke="#00ff88" strokeWidth="1.5" viewBox="0 0 24 24">
106
- <path strokeLinecap="round" strokeLinejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"/>
107
- </svg>
108
- </div>
109
- <div className="flex-1 min-w-0">
110
- <p className="text-white font-semibold text-sm truncate">{file.name}</p>
111
- <p className="text-white/40 text-xs mt-0.5 font-mono">{fmtBytes(file.size)}</p>
112
- </div>
113
- <button
114
- onClick={(e) => { e.stopPropagation(); reset() }}
115
- className="p-2 text-white/30 hover:text-red-400 transition-colors rounded-lg hover:bg-red-400/10"
116
- >
117
- <svg width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2" viewBox="0 0 24 24">
118
- <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12"/>
119
- </svg>
120
- </button>
121
- </motion.div>
122
- )}
123
- </motion.div>
124
-
125
- {/* Analyze button */}
126
- <motion.button
127
- disabled={!file || isLoading}
128
- onClick={handleAnalyze}
129
- className={`w-full py-4 rounded-xl font-bold text-sm tracking-widest uppercase transition-all duration-300 relative overflow-hidden
130
- ${file && !isLoading
131
- ? 'bg-gradient-to-r from-[#00ff88] to-[#00cc66] text-[#050508] shadow-[0_0_30px_#00ff8840] cursor-pointer'
132
- : 'bg-white/5 text-white/20 cursor-not-allowed border border-white/10'}`}
133
- whileHover={file && !isLoading ? { scale: 1.02, boxShadow: '0 0 50px #00ff8860' } : {}}
134
- whileTap={file && !isLoading ? { scale: 0.98 } : {}}
135
- >
136
- {/* Shimmer */}
137
- {file && !isLoading && (
138
- <motion.div
139
- className="absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent skew-x-12"
140
- animate={{ x: ['-100%', '200%'] }}
141
- transition={{ duration: 2.5, repeat: Infinity, ease: 'linear' }}
142
- />
143
- )}
144
- <span className="relative z-10">
145
- {isLoading ? '⏳ Analyzing...' : '▶ Analyze Video'}
146
- </span>
147
- </motion.button>
148
-
149
- {/* Agent pipeline (loading state) */}
150
- {isLoading && <AgentPipeline />}
151
- </div>
152
- )
153
- }
154
-
155
- function AgentPipeline() {
156
- const agentStep = useStore((s) => s.agentStep)
157
-
158
- return (
159
- <motion.div
160
- initial={{ opacity: 0, y: 12 }}
161
- animate={{ opacity: 1, y: 0 }}
162
- className="grid grid-cols-2 gap-3"
163
- >
164
- {AGENTS.map((agent, i) => {
165
- const active = i <= agentStep
166
- const current = i === agentStep
167
- return (
168
- <motion.div
169
- key={i}
170
- className={`rounded-xl p-3 border transition-all duration-500 flex items-center gap-3
171
- ${active
172
- ? 'border-[#00ff8844] bg-[#00ff8808] shadow-[0_0_15px_#00ff8815]'
173
- : 'border-white/5 bg-white/2'}`}
174
- animate={current ? { scale: [1, 1.02, 1] } : {}}
175
- transition={{ duration: 0.6, repeat: current ? Infinity : 0 }}
176
- >
177
- <div className={`w-2 h-2 rounded-full flex-shrink-0 transition-all duration-500
178
- ${active ? 'bg-[#00ff88] shadow-[0_0_8px_#00ff88]' : 'bg-white/15'}`} />
179
- <div>
180
- <p className="text-xs font-semibold text-white/80">{agent.label}</p>
181
- <p className={`text-[10px] font-mono transition-colors duration-300 ${active ? 'text-[#00ff88]' : 'text-white/30'}`}>
182
- {active ? (current ? agent.sub : 'Done ✓') : 'Waiting...'}
183
- </p>
184
- </div>
185
- </motion.div>
186
- )
187
- })}
188
- </motion.div>
189
- )
190
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/index.css DELETED
@@ -1,43 +0,0 @@
1
- @import "tailwindcss";
2
-
3
- * { box-sizing: border-box; margin: 0; padding: 0; }
4
-
5
- body {
6
- background: #050508;
7
- color: #e2e8f0;
8
- font-family: 'Space Grotesk', 'Inter', sans-serif;
9
- -webkit-font-smoothing: antialiased;
10
- }
11
-
12
- ::-webkit-scrollbar { width: 4px; height: 4px; }
13
- ::-webkit-scrollbar-track { background: #050508; }
14
- ::-webkit-scrollbar-thumb { background: rgba(0,255,136,0.2); border-radius: 4px; }
15
-
16
- /* Animated grid background */
17
- body::before {
18
- content: '';
19
- position: fixed;
20
- inset: 0;
21
- z-index: 0;
22
- pointer-events: none;
23
- background-image:
24
- linear-gradient(rgba(0,255,136,0.025) 1px, transparent 1px),
25
- linear-gradient(90deg, rgba(0,255,136,0.025) 1px, transparent 1px);
26
- background-size: 60px 60px;
27
- }
28
-
29
- /* Radial glow at top */
30
- body::after {
31
- content: '';
32
- position: fixed;
33
- top: -200px;
34
- left: 50%;
35
- transform: translateX(-50%);
36
- width: 800px;
37
- height: 600px;
38
- background: radial-gradient(ellipse, rgba(0,255,136,0.06) 0%, transparent 70%);
39
- pointer-events: none;
40
- z-index: 0;
41
- }
42
-
43
- main, header, footer { position: relative; z-index: 1; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/main.jsx DELETED
@@ -1,10 +0,0 @@
1
- import React from 'react'
2
- import ReactDOM from 'react-dom/client'
3
- import App from './App.jsx'
4
- import './index.css'
5
-
6
- ReactDOM.createRoot(document.getElementById('root')).render(
7
- <React.StrictMode>
8
- <App />
9
- </React.StrictMode>,
10
- )
 
 
 
 
 
 
 
 
 
 
 
frontend/src/main.tsx DELETED
@@ -1,10 +0,0 @@
1
- import { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import './index.css'
4
- import App from './App.tsx'
5
-
6
- createRoot(document.getElementById('root')!).render(
7
- <StrictMode>
8
- <App />
9
- </StrictMode>,
10
- )
 
 
 
 
 
 
 
 
 
 
 
frontend/src/store.ts DELETED
@@ -1,59 +0,0 @@
1
- import { create } from 'zustand'
2
-
3
- export type AppState = 'idle' | 'loading' | 'result' | 'error'
4
- export type Verdict = 'FAKE' | 'REAL' | null
5
-
6
- export interface FramePoint { frame: number; fake_pct: number }
7
- export interface AudioResult {
8
- available: boolean
9
- result: string
10
- confidence: number
11
- fake_probability: number
12
- model_score: number
13
- heuristic_score: number
14
- details: string[]
15
- features: Record<string, number>
16
- }
17
- export interface AnalysisResult {
18
- result: 'FAKE' | 'REAL'
19
- confidence: number
20
- details: string[]
21
- frame_timeline: FramePoint[]
22
- metadata: {
23
- frames_analyzed: number
24
- frames_with_faces: number
25
- video_duration_sec: number
26
- video_fps: number
27
- resolution: string
28
- }
29
- processing_time_sec: number
30
- audio: AudioResult
31
- }
32
-
33
- interface Store {
34
- state: AppState
35
- file: File | null
36
- result: AnalysisResult | null
37
- error: string | null
38
- agentStep: number
39
- setState: (s: AppState) => void
40
- setFile: (f: File | null) => void
41
- setResult: (r: AnalysisResult) => void
42
- setError: (e: string) => void
43
- setAgentStep: (n: number) => void
44
- reset: () => void
45
- }
46
-
47
- export const useStore = create<Store>((set) => ({
48
- state: 'idle',
49
- file: null,
50
- result: null,
51
- error: null,
52
- agentStep: -1,
53
- setState: (state) => set({ state }),
54
- setFile: (file) => set({ file }),
55
- setResult: (result) => set({ result, state: 'result' }),
56
- setError: (error) => set({ error, state: 'error' }),
57
- setAgentStep: (agentStep) => set({ agentStep }),
58
- reset: () => set({ state: 'idle', file: null, result: null, error: null, agentStep: -1 }),
59
- }))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/vite.config.js DELETED
@@ -1,18 +0,0 @@
1
- import { defineConfig } from 'vite'
2
- import react from '@vitejs/plugin-react'
3
- import tailwindcss from '@tailwindcss/vite'
4
-
5
- export default defineConfig({
6
- plugins: [react(), tailwindcss()],
7
- server: {
8
- port: 5173,
9
- proxy: {
10
- '/analyze': 'http://localhost:8000',
11
- '/health': 'http://localhost:8000',
12
- },
13
- },
14
- build: {
15
- outDir: '../frontend-dist',
16
- emptyOutDir: true,
17
- },
18
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
render.yaml DELETED
@@ -1,12 +0,0 @@
1
- services:
2
- - type: web
3
- name: authrix-api
4
- runtime: docker
5
- dockerfilePath: ./Dockerfile
6
- plan: standard
7
- healthCheckPath: /health
8
- envVars:
9
- - key: PORT
10
- value: 8000
11
- - key: PYTHONUNBUFFERED
12
- value: "1"
 
 
 
 
 
 
 
 
 
 
 
 
 
setup.bat DELETED
@@ -1,44 +0,0 @@
1
- @echo off
2
- echo.
3
- echo ==========================================
4
- echo DEEPFAKE AUTHENTICATOR -- SETUP
5
- echo ==========================================
6
- echo.
7
-
8
- where python >nul 2>&1
9
- if %errorlevel% neq 0 (
10
- echo ERROR: Python not found. Please install Python 3.9+
11
- pause
12
- exit /b 1
13
- )
14
-
15
- echo Creating virtual environment...
16
- python -m venv venv
17
- call venv\Scripts\activate.bat
18
-
19
- echo Upgrading pip...
20
- python -m pip install --upgrade pip -q
21
-
22
- echo Installing core dependencies...
23
- pip install fastapi==0.111.0 "uvicorn[standard]==0.29.0" python-multipart==0.0.9 -q
24
- pip install opencv-python==4.9.0.80 mediapipe==0.10.14 numpy==1.26.4 Pillow==10.3.0 -q
25
-
26
- echo Installing HuggingFace dependencies (optional)...
27
- pip install transformers==4.41.0 torch==2.3.0 --index-url https://download.pytorch.org/whl/cpu -q
28
- if %errorlevel% neq 0 (
29
- echo WARNING: torch/transformers install failed -- will use heuristic fallback
30
- )
31
-
32
- echo.
33
- echo ==========================================
34
- echo SETUP COMPLETE
35
- echo ==========================================
36
- echo.
37
- echo To start the server:
38
- echo venv\Scripts\activate.bat
39
- echo cd backend
40
- echo python main.py
41
- echo.
42
- echo Then open: http://localhost:8000
43
- echo.
44
- pause
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
setup.sh DELETED
@@ -1,55 +0,0 @@
1
- #!/bin/bash
2
- # Deepfake Authenticator — Setup Script
3
-
4
- set -e
5
-
6
- echo ""
7
- echo "╔══════════════════════════════════════════╗"
8
- echo "║ DEEPFAKE AUTHENTICATOR — SETUP ║"
9
- echo "╚══════════════════════════════════════════╝"
10
- echo ""
11
-
12
- # Check Python
13
- if ! command -v python3 &>/dev/null; then
14
- echo "❌ Python 3 not found. Please install Python 3.9+"
15
- exit 1
16
- fi
17
-
18
- PYTHON_VERSION=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
19
- echo "✅ Python $PYTHON_VERSION detected"
20
-
21
- # Create virtual environment
22
- echo ""
23
- echo "📦 Creating virtual environment..."
24
- python3 -m venv venv
25
- source venv/bin/activate
26
-
27
- # Upgrade pip
28
- pip install --upgrade pip -q
29
-
30
- # Install dependencies
31
- echo ""
32
- echo "📥 Installing dependencies..."
33
- echo " (This may take a few minutes for torch/transformers)"
34
- echo ""
35
-
36
- pip install fastapi==0.111.0 uvicorn[standard]==0.29.0 python-multipart==0.0.9 -q
37
- pip install opencv-python==4.9.0.80 mediapipe==0.10.14 numpy==1.26.4 Pillow==10.3.0 -q
38
-
39
- echo ""
40
- echo "🤖 Installing HuggingFace model dependencies..."
41
- echo " (Optional — skip with Ctrl+C if you want heuristic-only mode)"
42
- pip install transformers==4.41.0 torch==2.3.0 --index-url https://download.pytorch.org/whl/cpu -q || \
43
- echo "⚠️ torch/transformers install failed — will use heuristic fallback"
44
-
45
- echo ""
46
- echo "╔══════════════════════════════════════════╗"
47
- echo "║ SETUP COMPLETE ✅ ║"
48
- echo "╚══════════════════════════════════════════╝"
49
- echo ""
50
- echo "To start the server:"
51
- echo " source venv/bin/activate"
52
- echo " cd backend && python main.py"
53
- echo ""
54
- echo "Then open: http://localhost:8000"
55
- echo ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
start.bat DELETED
@@ -1,45 +0,0 @@
1
- @echo off
2
- title Deepfake Authenticator
3
- color 0A
4
-
5
- echo.
6
- echo ============================================
7
- echo DEEPFAKE AUTHENTICATOR - Starting...
8
- echo ============================================
9
- echo.
10
-
11
- :: Check if venv exists
12
- if not exist "venv\Scripts\activate.bat" (
13
- echo [!] Virtual environment not found.
14
- echo [*] Run setup.bat first to install dependencies.
15
- echo.
16
- pause
17
- exit /b 1
18
- )
19
-
20
- :: Activate venv
21
- call venv\Scripts\activate.bat
22
-
23
- :: Kill anything on port 8000
24
- echo [*] Clearing port 8000...
25
- for /f "tokens=5" %%a in ('netstat -aon ^| findstr ":8000 " 2^>nul') do (
26
- taskkill /PID %%a /F >nul 2>&1
27
- )
28
-
29
- echo [*] Starting server...
30
- echo [*] Open your browser at: http://localhost:8000
31
- echo.
32
- echo Press Ctrl+C to stop the server.
33
- echo ============================================
34
- echo.
35
-
36
- :: Open browser after 5 seconds in background
37
- start /b cmd /c "timeout /t 8 /nobreak >nul && start http://localhost:8000"
38
-
39
- :: Start server
40
- cd backend
41
- python -m uvicorn main:app --host 0.0.0.0 --port 8000 --log-level warning
42
-
43
- echo.
44
- echo Server stopped.
45
- pause
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
start.sh DELETED
@@ -1,34 +0,0 @@
1
- #!/bin/bash
2
- clear
3
- echo ""
4
- echo " ============================================"
5
- echo " DEEPFAKE AUTHENTICATOR - Starting..."
6
- echo " ============================================"
7
- echo ""
8
-
9
- # Check venv
10
- if [ ! -f "venv/bin/activate" ]; then
11
- echo " [!] Virtual environment not found."
12
- echo " [*] Run ./setup.sh first to install dependencies."
13
- echo ""
14
- exit 1
15
- fi
16
-
17
- source venv/bin/activate
18
-
19
- # Kill anything on port 8000
20
- echo " [*] Clearing port 8000..."
21
- lsof -ti:8000 | xargs kill -9 2>/dev/null
22
-
23
- echo " [*] Starting server..."
24
- echo " [*] Open your browser at: http://localhost:8000"
25
- echo ""
26
- echo " Press Ctrl+C to stop."
27
- echo " ============================================"
28
- echo ""
29
-
30
- # Open browser after 8s in background
31
- (sleep 8 && open "http://localhost:8000" 2>/dev/null || xdg-open "http://localhost:8000" 2>/dev/null) &
32
-
33
- cd backend
34
- python -m uvicorn main:app --host 0.0.0.0 --port 8000 --log-level warning