Deepfake Authenticator commited on
Commit ·
3337c5d
1
Parent(s): 4c0afe5
Initial Authrix deployment
Browse files- .dockerignore +17 -0
- BUSINESS_MODEL.md +262 -0
- DEPLOY_HF.md +101 -0
- Dockerfile +47 -0
- README.md +16 -123
- STRIPE_SETUP.md +315 -0
- backend/api_keys.json +15 -0
- backend/auth.py +132 -0
- backend/create_owner_key.py +27 -0
- backend/detector.py +38 -36
- backend/main.py +150 -50
- backend/requirements.txt +5 -3
- backend/stripe_integration.py +264 -0
- extension/README.md +39 -19
- extension/background.js +167 -50
- extension/content.js +99 -132
- extension/manifest.json +6 -4
- extension/offscreen.html +8 -0
- extension/offscreen.js +122 -0
- extension/popup.html +91 -17
- extension/popup.js +61 -22
- frontend-vanilla/index.html +1 -0
- frontend-vanilla/pricing.html +470 -0
- render.yaml +12 -0
- stitch_authrix_deepfake_detection_engine/authrix_analysis_results/code.html +328 -0
- stitch_authrix_deepfake_detection_engine/authrix_hero_section/code.html +312 -0
- stitch_authrix_deepfake_detection_engine/authrix_hero_section_v2/code.html +246 -0
- stitch_authrix_deepfake_detection_engine/authrix_kinetic_1/DESIGN.md +159 -0
- stitch_authrix_deepfake_detection_engine/authrix_kinetic_2/DESIGN.md +148 -0
- stitch_authrix_deepfake_detection_engine/authrix_system_processing/code.html +253 -0
- stitch_authrix_deepfake_detection_engine/authrix_upload_center/code.html +234 -0
.dockerignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
.git/
|
| 6 |
+
.gitignore
|
| 7 |
+
node_modules/
|
| 8 |
+
frontend/node_modules/
|
| 9 |
+
frontend/dist/
|
| 10 |
+
*.log
|
| 11 |
+
uploads/
|
| 12 |
+
*.mp4
|
| 13 |
+
*.webm
|
| 14 |
+
*.avi
|
| 15 |
+
.env
|
| 16 |
+
api_keys.json
|
| 17 |
+
usage.json
|
BUSINESS_MODEL.md
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 💰 Authrix Business Model & Monetization Guide
|
| 2 |
+
|
| 3 |
+
## Revenue Streams
|
| 4 |
+
|
| 5 |
+
### 1. **SaaS Subscription Tiers**
|
| 6 |
+
|
| 7 |
+
| Tier | Price | Analyses/Month | Features |
|
| 8 |
+
|------|-------|----------------|----------|
|
| 9 |
+
| **Free** | $0 | 10 | Browser extension, 2-min videos, community support |
|
| 10 |
+
| **Pro** | $9.99/mo | 100 | 10-min videos, API access (100 calls), email support |
|
| 11 |
+
| **Business** | $49/mo | 1,000 | Unlimited length, API (5K calls), white-label reports |
|
| 12 |
+
| **Enterprise** | Custom | Unlimited | On-premise, custom training, SLA, dedicated support |
|
| 13 |
+
|
| 14 |
+
### 2. **Pay-Per-Use API**
|
| 15 |
+
|
| 16 |
+
- $0.05 per video (< 5 min)
|
| 17 |
+
- $0.10 per video (5-15 min)
|
| 18 |
+
- $0.25 per video (> 15 min)
|
| 19 |
+
|
| 20 |
+
**Target Markets:**
|
| 21 |
+
- Social media platforms (Facebook, TikTok, Instagram)
|
| 22 |
+
- News organizations (CNN, BBC, Reuters)
|
| 23 |
+
- Content moderation companies (Spectrum Labs, ActiveFence)
|
| 24 |
+
- Dating apps (Tinder, Bumble, Hinge)
|
| 25 |
+
- Video platforms (YouTube, Vimeo, Twitch)
|
| 26 |
+
|
| 27 |
+
### 3. **Browser Extension Premium**
|
| 28 |
+
|
| 29 |
+
- **Free:** 5 analyses/day
|
| 30 |
+
- **Premium:** $4.99/month - unlimited analyses
|
| 31 |
+
|
| 32 |
+
**Distribution:**
|
| 33 |
+
- Chrome Web Store
|
| 34 |
+
- Firefox Add-ons
|
| 35 |
+
- Edge Add-ons
|
| 36 |
+
|
| 37 |
+
### 4. **B2B Enterprise Solutions**
|
| 38 |
+
|
| 39 |
+
#### **Social Media Platforms**
|
| 40 |
+
- Real-time API integration
|
| 41 |
+
- Bulk scanning (millions of videos/day)
|
| 42 |
+
- Custom model training on platform-specific content
|
| 43 |
+
- **Pricing:** $5,000-$50,000/month
|
| 44 |
+
|
| 45 |
+
#### **News Organizations**
|
| 46 |
+
- Verification dashboard
|
| 47 |
+
- Team collaboration
|
| 48 |
+
- Audit trails & reports
|
| 49 |
+
- **Pricing:** $500-$5,000/month
|
| 50 |
+
|
| 51 |
+
#### **Law Enforcement**
|
| 52 |
+
- Forensic-grade reports
|
| 53 |
+
- Chain of custody
|
| 54 |
+
- Expert witness support
|
| 55 |
+
- **Pricing:** $10,000-$100,000/year
|
| 56 |
+
|
| 57 |
+
### 5. **White-Label Licensing**
|
| 58 |
+
|
| 59 |
+
License the technology to:
|
| 60 |
+
- Security companies
|
| 61 |
+
- Social media platforms
|
| 62 |
+
- Government agencies
|
| 63 |
+
|
| 64 |
+
**Pricing:** $50,000-$500,000 one-time + 10-20% revenue share
|
| 65 |
+
|
| 66 |
+
### 6. **Training & Certification**
|
| 67 |
+
|
| 68 |
+
- **Online Courses:** "Deepfake Detection Fundamentals" - $299
|
| 69 |
+
- **Professional Certification:** "Certified Deepfake Analyst" - $999
|
| 70 |
+
- **Corporate Training:** Custom workshops - $5,000-$20,000
|
| 71 |
+
|
| 72 |
+
### 7. **Consulting Services**
|
| 73 |
+
|
| 74 |
+
- Custom model training: $10,000-$50,000
|
| 75 |
+
- Integration support: $200-$500/hour
|
| 76 |
+
- Security audits: $5,000-$25,000
|
| 77 |
+
|
| 78 |
+
### 8. **Data Marketplace**
|
| 79 |
+
|
| 80 |
+
- Sell anonymized deepfake datasets to researchers
|
| 81 |
+
- **Pricing:** $5,000-$50,000 per dataset
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## 📈 Growth Strategy
|
| 86 |
+
|
| 87 |
+
### Phase 1: Launch (Months 1-3)
|
| 88 |
+
- Launch free tier + browser extension
|
| 89 |
+
- Get 1,000 users
|
| 90 |
+
- Focus on product-market fit
|
| 91 |
+
- **Goal:** Validate demand
|
| 92 |
+
|
| 93 |
+
### Phase 2: Monetization (Months 4-6)
|
| 94 |
+
- Launch Pro tier
|
| 95 |
+
- Add API access
|
| 96 |
+
- Target first 10 paying customers
|
| 97 |
+
- **Goal:** $5,000 MRR
|
| 98 |
+
|
| 99 |
+
### Phase 3: Scale (Months 7-12)
|
| 100 |
+
- Launch Business tier
|
| 101 |
+
- B2B sales to news orgs
|
| 102 |
+
- First enterprise deal
|
| 103 |
+
- **Goal:** $50,000 MRR
|
| 104 |
+
|
| 105 |
+
### Phase 4: Enterprise (Year 2)
|
| 106 |
+
- On-premise deployments
|
| 107 |
+
- White-label licensing
|
| 108 |
+
- International expansion
|
| 109 |
+
- **Goal:** $500,000 MRR
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
## 🎯 Target Customer Segments
|
| 114 |
+
|
| 115 |
+
### 1. **Individual Users** (Free/Pro)
|
| 116 |
+
- Journalists
|
| 117 |
+
- Content creators
|
| 118 |
+
- Researchers
|
| 119 |
+
- Privacy-conscious users
|
| 120 |
+
|
| 121 |
+
### 2. **Small Businesses** (Pro/Business)
|
| 122 |
+
- Marketing agencies
|
| 123 |
+
- PR firms
|
| 124 |
+
- Small news outlets
|
| 125 |
+
- Influencer agencies
|
| 126 |
+
|
| 127 |
+
### 3. **Enterprise** (Business/Enterprise)
|
| 128 |
+
- Social media platforms
|
| 129 |
+
- News organizations
|
| 130 |
+
- Government agencies
|
| 131 |
+
- Law enforcement
|
| 132 |
+
- Financial institutions (KYC/AML)
|
| 133 |
+
|
| 134 |
+
---
|
| 135 |
+
|
| 136 |
+
## 💡 Marketing Channels
|
| 137 |
+
|
| 138 |
+
### 1. **Content Marketing**
|
| 139 |
+
- Blog: "How to Spot Deepfakes"
|
| 140 |
+
- YouTube tutorials
|
| 141 |
+
- Case studies
|
| 142 |
+
- Whitepapers
|
| 143 |
+
|
| 144 |
+
### 2. **SEO**
|
| 145 |
+
- Target keywords: "deepfake detector", "AI video verification"
|
| 146 |
+
- Build backlinks from tech blogs
|
| 147 |
+
|
| 148 |
+
### 3. **Partnerships**
|
| 149 |
+
- Integrate with video platforms
|
| 150 |
+
- Partner with news organizations
|
| 151 |
+
- Academic collaborations
|
| 152 |
+
|
| 153 |
+
### 4. **Paid Advertising**
|
| 154 |
+
- Google Ads (high-intent keywords)
|
| 155 |
+
- LinkedIn Ads (B2B)
|
| 156 |
+
- Twitter/X (tech audience)
|
| 157 |
+
|
| 158 |
+
### 5. **PR & Media**
|
| 159 |
+
- Press releases for major features
|
| 160 |
+
- Guest posts on tech blogs
|
| 161 |
+
- Conference speaking
|
| 162 |
+
|
| 163 |
+
---
|
| 164 |
+
|
| 165 |
+
## 📊 Financial Projections
|
| 166 |
+
|
| 167 |
+
### Year 1 (Conservative)
|
| 168 |
+
| Revenue Stream | Monthly | Annual |
|
| 169 |
+
|----------------|---------|--------|
|
| 170 |
+
| Pro subscriptions (50 users) | $500 | $6,000 |
|
| 171 |
+
| Business subscriptions (10) | $490 | $5,880 |
|
| 172 |
+
| Enterprise (5 deals) | $25,000 | $300,000 |
|
| 173 |
+
| API usage | $2,000 | $24,000 |
|
| 174 |
+
| **Total** | **$27,990** | **$335,880** |
|
| 175 |
+
|
| 176 |
+
### Year 1 (Optimistic)
|
| 177 |
+
| Revenue Stream | Monthly | Annual |
|
| 178 |
+
|----------------|---------|--------|
|
| 179 |
+
| Pro subscriptions (500 users) | $5,000 | $60,000 |
|
| 180 |
+
| Business subscriptions (50) | $2,450 | $29,400 |
|
| 181 |
+
| Enterprise (20 deals) | $100,000 | $1,200,000 |
|
| 182 |
+
| API usage | $20,000 | $240,000 |
|
| 183 |
+
| **Total** | **$127,450** | **$1,529,400** |
|
| 184 |
+
|
| 185 |
+
### Year 2 Target
|
| 186 |
+
- **$5M ARR**
|
| 187 |
+
- 50 enterprise customers
|
| 188 |
+
- 10,000 paying users
|
| 189 |
+
- Series A funding ($5-10M)
|
| 190 |
+
|
| 191 |
+
---
|
| 192 |
+
|
| 193 |
+
## 🚀 Quick Start: Monetization Setup
|
| 194 |
+
|
| 195 |
+
### 1. **Get Your API Key**
|
| 196 |
+
```bash
|
| 197 |
+
cd backend
|
| 198 |
+
python -c "from auth import create_api_key; print(create_api_key('your@email.com', 'pro'))"
|
| 199 |
+
```
|
| 200 |
+
|
| 201 |
+
### 2. **Use API with Key**
|
| 202 |
+
```bash
|
| 203 |
+
curl -X POST http://localhost:8000/analyze \
|
| 204 |
+
-H "X-API-Key: authrix_YOUR_KEY_HERE" \
|
| 205 |
+
-F "file=@video.mp4"
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
### 3. **Check Usage**
|
| 209 |
+
```bash
|
| 210 |
+
python -c "from auth import check_usage_limit; print(check_usage_limit('authrix_YOUR_KEY'))"
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
---
|
| 214 |
+
|
| 215 |
+
## 📞 Next Steps to Start Earning
|
| 216 |
+
|
| 217 |
+
### Immediate (Week 1):
|
| 218 |
+
1. ✅ Set up Stripe/PayPal for payments
|
| 219 |
+
2. ✅ Create landing page with pricing
|
| 220 |
+
3. ✅ Launch on Product Hunt
|
| 221 |
+
4. ✅ Post on HackerNews, Reddit
|
| 222 |
+
|
| 223 |
+
### Short-term (Month 1):
|
| 224 |
+
1. Get first 10 paying customers
|
| 225 |
+
2. Set up customer support (email/chat)
|
| 226 |
+
3. Create demo videos
|
| 227 |
+
4. Reach out to 50 potential B2B customers
|
| 228 |
+
|
| 229 |
+
### Medium-term (Months 2-3):
|
| 230 |
+
1. Launch Chrome Web Store listing
|
| 231 |
+
2. Write 10 blog posts for SEO
|
| 232 |
+
3. Close first enterprise deal
|
| 233 |
+
4. Build referral program
|
| 234 |
+
|
| 235 |
+
### Long-term (Months 4-12):
|
| 236 |
+
1. Raise seed funding ($500K-$2M)
|
| 237 |
+
2. Hire sales team
|
| 238 |
+
3. Expand to international markets
|
| 239 |
+
4. Build mobile apps
|
| 240 |
+
|
| 241 |
+
---
|
| 242 |
+
|
| 243 |
+
## 🎓 Resources
|
| 244 |
+
|
| 245 |
+
- **Stripe Integration:** https://stripe.com/docs/api
|
| 246 |
+
- **Landing Page Builder:** https://carrd.co, https://webflow.com
|
| 247 |
+
- **Email Marketing:** https://mailchimp.com, https://sendgrid.com
|
| 248 |
+
- **Customer Support:** https://intercom.com, https://zendesk.com
|
| 249 |
+
- **Analytics:** https://mixpanel.com, https://amplitude.com
|
| 250 |
+
|
| 251 |
+
---
|
| 252 |
+
|
| 253 |
+
## 📧 Contact for Enterprise Sales
|
| 254 |
+
|
| 255 |
+
**Email:** enterprise@authrix.ai
|
| 256 |
+
**Demo:** https://authrix.ai/demo
|
| 257 |
+
**Pricing:** https://authrix.ai/pricing
|
| 258 |
+
**API Docs:** https://docs.authrix.ai
|
| 259 |
+
|
| 260 |
+
---
|
| 261 |
+
|
| 262 |
+
**Built with ❤️ by the Authrix Team**
|
DEPLOY_HF.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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`.
|
Dockerfile
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
# System dependencies
|
| 4 |
+
RUN apt-get update && apt-get install -y \
|
| 5 |
+
libgl1-mesa-glx \
|
| 6 |
+
libglib2.0-0 \
|
| 7 |
+
libsm6 \
|
| 8 |
+
libxext6 \
|
| 9 |
+
libxrender-dev \
|
| 10 |
+
libgomp1 \
|
| 11 |
+
ffmpeg \
|
| 12 |
+
git \
|
| 13 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 14 |
+
|
| 15 |
+
# HuggingFace Spaces runs as user 1000
|
| 16 |
+
RUN useradd -m -u 1000 user
|
| 17 |
+
USER user
|
| 18 |
+
ENV HOME=/home/user \
|
| 19 |
+
PATH=/home/user/.local/bin:$PATH
|
| 20 |
+
|
| 21 |
+
WORKDIR /home/user/app
|
| 22 |
+
|
| 23 |
+
# Copy files
|
| 24 |
+
COPY --chown=user backend/ ./backend/
|
| 25 |
+
COPY --chown=user frontend-vanilla/ ./frontend-vanilla/
|
| 26 |
+
|
| 27 |
+
# Install dependencies
|
| 28 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 29 |
+
pip install --no-cache-dir -r backend/requirements.txt
|
| 30 |
+
|
| 31 |
+
# Pre-cache HuggingFace models
|
| 32 |
+
RUN python -c "\
|
| 33 |
+
from transformers import ViTForImageClassification, ViTImageProcessor; \
|
| 34 |
+
ViTImageProcessor.from_pretrained('dima806/deepfake_vs_real_image_detection'); \
|
| 35 |
+
ViTForImageClassification.from_pretrained('dima806/deepfake_vs_real_image_detection'); \
|
| 36 |
+
ViTImageProcessor.from_pretrained('prithivMLmods/Deep-Fake-Detector-v2-Model'); \
|
| 37 |
+
ViTForImageClassification.from_pretrained('prithivMLmods/Deep-Fake-Detector-v2-Model'); \
|
| 38 |
+
print('Models cached')"
|
| 39 |
+
|
| 40 |
+
WORKDIR /home/user/app/backend
|
| 41 |
+
|
| 42 |
+
RUN mkdir -p uploads
|
| 43 |
+
|
| 44 |
+
# HuggingFace Spaces uses port 7860
|
| 45 |
+
EXPOSE 7860
|
| 46 |
+
|
| 47 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
|
@@ -1,126 +1,19 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
- **Prediction** — REAL or FAKE verdict
|
| 8 |
-
- **Confidence Score** — 0–100% probability
|
| 9 |
-
- **Explainable Insights** — Human-readable analysis details
|
| 10 |
-
- **Frame Timeline** — Per-frame fake probability visualization
|
| 11 |
-
- **Agent Architecture** — Modular pipeline of 4 specialized agents
|
| 12 |
-
|
| 13 |
-
## Agent Pipeline
|
| 14 |
-
|
| 15 |
-
```
|
| 16 |
-
Video Upload
|
| 17 |
-
│
|
| 18 |
-
▼
|
| 19 |
-
┌─────────────────────┐
|
| 20 |
-
│ Frame Analyzer │ ← Extracts every 10th frame via OpenCV
|
| 21 |
-
└─────────────────────┘
|
| 22 |
-
│
|
| 23 |
-
▼
|
| 24 |
-
┌─────────────────────┐
|
| 25 |
-
│ Face Detector │ ← Detects & crops faces via MediaPipe
|
| 26 |
-
└─────────────────────┘
|
| 27 |
-
│
|
| 28 |
-
▼
|
| 29 |
-
┌─────────────────────┐
|
| 30 |
-
│ Decision Agent │ ← HuggingFace model OR heuristic fallback
|
| 31 |
-
└─────────────────────┘
|
| 32 |
-
│
|
| 33 |
-
▼
|
| 34 |
-
┌─────────────────────┐
|
| 35 |
-
│ Report Generator │ ← Builds verdict + explanation
|
| 36 |
-
└─────────────────────┘
|
| 37 |
-
│
|
| 38 |
-
▼
|
| 39 |
-
JSON Response → Frontend
|
| 40 |
-
```
|
| 41 |
-
|
| 42 |
-
## Tech Stack
|
| 43 |
-
|
| 44 |
-
| Layer | Technology |
|
| 45 |
-
|----------|-------------------------------------------------|
|
| 46 |
-
| Backend | Python, FastAPI, OpenCV, MediaPipe |
|
| 47 |
-
| AI Model | HuggingFace `prithivMLmods/Deepfake-Detection-Model` (or heuristic fallback) |
|
| 48 |
-
| Frontend | HTML, TailwindCSS, Vanilla JS |
|
| 49 |
-
|
| 50 |
-
## Quick Start
|
| 51 |
-
|
| 52 |
-
### Windows
|
| 53 |
-
```bat
|
| 54 |
-
setup.bat
|
| 55 |
-
venv\Scripts\activate.bat
|
| 56 |
-
cd backend
|
| 57 |
-
python main.py
|
| 58 |
-
```
|
| 59 |
-
|
| 60 |
-
### macOS / Linux
|
| 61 |
-
```bash
|
| 62 |
-
chmod +x setup.sh && ./setup.sh
|
| 63 |
-
source venv/bin/activate
|
| 64 |
-
cd backend && python main.py
|
| 65 |
-
```
|
| 66 |
-
|
| 67 |
-
Then open **http://localhost:8000** in your browser.
|
| 68 |
-
|
| 69 |
-
## API
|
| 70 |
|
| 71 |
-
##
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
**Response:**
|
| 78 |
-
```json
|
| 79 |
-
{
|
| 80 |
-
"result": "FAKE",
|
| 81 |
-
"confidence": 78.4,
|
| 82 |
-
"details": [
|
| 83 |
-
"Significant facial manipulation artifacts identified",
|
| 84 |
-
"Inconsistent manipulation across frames — typical of face-swap deepfakes",
|
| 85 |
-
"Unnatural texture blending detected at facial boundaries",
|
| 86 |
-
"High-frequency noise patterns inconsistent with natural video compression"
|
| 87 |
-
],
|
| 88 |
-
"frame_timeline": [
|
| 89 |
-
{ "frame": 0, "fake_pct": 72.1 },
|
| 90 |
-
{ "frame": 1, "fake_pct": 81.3 }
|
| 91 |
-
],
|
| 92 |
-
"metadata": {
|
| 93 |
-
"frames_analyzed": 24,
|
| 94 |
-
"frames_with_faces": 20,
|
| 95 |
-
"video_duration_sec": 8.5,
|
| 96 |
-
"video_fps": 30.0,
|
| 97 |
-
"resolution": "1280x720"
|
| 98 |
-
},
|
| 99 |
-
"processing_time_sec": 4.2
|
| 100 |
-
}
|
| 101 |
-
```
|
| 102 |
-
|
| 103 |
-
### `GET /health`
|
| 104 |
-
|
| 105 |
-
Returns server status and active model type.
|
| 106 |
-
|
| 107 |
-
## Detection Logic
|
| 108 |
-
|
| 109 |
-
**Scoring:** Average fake probability across all detected faces and frames.
|
| 110 |
-
- `> 60%` → **FAKE**
|
| 111 |
-
- `≤ 60%` → **REAL**
|
| 112 |
-
|
| 113 |
-
**Heuristic fallback** (when HuggingFace model is unavailable) analyzes:
|
| 114 |
-
1. High-frequency noise patterns (Laplacian variance)
|
| 115 |
-
2. Color channel inconsistency
|
| 116 |
-
3. DCT frequency artifacts (GAN compression signatures)
|
| 117 |
-
4. Skin tone uniformity
|
| 118 |
-
5. Edge coherence anomalies
|
| 119 |
-
|
| 120 |
-
## Constraints
|
| 121 |
-
|
| 122 |
-
- Runs fully locally — no data sent to external servers
|
| 123 |
-
- Free/open-source models only
|
| 124 |
-
- Supports: MP4, AVI, MOV, MKV, WebM, WMV
|
| 125 |
-
- Max file size: 100 MB
|
| 126 |
-
- Target inference time: < 10 seconds for short videos
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Authrix Deepfake Detector
|
| 3 |
+
emoji: 🔍
|
| 4 |
+
colorFrom: green
|
| 5 |
+
colorTo: emerald
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
app_port: 7860
|
| 9 |
+
---
|
| 10 |
|
| 11 |
+
# Authrix — AI Deepfake Detection Engine
|
| 12 |
|
| 13 |
+
Upload a video and get an instant REAL / FAKE verdict powered by an ensemble of ViT models.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
+
## Features
|
| 16 |
+
- Visual deepfake detection (2x ViT ensemble)
|
| 17 |
+
- Audio analysis (AI voice detection)
|
| 18 |
+
- Browser extension support
|
| 19 |
+
- REST API
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
STRIPE_SETUP.md
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/auth.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Authrix - API Key Authentication & Usage Tracking
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import secrets
|
| 6 |
+
import hashlib
|
| 7 |
+
from datetime import datetime, timedelta
|
| 8 |
+
from typing import Optional
|
| 9 |
+
import json
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
# Simple file-based storage (replace with database in production)
|
| 13 |
+
API_KEYS_FILE = Path("api_keys.json")
|
| 14 |
+
USAGE_FILE = Path("usage.json")
|
| 15 |
+
|
| 16 |
+
# Tier limits (analyses per month)
|
| 17 |
+
TIER_LIMITS = {
|
| 18 |
+
"free": 10,
|
| 19 |
+
"pro": 100,
|
| 20 |
+
"business": 1000,
|
| 21 |
+
"enterprise": 999999,
|
| 22 |
+
"owner": 999999, # Owner has unlimited access
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
def generate_api_key() -> str:
|
| 26 |
+
"""Generate a secure API key."""
|
| 27 |
+
return f"authrix_{secrets.token_urlsafe(32)}"
|
| 28 |
+
|
| 29 |
+
def hash_key(api_key: str) -> str:
|
| 30 |
+
"""Hash API key for storage."""
|
| 31 |
+
return hashlib.sha256(api_key.encode()).hexdigest()
|
| 32 |
+
|
| 33 |
+
def create_api_key(email: str, tier: str = "free") -> str:
|
| 34 |
+
"""Create a new API key for a user."""
|
| 35 |
+
api_key = generate_api_key()
|
| 36 |
+
key_hash = hash_key(api_key)
|
| 37 |
+
|
| 38 |
+
keys = load_api_keys()
|
| 39 |
+
keys[key_hash] = {
|
| 40 |
+
"email": email,
|
| 41 |
+
"tier": tier,
|
| 42 |
+
"created_at": datetime.now().isoformat(),
|
| 43 |
+
"active": True,
|
| 44 |
+
}
|
| 45 |
+
save_api_keys(keys)
|
| 46 |
+
|
| 47 |
+
return api_key
|
| 48 |
+
|
| 49 |
+
def validate_api_key(api_key: str) -> Optional[dict]:
|
| 50 |
+
"""Validate API key and return user info."""
|
| 51 |
+
if not api_key:
|
| 52 |
+
return None
|
| 53 |
+
|
| 54 |
+
key_hash = hash_key(api_key)
|
| 55 |
+
keys = load_api_keys()
|
| 56 |
+
|
| 57 |
+
if key_hash not in keys:
|
| 58 |
+
return None
|
| 59 |
+
|
| 60 |
+
key_data = keys[key_hash]
|
| 61 |
+
if not key_data.get("active", False):
|
| 62 |
+
return None
|
| 63 |
+
|
| 64 |
+
return key_data
|
| 65 |
+
|
| 66 |
+
def check_usage_limit(api_key: str) -> tuple[bool, int, int]:
|
| 67 |
+
"""
|
| 68 |
+
Check if user has exceeded their monthly limit.
|
| 69 |
+
Returns (allowed, used, limit).
|
| 70 |
+
"""
|
| 71 |
+
key_data = validate_api_key(api_key)
|
| 72 |
+
if not key_data:
|
| 73 |
+
return False, 0, 0
|
| 74 |
+
|
| 75 |
+
tier = key_data.get("tier", "free")
|
| 76 |
+
|
| 77 |
+
# Owner has unlimited access
|
| 78 |
+
if tier == "owner" or key_data.get("unlimited", False):
|
| 79 |
+
return True, 0, 999999
|
| 80 |
+
|
| 81 |
+
limit = TIER_LIMITS.get(tier, 10)
|
| 82 |
+
|
| 83 |
+
# Get current month usage
|
| 84 |
+
key_hash = hash_key(api_key)
|
| 85 |
+
usage = load_usage()
|
| 86 |
+
current_month = datetime.now().strftime("%Y-%m")
|
| 87 |
+
|
| 88 |
+
if key_hash not in usage:
|
| 89 |
+
usage[key_hash] = {}
|
| 90 |
+
|
| 91 |
+
used = usage[key_hash].get(current_month, 0)
|
| 92 |
+
|
| 93 |
+
return used < limit, used, limit
|
| 94 |
+
|
| 95 |
+
def increment_usage(api_key: str):
|
| 96 |
+
"""Increment usage counter for the current month."""
|
| 97 |
+
key_hash = hash_key(api_key)
|
| 98 |
+
usage = load_usage()
|
| 99 |
+
current_month = datetime.now().strftime("%Y-%m")
|
| 100 |
+
|
| 101 |
+
if key_hash not in usage:
|
| 102 |
+
usage[key_hash] = {}
|
| 103 |
+
|
| 104 |
+
usage[key_hash][current_month] = usage[key_hash].get(current_month, 0) + 1
|
| 105 |
+
save_usage(usage)
|
| 106 |
+
|
| 107 |
+
def load_api_keys() -> dict:
|
| 108 |
+
"""Load API keys from file."""
|
| 109 |
+
if not API_KEYS_FILE.exists():
|
| 110 |
+
return {}
|
| 111 |
+
return json.loads(API_KEYS_FILE.read_text())
|
| 112 |
+
|
| 113 |
+
def save_api_keys(keys: dict):
|
| 114 |
+
"""Save API keys to file."""
|
| 115 |
+
API_KEYS_FILE.write_text(json.dumps(keys, indent=2))
|
| 116 |
+
|
| 117 |
+
def load_usage() -> dict:
|
| 118 |
+
"""Load usage data from file."""
|
| 119 |
+
if not USAGE_FILE.exists():
|
| 120 |
+
return {}
|
| 121 |
+
return json.loads(USAGE_FILE.read_text())
|
| 122 |
+
|
| 123 |
+
def save_usage(usage: dict):
|
| 124 |
+
"""Save usage data to file."""
|
| 125 |
+
USAGE_FILE.write_text(json.dumps(usage, indent=2))
|
| 126 |
+
|
| 127 |
+
# Create a demo API key on first run
|
| 128 |
+
if not API_KEYS_FILE.exists():
|
| 129 |
+
demo_key = create_api_key("demo@authrix.ai", "pro")
|
| 130 |
+
print(f"\n🔑 Demo API Key created: {demo_key}")
|
| 131 |
+
print(f" Tier: Pro (100 analyses/month)")
|
| 132 |
+
print(f" Add to requests: X-API-Key: {demo_key}\n")
|
backend/create_owner_key.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/detector.py
CHANGED
|
@@ -25,11 +25,10 @@ class FrameAnalyzerAgent:
|
|
| 25 |
"""
|
| 26 |
self.sample_rate = sample_rate
|
| 27 |
|
| 28 |
-
def extract_frames(self, video_path: str, max_frames: int =
|
| 29 |
"""
|
| 30 |
Extract sampled frames spread evenly across the full video duration.
|
| 31 |
-
|
| 32 |
-
coverage of the whole video regardless of length.
|
| 33 |
"""
|
| 34 |
frames = []
|
| 35 |
cap = cv2.VideoCapture(video_path)
|
|
@@ -86,7 +85,7 @@ class FrameAnalyzerAgent:
|
|
| 86 |
# Detects and crops faces using MediaPipe
|
| 87 |
# ─────────────────────────────────────────────
|
| 88 |
class FaceDetectorAgent:
|
| 89 |
-
def __init__(self, min_detection_confidence: float = 0.
|
| 90 |
self.mp_face_detection = mp.solutions.face_detection
|
| 91 |
self.min_confidence = min_detection_confidence
|
| 92 |
|
|
@@ -335,41 +334,47 @@ class DecisionAgent:
|
|
| 335 |
) -> dict:
|
| 336 |
"""
|
| 337 |
Aggregate predictions with adaptive scoring.
|
| 338 |
-
|
| 339 |
-
Key insight: deepfakes have CONSISTENTLY elevated scores across many
|
| 340 |
-
frames, while false positives on real videos tend to have a few
|
| 341 |
-
outlier frames with high scores but low overall consistency.
|
| 342 |
-
|
| 343 |
-
Strategy:
|
| 344 |
-
- Quality-gate blurry crops
|
| 345 |
-
- Per-frame: mean of valid face scores
|
| 346 |
-
- Final: weighted blend of mean + median (robust to outliers)
|
| 347 |
-
- Also return consistency metrics for adaptive thresholding
|
| 348 |
"""
|
| 349 |
frame_scores = []
|
| 350 |
frames_with_faces = 0
|
| 351 |
frames_skipped_quality = 0
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
|
|
|
| 360 |
if score is not None:
|
| 361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
|
|
|
|
|
|
| 366 |
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
|
| 371 |
if frames_skipped_quality > 0:
|
| 372 |
-
logger.info(f"Skipped {frames_skipped_quality} frames due to low
|
| 373 |
|
| 374 |
if not frame_scores:
|
| 375 |
return {
|
|
@@ -388,15 +393,11 @@ class DecisionAgent:
|
|
| 388 |
else:
|
| 389 |
mean_prob = float(np.mean(probs))
|
| 390 |
median_prob = float(np.median(probs))
|
| 391 |
-
# Mean+median blend: robust to both outliers and sparse fakes
|
| 392 |
overall = mean_prob * 0.65 + median_prob * 0.35
|
| 393 |
|
| 394 |
overall = round(float(np.clip(overall, 0.0, 1.0)), 4)
|
| 395 |
|
| 396 |
-
# Consistency: fraction of frames above 0.50 — high for real deepfakes
|
| 397 |
consistency = sum(1 for p in probs if p > 0.50) / len(probs)
|
| 398 |
-
|
| 399 |
-
# Face coverage: how much of the video had detectable faces
|
| 400 |
face_coverage = frames_with_faces / max(len(frames), 1)
|
| 401 |
|
| 402 |
logger.info(
|
|
@@ -422,8 +423,9 @@ class DecisionAgent:
|
|
| 422 |
# Builds the final human-readable report
|
| 423 |
# ─────────────────────────────────────────────
|
| 424 |
class ReportGeneratorAgent:
|
| 425 |
-
#
|
| 426 |
-
|
|
|
|
| 427 |
|
| 428 |
def generate(self, analysis: dict, metadata: dict, audio: dict | None = None) -> dict:
|
| 429 |
prob = analysis["overall_fake_probability"]
|
|
|
|
| 25 |
"""
|
| 26 |
self.sample_rate = sample_rate
|
| 27 |
|
| 28 |
+
def extract_frames(self, video_path: str, max_frames: int = 50) -> list[np.ndarray]:
|
| 29 |
"""
|
| 30 |
Extract sampled frames spread evenly across the full video duration.
|
| 31 |
+
Increased max_frames from 40 to 50 for better coverage of extension captures.
|
|
|
|
| 32 |
"""
|
| 33 |
frames = []
|
| 34 |
cap = cv2.VideoCapture(video_path)
|
|
|
|
| 85 |
# Detects and crops faces using MediaPipe
|
| 86 |
# ─────────────────────────────────────────────
|
| 87 |
class FaceDetectorAgent:
|
| 88 |
+
def __init__(self, min_detection_confidence: float = 0.3): # Lowered from 0.5 for compressed video
|
| 89 |
self.mp_face_detection = mp.solutions.face_detection
|
| 90 |
self.min_confidence = min_detection_confidence
|
| 91 |
|
|
|
|
| 334 |
) -> dict:
|
| 335 |
"""
|
| 336 |
Aggregate predictions with adaptive scoring.
|
| 337 |
+
If no faces detected, falls back to full-frame analysis.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 338 |
"""
|
| 339 |
frame_scores = []
|
| 340 |
frames_with_faces = 0
|
| 341 |
frames_skipped_quality = 0
|
| 342 |
+
total_faces_detected = sum(len(crops) for crops in face_crops_per_frame)
|
| 343 |
+
|
| 344 |
+
# Fallback: if very few faces detected, analyze full frames instead
|
| 345 |
+
if total_faces_detected < 5:
|
| 346 |
+
logger.warning(f"Only {total_faces_detected} faces detected — using full-frame analysis")
|
| 347 |
+
for i, frame in enumerate(frames):
|
| 348 |
+
# Resize frame to 224x224 for model input
|
| 349 |
+
frame_resized = cv2.resize(frame, (224, 224))
|
| 350 |
+
score = self.analyze_face(frame_resized)
|
| 351 |
if score is not None:
|
| 352 |
+
frames_with_faces += 1
|
| 353 |
+
frame_scores.append({"frame_index": i, "fake_probability": round(score, 4)})
|
| 354 |
+
else:
|
| 355 |
+
frames_skipped_quality += 1
|
| 356 |
+
else:
|
| 357 |
+
# Normal face-based analysis
|
| 358 |
+
for i, crops in enumerate(face_crops_per_frame):
|
| 359 |
+
if not crops:
|
| 360 |
+
continue
|
| 361 |
|
| 362 |
+
valid_probs = []
|
| 363 |
+
for crop in crops:
|
| 364 |
+
score = self.analyze_face(crop)
|
| 365 |
+
if score is not None:
|
| 366 |
+
valid_probs.append(score)
|
| 367 |
|
| 368 |
+
if not valid_probs:
|
| 369 |
+
frames_skipped_quality += 1
|
| 370 |
+
continue
|
| 371 |
+
|
| 372 |
+
frames_with_faces += 1
|
| 373 |
+
frame_score = float(np.mean(valid_probs))
|
| 374 |
+
frame_scores.append({"frame_index": i, "fake_probability": round(frame_score, 4)})
|
| 375 |
|
| 376 |
if frames_skipped_quality > 0:
|
| 377 |
+
logger.info(f"Skipped {frames_skipped_quality} frames due to low quality")
|
| 378 |
|
| 379 |
if not frame_scores:
|
| 380 |
return {
|
|
|
|
| 393 |
else:
|
| 394 |
mean_prob = float(np.mean(probs))
|
| 395 |
median_prob = float(np.median(probs))
|
|
|
|
| 396 |
overall = mean_prob * 0.65 + median_prob * 0.35
|
| 397 |
|
| 398 |
overall = round(float(np.clip(overall, 0.0, 1.0)), 4)
|
| 399 |
|
|
|
|
| 400 |
consistency = sum(1 for p in probs if p > 0.50) / len(probs)
|
|
|
|
|
|
|
| 401 |
face_coverage = frames_with_faces / max(len(frames), 1)
|
| 402 |
|
| 403 |
logger.info(
|
|
|
|
| 423 |
# Builds the final human-readable report
|
| 424 |
# ─────────────────────────────────────────────
|
| 425 |
class ReportGeneratorAgent:
|
| 426 |
+
# Lowered threshold for compressed video captures (extension use case)
|
| 427 |
+
# Original files: 0.58, Compressed captures: 0.54
|
| 428 |
+
BASE_THRESHOLD = 0.54
|
| 429 |
|
| 430 |
def generate(self, analysis: dict, metadata: dict, audio: dict | None = None) -> dict:
|
| 431 |
prob = analysis["overall_fake_probability"]
|
backend/main.py
CHANGED
|
@@ -6,22 +6,85 @@ import os
|
|
| 6 |
import uuid
|
| 7 |
import logging
|
| 8 |
import shutil
|
|
|
|
| 9 |
from pathlib import Path
|
| 10 |
|
| 11 |
-
from fastapi import FastAPI, File, UploadFile, HTTPException
|
| 12 |
from fastapi.middleware.cors import CORSMiddleware
|
| 13 |
from fastapi.staticfiles import StaticFiles
|
| 14 |
from fastapi.responses import FileResponse
|
|
|
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
# ── Logging ──────────────────────────────────
|
| 19 |
logging.basicConfig(
|
| 20 |
level=logging.INFO,
|
| 21 |
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
| 22 |
)
|
| 23 |
logger = logging.getLogger(__name__)
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
# ── App setup ────────────────────────────────
|
| 26 |
app = FastAPI(
|
| 27 |
title="Deepfake Authenticator API",
|
|
@@ -43,8 +106,9 @@ UPLOAD_DIR.mkdir(exist_ok=True)
|
|
| 43 |
ALLOWED_EXTENSIONS = {".mp4", ".avi", ".mov", ".mkv", ".webm", ".wmv"}
|
| 44 |
MAX_FILE_SIZE_MB = 100
|
| 45 |
|
| 46 |
-
# ── Singleton authenticator
|
| 47 |
-
authenticator
|
|
|
|
| 48 |
|
| 49 |
@app.on_event("startup")
|
| 50 |
async def startup_event():
|
|
@@ -85,18 +149,17 @@ async def analyze_from_url(payload: dict):
|
|
| 85 |
if not video_url:
|
| 86 |
raise HTTPException(status_code=400, detail="No URL provided")
|
| 87 |
|
| 88 |
-
# Use a unique prefix — yt-dlp appends its own extension
|
| 89 |
tmp_prefix = UPLOAD_DIR / f"ext_{uuid.uuid4().hex}"
|
| 90 |
actual_path = None
|
| 91 |
downloaded = False
|
| 92 |
|
| 93 |
try:
|
| 94 |
-
#
|
| 95 |
try:
|
| 96 |
import yt_dlp
|
| 97 |
ydl_opts = {
|
| 98 |
"format": "bestvideo[ext=mp4][height<=720]+bestaudio[ext=m4a]/best[ext=mp4][height<=720]/best",
|
| 99 |
-
"outtmpl": str(tmp_prefix) + ".%(ext)s",
|
| 100 |
"quiet": True,
|
| 101 |
"no_warnings": True,
|
| 102 |
"merge_output_format": "mp4",
|
|
@@ -104,7 +167,6 @@ async def analyze_from_url(payload: dict):
|
|
| 104 |
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 105 |
ydl.download([video_url])
|
| 106 |
|
| 107 |
-
# Find whatever file yt-dlp created
|
| 108 |
for ext in (".mp4", ".webm", ".mkv", ".avi", ".mov"):
|
| 109 |
candidate = Path(str(tmp_prefix) + ext)
|
| 110 |
if candidate.exists() and candidate.stat().st_size > 1000:
|
|
@@ -113,7 +175,6 @@ async def analyze_from_url(payload: dict):
|
|
| 113 |
logger.info(f"yt-dlp: {actual_path.name} ({actual_path.stat().st_size // 1024}KB)")
|
| 114 |
break
|
| 115 |
|
| 116 |
-
# Glob fallback in case yt-dlp used a different naming scheme
|
| 117 |
if not downloaded:
|
| 118 |
for f in sorted(UPLOAD_DIR.glob(f"{tmp_prefix.name}*")):
|
| 119 |
if f.stat().st_size > 1000:
|
|
@@ -127,7 +188,7 @@ async def analyze_from_url(payload: dict):
|
|
| 127 |
except Exception as e:
|
| 128 |
logger.warning(f"yt-dlp failed ({e}) — trying direct fetch")
|
| 129 |
|
| 130 |
-
#
|
| 131 |
if not downloaded:
|
| 132 |
try:
|
| 133 |
import httpx
|
|
@@ -144,11 +205,14 @@ async def analyze_from_url(payload: dict):
|
|
| 144 |
if not downloaded or actual_path is None:
|
| 145 |
raise HTTPException(
|
| 146 |
status_code=400,
|
| 147 |
-
detail="Could not download video. For YouTube, ensure yt-dlp is installed: pip install yt-dlp"
|
| 148 |
)
|
| 149 |
|
| 150 |
-
#
|
| 151 |
-
|
|
|
|
|
|
|
|
|
|
| 152 |
return result
|
| 153 |
|
| 154 |
except HTTPException:
|
|
@@ -157,7 +221,6 @@ async def analyze_from_url(payload: dict):
|
|
| 157 |
logger.exception(f"analyze-url failed: {e}")
|
| 158 |
raise HTTPException(status_code=500, detail=str(e))
|
| 159 |
finally:
|
| 160 |
-
# Clean up all files with our prefix
|
| 161 |
for f in UPLOAD_DIR.glob(f"{tmp_prefix.name}*"):
|
| 162 |
try:
|
| 163 |
f.unlink()
|
|
@@ -166,21 +229,34 @@ async def analyze_from_url(payload: dict):
|
|
| 166 |
|
| 167 |
|
| 168 |
@app.post("/analyze")
|
| 169 |
-
async def analyze_video(
|
|
|
|
|
|
|
|
|
|
| 170 |
"""
|
| 171 |
Analyze an uploaded video for deepfake content.
|
| 172 |
-
|
| 173 |
-
Returns:
|
| 174 |
-
result: "REAL" or "FAKE"
|
| 175 |
-
confidence: 0–100 percentage
|
| 176 |
-
details: list of human-readable explanations
|
| 177 |
-
frame_timeline: per-frame fake probability for visualization
|
| 178 |
-
metadata: video info and processing stats
|
| 179 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
if not authenticator:
|
| 181 |
raise HTTPException(status_code=503, detail="Server is still initializing, please retry.")
|
| 182 |
|
| 183 |
-
# Validate file extension
|
| 184 |
suffix = Path(file.filename).suffix.lower()
|
| 185 |
if suffix not in ALLOWED_EXTENSIONS:
|
| 186 |
raise HTTPException(
|
|
@@ -188,49 +264,68 @@ async def analyze_video(file: UploadFile = File(...)):
|
|
| 188 |
detail=f"Unsupported file type '{suffix}'. Allowed: {', '.join(ALLOWED_EXTENSIONS)}",
|
| 189 |
)
|
| 190 |
|
| 191 |
-
# Save uploaded file with unique name
|
| 192 |
unique_name = f"{uuid.uuid4().hex}{suffix}"
|
| 193 |
save_path = UPLOAD_DIR / unique_name
|
|
|
|
| 194 |
|
| 195 |
try:
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
status_code=413,
|
| 204 |
-
detail=f"File too large ({size_mb:.1f} MB). Max allowed: {MAX_FILE_SIZE_MB} MB",
|
| 205 |
-
)
|
| 206 |
-
|
| 207 |
-
f.write(content)
|
| 208 |
|
|
|
|
| 209 |
logger.info(f"Saved upload: {unique_name} ({size_mb:.1f} MB)")
|
| 210 |
|
| 211 |
-
#
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
return result
|
| 214 |
|
| 215 |
except HTTPException:
|
| 216 |
raise
|
| 217 |
except Exception as e:
|
| 218 |
logger.exception(f"Analysis failed for {unique_name}: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
|
| 220 |
finally:
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
|
| 227 |
# ── Serve frontend ────────────────────────────
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
_vanilla_dir = Path(__file__).parent.parent / "frontend-vanilla"
|
| 231 |
|
| 232 |
if _react_dist.exists():
|
| 233 |
-
# Serve React SPA
|
| 234 |
app.mount("/assets", StaticFiles(directory=str(_react_dist / "assets")), name="assets")
|
| 235 |
|
| 236 |
@app.get("/")
|
|
@@ -240,7 +335,6 @@ if _react_dist.exists():
|
|
| 240 |
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 241 |
)
|
| 242 |
|
| 243 |
-
# Catch-all for React Router (SPA fallback)
|
| 244 |
@app.get("/{full_path:path}")
|
| 245 |
async def spa_fallback(full_path: str):
|
| 246 |
index = _react_dist / "index.html"
|
|
@@ -249,7 +343,6 @@ if _react_dist.exists():
|
|
| 249 |
return {"detail": "Not found"}
|
| 250 |
|
| 251 |
elif _vanilla_dir.exists():
|
| 252 |
-
# Fallback: vanilla HTML
|
| 253 |
@app.get("/script.js")
|
| 254 |
async def serve_script():
|
| 255 |
return FileResponse(
|
|
@@ -258,6 +351,13 @@ elif _vanilla_dir.exists():
|
|
| 258 |
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 259 |
)
|
| 260 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
@app.get("/")
|
| 262 |
async def serve_index():
|
| 263 |
return FileResponse(
|
|
|
|
| 6 |
import uuid
|
| 7 |
import logging
|
| 8 |
import shutil
|
| 9 |
+
import subprocess
|
| 10 |
from pathlib import Path
|
| 11 |
|
| 12 |
+
from fastapi import FastAPI, File, UploadFile, HTTPException, Header
|
| 13 |
from fastapi.middleware.cors import CORSMiddleware
|
| 14 |
from fastapi.staticfiles import StaticFiles
|
| 15 |
from fastapi.responses import FileResponse
|
| 16 |
+
from typing import Optional
|
| 17 |
|
| 18 |
+
# ── Logging (must be set up before any logger usage) ─────────────────────────
|
|
|
|
|
|
|
| 19 |
logging.basicConfig(
|
| 20 |
level=logging.INFO,
|
| 21 |
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
| 22 |
)
|
| 23 |
logger = logging.getLogger(__name__)
|
| 24 |
|
| 25 |
+
from detector import DeepfakeAuthenticator
|
| 26 |
+
from auth import validate_api_key, check_usage_limit, increment_usage
|
| 27 |
+
|
| 28 |
+
# ── Video conversion helper ───────────────────────────────────────────────────
|
| 29 |
+
def convert_to_mp4(src: Path):
|
| 30 |
+
"""
|
| 31 |
+
Convert a video file to mp4 using the bundled ffmpeg binary.
|
| 32 |
+
Returns the converted Path, or None if conversion failed.
|
| 33 |
+
"""
|
| 34 |
+
if src.suffix.lower() == ".mp4":
|
| 35 |
+
return None # already mp4
|
| 36 |
+
|
| 37 |
+
dst = src.with_suffix(".mp4")
|
| 38 |
+
|
| 39 |
+
# Use imageio-ffmpeg bundled binary (always available, no system install needed)
|
| 40 |
+
ffmpeg_bin = "ffmpeg"
|
| 41 |
+
try:
|
| 42 |
+
import imageio_ffmpeg
|
| 43 |
+
ffmpeg_bin = imageio_ffmpeg.get_ffmpeg_exe()
|
| 44 |
+
logger.info(f"Using bundled ffmpeg: {ffmpeg_bin}")
|
| 45 |
+
except Exception as e:
|
| 46 |
+
logger.warning(f"imageio_ffmpeg not available, trying system ffmpeg: {e}")
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
result = subprocess.run(
|
| 50 |
+
[
|
| 51 |
+
ffmpeg_bin, "-y", "-i", str(src),
|
| 52 |
+
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "28",
|
| 53 |
+
"-c:a", "aac", "-movflags", "+faststart",
|
| 54 |
+
str(dst),
|
| 55 |
+
],
|
| 56 |
+
capture_output=True,
|
| 57 |
+
timeout=120,
|
| 58 |
+
)
|
| 59 |
+
if result.returncode == 0 and dst.exists() and dst.stat().st_size > 1000:
|
| 60 |
+
logger.info(f"Converted {src.name} -> {dst.name} ({dst.stat().st_size // 1024} KB)")
|
| 61 |
+
return dst
|
| 62 |
+
stderr = result.stderr.decode(errors="ignore")[-500:]
|
| 63 |
+
logger.warning(f"ffmpeg exit {result.returncode}: {stderr}")
|
| 64 |
+
except Exception as e:
|
| 65 |
+
logger.warning(f"ffmpeg conversion failed: {e}")
|
| 66 |
+
|
| 67 |
+
# Fallback: moviepy
|
| 68 |
+
try:
|
| 69 |
+
try:
|
| 70 |
+
from moviepy import VideoFileClip
|
| 71 |
+
except ImportError:
|
| 72 |
+
from moviepy.editor import VideoFileClip
|
| 73 |
+
clip = VideoFileClip(str(src))
|
| 74 |
+
clip.write_videofile(
|
| 75 |
+
str(dst), codec="libx264", audio_codec="aac",
|
| 76 |
+
logger=None, preset="ultrafast",
|
| 77 |
+
)
|
| 78 |
+
clip.close()
|
| 79 |
+
if dst.exists() and dst.stat().st_size > 1000:
|
| 80 |
+
logger.info(f"moviepy converted {src.name} -> {dst.name}")
|
| 81 |
+
return dst
|
| 82 |
+
except Exception as e:
|
| 83 |
+
logger.warning(f"moviepy conversion also failed: {e}")
|
| 84 |
+
|
| 85 |
+
return None
|
| 86 |
+
|
| 87 |
+
|
| 88 |
# ── App setup ────────────────────────────────
|
| 89 |
app = FastAPI(
|
| 90 |
title="Deepfake Authenticator API",
|
|
|
|
| 106 |
ALLOWED_EXTENSIONS = {".mp4", ".avi", ".mov", ".mkv", ".webm", ".wmv"}
|
| 107 |
MAX_FILE_SIZE_MB = 100
|
| 108 |
|
| 109 |
+
# ── Singleton authenticator ───────────────────
|
| 110 |
+
authenticator = None
|
| 111 |
+
|
| 112 |
|
| 113 |
@app.on_event("startup")
|
| 114 |
async def startup_event():
|
|
|
|
| 149 |
if not video_url:
|
| 150 |
raise HTTPException(status_code=400, detail="No URL provided")
|
| 151 |
|
|
|
|
| 152 |
tmp_prefix = UPLOAD_DIR / f"ext_{uuid.uuid4().hex}"
|
| 153 |
actual_path = None
|
| 154 |
downloaded = False
|
| 155 |
|
| 156 |
try:
|
| 157 |
+
# yt-dlp: handles YouTube, Twitter, Instagram, TikTok
|
| 158 |
try:
|
| 159 |
import yt_dlp
|
| 160 |
ydl_opts = {
|
| 161 |
"format": "bestvideo[ext=mp4][height<=720]+bestaudio[ext=m4a]/best[ext=mp4][height<=720]/best",
|
| 162 |
+
"outtmpl": str(tmp_prefix) + ".%(ext)s",
|
| 163 |
"quiet": True,
|
| 164 |
"no_warnings": True,
|
| 165 |
"merge_output_format": "mp4",
|
|
|
|
| 167 |
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 168 |
ydl.download([video_url])
|
| 169 |
|
|
|
|
| 170 |
for ext in (".mp4", ".webm", ".mkv", ".avi", ".mov"):
|
| 171 |
candidate = Path(str(tmp_prefix) + ext)
|
| 172 |
if candidate.exists() and candidate.stat().st_size > 1000:
|
|
|
|
| 175 |
logger.info(f"yt-dlp: {actual_path.name} ({actual_path.stat().st_size // 1024}KB)")
|
| 176 |
break
|
| 177 |
|
|
|
|
| 178 |
if not downloaded:
|
| 179 |
for f in sorted(UPLOAD_DIR.glob(f"{tmp_prefix.name}*")):
|
| 180 |
if f.stat().st_size > 1000:
|
|
|
|
| 188 |
except Exception as e:
|
| 189 |
logger.warning(f"yt-dlp failed ({e}) — trying direct fetch")
|
| 190 |
|
| 191 |
+
# Fallback: direct HTTP fetch
|
| 192 |
if not downloaded:
|
| 193 |
try:
|
| 194 |
import httpx
|
|
|
|
| 205 |
if not downloaded or actual_path is None:
|
| 206 |
raise HTTPException(
|
| 207 |
status_code=400,
|
| 208 |
+
detail="Could not download video. For YouTube, ensure yt-dlp is installed: pip install yt-dlp",
|
| 209 |
)
|
| 210 |
|
| 211 |
+
# Convert if needed
|
| 212 |
+
converted = convert_to_mp4(actual_path)
|
| 213 |
+
analyze_path = converted if converted else actual_path
|
| 214 |
+
|
| 215 |
+
result = authenticator.analyze(str(analyze_path))
|
| 216 |
return result
|
| 217 |
|
| 218 |
except HTTPException:
|
|
|
|
| 221 |
logger.exception(f"analyze-url failed: {e}")
|
| 222 |
raise HTTPException(status_code=500, detail=str(e))
|
| 223 |
finally:
|
|
|
|
| 224 |
for f in UPLOAD_DIR.glob(f"{tmp_prefix.name}*"):
|
| 225 |
try:
|
| 226 |
f.unlink()
|
|
|
|
| 229 |
|
| 230 |
|
| 231 |
@app.post("/analyze")
|
| 232 |
+
async def analyze_video(
|
| 233 |
+
file: UploadFile = File(...),
|
| 234 |
+
x_api_key: Optional[str] = Header(None, alias="X-API-Key")
|
| 235 |
+
):
|
| 236 |
"""
|
| 237 |
Analyze an uploaded video for deepfake content.
|
| 238 |
+
Requires API key for usage tracking and tier limits.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
"""
|
| 240 |
+
# Check API key (allow localhost without key for development)
|
| 241 |
+
if x_api_key:
|
| 242 |
+
key_data = validate_api_key(x_api_key)
|
| 243 |
+
if not key_data:
|
| 244 |
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
| 245 |
+
|
| 246 |
+
allowed, used, limit = check_usage_limit(x_api_key)
|
| 247 |
+
if not allowed:
|
| 248 |
+
raise HTTPException(
|
| 249 |
+
status_code=429,
|
| 250 |
+
detail=f"Monthly limit exceeded ({used}/{limit}). Upgrade your plan at https://authrix.ai/pricing"
|
| 251 |
+
)
|
| 252 |
+
|
| 253 |
+
logger.info(f"API request from {key_data['email']} ({key_data['tier']}) - {used+1}/{limit}")
|
| 254 |
+
else:
|
| 255 |
+
logger.info("Local request (no API key)")
|
| 256 |
+
|
| 257 |
if not authenticator:
|
| 258 |
raise HTTPException(status_code=503, detail="Server is still initializing, please retry.")
|
| 259 |
|
|
|
|
| 260 |
suffix = Path(file.filename).suffix.lower()
|
| 261 |
if suffix not in ALLOWED_EXTENSIONS:
|
| 262 |
raise HTTPException(
|
|
|
|
| 264 |
detail=f"Unsupported file type '{suffix}'. Allowed: {', '.join(ALLOWED_EXTENSIONS)}",
|
| 265 |
)
|
| 266 |
|
|
|
|
| 267 |
unique_name = f"{uuid.uuid4().hex}{suffix}"
|
| 268 |
save_path = UPLOAD_DIR / unique_name
|
| 269 |
+
converted_path = None
|
| 270 |
|
| 271 |
try:
|
| 272 |
+
content = await file.read()
|
| 273 |
+
size_mb = len(content) / (1024 * 1024)
|
| 274 |
+
if size_mb > MAX_FILE_SIZE_MB:
|
| 275 |
+
raise HTTPException(
|
| 276 |
+
status_code=413,
|
| 277 |
+
detail=f"File too large ({size_mb:.1f} MB). Max allowed: {MAX_FILE_SIZE_MB} MB",
|
| 278 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
|
| 280 |
+
save_path.write_bytes(content)
|
| 281 |
logger.info(f"Saved upload: {unique_name} ({size_mb:.1f} MB)")
|
| 282 |
|
| 283 |
+
# Convert webm/mkv/etc to mp4 — OpenCV on Windows cannot decode webm natively
|
| 284 |
+
analyze_path = save_path
|
| 285 |
+
if suffix in (".webm", ".mkv", ".avi", ".wmv"):
|
| 286 |
+
logger.info(f"File has {suffix} extension — conversion needed")
|
| 287 |
+
converted_path = convert_to_mp4(save_path)
|
| 288 |
+
if converted_path:
|
| 289 |
+
analyze_path = converted_path
|
| 290 |
+
logger.info(f"✓ Conversion successful — using {analyze_path.name}")
|
| 291 |
+
else:
|
| 292 |
+
logger.error(f"✗ Conversion FAILED for {suffix} — will attempt direct analysis (likely to fail)")
|
| 293 |
+
else:
|
| 294 |
+
logger.info(f"File is {suffix} — no conversion needed")
|
| 295 |
+
|
| 296 |
+
logger.info(f"Calling authenticator.analyze({analyze_path})")
|
| 297 |
+
result = authenticator.analyze(str(analyze_path))
|
| 298 |
+
|
| 299 |
+
# Increment usage counter if API key provided
|
| 300 |
+
if x_api_key:
|
| 301 |
+
increment_usage(x_api_key)
|
| 302 |
+
|
| 303 |
return result
|
| 304 |
|
| 305 |
except HTTPException:
|
| 306 |
raise
|
| 307 |
except Exception as e:
|
| 308 |
logger.exception(f"Analysis failed for {unique_name}: {e}")
|
| 309 |
+
# Write detailed error to file for debugging
|
| 310 |
+
error_log = UPLOAD_DIR / "last_error.txt"
|
| 311 |
+
import traceback
|
| 312 |
+
error_log.write_text(f"File: {unique_name}\nError: {e}\n\n{traceback.format_exc()}")
|
| 313 |
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
|
| 314 |
finally:
|
| 315 |
+
for p in [save_path, converted_path]:
|
| 316 |
+
if p is not None and p.exists():
|
| 317 |
+
try:
|
| 318 |
+
p.unlink()
|
| 319 |
+
logger.info(f"Cleaned up: {p.name}")
|
| 320 |
+
except Exception:
|
| 321 |
+
pass
|
| 322 |
|
| 323 |
|
| 324 |
# ── Serve frontend ────────────────────────────
|
| 325 |
+
_react_dist = Path(__file__).parent.parent / "frontend-dist"
|
| 326 |
+
_vanilla_dir = Path(__file__).parent.parent / "frontend-vanilla"
|
|
|
|
| 327 |
|
| 328 |
if _react_dist.exists():
|
|
|
|
| 329 |
app.mount("/assets", StaticFiles(directory=str(_react_dist / "assets")), name="assets")
|
| 330 |
|
| 331 |
@app.get("/")
|
|
|
|
| 335 |
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 336 |
)
|
| 337 |
|
|
|
|
| 338 |
@app.get("/{full_path:path}")
|
| 339 |
async def spa_fallback(full_path: str):
|
| 340 |
index = _react_dist / "index.html"
|
|
|
|
| 343 |
return {"detail": "Not found"}
|
| 344 |
|
| 345 |
elif _vanilla_dir.exists():
|
|
|
|
| 346 |
@app.get("/script.js")
|
| 347 |
async def serve_script():
|
| 348 |
return FileResponse(
|
|
|
|
| 351 |
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 352 |
)
|
| 353 |
|
| 354 |
+
@app.get("/pricing")
|
| 355 |
+
async def serve_pricing():
|
| 356 |
+
return FileResponse(
|
| 357 |
+
str(_vanilla_dir / "pricing.html"),
|
| 358 |
+
headers={"Cache-Control": "no-store, no-cache, must-revalidate"},
|
| 359 |
+
)
|
| 360 |
+
|
| 361 |
@app.get("/")
|
| 362 |
async def serve_index():
|
| 363 |
return FileResponse(
|
backend/requirements.txt
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
fastapi==0.111.0
|
| 2 |
uvicorn[standard]==0.29.0
|
| 3 |
python-multipart==0.0.9
|
| 4 |
-
opencv-python==4.9.0.80
|
| 5 |
mediapipe==0.10.14
|
| 6 |
numpy==1.26.4
|
| 7 |
Pillow==10.3.0
|
| 8 |
|
| 9 |
-
# HuggingFace models
|
| 10 |
transformers>=4.41.0
|
| 11 |
torch>=2.3.0
|
| 12 |
torchvision
|
|
@@ -17,6 +17,8 @@ moviepy>=1.0.3
|
|
| 17 |
librosa>=0.10.0
|
| 18 |
soundfile>=0.12.1
|
| 19 |
|
| 20 |
-
#
|
|
|
|
| 21 |
yt-dlp>=2024.1.0
|
| 22 |
httpx>=0.27.0
|
|
|
|
|
|
| 1 |
fastapi==0.111.0
|
| 2 |
uvicorn[standard]==0.29.0
|
| 3 |
python-multipart==0.0.9
|
| 4 |
+
opencv-python-headless==4.9.0.80
|
| 5 |
mediapipe==0.10.14
|
| 6 |
numpy==1.26.4
|
| 7 |
Pillow==10.3.0
|
| 8 |
|
| 9 |
+
# HuggingFace models (CPU only for deployment)
|
| 10 |
transformers>=4.41.0
|
| 11 |
torch>=2.3.0
|
| 12 |
torchvision
|
|
|
|
| 17 |
librosa>=0.10.0
|
| 18 |
soundfile>=0.12.1
|
| 19 |
|
| 20 |
+
# Deployment extras
|
| 21 |
+
imageio-ffmpeg>=0.4.9
|
| 22 |
yt-dlp>=2024.1.0
|
| 23 |
httpx>=0.27.0
|
| 24 |
+
stripe>=8.0.0
|
backend/stripe_integration.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
extension/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
# Authrix Browser Extension
|
| 2 |
|
| 3 |
-
Detect deepfake videos on any webpage
|
| 4 |
|
| 5 |
## Install (Chrome / Edge / Brave)
|
| 6 |
|
| 7 |
1. Open `chrome://extensions`
|
| 8 |
-
2. Enable **Developer Mode** (top
|
| 9 |
3. Click **Load unpacked**
|
| 10 |
4. Select this `extension/` folder
|
| 11 |
5. The Authrix icon appears in your toolbar
|
|
@@ -13,6 +13,7 @@ Detect deepfake videos on any webpage without downloading them.
|
|
| 13 |
## Requirements
|
| 14 |
|
| 15 |
The Authrix backend must be running locally:
|
|
|
|
| 16 |
```bash
|
| 17 |
cd backend
|
| 18 |
python -m uvicorn main:app --host 0.0.0.0 --port 8000
|
|
@@ -20,28 +21,47 @@ python -m uvicorn main:app --host 0.0.0.0 --port 8000
|
|
| 20 |
|
| 21 |
## Usage
|
| 22 |
|
| 23 |
-
### Method 1 —
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
|
| 27 |
-
Click the Authrix icon in the toolbar:
|
| 28 |
-
- Auto-detects videos on the current page
|
| 29 |
-
- Paste any direct video URL manually
|
| 30 |
-
- Shows last analysis result
|
| 31 |
|
| 32 |
-
### Method 3 — Right-click
|
| 33 |
-
Right-click
|
|
|
|
| 34 |
|
| 35 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
## Limitations
|
| 43 |
|
| 44 |
-
- **DRM-protected
|
| 45 |
-
- **YouTube**
|
| 46 |
- Backend must be running on `localhost:8000`
|
| 47 |
-
-
|
|
|
|
| 1 |
# Authrix Browser Extension
|
| 2 |
|
| 3 |
+
Detect deepfake videos on any webpage — no download required.
|
| 4 |
|
| 5 |
## Install (Chrome / Edge / Brave)
|
| 6 |
|
| 7 |
1. Open `chrome://extensions`
|
| 8 |
+
2. Enable **Developer Mode** (top-right toggle)
|
| 9 |
3. Click **Load unpacked**
|
| 10 |
4. Select this `extension/` folder
|
| 11 |
5. The Authrix icon appears in your toolbar
|
|
|
|
| 13 |
## Requirements
|
| 14 |
|
| 15 |
The Authrix backend must be running locally:
|
| 16 |
+
|
| 17 |
```bash
|
| 18 |
cd backend
|
| 19 |
python -m uvicorn main:app --host 0.0.0.0 --port 8000
|
|
|
|
| 21 |
|
| 22 |
## Usage
|
| 23 |
|
| 24 |
+
### Method 1 — Toolbar popup (recommended)
|
| 25 |
+
Click the Authrix icon → **Capture & Analyze Video**
|
| 26 |
+
|
| 27 |
+
Records 12 seconds of the currently playing video, sends it to the local AI, and shows the verdict as an overlay on the page.
|
| 28 |
+
|
| 29 |
+
### Method 2 — Paste a URL
|
| 30 |
+
Click the Authrix icon → paste a direct video URL → **Analyze**
|
| 31 |
|
| 32 |
+
Works with direct `.mp4` / `.webm` links. For YouTube, Twitter, TikTok etc., `yt-dlp` must be installed on the backend (`pip install yt-dlp`).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
### Method 3 — Right-click menu
|
| 35 |
+
- Right-click anywhere on a page → **🔍 Analyze with Authrix** (captures tab stream)
|
| 36 |
+
- Right-click a video link → **🔗 Analyze video URL with Authrix**
|
| 37 |
|
| 38 |
+
## Architecture (MV3)
|
| 39 |
+
|
| 40 |
+
```
|
| 41 |
+
popup.js
|
| 42 |
+
└─ sends START_CAPTURE / ANALYZE_URL → background.js
|
| 43 |
|
| 44 |
+
background.js
|
| 45 |
+
├─ tabCapture.getMediaStreamId() (stream ID for the tab)
|
| 46 |
+
├─ creates offscreen document
|
| 47 |
+
└─ sends RECORD_OFFSCREEN → offscreen.js
|
| 48 |
+
|
| 49 |
+
offscreen.js
|
| 50 |
+
├─ getUserMedia({ chromeMediaSource: 'tab' })
|
| 51 |
+
├─ MediaRecorder records for 12s
|
| 52 |
+
└─ sends BLOB_READY → background.js
|
| 53 |
+
|
| 54 |
+
background.js
|
| 55 |
+
├─ POST /analyze (blob) or POST /analyze-url (URL)
|
| 56 |
+
└─ sends ANALYSIS_RESULT → content.js
|
| 57 |
+
|
| 58 |
+
content.js
|
| 59 |
+
└─ renders overlay with verdict, confidence, details
|
| 60 |
+
```
|
| 61 |
|
| 62 |
## Limitations
|
| 63 |
|
| 64 |
+
- **DRM-protected content** (Netflix, Disney+, Prime) cannot be captured
|
| 65 |
+
- **YouTube** tab capture works; URL analysis requires `yt-dlp` on the backend
|
| 66 |
- Backend must be running on `localhost:8000`
|
| 67 |
+
- Videos > 100 MB may time out
|
extension/background.js
CHANGED
|
@@ -1,40 +1,60 @@
|
|
| 1 |
/**
|
| 2 |
-
* Authrix Extension — Background Service Worker
|
| 3 |
*
|
| 4 |
-
*
|
| 5 |
-
*
|
| 6 |
-
*
|
|
|
|
|
|
|
|
|
|
| 7 |
*/
|
| 8 |
|
| 9 |
-
const API_BASE = '
|
| 10 |
-
const CAPTURE_SEC =
|
|
|
|
| 11 |
|
| 12 |
// ── Context menu ──────────────────────────────────────────────────────────────
|
| 13 |
chrome.runtime.onInstalled.addListener(() => {
|
| 14 |
chrome.contextMenus.create({
|
| 15 |
id: 'authrix-capture',
|
| 16 |
-
title: '🔍 Analyze with Authrix
|
| 17 |
contexts: ['page', 'video', 'frame'],
|
| 18 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
});
|
| 20 |
|
| 21 |
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
| 22 |
if (info.menuItemId === 'authrix-capture' && tab?.id) {
|
| 23 |
startCapture(tab.id);
|
| 24 |
}
|
|
|
|
|
|
|
|
|
|
| 25 |
});
|
| 26 |
|
| 27 |
// ── Message handler ───────────────────────────────────────────────────────────
|
| 28 |
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
| 29 |
|
| 30 |
if (msg.type === 'START_CAPTURE') {
|
| 31 |
-
const tabId =
|
| 32 |
startCapture(tabId)
|
| 33 |
.then(() => sendResponse({ ok: true }))
|
| 34 |
.catch(e => sendResponse({ ok: false, error: e.message }));
|
| 35 |
return true;
|
| 36 |
}
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
if (msg.type === 'CHECK_HEALTH') {
|
| 39 |
fetch(`${API_BASE}/health`)
|
| 40 |
.then(r => r.json())
|
|
@@ -43,29 +63,32 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
| 43 |
return true;
|
| 44 |
}
|
| 45 |
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
sendResponse({ ok: false, error: e.message });
|
| 62 |
-
});
|
| 63 |
-
return true;
|
| 64 |
}
|
| 65 |
|
| 66 |
-
if (msg.type === '
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
}
|
| 70 |
});
|
| 71 |
|
|
@@ -73,37 +96,112 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
| 73 |
async function startCapture(tabId) {
|
| 74 |
if (!tabId) throw new Error('No tab ID');
|
| 75 |
|
| 76 |
-
//
|
| 77 |
-
await chrome.tabs.sendMessage(tabId, { type: 'SHOW_CAPTURE_OVERLAY' })
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
-
// Get
|
| 80 |
-
// tabCapture.getMediaStreamId gives us a stream ID usable in content scripts
|
| 81 |
const streamId = await new Promise((resolve, reject) => {
|
| 82 |
-
chrome.tabCapture.getMediaStreamId({ targetTabId: tabId },
|
| 83 |
-
if (chrome.runtime.lastError)
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
| 85 |
});
|
| 86 |
});
|
| 87 |
|
| 88 |
-
//
|
| 89 |
-
await
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
| 92 |
durationMs: CAPTURE_SEC * 1000,
|
|
|
|
| 93 |
});
|
| 94 |
}
|
| 95 |
|
| 96 |
-
// ──
|
| 97 |
-
async function
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
const filename = `capture_${Date.now()}.webm`;
|
| 108 |
|
| 109 |
const fd = new FormData();
|
|
@@ -116,3 +214,22 @@ async function receiveAndAnalyze(base64Chunks, mimeType, tabId) {
|
|
| 116 |
}
|
| 117 |
return res.json();
|
| 118 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
/**
|
| 2 |
+
* Authrix Extension — Background Service Worker v3
|
| 3 |
*
|
| 4 |
+
* Architecture (MV3-compliant):
|
| 5 |
+
* 1. tabCapture.getMediaStreamId() → get stream ID for the target tab
|
| 6 |
+
* 2. Create offscreen document → only place getUserMedia(tab) works in MV3
|
| 7 |
+
* 3. offscreen.js records the stream and sends BLOB_READY back here
|
| 8 |
+
* 4. We POST the blob to the local FastAPI backend
|
| 9 |
+
* 5. Result is sent to the content script overlay on the original tab
|
| 10 |
*/
|
| 11 |
|
| 12 |
+
const API_BASE = 'https://aarav13-authrix.hf.space';
|
| 13 |
+
const CAPTURE_SEC = 20; // Increased from 12s for more sample frames
|
| 14 |
+
const OFFSCREEN_URL = chrome.runtime.getURL('offscreen.html');
|
| 15 |
|
| 16 |
// ── Context menu ──────────────────────────────────────────────────────────────
|
| 17 |
chrome.runtime.onInstalled.addListener(() => {
|
| 18 |
chrome.contextMenus.create({
|
| 19 |
id: 'authrix-capture',
|
| 20 |
+
title: '🔍 Analyze with Authrix',
|
| 21 |
contexts: ['page', 'video', 'frame'],
|
| 22 |
});
|
| 23 |
+
chrome.contextMenus.create({
|
| 24 |
+
id: 'authrix-url',
|
| 25 |
+
title: '🔗 Analyze video URL with Authrix',
|
| 26 |
+
contexts: ['link'],
|
| 27 |
+
});
|
| 28 |
});
|
| 29 |
|
| 30 |
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
| 31 |
if (info.menuItemId === 'authrix-capture' && tab?.id) {
|
| 32 |
startCapture(tab.id);
|
| 33 |
}
|
| 34 |
+
if (info.menuItemId === 'authrix-url' && tab?.id && info.linkUrl) {
|
| 35 |
+
analyzeUrl(info.linkUrl, tab.id);
|
| 36 |
+
}
|
| 37 |
});
|
| 38 |
|
| 39 |
// ── Message handler ───────────────────────────────────────────────────────────
|
| 40 |
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
| 41 |
|
| 42 |
if (msg.type === 'START_CAPTURE') {
|
| 43 |
+
const tabId = msg.tabId || sender.tab?.id;
|
| 44 |
startCapture(tabId)
|
| 45 |
.then(() => sendResponse({ ok: true }))
|
| 46 |
.catch(e => sendResponse({ ok: false, error: e.message }));
|
| 47 |
return true;
|
| 48 |
}
|
| 49 |
|
| 50 |
+
if (msg.type === 'ANALYZE_URL') {
|
| 51 |
+
const tabId = msg.tabId || sender.tab?.id;
|
| 52 |
+
analyzeUrl(msg.url, tabId)
|
| 53 |
+
.then(() => sendResponse({ ok: true }))
|
| 54 |
+
.catch(e => sendResponse({ ok: false, error: e.message }));
|
| 55 |
+
return true;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
if (msg.type === 'CHECK_HEALTH') {
|
| 59 |
fetch(`${API_BASE}/health`)
|
| 60 |
.then(r => r.json())
|
|
|
|
| 63 |
return true;
|
| 64 |
}
|
| 65 |
|
| 66 |
+
// ── Messages from offscreen document ─────────────────────────────────────
|
| 67 |
+
if (msg.type === 'BLOB_READY') {
|
| 68 |
+
handleBlobReady(msg.chunks, msg.mimeType, msg.tabId, msg.totalSize);
|
| 69 |
+
return false;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
if (msg.type === 'RECORD_PROGRESS') {
|
| 73 |
+
if (msg.tabId) {
|
| 74 |
+
chrome.tabs.sendMessage(msg.tabId, {
|
| 75 |
+
type: 'CAPTURE_PROGRESS',
|
| 76 |
+
elapsed: msg.elapsed,
|
| 77 |
+
total: msg.total,
|
| 78 |
+
}).catch(() => {});
|
| 79 |
+
}
|
| 80 |
+
return false;
|
|
|
|
|
|
|
|
|
|
| 81 |
}
|
| 82 |
|
| 83 |
+
if (msg.type === 'RECORD_ERROR') {
|
| 84 |
+
if (msg.tabId) {
|
| 85 |
+
chrome.tabs.sendMessage(msg.tabId, {
|
| 86 |
+
type: 'ANALYSIS_ERROR',
|
| 87 |
+
error: msg.error,
|
| 88 |
+
}).catch(() => {});
|
| 89 |
+
}
|
| 90 |
+
closeOffscreen();
|
| 91 |
+
return false;
|
| 92 |
}
|
| 93 |
});
|
| 94 |
|
|
|
|
| 96 |
async function startCapture(tabId) {
|
| 97 |
if (!tabId) throw new Error('No tab ID');
|
| 98 |
|
| 99 |
+
// Show loading overlay on the tab
|
| 100 |
+
await chrome.tabs.sendMessage(tabId, { type: 'SHOW_CAPTURE_OVERLAY' }).catch(() => {
|
| 101 |
+
// Content script may not be injected yet — inject it
|
| 102 |
+
return chrome.scripting.executeScript({
|
| 103 |
+
target: { tabId },
|
| 104 |
+
files: ['content.js'],
|
| 105 |
+
}).then(() =>
|
| 106 |
+
chrome.tabs.sendMessage(tabId, { type: 'SHOW_CAPTURE_OVERLAY' })
|
| 107 |
+
);
|
| 108 |
+
});
|
| 109 |
|
| 110 |
+
// Get stream ID (must be called from background, not offscreen)
|
|
|
|
| 111 |
const streamId = await new Promise((resolve, reject) => {
|
| 112 |
+
chrome.tabCapture.getMediaStreamId({ targetTabId: tabId }, id => {
|
| 113 |
+
if (chrome.runtime.lastError) {
|
| 114 |
+
reject(new Error(chrome.runtime.lastError.message));
|
| 115 |
+
} else {
|
| 116 |
+
resolve(id);
|
| 117 |
+
}
|
| 118 |
});
|
| 119 |
});
|
| 120 |
|
| 121 |
+
// Ensure offscreen document exists
|
| 122 |
+
await ensureOffscreen();
|
| 123 |
+
|
| 124 |
+
// Tell offscreen to start recording
|
| 125 |
+
await chrome.runtime.sendMessage({
|
| 126 |
+
type: 'RECORD_OFFSCREEN',
|
| 127 |
+
streamId,
|
| 128 |
durationMs: CAPTURE_SEC * 1000,
|
| 129 |
+
tabId,
|
| 130 |
});
|
| 131 |
}
|
| 132 |
|
| 133 |
+
// ── Analyze a direct video URL ────────────────────────────────────────────────
|
| 134 |
+
async function analyzeUrl(url, tabId) {
|
| 135 |
+
if (!url) throw new Error('No URL provided');
|
| 136 |
+
|
| 137 |
+
if (tabId) {
|
| 138 |
+
await chrome.tabs.sendMessage(tabId, { type: 'SHOW_URL_ANALYSIS_OVERLAY', url }).catch(() => {});
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
try {
|
| 142 |
+
const res = await fetch(`${API_BASE}/analyze-url`, {
|
| 143 |
+
method: 'POST',
|
| 144 |
+
headers: { 'Content-Type': 'application/json' },
|
| 145 |
+
body: JSON.stringify({ url }),
|
| 146 |
+
});
|
| 147 |
+
|
| 148 |
+
if (!res.ok) {
|
| 149 |
+
const err = await res.json().catch(() => ({}));
|
| 150 |
+
throw new Error(err.detail || `Backend error ${res.status}`);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
const result = await res.json();
|
| 154 |
+
result.file = url.length > 60 ? url.slice(0, 57) + '…' : url;
|
| 155 |
|
| 156 |
+
chrome.storage.local.set({ lastResult: result });
|
| 157 |
+
|
| 158 |
+
if (tabId) {
|
| 159 |
+
chrome.tabs.sendMessage(tabId, { type: 'ANALYSIS_RESULT', result }).catch(() => {});
|
| 160 |
+
}
|
| 161 |
+
} catch (err) {
|
| 162 |
+
if (tabId) {
|
| 163 |
+
chrome.tabs.sendMessage(tabId, { type: 'ANALYSIS_ERROR', error: err.message }).catch(() => {});
|
| 164 |
+
}
|
| 165 |
+
throw err;
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
// ── Handle blob from offscreen ────────────────────────────────────────────────
|
| 170 |
+
async function handleBlobReady(chunks, mimeType, tabId, totalSize) {
|
| 171 |
+
closeOffscreen();
|
| 172 |
+
|
| 173 |
+
try {
|
| 174 |
+
if (tabId) {
|
| 175 |
+
chrome.tabs.sendMessage(tabId, { type: 'CAPTURE_PROGRESS', elapsed: 12, total: 12 }).catch(() => {});
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
const result = await submitBlob(chunks, mimeType, totalSize);
|
| 179 |
+
result.file = 'Tab capture';
|
| 180 |
+
|
| 181 |
+
chrome.storage.local.set({ lastResult: result });
|
| 182 |
+
|
| 183 |
+
if (tabId) {
|
| 184 |
+
chrome.tabs.sendMessage(tabId, { type: 'ANALYSIS_RESULT', result }).catch(() => {});
|
| 185 |
+
}
|
| 186 |
+
} catch (err) {
|
| 187 |
+
if (tabId) {
|
| 188 |
+
chrome.tabs.sendMessage(tabId, { type: 'ANALYSIS_ERROR', error: err.message }).catch(() => {});
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
// ── Submit blob to backend ────────────────────────────────────────────────────
|
| 194 |
+
async function submitBlob(chunks, mimeType, totalSize) {
|
| 195 |
+
// Reassemble chunks from array format back to Uint8Array
|
| 196 |
+
const uint8Array = new Uint8Array(totalSize);
|
| 197 |
+
let offset = 0;
|
| 198 |
+
|
| 199 |
+
for (const chunk of chunks) {
|
| 200 |
+
uint8Array.set(chunk, offset);
|
| 201 |
+
offset += chunk.length;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
const blob = new Blob([uint8Array], { type: mimeType || 'video/webm' });
|
| 205 |
const filename = `capture_${Date.now()}.webm`;
|
| 206 |
|
| 207 |
const fd = new FormData();
|
|
|
|
| 214 |
}
|
| 215 |
return res.json();
|
| 216 |
}
|
| 217 |
+
|
| 218 |
+
// ── Offscreen document management ────────────────────────────────────────────
|
| 219 |
+
async function ensureOffscreen() {
|
| 220 |
+
const existing = await chrome.offscreen.hasDocument().catch(() => false);
|
| 221 |
+
if (!existing) {
|
| 222 |
+
await chrome.offscreen.createDocument({
|
| 223 |
+
url: OFFSCREEN_URL,
|
| 224 |
+
reasons: ['USER_MEDIA'],
|
| 225 |
+
justification: 'Record tab video stream for deepfake analysis',
|
| 226 |
+
});
|
| 227 |
+
}
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
async function closeOffscreen() {
|
| 231 |
+
const exists = await chrome.offscreen.hasDocument().catch(() => false);
|
| 232 |
+
if (exists) {
|
| 233 |
+
await chrome.offscreen.closeDocument().catch(() => {});
|
| 234 |
+
}
|
| 235 |
+
}
|
extension/content.js
CHANGED
|
@@ -1,25 +1,37 @@
|
|
| 1 |
/**
|
| 2 |
-
* Authrix Extension — Content Script
|
| 3 |
*
|
| 4 |
-
*
|
| 5 |
-
*
|
| 6 |
-
*
|
| 7 |
-
*
|
| 8 |
*/
|
| 9 |
|
| 10 |
-
// ── Message listener
|
| 11 |
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
|
|
| 12 |
if (msg.type === 'SHOW_CAPTURE_OVERLAY') {
|
| 13 |
-
showOverlay();
|
| 14 |
sendResponse({ ok: true });
|
| 15 |
}
|
| 16 |
|
| 17 |
-
if (msg.type === '
|
| 18 |
-
|
| 19 |
sendResponse({ ok: true });
|
| 20 |
}
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
if (msg.type === 'ANALYSIS_RESULT') {
|
|
|
|
| 23 |
renderResult(msg.result);
|
| 24 |
}
|
| 25 |
|
|
@@ -28,101 +40,25 @@ chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|
| 28 |
}
|
| 29 |
});
|
| 30 |
|
| 31 |
-
// ──
|
| 32 |
-
|
| 33 |
-
chrome.runtime.sendMessage({ type: 'START_CAPTURE' });
|
| 34 |
-
};
|
| 35 |
-
|
| 36 |
-
// ── Record stream ─────────────────────────────────────────────────────────────
|
| 37 |
-
async function recordStream(streamId, durationMs) {
|
| 38 |
-
try {
|
| 39 |
-
updateLoadingText('Connecting to video stream...');
|
| 40 |
-
|
| 41 |
-
// Get the MediaStream from the stream ID
|
| 42 |
-
const stream = await navigator.mediaDevices.getUserMedia({
|
| 43 |
-
video: { mandatory: { chromeMediaSource: 'tab', chromeMediaSourceId: streamId } },
|
| 44 |
-
audio: { mandatory: { chromeMediaSource: 'tab', chromeMediaSourceId: streamId } },
|
| 45 |
-
});
|
| 46 |
-
|
| 47 |
-
updateLoadingText('Recording video stream...');
|
| 48 |
-
activateStep(0);
|
| 49 |
-
|
| 50 |
-
// Pick best supported format
|
| 51 |
-
const mimeType = getSupportedMimeType();
|
| 52 |
-
const recorder = new MediaRecorder(stream, { mimeType, videoBitsPerSecond: 2_000_000 });
|
| 53 |
-
const chunks = [];
|
| 54 |
-
|
| 55 |
-
recorder.ondataavailable = e => { if (e.data.size > 0) chunks.push(e.data); };
|
| 56 |
-
|
| 57 |
-
recorder.onstop = async () => {
|
| 58 |
-
stream.getTracks().forEach(t => t.stop());
|
| 59 |
-
activateStep(1);
|
| 60 |
-
updateLoadingText('Processing captured frames...');
|
| 61 |
-
|
| 62 |
-
try {
|
| 63 |
-
// Convert chunks to base64 for message passing
|
| 64 |
-
const blob = new Blob(chunks, { type: mimeType });
|
| 65 |
-
const base64 = await blobToBase64(blob);
|
| 66 |
-
|
| 67 |
-
activateStep(2);
|
| 68 |
-
updateLoadingText('Running AI analysis...');
|
| 69 |
-
|
| 70 |
-
// Send to background for backend submission
|
| 71 |
-
chrome.runtime.sendMessage({
|
| 72 |
-
type: 'SEND_BLOB_CHUNKS',
|
| 73 |
-
chunks: [base64], // single base64 string of full blob
|
| 74 |
-
mimeType: mimeType,
|
| 75 |
-
});
|
| 76 |
-
} catch (err) {
|
| 77 |
-
showError('Failed to process recording: ' + err.message);
|
| 78 |
-
chrome.runtime.sendMessage({ type: 'ANALYSIS_ERROR_FROM_CONTENT', error: err.message });
|
| 79 |
-
}
|
| 80 |
-
};
|
| 81 |
-
|
| 82 |
-
recorder.onerror = e => {
|
| 83 |
-
showError('Recording error: ' + e.error?.message);
|
| 84 |
-
};
|
| 85 |
-
|
| 86 |
-
// Record for durationMs then stop
|
| 87 |
-
recorder.start(1000); // collect data every 1s
|
| 88 |
-
setTimeout(() => {
|
| 89 |
-
if (recorder.state === 'recording') recorder.stop();
|
| 90 |
-
}, durationMs);
|
| 91 |
-
|
| 92 |
-
// Update progress during recording
|
| 93 |
-
const startTime = Date.now();
|
| 94 |
-
const progressInterval = setInterval(() => {
|
| 95 |
-
if (recorder.state !== 'recording') { clearInterval(progressInterval); return; }
|
| 96 |
-
const elapsed = (Date.now() - startTime) / 1000;
|
| 97 |
-
const total = durationMs / 1000;
|
| 98 |
-
const pct = Math.min(99, Math.round((elapsed / total) * 60));
|
| 99 |
-
updateLoadingText(`Recording: ${elapsed.toFixed(0)}s / ${total}s (${pct}% captured)`);
|
| 100 |
-
}, 500);
|
| 101 |
-
|
| 102 |
-
} catch (err) {
|
| 103 |
-
showError(err.message.includes('Permission')
|
| 104 |
-
? 'Tab capture permission denied. Try reloading the page.'
|
| 105 |
-
: 'Stream capture failed: ' + err.message);
|
| 106 |
-
}
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
// ── Overlay UI ──────────────────────���─────────────────────────────────────────
|
| 110 |
-
function showOverlay() {
|
| 111 |
document.getElementById('authrix-overlay')?.remove();
|
| 112 |
|
| 113 |
const overlay = document.createElement('div');
|
| 114 |
overlay.id = 'authrix-overlay';
|
| 115 |
overlay.innerHTML = `
|
| 116 |
<div id="authrix-panel">
|
|
|
|
|
|
|
| 117 |
<div id="authrix-header">
|
| 118 |
<div id="authrix-logo">
|
| 119 |
<span id="authrix-logo-icon">◉</span>
|
| 120 |
<span>AUTHRIX AI</span>
|
| 121 |
</div>
|
| 122 |
-
<button id="authrix-close">✕</button>
|
| 123 |
</div>
|
| 124 |
|
| 125 |
-
<!-- Loading -->
|
| 126 |
<div id="authrix-loading">
|
| 127 |
<div id="authrix-radar">
|
| 128 |
<div class="authrix-ring r1"></div>
|
|
@@ -130,17 +66,24 @@ function showOverlay() {
|
|
| 130 |
<div class="authrix-ring r3"></div>
|
| 131 |
<div id="authrix-radar-dot"></div>
|
| 132 |
</div>
|
| 133 |
-
<div id="authrix-loading-text">
|
|
|
|
|
|
|
| 134 |
<div id="authrix-steps">
|
| 135 |
-
<div class="authrix-step" id="as0">
|
|
|
|
|
|
|
| 136 |
<div class="authrix-step" id="as1">🖼️ Extracting frames</div>
|
| 137 |
<div class="authrix-step" id="as2">🧠 Running AI models</div>
|
| 138 |
<div class="authrix-step" id="as3">📊 Generating report</div>
|
| 139 |
</div>
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
| 141 |
</div>
|
| 142 |
|
| 143 |
-
<!-- Result -->
|
| 144 |
<div id="authrix-result" style="display:none;">
|
| 145 |
<div id="authrix-verdict-row">
|
| 146 |
<div id="authrix-badge"></div>
|
|
@@ -155,34 +98,44 @@ function showOverlay() {
|
|
| 155 |
<span id="authrix-audio-icon">🎙️</span>
|
| 156 |
<span id="authrix-audio-label"></span>
|
| 157 |
</div>
|
|
|
|
| 158 |
<div id="authrix-actions">
|
| 159 |
<button id="authrix-reanalyze">↺ Capture Again</button>
|
| 160 |
<button id="authrix-open-app">Open Authrix App ↗</button>
|
| 161 |
</div>
|
| 162 |
</div>
|
| 163 |
|
| 164 |
-
<!-- Error -->
|
| 165 |
<div id="authrix-error" style="display:none;">
|
| 166 |
<div id="authrix-error-icon">⚠</div>
|
| 167 |
<div id="authrix-error-msg"></div>
|
| 168 |
<div id="authrix-error-hint"></div>
|
| 169 |
<button id="authrix-retry">↺ Retry</button>
|
| 170 |
</div>
|
|
|
|
| 171 |
</div>
|
| 172 |
`;
|
| 173 |
|
| 174 |
document.body.appendChild(overlay);
|
| 175 |
|
|
|
|
| 176 |
document.getElementById('authrix-close').onclick = () => overlay.remove();
|
| 177 |
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
|
|
|
|
| 178 |
document.getElementById('authrix-open-app').onclick = () =>
|
| 179 |
-
window.open('
|
|
|
|
| 180 |
document.getElementById('authrix-reanalyze').onclick = () =>
|
| 181 |
chrome.runtime.sendMessage({ type: 'START_CAPTURE' });
|
|
|
|
| 182 |
document.getElementById('authrix-retry').onclick = () =>
|
| 183 |
chrome.runtime.sendMessage({ type: 'START_CAPTURE' });
|
|
|
|
|
|
|
|
|
|
| 184 |
}
|
| 185 |
|
|
|
|
| 186 |
function updateLoadingText(text) {
|
| 187 |
const el = document.getElementById('authrix-loading-text');
|
| 188 |
if (el) el.textContent = text;
|
|
@@ -204,27 +157,30 @@ function showState(state) {
|
|
| 204 |
if (error) error.style.display = state === 'error' ? 'flex' : 'none';
|
| 205 |
}
|
| 206 |
|
|
|
|
| 207 |
function renderResult(data) {
|
| 208 |
-
activateStep(3);
|
| 209 |
-
|
| 210 |
const isFake = data.result === 'FAKE';
|
| 211 |
-
const conf = data.confidence
|
| 212 |
const color = isFake ? '#ff4466' : '#00ff9c';
|
| 213 |
|
|
|
|
| 214 |
const badge = document.getElementById('authrix-badge');
|
| 215 |
if (badge) {
|
| 216 |
-
badge.textContent
|
| 217 |
-
badge.style.color
|
| 218 |
badge.style.borderColor = color;
|
| 219 |
badge.style.boxShadow = `0 0 16px ${color}44`;
|
| 220 |
}
|
| 221 |
|
|
|
|
| 222 |
const vt = document.getElementById('authrix-verdict-text');
|
| 223 |
if (vt) { vt.textContent = isFake ? 'DEEPFAKE DETECTED' : 'AUTHENTIC VIDEO'; vt.style.color = color; }
|
| 224 |
|
|
|
|
| 225 |
const cv = document.getElementById('authrix-conf');
|
| 226 |
if (cv) { cv.textContent = conf + '%'; cv.style.color = color; }
|
| 227 |
|
|
|
|
| 228 |
const bar = document.getElementById('authrix-conf-bar');
|
| 229 |
if (bar) {
|
| 230 |
bar.style.background = isFake
|
|
@@ -234,6 +190,7 @@ function renderResult(data) {
|
|
| 234 |
setTimeout(() => { bar.style.width = conf + '%'; }, 80);
|
| 235 |
}
|
| 236 |
|
|
|
|
| 237 |
const dl = document.getElementById('authrix-details');
|
| 238 |
if (dl) {
|
| 239 |
dl.innerHTML = (data.details || []).slice(0, 3).map(d =>
|
|
@@ -241,58 +198,68 @@ function renderResult(data) {
|
|
| 241 |
).join('');
|
| 242 |
}
|
| 243 |
|
|
|
|
| 244 |
const audioRow = document.getElementById('authrix-audio-row');
|
| 245 |
if (audioRow && data.audio?.available) {
|
| 246 |
-
const isAI
|
| 247 |
const isMismatch = data.audio.result === 'AV_MISMATCH';
|
| 248 |
-
const aColor
|
| 249 |
const audioIcon = document.getElementById('authrix-audio-icon');
|
| 250 |
const audioLabel = document.getElementById('authrix-audio-label');
|
| 251 |
if (audioIcon) audioIcon.textContent = (isAI || isMismatch) ? '🤖' : '🎙️';
|
| 252 |
if (audioLabel) {
|
| 253 |
audioLabel.textContent = isMismatch
|
| 254 |
? 'AV Mismatch — face-swap detected'
|
| 255 |
-
: isAI
|
| 256 |
-
|
|
|
|
| 257 |
audioLabel.style.color = aColor;
|
| 258 |
}
|
| 259 |
audioRow.style.display = 'flex';
|
| 260 |
}
|
| 261 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
showState('result');
|
| 263 |
}
|
| 264 |
|
|
|
|
| 265 |
function showError(message) {
|
| 266 |
-
const isOffline = message
|
| 267 |
const errMsg = document.getElementById('authrix-error-msg');
|
| 268 |
const errHint = document.getElementById('authrix-error-hint');
|
| 269 |
-
if (errMsg) errMsg.textContent = isOffline ? 'Authrix server is not running' : (message || 'Unknown error');
|
| 270 |
-
if (errHint) errHint.textContent = isOffline
|
| 271 |
-
? 'Run: cd backend && python -m uvicorn main:app --port 8000'
|
| 272 |
-
: 'Make sure the video is playing before capturing.';
|
| 273 |
-
showState('error');
|
| 274 |
-
}
|
| 275 |
-
|
| 276 |
-
// ── Utilities ─────────────────────────────────────────────────────────────────
|
| 277 |
-
function getSupportedMimeType() {
|
| 278 |
-
const types = [
|
| 279 |
-
'video/webm;codecs=vp9,opus',
|
| 280 |
-
'video/webm;codecs=vp8,opus',
|
| 281 |
-
'video/webm',
|
| 282 |
-
'video/mp4',
|
| 283 |
-
];
|
| 284 |
-
return types.find(t => MediaRecorder.isTypeSupported(t)) || 'video/webm';
|
| 285 |
-
}
|
| 286 |
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
}
|
| 295 |
|
|
|
|
| 296 |
function escHtml(s) {
|
| 297 |
-
return String(s)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
}
|
|
|
|
| 1 |
/**
|
| 2 |
+
* Authrix Extension — Content Script v3
|
| 3 |
*
|
| 4 |
+
* Responsibilities:
|
| 5 |
+
* - Render the overlay UI (loading, result, error states)
|
| 6 |
+
* - Relay messages from background to the overlay
|
| 7 |
+
* - Recording is handled by offscreen.js (MV3 requirement)
|
| 8 |
*/
|
| 9 |
|
| 10 |
+
// ── Message listener ──────────────────────────────────────────────────────────
|
| 11 |
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
| 12 |
+
|
| 13 |
if (msg.type === 'SHOW_CAPTURE_OVERLAY') {
|
| 14 |
+
showOverlay('capture');
|
| 15 |
sendResponse({ ok: true });
|
| 16 |
}
|
| 17 |
|
| 18 |
+
if (msg.type === 'SHOW_URL_ANALYSIS_OVERLAY') {
|
| 19 |
+
showOverlay('url', msg.url);
|
| 20 |
sendResponse({ ok: true });
|
| 21 |
}
|
| 22 |
|
| 23 |
+
if (msg.type === 'CAPTURE_PROGRESS') {
|
| 24 |
+
const pct = Math.min(95, Math.round((msg.elapsed / msg.total) * 65));
|
| 25 |
+
updateLoadingText(`Recording: ${msg.elapsed}s / ${msg.total}s`);
|
| 26 |
+
if (msg.elapsed === 1) activateStep(0);
|
| 27 |
+
if (msg.elapsed >= msg.total) {
|
| 28 |
+
activateStep(1);
|
| 29 |
+
updateLoadingText('Processing frames — running AI analysis…');
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
if (msg.type === 'ANALYSIS_RESULT') {
|
| 34 |
+
activateStep(3);
|
| 35 |
renderResult(msg.result);
|
| 36 |
}
|
| 37 |
|
|
|
|
| 40 |
}
|
| 41 |
});
|
| 42 |
|
| 43 |
+
// ── Show overlay ──────────────────────────────────────────────────────────────
|
| 44 |
+
function showOverlay(mode = 'capture', url = '') {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
document.getElementById('authrix-overlay')?.remove();
|
| 46 |
|
| 47 |
const overlay = document.createElement('div');
|
| 48 |
overlay.id = 'authrix-overlay';
|
| 49 |
overlay.innerHTML = `
|
| 50 |
<div id="authrix-panel">
|
| 51 |
+
|
| 52 |
+
<!-- Header -->
|
| 53 |
<div id="authrix-header">
|
| 54 |
<div id="authrix-logo">
|
| 55 |
<span id="authrix-logo-icon">◉</span>
|
| 56 |
<span>AUTHRIX AI</span>
|
| 57 |
</div>
|
| 58 |
+
<button id="authrix-close" title="Close">✕</button>
|
| 59 |
</div>
|
| 60 |
|
| 61 |
+
<!-- Loading state -->
|
| 62 |
<div id="authrix-loading">
|
| 63 |
<div id="authrix-radar">
|
| 64 |
<div class="authrix-ring r1"></div>
|
|
|
|
| 66 |
<div class="authrix-ring r3"></div>
|
| 67 |
<div id="authrix-radar-dot"></div>
|
| 68 |
</div>
|
| 69 |
+
<div id="authrix-loading-text">
|
| 70 |
+
${mode === 'url' ? 'Downloading & analyzing video…' : 'Initializing capture…'}
|
| 71 |
+
</div>
|
| 72 |
<div id="authrix-steps">
|
| 73 |
+
<div class="authrix-step" id="as0">
|
| 74 |
+
${mode === 'url' ? '⬇️ Downloading video' : '🎬 Recording tab stream'}
|
| 75 |
+
</div>
|
| 76 |
<div class="authrix-step" id="as1">🖼️ Extracting frames</div>
|
| 77 |
<div class="authrix-step" id="as2">🧠 Running AI models</div>
|
| 78 |
<div class="authrix-step" id="as3">📊 Generating report</div>
|
| 79 |
</div>
|
| 80 |
+
${mode === 'url'
|
| 81 |
+
? `<div id="authrix-note" style="font-family:monospace;font-size:10px;word-break:break-all;">${escHtml(url.slice(0, 80))}${url.length > 80 ? '…' : ''}</div>`
|
| 82 |
+
: `<div id="authrix-note">Recording ~20 seconds of video for analysis</div>`
|
| 83 |
+
}
|
| 84 |
</div>
|
| 85 |
|
| 86 |
+
<!-- Result state -->
|
| 87 |
<div id="authrix-result" style="display:none;">
|
| 88 |
<div id="authrix-verdict-row">
|
| 89 |
<div id="authrix-badge"></div>
|
|
|
|
| 98 |
<span id="authrix-audio-icon">🎙️</span>
|
| 99 |
<span id="authrix-audio-label"></span>
|
| 100 |
</div>
|
| 101 |
+
<div id="authrix-meta"></div>
|
| 102 |
<div id="authrix-actions">
|
| 103 |
<button id="authrix-reanalyze">↺ Capture Again</button>
|
| 104 |
<button id="authrix-open-app">Open Authrix App ↗</button>
|
| 105 |
</div>
|
| 106 |
</div>
|
| 107 |
|
| 108 |
+
<!-- Error state -->
|
| 109 |
<div id="authrix-error" style="display:none;">
|
| 110 |
<div id="authrix-error-icon">⚠</div>
|
| 111 |
<div id="authrix-error-msg"></div>
|
| 112 |
<div id="authrix-error-hint"></div>
|
| 113 |
<button id="authrix-retry">↺ Retry</button>
|
| 114 |
</div>
|
| 115 |
+
|
| 116 |
</div>
|
| 117 |
`;
|
| 118 |
|
| 119 |
document.body.appendChild(overlay);
|
| 120 |
|
| 121 |
+
// Wire up buttons
|
| 122 |
document.getElementById('authrix-close').onclick = () => overlay.remove();
|
| 123 |
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
|
| 124 |
+
|
| 125 |
document.getElementById('authrix-open-app').onclick = () =>
|
| 126 |
+
window.open('https://aarav13-authrix.hf.space', '_blank');
|
| 127 |
+
|
| 128 |
document.getElementById('authrix-reanalyze').onclick = () =>
|
| 129 |
chrome.runtime.sendMessage({ type: 'START_CAPTURE' });
|
| 130 |
+
|
| 131 |
document.getElementById('authrix-retry').onclick = () =>
|
| 132 |
chrome.runtime.sendMessage({ type: 'START_CAPTURE' });
|
| 133 |
+
|
| 134 |
+
// Auto-activate first step for URL mode (no recording phase)
|
| 135 |
+
if (mode === 'url') activateStep(1);
|
| 136 |
}
|
| 137 |
|
| 138 |
+
// ── Loading helpers ───────────────────────────────────────────────────────────
|
| 139 |
function updateLoadingText(text) {
|
| 140 |
const el = document.getElementById('authrix-loading-text');
|
| 141 |
if (el) el.textContent = text;
|
|
|
|
| 157 |
if (error) error.style.display = state === 'error' ? 'flex' : 'none';
|
| 158 |
}
|
| 159 |
|
| 160 |
+
// ── Render result ─────────────────────────────────────────────────────────────
|
| 161 |
function renderResult(data) {
|
|
|
|
|
|
|
| 162 |
const isFake = data.result === 'FAKE';
|
| 163 |
+
const conf = data.confidence ?? 0;
|
| 164 |
const color = isFake ? '#ff4466' : '#00ff9c';
|
| 165 |
|
| 166 |
+
// Badge
|
| 167 |
const badge = document.getElementById('authrix-badge');
|
| 168 |
if (badge) {
|
| 169 |
+
badge.textContent = isFake ? '⚠' : '✓';
|
| 170 |
+
badge.style.color = color;
|
| 171 |
badge.style.borderColor = color;
|
| 172 |
badge.style.boxShadow = `0 0 16px ${color}44`;
|
| 173 |
}
|
| 174 |
|
| 175 |
+
// Verdict text
|
| 176 |
const vt = document.getElementById('authrix-verdict-text');
|
| 177 |
if (vt) { vt.textContent = isFake ? 'DEEPFAKE DETECTED' : 'AUTHENTIC VIDEO'; vt.style.color = color; }
|
| 178 |
|
| 179 |
+
// Confidence number
|
| 180 |
const cv = document.getElementById('authrix-conf');
|
| 181 |
if (cv) { cv.textContent = conf + '%'; cv.style.color = color; }
|
| 182 |
|
| 183 |
+
// Confidence bar
|
| 184 |
const bar = document.getElementById('authrix-conf-bar');
|
| 185 |
if (bar) {
|
| 186 |
bar.style.background = isFake
|
|
|
|
| 190 |
setTimeout(() => { bar.style.width = conf + '%'; }, 80);
|
| 191 |
}
|
| 192 |
|
| 193 |
+
// Detail bullets (max 3)
|
| 194 |
const dl = document.getElementById('authrix-details');
|
| 195 |
if (dl) {
|
| 196 |
dl.innerHTML = (data.details || []).slice(0, 3).map(d =>
|
|
|
|
| 198 |
).join('');
|
| 199 |
}
|
| 200 |
|
| 201 |
+
// Audio row
|
| 202 |
const audioRow = document.getElementById('authrix-audio-row');
|
| 203 |
if (audioRow && data.audio?.available) {
|
| 204 |
+
const isAI = data.audio.result === 'AI_VOICE';
|
| 205 |
const isMismatch = data.audio.result === 'AV_MISMATCH';
|
| 206 |
+
const aColor = (isAI || isMismatch) ? '#ff4466' : '#00ff9c';
|
| 207 |
const audioIcon = document.getElementById('authrix-audio-icon');
|
| 208 |
const audioLabel = document.getElementById('authrix-audio-label');
|
| 209 |
if (audioIcon) audioIcon.textContent = (isAI || isMismatch) ? '🤖' : '🎙️';
|
| 210 |
if (audioLabel) {
|
| 211 |
audioLabel.textContent = isMismatch
|
| 212 |
? 'AV Mismatch — face-swap detected'
|
| 213 |
+
: isAI
|
| 214 |
+
? `AI Voice (${data.audio.confidence}%)`
|
| 215 |
+
: `Human Voice (${data.audio.confidence}%)`;
|
| 216 |
audioLabel.style.color = aColor;
|
| 217 |
}
|
| 218 |
audioRow.style.display = 'flex';
|
| 219 |
}
|
| 220 |
|
| 221 |
+
// Metadata row
|
| 222 |
+
const meta = document.getElementById('authrix-meta');
|
| 223 |
+
if (meta && data.metadata) {
|
| 224 |
+
const m = data.metadata;
|
| 225 |
+
const parts = [];
|
| 226 |
+
if (m.video_duration_sec) parts.push(`${m.video_duration_sec}s`);
|
| 227 |
+
if (m.resolution && m.resolution !== '0x0') parts.push(m.resolution);
|
| 228 |
+
if (m.frames_with_faces != null) parts.push(`${m.frames_with_faces} faces`);
|
| 229 |
+
if (data.processing_time_sec) parts.push(`${data.processing_time_sec}s analysis`);
|
| 230 |
+
if (parts.length) {
|
| 231 |
+
meta.textContent = parts.join(' · ');
|
| 232 |
+
meta.style.cssText = 'font-size:10px;color:rgba(255,255,255,0.25);margin-bottom:12px;font-family:monospace;';
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
showState('result');
|
| 237 |
}
|
| 238 |
|
| 239 |
+
// ── Show error ────────────────────────────────────────────────────────────────
|
| 240 |
function showError(message) {
|
| 241 |
+
const isOffline = !message || message.includes('fetch') || message.includes('Failed to fetch') || message.includes('ERR_CONNECTION');
|
| 242 |
const errMsg = document.getElementById('authrix-error-msg');
|
| 243 |
const errHint = document.getElementById('authrix-error-hint');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
|
| 245 |
+
if (errMsg) {
|
| 246 |
+
errMsg.textContent = isOffline
|
| 247 |
+
? 'Authrix server is not running'
|
| 248 |
+
: (message || 'Unknown error');
|
| 249 |
+
}
|
| 250 |
+
if (errHint) {
|
| 251 |
+
errHint.textContent = isOffline
|
| 252 |
+
? 'Visit https://aarav13-authrix.hf.space to check server status'
|
| 253 |
+
: 'Make sure a video is playing before capturing.';
|
| 254 |
+
}
|
| 255 |
+
showState('error');
|
| 256 |
}
|
| 257 |
|
| 258 |
+
// ── Utility ───────────────────────────────────────────────────────────────────
|
| 259 |
function escHtml(s) {
|
| 260 |
+
return String(s)
|
| 261 |
+
.replace(/&/g, '&')
|
| 262 |
+
.replace(/</g, '<')
|
| 263 |
+
.replace(/>/g, '>')
|
| 264 |
+
.replace(/"/g, '"');
|
| 265 |
}
|
extension/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
{
|
| 2 |
"manifest_version": 3,
|
| 3 |
"name": "Authrix — Deepfake Detector",
|
| 4 |
-
"version": "2.
|
| 5 |
-
"description": "Detect deepfake videos on any webpage. Captures tab stream directly —
|
| 6 |
|
| 7 |
"permissions": [
|
| 8 |
"contextMenus",
|
|
@@ -10,11 +10,13 @@
|
|
| 10 |
"scripting",
|
| 11 |
"storage",
|
| 12 |
"tabCapture",
|
| 13 |
-
"tabs"
|
|
|
|
| 14 |
],
|
| 15 |
|
| 16 |
"host_permissions": [
|
| 17 |
-
"http://localhost:8000/*"
|
|
|
|
| 18 |
],
|
| 19 |
|
| 20 |
"background": {
|
|
|
|
| 1 |
{
|
| 2 |
"manifest_version": 3,
|
| 3 |
"name": "Authrix — Deepfake Detector",
|
| 4 |
+
"version": "2.2.0",
|
| 5 |
+
"description": "Detect deepfake videos on any webpage. Captures tab stream directly — works on YouTube, Twitter, TikTok, any platform.",
|
| 6 |
|
| 7 |
"permissions": [
|
| 8 |
"contextMenus",
|
|
|
|
| 10 |
"scripting",
|
| 11 |
"storage",
|
| 12 |
"tabCapture",
|
| 13 |
+
"tabs",
|
| 14 |
+
"offscreen"
|
| 15 |
],
|
| 16 |
|
| 17 |
"host_permissions": [
|
| 18 |
+
"http://localhost:8000/*",
|
| 19 |
+
"https://aarav13-authrix.hf.space/*"
|
| 20 |
],
|
| 21 |
|
| 22 |
"background": {
|
extension/offscreen.html
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head><meta charset="UTF-8"/></head>
|
| 4 |
+
<body>
|
| 5 |
+
<!-- Offscreen document: handles MediaRecorder since service workers can't -->
|
| 6 |
+
<script src="offscreen.js"></script>
|
| 7 |
+
</body>
|
| 8 |
+
</html>
|
extension/offscreen.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Authrix Extension — Offscreen Document
|
| 3 |
+
*
|
| 4 |
+
* Offscreen documents can use getUserMedia with chromeMediaSource:'tab',
|
| 5 |
+
* which is NOT available in content scripts or service workers in MV3.
|
| 6 |
+
*/
|
| 7 |
+
|
| 8 |
+
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
| 9 |
+
if (msg.type === 'RECORD_OFFSCREEN') {
|
| 10 |
+
startRecording(msg.streamId, msg.durationMs, msg.tabId)
|
| 11 |
+
.then(() => sendResponse({ ok: true }))
|
| 12 |
+
.catch(e => sendResponse({ ok: false, error: e.message }));
|
| 13 |
+
return true;
|
| 14 |
+
}
|
| 15 |
+
});
|
| 16 |
+
|
| 17 |
+
let activeRecorder = null;
|
| 18 |
+
|
| 19 |
+
async function startRecording(streamId, durationMs, tabId) {
|
| 20 |
+
if (activeRecorder && activeRecorder.state === 'recording') {
|
| 21 |
+
activeRecorder.stop();
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
const stream = await navigator.mediaDevices.getUserMedia({
|
| 25 |
+
video: {
|
| 26 |
+
mandatory: {
|
| 27 |
+
chromeMediaSource: 'tab',
|
| 28 |
+
chromeMediaSourceId: streamId,
|
| 29 |
+
},
|
| 30 |
+
},
|
| 31 |
+
audio: {
|
| 32 |
+
mandatory: {
|
| 33 |
+
chromeMediaSource: 'tab',
|
| 34 |
+
chromeMediaSourceId: streamId,
|
| 35 |
+
},
|
| 36 |
+
},
|
| 37 |
+
});
|
| 38 |
+
|
| 39 |
+
const mimeType = getSupportedMimeType();
|
| 40 |
+
const recorder = new MediaRecorder(stream, {
|
| 41 |
+
mimeType,
|
| 42 |
+
videoBitsPerSecond: 5_000_000, // Increased from 2.5Mbps for better quality
|
| 43 |
+
});
|
| 44 |
+
activeRecorder = recorder;
|
| 45 |
+
|
| 46 |
+
const chunks = [];
|
| 47 |
+
recorder.ondataavailable = e => {
|
| 48 |
+
if (e.data && e.data.size > 0) chunks.push(e.data);
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
recorder.onstop = async () => {
|
| 52 |
+
stream.getTracks().forEach(t => t.stop());
|
| 53 |
+
|
| 54 |
+
try {
|
| 55 |
+
const blob = new Blob(chunks, { type: mimeType });
|
| 56 |
+
|
| 57 |
+
// Convert blob to ArrayBuffer (more reliable than base64 for large files)
|
| 58 |
+
const arrayBuffer = await blob.arrayBuffer();
|
| 59 |
+
const uint8Array = new Uint8Array(arrayBuffer);
|
| 60 |
+
|
| 61 |
+
// Chunk into 1MB pieces to avoid message size limits
|
| 62 |
+
const CHUNK_SIZE = 1024 * 1024; // 1MB
|
| 63 |
+
const totalChunks = Math.ceil(uint8Array.length / CHUNK_SIZE);
|
| 64 |
+
const chunkedData = [];
|
| 65 |
+
|
| 66 |
+
for (let i = 0; i < totalChunks; i++) {
|
| 67 |
+
const start = i * CHUNK_SIZE;
|
| 68 |
+
const end = Math.min(start + CHUNK_SIZE, uint8Array.length);
|
| 69 |
+
const chunk = uint8Array.slice(start, end);
|
| 70 |
+
// Convert to regular array for JSON serialization
|
| 71 |
+
chunkedData.push(Array.from(chunk));
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
chrome.runtime.sendMessage({
|
| 75 |
+
type: 'BLOB_READY',
|
| 76 |
+
chunks: chunkedData,
|
| 77 |
+
mimeType,
|
| 78 |
+
tabId,
|
| 79 |
+
totalSize: uint8Array.length,
|
| 80 |
+
});
|
| 81 |
+
} catch (err) {
|
| 82 |
+
chrome.runtime.sendMessage({
|
| 83 |
+
type: 'RECORD_ERROR',
|
| 84 |
+
error: err.message,
|
| 85 |
+
tabId,
|
| 86 |
+
});
|
| 87 |
+
}
|
| 88 |
+
};
|
| 89 |
+
|
| 90 |
+
recorder.onerror = e => {
|
| 91 |
+
chrome.runtime.sendMessage({
|
| 92 |
+
type: 'RECORD_ERROR',
|
| 93 |
+
error: e.error?.message || 'MediaRecorder error',
|
| 94 |
+
tabId,
|
| 95 |
+
});
|
| 96 |
+
};
|
| 97 |
+
|
| 98 |
+
recorder.start(1000);
|
| 99 |
+
|
| 100 |
+
setTimeout(() => {
|
| 101 |
+
if (recorder.state === 'recording') recorder.stop();
|
| 102 |
+
}, durationMs);
|
| 103 |
+
|
| 104 |
+
// Progress ticks
|
| 105 |
+
const startTime = Date.now();
|
| 106 |
+
const tick = setInterval(() => {
|
| 107 |
+
if (recorder.state !== 'recording') { clearInterval(tick); return; }
|
| 108 |
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
| 109 |
+
const total = Math.round(durationMs / 1000);
|
| 110 |
+
chrome.runtime.sendMessage({ type: 'RECORD_PROGRESS', elapsed, total, tabId });
|
| 111 |
+
}, 800);
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
function getSupportedMimeType() {
|
| 115 |
+
const types = [
|
| 116 |
+
'video/webm;codecs=vp9,opus',
|
| 117 |
+
'video/webm;codecs=vp8,opus',
|
| 118 |
+
'video/webm',
|
| 119 |
+
'video/mp4',
|
| 120 |
+
];
|
| 121 |
+
return types.find(t => MediaRecorder.isTypeSupported(t)) || 'video/webm';
|
| 122 |
+
}
|
extension/popup.html
CHANGED
|
@@ -13,6 +13,7 @@
|
|
| 13 |
font-size: 13px;
|
| 14 |
}
|
| 15 |
|
|
|
|
| 16 |
.header {
|
| 17 |
padding: 14px 16px 12px;
|
| 18 |
border-bottom: 1px solid rgba(0,255,156,0.12);
|
|
@@ -43,14 +44,19 @@
|
|
| 43 |
border-color: rgba(255,68,102,0.3);
|
| 44 |
color: #ff4466; background: rgba(255,68,102,0.08);
|
| 45 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
-
|
|
|
|
| 48 |
|
| 49 |
/* Page info */
|
| 50 |
.page-card {
|
| 51 |
background: rgba(255,255,255,0.03);
|
| 52 |
border: 1px solid rgba(255,255,255,0.07);
|
| 53 |
-
border-radius: 8px; padding:
|
| 54 |
display: flex; align-items: center; gap: 10px;
|
| 55 |
}
|
| 56 |
.page-icon { font-size: 18px; flex-shrink: 0; }
|
|
@@ -62,7 +68,7 @@
|
|
| 62 |
|
| 63 |
/* Main capture button */
|
| 64 |
.btn-capture {
|
| 65 |
-
width: 100%; padding:
|
| 66 |
background: linear-gradient(135deg, #00ff9c, #00cc6a);
|
| 67 |
color: #002110; border: none; border-radius: 10px;
|
| 68 |
font-size: 13px; font-weight: 700; letter-spacing: 0.08em;
|
|
@@ -71,25 +77,61 @@
|
|
| 71 |
display: flex; align-items: center; justify-content: center; gap: 8px;
|
| 72 |
}
|
| 73 |
.btn-capture:hover {
|
| 74 |
-
transform: translateY(-
|
| 75 |
box-shadow: 0 0 36px rgba(0,255,156,0.5);
|
| 76 |
}
|
| 77 |
.btn-capture:active { transform: scale(0.98); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
/* How it works */
|
| 80 |
.how-it-works {
|
| 81 |
background: rgba(255,255,255,0.02);
|
| 82 |
border: 1px solid rgba(255,255,255,0.05);
|
| 83 |
-
border-radius: 8px; padding:
|
| 84 |
}
|
| 85 |
.how-label {
|
| 86 |
font-size: 9px; font-weight: 700; letter-spacing: 0.15em;
|
| 87 |
-
color: rgba(255,255,255,0.25); text-transform: uppercase; margin-bottom:
|
| 88 |
}
|
| 89 |
.how-step {
|
| 90 |
display: flex; align-items: center; gap: 8px;
|
| 91 |
-
font-size: 10px; color: rgba(255,255,255,0.4);
|
| 92 |
-
padding: 3px 0;
|
| 93 |
}
|
| 94 |
.how-step-num {
|
| 95 |
width: 16px; height: 16px; border-radius: 50%;
|
|
@@ -102,25 +144,27 @@
|
|
| 102 |
#last-result {
|
| 103 |
background: rgba(255,255,255,0.03);
|
| 104 |
border: 1px solid rgba(255,255,255,0.07);
|
| 105 |
-
border-radius: 8px; padding:
|
| 106 |
display: none;
|
| 107 |
}
|
| 108 |
.result-label {
|
| 109 |
font-size: 9px; font-weight: 700; letter-spacing: 0.15em;
|
| 110 |
-
color: rgba(255,255,255,0.25); text-transform: uppercase; margin-bottom:
|
| 111 |
}
|
| 112 |
.result-row { display: flex; align-items: center; justify-content: space-between; }
|
| 113 |
.result-verdict { font-size: 13px; font-weight: 700; letter-spacing: 0.06em; }
|
| 114 |
.result-conf { font-size: 16px; font-weight: 700; font-family: monospace; }
|
| 115 |
.result-bar-wrap {
|
| 116 |
height: 3px; background: rgba(255,255,255,0.06);
|
| 117 |
-
border-radius: 99px; overflow: hidden; margin:
|
| 118 |
}
|
| 119 |
.result-bar { height: 100%; border-radius: 99px; transition: width 0.8s ease; }
|
| 120 |
-
.result-file { font-size: 9px; color: rgba(255,255,255,0.2); font-family: monospace;
|
|
|
|
| 121 |
|
|
|
|
| 122 |
.footer {
|
| 123 |
-
padding:
|
| 124 |
border-top: 1px solid rgba(255,255,255,0.05);
|
| 125 |
display: flex; align-items: center; justify-content: space-between;
|
| 126 |
}
|
|
@@ -130,6 +174,20 @@
|
|
| 130 |
}
|
| 131 |
.footer-link:hover { color: #00ff9c; }
|
| 132 |
.model-info { font-size: 9px; color: rgba(255,255,255,0.2); letter-spacing: 0.06em; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
</style>
|
| 134 |
</head>
|
| 135 |
<body>
|
|
@@ -148,22 +206,38 @@
|
|
| 148 |
<div class="page-card">
|
| 149 |
<div class="page-icon">🎬</div>
|
| 150 |
<div style="flex:1;min-width:0;">
|
| 151 |
-
<div class="page-title" id="pageTitle">Loading
|
| 152 |
<div class="page-sub" id="pageSub">Detecting page</div>
|
| 153 |
</div>
|
| 154 |
</div>
|
| 155 |
|
| 156 |
-
<!--
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
<button class="btn-capture" id="btnCapture">
|
| 158 |
<span>⬤</span> Capture & Analyze Video
|
| 159 |
</button>
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
<!-- How it works -->
|
| 162 |
<div class="how-it-works">
|
| 163 |
<div class="how-label">How it works</div>
|
| 164 |
-
<div class="how-step"><div class="how-step-num">1</div>Records
|
| 165 |
<div class="how-step"><div class="how-step-num">2</div>Sends to local AI for analysis</div>
|
| 166 |
-
<div class="how-step"><div class="how-step-num">3</div>Shows FAKE / REAL verdict</div>
|
| 167 |
</div>
|
| 168 |
|
| 169 |
<!-- Last result -->
|
|
|
|
| 13 |
font-size: 13px;
|
| 14 |
}
|
| 15 |
|
| 16 |
+
/* ── Header ── */
|
| 17 |
.header {
|
| 18 |
padding: 14px 16px 12px;
|
| 19 |
border-bottom: 1px solid rgba(0,255,156,0.12);
|
|
|
|
| 44 |
border-color: rgba(255,68,102,0.3);
|
| 45 |
color: #ff4466; background: rgba(255,68,102,0.08);
|
| 46 |
}
|
| 47 |
+
.status-badge.loading {
|
| 48 |
+
border-color: rgba(255,170,0,0.3);
|
| 49 |
+
color: #ffaa00; background: rgba(255,170,0,0.08);
|
| 50 |
+
}
|
| 51 |
|
| 52 |
+
/* ── Body ── */
|
| 53 |
+
.body { padding: 14px 16px; display: flex; flex-direction: column; gap: 10px; }
|
| 54 |
|
| 55 |
/* Page info */
|
| 56 |
.page-card {
|
| 57 |
background: rgba(255,255,255,0.03);
|
| 58 |
border: 1px solid rgba(255,255,255,0.07);
|
| 59 |
+
border-radius: 8px; padding: 9px 12px;
|
| 60 |
display: flex; align-items: center; gap: 10px;
|
| 61 |
}
|
| 62 |
.page-icon { font-size: 18px; flex-shrink: 0; }
|
|
|
|
| 68 |
|
| 69 |
/* Main capture button */
|
| 70 |
.btn-capture {
|
| 71 |
+
width: 100%; padding: 13px;
|
| 72 |
background: linear-gradient(135deg, #00ff9c, #00cc6a);
|
| 73 |
color: #002110; border: none; border-radius: 10px;
|
| 74 |
font-size: 13px; font-weight: 700; letter-spacing: 0.08em;
|
|
|
|
| 77 |
display: flex; align-items: center; justify-content: center; gap: 8px;
|
| 78 |
}
|
| 79 |
.btn-capture:hover {
|
| 80 |
+
transform: translateY(-1px);
|
| 81 |
box-shadow: 0 0 36px rgba(0,255,156,0.5);
|
| 82 |
}
|
| 83 |
.btn-capture:active { transform: scale(0.98); }
|
| 84 |
+
.btn-capture:disabled {
|
| 85 |
+
opacity: 0.4; cursor: not-allowed; transform: none;
|
| 86 |
+
box-shadow: none;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/* Divider */
|
| 90 |
+
.divider {
|
| 91 |
+
display: flex; align-items: center; gap: 8px;
|
| 92 |
+
color: rgba(255,255,255,0.2); font-size: 10px; letter-spacing: 0.1em;
|
| 93 |
+
}
|
| 94 |
+
.divider::before, .divider::after {
|
| 95 |
+
content: ''; flex: 1; height: 1px;
|
| 96 |
+
background: rgba(255,255,255,0.07);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
/* URL input */
|
| 100 |
+
.url-row { display: flex; gap: 6px; }
|
| 101 |
+
.url-input {
|
| 102 |
+
flex: 1; padding: 8px 10px;
|
| 103 |
+
background: rgba(255,255,255,0.04);
|
| 104 |
+
border: 1px solid rgba(255,255,255,0.1);
|
| 105 |
+
border-radius: 7px; color: #dae5da;
|
| 106 |
+
font-size: 11px; font-family: monospace;
|
| 107 |
+
outline: none; transition: border-color 0.2s;
|
| 108 |
+
}
|
| 109 |
+
.url-input::placeholder { color: rgba(255,255,255,0.2); }
|
| 110 |
+
.url-input:focus { border-color: rgba(0,255,156,0.4); }
|
| 111 |
+
.btn-url {
|
| 112 |
+
padding: 8px 12px;
|
| 113 |
+
background: rgba(0,255,156,0.1);
|
| 114 |
+
border: 1px solid rgba(0,255,156,0.25);
|
| 115 |
+
border-radius: 7px; color: #00ff9c;
|
| 116 |
+
font-size: 11px; font-weight: 600; cursor: pointer;
|
| 117 |
+
transition: all 0.2s; white-space: nowrap;
|
| 118 |
+
}
|
| 119 |
+
.btn-url:hover { background: rgba(0,255,156,0.18); }
|
| 120 |
+
.btn-url:disabled { opacity: 0.4; cursor: not-allowed; }
|
| 121 |
|
| 122 |
/* How it works */
|
| 123 |
.how-it-works {
|
| 124 |
background: rgba(255,255,255,0.02);
|
| 125 |
border: 1px solid rgba(255,255,255,0.05);
|
| 126 |
+
border-radius: 8px; padding: 9px 12px;
|
| 127 |
}
|
| 128 |
.how-label {
|
| 129 |
font-size: 9px; font-weight: 700; letter-spacing: 0.15em;
|
| 130 |
+
color: rgba(255,255,255,0.25); text-transform: uppercase; margin-bottom: 7px;
|
| 131 |
}
|
| 132 |
.how-step {
|
| 133 |
display: flex; align-items: center; gap: 8px;
|
| 134 |
+
font-size: 10px; color: rgba(255,255,255,0.4); padding: 2px 0;
|
|
|
|
| 135 |
}
|
| 136 |
.how-step-num {
|
| 137 |
width: 16px; height: 16px; border-radius: 50%;
|
|
|
|
| 144 |
#last-result {
|
| 145 |
background: rgba(255,255,255,0.03);
|
| 146 |
border: 1px solid rgba(255,255,255,0.07);
|
| 147 |
+
border-radius: 8px; padding: 9px 12px;
|
| 148 |
display: none;
|
| 149 |
}
|
| 150 |
.result-label {
|
| 151 |
font-size: 9px; font-weight: 700; letter-spacing: 0.15em;
|
| 152 |
+
color: rgba(255,255,255,0.25); text-transform: uppercase; margin-bottom: 5px;
|
| 153 |
}
|
| 154 |
.result-row { display: flex; align-items: center; justify-content: space-between; }
|
| 155 |
.result-verdict { font-size: 13px; font-weight: 700; letter-spacing: 0.06em; }
|
| 156 |
.result-conf { font-size: 16px; font-weight: 700; font-family: monospace; }
|
| 157 |
.result-bar-wrap {
|
| 158 |
height: 3px; background: rgba(255,255,255,0.06);
|
| 159 |
+
border-radius: 99px; overflow: hidden; margin: 5px 0;
|
| 160 |
}
|
| 161 |
.result-bar { height: 100%; border-radius: 99px; transition: width 0.8s ease; }
|
| 162 |
+
.result-file { font-size: 9px; color: rgba(255,255,255,0.2); font-family: monospace;
|
| 163 |
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
| 164 |
|
| 165 |
+
/* Footer */
|
| 166 |
.footer {
|
| 167 |
+
padding: 9px 16px;
|
| 168 |
border-top: 1px solid rgba(255,255,255,0.05);
|
| 169 |
display: flex; align-items: center; justify-content: space-between;
|
| 170 |
}
|
|
|
|
| 174 |
}
|
| 175 |
.footer-link:hover { color: #00ff9c; }
|
| 176 |
.model-info { font-size: 9px; color: rgba(255,255,255,0.2); letter-spacing: 0.06em; }
|
| 177 |
+
|
| 178 |
+
/* Offline warning */
|
| 179 |
+
#offline-warn {
|
| 180 |
+
display: none;
|
| 181 |
+
background: rgba(255,68,102,0.06);
|
| 182 |
+
border: 1px solid rgba(255,68,102,0.2);
|
| 183 |
+
border-radius: 7px; padding: 8px 10px;
|
| 184 |
+
font-size: 10px; color: rgba(255,68,102,0.8);
|
| 185 |
+
line-height: 1.5;
|
| 186 |
+
}
|
| 187 |
+
#offline-warn code {
|
| 188 |
+
display: block; margin-top: 4px;
|
| 189 |
+
font-family: monospace; color: rgba(255,68,102,0.6);
|
| 190 |
+
}
|
| 191 |
</style>
|
| 192 |
</head>
|
| 193 |
<body>
|
|
|
|
| 206 |
<div class="page-card">
|
| 207 |
<div class="page-icon">🎬</div>
|
| 208 |
<div style="flex:1;min-width:0;">
|
| 209 |
+
<div class="page-title" id="pageTitle">Loading…</div>
|
| 210 |
<div class="page-sub" id="pageSub">Detecting page</div>
|
| 211 |
</div>
|
| 212 |
</div>
|
| 213 |
|
| 214 |
+
<!-- Offline warning -->
|
| 215 |
+
<div id="offline-warn">
|
| 216 |
+
Backend not reachable.
|
| 217 |
+
<code>Visit: aarav13-authrix.hf.space</code>
|
| 218 |
+
</div>
|
| 219 |
+
|
| 220 |
+
<!-- Main capture button -->
|
| 221 |
<button class="btn-capture" id="btnCapture">
|
| 222 |
<span>⬤</span> Capture & Analyze Video
|
| 223 |
</button>
|
| 224 |
|
| 225 |
+
<!-- Divider -->
|
| 226 |
+
<div class="divider">OR PASTE URL</div>
|
| 227 |
+
|
| 228 |
+
<!-- URL input -->
|
| 229 |
+
<div class="url-row">
|
| 230 |
+
<input class="url-input" id="urlInput" type="url"
|
| 231 |
+
placeholder="https://example.com/video.mp4" />
|
| 232 |
+
<button class="btn-url" id="btnUrl">Analyze</button>
|
| 233 |
+
</div>
|
| 234 |
+
|
| 235 |
<!-- How it works -->
|
| 236 |
<div class="how-it-works">
|
| 237 |
<div class="how-label">How it works</div>
|
| 238 |
+
<div class="how-step"><div class="how-step-num">1</div>Records 20s of the playing video</div>
|
| 239 |
<div class="how-step"><div class="how-step-num">2</div>Sends to local AI for analysis</div>
|
| 240 |
+
<div class="how-step"><div class="how-step-num">3</div>Shows FAKE / REAL verdict on page</div>
|
| 241 |
</div>
|
| 242 |
|
| 243 |
<!-- Last result -->
|
extension/popup.js
CHANGED
|
@@ -1,35 +1,45 @@
|
|
| 1 |
/**
|
| 2 |
-
* Authrix Extension — Popup Script
|
| 3 |
-
* Simple popup: check health, show status, trigger capture on click.
|
| 4 |
*/
|
| 5 |
|
| 6 |
-
const API_BASE = '
|
| 7 |
|
| 8 |
document.addEventListener('DOMContentLoaded', async () => {
|
| 9 |
-
await checkHealth();
|
| 10 |
-
loadLastResult();
|
| 11 |
-
wireButtons();
|
| 12 |
updatePageInfo();
|
|
|
|
|
|
|
| 13 |
});
|
| 14 |
|
| 15 |
// ── Health check ──────────────────────────────────────────────────────────────
|
| 16 |
async function checkHealth() {
|
| 17 |
const badge = document.getElementById('statusBadge');
|
| 18 |
const modelInfo = document.getElementById('modelInfo');
|
|
|
|
|
|
|
| 19 |
try {
|
| 20 |
-
const res = await fetch(`${API_BASE}/health`);
|
| 21 |
const d = await res.json();
|
|
|
|
| 22 |
if (d.ready) {
|
| 23 |
badge.textContent = 'ONLINE';
|
| 24 |
-
badge.classList.remove('offline');
|
| 25 |
modelInfo.textContent = (d.model || 'VIT ENSEMBLE').toUpperCase().slice(0, 22);
|
|
|
|
| 26 |
return true;
|
| 27 |
}
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
return false;
|
|
|
|
| 30 |
} catch {
|
| 31 |
badge.textContent = 'OFFLINE';
|
| 32 |
badge.classList.add('offline');
|
|
|
|
|
|
|
| 33 |
return false;
|
| 34 |
}
|
| 35 |
}
|
|
@@ -41,8 +51,11 @@ async function updatePageInfo() {
|
|
| 41 |
try {
|
| 42 |
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
| 43 |
if (tab?.title) {
|
| 44 |
-
title.textContent = tab.title.slice(0,
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
} catch {
|
| 48 |
title.textContent = 'Current page';
|
|
@@ -51,14 +64,32 @@ async function updatePageInfo() {
|
|
| 51 |
}
|
| 52 |
|
| 53 |
// ── Wire buttons ──────────────────────────────────────────────────────────────
|
| 54 |
-
function wireButtons() {
|
| 55 |
-
document.getElementById('btnCapture')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
| 57 |
if (!tab?.id) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
});
|
| 63 |
}
|
| 64 |
|
|
@@ -66,22 +97,30 @@ function wireButtons() {
|
|
| 66 |
function loadLastResult() {
|
| 67 |
chrome.storage.local.get('lastResult', ({ lastResult }) => {
|
| 68 |
if (!lastResult) return;
|
|
|
|
| 69 |
const el = document.getElementById('last-result');
|
| 70 |
el.style.display = 'block';
|
| 71 |
|
| 72 |
const isFake = lastResult.result === 'FAKE';
|
| 73 |
const color = isFake ? '#ff4466' : '#00ff9c';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
|
| 75 |
-
document.getElementById('
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
document.getElementById('lastConf').style.color = color;
|
| 79 |
-
document.getElementById('lastFile').textContent = lastResult.file || 'Tab capture';
|
| 80 |
|
| 81 |
const bar = document.getElementById('lastBar');
|
| 82 |
bar.style.background = isFake
|
| 83 |
? 'linear-gradient(90deg,#880022,#ff4466)'
|
| 84 |
: 'linear-gradient(90deg,#006633,#00ff9c)';
|
| 85 |
-
setTimeout(() => { bar.style.width =
|
| 86 |
});
|
| 87 |
}
|
|
|
|
| 1 |
/**
|
| 2 |
+
* Authrix Extension — Popup Script v3
|
|
|
|
| 3 |
*/
|
| 4 |
|
| 5 |
+
const API_BASE = 'https://aarav13-authrix.hf.space';
|
| 6 |
|
| 7 |
document.addEventListener('DOMContentLoaded', async () => {
|
| 8 |
+
const online = await checkHealth();
|
|
|
|
|
|
|
| 9 |
updatePageInfo();
|
| 10 |
+
loadLastResult();
|
| 11 |
+
wireButtons(online);
|
| 12 |
});
|
| 13 |
|
| 14 |
// ── Health check ──────────────────────────────────────────────────────────────
|
| 15 |
async function checkHealth() {
|
| 16 |
const badge = document.getElementById('statusBadge');
|
| 17 |
const modelInfo = document.getElementById('modelInfo');
|
| 18 |
+
const warn = document.getElementById('offline-warn');
|
| 19 |
+
|
| 20 |
try {
|
| 21 |
+
const res = await fetch(`${API_BASE}/health`, { signal: AbortSignal.timeout(3000) });
|
| 22 |
const d = await res.json();
|
| 23 |
+
|
| 24 |
if (d.ready) {
|
| 25 |
badge.textContent = 'ONLINE';
|
| 26 |
+
badge.classList.remove('offline', 'loading');
|
| 27 |
modelInfo.textContent = (d.model || 'VIT ENSEMBLE').toUpperCase().slice(0, 22);
|
| 28 |
+
if (warn) warn.style.display = 'none';
|
| 29 |
return true;
|
| 30 |
}
|
| 31 |
+
|
| 32 |
+
badge.textContent = 'LOADING…';
|
| 33 |
+
badge.classList.remove('offline');
|
| 34 |
+
badge.classList.add('loading');
|
| 35 |
+
if (warn) warn.style.display = 'none';
|
| 36 |
return false;
|
| 37 |
+
|
| 38 |
} catch {
|
| 39 |
badge.textContent = 'OFFLINE';
|
| 40 |
badge.classList.add('offline');
|
| 41 |
+
badge.classList.remove('loading');
|
| 42 |
+
if (warn) warn.style.display = 'block';
|
| 43 |
return false;
|
| 44 |
}
|
| 45 |
}
|
|
|
|
| 51 |
try {
|
| 52 |
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
| 53 |
if (tab?.title) {
|
| 54 |
+
title.textContent = tab.title.length > 44 ? tab.title.slice(0, 41) + '…' : tab.title;
|
| 55 |
+
}
|
| 56 |
+
if (tab?.url) {
|
| 57 |
+
try { sub.textContent = new URL(tab.url).hostname; }
|
| 58 |
+
catch { sub.textContent = 'Ready to capture'; }
|
| 59 |
}
|
| 60 |
} catch {
|
| 61 |
title.textContent = 'Current page';
|
|
|
|
| 64 |
}
|
| 65 |
|
| 66 |
// ── Wire buttons ──────────────────────────────────────────────────────────────
|
| 67 |
+
function wireButtons(online) {
|
| 68 |
+
const btnCapture = document.getElementById('btnCapture');
|
| 69 |
+
const btnUrl = document.getElementById('btnUrl');
|
| 70 |
+
const urlInput = document.getElementById('urlInput');
|
| 71 |
+
|
| 72 |
+
// Capture button
|
| 73 |
+
btnCapture.addEventListener('click', async () => {
|
| 74 |
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
| 75 |
if (!tab?.id) return;
|
| 76 |
+
chrome.runtime.sendMessage({ type: 'START_CAPTURE', tabId: tab.id });
|
| 77 |
+
window.close();
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
// URL analyze button
|
| 81 |
+
btnUrl.addEventListener('click', async () => {
|
| 82 |
+
const url = urlInput.value.trim();
|
| 83 |
+
if (!url) { urlInput.focus(); return; }
|
| 84 |
+
|
| 85 |
+
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
| 86 |
+
chrome.runtime.sendMessage({ type: 'ANALYZE_URL', url, tabId: tab?.id });
|
| 87 |
+
window.close();
|
| 88 |
+
});
|
| 89 |
|
| 90 |
+
// Allow Enter key in URL input
|
| 91 |
+
urlInput.addEventListener('keydown', e => {
|
| 92 |
+
if (e.key === 'Enter') btnUrl.click();
|
| 93 |
});
|
| 94 |
}
|
| 95 |
|
|
|
|
| 97 |
function loadLastResult() {
|
| 98 |
chrome.storage.local.get('lastResult', ({ lastResult }) => {
|
| 99 |
if (!lastResult) return;
|
| 100 |
+
|
| 101 |
const el = document.getElementById('last-result');
|
| 102 |
el.style.display = 'block';
|
| 103 |
|
| 104 |
const isFake = lastResult.result === 'FAKE';
|
| 105 |
const color = isFake ? '#ff4466' : '#00ff9c';
|
| 106 |
+
const conf = lastResult.confidence ?? 0;
|
| 107 |
+
|
| 108 |
+
const verdict = document.getElementById('lastVerdict');
|
| 109 |
+
verdict.textContent = lastResult.result;
|
| 110 |
+
verdict.style.color = color;
|
| 111 |
+
|
| 112 |
+
const confEl = document.getElementById('lastConf');
|
| 113 |
+
confEl.textContent = conf + '%';
|
| 114 |
+
confEl.style.color = color;
|
| 115 |
|
| 116 |
+
const fileEl = document.getElementById('lastFile');
|
| 117 |
+
const fileStr = lastResult.file || 'Tab capture';
|
| 118 |
+
fileEl.textContent = fileStr.length > 50 ? fileStr.slice(0, 47) + '…' : fileStr;
|
|
|
|
|
|
|
| 119 |
|
| 120 |
const bar = document.getElementById('lastBar');
|
| 121 |
bar.style.background = isFake
|
| 122 |
? 'linear-gradient(90deg,#880022,#ff4466)'
|
| 123 |
: 'linear-gradient(90deg,#006633,#00ff9c)';
|
| 124 |
+
setTimeout(() => { bar.style.width = conf + '%'; }, 80);
|
| 125 |
});
|
| 126 |
}
|
frontend-vanilla/index.html
CHANGED
|
@@ -276,6 +276,7 @@
|
|
| 276 |
<span onclick="showState('heroSection')" class="text-2xl font-black tracking-tighter text-emerald-400 drop-shadow-[0_0_8px_rgba(0,255,156,0.5)] cursor-pointer">AUTHRIX AI</span>
|
| 277 |
<div class="hidden md:flex items-center gap-6">
|
| 278 |
<a onclick="showState('heroSection')" class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm cursor-pointer">Dashboard</a>
|
|
|
|
| 279 |
<a onclick="openModal('agentsModal')" class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm cursor-pointer">Agents</a>
|
| 280 |
<a onclick="openModal('logsModal')" class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm cursor-pointer">Logs</a>
|
| 281 |
<a onclick="openModal('networkModal')" class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm cursor-pointer">Network</a>
|
|
|
|
| 276 |
<span onclick="showState('heroSection')" class="text-2xl font-black tracking-tighter text-emerald-400 drop-shadow-[0_0_8px_rgba(0,255,156,0.5)] cursor-pointer">AUTHRIX AI</span>
|
| 277 |
<div class="hidden md:flex items-center gap-6">
|
| 278 |
<a onclick="showState('heroSection')" class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm cursor-pointer">Dashboard</a>
|
| 279 |
+
<a href="/pricing" class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm cursor-pointer">Pricing</a>
|
| 280 |
<a onclick="openModal('agentsModal')" class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm cursor-pointer">Agents</a>
|
| 281 |
<a onclick="openModal('logsModal')" class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm cursor-pointer">Logs</a>
|
| 282 |
<a onclick="openModal('networkModal')" class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 py-1 px-2 rounded-sm cursor-pointer">Network</a>
|
frontend-vanilla/pricing.html
ADDED
|
@@ -0,0 +1,470 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Authrix Pricing - AI-Powered Deepfake Detection</title>
|
| 7 |
+
<style>
|
| 8 |
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 9 |
+
|
| 10 |
+
body {
|
| 11 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
| 12 |
+
background: linear-gradient(135deg, #0a1f0f 0%, #0c150f 100%);
|
| 13 |
+
color: #dae5da;
|
| 14 |
+
min-height: 100vh;
|
| 15 |
+
padding: 40px 20px;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.container {
|
| 19 |
+
max-width: 1200px;
|
| 20 |
+
margin: 0 auto;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/* Header */
|
| 24 |
+
.header {
|
| 25 |
+
text-align: center;
|
| 26 |
+
margin-bottom: 60px;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
.logo {
|
| 30 |
+
display: inline-flex;
|
| 31 |
+
align-items: center;
|
| 32 |
+
gap: 12px;
|
| 33 |
+
font-size: 28px;
|
| 34 |
+
font-weight: 700;
|
| 35 |
+
letter-spacing: 0.12em;
|
| 36 |
+
color: #00ff9c;
|
| 37 |
+
text-shadow: 0 0 20px rgba(0,255,156,0.4);
|
| 38 |
+
margin-bottom: 20px;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.logo-dot {
|
| 42 |
+
width: 12px;
|
| 43 |
+
height: 12px;
|
| 44 |
+
border-radius: 50%;
|
| 45 |
+
background: #00ff9c;
|
| 46 |
+
box-shadow: 0 0 16px #00ff9c;
|
| 47 |
+
animation: pulse 2s ease-in-out infinite;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
@keyframes pulse {
|
| 51 |
+
0%, 100% { opacity: 1; transform: scale(1); }
|
| 52 |
+
50% { opacity: 0.6; transform: scale(0.9); }
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
h1 {
|
| 56 |
+
font-size: 48px;
|
| 57 |
+
margin-bottom: 16px;
|
| 58 |
+
background: linear-gradient(135deg, #00ff9c, #00cc6a);
|
| 59 |
+
-webkit-background-clip: text;
|
| 60 |
+
-webkit-text-fill-color: transparent;
|
| 61 |
+
background-clip: text;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.subtitle {
|
| 65 |
+
font-size: 20px;
|
| 66 |
+
color: rgba(255,255,255,0.6);
|
| 67 |
+
max-width: 600px;
|
| 68 |
+
margin: 0 auto;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
/* Pricing Cards */
|
| 72 |
+
.pricing-grid {
|
| 73 |
+
display: grid;
|
| 74 |
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
| 75 |
+
gap: 30px;
|
| 76 |
+
margin-bottom: 60px;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
.pricing-card {
|
| 80 |
+
background: rgba(255,255,255,0.03);
|
| 81 |
+
border: 1px solid rgba(255,255,255,0.1);
|
| 82 |
+
border-radius: 16px;
|
| 83 |
+
padding: 40px 30px;
|
| 84 |
+
position: relative;
|
| 85 |
+
transition: all 0.3s ease;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
.pricing-card:hover {
|
| 89 |
+
transform: translateY(-8px);
|
| 90 |
+
border-color: rgba(0,255,156,0.3);
|
| 91 |
+
box-shadow: 0 20px 60px rgba(0,255,156,0.1);
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.pricing-card.popular {
|
| 95 |
+
border-color: #00ff9c;
|
| 96 |
+
box-shadow: 0 0 40px rgba(0,255,156,0.2);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.popular-badge {
|
| 100 |
+
position: absolute;
|
| 101 |
+
top: -12px;
|
| 102 |
+
right: 30px;
|
| 103 |
+
background: linear-gradient(135deg, #00ff9c, #00cc6a);
|
| 104 |
+
color: #002110;
|
| 105 |
+
padding: 6px 16px;
|
| 106 |
+
border-radius: 20px;
|
| 107 |
+
font-size: 12px;
|
| 108 |
+
font-weight: 700;
|
| 109 |
+
letter-spacing: 0.08em;
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
.plan-name {
|
| 113 |
+
font-size: 24px;
|
| 114 |
+
font-weight: 700;
|
| 115 |
+
margin-bottom: 12px;
|
| 116 |
+
color: #00ff9c;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
.plan-price {
|
| 120 |
+
font-size: 48px;
|
| 121 |
+
font-weight: 700;
|
| 122 |
+
margin-bottom: 8px;
|
| 123 |
+
font-family: monospace;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
.plan-price span {
|
| 127 |
+
font-size: 18px;
|
| 128 |
+
color: rgba(255,255,255,0.5);
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
.plan-interval {
|
| 132 |
+
font-size: 14px;
|
| 133 |
+
color: rgba(255,255,255,0.4);
|
| 134 |
+
margin-bottom: 24px;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
.plan-features {
|
| 138 |
+
list-style: none;
|
| 139 |
+
margin-bottom: 30px;
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
.plan-features li {
|
| 143 |
+
padding: 12px 0;
|
| 144 |
+
border-bottom: 1px solid rgba(255,255,255,0.05);
|
| 145 |
+
display: flex;
|
| 146 |
+
align-items: center;
|
| 147 |
+
gap: 12px;
|
| 148 |
+
font-size: 14px;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
.plan-features li:last-child {
|
| 152 |
+
border-bottom: none;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
.check {
|
| 156 |
+
color: #00ff9c;
|
| 157 |
+
font-size: 18px;
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
.cta-button {
|
| 161 |
+
width: 100%;
|
| 162 |
+
padding: 16px;
|
| 163 |
+
border: none;
|
| 164 |
+
border-radius: 10px;
|
| 165 |
+
font-size: 16px;
|
| 166 |
+
font-weight: 700;
|
| 167 |
+
cursor: pointer;
|
| 168 |
+
transition: all 0.3s ease;
|
| 169 |
+
letter-spacing: 0.06em;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
.cta-button.primary {
|
| 173 |
+
background: linear-gradient(135deg, #00ff9c, #00cc6a);
|
| 174 |
+
color: #002110;
|
| 175 |
+
box-shadow: 0 8px 24px rgba(0,255,156,0.3);
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.cta-button.primary:hover {
|
| 179 |
+
transform: translateY(-2px);
|
| 180 |
+
box-shadow: 0 12px 32px rgba(0,255,156,0.4);
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
.cta-button.secondary {
|
| 184 |
+
background: rgba(255,255,255,0.05);
|
| 185 |
+
color: #00ff9c;
|
| 186 |
+
border: 1px solid rgba(0,255,156,0.3);
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
.cta-button.secondary:hover {
|
| 190 |
+
background: rgba(0,255,156,0.1);
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
/* Enterprise Section */
|
| 194 |
+
.enterprise-section {
|
| 195 |
+
background: rgba(0,255,156,0.05);
|
| 196 |
+
border: 1px solid rgba(0,255,156,0.2);
|
| 197 |
+
border-radius: 16px;
|
| 198 |
+
padding: 50px;
|
| 199 |
+
text-align: center;
|
| 200 |
+
margin-bottom: 60px;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
.enterprise-section h2 {
|
| 204 |
+
font-size: 36px;
|
| 205 |
+
margin-bottom: 16px;
|
| 206 |
+
color: #00ff9c;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.enterprise-section p {
|
| 210 |
+
font-size: 18px;
|
| 211 |
+
color: rgba(255,255,255,0.6);
|
| 212 |
+
margin-bottom: 30px;
|
| 213 |
+
max-width: 700px;
|
| 214 |
+
margin-left: auto;
|
| 215 |
+
margin-right: auto;
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
.enterprise-features {
|
| 219 |
+
display: grid;
|
| 220 |
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
| 221 |
+
gap: 20px;
|
| 222 |
+
margin-bottom: 40px;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
.enterprise-feature {
|
| 226 |
+
background: rgba(255,255,255,0.03);
|
| 227 |
+
padding: 20px;
|
| 228 |
+
border-radius: 10px;
|
| 229 |
+
border: 1px solid rgba(255,255,255,0.1);
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
.enterprise-feature h3 {
|
| 233 |
+
font-size: 18px;
|
| 234 |
+
margin-bottom: 8px;
|
| 235 |
+
color: #00ff9c;
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
.enterprise-feature p {
|
| 239 |
+
font-size: 14px;
|
| 240 |
+
color: rgba(255,255,255,0.5);
|
| 241 |
+
margin: 0;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
/* FAQ */
|
| 245 |
+
.faq-section {
|
| 246 |
+
max-width: 800px;
|
| 247 |
+
margin: 0 auto;
|
| 248 |
+
}
|
| 249 |
+
|
| 250 |
+
.faq-section h2 {
|
| 251 |
+
text-align: center;
|
| 252 |
+
font-size: 36px;
|
| 253 |
+
margin-bottom: 40px;
|
| 254 |
+
color: #00ff9c;
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
.faq-item {
|
| 258 |
+
background: rgba(255,255,255,0.03);
|
| 259 |
+
border: 1px solid rgba(255,255,255,0.1);
|
| 260 |
+
border-radius: 10px;
|
| 261 |
+
padding: 24px;
|
| 262 |
+
margin-bottom: 16px;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.faq-question {
|
| 266 |
+
font-size: 18px;
|
| 267 |
+
font-weight: 600;
|
| 268 |
+
margin-bottom: 12px;
|
| 269 |
+
color: #00ff9c;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
.faq-answer {
|
| 273 |
+
font-size: 15px;
|
| 274 |
+
line-height: 1.6;
|
| 275 |
+
color: rgba(255,255,255,0.6);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
/* Footer */
|
| 279 |
+
.footer {
|
| 280 |
+
text-align: center;
|
| 281 |
+
margin-top: 80px;
|
| 282 |
+
padding-top: 40px;
|
| 283 |
+
border-top: 1px solid rgba(255,255,255,0.1);
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
.footer-links {
|
| 287 |
+
display: flex;
|
| 288 |
+
justify-content: center;
|
| 289 |
+
gap: 30px;
|
| 290 |
+
margin-bottom: 20px;
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
.footer-links a {
|
| 294 |
+
color: rgba(0,255,156,0.6);
|
| 295 |
+
text-decoration: none;
|
| 296 |
+
font-size: 14px;
|
| 297 |
+
transition: color 0.3s;
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
.footer-links a:hover {
|
| 301 |
+
color: #00ff9c;
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
.demo-badge {
|
| 305 |
+
display: inline-block;
|
| 306 |
+
background: rgba(255,170,0,0.1);
|
| 307 |
+
border: 1px solid rgba(255,170,0,0.3);
|
| 308 |
+
color: #ffaa00;
|
| 309 |
+
padding: 8px 16px;
|
| 310 |
+
border-radius: 20px;
|
| 311 |
+
font-size: 12px;
|
| 312 |
+
font-weight: 700;
|
| 313 |
+
letter-spacing: 0.08em;
|
| 314 |
+
margin-top: 20px;
|
| 315 |
+
}
|
| 316 |
+
</style>
|
| 317 |
+
</head>
|
| 318 |
+
<body>
|
| 319 |
+
<div class="container">
|
| 320 |
+
<!-- Header -->
|
| 321 |
+
<div class="header">
|
| 322 |
+
<div class="logo">
|
| 323 |
+
<div class="logo-dot"></div>
|
| 324 |
+
<a href="/" style="text-decoration: none; color: inherit;">AUTHRIX AI</a>
|
| 325 |
+
</div>
|
| 326 |
+
<h1>Choose Your Plan</h1>
|
| 327 |
+
<p class="subtitle">Protect yourself from deepfakes with AI-powered detection. Start free, upgrade anytime.</p>
|
| 328 |
+
</div>
|
| 329 |
+
|
| 330 |
+
<!-- Pricing Cards -->
|
| 331 |
+
<div class="pricing-grid">
|
| 332 |
+
<!-- Free -->
|
| 333 |
+
<div class="pricing-card">
|
| 334 |
+
<div class="plan-name">Free</div>
|
| 335 |
+
<div class="plan-price">$0<span>/mo</span></div>
|
| 336 |
+
<div class="plan-interval">Perfect for trying out</div>
|
| 337 |
+
<ul class="plan-features">
|
| 338 |
+
<li><span class="check">✓</span> 10 video analyses/month</li>
|
| 339 |
+
<li><span class="check">✓</span> Browser extension</li>
|
| 340 |
+
<li><span class="check">✓</span> Max 2-minute videos</li>
|
| 341 |
+
<li><span class="check">✓</span> Community support</li>
|
| 342 |
+
</ul>
|
| 343 |
+
<button class="cta-button secondary" onclick="showDemo('free')">Get Started Free</button>
|
| 344 |
+
</div>
|
| 345 |
+
|
| 346 |
+
<!-- Pro -->
|
| 347 |
+
<div class="pricing-card popular">
|
| 348 |
+
<div class="popular-badge">MOST POPULAR</div>
|
| 349 |
+
<div class="plan-name">Pro</div>
|
| 350 |
+
<div class="plan-price">$9.99<span>/mo</span></div>
|
| 351 |
+
<div class="plan-interval">For individuals & creators</div>
|
| 352 |
+
<ul class="plan-features">
|
| 353 |
+
<li><span class="check">✓</span> 100 analyses/month</li>
|
| 354 |
+
<li><span class="check">✓</span> Up to 10-minute videos</li>
|
| 355 |
+
<li><span class="check">✓</span> API access (100 calls/mo)</li>
|
| 356 |
+
<li><span class="check">✓</span> Priority processing</li>
|
| 357 |
+
<li><span class="check">✓</span> Email support</li>
|
| 358 |
+
<li><span class="check">✓</span> Batch upload</li>
|
| 359 |
+
</ul>
|
| 360 |
+
<button class="cta-button primary" onclick="showDemo('pro')">Subscribe to Pro</button>
|
| 361 |
+
</div>
|
| 362 |
+
|
| 363 |
+
<!-- Business -->
|
| 364 |
+
<div class="pricing-card">
|
| 365 |
+
<div class="plan-name">Business</div>
|
| 366 |
+
<div class="plan-price">$49<span>/mo</span></div>
|
| 367 |
+
<div class="plan-interval">For teams & organizations</div>
|
| 368 |
+
<ul class="plan-features">
|
| 369 |
+
<li><span class="check">✓</span> 1,000 analyses/month</li>
|
| 370 |
+
<li><span class="check">✓</span> Unlimited video length</li>
|
| 371 |
+
<li><span class="check">✓</span> API access (5K calls/mo)</li>
|
| 372 |
+
<li><span class="check">✓</span> White-label reports</li>
|
| 373 |
+
<li><span class="check">✓</span> Slack/Teams integration</li>
|
| 374 |
+
<li><span class="check">✓</span> Priority support</li>
|
| 375 |
+
<li><span class="check">✓</span> Custom branding</li>
|
| 376 |
+
</ul>
|
| 377 |
+
<button class="cta-button primary" onclick="showDemo('business')">Subscribe to Business</button>
|
| 378 |
+
</div>
|
| 379 |
+
</div>
|
| 380 |
+
|
| 381 |
+
<!-- Enterprise Section -->
|
| 382 |
+
<div class="enterprise-section">
|
| 383 |
+
<h2>Enterprise Solutions</h2>
|
| 384 |
+
<p>Custom solutions for large organizations, social media platforms, and government agencies</p>
|
| 385 |
+
|
| 386 |
+
<div class="enterprise-features">
|
| 387 |
+
<div class="enterprise-feature">
|
| 388 |
+
<h3>🏢 On-Premise Deployment</h3>
|
| 389 |
+
<p>Deploy Authrix on your own infrastructure for maximum security</p>
|
| 390 |
+
</div>
|
| 391 |
+
<div class="enterprise-feature">
|
| 392 |
+
<h3>🎯 Custom Model Training</h3>
|
| 393 |
+
<p>Train models on your specific content and use cases</p>
|
| 394 |
+
</div>
|
| 395 |
+
<div class="enterprise-feature">
|
| 396 |
+
<h3>⚡ Unlimited Analyses</h3>
|
| 397 |
+
<p>No limits on video analyses or API calls</p>
|
| 398 |
+
</div>
|
| 399 |
+
<div class="enterprise-feature">
|
| 400 |
+
<h3>🛡️ SLA Guarantees</h3>
|
| 401 |
+
<p>99.9% uptime with dedicated support team</p>
|
| 402 |
+
</div>
|
| 403 |
+
</div>
|
| 404 |
+
|
| 405 |
+
<button class="cta-button primary" onclick="showDemo('enterprise')" style="max-width: 300px; margin: 0 auto;">Contact Sales</button>
|
| 406 |
+
</div>
|
| 407 |
+
|
| 408 |
+
<!-- FAQ -->
|
| 409 |
+
<div class="faq-section">
|
| 410 |
+
<h2>Frequently Asked Questions</h2>
|
| 411 |
+
|
| 412 |
+
<div class="faq-item">
|
| 413 |
+
<div class="faq-question">How accurate is Authrix?</div>
|
| 414 |
+
<div class="faq-answer">Authrix uses an ensemble of state-of-the-art ViT models achieving 99%+ accuracy on benchmark datasets. We combine visual analysis with audio detection for comprehensive deepfake identification.</div>
|
| 415 |
+
</div>
|
| 416 |
+
|
| 417 |
+
<div class="faq-item">
|
| 418 |
+
<div class="faq-question">Can I cancel anytime?</div>
|
| 419 |
+
<div class="faq-answer">Yes! All subscriptions are month-to-month with no long-term commitment. Cancel anytime from your account dashboard.</div>
|
| 420 |
+
</div>
|
| 421 |
+
|
| 422 |
+
<div class="faq-item">
|
| 423 |
+
<div class="faq-question">What video formats are supported?</div>
|
| 424 |
+
<div class="faq-answer">We support MP4, AVI, MOV, MKV, WebM, and WMV formats. Maximum file size is 100MB for Pro tier, unlimited for Business and Enterprise.</div>
|
| 425 |
+
</div>
|
| 426 |
+
|
| 427 |
+
<div class="faq-item">
|
| 428 |
+
<div class="faq-question">Is my data secure?</div>
|
| 429 |
+
<div class="faq-answer">Absolutely. All videos are analyzed locally and deleted immediately after processing. We never store your content or share it with third parties.</div>
|
| 430 |
+
</div>
|
| 431 |
+
|
| 432 |
+
<div class="faq-item">
|
| 433 |
+
<div class="faq-question">Do you offer refunds?</div>
|
| 434 |
+
<div class="faq-answer">Yes, we offer a 30-day money-back guarantee. If you're not satisfied, contact support for a full refund.</div>
|
| 435 |
+
</div>
|
| 436 |
+
</div>
|
| 437 |
+
|
| 438 |
+
<!-- Footer -->
|
| 439 |
+
<div class="footer">
|
| 440 |
+
<div class="footer-links">
|
| 441 |
+
<a href="/">Home</a>
|
| 442 |
+
<a href="/pricing.html">Pricing</a>
|
| 443 |
+
<a href="https://docs.authrix.ai">API Docs</a>
|
| 444 |
+
<a href="mailto:support@authrix.ai">Support</a>
|
| 445 |
+
<a href="/terms.html">Terms</a>
|
| 446 |
+
<a href="/privacy.html">Privacy</a>
|
| 447 |
+
</div>
|
| 448 |
+
<p style="color: rgba(255,255,255,0.3); font-size: 14px;">
|
| 449 |
+
© 2026 Authrix AI. All rights reserved.
|
| 450 |
+
</p>
|
| 451 |
+
</div>
|
| 452 |
+
</div>
|
| 453 |
+
|
| 454 |
+
<script>
|
| 455 |
+
function showDemo(plan) {
|
| 456 |
+
const messages = {
|
| 457 |
+
free: 'Free tier selected! In production, this would redirect to signup.',
|
| 458 |
+
pro: 'Pro plan selected ($9.99/mo)! In production, this would open Stripe checkout.',
|
| 459 |
+
business: 'Business plan selected ($49/mo)! In production, this would open Stripe checkout.',
|
| 460 |
+
enterprise: 'Enterprise inquiry! In production, this would open a contact form or schedule a demo call.'
|
| 461 |
+
};
|
| 462 |
+
|
| 463 |
+
alert(messages[plan] + '\n\n💡 This is a demo. Payment integration will be added in production.');
|
| 464 |
+
|
| 465 |
+
// In production, this would be:
|
| 466 |
+
// window.location.href = `/checkout?plan=${plan}`;
|
| 467 |
+
}
|
| 468 |
+
</script>
|
| 469 |
+
</body>
|
| 470 |
+
</html>
|
render.yaml
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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"
|
stitch_authrix_deepfake_detection_engine/authrix_analysis_results/code.html
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
|
| 3 |
+
<html class="dark" lang="en"><head>
|
| 4 |
+
<meta charset="utf-8"/>
|
| 5 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 6 |
+
<title>AUTHRIX AI - Results Dashboard</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700;900&display=swap" rel="stylesheet"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
"colors": {
|
| 17 |
+
"inverse-surface": "#dae5da",
|
| 18 |
+
"on-error": "#690005",
|
| 19 |
+
"surface-tint": "#00e38a",
|
| 20 |
+
"error": "#ffb4ab",
|
| 21 |
+
"outline-variant": "#3b4b3f",
|
| 22 |
+
"outline": "#849587",
|
| 23 |
+
"background": "#0c150f",
|
| 24 |
+
"surface-container-high": "#232c25",
|
| 25 |
+
"secondary-container": "#00e3fd",
|
| 26 |
+
"on-surface-variant": "#b9cbbc",
|
| 27 |
+
"on-secondary-container": "#00616d",
|
| 28 |
+
"on-error-container": "#ffdad6",
|
| 29 |
+
"primary": "#f3fff3",
|
| 30 |
+
"primary-container": "#00ff9c",
|
| 31 |
+
"on-primary": "#00391f",
|
| 32 |
+
"surface-bright": "#323c34",
|
| 33 |
+
"primary-fixed-dim": "#00e38a",
|
| 34 |
+
"tertiary-fixed-dim": "#e4c44f",
|
| 35 |
+
"on-secondary-fixed": "#001f24",
|
| 36 |
+
"tertiary-fixed": "#ffe17a",
|
| 37 |
+
"on-tertiary-container": "#766000",
|
| 38 |
+
"on-secondary": "#00363d",
|
| 39 |
+
"on-tertiary-fixed": "#231b00",
|
| 40 |
+
"on-tertiary": "#3b2f00",
|
| 41 |
+
"surface-container-low": "#141e17",
|
| 42 |
+
"surface": "#0c150f",
|
| 43 |
+
"primary-fixed": "#56ffa7",
|
| 44 |
+
"secondary-fixed-dim": "#00daf3",
|
| 45 |
+
"on-secondary-fixed-variant": "#004f58",
|
| 46 |
+
"inverse-on-surface": "#29332b",
|
| 47 |
+
"surface-container": "#18221b",
|
| 48 |
+
"on-surface": "#dae5da",
|
| 49 |
+
"on-tertiary-fixed-variant": "#554500",
|
| 50 |
+
"error-container": "#93000a",
|
| 51 |
+
"surface-variant": "#2d3730",
|
| 52 |
+
"surface-container-highest": "#2d3730",
|
| 53 |
+
"secondary-fixed": "#9cf0ff",
|
| 54 |
+
"on-background": "#dae5da",
|
| 55 |
+
"on-primary-container": "#007142",
|
| 56 |
+
"inverse-primary": "#006d40",
|
| 57 |
+
"on-primary-fixed-variant": "#00522f",
|
| 58 |
+
"secondary": "#bdf4ff",
|
| 59 |
+
"tertiary": "#fffaff",
|
| 60 |
+
"surface-dim": "#0c150f",
|
| 61 |
+
"on-primary-fixed": "#002110",
|
| 62 |
+
"surface-container-lowest": "#07100a",
|
| 63 |
+
"tertiary-container": "#ffdd65"
|
| 64 |
+
},
|
| 65 |
+
"borderRadius": {
|
| 66 |
+
"DEFAULT": "0.25rem",
|
| 67 |
+
"lg": "0.5rem",
|
| 68 |
+
"xl": "0.75rem",
|
| 69 |
+
"full": "9999px"
|
| 70 |
+
},
|
| 71 |
+
"spacing": {
|
| 72 |
+
"xs": "4px",
|
| 73 |
+
"md": "16px",
|
| 74 |
+
"base": "4px",
|
| 75 |
+
"sm": "8px",
|
| 76 |
+
"xl": "48px",
|
| 77 |
+
"lg": "24px",
|
| 78 |
+
"gutter": "20px",
|
| 79 |
+
"margin": "32px"
|
| 80 |
+
},
|
| 81 |
+
"fontFamily": {
|
| 82 |
+
"h2": ["Space Grotesk"],
|
| 83 |
+
"h1": ["Space Grotesk"],
|
| 84 |
+
"h3": ["Space Grotesk"],
|
| 85 |
+
"label-xs": ["Space Grotesk"],
|
| 86 |
+
"data-mono": ["Space Grotesk"],
|
| 87 |
+
"body-base": ["Space Grotesk"]
|
| 88 |
+
},
|
| 89 |
+
"fontSize": {
|
| 90 |
+
"h2": ["32px", { "lineHeight": "1.2", "letterSpacing": "-0.01em", "fontWeight": "600" }],
|
| 91 |
+
"h1": ["48px", { "lineHeight": "1.1", "letterSpacing": "-0.02em", "fontWeight": "700" }],
|
| 92 |
+
"h3": ["24px", { "lineHeight": "1.3", "letterSpacing": "0em", "fontWeight": "600" }],
|
| 93 |
+
"label-xs": ["12px", { "lineHeight": "1", "letterSpacing": "0.1em", "fontWeight": "700" }],
|
| 94 |
+
"data-mono": ["14px", { "lineHeight": "1.5", "letterSpacing": "0.05em", "fontWeight": "500" }],
|
| 95 |
+
"body-base": ["16px", { "lineHeight": "1.6", "letterSpacing": "0.01em", "fontWeight": "400" }]
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
</script>
|
| 101 |
+
<style>
|
| 102 |
+
.glass-panel {
|
| 103 |
+
background-color: rgba(35, 44, 37, 0.2);
|
| 104 |
+
backdrop-filter: blur(20px);
|
| 105 |
+
box-shadow: inset 1px 1px 0 0 rgba(255, 255, 255, 0.05);
|
| 106 |
+
}
|
| 107 |
+
.glow-border-primary {
|
| 108 |
+
border: 1px solid rgba(0, 255, 156, 0.3);
|
| 109 |
+
box-shadow: 0 0 15px rgba(0, 255, 156, 0.1);
|
| 110 |
+
}
|
| 111 |
+
.glow-border-error {
|
| 112 |
+
border: 1px solid rgba(255, 180, 171, 0.3);
|
| 113 |
+
box-shadow: 0 0 15px rgba(255, 180, 171, 0.1);
|
| 114 |
+
}
|
| 115 |
+
.scanline {
|
| 116 |
+
position: absolute;
|
| 117 |
+
top: 0;
|
| 118 |
+
left: 0;
|
| 119 |
+
width: 100%;
|
| 120 |
+
height: 1px;
|
| 121 |
+
background: rgba(0, 255, 156, 0.1);
|
| 122 |
+
animation: scan 4s linear infinite;
|
| 123 |
+
}
|
| 124 |
+
@keyframes scan {
|
| 125 |
+
0% { top: 0; }
|
| 126 |
+
100% { top: 100%; }
|
| 127 |
+
}
|
| 128 |
+
.blinking-cursor::after {
|
| 129 |
+
content: '_';
|
| 130 |
+
animation: blink 1s step-end infinite;
|
| 131 |
+
}
|
| 132 |
+
@keyframes blink {
|
| 133 |
+
0%, 100% { opacity: 1; }
|
| 134 |
+
50% { opacity: 0; }
|
| 135 |
+
}
|
| 136 |
+
</style>
|
| 137 |
+
</head>
|
| 138 |
+
<body class="bg-background text-on-surface font-body-base min-h-screen flex flex-col relative overflow-x-hidden selection:bg-primary-container selection:text-on-primary-container">
|
| 139 |
+
<!-- Ambient Particles/Glows -->
|
| 140 |
+
<div class="fixed inset-0 pointer-events-none z-0">
|
| 141 |
+
<div class="absolute top-[-20%] left-[-10%] w-[50%] h-[50%] bg-primary-container/5 rounded-full blur-[120px]"></div>
|
| 142 |
+
<div class="absolute bottom-[-20%] right-[-10%] w-[40%] h-[40%] bg-error/5 rounded-full blur-[100px]"></div>
|
| 143 |
+
</div>
|
| 144 |
+
<!-- TopNavBar -->
|
| 145 |
+
<header class="fixed top-0 left-0 w-full z-50 flex justify-between items-center px-6 h-16 bg-emerald-950/20 backdrop-blur-xl border-b border-emerald-500/20 shadow-[0_4_20px_rgba(0,0,0,0.5)]">
|
| 146 |
+
<div class="flex items-center gap-md">
|
| 147 |
+
<span class="font-h3 text-h3 font-black tracking-tighter text-emerald-400 drop-shadow-[0_0_8px_rgba(0,255,156,0.5)]">AUTHRIX AI</span>
|
| 148 |
+
</div>
|
| 149 |
+
<nav class="hidden md:flex gap-lg">
|
| 150 |
+
<a class="font-label-xs text-label-xs uppercase text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] px-3 py-1 rounded" href="#">Dashboard</a>
|
| 151 |
+
<a class="font-label-xs text-label-xs uppercase text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] px-3 py-1 rounded" href="#">Agents</a>
|
| 152 |
+
<a class="font-label-xs text-label-xs uppercase text-emerald-400 border-b-2 border-emerald-400 pb-1 px-3 py-1" href="#">Logs</a>
|
| 153 |
+
<a class="font-label-xs text-label-xs uppercase text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] px-3 py-1 rounded" href="#">Network</a>
|
| 154 |
+
</nav>
|
| 155 |
+
<div class="flex items-center gap-sm">
|
| 156 |
+
<button class="text-emerald-400 hover:text-emerald-300 p-2 rounded-full hover:bg-emerald-400/10 transition-colors">
|
| 157 |
+
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 0;">sensors</span>
|
| 158 |
+
</button>
|
| 159 |
+
<button class="text-emerald-400 hover:text-emerald-300 p-2 rounded-full hover:bg-emerald-400/10 transition-colors">
|
| 160 |
+
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 0;">memory</span>
|
| 161 |
+
</button>
|
| 162 |
+
<button class="text-emerald-400 hover:text-emerald-300 p-2 rounded-full hover:bg-emerald-400/10 transition-colors">
|
| 163 |
+
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 0;">speed</span>
|
| 164 |
+
</button>
|
| 165 |
+
<button class="bg-primary-container text-on-primary-container font-label-xs text-label-xs uppercase px-4 py-2 rounded-DEFAULT ml-md shadow-[0_0_15px_rgba(0,255,156,0.3)] hover:bg-primary-fixed transition-colors">
|
| 166 |
+
GET STARTED
|
| 167 |
+
</button>
|
| 168 |
+
</div>
|
| 169 |
+
</header>
|
| 170 |
+
<main class="flex-grow pt-[80px] pb-xl px-gutter max-w-[1600px] mx-auto w-full z-10 flex flex-col gap-lg">
|
| 171 |
+
<!-- Cinematic Banner -->
|
| 172 |
+
<section class="glass-panel glow-border-primary rounded-xl relative overflow-hidden flex flex-col md:flex-row items-center justify-between p-lg md:p-margin min-h-[300px]">
|
| 173 |
+
<div class="scanline"></div>
|
| 174 |
+
<!-- Background Image -->
|
| 175 |
+
<div class="absolute inset-0 opacity-20 mix-blend-overlay pointer-events-none" data-alt="Abstract digital matrix code falling in a futuristic high-tech environment with green hues" style="background-image: url('https://lh3.googleusercontent.com/aida-public/AB6AXuCm6V5yQ3vKXnTfGWeqxvXPbNe2CvDhwLr4NreMaQM6OFtThX8-JDMNwORYSjTMxIXoCBzt8MticyWVNk4DNbUyoBJIlb4YsCoXkDzWVT3TThqqe3qzdYl-Jui2WDP5m7Oz3o0WWKG7NnnUmBUtZsaD8xv6cx0LodaRq6UhOxWBNDs1jbiBuzpNZ7n9tfrz4HYvK-nCymTLcymqGCsvfntziWmX7JJpsLc30o5fYaQrhz-l3htAV2sSSgwetQmTdz3-IlSPRaiYNGY'); background-size: cover; background-position: center;"></div>
|
| 176 |
+
<div class="relative z-10 flex flex-col md:flex-row items-center gap-margin w-full">
|
| 177 |
+
<!-- Circular Badge -->
|
| 178 |
+
<div class="relative w-48 h-48 rounded-full border-4 border-primary-container flex items-center justify-center shadow-[0_0_30px_rgba(0,255,156,0.4)] flex-shrink-0">
|
| 179 |
+
<div class="absolute inset-0 rounded-full border border-primary-container animate-[spin_10s_linear_infinite] opacity-50"></div>
|
| 180 |
+
<div class="absolute inset-2 rounded-full border border-dashed border-primary-container animate-[spin_15s_linear_infinite_reverse] opacity-30"></div>
|
| 181 |
+
<div class="text-center">
|
| 182 |
+
<span class="material-symbols-outlined text-primary-container text-5xl mb-2" style="font-variation-settings: 'FILL' 1;">verified_user</span>
|
| 183 |
+
<h2 class="font-h3 text-h3 text-primary-container uppercase tracking-widest drop-shadow-[0_0_5px_rgba(0,255,156,0.8)]">AUTHENTIC</h2>
|
| 184 |
+
</div>
|
| 185 |
+
</div>
|
| 186 |
+
<div class="flex-grow text-center md:text-left flex flex-col gap-sm">
|
| 187 |
+
<p class="font-data-mono text-data-mono text-primary-container/80 uppercase tracking-widest">Analysis Complete // ID: 8992-AX</p>
|
| 188 |
+
<h1 class="font-h1 text-h1 text-on-surface">Confidence Score</h1>
|
| 189 |
+
<div class="font-h1 text-[80px] leading-none text-primary-container drop-shadow-[0_0_15px_rgba(0,255,156,0.6)] font-black">98.7%</div>
|
| 190 |
+
</div>
|
| 191 |
+
</div>
|
| 192 |
+
</section>
|
| 193 |
+
<!-- Main Dashboard Grid -->
|
| 194 |
+
<div class="grid grid-cols-1 lg:grid-cols-12 gap-lg flex-grow">
|
| 195 |
+
<!-- Left: Insights Panel -->
|
| 196 |
+
<div class="lg:col-span-4 flex flex-col gap-md">
|
| 197 |
+
<h3 class="font-label-xs text-label-xs text-outline uppercase tracking-widest border-b border-surface-variant pb-2">Primary Vectors</h3>
|
| 198 |
+
<div class="glass-panel rounded-lg p-md flex items-start gap-md hover:glow-border-primary transition-all duration-300 transform hover:-translate-y-1">
|
| 199 |
+
<div class="w-10 h-10 rounded-full bg-surface-container-highest flex items-center justify-center flex-shrink-0 border border-surface-variant">
|
| 200 |
+
<span class="material-symbols-outlined text-primary-container" style="font-variation-settings: 'FILL' 0;">face</span>
|
| 201 |
+
</div>
|
| 202 |
+
<div>
|
| 203 |
+
<h4 class="font-body-base text-body-base font-semibold text-on-surface">Facial Morphology</h4>
|
| 204 |
+
<p class="font-data-mono text-data-mono text-on-surface-variant mt-1 text-sm">Consistent geometry. No micro-warping detected in periocular region.</p>
|
| 205 |
+
<div class="mt-3 flex items-center gap-2">
|
| 206 |
+
<div class="h-1 flex-grow bg-surface-container-highest rounded-full overflow-hidden">
|
| 207 |
+
<div class="h-full bg-primary-container w-[95%]"></div>
|
| 208 |
+
</div>
|
| 209 |
+
<span class="font-data-mono text-data-mono text-primary-container text-xs">95%</span>
|
| 210 |
+
</div>
|
| 211 |
+
</div>
|
| 212 |
+
</div>
|
| 213 |
+
<div class="glass-panel rounded-lg p-md flex items-start gap-md hover:glow-border-primary transition-all duration-300 transform hover:-translate-y-1">
|
| 214 |
+
<div class="w-10 h-10 rounded-full bg-surface-container-highest flex items-center justify-center flex-shrink-0 border border-surface-variant">
|
| 215 |
+
<span class="material-symbols-outlined text-primary-container" style="font-variation-settings: 'FILL' 0;">graphic_eq</span>
|
| 216 |
+
</div>
|
| 217 |
+
<div>
|
| 218 |
+
<h4 class="font-body-base text-body-base font-semibold text-on-surface">Audio Spectral Analysis</h4>
|
| 219 |
+
<p class="font-data-mono text-data-mono text-on-surface-variant mt-1 text-sm">Natural harmonic resonance. No synthetic vocal fry identified.</p>
|
| 220 |
+
<div class="mt-3 flex items-center gap-2">
|
| 221 |
+
<div class="h-1 flex-grow bg-surface-container-highest rounded-full overflow-hidden">
|
| 222 |
+
<div class="h-full bg-primary-container w-[99%]"></div>
|
| 223 |
+
</div>
|
| 224 |
+
<span class="font-data-mono text-data-mono text-primary-container text-xs">99%</span>
|
| 225 |
+
</div>
|
| 226 |
+
</div>
|
| 227 |
+
</div>
|
| 228 |
+
<div class="glass-panel rounded-lg p-md flex items-start gap-md hover:glow-border-error transition-all duration-300 transform hover:-translate-y-1">
|
| 229 |
+
<div class="w-10 h-10 rounded-full bg-surface-container-highest flex items-center justify-center flex-shrink-0 border border-surface-variant">
|
| 230 |
+
<span class="material-symbols-outlined text-error" style="font-variation-settings: 'FILL' 0;">visibility</span>
|
| 231 |
+
</div>
|
| 232 |
+
<div>
|
| 233 |
+
<h4 class="font-body-base text-body-base font-semibold text-on-surface">Lighting/Shadow Consistency</h4>
|
| 234 |
+
<p class="font-data-mono text-data-mono text-on-surface-variant mt-1 text-sm">Minor specular highlight anomaly detected on frame 412. Flagged for review.</p>
|
| 235 |
+
<div class="mt-3 flex items-center gap-2">
|
| 236 |
+
<div class="h-1 flex-grow bg-surface-container-highest rounded-full overflow-hidden">
|
| 237 |
+
<div class="h-full bg-error w-[42%]"></div>
|
| 238 |
+
</div>
|
| 239 |
+
<span class="font-data-mono text-data-mono text-error text-xs">42%</span>
|
| 240 |
+
</div>
|
| 241 |
+
</div>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
<!-- Right: Diagnostics/Metadata -->
|
| 245 |
+
<div class="lg:col-span-8 flex flex-col gap-md">
|
| 246 |
+
<h3 class="font-label-xs text-label-xs text-outline uppercase tracking-widest border-b border-surface-variant pb-2">System Diagnostics</h3>
|
| 247 |
+
<div class="glass-panel rounded-lg flex-grow p-md font-data-mono text-data-mono text-sm relative overflow-hidden flex flex-col">
|
| 248 |
+
<div class="scanline"></div>
|
| 249 |
+
<div class="flex justify-between items-center border-b border-surface-variant/50 pb-sm mb-sm text-on-surface-variant">
|
| 250 |
+
<span>METADATA_STREAM</span>
|
| 251 |
+
<span class="text-primary-container animate-pulse">ACTIVE</span>
|
| 252 |
+
</div>
|
| 253 |
+
<div class="flex-grow space-y-2 text-on-surface-variant overflow-y-auto pr-2">
|
| 254 |
+
<div class="flex">
|
| 255 |
+
<span class="text-outline w-32 shrink-0">SOURCE_HASH:</span>
|
| 256 |
+
<span class="text-primary-container break-all">a8f9c2e4b5d6...7f8e9a0b1c2d3</span>
|
| 257 |
+
</div>
|
| 258 |
+
<div class="flex">
|
| 259 |
+
<span class="text-outline w-32 shrink-0">FRAME_COUNT:</span>
|
| 260 |
+
<span>1440 @ 60FPS</span>
|
| 261 |
+
</div>
|
| 262 |
+
<div class="flex">
|
| 263 |
+
<span class="text-outline w-32 shrink-0">ENCODING:</span>
|
| 264 |
+
<span>H.265/HEVC Main 10 Profile</span>
|
| 265 |
+
</div>
|
| 266 |
+
<div class="flex">
|
| 267 |
+
<span class="text-outline w-32 shrink-0">RESOLUTION:</span>
|
| 268 |
+
<span>3840x2160 (4K UHD)</span>
|
| 269 |
+
</div>
|
| 270 |
+
<div class="my-4 border-t border-surface-variant/30 border-dashed"></div>
|
| 271 |
+
<div class="text-primary-container opacity-80">> INIT NEURAL_NET_V4.2</div>
|
| 272 |
+
<div class="text-primary-container opacity-80">> LOADING WEIGHTS... DONE</div>
|
| 273 |
+
<div class="text-primary-container opacity-80">> ANALYZING SPATIAL_TEMPORAL_NOISE</div>
|
| 274 |
+
<div class="text-primary-container opacity-80">> EXTRACTING BIOMETRIC_FEATURES</div>
|
| 275 |
+
<div class="text-primary-container opacity-80">> CROSS_REFERENCING KNOWN_GAN_MODELS</div>
|
| 276 |
+
<div class="text-on-surface mt-4 blinking-cursor">> SYSTEM_READY</div>
|
| 277 |
+
</div>
|
| 278 |
+
</div>
|
| 279 |
+
</div>
|
| 280 |
+
</div>
|
| 281 |
+
<!-- Bottom: Timeline Graph (Stylized Representation) -->
|
| 282 |
+
<section class="glass-panel rounded-xl p-lg flex flex-col gap-md">
|
| 283 |
+
<h3 class="font-label-xs text-label-xs text-outline uppercase tracking-widest border-b border-surface-variant pb-2">Probability Timeline</h3>
|
| 284 |
+
<div class="h-48 w-full relative flex items-end pt-4">
|
| 285 |
+
<!-- Grid Lines -->
|
| 286 |
+
<div class="absolute inset-0 flex flex-col justify-between pointer-events-none opacity-20">
|
| 287 |
+
<div class="w-full h-px bg-outline"></div>
|
| 288 |
+
<div class="w-full h-px bg-outline"></div>
|
| 289 |
+
<div class="w-full h-px bg-outline"></div>
|
| 290 |
+
<div class="w-full h-px bg-outline"></div>
|
| 291 |
+
</div>
|
| 292 |
+
<!-- Fake Graph Line (CSS Stylized) -->
|
| 293 |
+
<div class="w-full h-full relative z-10 flex items-end border-b border-l border-surface-variant">
|
| 294 |
+
<!-- Data points / segments -->
|
| 295 |
+
<div class="h-[20%] w-[10%] border-t-2 border-primary-container shadow-[0_-5px_10px_rgba(0,255,156,0.2)]"></div>
|
| 296 |
+
<div class="h-[15%] w-[10%] border-t-2 border-primary-container shadow-[0_-5px_10px_rgba(0,255,156,0.2)] transform -translate-y-2"></div>
|
| 297 |
+
<div class="h-[25%] w-[10%] border-t-2 border-primary-container shadow-[0_-5px_10px_rgba(0,255,156,0.2)] transform -translate-y-4"></div>
|
| 298 |
+
<div class="h-[10%] w-[10%] border-t-2 border-primary-container shadow-[0_-5px_10px_rgba(0,255,156,0.2)]"></div>
|
| 299 |
+
<div class="h-[30%] w-[10%] border-t-2 border-primary-container shadow-[0_-5px_10px_rgba(0,255,156,0.2)] transform -translate-y-6"></div>
|
| 300 |
+
<!-- Anomaly Spike -->
|
| 301 |
+
<div class="h-[80%] w-[5%] bg-error/20 border-t-2 border-l border-r border-error shadow-[0_0_20px_rgba(255,180,171,0.4)] relative group">
|
| 302 |
+
<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 bg-surface-container-highest border border-error text-error font-data-mono text-xs px-2 py-1 rounded hidden group-hover:block whitespace-nowrap z-20">
|
| 303 |
+
Spike: Frame 412
|
| 304 |
+
</div>
|
| 305 |
+
</div>
|
| 306 |
+
<div class="h-[35%] w-[15%] border-t-2 border-primary-container shadow-[0_-5px_10px_rgba(0,255,156,0.2)] transform -translate-y-8"></div>
|
| 307 |
+
<div class="h-[20%] w-[10%] border-t-2 border-primary-container shadow-[0_-5px_10px_rgba(0,255,156,0.2)] transform -translate-y-2"></div>
|
| 308 |
+
<div class="h-[15%] w-[20%] border-t-2 border-primary-container shadow-[0_-5px_10px_rgba(0,255,156,0.2)]"></div>
|
| 309 |
+
</div>
|
| 310 |
+
</div>
|
| 311 |
+
<div class="flex justify-between font-data-mono text-data-mono text-xs text-outline mt-2">
|
| 312 |
+
<span>00:00:00</span>
|
| 313 |
+
<span>00:01:12</span>
|
| 314 |
+
<span>00:02:24</span>
|
| 315 |
+
</div>
|
| 316 |
+
</section>
|
| 317 |
+
</main>
|
| 318 |
+
<!-- Footer -->
|
| 319 |
+
<footer class="w-full py-8 px-10 flex flex-col md:flex-row justify-between items-center opacity-80 bg-black border-t border-emerald-900/30 font-data-mono text-[10px] uppercase tracking-[0.2em] cursor-crosshair z-10 relative">
|
| 320 |
+
<span class="text-emerald-500 mb-4 md:mb-0">© 2024 AUTHRIX COMMAND. SYSTEM_STATE: VIGILANT</span>
|
| 321 |
+
<div class="flex gap-md">
|
| 322 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all" href="#">Kernel</a>
|
| 323 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all" href="#">Protocol</a>
|
| 324 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all" href="#">Override</a>
|
| 325 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all" href="#">Diagnostics</a>
|
| 326 |
+
</div>
|
| 327 |
+
</footer>
|
| 328 |
+
</body></html>
|
stitch_authrix_deepfake_detection_engine/authrix_hero_section/code.html
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
|
| 3 |
+
<html class="dark" lang="en"><head>
|
| 4 |
+
<meta charset="utf-8"/>
|
| 5 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 6 |
+
<title>Authrix - Deepfake Detection Platform</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Space+Grotesk:wght@600;700;900&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
"colors": {
|
| 17 |
+
"inverse-surface": "#e5e2e1",
|
| 18 |
+
"primary-container": "#00ff9c",
|
| 19 |
+
"on-secondary-fixed": "#002022",
|
| 20 |
+
"on-tertiary-fixed-variant": "#930010",
|
| 21 |
+
"primary-fixed-dim": "#00e38a",
|
| 22 |
+
"surface-dim": "#131313",
|
| 23 |
+
"on-background": "#e5e2e1",
|
| 24 |
+
"primary-fixed": "#56ffa7",
|
| 25 |
+
"surface-variant": "#353534",
|
| 26 |
+
"surface-container": "#201f1f",
|
| 27 |
+
"surface-tint": "#00e38a",
|
| 28 |
+
"on-secondary-fixed-variant": "#004f54",
|
| 29 |
+
"secondary-container": "#00eefc",
|
| 30 |
+
"error": "#ffb4ab",
|
| 31 |
+
"surface": "#131313",
|
| 32 |
+
"tertiary": "#fffbff",
|
| 33 |
+
"tertiary-fixed": "#ffdad6",
|
| 34 |
+
"outline": "#849587",
|
| 35 |
+
"on-primary-fixed": "#002110",
|
| 36 |
+
"on-tertiary-fixed": "#410003",
|
| 37 |
+
"outline-variant": "#3b4b3f",
|
| 38 |
+
"tertiary-container": "#ffd6d2",
|
| 39 |
+
"surface-container-lowest": "#0e0e0e",
|
| 40 |
+
"on-surface": "#e5e2e1",
|
| 41 |
+
"tertiary-fixed-dim": "#ffb3ac",
|
| 42 |
+
"surface-container-low": "#1c1b1b",
|
| 43 |
+
"surface-container-highest": "#353534",
|
| 44 |
+
"on-tertiary-container": "#c7041a",
|
| 45 |
+
"background": "#131313",
|
| 46 |
+
"on-secondary-container": "#00686f",
|
| 47 |
+
"surface-container-high": "#2a2a2a",
|
| 48 |
+
"error-container": "#93000a",
|
| 49 |
+
"on-secondary": "#00363a",
|
| 50 |
+
"primary": "#f3fff3",
|
| 51 |
+
"on-primary-fixed-variant": "#00522f",
|
| 52 |
+
"on-primary": "#00391f",
|
| 53 |
+
"surface-bright": "#3a3939",
|
| 54 |
+
"inverse-primary": "#006d40",
|
| 55 |
+
"secondary": "#d3fbff",
|
| 56 |
+
"inverse-on-surface": "#313030",
|
| 57 |
+
"secondary-fixed": "#7df4ff",
|
| 58 |
+
"on-tertiary": "#680008",
|
| 59 |
+
"on-error-container": "#ffdad6",
|
| 60 |
+
"secondary-fixed-dim": "#00dbe9",
|
| 61 |
+
"on-surface-variant": "#b9cbbc",
|
| 62 |
+
"on-error": "#690005",
|
| 63 |
+
"on-primary-container": "#007142"
|
| 64 |
+
},
|
| 65 |
+
"borderRadius": {
|
| 66 |
+
"DEFAULT": "0.125rem",
|
| 67 |
+
"lg": "0.25rem",
|
| 68 |
+
"xl": "0.5rem",
|
| 69 |
+
"full": "0.75rem"
|
| 70 |
+
},
|
| 71 |
+
"spacing": {
|
| 72 |
+
"margin": "48px",
|
| 73 |
+
"unit": "4px",
|
| 74 |
+
"container-max": "1440px",
|
| 75 |
+
"gutter": "24px",
|
| 76 |
+
"grid-overlay": "32px"
|
| 77 |
+
},
|
| 78 |
+
"fontFamily": {
|
| 79 |
+
"h1-display": ["Space Grotesk"],
|
| 80 |
+
"h2-technical": ["Space Grotesk"],
|
| 81 |
+
"label-caps": ["Space Grotesk"],
|
| 82 |
+
"body-main": ["Inter"],
|
| 83 |
+
"body-light": ["Inter"]
|
| 84 |
+
},
|
| 85 |
+
"fontSize": {
|
| 86 |
+
"h1-display": ["48px", { "lineHeight": "1.1", "letterSpacing": "-0.02em", "fontWeight": "700" }],
|
| 87 |
+
"h2-technical": ["32px", { "lineHeight": "1.2", "letterSpacing": "0.01em", "fontWeight": "600" }],
|
| 88 |
+
"label-caps": ["11px", { "lineHeight": "1", "letterSpacing": "0.15em", "fontWeight": "600" }],
|
| 89 |
+
"body-main": ["16px", { "lineHeight": "1.6", "letterSpacing": "0em", "fontWeight": "400" }],
|
| 90 |
+
"body-light": ["14px", { "lineHeight": "1.5", "letterSpacing": "0.03em", "fontWeight": "300" }]
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
</script>
|
| 96 |
+
<style>
|
| 97 |
+
body {
|
| 98 |
+
background-color: #050505;
|
| 99 |
+
background-image:
|
| 100 |
+
linear-gradient(rgba(0, 255, 156, 0.02) 1px, transparent 1px),
|
| 101 |
+
linear-gradient(90deg, rgba(0, 255, 156, 0.02) 1px, transparent 1px);
|
| 102 |
+
background-size: 32px 32px;
|
| 103 |
+
color: #e5e2e1;
|
| 104 |
+
position: relative;
|
| 105 |
+
overflow-x: hidden;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.scan-line {
|
| 109 |
+
position: absolute;
|
| 110 |
+
top: 0;
|
| 111 |
+
left: 0;
|
| 112 |
+
width: 100%;
|
| 113 |
+
height: 2px;
|
| 114 |
+
background: rgba(0, 255, 156, 0.3);
|
| 115 |
+
box-shadow: 0 0 10px rgba(0, 255, 156, 0.5), 0 0 20px rgba(0, 255, 156, 0.2);
|
| 116 |
+
animation: scan 4s linear infinite;
|
| 117 |
+
z-index: 10;
|
| 118 |
+
pointer-events: none;
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
@keyframes scan {
|
| 122 |
+
0% { top: 0; opacity: 0; }
|
| 123 |
+
10% { opacity: 1; }
|
| 124 |
+
90% { opacity: 1; }
|
| 125 |
+
100% { top: 100%; opacity: 0; }
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
.ambient-glow {
|
| 129 |
+
position: absolute;
|
| 130 |
+
width: 60vw;
|
| 131 |
+
height: 60vw;
|
| 132 |
+
border-radius: 50%;
|
| 133 |
+
background: radial-gradient(circle, rgba(0, 238, 252, 0.05) 0%, rgba(0, 0, 0, 0) 70%);
|
| 134 |
+
top: 50%;
|
| 135 |
+
left: 50%;
|
| 136 |
+
transform: translate(-50%, -50%);
|
| 137 |
+
z-index: -1;
|
| 138 |
+
pointer-events: none;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
.glass-panel {
|
| 142 |
+
background: rgba(19, 19, 19, 0.6);
|
| 143 |
+
backdrop-filter: blur(12px);
|
| 144 |
+
-webkit-backdrop-filter: blur(12px);
|
| 145 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 146 |
+
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.5);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.orb-container {
|
| 150 |
+
position: relative;
|
| 151 |
+
width: 400px;
|
| 152 |
+
height: 400px;
|
| 153 |
+
perspective: 1000px;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
.orb-core {
|
| 157 |
+
position: absolute;
|
| 158 |
+
top: 50%;
|
| 159 |
+
left: 50%;
|
| 160 |
+
transform: translate(-50%, -50%);
|
| 161 |
+
width: 120px;
|
| 162 |
+
height: 120px;
|
| 163 |
+
border-radius: 50%;
|
| 164 |
+
background: radial-gradient(circle, rgba(0, 255, 156, 0.8) 0%, rgba(0, 255, 156, 0.2) 60%, transparent 100%);
|
| 165 |
+
box-shadow: 0 0 60px rgba(0, 255, 156, 0.4);
|
| 166 |
+
z-index: 2;
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
.ring {
|
| 170 |
+
position: absolute;
|
| 171 |
+
top: 50%;
|
| 172 |
+
left: 50%;
|
| 173 |
+
border-radius: 50%;
|
| 174 |
+
border: 1px solid rgba(0, 238, 252, 0.3);
|
| 175 |
+
transform-style: preserve-3d;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.ring-1 { width: 200px; height: 200px; margin-top: -100px; margin-left: -100px; transform: rotateX(60deg) rotateY(20deg); border-top-color: rgba(0, 255, 156, 0.8); }
|
| 179 |
+
.ring-2 { width: 280px; height: 280px; margin-top: -140px; margin-left: -140px; transform: rotateX(40deg) rotateY(-30deg); border-right-color: rgba(0, 238, 252, 0.6); }
|
| 180 |
+
.ring-3 { width: 360px; height: 360px; margin-top: -180px; margin-left: -180px; transform: rotateX(80deg) rotateY(10deg); border-bottom-color: rgba(255, 180, 171, 0.4); }
|
| 181 |
+
|
| 182 |
+
.btn-primary {
|
| 183 |
+
background-color: #00ff9c;
|
| 184 |
+
color: #002110;
|
| 185 |
+
transition: all 0.3s ease;
|
| 186 |
+
box-shadow: 0 0 15px rgba(0, 255, 156, 0.3);
|
| 187 |
+
}
|
| 188 |
+
.btn-primary:hover {
|
| 189 |
+
background-color: #56ffa7;
|
| 190 |
+
box-shadow: 0 0 25px rgba(0, 255, 156, 0.5);
|
| 191 |
+
transform: translateY(-2px);
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
.btn-secondary {
|
| 195 |
+
background: transparent;
|
| 196 |
+
color: #e5e2e1;
|
| 197 |
+
border: 1px solid rgba(0, 255, 156, 0.5);
|
| 198 |
+
transition: all 0.3s ease;
|
| 199 |
+
}
|
| 200 |
+
.btn-secondary:hover {
|
| 201 |
+
border-color: #00ff9c;
|
| 202 |
+
box-shadow: 0 0 15px rgba(0, 255, 156, 0.2);
|
| 203 |
+
background: rgba(0, 255, 156, 0.05);
|
| 204 |
+
}
|
| 205 |
+
</style>
|
| 206 |
+
</head>
|
| 207 |
+
<body class="min-h-screen flex flex-col font-body-main text-body-main antialiased selection:bg-primary-container selection:text-on-primary-fixed">
|
| 208 |
+
<div class="scan-line"></div>
|
| 209 |
+
<div class="ambient-glow"></div>
|
| 210 |
+
<!-- TopNavBar -->
|
| 211 |
+
<nav class="bg-zinc-950/30 backdrop-blur-xl fixed top-0 w-full z-50 border-b border-white/5 shadow-[0_8px_32px_0_rgba(0,0,0,0.8)]">
|
| 212 |
+
<div class="flex justify-between items-center px-8 py-4 w-full max-w-screen-2xl mx-auto">
|
| 213 |
+
<div class="text-2xl font-black text-white tracking-tighter italic">Authrix</div>
|
| 214 |
+
<div class="hidden md:flex gap-8 items-center">
|
| 215 |
+
<a class="font-['Space_Grotesk'] font-medium tracking-tight text-zinc-400 font-medium hover:text-emerald-300 hover:bg-white/5 transition-all duration-300 ease-out px-3 py-2 rounded-md active:scale-95 transition-transform" href="#">Technology</a>
|
| 216 |
+
<a class="font-['Space_Grotesk'] font-medium tracking-tight text-emerald-400 font-bold border-b-2 border-emerald-400 pb-1 hover:text-emerald-300 hover:bg-white/5 transition-all duration-300 ease-out px-3 py-2 rounded-md active:scale-95 transition-transform" href="#">Solutions</a>
|
| 217 |
+
<a class="font-['Space_Grotesk'] font-medium tracking-tight text-zinc-400 font-medium hover:text-emerald-300 hover:bg-white/5 transition-all duration-300 ease-out px-3 py-2 rounded-md active:scale-95 transition-transform" href="#">Intelligence</a>
|
| 218 |
+
<a class="font-['Space_Grotesk'] font-medium tracking-tight text-zinc-400 font-medium hover:text-emerald-300 hover:bg-white/5 transition-all duration-300 ease-out px-3 py-2 rounded-md active:scale-95 transition-transform" href="#">Security</a>
|
| 219 |
+
</div>
|
| 220 |
+
<div class="flex items-center gap-4">
|
| 221 |
+
<button class="hidden lg:flex items-center justify-center w-10 h-10 rounded-full hover:bg-white/10 transition-colors text-zinc-400">
|
| 222 |
+
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 0;">language</span>
|
| 223 |
+
</button>
|
| 224 |
+
<button class="hidden lg:flex items-center justify-center w-10 h-10 rounded-full hover:bg-white/10 transition-colors text-zinc-400">
|
| 225 |
+
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 0;">account_circle</span>
|
| 226 |
+
</button>
|
| 227 |
+
<button class="font-['Space_Grotesk'] font-medium tracking-tight text-emerald-400 border border-emerald-400/30 px-5 py-2 rounded-md hover:bg-emerald-400/10 transition-colors">
|
| 228 |
+
Get Started
|
| 229 |
+
</button>
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
+
</nav>
|
| 233 |
+
<!-- Main Hero Section -->
|
| 234 |
+
<main class="flex-grow flex items-center justify-center pt-24 pb-12 px-6 lg:px-8 relative z-10 w-full max-w-container-max mx-auto">
|
| 235 |
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 lg:gap-8 items-center w-full">
|
| 236 |
+
<!-- Hero Left: Content -->
|
| 237 |
+
<div class="flex flex-col items-start max-w-2xl">
|
| 238 |
+
<div class="inline-flex items-center gap-2 px-3 py-1.5 rounded-full border border-primary-container/30 bg-primary-container/5 mb-8">
|
| 239 |
+
<span class="w-2 h-2 rounded-full bg-primary-container animate-pulse"></span>
|
| 240 |
+
<span class="font-label-caps text-label-caps text-primary-container uppercase tracking-widest">AI-Powered Authenticity Engine</span>
|
| 241 |
+
</div>
|
| 242 |
+
<h1 class="font-h1-display text-h1-display text-on-surface mb-6 leading-tight">
|
| 243 |
+
Verify Reality in <br/>
|
| 244 |
+
<span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-container to-secondary-container">Real-Time</span>
|
| 245 |
+
</h1>
|
| 246 |
+
<p class="font-body-main text-body-main text-on-surface-variant mb-10 max-w-lg leading-relaxed">
|
| 247 |
+
Detect deepfake videos and synthetic voices using multi-agent AI intelligence with explainable insights. Protect your digital perimeter with military-grade precision.
|
| 248 |
+
</p>
|
| 249 |
+
<div class="flex flex-col sm:flex-row gap-4 w-full sm:w-auto">
|
| 250 |
+
<button class="btn-primary font-h2-technical text-[16px] font-semibold px-8 py-4 rounded-md flex items-center justify-center gap-2">
|
| 251 |
+
<span class="material-symbols-outlined text-[20px]" style="font-variation-settings: 'FILL' 1;">troubleshoot</span>
|
| 252 |
+
Analyze Video
|
| 253 |
+
</button>
|
| 254 |
+
<button class="btn-secondary font-h2-technical text-[16px] font-medium px-8 py-4 rounded-md flex items-center justify-center gap-2">
|
| 255 |
+
View Demo
|
| 256 |
+
<span class="material-symbols-outlined text-[20px]">play_circle</span>
|
| 257 |
+
</button>
|
| 258 |
+
</div>
|
| 259 |
+
<!-- Trust Indicators Glass Card -->
|
| 260 |
+
<div class="mt-16 glass-panel rounded-lg p-6 w-full max-w-md">
|
| 261 |
+
<div class="flex justify-between items-center mb-4">
|
| 262 |
+
<span class="font-label-caps text-label-caps text-on-surface-variant">System Status</span>
|
| 263 |
+
<div class="flex items-center gap-2 text-primary-container">
|
| 264 |
+
<span class="material-symbols-outlined text-sm">check_circle</span>
|
| 265 |
+
<span class="font-body-light text-body-light text-xs">Operational</span>
|
| 266 |
+
</div>
|
| 267 |
+
</div>
|
| 268 |
+
<div class="h-1 w-full bg-surface-container-high rounded-full overflow-hidden">
|
| 269 |
+
<div class="h-full bg-primary-container w-full relative">
|
| 270 |
+
<div class="absolute top-0 right-0 h-full w-1/2 bg-gradient-to-l from-white/40 to-transparent animate-pulse"></div>
|
| 271 |
+
</div>
|
| 272 |
+
</div>
|
| 273 |
+
</div>
|
| 274 |
+
</div>
|
| 275 |
+
<!-- Hero Right: 3D Visual -->
|
| 276 |
+
<div class="flex justify-center items-center relative h-[500px] w-full">
|
| 277 |
+
<!-- Background Mesh/Particles abstraction -->
|
| 278 |
+
<div class="absolute inset-0 z-0 opacity-20 bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCI+PHBhdGggZD0iTTIwIDBMMCAyMEwyMCA0MEw0MCAyMEwyMCAweiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDBmZjljIiBzdHJva2Utb3BhY2l0eT0iMC4xIi8+PC9zdmc+')]"></div>
|
| 279 |
+
<div class="orb-container">
|
| 280 |
+
<div class="orb-core"></div>
|
| 281 |
+
<div class="ring ring-1"></div>
|
| 282 |
+
<div class="ring ring-2"></div>
|
| 283 |
+
<div class="ring ring-3"></div>
|
| 284 |
+
<!-- Decorative Data Points -->
|
| 285 |
+
<div class="absolute top-1/4 left-1/4 glass-panel px-3 py-1.5 rounded text-xs text-primary-container font-h2-technical z-10 shadow-[0_0_10px_rgba(0,255,156,0.2)]">
|
| 286 |
+
99.8% Match
|
| 287 |
+
</div>
|
| 288 |
+
<div class="absolute bottom-1/4 right-1/4 glass-panel px-3 py-1.5 rounded text-xs text-secondary-container font-h2-technical z-10 shadow-[0_0_10px_rgba(0,238,252,0.2)]">
|
| 289 |
+
Audio Sync OK
|
| 290 |
+
</div>
|
| 291 |
+
</div>
|
| 292 |
+
</div>
|
| 293 |
+
</div>
|
| 294 |
+
</main>
|
| 295 |
+
<!-- Footer -->
|
| 296 |
+
<footer class="bg-zinc-950 w-full py-12 px-8 border-t border-white/5 mt-auto relative z-20">
|
| 297 |
+
<div class="flex flex-col md:flex-row justify-between items-center w-full max-w-screen-2xl mx-auto">
|
| 298 |
+
<div class="text-lg font-bold text-emerald-400 mb-6 md:mb-0">
|
| 299 |
+
Authrix
|
| 300 |
+
</div>
|
| 301 |
+
<div class="flex flex-wrap justify-center gap-6 md:gap-8 mb-6 md:mb-0">
|
| 302 |
+
<a class="font-['Space_Grotesk'] text-xs uppercase tracking-widest text-zinc-500 hover:text-emerald-300 transition-colors opacity-80 hover:opacity-100" href="#">Privacy Protocol</a>
|
| 303 |
+
<a class="font-['Space_Grotesk'] text-xs uppercase tracking-widest text-zinc-500 hover:text-emerald-300 transition-colors opacity-80 hover:opacity-100" href="#">Terms of Engagement</a>
|
| 304 |
+
<a class="font-['Space_Grotesk'] text-xs uppercase tracking-widest text-zinc-500 hover:text-emerald-300 transition-colors opacity-80 hover:opacity-100" href="#">API Documentation</a>
|
| 305 |
+
<a class="font-['Space_Grotesk'] text-xs uppercase tracking-widest text-zinc-500 hover:text-emerald-300 transition-colors opacity-80 hover:opacity-100" href="#">Support</a>
|
| 306 |
+
</div>
|
| 307 |
+
<div class="font-['Space_Grotesk'] text-xs uppercase tracking-widest text-zinc-500">
|
| 308 |
+
© 2024 Authrix Intelligence Systems. All rights reserved.
|
| 309 |
+
</div>
|
| 310 |
+
</div>
|
| 311 |
+
</footer>
|
| 312 |
+
</body></html>
|
stitch_authrix_deepfake_detection_engine/authrix_hero_section_v2/code.html
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
|
| 3 |
+
<html class="dark" lang="en"><head>
|
| 4 |
+
<meta charset="utf-8"/>
|
| 5 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 6 |
+
<title>Authrix - AI Command Center</title>
|
| 7 |
+
<!-- Google Fonts -->
|
| 8 |
+
<link href="https://fonts.googleapis.com" rel="preconnect"/>
|
| 9 |
+
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet"/>
|
| 11 |
+
<!-- Material Symbols -->
|
| 12 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 13 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 14 |
+
<!-- Tailwind CSS -->
|
| 15 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 16 |
+
<!-- Tailwind Config -->
|
| 17 |
+
<script id="tailwind-config">
|
| 18 |
+
tailwind.config = {
|
| 19 |
+
darkMode: "class",
|
| 20 |
+
theme: {
|
| 21 |
+
extend: {
|
| 22 |
+
"colors": {
|
| 23 |
+
"inverse-surface": "#dae5da",
|
| 24 |
+
"on-error": "#690005",
|
| 25 |
+
"surface-tint": "#00e38a",
|
| 26 |
+
"error": "#ffb4ab",
|
| 27 |
+
"outline-variant": "#3b4b3f",
|
| 28 |
+
"outline": "#849587",
|
| 29 |
+
"background": "#0c150f",
|
| 30 |
+
"surface-container-high": "#232c25",
|
| 31 |
+
"secondary-container": "#00e3fd",
|
| 32 |
+
"on-surface-variant": "#b9cbbc",
|
| 33 |
+
"on-secondary-container": "#00616d",
|
| 34 |
+
"on-error-container": "#ffdad6",
|
| 35 |
+
"primary": "#f3fff3",
|
| 36 |
+
"primary-container": "#00ff9c",
|
| 37 |
+
"on-primary": "#00391f",
|
| 38 |
+
"surface-bright": "#323c34",
|
| 39 |
+
"primary-fixed-dim": "#00e38a",
|
| 40 |
+
"tertiary-fixed-dim": "#e4c44f",
|
| 41 |
+
"on-secondary-fixed": "#001f24",
|
| 42 |
+
"tertiary-fixed": "#ffe17a",
|
| 43 |
+
"on-tertiary-container": "#766000",
|
| 44 |
+
"on-secondary": "#00363d",
|
| 45 |
+
"on-tertiary-fixed": "#231b00",
|
| 46 |
+
"on-tertiary": "#3b2f00",
|
| 47 |
+
"surface-container-low": "#141e17",
|
| 48 |
+
"surface": "#0c150f",
|
| 49 |
+
"primary-fixed": "#56ffa7",
|
| 50 |
+
"secondary-fixed-dim": "#00daf3",
|
| 51 |
+
"on-secondary-fixed-variant": "#004f58",
|
| 52 |
+
"inverse-on-surface": "#29332b",
|
| 53 |
+
"surface-container": "#18221b",
|
| 54 |
+
"on-surface": "#dae5da",
|
| 55 |
+
"on-tertiary-fixed-variant": "#554500",
|
| 56 |
+
"error-container": "#93000a",
|
| 57 |
+
"surface-variant": "#2d3730",
|
| 58 |
+
"surface-container-highest": "#2d3730",
|
| 59 |
+
"secondary-fixed": "#9cf0ff",
|
| 60 |
+
"on-background": "#dae5da",
|
| 61 |
+
"on-primary-container": "#007142",
|
| 62 |
+
"inverse-primary": "#006d40",
|
| 63 |
+
"on-primary-fixed-variant": "#00522f",
|
| 64 |
+
"secondary": "#bdf4ff",
|
| 65 |
+
"tertiary": "#fffaff",
|
| 66 |
+
"surface-dim": "#0c150f",
|
| 67 |
+
"on-primary-fixed": "#002110",
|
| 68 |
+
"surface-container-lowest": "#07100a",
|
| 69 |
+
"tertiary-container": "#ffdd65"
|
| 70 |
+
},
|
| 71 |
+
"borderRadius": {
|
| 72 |
+
"DEFAULT": "0.25rem",
|
| 73 |
+
"lg": "0.5rem",
|
| 74 |
+
"xl": "0.75rem",
|
| 75 |
+
"full": "9999px"
|
| 76 |
+
},
|
| 77 |
+
"spacing": {
|
| 78 |
+
"xs": "4px",
|
| 79 |
+
"md": "16px",
|
| 80 |
+
"base": "4px",
|
| 81 |
+
"sm": "8px",
|
| 82 |
+
"xl": "48px",
|
| 83 |
+
"lg": "24px",
|
| 84 |
+
"gutter": "20px",
|
| 85 |
+
"margin": "32px"
|
| 86 |
+
},
|
| 87 |
+
"fontFamily": {
|
| 88 |
+
"h2": ["Space Grotesk"],
|
| 89 |
+
"h1": ["Space Grotesk"],
|
| 90 |
+
"h3": ["Space Grotesk"],
|
| 91 |
+
"label-xs": ["Space Grotesk"],
|
| 92 |
+
"data-mono": ["Space Grotesk"],
|
| 93 |
+
"body-base": ["Space Grotesk"]
|
| 94 |
+
},
|
| 95 |
+
"fontSize": {
|
| 96 |
+
"h2": ["32px", {"lineHeight": "1.2", "letterSpacing": "-0.01em", "fontWeight": "600"}],
|
| 97 |
+
"h1": ["48px", {"lineHeight": "1.1", "letterSpacing": "-0.02em", "fontWeight": "700"}],
|
| 98 |
+
"h3": ["24px", {"lineHeight": "1.3", "letterSpacing": "0em", "fontWeight": "600"}],
|
| 99 |
+
"label-xs": ["12px", {"lineHeight": "1", "letterSpacing": "0.1em", "fontWeight": "700"}],
|
| 100 |
+
"data-mono": ["14px", {"lineHeight": "1.5", "letterSpacing": "0.05em", "fontWeight": "500"}],
|
| 101 |
+
"body-base": ["16px", {"lineHeight": "1.6", "letterSpacing": "0.01em", "fontWeight": "400"}]
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
</script>
|
| 107 |
+
<style>
|
| 108 |
+
.material-symbols-outlined {
|
| 109 |
+
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
| 110 |
+
}
|
| 111 |
+
</style>
|
| 112 |
+
</head>
|
| 113 |
+
<body class="bg-background text-on-surface min-h-screen flex flex-col relative overflow-x-hidden font-body-base">
|
| 114 |
+
<!-- Atmospheric Background Layers -->
|
| 115 |
+
<div class="fixed inset-0 z-0 pointer-events-none">
|
| 116 |
+
<!-- Scan Lines Simulation -->
|
| 117 |
+
<div class="absolute inset-0 bg-[linear-gradient(rgba(0,0,0,0)_50%,rgba(0,0,0,0.25)_50%),linear-gradient(90deg,rgba(255,0,0,0.06),rgba(0,255,0,0.02),rgba(0,0,255,0.06))] bg-[size:100%_4px,3px_100%] opacity-20"></div>
|
| 118 |
+
<!-- Technical Grid -->
|
| 119 |
+
<div class="absolute inset-0 bg-[linear-gradient(to_right,#3b4b3f_1px,transparent_1px),linear-gradient(to_bottom,#3b4b3f_1px,transparent_1px)] bg-[size:48px_48px] opacity-10"></div>
|
| 120 |
+
<!-- Ambient Radial Glow -->
|
| 121 |
+
<div class="absolute top-1/4 -right-1/4 w-[800px] h-[800px] bg-primary-container/5 rounded-full blur-[120px]"></div>
|
| 122 |
+
</div>
|
| 123 |
+
<!-- TopNavBar Shared Component -->
|
| 124 |
+
<nav class="fixed top-0 left-0 w-full z-50 flex justify-between items-center px-6 h-16 bg-emerald-950/20 backdrop-blur-xl border-b border-emerald-500/20 shadow-[0_4_20px_rgba(0,0,0,0.5)] font-['Space_Grotesk'] tracking-wider uppercase text-xs">
|
| 125 |
+
<!-- Brand Logo -->
|
| 126 |
+
<div class="flex items-center">
|
| 127 |
+
<span class="text-2xl font-black tracking-tighter text-emerald-400 drop-shadow-[0_0_8px_rgba(0,255,156,0.5)]">AUTHRIX AI</span>
|
| 128 |
+
</div>
|
| 129 |
+
<!-- Navigation Links (Hidden on small screens) -->
|
| 130 |
+
<div class="hidden md:flex items-center space-x-lg">
|
| 131 |
+
<a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] py-1 px-2 rounded-sm active:scale-95 transition-transform duration-150" href="#">Dashboard</a>
|
| 132 |
+
<a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] py-1 px-2 rounded-sm active:scale-95 transition-transform duration-150" href="#">Agents</a>
|
| 133 |
+
<a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] py-1 px-2 rounded-sm active:scale-95 transition-transform duration-150" href="#">Logs</a>
|
| 134 |
+
<a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] py-1 px-2 rounded-sm active:scale-95 transition-transform duration-150" href="#">Network</a>
|
| 135 |
+
</div>
|
| 136 |
+
<!-- Trailing Actions -->
|
| 137 |
+
<div class="flex items-center space-x-md">
|
| 138 |
+
<!-- Search Icon (Search bar on right logic) -->
|
| 139 |
+
<button class="text-emerald-800/60 hover:text-emerald-400 transition-colors flex items-center justify-center p-1 rounded-full hover:bg-emerald-400/10">
|
| 140 |
+
<span class="material-symbols-outlined text-lg">search</span>
|
| 141 |
+
</button>
|
| 142 |
+
<!-- Icon Actions -->
|
| 143 |
+
<div class="hidden lg:flex items-center space-x-sm border-r border-emerald-500/20 pr-md mr-sm">
|
| 144 |
+
<button class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] p-1 rounded-sm active:scale-95 transition-transform duration-150"><span class="material-symbols-outlined text-lg">sensors</span></button>
|
| 145 |
+
<button class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] p-1 rounded-sm active:scale-95 transition-transform duration-150"><span class="material-symbols-outlined text-lg">memory</span></button>
|
| 146 |
+
<button class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] p-1 rounded-sm active:scale-95 transition-transform duration-150"><span class="material-symbols-outlined text-lg">speed</span></button>
|
| 147 |
+
</div>
|
| 148 |
+
<!-- Primary Action -->
|
| 149 |
+
<button class="text-emerald-400 border border-emerald-400/50 hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] px-sm py-1 rounded-DEFAULT active:scale-95 transition-transform duration-150 font-bold">
|
| 150 |
+
GET STARTED
|
| 151 |
+
</button>
|
| 152 |
+
</div>
|
| 153 |
+
</nav>
|
| 154 |
+
<!-- Main Content Canvas -->
|
| 155 |
+
<main class="flex-grow relative z-10 flex flex-col justify-center pt-[100px] pb-xl px-6 md:px-gutter max-w-7xl mx-auto w-full">
|
| 156 |
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-xl items-center min-h-[716px]">
|
| 157 |
+
<!-- Left Column: Command Console Content -->
|
| 158 |
+
<div class="flex flex-col items-start space-y-md">
|
| 159 |
+
<!-- Status Badge -->
|
| 160 |
+
<div class="inline-flex items-center gap-2 bg-surface-container/40 border border-outline/30 backdrop-blur-md px-sm py-xs rounded-none shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]">
|
| 161 |
+
<span class="w-2 h-2 rounded-full bg-primary-container shadow-[0_0_8px_#00ff9c] animate-pulse"></span>
|
| 162 |
+
<span class="font-label-xs text-label-xs text-on-surface-variant uppercase tracking-widest">AI-Powered Authenticity Engine</span>
|
| 163 |
+
</div>
|
| 164 |
+
<!-- Main Headline -->
|
| 165 |
+
<h1 class="font-h1 text-h1 text-on-surface drop-shadow-md">
|
| 166 |
+
Verify Reality <br class="hidden md:block"/>
|
| 167 |
+
in <span class="text-transparent bg-clip-text bg-gradient-to-r from-primary-container to-secondary-container">Real-Time</span>
|
| 168 |
+
</h1>
|
| 169 |
+
<!-- Subtext -->
|
| 170 |
+
<p class="font-body-base text-body-base text-on-surface-variant max-w-lg mt-sm mb-lg">
|
| 171 |
+
Deploy advanced neural networks to detect deepfakes, synthetic media, and manipulated data streams with military-grade precision. Establish an unbreakable perimeter of truth.
|
| 172 |
+
</p>
|
| 173 |
+
<!-- Action Group -->
|
| 174 |
+
<div class="flex flex-wrap items-center gap-md mt-sm">
|
| 175 |
+
<!-- Primary CTA -->
|
| 176 |
+
<button class="group relative px-lg py-sm bg-primary-container text-on-primary font-label-xs text-label-xs uppercase tracking-wider rounded-DEFAULT overflow-hidden transition-all duration-300 hover:shadow-[0_0_30px_rgba(0,255,156,0.4)] active:scale-95">
|
| 177 |
+
<span class="relative z-10 flex items-center gap-2">
|
| 178 |
+
Analyze Video
|
| 179 |
+
<span class="material-symbols-outlined text-[16px]">radar</span>
|
| 180 |
+
</span>
|
| 181 |
+
<!-- Neon sweep effect -->
|
| 182 |
+
<div class="absolute inset-0 -translate-x-full bg-gradient-to-r from-transparent via-white/40 to-transparent group-hover:animate-[shimmer_1s_infinite]"></div>
|
| 183 |
+
</button>
|
| 184 |
+
<!-- Secondary CTA -->
|
| 185 |
+
<button class="px-lg py-sm bg-transparent border border-surface-tint/50 text-surface-tint font-label-xs text-label-xs uppercase tracking-wider rounded-DEFAULT hover:bg-surface-tint/10 hover:border-surface-tint transition-all duration-300 active:scale-95 shadow-[inset_0_0_10px_rgba(0,227,138,0)] hover:shadow-[inset_0_0_15px_rgba(0,227,138,0.2)]">
|
| 186 |
+
View Demo
|
| 187 |
+
</button>
|
| 188 |
+
</div>
|
| 189 |
+
<!-- Micro-data readouts -->
|
| 190 |
+
<div class="flex items-center gap-lg mt-xl pt-lg border-t border-outline/20 w-full max-w-md">
|
| 191 |
+
<div>
|
| 192 |
+
<div class="font-data-mono text-data-mono text-primary-container">99.9%</div>
|
| 193 |
+
<div class="font-label-xs text-[10px] text-outline mt-1 uppercase">Accuracy Rate</div>
|
| 194 |
+
</div>
|
| 195 |
+
<div class="w-px h-8 bg-outline/30"></div>
|
| 196 |
+
<div>
|
| 197 |
+
<div class="font-data-mono text-data-mono text-secondary-container"><15ms</div>
|
| 198 |
+
<div class="font-label-xs text-[10px] text-outline mt-1 uppercase">Latency Ping</div>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
<!-- Right Column: Visualizer -->
|
| 203 |
+
<div class="relative w-full aspect-square max-w-lg mx-auto flex items-center justify-center lg:justify-end">
|
| 204 |
+
<!-- Tech Frame -->
|
| 205 |
+
<div class="absolute inset-0 bg-surface-container-low/20 backdrop-blur-3xl rounded-full border border-outline/10 shadow-[inset_0_0_40px_rgba(0,0,0,0.8)] flex items-center justify-center overflow-hidden">
|
| 206 |
+
<!-- Decorative Scanning Rings -->
|
| 207 |
+
<div class="absolute w-[120%] h-[120%] border border-primary-container/10 rounded-full animate-[spin_20s_linear_infinite]"></div>
|
| 208 |
+
<div class="absolute w-[100%] h-[100%] border border-secondary-container/20 rounded-full border-dashed animate-[spin_15s_linear_infinite_reverse]"></div>
|
| 209 |
+
<div class="absolute w-[80%] h-[80%] border-2 border-primary-container/5 rounded-full"></div>
|
| 210 |
+
<!-- Core Visual -->
|
| 211 |
+
<div class="relative w-3/4 h-3/4 rounded-full overflow-hidden mix-blend-screen shadow-[0_0_60px_rgba(0,255,156,0.2)]">
|
| 212 |
+
<img alt="3D glowing cybernetic orb visualization" class="w-full h-full object-cover object-center opacity-80" data-alt="Abstract 3D glowing cybernetic orb with concentric energy waves, neon emerald green and cyan light emitting from a dark core, futuristic high-tech data visualization aesthetic, dark void background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuAfjdr7a8Q86RDUDxW_XWXXmcIjP7JOyIcxR4xcrlyRQZmNeR_EenBL8QBXKm6GcHvwWsudOrefOpJ8iocKhqnG1eqqCIBpbIqEbbtgJLZlHjST1TxCI-TBoazDTYTnr-Lf6jMuMrrGKFRgWai675Vl0mgUwTbVz2lfL1OG_DrEnls6BoPqHRRX_RkxpE1mSNEqDTqkA0AqzoMCdoikLZbabr5YYlZfdq7lO9fh37RM5ICH1o9BBOqolMlclMSDtd04scZIRgjvhMQ"/>
|
| 213 |
+
<!-- Overlay gradient to mesh with background -->
|
| 214 |
+
<div class="absolute inset-0 bg-gradient-to-t from-background via-transparent to-transparent"></div>
|
| 215 |
+
</div>
|
| 216 |
+
</div>
|
| 217 |
+
<!-- Floating HUD elements -->
|
| 218 |
+
<div class="absolute top-10 -left-4 bg-surface/80 backdrop-blur-md border border-outline/30 px-sm py-1 font-data-mono text-[10px] text-primary-container">SYS.OPT.OK</div>
|
| 219 |
+
<div class="absolute bottom-20 -right-4 bg-surface/80 backdrop-blur-md border border-outline/30 px-sm py-1 font-data-mono text-[10px] text-secondary-container flex items-center gap-1">
|
| 220 |
+
<span class="material-symbols-outlined text-[12px]">sync</span> LIVE
|
| 221 |
+
</div>
|
| 222 |
+
</div>
|
| 223 |
+
</div>
|
| 224 |
+
</main>
|
| 225 |
+
<!-- Footer Shared Component -->
|
| 226 |
+
<footer class="w-full py-8 px-10 flex flex-col md:flex-row justify-between items-center opacity-80 bg-black border-t border-emerald-900/30 relative z-10 font-mono text-[10px] uppercase tracking-[0.2em]">
|
| 227 |
+
<!-- Copyright -->
|
| 228 |
+
<div class="text-emerald-500 mb-4 md:mb-0">
|
| 229 |
+
© 2024 AUTHRIX COMMAND. SYSTEM_STATE: VIGILANT
|
| 230 |
+
</div>
|
| 231 |
+
<!-- Links -->
|
| 232 |
+
<div class="flex items-center space-x-md">
|
| 233 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Kernel</a>
|
| 234 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Protocol</a>
|
| 235 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Override</a>
|
| 236 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Diagnostics</a>
|
| 237 |
+
</div>
|
| 238 |
+
</footer>
|
| 239 |
+
<!-- Inline styles for custom animations that Tailwind standard config doesn't cover natively without arbitrary values,
|
| 240 |
+
using minimal standard tailwind approach where possible, falling back to basic CSS for the shimmer -->
|
| 241 |
+
<style>
|
| 242 |
+
@keyframes shimmer {
|
| 243 |
+
100% { transform: translateX(100%); }
|
| 244 |
+
}
|
| 245 |
+
</style>
|
| 246 |
+
</body></html>
|
stitch_authrix_deepfake_detection_engine/authrix_kinetic_1/DESIGN.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Authrix Kinetic
|
| 3 |
+
colors:
|
| 4 |
+
surface: '#0c150f'
|
| 5 |
+
surface-dim: '#0c150f'
|
| 6 |
+
surface-bright: '#323c34'
|
| 7 |
+
surface-container-lowest: '#07100a'
|
| 8 |
+
surface-container-low: '#141e17'
|
| 9 |
+
surface-container: '#18221b'
|
| 10 |
+
surface-container-high: '#232c25'
|
| 11 |
+
surface-container-highest: '#2d3730'
|
| 12 |
+
on-surface: '#dae5da'
|
| 13 |
+
on-surface-variant: '#b9cbbc'
|
| 14 |
+
inverse-surface: '#dae5da'
|
| 15 |
+
inverse-on-surface: '#29332b'
|
| 16 |
+
outline: '#849587'
|
| 17 |
+
outline-variant: '#3b4b3f'
|
| 18 |
+
surface-tint: '#00e38a'
|
| 19 |
+
primary: '#f3fff3'
|
| 20 |
+
on-primary: '#00391f'
|
| 21 |
+
primary-container: '#00ff9c'
|
| 22 |
+
on-primary-container: '#007142'
|
| 23 |
+
inverse-primary: '#006d40'
|
| 24 |
+
secondary: '#bdf4ff'
|
| 25 |
+
on-secondary: '#00363d'
|
| 26 |
+
secondary-container: '#00e3fd'
|
| 27 |
+
on-secondary-container: '#00616d'
|
| 28 |
+
tertiary: '#fffaff'
|
| 29 |
+
on-tertiary: '#3b2f00'
|
| 30 |
+
tertiary-container: '#ffdd65'
|
| 31 |
+
on-tertiary-container: '#766000'
|
| 32 |
+
error: '#ffb4ab'
|
| 33 |
+
on-error: '#690005'
|
| 34 |
+
error-container: '#93000a'
|
| 35 |
+
on-error-container: '#ffdad6'
|
| 36 |
+
primary-fixed: '#56ffa7'
|
| 37 |
+
primary-fixed-dim: '#00e38a'
|
| 38 |
+
on-primary-fixed: '#002110'
|
| 39 |
+
on-primary-fixed-variant: '#00522f'
|
| 40 |
+
secondary-fixed: '#9cf0ff'
|
| 41 |
+
secondary-fixed-dim: '#00daf3'
|
| 42 |
+
on-secondary-fixed: '#001f24'
|
| 43 |
+
on-secondary-fixed-variant: '#004f58'
|
| 44 |
+
tertiary-fixed: '#ffe17a'
|
| 45 |
+
tertiary-fixed-dim: '#e4c44f'
|
| 46 |
+
on-tertiary-fixed: '#231b00'
|
| 47 |
+
on-tertiary-fixed-variant: '#554500'
|
| 48 |
+
background: '#0c150f'
|
| 49 |
+
on-background: '#dae5da'
|
| 50 |
+
surface-variant: '#2d3730'
|
| 51 |
+
typography:
|
| 52 |
+
h1:
|
| 53 |
+
fontFamily: Space Grotesk
|
| 54 |
+
fontSize: 48px
|
| 55 |
+
fontWeight: '700'
|
| 56 |
+
lineHeight: '1.1'
|
| 57 |
+
letterSpacing: -0.02em
|
| 58 |
+
h2:
|
| 59 |
+
fontFamily: Space Grotesk
|
| 60 |
+
fontSize: 32px
|
| 61 |
+
fontWeight: '600'
|
| 62 |
+
lineHeight: '1.2'
|
| 63 |
+
letterSpacing: -0.01em
|
| 64 |
+
h3:
|
| 65 |
+
fontFamily: Space Grotesk
|
| 66 |
+
fontSize: 24px
|
| 67 |
+
fontWeight: '600'
|
| 68 |
+
lineHeight: '1.3'
|
| 69 |
+
letterSpacing: 0em
|
| 70 |
+
body-base:
|
| 71 |
+
fontFamily: Space Grotesk
|
| 72 |
+
fontSize: 16px
|
| 73 |
+
fontWeight: '400'
|
| 74 |
+
lineHeight: '1.6'
|
| 75 |
+
letterSpacing: 0.01em
|
| 76 |
+
data-mono:
|
| 77 |
+
fontFamily: Space Grotesk
|
| 78 |
+
fontSize: 14px
|
| 79 |
+
fontWeight: '500'
|
| 80 |
+
lineHeight: '1.5'
|
| 81 |
+
letterSpacing: 0.05em
|
| 82 |
+
label-xs:
|
| 83 |
+
fontFamily: Space Grotesk
|
| 84 |
+
fontSize: 12px
|
| 85 |
+
fontWeight: '700'
|
| 86 |
+
lineHeight: '1'
|
| 87 |
+
letterSpacing: 0.1em
|
| 88 |
+
spacing:
|
| 89 |
+
base: 4px
|
| 90 |
+
xs: 4px
|
| 91 |
+
sm: 8px
|
| 92 |
+
md: 16px
|
| 93 |
+
lg: 24px
|
| 94 |
+
xl: 48px
|
| 95 |
+
gutter: 20px
|
| 96 |
+
margin: 32px
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## Brand & Style
|
| 100 |
+
|
| 101 |
+
The design system is engineered to evoke the high-stakes environment of a state-of-the-art cybersecurity lab and a futuristic AI command center. It targets elite technical operators who require high-precision data visualization and a sense of absolute control. The aesthetic is "Industrial-Futurism"—combining the raw, structural feel of hardware with the ethereal, cinematic quality of advanced software interfaces.
|
| 102 |
+
|
| 103 |
+
The brand personality is authoritative, vigilant, and cutting-edge. Every interface element should feel like a piece of high-end equipment rather than a standard web component. The user experience is built on the tension between deep obsidian voids and vibrant, hyper-focused energy emissions (neon glows), creating a sense of immense power being harnessed through a precise digital lens.
|
| 104 |
+
|
| 105 |
+
## Colors
|
| 106 |
+
|
| 107 |
+
The palette is anchored in a "Deep Space" foundation, utilizing Zinc-950 and pure blacks to maximize the luminous impact of the accent colors.
|
| 108 |
+
|
| 109 |
+
- **Primary Neon Green (#00ff9c):** Used for active states, successful validations, and primary calls to action. It represents "System GO" and operational health.
|
| 110 |
+
- **Cyan Secondary (#00e5ff):** Dedicated to data visualization, information overlays, and secondary interactive elements. It provides a "scanning" or "holographic" feel.
|
| 111 |
+
- **Alert Red (#ff3e3e):** Reserved strictly for critical system breaches, errors, and destructive actions.
|
| 112 |
+
- **Neutrals:** A range of Zinc grays (900 to 400) provide structural hierarchy.
|
| 113 |
+
|
| 114 |
+
The color application relies heavily on luminescence; colors are rarely flat but are instead accompanied by subtle radial glows and inner shadows to simulate light emitting from within the screen.
|
| 115 |
+
|
| 116 |
+
## Typography
|
| 117 |
+
|
| 118 |
+
This design system utilizes **Space Grotesk** across all levels to maintain a cohesive, technical identity.
|
| 119 |
+
|
| 120 |
+
- **Headlines:** Use Bold or Semi-Bold weights. High-level headers should feel architectural and commanding.
|
| 121 |
+
- **Body Text:** Standard reading weights prioritize legibility against the dark background.
|
| 122 |
+
- **Metadata & Technical Readouts:** Use the uppercase "data-mono" styling to simulate terminal outputs and sensor readings.
|
| 123 |
+
- **Emphasis:** Rather than italics, use color shifts (to Primary Neon) or weight increases to highlight critical information.
|
| 124 |
+
|
| 125 |
+
## Layout & Spacing
|
| 126 |
+
|
| 127 |
+
The design system employs a **Modular Grid System** with a high-density rhythm. The spacing is tight and deliberate, reflecting the efficiency of a command console.
|
| 128 |
+
|
| 129 |
+
- **Grid:** A 12-column fluid grid for main layouts, with a 20px gutter to ensure data density without crowding.
|
| 130 |
+
- **Rhythm:** All margins and padding follow a 4px baseline.
|
| 131 |
+
- **Density:** Use "Compact" spacing for data tables and "Spacious" spacing for dashboard overviews.
|
| 132 |
+
- **Borders as Spacing:** In many instances, thin 1px borders with 10% opacity are used in place of whitespace to define structural sections, reinforcing the industrial aesthetic.
|
| 133 |
+
|
| 134 |
+
## Elevation & Depth
|
| 135 |
+
|
| 136 |
+
Depth in this design system is achieved through **Luminous Layering** rather than traditional shadows.
|
| 137 |
+
|
| 138 |
+
1. **Glassmorphism:** Surfaces use a 10-20% opacity fill with a heavy (20px-40px) backdrop blur. This creates a "frosted laboratory glass" effect.
|
| 139 |
+
2. **Inner Shadows:** Use subtle, dark inner shadows on the top and left edges to create an "etched" or "recessed" look for input fields and containers.
|
| 140 |
+
3. **Border Glows:** Elements at higher elevation levels (e.g., modals) feature a 1px solid border with an outer "bloom" or neon glow effect using the Primary or Secondary color.
|
| 141 |
+
4. **Atmospheric Particles:** Background layers should feature very low-opacity animated particles or scan lines to give the UI a sense of "living" energy.
|
| 142 |
+
|
| 143 |
+
## Shapes
|
| 144 |
+
|
| 145 |
+
The design system adopts a **Sharp (0px)** philosophy for its core structural elements. This reinforces the industrial, high-precision nature of the brand.
|
| 146 |
+
|
| 147 |
+
- **Containers & Buttons:** Square corners suggest rigidity and technical accuracy.
|
| 148 |
+
- **Active Indicators:** Small 45-degree clipped corners can be used on decorative elements or active "tabs" to add a futuristic, military-spec feel.
|
| 149 |
+
- **Icons:** Must be stroke-based, using a consistent 1.5px or 2px weight, echoing the geometric precision of the typography.
|
| 150 |
+
|
| 151 |
+
## Components
|
| 152 |
+
|
| 153 |
+
- **Buttons:** Primary buttons feature a solid #00ff9c background with black text. On hover, they emit a strong radial neon glow. Secondary buttons are "Ghost" style with a 1px glowing border.
|
| 154 |
+
- **Input Fields:** Recessed into the background using inner shadows. The bottom border "lights up" with Cyan when focused. Use monospace font for input values.
|
| 155 |
+
- **Cards:** These are the primary glassmorphic containers. They feature a subtle "Scan Line" animation overlay (a 1px line moving vertically at 2% opacity).
|
| 156 |
+
- **Status Chips:** Small, rectangular tags with no border-radius. They use a "pulse" animation for active states (a soft glow that expands and contracts).
|
| 157 |
+
- **Data Grids:** High-density tables with subtle horizontal separators. Row hovers should trigger a full-width Cyan highlight at 5% opacity.
|
| 158 |
+
- **Command Line Interface (CLI) Modules:** Specialized components for raw data input, featuring a blinking underscore cursor and Primary color text.
|
| 159 |
+
- **Progress Gauges:** Circular or linear indicators using "segmented" bars rather than solid fills to emphasize the mechanical, digital readout style.
|
stitch_authrix_deepfake_detection_engine/authrix_kinetic_2/DESIGN.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Authrix Kinetic
|
| 3 |
+
colors:
|
| 4 |
+
surface: '#131313'
|
| 5 |
+
surface-dim: '#131313'
|
| 6 |
+
surface-bright: '#3a3939'
|
| 7 |
+
surface-container-lowest: '#0e0e0e'
|
| 8 |
+
surface-container-low: '#1c1b1b'
|
| 9 |
+
surface-container: '#201f1f'
|
| 10 |
+
surface-container-high: '#2a2a2a'
|
| 11 |
+
surface-container-highest: '#353534'
|
| 12 |
+
on-surface: '#e5e2e1'
|
| 13 |
+
on-surface-variant: '#b9cbbc'
|
| 14 |
+
inverse-surface: '#e5e2e1'
|
| 15 |
+
inverse-on-surface: '#313030'
|
| 16 |
+
outline: '#849587'
|
| 17 |
+
outline-variant: '#3b4b3f'
|
| 18 |
+
surface-tint: '#00e38a'
|
| 19 |
+
primary: '#f3fff3'
|
| 20 |
+
on-primary: '#00391f'
|
| 21 |
+
primary-container: '#00ff9c'
|
| 22 |
+
on-primary-container: '#007142'
|
| 23 |
+
inverse-primary: '#006d40'
|
| 24 |
+
secondary: '#d3fbff'
|
| 25 |
+
on-secondary: '#00363a'
|
| 26 |
+
secondary-container: '#00eefc'
|
| 27 |
+
on-secondary-container: '#00686f'
|
| 28 |
+
tertiary: '#fffbff'
|
| 29 |
+
on-tertiary: '#680008'
|
| 30 |
+
tertiary-container: '#ffd6d2'
|
| 31 |
+
on-tertiary-container: '#c7041a'
|
| 32 |
+
error: '#ffb4ab'
|
| 33 |
+
on-error: '#690005'
|
| 34 |
+
error-container: '#93000a'
|
| 35 |
+
on-error-container: '#ffdad6'
|
| 36 |
+
primary-fixed: '#56ffa7'
|
| 37 |
+
primary-fixed-dim: '#00e38a'
|
| 38 |
+
on-primary-fixed: '#002110'
|
| 39 |
+
on-primary-fixed-variant: '#00522f'
|
| 40 |
+
secondary-fixed: '#7df4ff'
|
| 41 |
+
secondary-fixed-dim: '#00dbe9'
|
| 42 |
+
on-secondary-fixed: '#002022'
|
| 43 |
+
on-secondary-fixed-variant: '#004f54'
|
| 44 |
+
tertiary-fixed: '#ffdad6'
|
| 45 |
+
tertiary-fixed-dim: '#ffb3ac'
|
| 46 |
+
on-tertiary-fixed: '#410003'
|
| 47 |
+
on-tertiary-fixed-variant: '#930010'
|
| 48 |
+
background: '#131313'
|
| 49 |
+
on-background: '#e5e2e1'
|
| 50 |
+
surface-variant: '#353534'
|
| 51 |
+
typography:
|
| 52 |
+
h1-display:
|
| 53 |
+
fontFamily: Space Grotesk
|
| 54 |
+
fontSize: 48px
|
| 55 |
+
fontWeight: '700'
|
| 56 |
+
lineHeight: '1.1'
|
| 57 |
+
letterSpacing: -0.02em
|
| 58 |
+
h2-technical:
|
| 59 |
+
fontFamily: Space Grotesk
|
| 60 |
+
fontSize: 32px
|
| 61 |
+
fontWeight: '600'
|
| 62 |
+
lineHeight: '1.2'
|
| 63 |
+
letterSpacing: 0.01em
|
| 64 |
+
body-main:
|
| 65 |
+
fontFamily: Inter
|
| 66 |
+
fontSize: 16px
|
| 67 |
+
fontWeight: '400'
|
| 68 |
+
lineHeight: '1.6'
|
| 69 |
+
letterSpacing: 0em
|
| 70 |
+
body-light:
|
| 71 |
+
fontFamily: Inter
|
| 72 |
+
fontSize: 14px
|
| 73 |
+
fontWeight: '300'
|
| 74 |
+
lineHeight: '1.5'
|
| 75 |
+
letterSpacing: 0.03em
|
| 76 |
+
label-caps:
|
| 77 |
+
fontFamily: Space Grotesk
|
| 78 |
+
fontSize: 11px
|
| 79 |
+
fontWeight: '600'
|
| 80 |
+
lineHeight: '1'
|
| 81 |
+
letterSpacing: 0.15em
|
| 82 |
+
rounded:
|
| 83 |
+
sm: 0.125rem
|
| 84 |
+
DEFAULT: 0.25rem
|
| 85 |
+
md: 0.375rem
|
| 86 |
+
lg: 0.5rem
|
| 87 |
+
xl: 0.75rem
|
| 88 |
+
full: 9999px
|
| 89 |
+
spacing:
|
| 90 |
+
unit: 4px
|
| 91 |
+
gutter: 24px
|
| 92 |
+
margin: 48px
|
| 93 |
+
container-max: 1440px
|
| 94 |
+
grid-overlay: 32px
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
## Brand & Style
|
| 98 |
+
|
| 99 |
+
The design system is engineered to evoke the atmosphere of a high-security AI research facility—clandestine, powerful, and hyper-intelligent. The visual narrative centers on "The Guardian in the Machine," balancing cold, technical precision with the ethereal glow of advanced neural networks.
|
| 100 |
+
|
| 101 |
+
The style is a sophisticated fusion of **Cinematic Glassmorphism** and **Technical Minimalism**. It utilizes deep layers of transparency and light to create a sense of infinite digital depth. Every interaction should feel like a high-stakes command within a tactical operations center, blending the sleek automotive precision of Tesla’s interface with the information-dense, holographic aesthetics of futuristic head-up displays (HUDs).
|
| 102 |
+
|
| 103 |
+
Targeting high-level security architects and C-suite executives, the UI prioritizes "Perceived Intelligence"—where the interface feels like it is thinking alongside the user through subtle animations, particle transitions, and reactive luminescence.
|
| 104 |
+
|
| 105 |
+
## Colors
|
| 106 |
+
|
| 107 |
+
The palette is anchored by a "True Deep" black (#050505) to provide a canvas for light-based hierarchy.
|
| 108 |
+
- **Neon Green (#00ff9c):** The primary kinetic color, used for active states, successful encryptions, and "Safe" status indicators.
|
| 109 |
+
- **Cyan Glow (#00f0ff):** Used for secondary accents, data visualization, and soft atmospheric backlighting.
|
| 110 |
+
- **Subtle Red (#ff3b3b):** Reserved strictly for critical breaches and system warnings; it should feel alarming but polished, never vibrating against the black background.
|
| 111 |
+
- **Grayscale:** Uses high-opacity whites for headings and low-opacity "Oxygen" blues for subtext to maintain the cinematic atmosphere.
|
| 112 |
+
|
| 113 |
+
## Typography
|
| 114 |
+
|
| 115 |
+
This design system utilizes a dual-font strategy. **Space Grotesk** provides a technical, geometric edge for headings and data labels, reinforcing the "AI Lab" aesthetic. **Inter** is utilized for body copy and dense technical logs to ensure maximum legibility at smaller scales.
|
| 116 |
+
|
| 117 |
+
Headings should be treated as structural elements—bold and authoritative. Subtext and secondary labels use lighter weights with increased letter spacing to mimic the look of a digital readout or tactical terminal. All capitalized labels should have a minimum of 0.1em tracking.
|
| 118 |
+
|
| 119 |
+
## Layout & Spacing
|
| 120 |
+
|
| 121 |
+
The layout philosophy follows a **Subtle Grid** model. All layouts are based on a 4px baseline unit, ensuring mathematical rhythm. A 12-column fluid grid is used for primary dashboard layouts, but it is visually reinforced by a background "grid mesh"—a very faint 32px x 32px grid pattern (opacity 2-4%) that anchors the UI elements.
|
| 122 |
+
|
| 123 |
+
Padding is generous to convey a "premium" feel. Layouts should utilize "Safe Zones" around critical data visualizations, allowing the UI to breathe despite high information density. Elements are often grouped in modular clusters to mimic a HUD-style modularity.
|
| 124 |
+
|
| 125 |
+
## Elevation & Depth
|
| 126 |
+
|
| 127 |
+
Depth in this design system is achieved through **Luminous Layering** rather than traditional shadows.
|
| 128 |
+
- **Glassmorphism:** Primary containers use a 10-20px backdrop blur with a 1px "inner-glow" border (white at 5-10% opacity).
|
| 129 |
+
- **Z-Axis Tiering:** Elements closer to the user have a brighter, more defined border and a soft Cyan or Green "under-glow" (box-shadow with 40px spread, 10% opacity).
|
| 130 |
+
- **Parallax Particles:** Use a subtle particle background on a separate Z-layer to create a sense of environmental volume.
|
| 131 |
+
- **Tonal Stepping:** The background is #050505, cards are "glass" at 3% white, and hovered elements increase to 8% white.
|
| 132 |
+
|
| 133 |
+
## Shapes
|
| 134 |
+
|
| 135 |
+
The shape language is "Soft-Technical." We use a **Soft (0.25rem)** base radius to maintain a modern, engineered feel without the aggression of sharp 90-degree corners.
|
| 136 |
+
|
| 137 |
+
- **Primary Radius:** 4px for inputs, buttons, and small widgets.
|
| 138 |
+
- **Container Radius:** 8px for large dashboard cards.
|
| 139 |
+
- **Specialty Shapes:** Use 45-degree chamfered corners (clipped corners) on decorative elements or "Scan" buttons to heighten the futuristic military-grade aesthetic.
|
| 140 |
+
|
| 141 |
+
## Components
|
| 142 |
+
|
| 143 |
+
- **Buttons:** Primary buttons feature a solid #00ff9c fill with black text. Secondary buttons are "Ghost" style with a 1px primary border and a soft neon outer glow on hover.
|
| 144 |
+
- **Input Fields:** Semi-transparent dark backgrounds with an animated "bottom-line" that expands from the center when focused, glowing in Secondary Cyan.
|
| 145 |
+
- **Data Chips:** Small, pill-shaped with high letter-spacing labels. Status chips for "Active" should have a pulsing 4px dot icon.
|
| 146 |
+
- **Cards:** Defined by "Glass" surfaces. Use a subtle top-left to bottom-right linear gradient (white at 5% to transparent).
|
| 147 |
+
- **Security Scanners:** Custom components including circular progress rings with "rotating data" segments and particle-line scans moving vertically across the component area.
|
| 148 |
+
- **Alerts:** Red borders with a low-opacity red background tint. The text should remain white for readability, while the icon carries the red warning color.
|
stitch_authrix_deepfake_detection_engine/authrix_system_processing/code.html
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
|
| 3 |
+
<html class="dark" lang="en"><head>
|
| 4 |
+
<meta charset="utf-8"/>
|
| 5 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 6 |
+
<title>AUTHRIX AI - Analysis State</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
"inverse-surface": "#dae5da",
|
| 18 |
+
"on-error": "#690005",
|
| 19 |
+
"surface-tint": "#00e38a",
|
| 20 |
+
"error": "#ffb4ab",
|
| 21 |
+
"outline-variant": "#3b4b3f",
|
| 22 |
+
"outline": "#849587",
|
| 23 |
+
"background": "#0c150f",
|
| 24 |
+
"surface-container-high": "#232c25",
|
| 25 |
+
"secondary-container": "#00e3fd",
|
| 26 |
+
"on-surface-variant": "#b9cbbc",
|
| 27 |
+
"on-secondary-container": "#00616d",
|
| 28 |
+
"on-error-container": "#ffdad6",
|
| 29 |
+
"primary": "#f3fff3",
|
| 30 |
+
"primary-container": "#00ff9c",
|
| 31 |
+
"on-primary": "#00391f",
|
| 32 |
+
"surface-bright": "#323c34",
|
| 33 |
+
"primary-fixed-dim": "#00e38a",
|
| 34 |
+
"tertiary-fixed-dim": "#e4c44f",
|
| 35 |
+
"on-secondary-fixed": "#001f24",
|
| 36 |
+
"tertiary-fixed": "#ffe17a",
|
| 37 |
+
"on-tertiary-container": "#766000",
|
| 38 |
+
"on-secondary": "#00363d",
|
| 39 |
+
"on-tertiary-fixed": "#231b00",
|
| 40 |
+
"on-tertiary": "#3b2f00",
|
| 41 |
+
"surface-container-low": "#141e17",
|
| 42 |
+
"surface": "#0c150f",
|
| 43 |
+
"primary-fixed": "#56ffa7",
|
| 44 |
+
"secondary-fixed-dim": "#00daf3",
|
| 45 |
+
"on-secondary-fixed-variant": "#004f58",
|
| 46 |
+
"inverse-on-surface": "#29332b",
|
| 47 |
+
"surface-container": "#18221b",
|
| 48 |
+
"on-surface": "#dae5da",
|
| 49 |
+
"on-tertiary-fixed-variant": "#554500",
|
| 50 |
+
"error-container": "#93000a",
|
| 51 |
+
"surface-variant": "#2d3730",
|
| 52 |
+
"surface-container-highest": "#2d3730",
|
| 53 |
+
"secondary-fixed": "#9cf0ff",
|
| 54 |
+
"on-background": "#dae5da",
|
| 55 |
+
"on-primary-container": "#007142",
|
| 56 |
+
"inverse-primary": "#006d40",
|
| 57 |
+
"on-primary-fixed-variant": "#00522f",
|
| 58 |
+
"secondary": "#bdf4ff",
|
| 59 |
+
"tertiary": "#fffaff",
|
| 60 |
+
"surface-dim": "#0c150f",
|
| 61 |
+
"on-primary-fixed": "#002110",
|
| 62 |
+
"surface-container-lowest": "#07100a",
|
| 63 |
+
"tertiary-container": "#ffdd65"
|
| 64 |
+
},
|
| 65 |
+
borderRadius: {
|
| 66 |
+
"DEFAULT": "0.25rem",
|
| 67 |
+
"lg": "0.5rem",
|
| 68 |
+
"xl": "0.75rem",
|
| 69 |
+
"full": "9999px"
|
| 70 |
+
},
|
| 71 |
+
spacing: {
|
| 72 |
+
"xs": "4px",
|
| 73 |
+
"md": "16px",
|
| 74 |
+
"base": "4px",
|
| 75 |
+
"sm": "8px",
|
| 76 |
+
"xl": "48px",
|
| 77 |
+
"lg": "24px",
|
| 78 |
+
"gutter": "20px",
|
| 79 |
+
"margin": "32px"
|
| 80 |
+
},
|
| 81 |
+
fontFamily: {
|
| 82 |
+
"h2": ["Space Grotesk"],
|
| 83 |
+
"h1": ["Space Grotesk"],
|
| 84 |
+
"h3": ["Space Grotesk"],
|
| 85 |
+
"label-xs": ["Space Grotesk"],
|
| 86 |
+
"data-mono": ["Space Grotesk"],
|
| 87 |
+
"body-base": ["Space Grotesk"]
|
| 88 |
+
},
|
| 89 |
+
fontSize: {
|
| 90 |
+
"h2": ["32px", { lineHeight: "1.2", letterSpacing: "-0.01em", fontWeight: "600" }],
|
| 91 |
+
"h1": ["48px", { lineHeight: "1.1", letterSpacing: "-0.02em", fontWeight: "700" }],
|
| 92 |
+
"h3": ["24px", { lineHeight: "1.3", letterSpacing: "0em", fontWeight: "600" }],
|
| 93 |
+
"label-xs": ["12px", { lineHeight: "1", letterSpacing: "0.1em", fontWeight: "700" }],
|
| 94 |
+
"data-mono": ["14px", { lineHeight: "1.5", letterSpacing: "0.05em", fontWeight: "500" }],
|
| 95 |
+
"body-base": ["16px", { lineHeight: "1.6", letterSpacing: "0.01em", fontWeight: "400" }]
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
</script>
|
| 101 |
+
<style>
|
| 102 |
+
.scan-line {
|
| 103 |
+
background: linear-gradient(to bottom, transparent, rgba(0, 255, 156, 0.2), transparent);
|
| 104 |
+
height: 2px;
|
| 105 |
+
width: 100%;
|
| 106 |
+
position: absolute;
|
| 107 |
+
top: 0;
|
| 108 |
+
left: 0;
|
| 109 |
+
opacity: 0.5;
|
| 110 |
+
}
|
| 111 |
+
.noise-bg {
|
| 112 |
+
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
|
| 113 |
+
opacity: 0.05;
|
| 114 |
+
mix-blend-mode: overlay;
|
| 115 |
+
pointer-events: none;
|
| 116 |
+
}
|
| 117 |
+
.glass-panel {
|
| 118 |
+
background: rgba(35, 44, 37, 0.2);
|
| 119 |
+
backdrop-filter: blur(20px);
|
| 120 |
+
border: 1px solid rgba(0, 255, 156, 0.1);
|
| 121 |
+
box-shadow: inset 1px 1px 0 rgba(255, 255, 255, 0.05);
|
| 122 |
+
}
|
| 123 |
+
.glow-border-active {
|
| 124 |
+
border: 1px solid rgba(0, 255, 156, 0.5);
|
| 125 |
+
box-shadow: 0 0 15px rgba(0, 255, 156, 0.2);
|
| 126 |
+
}
|
| 127 |
+
</style>
|
| 128 |
+
</head>
|
| 129 |
+
<body class="bg-background text-on-background min-h-screen font-body-base overflow-hidden relative">
|
| 130 |
+
<!-- Ambient Background Elements -->
|
| 131 |
+
<div class="absolute inset-0 noise-bg z-0"></div>
|
| 132 |
+
<div class="absolute top-0 left-0 w-full h-full z-0 overflow-hidden">
|
| 133 |
+
<div class="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-surface-container-high/30 via-background to-background"></div>
|
| 134 |
+
<div class="absolute top-1/4 left-1/4 w-96 h-96 bg-primary-container/5 rounded-full blur-[100px] pointer-events-none"></div>
|
| 135 |
+
</div>
|
| 136 |
+
<div class="scan-line z-0" style="top: 30%"></div>
|
| 137 |
+
<!-- Central Content Area -->
|
| 138 |
+
<div class="relative z-10 container mx-auto px-6 h-screen flex flex-col items-center justify-center">
|
| 139 |
+
<!-- Top Header Info -->
|
| 140 |
+
<div class="w-full max-w-3xl mb-xl text-center">
|
| 141 |
+
<h1 class="font-h1 text-h1 text-primary-container drop-shadow-[0_0_8px_rgba(0,255,156,0.3)] mb-sm">SYSTEM_STATE: ANALYSIS</h1>
|
| 142 |
+
<p class="font-data-mono text-data-mono text-primary-fixed-dim/70 tracking-[0.2em] uppercase">Initiating Deep Inspection Protocol</p>
|
| 143 |
+
</div>
|
| 144 |
+
<!-- Main Bento Grid Layout for Analysis Console -->
|
| 145 |
+
<div class="w-full max-w-4xl grid grid-cols-1 md:grid-cols-12 gap-gutter">
|
| 146 |
+
<!-- Visual / Orb Section (Left/Top) -->
|
| 147 |
+
<div class="md:col-span-5 glass-panel rounded-xl p-lg flex flex-col items-center justify-center relative min-h-[300px] border-outline/20">
|
| 148 |
+
<div class="absolute top-sm left-sm font-label-xs text-label-xs text-outline tracking-widest uppercase">Target Vector</div>
|
| 149 |
+
<!-- Central Rotating Orb Visual -->
|
| 150 |
+
<div class="relative w-48 h-48 rounded-full border border-primary-container/30 shadow-[0_0_30px_rgba(0,255,156,0.1)] flex items-center justify-center">
|
| 151 |
+
<div class="absolute inset-2 rounded-full border border-dashed border-primary-container/40"></div>
|
| 152 |
+
<div class="absolute inset-6 rounded-full bg-surface-container-high shadow-inner flex items-center justify-center overflow-hidden">
|
| 153 |
+
<img alt="Abstract glowing grid structure representing data analysis" class="w-full h-full object-cover opacity-60 mix-blend-screen" data-alt="Abstract glowing geometric grid structure with vibrant cyan and emerald neon lights against dark background, technological feel" src="https://lh3.googleusercontent.com/aida-public/AB6AXuDJdb_uls8CdiY4F9nfTubj-C8Q_ONhUf9a91ObNEcp65p0OrntMRuJI0C38IGnMHouwLDXGGbfpcWSmv6_t0RoIQeoLXUtfGy6tuTwxIiSMVBXHrKIT08HzkIeO5xTy_BpvPKs9thCqsvR5RCtdcWERQuAN62JbBXAJLgRBam6AqZTLX4Oj4W4nhE02AfHC__Ef2UmE41kdBX9auPEFAlMFjX3YJoY7MBXyjN3PK5EufCBhz4A2AIvR0fJlYbAN-JPfn57NzQba80"/>
|
| 154 |
+
</div>
|
| 155 |
+
<div class="absolute inset-0 rounded-full border-t-2 border-primary-container/80 blur-[2px]"></div>
|
| 156 |
+
<span class="material-symbols-outlined absolute text-primary-container text-4xl drop-shadow-[0_0_10px_rgba(0,255,156,0.8)]">troubleshoot</span>
|
| 157 |
+
</div>
|
| 158 |
+
<div class="mt-lg text-center w-full">
|
| 159 |
+
<div class="font-data-mono text-data-mono text-on-surface-variant flex items-center justify-between w-full border-b border-outline/10 pb-xs mb-xs">
|
| 160 |
+
<span>DATA_STREAM</span>
|
| 161 |
+
<span class="text-secondary-fixed">ACTIVE</span>
|
| 162 |
+
</div>
|
| 163 |
+
<div class="font-data-mono text-data-mono text-on-surface-variant flex items-center justify-between w-full border-b border-outline/10 pb-xs">
|
| 164 |
+
<span>THROUGHPUT</span>
|
| 165 |
+
<span class="text-tertiary-container">2.4 TB/s</span>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
<!-- Dynamic Status List Section (Right/Bottom) -->
|
| 170 |
+
<div class="md:col-span-7 glass-panel rounded-xl p-lg flex flex-col justify-center gap-md border-outline/20">
|
| 171 |
+
<div class="flex justify-between items-center mb-sm">
|
| 172 |
+
<div class="font-label-xs text-label-xs text-outline tracking-widest uppercase">Processing Pipeline</div>
|
| 173 |
+
<div class="font-data-mono text-data-mono text-primary-container bg-primary-container/10 px-2 py-1 rounded">68% COMPLETE</div>
|
| 174 |
+
</div>
|
| 175 |
+
<!-- Step 1: Completed -->
|
| 176 |
+
<div class="flex flex-col gap-xs opacity-60">
|
| 177 |
+
<div class="flex justify-between items-center">
|
| 178 |
+
<div class="flex items-center gap-sm">
|
| 179 |
+
<span class="material-symbols-outlined text-outline text-sm">check_circle</span>
|
| 180 |
+
<span class="font-data-mono text-data-mono text-on-surface">Extracting frames...</span>
|
| 181 |
+
</div>
|
| 182 |
+
<span class="font-data-mono text-data-mono text-outline">100%</span>
|
| 183 |
+
</div>
|
| 184 |
+
<div class="w-full h-1 bg-surface-container-highest overflow-hidden">
|
| 185 |
+
<div class="h-full bg-outline w-full"></div>
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
<!-- Step 2: Completed -->
|
| 189 |
+
<div class="flex flex-col gap-xs opacity-60">
|
| 190 |
+
<div class="flex justify-between items-center">
|
| 191 |
+
<div class="flex items-center gap-sm">
|
| 192 |
+
<span class="material-symbols-outlined text-outline text-sm">check_circle</span>
|
| 193 |
+
<span class="font-data-mono text-data-mono text-on-surface">Detecting faces...</span>
|
| 194 |
+
</div>
|
| 195 |
+
<span class="font-data-mono text-data-mono text-outline">100%</span>
|
| 196 |
+
</div>
|
| 197 |
+
<div class="w-full h-1 bg-surface-container-highest overflow-hidden">
|
| 198 |
+
<div class="h-full bg-outline w-full"></div>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
<!-- Step 3: Active Processing -->
|
| 202 |
+
<div class="flex flex-col gap-xs glow-border-active bg-primary-container/5 p-sm -mx-sm rounded-lg">
|
| 203 |
+
<div class="flex justify-between items-center">
|
| 204 |
+
<div class="flex items-center gap-sm">
|
| 205 |
+
<span class="material-symbols-outlined text-primary-container text-sm animate-pulse">memory</span>
|
| 206 |
+
<span class="font-data-mono text-data-mono text-primary-container font-bold">Running model inference...</span>
|
| 207 |
+
</div>
|
| 208 |
+
<span class="font-data-mono text-data-mono text-primary-container">42%</span>
|
| 209 |
+
</div>
|
| 210 |
+
<!-- Segmented Progress Bar for Active State -->
|
| 211 |
+
<div class="w-full h-2 bg-surface-container-highest flex gap-[2px]">
|
| 212 |
+
<div class="h-full bg-primary-container/80 flex-1 shadow-[0_0_8px_rgba(0,255,156,0.5)]"></div>
|
| 213 |
+
<div class="h-full bg-primary-container/80 flex-1 shadow-[0_0_8px_rgba(0,255,156,0.5)]"></div>
|
| 214 |
+
<div class="h-full bg-primary-container/80 flex-1 shadow-[0_0_8px_rgba(0,255,156,0.5)]"></div>
|
| 215 |
+
<div class="h-full bg-primary-container/80 flex-1 shadow-[0_0_8px_rgba(0,255,156,0.5)]"></div>
|
| 216 |
+
<div class="h-full bg-primary-container/20 flex-1"></div>
|
| 217 |
+
<div class="h-full bg-primary-container/20 flex-1"></div>
|
| 218 |
+
<div class="h-full bg-primary-container/20 flex-1"></div>
|
| 219 |
+
<div class="h-full bg-primary-container/20 flex-1"></div>
|
| 220 |
+
<div class="h-full bg-primary-container/20 flex-1"></div>
|
| 221 |
+
<div class="h-full bg-primary-container/20 flex-1"></div>
|
| 222 |
+
</div>
|
| 223 |
+
</div>
|
| 224 |
+
<!-- Step 4: Pending -->
|
| 225 |
+
<div class="flex flex-col gap-xs opacity-30 mt-sm">
|
| 226 |
+
<div class="flex justify-between items-center">
|
| 227 |
+
<div class="flex items-center gap-sm">
|
| 228 |
+
<span class="material-symbols-outlined text-outline text-sm">hourglass_empty</span>
|
| 229 |
+
<span class="font-data-mono text-data-mono text-on-surface">Analyzing audio patterns...</span>
|
| 230 |
+
</div>
|
| 231 |
+
<span class="font-data-mono text-data-mono text-outline">0%</span>
|
| 232 |
+
</div>
|
| 233 |
+
<div class="w-full h-1 bg-surface-container-highest overflow-hidden">
|
| 234 |
+
<div class="h-full bg-outline w-0"></div>
|
| 235 |
+
</div>
|
| 236 |
+
</div>
|
| 237 |
+
<!-- CLI Feedback Area -->
|
| 238 |
+
<div class="mt-auto bg-surface-container-highest/50 border border-outline/10 p-sm rounded font-data-mono text-data-mono text-xs text-primary-fixed-dim/60 shadow-inner">
|
| 239 |
+
> [SYS] Fetching neural weights... OK<br/>
|
| 240 |
+
> [SYS] Allocating tensor buffers... OK<br/>
|
| 241 |
+
> [INF] Batch 0x7F processing... <span class="text-primary-container animate-pulse">_</span>
|
| 242 |
+
</div>
|
| 243 |
+
</div>
|
| 244 |
+
</div>
|
| 245 |
+
<!-- Cancel Action (Ghost Button) -->
|
| 246 |
+
<div class="mt-xl">
|
| 247 |
+
<button class="font-data-mono text-data-mono text-error border border-error/50 px-lg py-sm rounded bg-error/5 hover:bg-error/10 hover:shadow-[0_0_15px_rgba(255,180,171,0.2)] transition-all flex items-center gap-xs">
|
| 248 |
+
<span class="material-symbols-outlined text-sm">cancel</span>
|
| 249 |
+
ABORT SEQUENCE
|
| 250 |
+
</button>
|
| 251 |
+
</div>
|
| 252 |
+
</div>
|
| 253 |
+
</body></html>
|
stitch_authrix_deepfake_detection_engine/authrix_upload_center/code.html
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
|
| 3 |
+
<html class="dark" lang="en"><head>
|
| 4 |
+
<meta charset="utf-8"/>
|
| 5 |
+
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
| 6 |
+
<title>Authrix - Secure Data Ingest</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet"/>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" rel="stylesheet"/>
|
| 11 |
+
<script id="tailwind-config">
|
| 12 |
+
tailwind.config = {
|
| 13 |
+
darkMode: "class",
|
| 14 |
+
theme: {
|
| 15 |
+
extend: {
|
| 16 |
+
colors: {
|
| 17 |
+
"inverse-surface": "#dae5da",
|
| 18 |
+
"on-error": "#690005",
|
| 19 |
+
"surface-tint": "#00e38a",
|
| 20 |
+
"error": "#ffb4ab",
|
| 21 |
+
"outline-variant": "#3b4b3f",
|
| 22 |
+
"outline": "#849587",
|
| 23 |
+
"background": "#0c150f",
|
| 24 |
+
"surface-container-high": "#232c25",
|
| 25 |
+
"secondary-container": "#00e3fd",
|
| 26 |
+
"on-surface-variant": "#b9cbbc",
|
| 27 |
+
"on-secondary-container": "#00616d",
|
| 28 |
+
"on-error-container": "#ffdad6",
|
| 29 |
+
"primary": "#f3fff3",
|
| 30 |
+
"primary-container": "#00ff9c",
|
| 31 |
+
"on-primary": "#00391f",
|
| 32 |
+
"surface-bright": "#323c34",
|
| 33 |
+
"primary-fixed-dim": "#00e38a",
|
| 34 |
+
"tertiary-fixed-dim": "#e4c44f",
|
| 35 |
+
"on-secondary-fixed": "#001f24",
|
| 36 |
+
"tertiary-fixed": "#ffe17a",
|
| 37 |
+
"on-tertiary-container": "#766000",
|
| 38 |
+
"on-secondary": "#00363d",
|
| 39 |
+
"on-tertiary-fixed": "#231b00",
|
| 40 |
+
"on-tertiary": "#3b2f00",
|
| 41 |
+
"surface-container-low": "#141e17",
|
| 42 |
+
"surface": "#0c150f",
|
| 43 |
+
"primary-fixed": "#56ffa7",
|
| 44 |
+
"secondary-fixed-dim": "#00daf3",
|
| 45 |
+
"on-secondary-fixed-variant": "#004f58",
|
| 46 |
+
"inverse-on-surface": "#29332b",
|
| 47 |
+
"surface-container": "#18221b",
|
| 48 |
+
"on-surface": "#dae5da",
|
| 49 |
+
"on-tertiary-fixed-variant": "#554500",
|
| 50 |
+
"error-container": "#93000a",
|
| 51 |
+
"surface-variant": "#2d3730",
|
| 52 |
+
"surface-container-highest": "#2d3730",
|
| 53 |
+
"secondary-fixed": "#9cf0ff",
|
| 54 |
+
"on-background": "#dae5da",
|
| 55 |
+
"on-primary-container": "#007142",
|
| 56 |
+
"inverse-primary": "#006d40",
|
| 57 |
+
"on-primary-fixed-variant": "#00522f",
|
| 58 |
+
"secondary": "#bdf4ff",
|
| 59 |
+
"tertiary": "#fffaff",
|
| 60 |
+
"surface-dim": "#0c150f",
|
| 61 |
+
"on-primary-fixed": "#002110",
|
| 62 |
+
"surface-container-lowest": "#07100a",
|
| 63 |
+
"tertiary-container": "#ffdd65"
|
| 64 |
+
},
|
| 65 |
+
borderRadius: {
|
| 66 |
+
"DEFAULT": "0.25rem",
|
| 67 |
+
"lg": "0.5rem",
|
| 68 |
+
"xl": "0.75rem",
|
| 69 |
+
"full": "9999px"
|
| 70 |
+
},
|
| 71 |
+
spacing: {
|
| 72 |
+
"xs": "4px",
|
| 73 |
+
"md": "16px",
|
| 74 |
+
"base": "4px",
|
| 75 |
+
"sm": "8px",
|
| 76 |
+
"xl": "48px",
|
| 77 |
+
"lg": "24px",
|
| 78 |
+
"gutter": "20px",
|
| 79 |
+
"margin": "32px"
|
| 80 |
+
},
|
| 81 |
+
fontFamily: {
|
| 82 |
+
"h2": ["Space Grotesk"],
|
| 83 |
+
"h1": ["Space Grotesk"],
|
| 84 |
+
"h3": ["Space Grotesk"],
|
| 85 |
+
"label-xs": ["Space Grotesk"],
|
| 86 |
+
"data-mono": ["Space Grotesk"],
|
| 87 |
+
"body-base": ["Space Grotesk"]
|
| 88 |
+
},
|
| 89 |
+
fontSize: {
|
| 90 |
+
"h2": ["32px", { lineHeight: "1.2", letterSpacing: "-0.01em", fontWeight: "600" }],
|
| 91 |
+
"h1": ["48px", { lineHeight: "1.1", letterSpacing: "-0.02em", fontWeight: "700" }],
|
| 92 |
+
"h3": ["24px", { lineHeight: "1.3", letterSpacing: "0em", fontWeight: "600" }],
|
| 93 |
+
"label-xs": ["12px", { lineHeight: "1", letterSpacing: "0.1em", fontWeight: "700" }],
|
| 94 |
+
"data-mono": ["14px", { lineHeight: "1.5", letterSpacing: "0.05em", fontWeight: "500" }],
|
| 95 |
+
"body-base": ["16px", { lineHeight: "1.6", letterSpacing: "0.01em", fontWeight: "400" }]
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
</script>
|
| 101 |
+
<style>
|
| 102 |
+
.scan-line {
|
| 103 |
+
background: linear-gradient(to bottom, transparent 0%, rgba(0, 255, 156, 0.1) 50%, transparent 100%);
|
| 104 |
+
animation: scan 4s linear infinite;
|
| 105 |
+
}
|
| 106 |
+
@keyframes scan {
|
| 107 |
+
0% { transform: translateY(-100%); }
|
| 108 |
+
100% { transform: translateY(200%); }
|
| 109 |
+
}
|
| 110 |
+
</style>
|
| 111 |
+
</head>
|
| 112 |
+
<body class="bg-background text-on-background min-h-screen flex flex-col font-body-base overflow-x-hidden selection:bg-primary-container selection:text-on-primary">
|
| 113 |
+
<!-- Atmospheric Layering -->
|
| 114 |
+
<div class="fixed inset-0 pointer-events-none z-0">
|
| 115 |
+
<div class="absolute inset-0 bg-[linear-gradient(to_right,#8495870a_1px,transparent_1px),linear-gradient(to_bottom,#8495870a_1px,transparent_1px)] bg-[size:3rem_3rem]"></div>
|
| 116 |
+
<div class="absolute top-[20%] left-[30%] w-[40vw] h-[40vw] bg-surface-tint/5 rounded-full blur-[120px] mix-blend-screen"></div>
|
| 117 |
+
<div class="absolute bottom-[10%] right-[20%] w-[35vw] h-[35vw] bg-secondary-container/5 rounded-full blur-[100px] mix-blend-screen"></div>
|
| 118 |
+
</div>
|
| 119 |
+
<!-- TopNavBar (Shared Component) -->
|
| 120 |
+
<header class="bg-emerald-950/20 backdrop-blur-xl border-emerald-500/20 fixed top-0 left-0 w-full z-50 flex justify-between items-center px-6 h-16 w-full border-b shadow-[0_4_20px_rgba(0,0,0,0.5)]">
|
| 121 |
+
<div class="flex items-center gap-8">
|
| 122 |
+
<span class="text-2xl font-black tracking-tighter text-emerald-400 drop-shadow-[0_0_8px_rgba(0,255,156,0.5)]">AUTHRIX AI</span>
|
| 123 |
+
<nav class="hidden md:flex gap-6 font-['Space_Grotesk'] tracking-wider uppercase text-xs">
|
| 124 |
+
<a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] py-2 px-3 rounded-DEFAULT" href="#">Dashboard</a>
|
| 125 |
+
<a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] py-2 px-3 rounded-DEFAULT" href="#">Agents</a>
|
| 126 |
+
<a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] py-2 px-3 rounded-DEFAULT" href="#">Logs</a>
|
| 127 |
+
<a class="text-emerald-800/60 hover:text-emerald-400 transition-colors hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] py-2 px-3 rounded-DEFAULT" href="#">Network</a>
|
| 128 |
+
</nav>
|
| 129 |
+
</div>
|
| 130 |
+
<div class="flex items-center gap-4">
|
| 131 |
+
<div class="flex gap-3 text-emerald-400">
|
| 132 |
+
<span class="material-symbols-outlined hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] p-1 rounded-DEFAULT cursor-pointer">sensors</span>
|
| 133 |
+
<span class="material-symbols-outlined hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] p-1 rounded-DEFAULT cursor-pointer">memory</span>
|
| 134 |
+
<span class="material-symbols-outlined hover:bg-emerald-400/10 hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] p-1 rounded-DEFAULT cursor-pointer">speed</span>
|
| 135 |
+
</div>
|
| 136 |
+
<button class="bg-primary-container text-on-primary font-['Space_Grotesk'] tracking-wider uppercase text-xs px-4 py-2 font-bold hover:shadow-[0_0_15px_rgba(0,255,156,0.3)] transition-all">GET STARTED</button>
|
| 137 |
+
</div>
|
| 138 |
+
</header>
|
| 139 |
+
<!-- Main Content Canvas -->
|
| 140 |
+
<main class="flex-grow relative z-10 flex flex-col items-center justify-center pt-32 pb-24 px-6">
|
| 141 |
+
<!-- Upload Panel Container -->
|
| 142 |
+
<div class="w-full max-w-3xl flex flex-col gap-8">
|
| 143 |
+
<!-- Header Section -->
|
| 144 |
+
<div class="text-center space-y-2">
|
| 145 |
+
<h1 class="font-h2 text-h2 text-primary-container drop-shadow-[0_0_12px_rgba(0,255,156,0.4)]">SECURE INGEST PORTAL</h1>
|
| 146 |
+
<p class="font-data-mono text-data-mono text-on-surface-variant">Awaiting encrypted payload via protocol AX-9.</p>
|
| 147 |
+
</div>
|
| 148 |
+
<!-- Primary Upload Zone (Glassmorphic) -->
|
| 149 |
+
<div class="relative bg-surface-container/40 backdrop-blur-2xl border border-outline/20 rounded-xl overflow-hidden group hover:border-primary-container/50 transition-colors duration-500 shadow-[inset_0_1px_1px_rgba(255,255,255,0.05),0_0_30px_rgba(0,0,0,0.5)]">
|
| 150 |
+
<!-- Inner scan line effect -->
|
| 151 |
+
<div class="absolute inset-0 w-full h-[2px] scan-line pointer-events-none opacity-50"></div>
|
| 152 |
+
<div class="p-10 flex flex-col items-center justify-center border-2 border-dashed border-outline-variant/50 m-4 rounded-lg bg-surface-container-low/30 hover:bg-primary-container/5 hover:border-primary-container hover:shadow-[0_0_40px_rgba(0,255,156,0.1)_inset] transition-all duration-300 cursor-pointer min-h-[300px]">
|
| 153 |
+
<div class="w-24 h-24 rounded-full bg-surface-variant flex items-center justify-center mb-6 shadow-[inset_0_2px_4px_rgba(0,0,0,0.4)] group-hover:bg-surface-container-highest transition-colors">
|
| 154 |
+
<span class="material-symbols-outlined text-[48px] text-on-surface-variant group-hover:text-primary-container drop-shadow-[0_0_8px_transparent] group-hover:drop-shadow-[0_0_12px_rgba(0,255,156,0.8)] transition-all duration-300" style="font-variation-settings: 'FILL' 1;">
|
| 155 |
+
cloud_upload
|
| 156 |
+
</span>
|
| 157 |
+
</div>
|
| 158 |
+
<h3 class="font-h3 text-h3 text-on-surface mb-2">INITIALIZE UPLOAD</h3>
|
| 159 |
+
<p class="font-body-base text-body-base text-on-surface-variant mb-6 text-center max-w-sm">Drag and drop verified .DAT, .BIN, or .LOG packets here, or click to browse terminal files.</p>
|
| 160 |
+
<div class="flex items-center gap-2 font-label-xs text-label-xs text-outline tracking-widest">
|
| 161 |
+
<span class="w-1.5 h-1.5 rounded-full bg-outline"></span>
|
| 162 |
+
MAXIMUM PAYLOAD: 5GB
|
| 163 |
+
<span class="w-1.5 h-1.5 rounded-full bg-outline"></span>
|
| 164 |
+
</div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
<!-- Upload Queue / Recent Activity (Minimalist Data Grid style) -->
|
| 168 |
+
<div class="mt-8 flex flex-col gap-4">
|
| 169 |
+
<div class="flex items-center justify-between border-b border-outline/20 pb-2">
|
| 170 |
+
<h4 class="font-label-xs text-label-xs text-primary-container uppercase tracking-widest flex items-center gap-2">
|
| 171 |
+
<span class="w-2 h-2 bg-primary-container shadow-[0_0_8px_rgba(0,255,156,0.8)]"></span>
|
| 172 |
+
ACTIVE QUEUE
|
| 173 |
+
</h4>
|
| 174 |
+
<span class="font-data-mono text-[10px] text-on-surface-variant">SYS_TIME: 14:02:09</span>
|
| 175 |
+
</div>
|
| 176 |
+
<!-- Queue Items -->
|
| 177 |
+
<div class="grid grid-cols-1 gap-2">
|
| 178 |
+
<!-- Item 1: Uploading -->
|
| 179 |
+
<div class="bg-surface-container-low border border-outline-variant/30 rounded-DEFAULT p-4 flex flex-col gap-3 relative overflow-hidden">
|
| 180 |
+
<div class="absolute bottom-0 left-0 h-[1px] bg-primary-container w-[45%] shadow-[0_0_5px_rgba(0,255,156,0.8)]"></div>
|
| 181 |
+
<div class="flex justify-between items-start">
|
| 182 |
+
<div class="flex items-center gap-3">
|
| 183 |
+
<span class="material-symbols-outlined text-primary-container text-[20px]">description</span>
|
| 184 |
+
<div>
|
| 185 |
+
<div class="font-data-mono text-data-mono text-on-surface">nexus_core_dump_v4.bin</div>
|
| 186 |
+
<div class="font-label-xs text-[10px] text-on-surface-variant mt-1">1.2 GB / 2.8 GB</div>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
<span class="font-label-xs text-[10px] border border-primary-container/50 text-primary-container px-2 py-1 bg-primary-container/10">UPLOADING 45%</span>
|
| 190 |
+
</div>
|
| 191 |
+
</div>
|
| 192 |
+
<!-- Item 2: Scanning -->
|
| 193 |
+
<div class="bg-surface-container-low border border-outline-variant/30 rounded-DEFAULT p-4 flex flex-col gap-3">
|
| 194 |
+
<div class="flex justify-between items-start">
|
| 195 |
+
<div class="flex items-center gap-3">
|
| 196 |
+
<span class="material-symbols-outlined text-secondary-container text-[20px]">radar</span>
|
| 197 |
+
<div>
|
| 198 |
+
<div class="font-data-mono text-data-mono text-on-surface">auth_log_sector7.dat</div>
|
| 199 |
+
<div class="font-label-xs text-[10px] text-on-surface-variant mt-1">450 MB</div>
|
| 200 |
+
</div>
|
| 201 |
+
</div>
|
| 202 |
+
<span class="font-label-xs text-[10px] border border-secondary-container/50 text-secondary-container px-2 py-1 bg-secondary-container/10 shadow-[0_0_10px_rgba(0,227,253,0.2)_inset]">SCANNING</span>
|
| 203 |
+
</div>
|
| 204 |
+
</div>
|
| 205 |
+
<!-- Item 3: Verified -->
|
| 206 |
+
<div class="bg-surface-container-low border border-outline-variant/30 rounded-DEFAULT p-4 flex flex-col gap-3 opacity-60">
|
| 207 |
+
<div class="flex justify-between items-start">
|
| 208 |
+
<div class="flex items-center gap-3">
|
| 209 |
+
<span class="material-symbols-outlined text-outline text-[20px]">check_circle</span>
|
| 210 |
+
<div>
|
| 211 |
+
<div class="font-data-mono text-data-mono text-on-surface line-through decoration-outline/50">legacy_key_archive.log</div>
|
| 212 |
+
<div class="font-label-xs text-[10px] text-outline mt-1">12 MB</div>
|
| 213 |
+
</div>
|
| 214 |
+
</div>
|
| 215 |
+
<span class="font-label-xs text-[10px] border border-outline/50 text-outline px-2 py-1">VERIFIED</span>
|
| 216 |
+
</div>
|
| 217 |
+
</div>
|
| 218 |
+
</div>
|
| 219 |
+
</div>
|
| 220 |
+
</div>
|
| 221 |
+
</main>
|
| 222 |
+
<!-- Footer (Shared Component) -->
|
| 223 |
+
<footer class="bg-black border-emerald-900/30 full-width border-t w-full py-8 px-10 flex flex-col md:flex-row justify-between items-center opacity-80 z-50 relative mt-auto">
|
| 224 |
+
<div class="text-emerald-500 font-mono text-[10px] uppercase tracking-[0.2em] mb-4 md:mb-0">
|
| 225 |
+
© 2024 AUTHRIX COMMAND. SYSTEM_STATE: VIGILANT
|
| 226 |
+
</div>
|
| 227 |
+
<nav class="flex gap-6 font-mono text-[10px] uppercase tracking-[0.2em]">
|
| 228 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Kernel</a>
|
| 229 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Protocol</a>
|
| 230 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Override</a>
|
| 231 |
+
<a class="text-emerald-900 hover:text-emerald-300 transition-all cursor-crosshair" href="#">Diagnostics</a>
|
| 232 |
+
</nav>
|
| 233 |
+
</footer>
|
| 234 |
+
</body></html>
|