diff --git a/database.py b/database.py
index 44e9e7aa64e4a5bd0a0f6c0964372d8af6a7bcb9..298894d9ecee62ba3794fbe03767b930ca2d7a06 100644
--- a/database.py
+++ b/database.py
@@ -1,6 +1,6 @@
 import os
 import sqlite3
-from typing import List
+from typing import List, Tuple
 
 DATABASE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "database.db")
 
@@ -50,8 +50,25 @@ with Connection() as conn:
 		url text NOT NULL,
 		workspace_id integer NOT NULL,
 		FOREIGN KEY (workspace_id) REFERENCES Workspaces(id))''')
+	conn.execute('''CREATE TABLE IF NOT EXISTS AddressToLatLong (
+		id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
+		address text NOT NULL,
+		latlong text NOT NULL)''')
 	conn.commit()
 
+def lookup_address(address: str):
+	with Connection() as conn:
+		conn.execute("SELECT latlong FROM AddressToLatLong WHERE address = ?", (address,))
+		res = conn.cursor.fetchone()[0]
+		(lat, long) = res.split(",")
+		return (float(lat), float(long))
+
+def set_address_latlong(address: str, latlong: Tuple[float, float]):
+	with Connection() as conn:
+		latlong_str = f"{ repr(latlong[0]) },{ repr(latlong[1]) }"
+		conn.execute("INSERT INTO AddressToLatLong (address, latlong) VALUES (?, ?)", (address, latlong_str))
+		conn.commit()
+
 class Workspace:
 	def __init__(self, name: str,
 		address: str, main_photo: str, additional_photos: List[str], description: str,
@@ -68,6 +85,31 @@ class Workspace:
 		self.opening_hours = opening_hours
 		self.checkin_instructions = checkin_instructions
 
+	def validate(self):
+		errors = []
+		if len(self.name.strip()) <= 0:
+			errors.append("Name must not be empty")
+		if len(self.address.strip()) <= 0:
+			errors.append("Address must not be empty")
+		if len(self.main_photo.strip()) <= 0:
+			errors.append("Main photo must not be empty")
+		if len(self.description.strip()) <= 0:
+			errors.append("Description must not be empty")
+		if len(self.website.strip()) <= 0:
+			errors.append("Website must not be empty")
+		if len(self.email.strip()) <= 0:
+			errors.append("Email must not be empty")
+		if len(self.phone_number.strip()) <= 0:
+			errors.append("Phone number must not be empty")
+		if len(self.opening_hours.strip()) <= 0:
+			errors.append("Opening hours must not be empty")
+
+		for x in self.additional_photos:
+			if len(x.strip()) <= 0:
+				errors.append("Each edditional photos must not be empty")
+
+		return errors
+
 	def from_query(conn, tuple):
 		(id, name, address, main_photo, description, website, email, phone_number, opening_hours, checkin_instructions) = tuple
 		additional_photos = []
@@ -96,5 +138,3 @@ def get_workspaces():
 	with Connection() as conn:
 		conn.execute("SELECT * FROM Workspaces")
 		return [Workspace.from_query(conn, x) for x in conn.cursor.fetchall()]
-
-print(get_workspaces())
\ No newline at end of file
diff --git a/import_database.py b/import_database.py
index deceed40a826f436a7db3a2546563523db90b263..e5a0e235ee5cc29abc3b0cf5d8d7215f7abc555f 100644
--- a/import_database.py
+++ b/import_database.py
@@ -1,14 +1,32 @@
 import database
+import os
+import json
 
-print(database.add_workspace(database.Workspace(
-	"CodeBase",
-	"CodeBase Edinburgh, 37a Castle Terrace, Edinburgh, EH1 2EL",
-	"https://images.squarespace-cdn.com/content/v1/55439320e4b0f92b5d6c4c8b/1505921023376-PAHUDHVOOKIYF4XQPHOO/5951229048_3e3d50fcb1_o.jpg?format=750w",
-	["https://images.squarespace-cdn.com/content/v1/55439320e4b0f92b5d6c4c8b/1636387943314-W9JWMS6ZX4DEZSPUV7T0/Copy+of+Unfiltered_square+%281%29.png?format=500w"],
-	"We've been exploring the world of startups...",
-	"https://www.thisiscodebase.com",
-	"hannah@thisiscodebase.com",
-	"+44 131 560 2003",
-	"Monday - Friday, 9am - 5pm",
-	"Tramsched members should contact Hannah using the email address listed."
-)))
+def import_workspace(data):
+	workspace = database.Workspace(
+		data["name"],
+		data["address"],
+		data["main_photo"],
+		data["additional_photos"],
+		data["description"],
+		data["website"],
+		data["email"],
+		data["phone_number"],
+		data["opening_hours"],
+		data["checkin_instructions"]
+	)
+
+	latlong = data["latlong"]
+	if latlong != None:
+		database.set_address_latlong(workspace.address, (latlong[0], latlong[1]))
+
+	database.add_workspace(workspace)
+
+file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "workspaces.json")
+
+with open(file, "r", encoding="utf8") as stream:
+	data = json.load(stream)
+	for workspace in data:
+		import_workspace(workspace)
+
+	print(f"Imported {len(data)} workspace(s)")
diff --git a/main.py b/main.py
index dc32f70e33060652797c6da10c685ca13c1bac44..03362b52eb47bbf559c48682ecc3126ab02c34b8 100644
--- a/main.py
+++ b/main.py
@@ -1,5 +1,7 @@
 from flask import Flask, request, render_template
 import database
+import json
+import re
 
 app = Flask(__name__)
 
@@ -9,5 +11,54 @@ def home():
 		workspaces = database.get_workspaces()
 		return render_template("home.html", workspaces = workspaces)
 
+@app.route("/map", methods = ["GET"])
+def map():
+	if request.method == "GET":
+		workspaces = database.get_workspaces()
+		data = [{"id": x.id, "name": x.name, "address": x.address, "phoneNumber": x.phone_number, "email": x.email, "website": x.website, "latlong": database.lookup_address(x.address)} for x in workspaces]
+		dumped = json.dumps(data)
+		return render_template("map.html", json = re.sub(r"(?i)\</script\>", "<\/script>", dumped)) # escape </script>
+
+@app.route("/admin/add-workspace", methods = ["GET", "POST"])
+def add_workspace():
+	if request.method == "GET":
+		return render_template("add-workspace.html")
+
+	if request.method == "POST":
+		additional_photos = request.form["additional_photos"]
+		additional_photos = [x.strip() for x in additional_photos.split("\n")] if len(additional_photos) > 0 else []
+		workspace = database.Workspace(
+			request.form["name"],
+			request.form["address"],
+			request.form["main_photo"],
+			additional_photos,
+			request.form["description"],
+			request.form["website"],
+			request.form["email"],
+			request.form["phone_number"],
+			request.form["opening_hours"],
+			request.form["checkin_instructions"]
+		)
+
+		errors = workspace.validate()
+		try:
+			latitude = float(request.form["latitude"])
+		except ValueError:
+			errors.append("Latitude must be a number")
+		try:
+			longitude = float(request.form["longitude"])
+		except ValueError:
+			errors.append("Longitude must be a number")
+
+		if len(errors) > 0:
+			messages = ["Errors were found:"]
+			messages.extend(errors)
+		else:
+			database.set_address_latlong(workspace.address, (latitude, longitude))
+			database.add_workspace(workspace)
+			messages = ["Workspace added successfully"]
+
+		return render_template("add-workspace-result.html", messages = messages)
+
 if __name__ == "__main__":
 	app.run(debug = True)
diff --git a/static/fonts/CircularXX-Bold.woff b/static/fonts/CircularXX-Bold.woff
new file mode 100644
index 0000000000000000000000000000000000000000..363c73f37720a9c6aa80c0b6523f4ceae4195d34
Binary files /dev/null and b/static/fonts/CircularXX-Bold.woff differ
diff --git a/static/fonts/CircularXX-Bold.woff2 b/static/fonts/CircularXX-Bold.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..93ece32448b60caa683680347ae2ce656a7f4b4a
Binary files /dev/null and b/static/fonts/CircularXX-Bold.woff2 differ
diff --git a/static/fonts/CircularXX-Book.woff b/static/fonts/CircularXX-Book.woff
new file mode 100644
index 0000000000000000000000000000000000000000..114a921b73659433cb184da36f82241ed3412c84
Binary files /dev/null and b/static/fonts/CircularXX-Book.woff differ
diff --git a/static/fonts/CircularXX-Book.woff2 b/static/fonts/CircularXX-Book.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..ddf961385466cbe561073b1c3f55106b1fccadf0
Binary files /dev/null and b/static/fonts/CircularXX-Book.woff2 differ
diff --git a/static/fonts/CircularXX-Medium.woff b/static/fonts/CircularXX-Medium.woff
new file mode 100644
index 0000000000000000000000000000000000000000..76031ed22d7736b5b634d33327c00c3ec2f803c9
Binary files /dev/null and b/static/fonts/CircularXX-Medium.woff differ
diff --git a/static/fonts/CircularXX-Medium.woff2 b/static/fonts/CircularXX-Medium.woff2
new file mode 100644
index 0000000000000000000000000000000000000000..c188c2464203863c5dad9f7f38d50a64b82c8dee
Binary files /dev/null and b/static/fonts/CircularXX-Medium.woff2 differ
diff --git a/static/scripts/map.js b/static/scripts/map.js
new file mode 100644
index 0000000000000000000000000000000000000000..b914908779ccf53be1a13621990d7dc31d58ebda
--- /dev/null
+++ b/static/scripts/map.js
@@ -0,0 +1,27 @@
+var map = L.map("map").setView([54.004, -2.55], 5);
+
+L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
+	attribution: "&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors"
+}).addTo(map);
+
+for (let workspace of mapData) {
+	L.marker(workspace.latlong).addTo(map)
+		.bindPopup(`<div class="popup"><div><a href="/workspace/${ encodeURIComponent(workspace.id) }" class="popup-title">${
+			escapeHTML(workspace.name)
+		}</a></div><div>Phone: <a href="tel:${ escapeHTML(workspace.phoneNumber) }">${
+			escapeHTML(workspace.phoneNumber)
+		}</a></div><div>Email: <a href="mailto:${ escapeHTML(workspace.email) }">${
+			escapeHTML(workspace.email)
+		}</a></div><div>Website: <a href="${ escapeHTML(workspace.website) }" target="_blank">${
+			escapeHTML(workspace.website)
+		}</a></div><div>${ escapeHTML(workspace.address) }</div></div>`);
+}
+
+function escapeHTML(html) {
+	let lookup = Object.create(null);
+	lookup["<"] = "&lt;"
+	lookup[">"] = "&gt;"
+	lookup["\""] = "&quot;"
+	lookup["&"] = "&amp";
+	return html.replace(/[<>"&]/g, v => lookup[v] || "");
+}
\ No newline at end of file
diff --git a/static/style.css b/static/style.css
index 640879bec84c1a5f8b9b2c04f308c0a357fdc125..099283c39bd291ca3fb9935f599ae1db27515911 100644
--- a/static/style.css
+++ b/static/style.css
@@ -1,3 +1,40 @@
+/* fonts obtained from and used by https://members.tramshedtech.co.uk/ */
+
+@font-face {
+  font-family: CircularXX;
+  src: url(/static/fonts/CircularXX-Book.woff2) format("woff2");
+  src: url(/static/fonts/CircularXX-Book.woff) format("woff");
+  font-weight: 400;
+  font-style: normal
+}
+
+@font-face {
+  font-family: CircularXX;
+  src: url(/static/fonts/CircularXX-Medium.woff2) format("woff2");
+  src: url(/static/fonts/CircularXX-Medium.woff) format("woff");
+  font-weight: 500;
+  font-style: normal
+}
+
+@font-face {
+  font-family: CircularXX;
+  src: url(/static/fonts/CircularXX-Bold.woff2) format("woff2");
+  src: url(/static/fonts/CircularXX-Bold.woff) format("woff");
+  font-weight: 700;
+  font-style: normal
+}
+
+body {
+  margin: 0;
+  font-family: CircularXX,sans-serif;
+  font-size: 1rem;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #111;
+  text-align: left;
+  background-color: #f5f5f5
+}
+
 h1
 { color: blue;}
 
@@ -50,3 +87,60 @@ nav li a:hover:last-child {
 
   background-color: #111;
 }
+
+.workspace-item {
+  background-color: #fff;
+  border-radius: 0.5rem;
+  box-shadow: 0 2px 2px 0 rgb(17 17 17 / 5%);
+  padding: 1.5rem;
+  margin: 0.75rem;
+  display: flex;
+}
+
+.workspace-item .image-col {
+  width: 200px;
+}
+
+.workspace-item .image-col .image {
+  max-width: 100%;
+}
+
+.workspace-item .main-col {
+  flex: 1 0;
+  margin-left: 1rem;
+}
+
+.workspace-item .title {
+  color: inherit;
+  display: inline-block;
+  font-size: 1.25rem;
+  margin-bottom: 1rem;
+  font-weight: 600;
+  line-height: 1.2;
+}
+
+.workspace-item .meta {
+  color: #666;
+}
+
+.workspace-item .dot {
+  color: #666;
+  font-weight: 700;
+}
+
+#map {
+  height: 600px;
+}
+
+#map .popup {
+  font-family: CircularXX,sans-serif;
+  font-size: 0.8rem;
+  font-weight: 400;
+  line-height: 1.5;
+}
+
+#map .popup-title {
+  color: inherit;
+  font-size: 1rem;
+  font-weight: 500;
+}
diff --git a/templates/add-workspace-result.html b/templates/add-workspace-result.html
new file mode 100644
index 0000000000000000000000000000000000000000..8695077fbfe71aff340eb153dffb015776bc3966
--- /dev/null
+++ b/templates/add-workspace-result.html
@@ -0,0 +1,7 @@
+{% extends "main.html" %}
+{% block title %}Add Workspace{% endblock %}
+{% block mainBlock %}
+{% for message in messages %}
+<div>{{ message }}</div>
+{% endfor %}
+{% endblock %}
\ No newline at end of file
diff --git a/templates/add-workspace.html b/templates/add-workspace.html
new file mode 100644
index 0000000000000000000000000000000000000000..160593e5fb1b5a0b39201471bf287259cf612cfb
--- /dev/null
+++ b/templates/add-workspace.html
@@ -0,0 +1,57 @@
+{% extends "main.html" %}
+{% block title %}Add Workspace{% endblock %}
+{% block mainBlock %}
+<form action="/admin/add-workspace" method="POST">
+	<table>
+		<tr>
+			<td><label for="name">Name:</label></td>
+			<td><input id="name" type="text" name="name"></td>
+		</tr>
+		<tr>
+			<td><label for="name">Address:</label></td>
+			<td><input id="address" type="text" name="address"></td>
+		</tr>
+		<tr>
+			<td><label for="main-photo">Main Photo:</label></td>
+			<td><input id="main-photo" type="text" name="main_photo"></td>
+		</tr>
+		<tr>
+			<td><label for="additional-photos">Additional Photos:</label></td>
+			<td><div><textarea id="additional-photos" name="additional_photos"></textarea></div></td>
+		</tr>
+		<tr>
+			<td><label for="description">Description:</label></td>
+			<td><div><textarea id="description" name="description"></textarea></div></td>
+		</tr>
+		<tr>
+			<td><label for="website">Website:</label></td>
+			<td><input id="website" type="text" name="website"></td>
+		</tr>
+		<tr>
+			<td><label for="email">Email:</label></td>
+			<td><input id="email" type="text" name="email"></td>
+		</tr>
+		<tr>
+			<td><label for="phone-number">Phone Number:</label></td>
+			<td><input id="phone-number" type="text" name="phone_number"></td>
+		</tr>
+		<tr>
+			<td><label for="opening-hours">Opening Hours:</label></td>
+			<td><input id="opening-hours" type="text" name="opening_hours"></td>
+		</tr>
+		<tr>
+			<td><label for="checkin-instructions">Check-in Instructions:</label></td>
+			<td><div><textarea id="checkin-instructions" name="checkin_instructions"></textarea></div></td>
+		</tr>
+		<tr>
+			<td><label for="latitude">Latitude:</label></td>
+			<td><input id="latitude" type="text" name="latitude"></td>
+		</tr>
+		<tr>
+			<td><label for="longitude">Longitude:</label></td>
+			<td><input id="longitude" type="text" name="longitude"></td>
+		</tr>
+	</table>
+	<input type="submit">
+</form>
+{% endblock %}
\ No newline at end of file
diff --git a/templates/home.html b/templates/home.html
index 9a1af7ec00074ae49b10865085a71494a7d8b8d3..d0e1565d3f9eb62d8cee21967f719837f8a44147 100644
--- a/templates/home.html
+++ b/templates/home.html
@@ -3,16 +3,23 @@
 {% block mainBlock %}
 <ul>
 	{% for workspace in workspaces %}
-	<li><a href="{{ workspace.website }}">{{ workspace.name }}</a>
-	<img src="{{ workspace.main_photo }}"></li>
-	<ul>
-		<li>{{ workspace.address }}</li>
-		<li>{{ workspace.description }}</li>
-		<li>{{ workspace.email }}</li>
-		<li>{{ workspace.phone_number }}</li>
-		<li>{{ workspace.opening_hours }}</li>
-		<li>{{ workspace.checkin_instructions }}</li>
-	</ul>
+	<div class="workspace-item">
+		<div class="image-col">
+			<a href="/workspace/{{ workspace.id }}"><img class="image" src="{{ workspace.main_photo }}"></a>
+		</div>
+		<div class="main-col">
+			<div><a href="/workspace/{{ workspace.id }}" class="title">{{ workspace.name }}</a></div>
+			<div>
+				<span class="meta"><a href="mailto:{{ workspace.email }}">{{ workspace.email }}</a></li> <span class="dot">&middot;</span>
+				<span class="meta"><a href="tel:{{ workspace.phone_number }}">{{ workspace.phone_number }}</a></span> <span class="dot">&middot;</span>
+				<span class="meta"><a href="{{ workspace.website }}" target="_blank">{{ workspace.website }}</a></span>
+			</div>
+			<div>
+				<span class="meta">{{ workspace.address }}</span> <span class="dot">&middot;</span>
+				<span class="meta">{{ workspace.opening_hours }}</span>
+			</div>
+		</div>
+	</div>
 	{% endfor %}
 </ul>
 {% endblock %}
\ No newline at end of file
diff --git a/templates/main.html b/templates/main.html
index 34903741b4d4bcc011c8909b6474a4f16a9e6add..4557b91d74188f5b7b04fecb70fa3f43c18c51cc 100644
--- a/templates/main.html
+++ b/templates/main.html
@@ -2,8 +2,6 @@
 <html lang="en">
 <head>
 
-	<link rel="stylesheet" href="static/style.css">
-
 	<!-- Bootstrap CSS -->
 	<link rel="stylesheet" href=
 "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
@@ -11,19 +9,21 @@
 "sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
 		crossorigin="anonymous">
 
+	<link rel="stylesheet" href="/static/style.css">
+
 	<title>{% block title %}{% endblock %} - Tramshed Workspaces</title>
 </head>
 
 <div class="parallax">
 <body class="body">
-  	<img src="static/Tramshed_Logo.jpg" alt="Logo" height="50" width="100">
+  	<img src="/static/Tramshed_Logo.jpg" alt="Logo" height="50" width="100">
 <!-- navigation -->
 	<nav>
 <ul>
-  <li><a href="#home">Home</a></li>
-  <li><a href="#Map">Map</a></li>
-  <li><a href="#contactUs">Contact Us</a></li>
-  <li style="float:right"><a class="active" href="#about">About</a></li>
+  <li><a href="/">Home</a></li>
+  <li><a href="/map">Map</a></li>
+  <li><a href="/contact-us">Contact Us</a></li>
+  <li style="float:right"><a class="active" href="/about">About</a></li>
 </ul>
 	</nav>
 	{% block mainBlock %}{% endblock %}
diff --git a/templates/map.html b/templates/map.html
new file mode 100644
index 0000000000000000000000000000000000000000..510eda426f53213e3922df5fec0070c33fecbb0e
--- /dev/null
+++ b/templates/map.html
@@ -0,0 +1,14 @@
+{% extends "main.html" %}
+{% block title %}Map{% endblock %}
+{% block mainBlock %}
+<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
+	integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
+	crossorigin=""/>
+<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
+	integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
+	crossorigin=""></script>
+<noscript>JavaScript must be supported and enabled in the browser to support interactive maps!</noscript>
+<script>var mapData = {{ json|safe }};</script>
+<div id="map"></div>
+<script src="/static/scripts/map.js"></script>
+{% endblock %}
\ No newline at end of file
diff --git a/workspaces.json b/workspaces.json
new file mode 100644
index 0000000000000000000000000000000000000000..2d7bd8d739332d2c6cfd3f996668e5d1b553bb4f
--- /dev/null
+++ b/workspaces.json
@@ -0,0 +1,13 @@
+[{
+	"name": "CodeBase",
+	"address": "CodeBase Edinburgh, 37a Castle Terrace, Edinburgh, EH1 2EL",
+	"main_photo": "https://images.squarespace-cdn.com/content/v1/55439320e4b0f92b5d6c4c8b/1505921023376-PAHUDHVOOKIYF4XQPHOO/5951229048_3e3d50fcb1_o.jpg?format=750w",
+	"additional_photos": ["https://images.squarespace-cdn.com/content/v1/55439320e4b0f92b5d6c4c8b/1636387943314-W9JWMS6ZX4DEZSPUV7T0/Copy+of+Unfiltered_square+%281%29.png?format=500w"],
+	"description": "We've been exploring the world of startups...",
+	"website": "https://www.thisiscodebase.com",
+	"email": "hannah@thisiscodebase.com",
+	"phone_number": "+44 131 560 2003",
+	"opening_hours": "Monday - Friday, 9am - 5pm",
+	"checkin_instructions": "Tramsched members should contact Hannah using the email address listed.",
+	"latlong": [55.9471623, -3.203928]
+}]
\ No newline at end of file