index.html :"<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Simulator</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
        integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
        crossorigin="anonymous">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css"
        integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
    <link rel="stylesheet" href="style.css">
    <link rel="stylesheet" href="spinner.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.8.2/pixi.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/pixi-viewport@4.2.3/dist/viewport.min.js"></script>
    <link rel="stylesheet" href="http://cdn.jsdelivr.net/chartist.js/latest/chartist.min.css">
    <script src="http://cdn.jsdelivr.net/chartist.js/latest/chartist.min.js"></script>
    <script src="Point.js"></script>
</head>


<body class="noselect">
<div class="container-fluid w-100 h-100 pt-0 pb-0">
    <div class="row h-100">
        <div class="col-2 pt-4 shadow-lg mr-0 status-panel bg-light ">
            <!-- <i class="fas fa-car h2"></i> -->
            <h2 class="pb-3 border-bottom">CityFlow</h2>
            <table class="table table-borderless table-hover border-bottom">
                <tbody>
                <tr>
                    <th scope="row" class="pl-0">Number of vehicles</th>
                    <td class="text-right" id="car-num">0</td>
                </tr>
                <tr class="d-none">
                    <th scope="row" class="pl-0">Total number of simulation step</th>
                    <td class="text-right" id="total-step-num">0</td>
                </tr>
                <tr>
                    <th scope="row" class="pl-0">Current step</th>
                    <td class="text-right" id="current-step-num">0</td>
                </tr>
                <tr class="d-none">
                    <th scope="row" class="pl-0">Current simulation progress</th>
                    <td class="text-right" id="progress-percentage">0</td>
                </tr>
                <tr class="d-none" id="speed-tr">
                    <th scope="row" class="pl-0">Average speed on lanes</th>
                    <td class="text-right" id="speed">0</td>
                </tr>
                <tr>
                    <th scope="row" class="pl-0">Selected</th>
                    <td class="text-right" id="selected-entity"></td>
                </tr>
                </tbody>
            </table>
            <div id="file-upload" class="border-dark">
                <div class="custom-file mb-3">
                    <input class="custom-file-input" type="file" id="roadnet-file">
                    <label class="custom-file-label" for="roadnet-file" id="roadnet-label">Roadnet File</label>
                </div>
                <div class="custom-file mb-3">
                    <input class="custom-file-input" type="file" id="replay-file">
                    <label class="custom-file-label" for="replay-file" id="replay-label">Replay File</label>
                </div>
                <div class="custom-file mb-3">
                    <input class="custom-file-input" type="file" id="overlay-image-file" accept="image/*">
                    <label class="custom-file-label" for="overlay-image-file" id="overlay-image-label">Overlay Image (Optional)</label>
                </div>
                <div class="custom-file">
                    <input class="custom-file-input" type="file" id="chart-file">
                    <label class="custom-file-label" for="replay-file" id="chart-label">Chart File (Optional)</label>
                </div>
                <div class="row m-3 align-items-center">
                    <div class="col-6">
                        <div class="custom-control custom-checkbox">
                            <input class="custom-control-input" type="checkbox" id="debug-mode" />
                            <label class="custom-control-label" for="debug-mode">
                                Debug*
                            </label>
                        </div>
                    </div>
                    <div class="col-6 text-center p-0">
                        <button class="btn btn-outline-secondary" id="start-btn">Start</button>
                    </div>
                </div>
            </div>

            <div id="control-box " class="border-bottom">
                <h5 class="mt-4">Control Box</h5>
                                <!-- Overlay Controls -->
                <div id="overlay-controls" class="mt-4">
                    <h5>Overlay Controls</h5>
                    
                    <!-- Opacity Control -->
                    <div class="form-group">
                        <label for="overlay-opacity">Opacity:</label>
                        <input type="range" class="form-control-range" id="overlay-opacity" min="0" max="1" step="0.01" value="1">
                    </div>
                    
                    <!-- Position X Control -->
                    <div class="form-group">
                        <label>Position X:</label>
                        <div class="input-group">
                            <div class="input-group-prepend">
                                <button class="btn btn-outline-secondary" type="button" id="decrease-pos-x">-</button>
                            </div>
                            <input type="number" class="form-control text-center" id="overlay-pos-x-input" value="0">
                            <input type="hidden" id="overlay-pos-x-display" value="0">
                            <div class="input-group-append">
                                <button class="btn btn-outline-secondary" type="button" id="increase-pos-x">+</button>
                            </div>
                        </div>
                    </div>
                    
                    <!-- Position Y Control -->
                    <div class="form-group">
                        <label>Position Y:</label>
                        <div class="input-group">
                            <div class="input-group-prepend">
                                <button class="btn btn-outline-secondary" type="button" id="decrease-pos-y">-</button>
                            </div>
                            <input type="number" class="form-control text-center" id="overlay-pos-y-input" value="0">
                            <input type="hidden" id="overlay-pos-y-display" value="0">
                            <div class="input-group-append">
                                <button class="btn btn-outline-secondary" type="button" id="increase-pos-y">+</button>
                            </div>
                        </div>
                    </div>

                    <!-- Step Size for Position Adjustment -->
                    <div class="form-group">
                        <label for="position-step">Position Step Size:</label>
                        <input type="number" class="form-control" id="position-step" min="1" step="1" value="5">
                    </div>
                    
                    <!-- Scale Control -->
                    <div class="form-group">
                        <label for="overlay-scale">Scale:</label>
                        <input type="range" class="form-control-range" id="overlay-scale" min="0.05" max="0.1" step="0.005" value="1">
                    </div>

                    <!-- Optional: Reset Controls -->
                    <button class="btn btn-secondary mt-2" id="reset-overlay">Reset Overlay</button>
                    
                    <div class="form-group form-check mt-2">
                        <input type="checkbox" class="form-check-input" id="toggle-overlay" checked>
                        <label class="form-check-label" for="toggle-overlay">Show Overlay</label>
                    </div>
                </div>
                <table class="table table-borderless table-hover mb-0">
                    <tbody>
                    <tr style="line-height: 10px; height: 10px;">
                        <th scope="row" class="pl-0" id="stats-name">Replay Speed</th>
                        <td class="text-right" id="replay-speed">0</td>
                    </tr>
                    </tbody>
                </table>
                <div class="row no-gutters">
                    <div class="col-1 text-center">
                        <span class="fas fa-minus" id="slow-btn" style="cursor: pointer"></span>
                    </div>
                    <div class="col-10">
                        <input type="range" class="custom-range" id="replay-control">
                    </div>
                    <div class="col-1 text-center">
                        <span class="fas fa-plus" id="fast-btn" style="cursor: pointer"></span>
                    </div>
                </div>
                <div class="row m-3">
                    <div class="col-12 text-center">
                        <button class="btn btn-outline-secondary w-100" id="pause">Pause</button>
                    </div>
                </div>

            </div>

            <h5 class="mt-4">Info Box</h5>
            <div id="info" class="alert alert-light border">
            </div>
        </div>
        <div class="col-10 h-100 d-flex flex-column p-0" >
            <div id="simulator-canvas" class="flex-grow-1 h-100">
                <div id="spinner" class="spinner d-none">
                    <div class="rect1"></div>
                    <div class="rect2"></div>
                    <div class="rect3"></div>
                    <div class="rect4"></div>
                    <div class="rect5"></div>
                </div>
                <div id="guide" class="ml-auto mr-auto mt-5 alert alert-secondary  w-50">
                    <h5>How to use this player</h5>
                    <hr />
                    <ul>
                        <li>Click <b>Roadnet File</b> to upload the roadnet log file(*.json)</li>
                        <li>Click <b>Replay File</b> to upload the replay file(*.txt)</li>
                        <li>(Optional) Click <b>Chart File</b> to upload the chart log file</li>
                        <li>Press the <b>Start</b> button to start replaying the simulation!</li>
                        <li>To restart, just press <b>Start</b> again</li>
                        <li>(*) The <b>debug</b> option enables displaying the ID of vehicles, roads and intersections during a mouse hover.
                            <b>This will cause a slower replaying</b>, so we suggest using it only for debugging purposes.</li>
                    </ul>
                    <h5>Navigation Keys</h5>
                    <hr />
                    <ul class="list-group">
                        <li class="list-group-item d-flex justify-content-between align-items-center">
                            Pause
                            <span class="badge badge-secondary">P</span>
                        </li>
                        <li class="list-group-item d-flex justify-content-between align-items-center">
                            Step Forward
                            <span class="badge badge-secondary">]</span>
                        </li>
                        <li class="list-group-item d-flex justify-content-between align-items-center">
                            Step Backward
                            <span class="badge badge-secondary">[</span>
                        </li>
                    </ul>

                </div>
            </div>
            <div id="chart-container" class="h-25 m-0 border-top pt-2 pl-4 d-none" >
                <div id="chart-title">Real-time Chart</div>
                <div id="chart" class="ct-chart"></div>
            </div>
        </div>
    </div>
</div>


<script src="script.js"></script>
</body>

</html>"

Point.js:"class Point {
    constructor(x, y) {
        if (arguments.length == 1) {
            this.x = x[0];
            this.y = x[1];
        } else if (arguments.length == 2) {
            this.x = x;
            this.y = y;
        }   
    }
    
    getLength() {
        return Math.sqrt(this.x*this.x + this.y*this.y);
    }
    
    getAngle() {
        return this.getAngleInRadians() * 180 / Math.PI;
    }

    getAngleInRadians() {
        return Math.atan2(this.y, this.x);
    }

    add(point) {
        return new Point(this.x + point.x, this.y + point.y)
    }

    directTo(point) {
        let x = point.x - this.x;
        let y = point.y - this.y;
        let length = Math.sqrt(x*x + y*y);
        let scale = length !== 0 ? 1 / length : 0;
        return new Point(x * scale, y * scale);
    }

    distanceTo(point) {
        let x = point.x - this.x;
        let y = point.y - this.y;
        return Math.sqrt(x*x + y*y);
    }

    moveAlong(direct, length) {
        return new Point(this.x + direct.x * length, this.y + direct.y * length);
    }

    moveAlongDirectTo(point, length) {
        let direct = this.directTo(point);
        return new Point(this.x + direct.x * length, this.y + direct.y * length);
    }

    rotate(angle) {
        angle = angle * Math.PI / 180;
        let sin = Math.sin(angle);
        let cos = Math.cos(angle);
        return new Point(this.x * cos - this.y * sin, this.x * sin + this.y * cos);
    }
}"

scripts.js:"/**
 * Draw Road Network
 */
id = Math.random().toString(36).substring(2, 15);

BACKGROUND_COLOR = 0xe8ebed;
LANE_COLOR = 0x586970;
LANE_BORDER_WIDTH = 1;
LANE_BORDER_COLOR = 0x82a8ba;
LANE_INNER_COLOR = 0xbed8e8;
LANE_DASH = 10;
LANE_GAP = 12;
TRAFFIC_LIGHT_WIDTH = 3;
MAX_TRAFFIC_LIGHT_NUM = 100000;
ROTATE = 90;

CAR_LENGTH = 5;
CAR_WIDTH = 2;
CAR_COLOR = 0xe8bed4;

CAR_COLORS = [0xf2bfd7, // pink
            0xb7ebe4,   // cyan
            0xdbebb7,   // blue
            0xf5ddb5, 
            0xd4b5f5];
CAR_COLORS_NUM = CAR_COLORS.length;

NUM_CAR_POOL = 150000;

LIGHT_RED = 0xdb635e;
LIGHT_GREEN = 0x85ee00;

TURN_SIGNAL_COLOR = 0xFFFFFF;
TURN_SIGNAL_WIDTH   = 1;
TURN_SIGNAL_LENGTH  = 5;

var overlayImageSprite = null;
var isDragging = false;
var dragData = null;
var dragStart = { x: 0, y: 0 };
var spriteStart = { x: 0, y: 0 };

var simulation, roadnet, steps;
var nodes = {};
var edges = {};
var logs;
var gettingLog = false;

let Application = PIXI.Application,
    Sprite = PIXI.Sprite,
    Graphics = PIXI.Graphics,
    Container = PIXI.Container,
    ParticleContainer = PIXI.particles.ParticleContainer,
    Texture = PIXI.Texture,
    Rectangle = PIXI.Rectangle
;

var controls = new function () {
    this.replaySpeedMax = 1;
    this.replaySpeedMin = 0.01;
    this.replaySpeed = 0.5;
    this.paused = false;
};

var trafficLightsG = {};

var app, viewport, renderer, simulatorContainer, carContainer, trafficLightContainer;
var turnSignalContainer;
var carPool;

var cnt = 0;
var frameElapsed = 0;
var totalStep;

var nodeCarNum = document.getElementById("car-num");
var nodeProgressPercentage = document.getElementById("progress-percentage");
var nodeTotalStep = document.getElementById("total-step-num");
var nodeCurrentStep = document.getElementById("current-step-num");
var nodeSelectedEntity = document.getElementById("selected-entity");

var SPEED = 3, SCALE_SPEED = 1.01;
var LEFT = 37, UP = 38, RIGHT = 39, DOWN = 40;
var MINUS = 189, EQUAL = 187, P = 80;
var LEFT_BRACKET = 219, RIGHT_BRACKET = 221; 
var ONE = 49, TWO = 50;
var SPACE = 32;

var keyDown = new Set();

var turnSignalTextures = [];

let pauseButton = document.getElementById("pause");
let nodeCanvas = document.getElementById("simulator-canvas");
let replayControlDom = document.getElementById("replay-control");
let replaySpeedDom = document.getElementById("replay-speed");

let loading = false;
let infoDOM = document.getElementById("info");
let selectedDOM = document.getElementById("selected-entity");

function infoAppend(msg) {
    infoDOM.innerText += "- " + msg + "\n";
}

function infoReset() {
    infoDOM.innerText = "";
}

/**
 * Upload files
 */
let ready = false;

let roadnetData = [];
let replayData = [];
let chartData = [];

function handleChooseFile(v, label_dom) {
    return function(evt) {
        let file = evt.target.files[0];
        label_dom.innerText = file.name;
    }
}

function uploadFile(v, file, callback) {
    let reader = new FileReader();
    reader.onloadstart = function () {
        infoAppend("Loading " + file.name);
    };
    reader.onerror = function() {
        infoAppend("Loading " + file.name + "failed");
    }
    reader.onload = function (e) {
        infoAppend(file.name + " loaded");
        v[0] = e.target.result;
        callback();
    };
    try {
        reader.readAsText(file);
    } catch (e) {
        infoAppend("Loading failed");
        console.error(e.message);
    }
}

// Handle Overlay Image Upload
let OverlayImageFileDom = document.getElementById("overlay-image-file");
let OverlayImageLabelDom = document.getElementById("overlay-image-label");

OverlayImageFileDom.addEventListener("change", handleChooseFileForImage, false);

// Function to handle the overlay image file selection
function handleChooseFileForImage(evt) {
    let file = evt.target.files[0];
    if (file) {
        OverlayImageLabelDom.innerText = file.name;
        uploadOverlayImage(file);
    }
}

// Function to upload and create the overlay image sprite
// Modify the uploadOverlayImage function to initialize control values
function uploadOverlayImage(file) {
    let reader = new FileReader();
    reader.onloadstart = function () {
        infoAppend("Loading overlay image...");
    };
    reader.onerror = function() {
        infoAppend("Loading overlay image failed.");
    }
    reader.onload = function (e) {
        infoAppend("Overlay image loaded.");
        let texture = PIXI.Texture.from(e.target.result);
        if (overlayImageSprite) {
            simulatorContainer.removeChild(overlayImageSprite);
        }
        overlayImageSprite = new PIXI.Sprite(texture);
        overlayImageSprite.anchor.set(0.5);
        overlayImageSprite.scale.set(1);
        overlayImageSprite.position.set(0, 0); // Centered initially
        overlayImageSprite.alpha = 1; // Default opacity

        // Disable existing mouse interactions
        overlayImageSprite.interactive = false;
        overlayImageSprite.buttonMode = false;
        //overlayImageSprite.off('pointerdown', onDragStart);
        //overlayImageSprite.off('pointerup', onDragEnd);
        //overlayImageSprite.off('pointerupoutside', onDragEnd);
        //overlayImageSprite.off('pointermove', onDragMove);

        
        // Add the sprite to the simulatorContainer below other elements
        //simulatorContainer.addChildAt(overlayImageSprite, simulatorContainer.children.length - 1); // Adjust index as needed

        // Initialize control values to match the sprite's properties
        initializeOverlayControls();
    };
    try {
        reader.readAsDataURL(file);
    } catch (e) {
        infoAppend("Loading overlay image failed.");
        console.error(e.message);
    }
}

let debugMode = false;
let chartLog;
let showChart = false;
let chartConainterDOM = document.getElementById("chart-container");
function start() {
    if (loading) return;
    loading = true;
    infoReset();
    uploadFile(roadnetData, RoadnetFileDom.files[0], function(){
    uploadFile(replayData, ReplayFileDom.files[0], function(){
        let after_update = function() {
            infoAppend("drawing roadnet");
            ready = false;
            document.getElementById("guide").classList.add("d-none");
            hideCanvas();
            try {
                simulation = JSON.parse(roadnetData[0]);
            } catch (e) {
                infoAppend("Parsing roadnet file failed");
                loading = false;
                return;
            }
            try {
                logs = replayData[0].split('\n');
                logs.pop();
            } catch (e) {
                infoAppend("Reading replay file failed");
                loading = false;
                return;
            }

            totalStep = logs.length;
            if (showChart) {
                chartConainterDOM.classList.remove("d-none");
                let chart_lines = chartData[0].split('\n');
                if (chart_lines.length == 0) {
                    infoAppend("Chart file is empty");
                    showChart = false;
                }
                chartLog = [];
                for (let i = 0 ; i < totalStep ; ++i) {
                    step_data = chart_lines[i + 1].split(/[ \t]+/);
                    chartLog.push([]);
                    for (let j = 0; j < step_data.length; ++j) {
                        chartLog[i].push(parseFloat(step_data[j]));
                    }
                }
                chart.init(chart_lines[0], chartLog[0].length, totalStep);
            }else {
                chartConainterDOM.classList.add("d-none");
            }

            controls.paused = false;
            cnt = 0;
            debugMode = document.getElementById("debug-mode").checked;
            setTimeout(function () {
                try {
                    drawRoadnet();
                } catch (e) {
                    infoAppend("Drawing roadnet failed");
                    console.error(e.message);
                    loading = false;
                    return;
                }
                ready = true;
                loading = false;
                infoAppend("Start replaying");
            }, 200);
        };


        if (ChartFileDom.value) {
            showChart = true;
            uploadFile(chartData, ChartFileDom.files[0], after_update);
        } else {
            showChart = false;
            after_update();
        }

    }); // replay callback
    }); // roadnet callback
}

let RoadnetFileDom = document.getElementById("roadnet-file");
let ReplayFileDom = document.getElementById("replay-file");
let ChartFileDom = document.getElementById("chart-file");

RoadnetFileDom.addEventListener("change",
    handleChooseFile(roadnetData, document.getElementById("roadnet-label")), false);
ReplayFileDom.addEventListener("change",
    handleChooseFile(replayData, document.getElementById("replay-label")), false);
ChartFileDom.addEventListener("change",
    handleChooseFile(chartData, document.getElementById("chart-label")), false);

document.getElementById("start-btn").addEventListener("click", start);

document.getElementById("slow-btn").addEventListener("click", function() {
    updateReplaySpeed(controls.replaySpeed - 0.1);
})

document.getElementById("fast-btn").addEventListener("click", function() {
    updateReplaySpeed(controls.replaySpeed + 0.1);
})

function updateReplaySpeed(speed){
    speed = Math.min(speed, 1);
    speed = Math.max(speed, 0);
    controls.replaySpeed = speed;
    replayControlDom.value = speed * 100;
    replaySpeedDom.innerHTML = speed.toFixed(2);
}

updateReplaySpeed(0.5);

replayControlDom.addEventListener('change', function(e){
    updateReplaySpeed(replayControlDom.value / 100);
});

document.addEventListener('keydown', function(e) {
    if (e.keyCode == P) {
        controls.paused = !controls.paused;
    } else if (e.keyCode == ONE) {
        updateReplaySpeed(Math.max(controls.replaySpeed / 1.5, controls.replaySpeedMin));
    } else if (e.keyCode == TWO ) {
        updateReplaySpeed(Math.min(controls.replaySpeed * 1.5, controls.replaySpeedMax));
    } else if (e.keyCode == LEFT_BRACKET) {
        cnt = (cnt - 1) % totalStep;
        cnt = (cnt + totalStep) % totalStep;
        drawStep(cnt);
    } else if (e.keyCode == RIGHT_BRACKET) {
        cnt = (cnt + 1) % totalStep;
        drawStep(cnt);
    } else {
        keyDown.add(e.keyCode)
    }
});

document.addEventListener('keyup', (e) => keyDown.delete(e.keyCode));

nodeCanvas.addEventListener('dblclick', function(e){
    controls.paused = !controls.paused;
});

pauseButton.addEventListener('click', function(e){
    controls.paused = !controls.paused;
});

function initCanvas() {
    app = new Application({
        width: nodeCanvas.offsetWidth,
        height: nodeCanvas.offsetHeight,
        transparent: false,
        backgroundColor: BACKGROUND_COLOR
    });

    nodeCanvas.appendChild(app.view);
    app.view.classList.add("d-none");

    renderer = app.renderer;
    renderer.interactive = true;
    renderer.autoResize = true;

    renderer.resize(nodeCanvas.offsetWidth, nodeCanvas.offsetHeight);
    app.ticker.add(run);
}

function showCanvas() {
    document.getElementById("spinner").classList.add("d-none");
    app.view.classList.remove("d-none");
}

function hideCanvas() {
    document.getElementById("spinner").classList.remove("d-none");
    app.view.classList.add("d-none");
}

function drawRoadnet() {
    if (simulatorContainer) {
        simulatorContainer.destroy(true);
    }
    app.stage.removeChildren();
    viewport = new Viewport.Viewport({
        screenWidth: window.innerWidth,
        screenHeight: window.innerHeight,
        interaction: app.renderer.plugins.interaction
    });
    viewport
        .drag()
        .pinch()
        .wheel()
        .decelerate();
    app.stage.addChild(viewport);
    simulatorContainer = new Container();
    viewport.addChild(simulatorContainer);

    roadnet = simulation.static;
    nodes = [];
    edges = [];
    trafficLightsG = {};

    for (let i = 0, len = roadnet.nodes.length;i < len;++i) {
        node = roadnet.nodes[i];
        node.point = new Point(transCoord(node.point));
        nodes[node.id] = node;
    }

    for (let i = 0, len = roadnet.edges.length;i < len;++i) {
        edge = roadnet.edges[i];
        edge.from = nodes[edge.from];
        edge.to = nodes[edge.to];
        for (let j = 0, len = edge.points.length;j < len;++j) {
            edge.points[j] = new Point(transCoord(edge.points[j]));
        }
        edges[edge.id] = edge;
    }

    /**
     * Draw Map
     */
    trafficLightContainer = new ParticleContainer(MAX_TRAFFIC_LIGHT_NUM, {tint: true});
    let mapContainer, mapGraphics;
    if (debugMode) {
        mapContainer = new Container();
        simulatorContainer.addChild(mapContainer);
    }else {
        mapGraphics = new Graphics();
        simulatorContainer.addChild(mapGraphics);
    }

    for (nodeId in nodes) {
        if (!nodes[nodeId].virtual) {
            let nodeGraphics;
            if (debugMode) {
                nodeGraphics = new Graphics();
                mapContainer.addChild(nodeGraphics);
            } else {
                nodeGraphics = mapGraphics;
            }
            drawNode(nodes[nodeId], nodeGraphics);
        }
    }
    for (edgeId in edges) {
        let edgeGraphics;
        if (debugMode) {
            edgeGraphics = new Graphics();
            mapContainer.addChild(edgeGraphics);
        } else {
            edgeGraphics = mapGraphics;
        }
        drawEdge(edges[edgeId], edgeGraphics);
    }
    let bounds = simulatorContainer.getBounds();
    simulatorContainer.pivot.set(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
    simulatorContainer.position.set(renderer.width / 2, renderer.height / 2);
    simulatorContainer.addChild(trafficLightContainer);

    // Add the overlayImageSprite if it exists
    if (overlayImageSprite) {
        simulatorContainer.addChildAt(overlayImageSprite, simulatorContainer.children.length - 1);
    }

    /**
     * Settings for Cars
     */
    TURN_SIGNAL_LENGTH = CAR_LENGTH;
    TURN_SIGNAL_WIDTH  = CAR_WIDTH / 2;

    var carG = new Graphics();
    carG.lineStyle(0);
    carG.beginFill(0xFFFFFF, 0.8);
    carG.drawRect(0, 0, CAR_LENGTH, CAR_WIDTH);

    let carTexture = renderer.generateTexture(carG);

    let signalG = new Graphics();
    signalG.beginFill(TURN_SIGNAL_COLOR, 0.7).drawRect(0,0, TURN_SIGNAL_LENGTH, TURN_SIGNAL_WIDTH)
           .drawRect(0, 3 * CAR_WIDTH - TURN_SIGNAL_WIDTH, TURN_SIGNAL_LENGTH, TURN_SIGNAL_WIDTH).endFill();
    let turnSignalTexture = renderer.generateTexture(signalG);

    let signalLeft = new Texture(turnSignalTexture, new Rectangle(0, 0, TURN_SIGNAL_LENGTH, CAR_WIDTH));
    let signalStraight = new Texture(turnSignalTexture, new Rectangle(0, CAR_WIDTH, TURN_SIGNAL_LENGTH, CAR_WIDTH));
    let signalRight = new Texture(turnSignalTexture, new Rectangle(0, CAR_WIDTH * 2, TURN_SIGNAL_LENGTH, CAR_WIDTH));
    turnSignalTextures = [signalLeft, signalStraight, signalRight];


    carPool = [];
    if (debugMode)
        carContainer = new Container();
    else
        carContainer = new ParticleContainer(NUM_CAR_POOL, {rotation: true, tint: true});


    turnSignalContainer = new ParticleContainer(NUM_CAR_POOL, {rotation: true, tint: true});
    simulatorContainer.addChild(carContainer);
    simulatorContainer.addChild(turnSignalContainer);
    for (let i = 0, len = NUM_CAR_POOL;i < len;++i) {
        //var car = Sprite.fromImage("images/car.png")
        let car = new Sprite(carTexture);
        let signal = new Sprite(turnSignalTextures[1]);
        car.anchor.set(1, 0.5);

        if (debugMode) {
            car.interactive = true;
            car.on('mouseover', function () {
                selectedDOM.innerText = car.name;
                car.alpha = 0.8;
            });
            car.on('mouseout', function () {
                // selectedDOM.innerText = "";
                car.alpha = 1;
            });
        }
        signal.anchor.set(1, 0.5);
        carPool.push([car, signal]);
    }
    showCanvas();

    return true;
}

function appendText(id, text) {
    let p = document.createElement("span");
    p.innerText = text;
    document.getElementById("info").appendChild(p);
    document.getElementById("info").appendChild(document.createElement("br"));
}

var statsFile = "";
var withRange = false;
var nodeStats, nodeRange;

initCanvas();


function transCoord(point) {
    return [point[0], -point[1]];
}

PIXI.Graphics.prototype.drawLine = function(pointA, pointB) {
    this.moveTo(pointA.x, pointA.y);
    this.lineTo(pointB.x, pointB.y);
}

PIXI.Graphics.prototype.drawDashLine = function(pointA, pointB, dash = 16, gap = 8) {
    let direct = pointA.directTo(pointB);
    let distance = pointA.distanceTo(pointB);

    let currentPoint = pointA;
    let currentDistance = 0;
    let length;
    let finish = false;
    while (true) {
        this.moveTo(currentPoint.x, currentPoint.y);
        if (currentDistance + dash >= distance) {
            length = distance - currentDistance;
            finish = true;
        } else {
            length = dash
        }
        currentPoint = currentPoint.moveAlong(direct, length);
        this.lineTo(currentPoint.x, currentPoint.y);
        if (finish) break;
        currentDistance += length;

        if (currentDistance + gap >= distance) {
            break;
        } else {
            currentPoint = currentPoint.moveAlong(direct, gap);
            currentDistance += gap;
        }
    }
};

function drawNode(node, graphics) {
    graphics.beginFill(LANE_COLOR);
    let outline = node.outline;
    for (let i = 0 ; i < outline.length ; i+=2) {
        outline[i+1] = -outline[i+1];
        if (i == 0)
            graphics.moveTo(outline[i], outline[i+1]);
        else
            graphics.lineTo(outline[i], outline[i+1]);
    }
    graphics.endFill();

    if (debugMode) {
        graphics.hitArea = new PIXI.Polygon(outline);
        graphics.interactive = true;
        graphics.on("mouseover", function () {
            selectedDOM.innerText = node.id;
            graphics.alpha = 0.5;
        });
        graphics.on("mouseout", function () {
            graphics.alpha = 1;
        });
    }

}

function drawEdge(edge, graphics) {
    let from = edge.from;
    let to = edge.to;
    let points = edge.points;

    let pointA, pointAOffset, pointB, pointBOffset;
    let prevPointBOffset = null;

    let roadWidth = 0;
    edge.laneWidths.forEach(function(l){
        roadWidth += l;
    }, 0);

    let coords = [], coords1 = [];

    for (let i = 1;i < points.length;++i) {
        if (i == 1){
            pointA = points[0].moveAlongDirectTo(points[1], from.virtual ? 0 : from.width);
            pointAOffset = points[0].directTo(points[1]).rotate(ROTATE);
        } else {
            pointA = points[i-1];
            pointAOffset = prevPointBOffset;
        }
        if (i == points.length - 1) {
            pointB = points[i].moveAlongDirectTo(points[i-1], to.virtual ? 0 : to.width);
            pointBOffset = points[i-1].directTo(points[i]).rotate(ROTATE);
        } else {
            pointB = points[i];
            pointBOffset = points[i-1].directTo(points[i+1]).rotate(ROTATE);
        }
        prevPointBOffset = pointBOffset;

        lightG = new Graphics();
        lightG.lineStyle(TRAFFIC_LIGHT_WIDTH, 0xFFFFFF);
        lightG.drawLine(new Point(0, 0), new Point(1, 0));
        lightTexture = renderer.generateTexture(lightG);

        // Draw Traffic Lights
        if (i == points.length-1 && !to.virtual) {
            edgeTrafficLights = [];
            prevOffset = offset = 0;
            for (lane = 0;lane < edge.nLane;++lane) {
                offset += edge.laneWidths[lane];
                var light = new Sprite(lightTexture);
                light.anchor.set(0, 0.5);
                light.scale.set(offset - prevOffset, 1);
                point_ = pointB.moveAlong(pointBOffset, prevOffset);
                light.position.set(point_.x, point_.y);
                light.rotation = pointBOffset.getAngleInRadians();
                edgeTrafficLights.push(light);
                prevOffset = offset;
                trafficLightContainer.addChild(light);
            }
            trafficLightsG[edge.id] = edgeTrafficLights;
        }

        // Draw Roads
        graphics.lineStyle(LANE_BORDER_WIDTH, LANE_BORDER_COLOR, 1);
        graphics.drawLine(pointA, pointB);

        pointA1 = pointA.moveAlong(pointAOffset, roadWidth);
        pointB1 = pointB.moveAlong(pointBOffset, roadWidth);

        graphics.lineStyle(0);
        graphics.beginFill(LANE_COLOR);

        coords = coords.concat([pointA.x, pointA.y, pointB.x, pointB.y]);
        coords1 = coords1.concat([pointA1.y, pointA1.x, pointB1.y, pointB1.x]);

        graphics.drawPolygon([pointA.x, pointA.y, pointB.x, pointB.y, pointB1.x, pointB1.y, pointA1.x, pointA1.y]);
        graphics.endFill();

        offset = 0;
        for (let lane = 0, len = edge.nLane-1;lane < len;++lane) {
            offset += edge.laneWidths[lane];
            graphics.lineStyle(LANE_BORDER_WIDTH, LANE_INNER_COLOR);
            graphics.drawDashLine(pointA.moveAlong(pointAOffset, offset), pointB.moveAlong(pointBOffset, offset), LANE_DASH, LANE_GAP);
        }

        offset += edge.laneWidths[edge.nLane-1];

        // graphics.lineStyle(LANE_BORDER_WIDTH, LANE_BORDER_COLOR);
        // graphics.drawLine(pointA.moveAlong(pointAOffset, offset), pointB.moveAlong(pointBOffset, offset));
    }

    if (debugMode) {
        coords = coords.concat(coords1.reverse());
        graphics.interactive = true;
        graphics.hitArea = new PIXI.Polygon(coords);
        graphics.on("mouseover", function () {
            graphics.alpha = 0.5;
            selectedDOM.innerText = edge.id;
        });

        graphics.on("mouseout", function () {
            graphics.alpha = 1;
        });
    }
}

function run(delta) {
    let redraw = false;

    if (ready && (!controls.paused || redraw)) {
        try {
            drawStep(cnt);
        }catch (e) {
            infoAppend("Error occurred when drawing");
            ready = false;
        }
        if (!controls.paused) {
            frameElapsed += 1;
            if (frameElapsed >= 1 / controls.replaySpeed ** 2) {
                cnt += 1;
                frameElapsed = 0;
                if (cnt == totalStep) cnt = 0;
            }
        }
    }
}

function _statusToColor(status) {
    switch (status) {
        case 'r':
            return LIGHT_RED;
        case 'g':
            return LIGHT_GREEN;
        default:
            return 0x808080;  
    }
}

function stringHash(str) {
    let hash = 0;
    let p = 127, p_pow = 1;
    let m = 1e9 + 9;
    for (let i = 0; i < str.length; i++) {
        hash = (hash + str.charCodeAt(i) * p_pow) % m;
        p_pow = (p_pow * p) % m;
    }
    return hash;
}

function drawStep(step) {
    if (showChart && (step > chart.ptr || step == 0)) {
        if (step == 0) {
            chart.clear();
        }
        chart.ptr = step;
        chart.addData(chartLog[step]);
    }

    let [carLogs, tlLogs] = logs[step].split(';');

    tlLogs = tlLogs.split(',');
    carLogs = carLogs.split(',');
    
    let tlLog, tlEdge, tlStatus;
    for (let i = 0, len = tlLogs.length;i < len;++i) {
        tlLog = tlLogs[i].split(' ');
        tlEdge = tlLog[0];
        tlStatus = tlLog.slice(1);
        for (let j = 0, len = tlStatus.length;j < len;++j) {
            trafficLightsG[tlEdge][j].tint = _statusToColor(tlStatus[j]);
            if (tlStatus[j] == 'i' ) {
                trafficLightsG[tlEdge][j].alpha = 0;
            }else{
                trafficLightsG[tlEdge][j].alpha = 1;
            }
        }
    }

    carContainer.removeChildren();
    turnSignalContainer.removeChildren();
    let carLog, position, length, width;
    for (let i = 0, len = carLogs.length - 1;i < len;++i) {
        carLog = carLogs[i].split(' ');
        position = transCoord([parseFloat(carLog[0]), parseFloat(carLog[1])]);
        length = parseFloat(carLog[5]);
        width = parseFloat(carLog[6]);
        carPool[i][0].position.set(position[0], position[1]);
        carPool[i][0].rotation = 2*Math.PI - parseFloat(carLog[2]);
        carPool[i][0].name = carLog[3];
        let carColorId = stringHash(carLog[3]) % CAR_COLORS_NUM;
        carPool[i][0].tint = CAR_COLORS[carColorId];
        carPool[i][0].width = length;
        carPool[i][0].height = width;
        carContainer.addChild(carPool[i][0]);

        let laneChange = parseInt(carLog[4]) + 1;
        carPool[i][1].position.set(position[0], position[1]);
        carPool[i][1].rotation = carPool[i][0].rotation;
        carPool[i][1].texture = turnSignalTextures[laneChange];
        carPool[i][1].width = length;
        carPool[i][1].height = width;
        turnSignalContainer.addChild(carPool[i][1]);
    }
    nodeCarNum.innerText = carLogs.length-1;
    nodeTotalStep.innerText = totalStep;
    nodeCurrentStep.innerText = cnt+1;
    nodeProgressPercentage.innerText = (cnt / totalStep * 100).toFixed(2) + "%";
    if (statsFile != "") {
        if (withRange) nodeRange.value = stats[step][1];
        nodeStats.innerText = stats[step][0].toFixed(2);
    }
}

/*
Chart
 */
let chart = {
    max_steps: 3600,
    data: {
        labels: [],
        series: [[]]
    },
    options: {
        showPoint: false,
        lineSmooth: false,
        axisX: {
            showGrid: false,
            showLabel: false
        }
    },
    init : function(title, series_cnt, max_step){
        document.getElementById("chart-title").innerText = title;
        this.max_steps = max_step;
        this.data.labels = new Array(this.max_steps);
        this.data.series = [];
        for (let i = 0 ; i < series_cnt ; ++i)
            this.data.series.push([]);
        this.chart = new Chartist.Line('#chart', this.data, this.options);
    },
    addData: function (value) {
        for (let i = 0 ; i < value.length; ++i) {
            this.data.series[i].push(value[i]);
            if (this.data.series[i].length > this.max_steps) {
                this.data.series[i].shift();
            }
        }
        this.chart.update();
    },
    clear: function() {
        for (let i = 0 ; i < this.data.series.length ; ++i)
            this.data.series[i] = [];
    },
    ptr: 0
};

// Optional: Keyboard shortcut to reset overlay
document.addEventListener('keydown', function(e) {
    if (e.key === 'r' || e.key === 'R') {
        if (overlayImageSprite) {
            overlayImageSprite.position.set(0, 0);
            overlayImageSprite.scale.set(1);
        }
    }
});


// Ensure the following code runs after the DOM is fully loaded
document.addEventListener('DOMContentLoaded', function() {
    // Get references to Overlay Control elements
    const opacitySlider = document.getElementById('overlay-opacity');
    const opacityInput = document.getElementById('overlay-opacity-input'); // If added

    const scaleSlider = document.getElementById('overlay-scale');
    const scaleInput = document.getElementById('overlay-scale-input');

    const rotateSlider = document.getElementById('overlay-rotation');
    const rotateInput = document.getElementById('overlay-rotation-input');

    const decreasePosXBtn = document.getElementById('decrease-pos-x');
    const increasePosXBtn = document.getElementById('increase-pos-x');
    const decreasePosYBtn = document.getElementById('decrease-pos-y');
    const increasePosYBtn = document.getElementById('increase-pos-y');

    const posXDisplay = document.getElementById('overlay-pos-x-display');
    const posYDisplay = document.getElementById('overlay-pos-y-display');

    const resetOverlayBtn = document.getElementById('reset-overlay');
    const toggleOverlayCheckbox = document.getElementById('toggle-overlay');

    const positionStepInput = document.getElementById('position-step');
    const rotationStepInput = document.getElementById('rotation-step'); // Optional: Add a step size input for rotation

    const POSITION_STEP_DEFAULT = 5; // Default step size
    const ROTATION_STEP_DEFAULT = 15; // Default rotation step size (degrees)

    // Opacity Control
    opacitySlider.addEventListener('input', function() {
        if (overlayImageSprite) {
            overlayImageSprite.alpha = parseFloat(this.value);
            opacityInput.value = this.value;
            infoAppend(`Overlay opacity set to ${this.value}`);
        }
    });

    opacityInput.addEventListener('change', function() {
        if (overlayImageSprite) {
            let value = parseFloat(this.value);
            value = Math.min(Math.max(value, 0), 1); // Clamp between 0 and 1
            overlayImageSprite.alpha = value;
            opacitySlider.value = value;
            infoAppend(`Overlay opacity set to ${value}`);
        }
    });

    // Scale Control
    scaleSlider.addEventListener('input', function() {
        if (overlayImageSprite) {
            const scale = parseFloat(this.value);
            overlayImageSprite.scale.set(scale, scale);
            scaleInput.value = scale.toFixed(2);
            infoAppend(`Overlay scale set to ${scale.toFixed(2)}`);
        }
    });
    
    scaleInput.addEventListener('change', function() {
        if (overlayImageSprite) {
            let scale = parseFloat(this.value);
            scale = Math.min(Math.max(scale, 0.06), 0.07); // Clamp between 0.06 and 0.07
            overlayImageSprite.scale.set(scale, scale);
            scaleSlider.value = scale;
            infoAppend(`Overlay scale set to ${scale}`);
        }
    });

    // Rotation Control
    rotateSlider.addEventListener('input', function() {
        if (overlayImageSprite) {
            const degrees = parseFloat(this.value);
            const radians = degrees * (Math.PI / 180);
            overlayImageSprite.rotation = radians;
            rotateInput.value = degrees.toString();
            infoAppend(`Overlay rotation set to ${degrees} degrees`);
        }
    });

    rotateInput.addEventListener('input', function() { // Changed from 'change' to 'input'
        if (overlayImageSprite) {
            let degrees = parseFloat(this.value);
            if (isNaN(degrees)) degrees = 0;
            degrees = ((degrees % 360) + 360) % 360; // Normalize between 0-359
            const radians = degrees * (Math.PI / 180);
            overlayImageSprite.rotation = radians;
            rotateSlider.value = degrees.toString();
            infoAppend(`Overlay rotation set to ${degrees} degrees`);
        }
    });

    // Position X Controls
    decreasePosXBtn.addEventListener('click', function() {
        if (overlayImageSprite) {
            const step = parseFloat(positionStepInput.value) || POSITION_STEP_DEFAULT;
            overlayImageSprite.position.x -= step;
            posXDisplay.value = overlayImageSprite.position.x;
            infoAppend(`Overlay Position X decreased by ${step} to ${overlayImageSprite.position.x}`);
        }
    });

    increasePosXBtn.addEventListener('click', function() {
        if (overlayImageSprite) {
            const step = parseFloat(positionStepInput.value) || POSITION_STEP_DEFAULT;
            overlayImageSprite.position.x += step;
            posXDisplay.value = overlayImageSprite.position.x;
            infoAppend(`Overlay Position X increased by ${step} to ${overlayImageSprite.position.x}`);
        }
    });

    // Position Y Controls
    decreasePosYBtn.addEventListener('click', function() {
        if (overlayImageSprite) {
            const step = parseFloat(positionStepInput.value) || POSITION_STEP_DEFAULT;
            overlayImageSprite.position.y -= step;
            posYDisplay.value = overlayImageSprite.position.y;
            infoAppend(`Overlay Position Y decreased by ${step} to ${overlayImageSprite.position.y}`);
        }
    });

    increasePosYBtn.addEventListener('click', function() {
        if (overlayImageSprite) {
            const step = parseFloat(positionStepInput.value) || POSITION_STEP_DEFAULT;
            overlayImageSprite.position.y += step;
            posYDisplay.value = overlayImageSprite.position.y;
            infoAppend(`Overlay Position Y increased by ${step} to ${overlayImageSprite.position.y}`);
        }
    });

    // Rotation Step Controls (Optional)
    // If you want to allow users to define step sizes for rotation
    rotationStepInput.addEventListener('change', function() {
        if (overlayImageSprite) {
            let step = parseFloat(this.value);
            step = Math.min(Math.max(step, 1), 360); // Clamp between 1 and 360
            ROTATION_STEP_DEFAULT = step;
            infoAppend(`Rotation step size set to ${step} degrees`);
        }
    });

    // Reset Overlay Controls
    resetOverlayBtn.addEventListener('click', function() {
        if (overlayImageSprite) {
            overlayImageSprite.alpha = 1;
            overlayImageSprite.position.set(0, 0);
            overlayImageSprite.scale.set(0.06, 0.06); // Set to minimum scale
            overlayImageSprite.rotation = 0;
            
            // Reset control values
            opacitySlider.value = 1;
            opacityInput.value = 1;
            scaleSlider.value = 0.06;
            scaleInput.value = 0.06;
            rotateSlider.value = 0;
            rotateInput.value = 0;
            posXDisplay.value = 0;
            posYDisplay.value = 0;
            positionStepInput.value = 5;
            toggleOverlayCheckbox.checked = true;
            
            infoAppend("Overlay properties reset to default.");
        }
    });

    // Toggle Overlay Visibility
    toggleOverlayCheckbox.addEventListener('change', function() {
        if (overlayImageSprite) {
            overlayImageSprite.visible = this.checked;
            infoAppend(`Overlay visibility set to ${this.checked}`);
        }
    });
});

/**
 * Initializes the control inputs to reflect the current overlay image properties.
 */
function initializeOverlayControls() {
    const opacitySlider = document.getElementById('overlay-opacity');
    const opacityInput = document.getElementById('overlay-opacity-input');
    
    const scaleSlider = document.getElementById('overlay-scale');
    const scaleInput = document.getElementById('overlay-scale-input');
    
    const rotateSlider = document.getElementById('overlay-rotation');
    const rotateInput = document.getElementById('overlay-rotation-input');
    
    const posXInput = document.getElementById('overlay-pos-x-input');
    const posYInput = document.getElementById('overlay-pos-y-input');
    const posXDisplay = document.getElementById('overlay-pos-x-display');
    const posYDisplay = document.getElementById('overlay-pos-y-display');

    if (overlayImageSprite) {
        // Initialize Opacity Controls
        opacitySlider.value = overlayImageSprite.alpha.toString();
        opacityInput.value = overlayImageSprite.alpha.toString();
        
        // Initialize Scale Controls
        // Ensure the scale is within the new limits
        let currentScale = overlayImageSprite.scale.x;
        if (currentScale < 0.06 || currentScale > 0.07) {
            currentScale = 0.06; // Reset to minimum if out of bounds
            overlayImageSprite.scale.set(currentScale, currentScale);
        }
        scaleSlider.value = currentScale.toFixed(3);
        scaleInput.value = currentScale.toFixed(3);
        
        // Initialize Rotation Controls
        let degrees = (overlayImageSprite.rotation * (180 / Math.PI)).toFixed(0);
        rotateSlider.value = degrees;
        rotateInput.value = degrees;
        
        // Initialize Position Controls
        posXInput.value = overlayImageSprite.position.x.toString();
        posYInput.value = overlayImageSprite.position.y.toString();
        posXDisplay.value = overlayImageSprite.position.x.toString();
        posYDisplay.value = overlayImageSprite.position.y.toString();
    }
}"

spinner.css:"/* https://tobiasahlin.com/spinkit/ */
.spinner {
  margin: 100px auto;
  width: 50px;
  height: 40px;
  text-align: center;
  font-size: 10px;
}

.spinner > div {
  background-color: #333;
  height: 100%;
  width: 6px;
  display: inline-block;
  
  -webkit-animation: sk-stretchdelay 1.2s infinite ease-in-out;
  animation: sk-stretchdelay 1.2s infinite ease-in-out;
}

.spinner .rect2 {
  -webkit-animation-delay: -1.1s;
  animation-delay: -1.1s;
}

.spinner .rect3 {
  -webkit-animation-delay: -1.0s;
  animation-delay: -1.0s;
}

.spinner .rect4 {
  -webkit-animation-delay: -0.9s;
  animation-delay: -0.9s;
}

.spinner .rect5 {
  -webkit-animation-delay: -0.8s;
  animation-delay: -0.8s;
}

@-webkit-keyframes sk-stretchdelay {
  0%, 40%, 100% { -webkit-transform: scaleY(0.4) }  
  20% { -webkit-transform: scaleY(1.0) }
}

@keyframes sk-stretchdelay {
  0%, 40%, 100% { 
    transform: scaleY(0.4);
    -webkit-transform: scaleY(0.4);
  }  20% { 
    transform: scaleY(1.0);
    -webkit-transform: scaleY(1.0);
  }
}"

style.css:"html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    width: 100%;
    overflow: hidden;
}

.noselect {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

#simulator-canvas {
    marigin: 0;
    padding: 0;
    height: 100%;
    width: 100%;
    overflow: hidden;
}

.status-panel {
    z-index: 3;
    height: 100vh;
    overflow-y: auto;
}

#info {
    max-height: 100%;
}

#chart {
    height: 100%;
}

#chart .ct-line {
    stroke-width: 1px;
    stroke-opacity: 0.8;
}

#chart .ct-label {
    font-weight: bold;
}

#chart-container {
    background-color: rgba(216, 220, 225, 0.8);
    position: absolute;
    bottom: 0;
    right: 0;
    left: 0;
    box-sizing: border-box;
}

#chart-title {
    font-weight: bold;
    font-size: large;
    color: #6c757d;
    width: 100%;
    text-align: center;
}"

Try to only adjust index.html and scripts.js, fix all bugs and inconsistencies