diff --git a/package-lock.json b/package-lock.json
index d11011cb7ad6274b62eb0a08a9dc6e9b94bb27b1..f62d6684bbbd84f7ebb2cfa68c3f50e106924ee7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,15 +14,15 @@
       }
     },
     "@babel/core": {
-      "version": "7.6.3",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.3.tgz",
-      "integrity": "sha512-QfQ5jTBgXLzJuo7Mo8bZK/ePywmgNRgk/UQykiKwEtZPiFIn8ZqE6jB+AnD1hbB1S2xQyL4//it5vuAUOVAMTw==",
+      "version": "7.6.4",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.4.tgz",
+      "integrity": "sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ==",
       "dev": true,
       "requires": {
         "@babel/code-frame": "^7.5.5",
-        "@babel/generator": "^7.6.3",
+        "@babel/generator": "^7.6.4",
         "@babel/helpers": "^7.6.2",
-        "@babel/parser": "^7.6.3",
+        "@babel/parser": "^7.6.4",
         "@babel/template": "^7.6.0",
         "@babel/traverse": "^7.6.3",
         "@babel/types": "^7.6.3",
@@ -32,7 +32,7 @@
         "lodash": "^4.17.13",
         "resolve": "^1.3.2",
         "semver": "^5.4.1",
-        "source-map": "^0.6.1"
+        "source-map": "^0.5.0"
       },
       "dependencies": {
         "debug": {
@@ -58,33 +58,19 @@
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
           "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
           "dev": true
-        },
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
         }
       }
     },
     "@babel/generator": {
-      "version": "7.6.3",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.3.tgz",
-      "integrity": "sha512-hLhYbAb3pHwxjlijC4AQ7mqZdcoujiNaW7izCT04CIowHK8psN0IN8QjDv0iyFtycF5FowUOTwDloIheI25aMw==",
+      "version": "7.6.4",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.4.tgz",
+      "integrity": "sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w==",
       "dev": true,
       "requires": {
         "@babel/types": "^7.6.3",
         "jsesc": "^2.5.1",
         "lodash": "^4.17.13",
-        "source-map": "^0.6.1"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
+        "source-map": "^0.5.0"
       }
     },
     "@babel/helper-function-name": {
@@ -176,9 +162,9 @@
       }
     },
     "@babel/parser": {
-      "version": "7.6.3",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.3.tgz",
-      "integrity": "sha512-sUZdXlva1dt2Vw2RqbMkmfoImubO0D0gaCrNngV6Hi0DA4x3o4mlrq0tbfY0dZEUIccH8I6wQ4qgEtwcpOR6Qg==",
+      "version": "7.6.4",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.4.tgz",
+      "integrity": "sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A==",
       "dev": true
     },
     "@babel/plugin-syntax-object-rest-spread": {
@@ -2676,11 +2662,6 @@
       "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
       "dev": true
     },
-    "fast-diff": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
-      "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w=="
-    },
     "fast-json-stable-stringify": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
@@ -7851,8 +7832,7 @@
     "uuid": {
       "version": "3.3.3",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
-      "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
-      "dev": true
+      "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
     },
     "v8-compile-cache": {
       "version": "2.0.3",
@@ -8202,19 +8182,16 @@
       "resolved": "https://registry.npmjs.org/y-array/-/y-array-10.1.4.tgz",
       "integrity": "sha1-4TGlsDDW3LyhAmjUKT7PRL2uezQ="
     },
+    "y-map": {
+      "version": "10.1.3",
+      "resolved": "https://registry.npmjs.org/y-map/-/y-map-10.1.3.tgz",
+      "integrity": "sha1-oVgCztusNp5Qa5b2je+PCi6DYZY="
+    },
     "y-memory": {
       "version": "8.0.9",
       "resolved": "https://registry.npmjs.org/y-memory/-/y-memory-8.0.9.tgz",
       "integrity": "sha512-OrcReh6DgZhz5R7JGXqAH53T0Ygw24qcxKj4jN9w2DIi2eIiKFCD5Y6apBTTNxiw2FaVP15F+M8phRRIMXFGBQ=="
     },
-    "y-text": {
-      "version": "9.5.1",
-      "resolved": "https://registry.npmjs.org/y-text/-/y-text-9.5.1.tgz",
-      "integrity": "sha512-uwNLY4LeLLR7Cu4xfvh9ZY4gmOYyjii4irDHYRuhPDs1XFcm6krVaALeggA42LevTQ+k5q1eHpdv5jnJha8r6g==",
-      "requires": {
-        "fast-diff": "^1.1.1"
-      }
-    },
     "y18n": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
diff --git a/package.json b/package.json
index 1c18e418eaf3809f7e449c201ee6acf2f4ce4fb1..8f4ee17620e57bc4c1c599f843ea466d9aa70e69 100644
--- a/package.json
+++ b/package.json
@@ -14,9 +14,10 @@
     "http-server": "^0.11.1",
     "peer": "git+https://github.com/peers/peerjs-server.git",
     "peerjs": "^1.1.0",
+    "uuid": "^3.3.3",
     "y-array": "^10.1.4",
+    "y-map": "^10.1.3",
     "y-memory": "^8.0.9",
-    "y-text": "^9.5.1",
     "yjs": "^12.3.3"
   },
   "devDependencies": {
diff --git a/public/index.html b/public/index.html
index 338342d8f9d5ace189dab9a6805568aa89fe193f..369b33f73eb472472a8c77aab87f8ec6866611df 100644
--- a/public/index.html
+++ b/public/index.html
@@ -2,6 +2,22 @@
 <html lang="en">
   <head>
     <meta charset="UTF-8" />
+    <link rel="manifest" href="manifest.json">
+    <link rel="shortcut icon" href="logo.png">
+    <script>
+      if (navigator.serviceWorker) {
+        navigator.serviceWorker
+          .register("service-worker.js")
+          .then(
+            (registration) =>
+              console.log(
+                `Service worker registered on scope ${registration.scope}`,
+              ),
+            (reason) =>
+              console.log(`Service worker failed to register ~ ${reason}`),
+          )
+      }
+    </script>
   </head>
   <body>
     <div>
@@ -16,52 +32,13 @@
       <ul id="connected-peers"></ul>
     </div>
 
-    <textarea style="width: 100%; height: 500px" id="textfield"></textarea>
-    <script src="js/app.js"></script>
-
-    <svg id="whiteboard" width="100%" height="100%"></svg>
-    <script>
-      var whiteboard = document.getElementById("whiteboard")
-
-      var painting = false
-      var path
-
-      whiteboard.onmousedown = function(e) {
-        painting = true
-
-        var mouse = {
-          x: e.offsetX,
-          y: e.offsetY,
-        }
-
-        path = document.createElementNS("http://www.w3.org/2000/svg", "path")
+    <svg
+      id="whiteboard"
+      width="100%"
+      height="100%"
+      style="position: fixed"
+    ></svg>
 
-        path.setAttribute("d", "M" + mouse.x + " " + mouse.y)
-        path.setAttribute("stroke", "blue")
-        path.setAttribute("stroke-width", 3)
-        path.setAttribute("fill", "none")
-        path.setAttribute("pointer-events", "none")
-
-        whiteboard.appendChild(path)
-      }
-
-      whiteboard.onmouseup = function(e) {
-        painting = false
-      }
-
-      whiteboard.onmousemove = function(e) {
-        var mouse = {
-          x: e.offsetX,
-          y: e.offsetY,
-        }
-
-        if (painting) {
-          path.setAttribute(
-            "d",
-            path.getAttribute("d") + " L" + mouse.x + " " + mouse.y,
-          )
-        }
-      }
-    </script>
+    <script src="js/app.js"></script>
   </body>
 </html>
diff --git a/public/js/.gitkeep b/public/js/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/public/logo.png b/public/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..e4266dca24a4059dcb0a7eec22db9384378d0bf3
Binary files /dev/null and b/public/logo.png differ
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 0000000000000000000000000000000000000000..1b94f0d658806221b9b2bec412b6311ba6a108a3
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,13 @@
+{
+  "short_name": "Drawing App",
+  "name": "Drawing App",
+  "icons": [
+    {
+      "src": "/logo.png",
+      "type": "image/png",
+      "sizes": "144x144"
+    }
+  ],
+  "display": "standalone",
+  "start_url": "/index.html"
+}
diff --git a/public/service-worker.js b/public/service-worker.js
new file mode 100644
index 0000000000000000000000000000000000000000..006388a3b8328861fec68f7f1d5c7d0bf1c82c9b
--- /dev/null
+++ b/public/service-worker.js
@@ -0,0 +1,66 @@
+self.addEventListener("install", (event) => {
+  console.info("Service worker installed.", event)
+})
+
+self.addEventListener("activate", (event) => {
+  console.info("Service worker activated.", event)
+})
+
+const CACHE_NAME = "APP-V0"
+const FILES_TO_CACHE = [
+  "/index.html",
+  "/js/app.js",
+  "/manifest.json",
+]
+const FILE_ALIASES = new Map([
+  ["/", "/index.html"]
+])
+
+const normalizeUrl = (url) => {
+  const url_ = new URL(url)
+  url_.pathname = url_.pathname.replace(/\/+/g, "/").replace(/\/$/, "")
+  if (FILE_ALIASES.has(url_.pathname)) {
+    url_.pathname = FILE_ALIASES.get(url_.pathname)
+  }
+  return url_.href
+}
+
+self.addEventListener("install", async (event) => {
+  const cache = await caches.open(CACHE_NAME)
+  const additions = cache.addAll(FILES_TO_CACHE)
+  await additions
+  console.info(`Files cached: [\n  ${FILES_TO_CACHE.join(`,\n  `)}\n]`)
+})
+
+self.addEventListener("activate", async (event) => {
+  const oldCacheKeys = (await caches.keys()).filter((key) => key != CACHE_NAME)
+  oldCacheKeys.forEach((key) => caches.delete(key))
+})
+
+
+self.addEventListener("fetch", (event) => {
+  const normalizedUrl = normalizeUrl(event.request.url)
+  let response = fetch(event.request)
+  if (FILES_TO_CACHE.includes(normalizedUrl)) {
+    response = response
+      .then(async (response) => {
+        const cache = await caches.open(CACHE_NAME)
+        await cache.put(normalizedUrl, response.clone())
+        return response
+      })
+      .catch(() => caches.match(normalizedUrl))
+      .catch(e => null)
+  }
+  event.respondWith(
+    fetch(event.request)
+      .then(async (response) => {
+        if (FILES_TO_CACHE.includes(new URL(normalizedUrl).pathname)) {
+          const cache = await caches.open(CACHE_NAME)
+          await cache.put(normalizedUrl, response.clone())
+        }
+        return response
+      })
+      .catch(() => caches.match(normalizedUrl))
+      .catch(e => null)
+  )
+})
diff --git a/src/app.js b/src/app.js
index 978f335c162f1d1fb4924e2729c0a488e28ee396..9d426d03ebf1db53bc478ea14c0410b074b52561 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,25 +1,25 @@
 const Y = require("yjs")
 require("y-memory")(Y)
+require("y-map")(Y)
 require("y-array")(Y)
-require("y-text")(Y)
 require("./y-webrtc")(Y)
 
+const uuidv4 = require("uuid/v4")
+
 Y({
   db: {
-    name: "memory"
+    name: "memory",
   },
   connector: {
     name: "webrtc",
     host: "localhost",
     port: 3000,
-    path: "/api"
+    path: "/api",
   },
   share: {
-    textfield: "Text"
-  }
-}).then(y => {
-  y.share.textfield.bind(document.getElementById("textfield"))
-
+    drawing: "Map",
+  },
+}).then((y) => {
   const userIDElem = document.getElementById("user-id")
   const peerIDElem = document.getElementById("peer-id")
   const peerButton = document.getElementById("peer-connect")
@@ -58,4 +58,95 @@ Y({
 
     peerIDElem.value = ""
   }
+
+  const whiteboard = document.getElementById("whiteboard")
+
+  var painting = false
+  var paths = new Map()
+  var pathID
+
+  function createOrUpdatePath(uid, points) {
+    var path = paths.get(uid)
+
+    if (path === undefined) {
+      path = document.createElementNS("http://www.w3.org/2000/svg", "path")
+
+      path.setAttribute("stroke", "blue")
+      path.setAttribute("stroke-width", 3)
+      path.setAttribute("fill", "none")
+      path.setAttribute("pointer-events", "none")
+
+      whiteboard.appendChild(path)
+
+      paths.set(uid, path)
+    }
+
+    points = points.toArray().filter((point) => point !== undefined)
+
+    if (points.length <= 0) {
+      path.removeAttribute("d")
+
+      return path
+    }
+
+    var pathString = "M" + points[0][0] + " " + points[0][1]
+
+    for (var i = 1; i < points.length; i++) {
+      pathString += " L" + points[i][0] + " " + points[i][1]
+    }
+
+    path.setAttribute("d", pathString)
+
+    return path
+  }
+
+  whiteboard.onmousedown = function(e) {
+    painting = true
+
+    const mouse = {
+      x: e.offsetX,
+      y: e.offsetY,
+    }
+
+    pathID = uuidv4()
+
+    const sharedPath = y.share.drawing.set(pathID, Y.Array)
+    sharedPath.push([[mouse.x, mouse.y]])
+  }
+
+  whiteboard.onmouseup = function() {
+    painting = false
+  }
+
+  whiteboard.onmousemove = function(e) {
+    const mouse = {
+      x: e.offsetX,
+      y: e.offsetY,
+    }
+
+    if (painting) {
+      const sharedPath = y.share.drawing.get(pathID)
+      sharedPath.push([[mouse.x, mouse.y]])
+    }
+  }
+
+  y.share.drawing.observe(function(lineEvent) {
+    const lineID = lineEvent.name
+
+    switch (lineEvent.type) {
+      case "add":
+        createOrUpdatePath(lineID, lineEvent.value)
+
+        lineEvent.value.observe(function(pointEvent) {
+          switch (pointEvent.type) {
+            case "insert":
+              console.log(pointEvent)
+              createOrUpdatePath(lineID, pointEvent.object)
+              break
+          }
+        })
+
+        break
+    }
+  })
 })
diff --git a/src/y-webrtc/index.js b/src/y-webrtc/index.js
index 0d2f040985880cf3d130ac7d038cf3a53b56654d..17e0ebaa7cea5903192828edc985a1a519371072 100644
--- a/src/y-webrtc/index.js
+++ b/src/y-webrtc/index.js
@@ -2,7 +2,7 @@
 "use strict"
 
 var {
-  peerjs: { Peer }
+  peerjs: { Peer },
 } = require("peerjs")
 
 function extend(Y) {
@@ -23,7 +23,7 @@ function extend(Y) {
       var peer = new Peer({
         host: this.webrtcOptions.host,
         port: this.webrtcOptions.port,
-        path: this.webrtcOptions.path
+        path: this.webrtcOptions.path,
       })
 
       this.peer = peer
@@ -31,7 +31,7 @@ function extend(Y) {
       this.peers = new Map()
 
       peer.on("open", function(id) {
-        console.log("My peer ID is: " + id)
+        //console.log("My peer ID is: " + id)
 
         for (var f of self.userEventListeners) {
           f({ action: "userID", id: id })
@@ -53,20 +53,20 @@ function extend(Y) {
       var self = this
 
       dataConnection.on("open", function() {
-        console.log("Connected to peer " + dataConnection.peer)
+        //console.log("Connected to peer " + dataConnection.peer)
 
         self.peers.set(dataConnection.peer, dataConnection)
         self.userJoined(dataConnection.peer, "master")
       })
 
       dataConnection.on("data", function(data) {
-        console.log("Message from peer " + dataConnection.peer + ":", data)
+        //console.log("Message from peer " + dataConnection.peer + ":", data)
 
         self.receiveMessage(dataConnection.peer, data)
       })
 
       dataConnection.on("close", function() {
-        console.log("Disconnected from peer " + dataConnection.peer)
+        //console.log("Disconnected from peer " + dataConnection.peer)
 
         self.peers.delete(dataConnection.peer)
         self.userLeft(dataConnection.peer)
@@ -87,7 +87,7 @@ function extend(Y) {
     }
 
     send(uid, message) {
-      console.log("Sending message", message, "to " + uid)
+      //console.log("Sending message", message, "to " + uid)
 
       var self = this
 
@@ -109,7 +109,7 @@ function extend(Y) {
     }
 
     broadcast(message) {
-      console.log("Broadcasting message", message)
+      //console.log("Broadcasting message", message)
 
       for (const uid of this.peers.keys()) {
         this.send(uid, message)