JSFiddle - React, Tailwind, and code Playground

by neonDog

HTML

<div class="window-body border">

<div class="column">

<div class="borde">

<div class="map-wrapper">

<img class="ingen-logo" src="https://vignette.wikia.nocookie.net/jurassicpark/images/b/b0/Ingenicon3.png/revision/latest?cb=20141208195042">

<svg class="map" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 595.28 768.52">
  <defs>
    <style>
      .cls-1 {
        fill: #10a1c7;
      }

      .cls-2 {
        
      }

      .cls-3, .cls-4, .cls-5, .cls-6, .cls-7 {
        fill: none;
      }

      .cls-3 {
        stroke: #e8e8e8;
      }

      .cls-3, .cls-4, .cls-5, .cls-7 {
        stroke-linecap: round;
        stroke-linejoin: round;
      }

      .cls-3, .cls-4 {
        stroke-width: 3px;
      }

      .cls-4 {
        stroke: #f15a29;
      }

      .cls-5, .cls-7 {
        stroke: #fbb040;
      }

      .cls-6 {
        /*stroke: #231f20; This is the main tour path that is followed*/
      }

      .cls-7 {
        stroke-width: 2px;
      }
      
    </style>
    
    <pattern id="img1" patternUnits="userSpaceOnUse" width="550" height="700">
      <image xlink:href="https://neoncreativestudios.co.uk/island.png" x="40" y="40"
             width="598" height="726" />
    </pattern>
  </defs>
  <rect id="sea" class="cls-1" width="595.28" height="768.52"/>
  <g id="island">
    <path class="cls-2" fill="url(#img1)"...

SCSS

html, body {
  margin:0;
  padding:0;
  font-family:sans-serif;
}

*, *:before, *:after {
  box-sizing: border-box;
}

@mixin input-box-borders {
  border-bottom: 3px solid #f4f4f4;
  border-right: 3px solid #f4f4f4;
  border-left: 3px solid #848484;
  border-top: 3px solid #848484;
}

@mixin button-box-borders {
  border-top: 3px solid #f4f4f4;
  border-left: 3px solid #f4f4f4;
  border-right: 3px solid #848484;
  border-bottom: 3px solid #848484;
}

@mixin button-box-borders-dark {
  border-top: 2px solid #333;
  border-left: 2px solid #333;
  border-right: 2px solid #aaa;
  border-bottom: 2px solid #aaa;
}

@mixin button-box-borders-jp {
  border:20px solid transparent;
  border-image: url('https://neoncreativestudios.co.uk/border2.png') 24;
}

.map-wrapper {
	width: 100%;
	height: 900px;
	position: relative;
  overflow:hidden;
  background:#10a1c7;
  display:inline-block;
  vertical-align:text-top;
  
   svg {
	  position: absolute;
    transform:scale(1);
   }
}

svg text {
  fill:white;
  font-family:sans-serif;
  font-size:12px;
  
}


.window-body {
  width:100%;
  border: 1px solid gray;
  background: #727481;
  position: relative;
  padding: 1px;
  box-shadow: 2px 2px 2px #424242;
  border-top: 1px solid white;
  border-left: 1px solid white;
  border-right: 1px solid #848484;
  border-bottom: 1px solid #848484;
  //margin:5px;
}

.border {
  @include button-box-borders-jp;
  padding:10px;
  background-color: #727481;
}

.button-container {
  height:120px;
}

.button-classic {
  display:inline;
  cursor: pointer;
  padding:8px;
  margin:0;
  font-size:1.5em;
  width: 65px;
  height: 65px;
  background-color: #aaa;
  color:#222;
  @include button-box-borders;
  
  &:focus {
    outline: none;
  }
  
  &:active {
    @include input-box-borders;
  }
  
}

.vehicle-status {
  visibility:hidden;
  
  &.show {
    visibility:visible;
  }
  .body {
    z-index:10;
    width:100%;
    position:absolute;
    margin-top:40px;
    //opacity:0.7;
    bottom:0;
   ...

JavaScript

const audioCtx = new AudioContext();

class vehicle {
	
  constructor(settings={}) {
 		
    this.id = settings.id || null;
    this.type = settings.type || 'explorer'; //Vehicle type
  	this.pathCompletion = settings.pathCompletion || 0; //Percentage of route completed
    this.path = settings.path || null; //which road/route the vehicle is on
    this.pathDirection = settings.pathDirection || true; //Travelling forwards
    
    this.currentStageIndex = null; //Current path stage index
    this.nextStagePercentage = null; //When vehicle moves to the next stage
    
    this.batteryLevel = 1; //Percentage
    this.chargeStatus = false; //false|true
    this.currentZone = null;
    
    this.speed = settings.speed || 0; //Vehicle's current speed
    this.acceleration = 0.001; //Vehicle's acceleration rate
    this.deacceleration = 0.005; //Vehicle's deacceleration rate
    this.targetSpeed = 10; //The desired speed
    this.speedWaitDuration = null;
  }
  
  setSpeed(targetSpeed=null,duration=null) {
  	if (targetSpeed !== null) {
  		this.targetSpeed = targetSpeed;
    //} else if (this.currentZone && 'targetSpeed' in this.currentZone) {
    // 	this.targetSpeed = this.currentZone.targetSpeed;
    }
    this.speedWaitDuration = duration ? new Date().getTime() + (duration * 1000) : null;
  }
  
  clearPathMeta() {
     this.currentStageIndex = null;
     this.nextStagePercentage = null;
     this.currentStage = null;
     this.currentZOne = null;
  }
  
  setZone(zone) {
  	if (zone === this.currentZone) return;
  	//Vehicle has entered a new zone
  	this.currentZone = zone;
  }
  
  setPath(path,pathCompletion) {
  	if (path === this.path) return;
    this.clearPathMeta(pathCompletion);
    this.path = path;
    this.pathCompletion = pathCompletion || 0;
  }

}



class vehicleController {
	
	constructor(settings={}) {
  	
    let defaults = {
    	onVehicleAdd:function(id,data){},
      onChange:function(){}
    };
    
    this.options =...