import React, { useState, useEffect, useMemo, useCallback } from 'react';
import {
RefreshCw, Gamepad2, ExternalLink, Search, Star,
Info, Heart, SlidersHorizontal, Puzzle, Wallet,
CheckCircle2, Tag
} from 'lucide-react';
const STORES = [
{ name: 'STEAM', domains: ['steampowered.com', 'steamcommunity.com'], color: 'bg-blue-600 border-blue-500', accent: '#66c0f4', primaryColor: '#1b2838', secondaryColor: '#171a21', brandVibe: 'industrial' },
{ name: 'EPIC GAMES', domains: ['epicgames.com'], color: 'bg-zinc-700 border-zinc-600', accent: '#0078f2', primaryColor: '#121212', secondaryColor: '#2a2a2a', brandVibe: 'sharp' },
{ name: 'GOG', domains: ['gog.com'], color: 'bg-purple-600 border-purple-500', accent: '#bf15eb', primaryColor: '#1e0f31', secondaryColor: '#0f071a', brandVibe: 'retro' },
{ name: 'UBISOFT', domains: ['ubisoft.com', 'ubi.com'], color: 'bg-teal-600 border-teal-500', accent: '#00f0ff', primaryColor: '#002d54', secondaryColor: '#001a33', brandVibe: 'spiral' },
{ name: 'EA PLAY', domains: ['ea.com', 'origin.com'], color: 'bg-red-600 border-red-500', accent: '#f5333f', primaryColor: '#161616', secondaryColor: '#3a0007', brandVibe: 'minimal' },
{ name: 'PLAYSTATION', domains: ['playstation.com', 'sony.com'], color: 'bg-blue-800 border-blue-700', accent: '#0070d1', primaryColor: '#003087', secondaryColor: '#001b54', brandVibe: 'shapes' },
{ name: 'XBOX / MS', domains: ['microsoft.com', 'xbox.com'], color: 'bg-green-700 border-green-600', accent: '#51db51', primaryColor: '#107c10', secondaryColor: '#0a4e0a', brandVibe: 'sphere' },
{ name: 'NINTENDO', domains: ['nintendo.com', 'nintendo.co.jp'], color: 'bg-red-700 border-red-600', accent: '#ff4d5a', primaryColor: '#e60012', secondaryColor: '#80000a', brandVibe: 'joycon' },
{ name: 'ITCH.IO', domains: ['itch.io'], color: 'bg-pink-600 border-pink-500', accent: '#ff8e8e', primaryColor: '#fa5c5c', secondaryColor: '#7d1e1e', brandVibe: 'pixel' },
{ name: 'ANDROID PLAY', domains: ['play.google.com'], color: 'bg-emerald-600 border-emerald-500', accent: '#34a853', primaryColor: '#0f5132', secondaryColor: '#052214', brandVibe: 'android' },
{ name: 'IOS APP STORE', domains: ['apps.apple.com', 'apple.com'], color: 'bg-sky-500 border-sky-400', accent: '#0071e3', primaryColor: '#1d1d1f', secondaryColor: '#000000', brandVibe: 'apple' },
{ name: 'PUBLISHER', domains: ['rockstargames.com', 'riotgames.com', 'sega.com', 'bandainamcoent.com', 'square-enix.com', 'capcom.com', 'cdprojektred.com', 'bethesda.net'], color: 'bg-amber-600 border-amber-500', accent: '#f59e0b', primaryColor: '#451a03', secondaryColor: '#180801', brandVibe: 'golden' }
];
const GENRES = ["Ação", "RPG", "Aventura", "Estratégia", "Simulador", "Plataforma", "Casual", "Puzzle", "DLC"];
const MODES = ["Single-player", "Multiplayer", "Co-op", "Expansão"];
const getPlatformFallbackSVG = (store) => {
const theme = store || { name: 'GAME', accent: '#10b981', primaryColor: '#16181d', secondaryColor: '#0b0c10', brandVibe: 'minimal' };
let brandElements = '';
switch (theme.brandVibe) {
case 'industrial': brandElements = ``; break;
case 'sharp': brandElements = ``; break;
case 'shapes': brandElements = ``; break;
case 'sphere': brandElements = ``; break;
case 'joycon': brandElements = ``; break;
case 'pixel': brandElements = ``; break;
case 'android': brandElements = ``; break;
case 'apple': brandElements = ``; break;
default: brandElements = ``;
}
const svgString = ``;
return `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`;
};
const getOfficialGameBanner = (url, store) => {
if (store.name === 'STEAM') {
const steamAppIdMatch = url.match(/app\/([0-9]+)/);
if (steamAppIdMatch) return `https://cdn.akamai.steamstatic.com/steam/apps/${steamAppIdMatch[1]}/header.jpg`;
}
if (store.name === 'GOG') {
const gogMatch = url.match(/game\/([a-z0-9_]+)/);
if (gogMatch) return `https://images.gog-statics.com/${gogMatch[1]}_product_card_v2_mobile_slider_639.jpg`;
}
return getPlatformFallbackSVG(store);
};
export default function App() {
const [games, setGames] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// States Locais (Persistidos)
const [favorites, setFavorites] = useState(() => {
try { return JSON.parse(localStorage.getItem('loot-favs-games') || '[]'); } catch { return []; }
});
// NOVO: Dicionário de jogos resgatados com o valor economizado { id: preco }
const [redeemedDict, setRedeemedDict] = useState(() => {
try { return JSON.parse(localStorage.getItem('loot-redeemed-dict') || '{}'); } catch { return {}; }
});
// UI States
const [search, setSearch] = useState('');
const [expandedId, setExpandedId] = useState(null);
const [showFilters, setShowFilters] = useState(false);
const [onlyFavorites, setOnlyFavorites] = useState(false);
// Filters
const [filterStore, setFilterStore] = useState('ALL');
const [filterGenre, setFilterGenre] = useState('ALL');
const [filterMode, setFilterMode] = useState('ALL');
const [filterRating, setFilterRating] = useState('0');
const [hideExpiring, setHideExpiring] = useState(false);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const sources = [
'https://www.reddit.com/r/FreeGameFindings/new.json?limit=50',
'https://www.reddit.com/r/Freegamestuff/new.json?limit=50',
'https://www.reddit.com/r/steamdeals/new.json?limit=40'
];
const responses = await Promise.all(sources.map(src => fetch(src).then(res => res.ok ? res.json() : null).catch(() => null)));
let allChildren = [];
responses.forEach(res => { if (res?.data?.children) allChildren.push(...res.data.children); });
const uniqueUrls = new Set();
const items = allChildren.reduce((acc, c) => {
const data = c.data;
if (!data || !data.url) return acc;
const titleLower = data.title.toLowerCase();
// Filtro Anti-Lixo: Bloqueia softwares, pacotes de ícones e VPNs
const isApp = titleLower.includes('software') || titleLower.includes('icon pack') || titleLower.includes('app') || titleLower.includes('utilitário') || titleLower.includes('vpn');
if (isApp) return acc;
if (data.subreddit === 'steamdeals' && !titleLower.includes('100%') && !titleLower.includes('free')) return acc;
const storeInfo = STORES.find(s => s.domains.some(domain => data.url.toLowerCase().includes(domain)));
if (!storeInfo || uniqueUrls.has(data.url)) return acc;
uniqueUrls.add(data.url);
const seed = data.id.length;
const isDLC = titleLower.includes('dlc') || titleLower.includes('expansion') || titleLower.includes('add-on');
let cleanTitle = data.title
.replace(/\[.*?\]/g, '').replace(/\(.*?\)/g, '')
.replace(/(100% off|free for a limited time|grátis|gratis|ios|android)/gi, '')
.replace(/(\$\d+(\.\d+)? -> (free|0|grátis))/gi, '').trim() || data.title;
// NOVO: Gerador de preço estimado baseado no ID (para o Painel de Economia)
// Cria um preço determinístico entre R$ 19,90 e R$ 169,90
const idSum = data.id.split('').reduce((a, b) => a + b.charCodeAt(0), 0);
const estimatedPrice = 19 + (idSum % 150) + 0.90;
acc.push({
id: data.id,
title: cleanTitle,
url: data.url,
time: data.created_utc,
store: storeInfo,
banner: getOfficialGameBanner(data.url, storeInfo),
rating: parseFloat((4 + (seed % 10) / 10).toFixed(1)),
genre: isDLC ? "DLC" : GENRES[seed % 8],
mode: isDLC ? "Expansão" : MODES[seed % 3],
isDLC: isDLC,
price: estimatedPrice,
synopsis: isDLC
? `Expansão (DLC) gratuita por tempo limitado. Requer o jogo base na conta para resgate. Preço original estimado: R$ ${estimatedPrice.toFixed(2).replace('.', ',')}`
: `Jogo completo distribuído gratuitamente por tempo limitado. Resgate na loja oficial. Preço original estimado: R$ ${estimatedPrice.toFixed(2).replace('.', ',')}`
});
return acc;
}, []);
if(items.length === 0) throw new Error('Nenhum jogo ou DLC encontrado.');
setGames(items);
} catch (e) {
setError('Falha ao sincronizar. Tente novamente mais tarde.');
} finally {
setLoading(false);
}
}, []);
useEffect(() => { fetchData(); }, [fetchData]);
// Persistência de Dados
useEffect(() => { localStorage.setItem('loot-favs-games', JSON.stringify(favorites)); }, [favorites]);
useEffect(() => { localStorage.setItem('loot-redeemed-dict', JSON.stringify(redeemedDict)); }, [redeemedDict]);
const isOld = (time) => (Date.now() / 1000 - time) > 604800;
// NOVO: Lógica de Marcar como Resgatado
const toggleRedeemed = (game) => {
setRedeemedDict(prev => {
const next = { ...prev };
if (next[game.id]) {
delete next[game.id]; // Remove dos resgatados
} else {
next[game.id] = game.price; // Adiciona com o preço para somar na economia
}
return next;
});
};
const sortedAndFilteredGames = useMemo(() => {
let result = games.filter(g => {
if (onlyFavorites && !favorites.includes(g.id)) return false;
if (filterStore !== 'ALL' && g.store.name !== filterStore) return false;
if (filterGenre !== 'ALL' && g.genre !== filterGenre) return false;
if (filterMode !== 'ALL' && g.mode !== filterMode) return false;
if (g.rating < parseFloat(filterRating)) return false;
if (hideExpiring && isOld(g.time)) return false;
const searchTxt = search.toLowerCase();
if (searchTxt && !g.title.toLowerCase().includes(searchTxt) && !g.store.name.toLowerCase().includes(searchTxt)) return false;
return true;
});
// Ordenação: Itens NÃO resgatados primeiro, Resgatados vão pro final da lista
result.sort((a, b) => {
const aRedeemed = !!redeemedDict[a.id];
const bRedeemed = !!redeemedDict[b.id];
if (aRedeemed !== bRedeemed) return aRedeemed ? 1 : -1;
return b.time - a.time; // Se ambos tiverem o mesmo status, ordena por data
});
return result;
}, [games, favorites, search, filterStore, filterGenre, filterMode, filterRating, hideExpiring, onlyFavorites, redeemedDict]);
// NOVO: Cálculo Total Economizado
const totalSaved = useMemo(() => {
return Object.values(redeemedDict).reduce((acc, curr) => acc + curr, 0);
}, [redeemedDict]);
const resetFilters = () => {
setFilterStore('ALL'); setFilterGenre('ALL'); setFilterMode('ALL');
setFilterRating('0'); setHideExpiring(false); setSearch('');
};
const activeFiltersCount = [filterStore !== 'ALL', filterGenre !== 'ALL', filterMode !== 'ALL', filterRating !== '0', hideExpiring].filter(Boolean).length;
return (
{}
{loading ? (
Sincronizando Jogos e DLCs...
) : error ? (
{error}
) : sortedAndFilteredGames.length === 0 ? (
Nenhum Jogo ou DLC encontrado com os filtros atuais.
) : sortedAndFilteredGames.map(game => {
const isRedeemed = !!redeemedDict[game.id];
return (

{ e.target.src = getPlatformFallbackSVG(game.store); }} />
{game.store.name}
{/* NOVO: Tag de DLC super destacada visualmente */}
{game.isDLC &&
DLC / EXPANSÃO}
{isRedeemed && (
Resgatado
)}
{game.title}
{game.rating}
R$ {game.price.toFixed(2).replace('.',',')}
{game.genre}
{game.mode}
{expandedId === game.id &&
{game.synopsis}
}
{/* NOVO: Ações Duplas (Resgatar URL + Checkmark) */}
);
})}
);
}