{"repo": "andrewpedot/darkmine", "files": {"app/page.tsx": {"abec6ee7288694db6727aad2fef1968b305bc332": {"t": 1785596456.2419956, "n": 48575, "h": "b80decd6a2eb6888", "c": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport Link from 'next/link';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n DropdownMenuLabel,\n} from '@/components/ui/dropdown-menu';\nimport { useToast } from '@/hooks/use-toast';\nimport { searchVideos } from './actions/search-videos';\nimport { analyzeMarketKeywords } from './actions/analyze-market';\n\nconst MOCK_CARDS = [\n {\n id: \"mock-1\",\n title: \"I Infiltrated the World's Most Secretive Financial Cults\",\n titlePtBR: \"Eu Me Infiltrei nos Cultos Financeiros Mais Secretos do Mundo\",\n channel: \"ShadowEconomy\",\n channelUrl: \"https://www.youtube.com/@ShadowEconomy\",\n channelCreatedAt: \"2021\",\n subscribers: \"12.4K\",\n views: \"2.1M\",\n publishedAt: \"18 Jan 2025\",\n outlierMultiplier: \"169x\",\n thumbnail: \"finance\",\n thumbnailUrl: \"\",\n lifespan: { type: \"evergreen\", label: \"🌲 Evergreen / Perene\" },\n statusPt: null,\n statusEs: null,\n niche: \"Finanças\",\n ctr: \"9.4%\",\n avgView: \"14min 32s\",\n score: 97,\n vph: \"1.8K\",\n vphRaw: 1800,\n outlierMultiplierRaw: 169,\n viewsRaw: 2100000,\n viewsPerDay: 1800 * 24,\n publishedAtRaw: new Date(Date.now() - 60 * 24 * 3600 * 1000).toISOString(),\n },\n {\n id: \"mock-2\",\n title: \"The Disappearance That the Government Tried to Erase Forever\",\n titlePtBR: \"O Desaparecimento Que o Governo Tentou Apagar Para Sempre\",\n channel: \"CrimeDark\",\n channelUrl: \"https://www.youtube.com/@CrimeDark\",\n channelCreatedAt: \"2019\",\n subscribers: \"8.7K\",\n views: \"1.4M\",\n publishedAt: \"03 Mar 2025\",\n outlierMultiplier: \"160x\",\n thumbnail: \"crime\",\n thumbnailUrl: \"\",\n lifespan: { type: \"hype\", label: \"🔥 Hype / Curto Prazo\" },\n statusPt: null,\n statusEs: null,\n niche: \"True Crime\",\n ctr: \"11.2%\",\n avgView: \"22min 08s\",\n score: 93,\n vph: \"2.3K\",\n vphRaw: 2300,\n outlierMultiplierRaw: 160,\n viewsRaw: 1400000,\n viewsPerDay: 2300 * 24,\n publishedAtRaw: new Date(Date.now() - 10 * 24 * 3600 * 1000).toISOString(),\n }\n];\n\nconst NICHES = [\"Todos\", \"Finanças\", \"True Crime\", \"Tech\", \"História\", \"Psicologia\", \"Geopolítica\", \"Estoicismo\", \"Espaço/Ciência\"];\n\nconst THUMBNAIL_COLORS: Record = {\n finance: \"linear-gradient(135deg, #0f2027, #1a3a2a, #0d1b2a)\",\n crime: \"linear-gradient(135deg, #1a0a0a, #2d0f0f, #0d0d1a)\",\n tech: \"linear-gradient(135deg, #070d1f, #0a1628, #0d1117)\",\n psychology: \"linear-gradient(135deg, #1a1025, #2d1b4e, #130b20)\",\n geopolitics: \"linear-gradient(135deg, #0a192f, #112240, #020c1b)\",\n stoicism: \"linear-gradient(135deg, #2a2a2a, #3f3f3f, #1a1a1a)\",\n space: \"linear-gradient(135deg, #000000, #0a0a2a, #00001a)\",\n default: \"linear-gradient(135deg, #111827, #374151, #1f2937)\",\n};\n\nconst THUMBNAIL_ICONS: Record = {\n finance: \"💰\",\n crime: \"🕵️\",\n tech: \"🤖\",\n psychology: \"🧠\",\n geopolitics: \"🌍\",\n stoicism: \"🏛️\",\n space: \"🚀\",\n default: \"📹\",\n};\n\nconst MARKETS = [\n { code: 'de', label: 'Alemão', flag: '🇩🇪', region: 'DE', lang: 'de' },\n { code: 'fr', label: 'Francês', flag: '🇫🇷', region: 'FR', lang: 'fr' },\n { code: 'es', label: 'Espanhol', flag: '🇪🇸', region: 'ES', lang: 'es' },\n { code: 'it', label: 'Italiano', flag: '🇮🇹', region: 'IT', lang: 'it' },\n { code: 'pl', label: 'Polonês', flag: '🇵🇱', region: 'PL', lang: 'pl' },\n { code: 'hu', label: 'Húngaro', flag: '🇭🇺', region: 'HU', lang: 'hu' },\n { code: 'el', label: 'Grego', flag: '🇬🇷', region: 'GR', lang: 'el' },\n { code: 'cs', label: 'Tcheco', flag: '🇨🇿', region: 'CZ', lang: 'cs' },\n { code: 'he', label: 'Hebraico', flag: '🇮🇱', region: 'IL', lang: 'he' },\n];\n\nfunction parseDuration(pt: string) {\n if (!pt) return \"N/A\";\n const match = pt.match(/PT(\\d+H)?(\\d+M)?(\\d+S)?/);\n if (!match) return pt;\n const h = match[1] ? match[1].replace('H', '') : '';\n const m = match[2] ? match[2].replace('M', '') : '0';\n const s = match[3] ? match[3].replace('S', '') : '00';\n \n if (h) return `${h}h ${m}min ${s}s`;\n return `${m}min ${s}s`;\n}\n\nfunction formatNumber(num: number) {\n if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';\n if (num >= 1000) return (num / 1000).toFixed(1) + 'K';\n return Math.round(num).toString();\n}\n\nfunction getThumbnailType(niche: string) {\n if (niche === 'Finanças') return 'finance';\n if (niche === 'True Crime') return 'crime';\n if (niche === 'Tech') return 'tech';\n if (niche === 'Psicologia') return 'psychology';\n if (niche === 'Geopolítica') return 'geopolitics';\n if (niche === 'Estoicismo') return 'stoicism';\n if (niche === 'Espaço/Ciência') return 'space';\n return 'default';\n}\n\nfunction formatScore(multiplierRaw: number, vphRaw: number) {\n return Math.min(Math.round(multiplierRaw * 10 + (vphRaw > 1000 ? 20 : 0)), 99);\n}\n\nfunction ScoreRing({ score }: { score: number }) {\n const r = 18;\n const circ = 2 * Math.PI * r;\n const offset = circ - (score / 100) * circ;\n return (\n
\n \n \n = 90 ? \"#a855f7\" : score >= 70 ? \"#22d3ee\" : \"#f59e0b\"}\n strokeWidth=\"4\"\n strokeDasharray={circ}\n strokeDashoffset={offset}\n strokeLinecap=\"round\"\n transform=\"rotate(-90 24 24)\"\n style={{ filter: `drop-shadow(0 0 6px ${score >= 90 ? \"#a855f7\" : \"#22d3ee\"})` }}\n />\n \n {score}\n \n \n Score\n
\n );\n}\n\ninterface AnalyzedMarket {\n langCode: string;\n status: 'blue_ocean' | 'saturated';\n analysis?: string;\n}\n\nfunction VideoCard({ card }: { card: any }) {\n const [hovered, setHovered] = useState(false);\n const [clicked, setClicked] = useState(false);\n const [showPtBR, setShowPtBR] = useState(false);\n const [translatedTitle, setTranslatedTitle] = useState('');\n const [isTranslating, setIsTranslating] = useState(false);\n const [saved, setSaved] = useState(false);\n const [analyzedMarkets, setAnalyzedMarkets] = useState(\n card.analyzedMarkets || []\n );\n const [isChecking, setIsChecking] = useState(false);\n const [selectedMarket, setSelectedMarket] = useState(null);\n\n useEffect(() => {\n const lib = JSON.parse(localStorage.getItem('darkmine_library') || '[]');\n if (lib.find((c: any) => c.id === card.id)) {\n setSaved(true);\n }\n }, [card.id]);\n\n const handleSave = async (e: any) => {\n e.stopPropagation();\n \n if (saved) {\n const lib = JSON.parse(localStorage.getItem('darkmine_library') || '[]');\n const newLib = lib.map((c: any) => c.id === card.id ? { ...c, analyzedMarkets: analyzedMarkets } : c);\n localStorage.setItem('darkmine_library', JSON.stringify(newLib));\n setSaved(false);\n window.dispatchEvent(new Event('libraryUpdated'));\n return;\n }\n\n const saveToApi = async () => {\n try {\n const res = await fetch('/api/library/save', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n title: card.title,\n title_en: card.title,\n language: 'en',\n source: 'darkmine',\n source_id: card.id,\n source_session_id: card.sessionId || null,\n vpd: card.vphRaw || card.viewsPerDay || 0,\n anomaly_ratio: card.outlierMultiplierRaw || card.outlierMultiplier || 0,\n dark_score: card.facelessScore || 0,\n opportunity_score: card.score || 0,\n tags: [card.niche || 'darkmine'],\n }),\n });\n const data = await res.json();\n if (!data.success) {\n console.error('Failed to save to library:', data.error);\n }\n } catch (err) {\n console.error('Save to library error:', err);\n }\n };\n\n const newCard = { ...card, libraryStatus: 'Pendente', analyzedMarkets };\n const lib = JSON.parse(localStorage.getItem('darkmine_library') || '[]');\n lib.push(newCard);\n localStorage.setItem('darkmine_library', JSON.stringify(lib));\n setSaved(true);\n saveToApi();\n window.dispatchEvent(new Event('libraryUpdated'));\n };\n\n const checkMarket = async (keywords: string, region: string, lang: string) => {\n const apiKey = process.env.NEXT_PUBLIC_YOUTUBE_API_KEY;\n if (!apiKey) throw new Error(\"API Key missing\");\n const now = new Date();\n const originalVph = card.vphRaw;\n\n const res = await fetch(`https://www.googleapis.com/youtube/v3/search?part=snippet&maxResults=5&q=${encodeURIComponent(keywords)}&type=video®ionCode=${region}&relevanceLanguage=${lang}&key=${apiKey}`);\n const data = await res.json();\n if (!data.items || data.items.length === 0) return 'Oceano Azul';\n \n const vIds = data.items.map((i: any) => i.id.videoId).join(',');\n const vRes = await fetch(`https://www.googleapis.com/youtube/v3/videos?part=statistics,snippet&id=${vIds}&key=${apiKey}`);\n const vData = await vRes.json();\n \n let hasRecentHighVph = false;\n let highViewsFound = false;\n \n for (const v of vData.items || []) {\n const pub = new Date(v.snippet.publishedAt);\n const hours = Math.max((now.getTime() - pub.getTime()) / (1000 * 60 * 60), 1);\n const views = parseInt(v.statistics.viewCount || '0', 10);\n const vph = views / hours;\n \n if (views > 500000) {\n highViewsFound = true;\n }\n \n if (hours < 24 * 365 && vph >= originalVph * 0.1) {\n hasRecentHighVph = true;\n }\n }\n \n if (hasRecentHighVph || highViewsFound) return 'Saturado';\n return 'Oceano Azul';\n };\n\n const { toast } = useToast();\n\n const handleCheckMarket = async (market: typeof MARKETS[0]) => {\n setIsChecking(true);\n setSelectedMarket(market.code);\n \n try {\n const aiAnalysis = await analyzeMarketKeywords(card.title, market.label, market.code);\n \n const result = await checkMarket(aiAnalysis.keywords, market.region, market.lang);\n const status = result === 'Oceano Azul' ? 'blue_ocean' : 'saturated';\n \n setAnalyzedMarkets(prev => {\n const exists = prev.find(m => m.langCode === market.code);\n if (exists) {\n return prev.map(m => m.langCode === market.code ? { ...m, status, analysis: aiAnalysis.analise_textual } : m);\n }\n return [...prev, { langCode: market.code, status, analysis: aiAnalysis.analise_textual }];\n });\n \n return result;\n } catch (e) {\n console.error(e);\n const result = 'Saturado';\n \n setAnalyzedMarkets(prev => {\n const exists = prev.find(m => m.langCode === market.code);\n if (exists) {\n return prev.map(m => m.langCode === market.code ? { ...m, status: 'saturated', analysis: 'Erro ao analisar o mercado.' } : m);\n }\n return [...prev, { langCode: market.code, status: 'saturated', analysis: 'Erro ao analisar o mercado.' }];\n });\n \n return result;\n } finally {\n setIsChecking(false);\n setSelectedMarket(null);\n }\n };\n\n let targetMarket = 'Brasil (PT-BR)';\n let btnText = 'Gerar Estrutura de Roteiro';\n \n // Define mercado alvo baseado no primeiro Oceano Azul encontrado\n const blueOceanMarket = analyzedMarkets.find(m => m.status === 'blue_ocean');\n if (blueOceanMarket) {\n const marketInfo = MARKETS.find(m => m.code === blueOceanMarket.langCode);\n if (marketInfo) {\n targetMarket = `${marketInfo.label} (${marketInfo.code.toUpperCase()})`;\n btnText = `Clonar para ${marketInfo.label}`;\n }\n }\n\n const handleTranslate = async (e: any) => {\n e.stopPropagation();\n if (showPtBR) {\n setShowPtBR(false);\n return;\n }\n \n if (!translatedTitle && card.title) {\n setIsTranslating(true);\n try {\n const res = await fetch(`https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=pt&dt=t&q=${encodeURIComponent(card.title)}`);\n const data = await res.json();\n const translatedText = data[0].map((item: any) => item[0]).join('');\n setTranslatedTitle(translatedText);\n } catch (error) {\n console.error(error);\n setTranslatedTitle(card.title);\n }\n setIsTranslating(false);\n }\n setShowPtBR(true);\n };\n\n const handleOpenVideo = (e: any) => {\n e.stopPropagation();\n window.open(card.videoUrl || `https://www.youtube.com/watch?v=${card.id}`, '_blank');\n };\n\n return (\n setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n >\n {/* Thumbnail */}\n \n {card.thumbnailUrl && (\n // eslint-disable-next-line @next/next/no-img-element\n {card.title}\n )}\n
\n \n \n {THUMBNAIL_ICONS[card.thumbnail] || THUMBNAIL_ICONS.default}\n \n {card.niche}\n
\n \n {/* Top-left: lifespan */}\n {card.lifespan && (\n
\n \n {card.lifespan.label}\n \n
\n )}\n \n {/* Top-right: bookmark + multiplier */}\n
\n \n \n \n \n \n
\n \n {typeof card.outlierMultiplier === 'number' ? `${card.outlierMultiplier}x` : card.outlierMultiplier}\n \n views/subs\n
\n
\n\n {/* Hover overlay with play button */}\n
\n
\n \n Assistir\n
\n
\n\n {card.avgView && (\n
\n {card.avgView}\n
\n )}\n \n\n {/* Card body */}\n
\n {/* Title + Score */}\n
\n
\n

\n {showPtBR ? (translatedTitle || card.titlePtBR || 'Traduzindo...') : card.title}\n

\n {/* Bilingual toggle */}\n \n \n {isTranslating ? (\n \n ) : (\n \n )}\n \n {isTranslating ? 'Traduzindo...' : showPtBR ? '🇧🇷 PT-BR (clique para EN)' : '🌐 Ver título em PT-BR'}\n \n
\n {card.score != null && }\n
\n\n {/* Novas Badges do Radar de Anomalias */}\n
\n
\n 🔥 VPH: {card.vph || (card.vphRaw ? card.vphRaw.toLocaleString('pt-BR') : card.viewsPerDay ? Math.round(card.viewsPerDay / 24).toLocaleString('pt-BR') : '0')}\n
\n {card.syntheticMedia && (\n
\n 🤖 IA Confirmada\n
\n )}\n
\n 📈 Anomalia: {typeof card.outlierMultiplier === 'number' ? `${card.outlierMultiplier}x` : card.outlierMultiplier}\n
\n {card.facelessScore != null && (\n
= 80\n ? 'bg-purple-950/60 border border-purple-400/60 text-purple-300'\n : card.facelessScore >= 50\n ? 'bg-cyan-950/40 border border-cyan-500/50 text-cyan-400'\n : 'bg-gray-900/40 border border-gray-600/40 text-gray-400'\n }`}>\n 🕶️ Dark Score: {card.facelessScore}\n
\n )}\n
\n\n\n {/* Stats grid */}\n
\n {/* Canal + external link */}\n
\n 👤\n
\n
Canal
\n
\n {card.channel}\n e.stopPropagation()}\n >\n \n \n \n \n
\n
\n
\n
\n 📊\n
\n
Inscritos
\n
{card.subscribers}
\n
\n
\n
\n 👁️\n
\n
Views
\n
{card.views}
\n
\n
\n
\n 📅\n
\n
Data
\n
{card.publishedAt}
\n
\n
\n {/* Canal criado em — full width */}\n
\n 🏛️\n
\n
Canal criado em
\n
{card.channelCreatedAt}
\n
\n
\n
\n\n {/* Market Tags */}\n {analyzedMarkets.length > 0 && (\n
\n {analyzedMarkets.map((market) => (\n \n #{market.langCode}\n \n ))}\n
\n )}\n\n {/* CTR + VPH row — only for mock/enriched cards */}\n {(card.ctr || card.vph) && (\n
\n
\n {card.ctr && (\n
\n CTR\n {card.ctr}\n
\n )}\n {card.ctr && card.vph &&
}\n {card.vph && (\n
\n 🔥\n {card.vph}\n VPH\n
\n )}\n
\n
\n
100 ? 'bg-emerald-400 animate-pulse' : 'bg-gray-500'}`} />\n {(card.vphRaw || 0) > 100 ? 'Alta tração' : 'Tração normal'}\n
\n
\n )}\n\n {/* Dynamic Action Section */}\n
\n \n \n m.status === 'blue_ocean').length > 0\n ? 'border-cyan-400/50 bg-cyan-900/20 text-cyan-300 shadow-[0_0_8px_rgba(34,211,238,0.2)]'\n : analyzedMarkets.filter(m => m.status === 'saturated').length > 0\n ? 'border-red-500/30 bg-red-900/10 text-red-400/70 shadow-[0_0_8px_rgba(239,68,68,0.2)]'\n : 'border-white/10 text-gray-400 hover:text-white hover:border-purple-500/40 hover:bg-purple-950/20'\n }`}\n >\n {isChecking ? (\n <>\n \n Analisando {selectedMarket && MARKETS.find(m => m.code === selectedMarket)?.label}...\n \n ) : (\n <>\n 🌍\n Analisar Mercados\n \n \n \n \n )}\n \n \n \n \n Idiomas Alto RPM\n \n {MARKETS.map((market) => (\n handleCheckMarket(market)}\n disabled={isChecking}\n className=\"cursor-pointer hover:bg-purple-950/30 focus:bg-purple-950/30\"\n >\n {market.flag}\n {market.label}\n {market.code}\n \n ))}\n \n \n \n \n {\n setClicked(true);\n window.open(`/hook?title=${encodeURIComponent(card.title)}&market=${encodeURIComponent(targetMarket)}`, '_blank');\n setTimeout(() => setClicked(false), 2000);\n }}\n className=\"btn-primary w-full py-2.5 rounded-xl text-sm font-bold text-white flex items-center justify-center gap-2\"\n >\n \n \n \n {clicked ? 'Redirecionando...' : btnText}\n \n
\n
\n
\n );\n}\n\nexport default function DarkMinePage() {\n const [activeNiche, setActiveNiche] = useState('Todos');\n const [searchQuery, setSearchQuery] = useState('');\n const [maxSubs, setMaxSubs] = useState('50000');\n const [mining, setMining] = useState(false);\n const [mined, setMined] = useState(false);\n const [showSettings, setShowSettings] = useState(false);\n const [postFilter, setPostFilter] = useState<'Todos' | 'Evergreen' | 'Hype'>('Todos');\n const [errorMsg, setErrorMsg] = useState('');\n const [cards, setCards] = useState(MOCK_CARDS);\n const [onlyAI, setOnlyAI] = useState(false);\n\n const displayCards = [...cards].filter(c => {\n const now = new Date();\n const pubDate = new Date(c.publishedAtRaw || 0);\n const daysOld = (now.getTime() - pubDate.getTime()) / (1000 * 3600 * 24);\n if (postFilter === 'Evergreen') return daysOld > 30;\n if (postFilter === 'Hype') return daysOld < 14;\n return true;\n }).sort((a, b) => {\n if (postFilter === 'Hype') return (b.vphRaw || 0) - (a.vphRaw || 0);\n return (b.outlierMultiplierRaw || 0) - (a.outlierMultiplierRaw || 0);\n });\n\n const isTrendingMode = !searchQuery.trim();\n\n const handleMine = async () => {\n setMining(true);\n setMined(false);\n setErrorMsg('');\n\n try {\n const limitSubs = parseInt(maxSubs, 10) || 50000;\n // Passa a query do usuário (pode ser vazia — nesse caso a action usa query padrão por nicho)\n const bestResults = await searchVideos(searchQuery.trim(), limitSubs, activeNiche, onlyAI);\n\n setCards(bestResults);\n setMined(true);\n } catch (err: any) {\n console.error(err);\n // Em produção, erros de Server Action vêm sem .message (apenas digest)\n // Detectamos os casos mais comuns pelo texto quando disponível\n const msg: string = err?.message || '';\n if (msg.includes('expired') || msg.includes('keyExpired')) {\n setErrorMsg('🔑 YouTube API Key expirada. Renove a chave no Google Cloud Console e atualize o .env.local (NEXT_PUBLIC_YOUTUBE_API_KEY).');\n } else if (msg.includes('quota') || msg.includes('quotaExceeded')) {\n setErrorMsg('⚠️ Cota da YouTube API esgotada para hoje. Tente novamente amanhã.');\n } else if (msg.includes('API Key') || msg.includes('keyInvalid')) {\n setErrorMsg('🔑 YouTube API Key inválida ou não configurada. Verifique suas variáveis de ambiente.');\n } else if (msg) {\n setErrorMsg(msg);\n } else {\n // Erro sem mensagem = Server Action falhou em produção\n setErrorMsg('❌ Erro ao conectar com a API do YouTube. Verifique se a API Key está válida e configurada corretamente nas variáveis de ambiente da Netlify.');\n }\n } finally {\n setMining(false);\n }\n };\n\n return (\n
\n {/* Ambient glow blobs */}\n
\n
\n\n {/* ── HEADER ── */}\n
\n
\n {/* Logo */}\n
\n
\n \n \n \n
\n
\n Dark Miner\n \n
\n
\n\n {/* Right side */}\n
\n
\n
\n API Conectada\n
\n setShowSettings(!showSettings)}\n className=\"flex items-center gap-2 px-4 py-2 rounded-xl text-sm font-medium text-gray-300 border border-white/10 transition-all hover:border-purple-500/40 hover:text-purple-300 hover:bg-purple-950/20\"\n >\n \n \n \n \n Configurações\n \n
\n
\n
\n\n
\n\n {/* ── SEARCH & CONTROL AREA ── */}\n
\n
\n
\n

Motor de Mineração

\n
\n\n {/* Search row */}\n
\n
\n \n \n \n setSearchQuery(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleMine()}\n placeholder={isTrendingMode ? '🔥 Modo Trending — deixe vazio para ver o que o YouTube está recomendando agora' : 'Ex: conspiracy, finance collapse, true crime cold case...'}\n className=\"input-dark w-full pl-10 pr-4 py-3 rounded-xl text-sm font-mono\"\n />\n
\n
\n \n \n \n setMaxSubs(e.target.value)}\n placeholder=\"Max Inscritos\"\n className=\"input-dark w-full pl-10 pr-4 py-3 rounded-xl text-sm font-mono\"\n />\n subs\n
\n
\n\n {/* Niche filters + AI toggle */}\n
\n Nicho:\n {NICHES.map(n => (\n setActiveNiche(n)}\n className={`tag-filter px-3.5 py-1.5 rounded-full text-xs font-semibold border transition-all ${activeNiche === n ? 'active' : 'border-white/10 text-gray-400'}`}\n >\n {n}\n \n ))}\n {/* Separador */}\n
\n {/* Toggle IA Confirmada */}\n setOnlyAI(v => !v)}\n title={onlyAI ? 'Filtro IA ativo: apenas vídeos com contensSyntheticMedia=true' : 'Ativar para buscar APENAS conteúdo com IA declarada'}\n className={`flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-semibold border transition-all ${\n onlyAI\n ? 'bg-green-950/60 border-green-400/70 text-green-300 shadow-[0_0_8px_rgba(74,222,128,0.25)]'\n : 'border-white/10 text-gray-500 hover:border-green-500/40 hover:text-green-400'\n }`}\n >\n 🤖 {onlyAI ? 'Apenas IA (ativo)' : 'Apenas IA'}\n \n
\n\n {/* Aviso quando filtro IA está ativo */}\n {onlyAI && (\n
\n 🤖\n \n Filtro IA ativo: Apenas vídeos onde o criador declarou uso de IA no YouTube Studio (containsSyntheticMedia = true). Resultado pode ser menor — muitos canais usam IA mas não marcam a opção.\n \n
\n )}\n\n {/* Error Message */}\n {errorMsg && (\n
\n \n \n \n {errorMsg}\n
\n )}\n\n {/* Mine button + Sort dropdown */}\n
\n \n {mining ? (\n <>\n \n \n \n \n Minerando Oportunidades...\n \n ) : mined ? (\n <>✅ {cards.length} Outliers Encontrados — {isTrendingMode ? 'Atualizar Trending' : 'Minerar Novamente'}\n ) : (\n <>\n \n \n \n {isTrendingMode ? '🔥 Ver Trending Agora' : 'Minerar Oportunidades'}\n \n )}\n \n {/* Post-Search Filters */}\n
\n {['Todos', 'Evergreen', 'Hype'].map((filter) => (\n setPostFilter(filter as any)}\n className={`px-4 py-2 rounded-lg text-sm font-semibold transition-all flex items-center gap-1.5 ${postFilter === filter ? 'bg-purple-600/20 text-purple-300 shadow-[0_0_10px_rgba(168,85,247,0.1)]' : 'text-gray-400 hover:text-white hover:bg-white/5'}`}\n >\n {filter === 'Evergreen' && '🌲 '}\n {filter === 'Hype' && '🔥 '}\n {filter}\n \n ))}\n
\n
\n
\n\n {/* ── TRENDING OUTLIERS ── */}\n
\n
\n
\n

\n 🔥 Trending Outliers\n (Oportunidades do Dia)\n

\n
\n
\n
\n Live · Atualizado Agora\n
\n
\n\n {/* Cards grid */}\n
\n {displayCards.map(card => (\n \n ))}\n
\n \n {cards.length === 0 && !mining && (\n
\n 🕵️\n

Nenhum resultado. Realize uma mineração acima.

\n
\n )}\n
\n\n {/* ── BOTTOM HINT ── */}\n
\n \n \n \n Dark Miner v1.0 Alpha · Conectado à API do YouTube\n
\n
\n\n {/* Settings Modal */}\n {showSettings && (\n
setShowSettings(false)}>\n
e.stopPropagation()}>\n

\n \n \n \n \n Configurações\n

\n
\n {[\n { label: 'YouTube Data API Key', placeholder: 'AIza...', type: 'password', defaultValue: process.env.NEXT_PUBLIC_YOUTUBE_API_KEY ? '********' : '' },\n { label: 'OpenAI API Key (roteiros)', placeholder: 'sk-...', type: 'password' },\n { label: 'Idioma Alvo', placeholder: 'pt-BR', type: 'text' },\n ].map(field => (\n
\n \n \n
\n ))}\n \n
\n
\n
\n )}\n
\n );\n}\n"}}}}