privateuserh commited on
Commit
e642dfc
·
verified ·
1 Parent(s): e3d3537

Delete index.html

Browse files
Files changed (1) hide show
  1. index.html +0 -718
index.html DELETED
@@ -1,718 +0,0 @@
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, maximum-scale=1.0, user-scalable=no">
6
- <title>DMIM - Performance Art Trend Explorer</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
- <link rel="stylesheet" href="styles.css"
10
- /* Your existing CSS styles */
11
- ::-webkit-scrollbar { width: 4px; }
12
- ::-webkit-scrollbar-track { background: #f1f1f1; }
13
- ::-webkit-scrollbar-thumb { background: #4a6fdc; border-radius: 3px; }
14
- ::-webkit-scrollbar-thumb:hover { background: #3a5bc7; }
15
- .trend-card:hover { transform: translateY(-2px); }
16
- .dmim-bg { background-color: #4a6fdc; }
17
- * { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }
18
- @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
19
- .explainer-animate { animation: fadeIn 0.3s ease-out forwards; }
20
- .percentage-up { color: #10b981; }
21
- .percentage-down { color: #ef4444; }
22
- .percentage-neutral { color: #6b7280; }
23
- .sentiment-positive { background-color: rgba(16, 185, 129, 0.1); border-left: 3px solid #10b981; }
24
- .sentiment-negative { background-color: rgba(239, 68, 68, 0.1); border-left: 3px solid #ef4444; }
25
- .sentiment-neutral { background-color: rgba(156, 163, 175, 0.1); border-left: 3px solid #9ca3af; }
26
- .sentiment-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; border-radius: 50%; cursor: pointer; }
27
- .sentiment-slider.positive::-webkit-slider-thumb { background: #10b981; }
28
- .sentiment-slider.negative::-webkit-slider-thumb { background: #ef4444; }
29
- .sentiment-slider.neutral::-webkit-slider-thumb { background: #6b7280; }
30
- /* Floating button styles */
31
- .floating-btn { position: fixed; bottom: 80px; right: 20px; width: 50px; height: 50px; border-radius: 50%; background-color: #4a6fdc; color: white; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); z-index: 40; cursor: pointer; transition: all 0.3s ease; }
32
- .floating-btn:hover { transform: scale(1.1); box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3); }
33
- /* Legend modal styles */
34
- .legend-item { display: flex; align-items: center; margin-bottom: 12px; }
35
- .legend-icon { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; margin-right: 12px; flex-shrink: 0; }
36
- </style>
37
- </head>
38
- <body class="bg-gray-50 font-sans text-gray-800">
39
- <script src="script.js"></script>
40
- <script>
41
- // ...
42
- const BACKEND_API_BASE_URL = "/api"; // This relative path will be handled by your FastAPI proxy
43
-
44
- // ... (rest of your frontend logic to interact with backend endpoints)
45
- // Example:
46
- async function fetchAllTrends() {
47
- try {
48
- const response = await fetch(`${BACKEND_API_BASE_URL}/trends`);
49
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
50
- const data = await response.json();
51
- // Process data and update UI
52
- return data;
53
- } catch (error) {
54
- console.error("Error fetching trends:", error);
55
- showToast('Error fetching trends.');
56
- return [];
57
- }
58
- }
59
-
60
- // Ensure your static assets are correctly linked
61
- // e.g., <link rel="stylesheet" href="/static/styles.css">
62
- // <script src="/static/script.js"></script>
63
- </script>
64
- <script>
65
- // Set the base URL for your deployed Cloudflare Worker API
66
- // *** IMPORTANT: REPLACE THIS WITH THE ACTUAL URL OF YOUR DEPLOYED CLOUDFLARE WORKER ***
67
- const CLOUDFLARE_WORKER_API_BASE_URL = 'https://dmimapiworker.dmimx.workers.dev';
68
-
69
- // Global state, will be populated by fetch calls
70
- let allTrends = [];
71
- let userData = {
72
- dmimBalance: 0,
73
- savedTrends: []
74
- };
75
- let currentSentimentTrend = null;
76
-
77
- // Static metadata for categories (icons, colors) - these are frontend-only display properties
78
- // and don't need to be fetched from the database.
79
- const performanceCategoriesMeta = {
80
- music: { name: "Music", icon: '<i class="fas fa-music text-purple-500"></i>', color: 'bg-purple-500' },
81
- theater: { name: "Theater", icon: '<i class="fas fa-theater-masks text-yellow-500"></i>', color: 'bg-yellow-500' },
82
- dance: { name: "Dance", icon: '<i class="fas fa-child text-pink-500"></i>', color: 'bg-pink-500' },
83
- comedy: { name: "Comedy", icon: '<i class="fas fa-laugh-squint text-blue-500"></i>', color: 'bg-blue-500' },
84
- emerging: { name: "Emerging", icon: '<i class="fas fa-star text-green-500"></i>', color: 'bg-green-500' }
85
- };
86
-
87
- // Helper to get category metadata
88
- function getCategoryMeta(categoryKey) {
89
- return performanceCategoriesMeta[categoryKey] || { name: categoryKey, icon: '<i class="fas fa-hashtag"></i>', color: 'bg-gray-500' };
90
- }
91
-
92
- // Function to calculate market share percentage with sentiment adjustment
93
- // This logic remains client-side, using current_searches and sentiment from fetched D1 data.
94
- function calculateMarketShare(trend) {
95
- const totalAllSearches = allTrends.reduce((sum, t) => sum + t.current_searches, 0);
96
-
97
- const globalPercentage = totalAllSearches > 0 ? (trend.current_searches / totalAllSearches) * 100 : 0;
98
-
99
- const rawChange = trend.previous_searches > 0
100
- ? ((trend.current_searches - trend.previous_searches) / trend.previous_searches) * 100
101
- : trend.current_searches > 0 ? 100 : 0;
102
-
103
- // Use the sentiment value directly from the fetched trend object (which comes from D1)
104
- const sentimentToUse = trend.sentiment || 0;
105
- const sentimentMultiplier = 1 + (sentimentToUse / 200);
106
- const adjustedChange = rawChange * sentimentMultiplier;
107
-
108
- return {
109
- percentage: adjustedChange > 0 ? globalPercentage.toFixed(2) : (globalPercentage * sentimentMultiplier).toFixed(2), // Apply sentiment to displayed percentage too
110
- change: adjustedChange.toFixed(2),
111
- rawChange: rawChange.toFixed(2),
112
- category: trend.category,
113
- sentiment: sentimentToUse,
114
- sentimentHeadline: trend.sentiment_headline || ""
115
- };
116
- }
117
-
118
- // Helper functions for CSS classes and labels (remain unchanged)
119
- function getPercentageClass(change) {
120
- const numChange = parseFloat(change);
121
- if (numChange > 0) return 'percentage-up';
122
- if (numChange < 0) return 'percentage-down';
123
- return 'percentage-neutral';
124
- }
125
-
126
- function getSentimentClass(sentiment) {
127
- const numSentiment = parseInt(sentiment);
128
- if (numSentiment > 20) return 'sentiment-positive';
129
- if (numSentiment < -20) return 'sentiment-negative';
130
- return 'sentiment-neutral';
131
- }
132
-
133
- function getSentimentLabel(sentiment) {
134
- const numSentiment = parseInt(sentiment);
135
- if (numSentiment > 20) return 'Positive';
136
- if (numSentiment < -20) return 'Negative';
137
- return 'Neutral';
138
- }
139
-
140
- function getPlatformIcon(platform) {
141
- return getCategoryMeta(platform).icon;
142
- }
143
-
144
- // Function to render trending cards
145
- function renderTrendingCards() {
146
- const container = document.getElementById('trendingCardsContainer');
147
- container.innerHTML = '';
148
-
149
- const topTrends = [...allTrends].sort((a, b) => b.current_searches - a.current_searches).slice(0, 8);
150
-
151
- topTrends.forEach(item => {
152
- const marketShare = calculateMarketShare(item);
153
- const percentageClass = getPercentageClass(marketShare.change);
154
- const sentimentClass = getSentimentClass(item.sentiment);
155
- const categoryMeta = getCategoryMeta(item.category);
156
-
157
- const card = document.createElement('div');
158
- card.className = `trend-card bg-white rounded-lg shadow-sm p-4 h-40 flex flex-col justify-between transition cursor-pointer ${sentimentClass}`;
159
- card.innerHTML = `
160
- <div class="flex items-start justify-between mb-2">
161
- <div>
162
- <span class="inline-flex items-center bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">
163
- ${getPlatformIcon(item.category)}
164
- <span class="ml-1">${item.hashtag}</span>
165
- </span>
166
- </div>
167
- <span class="inline-flex items-center bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">
168
- <i class="fas fa-chart-line ${percentageClass} mr-1"></i>
169
- ${marketShare.percentage}%
170
- <span class="ml-1 ${percentageClass}">(${marketShare.change > 0 ? '+' : ''}${marketShare.change}%)</span>
171
- </span>
172
- </div>
173
- <div class="flex justify-between items-center mb-3">
174
- <span class="text-xs text-gray-500">${item.current_searches.toLocaleString()} searches</span>
175
- <span class="text-xs px-2 py-1 rounded-full ${categoryMeta.color} text-white">
176
- ${categoryMeta.name}
177
- </span>
178
- </div>
179
- <div class="flex justify-between items-center text-xs text-gray-500">
180
- <span>
181
- <span class="${getSentimentClass(item.sentiment).replace('sentiment-', 'text-')}">
182
- <i class="fas ${item.sentiment > 20 ? 'fa-smile' : item.sentiment < -20 ? 'fa-frown' : 'fa-meh'}"></i>
183
- ${getSentimentLabel(item.sentiment)}
184
- </span>
185
- </span>
186
- <button class="save-trend-btn text-gray-400 hover:text-dmim-bg" data-hashtag="${item.hashtag}">
187
- <i class="fas fa-bookmark"></i> Save
188
- </button>
189
- </div>
190
- `;
191
- container.appendChild(card);
192
-
193
- card.addEventListener('click', function() {
194
- openSentimentModal(item.hashtag);
195
- });
196
- });
197
-
198
- document.querySelectorAll('.save-trend-btn').forEach(btn => {
199
- btn.addEventListener('click', function(e) {
200
- e.stopPropagation();
201
- const hashtag = this.getAttribute('data-hashtag');
202
- saveTrend(hashtag);
203
- });
204
- });
205
- }
206
-
207
- // Function to render saved trends
208
- function renderSavedTrends() {
209
- const container = document.getElementById('savedTrendsContainer');
210
- container.innerHTML = '';
211
-
212
- const trendSelect = document.getElementById('trendSelect');
213
- trendSelect.innerHTML = '<option value="">-- Select a saved trend --</option>';
214
-
215
- userData.savedTrends.forEach(trend => {
216
- const marketShare = calculateMarketShare(trend);
217
- // Prioritize user_sentiment if explicitly set, else use global sentiment
218
- const sentimentClass = getSentimentClass(trend.user_sentiment !== null ? trend.user_sentiment : trend.sentiment);
219
- const categoryMeta = getCategoryMeta(trend.category);
220
-
221
- const element = document.createElement('div');
222
- element.className = `flex items-center p-3 rounded-lg bg-white shadow-sm cursor-pointer hover:bg-gray-50 ${sentimentClass}`;
223
- element.innerHTML = `
224
- <div class="flex-shrink-0 mr-3">
225
- <div class="w-8 h-8 rounded-full flex items-center justify-center text-white ${categoryMeta.color}">
226
- ${getPlatformIcon(trend.category)}
227
- </div>
228
- </div>
229
- <div class="flex-1">
230
- <div class="flex items-center">
231
- <h4 class="font-medium text-sm">${trend.hashtag}</h4>
232
- ${trend.staked_amount > 0 ? '<span class="ml-1 text-green-500"><i class="fas fa-check-circle"></i></span>' : ''}
233
- </div>
234
- <div class="flex items-center">
235
- <p class="text-gray-500 text-xs mr-2">${categoryMeta.name}</p>
236
- <span class="text-xs ${percentageClass}">
237
- ${marketShare.percentage}% (${marketShare.change > 0 ? '+' : ''}${marketShare.change}%)
238
- </span>
239
- </div>
240
- </div>
241
- <button class="text-gray-500 hover:text-dmim-bg sentiment-btn" data-hashtag="${trend.hashtag}">
242
- <i class="fas fa-ellipsis-v"></i>
243
- </button>
244
- `;
245
- container.appendChild(element);
246
-
247
- const option = document.createElement('option');
248
- option.value = trend.hashtag;
249
- option.textContent = trend.hashtag;
250
- trendSelect.appendChild(option);
251
-
252
- element.addEventListener('click', function() {
253
- openSentimentModal(trend.hashtag);
254
- });
255
- });
256
-
257
- document.getElementById('dmimBalance').textContent = userData.dmimBalance + ' DMIM';
258
- document.getElementById('dmimBalanceDisplay').textContent = userData.dmimBalance + ' DMIM';
259
- }
260
-
261
- // Function to open sentiment modal
262
- async function openSentimentModal(hashtag) {
263
- currentSentimentTrend = hashtag;
264
-
265
- const trendData = allTrends.find(t => t.hashtag === hashtag);
266
-
267
- if (!trendData) {
268
- showToast('Trend data not found for sentiment adjustment.');
269
- return;
270
- }
271
-
272
- document.getElementById('sentimentTrendName').textContent = hashtag;
273
- // Display either user_sentiment (if explicitly set) or global sentiment from the database
274
- document.getElementById('sentimentSlider').value = trendData.user_sentiment !== null ? trendData.user_sentiment : trendData.sentiment;
275
- document.getElementById('sentimentHeadline').value = trendData.user_sentiment_headline || trendData.sentiment_headline || "";
276
-
277
- updateSentimentSlider(document.getElementById('sentimentSlider').value);
278
-
279
- document.getElementById('sentimentModal').classList.remove('hidden');
280
- }
281
-
282
- // Function to update sentiment slider appearance (logic remains same)
283
- function updateSentimentSlider(value) {
284
- const slider = document.getElementById('sentimentSlider');
285
- slider.value = value;
286
- slider.classList.remove('positive', 'negative', 'neutral');
287
- if (value > 20) {
288
- slider.classList.add('positive');
289
- } else if (value < -20) {
290
- slider.classList.add('negative');
291
- } else {
292
- slider.classList.add('neutral');
293
- }
294
-
295
- const impactText = document.getElementById('sentimentImpactText');
296
- if (value > 20) {
297
- impactText.textContent = `Positive sentiment will boost growth by ${Math.round(value/2)}%.`;
298
- impactText.className = "text-sm text-green-600";
299
- } else if (value < -20) {
300
- impactText.textContent = `Negative sentiment will reduce growth by ${Math.round(Math.abs(value)/2)}%.`;
301
- impactText.className = "text-sm text-red-600";
302
- } else {
303
- impactText.textContent = "Neutral sentiment will not affect trend growth.";
304
- impactText.className = "text-sm text-gray-600";
305
- }
306
- }
307
-
308
- // Function to show search results
309
- async function showSearchResults(query) {
310
- const container = document.getElementById('searchResultsContainer');
311
- container.innerHTML = '<p class="text-center text-gray-500 mt-8">Searching...</p>';
312
-
313
- if (!query) {
314
- container.innerHTML = '';
315
- return;
316
- }
317
-
318
- try {
319
- const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/search?query=${encodeURIComponent(query)}`);
320
- if (!response.ok) {
321
- const errorData = await response.json();
322
- throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
323
- }
324
- const results = await response.json();
325
-
326
- container.innerHTML = '';
327
- if (results.length === 0) {
328
- container.innerHTML = '<p class="text-center text-gray-500 mt-8">No results found. Consider adding it as a new trend!</p>';
329
- showToast(`No results found for ${query}`);
330
- return;
331
- }
332
-
333
- results.forEach(item => {
334
- const marketShare = calculateMarketShare(item);
335
- const percentageClass = getPercentageClass(marketShare.change);
336
- const sentimentClass = getSentimentClass(item.sentiment);
337
- const categoryMeta = getCategoryMeta(item.category);
338
-
339
- const card = document.createElement('div');
340
- card.className = `trend-card bg-white rounded-lg shadow-sm p-4 h-40 flex flex-col justify-between transition cursor-pointer ${sentimentClass}`;
341
- card.innerHTML = `
342
- <div class="flex items-start justify-between mb-2">
343
- <div>
344
- <span class="inline-flex items-center bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">
345
- ${getPlatformIcon(item.category)}
346
- <span class="ml-1">${item.hashtag}</span>
347
- </span>
348
- </div>
349
- <span class="inline-flex items-center bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full">
350
- <i class="fas fa-chart-line ${percentageClass} mr-1"></i>
351
- ${marketShare.percentage}%
352
- <span class="ml-1 ${percentageClass}">(${marketShare.change > 0 ? '+' : ''}${marketShare.change}%)</span>
353
- </span>
354
- </div>
355
- <div class="flex justify-between items-center mb-3">
356
- <span class="text-xs text-gray-500">${item.current_searches.toLocaleString()} searches</span>
357
- <span class="text-xs px-2 py-1 rounded-full ${categoryMeta.color} text-white">
358
- ${categoryMeta.name}
359
- </span>
360
- </div>
361
- <div class="flex justify-between items-center text-xs text-gray-500">
362
- <span>
363
- <span class="${getSentimentClass(item.sentiment).replace('sentiment-', 'text-')}">
364
- <i class="fas ${item.sentiment > 20 ? 'fa-smile' : item.sentiment < -20 ? 'fa-frown' : 'fa-meh'}"></i>
365
- ${getSentimentLabel(item.sentiment)}
366
- </span>
367
- </span>
368
- <button class="save-trend-btn text-gray-400 hover:text-dmim-bg" data-hashtag="${item.hashtag}">
369
- <i class="fas fa-bookmark"></i> Save
370
- </button>
371
- </div>
372
- `;
373
- container.appendChild(card);
374
-
375
- card.addEventListener('click', function() {
376
- openSentimentModal(item.hashtag);
377
- });
378
- });
379
-
380
-
381
- document.querySelectorAll('.save-trend-btn').forEach(btn => {
382
- btn.addEventListener('click', function(e) {
383
- e.stopPropagation();
384
- const hashtag = this.getAttribute('data-hashtag');
385
- saveTrend(hashtag);
386
- });
387
- });
388
-
389
- showToast(`Found ${results.length} results for ${query}`);
390
-
391
- } catch (error) {
392
- console.error("Error searching trends:", error);
393
- showToast(`Error searching for ${query}: ${error.message}`);
394
- container.innerHTML = '<p class="text-center text-red-500 mt-8">Failed to fetch search results.</p>';
395
- }
396
- }
397
-
398
- // Function to save a trend (calls backend API)
399
- async function saveTrend(hashtag) {
400
- // Check if already saved locally to prevent unnecessary API calls
401
- if (userData.savedTrends.some(t => t.hashtag === hashtag)) {
402
- showToast('This trend is already saved');
403
- return;
404
- }
405
-
406
- try {
407
- const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends/${encodeURIComponent(hashtag)}/save`, {
408
- method: 'PUT',
409
- headers: { 'Content-Type': 'application/json' },
410
- });
411
- if (!response.ok) {
412
- const errorData = await response.json();
413
- throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
414
- }
415
-
416
- await initApp();
417
- showToast(`${hashtag} saved to your library`);
418
- } catch (error) {
419
- console.error("Error saving trend:", error);
420
- showToast(`Error saving ${hashtag}: ${error.message}`);
421
- }
422
- }
423
-
424
- // Function to stake DMIM to a trend (calls backend API)
425
- async function stakeDmim(hashtag, amount) {
426
- if (!hashtag || !amount || amount <= 0) {
427
- showToast('Please select a trend and enter a valid amount');
428
- return;
429
- }
430
-
431
- if (amount > userData.dmimBalance) {
432
- showToast('Insufficient DMIM balance');
433
- return;
434
- }
435
-
436
- try {
437
- const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends/${encodeURIComponent(hashtag)}/stake`, {
438
- method: 'PUT',
439
- headers: { 'Content-Type': 'application/json' },
440
- body: JSON.stringify({ amount: amount })
441
- });
442
- if (!response.ok) {
443
- const errorData = await response.json();
444
- throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
445
- }
446
-
447
- userData.dmimBalance -= amount; // Optimistic update
448
- await initApp();
449
- showToast(`Staked ${amount} DMIM to ${hashtag}`);
450
- } catch (error) {
451
- console.error("Error staking DMIM:", error);
452
- showToast(`Error staking DMIM to ${hashtag}: ${error.message}`);
453
- }
454
- }
455
-
456
- // Function to add DMIM tokens (client-side simulation for demo)
457
- // In a real app, this would be an API call to a proper financial system
458
- async function addDmim(amount) {
459
- userData.dmimBalance += amount;
460
- renderSavedTrends();
461
- showToast(`Added ${amount} DMIM to your balance`);
462
- }
463
-
464
- // Function to save sentiment for a trend (calls backend API)
465
- async function saveSentiment(hashtag, sentiment, headline) {
466
- try {
467
- const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends/${encodeURIComponent(hashtag)}/sentiment`, {
468
- method: 'PUT',
469
- headers: { 'Content-Type': 'application/json' },
470
- body: JSON.stringify({ sentiment: sentiment, headline: headline })
471
- });
472
- if (!response.ok) {
473
- const errorData = await response.json();
474
- throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
475
- }
476
-
477
- await initApp();
478
- showToast(`Sentiment updated for ${hashtag}`);
479
- } catch (error) {
480
- console.error("Error saving sentiment:", error);
481
- showToast(`Error updating sentiment for ${hashtag}: ${error.message}`);
482
- } finally {
483
- document.getElementById('sentimentModal').classList.add('hidden');
484
- }
485
- }
486
-
487
- // Initialize the app - fetches all data from backend
488
- async function initApp() {
489
- try {
490
- // Fetch all trends
491
- const trendsResponse = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends`);
492
- if (!trendsResponse.ok) {
493
- const errorData = await trendsResponse.json();
494
- throw new Error(errorData.error || `Failed to fetch trends with status: ${trendsResponse.status}`);
495
- }
496
- allTrends = await trendsResponse.json();
497
-
498
- // Filter saved trends for the local userData object
499
- userData.savedTrends = allTrends.filter(t => t.is_saved_by_user);
500
-
501
- // Fetch DMIM balance
502
- const dmimResponse = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/dmim_balance`);
503
- if (dmimResponse.ok) {
504
- const dmimData = await dmimResponse.json();
505
- userData.dmimBalance = dmimData.balance;
506
- } else {
507
- console.warn("Could not fetch DMIM balance, using default for demo.");
508
- userData.dmimBalance = 1000; // Fallback for demo if endpoint fails
509
- }
510
-
511
- renderTrendingCards();
512
- renderSavedTrends();
513
-
514
- } catch (error) {
515
- console.error("Failed to initialize app from backend:", error);
516
- showToast(`Failed to load data: ${error.message}. Please try again.`);
517
- }
518
- }
519
-
520
- // --- Event Listeners (remain mostly the same, call updated async functions) ---
521
-
522
- // Tab switching functionality
523
- document.querySelectorAll('.tab-button').forEach(button => {
524
- button.addEventListener('click', function() {
525
- document.querySelectorAll('.tab-button').forEach(btn => {
526
- btn.classList.remove('active', 'text-dmim-bg');
527
- btn.classList.add('text-gray-500');
528
- });
529
- this.classList.add('active', 'text-dmim-bg');
530
- this.classList.remove('text-gray-500');
531
- document.querySelectorAll('#mainContent > div').forEach(tab => {
532
- tab.classList.add('hidden');
533
- });
534
- const tabId = this.getAttribute('data-tab');
535
- document.getElementById(tabId).classList.remove('hidden');
536
- document.getElementById('mainContent').scrollTo(0, 0);
537
- if (tabId === 'searchTab') {
538
- setTimeout(() => { document.getElementById('trendSearchInput').focus(); }, 100);
539
- }
540
- });
541
- });
542
-
543
- // Search trend functionality
544
- document.getElementById('searchTrendBtn').addEventListener('click', function() {
545
- const query = document.getElementById('trendSearchInput').value.trim();
546
- showSearchResults(query);
547
- });
548
-
549
- document.getElementById('trendSearchInput').addEventListener('keypress', function(e) {
550
- if (e.key === 'Enter') {
551
- const query = this.value.trim();
552
- showSearchResults(query);
553
- }
554
- });
555
-
556
- // Add trend button
557
- document.getElementById('addTrendBtn').addEventListener('click', function() {
558
- document.getElementById('addTrendModal').classList.remove('hidden');
559
- // Reset fields for new trend
560
- document.getElementById('newHashtag').value = '';
561
- document.querySelectorAll('.platform-btn').forEach(b => {
562
- b.classList.remove('bg-dmim-bg', 'text-white');
563
- b.classList.add('bg-gray-200', 'text-gray-700');
564
- });
565
- });
566
-
567
- // Cancel add trend
568
- document.getElementById('cancelAddTrend').addEventListener('click', function() {
569
- document.getElementById('addTrendModal').classList.add('hidden');
570
- });
571
-
572
- // Save new trend
573
- document.getElementById('saveTrend').addEventListener('click', async function() {
574
- const hashtag = document.getElementById('newHashtag').value.trim();
575
- const platform = document.querySelector('.platform-btn.bg-dmim-bg')?.getAttribute('data-platform');
576
-
577
- if (!hashtag || !platform) {
578
- showToast('Please enter a hashtag and select a platform');
579
- return;
580
- }
581
-
582
- try {
583
- const response = await fetch(`${CLOUDFLARE_WORKER_API_BASE_URL}/trends`, {
584
- method: 'POST',
585
- headers: { 'Content-Type': 'application/json' },
586
- body: JSON.stringify({
587
- hashtag: hashtag,
588
- category: platform,
589
- current_searches: 1000,
590
- previous_searches: 0,
591
- sentiment: 0,
592
- sentiment_headline: "",
593
- is_saved_by_user: true, // Auto-save when creating
594
- staked_amount: 0,
595
- user_sentiment: 0,
596
- user_sentiment_headline: ""
597
- })
598
- });
599
- if (!response.ok) {
600
- const errorData = await response.json();
601
- throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
602
- }
603
-
604
- await initApp();
605
- document.getElementById('addTrendModal').classList.add('hidden');
606
- showToast(`${hashtag} added and saved to your library`);
607
-
608
- } catch (error) {
609
- console.error("Error adding new trend:", error);
610
- showToast(`Error adding new trend ${hashtag}: ${error.message}`);
611
- }
612
- });
613
-
614
- // Platform selection in add trend modal
615
- document.querySelectorAll('.platform-btn').forEach(btn => {
616
- btn.addEventListener('click', function() {
617
- document.querySelectorAll('.platform-btn').forEach(b => {
618
- b.classList.remove('bg-dmim-bg', 'text-white');
619
- b.classList.add('bg-gray-200', 'text-gray-700');
620
- });
621
- this.classList.remove('bg-gray-200', 'text-gray-700');
622
- this.classList.add('bg-dmim-bg', 'text-white');
623
- });
624
- });
625
-
626
- // Stake DMIM button
627
- document.getElementById('stakeBtn').addEventListener('click', function() {
628
- const hashtag = document.getElementById('trendSelect').value;
629
- const amount = parseFloat(document.getElementById('stakeAmount').value);
630
- stakeDmim(hashtag, amount);
631
- });
632
-
633
- // DMIM balance button
634
- document.getElementById('dmimBalanceBtn').addEventListener('click', function() {
635
- document.getElementById('dmimModal').classList.remove('hidden');
636
- });
637
-
638
- // Close DMIM modal
639
- document.getElementById('closeDmimModal').addEventListener('click', function() {
640
- document.getElementById('dmimModal').classList.add('hidden');
641
- });
642
-
643
- // Add DMIM button (calls client-side for demo)
644
- document.getElementById('addDmimBtn').addEventListener('click', function() {
645
- addDmim(1000);
646
- });
647
-
648
- // Explainer button
649
- document.getElementById('explainerBtn').addEventListener('click', function() {
650
- document.getElementById('explainerModal').classList.remove('hidden');
651
- });
652
-
653
- // Close explainer modal
654
- document.getElementById('closeExplainerModal').addEventListener('click', function() {
655
- document.getElementById('explainerModal').classList.add('hidden');
656
- });
657
-
658
- // Close explainer button
659
- document.getElementById('closeExplainerBtn').addEventListener('click', function() {
660
- document.getElementById('explainerModal').classList.add('hidden');
661
- });
662
-
663
- // Stake info button
664
- document.getElementById('stakeInfoBtn').addEventListener('click', function() {
665
- document.getElementById('explainerModal').classList.remove('hidden');
666
- });
667
-
668
- // Sentiment slider change
669
- document.getElementById('sentimentSlider').addEventListener('input', function() {
670
- updateSentimentSlider(this.value);
671
- });
672
-
673
- // Save sentiment (calls backend API)
674
- document.getElementById('saveSentiment').addEventListener('click', function() {
675
- const sentiment = parseInt(document.getElementById('sentimentSlider').value);
676
- const headline = document.getElementById('sentimentHeadline').value.trim();
677
- saveSentiment(currentSentimentTrend, sentiment, headline);
678
- });
679
-
680
- // Cancel sentiment
681
- document.getElementById('cancelSentiment').addEventListener('click', function() {
682
- document.getElementById('sentimentModal').classList.add('hidden');
683
- });
684
-
685
- // Sentiment help button
686
- document.getElementById('sentimentHelpBtn').addEventListener('click', function() {
687
- document.getElementById('sentimentLegendModal').classList.remove('hidden');
688
- });
689
-
690
- // Close legend modal
691
- document.getElementById('closeLegendModal').addEventListener('click', function() {
692
- document.getElementById('sentimentLegendModal').classList.add('hidden');
693
- });
694
-
695
- // Close legend button
696
- document.getElementById('closeLegendBtn').addEventListener('click', function() {
697
- document.getElementById('sentimentLegendModal').classList.add('hidden');
698
- });
699
-
700
- // Toast notification function
701
- function showToast(message) {
702
- const toast = document.getElementById('toast');
703
- const toastMessage = document.getElementById('toastMessage');
704
-
705
- toastMessage.textContent = message;
706
- toast.classList.remove('hidden');
707
-
708
- setTimeout(() => {
709
- toast.classList.add('hidden');
710
- }, 3000);
711
- }
712
-
713
- // Initialize the app when DOM is loaded
714
- document.addEventListener('DOMContentLoaded', initApp);
715
- </script>
716
- <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=privateuserh/privdmi2-01pa" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p>
717
- </body>
718
- </html>