openfree commited on
Commit
2c94166
·
verified ·
1 Parent(s): 2102304

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +335 -429
index.html CHANGED
@@ -345,13 +345,13 @@
345
  <div class="price-ticker glass" id="priceTicker">
346
  <div class="ticker-item">
347
  <span class="ticker-symbol">BTC</span>
348
- <span class="ticker-price" id="btcPrice">$--,---</span>
349
- <span class="ticker-change up" id="btcChange">+0.0%</span>
350
  </div>
351
  <div class="ticker-item">
352
  <span class="ticker-symbol">ETH</span>
353
- <span class="ticker-price" id="ethPrice">$--,---</span>
354
- <span class="ticker-change up" id="ethChange">+0.0%</span>
355
  </div>
356
  </div>
357
 
@@ -525,10 +525,146 @@
525
  <div class="toast-container" id="toastContainer"></div>
526
 
527
  <script>
528
- // ==================== Configuration ====================
529
- const API_BASE = '/api';
530
- const SESSION_KEY = 'pricegen_session';
531
- const USER_KEY = 'pricegen_user';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
532
 
533
  // ==================== i18n ====================
534
  const i18n = {
@@ -576,13 +712,11 @@
576
  }
577
  };
578
 
579
- let currentLang = 'en';
580
  let currentUser = null;
581
- let sessionToken = null;
582
  let currentSort = 'hot';
583
  let currentCategory = '';
584
  let selectedMarket = null;
585
- let categories = {};
586
 
587
  function t(key) { return i18n[currentLang][key] || i18n.en[key] || key; }
588
 
@@ -599,178 +733,30 @@
599
  });
600
  }
601
 
602
- // ==================== Session Management ====================
603
- function saveSession(token, user) {
604
- sessionToken = token;
605
- currentUser = user;
606
- localStorage.setItem(SESSION_KEY, token);
607
- localStorage.setItem(USER_KEY, JSON.stringify(user));
608
- }
609
-
610
- function loadSession() {
611
- sessionToken = localStorage.getItem(SESSION_KEY);
612
- const userStr = localStorage.getItem(USER_KEY);
613
- if (userStr) {
614
- try {
615
- currentUser = JSON.parse(userStr);
616
- } catch (e) {
617
- currentUser = null;
618
- }
619
- }
620
- return sessionToken && currentUser;
621
- }
622
-
623
- function clearSession() {
624
- sessionToken = null;
625
- currentUser = null;
626
- localStorage.removeItem(SESSION_KEY);
627
- localStorage.removeItem(USER_KEY);
628
- }
629
-
630
- // ==================== API ====================
631
- async function fetchAPI(endpoint, options = {}) {
632
- const headers = {
633
- 'Content-Type': 'application/json',
634
- ...options.headers
635
- };
636
-
637
- // Add authentication headers
638
- if (sessionToken) {
639
- headers['Authorization'] = `Bearer ${sessionToken}`;
640
- }
641
- if (currentUser?.id) {
642
- headers['X-User-ID'] = currentUser.id.toString();
643
- }
644
-
645
- try {
646
- const response = await fetch(`${API_BASE}${endpoint}`, {
647
- ...options,
648
- headers
649
- });
650
-
651
- if (response.status === 401) {
652
- // Session expired or invalid
653
- clearSession();
654
- updateUserCard();
655
- showToast(t('loginRequired'), 'error');
656
- return null;
657
- }
658
-
659
- if (!response.ok) {
660
- const error = await response.json();
661
- throw new Error(error.detail || 'API Error');
662
- }
663
-
664
- return await response.json();
665
- } catch (error) {
666
- console.error('API Error:', error);
667
- throw error;
668
- }
669
- }
670
-
671
- // ==================== Authentication ====================
672
- function handleLoginClick() {
673
- if (currentUser) {
674
- // Toggle dropdown
675
- const menu = document.getElementById('userDropdownMenu');
676
- menu.classList.toggle('active');
677
- } else {
678
- // Show login modal
679
- document.getElementById('loginModal').classList.add('active');
680
- }
681
- }
682
-
683
- function closeLoginModal() {
684
- document.getElementById('loginModal').classList.remove('active');
685
- }
686
-
687
- async function loginWithHuggingFace() {
688
- // HuggingFace OAuth flow
689
- // In a real HF Space, this would use the built-in OAuth
690
- // For now, we'll show a message
691
- showToast('HuggingFace OAuth requires deployment on HF Spaces', 'error');
692
-
693
- // If running on HF Spaces, uncomment this:
694
- // window.location.href = '/login/huggingface';
695
- }
696
-
697
- async function demoLogin(userId) {
698
- showToast(t('loggingIn'), 'success');
699
-
700
- try {
701
- const data = await fetchAPI(`/auth/demo-login?user_id=${userId}`, {
702
- method: 'POST'
703
- });
704
-
705
- if (data?.success) {
706
- saveSession(data.session_token, data.user);
707
- updateUserCard();
708
- closeLoginModal();
709
- showToast(`${t('welcome')}, ${data.user.username}!`, 'success');
710
- loadNotificationCount();
711
- }
712
- } catch (error) {
713
- showToast('Login failed: ' + error.message, 'error');
714
- }
715
- }
716
-
717
- async function handleLogout() {
718
- try {
719
- await fetchAPI('/auth/logout', { method: 'POST' });
720
- } catch (e) {
721
- // Ignore errors on logout
722
- }
723
 
724
- clearSession();
725
- updateUserCard();
726
- document.getElementById('userDropdownMenu').classList.remove('active');
727
- showToast(t('loggedOut'), 'success');
728
- }
729
-
730
- async function checkAuthStatus() {
731
- if (!sessionToken) return false;
732
 
733
- try {
734
- const data = await fetchAPI('/auth/me');
735
- if (data?.authenticated && data.user) {
736
- currentUser = data.user;
737
- localStorage.setItem(USER_KEY, JSON.stringify(data.user));
738
- return true;
739
- }
740
- } catch (e) {
741
- // Session invalid
742
  }
 
743
 
744
- clearSession();
745
- return false;
746
- }
747
-
748
- // ==================== Categories ====================
749
- async function loadCategories() {
750
- try {
751
- const data = await fetchAPI('/categories');
752
- categories = data.categories;
753
- const categoryList = document.getElementById('categoryList');
754
-
755
- let html = `<div class="category-item active" data-category="" onclick="selectCategory('')">
756
- <span class="category-icon">🌐</span><span class="category-name">${t('all')}</span>
757
- </div>`;
758
-
759
- for (const [key, cat] of Object.entries(data.categories)) {
760
- const name = currentLang === 'kr' ? cat.name_kr : cat.name;
761
- html += `<div class="category-item" data-category="${key}" onclick="selectCategory('${key}')">
762
- <span class="category-icon">${cat.icon}</span><span class="category-name">${name}</span>
763
- </div>`;
764
- }
765
- categoryList.innerHTML = html;
766
-
767
- // Populate create market category select
768
- const categorySelect = document.getElementById('marketCategory');
769
- categorySelect.innerHTML = Object.entries(data.categories).map(([key, cat]) =>
770
  `<option value="${key}">${cat.icon} ${currentLang === 'kr' ? cat.name_kr : cat.name}</option>`
771
  ).join('');
772
-
773
- } catch (error) { console.error('Failed to load categories:', error); }
774
  }
775
 
776
  function selectCategory(category) {
@@ -782,14 +768,30 @@
782
  }
783
 
784
  // ==================== Markets ====================
785
- async function loadMarkets() {
786
- try {
787
- const params = new URLSearchParams({ sort: currentSort, status: 'all', limit: '20' });
788
- if (currentCategory) params.append('category', currentCategory);
789
-
790
- const data = await fetchAPI(`/markets?${params}`);
791
- renderMarkets(data.markets);
792
- } catch (error) { console.error('Failed to load markets:', error); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
793
  }
794
 
795
  function renderMarkets(markets) {
@@ -805,18 +807,14 @@
805
  grid.innerHTML = markets.map(market => {
806
  const statusClass = market.status === 'resolved'
807
  ? (market.resolved_outcome === 'yes' ? 'resolved-yes' : 'resolved-no')
808
- : (market.time_status === 'urgent' ? 'urgent' : 'active');
809
-
810
- const statusText = market.status === 'resolved'
811
- ? `✅ ${market.resolved_outcome.toUpperCase()}`
812
- : market.time_remaining;
813
 
814
  const catName = currentLang === 'kr' ? market.category_info?.name_kr : market.category_info?.name;
815
 
816
  return `<div class="market-card glass ${market.status === 'resolved' ? 'resolved' : ''}" onclick="openMarketModal(${market.id})">
817
  <div class="market-header">
818
  <div class="market-category"><span>${market.category_info?.icon || '📊'}</span><span>${catName || market.category}</span></div>
819
- <span class="market-status ${statusClass}">${statusText}</span>
820
  </div>
821
  <h3 class="market-title">${market.title}</h3>
822
  <div class="prob-bar-container">
@@ -834,54 +832,37 @@
834
  }
835
 
836
  // ==================== Rankings ====================
837
- async function loadRankings() {
838
- try {
839
- const data = await fetchAPI('/rankings');
840
- const container = document.getElementById('genRanking');
 
 
841
 
842
- container.innerHTML = data.gen_ranking.map((user, index) => {
843
- const posClass = index === 0 ? 'gold' : index === 1 ? 'silver' : index === 2 ? 'bronze' : '';
844
- const posText = index < 3 ? ['🥇', '🥈', '🥉'][index] : (index + 1);
845
-
846
- return `<div class="ranking-item">
847
- <div class="ranking-position ${posClass}">${posText}</div>
848
- <img class="ranking-avatar" src="${user.avatar_url || 'https://api.dicebear.com/7.x/avataaars/svg?seed=' + user.username}" onerror="this.src='https://api.dicebear.com/7.x/avataaars/svg?seed=default'">
849
- <div class="ranking-info">
850
- <div class="ranking-name">@${user.username}</div>
851
- <div class="ranking-value">${user.gen_balance.toLocaleString()} GEN</div>
852
- </div>
853
- </div>`;
854
- }).join('');
855
- } catch (error) { console.error('Failed to load rankings:', error); }
856
- }
857
-
858
- // ==================== Prices ====================
859
- async function loadPrices() {
860
- try {
861
- const data = await fetchAPI('/prices');
862
- if (data.prices.BTC?.price) {
863
- document.getElementById('btcPrice').textContent = `$${data.prices.BTC.price.toLocaleString(undefined, {maximumFractionDigits: 0})}`;
864
- const btcChange = document.getElementById('btcChange');
865
- btcChange.textContent = `${data.prices.BTC.change_24h >= 0 ? '+' : ''}${data.prices.BTC.change_24h}%`;
866
- btcChange.className = `ticker-change ${data.prices.BTC.change_24h >= 0 ? 'up' : 'down'}`;
867
- }
868
- if (data.prices.ETH?.price) {
869
- document.getElementById('ethPrice').textContent = `$${data.prices.ETH.price.toLocaleString(undefined, {maximumFractionDigits: 0})}`;
870
- const ethChange = document.getElementById('ethChange');
871
- ethChange.textContent = `${data.prices.ETH.change_24h >= 0 ? '+' : ''}${data.prices.ETH.change_24h}%`;
872
- ethChange.className = `ticker-change ${data.prices.ETH.change_24h >= 0 ? 'up' : 'down'}`;
873
- }
874
- } catch (error) { console.error('Failed to load prices:', error); }
875
  }
876
 
877
- // ==================== Market Modal ====================
878
- async function openMarketModal(marketId) {
879
- try {
880
- const data = await fetchAPI(`/markets/${marketId}`);
881
- selectedMarket = data.market;
882
- renderMarketModal(data.market);
883
- document.getElementById('marketModal').classList.add('active');
884
- } catch (error) { showToast(t('marketNotFound'), 'error'); }
 
 
 
 
 
885
  }
886
 
887
  function renderMarketModal(market) {
@@ -903,6 +884,25 @@
903
  </div>`;
904
  }
905
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
906
  content.innerHTML = `
907
  <button class="modal-close" onclick="closeModal()"><i class="fas fa-times"></i></button>
908
  <div class="market-category" style="display: inline-flex; margin-bottom: 1rem;">
@@ -920,12 +920,6 @@
920
 
921
  ${aiSection}
922
 
923
- <div style="text-align: center; margin: 1rem 0;">
924
- <button class="btn btn-ai" onclick="getAIPrediction(${market.id})" id="aiPredictBtn">
925
- <i class="fas fa-robot"></i> ${t('getAIPrediction')}
926
- </button>
927
- </div>
928
-
929
  <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin: 1.5rem 0;">
930
  <div style="text-align: center; padding: 1rem; background: rgba(255,255,255,0.05); border-radius: 12px;">
931
  <div style="font-family: 'JetBrains Mono'; font-size: 1.25rem; color: var(--accent-cyan);">${market.total_volume.toLocaleString()}</div>
@@ -937,7 +931,7 @@
937
  </div>
938
  <div style="text-align: center; padding: 1rem; background: rgba(255,255,255,0.05); border-radius: 12px;">
939
  <div style="font-size: 1rem; color: ${market.status === 'resolved' ? 'var(--success)' : 'var(--text-primary)'};">
940
- ${market.status === 'resolved' ? '✅ ' + market.resolved_outcome.toUpperCase() : market.time_remaining || 'Active'}
941
  </div>
942
  <div style="font-size: 0.8rem; color: var(--text-muted);">${t('status')}</div>
943
  </div>
@@ -985,18 +979,7 @@
985
  </div>
986
  ` : ''}
987
  <div class="comment-list">
988
- ${market.comments?.map(comment => `
989
- <div class="comment-item">
990
- <img class="comment-avatar" src="${comment.avatar_url || 'https://api.dicebear.com/7.x/avataaars/svg?seed=' + comment.username}" onerror="this.src='https://api.dicebear.com/7.x/avataaars/svg?seed=default'">
991
- <div class="comment-content">
992
- <div class="comment-header">
993
- <span class="comment-author">@${comment.username}</span>
994
- <span class="comment-time">${formatTime(comment.created_at)}</span>
995
- </div>
996
- <p class="comment-text">${escapeHtml(comment.content)}</p>
997
- </div>
998
- </div>
999
- `).join('') || `<p style="color: var(--text-muted); text-align: center; padding: 1rem;">${t('noComments')}</p>`}
1000
  </div>
1001
  </div>
1002
  `;
@@ -1009,64 +992,38 @@
1009
 
1010
  function setBetAmount(amount) { document.getElementById('betAmount').value = amount; }
1011
 
1012
- async function placeBet(choice) {
1013
  if (!currentUser) { showToast(t('loginRequired'), 'error'); return; }
1014
  const amount = parseInt(document.getElementById('betAmount').value);
1015
  if (isNaN(amount) || amount < 10) { showToast('Min bet: 10 GEN', 'error'); return; }
1016
  if (amount > currentUser.gen_balance) { showToast(t('insufficientBalance'), 'error'); return; }
1017
 
1018
- try {
1019
- const data = await fetchAPI('/bets', {
1020
- method: 'POST',
1021
- body: JSON.stringify({ market_id: selectedMarket.id, choice: choice, amount: amount })
1022
- });
1023
-
1024
- if (data) {
1025
- currentUser = data.user;
1026
- localStorage.setItem(USER_KEY, JSON.stringify(currentUser));
1027
- updateUserCard();
1028
- showToast(data.message, 'success');
1029
- closeModal();
1030
- loadMarkets();
1031
- }
1032
- } catch (error) { showToast(error.message, 'error'); }
1033
- }
1034
-
1035
- async function getAIPrediction(marketId) {
1036
- const btn = document.getElementById('aiPredictBtn');
1037
- btn.innerHTML = `<span class="loading-spinner"></span> ${t('analyzing')}`;
1038
- btn.disabled = true;
1039
 
1040
- try {
1041
- const data = await fetchAPI(`/markets/${marketId}/ai-prediction`);
1042
-
1043
- // Refresh modal with new prediction
1044
- const marketData = await fetchAPI(`/markets/${marketId}`);
1045
- selectedMarket = marketData.market;
1046
- renderMarketModal(marketData.market);
1047
-
1048
- showToast('AI prediction updated!', 'success');
1049
- } catch (error) {
1050
- showToast('AI prediction failed: ' + error.message, 'error');
1051
- btn.innerHTML = `<i class="fas fa-robot"></i> ${t('getAIPrediction')}`;
1052
- btn.disabled = false;
1053
- }
1054
  }
1055
 
1056
- async function submitComment() {
1057
  if (!currentUser || !selectedMarket) return;
1058
  const input = document.getElementById('commentInput');
1059
  const content = input.value.trim();
1060
  if (!content) return;
1061
 
1062
- try {
1063
- await fetchAPI('/comments', {
1064
- method: 'POST',
1065
- body: JSON.stringify({ market_id: selectedMarket.id, content: content })
1066
- });
1067
- showToast(t('commentPosted'), 'success');
1068
- openMarketModal(selectedMarket.id);
1069
- } catch (error) { showToast(error.message, 'error'); }
 
 
1070
  }
1071
 
1072
  // ==================== Create Market ====================
@@ -1085,7 +1042,7 @@
1085
  document.getElementById('createMarketModal').classList.remove('active');
1086
  }
1087
 
1088
- async function submitCreateMarket() {
1089
  if (!currentUser) { showToast(t('loginRequired'), 'error'); return; }
1090
 
1091
  const title = document.getElementById('marketTitle').value.trim();
@@ -1098,88 +1055,69 @@
1098
  if (!endDate) { showToast('End date is required', 'error'); return; }
1099
  if (currentUser.gen_balance < 100) { showToast('Need 100 GEN to create market', 'error'); return; }
1100
 
1101
- try {
1102
- const data = await fetchAPI('/markets', {
1103
- method: 'POST',
1104
- body: JSON.stringify({
1105
- title: title,
1106
- description: description,
1107
- category: category,
1108
- end_date: new Date(endDate).toISOString(),
1109
- resolution_source: resolution
1110
- })
1111
- });
1112
-
1113
- if (data) {
1114
- showToast(data.message, 'success');
1115
- closeCreateMarketModal();
1116
-
1117
- // Refresh user balance
1118
- const userData = await fetchAPI(`/users/${currentUser.id}`);
1119
- if (userData) {
1120
- currentUser = userData.user;
1121
- localStorage.setItem(USER_KEY, JSON.stringify(currentUser));
1122
- updateUserCard();
1123
- }
1124
-
1125
- loadMarkets();
1126
- }
1127
- } catch (error) { showToast(error.message, 'error'); }
1128
  }
1129
 
1130
- // ==================== Notifications ====================
1131
- async function loadNotificationCount() {
1132
- if (!currentUser) return;
1133
- try {
1134
- const data = await fetchAPI(`/users/${currentUser.id}/notifications?unread_only=true`);
1135
- if (data) {
1136
- const count = data.unread_count;
1137
- const badge = document.getElementById('notifCount');
1138
- if (count > 0) {
1139
- badge.textContent = count > 99 ? '99+' : count;
1140
- badge.style.display = 'flex';
1141
- } else {
1142
- badge.style.display = 'none';
1143
- }
1144
- }
1145
- } catch (error) { console.error('Failed to load notifications:', error); }
1146
  }
1147
 
1148
- async function showNotifications() {
1149
- if (!currentUser) { showToast(t('loginRequired'), 'error'); return; }
1150
-
1151
- try {
1152
- const data = await fetchAPI(`/users/${currentUser.id}/notifications`);
1153
- if (!data) return;
1154
-
1155
- const list = document.getElementById('notificationsList');
1156
-
1157
- if (!data.notifications || data.notifications.length === 0) {
1158
- list.innerHTML = `<p style="color: var(--text-muted); text-align: center; padding: 2rem;">${t('noNotifications')}</p>`;
1159
- } else {
1160
- list.innerHTML = data.notifications.map(notif => {
1161
- const bgColor = notif.is_read ? 'transparent' : 'rgba(0, 245, 212, 0.1)';
1162
- return `<div style="padding: 1rem; background: ${bgColor}; border-radius: 12px; margin-bottom: 0.75rem; border-left: 3px solid ${notif.is_read ? 'var(--glass-border)' : 'var(--accent-cyan)'};">
1163
- <div style="font-weight: 600; margin-bottom: 0.25rem;">${notif.title}</div>
1164
- <div style="color: var(--text-secondary); font-size: 0.9rem;">${notif.message || ''}</div>
1165
- <div style="color: var(--text-muted); font-size: 0.75rem; margin-top: 0.5rem;">${formatTime(notif.created_at)}</div>
1166
- </div>`;
1167
- }).join('');
1168
- }
1169
-
1170
- document.getElementById('notificationsModal').classList.add('active');
1171
-
1172
- // Mark as read
1173
- await fetchAPI(`/users/${currentUser.id}/notifications/read`, { method: 'POST' });
1174
- loadNotificationCount();
1175
- } catch (error) { showToast('Failed to load notifications', 'error'); }
1176
  }
1177
 
1178
- function closeNotificationsModal() {
1179
- document.getElementById('notificationsModal').classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1180
  }
1181
 
1182
- // ==================== User ====================
1183
  function updateUserCard() {
1184
  const card = document.getElementById('userCard');
1185
  const loginBtn = document.getElementById('loginBtn');
@@ -1192,26 +1130,13 @@
1192
  </div>`;
1193
 
1194
  loginBtn.innerHTML = `<i class="fas fa-user"></i> <span data-i18n="login">${t('login')}</span>`;
1195
- loginBtn.onclick = handleLoginClick;
1196
  return;
1197
  }
1198
 
1199
  const winRate = currentUser.wins + currentUser.losses > 0 ? Math.round(currentUser.wins * 100 / (currentUser.wins + currentUser.losses)) : 0;
1200
 
1201
- // Badges section
1202
- let badgesHtml = '';
1203
- if (currentUser.badges && currentUser.badges.length > 0) {
1204
- badgesHtml = `
1205
- <div class="badges-section">
1206
- <div class="badges-title">${t('badges')}</div>
1207
- <div class="badges-list">
1208
- ${currentUser.badges.map(b => `<span class="badge-item" title="${b.description}">${b.icon}</span>`).join('')}
1209
- </div>
1210
- </div>`;
1211
- }
1212
-
1213
  card.innerHTML = `
1214
- <img class="user-avatar" src="${currentUser.avatar_url || 'https://api.dicebear.com/7.x/avataaars/svg?seed=' + currentUser.username}" onerror="this.src='https://api.dicebear.com/7.x/avataaars/svg?seed=default'">
1215
  <div class="user-name">@${currentUser.username}</div>
1216
  <div class="user-balance">
1217
  <span class="balance-icon">💎</span>
@@ -1222,42 +1147,39 @@
1222
  <div class="user-stat"><div class="user-stat-value" style="color: var(--success);">${currentUser.wins}</div><div class="user-stat-label">${t('wins')}</div></div>
1223
  <div class="user-stat"><div class="user-stat-value">${winRate}%</div><div class="user-stat-label">${t('winRate')}</div></div>
1224
  </div>
1225
- ${badgesHtml}
1226
  `;
1227
 
1228
  loginBtn.innerHTML = `
1229
- <img src="${currentUser.avatar_url || 'https://api.dicebear.com/7.x/avataaars/svg?seed=' + currentUser.username}" style="width: 24px; height: 24px; border-radius: 6px;" onerror="this.src='https://api.dicebear.com/7.x/avataaars/svg?seed=default'">
1230
  ${currentUser.username}
1231
  `;
1232
-
1233
- loadNotificationCount();
 
 
 
 
 
1234
  }
1235
 
1236
  function showProfile() {
1237
  document.getElementById('userDropdownMenu').classList.remove('active');
1238
- // Could open a profile modal here
1239
  showToast('Profile feature coming soon!', 'success');
1240
  }
1241
 
1242
  function showMyBets() {
1243
  document.getElementById('userDropdownMenu').classList.remove('active');
1244
- // Could open a bets modal here
1245
  showToast('My Bets feature coming soon!', 'success');
1246
  }
1247
 
1248
- async function claimDailyBonus() {
1249
  if (!currentUser) { showToast(t('loginRequired'), 'error'); return; }
1250
- try {
1251
- const data = await fetchAPI('/daily-bonus', { method: 'POST' });
1252
- if (data) {
1253
- if (data.success) {
1254
- currentUser.gen_balance = data.balance;
1255
- localStorage.setItem(USER_KEY, JSON.stringify(currentUser));
1256
- updateUserCard();
1257
- }
1258
- showToast(data.message, data.success ? 'success' : 'error');
1259
- }
1260
- } catch (error) { showToast(error.message, 'error'); }
1261
  }
1262
 
1263
  // ==================== Utility ====================
@@ -1270,20 +1192,9 @@
1270
  setTimeout(() => { toast.style.animation = 'slideIn 0.3s ease reverse'; setTimeout(() => toast.remove(), 300); }, 3000);
1271
  }
1272
 
1273
- function formatTime(dateStr) {
1274
- if (!dateStr) return '';
1275
- const date = new Date(dateStr);
1276
- const now = new Date();
1277
- const diff = now - date;
1278
- if (diff < 60000) return currentLang === 'kr' ? '방금 전' : 'just now';
1279
- if (diff < 3600000) return currentLang === 'kr' ? `${Math.floor(diff / 60000)}분 전` : `${Math.floor(diff / 60000)}m ago`;
1280
- if (diff < 86400000) return currentLang === 'kr' ? `${Math.floor(diff / 3600000)}시간 전` : `${Math.floor(diff / 3600000)}h ago`;
1281
- return currentLang === 'kr' ? `${Math.floor(diff / 86400000)}일 전` : `${Math.floor(diff / 86400000)}d ago`;
1282
- }
1283
-
1284
  function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; }
1285
 
1286
- function refreshMarkets() { loadMarkets(); loadRankings(); loadPrices(); showToast('Refreshed!', 'success'); }
1287
 
1288
  // ==================== Events ====================
1289
  document.querySelectorAll('.sort-tab').forEach(tab => {
@@ -1305,7 +1216,6 @@
1305
  }
1306
  });
1307
 
1308
- // Close dropdown when clicking outside
1309
  document.addEventListener('click', (e) => {
1310
  const dropdown = document.getElementById('userDropdown');
1311
  const menu = document.getElementById('userDropdownMenu');
@@ -1320,16 +1230,12 @@
1320
  document.getElementById('loginModal').addEventListener('click', (e) => { if (e.target.id === 'loginModal') closeLoginModal(); });
1321
 
1322
  // ==================== Init ====================
1323
- async function init() {
1324
- // Load session from localStorage
1325
- if (loadSession()) {
1326
- // Verify session is still valid
1327
- await checkAuthStatus();
1328
- }
1329
-
1330
  updateUserCard();
1331
- await Promise.all([loadCategories(), loadMarkets(), loadRankings(), loadPrices()]);
1332
- setInterval(loadPrices, 30000);
1333
  }
1334
 
1335
  init();
 
345
  <div class="price-ticker glass" id="priceTicker">
346
  <div class="ticker-item">
347
  <span class="ticker-symbol">BTC</span>
348
+ <span class="ticker-price" id="btcPrice">$98,500</span>
349
+ <span class="ticker-change up" id="btcChange">+2.3%</span>
350
  </div>
351
  <div class="ticker-item">
352
  <span class="ticker-symbol">ETH</span>
353
+ <span class="ticker-price" id="ethPrice">$3,450</span>
354
+ <span class="ticker-change up" id="ethChange">+1.8%</span>
355
  </div>
356
  </div>
357
 
 
525
  <div class="toast-container" id="toastContainer"></div>
526
 
527
  <script>
528
+ // ==================== Demo Data ====================
529
+ const DEMO_MARKETS = [
530
+ {
531
+ id: 1,
532
+ title: "Bitcoin이 2025년 3월까지 $150K를 돌파할 것인가?",
533
+ description: "BTC가 Q1 2025에 $150,000를 돌파할지 예측하세요.",
534
+ category: "crypto",
535
+ category_info: { icon: "💰", name: "Crypto", name_kr: "암호화폐" },
536
+ yes_pool: 25000, no_pool: 18000,
537
+ yes_pct: 58, no_pct: 42,
538
+ yes_odds: 1.72, no_odds: 2.39,
539
+ total_volume: 43000, participant_count: 86,
540
+ status: "active", time_remaining: "85일 남음",
541
+ resolution_source: "CoinGecko BTC/USD 가격",
542
+ ai_prediction_yes: 0.62,
543
+ ai_prediction_reason: "기관 투자 증가와 ETF 승인으로 상승 모멘텀",
544
+ comments: [
545
+ { username: "CryptoWhale", content: "ETF 승인 이후 상승 추세가 강해질 것 같습니다.", created_at: "2025-01-04 10:30" },
546
+ { username: "TraderKim", content: "조금 더 지켜봐야 할 것 같아요.", created_at: "2025-01-03 15:20" }
547
+ ]
548
+ },
549
+ {
550
+ id: 2,
551
+ title: "Tesla 주가가 2025년 6월까지 $500를 넘길까?",
552
+ description: "테슬라 주식이 $500를 초과할지 예측하세요.",
553
+ category: "stocks",
554
+ category_info: { icon: "📈", name: "Stocks", name_kr: "주식" },
555
+ yes_pool: 12000, no_pool: 15000,
556
+ yes_pct: 44, no_pct: 56,
557
+ yes_odds: 2.25, no_odds: 1.80,
558
+ total_volume: 27000, participant_count: 54,
559
+ status: "active", time_remaining: "176일 남음",
560
+ resolution_source: "NASDAQ TSLA 종가",
561
+ ai_prediction_yes: 0.45,
562
+ ai_prediction_reason: "EV 경쟁 심화로 불확실성 존재",
563
+ comments: []
564
+ },
565
+ {
566
+ id: 3,
567
+ title: "USD/KRW 환율이 2025년 상반기에 1,500원을 넘길까?",
568
+ description: "달러-원 환율 예측",
569
+ category: "forex",
570
+ category_info: { icon: "💱", name: "Forex", name_kr: "외환" },
571
+ yes_pool: 8500, no_pool: 6500,
572
+ yes_pct: 57, no_pct: 43,
573
+ yes_odds: 1.76, no_odds: 2.31,
574
+ total_volume: 15000, participant_count: 30,
575
+ status: "active", time_remaining: "176일 남음",
576
+ resolution_source: "한국은행 기준환율",
577
+ ai_prediction_yes: 0.55,
578
+ ai_prediction_reason: "미국 금리 인하 지연으로 달러 강세 지속 가능",
579
+ comments: []
580
+ },
581
+ {
582
+ id: 4,
583
+ title: "AI 기업이 2025년 말까지 시가총액 1위가 될까?",
584
+ description: "AI 회사가 세계 시가총액 1위가 될지 예측",
585
+ category: "tech",
586
+ category_info: { icon: "⚡", name: "Tech & AI", name_kr: "테크/AI" },
587
+ yes_pool: 18000, no_pool: 12000,
588
+ yes_pct: 60, no_pct: 40,
589
+ yes_odds: 1.67, no_odds: 2.50,
590
+ total_volume: 30000, participant_count: 60,
591
+ status: "active", time_remaining: "360일 남음",
592
+ resolution_source: "Bloomberg 시가총액 순위",
593
+ ai_prediction_yes: 0.68,
594
+ ai_prediction_reason: "NVIDIA의 AI 칩 독점으로 급성장 중",
595
+ comments: []
596
+ },
597
+ {
598
+ id: 5,
599
+ title: "2025년 Fed가 3회 이상 금리를 인하할까?",
600
+ description: "연준의 금리 정책 예측",
601
+ category: "economy",
602
+ category_info: { icon: "🏦", name: "Economy", name_kr: "경제" },
603
+ yes_pool: 22000, no_pool: 15000,
604
+ yes_pct: 59, no_pct: 41,
605
+ yes_odds: 1.68, no_odds: 2.47,
606
+ total_volume: 37000, participant_count: 74,
607
+ status: "active", time_remaining: "360일 남음",
608
+ resolution_source: "Federal Reserve 공식 발표",
609
+ ai_prediction_yes: 0.52,
610
+ ai_prediction_reason: "인플레이션 둔화 속도에 따라 결정될 전망",
611
+ comments: []
612
+ },
613
+ {
614
+ id: 6,
615
+ title: "Bitcoin이 2024년 12월에 $100K를 돌파했다",
616
+ description: "BTC $100K 달성 여부",
617
+ category: "crypto",
618
+ category_info: { icon: "💰", name: "Crypto", name_kr: "암호화폐" },
619
+ yes_pool: 45000, no_pool: 12000,
620
+ yes_pct: 79, no_pct: 21,
621
+ yes_odds: 1.27, no_odds: 4.75,
622
+ total_volume: 57000, participant_count: 114,
623
+ status: "resolved", resolved_outcome: "yes",
624
+ time_remaining: "✅ YES",
625
+ resolution_source: "Official Source",
626
+ ai_prediction_yes: null,
627
+ comments: []
628
+ },
629
+ {
630
+ id: 7,
631
+ title: "Trump가 2024년 대선에서 승리했다",
632
+ description: "2024 미국 대선 결과",
633
+ category: "politics",
634
+ category_info: { icon: "🏛️", name: "Politics", name_kr: "정치" },
635
+ yes_pool: 38000, no_pool: 25000,
636
+ yes_pct: 60, no_pct: 40,
637
+ yes_odds: 1.66, no_odds: 2.52,
638
+ total_volume: 63000, participant_count: 126,
639
+ status: "resolved", resolved_outcome: "yes",
640
+ time_remaining: "✅ YES",
641
+ resolution_source: "Official Source",
642
+ ai_prediction_yes: null,
643
+ comments: []
644
+ }
645
+ ];
646
+
647
+ const DEMO_USERS = [
648
+ { id: 1, username: 'CryptoWhale', gen_balance: 12500, total_bets: 125, wins: 62, losses: 41 },
649
+ { id: 2, username: 'TraderKim', gen_balance: 8750, total_bets: 87, wins: 43, losses: 29 },
650
+ { id: 3, username: 'AI_Master', gen_balance: 6200, total_bets: 62, wins: 31, losses: 20 },
651
+ { id: 4, username: 'ForexPro', gen_balance: 5100, total_bets: 51, wins: 25, losses: 17 },
652
+ { id: 5, username: 'StockGuru', gen_balance: 4800, total_bets: 48, wins: 24, losses: 16 },
653
+ ];
654
+
655
+ const CATEGORIES = {
656
+ all: { icon: "🌐", name: "All", name_kr: "전체" },
657
+ crypto: { icon: "💰", name: "Crypto", name_kr: "암호화폐" },
658
+ stocks: { icon: "📈", name: "Stocks", name_kr: "주식" },
659
+ forex: { icon: "💱", name: "Forex", name_kr: "외환" },
660
+ futures: { icon: "📊", name: "Futures", name_kr: "선물" },
661
+ economy: { icon: "🏦", name: "Economy", name_kr: "경제" },
662
+ tech: { icon: "⚡", name: "Tech & AI", name_kr: "테크/AI" },
663
+ geopolitics: { icon: "🌍", name: "Geopolitics", name_kr: "지정학" },
664
+ politics: { icon: "🏛️", name: "Politics", name_kr: "정치" },
665
+ sports: { icon: "🏆", name: "Sports", name_kr: "스포츠" },
666
+ korea: { icon: "🇰🇷", name: "Korea", name_kr: "한국" }
667
+ };
668
 
669
  // ==================== i18n ====================
670
  const i18n = {
 
712
  }
713
  };
714
 
715
+ let currentLang = 'kr';
716
  let currentUser = null;
 
717
  let currentSort = 'hot';
718
  let currentCategory = '';
719
  let selectedMarket = null;
 
720
 
721
  function t(key) { return i18n[currentLang][key] || i18n.en[key] || key; }
722
 
 
733
  });
734
  }
735
 
736
+ // ==================== Categories ====================
737
+ function loadCategories() {
738
+ const categoryList = document.getElementById('categoryList');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739
 
740
+ let html = `<div class="category-item active" data-category="" onclick="selectCategory('')">
741
+ <span class="category-icon">🌐</span><span class="category-name">${t('all')}</span>
742
+ </div>`;
 
 
 
 
 
743
 
744
+ for (const [key, cat] of Object.entries(CATEGORIES)) {
745
+ if (key === 'all') continue;
746
+ const name = currentLang === 'kr' ? cat.name_kr : cat.name;
747
+ html += `<div class="category-item" data-category="${key}" onclick="selectCategory('${key}')">
748
+ <span class="category-icon">${cat.icon}</span><span class="category-name">${name}</span>
749
+ </div>`;
 
 
 
750
  }
751
+ categoryList.innerHTML = html;
752
 
753
+ // Populate create market category select
754
+ const categorySelect = document.getElementById('marketCategory');
755
+ categorySelect.innerHTML = Object.entries(CATEGORIES)
756
+ .filter(([k]) => k !== 'all')
757
+ .map(([key, cat]) =>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
758
  `<option value="${key}">${cat.icon} ${currentLang === 'kr' ? cat.name_kr : cat.name}</option>`
759
  ).join('');
 
 
760
  }
761
 
762
  function selectCategory(category) {
 
768
  }
769
 
770
  // ==================== Markets ====================
771
+ function loadMarkets() {
772
+ let markets = [...DEMO_MARKETS];
773
+
774
+ // Filter by category
775
+ if (currentCategory) {
776
+ markets = markets.filter(m => m.category === currentCategory);
777
+ }
778
+
779
+ // Sort
780
+ switch (currentSort) {
781
+ case 'volume':
782
+ markets.sort((a, b) => b.total_volume - a.total_volume);
783
+ break;
784
+ case 'newest':
785
+ markets.sort((a, b) => b.id - a.id);
786
+ break;
787
+ case 'ending':
788
+ markets.sort((a, b) => (a.status === 'active' ? 0 : 1) - (b.status === 'active' ? 0 : 1));
789
+ break;
790
+ default: // hot
791
+ markets.sort((a, b) => b.participant_count - a.participant_count);
792
+ }
793
+
794
+ renderMarkets(markets);
795
  }
796
 
797
  function renderMarkets(markets) {
 
807
  grid.innerHTML = markets.map(market => {
808
  const statusClass = market.status === 'resolved'
809
  ? (market.resolved_outcome === 'yes' ? 'resolved-yes' : 'resolved-no')
810
+ : 'active';
 
 
 
 
811
 
812
  const catName = currentLang === 'kr' ? market.category_info?.name_kr : market.category_info?.name;
813
 
814
  return `<div class="market-card glass ${market.status === 'resolved' ? 'resolved' : ''}" onclick="openMarketModal(${market.id})">
815
  <div class="market-header">
816
  <div class="market-category"><span>${market.category_info?.icon || '📊'}</span><span>${catName || market.category}</span></div>
817
+ <span class="market-status ${statusClass}">${market.time_remaining}</span>
818
  </div>
819
  <h3 class="market-title">${market.title}</h3>
820
  <div class="prob-bar-container">
 
832
  }
833
 
834
  // ==================== Rankings ====================
835
+ function loadRankings() {
836
+ const container = document.getElementById('genRanking');
837
+
838
+ container.innerHTML = DEMO_USERS.map((user, index) => {
839
+ const posClass = index === 0 ? 'gold' : index === 1 ? 'silver' : index === 2 ? 'bronze' : '';
840
+ const posText = index < 3 ? ['🥇', '🥈', '🥉'][index] : (index + 1);
841
 
842
+ return `<div class="ranking-item">
843
+ <div class="ranking-position ${posClass}">${posText}</div>
844
+ <img class="ranking-avatar" src="https://api.dicebear.com/7.x/avataaars/svg?seed=${user.username}" onerror="this.src='https://api.dicebear.com/7.x/avataaars/svg?seed=default'">
845
+ <div class="ranking-info">
846
+ <div class="ranking-name">@${user.username}</div>
847
+ <div class="ranking-value">${user.gen_balance.toLocaleString()} GEN</div>
848
+ </div>
849
+ </div>`;
850
+ }).join('');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
851
  }
852
 
853
+ // ==================== Market Modal - 핵심 수정 ====================
854
+ function openMarketModal(marketId) {
855
+ // 데모 데이터에서 마켓 찾기
856
+ const market = DEMO_MARKETS.find(m => m.id === marketId);
857
+
858
+ if (!market) {
859
+ showToast(t('marketNotFound'), 'error');
860
+ return;
861
+ }
862
+
863
+ selectedMarket = market;
864
+ renderMarketModal(market);
865
+ document.getElementById('marketModal').classList.add('active');
866
  }
867
 
868
  function renderMarketModal(market) {
 
884
  </div>`;
885
  }
886
 
887
+ // Comments HTML
888
+ let commentsHtml = '';
889
+ if (market.comments && market.comments.length > 0) {
890
+ commentsHtml = market.comments.map(comment => `
891
+ <div class="comment-item">
892
+ <img class="comment-avatar" src="https://api.dicebear.com/7.x/avataaars/svg?seed=${comment.username}" onerror="this.src='https://api.dicebear.com/7.x/avataaars/svg?seed=default'">
893
+ <div class="comment-content">
894
+ <div class="comment-header">
895
+ <span class="comment-author">@${comment.username}</span>
896
+ <span class="comment-time">${comment.created_at}</span>
897
+ </div>
898
+ <p class="comment-text">${escapeHtml(comment.content)}</p>
899
+ </div>
900
+ </div>
901
+ `).join('');
902
+ } else {
903
+ commentsHtml = `<p style="color: var(--text-muted); text-align: center; padding: 1rem;">${t('noComments')}</p>`;
904
+ }
905
+
906
  content.innerHTML = `
907
  <button class="modal-close" onclick="closeModal()"><i class="fas fa-times"></i></button>
908
  <div class="market-category" style="display: inline-flex; margin-bottom: 1rem;">
 
920
 
921
  ${aiSection}
922
 
 
 
 
 
 
 
923
  <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin: 1.5rem 0;">
924
  <div style="text-align: center; padding: 1rem; background: rgba(255,255,255,0.05); border-radius: 12px;">
925
  <div style="font-family: 'JetBrains Mono'; font-size: 1.25rem; color: var(--accent-cyan);">${market.total_volume.toLocaleString()}</div>
 
931
  </div>
932
  <div style="text-align: center; padding: 1rem; background: rgba(255,255,255,0.05); border-radius: 12px;">
933
  <div style="font-size: 1rem; color: ${market.status === 'resolved' ? 'var(--success)' : 'var(--text-primary)'};">
934
+ ${market.time_remaining || 'Active'}
935
  </div>
936
  <div style="font-size: 0.8rem; color: var(--text-muted);">${t('status')}</div>
937
  </div>
 
979
  </div>
980
  ` : ''}
981
  <div class="comment-list">
982
+ ${commentsHtml}
 
 
 
 
 
 
 
 
 
 
 
983
  </div>
984
  </div>
985
  `;
 
992
 
993
  function setBetAmount(amount) { document.getElementById('betAmount').value = amount; }
994
 
995
+ function placeBet(choice) {
996
  if (!currentUser) { showToast(t('loginRequired'), 'error'); return; }
997
  const amount = parseInt(document.getElementById('betAmount').value);
998
  if (isNaN(amount) || amount < 10) { showToast('Min bet: 10 GEN', 'error'); return; }
999
  if (amount > currentUser.gen_balance) { showToast(t('insufficientBalance'), 'error'); return; }
1000
 
1001
+ // Demo: Deduct and show success
1002
+ currentUser.gen_balance -= amount;
1003
+ const odds = choice === 'yes' ? selectedMarket.yes_odds : selectedMarket.no_odds;
1004
+ const potentialWin = Math.round(amount * odds);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1005
 
1006
+ showToast(`${t('betPlaced')} ${amount} GEN → ${potentialWin} GEN (${odds}x)`, 'success');
1007
+ updateUserCard();
1008
+ closeModal();
 
 
 
 
 
 
 
 
 
 
 
1009
  }
1010
 
1011
+ function submitComment() {
1012
  if (!currentUser || !selectedMarket) return;
1013
  const input = document.getElementById('commentInput');
1014
  const content = input.value.trim();
1015
  if (!content) return;
1016
 
1017
+ // Add to demo comments
1018
+ if (!selectedMarket.comments) selectedMarket.comments = [];
1019
+ selectedMarket.comments.unshift({
1020
+ username: currentUser.username,
1021
+ content: content,
1022
+ created_at: new Date().toLocaleString()
1023
+ });
1024
+
1025
+ showToast(t('commentPosted'), 'success');
1026
+ renderMarketModal(selectedMarket);
1027
  }
1028
 
1029
  // ==================== Create Market ====================
 
1042
  document.getElementById('createMarketModal').classList.remove('active');
1043
  }
1044
 
1045
+ function submitCreateMarket() {
1046
  if (!currentUser) { showToast(t('loginRequired'), 'error'); return; }
1047
 
1048
  const title = document.getElementById('marketTitle').value.trim();
 
1055
  if (!endDate) { showToast('End date is required', 'error'); return; }
1056
  if (currentUser.gen_balance < 100) { showToast('Need 100 GEN to create market', 'error'); return; }
1057
 
1058
+ // Demo: Create market
1059
+ currentUser.gen_balance -= 100;
1060
+
1061
+ const newMarket = {
1062
+ id: DEMO_MARKETS.length + 1,
1063
+ title: title,
1064
+ description: description,
1065
+ category: category,
1066
+ category_info: CATEGORIES[category],
1067
+ yes_pool: 0, no_pool: 0,
1068
+ yes_pct: 50, no_pct: 50,
1069
+ yes_odds: 2.0, no_odds: 2.0,
1070
+ total_volume: 0, participant_count: 0,
1071
+ status: "active",
1072
+ time_remaining: "7일 남음",
1073
+ resolution_source: resolution || 'N/A',
1074
+ ai_prediction_yes: null,
1075
+ comments: []
1076
+ };
1077
+
1078
+ DEMO_MARKETS.unshift(newMarket);
1079
+
1080
+ showToast('✅ Market created!', 'success');
1081
+ closeCreateMarketModal();
1082
+ updateUserCard();
1083
+ loadMarkets();
 
1084
  }
1085
 
1086
+ // ==================== User & Auth ====================
1087
+ function handleLoginClick() {
1088
+ if (currentUser) {
1089
+ const menu = document.getElementById('userDropdownMenu');
1090
+ menu.classList.toggle('active');
1091
+ } else {
1092
+ document.getElementById('loginModal').classList.add('active');
1093
+ }
 
 
 
 
 
 
 
 
1094
  }
1095
 
1096
+ function closeLoginModal() {
1097
+ document.getElementById('loginModal').classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1098
  }
1099
 
1100
+ function loginWithHuggingFace() {
1101
+ showToast('HuggingFace OAuth requires deployment on HF Spaces', 'error');
1102
+ }
1103
+
1104
+ function demoLogin(userId) {
1105
+ const user = DEMO_USERS.find(u => u.id === userId);
1106
+ if (user) {
1107
+ currentUser = { ...user };
1108
+ updateUserCard();
1109
+ closeLoginModal();
1110
+ showToast(`${t('welcome')}, ${user.username}!`, 'success');
1111
+ }
1112
+ }
1113
+
1114
+ function handleLogout() {
1115
+ currentUser = null;
1116
+ updateUserCard();
1117
+ document.getElementById('userDropdownMenu').classList.remove('active');
1118
+ showToast(t('loggedOut'), 'success');
1119
  }
1120
 
 
1121
  function updateUserCard() {
1122
  const card = document.getElementById('userCard');
1123
  const loginBtn = document.getElementById('loginBtn');
 
1130
  </div>`;
1131
 
1132
  loginBtn.innerHTML = `<i class="fas fa-user"></i> <span data-i18n="login">${t('login')}</span>`;
 
1133
  return;
1134
  }
1135
 
1136
  const winRate = currentUser.wins + currentUser.losses > 0 ? Math.round(currentUser.wins * 100 / (currentUser.wins + currentUser.losses)) : 0;
1137
 
 
 
 
 
 
 
 
 
 
 
 
 
1138
  card.innerHTML = `
1139
+ <img class="user-avatar" src="https://api.dicebear.com/7.x/avataaars/svg?seed=${currentUser.username}" onerror="this.src='https://api.dicebear.com/7.x/avataaars/svg?seed=default'">
1140
  <div class="user-name">@${currentUser.username}</div>
1141
  <div class="user-balance">
1142
  <span class="balance-icon">💎</span>
 
1147
  <div class="user-stat"><div class="user-stat-value" style="color: var(--success);">${currentUser.wins}</div><div class="user-stat-label">${t('wins')}</div></div>
1148
  <div class="user-stat"><div class="user-stat-value">${winRate}%</div><div class="user-stat-label">${t('winRate')}</div></div>
1149
  </div>
 
1150
  `;
1151
 
1152
  loginBtn.innerHTML = `
1153
+ <img src="https://api.dicebear.com/7.x/avataaars/svg?seed=${currentUser.username}" style="width: 24px; height: 24px; border-radius: 6px;" onerror="this.src='https://api.dicebear.com/7.x/avataaars/svg?seed=default'">
1154
  ${currentUser.username}
1155
  `;
1156
+ }
1157
+
1158
+ function claimDailyBonus() {
1159
+ if (!currentUser) { showToast(t('loginRequired'), 'error'); return; }
1160
+ currentUser.gen_balance += 50;
1161
+ updateUserCard();
1162
+ showToast(`🎁 ${t('bonusReceived')} +50 GEN`, 'success');
1163
  }
1164
 
1165
  function showProfile() {
1166
  document.getElementById('userDropdownMenu').classList.remove('active');
 
1167
  showToast('Profile feature coming soon!', 'success');
1168
  }
1169
 
1170
  function showMyBets() {
1171
  document.getElementById('userDropdownMenu').classList.remove('active');
 
1172
  showToast('My Bets feature coming soon!', 'success');
1173
  }
1174
 
1175
+ function showNotifications() {
1176
  if (!currentUser) { showToast(t('loginRequired'), 'error'); return; }
1177
+ document.getElementById('notificationsList').innerHTML = `<p style="color: var(--text-muted); text-align: center; padding: 2rem;">${t('noNotifications')}</p>`;
1178
+ document.getElementById('notificationsModal').classList.add('active');
1179
+ }
1180
+
1181
+ function closeNotificationsModal() {
1182
+ document.getElementById('notificationsModal').classList.remove('active');
 
 
 
 
 
1183
  }
1184
 
1185
  // ==================== Utility ====================
 
1192
  setTimeout(() => { toast.style.animation = 'slideIn 0.3s ease reverse'; setTimeout(() => toast.remove(), 300); }, 3000);
1193
  }
1194
 
 
 
 
 
 
 
 
 
 
 
 
1195
  function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; }
1196
 
1197
+ function refreshMarkets() { loadMarkets(); loadRankings(); showToast('Refreshed!', 'success'); }
1198
 
1199
  // ==================== Events ====================
1200
  document.querySelectorAll('.sort-tab').forEach(tab => {
 
1216
  }
1217
  });
1218
 
 
1219
  document.addEventListener('click', (e) => {
1220
  const dropdown = document.getElementById('userDropdown');
1221
  const menu = document.getElementById('userDropdownMenu');
 
1230
  document.getElementById('loginModal').addEventListener('click', (e) => { if (e.target.id === 'loginModal') closeLoginModal(); });
1231
 
1232
  // ==================== Init ====================
1233
+ function init() {
1234
+ loadCategories();
1235
+ loadMarkets();
1236
+ loadRankings();
 
 
 
1237
  updateUserCard();
1238
+ updateAllTranslations();
 
1239
  }
1240
 
1241
  init();