-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathpropforge-kodeWeave.json
More file actions
30 lines (30 loc) · 72.7 KB
/
propforge-kodeWeave.json
File metadata and controls
30 lines (30 loc) · 72.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"name": "PropForge",
"version": "0.0.1",
"title": "PropForge – Offline Prop Firm Trading Simulator",
"description": "PropForge: A Realistic Prop Firm Simulator for Index Futures\n\nPropForge is a browser-based, fully offline training simulator for serious traders.\n\nTrade MNQ/NQ using realistic bracket orders, strict margin logic, and stat tracking. Observe transparent rule-based bots in Spectator Mode — no signals, no AI.\n\nDesigned to train discipline, strategy, and self-reliance.\n\nIdeal for Topstep-style challenge prep, journaling mental reps, or simply sharpening your edge without dopamine loops or fake resets.",
"author": "Michael Schwartz",
"url": "https://michaelsboost.com/PropForge",
"meta": "",
"libraries": [
"https://cdnjs.cloudflare.com/ajax/libs/picocss/2.0.6/pico.min.css",
"https://michaelsboost.com/TailwindCSSMod/tailwind-mod-noreset.min.js"
],
"html_pre_processor": "html",
"css_pre_processor": "css",
"javascript_pre_processor": "javascript",
"html": "<div class=\"container p-4 flex flex-col gap-6\">\n <!-- Header with Challenge Progress -->\n <div class=\"flex flex-col md:flex-row justify-between items-start md:items-center gap-4\">\n <!-- Header & Settings Button for xs screens -->\n <div class=\"w-full md:w-auto flex justify-between items-start md:items-center\">\n <a class=\"no-underline\" href=\"https://github.com/michaelsboost/PropForge/\" target=\"_blank\">\n <h1 class=\"text-3xl font-bold flex items-center gap-2\">\n \n <svg \n class=\"h-14\"\n style=\"isolation:isolate\"\n viewBox=\"0 0 512 512\"\n xmlns=\"http://www.w3.org/2000/svg\"\n xmlns:svg=\"http://www.w3.org/2000/svg\">\n <g>\n <g>\n <g>\n <rect\n x=\"255.0175\"\n y=\"69\"\n width=\"11.894\"\n height=\"215.133\"\n fill=\"#ebebeb\"\n id=\"rect3\"\n style=\"stroke-width:1.41421\" />\n <rect\n x=\"234.487\"\n y=\"120.301\"\n width=\"52.956001\"\n height=\"112.531\"\n fill=\"#ebebeb\"\n id=\"rect4\" />\n </g>\n <path\n d=\"m 198.08,349.22 c 14.7,4.648 24.775,12.615 23.168,32.55 q -2.49,30.881 -43.39,39.717 V 443 h 41.889 c 16.29,-19.524 69.814,-21.076 85.328,0 h 39.562 V 421.487 C 267.841,400.98 295.767,320.306 399.973,318.754 V 297.372 H 198.08 Z m -6.62,-1.828 c -28.335,-6.907 -68.155,-7.221 -79.433,-50.02 h 79.433 z\"\n fill-rule=\"evenodd\"\n fill=\"#ebebeb\"\n id=\"path4\" />\n </g>\n <g>\n <path\n d=\"M 100.688,105.407 V 88.858 H 53.861 v 334.284 h 46.827 V 406.593 H 68.754 V 105.407 Z\"\n fill=\"#ebebeb\"\n id=\"path5\" />\n <path\n d=\"M 411.312,105.407 V 88.858 h 46.827 v 334.284 h -46.827 v -16.549 h 31.934 V 105.407 Z\"\n fill=\"#ebebeb\"\n id=\"path6\" />\n </g>\n </g>\n </svg>\n \n <span class=\"gradient-text\">PropForge</span>\n </h1>\n <p class=\"text-sm text-slate-400 mt-1\">Trade your way to a funded account</p>\n </a>\n\n <!-- Performance button for small screens -->\n <button data-open=\"performance\" class=\"block md:hidden bg-transparent border-0 shadow-none\">\n <svg class=\"w-12 h-12 trading-card transition hover:border-white\" viewBox=\"0 0 64 64\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <!-- Line path -->\n <path\n id=\"chart-line\"\n d=\"M8 48 L20 32 L32 40 L44 20 L56 28\"\n stroke=\"currentColor\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n stroke-dasharray=\"100\"\n stroke-dashoffset=\"100\">\n <animate attributeName=\"stroke-dashoffset\" from=\"100\" to=\"0\" dur=\"1.4s\" fill=\"freeze\" />\n </path>\n \n <!-- Circles on data points including the start -->\n <circle cx=\"8\" cy=\"48\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"0.2s\" fill=\"freeze\" />\n </circle>\n <circle cx=\"20\" cy=\"32\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"0.5s\" fill=\"freeze\" />\n </circle>\n <circle cx=\"32\" cy=\"40\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"0.8s\" fill=\"freeze\" />\n </circle>\n <circle cx=\"44\" cy=\"20\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"1.1s\" fill=\"freeze\" />\n </circle>\n <circle cx=\"56\" cy=\"28\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"1.4s\" fill=\"freeze\" />\n </circle>\n </svg>\n </button>\n </div>\n\n <!-- Progress & Performance Button for md+ screens -->\n <div class=\"w-full md:w-auto flex items-center gap-4\">\n <!-- Challenge Badge -->\n <div class=\"w-full m-auto flex items-center justify-center gap-3 px-4 py-2 rounded-full bg-slate-800 border border-slate-700\">\n <div class=\"w-full flex flex-col justify-between gap-1\">\n <div class=\"font-bold text-center\" id=\"account-tier\">$25K Challenge</div>\n <div class=\"progress-bar bg-slate-700 h-4 rounded-full overflow-hidden\">\n <div class=\"progress-fill bg-blue-500 h-full w-0 transition-all duration-500\"></div>\n </div>\n <div class=\"text-xs text-center text-slate-400\" id=\"account-phase\">Phase 1/2</div>\n </div>\n <div class=\"w-8 h-8 rounded-full bg-purple-600 flex items-center justify-center p-2\">\n <span class=\"progress-label text-xs font-bold\">0%</span>\n </div>\n </div>\n\n <!-- Performance button for large screens -->\n <button data-open=\"performance\" class=\"hidden md:block bg-transparent border-0 shadow-none\">\n <svg class=\"w-12 h-12 trading-card transition hover:border-white\" viewBox=\"0 0 64 64\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <!-- Line path -->\n <path\n id=\"chart-line\"\n d=\"M8 48 L20 32 L32 40 L44 20 L56 28\"\n stroke=\"currentColor\"\n stroke-linejoin=\"round\"\n fill=\"none\"\n stroke-dasharray=\"100\"\n stroke-dashoffset=\"100\">\n <animate attributeName=\"stroke-dashoffset\" from=\"100\" to=\"0\" dur=\"1.4s\" fill=\"freeze\" />\n </path>\n \n <!-- Circles on data points including the start -->\n <circle cx=\"8\" cy=\"48\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"0.2s\" fill=\"freeze\" />\n </circle>\n <circle cx=\"20\" cy=\"32\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"0.5s\" fill=\"freeze\" />\n </circle>\n <circle cx=\"32\" cy=\"40\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"0.8s\" fill=\"freeze\" />\n </circle>\n <circle cx=\"44\" cy=\"20\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"1.1s\" fill=\"freeze\" />\n </circle>\n <circle cx=\"56\" cy=\"28\" r=\"2\">\n <animate attributeName=\"r\" from=\"0\" to=\"2\" dur=\"0.5s\" begin=\"1.4s\" fill=\"freeze\" />\n </circle>\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Challenge Requirements -->\n <div class=\"grid grid-cols-1 lg:grid-cols-3 gap-6\">\n <!-- Current Challenge Card -->\n <div class=\"trading-card rounded-xl p-5\">\n <h3 class=\"font-bold text-lg mb-4 flex items-center gap-2\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 text-blue-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z\" clip-rule=\"evenodd\" />\n </svg>\n <span id=\"challenge-title\">Current Challenge</span>\n </h3>\n <div class=\"space-y-3\">\n <div class=\"flex justify-between items-center\">\n <span class=\"text-sm text-slate-400\">Profit Target:</span>\n <span id=\"profit-target\" class=\"font-mono font-medium\">$1,250</span>\n </div>\n <div class=\"flex justify-between items-center\">\n <span class=\"text-sm text-slate-400\">Max Lots Allowed:</span>\n <span class=\"font-mono font-medium\" id=\"max-lots\">4</span>\n </div>\n <div class=\"flex justify-between items-center\">\n <span class=\"text-sm text-slate-400\">Max Total Loss:</span>\n <span id=\"total-loss\" class=\"font-mono font-medium text-red-400\">$1,500</span>\n </div>\n </div>\n </div>\n \n <!-- Next Tier Card -->\n <div class=\"trading-card rounded-xl p-5\">\n <h3 class=\"font-bold text-lg mb-4 flex items-center gap-2\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5 text-purple-400\" viewBox=\"0 0 20 20\" fill=\"currentColor\">\n <path fill-rule=\"evenodd\" d=\"M5 5a3 3 0 015-2.236A3 3 0 0114.83 6H16a2 2 0 110 4h-5V9a1 1 0 10-2 0v1H4a2 2 0 110-4h1.17C5.06 5.687 5 5.35 5 5zm4 1V5a1 1 0 10-1 1h1zm3 0a1 1 0 10-1-1v1h1z\" clip-rule=\"evenodd\" />\n <path d=\"M9 11H3v5a2 2 0 002 2h4v-7zM11 18h4a2 2 0 002-2v-5h-6v7z\" />\n </svg>\n Next Tier\n </h3>\n <div class=\"space-y-3\">\n <div class=\"flex items-center gap-3\">\n <div class=\"flex-1\">\n <div class=\"text-sm mb-1\">Complete 2x $25K Challenges</div>\n <div class=\"w-full h-1.5 bg-slate-700 rounded-full overflow-hidden\">\n <div id=\"tier-progress\" class=\"h-full bg-purple-500\" style=\"width: 0%\"></div>\n </div>\n </div>\n <span id=\"tier-progress-text\" class=\"text-xs font-mono\">0/2</span>\n </div>\n <div class=\"text-sm text-slate-400\" id=\"next-tier-hint\">\n Unlocks $50K Challenge with higher limits\n </div>\n </div>\n </div>\n \n <!-- Account Summary -->\n <div class=\"trading-card rounded-xl p-5\">\n <h2 class=\"font-bold text-lg mb-4\">💰 Account Summary</h2>\n <div class=\"space-y-4\">\n <div class=\"flex justify-between items-center\">\n <span class=\"text-slate-400\">Balance:</span>\n <span id=\"balance\" class=\"font-mono font-bold text-xl\">$25,000.00</span>\n </div>\n <div data-find=\"margin\" class=\"hidden flex justify-between items-center\">\n <span class=\"text-slate-400\">Margin Used:</span>\n <span id=\"marginUsed\" class=\"font-mono\">$0.00</span>\n </div>\n <div data-find=\"margin\" class=\"hidden flex justify-between items-center\">\n <span class=\"text-slate-400\">Available Margin:</span>\n <span id=\"availableMargin\" class=\"font-mono text-green-400\">$25,000.00</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Main Trading Interface -->\n <div class=\"grid grid-cols-1 gap-6\">\n <!-- Chart Column -->\n <div class=\"trading-card rounded-xl p-5\">\n <div class=\"flex justify-between items-center mb-4\">\n <h2 class=\"font-bold text-lg\">Price Chart</h2>\n <div class=\"flex items-center gap-2\">\n <span class=\"inline-block mb-4 font-mono px-3 py-1 text-sm\">C:<span id=\"current-profit\">$0.00</span></span>\n <span class=\"inline-block mb-4 font-mono px-3 py-1 text-sm\">U:<span id=\"open-pnl\">$0.00</span></span>\n <span class=\"inline-block mb-4 font-mono px-3 py-1 text-sm\">L:<span id=\"totalOpenLots\">1</span></span>\n </div>\n </div>\n \n <!-- Chart Canvas -->\n <div class=\"relative\">\n <canvas id=\"chart\" class=\"w-full h-[400px]\"></canvas>\n <div id=\"tradeMessage\" class=\"text-sm text-red-400 mt-2 h-5\"></div>\n </div>\n \n <!-- Trading Controls -->\n <div class=\"mt-6 grid grid-cols-3 gap-3\">\n <button id=\"buy\" class=\"bg-green-600 hover:bg-green-700 text-white font-bold py-3 rounded-lg flex items-center justify-center gap-2 border-0\">\n <span>BUY</span>\n <span class=\"text-xs opacity-80\">(B)</span>\n </button>\n <button id=\"sell\" class=\"bg-red-600 hover:bg-red-700 text-white font-bold py-3 rounded-lg flex items-center justify-center gap-2 border-0\">\n <span>SELL</span>\n <span class=\"text-xs opacity-80\">(S)</span>\n </button>\n <button id=\"close\" class=\"bg-amber-500 hover:bg-amber-600 text-white font-bold py-3 rounded-lg flex items-center justify-center gap-2 border-0\">\n <span>Close All</span>\n <span class=\"text-xs opacity-80\">(X)</span>\n </button>\n </div>\n \n <!-- Contract Selection -->\n <div class=\"mt-6 grid grid-cols-2 gap-4\">\n <div>\n <label class=\"block text-sm text-slate-400 mb-1\">Contract</label>\n <div class=\"flex gap-2\">\n <!-- MNQ Button -->\n <label class=\"flex-1\">\n <input type=\"radio\" name=\"contract\" value=\"micro\" class=\"peer hidden\" checked>\n <div class=\"w-full h-full bg-slate-800 hover:bg-slate-700 border border-solid border-blue-500/30 \n py-2 rounded-lg text-center\n text-slate-300 peer-checked:text-blue-400\">\n MNQ\n </div>\n </label>\n\n <!-- NQ Button -->\n <label class=\"flex-1\">\n <input type=\"radio\" name=\"contract\" value=\"mini\" class=\"peer hidden\">\n <div class=\"w-full h-full bg-slate-800 hover:bg-slate-700 border border-solid border-blue-500/30 \n py-2 rounded-lg text-center\n text-slate-300 peer-checked:text-blue-400\">\n NQ\n </div>\n </label>\n </div>\n </div>\n \n <div>\n <nav>\n <label class=\"block text-sm text-slate-400 mb-1\">Position Size</label>\n <span id=\"lotSizeDisplay\" class=\"text-sm text-slate-400\">1</span>\n </nav>\n <!-- Lot Size Selection -->\n <div class=\"lot-option w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2 flex gap-2 flex-wrap justify-center text-sm\">\n <label class=\"flex items-center gap-1\">\n <button id=\"lotDecrease\" class=\"px-3 py-1 bg-gray-700 text-white rounded border-0\">−</button>\n </label>\n <div class=\"flex flex-row items-center gap-1\">\n <label class=\"flex flex-col items-center gap-1\">\n <input type=\"radio\" name=\"lot\" value=\"1\" checked class=\"form-radio\"> \n <div>1</div>\n </label>\n <label class=\"flex flex-col items-center gap-1\">\n <input type=\"radio\" name=\"lot\" value=\"3\" class=\"form-radio\"> \n <div>3</div>\n </label>\n <label class=\"flex flex-col items-center gap-1\">\n <input type=\"radio\" name=\"lot\" value=\"5\" class=\"form-radio\"> \n <div>5</div>\n </label>\n <label class=\"flex flex-col items-center gap-1\">\n <input type=\"radio\" name=\"lot\" value=\"10\" class=\"form-radio\"> \n <div>10</div>\n </label>\n <label class=\"flex flex-col items-center gap-1\">\n <input type=\"radio\" name=\"lot\" value=\"15\" class=\"form-radio\"> <div>15</div>\n </label>\n </div>\n <label class=\"flex items-center gap-1\">\n <button id=\"lotIncrease\" class=\"px-3 py-1 bg-gray-700 text-white rounded border-0\">+</button>\n </label>\n </div>\n </div>\n </div>\n \n <!-- Risk Management -->\n <div class=\"mt-6 grid grid-cols-2 gap-4\">\n <div>\n <label class=\"block text-sm text-slate-400 mb-1\">Stop Loss ($)</label>\n <input id=\"stopLossAmount\" type=\"number\" value=\"50\" class=\"w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2\">\n </div>\n <div>\n <label class=\"block text-sm text-slate-400 mb-1\">Take Profit ($)</label>\n <input id=\"takeProfitAmount\" type=\"number\" value=\"100\" class=\"w-full bg-slate-800 border border-slate-700 rounded-lg px-3 py-2\">\n </div>\n </div>\n </div>\n </div>\n \n <!-- Trade History -->\n <div class=\"trading-card rounded-xl p-5 mt-6\">\n <div class=\"flex justify-between items-center mb-4\">\n <h2 class=\"font-bold text-lg\">📋 Trade History</h2>\n <div class=\"flex gap-4\">\n <div class=\"text-sm\">\n <span class=\"text-slate-400\">Total Trades:</span>\n <span id=\"totalTrades\" class=\"font-bold ml-2\">0</span>\n </div>\n <div class=\"text-sm\">\n <span class=\"text-slate-400\">Profitability:</span>\n <span id=\"winlossratio\" class=\"font-bold ml-2\">0%</span>\n </div>\n </div>\n </div>\n \n <div class=\"overflow-x-auto max-h-96 overflow-y-auto\">\n <table id=\"history\" class=\"w-full text-sm\">\n <thead class=\"sticky inset-x-0 top-0\">\n <tr class=\"text-left border-b border-slate-700\">\n <th class=\"pb-2 text-slate-400\">Time</th>\n <th class=\"pb-2 text-slate-400\">Type</th>\n <th class=\"pb-2 text-slate-400\">Size</th>\n <th class=\"pb-2 text-slate-400\">Entry</th>\n <th class=\"pb-2 text-slate-400\">Exit</th>\n <th class=\"pb-2 text-slate-400\">P&L</th>\n <th class=\"pb-2 text-slate-400\">Duration</th>\n <th class=\"pb-2 text-slate-400\">Reason</th>\n </tr>\n </thead>\n <tbody class=\"divide-y divide-slate-700\">\n <tr>\n <td colspan=\"8\" class=\"py-8 text-center text-slate-500\">No trades yet</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n</div>",
"css": "/* Custom hybrid styles */ \n:root {\n --bg-dark: #14152f;\n --bg-darker: #0e0f20;\n --bg-panel: #1c1d38;\n --green: #00c853;\n --red: #ff3d00;\n --yellow: #ffd600;\n --blue: #2962ff;\n --purple: #8b5cf6;\n}\nbody {\n background-color: #0f172a;\n color: white;\n font-family: 'Inter', sans-serif;\n}\n.trading-card {\n background: #1e293b;\n border-radius: 12px;\n border: 1px solid #334155;\n}\n.challenge-badge {\n background: linear-gradient(135deg, var(--bg-panel) 0%, var(--bg-darker) 100%);\n border: 1px solid var(--purple);\n border-radius: 999px;\n padding: 4px 12px;\n font-size: 0.85rem;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n.phase-indicator {\n position: relative;\n height: 8px;\n background: rgba(255,255,255,0.1);\n border-radius: 4px;\n overflow: hidden;\n}\n.phase-progress {\n position: absolute;\n left: 0;\n top: 0;\n bottom: 0;\n background: linear-gradient(90deg, var(--blue), var(--purple));\n width: 0%;\n transition: width 0.3s ease;\n}\n#chart {\n height: 400px;\n width: 100%;\n}\n\n/* Trade type indicators */\n.trade-type-buy {\n color: var(--green);\n background: rgba(0, 200, 83, 0.1);\n}\n.trade-type-sell {\n color: var(--red);\n background: rgba(255, 61, 0, 0.1);\n}\n\n.lot-option label {\n width: 2rem;\n}\n.lot-option input[type=\"radio\"] {\n margin-inline-end: 0 !important;\n margin-top: 0;\n}\n",
"javascript": "/*\n=== TODOS ===\n\n1. ✏️ Drawing Tools & Chart Markups (Manual User Tools First)\n - [ ] Trendlines (snap to candles, draggable)\n - [ ] Support/Resistance Zones (horizontal lines or boxes)\n - [ ] Channels (parallel lines, optionally snapped)\n - [ ] Freehand Tool (optional - only if lightweight)\n - [ ] Erase / Select / Modify drawings\n - [ ] Store drawings in localStorage (small, efficient format)\n - [ ] Ensure drawings persist across sessions & chart loads\n\n2. 🧠 Chart Indicator Engine (Lightweight but Bot-Compatible)\n - [ ] Swing High/Low detection\n - [ ] Auto Support/Resistance from structure\n - [ ] Basic Trendline detection (for bot use only)\n - [ ] Optional: Moving Averages, ATR, or VWAP (if needed for bot logic)\n\n3. 🤖 Strategy Bot Framework\n - [ ] Base bot engine to run logic on OHLC stream\n - [ ] Visualize trades with ghost entries/exits, lines/arrows\n - [ ] Bots:\n - ICT Ghost (S/R + FVG + SMT logic)\n - FVG Sniper (Fair Value Gap entries, 1:3 RR)\n - Price Action Pro (Candlestick patterns, S/R, trendlines)\n - Trend Trader (MA crossovers or swing alignment)\n - S/R Hunter (zones + price reaction)\n - Breakout Bot (range + volume or structure breaks)\n - Structure Bot (HH/HL or LH/LL recognition)\n - Scalper (fast entries/exits on short timeframe logic)\n - [ ] Update bot stats live (PnL, win rate, trades)\n - [ ] Save bot stats in localStorage\n\n4. 🏆 Leaderboard UI\n - [ ] Display user vs. bots:\n - Win rate\n - Avg win/loss\n - ROI\n - Max drawdown\n - [ ] Optional: filter by bot type or session\n\n// 🚫 Replay system intentionally excluded due to localStorage space limits.\n*/\n\n// === PHASE CONFIGURATION ===\nconst Phases = {\n \"25k_phase1\": {\n name: \"25K Challenge - Phase 1\",\n target: 1250,\n maxLoss: 1500,\n maxLots: 2,\n startBalance: 25000,\n next: \"25k_phase2\"\n },\n \"25k_phase2\": {\n name: \"25K Challenge - Phase 2 (Sim Funded)\",\n target: 1250,\n maxLoss: 1500,\n maxLots: 2,\n startBalance: 25000,\n next: \"50k_phase1\"\n },\n \"50k_phase1\": {\n name: \"50K Challenge - Phase 1\",\n target: 2500,\n maxLoss: 3000,\n maxLots: 5,\n startBalance: 50000,\n next: \"50k_phase2\"\n },\n \"50k_phase2\": {\n name: \"50K Challenge - Phase 2 (Sim Funded)\",\n target: 2500,\n maxLoss: 3000,\n maxLots: 5,\n startBalance: 50000,\n next: \"100k_phase1\"\n },\n \"100k_phase1\": {\n name: \"100K Challenge - Phase 1\",\n target: 6000,\n maxLoss: 6000,\n maxLots: 12,\n startBalance: 100000,\n next: \"100k_phase2\"\n },\n \"100k_phase2\": {\n name: \"100K Challenge - Phase 2 (Sim Funded)\",\n target: 6000,\n maxLoss: 6000,\n maxLots: 12,\n startBalance: 100000,\n next: \"150k_phase1\"\n },\n \"150k_phase1\": {\n name: \"150K Challenge - Phase 1\",\n target: 9000,\n maxLoss: 7500,\n maxLots: 15,\n startBalance: 150000,\n next: \"150k_phase2\"\n },\n \"150k_phase2\": {\n name: \"150K Challenge - Phase 2 (Sim Funded)\",\n target: 9000,\n maxLoss: 7500,\n maxLots: 15,\n startBalance: 150000,\n next: \"300k_phase1\"\n },\n \"300k_phase1\": {\n name: \"300K Challenge - Phase 1\",\n target: 18000,\n maxLoss: 12000,\n maxLots: 25,\n startBalance: 300000,\n next: \"300k_phase2\"\n },\n \"300k_phase2\": {\n name: \"300K Challenge - Phase 2 (Sim Funded)\",\n target: 18000,\n maxLoss: 12000,\n maxLots: 25,\n startBalance: 300000,\n next: \"1m_phase1\"\n },\n \"1m_phase1\": {\n name: \"1M Challenge - Phase 1\",\n target: 40000,\n maxLoss: 25000,\n maxLots: 40,\n startBalance: 1000000,\n next: \"1m_phase2\"\n },\n \"1m_phase2\": {\n name: \"1M Challenge - Phase 2 (Sim Funded)\",\n target: 40000,\n maxLoss: 25000,\n maxLots: 40,\n startBalance: 1000000,\n next: null\n }\n};\n\n// === CORE MODULE ===\nconst Core = (() => {\n const state = {\n phase: \"25k_phase1\",\n balance: 25000,\n marginUsed: 0,\n contract: 'mini',\n lotSize: 1,\n stopLoss: 10,\n takeProfit: 10,\n trades: [],\n openTrades: [],\n currentPrice: 4215.25,\n todayPNL: 0,\n justAdvanced: false,\n isFullyTrained: false,\n challenge: {\n phase: 1,\n level: '25K',\n profitTarget: 1250,\n maxTotalLoss: 1500,\n startingBalance: 25000,\n phaseProgress: 0\n },\n candles: (() => {\n const candles = [];\n let price = 4200;\n let trend = 1;\n for (let i = 0; i < 100; i++) {\n const volatility = 3;\n const open = price;\n const bodySize = Math.random() * volatility;\n const direction = Math.random() > 0.3 ? trend : -trend;\n const close = open + bodySize * direction;\n const high = Math.max(open, close) + Math.random();\n const low = Math.min(open, close) - Math.random();\n price = close;\n trend = direction;\n candles.push({ open, close, high, low, timestamp: Date.now() - (1000 * (100 - i)) });\n }\n return candles;\n })(),\n };\n \n function clearStorage() {\n // Clear local storage\n localStorage.removeItem('PropForge');\n \n // Clear session storage specific to PropForge (if you use a specific key)\n sessionStorage.removeItem('PropForge');\n \n // Clear cookies specific to PropForge\n document.cookie.split(\";\").forEach(function(c) {\n if (c.trim().startsWith('PropForge')) {\n document.cookie = c.trim().split(\"=\")[0] + \n '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/';\n }\n });\n \n // Clear service worker caches specific to PropForge\n if ('caches' in window) {\n caches.keys().then(function(names) {\n names.forEach(function(name) {\n if (name === 'PropForge-cache') {\n caches.delete(name);\n }\n });\n });\n }\n \n // Unregister service workers specific to PropForge\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.getRegistrations().then(function(registrations) {\n registrations.forEach(function(registration) {\n if (registration.scope.includes('PropForge')) {\n registration.unregister();\n }\n });\n });\n }\n }\n\n function resetChallenge(string) {\n const currentPhase = Core.state.phase;\n const config = Phases[currentPhase];\n\n if (string) {\n alert(\"🚨 Max total loss exceeded. Resetting the challenge...\");\n }\n \n Core.state.balance = config.startBalance;\n Core.state.marginUsed = 0;\n Core.state.trades = [];\n Core.state.openTrades = [];\n Core.state.todayPNL = 0;\n \n Core.state.challenge = {\n phase: 1,\n level: '25K',\n profitTarget: 1250,\n maxTotalLoss: 1500,\n startingBalance: 25000,\n speed: 'tick',\n phaseProgress: 0\n };\n \n Stats.update(Core.state);\n Chart.draw(Core.state);\n\n clearStorage();\n }\n\n function resetPhase() {\n const config = Phases[state.phase];\n state.balance = config.startBalance;\n state.marginUsed = 0;\n state.trades = [];\n state.openTrades = [];\n state.todayPNL = 0;\n clearStorage();\n }\n\n function showPhaseCompleteNotification(challenge) {\n if (Core.state.isFullyTrained) return;\n \n const title = `${challenge.level} Phase ${challenge.phase} Complete!`;\n const body = \"You've successfully advanced to the next challenge phase.\";\n \n // Request permission if not already granted\n if (Notification.permission === \"granted\") {\n new Notification(title, { body });\n } else if (Notification.permission !== \"denied\") {\n Notification.requestPermission().then(permission => {\n if (permission === \"granted\") {\n new Notification(title, { body });\n }\n });\n }\n }\n\n function advancePhase() {\n const currentPhaseKey = Core.state.phase;\n const currentPhase = Phases[currentPhaseKey];\n const nextPhaseKey = currentPhase?.next;\n \n Core.state.justAdvanced = true;\n \n // ✅ Exit if there's no next phase\n if (!nextPhaseKey) {\n if (!Core.state.isFullyTrained) {\n Core.state.isFullyTrained = true;\n \n // ✅ Trigger final training complete notification\n const title = \"🎉 Training Complete!\";\n const body = \"You're now free to trade without limits.\";\n \n if (Notification.permission === \"granted\") {\n new Notification(title, { body });\n } else if (Notification.permission !== \"denied\") {\n Notification.requestPermission().then(permission => {\n if (permission === \"granted\") {\n new Notification(title, { body });\n }\n });\n }\n \n Stats.update(Core.state); // ✅ Ensure UI reflects free mode\n }\n \n Core.state.justAdvanced = false;\n return;\n }\n \n // Move to next phase config\n const nextConfig = Phases[nextPhaseKey];\n Core.state.phase = nextPhaseKey;\n \n Core.state.balance = nextConfig.startBalance;\n Core.state.marginUsed = 0;\n Core.state.trades = [];\n Core.state.openTrades = [];\n Core.state.todayPNL = 0;\n \n Core.state.challenge = {\n phase: nextConfig.name.includes(\"Phase 1\") ? 1 : 2,\n level: nextConfig.name.split(\" \")[0].replace(\"K\", \"K\"), // e.g. '50K'\n profitTarget: nextConfig.target,\n maxTotalLoss: nextConfig.maxLoss,\n startingBalance: nextConfig.startBalance,\n phaseProgress: 0\n };\n \n setTimeout(() => {\n Core.state.justAdvanced = false;\n }, 1500);\n \n Stats.update(Core.state);\n }\n\n function evaluatePhaseRules() {\n if (state.justAdvanced) return;\n \n const config = Phases[state.phase];\n if (!config) return; // ✅ Prevent error after all phases done\n \n const totalProfit = state.balance - config.startBalance;\n \n // Max loss enforcement (if desired)\n if (state.balance <= config.startBalance - config.maxLoss) {\n alert(\"🚨 Challenge failed. Restarting 25K Phase 1.\");\n Core.state.phase = \"25k_phase1\";\n resetPhase();\n return;\n }\n \n // ✅ Profit Target Met\n if (totalProfit >= config.target) {\n Core.state.justAdvanced = true;\n showPhaseCompleteNotification(Core.state.challenge);\n advancePhase();\n }\n }\n\n function getMaxLotsAllowed() {\n const config = Phases[state.phase];\n if (!config) return Infinity; // Free mode\n return state.contract === 'micro'\n ? config.maxLots * 10 // 10 micros = 1 mini\n : config.maxLots;\n }\n\n function simulateTick() {\n const { challenge } = state;\n \n // Adjust volatility based on speed\n let changeFactor = 2;\n \n // Simulate price change\n const change = (Math.random() * changeFactor - changeFactor / 2).toFixed(2);\n state.currentPrice = parseFloat((state.currentPrice + parseFloat(change)).toFixed(2));\n \n const last = state.candles[state.candles.length - 1];\n const now = Date.now();\n \n if (!last || now - last.timestamp > 1000) {\n state.candles.push({\n open: state.currentPrice,\n high: state.currentPrice,\n low: state.currentPrice,\n close: state.currentPrice,\n timestamp: now\n });\n } else {\n last.high = Math.max(last.high, state.currentPrice);\n last.low = Math.min(last.low, state.currentPrice);\n last.close = state.currentPrice;\n }\n \n if (state.candles.length > 200) state.candles.shift();\n \n Trades.evaluate(state); // triggers daily loss check\n evaluatePhaseRules(); // handles progress/advancement\n\n // === Check unrealized + realized loss total\n const floatingLoss = state.openTrades.reduce((acc, trade) => {\n const entry = trade.entry;\n const current = state.currentPrice;\n const unrealized = trade.type === 'buy'\n ? (current - entry)\n : (entry - current);\n \n return acc + (unrealized * trade.qty * trade.contractValue);\n }, 0);\n \n const totalLoss = state.challenge.startingBalance - (state.balance + floatingLoss);\n \n if (totalLoss >= state.challenge.maxTotalLoss) {\n resetChallenge();\n return; // Stop this tick from continuing\n }\n\n Stats.update(state);\n Chart.draw(state);\n }\n\n setInterval(simulateTick, 100);\n\n return { state, resetPhase, advancePhase, getMaxLotsAllowed };\n})();\n\n// === MODAL MODULE ===\nconst Modal = (() => {\n function render({\n large,\n title = \"Are you sure you want to proceed?\",\n content,\n CloseLabel,\n ConfirmLabel,\n onLoad,\n onClose,\n onConfirm\n }) {\n const hClass = \"text-lg font-thin m-0\";\n const buttonClass = \"text-xs w-auto px-3 py-2 m-0 capitalize rounded-md\";\n const svgClass = \"w-3\";\n const times = `<svg class=\"${svgClass}\" viewBox=\"0 0 384 512\">\n <path fill=\"currentColor\" d=\"M342.6 150.6c12.5-12.5 12.5-32.8 \n 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5\n -45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 \n 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 \n 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z\"/>\n </svg>`;\n\n const html = `<article class=\"${large ? 'flex flex-col h-3/4' : ''} rounded-md\">\n <header class=\"${large ? 'flex-none' : ''} flex justify-between items-center\">\n <h1 class=\"${hClass}\">${title}</h1>\n <button class=\"${buttonClass} bg-transparent border-0\" style=\"color: unset;\" aria-label=\"Close\">${times}</button>\n </header>\n <main class=\"font-thin ${large ? 'flex-grow' : ''}\">\n ${content || ''}\n </main>\n <footer ${large ? 'class=\"flex-none\"' : ''}>\n <button class=\"${buttonClass} bg-transparent border border-gray-600\" aria-label=\"Close\">${CloseLabel || 'close'}</button>\n ${onConfirm ? `<button class=\"${buttonClass}\" aria-label=\"Confirm\">${ConfirmLabel || 'confirm'}</button>` : ''}\n </footer>\n </article>`;\n\n const modal = document.createElement('dialog');\n modal.open = true;\n modal.innerHTML = html;\n document.body.appendChild(modal);\n\n if (onLoad && typeof onLoad === 'function') onLoad();\n\n const timesBtn = modal.querySelector('header button');\n const closeBtn = modal.querySelector('footer button:first-child');\n const confirmBtn = modal.querySelector('footer button:last-child');\n\n const closeModal = () => {\n document.body.removeChild(modal);\n };\n\n timesBtn.onclick = () => {\n if (onClose) onClose();\n closeModal();\n };\n\n closeBtn.onclick = () => {\n if (onClose) onClose();\n closeModal();\n };\n\n if (onConfirm && confirmBtn) {\n confirmBtn.onclick = () => {\n onConfirm();\n closeModal();\n };\n }\n }\n\n return { render };\n})();\n\n// === CHART MODULE ===\nconst Chart = (() => {\n const canvas = document.getElementById(\"chart\");\n const ctx = canvas.getContext(\"2d\");\n\n let scaleX = 1;\n let offsetX = 0;\n let isDragging = false;\n let lastX = 0;\n let selectedLine = null;\n let dragOffset = 0;\n\n canvas.addEventListener(\"wheel\", (e) => {\n e.preventDefault();\n const delta = e.deltaY > 0 ? 0.9 : 1.1;\n scaleX *= delta;\n scaleX = Math.max(0.5, Math.min(scaleX, 10));\n });\n\n function getXYFromEvent(e) {\n const rect = canvas.getBoundingClientRect();\n const clientX = e.touches ? e.touches[0].clientX : e.clientX;\n const clientY = e.touches ? e.touches[0].clientY : e.clientY;\n return { x: clientX - rect.left, y: clientY - rect.top };\n }\n\n function handleDown(e) {\n const { x, y } = getXYFromEvent(e);\n selectedLine = null;\n\n const state = Core.state;\n const height = canvas.height;\n const padding = 10;\n const viewCount = Math.floor(60 / scaleX);\n const start = Math.max(0, state.candles.length - viewCount - Math.floor(offsetX));\n const candles = state.candles.slice(start, start + viewCount);\n let max = Math.max(...candles.map(c => c.high));\n let min = Math.min(...candles.map(c => c.low));\n const priceBuffer = (max - min) * 0.05;\n max += priceBuffer;\n min -= priceBuffer;\n\n state.openTrades.forEach(trade => {\n max = Math.max(max, trade.entry, trade.stop, trade.target);\n min = Math.min(min, trade.entry, trade.stop, trade.target);\n });\n\n const scaleY = (height - 2 * padding) / (max - min);\n\n state.openTrades.forEach(trade => {\n const test = (price, key) => {\n const py = height - (price - min) * scaleY - padding;\n const tolerance = e.touches ? 10 : 6;\n if (Math.abs(py - y) < tolerance) {\n selectedLine = { trade, key };\n dragOffset = py - y;\n }\n };\n test(trade.stop, 'stop');\n test(trade.target, 'target');\n });\n\n if (!selectedLine) {\n isDragging = true;\n lastX = e.touches ? e.touches[0].clientX : e.clientX;\n }\n }\n\n function handleUpLeave() {\n isDragging = false;\n selectedLine = null;\n }\n\n function handleMove(e) {\n if (!canvas) return;\n\n const state = Core.state;\n const rect = canvas.getBoundingClientRect();\n const clientX = e.touches ? e.touches[0].clientX : e.clientX;\n const clientY = e.touches ? e.touches[0].clientY : e.clientY;\n const y = clientY - rect.top;\n\n const height = canvas.height;\n const padding = 10;\n const viewCount = Math.floor(60 / scaleX);\n const start = Math.max(0, state.candles.length - viewCount - Math.floor(offsetX));\n const candles = state.candles.slice(start, start + viewCount);\n let max = Math.max(...candles.map(c => c.high));\n let min = Math.min(...candles.map(c => c.low));\n const priceBuffer = (max - min) * 0.05;\n max += priceBuffer;\n min -= priceBuffer;\n\n state.openTrades.forEach(trade => {\n max = Math.max(max, trade.entry, trade.stop, trade.target);\n min = Math.min(min, trade.entry, trade.stop, trade.target);\n });\n\n const scaleY = (height - 2 * padding) / (max - min);\n\n if (selectedLine) {\n e.preventDefault();\n const newPrice = ((height - (y + dragOffset) - padding) / scaleY) + min;\n const rounded = parseFloat(newPrice.toFixed(2));\n selectedLine.trade[selectedLine.key] = rounded;\n \n if (selectedLine.key === 'stop') selectedLine.trade.stopMoved = true;\n if (selectedLine.key === 'target') selectedLine.trade.targetMoved = true;\n }\n }\n\n canvas.addEventListener(\"mousedown\", handleDown);\n canvas.addEventListener(\"touchstart\", handleDown);\n canvas.addEventListener(\"mousemove\", handleMove);\n canvas.addEventListener(\"touchmove\", handleMove, { passive: false });\n canvas.addEventListener(\"mouseup\", handleUpLeave);\n canvas.addEventListener(\"touchend\", handleUpLeave);\n canvas.addEventListener(\"mouseleave\", handleUpLeave);\n canvas.addEventListener(\"touchcancel\", handleUpLeave);\n\n function draw(state) {\n if (!canvas || !ctx) return;\n\n canvas.width = canvas.offsetWidth;\n canvas.height = canvas.offsetHeight;\n const width = canvas.width;\n const height = canvas.height;\n const padding = 10;\n const labelMargin = 60;\n\n ctx.clearRect(0, 0, width, height);\n\n const viewCount = Math.floor(60 / scaleX);\n const start = Math.max(0, state.candles.length - viewCount - Math.floor(offsetX));\n const candles = state.candles.slice(start, start + viewCount);\n if (candles.length < 2) return;\n\n let max = Math.max(...candles.map(c => c.high));\n let min = Math.min(...candles.map(c => c.low));\n const priceBuffer = (max - min) * 0.05;\n max += priceBuffer;\n min -= priceBuffer;\n\n state.openTrades.forEach(trade => {\n max = Math.max(max, trade.entry, trade.stop, trade.target);\n min = Math.min(min, trade.entry, trade.stop, trade.target);\n });\n\n const scaleY = (height - 2 * padding) / (max - min);\n const candleWidth = (width - labelMargin) / candles.length;\n\n // Grid\n ctx.strokeStyle = \"#333\";\n ctx.fillStyle = \"#888\";\n ctx.font = \"10px sans-serif\";\n ctx.textAlign = \"right\";\n ctx.textBaseline = \"middle\";\n\n const steps = 10;\n for (let i = 0; i <= steps; i++) {\n const y = padding + ((height - 2 * padding) * i / steps);\n const price = max - ((max - min) * i / steps);\n ctx.beginPath();\n ctx.moveTo(0, y);\n ctx.lineTo(width, y);\n ctx.stroke();\n ctx.fillText(price.toFixed(2), width - 4, y);\n }\n\n // Candles\n candles.forEach((c, i) => {\n const x = i * candleWidth;\n const openY = height - (c.open - min) * scaleY - padding;\n const closeY = height - (c.close - min) * scaleY - padding;\n const highY = height - (c.high - min) * scaleY - padding;\n const lowY = height - (c.low - min) * scaleY - padding;\n\n const isBullish = c.close >= c.open;\n ctx.strokeStyle = isBullish ? \"#089a81\" : \"#f33645\";\n ctx.fillStyle = isBullish ? \"#089a81\" : \"#f33645\";\n\n ctx.beginPath();\n ctx.moveTo(x + candleWidth / 2, highY);\n ctx.lineTo(x + candleWidth / 2, lowY);\n ctx.stroke();\n\n const bodyTop = isBullish ? closeY : openY;\n const bodyHeight = Math.max(1, Math.abs(openY - closeY));\n ctx.fillRect(x + 1, bodyTop, candleWidth - 2, bodyHeight);\n });\n\n // Trade Lines\n state.openTrades.forEach(trade => {\n const drawLine = (price, color, label, key) => {\n const y = height - (price - min) * scaleY - padding;\n\n ctx.strokeStyle = (selectedLine && selectedLine.trade === trade && selectedLine.key === key)\n ? \"#ff0\" // Highlight if being dragged\n : color;\n\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.moveTo(0, y);\n ctx.lineTo(width, y);\n ctx.stroke();\n\n let dollar = '';\n if (label === 'SL' || label === 'TP') {\n const direction = trade.type === 'buy' ? 1 : -1;\n const delta = (price - trade.entry) * direction;\n \n // Round to nearest tick\n const tickSize = 0.25;\n const roundedTicks = Math.round(delta / tickSize);\n const tickValue = state.contract === 'micro' ? 0.50 : 5.00;\n const pnl = roundedTicks * tickValue * trade.qty;\n \n const sign = pnl >= 0 ? '+' : '';\n dollar = ` → ${sign}$${pnl.toFixed(2)} (${roundedTicks} ticks)`;\n }\n\n ctx.fillStyle = ctx.strokeStyle;\n ctx.font = \"11px sans-serif\";\n ctx.textAlign = \"left\";\n ctx.fillText(`${label} ${price.toFixed(2)}${dollar}`, 8, y - 4);\n };\n\n drawLine(trade.entry, trade.type === 'buy' ? '#0f0' : '#f00', 'Entry');\n drawLine(trade.stop, '#888', 'SL', 'stop');\n drawLine(trade.target, '#888', 'TP', 'target');\n });\n\n // Live Price Box\n const liveY = height - (state.currentPrice - min) * scaleY - padding;\n ctx.fillStyle = \"#111\";\n ctx.strokeStyle = \"#0ff\";\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.rect(width - labelMargin, liveY - 10, 55, 20);\n ctx.fill();\n ctx.stroke();\n\n ctx.fillStyle = \"#0ff\";\n ctx.font = \"12px sans-serif\";\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(state.currentPrice.toFixed(2), width - labelMargin + 27.5, liveY);\n }\n\n return { draw };\n})();\n\n// === TRADES MODULE ===\nconst Trades = (() => {\n function showTradeMessage(msg) {\n const el = document.getElementById(\"tradeMessage\");\n if (el) {\n el.textContent = msg;\n setTimeout(() => { el.textContent = \"\"; }, 3000);\n }\n }\n\n function placeFreeModeTrade(type, state, lot, entry) {\n const contract = state.contract === 'micro' ? 2.0 : 20.0;\n const margin = state.contract === 'micro' ? 500 : 10000;\n const requiredMargin = lot * margin;\n const availableMargin = state.balance - state.marginUsed;\n\n // Show margin they earned it\n document.querySelectorAll('[data-find=margin]').forEach(e => {\n if (!e.classList.contains('hidden')) return;\n e.classList.remove('hidden');\n });\n \n if (requiredMargin > availableMargin) {\n showTradeMessage(`❌ Not enough margin: Need $${requiredMargin}, Have $${availableMargin}`);\n return;\n }\n \n const trade = {\n type,\n qty: lot,\n entry,\n stop: type === 'buy' ? entry - state.stopLoss : entry + state.stopLoss,\n target: type === 'buy' ? entry + state.takeProfit : entry - state.takeProfit,\n entryTime: Date.now(),\n contractValue: contract,\n marginRequired: requiredMargin\n };\n \n state.openTrades.push(trade);\n state.marginUsed += requiredMargin;\n showTradeMessage(`✅ Placed ${type} (Free Mode)`);\n }\n\n function place(type, state) {\n const entry = state.currentPrice;\n const lot = state.lotSize;\n const config = Phases[state.phase] || null;\n \n // Convert both open trades and current lot into mini-equivalent lots\n const currentLotMiniEquiv = (state.contract === 'micro') ? lot / 10 : lot;\n const openMiniLots = state.openTrades.reduce((sum, t) => {\n const lotEquiv = (t.contractValue === 2.0) ? t.qty / 10 : t.qty;\n return sum + lotEquiv;\n }, 0);\n \n const totalMiniLots = currentLotMiniEquiv + openMiniLots;\n \n // === ⛑️ If fully trained, allow unlimited lots ===\n const isFreeMode = Core.state.isFullyTrained && !config;\n \n if (isFreeMode) {\n return placeFreeModeTrade(type, state, lot, entry);\n }\n \n // Always enforce lot limits before any trade:\n const maxLotsAllowed = Core.getMaxLotsAllowed();\n if (totalMiniLots > maxLotsAllowed) {\n showTradeMessage(`❌ Max lot size exceeded: Limit is ${maxLotsAllowed}, Attempted ${totalMiniLots.toFixed(2)}`);\n return;\n }\n \n // 🧠 Explanation:\n // If MNQ moves from 14,000.00 to 14,001.00, you earn/lose $2 per contract.\n // If NQ moves from 14,000.00 to 14,001.00, you earn/lose $20 per contract.\n\n const contract = state.contract === 'micro' ? 2.0 : 20.0;\n const margin = state.contract === 'micro' ? 500 : 10000;\n\n const opposing = state.openTrades.find(t => t.type !== type);\n const sameSide = state.openTrades.find(t => t.type === type);\n\n const availableMargin = state.balance - state.marginUsed;\n\n // === 1. Reduce or Reverse Existing Opposing Position ===\n if (opposing) {\n let remaining = lot;\n for (let i = 0; i < state.openTrades.length && remaining > 0; i++) {\n const trade = state.openTrades[i];\n if (trade.type !== type) {\n const closeQty = Math.min(remaining, trade.qty);\n const pnl = (type === 'buy'\n ? (entry - trade.entry)\n : (trade.entry - entry)) * closeQty * trade.contractValue;\n\n state.trades.push({\n ...trade,\n exit: entry,\n pnl,\n qty: closeQty,\n duration: Date.now() - trade.entryTime,\n exitReason: 'Reduced',\n time: new Date().toLocaleTimeString()\n });\n\n trade.qty -= closeQty;\n state.marginUsed -= closeQty * margin;\n state.balance += pnl;\n remaining -= closeQty;\n }\n }\n\n // Remove empty trades\n state.openTrades = state.openTrades.filter(t => t.qty > 0);\n\n // If leftover, treat as new entry (flipping position)\n if (remaining > 0) {\n const requiredMargin = remaining * margin;\n if (requiredMargin > (state.balance - state.marginUsed)) {\n showTradeMessage(`❌ Not enough margin: Need $${requiredMargin}, Have $${availableMargin}`);\n return;\n }\n\n const newTrade = {\n type,\n qty: remaining,\n entry,\n stop: type === 'buy' ? entry - state.stopLoss : entry + state.stopLoss,\n target: type === 'buy' ? entry + state.takeProfit : entry - state.takeProfit,\n entryTime: Date.now(),\n contractValue: contract,\n marginRequired: requiredMargin\n };\n\n state.openTrades.push(newTrade);\n state.marginUsed += requiredMargin;\n }\n\n return;\n }\n\n // === 2. Add to Same-Side Position ===\n if (sameSide) {\n const additionalMargin = lot * margin;\n if (additionalMargin > availableMargin) {\n showTradeMessage(`❌ Not enough margin to scale in: Need $${additionalMargin}, Have $${availableMargin}`);\n return;\n }\n \n const totalQty = sameSide.qty + lot;\n sameSide.entry = ((sameSide.entry * sameSide.qty) + (entry * lot)) / totalQty;\n sameSide.qty = totalQty;\n sameSide.marginRequired += additionalMargin;\n state.marginUsed += additionalMargin;\n \n // ✅ Only auto-adjust SL/TP if user hasn't manually moved them\n if (!sameSide.stopMoved) {\n sameSide.stop = type === 'buy'\n ? sameSide.entry - state.stopLoss\n : sameSide.entry + state.stopLoss;\n }\n if (!sameSide.targetMoved) {\n sameSide.target = type === 'buy'\n ? sameSide.entry + state.takeProfit\n : sameSide.entry - state.takeProfit;\n }\n \n return;\n }\n\n // === 3. New Trade (First Entry) ===\n const requiredMargin = lot * margin;\n if (requiredMargin > availableMargin) {\n showTradeMessage(`❌ Not enough margin: Need $${requiredMargin}, Have $${availableMargin}`);\n return;\n }\n\n const trade = {\n type,\n qty: lot,\n entry,\n stop: type === 'buy' ? entry - state.stopLoss : entry + state.stopLoss,\n target: type === 'buy' ? entry + state.takeProfit : entry - state.takeProfit,\n entryTime: Date.now(),\n contractValue: contract,\n marginRequired: requiredMargin\n };\n\n state.openTrades.push(trade);\n state.marginUsed += requiredMargin;\n }\n\n function evaluate(state) {\n const now = Date.now();\n state.openTrades = state.openTrades.filter(trade => {\n const current = state.currentPrice;\n const hitTP = trade.type === 'buy' ? current >= trade.target : current <= trade.target;\n const hitSL = trade.type === 'buy' ? current <= trade.stop : current >= trade.stop;\n\n if (hitTP || hitSL) {\n const exit = current;\n const pnl = (trade.type === 'buy'\n ? (exit - trade.entry)\n : (trade.entry - exit)) * trade.qty * trade.contractValue;\n\n state.trades.push({\n ...trade,\n exit,\n pnl,\n duration: now - trade.entryTime,\n exitReason: hitTP ? 'TP' : 'SL',\n time: new Date().toLocaleTimeString()\n });\n\n state.marginUsed -= trade.marginRequired;\n state.balance += pnl;\n saveStateToLocalStorage();\n return false;\n }\n\n return true;\n });\n }\n\n return { place, evaluate };\n})();\n\n// === STATS + UI MODULE ===\nconst Stats = (() => {\n let lastTradeCount = 0;\n\n function updateIfChanged(id, newValue) {\n const el = document.getElementById(id);\n if (!el) return;\n if (el.textContent !== newValue) {\n el.textContent = newValue;\n }\n }\n\n function updateTierUI() {\n const ch = Core.state.challenge;\n const tiers = [\"25K\", \"50K\", \"100K\", \"150K\", \"300K\", \"500K\", \"1M\"];\n const index = tiers.indexOf(ch.level);\n const next = tiers[index + 1];\n\n const tierProgress = ((index + 1) / tiers.length) * 100;\n document.getElementById(\"tier-progress\").style.width = `${tierProgress}%`;\n updateIfChanged(\"tier-progress-text\", `${index + 1}/${tiers.length}`);\n\n const hint = next\n ? `Unlocks $${next} Challenge with higher limits`\n : `All challenges completed`;\n updateIfChanged(\"next-tier-hint\", hint);\n\n const desc = next\n ? `Complete ${tiers.filter(t => t === ch.level).length}x $${ch.level} Challenges`\n : `All challenges completed`;\n const descEl = document.querySelector(\".trading-card .text-sm.mb-1\");\n if (descEl && descEl.textContent !== desc) {\n descEl.textContent = desc;\n }\n }\n \n function update(state) {\n const ch = state.challenge;\n const trades = state.trades;\n const wins = trades.filter(t => t.pnl > 0);\n const losses = trades.filter(t => t.pnl < 0);\n const totalPnl = trades.reduce((sum, t) => sum + t.pnl, 0);\n const winRate = trades.length ? ((wins.length / trades.length) * 100).toFixed(1) + '%' : '0%';\n const avgWin = wins.length ? wins.reduce((a, b) => a + b.pnl, 0) / wins.length : 0;\n const avgLoss = losses.length ? losses.reduce((a, b) => a + b.pnl, 0) / losses.length : 0;\n const avgWinDuration = wins.length ? wins.reduce((a, b) => a + b.duration, 0) / wins.length : 0;\n const avgLossDuration = losses.length ? losses.reduce((a, b) => a + b.duration, 0) / losses.length : 0;\n\n // === Challenge + UI ===\n const profit = state.balance - ch.startingBalance;\n const phaseProgress = Math.min(100, (profit / ch.profitTarget) * 100);\n const tiers = [\"25K\", \"50K\", \"100K\", \"150K\", \"300K\", \"500K\", \"1M\"];\n const index = tiers.indexOf(ch.level);\n const tierProgress = (index / tiers.length) * 100;\n\n document.querySelector(\".progress-fill\").style.width = `${phaseProgress}%`;\n updateIfChanged(\"tier-progress-text\", `${index + 1}/${tiers.length}`);\n updateIfChanged(\"account-tier\", `$${ch.level} Challenge`);\n updateIfChanged(\"account-phase\", `Phase ${ch.phase}/2`);\n document.querySelector(\".progress-label\").textContent = `${phaseProgress.toFixed(0)}%`;\n\n updateIfChanged(\"challenge-title\", Core.state.isFullyTrained\n ? \"🎉 Free Mode (No Restrictions)\"\n : \"Current Challenge\");\n\n if (Core.state.isFullyTrained) {\n updateIfChanged(\"profit-target\", `🎯 Training complete`);\n updateIfChanged(\"total-loss\", `∞`);\n updateIfChanged(\"max-lots\", `∞`);\n document.querySelector(\".progress-fill\").style.background = 'gold';\n document.getElementById(\"tier-progress\").style.background = 'gold';\n } else {\n const phase = Phases[Core.state.phase];\n updateIfChanged(\"profit-target\", `$${ch.profitTarget.toLocaleString()}`);\n updateIfChanged(\"total-loss\", `$${ch.maxTotalLoss.toLocaleString()}`);\n const maxLots = Core.getMaxLotsAllowed();\n updateIfChanged(\"max-lots\", `${maxLots}`);\n }\n\n const totalOpenLots = state.openTrades.reduce((sum, t) => sum + t.qty, 0);\n updateIfChanged(\"totalOpenLots\", totalOpenLots.toString());\n\n updateIfChanged(\"current-profit\", `$${profit.toLocaleString(undefined, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2\n })}`);\n\n const openPNL = state.openTrades.reduce((acc, trade) => {\n const delta = trade.type === 'buy'\n ? (state.currentPrice - trade.entry)\n : (trade.entry - state.currentPrice);\n return acc + (delta * trade.qty * trade.contractValue);\n }, 0);\n\n updateIfChanged(\"open-pnl\", `$${openPNL.toLocaleString(undefined, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2\n })}`);\n\n updateIfChanged(\"balance\", `$${state.balance.toLocaleString(undefined, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2\n })}`);\n\n updateIfChanged(\"lotSizeDisplay\", Core.state.lotSize.toString());\n\n updateIfChanged(\"marginUsed\", `$${state.marginUsed.toLocaleString(undefined, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2\n })}`);\n\n updateIfChanged(\"availableMargin\", `$${(state.balance - state.marginUsed).toLocaleString(undefined, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2\n })}`);\n\n updateIfChanged(\"totalTrades\", trades.length.toString());\n updateIfChanged(\"winlossratio\", winRate);\n\n // === Trade History Table ===\n if (trades.length !== lastTradeCount) {\n lastTradeCount = trades.length;\n const historyEl = document.getElementById(\"history\").querySelector(\"tbody\");\n historyEl.innerHTML = trades.map(t => `\n <tr>\n <td>${t.time}</td>\n <td>${t.type}</td>\n <td>${t.qty}</td>\n <td>${t.entry.toFixed(2)}</td>\n <td>${t.exit.toFixed(2)}</td>\n <td style=\"color:${t.pnl >= 0 ? 'limegreen' : 'red'}\">${t.pnl.toFixed(2)}</td>\n <td>${(t.duration / 1000).toFixed(1)}s</td>\n <td>${t.exitReason || 'Manual'}</td>\n </tr>\n `).reverse().join(\"\");\n }\n }\n\n return { update };\n})();\n\n// Save/Load trade History\nfunction saveStateToLocalStorage() {\n const stateCopy = { ...Core.state };\n\n // Remove volatile open trades\n stateCopy.openTrades = [];\n\n try {\n localStorage.setItem(\"PropForge\", JSON.stringify(stateCopy));\n } catch (err) {\n console.error(\"Failed to save state:\", err);\n }\n}\nfunction loadStateFromLocalStorage() {\n const stored = localStorage.getItem(\"PropForge\");\n if (stored) {\n try {\n const parsed = JSON.parse(stored);\n\n // Restore safely\n Object.assign(Core.state, parsed);\n } catch (err) {\n console.error(\"Failed to load state:\", err);\n }\n }\n}\n\n// Button for lot size\nfunction adjustLotSize(delta) {\n const config = Phases[Core.state.phase];\n const newSize = Core.state.lotSize + delta;\n\n if (Core.state.isFullyTrained) {\n if (!config) return; // No phase config = end of training, skip challenge logic\n }\n\n if (newSize < 1) {\n alert(\"❌ Lot size can't be less than 1\");\n return;\n }\n\n if (newSize > config.maxLots) {\n alert(`❌ Lot size can't exceed max allowed: ${config.maxLots}`);\n return;\n }\n\n Core.state.lotSize = newSize;\n document.getElementById(\"lotSizeDisplay\").textContent = newSize;\n\n // Sync radio buttons\n document.querySelectorAll('input[name=\"lot\"]').forEach(radio => {\n radio.checked = parseInt(radio.value) === newSize;\n });\n}\n\n// === INPUT EVENT HANDLERS ===\ndocument.querySelectorAll('input[name=\"contract\"]').forEach(radio => {\n radio.addEventListener('change', () => {\n const newContract = radio.value;\n \n // Prevent contract change while trades are open\n if (Core.state.openTrades.length > 0) {\n radio.checked = false;\n document.querySelector(`input[name=\"contract\"][value=\"${Core.state.contract}\"]`).checked = true;\n alert(\"❌ Cannot change contract type while trades are open\");\n return;\n }\n \n Core.state.contract = newContract;\n });\n});\ndocument.querySelectorAll('input[name=\"lot\"]').forEach(radio => {\n radio.addEventListener('change', () => {\n const selected = parseInt(radio.value);\n const maxAllowed = Phases[Core.state.phase].maxLots;\n\n if (selected > maxAllowed) {\n // Revert the change and notify the user\n radio.checked = false;\n alert(`❌ Max allowed lot size: ${maxAllowed}`);\n \n // Optionally reset to a valid value (like 1)\n Core.state.lotSize = Math.min(Core.state.lotSize, maxAllowed);\n document.getElementById(\"lotSizeDisplay\").textContent = Core.state.lotSize;\n\n // Uncheck all radios\n document.querySelectorAll('input[name=\"lot\"]').forEach(r => r.checked = false);\n\n // Re-check current valid lot size if matching one exists\n document.querySelectorAll('input[name=\"lot\"]').forEach(r => {\n if (parseInt(r.value) === Core.state.lotSize) r.checked = true;\n });\n } else {\n // Accept the new size\n Core.state.lotSize = selected;\n document.getElementById(\"lotSizeDisplay\").textContent = selected;\n }\n });\n});\n\ndocument.getElementById(\"stopLossAmount\").addEventListener('input', (e) => {\n Core.state.stopLoss = parseFloat(e.target.value);\n});\ndocument.getElementById(\"takeProfitAmount\").addEventListener('input', (e) => {\n Core.state.takeProfit = parseFloat(e.target.value);\n});\ndocument.addEventListener(\"keydown\", (e) => {\n if (e.key === \"+\" || e.key === \"=\") adjustLotSize(1);\n if (e.key === \"-\" || e.key === \"_\") adjustLotSize(-1);\n if (e.key.toLowerCase() === \"b\") Trades.place(\"buy\", Core.state);\n if (e.key.toLowerCase() === \"s\") Trades.place(\"sell\", Core.state);\n if (e.key.toLowerCase() === \"x\") {\n document.getElementById(\"close\").click();\n }\n\n // hotkey for pass / fail test\n // if (e.shiftKey && e.key.toLowerCase() === \"h\") {\n // const config = Phases[Core.state.phase];\n // Core.state.balance = config.startBalance + parseInt(config.target / 1.05);\n // Stats.update(Core.state);\n // }\n // if (e.shiftKey && e.key.toLowerCase() === \"p\") {\n // const config = Phases[Core.state.phase];\n // Core.state.balance = config.startBalance + config.target + 1;\n // Stats.update(Core.state);\n // }\n // if (e.shiftKey && e.key.toLowerCase() === \"f\") {\n // const config = Phases[Core.state.phase];\n // Core.state.balance = config.startBalance - config.maxLoss - 1;\n // Stats.update(Core.state);\n // }\n});\n\n// === BUTTON EVENT HANDLERS ===\ndocument.getElementById(\"buy\").addEventListener(\"click\", () => {\n Trades.place(\"buy\", Core.state);\n});\ndocument.getElementById(\"sell\").addEventListener(\"click\", () => {\n Trades.place(\"sell\", Core.state);\n});\ndocument.getElementById(\"close\").addEventListener(\"click\", () => {\n const state = Core.state;\n const now = Date.now();\n\n state.openTrades.forEach(trade => {\n const current = state.currentPrice;\n const pnl = (trade.type === 'buy'\n ? (current - trade.entry)\n : (trade.entry - current)) * trade.qty * trade.contractValue;\n\n state.trades.push({\n ...trade,\n exit: current,\n pnl,\n duration: now - trade.entryTime,\n exitReason: 'Manual',\n time: new Date().toLocaleTimeString()\n });\n\n state.marginUsed -= trade.marginRequired;\n state.balance += pnl;\n saveStateToLocalStorage();\n });\n\n state.openTrades = [];\n Stats.update(state);\n});\ndocument.querySelectorAll('button[data-open=\"performance\"]').forEach(btn => {\n function formatMs(ms) {\n const sec = ms / 1000;\n if (sec < 30) return \"< 30s\";\n if (sec < 60) return \"30s - 1m\";\n if (sec < 180) return \"1-3m\";\n if (sec < 600) return \"3-10m\";\n return \"> 10m\";\n }\n \n btn.onclick = function () {\n const state = Core.state;\n const ch = state.challenge;\n const trades = state.trades;\n const wins = trades.filter(t => t.pnl > 0);\n const losses = trades.filter(t => t.pnl < 0);\n const totalPnl = trades.reduce((sum, t) => sum + t.pnl, 0);\n const winRate = trades.length ? ((wins.length / trades.length) * 100).toFixed(1) + '%' : '0%';\n const avgWin = wins.length ? wins.reduce((a, b) => a + b.pnl, 0) / wins.length : 0;\n const avgLoss = losses.length ? losses.reduce((a, b) => a + b.pnl, 0) / losses.length : 0;\n const avgWinDuration = wins.length ? wins.reduce((a, b) => a + b.duration, 0) / wins.length : 0;\n const avgLossDuration = losses.length ? losses.reduce((a, b) => a + b.duration, 0) / losses.length : 0;\n\n const formatPnL = (n) => {\n const sign = n >= 0 ? '+' : '-';\n return `${sign}$${Math.abs(n).toFixed(2)}`;\n };\n\n const formatSeconds = (ms) => `${(ms / 1000).toFixed(1)}s`;\n\n const bestTrade = trades.length ? trades.reduce((a, b) => b.pnl > a.pnl ? b : a) : null;\n const worstTrade = trades.length > 1 ? trades.reduce((a, b) => b.pnl < a.pnl ? b : a) : null;\n\n const durationCounts = {};\n trades.forEach(t => {\n const bucket = formatMs(t.duration);\n durationCounts[bucket] = (durationCounts[bucket] || 0) + 1;\n });\n const buckets = Object.entries(durationCounts).sort((a, b) => b[1] - a[1]);\n const bestBracket = buckets[0]?.[0] ?? '--';\n const worstBracket = buckets[buckets.length - 1]?.[0] ?? '--';\n\n const stats = [\n {\n label: \"Total PnL\",\n value: `$${totalPnl.toFixed(2)}`,\n class: totalPnl >= 0 ? \"text-green-400\" : \"text-red-400\"\n },\n {\n label: \"Total Trades\",\n value: trades.length\n },\n {\n label: \"Win Rate\",\n value: winRate\n },\n {\n label: \"Avg Win\",\n value: `$${avgWin.toFixed(2)}`\n },\n {\n label: \"Avg Loss\",\n value: `$${avgLoss.toFixed(2)}`\n },\n {\n label: \"Best Trade\",\n value: bestTrade ? formatPnL(bestTrade.pnl) : '--',\n class: bestTrade ? (bestTrade.pnl >= 0 ? \"text-green-400\" : \"text-red-400\") : \"\"\n },\n {\n label: \"Worst Trade\",\n value: worstTrade ? formatPnL(worstTrade.pnl) : '--',\n class: worstTrade ? (worstTrade.pnl >= 0 ? \"text-green-400\" : \"text-red-400\") : \"\"\n },\n {\n label: \"Avg Win Duration\",\n value: formatSeconds(avgWinDuration)\n },\n {\n label: \"Avg Loss Duration\",\n value: formatSeconds(avgLossDuration)\n },\n {\n label: \"Best Duration Bracket\",\n value: bestBracket\n },\n {\n label: \"Worst Duration Bracket\",\n value: worstBracket\n },\n {\n label: \"Challenge\",\n value: `${ch.level} Phase ${ch.phase}/2`\n }\n ];\n\n const content = `\n <div class=\"trading-card rounded-xl p-5\">\n <div class=\"grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4 text-sm text-slate-300\">\n ${stats.map(stat => `\n <div>\n <span class=\"text-slate-500 block\">${stat.label}</span>\n <span class=\"font-bold ${stat.class ?? ''}\">${stat.value}</span>\n </div>\n `).join('')}\n </div>\n\n <div class=\"grid grid-cols-2 gap-3 mt-4 text-sm\">\n <button id=\"importBackupBtn\" class=\"bg-green-600 text-white text-sm px-4 py-2 rounded-md text-center cursor-pointer w-full sm:w-auto border-0\">\n <svg class=\"h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5\"></path>\n </svg>\n Import Backup (.json)\n </button>\n <button id=\"exportBackupBtn\" class=\"bg-blue-600 text-white text-sm px-4 py-2 rounded-md text-center cursor-pointer w-full sm:w-auto border-0\">\n <svg class=\"h-4 w-4\" xmlns=\"http://www.w3.org/2000/svg\" fill=\"none\" viewBox=\"0 0 24 24\" stroke-width=\"1.5\" stroke=\"currentColor\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5\"></path>\n </svg>\n Export Backup (.json)\n </button>\n </div>\n <input type=\"file\" id=\"importBackupInput\" accept=\".json\" class=\"hidden\" />\n </div>\n `;\n\n Modal.render({\n title: \"📈 Performance Card\",\n content,\n ConfirmLabel: \"Reset\",\n CloseLabel: \"Cancel\",\n onLoad: () => {\n setTimeout(() => {\n const exportBtn = document.getElementById(\"exportBackupBtn\");\n const importBtn = document.getElementById(\"importBackupBtn\");\n const importInput = document.getElementById(\"importBackupInput\");\n \n importBtn.addEventListener(\"click\", () => {\n importInput.click(); // ← Triggers the file picker\n });\n \n exportBtn.addEventListener(\"click\", () => {\n const stateCopy = { ...Core.state, openTrades: [] };\n const dataStr = JSON.stringify(stateCopy, null, 2);\n const blob = new Blob([dataStr], { type: \"application/json\" });\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = `trading-backup-${new Date().toISOString().split(\"T\")[0]}.json`;\n a.click();\n URL.revokeObjectURL(url);\n });\n \n importInput.addEventListener(\"change\", (e) => {\n const file = e.target.files[0];\n if (!file) return;\n \n const reader = new FileReader();\n reader.onload = (event) => {\n try {\n const imported = JSON.parse(event.target.result);\n if (!imported || typeof imported !== \"object\" || !imported.trades || !imported.challenge) {\n alert(\"Invalid backup file.\");\n return;\n }\n \n Object.assign(Core.state, imported);\n Core.state.openTrades = [];\n saveStateToLocalStorage();\n \n Stats.update(Core.state);\n Chart.draw(Core.state);\n\n location.reload(true);\n } catch (err) {\n alert(\"❌ Failed to import backup.\");\n }\n };\n \n reader.readAsText(file);\n });\n }, 50); // slight delay to ensure DOM is mounted\n\n },\n onConfirm: () => Core.resetPhase()\n });\n };\n});\n\nwindow.addEventListener('DOMContentLoaded', () => {\n loadStateFromLocalStorage();\n document.getElementById(\"lotDecrease\").addEventListener(\"click\", () => adjustLotSize(-1));\n document.getElementById(\"lotIncrease\").addEventListener(\"click\", () => adjustLotSize(1));\n \n document.querySelector(`input[name=\"contract\"][value=\"${Core.state.contract}\"]`).checked = true;\n document.getElementById(\"lotSizeDisplay\").textContent = Core.state.lotSize;\n document.querySelector(`input[name=\"lot\"][value=\"${Core.state.lotSize}\"]`).checked = true;\n document.getElementById(\"stopLossAmount\").value = Core.state.stopLoss;\n document.getElementById(\"takeProfitAmount\").value = Core.state.takeProfit;\n});",
"logo": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gR2VuZXJhdG9yOiBHcmF2aXQuaW8gLS0+Cgo8c3ZnCiAgIHN0eWxlPSJpc29sYXRpb246aXNvbGF0ZSIKICAgdmlld0JveD0iMCAwIDUxMiA1MTIiCiAgIHdpZHRoPSI1MTJwdCIKICAgaGVpZ2h0PSI1MTJwdCIKICAgdmVyc2lvbj0iMS4xIgogICBpZD0ic3ZnOCIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzMSI+CiAgICA8Y2xpcFBhdGgKICAgICAgIGlkPSJfY2xpcFBhdGhfdkoxb2VMR243blZWc1U2Qlp6c3ZCcjJxdVdnZExWZlgiPgogICAgICA8cmVjdAogICAgICAgICB3aWR0aD0iNTEyIgogICAgICAgICBoZWlnaHQ9IjUxMiIKICAgICAgICAgaWQ9InJlY3QxIgogICAgICAgICB4PSIwIgogICAgICAgICB5PSIwIiAvPgogICAgPC9jbGlwUGF0aD4KICA8L2RlZnM+CiAgPGcKICAgICBjbGlwLXBhdGg9InVybCgjX2NsaXBQYXRoX3ZKMW9lTEduN25WVnNVNkJaenN2QnIycXVXZ2RMVmZYKSIKICAgICBpZD0iZzgiPgogICAgPHJlY3QKICAgICAgIHdpZHRoPSI1MTIiCiAgICAgICBoZWlnaHQ9IjUxMiIKICAgICAgIHN0eWxlPSJmaWxsOiMyMTIwMjAiCiAgICAgICBpZD0icmVjdDIiCiAgICAgICB4PSIwIgogICAgICAgeT0iMCIgLz4KICAgIDxnCiAgICAgICBpZD0iZzciPgogICAgICA8ZwogICAgICAgICBpZD0iZzUiPgogICAgICAgIDxnCiAgICAgICAgICAgaWQ9Imc0Ij4KICAgICAgICAgIDxyZWN0CiAgICAgICAgICAgICB4PSIyNTUuMDE3NSIKICAgICAgICAgICAgIHk9IjY5IgogICAgICAgICAgICAgd2lkdGg9IjExLjg5NCIKICAgICAgICAgICAgIGhlaWdodD0iMjE1LjEzMyIKICAgICAgICAgICAgIGZpbGw9IiNlYmViZWIiCiAgICAgICAgICAgICBpZD0icmVjdDMiCiAgICAgICAgICAgICBzdHlsZT0ic3Ryb2tlLXdpZHRoOjEuNDE0MjEiIC8+CiAgICAgICAgICA8cmVjdAogICAgICAgICAgICAgeD0iMjM0LjQ4NyIKICAgICAgICAgICAgIHk9IjEyMC4zMDEiCiAgICAgICAgICAgICB3aWR0aD0iNTIuOTU2MDAxIgogICAgICAgICAgICAgaGVpZ2h0PSIxMTIuNTMxIgogICAgICAgICAgICAgZmlsbD0iI2ViZWJlYiIKICAgICAgICAgICAgIGlkPSJyZWN0NCIgLz4KICAgICAgICA8L2c+CiAgICAgICAgPHBhdGgKICAgICAgICAgICBkPSJtIDE5OC4wOCwzNDkuMjIgYyAxNC43LDQuNjQ4IDI0Ljc3NSwxMi42MTUgMjMuMTY4LDMyLjU1IHEgLTIuNDksMzAuODgxIC00My4zOSwzOS43MTcgViA0NDMgaCA0MS44ODkgYyAxNi4yOSwtMTkuNTI0IDY5LjgxNCwtMjEuMDc2IDg1LjMyOCwwIGggMzkuNTYyIFYgNDIxLjQ4NyBDIDI2Ny44NDEsNDAwLjk4IDI5NS43NjcsMzIwLjMwNiAzOTkuOTczLDMxOC43NTQgViAyOTcuMzcyIEggMTk4LjA4IFogbSAtNi42MiwtMS44MjggYyAtMjguMzM1LC02LjkwNyAtNjguMTU1LC03LjIyMSAtNzkuNDMzLC01MC4wMiBoIDc5LjQzMyB6IgogICAgICAgICAgIGZpbGwtcnVsZT0iZXZlbm9kZCIKICAgICAgICAgICBmaWxsPSIjZWJlYmViIgogICAgICAgICAgIGlkPSJwYXRoNCIgLz4KICAgICAgPC9nPgogICAgICA8ZwogICAgICAgICBpZD0iZzYiPgogICAgICAgIDxwYXRoCiAgICAgICAgICAgZD0iTSAxMDAuNjg4LDEwNS40MDcgViA4OC44NTggSCA1My44NjEgdiAzMzQuMjg0IGggNDYuODI3IFYgNDA2LjU5MyBIIDY4Ljc1NCBWIDEwNS40MDcgWiIKICAgICAgICAgICBmaWxsPSIjZWJlYmViIgogICAgICAgICAgIGlkPSJwYXRoNSIgLz4KICAgICAgICA8cGF0aAogICAgICAgICAgIGQ9Ik0gNDExLjMxMiwxMDUuNDA3IFYgODguODU4IGggNDYuODI3IHYgMzM0LjI4NCBoIC00Ni44MjcgdiAtMTYuNTQ5IGggMzEuOTM0IFYgMTA1LjQwNyBaIgogICAgICAgICAgIGZpbGw9IiNlYmViZWIiCiAgICAgICAgICAgaWQ9InBhdGg2IiAvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K",
"console": false,
"dark": true,
"previewDark": true,
"module": true,
"autorun": true,
"pwa": false,
"preview": true,
"activePanel": "html",
"columns": false,
"columnsRight": true
}