<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FOMO Trap™ Simulator</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f0f4f8;
padding-top: 4.5rem;
display: flex;
flex-direction: column;
min-height: 100vh;
}
.main-content {
flex-grow: 1;
}
[data-tooltip] { position: relative; cursor: pointer; }
[data-tooltip]::before {
content: attr(data-tooltip); position: absolute; bottom: 125%; left: 50%;
transform: translateX(-50%); background-color: #334155; color: white;
padding: 4px 8px; border-radius: 4px; font-size: 0.8rem; white-space: nowrap;
opacity: 0; visibility: hidden; transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out; z-index: 50;
}
[data-tooltip]:hover::before { opacity: 1; visibility: visible; }
.chartjs-tooltip { background: rgba(0, 0, 0, 0.7); color: white; padding: 5px; border-radius: 3px; font-size: 0.8rem; }
.message-enter { opacity: 0; transform: translateY(-10px); transition: opacity 0.3s ease, transform 0.3s ease; }
.message-enter-active { opacity: 1; transform: translateY(0); }
#stockChartContainer { position: relative; height: 45vh; width: 100%; }
#countdownTimer {
position: absolute; top: 1rem; left: 1rem; z-index: 10;
background-color: rgba(51, 65, 85, 0.8); color: white;
padding: 0.25rem 0.75rem; border-radius: 0.375rem;
font-family: monospace; font-size: 0.875rem; font-weight: 600;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
button:disabled, input[type="range"]:disabled { opacity: 0.4; cursor: not-allowed; filter: grayscale(50%); }
button:disabled:hover { filter: grayscale(50%) brightness(100%); background-color: initial; }
#pauseResumeButton:disabled:hover { background-color: rgb(37 99 235); }
#startButton:disabled:hover { background-color: rgb(22 163 74); }
#muteButton:disabled:hover { background-color: rgb(100 116 139); }
#simulationControlButtons {
position: fixed; top: 1rem; left: 1rem; z-index: 20;
display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;
}
#startButton, #pauseResumeButton { width: 8rem; text-align: center; }
#muteButton { width: 2.5rem; height: 2.5rem; padding: 0.5rem; display: flex; align-items: center; justify-content: center; }
button:focus-visible, input[type="range"]:focus-visible, input[type="number"]:focus-visible {
outline: 2px solid transparent; outline-offset: 2px;
box-shadow: 0 0 0 2px #f0f4f8, 0 0 0 4px rgb(59 130 246);
}
.card-hover:hover { transform: translateY(-2px); box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.07), 0 4px 6px -4px rgb(0 0 0 / 0.07); }
@keyframes flash-green { 0%, 100% { background-color: transparent; } 50% { background-color: rgba(74, 222, 128, 0.2); } }
@keyframes flash-red { 0%, 100% { background-color: transparent; } 50% { background-color: rgba(248, 113, 113, 0.2); } }
.flash-bg-green { animation: flash-green 0.6s ease-in-out; }
.flash-bg-red { animation: flash-red 0.6s ease-in-out; }
#newsTickerContainer {
background-color: #1f2937; color: #f3f4f6; overflow: hidden;
white-space: nowrap; box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
}
#newsTickerContent { display: inline-block; padding-left: 100%; animation: scroll-left 60s linear infinite; }
#newsTickerContent span { display: inline-block; padding: 0 1.5rem; }
#newsTickerContainer:hover #newsTickerContent { animation-play-state: paused; }
@keyframes scroll-left { 0% { transform: translateX(0); } 100% { transform: translateX(-100%); } }
@keyframes blink { 50% { opacity: 0.3; } }
.animate-blink { animation: blink 1.2s linear infinite; }
/* Modal Styles */
.modal-overlay {
position: fixed; inset: 0; background-color: rgba(0, 0, 0, 0.5);
display: flex; align-items: center; justify-content: center;
padding: 1rem; z-index: 30; transition: opacity 0.3s ease-in-out;
}
.modal-overlay.hidden { opacity: 0; pointer-events: none; }
.modal-overlay:not(.hidden) { opacity: 1; pointer-events: auto; }
.modal-content {
background-color: white; border-radius: 0.5rem;
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
position: relative; width: 85%; max-width: 48rem;
max-height: 85vh; display: flex; flex-direction: column;
overflow: hidden;
}
.modal-header {
background-image: linear-gradient(to right, #ef4444, #f97316); color: white;
padding: 1rem 1.5rem; border-bottom: 1px solid rgba(255,255,255,0.2);
flex-shrink: 0;
}
.modal-body {
padding: 1.5rem; overflow-y: auto; background-color: #f9fafb;
flex-grow: 1;
}
.modal-close-button { position: absolute; top: 0.5rem; right: 0.5rem; padding: 0.25rem; color: rgba(255, 255, 255, 0.7); transition: color 0.15s ease-in-out; }
.modal-close-button:hover { color: white; }
#fomoMeterBar { transition-property: width, background-color; transition-duration: 500ms; transition-timing-function: ease-out; }
#fomoLevelText { text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.4); }
</style>
</head>
<body class="p-4 md:p-8">
<div id="simulationControlButtons">
<button id="startButton" class="px-4 py-2 bg-green-600 text-white rounded-md hover:bg-green-700 active:bg-green-800 transition duration-150 ease-in-out focus-visible:ring-green-500">Start Sim</button>
<button id="pauseResumeButton" class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 active:bg-blue-800 transition duration-150 ease-in-out focus-visible:ring-blue-500" disabled>Pause</button>
<button id="muteButton" class="bg-slate-500 text-white rounded-md hover:bg-slate-600 active:bg-slate-700 transition duration-150 ease-in-out focus-visible:ring-slate-500" disabled aria-label="Mute Music">
<span id="muteIconContainer"></span>
</button>
</div>
<div class="main-content">
<h1 class="text-3xl font-bold text-center text-slate-800 mb-2">FOMO Trap™</h1>
<p class="text-center text-slate-600 text-md mb-8">The financial cost of emotional decisions—quantified</p>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="lg:col-span-2 space-y-6">
<div class="bg-white p-4 sm:p-6 rounded-lg shadow-md transition duration-200 ease-in-out card-hover">
<h2 class="text-xl font-semibold text-slate-700 mb-2" data-tooltip="Rises with market volatility and strong trends, indicating potential FOMO pressure.">
FOMO Meter <span class="text-sm text-blue-600 cursor-help">(?)</span>
</h2>
<div class="relative w-full bg-slate-200 rounded-full h-6 overflow-hidden">
<div id="fomoMeterBar" class="h-full rounded-full" style="width: 0%;"></div>
<div id="fomoLevelText" class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-white">Low</div>
</div>
<div class="mt-2 text-center">
<span class="text-xs font-semibold text-slate-600 mr-1">Advisor:</span>
<span id="advisorText" class="text-sm text-slate-500 h-6 inline-block"></span>
</div>
</div>
<div class="bg-white p-4 sm:p-6 rounded-lg shadow-md relative transition duration-200 ease-in-out card-hover">
<h2 class="text-xl font-semibold text-slate-700 mb-4">Stock Price (INR)</h2>
<div id="stockChartContainer">
<canvas id="stockChart"></canvas>
<div id="countdownTimer">04:30</div>
</div>
<div class="text-center text-xs text-slate-500 mt-2 pt-2 border-t border-slate-100">
Value Zone: <span id="valueZoneIndicator" class="font-medium text-blue-600">₹??.?? - ₹??.??</span>
</div>
<div id="newsTickerContainer" class="h-8 flex items-center mt-4 rounded-md">
<div id="newsTickerContent" class="text-sm">
<span>Welcome to the FOMO Trap™ Simulator...</span>
</div>
</div>
<div class="flex justify-center items-center space-x-2 mt-4 pt-4 border-t border-slate-100">
<label for="speedSlider" class="text-sm text-slate-600">Speed:</label>
<input type="range" id="speedSlider" min="100" max="2000" step="100" class="w-32 h-2 bg-slate-200 rounded-lg appearance-none cursor-pointer accent-blue-600 focus-visible:ring-blue-500" disabled>
<span id="speedValue" class="text-sm text-slate-600 w-10 text-right">0.1s</span>
</div>
</div>
<div class="bg-white p-4 sm:p-6 rounded-lg shadow-md transition duration-200 ease-in-out card-hover">
<h2 class="text-xl font-semibold text-slate-700 mb-4">Trade History</h2>
<div class="max-h-48 overflow-y-auto">
<ul id="tradeHistoryList" class="text-sm space-y-1 text-slate-600">
<li>No trades yet.</li>
</ul>
</div>
</div>
</div> <div class="space-y-6">
<div class="bg-white p-4 sm:p-6 rounded-lg shadow-md transition duration-200 ease-in-out card-hover">
<h2 class="text-xl font-semibold text-slate-700 mb-4">Portfolio</h2>
<div class="space-y-2 text-slate-600">
<div class="flex justify-between"><span>Capital:</span><span id="capitalValue" class="font-medium">₹1,000.00</span></div>
<div class="flex justify-between"><span>Shares Held:</span><span id="sharesValue" class="font-medium">0</span></div>
<div class="flex justify-between"><span>Current Price:</span><span id="currentPriceValue" class="font-medium">₹---.--</span></div>
<hr class="my-2 border-slate-200">
<div class="flex justify-between font-semibold text-slate-800">
<span>Total Value:</span>
<span id="totalValue" class="font-medium px-1 rounded-sm" aria-live="polite">₹1,000.00</span>
</div>
</div>
</div>
<div class="bg-white p-4 sm:p-6 rounded-lg shadow-md transition duration-200 ease-in-out card-hover">
<h2 class="text-xl font-semibold text-slate-700 mb-4">Trade Controls</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 items-end">
<div>
<label for="tradeQuantity" class="block text-sm font-medium text-slate-600 mb-1">Quantity</label>
<input type="number" id="tradeQuantity" value="10" min="1" class="w-full p-2 border border-slate-300 rounded-md focus:ring-blue-500 focus:border-blue-500 focus-visible:ring-blue-500">
</div>
<div class="flex space-x-3">
<button id="buyButton" class="flex-1 bg-green-500 text-white px-6 py-2 rounded-md hover:bg-green-600 active:bg-green-800 transition duration-150 ease-in-out focus-visible:ring-green-500" disabled>Buy</button>
<button id="sellButton" class="flex-1 bg-red-500 text-white px-6 py-2 rounded-md hover:bg-red-600 active:bg-red-800 transition duration-150 ease-in-out focus-visible:ring-red-500" disabled>Sell</button>
</div>
</div>
<div id="tradeFeedback" class="mt-4 text-sm text-slate-600 h-10" aria-live="polite"></div>
</div>
<div class="bg-white p-4 sm:p-6 rounded-lg shadow-md transition duration-200 ease-in-out card-hover">
<h2 class="text-xl font-semibold text-slate-700 mb-4">Performance Analysis</h2>
<div class="space-y-2 text-sm text-slate-600">
<div class="flex justify-between"><span>Trades Made:</span><span id="tradesMade" class="font-medium">0</span></div>
<div class="flex justify-between">
<span>Profit/Loss:</span>
<span id="profitLoss" class="font-medium px-1 rounded-sm" aria-live="polite">₹0.00</span>
</div>
<div class="flex justify-between">
<span data-tooltip="Trades potentially influenced by FOMO (buying high, selling low, buying false bottoms/consolidations).">FOMO Trades: <span class="text-sm text-blue-600 cursor-help">(?)</span></span>
<span id="fomoTradesCount" class="font-medium">0</span>
</div>
<div class="flex justify-between">
<span data-tooltip="Trades made within defined 'value zones'.">Value Trades: <span class="text-sm text-blue-600 cursor-help">(?)</span></span>
<span id="valueTradesCount" class="font-medium">0</span>
</div>
<div id="finalAnalysis" class="mt-3 pt-3 border-t border-slate-200 text-center font-medium text-blue-700 hidden">
Simulation Complete! Review your results.
</div>
<div id="counterfactualSection" class="mt-4 pt-4 border-t border-dashed border-slate-300 hidden"> <h3 class="text-base font-semibold text-slate-700 mb-2">Key Trade Analysis:</h3>
<ul id="counterfactualAnalysisOutputMain" class="text-xs space-y-1 text-slate-600 max-h-32 overflow-y-auto">
</ul>
</div>
</div>
</div>
</div> </div> </div> <footer class="mt-12 py-4 border-t border-slate-200">
<p class="text-center text-xs text-slate-500">
© 2025 NidaaanInvestwise. Educational content only, not financial advice.
<a href="https://www.nidaaaninvestwise.com" target="_blank" rel="noopener noreferrer" class="text-blue-600 hover:underline">
www.nidaaaninvestwise.com
</a>
</p>
</footer>
<div id="simEndOverlay" class="modal-overlay hidden">
<div class="modal-content text-center w-[85%] max-w-xl">
<h3 class="text-xl lg:text-2xl font-semibold text-slate-800 mb-4">Simulation Ended</h3>
<p class="text-slate-600 mb-6">Your 4.5 minute trading session is complete.</p>
<button id="showResultsButton" class="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 active:bg-blue-800 transition duration-150 ease-in-out focus-visible:ring-blue-500">
See Results & Analysis
</button>
</div>
</div>
<div id="analysisModalOverlay" class="modal-overlay hidden">
<div class="modal-content w-[85%] max-w-3xl max-h-[85vh] p-0" role="dialog" aria-modal="true" aria-labelledby="analysisModalTitle">
<button id="closeAnalysisModalButton" class="modal-close-button z-10" aria-label="Close Analysis"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
</button>
<div class="modal-header rounded-t-lg">
<h3 id="analysisModalTitle" class="text-xl lg:text-2xl font-semibold text-white text-center">Look what FOMO cost you</h3>
</div>
<div class="modal-body">
<ul id="counterfactualAnalysisOutputModal" class="text-sm space-y-2 text-slate-700">
</ul>
<p id="analysisInsight" class="mt-4 pt-4 border-t border-slate-200 text-xs text-slate-500 italic">
Remember: Emotional trades (Panic Sells, FOMO Buys) are flagged based on the conditions when you traded. While the outcome might sometimes be lucky due to market randomness, focusing on a disciplined process is key.
</p>
</div>
</div>
</div>
<script>
// --- Configuration ---
const INITIAL_CAPITAL = 1000; const STARTING_PRICE = 45; const MIN_PRICE_FLOOR = 18;
const FALSE_BOTTOM_THRESHOLD = 25; const MAX_DATA_POINTS = 100;
const SIMULATION_DURATION_MINUTES = 4.5; const TICKS_PER_SECOND = 1;
const SIMULATION_TICKS = SIMULATION_DURATION_MINUTES * 60 * TICKS_PER_SECOND;
const TOTAL_SECONDS = SIMULATION_DURATION_MINUTES * 60;
const CAP_TIME_1 = 30 * TICKS_PER_SECOND; const CAP_PRICE_1 = STARTING_PRICE * 1.31;
const CAP_TIME_2 = 120 * TICKS_PER_SECOND; const CAP_PRICE_2 = STARTING_PRICE * 2.21;
const CAP_TIME_3 = 240 * TICKS_PER_SECOND; const CAP_PRICE_3 = STARTING_PRICE * 2.90;
const VALUE_ZONE_LOW_FACTOR = 0.9; const VALUE_ZONE_HIGH_FACTOR = 1.1;
const VALUE_ZONE_LOW = STARTING_PRICE * VALUE_ZONE_LOW_FACTOR; const VALUE_ZONE_HIGH = STARTING_PRICE * VALUE_ZONE_HIGH_FACTOR;
const VALUE_ZONE_BOUNCE_PROBABILITY = 0.6; const CONSOLIDATION_CHANCE = 0.03;
const CONSOLIDATION_MIN_TICKS = 10; const CONSOLIDATION_MAX_TICKS = 30;
const BUY_TRAP_PROBABILITY = 0.90; const SELL_TRAP_PROBABILITY = 0.90;
const BUY_TRAP_FACTOR_MIN = 0.02; const BUY_TRAP_FACTOR_MAX = 0.05;
const SELL_TRAP_FACTOR_MIN = 0.025; const SELL_TRAP_FACTOR_MAX = 0.06;
const MAX_TICKER_ITEMS = 20;
const DEFAULT_SIMULATION_SPEED = 100;
// --- State Variables ---
let capital = INITIAL_CAPITAL; let shares = 0; let currentPrice = STARTING_PRICE;
let portfolioValue = INITIAL_CAPITAL; let prevPortfolioValue = INITIAL_CAPITAL;
let priceHistory = [currentPrice];
let chartLabels = ['Start']; let simulationInterval; let tickCount = 0;
let isPaused = false; let simulationRunning = false;
let simulationSpeed = DEFAULT_SIMULATION_SPEED;
let trades = []; let fomoLevel = 0;
let recentPriceChanges = []; let fomoTrades = 0; let valueTrades = 0;
let isConsolidating = false; let consolidationLevel = null; let consolidationTicksRemaining = 0;
let isMuted = false;
let positiveBuzzTicks = 0;
let currentFomoCategory = 'Low';
// --- DOM Elements ---
const capitalValueEl = document.getElementById('capitalValue');
const sharesValueEl = document.getElementById('sharesValue');
const currentPriceValueEl = document.getElementById('currentPriceValue');
const totalValueEl = document.getElementById('totalValue');
const tradeQuantityInput = document.getElementById('tradeQuantity');
const buyButton = document.getElementById('buyButton');
const sellButton = document.getElementById('sellButton');
const tradeFeedbackEl = document.getElementById('tradeFeedback');
const tradeHistoryListEl = document.getElementById('tradeHistoryList');
const fomoMeterBarEl = document.getElementById('fomoMeterBar');
const fomoLevelTextEl = document.getElementById('fomoLevelText');
const advisorTextEl = document.getElementById('advisorText');
const newsTickerContentEl = document.getElementById('newsTickerContent');
const startButton = document.getElementById('startButton');
const pauseResumeButton = document.getElementById('pauseResumeButton');
const muteButton = document.getElementById('muteButton');
const muteIconContainer = document.getElementById('muteIconContainer');
const speedSlider = document.getElementById('speedSlider');
const speedValueEl = document.getElementById('speedValue');
const tradesMadeEl = document.getElementById('tradesMade');
const profitLossEl = document.getElementById('profitLoss');
const fomoTradesCountEl = document.getElementById('fomoTradesCount');
const valueTradesCountEl = document.getElementById('valueTradesCount');
const finalAnalysisEl = document.getElementById('finalAnalysis');
const countdownTimerEl = document.getElementById('countdownTimer');
const valueZoneIndicatorEl = document.getElementById('valueZoneIndicator');
const simEndOverlayEl = document.getElementById('simEndOverlay');
const showResultsButtonEl = document.getElementById('showResultsButton');
const analysisModalOverlayEl = document.getElementById('analysisModalOverlay');
const closeAnalysisModalButtonEl = document.getElementById('closeAnalysisModalButton');
const counterfactualAnalysisOutputModalEl = document.getElementById('counterfactualAnalysisOutputModal');
const counterfactualSectionEl = document.getElementById('counterfactualSection');
const counterfactualAnalysisOutputMainEl = document.getElementById('counterfactualAnalysisOutputMain');
// --- SVG Icons ---
const speakerWaveIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M19.114 5.636a9 9 0 0 1 0 12.728M16.463 8.288a5.25 5.25 0 0 1 0 7.424M6.75 8.25l4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z" /></svg>`;
const speakerXMarkIcon = `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M17.25 9.75 19.5 12m0 0 2.25 2.25M19.5 12l2.25-2.25M19.5 12l-2.25 2.25m-10.5-6 4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.01 9.01 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z" /></svg>`;
// --- Audio Setup (Tone.js) ---
let synth = null; let musicLoop = null; let audioReady = false;
function setupAudio() { /* ... no changes ... */
if (audioReady) return; try { synth = new Tone.FMSynth({ volume: -12, harmonicity: 1.5, modulationIndex: 5, envelope: { attack: 0.01, decay: 0.2, sustain: 0.1, release: 0.5 }, modulationEnvelope: { attack: 0.05, decay: 0.1, sustain: 0.05, release: 0.2 } }).toDestination(); const pattern = ["C3", null, "E3", null, "G3", null, null, null]; let patternIndex = 0; musicLoop = new Tone.Loop(time => { let note = pattern[patternIndex % pattern.length]; if (note) { synth.triggerAttackRelease(note, "8n", time); } patternIndex++; }, "2n").start(0); Tone.Transport.bpm.value = 90; Tone.Destination.mute = isMuted; audioReady = true; muteButton.disabled = false; updateMuteButtonIcon(); } catch (error) { console.error("Failed to initialize audio:", error); muteButton.disabled = true; }
}
function updateMuteButtonIcon() { /* ... no changes ... */
if (!muteIconContainer) return; if (isMuted) { muteIconContainer.innerHTML = speakerXMarkIcon; muteButton.setAttribute('aria-label', 'Unmute Music'); } else { muteIconContainer.innerHTML = speakerWaveIcon; muteButton.setAttribute('aria-label', 'Mute Music'); }
}
// --- Chart.js Setup ---
const ctx = document.getElementById('stockChart').getContext('2d');
let stockChart;
let pointRadius = []; let pointBackgroundColor = []; let pointBorderColor = [];
function initializeChart() { /* ... no changes ... */
const initialData = Array(MAX_DATA_POINTS).fill(null); initialData[MAX_DATA_POINTS - 1] = currentPrice; const initialLabels = Array(MAX_DATA_POINTS).fill(''); initialLabels[MAX_DATA_POINTS - 1] = 'Start'; pointRadius = Array(MAX_DATA_POINTS).fill(0); pointBackgroundColor = Array(MAX_DATA_POINTS).fill('transparent'); pointBorderColor = Array(MAX_DATA_POINTS).fill('transparent');
stockChart = new Chart(ctx, { type: 'line', data: { labels: initialLabels, datasets: [{ label: 'Stock Price', data: initialData, borderColor: 'rgb(59, 130, 246)', backgroundColor: 'rgba(59, 130, 246, 0.1)', borderWidth: 2, pointRadius: pointRadius, pointBackgroundColor: pointBackgroundColor, pointBorderColor: pointBorderColor, pointHoverRadius: 5, tension: 0.1, fill: true }] }, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: false, ticks: { callback: function(value) { return '₹' + value.toFixed(2); } }, grid: { color: '#e5e7eb' } }, x: { ticks: { display: false }, grid: { display: false } } }, plugins: { tooltip: { enabled: true, mode: 'index', intersect: false, callbacks: { label: function(context) { let label = context.dataset.label || ''; if (label) label += ': '; if (context.parsed.y !== null) label += '₹' + context.parsed.y.toFixed(2); return label; } }, backgroundColor: 'rgba(51, 65, 85, 0.9)', titleFont: { weight: 'bold', family: 'Inter' }, bodyFont: { family: 'Inter' }, padding: 8, cornerRadius: 4, displayColors: false, }, legend: { display: false } }, animation: { duration: 0 }, hover: { mode: 'nearest', intersect: true } } });
}
// --- Simulation Logic ---
function enterConsolidation() { /* ... no changes ... */
if (currentPrice < MIN_PRICE_FLOOR * 1.2 || currentPrice > CAP_PRICE_3 * 0.95) return; isConsolidating = true; consolidationLevel = currentPrice; consolidationTicksRemaining = Math.floor(Math.random() * (CONSOLIDATION_MAX_TICKS - CONSOLIDATION_MIN_TICKS + 1)) + CONSOLIDATION_MIN_TICKS;
}
function generateNextPrice() { /* ... no changes ... */
let baseChangePercent = (Math.random() - 0.5) * 0.015; let momentumFactor = 0; let pullToConsolidation = 0; let trapAdjustment = 0;
if (isConsolidating) { consolidationTicksRemaining--; if (consolidationTicksRemaining <= 0) { isConsolidating = false; consolidationLevel = null; } else { baseChangePercent *= 0.2; momentumFactor *= 0.5; pullToConsolidation = (consolidationLevel - currentPrice) * 0.015; } }
if (positiveBuzzTicks > 0) { baseChangePercent += 0.003; positiveBuzzTicks--; }
if (recentPriceChanges.length > 0 && !isConsolidating) { const avgChange = recentPriceChanges.reduce((a, b) => a + b, 0) / recentPriceChanges.length; momentumFactor = avgChange * 0.5; }
let baseNetChange = currentPrice * (baseChangePercent + momentumFactor) + pullToConsolidation; baseNetChange += (Math.random() - 0.5) * (currentPrice * 0.003);
const lastTrade = trades.length > 0 ? trades[trades.length - 1] : null;
if (lastTrade && tickCount === lastTrade.tick + 1) { if (lastTrade.type === 'buy') { if (Math.random() < BUY_TRAP_PROBABILITY) { const dropPercent = BUY_TRAP_FACTOR_MIN + Math.random() * (BUY_TRAP_FACTOR_MAX - BUY_TRAP_FACTOR_MIN); trapAdjustment = -Math.abs(currentPrice * dropPercent); addTradeFeedback("Price dropped immediately after your buy!", "warning"); if (!lastTrade.fomo && !lastTrade.value) { lastTrade.fomo = true; fomoTrades++; fomoTradesCountEl.textContent = fomoTrades; updateTradeHistory(lastTrade); } } } else if (lastTrade.type === 'sell') { if (Math.random() < SELL_TRAP_PROBABILITY) { const risePercent = SELL_TRAP_FACTOR_MIN + Math.random() * (SELL_TRAP_FACTOR_MAX - SELL_TRAP_FACTOR_MIN); trapAdjustment = Math.abs(currentPrice * risePercent); addTradeFeedback("Price surged right after you sold!", "warning"); if (!lastTrade.panic && currentPrice < VALUE_ZONE_LOW * 1.1) { lastTrade.panic = true; fomoTrades++; fomoTradesCountEl.textContent = fomoTrades; updateTradeHistory(lastTrade); } } } }
let nextPrice = currentPrice + baseNetChange + trapAdjustment;
let maxAllowedPrice = Infinity; if (tickCount <= CAP_TIME_1) maxAllowedPrice = CAP_PRICE_1; else if (tickCount <= CAP_TIME_2) maxAllowedPrice = CAP_PRICE_2; else if (tickCount <= CAP_TIME_3) maxAllowedPrice = CAP_PRICE_3; else maxAllowedPrice = CAP_PRICE_3; nextPrice = Math.min(nextPrice, maxAllowedPrice); nextPrice = Math.max(MIN_PRICE_FLOOR, nextPrice);
const change = (nextPrice - currentPrice) / currentPrice; recentPriceChanges.push(change); if (recentPriceChanges.length > 5) recentPriceChanges.shift(); return nextPrice;
}
function updateFOMOMeter() { /* ... no changes ... */
if (recentPriceChanges.length < 3) fomoLevel = 0; else { const mean = recentPriceChanges.reduce((a, b) => a + b, 0) / recentPriceChanges.length; const variance = recentPriceChanges.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / recentPriceChanges.length; const volatility = Math.sqrt(variance); const momentum = mean; let calculatedLevel = (volatility * 3000) + Math.max(0, momentum * 5000); fomoLevel = Math.min(100, Math.max(0, calculatedLevel)); }
fomoMeterBarEl.style.width = `${fomoLevel}%`; let newCategory = 'Low'; let levelText = 'Low'; let advisorMessage = ''; let barColorClass = 'bg-sky-500';
if (fomoLevel > 75) { newCategory = 'Extreme'; levelText = 'Extreme'; barColorClass = 'bg-red-500'; advisorMessage = "Extreme FOMO! High risk of emotional trade."; } else if (fomoLevel > 50) { newCategory = 'High'; levelText = 'High'; barColorClass = 'bg-amber-500'; advisorMessage = "Caution: FOMO levels high. Stick to your plan."; } else if (fomoLevel > 25) { newCategory = 'Moderate'; levelText = 'Moderate'; barColorClass = 'bg-emerald-500'; advisorMessage = "Market heating up. Stay objective."; }
fomoLevelTextEl.textContent = levelText; if (newCategory !== currentFomoCategory) { advisorTextEl.textContent = advisorMessage; currentFomoCategory = newCategory; } const colorClasses = ['bg-sky-500', 'bg-emerald-500', 'bg-amber-500', 'bg-red-500']; fomoMeterBarEl.classList.remove(...colorClasses); fomoMeterBarEl.classList.add(barColorClass); if (fomoLevel > 50) { fomoMeterBarEl.classList.add('animate-pulse'); } else { fomoMeterBarEl.classList.remove('animate-pulse'); }
}
function addSocialComment(comment, type = "neutral") { /* ... no changes ... */
if (!newsTickerContentEl) return; const newItem = document.createElement('span'); const symbolSpan = document.createElement('span'); symbolSpan.className = 'inline-block mr-1 animate-blink'; let symbol = '▪'; switch (type) { case 'positive': symbol = '▲'; symbolSpan.className += ' text-green-400'; break; case 'negative': symbol = '▼'; symbolSpan.className += ' text-red-400'; break; case 'warning': symbol = '!'; symbolSpan.className += ' text-yellow-400 font-bold'; break; case 'news': symbol = '*'; symbolSpan.className += ' text-blue-400'; break; case 'info': symbol = 'i'; symbolSpan.className += ' text-cyan-400 italic'; break; case 'neutral': symbol = '-'; symbolSpan.className += ' text-slate-400'; break; } symbolSpan.textContent = symbol; newItem.appendChild(symbolSpan); newItem.appendChild(document.createTextNode(comment)); newItem.className = 'mx-4'; newsTickerContentEl.appendChild(newItem); while (newsTickerContentEl.children.length > MAX_TICKER_ITEMS) { if(newsTickerContentEl.firstChild) { newsTickerContentEl.removeChild(newsTickerContentEl.firstChild); } } const placeholder = newsTickerContentEl.querySelector('.italic.text-slate-500'); if (placeholder && placeholder.textContent.includes("Welcome")) { placeholder.remove(); }
}
function generateSocialChatter() { /* ... no changes ... */
const lastPrice = priceHistory[priceHistory.length - 2] || currentPrice; const change = (currentPrice - lastPrice) / lastPrice; const commentProbability = 0.15 + (fomoLevel / 300); if (Math.random() < commentProbability) { let buzzType = "info"; let comment = ""; if (fomoLevel > 70 && change > 0.005) { const comments = ["🚀 Analyst Upgrade!", "This is flying!", "#ToTheMoon", "Don't miss out!"]; comment = comments[Math.floor(Math.random() * comments.length)]; buzzType = "news"; positiveBuzzTicks = 3; } else if (fomoLevel > 50 && change < -0.01) { const comments = ["📉 Insider Selling Reported!", "This looks bad...", "Get out now!", "Panic!", "#FallingKnife"]; comment = comments[Math.floor(Math.random() * comments.length)]; buzzType = "negative"; positiveBuzzTicks = 0; } else if (isConsolidating) { const comments = ["Choppy action.", "Market waiting for news?", "Low volume consolidation.", "Deciding direction..."]; comment = comments[Math.floor(Math.random() * comments.length)]; buzzType = "info"; positiveBuzzTicks = 0; } else if (currentPrice < MIN_PRICE_FLOOR * 1.2) { const comments = ["Looks like a bottom forming?", "Good entry point here?", "Testing support again.", "Accumulating?"]; comment = comments[Math.floor(Math.random() * comments.length)]; buzzType = "info"; positiveBuzzTicks = 1; } else if (change > 0.005) { const comments = ["Nice little pop.", "Looking bullish.", "Green day!", "Moving up steadily."]; comment = comments[Math.floor(Math.random() * comments.length)]; buzzType = "positive"; positiveBuzzTicks = 2; } else if (change < -0.005) { const comments = ["Bit of a pullback.", "Red candle forming.", "Profit taking?", "Losing steam."]; comment = comments[Math.floor(Math.random() * comments.length)]; buzzType = "negative"; positiveBuzzTicks = 0; } else if (tickCount % 15 === 0) { const comments = ["Market flat.", "Watching paint dry...", "No major news.", "Sideways movement."]; comment = comments[Math.floor(Math.random() * comments.length)]; buzzType = "neutral"; positiveBuzzTicks = 0; } if (comment) { addSocialComment(comment, buzzType); } }
}
function updateTimerDisplay() { /* ... no changes ... */
const secondsElapsed = tickCount; const secondsRemaining = Math.max(0, TOTAL_SECONDS - secondsElapsed); const minutes = Math.floor(secondsRemaining / 60); const seconds = secondsRemaining % 60; const formattedTime = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; countdownTimerEl.textContent = formattedTime;
}
function flashElementBackground(element, flashClass) { /* ... no changes ... */
if (!element) return; element.classList.add(flashClass); setTimeout(() => { element.classList.remove(flashClass); }, 600);
}
function updateUI() { /* ... no changes ... */
capitalValueEl.textContent = `₹${capital.toFixed(2)}`; sharesValueEl.textContent = shares; currentPriceValueEl.textContent = `₹${currentPrice.toFixed(2)}`; portfolioValue = capital + (shares * currentPrice); totalValueEl.textContent = `₹${portfolioValue.toFixed(2)}`; if (portfolioValue > prevPortfolioValue) { flashElementBackground(totalValueEl, 'flash-bg-green'); } else if (portfolioValue < prevPortfolioValue) { flashElementBackground(totalValueEl, 'flash-bg-red'); } prevPortfolioValue = portfolioValue;
stockChart.data.labels.push(tickCount.toString()); stockChart.data.datasets[0].data.push(currentPrice); pointRadius.push(0); pointBackgroundColor.push('transparent'); pointBorderColor.push('transparent'); if (stockChart.data.labels.length > MAX_DATA_POINTS) { stockChart.data.labels.shift(); stockChart.data.datasets[0].data.shift(); pointRadius.shift(); pointBackgroundColor.shift(); pointBorderColor.shift(); } stockChart.data.datasets[0].pointRadius = pointRadius; stockChart.data.datasets[0].pointBackgroundColor = pointBackgroundColor; stockChart.data.datasets[0].pointBorderColor = pointBorderColor; stockChart.update('none');
tradesMadeEl.textContent = trades.length; const profit = portfolioValue - INITIAL_CAPITAL; profitLossEl.textContent = `₹${profit.toFixed(2)}`; profitLossEl.className = `font-medium px-1 rounded-sm ${profit >= 0 ? 'text-green-600' : 'text-red-600'}`; if (profit > parseFloat(profitLossEl.dataset.prevProfit || 0)) { flashElementBackground(profitLossEl, 'flash-bg-green'); } else if (profit < parseFloat(profitLossEl.dataset.prevProfit || 0)) { flashElementBackground(profitLossEl, 'flash-bg-red'); } profitLossEl.dataset.prevProfit = profit; fomoTradesCountEl.textContent = fomoTrades; valueTradesCountEl.textContent = valueTrades; updateFOMOMeter(); updateTimerDisplay();
}
function addTradeFeedback(message, type = "info") { /* ... no changes ... */
let textColor = 'text-slate-600'; if (type === 'success') textColor = 'text-green-600'; if (type === 'error') textColor = 'text-red-600'; if (type === 'warning') textColor = 'text-yellow-700'; tradeFeedbackEl.textContent = message; tradeFeedbackEl.className = `mt-2 text-sm text-slate-600 h-10 ${textColor} font-medium`; setTimeout(() => { if (tradeFeedbackEl.textContent === message) { tradeFeedbackEl.textContent = ''; tradeFeedbackEl.className = 'mt-2 text-sm text-slate-600 h-10'; } }, 5000);
}
function updateTradeHistory(trade) { /* ... no changes ... */
let li = tradeHistoryListEl.querySelector(`[data-tick="${trade.tick}"]`); if (!li) { li = document.createElement('li'); li.setAttribute('data-tick', trade.tick); tradeHistoryListEl.insertBefore(li, tradeHistoryListEl.firstChild); } const typeClass = trade.type === 'buy' ? 'text-green-700' : 'text-red-700'; let tag = ''; if (trade.fomo && trade.price > VALUE_ZONE_HIGH) tag = '<span class="ml-2 text-xs bg-yellow-200 text-yellow-800 px-1 rounded">FOMO</span>'; else if (trade.fomo && trade.price < FALSE_BOTTOM_THRESHOLD) tag = '<span class="ml-2 text-xs bg-orange-300 text-orange-800 px-1 rounded">Trap</span>'; else if (trade.panic) tag = '<span class="ml-2 text-xs bg-red-200 text-red-800 px-1 rounded">Panic</span>'; else if (trade.value) tag = '<span class="ml-2 text-xs bg-blue-200 text-blue-800 px-1 rounded">Value</span>'; li.innerHTML = `<span class="font-medium ${typeClass}">${trade.type.toUpperCase()}</span> ${trade.quantity} shares @ ₹${trade.price.toFixed(2)} (Tick ${trade.tick}) ${tag}`; const placeholder = tradeHistoryListEl.querySelector('li:only-child'); if (placeholder && placeholder.textContent.includes("No trades yet")) placeholder.remove();
}
function executeTrade(type) { /* ... no changes ... */
const quantity = parseInt(tradeQuantityInput.value); if (isNaN(quantity) || quantity <= 0) { addTradeFeedback("Please enter a valid quantity.", "error"); return; } let isFomoTrade = false, isPanicTrade = false, isValueTrade = false, feedback = "";
if (type === 'buy') { const cost = quantity * currentPrice; if (cost > capital) { addTradeFeedback("Not enough capital.", "error"); return; } capital -= cost; shares += quantity; if (currentPrice >= VALUE_ZONE_LOW && currentPrice <= VALUE_ZONE_HIGH) { isValueTrade = true; valueTrades++; feedback = "Bought within the value zone."; addTradeFeedback(feedback, "success"); } else { feedback = `Bought ${quantity} shares at ₹${currentPrice.toFixed(2)}.`; addTradeFeedback(feedback, "info"); isFomoTrade = true; } }
else if (type === 'sell') { if (quantity > shares) { addTradeFeedback("Not enough shares to sell.", "error"); return; } const revenue = quantity * currentPrice; capital += revenue; shares -= quantity; feedback = `Sold ${quantity} shares at ₹${currentPrice.toFixed(2)}.`; addTradeFeedback(feedback, "info"); if (currentPrice < VALUE_ZONE_LOW * 1.1) { isPanicTrade = true; } }
const trade = { type, quantity, price: currentPrice, tick: tickCount, fomo: isFomoTrade, panic: isPanicTrade, value: isValueTrade }; trades.push(trade); updateTradeHistory(trade); updateUI();
const currentDataIndex = stockChart.data.labels.length - 1; if (currentDataIndex >= 0) { pointRadius[currentDataIndex] = 5; if (type === 'buy') { pointBackgroundColor[currentDataIndex] = 'rgba(34, 197, 94, 0.8)'; pointBorderColor[currentDataIndex] = 'rgba(22, 163, 74, 1)'; } else { pointBackgroundColor[currentDataIndex] = 'rgba(239, 68, 68, 0.8)'; pointBorderColor[currentDataIndex] = 'rgba(220, 38, 38, 1)'; } }
}
// --- Counterfactual Analysis ---
function runCounterfactualAnalysis() { /* ... no changes ... */
const modalList = counterfactualAnalysisOutputModalEl; const mainList = counterfactualAnalysisOutputMainEl; if (!modalList || !mainList) return; modalList.innerHTML = ''; mainList.innerHTML = ''; if (trades.length === 0 || priceHistory.length <= 1) { const noTradesMsg = '<li>No trades made for analysis.</li>'; modalList.innerHTML = noTradesMsg; mainList.innerHTML = noTradesMsg; return; } const finalPrice = priceHistory[priceHistory.length - 1]; let analysisResultsHtml = []; trades.forEach(trade => { let resultText = ''; if (trade.panic) { const actualRevenue = trade.quantity * trade.price; const heldValue = trade.quantity * finalPrice; const difference = heldValue - actualRevenue; resultText = `Panic Sell @ Tick ${trade.tick} (₹${trade.price.toFixed(2)}): Holding instead would have resulted in <span class="${difference >= 0 ? 'text-green-600' : 'text-red-600'} font-medium">${difference >= 0 ? '+' : ''}₹${difference.toFixed(2)}</span>.`; } else if (trade.fomo) { const actualProfitLoss = trade.quantity * (finalPrice - trade.price); const counterfactualProfitLoss = 0; const difference = counterfactualProfitLoss - actualProfitLoss; let tradeType = "FOMO Buy"; if (trade.price < FALSE_BOTTOM_THRESHOLD) tradeType = "Trap Buy"; resultText = `${tradeType} @ Tick ${trade.tick} (₹${trade.price.toFixed(2)}): Not making this trade would have resulted in <span class="${difference >= 0 ? 'text-green-600' : 'text-red-600'} font-medium">${difference >= 0 ? '+' : ''}₹${difference.toFixed(2)}</span> vs this trade's outcome.`; } if (resultText) { analysisResultsHtml.push(`<li>${resultText}</li>`); } }); if (analysisResultsHtml.length > 0) { const finalHtml = analysisResultsHtml.join(''); modalList.innerHTML = finalHtml; mainList.innerHTML = finalHtml; } else { const noEmotionalTradesMsg = '<li>No significant emotional trades identified for counterfactual analysis.</li>'; modalList.innerHTML = noEmotionalTradesMsg; mainList.innerHTML = noEmotionalTradesMsg; }
}
// --- Simulation Control Functions ---
function simulationStep() { /* ... no changes ... */
if (!simulationRunning || isPaused) return; if (tickCount >= SIMULATION_TICKS) { endSimulation(); return; }; if (!isConsolidating && Math.random() < CONSOLIDATION_CHANCE && tickCount > 30 && tickCount < SIMULATION_TICKS - 30) { enterConsolidation(); } tickCount++; currentPrice = generateNextPrice(); priceHistory.push(currentPrice); updateFOMOMeter(); generateSocialChatter(); updateUI(); clearTimeout(simulationInterval); if (simulationRunning && !isPaused) { simulationInterval = setTimeout(simulationStep, simulationSpeed); }
}
async function startSimulation() { /* ... no changes ... */
if (simulationRunning) return; if (!audioReady) { try { await Tone.start(); console.log("Audio Context started"); setupAudio(); } catch (e) { console.error("Tone.start failed:", e); muteButton.disabled = true; } } simulationRunning = true; isPaused = false; if (audioReady) { Tone.Transport.start(); } pauseResumeButton.disabled = false; speedSlider.disabled = false; buyButton.disabled = false; sellButton.disabled = false; startButton.disabled = true; addTradeFeedback("Simulation Started!", "info"); simulationInterval = setTimeout(simulationStep, simulationSpeed);
}
function togglePause() { /* ... no changes ... */
if (!simulationRunning) return; isPaused = !isPaused; if (isPaused) { pauseResumeButton.textContent = 'Resume'; pauseResumeButton.classList.replace('bg-blue-600', 'bg-yellow-500'); pauseResumeButton.classList.replace('hover:bg-blue-700', 'hover:bg-yellow-600'); clearTimeout(simulationInterval); if (audioReady) Tone.Transport.pause(); addTradeFeedback("Simulation Paused.", "info"); } else { pauseResumeButton.textContent = 'Pause'; pauseResumeButton.classList.replace('bg-yellow-500', 'bg-blue-600'); pauseResumeButton.classList.replace('hover:bg-yellow-600', 'hover:bg-blue-700'); if (audioReady) Tone.Transport.start(); simulationInterval = setTimeout(simulationStep, simulationSpeed); addTradeFeedback("Simulation Resumed.", "info"); }
}
function updateSpeed() { /* ... no changes ... */
simulationSpeed = parseInt(speedSlider.max) + parseInt(speedSlider.min) - parseInt(speedSlider.value); speedValueEl.textContent = (simulationSpeed / 1000).toFixed(1) + 's'; if (simulationRunning && !isPaused) { clearTimeout(simulationInterval); simulationInterval = setTimeout(simulationStep, simulationSpeed); }
}
function endSimulation() { /* ... no changes ... */
simulationRunning = false; isPaused = true; clearTimeout(simulationInterval); if (audioReady) Tone.Transport.stop(); pauseResumeButton.disabled = true; buyButton.disabled = true; sellButton.disabled = true; speedSlider.disabled = true; muteButton.disabled = true; pauseResumeButton.classList.add('opacity-50', 'cursor-not-allowed'); buyButton.classList.add('opacity-50', 'cursor-not-allowed'); sellButton.classList.add('opacity-50', 'cursor-not-allowed'); finalAnalysisEl.classList.remove('hidden'); addTradeFeedback("Simulation Complete!", "info"); updateTimerDisplay(); runCounterfactualAnalysis(); simEndOverlayEl.classList.remove('hidden');
const finalValue = portfolioValue.toFixed(2); const profit = (portfolioValue - INITIAL_CAPITAL).toFixed(2); const profitPercent = ((portfolioValue / INITIAL_CAPITAL - 1) * 100).toFixed(1); let summaryMessage = `🏁 Simulation Over! Final Value: ₹${finalValue} (Profit: ₹${profit}, ${profitPercent}%).`; if (fomoTrades > trades.length / 2 && fomoTrades > 0) summaryMessage += " Looks like FOMO/Panic/False Bottoms played a big role!"; else if (valueTrades > trades.length / 2 && valueTrades > 0) summaryMessage += " Good job utilizing value zones!"; addSocialComment(summaryMessage, "info");
}
function toggleMute() { /* ... no changes ... */
if (!audioReady) return; isMuted = !isMuted; Tone.Destination.mute = isMuted; updateMuteButtonIcon();
}
// --- Event Listeners ---
startButton.addEventListener('click', startSimulation);
buyButton.addEventListener('click', () => executeTrade('buy'));
sellButton.addEventListener('click', () => executeTrade('sell'));
pauseResumeButton.addEventListener('click', togglePause);
muteButton.addEventListener('click', toggleMute);
speedSlider.addEventListener('input', updateSpeed);
showResultsButtonEl.addEventListener('click', () => {
simEndOverlayEl.classList.add('hidden');
analysisModalOverlayEl.classList.remove('hidden');
});
closeAnalysisModalButtonEl.addEventListener('click', () => {
analysisModalOverlayEl.classList.add('hidden');
// Show the persistent results section in the card
counterfactualSectionEl.classList.remove('hidden');
});
// --- REMOVED Overlay click listeners ---
// --- Initialization ---
window.onload = () => { /* ... no changes ... */
capitalValueEl.textContent = `₹${INITIAL_CAPITAL.toFixed(2)}`; totalValueEl.textContent = `₹${INITIAL_CAPITAL.toFixed(2)}`; currentPriceValueEl.textContent = `₹${currentPrice.toFixed(2)}`; speedValueEl.textContent = (DEFAULT_SIMULATION_SPEED / 1000).toFixed(1) + 's'; speedSlider.value = parseInt(speedSlider.max) + parseInt(speedSlider.min) - DEFAULT_SIMULATION_SPEED; valueZoneIndicatorEl.textContent = `₹${VALUE_ZONE_LOW.toFixed(2)} - ₹${VALUE_ZONE_HIGH.toFixed(2)}`; initializeChart(); updateTimerDisplay(); updateMuteButtonIcon(); advisorTextEl.textContent = '';
};
</script>
</body>
</html>