dhruv575 commited on
Commit
febf196
·
1 Parent(s): 426fb3b

Frontend updates

Browse files
__pycache__/simulation_utils.cpython-314.pyc ADDED
Binary file (44.7 kB). View file
 
static/index.html CHANGED
@@ -4,369 +4,482 @@
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Safe Choices - Prediction Market Simulation</title>
7
- <link rel="stylesheet" href="/static/styles.css?v=8">
 
 
 
8
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
  </head>
10
  <body>
11
- <!-- Top Navigation Bar -->
12
- <header class="top-bar">
13
- <div class="nav-container">
14
- <div class="logo-section">
15
- <h1 class="app-title">Safe Choices</h1>
 
 
 
 
 
 
 
 
 
 
16
  </div>
17
- <div class="nav-info">
18
- <span class="dataset-info">Polymarket 2025 Dataset</span>
 
 
 
19
  </div>
20
  </div>
21
  </header>
22
 
23
- <!-- Main Container -->
24
- <div class="main-container">
25
- <!-- Left Sidebar - Navigation -->
26
  <aside class="sidebar">
27
- <div class="sidebar-label">View</div>
28
- <div class="view-tabs">
29
- <button class="view-tab active" data-view="simulation" onclick="selectView('simulation')">
30
- <div class="tab-title">Simulation</div>
31
- </button>
32
- <button class="view-tab" data-view="methodology" onclick="selectView('methodology')">
33
- <div class="tab-title">Methodology</div>
34
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  </div>
36
 
37
- <div class="sidebar-label simulation-view-only">Simulation Type</div>
38
- <div class="simulation-tabs simulation-view-only">
39
- <button class="sim-tab active" data-sim="single" onclick="selectSimulation('single')">
40
- <div class="tab-title">Single Fund</div>
41
- <div class="tab-subtitle">All capital in one fund</div>
42
- </button>
43
- <button class="sim-tab" data-sim="threshold" onclick="selectSimulation('threshold')">
44
- <div class="tab-title">Single Fund Threshold</div>
45
- <div class="tab-subtitle">Stop at target return</div>
46
- </button>
47
- <button class="sim-tab" data-sim="multi" onclick="selectSimulation('multi')">
48
- <div class="tab-title">Multi Fund</div>
49
- <div class="tab-subtitle">Diversified portfolio</div>
50
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  </div>
52
- </aside>
53
 
54
- <!-- Right Content Area -->
55
- <div class="content-area">
56
- <!-- Simulation Controls -->
57
- <section class="controls-section">
58
- <div class="controls-header">
59
- <h2>Parameters</h2>
 
 
 
 
 
60
  </div>
 
 
61
 
62
- <!-- Hidden default values -->
63
- <input type="hidden" id="startingCapital" value="10000">
64
- <input type="hidden" id="startDate" value="2025-01-01">
65
- <input type="hidden" id="maxDuration" value="365">
66
-
67
- <div class="controls-grid">
68
- <!-- Base Parameters -->
69
- <div class="control-group">
70
- <label class="control-label">Number of Simulations</label>
71
- <input type="number" id="numSimulations" class="control-input" value="100" min="10" max="100" step="10">
72
- <div class="control-subtitle">Monte Carlo runs (max 100)</div>
 
 
 
73
  </div>
74
 
75
- <!-- Probability Thresholds -->
76
- <div class="control-group">
77
- <label class="control-label">Min Probability 7d</label>
78
- <input type="number" id="minProb7d" class="control-input" value="90" min="50" max="99" step="1">
79
- <div class="control-subtitle">Minimum probability 7 days before</div>
80
- </div>
 
 
 
 
 
 
 
 
 
 
 
81
 
82
- <div class="control-group">
83
- <label class="control-label">Min Probability Current</label>
84
- <input type="number" id="minProbCurrent" class="control-input" value="90" min="50" max="99" step="1">
85
- <div class="control-subtitle">Minimum probability at investment</div>
86
- </div>
 
 
 
 
 
87
 
88
- <div class="control-group">
89
- <label class="control-label">Days Before Resolution</label>
90
- <input type="number" id="daysBefore" class="control-input" value="1" min="1" max="7" step="1">
91
- <div class="control-subtitle">Investment timing before market closes</div>
92
- </div>
 
 
 
 
 
93
 
94
- <div class="control-group">
95
- <label class="control-label">Trading Frequency</label>
96
- <select id="investmentProbability" class="control-input">
97
- <option value="0.02">Low</option>
98
- <option value="0.04" selected>Medium</option>
99
- <option value="0.06">High</option>
100
- </select>
101
- <div class="control-subtitle">How often to invest when markets are available</div>
102
- </div>
 
103
 
104
- <!-- Threshold-specific controls -->
105
- <div class="control-group threshold-only">
106
- <label class="control-label">Target Return</label>
107
- <select id="targetReturn" class="control-input">
108
- <option value="4.14">4.14% (Treasury Rate)</option>
109
- <option value="10.56">10.56% (NASDAQ Average)</option>
110
- <option value="custom">Custom</option>
111
- </select>
112
- <input type="number" id="customTarget" class="control-input" style="display: none; margin-top: 8px;" min="1" max="50" step="0.1">
113
- <div class="control-subtitle">Stop when target reached</div>
114
- </div>
115
 
116
- <!-- Multi-fund specific controls -->
117
- <div class="control-group multi-only">
118
- <label class="control-label">Number of Funds</label>
119
- <input type="number" id="numFunds" class="control-input" value="5" min="2" max="10" step="1">
120
- <div class="control-subtitle">Independent funds (max 10)</div>
121
- </div>
122
- </div>
 
 
 
 
 
 
123
 
124
- <!-- Run Button -->
125
- <div class="run-section">
126
- <button class="run-button" onclick="runSimulation()" id="runBtn">
127
- <span class="run-text">Run Simulation</span>
128
- <div class="run-spinner" style="display: none;"></div>
129
- </button>
130
- <div class="run-info">
131
- <span id="estimatedTime">Estimated: ~5-10 seconds</span>
 
 
 
132
  </div>
133
- </div>
134
- </section>
135
 
136
- <!-- Progress Bar -->
137
- <section class="progress-section" style="display: none;">
138
- <div class="progress-container">
139
- <div class="progress-label">
140
- <span id="progressText">Running simulation...</span>
141
- <span id="progressPercent">0%</span>
 
 
 
 
142
  </div>
143
- <div class="progress-bar">
144
- <div class="progress-fill" id="progressFill"></div>
 
 
 
 
 
 
 
 
 
 
145
  </div>
146
- </div>
147
- </section>
148
-
149
- <!-- Results Section - Hidden until simulation runs -->
150
- <section class="results-section" id="resultsSection" style="display: none;">
151
- <div class="results-header">
152
- <h2>Results</h2>
153
- <button class="export-button" onclick="exportResults()" id="exportBtn">Export</button>
154
- </div>
155
-
156
- <!-- Summary Statistics -->
157
- <div class="stats-grid">
158
- <div class="stat-card">
159
- <div class="stat-header">
160
- <h3>Performance</h3>
161
  </div>
162
- <div class="stat-content">
163
- <div class="stat-item">
164
- <span class="stat-label">Average Return:</span>
165
- <span class="stat-value" id="avgReturn">--</span>
166
- </div>
167
- <div class="stat-item">
168
- <span class="stat-label">Median Return:</span>
169
- <span class="stat-value" id="medianReturn">--</span>
170
- </div>
171
- <div class="stat-item">
172
- <span class="stat-label">Success Rate:</span>
173
- <span class="stat-value" id="successRate">--</span>
174
- </div>
175
  </div>
176
  </div>
177
 
178
- <div class="stat-card">
179
- <div class="stat-header">
180
- <h3>Risk Metrics</h3>
181
- </div>
182
- <div class="stat-content">
183
- <div class="stat-item">
184
- <span class="stat-label">Bust Rate:</span>
185
- <span class="stat-value" id="bustRate">--</span>
186
- </div>
187
- <div class="stat-item">
188
- <span class="stat-label">Volatility:</span>
189
- <span class="stat-value" id="volatility">--</span>
190
- </div>
191
- <div class="stat-item">
192
- <span class="stat-label">Max Drawdown:</span>
193
- <span class="stat-value" id="maxDrawdown">--</span>
 
 
 
 
 
194
  </div>
195
  </div>
196
- </div>
197
 
198
- <div class="stat-card multi-stats">
199
- <div class="stat-header">
200
  <h3>Portfolio Stats</h3>
201
- </div>
202
- <div class="stat-content">
203
- <div class="stat-item">
204
- <span class="stat-label">Avg Surviving Funds:</span>
205
- <span class="stat-value" id="avgSurvivingFunds">--</span>
206
- </div>
207
- <div class="stat-item">
208
- <span class="stat-label">Survivorship Rate:</span>
209
- <span class="stat-value" id="survivorshipRate">--</span>
 
 
 
 
210
  </div>
211
- <div class="stat-item">
212
- <span class="stat-label">Diversification Benefit:</span>
213
- <span class="stat-value" id="diversificationBenefit">--</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  </div>
215
  </div>
216
  </div>
217
 
218
- <div class="stat-card threshold-stats">
219
- <div class="stat-header">
220
- <h3>Target Achievement</h3>
221
- </div>
222
- <div class="stat-content">
223
- <div class="stat-item">
224
- <span class="stat-label">Target Reached:</span>
225
- <span class="stat-value" id="targetReached">--</span>
 
 
 
 
 
226
  </div>
227
- <div class="stat-item">
228
- <span class="stat-label">Avg Time to Target:</span>
229
- <span class="stat-value" id="avgTimeToTarget">--</span>
230
- </div>
231
- <div class="stat-item">
232
- <span class="stat-label">vs Never Stop:</span>
233
- <span class="stat-value" id="vsNeverStop">--</span>
234
  </div>
235
  </div>
236
- </div>
237
- </div>
238
 
239
- <!-- Charts - Always Visible -->
240
- <div class="charts-grid">
241
- <div class="chart-card">
242
- <div class="chart-header">
243
- <h3>Return Distribution</h3>
 
 
244
  </div>
245
- <div class="chart-container">
246
- <canvas id="returnChart" width="400" height="200"></canvas>
 
 
 
 
 
 
247
  </div>
248
  </div>
 
 
249
 
250
- <div class="chart-card">
251
- <div class="chart-header">
252
- <h3>Capital Evolution</h3>
 
 
 
 
 
 
 
 
 
253
  </div>
254
- <div class="chart-container">
255
- <canvas id="capitalChart" width="400" height="200"></canvas>
 
 
 
 
 
 
 
 
 
 
 
256
  </div>
257
- </div>
258
-
259
- <div class="chart-card multi-chart">
260
- <div class="chart-header">
261
- <h3>Fund Survivorship</h3>
 
 
 
 
 
262
  </div>
263
- <div class="chart-container">
264
- <canvas id="survivorshipChart" width="400" height="200"></canvas>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
265
  </div>
266
- </div>
267
- </div>
268
- </section>
269
- </div>
270
-
271
- <!-- Methodology Content -->
272
- <div class="methodology-area" style="display: none;">
273
- <section class="methodology-section">
274
- <div class="methodology-content">
275
- <h1>Safe Choices</h1>
276
- <h2>An effort to assess the safety of "safe choices" on prediction markets</h2>
277
-
278
- <div class="methodology-intro">
279
- <p>Let's conduct an experiment. 50 hedge funds swindle investors into inundating their coffers with $100 in deployable assets each, operating something they call the <em>State Earthquake Trade</em>. Each chooses a different state and in our fictional setting, the probability that each state is hit by an earthquake over the course of a year is 10%. The hedge funds deploy all of their capital every year on a contract that will return a 1.11x multiple if their associated state is not hit by an earthquake.</p>
280
-
281
- <p>Over 100,000 simulations of 50 funds, the average, minimum, and maximum number of surviving hedge funds after 10 years were <strong>17.42, 4, and 32</strong> respectively.</p>
282
-
283
- <p>These 17 hedge funds will have produced 11% returns year on year for 10 years, an excellent outcome by all standards. In this fictional realm where market probabilities are strictly equivalent to the odds they trade at (i.e. a world where the strong form of Fama's Efficient Market Hypothesis holds true), luck becomes the differentiating factor between visionaries of the investing world and going bust.</p>
284
- </div>
285
-
286
- <h3>Research Questions</h3>
287
- <p>We aim to explore the efficiency of "safe choices" (markets trading at ≥90 cents 24 hours before resolution) and "very safe choices" (≥95 cents). Key questions include:</p>
288
- <ul>
289
- <li>How often will traders making random walks across these markets end up with nothing?</li>
290
- <li>How often will they beat the market?</li>
291
- <li>If they stop at a certain return threshold (e.g., 12%), how often will they make it there?</li>
292
- <li>Were they to deploy capital amongst several smaller funds, would they have a higher survivorship rate?</li>
293
- </ul>
294
-
295
- <div class="hypothesis-box">
296
- <strong>Hypothesis:</strong> Inefficiencies in "safe" markets provide opportunities for positive expected value trades and ROI greater than investing in traditional benchmarks such as the risk-free rate.
297
- </div>
298
-
299
- <h3>Dataset Collection</h3>
300
- <p>We collect data through the Polymarket Gamma API (filtering markets by end date) and the CLOB API (fetching price history via CLOB token id).</p>
301
-
302
- <p>Markets are filtered to exclude those with inherent variability:</p>
303
- <ul>
304
- <li><strong>Sports:</strong> NFL, NBA, MLB, NHL, UFC, Soccer, etc.</li>
305
- <li><strong>Esports:</strong> LoL, Dota, CS:GO, Valorant, etc.</li>
306
- <li><strong>Crypto:</strong> Bitcoin, Ethereum, price predictions, etc.</li>
307
- <li><strong>Weather:</strong> Temperature, rain, storms, etc.</li>
308
- </ul>
309
-
310
- <p>For remaining markets, we log price history every 24 hours from 7 days before to 1 day before resolution. The dataset covers 2024-2025 and is available on <a href="https://www.kaggle.com/datasets/dhruvgup/polymarket-closed-2025-markets-7-day-price-history/data" target="_blank">Kaggle</a>.</p>
311
-
312
- <h3>Dataset Exploration</h3>
313
- <p><strong>Key Questions Addressed:</strong></p>
314
- <ol>
315
- <li><strong>Probability Calibration:</strong> What percentage of markets at different probability thresholds (90-95%, 95-98%, 98-99%, 99-100%) actually resolve to the expected outcome?</li>
316
- <li><strong>Confidence Intervals:</strong> Win rate and 95% confidence intervals for each probability band</li>
317
- <li><strong>Time Horizon Analysis:</strong> Comparison of outcomes at 48 hours vs. 7 days before resolution</li>
318
- <li><strong>Distribution Analysis:</strong> Volume-weighted vs. unweighted win rates</li>
319
- <li><strong>Expected Value Calculation:</strong> Expected return for each probability band</li>
320
- </ol>
321
-
322
- <h3>Simulation Model</h3>
323
- <div class="sim-type-box">
324
- <p>The simulation uses a <strong>day-by-day investment decision model</strong>:</p>
325
- <ol>
326
- <li>Each day, if not invested, decide to invest with probability <strong>α</strong> (investment_probability)</li>
327
- <li>If investing, select uniformly at random from markets that resolve exactly `days_before` days from now and meet thresholds</li>
328
- <li>Remain "locked in" until market resolves, then free to invest again</li>
329
- <li>Continue until: bust, reach target return, or simulation period ends</li>
330
- </ol>
331
- <p><strong>α controls trading frequency:</strong> Low (0.1) = cautious, High (0.2) = aggressive</p>
332
- </div>
333
-
334
- <h3>Simulation Types</h3>
335
-
336
- <div class="sim-type-box">
337
- <h4>1. Single Fund Simulation</h4>
338
- <p>A single trader deploys all capital sequentially across safe markets, reinvesting all winnings.</p>
339
- <p><strong>Parameters:</strong> starting_capital ($10,000), days_before (1-7), min_prob_7d (0.90), min_prob_current (0.90), investment_probability (α), max_duration_days (365)</p>
340
- </div>
341
-
342
- <div class="sim-type-box">
343
- <h4>2. Single Fund Threshold Simulation</h4>
344
- <p>Identical to Single Fund but with target return thresholds—trading stops when target is reached.</p>
345
- <p><strong>Benchmarks:</strong> Treasury Rate (4.14%) or NASDAQ Average (10.56%)</p>
346
- </div>
347
-
348
- <div class="sim-type-box">
349
- <h4>3. Multi-Fund Simulation</h4>
350
- <p>Capital is divided into multiple independent funds, each operating as separate single funds. Tests diversification benefits.</p>
351
- <p><strong>Parameters:</strong> n_funds (1, 3, 5, or 10 funds), capital divided equally, same α for all funds</p>
352
- </div>
353
-
354
- <h3>Technical Implementation</h3>
355
- <ul>
356
- <li>Uses actual Polymarket data filtered for 2025+ resolution dates</li>
357
- <li>Vectorized operations for performance (1000 simulations in ~10-20 seconds)</li>
358
- <li>Left-skewed exponential market selection using skew_factor</li>
359
- <li>Binary outcomes: win = 1/probability return, loss = total loss</li>
360
- <li>Reproducible results via random seeds</li>
361
- </ul>
362
-
363
- <h3>Limitations</h3>
364
- <p>Volume and market movement: If we count the starting capital as $10,000 distributed over 5 funds, we may not need to worry about available liquidity. However, were we to scale this experiment up, this becomes a real concern.</p>
365
- </div>
366
- </section>
367
  </div>
368
- </div>
369
 
370
- <script src="/static/script.js?v=8"></script>
371
  </body>
372
  </html>
 
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>Safe Choices - Prediction Market Simulation</title>
7
+ <link rel="stylesheet" href="/static/styles.css?v=10">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
12
  </head>
13
  <body>
14
+ <!-- Header -->
15
+ <header class="header">
16
+ <div class="header-content">
17
+ <div class="brand">
18
+ <div class="brand-icon">
19
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
20
+ <path d="M12 2L2 7l10 5 10-5-10-5z"/>
21
+ <path d="M2 17l10 5 10-5"/>
22
+ <path d="M2 12l10 5 10-5"/>
23
+ </svg>
24
+ </div>
25
+ <div class="brand-text">
26
+ <h1>Safe Choices</h1>
27
+ <span class="brand-tagline">Prediction Market Simulator</span>
28
+ </div>
29
  </div>
30
+ <div class="header-meta">
31
+ <div class="data-badge">
32
+ <span class="badge-dot"></span>
33
+ Polymarket 2025
34
+ </div>
35
  </div>
36
  </div>
37
  </header>
38
 
39
+ <!-- Main Layout -->
40
+ <main class="main">
41
+ <!-- Sidebar -->
42
  <aside class="sidebar">
43
+ <!-- View Toggle -->
44
+ <div class="sidebar-section">
45
+ <div class="section-label">View</div>
46
+ <div class="toggle-group">
47
+ <button class="toggle-btn active" data-view="simulation" onclick="selectView('simulation')">
48
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
49
+ <rect x="3" y="3" width="18" height="18" rx="2"/>
50
+ <path d="M3 9h18"/>
51
+ <path d="M9 21V9"/>
52
+ </svg>
53
+ Simulator
54
+ </button>
55
+ <button class="toggle-btn" data-view="methodology" onclick="selectView('methodology')">
56
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
57
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
58
+ <path d="M14 2v6h6"/>
59
+ <path d="M16 13H8"/>
60
+ <path d="M16 17H8"/>
61
+ <path d="M10 9H8"/>
62
+ </svg>
63
+ Methodology
64
+ </button>
65
+ </div>
66
  </div>
67
 
68
+ <!-- Simulation Type -->
69
+ <div class="sidebar-section simulation-view-only">
70
+ <div class="section-label">Strategy</div>
71
+ <div class="strategy-cards">
72
+ <button class="strategy-card active" data-sim="single" onclick="selectSimulation('single')">
73
+ <div class="strategy-icon">
74
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
75
+ <circle cx="12" cy="12" r="10"/>
76
+ <path d="M12 6v6l4 2"/>
77
+ </svg>
78
+ </div>
79
+ <div class="strategy-info">
80
+ <span class="strategy-name">Single Fund</span>
81
+ <span class="strategy-desc">All-in sequential betting</span>
82
+ </div>
83
+ </button>
84
+ <button class="strategy-card" data-sim="threshold" onclick="selectSimulation('threshold')">
85
+ <div class="strategy-icon">
86
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
87
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
88
+ <polyline points="22 4 12 14.01 9 11.01"/>
89
+ </svg>
90
+ </div>
91
+ <div class="strategy-info">
92
+ <span class="strategy-name">Target Return</span>
93
+ <span class="strategy-desc">Stop at profit goal</span>
94
+ </div>
95
+ </button>
96
+ <button class="strategy-card" data-sim="multi" onclick="selectSimulation('multi')">
97
+ <div class="strategy-icon">
98
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
99
+ <rect x="3" y="3" width="7" height="7"/>
100
+ <rect x="14" y="3" width="7" height="7"/>
101
+ <rect x="14" y="14" width="7" height="7"/>
102
+ <rect x="3" y="14" width="7" height="7"/>
103
+ </svg>
104
+ </div>
105
+ <div class="strategy-info">
106
+ <span class="strategy-name">Multi Fund</span>
107
+ <span class="strategy-desc">Diversified portfolio</span>
108
+ </div>
109
+ </button>
110
+ </div>
111
  </div>
 
112
 
113
+ <!-- Quick Info -->
114
+ <div class="sidebar-section simulation-view-only">
115
+ <div class="quick-stats">
116
+ <div class="quick-stat">
117
+ <span class="quick-stat-value" id="marketCount">4,265</span>
118
+ <span class="quick-stat-label">Markets</span>
119
+ </div>
120
+ <div class="quick-stat">
121
+ <span class="quick-stat-value">2025</span>
122
+ <span class="quick-stat-label">Dataset</span>
123
+ </div>
124
  </div>
125
+ </div>
126
+ </aside>
127
 
128
+ <!-- Content -->
129
+ <div class="content">
130
+ <!-- Simulation View -->
131
+ <div class="simulation-content" id="simulationContent">
132
+ <!-- Parameters Panel -->
133
+ <section class="panel parameters-panel">
134
+ <div class="panel-header">
135
+ <h2>
136
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
137
+ <circle cx="12" cy="12" r="3"/>
138
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
139
+ </svg>
140
+ Parameters
141
+ </h2>
142
  </div>
143
 
144
+ <!-- Hidden defaults -->
145
+ <input type="hidden" id="startingCapital" value="10000">
146
+ <input type="hidden" id="startDate" value="2025-01-01">
147
+ <input type="hidden" id="maxDuration" value="365">
148
+
149
+ <div class="params-grid">
150
+ <!-- Core Parameters -->
151
+ <div class="param-group">
152
+ <label class="param-label">
153
+ Simulations
154
+ <span class="param-hint" title="Number of Monte Carlo runs">?</span>
155
+ </label>
156
+ <div class="param-input-wrap">
157
+ <input type="number" id="numSimulations" class="param-input" value="100" min="10" max="100" step="10">
158
+ <span class="param-unit">runs</span>
159
+ </div>
160
+ </div>
161
 
162
+ <div class="param-group">
163
+ <label class="param-label">
164
+ Days Before
165
+ <span class="param-hint" title="Days before market closes to invest">?</span>
166
+ </label>
167
+ <div class="param-input-wrap">
168
+ <input type="number" id="daysBefore" class="param-input" value="1" min="1" max="7" step="1">
169
+ <span class="param-unit">days</span>
170
+ </div>
171
+ </div>
172
 
173
+ <div class="param-group">
174
+ <label class="param-label">
175
+ Min Prob @ 7d
176
+ <span class="param-hint" title="Minimum probability 7 days before resolution">?</span>
177
+ </label>
178
+ <div class="param-input-wrap">
179
+ <input type="number" id="minProb7d" class="param-input" value="90" min="50" max="99" step="1">
180
+ <span class="param-unit">%</span>
181
+ </div>
182
+ </div>
183
 
184
+ <div class="param-group">
185
+ <label class="param-label">
186
+ Min Prob @ Entry
187
+ <span class="param-hint" title="Minimum probability at investment time">?</span>
188
+ </label>
189
+ <div class="param-input-wrap">
190
+ <input type="number" id="minProbCurrent" class="param-input" value="90" min="50" max="99" step="1">
191
+ <span class="param-unit">%</span>
192
+ </div>
193
+ </div>
194
 
195
+ <div class="param-group">
196
+ <label class="param-label">
197
+ Trading Frequency
198
+ <span class="param-hint" title="How often to trade when opportunities exist">?</span>
199
+ </label>
200
+ <select id="investmentProbability" class="param-input param-select">
201
+ <option value="0.02">Conservative</option>
202
+ <option value="0.04" selected>Moderate</option>
203
+ <option value="0.06">Aggressive</option>
204
+ </select>
205
+ </div>
206
 
207
+ <!-- Threshold-specific -->
208
+ <div class="param-group threshold-only">
209
+ <label class="param-label">
210
+ Target Return
211
+ <span class="param-hint" title="Stop trading when this return is reached">?</span>
212
+ </label>
213
+ <select id="targetReturn" class="param-input param-select">
214
+ <option value="4.14">4.14% (Treasury)</option>
215
+ <option value="10.56">10.56% (NASDAQ)</option>
216
+ <option value="custom">Custom...</option>
217
+ </select>
218
+ <input type="number" id="customTarget" class="param-input custom-target-input" min="1" max="100" step="0.1" placeholder="Enter %">
219
+ </div>
220
 
221
+ <!-- Multi-fund specific -->
222
+ <div class="param-group multi-only">
223
+ <label class="param-label">
224
+ Number of Funds
225
+ <span class="param-hint" title="Split capital across independent funds">?</span>
226
+ </label>
227
+ <div class="param-input-wrap">
228
+ <input type="number" id="numFunds" class="param-input" value="5" min="2" max="10" step="1">
229
+ <span class="param-unit">funds</span>
230
+ </div>
231
+ </div>
232
  </div>
 
 
233
 
234
+ <!-- Run Button -->
235
+ <div class="run-container">
236
+ <button class="run-btn" onclick="runSimulation()" id="runBtn">
237
+ <svg class="run-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
238
+ <polygon points="5 3 19 12 5 21 5 3"/>
239
+ </svg>
240
+ <span class="run-text">Run Simulation</span>
241
+ <div class="run-spinner"></div>
242
+ </button>
243
+ <span class="run-estimate" id="estimatedTime">~5-10 seconds</span>
244
  </div>
245
+ </section>
246
+
247
+ <!-- Progress Bar -->
248
+ <section class="panel progress-panel" id="progressPanel">
249
+ <div class="progress-content">
250
+ <div class="progress-header">
251
+ <span id="progressText">Initializing...</span>
252
+ <span id="progressPercent">0%</span>
253
+ </div>
254
+ <div class="progress-track">
255
+ <div class="progress-fill" id="progressFill"></div>
256
+ </div>
257
  </div>
258
+ </section>
259
+
260
+ <!-- Results Section -->
261
+ <section class="results" id="resultsSection">
262
+ <!-- Key Metrics -->
263
+ <div class="metrics-row">
264
+ <div class="metric-card metric-primary">
265
+ <div class="metric-label">Average Return</div>
266
+ <div class="metric-value" id="avgReturn">--</div>
 
 
 
 
 
 
267
  </div>
268
+ <div class="metric-card">
269
+ <div class="metric-label">Median Return</div>
270
+ <div class="metric-value" id="medianReturn">--</div>
271
+ </div>
272
+ <div class="metric-card">
273
+ <div class="metric-label">Success Rate</div>
274
+ <div class="metric-value" id="successRate">--</div>
275
+ </div>
276
+ <div class="metric-card metric-risk">
277
+ <div class="metric-label">Bust Rate</div>
278
+ <div class="metric-value" id="bustRate">--</div>
 
 
279
  </div>
280
  </div>
281
 
282
+ <!-- Secondary Stats -->
283
+ <div class="stats-row">
284
+ <div class="stat-panel">
285
+ <h3>Risk Analysis</h3>
286
+ <div class="stat-list">
287
+ <div class="stat-item">
288
+ <span class="stat-label">Volatility</span>
289
+ <span class="stat-value" id="volatility">--</span>
290
+ </div>
291
+ <div class="stat-item">
292
+ <span class="stat-label">Max Drawdown</span>
293
+ <span class="stat-value" id="maxDrawdown">--</span>
294
+ </div>
295
+ <div class="stat-item">
296
+ <span class="stat-label">5th Percentile</span>
297
+ <span class="stat-value" id="percentile5">--</span>
298
+ </div>
299
+ <div class="stat-item">
300
+ <span class="stat-label">95th Percentile</span>
301
+ <span class="stat-value" id="percentile95">--</span>
302
+ </div>
303
  </div>
304
  </div>
 
305
 
306
+ <div class="stat-panel multi-stats">
 
307
  <h3>Portfolio Stats</h3>
308
+ <div class="stat-list">
309
+ <div class="stat-item">
310
+ <span class="stat-label">Surviving Funds</span>
311
+ <span class="stat-value" id="avgSurvivingFunds">--</span>
312
+ </div>
313
+ <div class="stat-item">
314
+ <span class="stat-label">Survivorship Rate</span>
315
+ <span class="stat-value" id="survivorshipRate">--</span>
316
+ </div>
317
+ <div class="stat-item">
318
+ <span class="stat-label">Diversification</span>
319
+ <span class="stat-value" id="diversificationBenefit">--</span>
320
+ </div>
321
  </div>
322
+ </div>
323
+
324
+ <div class="stat-panel threshold-stats">
325
+ <h3>Target Performance</h3>
326
+ <div class="stat-list">
327
+ <div class="stat-item">
328
+ <span class="stat-label">Target Reached</span>
329
+ <span class="stat-value" id="targetReached">--</span>
330
+ </div>
331
+ <div class="stat-item">
332
+ <span class="stat-label">Avg Time to Target</span>
333
+ <span class="stat-value" id="avgTimeToTarget">--</span>
334
+ </div>
335
+ <div class="stat-item">
336
+ <span class="stat-label">vs Never Stop</span>
337
+ <span class="stat-value" id="vsNeverStop">--</span>
338
+ </div>
339
  </div>
340
  </div>
341
  </div>
342
 
343
+ <!-- Charts -->
344
+ <div class="charts-row">
345
+ <div class="chart-panel">
346
+ <div class="chart-header">
347
+ <h3>Return Distribution</h3>
348
+ <button class="export-btn" onclick="exportResults()" id="exportBtn">
349
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
350
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
351
+ <polyline points="7 10 12 15 17 10"/>
352
+ <line x1="12" y1="15" x2="12" y2="3"/>
353
+ </svg>
354
+ Export
355
+ </button>
356
  </div>
357
+ <div class="chart-container">
358
+ <canvas id="returnChart"></canvas>
 
 
 
 
 
359
  </div>
360
  </div>
 
 
361
 
362
+ <div class="chart-panel">
363
+ <div class="chart-header">
364
+ <h3>Capital Evolution</h3>
365
+ </div>
366
+ <div class="chart-container">
367
+ <canvas id="capitalChart"></canvas>
368
+ </div>
369
  </div>
370
+
371
+ <div class="chart-panel multi-chart">
372
+ <div class="chart-header">
373
+ <h3>Fund Survivorship</h3>
374
+ </div>
375
+ <div class="chart-container">
376
+ <canvas id="survivorshipChart"></canvas>
377
+ </div>
378
  </div>
379
  </div>
380
+ </section>
381
+ </div>
382
 
383
+ <!-- Methodology View -->
384
+ <div class="methodology-content" id="methodologyContent">
385
+ <article class="article">
386
+ <header class="article-header">
387
+ <h1>Safe Choices</h1>
388
+ <p class="article-subtitle">Assessing the efficiency of "safe bets" on prediction markets</p>
389
+ </header>
390
+
391
+ <section class="article-section">
392
+ <div class="callout callout-intro">
393
+ <p>Imagine 50 hedge funds, each deploying $100 on a "State Earthquake Trade." Each fund bets on their state NOT being hit by an earthquake (10% annual probability), earning 1.11x if correct.</p>
394
+ <p>After 10 years across 100,000 simulations: <strong>17.42 funds survive on average</strong> (range: 4-32). These survivors produced 11% annual returns for a decade. In an efficient market, luck becomes the differentiating factor between investment legends and bankruptcy.</p>
395
  </div>
396
+ </section>
397
+
398
+ <section class="article-section">
399
+ <h2>Research Questions</h2>
400
+ <p>We explore the efficiency of "safe choices" (markets trading at 90+ cents before resolution):</p>
401
+ <ul class="article-list">
402
+ <li>How often will traders making random walks across these markets go bust?</li>
403
+ <li>How often will they beat traditional benchmarks?</li>
404
+ <li>Does stopping at a return threshold (e.g., 12%) improve outcomes?</li>
405
+ <li>Does splitting capital across multiple funds improve survivorship?</li>
406
+ </ul>
407
+ <div class="callout callout-hypothesis">
408
+ <strong>Hypothesis:</strong> Inefficiencies in "safe" markets provide opportunities for positive expected value trades exceeding traditional benchmarks like the risk-free rate.
409
  </div>
410
+ </section>
411
+
412
+ <section class="article-section">
413
+ <h2>Dataset</h2>
414
+ <p>Data collected via Polymarket's Gamma API and CLOB API, filtering out high-variability categories:</p>
415
+ <div class="tag-group">
416
+ <span class="tag tag-excluded">Sports</span>
417
+ <span class="tag tag-excluded">Esports</span>
418
+ <span class="tag tag-excluded">Crypto Prices</span>
419
+ <span class="tag tag-excluded">Weather</span>
420
  </div>
421
+ <p>For remaining markets, we log probability snapshots from 7 days to 1 day before resolution. The dataset covers 2024-2025 and is available on <a href="https://www.kaggle.com/datasets/dhruvgup/polymarket-closed-2025-markets-7-day-price-history/data" target="_blank" rel="noopener">Kaggle</a>.</p>
422
+ </section>
423
+
424
+ <section class="article-section">
425
+ <h2>Simulation Model</h2>
426
+ <div class="model-steps">
427
+ <div class="model-step">
428
+ <span class="step-num">1</span>
429
+ <span class="step-text">Each day, decide to invest with probability <strong>alpha</strong> (trading frequency)</span>
430
+ </div>
431
+ <div class="model-step">
432
+ <span class="step-num">2</span>
433
+ <span class="step-text">Select uniformly at random from eligible markets meeting thresholds</span>
434
+ </div>
435
+ <div class="model-step">
436
+ <span class="step-num">3</span>
437
+ <span class="step-text">Remain locked until resolution, then reinvest winnings</span>
438
+ </div>
439
+ <div class="model-step">
440
+ <span class="step-num">4</span>
441
+ <span class="step-text">Continue until: bust, target reached, or simulation ends</span>
442
+ </div>
443
  </div>
444
+ </section>
445
+
446
+ <section class="article-section">
447
+ <h2>Strategies</h2>
448
+ <div class="strategy-explainer">
449
+ <div class="strategy-box">
450
+ <h3>Single Fund</h3>
451
+ <p>Deploy all capital sequentially across safe markets, reinvesting all winnings. High risk, high reward.</p>
452
+ </div>
453
+ <div class="strategy-box">
454
+ <h3>Target Return</h3>
455
+ <p>Same as Single Fund but stop trading once a target return (e.g., Treasury rate or NASDAQ average) is reached.</p>
456
+ </div>
457
+ <div class="strategy-box">
458
+ <h3>Multi Fund</h3>
459
+ <p>Split capital into N independent funds. Tests whether diversification improves survivorship rates.</p>
460
+ </div>
461
+ </div>
462
+ </section>
463
+
464
+ <section class="article-section">
465
+ <h2>Technical Notes</h2>
466
+ <ul class="article-list">
467
+ <li>Uses actual Polymarket data filtered for 2025+ resolution</li>
468
+ <li>Vectorized operations for performance (~1000 sims in 10-20s)</li>
469
+ <li>Binary outcomes: win = 1/probability return, loss = total loss</li>
470
+ <li>Reproducible results via random seeds</li>
471
+ </ul>
472
+ </section>
473
+
474
+ <section class="article-section">
475
+ <h2>Limitations</h2>
476
+ <p>At $10,000 scale distributed across funds, liquidity isn't a concern. However, scaling this strategy significantly would face real liquidity constraints and potential market impact.</p>
477
+ </section>
478
+ </article>
479
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  </div>
481
+ </main>
482
 
483
+ <script src="/static/script.js?v=10"></script>
484
  </body>
485
  </html>
static/script.js CHANGED
@@ -1,213 +1,199 @@
1
- // Global variables
 
 
2
  let currentSimType = 'single';
 
3
  let simulationResults = null;
4
  let charts = {};
5
 
6
- // Initialize the application
7
- document.addEventListener('DOMContentLoaded', function() {
8
- initializeDefaults();
 
 
 
9
  setupEventListeners();
10
  updateEstimatedTime();
11
  initializeEmptyCharts();
12
- });
13
-
14
- // Initialize default values and constraints
15
- function initializeDefaults() {
16
- // Set minimum start date and default to 2025-01-01
17
- const startDateInput = document.getElementById('startDate');
18
- const defaultDate = new Date('2025-01-01');
19
-
20
- startDateInput.value = defaultDate.toISOString().split('T')[0];
21
- startDateInput.min = '2025-01-01';
22
-
23
- // Initialize target return dropdown handler
24
- document.getElementById('targetReturn').addEventListener('change', function() {
25
- const customInput = document.getElementById('customTarget');
26
- if (this.value === 'custom') {
27
- customInput.style.display = 'block';
28
- customInput.value = '12.0';
29
- } else {
30
- customInput.style.display = 'none';
31
- }
32
- });
33
  }
34
 
35
- // Setup event listeners
36
  function setupEventListeners() {
37
- // Input validation
38
- document.getElementById('numSimulations').addEventListener('input', function() {
39
- this.value = Math.min(Math.max(parseInt(this.value) || 10, 10), 100);
 
40
  updateEstimatedTime();
41
  });
42
-
43
- document.getElementById('numFunds').addEventListener('input', function() {
44
- this.value = Math.min(Math.max(parseInt(this.value) || 2, 2), 10);
 
 
45
  });
46
-
47
- // Update estimated time when parameters change
48
- const timeInputs = ['numSimulations', 'maxDuration', 'numFunds'];
49
- timeInputs.forEach(id => {
50
- document.getElementById(id).addEventListener('input', updateEstimatedTime);
 
 
 
 
 
 
51
  });
52
-
53
- // Real-time validation
54
- const inputs = document.querySelectorAll('.control-input');
55
- inputs.forEach(input => {
56
  input.addEventListener('input', validateInputs);
57
  });
58
  }
59
 
60
- // Current view state
61
- let currentView = 'simulation';
62
-
63
- // Select view (simulation or methodology)
64
  function selectView(view) {
65
  currentView = view;
66
-
67
- // Update view tab appearance
68
- document.querySelectorAll('.view-tab').forEach(tab => {
69
- tab.classList.remove('active');
70
  });
71
- document.querySelector(`[data-view="${view}"]`).classList.add('active');
72
-
73
- // Show/hide content areas
74
- const contentArea = document.querySelector('.content-area');
75
- const methodologyArea = document.querySelector('.methodology-area');
76
-
77
  if (view === 'simulation') {
78
- contentArea.style.display = 'flex';
79
- methodologyArea.style.display = 'none';
80
  document.querySelectorAll('.simulation-view-only').forEach(el => {
81
  el.classList.remove('hidden');
82
  });
83
  } else {
84
- contentArea.style.display = 'none';
85
- methodologyArea.style.display = 'block';
86
  document.querySelectorAll('.simulation-view-only').forEach(el => {
87
  el.classList.add('hidden');
88
  });
89
  }
90
  }
91
 
92
- // Select simulation type
93
  function selectSimulation(type) {
94
  currentSimType = type;
95
-
96
- // Update tab appearance
97
- document.querySelectorAll('.sim-tab').forEach(tab => {
98
- tab.classList.remove('active');
99
  });
100
- document.querySelector(`[data-sim="${type}"]`).classList.add('active');
101
-
102
- // Show/hide conditional controls using classes
103
  document.querySelectorAll('.threshold-only').forEach(el => {
104
  el.classList.toggle('visible', type === 'threshold');
105
  });
106
-
107
  document.querySelectorAll('.multi-only').forEach(el => {
108
  el.classList.toggle('visible', type === 'multi');
109
  });
110
-
111
- updateEstimatedTime();
112
- }
113
 
114
- // Update estimated execution time
115
- function updateEstimatedTime() {
116
- const numSims = parseInt(document.getElementById('numSimulations').value) || 100;
117
- const maxDuration = parseInt(document.getElementById('maxDuration').value) || 365;
118
- const numFunds = currentSimType === 'multi' ? (parseInt(document.getElementById('numFunds').value) || 5) : 1;
119
-
120
- // Rough estimation based on complexity
121
- let baseTime = numSims * 0.05; // ~50ms per simulation
122
- baseTime *= (maxDuration / 365); // Scale by duration
123
- if (currentSimType === 'multi') {
124
- baseTime *= Math.sqrt(numFunds); // Multi-fund overhead
125
- }
126
-
127
- const estimatedSeconds = Math.max(2, Math.ceil(baseTime));
128
-
129
- let timeText = '';
130
- if (estimatedSeconds < 60) {
131
- timeText = `~${estimatedSeconds} seconds`;
132
- } else {
133
- const minutes = Math.ceil(estimatedSeconds / 60);
134
- timeText = `~${minutes} minute${minutes > 1 ? 's' : ''}`;
135
- }
136
-
137
- document.getElementById('estimatedTime').textContent = `Estimated time: ${timeText}`;
138
  }
139
 
140
- // Validate all inputs
141
  function validateInputs() {
142
  const errors = [];
143
-
144
- // Check required fields
145
- const startingCapital = parseFloat(document.getElementById('startingCapital').value);
146
- if (!startingCapital || startingCapital < 1000) {
147
- errors.push('Starting capital must be at least $1,000');
148
- }
149
-
150
- const startDate = new Date(document.getElementById('startDate').value);
151
- const minDate = new Date('2025-01-01');
152
- if (startDate < minDate) {
153
- errors.push('Start date must be 2025-01-01 or later');
154
- }
155
-
156
  const minProb7d = parseFloat(document.getElementById('minProb7d').value);
157
  const minProbCurrent = parseFloat(document.getElementById('minProbCurrent').value);
 
158
  if (minProb7d < 50 || minProb7d > 99) {
159
  errors.push('7-day probability must be between 50% and 99%');
160
  }
161
  if (minProbCurrent < 50 || minProbCurrent > 99) {
162
  errors.push('Current probability must be between 50% and 99%');
163
  }
164
-
165
- // Update run button state
166
  const runBtn = document.getElementById('runBtn');
167
  runBtn.disabled = errors.length > 0;
168
-
169
  return errors.length === 0;
170
  }
171
 
172
- // Main simulation runner
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  async function runSimulation() {
174
  if (!validateInputs()) return;
175
-
176
- // Get parameters
177
  const params = getSimulationParameters();
178
-
179
- // Update UI for running state
180
  showProgress();
181
  disableControls();
182
-
183
  try {
184
- // Call the backend API
185
  const results = await callSimulationAPI(params);
186
-
187
- // Store results and display
188
  simulationResults = results;
189
  displayResults(results);
190
-
191
  } catch (error) {
192
  console.error('Simulation error:', error);
193
- alert('An error occurred during simulation: ' + (error.message || 'Unknown error'));
194
  } finally {
195
  hideProgress();
196
  enableControls();
197
  }
198
  }
199
 
200
- // Call the backend API for real simulations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  async function callSimulationAPI(params) {
202
  const progressFill = document.getElementById('progressFill');
203
  const progressPercent = document.getElementById('progressPercent');
204
  const progressText = document.getElementById('progressText');
205
-
206
- progressText.textContent = 'Sending request to backend...';
207
  progressFill.style.width = '10%';
208
  progressPercent.textContent = '10%';
209
-
210
- // Prepare request body
211
  const requestBody = {
212
  simType: params.simType,
213
  startingCapital: params.startingCapital,
@@ -219,362 +205,161 @@ async function callSimulationAPI(params) {
219
  daysBefore: params.daysBefore,
220
  investmentProbability: params.investmentProbability
221
  };
222
-
223
- if (params.simType === 'threshold' && params.targetReturn !== undefined) {
224
  requestBody.targetReturn = params.targetReturn;
225
  }
226
-
227
- if (params.simType === 'multi' && params.numFunds !== undefined) {
228
  requestBody.numFunds = params.numFunds;
229
  }
230
-
231
- // Make API call
 
 
 
232
  const response = await fetch('/api/simulate', {
233
  method: 'POST',
234
- headers: {
235
- 'Content-Type': 'application/json',
236
- },
237
  body: JSON.stringify(requestBody)
238
  });
239
-
240
  if (!response.ok) {
241
  const errorData = await response.json().catch(() => ({ detail: response.statusText }));
242
- throw new Error(errorData.detail || `HTTP ${response.status}: ${response.statusText}`);
243
  }
244
-
245
  progressText.textContent = 'Processing results...';
246
- progressFill.style.width = '90%';
247
- progressPercent.textContent = '90%';
248
-
249
  const results = await response.json();
250
-
251
  progressFill.style.width = '100%';
252
  progressPercent.textContent = '100%';
253
  progressText.textContent = 'Complete!';
254
-
255
- return results;
256
- }
257
-
258
- // Get simulation parameters from UI
259
- function getSimulationParameters() {
260
- const params = {
261
- simType: currentSimType,
262
- startingCapital: parseFloat(document.getElementById('startingCapital').value),
263
- numSimulations: parseInt(document.getElementById('numSimulations').value),
264
- startDate: document.getElementById('startDate').value,
265
- maxDuration: parseInt(document.getElementById('maxDuration').value),
266
- minProb7d: parseFloat(document.getElementById('minProb7d').value) / 100,
267
- minProbCurrent: parseFloat(document.getElementById('minProbCurrent').value) / 100,
268
- daysBefore: parseInt(document.getElementById('daysBefore').value),
269
- investmentProbability: parseFloat(document.getElementById('investmentProbability').value)
270
- };
271
-
272
- // Type-specific parameters
273
- if (currentSimType === 'threshold') {
274
- const targetSelect = document.getElementById('targetReturn').value;
275
- if (targetSelect === 'custom') {
276
- params.targetReturn = parseFloat(document.getElementById('customTarget').value) / 100;
277
- } else {
278
- params.targetReturn = parseFloat(targetSelect) / 100;
279
- }
280
- }
281
-
282
- if (currentSimType === 'multi') {
283
- params.numFunds = parseInt(document.getElementById('numFunds').value);
284
- }
285
-
286
- return params;
287
- }
288
 
289
- // Legacy function - kept for reference but not used when API is available
290
- async function simulateComputation(params) {
291
- // This function is replaced by callSimulationAPI
292
- // Kept for fallback if needed
293
- return await callSimulationAPI(params);
294
  }
295
 
296
- // Generate a realistic simulation run
297
- function generateSimulationRun(params, seed) {
298
- const rng = new SimpleRNG(seed + 12345);
299
-
300
- const run = {
301
- finalCapital: 0,
302
- totalReturn: 0,
303
- numTrades: 0,
304
- wentBust: false,
305
- reachedTarget: false,
306
- simulationDays: 0
307
- };
308
-
309
- if (params.simType === 'multi') {
310
- run.survivingFunds = 0;
311
- run.fundResults = [];
312
-
313
- for (let f = 0; f < params.numFunds; f++) {
314
- const fundRun = generateSingleFundRun(params, rng.next());
315
- run.fundResults.push(fundRun);
316
- if (!fundRun.wentBust) run.survivingFunds++;
317
- }
318
-
319
- run.finalCapital = run.fundResults.reduce((sum, fund) => sum + fund.finalCapital, 0);
320
- run.totalReturn = (run.finalCapital - params.startingCapital) / params.startingCapital;
321
- run.numTrades = run.fundResults.reduce((sum, fund) => sum + fund.numTrades, 0);
322
- run.wentBust = run.survivingFunds === 0;
323
- } else {
324
- const singleRun = generateSingleFundRun(params, seed);
325
- Object.assign(run, singleRun);
326
- }
327
-
328
- return run;
329
- }
330
 
331
- // Generate a single fund run with realistic market behavior
332
- function generateSingleFundRun(params, seed) {
333
- const rng = new SimpleRNG(seed);
334
- let capital = params.simType === 'multi' ? params.startingCapital / params.numFunds : params.startingCapital;
335
-
336
- const run = {
337
- finalCapital: capital,
338
- totalReturn: 0,
339
- numTrades: 0,
340
- wentBust: false,
341
- reachedTarget: false,
342
- simulationDays: 0,
343
- capitalHistory: [capital]
344
- };
345
-
346
- const maxTrades = Math.floor(params.maxDuration / 7) + rng.nextInt(5, 20);
347
- let day = 0;
348
-
349
- for (let trade = 0; trade < maxTrades && capital > 0 && day < params.maxDuration; trade++) {
350
- // Check target return for threshold simulations
351
- if (params.simType === 'threshold' && params.targetReturn) {
352
- const currentReturn = (capital - (params.startingCapital / (params.numFunds || 1))) / (params.startingCapital / (params.numFunds || 1));
353
- if (currentReturn >= params.targetReturn) {
354
- run.reachedTarget = true;
355
- break;
356
- }
357
- }
358
-
359
- // Market probability based on thresholds (realistic distribution)
360
- const marketProb = Math.max(params.minProbCurrent,
361
- params.minProbCurrent + rng.nextGaussian() * 0.05);
362
-
363
- // Win probability (slightly higher than market prob to simulate edge)
364
- const winProb = Math.min(0.99, marketProb + 0.01 + rng.nextGaussian() * 0.02);
365
-
366
- const won = rng.nextFloat() < winProb;
367
-
368
- if (won) {
369
- capital = capital / marketProb; // Return = 1/probability
370
- } else {
371
- capital = 0; // Total loss
372
- run.wentBust = true;
373
- break;
374
- }
375
-
376
- run.numTrades++;
377
- run.capitalHistory.push(capital);
378
-
379
- // Advance time (realistic trading frequency)
380
- day += rng.nextInt(1, 14);
381
-
382
- // Ending factor check
383
- const endingFactor = 0.05 + (day * 0.001);
384
- if (rng.nextFloat() < endingFactor) break;
385
- }
386
-
387
- run.finalCapital = capital;
388
- run.simulationDays = day;
389
- const initialCapital = params.simType === 'multi' ? params.startingCapital / params.numFunds : params.startingCapital;
390
- run.totalReturn = (capital - initialCapital) / initialCapital;
391
-
392
- return run;
393
- }
394
 
395
- // Calculate summary statistics
396
- function calculateSummaryStats(runs, params) {
397
- const returns = runs.map(r => r.totalReturn);
398
- const capitals = runs.map(r => r.finalCapital);
399
- const trades = runs.map(r => r.numTrades);
400
-
401
- const summary = {
402
- avgReturn: mean(returns),
403
- medianReturn: median(returns),
404
- returnVolatility: standardDeviation(returns),
405
- avgFinalCapital: mean(capitals),
406
- medianFinalCapital: median(capitals),
407
- bustRate: runs.filter(r => r.wentBust).length / runs.length,
408
- positiveReturnRate: returns.filter(r => r > 0).length / returns.length,
409
- avgTrades: mean(trades),
410
-
411
- // Percentiles
412
- return5th: percentile(returns, 5),
413
- return95th: percentile(returns, 95),
414
-
415
- // Max drawdown approximation
416
- maxDrawdown: Math.abs(Math.min(...returns))
417
- };
418
-
419
- // Type-specific stats
420
- if (params.simType === 'threshold' && params.targetReturn) {
421
- const reachedTarget = runs.filter(r => r.reachedTarget).length;
422
- summary.targetReachedRate = reachedTarget / runs.length;
423
- summary.avgTimeToTarget = mean(runs.filter(r => r.reachedTarget).map(r => r.simulationDays)) || 0;
424
- }
425
-
426
- if (params.simType === 'multi') {
427
- const survivingFunds = runs.map(r => r.survivingFunds);
428
- summary.avgSurvivingFunds = mean(survivingFunds);
429
- summary.survivorshipRate = mean(survivingFunds) / params.numFunds;
430
- summary.portfolioBustRate = runs.filter(r => r.survivingFunds === 0).length / runs.length;
431
- }
432
-
433
- return summary;
434
- }
435
 
436
- // Display results in UI
437
- function displayResults(results) {
438
- const { summary, parameters } = results;
439
-
440
- // Show results section (always visible, just update content)
441
- document.getElementById('resultsSection').style.display = 'block';
442
- document.getElementById('resultsSection').scrollIntoView({ behavior: 'smooth' });
443
-
444
- // Update basic statistics
445
- document.getElementById('avgReturn').textContent = formatPercentage(summary.avgReturn);
446
- document.getElementById('avgReturn').className = `stat-value ${getReturnClass(summary.avgReturn)}`;
447
-
448
- document.getElementById('medianReturn').textContent = formatPercentage(summary.medianReturn);
449
- document.getElementById('medianReturn').className = `stat-value ${getReturnClass(summary.medianReturn)}`;
450
-
451
- document.getElementById('successRate').textContent = formatPercentage(summary.positiveReturnRate);
452
-
453
- document.getElementById('bustRate').textContent = formatPercentage(summary.bustRate);
454
- document.getElementById('bustRate').className = `stat-value ${summary.bustRate > 0.1 ? 'negative' : 'positive'}`;
455
-
456
  document.getElementById('volatility').textContent = formatPercentage(summary.returnVolatility);
457
  document.getElementById('maxDrawdown').textContent = formatPercentage(summary.maxDrawdown);
458
-
459
- // Type-specific statistics
 
 
460
  if (parameters.simType === 'multi') {
461
  document.querySelectorAll('.multi-stats, .multi-chart').forEach(el => {
462
  el.classList.add('visible');
463
  });
464
-
465
- document.getElementById('avgSurvivingFunds').textContent =
466
- `${summary.avgSurvivingFunds.toFixed(1)} / ${parameters.numFunds}`;
467
- document.getElementById('survivorshipRate').textContent = formatPercentage(summary.survivorshipRate);
468
-
469
- // Simple diversification benefit calculation
470
- const diversificationBenefit = summary.returnVolatility < 0.3 ? 'Positive' : 'Limited';
471
- document.getElementById('diversificationBenefit').textContent = diversificationBenefit;
472
  } else {
473
  document.querySelectorAll('.multi-stats, .multi-chart').forEach(el => {
474
  el.classList.remove('visible');
475
  });
476
  }
477
-
478
  if (parameters.simType === 'threshold') {
479
  document.querySelectorAll('.threshold-stats').forEach(el => {
480
  el.classList.add('visible');
481
  });
482
-
483
  document.getElementById('targetReached').textContent = formatPercentage(summary.targetReachedRate || 0);
484
- document.getElementById('avgTimeToTarget').textContent =
485
  summary.avgTimeToTarget ? `${Math.round(summary.avgTimeToTarget)} days` : 'N/A';
486
- document.getElementById('vsNeverStop').textContent =
487
- summary.targetReachedRate > 0.5 ? 'Better' : 'Similar';
488
  } else {
489
  document.querySelectorAll('.threshold-stats').forEach(el => {
490
  el.classList.remove('visible');
491
  });
492
  }
493
-
494
  // Generate charts
495
  generateCharts(results);
496
  }
497
 
498
- // Initialize empty charts on page load
 
 
 
 
 
 
 
 
 
 
 
 
 
 
499
  function initializeEmptyCharts() {
500
- // Return distribution chart
501
- charts.return = createEmptyReturnChart();
502
-
503
- // Capital evolution chart
504
- charts.capital = createEmptyCapitalChart();
505
-
506
- // Survivorship chart (hidden initially)
507
- charts.survivorship = createEmptySurvivorshipChart();
 
 
 
 
508
  }
509
 
510
- // Generate all charts
511
  function generateCharts(results) {
512
- const { runs, summary, parameters } = results;
513
-
514
  // Destroy existing charts
515
- Object.values(charts).forEach(chart => {
516
- if (chart) chart.destroy();
517
- });
518
  charts = {};
519
-
520
- // Return distribution histogram
521
  charts.return = createReturnDistributionChart(runs);
522
-
523
- // Capital evolution (sample of runs)
524
  charts.capital = createCapitalEvolutionChart(runs);
525
-
526
- // Multi-fund specific chart
527
  if (parameters.simType === 'multi') {
528
  charts.survivorship = createSurvivorshipChart(runs, parameters.numFunds);
529
  } else {
530
- charts.survivorship = createEmptySurvivorshipChart();
531
  }
532
  }
533
 
534
- // Create empty return chart with default range
535
- function createEmptyReturnChart() {
536
- const ctx = document.getElementById('returnChart').getContext('2d');
537
- const defaultBins = [];
538
- for (let i = -100; i <= 100; i += 2) {
539
- defaultBins.push(i);
540
- }
541
-
542
- return new Chart(ctx, {
543
- type: 'bar',
544
- data: {
545
- labels: defaultBins.map(b => `${b}%`),
546
- datasets: [{
547
- label: 'Frequency',
548
- data: new Array(defaultBins.length).fill(0),
549
- backgroundColor: 'rgba(86, 175, 226, 0.3)',
550
- borderColor: '#56AFE2',
551
- borderWidth: 1
552
- }]
553
- },
554
- options: getChartOptions('Return (%)', 'Frequency')
555
- });
556
- }
557
-
558
- // Create return distribution chart with smart 2% bins
559
  function createReturnDistributionChart(runs) {
560
  const ctx = document.getElementById('returnChart').getContext('2d');
561
  const returns = runs.map(r => r.totalReturn * 100);
562
-
563
- // Calculate smart range based on data
564
  const minReturn = Math.min(...returns);
565
  const maxReturn = Math.max(...returns);
566
-
567
- // Round to nearest 10% for cleaner bounds, with padding
568
  const binStart = Math.floor(minReturn / 10) * 10;
569
- const binEnd = Math.min(Math.ceil(maxReturn / 10) * 10, 200); // Cap at 200%
570
-
571
- // Generate 2% bins within the smart range
572
  const bins = [];
573
- for (let i = binStart; i <= binEnd; i += 2) {
574
- bins.push(i);
575
- }
576
-
577
- // Count returns in bins
578
  const binCounts = new Array(bins.length).fill(0);
579
  returns.forEach(ret => {
580
  for (let i = 0; i < bins.length - 1; i++) {
@@ -583,12 +368,9 @@ function createReturnDistributionChart(runs) {
583
  break;
584
  }
585
  }
586
- // Handle values at or above the last bin
587
- if (ret >= bins[bins.length - 1]) {
588
- binCounts[bins.length - 1]++;
589
- }
590
  });
591
-
592
  return new Chart(ctx, {
593
  type: 'bar',
594
  data: {
@@ -596,112 +378,69 @@ function createReturnDistributionChart(runs) {
596
  datasets: [{
597
  label: 'Frequency',
598
  data: binCounts,
599
- backgroundColor: 'rgba(86, 175, 226, 0.3)',
600
- borderColor: '#56AFE2',
601
- borderWidth: 1
 
602
  }]
603
  },
604
- options: getChartOptions('Return (%)', 'Frequency')
605
- });
606
- }
607
-
608
- // Create empty capital evolution chart
609
- function createEmptyCapitalChart() {
610
- const ctx = document.getElementById('capitalChart').getContext('2d');
611
- return new Chart(ctx, {
612
- type: 'line',
613
- data: {
614
- labels: [],
615
- datasets: []
616
- },
617
- options: getLineChartOptions('Trade Number', 'Capital ($)')
618
  });
619
  }
620
 
621
- // Create capital evolution chart
622
  function createCapitalEvolutionChart(runs) {
623
  const ctx = document.getElementById('capitalChart').getContext('2d');
624
-
625
- // Distinct color palette for capital evolution
626
  const colors = [
627
- { border: '#56AFE2', bg: 'rgba(86, 175, 226, 0.15)' }, // Cyan blue
628
- { border: '#10B981', bg: 'rgba(16, 185, 129, 0.15)' }, // Green
629
- { border: '#F59E0B', bg: 'rgba(245, 158, 11, 0.15)' }, // Amber
630
- { border: '#EF4444', bg: 'rgba(239, 68, 68, 0.15)' }, // Red
631
- { border: '#A855F7', bg: 'rgba(168, 85, 247, 0.15)' } // Purple
632
  ];
633
-
634
- // Sample up to 5 runs
635
  const sampleRuns = runs.slice(0, 5);
636
  const datasets = sampleRuns.map((run, i) => {
637
- // Use capitalHistory if available
638
  let data = [];
639
- if (run.capitalHistory && run.capitalHistory.length > 0) {
640
- // Handle both array of numbers and array of objects
641
- data = run.capitalHistory.map((item, idx) => {
642
- const capital = typeof item === 'object' ? item.capital : item;
643
- const day = typeof item === 'object' ? item.day : idx;
644
- return { x: day, y: capital };
645
- });
646
  } else {
647
- // Fallback: just show final capital with start and end
648
- data = [
649
- { x: 0, y: 10000 }, // Starting capital
650
- { x: 1, y: run.finalCapital }
651
- ];
652
  }
653
-
654
  return {
655
  label: `Run ${i + 1}`,
656
  data: data,
657
- borderColor: colors[i % colors.length].border,
658
- backgroundColor: colors[i % colors.length].bg,
659
  borderWidth: 2,
660
  fill: false,
661
- tension: 0.3, // Smooth curves
662
- pointRadius: 0 // Hide points for cleaner look
663
  };
664
  });
665
-
666
  return new Chart(ctx, {
667
  type: 'line',
668
  data: { datasets },
669
- options: getLineChartOptions('Trade Number', 'Capital ($)')
670
- });
671
- }
672
-
673
- // Create empty survivorship chart
674
- function createEmptySurvivorshipChart() {
675
- const ctx = document.getElementById('survivorshipChart').getContext('2d');
676
- return new Chart(ctx, {
677
- type: 'bar',
678
- data: {
679
- labels: [],
680
- datasets: [{
681
- label: 'Frequency',
682
- data: [],
683
- backgroundColor: 'rgba(135, 191, 255, 0.15)',
684
- borderColor: '#87BFFF',
685
- borderWidth: 1
686
- }]
687
- },
688
- options: getChartOptions('Surviving Funds', 'Frequency')
689
  });
690
  }
691
 
692
- // Create survivorship chart for multi-fund
693
  function createSurvivorshipChart(runs, numFunds) {
694
  const ctx = document.getElementById('survivorshipChart').getContext('2d');
695
  const survivingCounts = runs.map(r => r.survivingFunds);
696
-
697
- // Preset bins: 0 to numFunds
698
  const labels = [];
699
  const data = [];
700
  for (let i = 0; i <= numFunds; i++) {
701
  labels.push(i.toString());
702
  data.push(survivingCounts.filter(c => c === i).length);
703
  }
704
-
705
  return new Chart(ctx, {
706
  type: 'bar',
707
  data: {
@@ -709,178 +448,105 @@ function createSurvivorshipChart(runs, numFunds) {
709
  datasets: [{
710
  label: 'Frequency',
711
  data: data,
712
- backgroundColor: 'rgba(135, 191, 255, 0.15)',
713
- borderColor: '#87BFFF',
714
- borderWidth: 1
 
715
  }]
716
  },
717
- options: getChartOptions('Surviving Funds', 'Frequency')
718
  });
719
  }
720
 
721
- // Helper function for chart options (bar charts)
722
- function getChartOptions(xLabel, yLabel) {
723
- return {
724
- responsive: true,
725
- maintainAspectRatio: false,
726
- plugins: {
727
- legend: { display: false }
728
- },
729
- scales: {
730
- x: {
731
- title: {
732
- display: true,
733
- text: xLabel,
734
- color: '#87BFFF',
735
- font: { size: 12 }
736
- },
737
- grid: {
738
- color: 'rgba(135, 191, 255, 0.1)',
739
- drawBorder: false
740
- },
741
- ticks: {
742
- color: '#87BFFF',
743
- font: { size: 11 }
744
- }
745
- },
746
- y: {
747
- title: {
748
- display: true,
749
- text: yLabel,
750
- color: '#87BFFF',
751
- font: { size: 12 }
752
- },
753
- grid: {
754
- color: 'rgba(135, 191, 255, 0.1)',
755
- drawBorder: false
756
- },
757
- ticks: {
758
- color: '#87BFFF',
759
- font: { size: 11 }
760
- }
761
- }
762
- }
763
- };
764
- }
765
-
766
- // Helper function for line chart options
767
- function getLineChartOptions(xLabel, yLabel) {
768
- return {
769
  responsive: true,
770
  maintainAspectRatio: false,
771
  plugins: {
772
- legend: {
773
- display: true,
774
  labels: {
775
- color: '#87BFFF',
776
  font: { size: 11 },
777
  usePointStyle: true,
778
- padding: 12
779
  }
780
  }
781
  },
782
  scales: {
783
  x: {
784
- type: 'linear',
785
- title: {
786
- display: true,
787
- text: xLabel,
788
- color: '#87BFFF',
789
- font: { size: 12 }
790
- },
791
- grid: {
792
- color: 'rgba(135, 191, 255, 0.1)',
793
- drawBorder: false
794
- },
795
- ticks: {
796
- color: '#87BFFF',
797
- font: { size: 11 }
798
- }
799
  },
800
  y: {
801
- title: {
802
- display: true,
803
- text: yLabel,
804
- color: '#87BFFF',
805
- font: { size: 12 }
806
- },
807
- grid: {
808
- color: 'rgba(135, 191, 255, 0.1)',
809
- drawBorder: false
810
- },
811
- ticks: {
812
- color: '#87BFFF',
813
- font: { size: 11 }
814
- }
815
  }
816
  }
817
  };
 
 
 
 
 
 
818
  }
819
 
820
- // Export results
821
  function exportResults() {
822
  if (!simulationResults) return;
823
-
824
  const data = {
825
  timestamp: new Date().toISOString(),
826
  parameters: simulationResults.parameters,
827
  summary: simulationResults.summary,
828
  runs: simulationResults.runs
829
  };
830
-
831
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
832
  const url = URL.createObjectURL(blob);
833
-
834
  const a = document.createElement('a');
835
  a.href = url;
836
- a.download = `safe_choices_simulation_${Date.now()}.json`;
837
  document.body.appendChild(a);
838
  a.click();
839
  document.body.removeChild(a);
840
  URL.revokeObjectURL(url);
841
  }
842
 
843
- // UI control functions
844
  function showProgress() {
845
- document.querySelector('.progress-section').style.display = 'block';
846
  document.getElementById('progressFill').style.width = '0%';
847
  }
848
 
849
  function hideProgress() {
850
  setTimeout(() => {
851
- document.querySelector('.progress-section').style.display = 'none';
852
  }, 500);
853
  }
854
 
855
  function disableControls() {
856
  const runBtn = document.getElementById('runBtn');
857
  runBtn.disabled = true;
 
858
  document.querySelector('.run-text').textContent = 'Running...';
859
- document.querySelector('.run-spinner').style.display = 'inline-block';
860
  }
861
 
862
  function enableControls() {
863
  const runBtn = document.getElementById('runBtn');
864
  runBtn.disabled = false;
 
865
  document.querySelector('.run-text').textContent = 'Run Simulation';
866
- document.querySelector('.run-spinner').style.display = 'none';
867
- }
868
-
869
- // Utility functions
870
- function sleep(ms) {
871
- return new Promise(resolve => setTimeout(resolve, ms));
872
  }
873
 
874
  function formatPercentage(value) {
 
875
  return `${(value * 100).toFixed(1)}%`;
876
  }
877
 
878
- function getReturnClass(value) {
879
- if (value > 0.02) return 'positive';
880
- if (value < -0.02) return 'negative';
881
- return 'neutral';
882
- }
883
-
884
  function mean(arr) {
885
  return arr.reduce((a, b) => a + b, 0) / arr.length;
886
  }
@@ -893,8 +559,7 @@ function median(arr) {
893
 
894
  function standardDeviation(arr) {
895
  const avg = mean(arr);
896
- const squareDiffs = arr.map(value => Math.pow(value - avg, 2));
897
- return Math.sqrt(mean(squareDiffs));
898
  }
899
 
900
  function percentile(arr, p) {
@@ -905,52 +570,3 @@ function percentile(arr, p) {
905
  const weight = index % 1;
906
  return sorted[lower] * (1 - weight) + sorted[upper] * weight;
907
  }
908
-
909
- function createHistogram(data, bins) {
910
- const min = Math.min(...data);
911
- const max = Math.max(...data);
912
- const binWidth = (max - min) / bins;
913
-
914
- const histogram = new Array(bins).fill(0);
915
- const labels = [];
916
-
917
- for (let i = 0; i < bins; i++) {
918
- const binStart = min + i * binWidth;
919
- const binEnd = min + (i + 1) * binWidth;
920
- labels.push(`${binStart.toFixed(1)}`);
921
-
922
- for (const value of data) {
923
- if (value >= binStart && (value < binEnd || i === bins - 1)) {
924
- histogram[i]++;
925
- }
926
- }
927
- }
928
-
929
- return { labels, data: histogram };
930
- }
931
-
932
- // Simple RNG for reproducible results
933
- class SimpleRNG {
934
- constructor(seed) {
935
- this.seed = seed % 2147483647;
936
- if (this.seed <= 0) this.seed += 2147483646;
937
- }
938
-
939
- next() {
940
- return this.seed = this.seed * 16807 % 2147483647;
941
- }
942
-
943
- nextFloat() {
944
- return (this.next() - 1) / 2147483646;
945
- }
946
-
947
- nextGaussian() {
948
- const u = 0.5 - this.nextFloat();
949
- const v = this.nextFloat();
950
- return Math.sqrt(-2.0 * Math.log(v)) * Math.cos(2.0 * Math.PI * u);
951
- }
952
-
953
- nextInt(min, max) {
954
- return Math.floor(this.nextFloat() * (max - min + 1)) + min;
955
- }
956
- }
 
1
+ // Safe Choices - Simulation Controller
2
+
3
+ // ===== STATE =====
4
  let currentSimType = 'single';
5
+ let currentView = 'simulation';
6
  let simulationResults = null;
7
  let charts = {};
8
 
9
+ // ===== INITIALIZATION =====
10
+ document.addEventListener('DOMContentLoaded', () => {
11
+ initializeApp();
12
+ });
13
+
14
+ function initializeApp() {
15
  setupEventListeners();
16
  updateEstimatedTime();
17
  initializeEmptyCharts();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  }
19
 
 
20
  function setupEventListeners() {
21
+ // Number input constraints
22
+ const numSimulations = document.getElementById('numSimulations');
23
+ numSimulations.addEventListener('input', () => {
24
+ numSimulations.value = Math.min(Math.max(parseInt(numSimulations.value) || 10, 10), 100);
25
  updateEstimatedTime();
26
  });
27
+
28
+ const numFunds = document.getElementById('numFunds');
29
+ numFunds.addEventListener('input', () => {
30
+ numFunds.value = Math.min(Math.max(parseInt(numFunds.value) || 2, 2), 10);
31
+ updateEstimatedTime();
32
  });
33
+
34
+ // Target return custom input toggle
35
+ const targetReturn = document.getElementById('targetReturn');
36
+ const customTarget = document.getElementById('customTarget');
37
+ targetReturn.addEventListener('change', () => {
38
+ if (targetReturn.value === 'custom') {
39
+ customTarget.classList.add('visible');
40
+ customTarget.value = '15';
41
+ } else {
42
+ customTarget.classList.remove('visible');
43
+ }
44
  });
45
+
46
+ // Validate inputs on change
47
+ document.querySelectorAll('.param-input').forEach(input => {
 
48
  input.addEventListener('input', validateInputs);
49
  });
50
  }
51
 
52
+ // ===== VIEW SWITCHING =====
 
 
 
53
  function selectView(view) {
54
  currentView = view;
55
+
56
+ // Update toggle buttons
57
+ document.querySelectorAll('.toggle-btn').forEach(btn => {
58
+ btn.classList.toggle('active', btn.dataset.view === view);
59
  });
60
+
61
+ // Show/hide content
62
+ const simContent = document.getElementById('simulationContent');
63
+ const methContent = document.getElementById('methodologyContent');
64
+
 
65
  if (view === 'simulation') {
66
+ simContent.style.display = 'flex';
67
+ methContent.style.display = 'none';
68
  document.querySelectorAll('.simulation-view-only').forEach(el => {
69
  el.classList.remove('hidden');
70
  });
71
  } else {
72
+ simContent.style.display = 'none';
73
+ methContent.style.display = 'block';
74
  document.querySelectorAll('.simulation-view-only').forEach(el => {
75
  el.classList.add('hidden');
76
  });
77
  }
78
  }
79
 
80
+ // ===== SIMULATION TYPE SWITCHING =====
81
  function selectSimulation(type) {
82
  currentSimType = type;
83
+
84
+ // Update strategy cards
85
+ document.querySelectorAll('.strategy-card').forEach(card => {
86
+ card.classList.toggle('active', card.dataset.sim === type);
87
  });
88
+
89
+ // Show/hide conditional parameters
 
90
  document.querySelectorAll('.threshold-only').forEach(el => {
91
  el.classList.toggle('visible', type === 'threshold');
92
  });
93
+
94
  document.querySelectorAll('.multi-only').forEach(el => {
95
  el.classList.toggle('visible', type === 'multi');
96
  });
 
 
 
97
 
98
+ updateEstimatedTime();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
+ // ===== VALIDATION =====
102
  function validateInputs() {
103
  const errors = [];
104
+
 
 
 
 
 
 
 
 
 
 
 
 
105
  const minProb7d = parseFloat(document.getElementById('minProb7d').value);
106
  const minProbCurrent = parseFloat(document.getElementById('minProbCurrent').value);
107
+
108
  if (minProb7d < 50 || minProb7d > 99) {
109
  errors.push('7-day probability must be between 50% and 99%');
110
  }
111
  if (minProbCurrent < 50 || minProbCurrent > 99) {
112
  errors.push('Current probability must be between 50% and 99%');
113
  }
114
+
 
115
  const runBtn = document.getElementById('runBtn');
116
  runBtn.disabled = errors.length > 0;
117
+
118
  return errors.length === 0;
119
  }
120
 
121
+ // ===== TIME ESTIMATION =====
122
+ function updateEstimatedTime() {
123
+ const numSims = parseInt(document.getElementById('numSimulations').value) || 100;
124
+ const numFunds = currentSimType === 'multi' ? (parseInt(document.getElementById('numFunds').value) || 5) : 1;
125
+
126
+ let baseTime = numSims * 0.05;
127
+ if (currentSimType === 'multi') {
128
+ baseTime *= Math.sqrt(numFunds);
129
+ }
130
+
131
+ const seconds = Math.max(2, Math.ceil(baseTime));
132
+ const timeText = seconds < 60 ? `~${seconds}s` : `~${Math.ceil(seconds / 60)}min`;
133
+
134
+ document.getElementById('estimatedTime').textContent = timeText;
135
+ }
136
+
137
+ // ===== SIMULATION EXECUTION =====
138
  async function runSimulation() {
139
  if (!validateInputs()) return;
140
+
 
141
  const params = getSimulationParameters();
142
+
 
143
  showProgress();
144
  disableControls();
145
+
146
  try {
 
147
  const results = await callSimulationAPI(params);
 
 
148
  simulationResults = results;
149
  displayResults(results);
 
150
  } catch (error) {
151
  console.error('Simulation error:', error);
152
+ alert('Simulation failed: ' + (error.message || 'Unknown error'));
153
  } finally {
154
  hideProgress();
155
  enableControls();
156
  }
157
  }
158
 
159
+ function getSimulationParameters() {
160
+ const params = {
161
+ simType: currentSimType,
162
+ startingCapital: parseFloat(document.getElementById('startingCapital').value),
163
+ numSimulations: parseInt(document.getElementById('numSimulations').value),
164
+ startDate: document.getElementById('startDate').value,
165
+ maxDuration: parseInt(document.getElementById('maxDuration').value),
166
+ minProb7d: parseFloat(document.getElementById('minProb7d').value) / 100,
167
+ minProbCurrent: parseFloat(document.getElementById('minProbCurrent').value) / 100,
168
+ daysBefore: parseInt(document.getElementById('daysBefore').value),
169
+ investmentProbability: parseFloat(document.getElementById('investmentProbability').value)
170
+ };
171
+
172
+ if (currentSimType === 'threshold') {
173
+ const targetSelect = document.getElementById('targetReturn').value;
174
+ if (targetSelect === 'custom') {
175
+ params.targetReturn = parseFloat(document.getElementById('customTarget').value) / 100;
176
+ } else {
177
+ params.targetReturn = parseFloat(targetSelect) / 100;
178
+ }
179
+ }
180
+
181
+ if (currentSimType === 'multi') {
182
+ params.numFunds = parseInt(document.getElementById('numFunds').value);
183
+ }
184
+
185
+ return params;
186
+ }
187
+
188
  async function callSimulationAPI(params) {
189
  const progressFill = document.getElementById('progressFill');
190
  const progressPercent = document.getElementById('progressPercent');
191
  const progressText = document.getElementById('progressText');
192
+
193
+ progressText.textContent = 'Connecting to server...';
194
  progressFill.style.width = '10%';
195
  progressPercent.textContent = '10%';
196
+
 
197
  const requestBody = {
198
  simType: params.simType,
199
  startingCapital: params.startingCapital,
 
205
  daysBefore: params.daysBefore,
206
  investmentProbability: params.investmentProbability
207
  };
208
+
209
+ if (params.targetReturn !== undefined) {
210
  requestBody.targetReturn = params.targetReturn;
211
  }
212
+ if (params.numFunds !== undefined) {
 
213
  requestBody.numFunds = params.numFunds;
214
  }
215
+
216
+ progressText.textContent = 'Running simulation...';
217
+ progressFill.style.width = '30%';
218
+ progressPercent.textContent = '30%';
219
+
220
  const response = await fetch('/api/simulate', {
221
  method: 'POST',
222
+ headers: { 'Content-Type': 'application/json' },
 
 
223
  body: JSON.stringify(requestBody)
224
  });
225
+
226
  if (!response.ok) {
227
  const errorData = await response.json().catch(() => ({ detail: response.statusText }));
228
+ throw new Error(errorData.detail || `HTTP ${response.status}`);
229
  }
230
+
231
  progressText.textContent = 'Processing results...';
232
+ progressFill.style.width = '80%';
233
+ progressPercent.textContent = '80%';
234
+
235
  const results = await response.json();
236
+
237
  progressFill.style.width = '100%';
238
  progressPercent.textContent = '100%';
239
  progressText.textContent = 'Complete!';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
+ return results;
 
 
 
 
242
  }
243
 
244
+ // ===== RESULTS DISPLAY =====
245
+ function displayResults(results) {
246
+ const { summary, parameters } = results;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
+ // Show results section
249
+ const resultsSection = document.getElementById('resultsSection');
250
+ resultsSection.classList.add('visible');
251
+ resultsSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
 
253
+ // Primary metrics
254
+ updateMetric('avgReturn', summary.avgReturn, true);
255
+ updateMetric('medianReturn', summary.medianReturn, true);
256
+ updateMetric('successRate', summary.positiveReturnRate);
257
+ updateMetric('bustRate', summary.bustRate, false, true);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
+ // Risk metrics
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  document.getElementById('volatility').textContent = formatPercentage(summary.returnVolatility);
261
  document.getElementById('maxDrawdown').textContent = formatPercentage(summary.maxDrawdown);
262
+ document.getElementById('percentile5').textContent = formatPercentage(summary.return5th || -1);
263
+ document.getElementById('percentile95').textContent = formatPercentage(summary.return95th || 0);
264
+
265
+ // Type-specific stats
266
  if (parameters.simType === 'multi') {
267
  document.querySelectorAll('.multi-stats, .multi-chart').forEach(el => {
268
  el.classList.add('visible');
269
  });
270
+ document.getElementById('avgSurvivingFunds').textContent =
271
+ `${summary.avgSurvivingFunds?.toFixed(1) || '--'} / ${parameters.numFunds}`;
272
+ document.getElementById('survivorshipRate').textContent = formatPercentage(summary.survivorshipRate || 0);
273
+ document.getElementById('diversificationBenefit').textContent =
274
+ summary.returnVolatility < 0.3 ? 'Positive' : 'Limited';
 
 
 
275
  } else {
276
  document.querySelectorAll('.multi-stats, .multi-chart').forEach(el => {
277
  el.classList.remove('visible');
278
  });
279
  }
280
+
281
  if (parameters.simType === 'threshold') {
282
  document.querySelectorAll('.threshold-stats').forEach(el => {
283
  el.classList.add('visible');
284
  });
 
285
  document.getElementById('targetReached').textContent = formatPercentage(summary.targetReachedRate || 0);
286
+ document.getElementById('avgTimeToTarget').textContent =
287
  summary.avgTimeToTarget ? `${Math.round(summary.avgTimeToTarget)} days` : 'N/A';
288
+ document.getElementById('vsNeverStop').textContent =
289
+ (summary.targetReachedRate || 0) > 0.5 ? 'Better' : 'Similar';
290
  } else {
291
  document.querySelectorAll('.threshold-stats').forEach(el => {
292
  el.classList.remove('visible');
293
  });
294
  }
295
+
296
  // Generate charts
297
  generateCharts(results);
298
  }
299
 
300
+ function updateMetric(id, value, showSign = false, isRisk = false) {
301
+ const el = document.getElementById(id);
302
+ el.textContent = formatPercentage(value);
303
+
304
+ // Add color classes
305
+ el.classList.remove('positive', 'negative', 'high-risk');
306
+ if (isRisk) {
307
+ if (value > 0.1) el.classList.add('high-risk');
308
+ } else if (showSign) {
309
+ if (value > 0.02) el.classList.add('positive');
310
+ else if (value < -0.02) el.classList.add('negative');
311
+ }
312
+ }
313
+
314
+ // ===== CHARTS =====
315
  function initializeEmptyCharts() {
316
+ charts.return = createEmptyChart('returnChart', 'bar');
317
+ charts.capital = createEmptyChart('capitalChart', 'line');
318
+ charts.survivorship = createEmptyChart('survivorshipChart', 'bar');
319
+ }
320
+
321
+ function createEmptyChart(canvasId, type) {
322
+ const ctx = document.getElementById(canvasId).getContext('2d');
323
+ return new Chart(ctx, {
324
+ type: type,
325
+ data: { labels: [], datasets: [] },
326
+ options: getChartOptions(type)
327
+ });
328
  }
329
 
 
330
  function generateCharts(results) {
331
+ const { runs, parameters } = results;
332
+
333
  // Destroy existing charts
334
+ Object.values(charts).forEach(chart => chart?.destroy());
 
 
335
  charts = {};
336
+
337
+ // Return distribution
338
  charts.return = createReturnDistributionChart(runs);
339
+
340
+ // Capital evolution
341
  charts.capital = createCapitalEvolutionChart(runs);
342
+
343
+ // Survivorship (multi-fund only)
344
  if (parameters.simType === 'multi') {
345
  charts.survivorship = createSurvivorshipChart(runs, parameters.numFunds);
346
  } else {
347
+ charts.survivorship = createEmptyChart('survivorshipChart', 'bar');
348
  }
349
  }
350
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  function createReturnDistributionChart(runs) {
352
  const ctx = document.getElementById('returnChart').getContext('2d');
353
  const returns = runs.map(r => r.totalReturn * 100);
354
+
 
355
  const minReturn = Math.min(...returns);
356
  const maxReturn = Math.max(...returns);
 
 
357
  const binStart = Math.floor(minReturn / 10) * 10;
358
+ const binEnd = Math.min(Math.ceil(maxReturn / 10) * 10, 200);
359
+
 
360
  const bins = [];
361
+ for (let i = binStart; i <= binEnd; i += 5) bins.push(i);
362
+
 
 
 
363
  const binCounts = new Array(bins.length).fill(0);
364
  returns.forEach(ret => {
365
  for (let i = 0; i < bins.length - 1; i++) {
 
368
  break;
369
  }
370
  }
371
+ if (ret >= bins[bins.length - 1]) binCounts[bins.length - 1]++;
 
 
 
372
  });
373
+
374
  return new Chart(ctx, {
375
  type: 'bar',
376
  data: {
 
378
  datasets: [{
379
  label: 'Frequency',
380
  data: binCounts,
381
+ backgroundColor: 'rgba(59, 130, 246, 0.6)',
382
+ borderColor: 'rgba(59, 130, 246, 1)',
383
+ borderWidth: 1,
384
+ borderRadius: 4
385
  }]
386
  },
387
+ options: getChartOptions('bar')
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  });
389
  }
390
 
 
391
  function createCapitalEvolutionChart(runs) {
392
  const ctx = document.getElementById('capitalChart').getContext('2d');
393
+
 
394
  const colors = [
395
+ { border: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
396
+ { border: '#10b981', bg: 'rgba(16, 185, 129, 0.1)' },
397
+ { border: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
398
+ { border: '#ef4444', bg: 'rgba(239, 68, 68, 0.1)' },
399
+ { border: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' }
400
  ];
401
+
 
402
  const sampleRuns = runs.slice(0, 5);
403
  const datasets = sampleRuns.map((run, i) => {
 
404
  let data = [];
405
+ if (run.capitalHistory?.length > 0) {
406
+ data = run.capitalHistory.map((item, idx) => ({
407
+ x: typeof item === 'object' ? item.day : idx,
408
+ y: typeof item === 'object' ? item.capital : item
409
+ }));
 
 
410
  } else {
411
+ data = [{ x: 0, y: 10000 }, { x: 1, y: run.finalCapital }];
 
 
 
 
412
  }
413
+
414
  return {
415
  label: `Run ${i + 1}`,
416
  data: data,
417
+ borderColor: colors[i].border,
418
+ backgroundColor: colors[i].bg,
419
  borderWidth: 2,
420
  fill: false,
421
+ tension: 0.3,
422
+ pointRadius: 0
423
  };
424
  });
425
+
426
  return new Chart(ctx, {
427
  type: 'line',
428
  data: { datasets },
429
+ options: getChartOptions('line')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
  });
431
  }
432
 
 
433
  function createSurvivorshipChart(runs, numFunds) {
434
  const ctx = document.getElementById('survivorshipChart').getContext('2d');
435
  const survivingCounts = runs.map(r => r.survivingFunds);
436
+
 
437
  const labels = [];
438
  const data = [];
439
  for (let i = 0; i <= numFunds; i++) {
440
  labels.push(i.toString());
441
  data.push(survivingCounts.filter(c => c === i).length);
442
  }
443
+
444
  return new Chart(ctx, {
445
  type: 'bar',
446
  data: {
 
448
  datasets: [{
449
  label: 'Frequency',
450
  data: data,
451
+ backgroundColor: 'rgba(139, 92, 246, 0.6)',
452
+ borderColor: 'rgba(139, 92, 246, 1)',
453
+ borderWidth: 1,
454
+ borderRadius: 4
455
  }]
456
  },
457
+ options: getChartOptions('bar')
458
  });
459
  }
460
 
461
+ function getChartOptions(type) {
462
+ const baseOptions = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  responsive: true,
464
  maintainAspectRatio: false,
465
  plugins: {
466
+ legend: {
467
+ display: type === 'line',
468
  labels: {
469
+ color: '#9ca3af',
470
  font: { size: 11 },
471
  usePointStyle: true,
472
+ padding: 16
473
  }
474
  }
475
  },
476
  scales: {
477
  x: {
478
+ grid: { color: 'rgba(255, 255, 255, 0.05)' },
479
+ ticks: { color: '#6b7280', font: { size: 10 } }
 
 
 
 
 
 
 
 
 
 
 
 
 
480
  },
481
  y: {
482
+ grid: { color: 'rgba(255, 255, 255, 0.05)' },
483
+ ticks: { color: '#6b7280', font: { size: 10 } }
 
 
 
 
 
 
 
 
 
 
 
 
484
  }
485
  }
486
  };
487
+
488
+ if (type === 'line') {
489
+ baseOptions.scales.x.type = 'linear';
490
+ }
491
+
492
+ return baseOptions;
493
  }
494
 
495
+ // ===== EXPORT =====
496
  function exportResults() {
497
  if (!simulationResults) return;
498
+
499
  const data = {
500
  timestamp: new Date().toISOString(),
501
  parameters: simulationResults.parameters,
502
  summary: simulationResults.summary,
503
  runs: simulationResults.runs
504
  };
505
+
506
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
507
  const url = URL.createObjectURL(blob);
508
+
509
  const a = document.createElement('a');
510
  a.href = url;
511
+ a.download = `safe_choices_${currentSimType}_${Date.now()}.json`;
512
  document.body.appendChild(a);
513
  a.click();
514
  document.body.removeChild(a);
515
  URL.revokeObjectURL(url);
516
  }
517
 
518
+ // ===== UI HELPERS =====
519
  function showProgress() {
520
+ document.getElementById('progressPanel').classList.add('visible');
521
  document.getElementById('progressFill').style.width = '0%';
522
  }
523
 
524
  function hideProgress() {
525
  setTimeout(() => {
526
+ document.getElementById('progressPanel').classList.remove('visible');
527
  }, 500);
528
  }
529
 
530
  function disableControls() {
531
  const runBtn = document.getElementById('runBtn');
532
  runBtn.disabled = true;
533
+ runBtn.classList.add('running');
534
  document.querySelector('.run-text').textContent = 'Running...';
 
535
  }
536
 
537
  function enableControls() {
538
  const runBtn = document.getElementById('runBtn');
539
  runBtn.disabled = false;
540
+ runBtn.classList.remove('running');
541
  document.querySelector('.run-text').textContent = 'Run Simulation';
 
 
 
 
 
 
542
  }
543
 
544
  function formatPercentage(value) {
545
+ if (value === null || value === undefined || isNaN(value)) return '--';
546
  return `${(value * 100).toFixed(1)}%`;
547
  }
548
 
549
+ // ===== UTILITY FUNCTIONS =====
 
 
 
 
 
550
  function mean(arr) {
551
  return arr.reduce((a, b) => a + b, 0) / arr.length;
552
  }
 
559
 
560
  function standardDeviation(arr) {
561
  const avg = mean(arr);
562
+ return Math.sqrt(arr.reduce((sum, val) => sum + Math.pow(val - avg, 2), 0) / arr.length);
 
563
  }
564
 
565
  function percentile(arr, p) {
 
570
  const weight = index % 1;
571
  return sorted[lower] * (1 - weight) + sorted[upper] * weight;
572
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/styles.css CHANGED
@@ -1,431 +1,655 @@
1
- /* Polymarket Macro Dashboard - Dark Mode Style */
2
 
3
  :root {
4
- /* Dark Mode Colors */
5
- --bg-primary: #1D2B3A;
6
- --bg-secondary: #253547;
7
- --bg-tertiary: #2a3d52;
8
- --text-primary: #ffffff;
9
- --text-secondary: #87BFFF;
10
- --accent: #87BFFF;
11
- --accent-dark: #56AFE2;
12
- --accent-hover: #a8d0ff;
13
- --accent-light: rgba(135, 191, 255, 0.1);
14
-
15
- /* Borders */
16
- --border-color: rgba(135, 191, 255, 0.15);
17
- --border-hover: rgba(135, 191, 255, 0.3);
18
-
19
- /* Status Colors (minimal use) */
20
  --success: #10b981;
21
- --error: #ef4444;
 
 
22
  --warning: #f59e0b;
23
-
24
- /* Design Tokens */
25
- --radius-card: 8px;
26
- --spacing-unit: 16px;
27
- --font-body: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
 
 
 
 
 
 
 
28
  }
29
 
30
- /* Reset and base styles */
31
- * {
32
  margin: 0;
33
  padding: 0;
34
  box-sizing: border-box;
35
  }
36
 
 
 
 
 
 
37
  body {
38
- font-family: var(--font-body);
39
- background-color: var(--bg-primary);
40
  color: var(--text-primary);
41
  line-height: 1.6;
42
  min-height: 100vh;
43
  -webkit-font-smoothing: antialiased;
44
- -moz-osx-font-smoothing: grayscale;
45
  }
46
 
47
- /* Top Navigation Bar */
48
- .top-bar {
49
- background-color: var(--bg-primary);
50
- border-bottom: 1px solid var(--border-color);
51
  position: sticky;
52
  top: 0;
53
  z-index: 100;
 
54
  }
55
 
56
- .nav-container {
57
- max-width: 1400px;
58
  margin: 0 auto;
59
- padding: 0 calc(var(--spacing-unit) * 2);
 
60
  display: flex;
61
  align-items: center;
62
  justify-content: space-between;
63
- height: 64px;
64
  }
65
 
66
- .logo-section {
67
  display: flex;
68
  align-items: center;
69
  gap: 12px;
70
  }
71
 
72
- .logo {
73
- height: 28px;
74
- width: auto;
 
 
 
 
 
 
75
  }
76
 
77
- .app-title {
78
  font-size: 18px;
79
- font-weight: 600;
80
- color: var(--text-primary);
81
- letter-spacing: -0.01em;
82
  }
83
 
84
- .subtitle {
85
- font-size: 13px;
86
- color: var(--text-secondary);
87
  font-weight: 400;
88
  }
89
 
90
- .nav-info {
91
  display: flex;
92
  align-items: center;
 
 
 
 
 
 
 
93
  }
94
 
95
- .dataset-info {
96
- font-size: 13px;
97
- color: var(--text-secondary);
98
- background: var(--bg-secondary);
99
- padding: 6px 12px;
100
- border-radius: 6px;
101
- border: 1px solid var(--border-color);
102
  }
103
 
104
- /* Main Container */
105
- .main-container {
106
- max-width: 1400px;
 
 
 
 
 
107
  margin: 0 auto;
108
- padding: calc(var(--spacing-unit) * 2);
109
  display: grid;
110
- grid-template-columns: 280px 1fr;
111
- gap: calc(var(--spacing-unit) * 2);
 
112
  }
113
 
114
- /* Left Sidebar - Simulation Type Selection */
115
  .sidebar {
116
  display: flex;
117
  flex-direction: column;
118
- gap: calc(var(--spacing-unit) * 0.5);
 
 
 
119
  }
120
 
121
- .sidebar-label {
 
 
 
 
 
 
122
  font-size: 11px;
123
  font-weight: 600;
124
- color: var(--text-secondary);
125
  text-transform: uppercase;
126
- letter-spacing: 0.05em;
127
- margin-bottom: calc(var(--spacing-unit) * 0.5);
128
- padding: 0;
129
  }
130
 
131
- .simulation-tabs {
 
132
  display: flex;
133
  flex-direction: column;
134
- gap: calc(var(--spacing-unit) * 0.5);
135
  }
136
 
137
- .sim-tab {
138
- width: 100%;
139
- padding: calc(var(--spacing-unit) * 1);
140
- border: 1px solid var(--border-color);
141
- border-radius: var(--radius-card);
142
- background: var(--bg-primary);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  cursor: pointer;
144
- transition: all 0.15s ease;
145
  text-align: left;
146
- margin: 0;
147
  }
148
 
149
- .sim-tab:hover {
150
- border-color: var(--border-hover);
151
- background: var(--bg-secondary);
 
152
  }
153
 
154
- .sim-tab.active {
155
- border-color: var(--accent-dark);
156
- background: var(--bg-secondary);
157
- border-left: 3px solid var(--accent-dark);
158
  }
159
 
160
- .tab-title {
161
- font-size: 14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  font-weight: 600;
163
  color: var(--text-primary);
164
- margin-bottom: 3px;
165
  }
166
 
167
- .tab-subtitle {
168
- font-size: 12px;
169
- color: var(--text-secondary);
170
- font-weight: 400;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  }
172
 
173
- /* Right Content Area */
174
- .content-area {
 
 
 
 
 
 
 
 
 
 
 
175
  display: flex;
176
  flex-direction: column;
177
- gap: calc(var(--spacing-unit) * 2);
178
- grid-column: 2;
 
 
 
179
  }
180
 
181
- /* Controls Section */
182
- .controls-section {
183
- background: var(--bg-secondary);
184
- border-radius: var(--radius-card);
185
- padding: calc(var(--spacing-unit) * 2);
186
- border: 1px solid var(--border-color);
187
  }
188
 
189
- .controls-header {
190
- margin-bottom: calc(var(--spacing-unit) * 2);
191
  }
192
 
193
- .controls-header h2 {
194
- font-size: 22px;
 
 
 
195
  font-weight: 600;
196
- margin-bottom: 6px;
197
  color: var(--text-primary);
198
- letter-spacing: -0.01em;
199
  }
200
 
201
- .controls-header p {
202
- font-size: 14px;
203
- color: var(--text-secondary);
204
- font-weight: 400;
205
  }
206
 
207
- .controls-grid {
 
208
  display: grid;
209
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
210
- gap: calc(var(--spacing-unit) * 1.5);
211
- margin-bottom: calc(var(--spacing-unit) * 2);
212
  }
213
 
214
- .control-group {
215
  display: flex;
216
  flex-direction: column;
 
217
  }
218
 
219
- .control-group.full-width {
220
- grid-column: 1 / -1;
 
 
 
 
 
221
  }
222
 
223
- .control-label {
224
- font-size: 13px;
225
- font-weight: 500;
226
- color: var(--text-primary);
227
- margin-bottom: 8px;
 
 
 
 
 
 
 
228
  }
229
 
230
- .control-input {
231
- padding: 10px 12px;
232
- border: 1px solid var(--border-color);
233
- border-radius: 6px;
234
- font-size: 14px;
235
- background: var(--bg-primary);
 
 
 
 
 
 
236
  color: var(--text-primary);
237
- transition: border-color 0.15s ease;
238
- outline: none;
239
- font-family: var(--font-body);
 
240
  }
241
 
242
- .control-input:focus {
 
243
  border-color: var(--accent);
244
- outline: 2px solid var(--accent-light);
245
- outline-offset: 0;
246
  }
247
 
248
- .control-subtitle {
 
 
 
 
 
 
249
  font-size: 12px;
250
- color: var(--text-secondary);
251
- margin-top: 6px;
252
- font-weight: 400;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  }
254
 
255
- /* Run Section */
256
- .run-section {
257
  display: flex;
258
  align-items: center;
259
  justify-content: center;
260
- gap: var(--spacing-unit);
261
- padding-top: calc(var(--spacing-unit) * 2);
262
- border-top: 1px solid var(--border-color);
263
- margin-top: calc(var(--spacing-unit) * 0.5);
264
  }
265
 
266
- .run-button {
267
- background: var(--accent-dark);
268
- color: var(--bg-primary);
269
- border: none;
270
  padding: 12px 28px;
271
- border-radius: 6px;
 
 
 
272
  font-size: 14px;
273
  font-weight: 600;
 
274
  cursor: pointer;
275
- transition: background-color 0.15s ease;
276
- display: flex;
277
- align-items: center;
278
- gap: 8px;
279
- font-family: var(--font-body);
280
  }
281
 
282
- .run-button:hover:not(:disabled) {
283
- background: var(--accent);
 
284
  }
285
 
286
- .run-button:disabled {
287
- background: var(--bg-tertiary);
288
- color: var(--text-secondary);
289
  cursor: not-allowed;
290
- opacity: 0.6;
 
 
 
 
 
 
 
 
291
  }
292
 
293
  .run-spinner {
 
294
  width: 18px;
295
  height: 18px;
296
- border: 2px solid transparent;
297
- border-top: 2px solid var(--bg-primary);
298
  border-radius: 50%;
299
  animation: spin 0.8s linear infinite;
300
  }
301
 
302
  @keyframes spin {
303
- 0% { transform: rotate(0deg); }
304
- 100% { transform: rotate(360deg); }
305
  }
306
 
307
- .run-info {
308
- font-size: 13px;
309
- color: var(--text-secondary);
310
  }
311
 
312
- /* Progress Section */
313
- .progress-section {
314
- background: var(--bg-secondary);
315
- border-radius: var(--radius-card);
316
- padding: calc(var(--spacing-unit) * 2);
317
- border: 1px solid var(--border-color);
318
  }
319
 
320
- .progress-container {
321
- max-width: 600px;
 
 
 
 
322
  margin: 0 auto;
323
  }
324
 
325
- .progress-label {
326
  display: flex;
327
  justify-content: space-between;
328
  align-items: center;
329
  margin-bottom: 10px;
330
- font-size: 14px;
 
 
 
331
  color: var(--text-primary);
 
332
  }
333
 
334
- .progress-bar {
335
- width: 100%;
 
 
 
 
336
  height: 6px;
337
- background: var(--bg-primary);
338
  border-radius: 3px;
339
  overflow: hidden;
340
  }
341
 
342
  .progress-fill {
343
  height: 100%;
344
- background: var(--accent);
345
- border-radius: 3px;
346
- transition: width 0.2s ease;
347
  width: 0%;
 
 
 
348
  }
349
 
350
- /* Results Section */
351
- .results-section {
352
- display: flex;
353
  flex-direction: column;
354
- gap: calc(var(--spacing-unit) * 2);
355
  }
356
 
357
- .results-header {
358
  display: flex;
359
- justify-content: space-between;
360
- align-items: center;
361
- background: var(--bg-secondary);
362
- padding: calc(var(--spacing-unit) * 1.75) calc(var(--spacing-unit) * 2);
363
- border-radius: var(--radius-card);
364
- border: 1px solid var(--border-color);
365
  }
366
 
367
- .results-header h2 {
368
- font-size: 22px;
369
- font-weight: 600;
370
- color: var(--text-primary);
371
- letter-spacing: -0.01em;
372
  }
373
 
374
- .export-button {
375
- background: var(--bg-primary);
376
- color: var(--accent);
377
- border: 1px solid var(--border-color);
378
- padding: 8px 16px;
379
- border-radius: 6px;
380
- font-size: 13px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
  font-weight: 500;
382
- cursor: pointer;
383
- transition: all 0.15s ease;
384
- font-family: var(--font-body);
 
385
  }
386
 
387
- .export-button:hover {
388
- background: var(--bg-tertiary);
389
- border-color: var(--accent);
 
 
390
  }
391
 
392
- /* Statistics Grid - fixed spacing */
393
- .stats-grid {
394
- display: grid;
395
- grid-template-columns: repeat(2, 1fr);
396
- gap: calc(var(--spacing-unit) * 2);
397
- margin-bottom: calc(var(--spacing-unit) * 1.5);
398
  }
399
 
400
- @media (max-width: 900px) {
401
- .stats-grid {
402
- grid-template-columns: 1fr;
403
- }
404
  }
405
 
406
- .stat-card {
407
- background: var(--bg-secondary);
408
- border-radius: var(--radius-card);
409
- padding: calc(var(--spacing-unit) * 1.5);
410
- border: 1px solid var(--border-color);
411
- min-height: 150px;
412
  }
413
 
414
- .stat-header {
415
- margin-bottom: calc(var(--spacing-unit) * 1.5);
 
 
 
416
  }
417
 
418
- .stat-header h3 {
419
- font-size: 16px;
420
  font-weight: 600;
421
  color: var(--text-primary);
422
- letter-spacing: -0.01em;
 
 
423
  }
424
 
425
- .stat-content {
426
  display: flex;
427
  flex-direction: column;
428
- gap: calc(var(--spacing-unit) * 1.5);
429
  }
430
 
431
  .stat-item {
@@ -434,329 +658,358 @@ body {
434
  align-items: center;
435
  }
436
 
437
- .stat-label {
438
  font-size: 13px;
439
  color: var(--text-secondary);
440
- font-weight: 400;
441
  }
442
 
443
- .stat-value {
444
- font-size: 16px;
445
  font-weight: 600;
446
  color: var(--text-primary);
447
- letter-spacing: -0.01em;
448
  }
449
 
450
- .stat-value.positive {
451
- color: var(--success);
452
- }
453
-
454
- .stat-value.negative {
455
- color: var(--error);
456
  }
457
 
458
- .stat-value.neutral {
459
- color: var(--accent-dark);
 
460
  }
461
 
462
- /* Charts Grid - fixed layout */
463
- .charts-grid {
464
  display: grid;
465
  grid-template-columns: repeat(2, 1fr);
466
- gap: calc(var(--spacing-unit) * 2);
467
- margin-top: calc(var(--spacing-unit) * 2);
468
  }
469
 
470
- @media (max-width: 1100px) {
471
- .charts-grid {
472
- grid-template-columns: 1fr;
473
- }
474
- }
475
-
476
- .chart-card {
477
- background: var(--bg-secondary);
478
- border-radius: var(--radius-card);
479
- padding: calc(var(--spacing-unit) * 1.5);
480
- border: 1px solid var(--border-color);
481
  }
482
 
483
  .chart-header {
484
- margin-bottom: calc(var(--spacing-unit) * 1.5);
 
 
 
485
  }
486
 
487
  .chart-header h3 {
488
- font-size: 16px;
489
  font-weight: 600;
490
  color: var(--text-primary);
491
- letter-spacing: -0.01em;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  }
493
 
494
  .chart-container {
 
495
  position: relative;
496
- height: 300px;
497
- width: 100%;
498
- padding: 8px 0;
499
  }
500
 
501
- /* Responsive Design */
502
- @media (max-width: 1024px) {
503
- .main-container {
504
- grid-template-columns: 1fr;
505
- gap: calc(var(--spacing-unit) * 1.5);
506
- }
507
-
508
- .sidebar {
509
- order: 2;
510
- }
511
-
512
- .content-area {
513
- order: 1;
514
- }
515
-
516
- .simulation-tabs {
517
- flex-direction: row;
518
- flex-wrap: wrap;
519
- }
520
-
521
- .sim-tab {
522
- flex: 1;
523
- min-width: 200px;
524
- }
525
  }
526
 
527
- @media (max-width: 768px) {
528
- .nav-container {
529
- padding: 0 var(--spacing-unit);
530
- height: 56px;
531
- }
532
-
533
- .app-title {
534
- font-size: 16px;
535
- }
536
-
537
- .subtitle {
538
- font-size: 12px;
539
- }
540
-
541
- .main-container {
542
- padding: var(--spacing-unit);
543
- gap: var(--spacing-unit);
544
- }
545
-
546
- .controls-section {
547
- padding: calc(var(--spacing-unit) * 1.5);
548
- }
549
-
550
- .controls-grid {
551
- grid-template-columns: 1fr;
552
- gap: var(--spacing-unit);
553
- }
554
-
555
- .charts-grid {
556
- grid-template-columns: 1fr;
557
- gap: calc(var(--spacing-unit) * 1.5);
558
- }
559
-
560
- .chart-card,
561
- .stat-card {
562
- padding: calc(var(--spacing-unit) * 1.5);
563
- }
564
-
565
- .results-header {
566
- flex-direction: column;
567
- gap: var(--spacing-unit);
568
- align-items: stretch;
569
- padding: calc(var(--spacing-unit) * 1.5);
570
- }
571
  }
572
 
573
- /* Utility classes */
574
- .hidden {
575
- display: none !important;
 
576
  }
577
 
578
- .loading {
579
- opacity: 0.6;
580
- pointer-events: none;
 
 
581
  }
582
 
583
- /* Conditional visibility classes */
584
- .threshold-only, .multi-only {
585
- display: none;
 
 
 
586
  }
587
 
588
- .threshold-only.visible, .multi-only.visible {
589
- display: flex;
 
590
  }
591
 
592
- .multi-stats, .threshold-stats, .multi-chart {
593
- display: none;
594
  }
595
 
596
- .multi-stats.visible, .threshold-stats.visible, .multi-chart.visible {
597
- display: block;
 
 
 
 
 
598
  }
599
 
600
- /* View tabs */
601
- .view-tabs {
602
- display: flex;
603
- flex-direction: column;
604
- gap: calc(var(--spacing-unit) * 0.5);
605
- margin-bottom: calc(var(--spacing-unit) * 1.5);
606
  }
607
 
608
- .view-tab {
609
- width: 100%;
610
- padding: calc(var(--spacing-unit) * 0.75) calc(var(--spacing-unit) * 1);
611
- border: 1px solid var(--border-color);
612
- border-radius: var(--radius-card);
613
- background: var(--bg-primary);
614
- cursor: pointer;
615
- transition: all 0.15s ease;
616
- text-align: left;
617
  }
618
 
619
- .view-tab:hover {
620
- border-color: var(--border-hover);
621
- background: var(--bg-secondary);
 
 
622
  }
623
 
624
- .view-tab.active {
625
- border-color: var(--accent-dark);
626
- background: var(--bg-secondary);
627
- border-left: 3px solid var(--accent-dark);
628
  }
629
 
630
- .view-tab .tab-title {
631
- font-size: 13px;
632
- font-weight: 600;
633
- color: var(--text-primary);
634
  }
635
 
636
- /* Simulation view only visibility */
637
- .simulation-view-only {
638
- display: block;
 
 
639
  }
640
 
641
- .simulation-view-only.hidden {
642
- display: none;
 
 
 
 
 
643
  }
644
 
645
- /* Methodology Section */
646
- .methodology-area {
647
- grid-column: 2;
648
  }
649
 
650
- .methodology-section {
651
- background: var(--bg-secondary);
652
- border-radius: var(--radius-card);
653
- padding: calc(var(--spacing-unit) * 2);
654
- border: 1px solid var(--border-color);
655
  }
656
 
657
- .methodology-content {
658
- max-width: 900px;
659
- margin: 0 auto;
660
- color: var(--text-primary);
661
- line-height: 1.7;
662
  }
663
 
664
- .methodology-content h1 {
665
- font-size: 28px;
666
- font-weight: 700;
667
- color: var(--accent);
668
- margin-bottom: 8px;
 
669
  }
670
 
671
- .methodology-content h2 {
672
- font-size: 16px;
673
- font-weight: 400;
674
- color: var(--text-secondary);
675
- margin-bottom: calc(var(--spacing-unit) * 2);
676
- font-style: italic;
677
  }
678
 
679
- .methodology-content h3 {
680
- font-size: 18px;
681
- font-weight: 600;
682
- color: var(--text-primary);
683
- margin-top: calc(var(--spacing-unit) * 2);
684
- margin-bottom: calc(var(--spacing-unit) * 1);
685
- padding-bottom: 8px;
686
- border-bottom: 1px solid var(--border-color);
687
  }
688
 
689
- .methodology-content h4 {
690
- font-size: 16px;
691
- font-weight: 600;
692
- color: var(--accent-dark);
693
- margin-bottom: 8px;
694
  }
695
 
696
- .methodology-content p {
697
- font-size: 14px;
698
- margin-bottom: calc(var(--spacing-unit) * 1);
699
- color: var(--text-primary);
 
 
 
700
  }
701
 
702
- .methodology-content ul, .methodology-content ol {
703
- margin-left: calc(var(--spacing-unit) * 1.5);
704
- margin-bottom: calc(var(--spacing-unit) * 1);
 
 
 
 
 
 
 
 
 
705
  }
706
 
707
- .methodology-content li {
708
  font-size: 14px;
709
- margin-bottom: 6px;
710
  color: var(--text-primary);
711
  }
712
 
713
- .methodology-content a {
714
- color: var(--accent);
715
- text-decoration: none;
716
  }
717
 
718
- .methodology-content a:hover {
719
- text-decoration: underline;
 
 
 
720
  }
721
 
722
- .methodology-content strong {
723
- color: var(--text-primary);
 
 
 
 
 
 
724
  font-weight: 600;
 
 
725
  }
726
 
727
- .methodology-content em {
728
- font-style: italic;
729
  color: var(--text-secondary);
 
 
730
  }
731
 
732
- .methodology-intro {
733
- background: var(--bg-tertiary);
734
- padding: calc(var(--spacing-unit) * 1.5);
735
- border-radius: var(--radius-card);
736
- margin-bottom: calc(var(--spacing-unit) * 2);
737
- border-left: 3px solid var(--accent-dark);
738
- }
739
 
740
- .methodology-intro p:last-child {
741
- margin-bottom: 0;
742
- }
 
 
 
743
 
744
- .hypothesis-box {
745
- background: var(--bg-primary);
746
- padding: calc(var(--spacing-unit) * 1.5);
747
- border-radius: var(--radius-card);
748
- margin: calc(var(--spacing-unit) * 1.5) 0;
749
- border: 1px solid var(--accent-dark);
750
- font-size: 14px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
751
  }
752
 
753
- .sim-type-box {
754
- background: var(--bg-tertiary);
755
- padding: calc(var(--spacing-unit) * 1.5);
756
- border-radius: var(--radius-card);
757
- margin-bottom: calc(var(--spacing-unit) * 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
758
  }
759
 
760
- .sim-type-box p:last-child {
761
- margin-bottom: 0;
 
762
  }
 
1
+ /* Safe Choices - Modern Dark Theme */
2
 
3
  :root {
4
+ /* Color Palette */
5
+ --bg-base: #0a0f1a;
6
+ --bg-surface: #111827;
7
+ --bg-elevated: #1f2937;
8
+ --bg-hover: #374151;
9
+
10
+ --text-primary: #f9fafb;
11
+ --text-secondary: #9ca3af;
12
+ --text-muted: #6b7280;
13
+
14
+ --accent: #3b82f6;
15
+ --accent-light: #60a5fa;
16
+ --accent-dark: #2563eb;
17
+ --accent-glow: rgba(59, 130, 246, 0.15);
18
+
 
19
  --success: #10b981;
20
+ --success-bg: rgba(16, 185, 129, 0.1);
21
+ --danger: #ef4444;
22
+ --danger-bg: rgba(239, 68, 68, 0.1);
23
  --warning: #f59e0b;
24
+
25
+ --border: rgba(255, 255, 255, 0.08);
26
+ --border-accent: rgba(59, 130, 246, 0.3);
27
+
28
+ /* Sizing */
29
+ --radius-sm: 6px;
30
+ --radius-md: 10px;
31
+ --radius-lg: 14px;
32
+ --radius-xl: 20px;
33
+
34
+ /* Typography */
35
+ --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
36
  }
37
 
38
+ /* Reset */
39
+ *, *::before, *::after {
40
  margin: 0;
41
  padding: 0;
42
  box-sizing: border-box;
43
  }
44
 
45
+ html {
46
+ font-size: 14px;
47
+ scroll-behavior: smooth;
48
+ }
49
+
50
  body {
51
+ font-family: var(--font);
52
+ background: var(--bg-base);
53
  color: var(--text-primary);
54
  line-height: 1.6;
55
  min-height: 100vh;
56
  -webkit-font-smoothing: antialiased;
 
57
  }
58
 
59
+ /* ===== HEADER ===== */
60
+ .header {
61
+ background: var(--bg-surface);
62
+ border-bottom: 1px solid var(--border);
63
  position: sticky;
64
  top: 0;
65
  z-index: 100;
66
+ backdrop-filter: blur(12px);
67
  }
68
 
69
+ .header-content {
70
+ max-width: 1440px;
71
  margin: 0 auto;
72
+ padding: 0 24px;
73
+ height: 64px;
74
  display: flex;
75
  align-items: center;
76
  justify-content: space-between;
 
77
  }
78
 
79
+ .brand {
80
  display: flex;
81
  align-items: center;
82
  gap: 12px;
83
  }
84
 
85
+ .brand-icon {
86
+ width: 40px;
87
+ height: 40px;
88
+ background: linear-gradient(135deg, var(--accent), var(--accent-dark));
89
+ border-radius: var(--radius-md);
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ color: white;
94
  }
95
 
96
+ .brand-text h1 {
97
  font-size: 18px;
98
+ font-weight: 700;
99
+ letter-spacing: -0.02em;
 
100
  }
101
 
102
+ .brand-tagline {
103
+ font-size: 12px;
104
+ color: var(--text-muted);
105
  font-weight: 400;
106
  }
107
 
108
+ .data-badge {
109
  display: flex;
110
  align-items: center;
111
+ gap: 8px;
112
+ padding: 6px 12px;
113
+ background: var(--bg-elevated);
114
+ border-radius: var(--radius-sm);
115
+ font-size: 12px;
116
+ font-weight: 500;
117
+ color: var(--text-secondary);
118
  }
119
 
120
+ .badge-dot {
121
+ width: 6px;
122
+ height: 6px;
123
+ background: var(--success);
124
+ border-radius: 50%;
125
+ animation: pulse 2s infinite;
 
126
  }
127
 
128
+ @keyframes pulse {
129
+ 0%, 100% { opacity: 1; }
130
+ 50% { opacity: 0.5; }
131
+ }
132
+
133
+ /* ===== MAIN LAYOUT ===== */
134
+ .main {
135
+ max-width: 1440px;
136
  margin: 0 auto;
137
+ padding: 24px;
138
  display: grid;
139
+ grid-template-columns: 260px 1fr;
140
+ gap: 24px;
141
+ min-height: calc(100vh - 64px);
142
  }
143
 
144
+ /* ===== SIDEBAR ===== */
145
  .sidebar {
146
  display: flex;
147
  flex-direction: column;
148
+ gap: 24px;
149
+ position: sticky;
150
+ top: 88px;
151
+ height: fit-content;
152
  }
153
 
154
+ .sidebar-section {
155
+ display: flex;
156
+ flex-direction: column;
157
+ gap: 10px;
158
+ }
159
+
160
+ .section-label {
161
  font-size: 11px;
162
  font-weight: 600;
 
163
  text-transform: uppercase;
164
+ letter-spacing: 0.08em;
165
+ color: var(--text-muted);
166
+ padding-left: 4px;
167
  }
168
 
169
+ /* Toggle Buttons */
170
+ .toggle-group {
171
  display: flex;
172
  flex-direction: column;
173
+ gap: 6px;
174
  }
175
 
176
+ .toggle-btn {
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 10px;
180
+ padding: 10px 14px;
181
+ background: transparent;
182
+ border: 1px solid var(--border);
183
+ border-radius: var(--radius-md);
184
+ color: var(--text-secondary);
185
+ font-size: 13px;
186
+ font-weight: 500;
187
+ cursor: pointer;
188
+ transition: all 0.2s;
189
+ }
190
+
191
+ .toggle-btn:hover {
192
+ background: var(--bg-elevated);
193
+ color: var(--text-primary);
194
+ border-color: var(--border);
195
+ }
196
+
197
+ .toggle-btn.active {
198
+ background: var(--accent-glow);
199
+ border-color: var(--border-accent);
200
+ color: var(--accent-light);
201
+ }
202
+
203
+ .toggle-btn svg {
204
+ flex-shrink: 0;
205
+ opacity: 0.7;
206
+ }
207
+
208
+ .toggle-btn.active svg {
209
+ opacity: 1;
210
+ }
211
+
212
+ /* Strategy Cards */
213
+ .strategy-cards {
214
+ display: flex;
215
+ flex-direction: column;
216
+ gap: 8px;
217
+ }
218
+
219
+ .strategy-card {
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 12px;
223
+ padding: 14px;
224
+ background: var(--bg-surface);
225
+ border: 1px solid var(--border);
226
+ border-radius: var(--radius-md);
227
  cursor: pointer;
228
+ transition: all 0.2s;
229
  text-align: left;
 
230
  }
231
 
232
+ .strategy-card:hover {
233
+ background: var(--bg-elevated);
234
+ border-color: rgba(255, 255, 255, 0.12);
235
+ transform: translateX(2px);
236
  }
237
 
238
+ .strategy-card.active {
239
+ background: var(--accent-glow);
240
+ border-color: var(--border-accent);
 
241
  }
242
 
243
+ .strategy-icon {
244
+ width: 36px;
245
+ height: 36px;
246
+ background: var(--bg-elevated);
247
+ border-radius: var(--radius-sm);
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ color: var(--text-muted);
252
+ flex-shrink: 0;
253
+ }
254
+
255
+ .strategy-card.active .strategy-icon {
256
+ background: var(--accent);
257
+ color: white;
258
+ }
259
+
260
+ .strategy-info {
261
+ display: flex;
262
+ flex-direction: column;
263
+ gap: 2px;
264
+ }
265
+
266
+ .strategy-name {
267
+ font-size: 13px;
268
  font-weight: 600;
269
  color: var(--text-primary);
 
270
  }
271
 
272
+ .strategy-desc {
273
+ font-size: 11px;
274
+ color: var(--text-muted);
275
+ }
276
+
277
+ /* Quick Stats */
278
+ .quick-stats {
279
+ display: grid;
280
+ grid-template-columns: 1fr 1fr;
281
+ gap: 8px;
282
+ }
283
+
284
+ .quick-stat {
285
+ background: var(--bg-surface);
286
+ border: 1px solid var(--border);
287
+ border-radius: var(--radius-md);
288
+ padding: 12px;
289
+ text-align: center;
290
+ }
291
+
292
+ .quick-stat-value {
293
+ display: block;
294
+ font-size: 18px;
295
+ font-weight: 700;
296
+ color: var(--accent-light);
297
  }
298
 
299
+ .quick-stat-label {
300
+ font-size: 10px;
301
+ color: var(--text-muted);
302
+ text-transform: uppercase;
303
+ letter-spacing: 0.05em;
304
+ }
305
+
306
+ /* ===== CONTENT ===== */
307
+ .content {
308
+ min-width: 0;
309
+ }
310
+
311
+ .simulation-content {
312
  display: flex;
313
  flex-direction: column;
314
+ gap: 20px;
315
+ }
316
+
317
+ .methodology-content {
318
+ display: none;
319
  }
320
 
321
+ /* ===== PANELS ===== */
322
+ .panel {
323
+ background: var(--bg-surface);
324
+ border: 1px solid var(--border);
325
+ border-radius: var(--radius-lg);
326
+ padding: 24px;
327
  }
328
 
329
+ .panel-header {
330
+ margin-bottom: 20px;
331
  }
332
 
333
+ .panel-header h2 {
334
+ display: flex;
335
+ align-items: center;
336
+ gap: 10px;
337
+ font-size: 16px;
338
  font-weight: 600;
 
339
  color: var(--text-primary);
 
340
  }
341
 
342
+ .panel-header h2 svg {
343
+ color: var(--accent);
 
 
344
  }
345
 
346
+ /* ===== PARAMETERS ===== */
347
+ .params-grid {
348
  display: grid;
349
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
350
+ gap: 20px;
351
+ margin-bottom: 24px;
352
  }
353
 
354
+ .param-group {
355
  display: flex;
356
  flex-direction: column;
357
+ gap: 8px;
358
  }
359
 
360
+ .param-label {
361
+ display: flex;
362
+ align-items: center;
363
+ gap: 6px;
364
+ font-size: 12px;
365
+ font-weight: 500;
366
+ color: var(--text-secondary);
367
  }
368
 
369
+ .param-hint {
370
+ width: 14px;
371
+ height: 14px;
372
+ background: var(--bg-elevated);
373
+ border-radius: 50%;
374
+ font-size: 9px;
375
+ font-weight: 600;
376
+ color: var(--text-muted);
377
+ display: inline-flex;
378
+ align-items: center;
379
+ justify-content: center;
380
+ cursor: help;
381
  }
382
 
383
+ .param-input-wrap {
384
+ position: relative;
385
+ display: flex;
386
+ align-items: center;
387
+ }
388
+
389
+ .param-input {
390
+ width: 100%;
391
+ padding: 10px 14px;
392
+ background: var(--bg-base);
393
+ border: 1px solid var(--border);
394
+ border-radius: var(--radius-sm);
395
  color: var(--text-primary);
396
+ font-size: 14px;
397
+ font-weight: 500;
398
+ font-family: var(--font);
399
+ transition: all 0.2s;
400
  }
401
 
402
+ .param-input:focus {
403
+ outline: none;
404
  border-color: var(--accent);
405
+ box-shadow: 0 0 0 3px var(--accent-glow);
 
406
  }
407
 
408
+ .param-input-wrap .param-input {
409
+ padding-right: 44px;
410
+ }
411
+
412
+ .param-unit {
413
+ position: absolute;
414
+ right: 12px;
415
  font-size: 12px;
416
+ color: var(--text-muted);
417
+ pointer-events: none;
418
+ }
419
+
420
+ .param-select {
421
+ cursor: pointer;
422
+ appearance: none;
423
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
424
+ background-repeat: no-repeat;
425
+ background-position: right 12px center;
426
+ padding-right: 36px;
427
+ }
428
+
429
+ .custom-target-input {
430
+ display: none;
431
+ margin-top: 8px;
432
+ }
433
+
434
+ .custom-target-input.visible {
435
+ display: block;
436
+ }
437
+
438
+ /* Conditional visibility */
439
+ .threshold-only,
440
+ .multi-only {
441
+ display: none;
442
+ }
443
+
444
+ .threshold-only.visible,
445
+ .multi-only.visible {
446
+ display: flex;
447
  }
448
 
449
+ /* ===== RUN BUTTON ===== */
450
+ .run-container {
451
  display: flex;
452
  align-items: center;
453
  justify-content: center;
454
+ gap: 16px;
455
+ padding-top: 20px;
456
+ border-top: 1px solid var(--border);
 
457
  }
458
 
459
+ .run-btn {
460
+ display: flex;
461
+ align-items: center;
462
+ gap: 10px;
463
  padding: 12px 28px;
464
+ background: linear-gradient(135deg, var(--accent), var(--accent-dark));
465
+ border: none;
466
+ border-radius: var(--radius-md);
467
+ color: white;
468
  font-size: 14px;
469
  font-weight: 600;
470
+ font-family: var(--font);
471
  cursor: pointer;
472
+ transition: all 0.2s;
473
+ box-shadow: 0 4px 14px rgba(59, 130, 246, 0.3);
 
 
 
474
  }
475
 
476
+ .run-btn:hover:not(:disabled) {
477
+ transform: translateY(-1px);
478
+ box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4);
479
  }
480
 
481
+ .run-btn:disabled {
482
+ opacity: 0.5;
 
483
  cursor: not-allowed;
484
+ transform: none;
485
+ }
486
+
487
+ .run-btn.running .run-icon {
488
+ display: none;
489
+ }
490
+
491
+ .run-btn.running .run-spinner {
492
+ display: block;
493
  }
494
 
495
  .run-spinner {
496
+ display: none;
497
  width: 18px;
498
  height: 18px;
499
+ border: 2px solid rgba(255, 255, 255, 0.3);
500
+ border-top-color: white;
501
  border-radius: 50%;
502
  animation: spin 0.8s linear infinite;
503
  }
504
 
505
  @keyframes spin {
506
+ to { transform: rotate(360deg); }
 
507
  }
508
 
509
+ .run-estimate {
510
+ font-size: 12px;
511
+ color: var(--text-muted);
512
  }
513
 
514
+ /* ===== PROGRESS ===== */
515
+ .progress-panel {
516
+ display: none;
517
+ padding: 20px 24px;
 
 
518
  }
519
 
520
+ .progress-panel.visible {
521
+ display: block;
522
+ }
523
+
524
+ .progress-content {
525
+ max-width: 500px;
526
  margin: 0 auto;
527
  }
528
 
529
+ .progress-header {
530
  display: flex;
531
  justify-content: space-between;
532
  align-items: center;
533
  margin-bottom: 10px;
534
+ font-size: 13px;
535
+ }
536
+
537
+ #progressText {
538
  color: var(--text-primary);
539
+ font-weight: 500;
540
  }
541
 
542
+ #progressPercent {
543
+ color: var(--accent-light);
544
+ font-weight: 600;
545
+ }
546
+
547
+ .progress-track {
548
  height: 6px;
549
+ background: var(--bg-elevated);
550
  border-radius: 3px;
551
  overflow: hidden;
552
  }
553
 
554
  .progress-fill {
555
  height: 100%;
 
 
 
556
  width: 0%;
557
+ background: linear-gradient(90deg, var(--accent), var(--accent-light));
558
+ border-radius: 3px;
559
+ transition: width 0.3s ease;
560
  }
561
 
562
+ /* ===== RESULTS ===== */
563
+ .results {
564
+ display: none;
565
  flex-direction: column;
566
+ gap: 20px;
567
  }
568
 
569
+ .results.visible {
570
  display: flex;
 
 
 
 
 
 
571
  }
572
 
573
+ /* Key Metrics */
574
+ .metrics-row {
575
+ display: grid;
576
+ grid-template-columns: repeat(4, 1fr);
577
+ gap: 16px;
578
  }
579
 
580
+ .metric-card {
581
+ background: var(--bg-surface);
582
+ border: 1px solid var(--border);
583
+ border-radius: var(--radius-lg);
584
+ padding: 20px;
585
+ text-align: center;
586
+ transition: all 0.2s;
587
+ }
588
+
589
+ .metric-card:hover {
590
+ border-color: rgba(255, 255, 255, 0.12);
591
+ }
592
+
593
+ .metric-card.metric-primary {
594
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(59, 130, 246, 0.05));
595
+ border-color: var(--border-accent);
596
+ }
597
+
598
+ .metric-card.metric-risk .metric-value.high-risk {
599
+ color: var(--danger);
600
+ }
601
+
602
+ .metric-label {
603
+ font-size: 11px;
604
  font-weight: 500;
605
+ text-transform: uppercase;
606
+ letter-spacing: 0.05em;
607
+ color: var(--text-muted);
608
+ margin-bottom: 8px;
609
  }
610
 
611
+ .metric-value {
612
+ font-size: 28px;
613
+ font-weight: 700;
614
+ color: var(--text-primary);
615
+ letter-spacing: -0.02em;
616
  }
617
 
618
+ .metric-value.positive {
619
+ color: var(--success);
 
 
 
 
620
  }
621
 
622
+ .metric-value.negative {
623
+ color: var(--danger);
 
 
624
  }
625
 
626
+ /* Stats Panels */
627
+ .stats-row {
628
+ display: grid;
629
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
630
+ gap: 16px;
 
631
  }
632
 
633
+ .stat-panel {
634
+ background: var(--bg-surface);
635
+ border: 1px solid var(--border);
636
+ border-radius: var(--radius-lg);
637
+ padding: 20px;
638
  }
639
 
640
+ .stat-panel h3 {
641
+ font-size: 13px;
642
  font-weight: 600;
643
  color: var(--text-primary);
644
+ margin-bottom: 16px;
645
+ padding-bottom: 12px;
646
+ border-bottom: 1px solid var(--border);
647
  }
648
 
649
+ .stat-list {
650
  display: flex;
651
  flex-direction: column;
652
+ gap: 12px;
653
  }
654
 
655
  .stat-item {
 
658
  align-items: center;
659
  }
660
 
661
+ .stat-item .stat-label {
662
  font-size: 13px;
663
  color: var(--text-secondary);
 
664
  }
665
 
666
+ .stat-item .stat-value {
667
+ font-size: 14px;
668
  font-weight: 600;
669
  color: var(--text-primary);
 
670
  }
671
 
672
+ .multi-stats,
673
+ .threshold-stats {
674
+ display: none;
 
 
 
675
  }
676
 
677
+ .multi-stats.visible,
678
+ .threshold-stats.visible {
679
+ display: block;
680
  }
681
 
682
+ /* Charts */
683
+ .charts-row {
684
  display: grid;
685
  grid-template-columns: repeat(2, 1fr);
686
+ gap: 16px;
 
687
  }
688
 
689
+ .chart-panel {
690
+ background: var(--bg-surface);
691
+ border: 1px solid var(--border);
692
+ border-radius: var(--radius-lg);
693
+ padding: 20px;
 
 
 
 
 
 
694
  }
695
 
696
  .chart-header {
697
+ display: flex;
698
+ justify-content: space-between;
699
+ align-items: center;
700
+ margin-bottom: 16px;
701
  }
702
 
703
  .chart-header h3 {
704
+ font-size: 14px;
705
  font-weight: 600;
706
  color: var(--text-primary);
707
+ }
708
+
709
+ .export-btn {
710
+ display: flex;
711
+ align-items: center;
712
+ gap: 6px;
713
+ padding: 6px 12px;
714
+ background: var(--bg-elevated);
715
+ border: 1px solid var(--border);
716
+ border-radius: var(--radius-sm);
717
+ color: var(--text-secondary);
718
+ font-size: 12px;
719
+ font-weight: 500;
720
+ font-family: var(--font);
721
+ cursor: pointer;
722
+ transition: all 0.2s;
723
+ }
724
+
725
+ .export-btn:hover {
726
+ background: var(--bg-hover);
727
+ color: var(--text-primary);
728
  }
729
 
730
  .chart-container {
731
+ height: 280px;
732
  position: relative;
 
 
 
733
  }
734
 
735
+ .multi-chart {
736
+ display: none;
737
+ grid-column: span 2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
  }
739
 
740
+ .multi-chart.visible {
741
+ display: block;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
742
  }
743
 
744
+ /* ===== METHODOLOGY ===== */
745
+ .article {
746
+ max-width: 800px;
747
+ margin: 0 auto;
748
  }
749
 
750
+ .article-header {
751
+ text-align: center;
752
+ margin-bottom: 40px;
753
+ padding-bottom: 32px;
754
+ border-bottom: 1px solid var(--border);
755
  }
756
 
757
+ .article-header h1 {
758
+ font-size: 36px;
759
+ font-weight: 700;
760
+ color: var(--accent-light);
761
+ margin-bottom: 8px;
762
+ letter-spacing: -0.02em;
763
  }
764
 
765
+ .article-subtitle {
766
+ font-size: 16px;
767
+ color: var(--text-secondary);
768
  }
769
 
770
+ .article-section {
771
+ margin-bottom: 36px;
772
  }
773
 
774
+ .article-section h2 {
775
+ font-size: 20px;
776
+ font-weight: 600;
777
+ color: var(--text-primary);
778
+ margin-bottom: 16px;
779
+ padding-bottom: 8px;
780
+ border-bottom: 1px solid var(--border);
781
  }
782
 
783
+ .article-section p {
784
+ font-size: 15px;
785
+ color: var(--text-secondary);
786
+ margin-bottom: 16px;
787
+ line-height: 1.7;
 
788
  }
789
 
790
+ .article-list {
791
+ margin-left: 20px;
792
+ margin-bottom: 16px;
 
 
 
 
 
 
793
  }
794
 
795
+ .article-list li {
796
+ font-size: 15px;
797
+ color: var(--text-secondary);
798
+ margin-bottom: 8px;
799
+ line-height: 1.6;
800
  }
801
 
802
+ .article-section a {
803
+ color: var(--accent-light);
804
+ text-decoration: none;
 
805
  }
806
 
807
+ .article-section a:hover {
808
+ text-decoration: underline;
 
 
809
  }
810
 
811
+ /* Callouts */
812
+ .callout {
813
+ padding: 20px 24px;
814
+ border-radius: var(--radius-lg);
815
+ margin-bottom: 20px;
816
  }
817
 
818
+ .callout-intro {
819
+ background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(59, 130, 246, 0.05));
820
+ border-left: 3px solid var(--accent);
821
+ }
822
+
823
+ .callout-intro p {
824
+ color: var(--text-primary);
825
  }
826
 
827
+ .callout-intro p:last-child {
828
+ margin-bottom: 0;
 
829
  }
830
 
831
+ .callout-hypothesis {
832
+ background: var(--bg-elevated);
833
+ border: 1px solid var(--accent);
 
 
834
  }
835
 
836
+ .callout-hypothesis strong {
837
+ color: var(--accent-light);
 
 
 
838
  }
839
 
840
+ /* Tags */
841
+ .tag-group {
842
+ display: flex;
843
+ flex-wrap: wrap;
844
+ gap: 8px;
845
+ margin: 16px 0;
846
  }
847
 
848
+ .tag {
849
+ padding: 4px 12px;
850
+ border-radius: 20px;
851
+ font-size: 12px;
852
+ font-weight: 500;
 
853
  }
854
 
855
+ .tag-excluded {
856
+ background: var(--danger-bg);
857
+ color: var(--danger);
 
 
 
 
 
858
  }
859
 
860
+ /* Model Steps */
861
+ .model-steps {
862
+ display: flex;
863
+ flex-direction: column;
864
+ gap: 12px;
865
  }
866
 
867
+ .model-step {
868
+ display: flex;
869
+ align-items: center;
870
+ gap: 16px;
871
+ padding: 16px;
872
+ background: var(--bg-elevated);
873
+ border-radius: var(--radius-md);
874
  }
875
 
876
+ .step-num {
877
+ width: 28px;
878
+ height: 28px;
879
+ background: var(--accent);
880
+ border-radius: 50%;
881
+ display: flex;
882
+ align-items: center;
883
+ justify-content: center;
884
+ font-size: 13px;
885
+ font-weight: 700;
886
+ color: white;
887
+ flex-shrink: 0;
888
  }
889
 
890
+ .step-text {
891
  font-size: 14px;
 
892
  color: var(--text-primary);
893
  }
894
 
895
+ .step-text strong {
896
+ color: var(--accent-light);
 
897
  }
898
 
899
+ /* Strategy Explainer */
900
+ .strategy-explainer {
901
+ display: grid;
902
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
903
+ gap: 16px;
904
  }
905
 
906
+ .strategy-box {
907
+ background: var(--bg-elevated);
908
+ border-radius: var(--radius-lg);
909
+ padding: 20px;
910
+ }
911
+
912
+ .strategy-box h3 {
913
+ font-size: 15px;
914
  font-weight: 600;
915
+ color: var(--accent-light);
916
+ margin-bottom: 8px;
917
  }
918
 
919
+ .strategy-box p {
920
+ font-size: 13px;
921
  color: var(--text-secondary);
922
+ margin-bottom: 0;
923
+ line-height: 1.6;
924
  }
925
 
926
+ /* ===== RESPONSIVE ===== */
927
+ @media (max-width: 1024px) {
928
+ .main {
929
+ grid-template-columns: 1fr;
930
+ padding: 16px;
931
+ }
 
932
 
933
+ .sidebar {
934
+ position: static;
935
+ flex-direction: row;
936
+ flex-wrap: wrap;
937
+ gap: 16px;
938
+ }
939
 
940
+ .sidebar-section {
941
+ flex: 1;
942
+ min-width: 200px;
943
+ }
944
+
945
+ .toggle-group {
946
+ flex-direction: row;
947
+ }
948
+
949
+ .strategy-cards {
950
+ flex-direction: row;
951
+ flex-wrap: wrap;
952
+ }
953
+
954
+ .strategy-card {
955
+ flex: 1;
956
+ min-width: 180px;
957
+ }
958
+
959
+ .metrics-row {
960
+ grid-template-columns: repeat(2, 1fr);
961
+ }
962
+
963
+ .charts-row {
964
+ grid-template-columns: 1fr;
965
+ }
966
+
967
+ .multi-chart {
968
+ grid-column: span 1;
969
+ }
970
  }
971
 
972
+ @media (max-width: 640px) {
973
+ .header-content {
974
+ padding: 0 16px;
975
+ }
976
+
977
+ .brand-tagline {
978
+ display: none;
979
+ }
980
+
981
+ .params-grid {
982
+ grid-template-columns: 1fr 1fr;
983
+ }
984
+
985
+ .metrics-row {
986
+ grid-template-columns: 1fr;
987
+ }
988
+
989
+ .metric-value {
990
+ font-size: 24px;
991
+ }
992
+
993
+ .stats-row {
994
+ grid-template-columns: 1fr;
995
+ }
996
+
997
+ .strategy-explainer {
998
+ grid-template-columns: 1fr;
999
+ }
1000
+
1001
+ .run-container {
1002
+ flex-direction: column;
1003
+ gap: 12px;
1004
+ }
1005
+
1006
+ .run-btn {
1007
+ width: 100%;
1008
+ justify-content: center;
1009
+ }
1010
  }
1011
 
1012
+ /* Hidden utility */
1013
+ .simulation-view-only.hidden {
1014
+ display: none !important;
1015
  }