Advanced Election Simulator

@import url(‘https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@300;400;600;700&display=swap’);

* {

margin: 0;

padding: 0;

box-sizing: border-box;

}

body {

font-family: ‘Rajdhani’, sans-serif;

background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%);

color: #e0e0e0;

min-height: 100vh;

overflow-x: hidden;

}

.cyber-grid {

position: fixed;

top: 0;

left: 0;

width: 100%;

height: 100%;

background-image:

linear-gradient(rgba(0, 255, 255, 0.03) 1px, transparent 1px),

linear-gradient(90deg, rgba(0, 255, 255, 0.03) 1px, transparent 1px);

background-size: 50px 50px;

z-index: -1;

animation: gridPulse 4s ease-in-out infinite;

}

@keyframes gridPulse {

0%, 100% { opacity: 0.3; }

50% { opacity: 0.6; }

}

.container {

/* CHANGED: Removed max-width to allow it to expand */

width: 95%; /* Keeps a small margin on the very edges */

margin: 0 auto;

padding: 30px; /* Increased padding for better internal spacing */

position: relative;

z-index: 1;

}

.header {

text-align: center;

padding: 40px 20px;

background: linear-gradient(135deg, rgba(0, 255, 255, 0.1) 0%, rgba(255, 0, 255, 0.1) 100%);

border: 1px solid rgba(0, 255, 255, 0.3);

border-radius: 20px;

margin-bottom: 30px;

backdrop-filter: blur(10px);

box-shadow: 0 8px 32px rgba(0, 255, 255, 0.1);

position: relative;

overflow: hidden;

}

.header::before {

content: ”;

position: absolute;

top: -50%;

left: -50%;

width: 200%;

height: 200%;

background: linear-gradient(45deg, transparent, rgba(0, 255, 255, 0.05), transparent);

animation: shimmer 3s linear infinite;

}

@keyframes shimmer {

0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); }

100% { transform: translateX(100%) translateY(100%) rotate(45deg); }

}

.header h1 {

font-family: ‘Orbitron’, monospace;

font-size: 3.5rem;

font-weight: 900;

background: linear-gradient(135deg, #00ffff 0%, #ff00ff 50%, #00ff00 100%);

-webkit-background-clip: text;

-webkit-text-fill-color: transparent;

background-clip: text;

text-shadow: 0 0 30px rgba(0, 255, 255, 0.5);

margin-bottom: 10px;

position: relative;

z-index: 2;

}

.header p {

font-size: 1.3rem;

color: #a0a0a0;

position: relative;

z-index: 2;

margin-bottom: 20px; /* Added for spacing */

}

.header .intro-text {

font-size: 1.1rem;

color: #c0c0c0;

max-width: 800px;

margin: 0 auto;

line-height: 1.6;

}

.main-grid {

display: grid;

grid-template-columns: 1fr 1fr 1fr;

gap: 25px;

margin-bottom: 30px;

}

.panel {

background: linear-gradient(135deg, rgba(0, 0, 0, 0.8) 0%, rgba(20, 20, 40, 0.9) 100%);

border: 1px solid rgba(0, 255, 255, 0.2);

border-radius: 15px;

padding: 25px;

backdrop-filter: blur(10px);

box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);

position: relative;

overflow: hidden;

transition: all 0.3s ease;

}

.panel:hover {

border-color: rgba(0, 255, 255, 0.4);

box-shadow: 0 12px 40px rgba(0, 255, 255, 0.1);

transform: translateY(-2px);

}

.panel::before {

content: ”;

position: absolute;

top: 0;

left: -100%;

width: 100%;

height: 2px;

background: linear-gradient(90deg, transparent, #00ffff, transparent);

animation: scanLine 2s linear infinite;

}

@keyframes scanLine {

0% { left: -100%; }

100% { left: 100%; }

}

.panel-title {

font-family: ‘Orbitron’, monospace;

font-size: 1.4rem;

font-weight: 700;

color: #00ffff;

margin-bottom: 20px;

text-transform: uppercase;

letter-spacing: 2px;

display: flex;

align-items: center;

gap: 10px;

}

.panel-title::before {

content: ”;

width: 8px;

height: 8px;

background: #00ffff;

border-radius: 50%;

box-shadow: 0 0 10px #00ffff;

animation: pulse 2s ease-in-out infinite;

}

@keyframes pulse {

0%, 100% { opacity: 1; transform: scale(1); }

50% { opacity: 0.5; transform: scale(1.2); }

}

.input-group {

margin-bottom: 20px;

}

.input-label {

display: block;

font-weight: 600;

margin-bottom: 8px;

color: #b0b0b0;

font-size: 1.1rem;

text-transform: uppercase;

letter-spacing: 1px;

}

.cyber-input {

width: 100%;

padding: 12px 15px;

background: rgba(0, 0, 0, 0.6);

border: 2px solid rgba(0, 255, 255, 0.3);

border-radius: 8px;

color: #00ffff;

font-size: 1.1rem;

font-family: ‘Rajdhani’, sans-serif;

transition: all 0.3s ease;

outline: none;

}

.cyber-input:focus {

border-color: #00ffff;

box-shadow: 0 0 20px rgba(0, 255, 255, 0.3);

background: rgba(0, 255, 255, 0.05);

}

.cyber-select {

width: 100%;

padding: 12px 15px;

background: rgba(0, 0, 0, 0.6);

border: 2px solid rgba(0, 255, 255, 0.3);

border-radius: 8px;

color: #00ffff;

font-size: 1.1rem;

font-family: ‘Rajdhani’, sans-serif;

cursor: pointer;

outline: none;

}

.candidate-section {

grid-column: 1 / -1;

background: linear-gradient(135deg, rgba(0, 0, 0, 0.9) 0%, rgba(30, 30, 60, 0.9) 100%);

border: 1px solid rgba(255, 0, 255, 0.2);

border-radius: 15px;

padding: 30px;

margin-top: 20px;

}

.candidate-grid {

display: grid;

grid-template-columns: 2fr repeat(4, 1fr) auto;

gap: 15px;

align-items: center;

margin-bottom: 15px;

padding: 15px;

background: rgba(0, 0, 0, 0.4);

border: 1px solid rgba(255, 0, 255, 0.1);

border-radius: 10px;

transition: all 0.3s ease;

}

.candidate-grid:hover {

border-color: rgba(255, 0, 255, 0.3);

background: rgba(255, 0, 255, 0.05);

}

.grade-header {

display: grid;

grid-template-columns: 2fr repeat(4, 1fr) auto;

gap: 15px;

margin-bottom: 10px;

padding: 10px 15px;

background: rgba(255, 0, 255, 0.1);

border-radius: 8px;

font-weight: 700;

text-transform: uppercase;

letter-spacing: 1px;

color: #ff00ff;

}

.cyber-btn {

background: linear-gradient(135deg, #00ffff 0%, #ff00ff 100%);

color: #000;

border: none;

padding: 15px 30px;

border-radius: 8px;

font-family: ‘Orbitron’, monospace;

font-weight: 700;

font-size: 1.1rem;

cursor: pointer;

text-transform: uppercase;

letter-spacing: 1px;

transition: all 0.3s ease;

position: relative;

overflow: hidden;

}

.cyber-btn:hover {

transform: translateY(-2px);

box-shadow: 0 10px 30px rgba(0, 255, 255, 0.3);

}

.cyber-btn::before {

content: ”;

position: absolute;

top: 0;

left: -100%;

width: 100%;

height: 100%;

background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);

transition: left 0.5s;

}

.cyber-btn:hover::before {

left: 100%;

}

.cyber-btn-small {

padding: 8px 15px;

font-size: 0.9rem;

}

.cyber-btn-danger {

background: linear-gradient(135deg, #ff0040 0%, #ff4080 100%);

color: white;

}

.stats-display {

display: grid;

grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));

gap: 20px;

margin: 20px 0;

}

.stat-card {

background: rgba(0, 0, 0, 0.8);

border: 1px solid rgba(0, 255, 255, 0.3);

border-radius: 10px;

padding: 20px;

text-align: center;

position: relative;

overflow: hidden;

}

.stat-card::before {

content: ”;

position: absolute;

top: -2px;

left: -2px;

right: -2px;

bottom: -2px;

background: linear-gradient(45deg, #00ffff, #ff00ff, #00ff00, #ffff00);

border-radius: 10px;

z-index: -1;

animation: borderRotate 3s linear infinite;

}

@keyframes borderRotate {

0% { transform: rotate(0deg); }

100% { transform: rotate(360deg); }

}

.stat-number {

font-family: ‘Orbitron’, monospace;

font-size: 2.5rem;

font-weight: 900;

color: #00ffff;

margin-bottom: 5px;

text-shadow: 0 0 10px rgba(0, 255, 255, 0.5);

}

.stat-label {

font-size: 0.9rem;

color: #a0a0a0;

text-transform: uppercase;

letter-spacing: 1px;

}

.results-section {

margin-top: 30px;

display: none;

}

.charts-grid {

display: grid;

grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));

gap: 25px;

margin: 25px 0;

}

.chart-panel {

background: linear-gradient(135deg, rgba(0, 0, 0, 0.9) 0%, rgba(20, 20, 40, 0.9) 100%);

border: 1px solid rgba(0, 255, 255, 0.2);

border-radius: 15px;

padding: 25px;

position: relative;

overflow: hidden;

}

.chart-title {

font-family: ‘Orbitron’, monospace;

font-size: 1.2rem;

color: #ff00ff;

margin-bottom: 10px; /* Reduced for explainer */

text-transform: uppercase;

letter-spacing: 1px;

}

.chart-explainer {

font-size: 0.9rem;

color: #909090;

margin-bottom: 15px;

line-height: 1.4;

}

.results-table {

width: 100%;

border-collapse: collapse;

margin-top: 20px;

background: rgba(0, 0, 0, 0.8);

border-radius: 10px;

overflow: hidden;

}

.results-table th {

background: linear-gradient(135deg, #00ffff 0%, #ff00ff 100%);

color: #000;

padding: 15px;

font-family: ‘Orbitron’, monospace;

font-weight: 700;

text-transform: uppercase;

letter-spacing: 1px;

}

.results-table td {

padding: 12px 15px;

border-bottom: 1px solid rgba(0, 255, 255, 0.1);

color: #e0e0e0;

}

.results-table tbody tr:hover {

background: rgba(0, 255, 255, 0.05);

}

.warning {

background: rgba(255, 165, 0, 0.1);

border: 1px solid rgba(255, 165, 0, 0.3);

color: #ffb84d;

padding: 15px;

border-radius: 8px;

margin: 15px 0;

}

.success {

background: rgba(0, 255, 0, 0.1);

border: 1px solid rgba(0, 255, 0, 0.3);

color: #4dff4d;

padding: 15px;

border-radius: 8px;

margin: 15px 0;

}

canvas {

max-width: 100%;

height: 350px !important;

}

.loading {

display: none;

text-align: center;

padding: 40px;

color: #00ffff;

font-size: 1.2rem;

}

.loading::before {

content: ”;

display: inline-block;

width: 40px;

height: 40px;

border: 4px solid rgba(0, 255, 255, 0.3);

border-top: 4px solid #00ffff;

border-radius: 50%;

animation: spin 1s linear infinite;

margin-bottom: 15px;

}

@keyframes spin {

0% { transform: rotate(0deg); }

100% { transform: rotate(360deg); }

}

@media (max-width: 1200px) {

.main-grid {

grid-template-columns: 1fr 1fr;

}

.candidate-grid, .grade-header {

grid-template-columns: 1.5fr repeat(4, 0.8fr) auto;

font-size: 0.9rem;

}

}

@media (max-width: 768px) {

.main-grid {

grid-template-columns: 1fr;

}

.header h1 {

font-size: 2.5rem;

}

.candidate-grid, .grade-header {

grid-template-columns: 1fr;

gap: 10px;

text-align: left;

}

.charts-grid {

grid-template-columns: 1fr;

}

}

⚡ NEURAL ELECTION SIMULATOR

Advanced Monte Carlo Analysis with Grade-Specific Demographics

Configure student populations, voter turnout, and candidate popularities across different grades.

The simulator runs multiple Monte Carlo trials to project election outcomes, predict winners,

and visualize potential vote distributions and probabilities.

📊 Student Population

⚙️ Simulation Config

100 (Fast)

500 (Balanced)

1,000 (Detailed)

5,000 (Neural)

10,000 (Quantum)

Higher variance means more random fluctuations in votes per simulation.

📈 Live Stats
955
TOTAL STUDENTS

716
PROJECTED VOTERS

These stats update automatically based on your student population and turnout inputs.

👥 Candidates & Grade-Specific Popularity
CANDIDATE NAME
GRADE 9 %
GRADE 10 %
GRADE 11 %
GRADE 12 %
ACTIONS

Input each candidate’s popularity percentage for each grade.

Ensure percentages for each grade sum to 100% across all candidates.

PROCESSING NEURAL NETWORKS…

🏆 Simulation Results
0
VOTES CAST (AVG)

NEURAL PREDICTION

0%
WIN PROBABILITY

0
AVG MARGIN

🎯 Vote Distribution Analysis

This bar chart shows the average number of votes each candidate received across all simulation runs.

It gives you a clear picture of typical vote counts.

📊 Win Probability Matrix

A pie chart illustrating each candidate’s probability of winning the election,

based on the percentage of simulations they won.

📈 Grade-Level Breakdown

This stacked bar chart displays the average votes each candidate secured

from each grade level, highlighting their popularity within specific demographics.

⚡ Simulation Variance

Shows the standard deviation of votes for each candidate across all simulations.

A higher bar indicates more fluctuation in vote counts for that candidate.

🔥 Victory Margin Distribution

A histogram showing the distribution of victory margins (difference between winner’s votes and runner-up’s votes)

across all simulations where a clear winner emerged.

🎲 Monte Carlo Convergence

Tracks the win rate of the leading candidate over the course of the simulations.

A flattening curve indicates the simulation is converging and results are stabilizing.

CANDIDATE AVG VOTES VOTE RANGE WIN RATE VOTE SHARE GRADE 9 AVG GRADE 10 AVG GRADE 11 AVG GRADE 12 AVG

let charts = {}; // Object to hold Chart.js instances

// Initial update of total students and projected voters

document.addEventListener(‘DOMContentLoaded’, () => {

updateTotalStudents();

addEventListeners(); // Add listeners for initial candidates

checkPopularityTotals(); // Check initial candidate popularity

});

function updateTotalStudents() {

const grade9 = parseInt(document.getElementById(‘grade9’).value) || 0;

const grade10 = parseInt(document.getElementById(‘grade10’).value) || 0;

const grade11 = parseInt(document.getElementById(‘grade11’).value) || 0;

const grade12 = parseInt(document.getElementById(‘grade12’).value) || 0;

const total = grade9 + grade10 + grade11 + grade12;

const turnout = parseInt(document.getElementById(‘turnout’).value) || 75;

const voters = Math.round(total * turnout / 100);

document.getElementById(‘totalStudents’).textContent = total.toLocaleString();

document.getElementById(‘totalVoters’).textContent = voters.toLocaleString();

}

function checkPopularityTotals() {

const candidates = document.querySelectorAll(‘#candidatesList .candidate-grid’);

const grades = [‘Grade 9’, ‘Grade 10’, ‘Grade 11’, ‘Grade 12’];

let warnings = [];

for (let gradeIndex = 0; gradeIndex {

const inputs = row.querySelectorAll(‘input[type=”number”]’);

// Ensure the input exists for the given grade index before trying to get its value

if (inputs[gradeIndex]) { // The input indices here correctly map to popularity inputs

total += parseInt(inputs[gradeIndex].value) || 0;

}

});

// Allow for slight floating point inaccuracies if any, otherwise strict 100

if (Math.abs(total – 100) > 0.1) {

warnings.push(`${grades[gradeIndex]}: ${total}%`);

}

}

const warning = document.getElementById(‘popularityWarning’);

if (warnings.length > 0) {

warning.style.display = ‘block’;

warning.innerHTML = `⚠️ Popularity totals should sum to 100% per grade. Current totals: ${warnings.join(‘ | ‘)}`;

} else {

warning.style.display = ‘none’;

}

}

function addCandidate() {

const container = document.getElementById(‘candidatesList’);

const newRow = document.createElement(‘div’);

newRow.className = ‘candidate-grid’;

newRow.innerHTML = `

`;

container.appendChild(newRow);

addEventListeners(); // Re-add listeners for new inputs

checkPopularityTotals(); // Re-check totals after adding

}

function removeCandidate(button) {

const row = button.closest(‘.candidate-grid’);

row.remove();

checkPopularityTotals(); // Re-check totals after removing

}

function resetCandidates() {

const container = document.getElementById(‘candidatesList’);

container.innerHTML = `

`;

addEventListeners(); // Re-add listeners for new inputs

checkPopularityTotals(); // Re-check totals after reset

}

function addEventListeners() {

// Remove existing listeners to prevent duplicates

document.querySelectorAll(‘#grade9, #grade10, #grade11, #grade12, #turnout’)

.forEach(input => input.removeEventListener(‘input’, updateTotalStudents));

// Add new listeners

document.querySelectorAll(‘#grade9, #grade10, #grade11, #grade12, #turnout’)

.forEach(input => input.addEventListener(‘input’, updateTotalStudents));

// Remove existing listeners for candidate popularity inputs

document.querySelectorAll(‘#candidatesList .candidate-grid input[type=”number”]’)

.forEach(input => input.removeEventListener(‘input’, checkPopularityTotals));

// Add new listeners for candidate popularity inputs

document.querySelectorAll(‘#candidatesList .candidate-grid input[type=”number”]’)

.forEach(input => input.addEventListener(‘input’, checkPopularityTotals));

}

async function runSimulation() {

const loadingIndicator = document.getElementById(‘loadingIndicator’);

const resultsSection = document.getElementById(‘resultsSection’);

const resultsMessage = document.getElementById(‘resultsMessage’);

loadingIndicator.style.display = ‘block’;

resultsSection.style.display = ‘none’;

resultsMessage.textContent = ”; // Clear previous messages

const gradePopulations = {

grade9: parseInt(document.getElementById(‘grade9’).value) || 0,

grade10: parseInt(document.getElementById(‘grade10’).value) || 0,

grade11: parseInt(document.getElementById(‘grade11’).value) || 0,

grade12: parseInt(document.getElementById(‘grade12’).value) || 0

};

const turnout = parseInt(document.getElementById(‘turnout’).value) || 75;

const numSimulations = parseInt(document.getElementById(‘simulations’).value) || 500;

const varianceFactor = parseInt(document.getElementById(‘variance’).value) || 8;

const candidateRows = document.querySelectorAll(‘#candidatesList .candidate-grid’);

const candidates = Array.from(candidateRows).map(row => {

const inputs = row.querySelectorAll(‘input’);

return {

name: inputs[0].value || ‘Unknown Candidate’,

popularity: {

9: (parseInt(inputs[1].value) || 0),

10: (parseInt(inputs[2].value) || 0),

11: (parseInt(inputs[3].value) || 0),

12: (parseInt(inputs[4].value) || 0),

}

};

});

// Frontend validation for popularity totals (important to keep)

let popularityValid = true;

for (let gradeIndex = 0; gradeIndex {

const gradeKey = (9 + gradeIndex).toString();

total += cand.popularity[gradeKey];

});

if (Math.abs(total – 100) > 0.1) {

popularityValid = false;

break;

}

}

if (!popularityValid) {

loadingIndicator.style.display = ‘none’;

resultsSection.style.display = ‘block’;

resultsMessage.className = ‘warning’;

resultsMessage.innerHTML = ‘⚠️ **ERROR:** Popularity percentages for each grade must sum to 100%. Please correct the candidate percentages.’;

return;

}

if (candidates.length === 0) {

loadingIndicator.style.display = ‘none’;

resultsSection.style.display = ‘block’;

resultsMessage.className = ‘warning’;

resultsMessage.innerHTML = ‘⚠️ **ERROR:** Please add at least one candidate to run the simulation.’;

return;

}

try {

// In a real application, this would fetch from a backend server.

// For this self-contained HTML, we’ll simulate the backend logic here.

const results = simulateElection(gradePopulations, turnout, numSimulations, varianceFactor, candidates);

// Update display stats

document.getElementById(‘totalVotesCast’).textContent = results.total_votes_cast.toFixed(0).toLocaleString();

document.getElementById(‘winnerName’).textContent = results.winner_name;

document.getElementById(‘winProbability’).textContent = results.win_probability.toFixed(2) + ‘%’;

document.getElementById(‘avgMargin’).textContent = results.avg_margin.toFixed(0);

// Populate results table

const resultsTableBody = document.getElementById(‘resultsTableBody’);

resultsTableBody.innerHTML = ”; // Clear previous results

results.candidate_results.forEach(cand => {

const row = resultsTableBody.insertRow();

row.insertCell().textContent = cand.name;

row.insertCell().textContent = cand.avg_votes.toFixed(0).toLocaleString();

row.insertCell().textContent = cand.vote_range;

row.insertCell().textContent = `${cand.win_rate.toFixed(2)}%`;

row.insertCell().textContent = `${cand.vote_share.toFixed(2)}%`;

row.insertCell().textContent = cand.grade_9_avg.toFixed(0).toLocaleString();

row.insertCell().textContent = cand.grade_10_avg.toFixed(0).toLocaleString();

row.insertCell().textContent = cand.grade_11_avg.toFixed(0).toLocaleString();

row.insertCell().textContent = cand.grade_12_avg.toFixed(0).toLocaleString();

});

// Chart Data Preparation

const candidateNames = results.candidate_names;

const candidateColors = [‘#00ffff’, ‘#ff00ff’, ‘#00ff00’, ‘#ffff00’, ‘#ff8c00’, ‘#8a2be2’]; // More colors if needed

// Vote Distribution Chart

updateChart(‘voteChart’, ‘bar’, {

labels: candidateNames,

datasets: [{

label: ‘Average Votes’,

data: results.vote_distribution_data,

backgroundColor: candidateColors.slice(0, candidateNames.length),

borderColor: candidateColors.slice(0, candidateNames.length).map(c => c + ’80’),

borderWidth: 1

}]

}, {

responsive: true,

maintainAspectRatio: false,

scales: {

y: {

beginAtZero: true,

title: { display: true, text: ‘Average Votes’ }

}

}

});

// Win Probability Chart

updateChart(‘probabilityChart’, ‘pie’, {

labels: candidateNames,

datasets: [{

label: ‘Win Probability’,

data: results.win_prob_data,

backgroundColor: candidateColors.slice(0, candidateNames.length),

hoverOffset: 4

}]

}, {

responsive: true,

maintainAspectRatio: false,

plugins: {

tooltip: {

callbacks: {

label: function(context) {

let label = context.label || ”;

if (label) {

label += ‘: ‘;

}

if (context.parsed !== null) {

label += context.parsed.toFixed(2) + ‘%’;

}

return label;

}

}

}

}

});

// Grade-Level Breakdown Chart

const gradeBreakdownDatasets = candidates.map((cand, idx) => ({

label: cand.name,

data: results.grade_breakdown_data[cand.name].map(val => val.toFixed(0)),

backgroundColor: candidateColors[idx % candidateColors.length],

borderColor: candidateColors[idx % candidateColors.length],

borderWidth: 1,

fill: false

}));

updateChart(‘gradeChart’, ‘bar’, {

labels: [‘Grade 9’, ‘Grade 10’, ‘Grade 11’, ‘Grade 12’],

datasets: gradeBreakdownDatasets

}, {

responsive: true,

maintainAspectRatio: false,

scales: {

x: { stacked: true },

y: { stacked: true, beginAtZero: true, title: { display: true, text: ‘Average Votes per Grade’ } }

}

});

// Simulation Variance Chart

updateChart(‘varianceChart’, ‘bar’, {

labels: candidateNames,

datasets: [{

label: ‘Vote Std Dev’,

data: results.variance_data,

backgroundColor: candidateColors.slice(0, candidateNames.length).map(c => c + ’60’),

borderColor: candidateColors.slice(0, candidateNames.length),

borderWidth: 1

}]

}, {

responsive: true,

maintainAspectRatio: false,

scales: {

y: {

beginAtZero: true,

title: { display: true, text: ‘Standard Deviation of Votes’ }

}

}

});

// Victory Margin Distribution Chart

updateChart(‘marginChart’, ‘bar’, {

labels: results.margin_chart_data.labels,

datasets: [{

label: ‘Number of Simulations’,

data: results.margin_chart_data.data,

backgroundColor: ‘#ff8c0060’,

borderColor: ‘#ff8c00’,

borderWidth: 1

}]

}, {

responsive: true,

maintainAspectRatio: false,

scales: {

x: { title: { display: true, text: ‘Victory Margin (Votes)’ } },

y: { beginAtZero: true, title: { display: true, text: ‘Frequency’ } }

}

});

// Monte Carlo Convergence Chart

updateChart(‘convergenceChart’, ‘line’, {

labels: results.convergence_data.map(d => d.iteration),

datasets: [{

label: ‘Leading Candidate Win Rate (%)’,

data: results.convergence_data.map(d => d.win_rate),

borderColor: ‘#00ccff’,

backgroundColor: ‘#00ccff20’,

fill: true,

tension: 0.1

}]

}, {

responsive: true,

maintainAspectRatio: false,

scales: {

x: { title: { display: true, text: ‘Simulation Iteration’ } },

y: { beginAtZero: true, max: 100, title: { display: true, text: ‘Win Rate (%)’ } }

}

});

resultsMessage.className = ‘success’;

resultsMessage.innerHTML = ‘✅ Simulation Complete! Analyze the neural predictions below.’;

} catch (error) {

console.error(‘Simulation error:’, error);

resultsMessage.className = ‘warning’;

resultsMessage.innerHTML = `⚠️ **ERROR:** ${error.message}`;

} finally {

loadingIndicator.style.display = ‘none’;

resultsSection.style.display = ‘block’;

}

}

function updateChart(canvasId, type, data, options) {

const ctx = document.getElementById(canvasId).getContext(‘2d’);

if (charts[canvasId]) {

charts[canvasId].destroy(); // Destroy previous chart instance

}

charts[canvasId] = new Chart(ctx, {

type: type,

data: data,

options: options

});

}

// — BACKEND SIMULATION LOGIC (Copied from previous Python backend) —

// This is a simplified JavaScript translation of the core simulation.

// It’s placed here to make the HTML file self-contained for online compilers.

function gaussianRandom(mean, stdDev) {

let u = 0, v = 0;

while (u === 0) u = Math.random(); // Converting [0,1) to (0,1)

while (v === 0) v = Math.random();

let num = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);

return num * stdDev + mean;

}

function simulateElection(gradePopulations, turnout, numSimulations, varianceFactor, candidates) {

const grades = [‘9′, ’10’, ’11’, ’12’];

const candidateNames = candidates.map(c => c.name);

let simulationResults = []; // Stores vote counts for each candidate per simulation

let winnerCounts = {}; // Stores how many times each candidate won

candidateNames.forEach(name => winnerCounts[name] = 0);

let convergenceData = [];

let leadingCandidateWins = 0;

for (let sim = 0; sim currentSimVotes[name] = 0);

let totalVotesThisSim = 0;

let votesByGrade = {}; // {candidateName: {grade: votes}}

grades.forEach(grade => {

const gradePop = gradePopulations[`grade${grade}`];

const numVoters = Math.round(gradePop * turnout / 100);

totalVotesThisSim += numVoters;

// Calculate a base percentage for each candidate in this grade

let basePercentages = {};

candidates.forEach(cand => {

basePercentages[cand.name] = cand.popularity[grade] / 100;

});

// Distribute votes with variance

let gradeVotesDistributed = 0;

let candidateGradeVotes = {};

// Generate votes for each candidate in this grade

let rawVotes = {};

let sumRawVotes = 0;

candidates.forEach(cand => {

const meanVotes = numVoters * basePercentages[cand.name];

const stdDev = (varianceFactor / 100) * meanVotes; // Variance as a percentage of mean

let votes = Math.max(0, Math.round(gaussianRandom(meanVotes, stdDev)));

rawVotes[cand.name] = votes;

sumRawVotes += votes;

});

// Normalize votes to total ‘numVoters’ for the grade

candidates.forEach(cand => {

if (sumRawVotes > 0) {

candidateGradeVotes[cand.name] = Math.round(rawVotes[cand.name] / sumRawVotes * numVoters);

} else {

candidateGradeVotes[cand.name] = 0;

}

currentSimVotes[cand.name] += candidateGradeVotes[cand.name];

gradeVotesDistributed += candidateGradeVotes[cand.name];

});

// Adjust for any rounding errors to make total sum exactly numVoters

let diff = numVoters – gradeVotesDistributed;

if (diff !== 0 && candidates.length > 0) {

// Distribute remaining difference to candidates with highest votes

let sortedCandidates = Object.keys(candidateGradeVotes).sort((a, b) => candidateGradeVotes[b] – candidateGradeVotes[a]);

for (let i = 0; i 0) {

candidateGradeVotes[sortedCandidates[i % sortedCandidates.length]]++;

} else {

candidateGradeVotes[sortedCandidates[i % sortedCandidates.length]]–;

}

}

// Update currentSimVotes from corrected gradeVotes

candidates.forEach(cand => {

currentSimVotes[cand.name] = (currentSimVotes[cand.name] – gradeVotesDistributed) + candidateGradeVotes[cand.name];

});

}

votesByGrade[grade] = candidateGradeVotes; // Store grade-specific votes

});

simulationResults.push({

votes: currentSimVotes,

gradeVotes: votesByGrade,

totalVotes: totalVotesThisSim // Sum of votes cast in this simulation

});

// Update winner counts for convergence

let maxVotes = -1;

let currentWinner = null;

candidateNames.forEach(name => {

if (currentSimVotes[name] > maxVotes) {

maxVotes = currentSimVotes[name];

currentWinner = name;

}

});

// Check for ties in this simulation

let tie = false;

candidateNames.forEach(name => {

if (name !== currentWinner && currentSimVotes[name] === maxVotes) {

tie = true;

}

});

if (!tie && currentWinner) {

winnerCounts[currentWinner]++;

}

// Convergence data: Win rate of the current leading candidate

if (sim % 10 === 0 || sim === numSimulations – 1) { // Log every 10 simulations or at the end

let currentLeader = null;

let leaderWinRate = 0;

let maxWins = -1;

Object.keys(winnerCounts).forEach(candName => {

if (winnerCounts[candName] > maxWins) {

maxWins = winnerCounts[candName];

currentLeader = candName;

}

});

if (sim + 1 > 0 && currentLeader) {

leaderWinRate = (winnerCounts[currentLeader] / (sim + 1)) * 100;

}

convergenceData.push({

iteration: sim + 1,

win_rate: leaderWinRate

});

}

}

// Calculate overall results

let avgVotes = {};

let minMaxVotes = {};

let winRates = {};

let voteShares = {};

let avgGradeVotes = {}; // {candidateName: {grade: avgVotes}}

candidateNames.forEach(name => {

avgVotes[name] = 0;

minMaxVotes[name] = { min: Infinity, max: -Infinity };

winRates[name] = (winnerCounts[name] / numSimulations) * 100;

avgGradeVotes[name] = { ‘9’: 0, ’10’: 0, ’11’: 0, ’12’: 0 };

});

let totalAvgVotesCast = 0;

simulationResults.forEach(simRes => {

candidateNames.forEach(name => {

avgVotes[name] += simRes.votes[name];

minMaxVotes[name].min = Math.min(minMaxVotes[name].min, simRes.votes[name]);

minMaxVotes[name].max = Math.max(minMaxVotes[name].max, simRes.votes[name]);

grades.forEach(grade => {

avgGradeVotes[name][grade] += simRes.gradeVotes[grade][name];

});

});

totalAvgVotesCast += simRes.totalVotes;

});

candidateNames.forEach(name => {

avgVotes[name] /= numSimulations;

grades.forEach(grade => {

avgGradeVotes[name][grade] /= numSimulations;

});

});

totalAvgVotesCast /= numSimulations;

// Calculate vote shares

candidateNames.forEach(name => {

if (totalAvgVotesCast > 0) {

voteShares[name] = (avgVotes[name] / totalAvgVotesCast) * 100;

} else {

voteShares[name] = 0;

}

});

// Determine overall winner and win probability

let overallWinner = ‘No clear winner’;

let maxWinRate = 0;

candidateNames.forEach(name => {

if (winRates[name] > maxWinRate) {

maxWinRate = winRates[name];

overallWinner = name;

} else if (winRates[name] === maxWinRate && name !== overallWinner) {

overallWinner = ‘Too close to call’; // Handle ties in overall win rate

}

});

// Calculate average margin

let totalMargin = 0;

let marginCount = 0;

simulationResults.forEach(simRes => {

const sortedVotes = Object.values(simRes.votes).sort((a, b) => b – a);

if (sortedVotes.length >= 2) {

totalMargin += (sortedVotes[0] – sortedVotes[1]);

marginCount++;

}

});

const avgMargin = marginCount > 0 ? totalMargin / marginCount : 0;

// Prepare results for charts

const voteDistributionData = candidateNames.map(name => avgVotes[name]);

const winProbData = candidateNames.map(name => winRates[name]);

let gradeBreakdownData = {};

candidateNames.forEach(name => {

gradeBreakdownData[name] = grades.map(grade => avgGradeVotes[name][grade]);

});

// Calculate Variance (Standard Deviation)

let varianceData = candidateNames.map(name => {

const votesForCandidate = simulationResults.map(res => res.votes[name]);

const n = votesForCandidate.length;

if (n a + b, 0) / n;

const sumOfSquares = votesForCandidate.reduce((sum, val) => sum + Math.pow(val – mean, 2), 0);

return Math.sqrt(sumOfSquares / n); // Population standard deviation

});

// Victory Margin Distribution Data (Histogram Bins)

const marginBins = {};

const marginBinSize = 50; // Group margins into bins of 50

simulationResults.forEach(simRes => {

const sortedVotes = Object.values(simRes.votes).sort((a, b) => b – a);

if (sortedVotes.length >= 2) {

const margin = sortedVotes[0] – sortedVotes[1];

const bin = Math.floor(margin / marginBinSize) * marginBinSize;

marginBins[bin] = (marginBins[bin] || 0) + 1;

}

});

const sortedMarginBins = Object.keys(marginBins).sort((a, b) => parseInt(a) – parseInt(b));

const marginChartLabels = sortedMarginBins.map(bin => `${bin}-${parseInt(bin) + marginBinSize – 1}`);

const marginChartData = sortedMarginBins.map(bin => marginBins[bin]);

let candidateResults = candidateNames.map(name => ({

name: name,

avg_votes: avgVotes[name],

vote_range: `${minMaxVotes[name].min.toFixed(0)} – ${minMaxVotes[name].max.toFixed(0)}`,

win_rate: winRates[name],

vote_share: voteShares[name],

grade_9_avg: avgGradeVotes[name][‘9′],

grade_10_avg: avgGradeVotes[name][’10’],

grade_11_avg: avgGradeVotes[name][’11’],

grade_12_avg: avgGradeVotes[name][’12’]

}));

return {

total_votes_cast: totalAvgVotesCast,

winner_name: overallWinner,

win_probability: maxWinRate,

avg_margin: avgMargin,

candidate_names: candidateNames,

vote_distribution_data: voteDistributionData,

win_prob_data: winProbData,

grade_breakdown_data: gradeBreakdownData,

variance_data: varianceData,

margin_chart_data: {

labels: marginChartLabels,

data: marginChartData

},

convergence_data: convergenceData,

candidate_results: candidateResults

};

}

// — END BACKEND SIMULATION LOGIC —

// Initialize event listeners when the page loads

document.addEventListener(‘DOMContentLoaded’, addEventListeners);

document.addEventListener(‘DOMContentLoaded’, updateTotalStudents);

document.addEventListener(‘DOMContentLoaded’, checkPopularityTotals);