Spaces:
Build error
Build error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>PlayStore Search</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| </head> | |
| <body class="min-h-screen bg-zinc-950 text-white"> | |
| <div class="w-full max-w-lg mx-auto p-6 space-y-6"> | |
| <div class="space-y-2 text-center"> | |
| <h1 class="text-2xl font-bold tracking-tight">PlayStore Search</h1> | |
| <p class="text-sm text-zinc-400">Search for apps, games, and more</p> | |
| </div> | |
| <div class="relative"> | |
| <svg class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-zinc-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> | |
| <circle cx="11" cy="11" r="8"/> | |
| <path d="m21 21-4.35-4.35"/> | |
| </svg> | |
| <input | |
| id="searchInput" | |
| type="text" | |
| placeholder="Search apps..." | |
| class="flex h-10 w-full rounded-md border border-zinc-700 bg-zinc-900 px-10 py-2 text-sm placeholder:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" | |
| /> | |
| <kbd class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-zinc-700 bg-zinc-800 px-1.5 font-mono text-[10px] font-medium text-zinc-400"> | |
| ⌘K | |
| </kbd> | |
| </div> | |
| <button | |
| id="searchBtn" | |
| class="inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:ring-offset-zinc-950 w-full" | |
| > | |
| Search | |
| </button> | |
| <div class="flex items-center justify-between text-xs text-zinc-500"> | |
| <span id="locationInfo">Detecting location...</span> | |
| </div> | |
| <div class="space-y-3"> | |
| <h2 class="text-lg font-semibold">Trending Apps</h2> | |
| <div id="trending" class="space-y-2"> | |
| <p class="text-sm text-zinc-500">Loading...</p> | |
| </div> | |
| </div> | |
| <div id="results" class="space-y-3"></div> | |
| </div> | |
| <script> | |
| const API_BASE = window.location.origin; | |
| const searchInput = document.getElementById('searchInput'); | |
| const searchBtn = document.getElementById('searchBtn'); | |
| const results = document.getElementById('results'); | |
| const trending = document.getElementById('trending'); | |
| const locationInfo = document.getElementById('locationInfo'); | |
| let userCountry = 'us'; | |
| let userLang = 'en'; | |
| async function detectLocation() { | |
| try { | |
| const res = await fetch('https://ipapi.co/json/'); | |
| const data = await res.json(); | |
| userCountry = data.country_code?.toLowerCase() || 'us'; | |
| userLang = data.languages?.[0]?.split('-')[0] || 'en'; | |
| locationInfo.textContent = `📍 ${data.country_name} (${userCountry.toUpperCase()}) • ${userLang.toUpperCase()}`; | |
| } catch { | |
| locationInfo.textContent = '📍 Default: US • EN'; | |
| } | |
| } | |
| function renderApp(app) { | |
| return ` | |
| <div class="flex items-center gap-3 rounded-lg border border-zinc-800 bg-zinc-900 p-3 hover:bg-zinc-800 transition-colors cursor-pointer" onclick="window.open('${app.url}', '_blank')"> | |
| <img src="${app.icon}" alt="${app.title}" class="w-10 h-10 rounded-lg" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22 fill=%22%2371717a%22><rect width=%2224%22 height=%2224%22 rx=%224%22/></svg>'" /> | |
| <div class="min-w-0 flex-1"> | |
| <p class="text-sm font-medium truncate">${app.title}</p> | |
| <p class="text-xs text-zinc-500 truncate">${app.developer}</p> | |
| </div> | |
| <span class="text-xs text-zinc-500">${app.appId.substring(0, 20)}...</span> | |
| </div> | |
| `; | |
| } | |
| async function loadTrending() { | |
| try { | |
| const res = await fetch(`${API_BASE}/api/trending?country=${userCountry}&lang=${userLang}`); | |
| const data = await res.json(); | |
| if (data.apps && data.apps.length > 0) { | |
| trending.innerHTML = data.apps.map(renderApp).join(''); | |
| } else { | |
| trending.innerHTML = '<p class="text-sm text-zinc-500">No trending apps found</p>'; | |
| } | |
| } catch (e) { | |
| trending.innerHTML = '<p class="text-sm text-zinc-500">API not available. Start the backend server.</p>'; | |
| } | |
| } | |
| async function doSearch() { | |
| const query = searchInput.value.trim(); | |
| if (!query) return; | |
| results.innerHTML = '<p class="text-sm text-zinc-500">Searching...</p>'; | |
| try { | |
| const res = await fetch(`${API_BASE}/api/search?q=${encodeURIComponent(query)}&country=${userCountry}&lang=${userLang}`); | |
| const data = await res.json(); | |
| if (data.results && data.results.length > 0) { | |
| results.innerHTML = data.results.map(renderApp).join(''); | |
| } else { | |
| results.innerHTML = '<p class="text-sm text-zinc-500">No results found</p>'; | |
| } | |
| } catch (e) { | |
| results.innerHTML = '<p class="text-sm text-zinc-500">API not available. Start the backend server.</p>'; | |
| } | |
| } | |
| searchBtn.addEventListener('click', doSearch); | |
| searchInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter') doSearch(); | |
| }); | |
| detectLocation().then(loadTrending); | |
| </script> | |
| </body> | |
| </html> | |