openfree commited on
Commit
fced3bb
·
verified ·
1 Parent(s): 933ec24

Upload index (8).html

Browse files
Files changed (1) hide show
  1. index (8).html +1338 -0
index (8).html ADDED
@@ -0,0 +1,1338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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">
6
+ <title>PriceGen.ai - AI-Powered Prediction Market</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
10
+
11
+ <style>
12
+ :root {
13
+ --glass-bg: rgba(255, 255, 255, 0.08);
14
+ --glass-border: rgba(255, 255, 255, 0.15);
15
+ --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
16
+ --accent-cyan: #00f5d4;
17
+ --accent-magenta: #f72585;
18
+ --accent-purple: #7209b7;
19
+ --accent-blue: #4361ee;
20
+ --text-primary: #ffffff;
21
+ --text-secondary: rgba(255, 255, 255, 0.7);
22
+ --text-muted: rgba(255, 255, 255, 0.4);
23
+ --success: #00f5a0;
24
+ --danger: #ff4757;
25
+ --warning: #ffa502;
26
+ }
27
+
28
+ * { margin: 0; padding: 0; box-sizing: border-box; }
29
+
30
+ body {
31
+ font-family: 'Outfit', -apple-system, BlinkMacSystemFont, sans-serif;
32
+ background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
33
+ min-height: 100vh;
34
+ color: var(--text-primary);
35
+ overflow-x: hidden;
36
+ }
37
+
38
+ .bg-animation {
39
+ position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; overflow: hidden;
40
+ }
41
+ .bg-animation::before {
42
+ content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%;
43
+ background: radial-gradient(circle at 20% 80%, rgba(114, 9, 183, 0.3) 0%, transparent 50%),
44
+ radial-gradient(circle at 80% 20%, rgba(0, 245, 212, 0.2) 0%, transparent 50%),
45
+ radial-gradient(circle at 40% 40%, rgba(247, 37, 133, 0.2) 0%, transparent 40%);
46
+ animation: bgMove 20s ease-in-out infinite;
47
+ }
48
+ @keyframes bgMove {
49
+ 0%, 100% { transform: translate(0, 0) rotate(0deg); }
50
+ 33% { transform: translate(2%, 2%) rotate(2deg); }
51
+ 66% { transform: translate(-2%, 1%) rotate(-2deg); }
52
+ }
53
+
54
+ .glass {
55
+ background: var(--glass-bg);
56
+ backdrop-filter: blur(20px);
57
+ -webkit-backdrop-filter: blur(20px);
58
+ border: 1px solid var(--glass-border);
59
+ border-radius: 24px;
60
+ box-shadow: var(--glass-shadow);
61
+ }
62
+
63
+ .header {
64
+ position: sticky; top: 0; z-index: 100;
65
+ padding: 1rem 2rem;
66
+ display: flex; align-items: center; justify-content: space-between; gap: 2rem;
67
+ }
68
+
69
+ .logo { display: flex; align-items: center; gap: 0.75rem; text-decoration: none; }
70
+ .logo-icon {
71
+ width: 48px; height: 48px;
72
+ background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple));
73
+ border-radius: 14px;
74
+ display: flex; align-items: center; justify-content: center;
75
+ font-size: 1.5rem;
76
+ box-shadow: 0 4px 20px rgba(0, 245, 212, 0.3);
77
+ }
78
+ .logo-text {
79
+ font-size: 1.5rem; font-weight: 700;
80
+ background: linear-gradient(135deg, var(--accent-cyan), var(--accent-magenta));
81
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
82
+ }
83
+ .logo-version {
84
+ font-size: 0.7rem;
85
+ color: var(--text-muted);
86
+ margin-left: 0.5rem;
87
+ }
88
+
89
+ .price-ticker { display: flex; gap: 1.5rem; padding: 0.75rem 1.5rem; border-radius: 16px; }
90
+ .ticker-item { display: flex; align-items: center; gap: 0.75rem; }
91
+ .ticker-symbol { font-weight: 600; font-size: 0.9rem; color: var(--text-secondary); }
92
+ .ticker-price { font-family: 'JetBrains Mono', monospace; font-weight: 500; font-size: 1rem; }
93
+ .ticker-change { font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; padding: 0.25rem 0.5rem; border-radius: 6px; }
94
+ .ticker-change.up { color: var(--success); background: rgba(0, 245, 160, 0.15); }
95
+ .ticker-change.down { color: var(--danger); background: rgba(255, 71, 87, 0.15); }
96
+
97
+ .nav-actions { display: flex; align-items: center; gap: 1rem; }
98
+
99
+ .btn {
100
+ padding: 0.75rem 1.5rem; border-radius: 12px;
101
+ font-family: 'Outfit', sans-serif; font-weight: 600; font-size: 0.95rem;
102
+ cursor: pointer; transition: all 0.3s ease; border: none;
103
+ display: flex; align-items: center; gap: 0.5rem;
104
+ }
105
+ .btn-primary {
106
+ background: linear-gradient(135deg, var(--accent-cyan), var(--accent-blue));
107
+ color: #0a0a0a;
108
+ box-shadow: 0 4px 20px rgba(0, 245, 212, 0.3);
109
+ }
110
+ .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(0, 245, 212, 0.4); }
111
+ .btn-glass { background: var(--glass-bg); border: 1px solid var(--glass-border); color: var(--text-primary); }
112
+ .btn-glass:hover { background: rgba(255, 255, 255, 0.15); }
113
+ .btn-ai {
114
+ background: linear-gradient(135deg, var(--accent-purple), var(--accent-magenta));
115
+ color: white;
116
+ box-shadow: 0 4px 20px rgba(114, 9, 183, 0.3);
117
+ }
118
+ .btn-ai:hover { transform: translateY(-2px); box-shadow: 0 6px 25px rgba(114, 9, 183, 0.4); }
119
+ .btn-danger {
120
+ background: linear-gradient(135deg, var(--danger), #ff6b81);
121
+ color: white;
122
+ }
123
+
124
+ .lang-toggle {
125
+ padding: 0.5rem 1rem; border-radius: 10px;
126
+ background: rgba(255, 255, 255, 0.1); border: 1px solid var(--glass-border);
127
+ color: var(--text-primary); font-size: 0.85rem; cursor: pointer;
128
+ }
129
+ .lang-toggle:hover { background: rgba(255, 255, 255, 0.2); }
130
+
131
+ .notification-badge {
132
+ position: relative;
133
+ }
134
+ .notification-badge .badge-count {
135
+ position: absolute; top: -5px; right: -5px;
136
+ background: var(--danger); color: white;
137
+ font-size: 0.7rem; font-weight: 700;
138
+ width: 18px; height: 18px; border-radius: 50%;
139
+ display: flex; align-items: center; justify-content: center;
140
+ }
141
+
142
+ .main-container {
143
+ display: grid; grid-template-columns: 280px 1fr 320px;
144
+ gap: 1.5rem; padding: 1.5rem 2rem; max-width: 1800px; margin: 0 auto;
145
+ }
146
+
147
+ .sidebar { position: sticky; top: 100px; height: fit-content; }
148
+ .sidebar-section { padding: 1.5rem; margin-bottom: 1.5rem; }
149
+ .sidebar-title { font-size: 0.85rem; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 1rem; }
150
+
151
+ .category-list { display: flex; flex-direction: column; gap: 0.5rem; }
152
+ .category-item {
153
+ display: flex; align-items: center; gap: 0.75rem;
154
+ padding: 0.75rem 1rem; border-radius: 12px;
155
+ cursor: pointer; transition: all 0.2s ease; color: var(--text-secondary);
156
+ }
157
+ .category-item:hover, .category-item.active { background: rgba(255, 255, 255, 0.1); color: var(--text-primary); }
158
+ .category-item.active { background: linear-gradient(135deg, rgba(0, 245, 212, 0.2), rgba(114, 9, 183, 0.2)); border: 1px solid rgba(0, 245, 212, 0.3); }
159
+ .category-icon { font-size: 1.25rem; }
160
+ .category-name { font-weight: 500; }
161
+
162
+ .ranking-list { display: flex; flex-direction: column; gap: 0.75rem; }
163
+ .ranking-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.5rem; border-radius: 10px; transition: background 0.2s; }
164
+ .ranking-item:hover { background: rgba(255, 255, 255, 0.05); }
165
+ .ranking-position { width: 28px; height: 28px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.85rem; }
166
+ .ranking-position.gold { background: linear-gradient(135deg, #ffd700, #ffaa00); color: #1a1a1a; }
167
+ .ranking-position.silver { background: linear-gradient(135deg, #c0c0c0, #a0a0a0); color: #1a1a1a; }
168
+ .ranking-position.bronze { background: linear-gradient(135deg, #cd7f32, #a0522d); color: white; }
169
+ .ranking-avatar { width: 32px; height: 32px; border-radius: 10px; object-fit: cover; border: 2px solid var(--glass-border); }
170
+ .ranking-info { flex: 1; min-width: 0; }
171
+ .ranking-name { font-weight: 500; font-size: 0.9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
172
+ .ranking-value { font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; color: var(--accent-cyan); }
173
+
174
+ .main-content { min-width: 0; }
175
+ .content-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem; }
176
+ .content-title { font-size: 1.75rem; font-weight: 700; }
177
+
178
+ .sort-tabs { display: flex; gap: 0.5rem; padding: 0.25rem; border-radius: 12px; }
179
+ .sort-tab {
180
+ padding: 0.5rem 1rem; border-radius: 10px; font-size: 0.9rem; font-weight: 500;
181
+ color: var(--text-secondary); cursor: pointer; transition: all 0.2s; border: none; background: transparent;
182
+ }
183
+ .sort-tab:hover { color: var(--text-primary); }
184
+ .sort-tab.active { background: rgba(0, 245, 212, 0.2); color: var(--accent-cyan); }
185
+
186
+ .markets-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 1.25rem; }
187
+
188
+ .market-card { padding: 1.5rem; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; }
189
+ .market-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: linear-gradient(90deg, var(--accent-cyan), var(--accent-purple)); opacity: 0; transition: opacity 0.3s; }
190
+ .market-card:hover { transform: translateY(-4px); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4); }
191
+ .market-card:hover::before { opacity: 1; }
192
+ .market-card.resolved { opacity: 0.7; }
193
+
194
+ .market-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 1rem; }
195
+ .market-category { display: flex; align-items: center; gap: 0.5rem; padding: 0.35rem 0.75rem; background: rgba(255, 255, 255, 0.1); border-radius: 8px; font-size: 0.8rem; color: var(--text-secondary); }
196
+ .market-status { padding: 0.35rem 0.75rem; border-radius: 8px; font-size: 0.75rem; font-weight: 600; }
197
+ .market-status.active { background: rgba(0, 245, 160, 0.15); color: var(--success); }
198
+ .market-status.urgent { background: rgba(255, 165, 2, 0.15); color: var(--warning); animation: pulse 2s infinite; }
199
+ .market-status.resolved-yes { background: rgba(0, 245, 160, 0.2); color: var(--success); }
200
+ .market-status.resolved-no { background: rgba(255, 71, 87, 0.2); color: var(--danger); }
201
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
202
+
203
+ .market-title { font-size: 1.1rem; font-weight: 600; line-height: 1.4; margin-bottom: 1rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
204
+
205
+ .prob-bar-container { margin-bottom: 1rem; }
206
+ .prob-bar { display: flex; height: 40px; border-radius: 12px; overflow: hidden; gap: 3px; }
207
+ .prob-yes { background: linear-gradient(135deg, #00f5a0, #00d9f5); display: flex; align-items: center; justify-content: center; font-weight: 700; color: #0a0a0a; font-size: 0.9rem; transition: flex 0.5s ease; }
208
+ .prob-no { background: linear-gradient(135deg, #ff4757, #ff6b81); display: flex; align-items: center; justify-content: center; font-weight: 700; color: white; font-size: 0.9rem; transition: flex 0.5s ease; }
209
+
210
+ .market-stats { display: flex; justify-content: space-between; color: var(--text-secondary); font-size: 0.85rem; }
211
+ .market-stat { display: flex; align-items: center; gap: 0.4rem; }
212
+ .market-stat-value { font-family: 'JetBrains Mono', monospace; color: var(--accent-cyan); font-weight: 500; }
213
+
214
+ .user-panel { position: sticky; top: 100px; height: fit-content; }
215
+ .user-card { padding: 1.5rem; text-align: center; margin-bottom: 1.5rem; }
216
+ .user-avatar { width: 80px; height: 80px; border-radius: 20px; margin: 0 auto 1rem; border: 3px solid var(--glass-border); object-fit: cover; background: linear-gradient(135deg, var(--accent-purple), var(--accent-cyan)); }
217
+ .user-name { font-size: 1.25rem; font-weight: 700; margin-bottom: 0.5rem; }
218
+ .user-balance { display: flex; align-items: center; justify-content: center; gap: 0.5rem; padding: 1rem; background: linear-gradient(135deg, rgba(0, 245, 212, 0.15), rgba(114, 9, 183, 0.15)); border-radius: 16px; margin: 1rem 0; }
219
+ .balance-icon { font-size: 1.5rem; }
220
+ .balance-amount { font-family: 'JetBrains Mono', monospace; font-size: 1.5rem; font-weight: 700; background: linear-gradient(135deg, var(--accent-cyan), var(--accent-magenta)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; }
221
+ .balance-label { font-size: 0.85rem; color: var(--text-muted); }
222
+ .user-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.5rem; margin-top: 1rem; }
223
+ .user-stat { padding: 0.75rem; background: rgba(255, 255, 255, 0.05); border-radius: 12px; }
224
+ .user-stat-value { font-family: 'JetBrains Mono', monospace; font-size: 1.1rem; font-weight: 600; }
225
+ .user-stat-label { font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
226
+
227
+ .badges-section { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--glass-border); }
228
+ .badges-title { font-size: 0.85rem; font-weight: 600; color: var(--text-muted); margin-bottom: 0.75rem; }
229
+ .badges-list { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; }
230
+ .badge-item { font-size: 1.5rem; padding: 0.25rem; cursor: help; transition: transform 0.2s; }
231
+ .badge-item:hover { transform: scale(1.2); }
232
+
233
+ .quick-actions { padding: 1.5rem; }
234
+ .quick-action-btn { width: 100%; margin-bottom: 0.75rem; }
235
+
236
+ .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(10px); z-index: 1000; display: none; align-items: center; justify-content: center; padding: 2rem; overflow-y: auto; }
237
+ .modal-overlay.active { display: flex; }
238
+ .modal-content { width: 100%; max-width: 800px; max-height: 90vh; overflow-y: auto; padding: 2rem; position: relative; }
239
+ .modal-close { position: absolute; top: 1.5rem; right: 1.5rem; width: 40px; height: 40px; border-radius: 12px; background: rgba(255, 255, 255, 0.1); border: none; color: var(--text-primary); font-size: 1.25rem; cursor: pointer; transition: all 0.2s; }
240
+ .modal-close:hover { background: rgba(255, 71, 87, 0.3); }
241
+ .modal-title { font-size: 1.5rem; font-weight: 700; margin-bottom: 1rem; padding-right: 3rem; }
242
+ .modal-description { color: var(--text-secondary); line-height: 1.6; margin-bottom: 1.5rem; }
243
+
244
+ .ai-prediction-box { background: linear-gradient(135deg, rgba(114, 9, 183, 0.3), rgba(247, 37, 133, 0.3)); border: 1px solid rgba(114, 9, 183, 0.5); border-radius: 16px; padding: 1.25rem; margin: 1rem 0; }
245
+ .ai-prediction-header { display: flex; align-items: center; gap: 0.5rem; font-weight: 600; margin-bottom: 0.75rem; }
246
+ .ai-prediction-content { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
247
+ .ai-prediction-value { font-family: 'JetBrains Mono', monospace; font-size: 1.5rem; font-weight: 700; }
248
+ .ai-prediction-reason { color: var(--text-secondary); font-size: 0.9rem; flex: 1; min-width: 200px; }
249
+ .ai-confidence { padding: 0.25rem 0.5rem; border-radius: 6px; font-size: 0.75rem; font-weight: 600; }
250
+ .ai-confidence.high { background: rgba(0, 245, 160, 0.2); color: var(--success); }
251
+ .ai-confidence.medium { background: rgba(255, 165, 2, 0.2); color: var(--warning); }
252
+ .ai-confidence.low { background: rgba(255, 71, 87, 0.2); color: var(--danger); }
253
+
254
+ .betting-section { background: rgba(255, 255, 255, 0.05); border-radius: 20px; padding: 1.5rem; margin: 1.5rem 0; }
255
+ .betting-title { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
256
+ .amount-input-container { margin-bottom: 1rem; }
257
+ .amount-label { font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 0.5rem; display: block; }
258
+ .amount-input { width: 100%; padding: 1rem 1.25rem; background: rgba(255, 255, 255, 0.08); border: 1px solid var(--glass-border); border-radius: 12px; color: var(--text-primary); font-family: 'JetBrains Mono', monospace; font-size: 1.25rem; outline: none; transition: all 0.2s; }
259
+ .amount-input:focus { border-color: var(--accent-cyan); box-shadow: 0 0 0 3px rgba(0, 245, 212, 0.1); }
260
+ .amount-presets { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
261
+ .preset-btn { flex: 1; padding: 0.5rem; background: rgba(255, 255, 255, 0.05); border: 1px solid var(--glass-border); border-radius: 8px; color: var(--text-secondary); font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; cursor: pointer; transition: all 0.2s; }
262
+ .preset-btn:hover { background: rgba(255, 255, 255, 0.1); color: var(--text-primary); }
263
+ .bet-buttons { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
264
+ .bet-btn { padding: 1rem; border-radius: 16px; font-size: 1rem; font-weight: 700; border: none; cursor: pointer; transition: all 0.3s; }
265
+ .bet-btn.yes { background: linear-gradient(135deg, #00f5a0, #00d9f5); color: #0a0a0a; }
266
+ .bet-btn.no { background: linear-gradient(135deg, #ff4757, #ff6b81); color: white; }
267
+ .bet-btn:hover { transform: translateY(-2px); }
268
+ .bet-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
269
+ .bet-odds { font-size: 0.85rem; font-weight: 500; opacity: 0.8; display: block; margin-top: 0.25rem; }
270
+
271
+ .comments-section { margin-top: 1.5rem; }
272
+ .comments-title { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; }
273
+ .comment-input-container { display: flex; gap: 0.75rem; margin-bottom: 1rem; }
274
+ .comment-input { flex: 1; padding: 0.75rem 1rem; background: rgba(255, 255, 255, 0.08); border: 1px solid var(--glass-border); border-radius: 12px; color: var(--text-primary); font-family: 'Outfit', sans-serif; outline: none; }
275
+ .comment-list { max-height: 300px; overflow-y: auto; }
276
+ .comment-item { display: flex; gap: 0.75rem; padding: 1rem; background: rgba(255, 255, 255, 0.03); border-radius: 12px; margin-bottom: 0.75rem; }
277
+ .comment-avatar { width: 36px; height: 36px; border-radius: 10px; object-fit: cover; }
278
+ .comment-content { flex: 1; }
279
+ .comment-header { display: flex; justify-content: space-between; margin-bottom: 0.25rem; }
280
+ .comment-author { font-weight: 600; font-size: 0.9rem; }
281
+ .comment-time { font-size: 0.75rem; color: var(--text-muted); }
282
+ .comment-text { color: var(--text-secondary); font-size: 0.9rem; line-height: 1.5; }
283
+
284
+ .login-prompt { text-align: center; padding: 2rem; }
285
+ .login-prompt-icon { font-size: 3rem; margin-bottom: 1rem; opacity: 0.5; }
286
+ .login-prompt-text { color: var(--text-secondary); margin-bottom: 1.5rem; }
287
+
288
+ .create-market-form { display: flex; flex-direction: column; gap: 1rem; }
289
+ .form-group { display: flex; flex-direction: column; gap: 0.5rem; }
290
+ .form-label { font-size: 0.9rem; font-weight: 500; color: var(--text-secondary); }
291
+ .form-input, .form-select, .form-textarea { padding: 0.75rem 1rem; background: rgba(255, 255, 255, 0.08); border: 1px solid var(--glass-border); border-radius: 12px; color: var(--text-primary); font-family: 'Outfit', sans-serif; outline: none; }
292
+ .form-input:focus, .form-select:focus, .form-textarea:focus { border-color: var(--accent-cyan); }
293
+ .form-select { cursor: pointer; }
294
+ .form-textarea { min-height: 100px; resize: vertical; }
295
+
296
+ .toast-container { position: fixed; bottom: 2rem; right: 2rem; z-index: 2000; display: flex; flex-direction: column; gap: 0.75rem; }
297
+ .toast { padding: 1rem 1.5rem; border-radius: 12px; display: flex; align-items: center; gap: 0.75rem; animation: slideIn 0.3s ease; min-width: 300px; }
298
+ @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
299
+ .toast.success { background: linear-gradient(135deg, rgba(0, 245, 160, 0.2), rgba(0, 217, 245, 0.2)); border: 1px solid rgba(0, 245, 160, 0.3); }
300
+ .toast.error { background: linear-gradient(135deg, rgba(255, 71, 87, 0.2), rgba(255, 107, 129, 0.2)); border: 1px solid rgba(255, 71, 87, 0.3); }
301
+
302
+ .loading-spinner { display: inline-block; width: 20px; height: 20px; border: 2px solid var(--glass-border); border-top-color: var(--accent-cyan); border-radius: 50%; animation: spin 1s linear infinite; }
303
+ @keyframes spin { to { transform: rotate(360deg); } }
304
+
305
+ .auth-status { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.5rem; }
306
+ .auth-status.authenticated { color: var(--success); }
307
+
308
+ .user-dropdown { position: relative; }
309
+ .user-dropdown-menu {
310
+ position: absolute; top: 100%; right: 0; margin-top: 0.5rem;
311
+ background: rgba(30, 30, 50, 0.95); backdrop-filter: blur(20px);
312
+ border: 1px solid var(--glass-border); border-radius: 12px;
313
+ padding: 0.5rem; min-width: 200px;
314
+ display: none; z-index: 1000;
315
+ }
316
+ .user-dropdown-menu.active { display: block; }
317
+ .user-dropdown-item {
318
+ display: flex; align-items: center; gap: 0.75rem;
319
+ padding: 0.75rem 1rem; border-radius: 8px;
320
+ color: var(--text-secondary); cursor: pointer; transition: all 0.2s;
321
+ }
322
+ .user-dropdown-item:hover { background: rgba(255, 255, 255, 0.1); color: var(--text-primary); }
323
+ .user-dropdown-item.danger { color: var(--danger); }
324
+ .user-dropdown-item.danger:hover { background: rgba(255, 71, 87, 0.2); }
325
+
326
+ ::-webkit-scrollbar { width: 8px; height: 8px; }
327
+ ::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.05); border-radius: 4px; }
328
+ ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 4px; }
329
+ ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); }
330
+
331
+ @media (max-width: 1200px) { .main-container { grid-template-columns: 1fr; } .sidebar, .user-panel { display: none; } }
332
+ @media (max-width: 768px) { .header { padding: 1rem; flex-wrap: wrap; } .price-ticker { display: none; } .main-container { padding: 1rem; } .markets-grid { grid-template-columns: 1fr; } .modal-content { padding: 1.5rem; } }
333
+ </style>
334
+ </head>
335
+ <body>
336
+ <div class="bg-animation"></div>
337
+
338
+ <header class="header glass">
339
+ <a href="/" class="logo">
340
+ <div class="logo-icon">🎯</div>
341
+ <span class="logo-text">PriceGen.ai</span>
342
+ <span class="logo-version">v7.1</span>
343
+ </a>
344
+
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
+
358
+ <div class="nav-actions">
359
+ <button class="lang-toggle" onclick="toggleLanguage()" id="langBtn">🇰🇷 한국어</button>
360
+ <button class="btn btn-glass notification-badge" onclick="showNotifications()" id="notifBtn">
361
+ <i class="fas fa-bell"></i>
362
+ <span class="badge-count" id="notifCount" style="display: none;">0</span>
363
+ </button>
364
+ <button class="btn btn-glass" onclick="claimDailyBonus()">
365
+ <i class="fas fa-gift"></i> <span data-i18n="dailyBonus">Daily Bonus</span>
366
+ </button>
367
+ <div class="user-dropdown" id="userDropdown">
368
+ <button class="btn btn-primary" id="loginBtn" onclick="handleLoginClick()">
369
+ <i class="fas fa-user"></i> <span data-i18n="login">Login</span>
370
+ </button>
371
+ <div class="user-dropdown-menu" id="userDropdownMenu">
372
+ <div class="user-dropdown-item" onclick="showProfile()">
373
+ <i class="fas fa-user-circle"></i> <span data-i18n="profile">Profile</span>
374
+ </div>
375
+ <div class="user-dropdown-item" onclick="showMyBets()">
376
+ <i class="fas fa-chart-line"></i> <span data-i18n="myBets">My Bets</span>
377
+ </div>
378
+ <div class="user-dropdown-item danger" onclick="handleLogout()">
379
+ <i class="fas fa-sign-out-alt"></i> <span data-i18n="logout">Logout</span>
380
+ </div>
381
+ </div>
382
+ </div>
383
+ </div>
384
+ </header>
385
+
386
+ <div class="main-container">
387
+ <aside class="sidebar">
388
+ <div class="sidebar-section glass">
389
+ <div class="sidebar-title" data-i18n="categories">Categories</div>
390
+ <div class="category-list" id="categoryList"></div>
391
+ </div>
392
+
393
+ <div class="sidebar-section glass">
394
+ <div class="sidebar-title">🏆 <span data-i18n="topHolders">GEN Top 10</span></div>
395
+ <div class="ranking-list" id="genRanking"></div>
396
+ </div>
397
+ </aside>
398
+
399
+ <main class="main-content">
400
+ <div class="content-header">
401
+ <h1 class="content-title" data-i18n="predictionMarkets">Prediction Markets</h1>
402
+ <div class="sort-tabs glass">
403
+ <button class="sort-tab active" data-sort="hot">🔥 <span data-i18n="hot">Hot</span></button>
404
+ <button class="sort-tab" data-sort="volume">📊 <span data-i18n="volume">Volume</span></button>
405
+ <button class="sort-tab" data-sort="newest">🆕 <span data-i18n="newest">Newest</span></button>
406
+ <button class="sort-tab" data-sort="ending">⏰ <span data-i18n="ending">Ending</span></button>
407
+ </div>
408
+ </div>
409
+ <div class="markets-grid" id="marketsGrid"></div>
410
+ </main>
411
+
412
+ <aside class="user-panel">
413
+ <div class="user-card glass" id="userCard">
414
+ <div class="login-prompt">
415
+ <div class="login-prompt-icon">🔐</div>
416
+ <p class="login-prompt-text" data-i18n="loginPrompt">Login to participate in prediction markets!</p>
417
+ <button class="btn btn-primary" onclick="handleLoginClick()">
418
+ <i class="fas fa-rocket"></i> <span data-i18n="hfLogin">HuggingFace Login</span>
419
+ </button>
420
+ </div>
421
+ </div>
422
+ <div class="quick-actions glass">
423
+ <div class="sidebar-title" data-i18n="quickActions">Quick Actions</div>
424
+ <button class="btn btn-glass quick-action-btn" onclick="refreshMarkets()">
425
+ <i class="fas fa-sync-alt"></i> <span data-i18n="refresh">Refresh</span>
426
+ </button>
427
+ <button class="btn btn-primary quick-action-btn" onclick="showCreateMarket()">
428
+ <i class="fas fa-plus"></i> <span data-i18n="createMarket">Create Market</span>
429
+ </button>
430
+ </div>
431
+ </aside>
432
+ </div>
433
+
434
+ <!-- Market Modal -->
435
+ <div class="modal-overlay" id="marketModal">
436
+ <div class="modal-content glass" id="modalContent"></div>
437
+ </div>
438
+
439
+ <!-- Create Market Modal -->
440
+ <div class="modal-overlay" id="createMarketModal">
441
+ <div class="modal-content glass">
442
+ <button class="modal-close" onclick="closeCreateMarketModal()"><i class="fas fa-times"></i></button>
443
+ <h2 class="modal-title">📝 <span data-i18n="createMarket">Create Market</span></h2>
444
+ <p style="color: var(--text-secondary); margin-bottom: 1.5rem;">Creating a market costs <strong style="color: var(--accent-cyan);">100 GEN</strong>. You'll earn 2% of the total volume when it resolves.</p>
445
+
446
+ <div class="create-market-form">
447
+ <div class="form-group">
448
+ <label class="form-label">Market Question *</label>
449
+ <input type="text" class="form-input" id="marketTitle" placeholder="e.g., Will Bitcoin reach $200K by Dec 2025?">
450
+ </div>
451
+ <div class="form-group">
452
+ <label class="form-label">Description</label>
453
+ <textarea class="form-textarea" id="marketDescription" placeholder="Additional details about this prediction..."></textarea>
454
+ </div>
455
+ <div class="form-group">
456
+ <label class="form-label">Category *</label>
457
+ <select class="form-select" id="marketCategory"></select>
458
+ </div>
459
+ <div class="form-group">
460
+ <label class="form-label">End Date *</label>
461
+ <input type="datetime-local" class="form-input" id="marketEndDate">
462
+ </div>
463
+ <div class="form-group">
464
+ <label class="form-label">Resolution Source</label>
465
+ <input type="text" class="form-input" id="marketResolution" placeholder="e.g., CoinGecko BTC/USD price">
466
+ </div>
467
+ <button class="btn btn-primary" onclick="submitCreateMarket()" style="width: 100%; margin-top: 1rem;">
468
+ <i class="fas fa-check"></i> Create Market (100 GEN)
469
+ </button>
470
+ </div>
471
+ </div>
472
+ </div>
473
+
474
+ <!-- Notifications Modal -->
475
+ <div class="modal-overlay" id="notificationsModal">
476
+ <div class="modal-content glass" style="max-width: 500px;">
477
+ <button class="modal-close" onclick="closeNotificationsModal()"><i class="fas fa-times"></i></button>
478
+ <h2 class="modal-title">🔔 Notifications</h2>
479
+ <div id="notificationsList"></div>
480
+ </div>
481
+ </div>
482
+
483
+ <!-- Login Modal -->
484
+ <div class="modal-overlay" id="loginModal">
485
+ <div class="modal-content glass" style="max-width: 400px;">
486
+ <button class="modal-close" onclick="closeLoginModal()"><i class="fas fa-times"></i></button>
487
+ <h2 class="modal-title" style="text-align: center;">🔐 Login</h2>
488
+ <p style="color: var(--text-secondary); text-align: center; margin-bottom: 2rem;">
489
+ Connect with HuggingFace to participate in prediction markets!
490
+ </p>
491
+
492
+ <div style="display: flex; flex-direction: column; gap: 1rem;">
493
+ <!-- HuggingFace OAuth Button (for production) -->
494
+ <button class="btn btn-primary" onclick="loginWithHuggingFace()" style="width: 100%; justify-content: center;">
495
+ <i class="fas fa-rocket"></i> Login with HuggingFace
496
+ </button>
497
+
498
+ <div style="text-align: center; color: var(--text-muted); font-size: 0.85rem;">
499
+ — or try demo mode —
500
+ </div>
501
+
502
+ <!-- Demo Login Buttons -->
503
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem;">
504
+ <button class="btn btn-glass" onclick="demoLogin(1)" style="font-size: 0.85rem;">
505
+ 🐋 CryptoWhale
506
+ </button>
507
+ <button class="btn btn-glass" onclick="demoLogin(2)" style="font-size: 0.85rem;">
508
+ 📈 TraderKim
509
+ </button>
510
+ <button class="btn btn-glass" onclick="demoLogin(3)" style="font-size: 0.85rem;">
511
+ 🤖 AI_Master
512
+ </button>
513
+ <button class="btn btn-glass" onclick="demoLogin(6)" style="font-size: 0.85rem;">
514
+ 🌱 Newbie2025
515
+ </button>
516
+ </div>
517
+ </div>
518
+
519
+ <p style="color: var(--text-muted); font-size: 0.75rem; text-align: center; margin-top: 1.5rem;">
520
+ Demo accounts are for testing purposes only.
521
+ </p>
522
+ </div>
523
+ </div>
524
+
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 = {
535
+ en: {
536
+ categories: "Categories", all: "All", topHolders: "GEN Top 10", predictionMarkets: "Prediction Markets",
537
+ hot: "Hot", volume: "Volume", newest: "Newest", ending: "Ending Soon",
538
+ dailyBonus: "Daily Bonus", login: "Login", logout: "Logout", profile: "Profile", myBets: "My Bets",
539
+ loginPrompt: "Login to participate in prediction markets!",
540
+ hfLogin: "HuggingFace Login", quickActions: "Quick Actions", refresh: "Refresh", createMarket: "Create Market",
541
+ noMarkets: "No markets available.", placeBet: "Place Bet", betAmount: "Bet Amount",
542
+ balance: "Balance", yesBet: "YES Bet", noBet: "NO Bet", odds: "odds",
543
+ totalVolume: "Total Volume", participants: "Participants", status: "Status",
544
+ resolutionCriteria: "Resolution Criteria", communityOpinions: "Community Opinions",
545
+ writeComment: "Write a comment...", post: "Post", noComments: "No comments yet.",
546
+ bets: "Bets", wins: "Wins", winRate: "Win Rate", welcome: "Welcome",
547
+ insufficientBalance: "Insufficient balance.", betPlaced: "Bet placed!",
548
+ potentialWin: "Potential win", alreadyClaimed: "Already claimed today. Come back tomorrow!",
549
+ bonusReceived: "You received bonus!", loginRequired: "Login required.",
550
+ commentPosted: "Comment posted.", marketNotFound: "Market not found.",
551
+ left: "left", endingSoon: "Ending soon", ended: "Ended", resolved: "Resolved",
552
+ aiPrediction: "AI Prediction", getAIPrediction: "Get AI Prediction", analyzing: "Analyzing...",
553
+ badges: "Badges", noBadges: "No badges yet", noNotifications: "No notifications",
554
+ loggingIn: "Logging in...", loggedOut: "Logged out successfully"
555
+ },
556
+ kr: {
557
+ categories: "카테고리", all: "전체", topHolders: "GEN 보유 TOP 10", predictionMarkets: "예측 마켓",
558
+ hot: "핫한", volume: "거래량", newest: "최신", ending: "마감임박",
559
+ dailyBonus: "일일 보너스", login: "로그인", logout: "로그아웃", profile: "프로필", myBets: "내 베팅",
560
+ loginPrompt: "로그인하여 예측 마켓에 참여하세요!",
561
+ hfLogin: "HuggingFace 로그인", quickActions: "빠른 액션", refresh: "새로고침", createMarket: "마켓 생성",
562
+ noMarkets: "마켓이 없습니다.", placeBet: "베팅하기", betAmount: "베팅 금액",
563
+ balance: "잔액", yesBet: "YES 베팅", noBet: "NO 베팅", odds: "배당",
564
+ totalVolume: "총 거래량", participants: "참여자", status: "상태",
565
+ resolutionCriteria: "판정 기준", communityOpinions: "커뮤니티 의견",
566
+ writeComment: "의견을 남겨주세요...", post: "작성", noComments: "아직 댓글이 없습니다.",
567
+ bets: "베팅", wins: "승리", winRate: "승률", welcome: "환영합니다",
568
+ insufficientBalance: "잔액이 부족합니다.", betPlaced: "베팅 완료!",
569
+ potentialWin: "예상 수익", alreadyClaimed: "오늘 보너스를 이미 받았습니다. 내일 다시 오세요!",
570
+ bonusReceived: "보너스를 받았습니다!", loginRequired: "로그인이 필요합니다.",
571
+ commentPosted: "댓글이 작성되었습니다.", marketNotFound: "마켓을 찾을 수 없습니다.",
572
+ left: "남음", endingSoon: "곧 마감", ended: "마감됨", resolved: "판정완료",
573
+ aiPrediction: "AI 예측", getAIPrediction: "AI 예측 받기", analyzing: "분석 중...",
574
+ badges: "뱃지", noBadges: "뱃지 없음", noNotifications: "알림 없음",
575
+ loggingIn: "로그인 중...", loggedOut: "로그아웃되었습니다"
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
+
589
+ function toggleLanguage() {
590
+ currentLang = currentLang === 'en' ? 'kr' : 'en';
591
+ document.getElementById('langBtn').innerHTML = currentLang === 'en' ? '🇰🇷 한국어' : '🇺🇸 English';
592
+ updateAllTranslations();
593
+ loadMarkets();
594
+ }
595
+
596
+ function updateAllTranslations() {
597
+ document.querySelectorAll('[data-i18n]').forEach(el => {
598
+ el.textContent = t(el.dataset.i18n);
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) {
777
+ currentCategory = category;
778
+ document.querySelectorAll('.category-item').forEach(item => {
779
+ item.classList.toggle('active', item.dataset.category === category);
780
+ });
781
+ loadMarkets();
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) {
796
+ const grid = document.getElementById('marketsGrid');
797
+
798
+ if (!markets || markets.length === 0) {
799
+ grid.innerHTML = `<div class="glass" style="grid-column: 1/-1; padding: 3rem; text-align: center;">
800
+ <p style="color: var(--text-muted); font-size: 1.1rem;">📭 ${t('noMarkets')}</p>
801
+ </div>`;
802
+ return;
803
+ }
804
+
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">
823
+ <div class="prob-bar">
824
+ <div class="prob-yes" style="flex: ${market.yes_pct}">YES ${market.yes_pct}%</div>
825
+ <div class="prob-no" style="flex: ${market.no_pct}">NO ${market.no_pct}%</div>
826
+ </div>
827
+ </div>
828
+ <div class="market-stats">
829
+ <div class="market-stat"><i class="fas fa-chart-bar"></i><span class="market-stat-value">${market.total_volume.toLocaleString()}</span><span>GEN</span></div>
830
+ <div class="market-stat"><i class="fas fa-users"></i><span class="market-stat-value">${market.participant_count}</span></div>
831
+ </div>
832
+ </div>`;
833
+ }).join('');
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) {
888
+ const content = document.getElementById('modalContent');
889
+ const canBet = market.status === 'active';
890
+ const catName = currentLang === 'kr' ? market.category_info?.name_kr : market.category_info?.name;
891
+
892
+ // AI Prediction section
893
+ let aiSection = '';
894
+ if (market.ai_prediction_yes !== null && market.ai_prediction_yes !== undefined) {
895
+ const aiYes = Math.round(market.ai_prediction_yes * 100);
896
+ aiSection = `
897
+ <div class="ai-prediction-box">
898
+ <div class="ai-prediction-header"><i class="fas fa-robot"></i> ${t('aiPrediction')}</div>
899
+ <div class="ai-prediction-content">
900
+ <div class="ai-prediction-value">YES ${aiYes}%</div>
901
+ <div class="ai-prediction-reason">${market.ai_prediction_reason || ''}</div>
902
+ </div>
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;">
909
+ <span>${market.category_info?.icon || '📊'}</span><span>${catName || market.category}</span>
910
+ </div>
911
+ <h2 class="modal-title">${market.title}</h2>
912
+ <p class="modal-description">${market.description || ''}</p>
913
+
914
+ <div class="prob-bar-container">
915
+ <div class="prob-bar" style="height: 60px; font-size: 1.1rem;">
916
+ <div class="prob-yes" style="flex: ${market.yes_pct}">YES ${market.yes_pct}% <span style="opacity: 0.7; margin-left: 0.5rem;">(${market.yes_odds}x)</span></div>
917
+ <div class="prob-no" style="flex: ${market.no_pct}">NO ${market.no_pct}% <span style="opacity: 0.7; margin-left: 0.5rem;">(${market.no_odds}x)</span></div>
918
+ </div>
919
+ </div>
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>
932
+ <div style="font-size: 0.8rem; color: var(--text-muted);">${t('totalVolume')} (GEN)</div>
933
+ </div>
934
+ <div style="text-align: center; padding: 1rem; background: rgba(255,255,255,0.05); border-radius: 12px;">
935
+ <div style="font-family: 'JetBrains Mono'; font-size: 1.25rem; color: var(--accent-magenta);">${market.participant_count}</div>
936
+ <div style="font-size: 0.8rem; color: var(--text-muted);">${t('participants')}</div>
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>
944
+ </div>
945
+
946
+ ${canBet ? `
947
+ <div class="betting-section">
948
+ <div class="betting-title"><i class="fas fa-coins"></i> ${t('placeBet')}</div>
949
+ ${currentUser ? `
950
+ <div class="amount-input-container">
951
+ <label class="amount-label">${t('betAmount')} (${t('balance')}: ${currentUser.gen_balance.toLocaleString()} GEN)</label>
952
+ <input type="number" class="amount-input" id="betAmount" value="100" min="10" max="10000" step="10">
953
+ </div>
954
+ <div class="amount-presets">
955
+ <button class="preset-btn" onclick="setBetAmount(50)">50</button>
956
+ <button class="preset-btn" onclick="setBetAmount(100)">100</button>
957
+ <button class="preset-btn" onclick="setBetAmount(500)">500</button>
958
+ <button class="preset-btn" onclick="setBetAmount(1000)">1K</button>
959
+ <button class="preset-btn" onclick="setBetAmount(5000)">5K</button>
960
+ </div>
961
+ <div class="bet-buttons">
962
+ <button class="bet-btn yes" onclick="placeBet('yes')">${t('yesBet')}<span class="bet-odds">${market.yes_odds}x ${t('odds')}</span></button>
963
+ <button class="bet-btn no" onclick="placeBet('no')">${t('noBet')}<span class="bet-odds">${market.no_odds}x ${t('odds')}</span></button>
964
+ </div>
965
+ ` : `
966
+ <div class="login-prompt" style="padding: 1rem;">
967
+ <p style="color: var(--text-secondary); margin-bottom: 1rem;">${t('loginRequired')}</p>
968
+ <button class="btn btn-primary" onclick="handleLoginClick()"><i class="fas fa-rocket"></i> ${t('login')}</button>
969
+ </div>
970
+ `}
971
+ </div>
972
+ ` : ''}
973
+
974
+ <div style="background: rgba(255,255,255,0.05); padding: 1rem; border-radius: 12px; margin-top: 1rem;">
975
+ <div style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 0.5rem;">📋 ${t('resolutionCriteria')}</div>
976
+ <div style="color: var(--text-secondary);">${market.resolution_source || 'N/A'}</div>
977
+ </div>
978
+
979
+ <div class="comments-section">
980
+ <div class="comments-title"><i class="fas fa-comments"></i> ${t('communityOpinions')} (${market.comments?.length || 0})</div>
981
+ ${currentUser ? `
982
+ <div class="comment-input-container">
983
+ <input type="text" class="comment-input" id="commentInput" placeholder="${t('writeComment')}" maxlength="500">
984
+ <button class="btn btn-primary" onclick="submitComment()"><i class="fas fa-paper-plane"></i></button>
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
+ `;
1003
+ }
1004
+
1005
+ function closeModal() {
1006
+ document.getElementById('marketModal').classList.remove('active');
1007
+ selectedMarket = null;
1008
+ }
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 ====================
1073
+ function showCreateMarket() {
1074
+ if (!currentUser) { showToast(t('loginRequired'), 'error'); return; }
1075
+
1076
+ // Set default end date to 7 days from now
1077
+ const defaultDate = new Date();
1078
+ defaultDate.setDate(defaultDate.getDate() + 7);
1079
+ document.getElementById('marketEndDate').value = defaultDate.toISOString().slice(0, 16);
1080
+
1081
+ document.getElementById('createMarketModal').classList.add('active');
1082
+ }
1083
+
1084
+ function closeCreateMarketModal() {
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();
1092
+ const description = document.getElementById('marketDescription').value.trim();
1093
+ const category = document.getElementById('marketCategory').value;
1094
+ const endDate = document.getElementById('marketEndDate').value;
1095
+ const resolution = document.getElementById('marketResolution').value.trim();
1096
+
1097
+ if (!title || title.length < 10) { showToast('Title must be at least 10 characters', 'error'); return; }
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');
1186
+
1187
+ if (!currentUser) {
1188
+ card.innerHTML = `<div class="login-prompt">
1189
+ <div class="login-prompt-icon">🔐</div>
1190
+ <p class="login-prompt-text">${t('loginPrompt')}</p>
1191
+ <button class="btn btn-primary" onclick="handleLoginClick()"><i class="fas fa-rocket"></i> ${t('hfLogin')}</button>
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>
1218
+ <div><div class="balance-amount">${currentUser.gen_balance.toLocaleString()}</div><div class="balance-label">GEN</div></div>
1219
+ </div>
1220
+ <div class="user-stats">
1221
+ <div class="user-stat"><div class="user-stat-value">${currentUser.total_bets}</div><div class="user-stat-label">${t('bets')}</div></div>
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 ====================
1264
+ function showToast(message, type = 'success') {
1265
+ const container = document.getElementById('toastContainer');
1266
+ const toast = document.createElement('div');
1267
+ toast.className = `toast glass ${type}`;
1268
+ toast.innerHTML = `<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-circle'}"></i><span>${message}</span>`;
1269
+ container.appendChild(toast);
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 => {
1290
+ tab.addEventListener('click', () => {
1291
+ document.querySelectorAll('.sort-tab').forEach(t => t.classList.remove('active'));
1292
+ tab.classList.add('active');
1293
+ currentSort = tab.dataset.sort;
1294
+ loadMarkets();
1295
+ });
1296
+ });
1297
+
1298
+ document.addEventListener('keydown', (e) => {
1299
+ if (e.key === 'Escape') {
1300
+ closeModal();
1301
+ closeCreateMarketModal();
1302
+ closeNotificationsModal();
1303
+ closeLoginModal();
1304
+ document.getElementById('userDropdownMenu').classList.remove('active');
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');
1312
+ if (!dropdown.contains(e.target)) {
1313
+ menu.classList.remove('active');
1314
+ }
1315
+ });
1316
+
1317
+ document.getElementById('marketModal').addEventListener('click', (e) => { if (e.target.id === 'marketModal') closeModal(); });
1318
+ document.getElementById('createMarketModal').addEventListener('click', (e) => { if (e.target.id === 'createMarketModal') closeCreateMarketModal(); });
1319
+ document.getElementById('notificationsModal').addEventListener('click', (e) => { if (e.target.id === 'notificationsModal') closeNotificationsModal(); });
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();
1336
+ </script>
1337
+ </body>
1338
+ </html>