// script.js // Set the base URL for your deployed Cloudflare Worker API // *** IMPORTANT: REPLACE THIS WITH THE ACTUAL URL OF YOUR DEPLOYED CLOUDFLARE WORKER *** const CLOUDFLARE_WORKER_API_BASE_URL = 'https://dmimapiworker.dmimx.workers.dev'; // Global state, will be populated by fetch calls let allTrends = []; let userData = { dmimBalance: 0, savedTrends: [] }; let currentSentimentTrend = null; // Static metadata for categories (icons, colors) - these are frontend-only display properties const performanceCategoriesMeta = { music: { name: "Music", icon: '', color: 'bg-purple-500' }, theater: { name: "Theater", icon: '', color: 'bg-yellow-500' }, dance: { name: "Dance", icon: '', color: 'bg-pink-500' }, comedy: { name: "Comedy", icon: '', color: 'bg-blue-500' }, emerging: { name: "Emerging", icon: '', color: 'bg-green-500' } }; // Helper to get category metadata function getCategoryMeta(categoryKey) { return performanceCategoriesMeta[categoryKey] || { name: categoryKey, icon: '', color: 'bg-gray-500' }; } // Function to calculate market share percentage with sentiment adjustment function calculateMarketShare(trend) { const totalAllSearches = allTrends.reduce((sum, t) => sum + t.current_searches, 0); const globalPercentage = totalAllSearches > 0 ? (trend.current_searches / totalAllSearches) * 100 : 0; const rawChange = trend.previous_searches > 0 ? ((trend.current_searches - trend.previous_searches) / trend.previous_searches) * 100 : trend.current_searches > 0 ? 100 : 0; const sentimentToUse = trend.sentiment || 0; const sentimentMultiplier = 1 + (sentimentToUse / 200); const adjustedChange = rawChange * sentimentMultiplier; return { percentage: adjustedChange > 0 ? globalPercentage.toFixed(2) : (globalPercentage * sentimentMultiplier).toFixed(2), // Apply sentiment to displayed percentage too change: adjustedChange.toFixed(2), rawChange: rawChange.toFixed(2), category: trend.category, sentiment: sentimentToUse, sentimentHeadline: trend.sentiment_headline || "" }; } // Helper functions for CSS classes and labels function getPercentageClass(change) { const numChange = parseFloat(change); if (numChange > 0) return 'percentage-up'; if (numChange < 0) return 'percentage-down'; return 'percentage-neutral'; } function getSentimentClass(sentiment) { const numSentiment = parseInt(sentiment); if (numSentiment > 20) return 'sentiment-positive'; if (numSentiment < -20) return 'sentiment-negative'; return 'sentiment-neutral'; } function getSentimentLabel(sentiment) { const numSentiment = parseInt(sentiment); if (numSentiment > 20) return 'Positive'; if (numSentiment < -20) return 'Negative'; return 'Neutral'; } function getPlatformIcon(platform) { return getCategoryMeta(platform).icon; } // Function to render trending cards function renderTrendingCards() { const container = document.getElementById('trendingCardsContainer'); container.innerHTML = ''; const topTrends = [...allTrends].sort((a, b) => b.current_searches - a.current_searches).slice(0, 8); topTrends.forEach(item => { const marketShare = calculateMarketShare(item); const percentageClass = getPercentageClass(marketShare.change); const sentimentClass = getSentimentClass(item.sentiment); const categoryMeta = getCategoryMeta(item.category); const card = document.createElement('div'); card.className = `trend-card bg-white rounded-lg shadow-sm p-4 h-40 flex flex-col justify-between transition cursor-pointer ${sentimentClass}`; card.innerHTML = `
${categoryMeta.name}
${marketShare.percentage}% (${marketShare.change > 0 ? '+' : ''}${marketShare.change}%)Searching...
'; if (!query) { container.innerHTML = ''; return; } try { const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/search?query=${encodeURIComponent(query)}`); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || `HTTP error! status: ${response.status}`); } const results = await response.json(); container.innerHTML = ''; if (results.length === 0) { container.innerHTML = 'No results found. Consider adding it as a new trend!
'; showToast(`No results found for ${query}`); return; } results.forEach(item => { const marketShare = calculateMarketShare(item); const percentageClass = getPercentageClass(marketShare.change); const sentimentClass = getSentimentClass(item.sentiment); const categoryMeta = getCategoryMeta(item.category); const card = document.createElement('div'); card.className = `trend-card bg-white rounded-lg shadow-sm p-4 h-40 flex flex-col justify-between transition cursor-pointer ${sentimentClass}`; card.innerHTML = `Failed to fetch search results.
'; } } // Function to save a trend (calls backend API) async function saveTrend(hashtag) { if (userData.savedTrends.some(t => t.hashtag === hashtag)) { showToast('This trend is already saved'); return; } try { const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends/${encodeURIComponent(hashtag)}/save`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || `HTTP error! status: ${response.status}`); } await initApp(); showToast(`${hashtag} saved to your library`); } catch (error) { console.error("Error saving trend:", error); showToast(`Error saving ${hashtag}: ${error.message}`); } } // Function to stake DMIM to a trend (calls backend API) async function stakeDmim(hashtag, amount) { if (!hashtag || !amount || amount <= 0) { showToast('Please select a trend and enter a valid amount'); return; } if (amount > userData.dmimBalance) { showToast('Insufficient DMIM balance'); return; } try { const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends/${encodeURIComponent(hashtag)}/stake`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: amount }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || `HTTP error! status: ${response.status}`); } userData.dmimBalance -= amount; // Optimistic update await initApp(); showToast(`Staked ${amount} DMIM to ${hashtag}`); } catch (error) { console.error("Error staking DMIM:", error); showToast(`Error staking DMIM to ${hashtag}: ${error.message}`); } } // Function to add DMIM tokens (client-side simulation for demo) async function addDmim(amount) { userData.dmimBalance += amount; renderSavedTrends(); showToast(`Added ${amount} DMIM to your balance`); } // Function to save sentiment for a trend (calls backend API) async function saveSentiment(hashtag, sentiment, headline) { try { const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends/${encodeURIComponent(hashtag)}/sentiment`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sentiment: sentiment, headline: headline }) }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || `HTTP error! status: ${response.status}`); } await initApp(); showToast(`Sentiment updated for ${hashtag}`); } catch (error) { console.error("Error saving sentiment:", error); showToast(`Error updating sentiment for ${hashtag}: ${error.message}`); } finally { document.getElementById('sentimentModal').classList.add('hidden'); } } // Initialize the app - fetches all data from backend async function initApp() { try { // Fetch all trends const trendsResponse = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends`); if (!trendsResponse.ok) { const errorData = await trendsResponse.json(); throw new Error(errorData.error || `Failed to fetch trends with status: ${trendsResponse.status}`); } allTrends = await trendsResponse.json(); // Filter saved trends for the local userData object userData.savedTrends = allTrends.filter(t => t.is_saved_by_user); // Fetch DMIM balance const dmimResponse = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/dmim_balance`); if (dmimResponse.ok) { const dmimData = await dmimResponse.json(); userData.dmimBalance = dmimData.balance; } else { console.warn("Could not fetch DMIM balance, using default for demo."); userData.dmimBalance = 1000; } renderTrendingCards(); renderSavedTrends(); } catch (error) { console.error("Failed to initialize app from backend:", error); showToast(`Failed to load data: ${error.message}. Please try again.`); } } // --- Event Listeners --- // Tab switching functionality document.querySelectorAll('.tab-button').forEach(button => { button.addEventListener('click', function() { document.querySelectorAll('.tab-button').forEach(btn => { btn.classList.remove('active', 'text-dmim-bg'); btn.classList.add('text-gray-500'); }); this.classList.add('active', 'text-dmim-bg'); this.classList.remove('text-gray-500'); document.querySelectorAll('#mainContent > div').forEach(tab => { tab.classList.add('hidden');