| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Silverstone British GP β Crowd Flow Optimiser</title> |
| <style> |
| |
| |
| |
| * { |
| box-sizing: border-box; |
| margin: 0; |
| padding: 0; |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| } |
| |
| body { |
| background-color: #121212; |
| color: #ffffff; |
| padding: 20px; |
| } |
| |
| header { |
| text-align: center; |
| margin-bottom: 20px; |
| } |
| |
| header h1 { |
| color: #e10600; |
| font-size: 24px; |
| margin-bottom: 5px; |
| } |
| |
| header p { |
| color: #aaa; |
| font-size: 14px; |
| } |
| |
| |
| .dashboard { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 20px; |
| max-width: 1450px; |
| margin: 0 auto; |
| } |
| |
| |
| .controls-panel { |
| flex: 1; |
| min-width: 320px; |
| background-color: #1e1e1e; |
| padding: 20px; |
| border-radius: 10px; |
| border: 1px solid #333; |
| } |
| |
| .controls-panel h2 { |
| font-size: 18px; |
| margin-bottom: 15px; |
| color: #00cc66; |
| } |
| |
| .slider-group { |
| margin-bottom: 15px; |
| } |
| |
| .slider-group label { |
| display: block; |
| font-size: 13px; |
| margin-bottom: 6px; |
| color: #ddd; |
| } |
| |
| .slider-group input[type="range"] { |
| width: 100%; |
| cursor: pointer; |
| } |
| |
| .value-badge { |
| color: #e10600; |
| font-weight: bold; |
| } |
| |
| |
| .order-btn { |
| width: 100%; |
| padding: 12px; |
| background-color: #e10600; |
| color: white; |
| border: none; |
| border-radius: 6px; |
| font-weight: bold; |
| font-size: 14px; |
| cursor: pointer; |
| margin-top: 10px; |
| transition: background 0.2s; |
| } |
| |
| .order-btn:hover { |
| background-color: #ff1e1e; |
| } |
| |
| .active-orders-list { |
| margin-top: 15px; |
| max-height: 180px; |
| overflow-y: auto; |
| background: #141414; |
| padding: 10px; |
| border-radius: 6px; |
| border: 1px solid #333; |
| } |
| |
| .order-card { |
| background: #252525; |
| padding: 8px; |
| margin-bottom: 8px; |
| border-radius: 4px; |
| border-left: 3px solid #f39c12; |
| font-size: 12px; |
| } |
| |
| .order-card.ready { |
| border-left-color: #00cc66; |
| } |
| |
| |
| .modal { |
| display: none; |
| position: fixed; |
| z-index: 1000; |
| left: 0; |
| top: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0, 0, 0, 0.8); |
| justify-content: center; |
| align-items: center; |
| } |
| |
| .modal-content { |
| background-color: #1e1e1e; |
| padding: 25px; |
| border-radius: 10px; |
| border: 1px solid #444; |
| width: 400px; |
| max-width: 90%; |
| } |
| |
| .modal-content h3 { |
| color: #e10600; |
| margin-bottom: 15px; |
| } |
| |
| .form-group { |
| margin-bottom: 12px; |
| } |
| |
| .form-group label { |
| display: block; |
| font-size: 13px; |
| margin-bottom: 5px; |
| color: #ccc; |
| } |
| |
| .form-group select, .form-group input { |
| width: 100%; |
| padding: 8px; |
| background: #2b2b2b; |
| border: 1px solid #444; |
| color: white; |
| border-radius: 4px; |
| } |
| |
| .modal-actions { |
| display: flex; |
| gap: 10px; |
| margin-top: 20px; |
| } |
| |
| .btn-submit { |
| flex: 1; |
| background: #00cc66; |
| color: black; |
| padding: 10px; |
| border: none; |
| border-radius: 4px; |
| font-weight: bold; |
| cursor: pointer; |
| } |
| |
| .btn-cancel { |
| flex: 1; |
| background: #444; |
| color: white; |
| padding: 10px; |
| border: none; |
| border-radius: 4px; |
| cursor: pointer; |
| } |
| |
| |
| .map-panel { |
| flex: 2; |
| min-width: 450px; |
| background-color: #1e1e1e; |
| padding: 20px; |
| border-radius: 10px; |
| border: 1px solid #333; |
| } |
| |
| canvas { |
| width: 100%; |
| height: auto; |
| background-color: #181818; |
| border-radius: 8px; |
| border: 1px solid #444; |
| display: block; |
| } |
| |
| |
| .report-box { |
| margin-top: 15px; |
| padding: 15px; |
| background-color: #252525; |
| border-radius: 8px; |
| border-left: 5px solid #00cc66; |
| font-size: 14px; |
| line-height: 1.5; |
| } |
| |
| .report-box.alert { |
| border-left-color: #ff4444; |
| background-color: #2a1a1a; |
| } |
| |
| .report-box h3 { |
| margin-bottom: 8px; |
| font-size: 16px; |
| } |
| |
| .badge { |
| display: inline-block; |
| padding: 3px 8px; |
| border-radius: 4px; |
| font-size: 12px; |
| font-weight: bold; |
| } |
| |
| .badge-red { background-color: #ff4444; color: white; } |
| .badge-green { background-color: #00cc66; color: black; } |
| .badge-purple { background-color: #a020f0; color: white; } |
| </style> |
| </head> |
| <body> |
|
|
| <header> |
| <h1>ποΈ SILVERSTONE BRITISH GP β CROWD FLOW OPTIMISER</h1> |
| <p>Real-time interconnected crowd routing, dynamic bottleneck propagation & online ordering integration</p> |
| </header> |
|
|
| <div class="dashboard"> |
| |
| |
| |
| <div class="controls-panel"> |
| <h2>βοΈ Grandstand Crowd Inputs</h2> |
| |
| <div class="slider-group"> |
| <label for="hamilton">Hamilton Straight GS: <span id="hamilton-val" class="value-badge">65</span> fans/min</label> |
| <input type="range" id="hamilton" min="10" max="100" value="65" oninput="updateSimulation()"> |
| </div> |
|
|
| <div class="slider-group"> |
| <label for="becketts">Becketts GS: <span id="becketts-val" class="value-badge">60</span> fans/min</label> |
| <input type="range" id="becketts" min="10" max="100" value="60" oninput="updateSimulation()"> |
| </div> |
|
|
| <div class="slider-group"> |
| <label for="stowe">Stowe GS: <span id="stowe-val" class="value-badge">40</span> fans/min</label> |
| <input type="range" id="stowe" min="10" max="100" value="40" oninput="updateSimulation()"> |
| </div> |
|
|
| <div class="slider-group"> |
| <label for="village">Village GS: <span id="village-val" class="value-badge">55</span> fans/min</label> |
| <input type="range" id="village" min="10" max="100" value="55" oninput="updateSimulation()"> |
| </div> |
|
|
| <div class="slider-group"> |
| <label for="club">Club GS: <span id="club-val" class="value-badge">50</span> fans/min</label> |
| <input type="range" id="club" min="10" max="100" value="50" oninput="updateSimulation()"> |
| </div> |
|
|
| <hr style="border-color: #333; margin: 15px 0;"> |
|
|
| |
| <h2 style="color: #f39c12;">π Click & Collect Food and Merch Express</h2> |
| <button class="order-btn" onclick="openOrderModal()">π Place Express Food/Merch Order</button> |
|
|
| <div style="margin-top: 15px;"> |
| <span style="font-size: 13px; font-weight: bold; color: #aaa;">Active Mobile Orders:</span> |
| <div id="ordersContainer" class="active-orders-list"> |
| <p style="color: #666; font-size: 12px; text-align: center;">No active mobile orders</p> |
| </div> |
| </div> |
|
|
| <hr style="border-color: #333; margin: 15px 0;"> |
|
|
| <div style="font-size: 12px; color: #aaa; line-height: 1.4;"> |
| <p><strong>π‘ Dynamic Flow Rules:</strong></p> |
| <p>β’ <strong>Gate 1 Threshold:</strong> 50 fans/min</p> |
| <p>β’ <strong>Gate 3 Threshold:</strong> 45 fans/min</p> |
| <p>β’ Bottlenecks cause cascading <span class="badge badge-red">RED</span> corridor congestions.</p> |
| <p>β’ Active rerouting renders complex multi-node <span class="badge badge-purple">PURPLE</span> Google Maps detours.</p> |
| </div> |
| </div> |
|
|
| |
| |
| |
| <div class="map-panel"> |
| <canvas id="silverstoneCanvas" width="900" height="520"></canvas> |
| <div id="reportBox" class="report-box"> |
| |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="orderModal" class="modal"> |
| <div class="modal-content"> |
| <h3>π Place Online Pickup Order</h3> |
| <div class="form-group"> |
| <label for="pickupLocation">Select Pickup Location:</label> |
| <select id="pickupLocation"> |
| <option value="Main Fan Zone">Main Fan Zone</option> |
| <option value="Becketts Food">Becketts Food</option> |
| <option value="Village Plaza">Village Plaza</option> |
| <option value="Club Eats">Club Eats</option> |
| </select> |
| </div> |
| <div class="form-group"> |
| <label for="itemSelect">Select Express Menu Item:</label> |
| <select id="itemSelect"> |
| <option value="F1 Burger & Chips Combo (Β£14.00)">F1 Burger & Chips Combo (Β£14.00)</option> |
| <option value="Woodfired Pizza Slices (Β£11.50)">Woodfired Pizza Slices (Β£11.50)</option> |
| <option value="Grid Soft Drink & Fries (Β£7.00)">Grid Soft Drink & Fries (Β£7.00)</option> |
| <option value="Silverstone Official Cap (Β£30.00)">Silverstone Official Cap (Β£30.00)</option> |
| </select> |
| </div> |
| <div class="form-group"> |
| <label for="custName">Customer Name / Ticket ID:</label> |
| <input type="text" id="custName" placeholder="e.g. John - Row C" value="Seat 14B"> |
| </div> |
| <div class="modal-actions"> |
| <button class="btn-submit" onclick="submitOrder()">Confirm Order</button> |
| <button class="btn-cancel" onclick="closeOrderModal()">Cancel</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| |
| |
| <script> |
| |
| const GATE_1_CAPACITY = 50; |
| const GATE_3_CAPACITY = 45; |
| |
| |
| let activeOrders = []; |
| let orderCounter = 101; |
| |
| function updateSimulation() { |
| |
| const hamiltonCrowd = parseInt(document.getElementById('hamilton').value); |
| const beckettsCrowd = parseInt(document.getElementById('becketts').value); |
| const stoweCrowd = parseInt(document.getElementById('stowe').value); |
| const villageCrowd = parseInt(document.getElementById('village').value); |
| const clubCrowd = parseInt(document.getElementById('club').value); |
| |
| |
| document.getElementById('hamilton-val').innerText = hamiltonCrowd; |
| document.getElementById('becketts-val').innerText = beckettsCrowd; |
| document.getElementById('stowe-val').innerText = stoweCrowd; |
| document.getElementById('village-val').innerText = villageCrowd; |
| document.getElementById('club-val').innerText = clubCrowd; |
| |
| |
| const gate1Load = Math.round(hamiltonCrowd * 0.5 + beckettsCrowd * 0.3 + villageCrowd * 0.3); |
| const gate2Load = Math.round(hamiltonCrowd * 0.2 + clubCrowd * 0.4); |
| const gate3Load = Math.round(stoweCrowd * 0.6 + beckettsCrowd * 0.4); |
| const gate4Load = Math.round(villageCrowd * 0.4 + hamiltonCrowd * 0.2); |
| |
| |
| const isGate1Bottleneck = gate1Load > GATE_1_CAPACITY; |
| const isGate3Bottleneck = gate3Load > GATE_3_CAPACITY; |
| const excessGate1 = isGate1Bottleneck ? (gate1Load - GATE_1_CAPACITY) : 0; |
| const excessGate3 = isGate3Bottleneck ? (gate3Load - GATE_3_CAPACITY) : 0; |
| |
| |
| drawCanvasMap( |
| hamiltonCrowd, beckettsCrowd, stoweCrowd, villageCrowd, clubCrowd, |
| gate1Load, gate3Load, isGate1Bottleneck, isGate3Bottleneck |
| ); |
| |
| |
| updateReport(gate1Load, gate2Load, gate3Load, gate4Load, isGate1Bottleneck, isGate3Bottleneck, excessGate1, excessGate3); |
| } |
| |
| |
| function openOrderModal() { |
| document.getElementById('orderModal').style.display = 'flex'; |
| } |
| |
| function closeOrderModal() { |
| document.getElementById('orderModal').style.display = 'none'; |
| } |
| |
| function submitOrder() { |
| const location = document.getElementById('pickupLocation').value; |
| const item = document.getElementById('itemSelect').value; |
| const customer = document.getElementById('custName').value || 'Guest'; |
| |
| const newOrder = { |
| id: `#ORD-${orderCounter++}`, |
| location: location, |
| item: item, |
| customer: customer, |
| status: 'Preparing' |
| }; |
| |
| activeOrders.push(newOrder); |
| closeOrderModal(); |
| renderOrdersUI(); |
| updateSimulation(); |
| |
| |
| setTimeout(() => { |
| newOrder.status = 'Ready for Pickup'; |
| renderOrdersUI(); |
| updateSimulation(); |
| }, 8000); |
| } |
| |
| function completeOrder(orderId) { |
| activeOrders = activeOrders.filter(o => o.id !== orderId); |
| renderOrdersUI(); |
| updateSimulation(); |
| } |
| |
| function renderOrdersUI() { |
| const container = document.getElementById('ordersContainer'); |
| if (activeOrders.length === 0) { |
| container.innerHTML = `<p style="color: #666; font-size: 12px; text-align: center;">No active mobile orders</p>`; |
| return; |
| } |
| |
| container.innerHTML = activeOrders.map(o => ` |
| <div class="order-card ${o.status === 'Ready for Pickup' ? 'ready' : ''}"> |
| <div style="display:flex; justify-space-between; font-weight:bold;"> |
| <span>${o.id} - ${o.customer}</span> |
| <span style="color: ${o.status === 'Ready for Pickup' ? '#00cc66' : '#f39c12'}">${o.status}</span> |
| </div> |
| <div style="color: #aaa; margin-top:3px;">π ${o.location}</div> |
| <div style="color: #ddd;">π ${o.item}</div> |
| ${o.status === 'Ready for Pickup' ? `<button onclick="completeOrder('${o.id}')" style="margin-top:5px; background:#00cc66; color:black; border:none; padding:2px 6px; border-radius:3px; cursor:pointer; font-size:10px; font-weight:bold;">Collect Order</button>` : ''} |
| </div> |
| `).join(''); |
| } |
| |
| function drawCanvasMap(hamilton, becketts, stowe, village, club, gate1Load, gate3Load, isGate1Bottleneck, isGate3Bottleneck) { |
| const canvas = document.getElementById('silverstoneCanvas'); |
| const ctx = canvas.getContext('2d'); |
| |
| |
| ctx.fillStyle = '#181818'; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| |
| function toCanvas(x, y) { |
| return { |
| x: x * 65 + 430, |
| y: -y * 48 + 260 |
| }; |
| } |
| |
| |
| function drawPath(points, color, isDashed = false) { |
| ctx.beginPath(); |
| if (isDashed) ctx.setLineDash([8, 6]); |
| points.forEach((ptKey, idx) => { |
| const pos = typeof ptKey === 'string' ? nodes[ptKey].pos : toCanvas(ptKey.x, ptKey.y); |
| if (idx === 0) ctx.moveTo(pos.x, pos.y); |
| else ctx.lineTo(pos.x, pos.y); |
| }); |
| ctx.strokeStyle = color; |
| ctx.lineWidth = isDashed ? 5 : 4; |
| ctx.stroke(); |
| if (isDashed) ctx.setLineDash([]); |
| } |
| |
| |
| |
| |
| const trackPoints = [ |
| {x: -1, y: 0}, {x: 1, y: -2}, {x: 3, y: -3}, {x: 2, y: -4}, |
| {x: -1, y: -3}, {x: -2, y: -1}, {x: -3, y: 1}, {x: -1, y: 3}, |
| {x: 1, y: 4}, {x: 3, y: 4}, {x: 4, y: 2}, {x: 2, y: 0}, {x: -1, y: 0} |
| ]; |
| |
| |
| ctx.beginPath(); |
| trackPoints.forEach((pt, idx) => { |
| const c = toCanvas(pt.x, pt.y); |
| if (idx === 0) ctx.moveTo(c.x, c.y); |
| else ctx.lineTo(c.x, c.y); |
| }); |
| ctx.strokeStyle = '#333333'; |
| ctx.lineWidth = 14; |
| ctx.stroke(); |
| |
| |
| ctx.beginPath(); |
| ctx.setLineDash([6, 6]); |
| trackPoints.forEach((pt, idx) => { |
| const c = toCanvas(pt.x, pt.y); |
| if (idx === 0) ctx.moveTo(c.x, c.y); |
| else ctx.lineTo(c.x, c.y); |
| }); |
| ctx.strokeStyle = '#aaaaaa'; |
| ctx.lineWidth = 1.5; |
| ctx.stroke(); |
| ctx.setLineDash([]); |
| |
| |
| |
| |
| |
| const nodes = { |
| |
| "Hamilton GS": { pos: toCanvas(-2.2, -2.5), type: "gs", label: `Hamilton (${hamilton})` }, |
| "Becketts GS": { pos: toCanvas(1, 4.8), type: "gs", label: `Becketts (${becketts})` }, |
| "Stowe GS": { pos: toCanvas(4.8, 1.5), type: "gs", label: `Stowe (${stowe})` }, |
| "Village GS": { pos: toCanvas(-4.2, 1.2), type: "gs", label: `Village (${village})` }, |
| "Club GS": { pos: toCanvas(2.8, -4.5), type: "gs", label: `Club (${club})` }, |
| |
| |
| "Main Fan Zone": { pos: toCanvas(-4.2, -1.0), type: "food", label: "π Main Fan Zone" }, |
| "Becketts Food": { pos: toCanvas(2.8, 4.2), type: "food", label: "π Becketts Food" }, |
| "Village Plaza": { pos: toCanvas(-3.2, 3.2), type: "food", label: "π Village Plaza" }, |
| "Club Eats": { pos: toCanvas(-0.5, -4.5), type: "food", label: "π¦ Club Eats" }, |
| |
| |
| "Arena Crossroad": { pos: toCanvas(-3.5, -2.2), type: "hub", label: "π Arena Hub" }, |
| "Becketts Interchange": { pos: toCanvas(-1.0, 4.2), type: "hub", label: "π Becketts Hub" }, |
| "South Bypass": { pos: toCanvas(-2.2, -3.8), type: "hub", label: "π South Hub" }, |
| |
| |
| "Main Gate 1": { pos: toCanvas(-4.8, -2.2), type: "gate", label: "πͺ Main Gate 1" }, |
| "South Gate 2": { pos: toCanvas(1.5, -4.8), type: "gate", label: "πͺ South Gate 2" }, |
| "East Gate 3": { pos: toCanvas(5.2, -1.5), type: "gate", label: "πͺ East Gate 3" }, |
| "West Gate 4": { pos: toCanvas(-4.8, 3.2), type: "gate", label: "πͺ West Gate 4" } |
| }; |
| |
| |
| |
| |
| |
| const pathColorG1 = isGate1Bottleneck ? "#ff4444" : "#00cc66"; |
| const pathColorG3 = isGate3Bottleneck ? "#ff4444" : "#00cc66"; |
| |
| |
| drawPath(["Hamilton GS", "Arena Crossroad"], pathColorG1); |
| drawPath(["Arena Crossroad", "Main Fan Zone"], pathColorG1); |
| drawPath(["Main Fan Zone", "Main Gate 1"], pathColorG1); |
| drawPath(["Village GS", "Main Fan Zone"], pathColorG1); |
| |
| |
| drawPath(["Becketts GS", "Becketts Interchange"], pathColorG3); |
| drawPath(["Becketts GS", "Becketts Food"], pathColorG3); |
| drawPath(["Becketts Food", "Stowe GS"], pathColorG3); |
| drawPath(["Stowe GS", "East Gate 3"], pathColorG3); |
| |
| |
| drawPath(["Becketts Interchange", "Village Plaza"], pathColorG1); |
| drawPath(["Village Plaza", "Village GS"], "#00cc66"); |
| drawPath(["Village Plaza", "West Gate 4"], "#00cc66"); |
| |
| |
| drawPath(["Hamilton GS", "South Bypass"], "#00cc66"); |
| drawPath(["South Bypass", "Club Eats"], "#00cc66"); |
| drawPath(["Club GS", "Club Eats"], "#00cc66"); |
| drawPath(["Club Eats", "South Gate 2"], "#00cc66"); |
| |
| |
| |
| |
| |
| if (isGate1Bottleneck) { |
| |
| drawPath(["Main Fan Zone", "Arena Crossroad", "South Bypass", "Club Eats", "South Gate 2"], "#a020f0", true); |
| |
| |
| drawPath(["Arena Crossroad", "Village GS", "West Gate 4"], "#a020f0", true); |
| } |
| |
| if (isGate3Bottleneck) { |
| |
| drawPath(["Becketts Interchange", "Village Plaza", "Main Fan Zone", "South Bypass", "South Gate 2"], "#a020f0", true); |
| } |
| |
| |
| |
| |
| for (let key in nodes) { |
| const n = nodes[key]; |
| ctx.font = "bold 11px Arial"; |
| const textWidth = ctx.measureText(n.label).width; |
| const boxPadding = 7; |
| |
| let boxColor = "#3498db"; |
| if (n.type === "food") boxColor = "#f39c12"; |
| if (n.type === "hub") boxColor = "#7f8c8d"; |
| if (n.type === "gate") { |
| if ((key === "Main Gate 1" && isGate1Bottleneck) || (key === "East Gate 3" && isGate3Bottleneck)) { |
| boxColor = "#ff4444"; |
| } else { |
| boxColor = "#00cc66"; |
| } |
| } |
| |
| |
| ctx.fillStyle = boxColor; |
| ctx.beginPath(); |
| ctx.roundRect( |
| n.pos.x - textWidth / 2 - boxPadding, |
| n.pos.y - 12, |
| textWidth + boxPadding * 2, |
| 24, |
| 6 |
| ); |
| ctx.fill(); |
| ctx.strokeStyle = "#ffffff"; |
| ctx.lineWidth = 1; |
| ctx.stroke(); |
| |
| |
| ctx.fillStyle = "#ffffff"; |
| ctx.textAlign = "center"; |
| ctx.textBaseline = "middle"; |
| ctx.fillText(n.label, n.pos.x, n.pos.y); |
| |
| |
| if (n.type === "food") { |
| const pendingCount = activeOrders.filter(o => o.location === key).length; |
| if (pendingCount > 0) { |
| ctx.fillStyle = "#e10600"; |
| ctx.beginPath(); |
| ctx.arc(n.pos.x + textWidth / 2 + 5, n.pos.y - 10, 10, 0, Math.PI * 2); |
| ctx.fill(); |
| ctx.strokeStyle = "#ffffff"; |
| ctx.lineWidth = 1.5; |
| ctx.stroke(); |
| |
| ctx.fillStyle = "#ffffff"; |
| ctx.font = "bold 10px Arial"; |
| ctx.fillText(pendingCount, n.pos.x + textWidth / 2 + 5, n.pos.y - 10); |
| } |
| } |
| } |
| |
| |
| ctx.font = "11px Arial"; |
| ctx.fillStyle = "#ffffff"; |
| ctx.textAlign = "left"; |
| ctx.fillText("π’ Clear Pathway", 20, 25); |
| ctx.fillText("π΄ Congested Network Segment", 140, 25); |
| ctx.fillText("π£ Google Maps Active Multi-Leg Detour", 330, 25); |
| } |
| |
| function updateReport(gate1, gate2, gate3, gate4, isG1Alert, isG3Alert, excess1, excess3) { |
| const reportDiv = document.getElementById('reportBox'); |
| |
| if (isG1Alert || isG3Alert) { |
| reportDiv.className = "report-box alert"; |
| let alertsHtml = `<h3 style="color: #ff4444;">π΄ NETWORK BOTTLENECK ALERT ACTIVATED</h3>`; |
| |
| if (isG1Alert) { |
| alertsHtml += `<p>β’ <strong>Main Gate 1 Bottleneck:</strong> Load <code>${gate1}</code> fans/min (Max limit: ${GATE_1_CAPACITY}). Overflow: <code>${excess1}</code> fans.</p>`; |
| } |
| if (isG3Alert) { |
| alertsHtml += `<p>β’ <strong>East Gate 3 Bottleneck:</strong> Load <code>${gate3}</code> fans/min (Max limit: ${GATE_3_CAPACITY}). Overflow: <code>${excess3}</code> fans.</p>`; |
| } |
| |
| alertsHtml += ` |
| <hr style="border-color: #552222; margin: 10px 0;"> |
| <h4 style="color: #a020f0;">π Dynamic Rerouting Protocol:</h4> |
| <p>β’ Activating interconnected <span class="badge badge-purple">PURPLE DETOURS</span> across <strong>Arena Hub</strong>, <strong>South Bypass</strong>, and <strong>Becketts Interchange</strong>.</p> |
| <p>β’ Directing overflow pedestrian traffic to under-utilized exit points at <strong>South Gate 2</strong> and <strong>West Gate 4</strong>.</p> |
| `; |
| reportDiv.innerHTML = alertsHtml; |
| } else { |
| reportDiv.className = "report-box"; |
| reportDiv.innerHTML = ` |
| <h3 style="color: #00cc66;">π’ ALL INTERCONNECTED PATHWAYS OPTIMAL</h3> |
| <p>β’ <strong>Main Gate 1 Load:</strong> <code>${gate1}</code> / ${GATE_1_CAPACITY} fans/min (Safe)</p> |
| <p>β’ <strong>South Gate 2 Load:</strong> <code>${gate2}</code> / 50 fans/min (Safe)</p> |
| <p>β’ <strong>East Gate 3 Load:</strong> <code>${gate3}</code> / ${GATE_3_CAPACITY} fans/min (Safe)</p> |
| <p>β’ <strong>West Gate 4 Load:</strong> <code>${gate4}</code> / 45 fans/min (Safe)</p> |
| <p style="margin-top: 5px; color: #aaa;"><em>Status: Grid flow is balanced across all junctions. No purple detour routing required.</em></p> |
| `; |
| } |
| } |
| |
| |
| window.onload = updateSimulation; |
| </script> |
| </body> |
| </html> |