diff --git a/client/public/images/icons/arrow-pointer-solid.svg b/client/public/images/icons/arrow-pointer-solid.svg
new file mode 100644
index 00000000..4499db1b
--- /dev/null
+++ b/client/public/images/icons/arrow-pointer-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/public/images/icons/bullseye-solid.svg b/client/public/images/icons/bullseye-solid.svg
new file mode 100644
index 00000000..c6400970
--- /dev/null
+++ b/client/public/images/icons/bullseye-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/public/images/icons/grip-lines-solid.svg b/client/public/images/icons/grip-lines-solid.svg
new file mode 100644
index 00000000..85af24c3
--- /dev/null
+++ b/client/public/images/icons/grip-lines-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/public/images/icons/trash-can-regular.svg b/client/public/images/icons/trash-can-regular.svg
new file mode 100644
index 00000000..011e1a5e
--- /dev/null
+++ b/client/public/images/icons/trash-can-regular.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/atc/board/tower.ts b/client/src/atc/board/tower.ts
new file mode 100644
index 00000000..5c7a0fb4
--- /dev/null
+++ b/client/src/atc/board/tower.ts
@@ -0,0 +1,193 @@
+import { getUnitsManager } from "../..";
+import { Dropdown } from "../../controls/dropdown";
+import { ATC } from "../atc";
+import { ATCBoard } from "../atcboard";
+
+
+export class ATCBoardTower extends ATCBoard {
+
+ constructor( atc:ATC, element:HTMLElement ) {
+
+ super( atc, element );
+
+ }
+
+
+ update() {
+
+ const flights = this.sortFlights( Object.values( this.getATC().getDataHandler().getFlights( this.getBoardId() ) ) );
+ const missionTime = this.getATC().getMissionDateTime().getTime();
+ const selectableUnits = getUnitsManager().getSelectableAircraft();
+ const stripBoard = this.getStripBoardElement();
+
+ for( const strip of stripBoard.children ) {
+ strip.toggleAttribute( "data-updating", true );
+ }
+
+
+ flights.forEach( flight => {
+
+ let strip = this.getStrip( flight.id );
+
+ if ( strip.isDeleted === true ) {
+ return;
+ }
+
+ const flightData:FlightData = {
+ latitude: -1,
+ longitude: -1,
+ altitude: -1,
+ heading: -1,
+ speed: -1,
+ ...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getFlightData() : {} )
+ };
+
+ if ( !strip ) {
+
+ const template = `
+
+
+
+
000
+
-
+
+
+
-
+
+
+
`;
+
+ stripBoard.insertAdjacentHTML( "beforeend", template );
+
+
+ strip = {
+ "id": flight.id,
+ "element": stripBoard.lastElementChild,
+ "dropdowns": {},
+ "unitId": flight.unitId
+ };
+
+
+ strip.element.querySelectorAll( `input[type="text"]` ).forEach( input => {
+
+ if ( input instanceof HTMLInputElement ) {
+
+ switch ( input.name ) {
+
+ case "assignedAltitude":
+
+ input.addEventListener( "change", ( ev ) => {
+
+ let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
+
+ if ( isNaN( val ) || val < 0 || val > 40 ) {
+ val = 0;
+ }
+
+ this.updateFlight( flight.id, {
+ "assignedAltitude": val
+ });
+
+ });
+
+ break;
+
+ case "assignedSpeed":
+
+ input.addEventListener( "change", ( ev ) => {
+
+ let val = parseInt( input.value.replace( /[^\d]/g, "" ) );
+
+ if ( isNaN( val ) || val < 0 || val > 750 ) {
+ val = 0;
+ }
+
+ this.updateFlight( flight.id, {
+ "assignedSpeed": val
+ });
+
+ });
+
+ break;
+
+ }
+
+ }
+
+ });
+
+ strip.element.querySelectorAll( ".select-unit" ).forEach( el => {
+
+ el.addEventListener( "click", ev => {
+ ev.preventDefault();
+ getUnitsManager().selectUnit( flight.unitId );
+ });
+
+ });
+
+ this.addStrip( strip );
+
+ } else {
+
+ //
+ // Altitude
+ //
+
+ let assignedAltitude = strip.element.querySelector( `input[name="assignedAltitude"]`);
+
+ if ( !assignedAltitude.matches( ":focus" ) && assignedAltitude.value !== flight.assignedAltitude ) {
+ assignedAltitude.value = flight.assignedAltitude;
+ }
+
+ flightData.altitude = Math.floor( flightData.altitude / 0.3048 );
+
+ strip.element.querySelectorAll( `[data-point="altitude"]` ).forEach( el => {
+ if ( el instanceof HTMLElement ) {
+ el.innerText = "" + flightData.altitude;
+ }
+ });
+
+ const altitudeDelta = ( flight.assignedAltitude === 0 ) ? 0 : ( flight.assignedAltitude * 1000 ) - flightData.altitude;
+
+ strip.element.toggleAttribute( "data-altitude-assigned", ( flight.assignedAltitude > 0 ) );
+ strip.element.toggleAttribute( "data-warning-altitude", ( altitudeDelta >= 300 || altitudeDelta <= -300 ) );
+
+
+ //
+ // Speed
+ //
+
+ let assignedSpeed = strip.element.querySelector( `input[name="assignedSpeed"]`);
+
+ if ( !assignedSpeed.matches( ":focus" ) && assignedSpeed.value !== flight.assignedSpeed ) {
+ assignedSpeed.value = flight.assignedSpeed;
+ }
+
+ flightData.speed = Math.floor( flightData.speed * 1.94384 );
+
+ strip.element.querySelectorAll( `[data-point="speed"]` ).forEach( el => {
+ if ( el instanceof HTMLElement ) {
+ el.innerText = "" + flightData.speed;
+ }
+ });
+
+ const speedDelta = ( flight.assignedSpeed === 0 ) ? 0 : flight.assignedSpeed - flightData.speed;
+
+ strip.element.toggleAttribute( "data-speed-assigned", ( flight.assignedSpeed > 0 ) );
+ strip.element.toggleAttribute( "data-warning-speed", ( speedDelta >= 25 || speedDelta <= -25 ) );
+
+
+
+ }
+
+ strip.element.toggleAttribute( "data-updating", false );
+
+ });
+
+ stripBoard.querySelectorAll( `[data-updating]` ).forEach( strip => {
+ this.deleteStrip( strip.getAttribute( "data-flight-id" ) || "" );
+ });
+
+ }
+
+}
\ No newline at end of file
diff --git a/client/views/atc/addflight.ejs b/client/views/atc/addflight.ejs
new file mode 100644
index 00000000..73f7c9f4
--- /dev/null
+++ b/client/views/atc/addflight.ejs
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/client/views/atc/board.ejs b/client/views/atc/board.ejs
new file mode 100644
index 00000000..007b91f2
--- /dev/null
+++ b/client/views/atc/board.ejs
@@ -0,0 +1,25 @@
+
\ No newline at end of file