diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml
index 3fcdde198a..8ae0ea649d 100644
--- a/.github/workflows/build-docker.yml
+++ b/.github/workflows/build-docker.yml
@@ -6,7 +6,7 @@ on:
       - master
 
 jobs:
-  build-docker:
+  build:
     runs-on: ubuntu-latest
 
     steps:
diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml
index 92ab34f413..86936cbb2c 100644
--- a/.github/workflows/build-packages.yml
+++ b/.github/workflows/build-packages.yml
@@ -13,10 +13,10 @@ jobs:
     steps:
       - uses: actions/checkout@v1
 
-      - name: Setup Node.js 12.x
+      - name: Setup Node.js 14.x
         uses: actions/setup-node@v1
         with:
-          node-version: 12.x
+          node-version: 14.x
 
       - name: Install dependencies
         run: |
diff --git a/.github/workflows/cancel.yml b/.github/workflows/cancel.yml
index 19496c5bfd..1e2ed41b69 100644
--- a/.github/workflows/cancel.yml
+++ b/.github/workflows/cancel.yml
@@ -1,9 +1,11 @@
-name: Cancel
-on: [push]
+name: Cancel previous runs
+
+on: push
+
 jobs:
   cancel:
-    name: "Cancel Previous Runs"
     runs-on: ubuntu-latest
+
     timeout-minutes: 3
     steps:
       - uses: styfle/cancel-workflow-action@0.6.0
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 1abc8bfd7a..a11350a4e6 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,10 +1,6 @@
 name: Lint
 
-on:
-  push:
-    branches:
-      - master
-  pull_request:
+on: push
 
 jobs:
   lint:
@@ -13,10 +9,10 @@ jobs:
     steps:
       - uses: actions/checkout@v1
 
-      - name: Setup Node.js 12.x
+      - name: Setup Node.js 14.x
         uses: actions/setup-node@v1
         with:
-          node-version: 12.x
+          node-version: 14.x
 
       - name: Install and lint
         run: |
@@ -24,5 +20,3 @@ jobs:
           npm run test:other
           npm run test:code
           npm run test:typecheck
-        env:
-          CI: true
diff --git a/.github/workflows/locales-coverage.yml b/.github/workflows/locales-coverage.yml
index d391639e1b..c7b6fc799c 100644
--- a/.github/workflows/locales-coverage.yml
+++ b/.github/workflows/locales-coverage.yml
@@ -14,18 +14,18 @@ jobs:
         with:
           token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
 
-      - name: Setup Node.js 12.x
+      - name: Setup Node.js 14.x
         uses: actions/setup-node@v1
         with:
-          node-version: 12.x
+          node-version: 14.x
 
       - name: Create report file
         run: |
           npm run locales-coverage
           FILE_CHANGED=$(git diff src/locales/percentages.json)
           if [ ! -z "${FILE_CHANGED}" ]; then
-            git config --global user.name 'Kostas Bariotis'
-            git config --global user.email 'konmpar@gmail.com'
+            git config --global user.name 'Excalidraw Bot'
+            git config --global user.email 'bot@excalidraw.com'
             git add src/locales/percentages.json
             git commit -am "Auto commit: Calculate translation coverage"
             git push
@@ -43,5 +43,5 @@ jobs:
         uses: kt3k/update-pr-description@v1.0.1
         with:
           pr_body: ${{ steps.getCommentBody.outputs.body }}
-          pr_title: "chore: New Crowdin updates"
+          pr_title: "chore: Update translations from Crowdin"
           github_token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
diff --git a/.github/workflows/semantic-pr-title.yml b/.github/workflows/semantic-pr-title.yml
index be84d22944..78a2674df4 100644
--- a/.github/workflows/semantic-pr-title.yml
+++ b/.github/workflows/semantic-pr-title.yml
@@ -10,6 +10,7 @@ on:
 jobs:
   main:
     runs-on: ubuntu-latest
+
     steps:
       - uses: amannn/action-semantic-pull-request@v3.0.0
         env:
diff --git a/.github/workflows/sentry-production.yml b/.github/workflows/sentry-production.yml
index 8b97972897..7408949dbb 100644
--- a/.github/workflows/sentry-production.yml
+++ b/.github/workflows/sentry-production.yml
@@ -8,13 +8,14 @@ on:
 jobs:
   release:
     runs-on: ubuntu-latest
+
     steps:
       - uses: actions/checkout@v1.0.0
 
-      - name: Setup Node.js 12.x
+      - name: Setup Node.js 14.x
         uses: actions/setup-node@v1
         with:
-          node-version: 12.x
+          node-version: 14.x
 
       - name: Install and build
         run: |
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index fd99330d84..57434ceed1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,10 +1,6 @@
 name: Tests
 
-on:
-  push:
-    branches:
-      - master
-  pull_request:
+on: push
 
 jobs:
   test:
@@ -13,14 +9,12 @@ jobs:
     steps:
       - uses: actions/checkout@v1
 
-      - name: Setup Node.js 12.x
+      - name: Setup Node.js 14.x
         uses: actions/setup-node@v1
         with:
-          node-version: 12.x
+          node-version: 14.x
 
       - name: Install and test
         run: |
           npm ci
           npm run test:app
-        env:
-          CI: true
diff --git a/README.md b/README.md
index 148812e773..267db969cc 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
 <div align="center" style="display:flex;flex-direction:column;">
   <a href="https://excalidraw.com">
-    <img src="./public/og-image.png" alt="Excalidraw logo: Sketch handrawn like diagrams." />
+    <img width="540" src="./public/og-image-sm.png" alt="Excalidraw logo: Sketch handrawn like diagrams." />
   </a>
-  <h3>Virtual whiteboard for sketching hand-drawn like diagrams.</h3>
+  <h3>Virtual whiteboard for sketching hand-drawn like diagrams.<br>Collaborative and end to end encrypted.</h3>
   <p>
     <a href="https://twitter.com/Excalidraw">
       <img alt="Follow Excalidraw on Twitter" src="https://img.shields.io/twitter/follow/excalidraw.svg?label=follow+excalidraw&style=social&logo=twitter">
@@ -10,9 +10,6 @@
     <a target="_blank" href="https://crowdin.com/project/excalidraw">
       <img src="https://badges.crowdin.net/excalidraw/localized.svg">
     </a>
-    <a target="_blank" href="https://hub.docker.com/r/excalidraw/excalidraw">
-      <img src="https://img.shields.io/docker/pulls/excalidraw/excalidraw">
-    </a>
   </p>
 </div>
 
@@ -20,13 +17,51 @@
 
 Go to [excalidraw.com](https://excalidraw.com) to start sketching.
 
-Read our [blog](https://blog.excalidraw.com) and follow the [guides](https://howto.excalidraw.com) to learn more about Excalidraw and how to use it effectively.
+Read the latest news and updates on our [blog](https://blog.excalidraw.com). A good start is to see all the updates of [One Year of Excalidraw](https://blog.excalidraw.com/one-year-of-excalidraw/).
+
+## Documentation
+
+### Shortcuts
+
+You can almost do anything with shortcuts. Click on the help icon on the bottom right corner to see them all.
+
+### Curved lines and arrows
+
+Choose line or arrow and click click click instead of drag.
+
+### Charts
+
+You can easily create charts by copy pasting data from Excel or just plain comma separated text.
+
+### Translating
+
+To translate Excalidraw into other languages, please visit [our Crowdin page](https://crowdin.com/project/excalidraw). To add a new language, [open an issue](https://github.com/excalidraw/excalidraw/issues/new) so we can get things set up on our end first.
+
+Translations will be available on the app if they exceed a certain threshold of completion (currently 85%).
+
+### Create a collaboration session manually
+
+In order to create a session manually you just need to generate a link of this form:
+
+```
+https://excalidraw.com/#room=[0-9a-f]{20},[a-zA-Z0-9_-]{22}
+```
+
+#### Example
+
+```
+https://excalidraw.com/#room=91bd46ae3aa84dff9d20,pfLqgEoY1c2ioq8LmGwsFA
+```
+
+The first set of digits is the room. This is visible from the server that’s going to dispatch messages to everyone that knows this number.
+
+The second set of digits is the encryption key. The Excalidraw server doesn’t know about it. This is what all the participants use to encrypt/decrypt the messages.
 
 ## Shape libraries
 
 Find a growing list of libraries containing assets for your drawings at [libraries.excalidraw.com](https://libraries.excalidraw.com).
 
-## Run the code
+## Developement
 
 ### Code Sandbox
 
@@ -63,7 +98,7 @@ You can use docker-compose to work on excalidraw locally if you don't want to se
 docker-compose up --build -d
 ```
 
-## Self hosting
+### Self hosting
 
 We publish a Docker image with the Excalidraw client at [excalidraw/excalidraw](https://hub.docker.com/r/excalidraw/excalidraw). You can use it to self host your own client under your own domain, on Kubernetes, AWS ECS, etc.
 
@@ -82,45 +117,11 @@ We are working towards providing a full-fledged solution for self hosting your o
 
 Pull requests are welcome. For major changes, please [open an issue](https://github.com/excalidraw/excalidraw/issues/new) first to discuss what you would like to change.
 
-## Translating
-
-To translate Excalidraw into other languages, please visit [our Crowdin page](https://crowdin.com/project/excalidraw). To add a new language, [open an issue](https://github.com/excalidraw/excalidraw/issues/new) so we can get things set up on our end first.
-
-Translations will be available on the app if they exceed a certain threshold of completion (currently 85%).
+## Notable used tools
 
-## Excalidraw is built using these awesome tools
-
-- [React](https://reactjs.org)
+- [Create React App](https://github.com/facebook/create-react-app)
 - [Rough.js](https://roughjs.com)
 - [TypeScript](https://www.typescriptlang.org)
 - [Vercel](https://vercel.com)
 
 And the main source of inspiration for starting the project is the awesome [Zwibbler](https://zwibbler.com/demo/) app.
-
-## Testimonials
-
-<a href="https://twitter.com/Lissy_Sykes/status/1213813117177729026"><img width="398" src="https://user-images.githubusercontent.com/197597/71783813-dbf8a600-2fa0-11ea-9c0d-bb3cc45969e6.png"></a> <a href="https://twitter.com/dan_abramov/status/1213762494428262400"><img width="398" src="https://user-images.githubusercontent.com/197597/71783990-4d395880-2fa3-11ea-9ad7-186138db5003.png"></a>
-
-<a href="https://twitter.com/kyehohenberger/status/1214288572037025792"><img width="423" src="https://user-images.githubusercontent.com/197597/71851802-34f13880-308c-11ea-9416-191099e6349c.png"></a> <a href="https://twitter.com/lucasazzola/status/1215126440330416128"><img width="429" src="https://user-images.githubusercontent.com/197597/72039003-48e99580-3258-11ea-8daa-85dd055f2a82.png">
-
-<a href="https://twitter.com/jordwalke/status/1214858186789806080"><img width="434" src="https://user-images.githubusercontent.com/197597/72036874-07a1b780-3251-11ea-99e8-6bafd93483a0.png"></a>
-
-## Contributors
-
-### Code Contributors
-
-This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. <a href="https://github.com/excalidraw/excalidraw/graphs/contributors"><img src="https://opencollective.com/excalidraw/contributors.svg?width=890&button=false" /></a>
-
-### Financial Contributors
-
-Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/excalidraw/contribute)]
-
-#### Individuals
-
-<a href="https://opencollective.com/excalidraw"><img src="https://opencollective.com/excalidraw/individuals.svg?width=890"></a>
-
-#### Organizations
-
-Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/excalidraw/contribute)]
-
-<a href="https://opencollective.com/excalidraw/organization/0/website"><img src="https://opencollective.com/excalidraw/organization/0/avatar.svg"></a> <a href="https://opencollective.com/excalidraw/organization/1/website"><img src="https://opencollective.com/excalidraw/organization/1/avatar.svg"></a> <a href="https://opencollective.com/excalidraw/organization/2/website"><img src="https://opencollective.com/excalidraw/organization/2/avatar.svg"></a> <a href="https://opencollective.com/excalidraw/organization/3/website"><img src="https://opencollective.com/excalidraw/organization/3/avatar.svg"></a> <a href="https://opencollective.com/excalidraw/organization/4/website"><img src="https://opencollective.com/excalidraw/organization/4/avatar.svg"></a> <a href="https://opencollective.com/excalidraw/organization/5/website"><img src="https://opencollective.com/excalidraw/organization/5/avatar.svg"></a> <a href="https://opencollective.com/excalidraw/organization/6/website"><img src="https://opencollective.com/excalidraw/organization/6/avatar.svg"></a> <a href="https://opencollective.com/excalidraw/organization/7/website"><img src="https://opencollective.com/excalidraw/organization/7/avatar.svg"></a> <a href="https://opencollective.com/excalidraw/organization/8/website"><img src="https://opencollective.com/excalidraw/organization/8/avatar.svg"></a> <a href="https://opencollective.com/excalidraw/organization/9/website"><img src="https://opencollective.com/excalidraw/organization/9/avatar.svg"></a>
diff --git a/package-lock.json b/package-lock.json
index bee71f5ff7..2c486d4f47 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4,9 +4,9 @@
   "lockfileVersion": 1,
   "dependencies": {
     "@apidevtools/json-schema-ref-parser": {
-      "version": "9.0.6",
-      "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz",
-      "integrity": "sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==",
+      "version": "9.0.7",
+      "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.7.tgz",
+      "integrity": "sha512-QdwOGF1+eeyFh+17v2Tz626WX0nucd1iKOm6JUTUvCZdbolblCOOQCxGrQPY0f7jEhn36PiAWqZnsC2r5vmUWg==",
       "dev": true,
       "requires": {
         "@jsdevtools/ono": "^7.1.3",
@@ -1308,9 +1308,9 @@
       "integrity": "sha512-Jj2xW+8+8XPfWGkv9HPv/uR+Qrmq37NPYT352wf7MvE9LrstpLVmFg3LqG6MCRr5miLAom5sen2gZ+iOhVDeRA=="
     },
     "@firebase/app": {
-      "version": "0.6.13",
-      "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.13.tgz",
-      "integrity": "sha512-xGrJETzvCb89VYbGSHFHCW7O/y067HRxT7MGehUE1xMxdPVBDNayHnxEuKwzfGvXAjVmajXBKFlKxaCWpgSjCQ==",
+      "version": "0.6.14",
+      "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.6.14.tgz",
+      "integrity": "sha512-ZQKuiJ+fzr4tULgWoXbW+AZVTGsejOkSrlQ+zx78WiGKIubpFJLklnP3S0oYr/1nHzr4vaKuM4G8IL1Wv/+MpQ==",
       "requires": {
         "@firebase/app-types": "0.6.1",
         "@firebase/component": "0.1.21",
@@ -1334,9 +1334,9 @@
       "integrity": "sha512-L/ZnJRAq7F++utfuoTKX4CLBG5YR7tFO3PLzG1/oXXKEezJ0kRL3CMRoueBEmTCzVb/6SIs2Qlaw++uDgi5Xyg=="
     },
     "@firebase/auth": {
-      "version": "0.16.1",
-      "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.1.tgz",
-      "integrity": "sha512-7juD7D/kaxNti/xa5G+ZGJJs+bdJUWOW0MlNBtXwiG+TjMh69EDmwJnQmmc9h/32QVvXt1qo1OGWOoMMpF/2Gg==",
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-0.16.2.tgz",
+      "integrity": "sha512-68TlDL0yh3kF8PiCzI8m8RWd/bf/xCLUsdz1NZ2Dwea0sp6e2WAhu0sem1GfhwuEwL+Ns4jCdX7qbe/OQlkVEA==",
       "requires": {
         "@firebase/auth-types": "0.10.1"
       }
@@ -1368,9 +1368,9 @@
       }
     },
     "@firebase/database": {
-      "version": "0.8.2",
-      "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.8.2.tgz",
-      "integrity": "sha512-E86yrom0Ii+61UScG44y1q3H3NuozzGGTGbYmiyTe1qK8Qvzuiu7yyfdDnqFW2fkeKvTRLoDeCpgZy27FgEndQ==",
+      "version": "0.9.1",
+      "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.9.1.tgz",
+      "integrity": "sha512-JdxgNvniSZiAx+lrdAQxkCZOTv+UfdmhRm9JA4RTs4XOpvwzmRtJTAIGBn+9CWXUAkWkjt5CYHLmYysD7NGj6g==",
       "requires": {
         "@firebase/auth-interop-types": "0.1.5",
         "@firebase/component": "0.1.21",
@@ -1405,9 +1405,9 @@
       }
     },
     "@firebase/firestore": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-2.1.2.tgz",
-      "integrity": "sha512-8yUdBLLr6UhE+IjPR+fxLBD0bDnEqF9GalohfURZeLQPaL3b+LtqqGCLvvXC4MKT0lJAHOV8J9LA6rHj8vI0/Q==",
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-2.1.4.tgz",
+      "integrity": "sha512-chSOvJyVoS7HmH7YOyqQP66wMwmsYNo2nPbFkrmQM/fRGXntNxXD1Greu1uts2hNyNeDLNrFHW5y7PlE3LAbwQ==",
       "requires": {
         "@firebase/component": "0.1.21",
         "@firebase/firestore-types": "2.1.0",
@@ -1649,17 +1649,17 @@
       "dev": true
     },
     "@google-cloud/pubsub": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.7.0.tgz",
-      "integrity": "sha512-wc/XOo5Ibo3GWmuaLu80EBIhXSdu2vf99HUqBbdsSSkmRNIka2HqoIhLlOFnnncQn0lZnGL7wtKGIDLoH9LiBg==",
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-2.8.0.tgz",
+      "integrity": "sha512-AoSKAbpHCoLq6jO9vMX+K6hJhkayafan24Rs2RKHU8Y0qF6IGSm1+ly0OG12TgziHWg818/6dljWWKgwDcp8KA==",
       "dev": true,
       "requires": {
         "@google-cloud/paginator": "^3.0.0",
         "@google-cloud/precise-date": "^2.0.0",
         "@google-cloud/projectify": "^2.0.0",
         "@google-cloud/promisify": "^2.0.0",
-        "@opentelemetry/api": "^0.11.0",
-        "@opentelemetry/tracing": "^0.11.0",
+        "@opentelemetry/api": "^0.12.0",
+        "@opentelemetry/tracing": "^0.12.0",
         "@types/duplexify": "^3.6.0",
         "@types/long": "^4.0.0",
         "arrify": "^2.0.0",
@@ -2460,28 +2460,28 @@
       }
     },
     "@opentelemetry/api": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-0.11.0.tgz",
-      "integrity": "sha512-K+1ADLMxduhsXoZ0GRfi9Pw162FvzBQLDQlHru1lg86rpIU+4XqdJkSGo6y3Kg+GmOWq1HNHOA/ydw/rzHQkRg==",
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-0.12.0.tgz",
+      "integrity": "sha512-Dn4vU5GlaBrIWzLpsM6xbJwKHdlpwBQ4Bd+cL9ofJP3hKT8jBXpBpribmyaqAzrajzzl2Yt8uTa9rFVLfjDAvw==",
       "dev": true,
       "requires": {
-        "@opentelemetry/context-base": "^0.11.0"
+        "@opentelemetry/context-base": "^0.12.0"
       }
     },
     "@opentelemetry/context-base": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.11.0.tgz",
-      "integrity": "sha512-ESRk+572bftles7CVlugAj5Azrz61VO0MO0TS2pE9MLVL/zGmWuUBQryART6/nsrFqo+v9HPt37GPNcECTZR1w==",
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/@opentelemetry/context-base/-/context-base-0.12.0.tgz",
+      "integrity": "sha512-UXwSsXo3F3yZ1dIBOG9ID8v2r9e+bqLWoizCtTb8rXtwF+N5TM7hzzvQz72o3nBU+zrI/D5e+OqAYK8ZgDd3DA==",
       "dev": true
     },
     "@opentelemetry/core": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-0.11.0.tgz",
-      "integrity": "sha512-ZEKjBXeDGBqzouz0uJmrbEKNExEsQOhsZ3tJDCLcz5dUNoVw642oIn2LYWdQK2YdIfZbEmltiF65/csGsaBtFA==",
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-0.12.0.tgz",
+      "integrity": "sha512-oLZIkmTNWTJXzo1eA4dGu/S7wOVtylsgnEsCmhSJGhrJVDXm1eW/aGuNs3DVBeuxp0ZvQLAul3/PThsC3YrnzA==",
       "dev": true,
       "requires": {
-        "@opentelemetry/api": "^0.11.0",
-        "@opentelemetry/context-base": "^0.11.0",
+        "@opentelemetry/api": "^0.12.0",
+        "@opentelemetry/context-base": "^0.12.0",
         "semver": "^7.1.3"
       },
       "dependencies": {
@@ -2506,32 +2506,32 @@
       }
     },
     "@opentelemetry/resources": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-0.11.0.tgz",
-      "integrity": "sha512-o7DwV1TcezqBtS5YW2AWBcn01nVpPptIbTr966PLlVBcS//w8LkjeOShiSZxQ0lmV4b2en0FiSouSDoXk/5qIQ==",
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-0.12.0.tgz",
+      "integrity": "sha512-8cYvIKB68cyupc7D6SWzkLtt13mbjgxMahL4JKCM6hWPyiGSJlPFEAey4XFXI5LLpPZRYTPHLVoLqI/xwCFZZA==",
       "dev": true,
       "requires": {
-        "@opentelemetry/api": "^0.11.0",
-        "@opentelemetry/core": "^0.11.0"
+        "@opentelemetry/api": "^0.12.0",
+        "@opentelemetry/core": "^0.12.0"
       }
     },
     "@opentelemetry/semantic-conventions": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.11.0.tgz",
-      "integrity": "sha512-xsthnI/J+Cx0YVDGgUzvrH0ZTtfNtl866M454NarYwDrc0JvC24sYw+XS5PJyk2KDzAHtb0vlrumUc1OAut/Fw==",
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-0.12.0.tgz",
+      "integrity": "sha512-BuCcDW0uLNYYTns0/LwXkJ8lp8aDm7kpS+WunEmPAPRSCe6ciOYRvzn5reqJfX93rf+6A3U2SgrBnCTH+0qoQQ==",
       "dev": true
     },
     "@opentelemetry/tracing": {
-      "version": "0.11.0",
-      "resolved": "https://registry.npmjs.org/@opentelemetry/tracing/-/tracing-0.11.0.tgz",
-      "integrity": "sha512-QweFmxzl32BcyzwdWCNjVXZT1WeENNS/RWETq/ohqu+fAsTcMyGcr6cOq/yDdFmtBy+bm5WVVdeByEjNS+c4/w==",
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/@opentelemetry/tracing/-/tracing-0.12.0.tgz",
+      "integrity": "sha512-2TUGhTGkhgnxTciHCNAILPSeyXageJewRqfP9wOrx65sKd/jgvNYoY8nYf4EVWVMirDOxKDsmYgUkjdQrwb2dg==",
       "dev": true,
       "requires": {
-        "@opentelemetry/api": "^0.11.0",
-        "@opentelemetry/context-base": "^0.11.0",
-        "@opentelemetry/core": "^0.11.0",
-        "@opentelemetry/resources": "^0.11.0",
-        "@opentelemetry/semantic-conventions": "^0.11.0"
+        "@opentelemetry/api": "^0.12.0",
+        "@opentelemetry/context-base": "^0.12.0",
+        "@opentelemetry/core": "^0.12.0",
+        "@opentelemetry/resources": "^0.12.0",
+        "@opentelemetry/semantic-conventions": "^0.12.0"
       }
     },
     "@pmmmwh/react-refresh-webpack-plugin": {
@@ -2663,70 +2663,86 @@
       }
     },
     "@sentry/browser": {
-      "version": "5.29.2",
-      "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.29.2.tgz",
-      "integrity": "sha512-uxZ7y7rp85tJll+RZtXRhXPbnFnOaxZqJEv05vJlXBtBNLQtlczV5iCtU9mZRLVHDtmZ5VVKUV8IKXntEqqDpQ==",
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.0.3.tgz",
+      "integrity": "sha512-Ukxh83Twql4UmUgds9wPWllE62NG71cYvm5AM6daTojvM8wFR2jh7G6GiA0WYfgMb2fw6SlbevB2xb6RDG5DzQ==",
       "requires": {
-        "@sentry/core": "5.29.2",
-        "@sentry/types": "5.29.2",
-        "@sentry/utils": "5.29.2",
+        "@sentry/core": "6.0.3",
+        "@sentry/types": "6.0.3",
+        "@sentry/utils": "6.0.3",
         "tslib": "^1.9.3"
       }
     },
     "@sentry/core": {
-      "version": "5.29.2",
-      "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.29.2.tgz",
-      "integrity": "sha512-7WYkoxB5IdlNEbwOwqSU64erUKH4laavPsM0/yQ+jojM76ErxlgEF0u//p5WaLPRzh3iDSt6BH+9TL45oNZeZw==",
-      "requires": {
-        "@sentry/hub": "5.29.2",
-        "@sentry/minimal": "5.29.2",
-        "@sentry/types": "5.29.2",
-        "@sentry/utils": "5.29.2",
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.0.3.tgz",
+      "integrity": "sha512-UykB/4/98y2DkNvwTiL2ofFPuK3KDHc7rIRNsdj6dg6D+Cf7FRexgmWUUkZrpC/y+QBj0TPqkcFDcZAuQDa3Ag==",
+      "requires": {
+        "@sentry/hub": "6.0.3",
+        "@sentry/minimal": "6.0.3",
+        "@sentry/types": "6.0.3",
+        "@sentry/utils": "6.0.3",
         "tslib": "^1.9.3"
       }
     },
     "@sentry/hub": {
-      "version": "5.29.2",
-      "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.29.2.tgz",
-      "integrity": "sha512-LaAIo2hwUk9ykeh9RF0cwLy6IRw+DjEee8l1HfEaDFUM6TPGlNNGObMJNXb9/95jzWp7jWwOpQjoIE3jepdQJQ==",
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.0.3.tgz",
+      "integrity": "sha512-BfV32tE09rjTWM9W0kk8gzxUC2k1h57Z5dNWJ35na79+LguNNtCcI6fHlFQ3PkJca6ITYof9FI8iQHUfsHFZnw==",
       "requires": {
-        "@sentry/types": "5.29.2",
-        "@sentry/utils": "5.29.2",
+        "@sentry/types": "6.0.3",
+        "@sentry/utils": "6.0.3",
         "tslib": "^1.9.3"
       }
     },
     "@sentry/integrations": {
-      "version": "5.29.2",
-      "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-5.29.2.tgz",
-      "integrity": "sha512-bH50B0xubbHrJFq8xZRxOc5BgXe1PXKfC0OqQkhhSd+Bu2WDLCHcn0CEzV+8thZTYkipAoFAFJNdEWcsM2Wcew==",
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-6.0.3.tgz",
+      "integrity": "sha512-SE/rQ+ttfoC6FlHDibB4e9lV95j78YkjQ6PvYNUe+zGkGIretCJREqgaS+W3qTNYvOdbUViuiiqtdfyvW9nM2g==",
       "requires": {
-        "@sentry/types": "5.29.2",
-        "@sentry/utils": "5.29.2",
-        "localforage": "1.8.1",
+        "@sentry/types": "6.0.3",
+        "@sentry/utils": "6.0.3",
+        "localforage": "^1.8.1",
         "tslib": "^1.9.3"
+      },
+      "dependencies": {
+        "@sentry/types": {
+          "version": "6.0.3",
+          "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.0.3.tgz",
+          "integrity": "sha512-266aBQbk9AGedhG2dzXshWbn23LYLElXqlI74DLku48UrU2v7TGKdyik/8/nfOfquCoRSp0GFGYHbItwU124XQ=="
+        },
+        "@sentry/utils": {
+          "version": "6.0.3",
+          "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.0.3.tgz",
+          "integrity": "sha512-lvuBFvZHYs1zYwI8dkC8Z8ryb0aYnwPFUl1rbZiMwJpYI2Dgl1jpqqZWv9luux2rSRYOMid74uGedV708rvEgA==",
+          "requires": {
+            "@sentry/types": "6.0.3",
+            "tslib": "^1.9.3"
+          }
+        }
       }
     },
     "@sentry/minimal": {
-      "version": "5.29.2",
-      "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.29.2.tgz",
-      "integrity": "sha512-0aINSm8fGA1KyM7PavOBe1GDZDxrvnKt+oFnU0L+bTcw8Lr+of+v6Kwd97rkLRNOLw621xP076dL/7LSIzMuhw==",
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.0.3.tgz",
+      "integrity": "sha512-YsW+nw0SMyyb7UQdjZeKlZjxbGsJFpXNLh9iIp6fHKnoLTTv17YPm2ej9sOikDsQuVotaPg/xn/Qt5wySGHIxw==",
       "requires": {
-        "@sentry/hub": "5.29.2",
-        "@sentry/types": "5.29.2",
+        "@sentry/hub": "6.0.3",
+        "@sentry/types": "6.0.3",
         "tslib": "^1.9.3"
       }
     },
     "@sentry/types": {
-      "version": "5.29.2",
-      "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.29.2.tgz",
-      "integrity": "sha512-dM9wgt8wy4WRty75QkqQgrw9FV9F+BOMfmc0iaX13Qos7i6Qs2Q0dxtJ83SoR4YGtW8URaHzlDtWlGs5egBiMA=="
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.0.3.tgz",
+      "integrity": "sha512-266aBQbk9AGedhG2dzXshWbn23LYLElXqlI74DLku48UrU2v7TGKdyik/8/nfOfquCoRSp0GFGYHbItwU124XQ=="
     },
     "@sentry/utils": {
-      "version": "5.29.2",
-      "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.29.2.tgz",
-      "integrity": "sha512-nEwQIDjtFkeE4k6yIk4Ka5XjGRklNLThWLs2xfXlL7uwrYOH2B9UBBOOIRUraBm/g/Xrra3xsam/kRxuiwtXZQ==",
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.0.3.tgz",
+      "integrity": "sha512-lvuBFvZHYs1zYwI8dkC8Z8ryb0aYnwPFUl1rbZiMwJpYI2Dgl1jpqqZWv9luux2rSRYOMid74uGedV708rvEgA==",
       "requires": {
-        "@sentry/types": "5.29.2",
+        "@sentry/types": "6.0.3",
         "tslib": "^1.9.3"
       }
     },
@@ -2992,9 +3008,9 @@
       }
     },
     "@testing-library/jest-dom": {
-      "version": "5.11.8",
-      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.8.tgz",
-      "integrity": "sha512-ScyKrWQM5xNcr79PkSewnA79CLaoxVskE+f7knTOhDD9ftZSA1Jw8mj+pneqhEu3x37ncNfW84NUr7lqK+mXjA==",
+      "version": "5.11.9",
+      "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.11.9.tgz",
+      "integrity": "sha512-Mn2gnA9d1wStlAIT2NU8J15LNob0YFBVjs2aEQ3j8rsfRQo+lAs7/ui1i2TGaJjapLmuNPLTsrm+nPjmZDwpcQ==",
       "requires": {
         "@babel/runtime": "^7.9.2",
         "@types/testing-library__jest-dom": "^5.9.1",
@@ -3298,14 +3314,6 @@
       "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
       "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
     },
-    "@types/nanoid": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/@types/nanoid/-/nanoid-2.1.0.tgz",
-      "integrity": "sha512-xdkn/oRTA0GSNPLIKZgHWqDTWZsVrieKomxJBOQUK9YDD+zfSgmwD5t4WJYra5S7XyhTw7tfvwznW+pFexaepQ==",
-      "requires": {
-        "@types/node": "*"
-      }
-    },
     "@types/node": {
       "version": "13.5.1",
       "resolved": "https://registry.npmjs.org/@types/node/-/node-13.5.1.tgz",
@@ -3368,9 +3376,9 @@
       }
     },
     "@types/socket.io-client": {
-      "version": "1.4.34",
-      "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.34.tgz",
-      "integrity": "sha512-Lzia5OTQFJZJ5R4HsEEldywiiqT9+W2rDbyHJiiTGqOcju89sCsQ8aUXDljY6Ls33wKZZGC0bfMhr/VpOyjtXg=="
+      "version": "1.4.35",
+      "resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.35.tgz",
+      "integrity": "sha512-MI8YmxFS+jMkIziycT5ickBWK1sZwDwy16mgH/j99Mcom6zRG/NimNGQ3vJV0uX5G6g/hEw0FG3w3b3sT5OUGw=="
     },
     "@types/source-list-map": {
       "version": "0.1.2",
@@ -5108,10 +5116,10 @@
       "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
       "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
     },
-    "browser-nativefs": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/browser-nativefs/-/browser-nativefs-0.12.0.tgz",
-      "integrity": "sha512-ZCHJcQI6bBm9YjB+6wMT1nWg+/mnWnz7r3gJ8sx7RjgLtWROFq+BuD12cAncD6y45MIbUqFM8eMKXoHXOxSFxA=="
+    "browser-fs-access": {
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/browser-fs-access/-/browser-fs-access-0.13.0.tgz",
+      "integrity": "sha512-qP8zFVhRQThxYgBXdlFHbzIrWb1us0G5kL2ZL0vW4BO5llKE4qBAcQsQrw4KN+6vjw8sKeWaGWJtzijfRT4N0Q=="
     },
     "browser-process-hrtime": {
       "version": "1.0.0",
@@ -7940,9 +7948,9 @@
       }
     },
     "eslint-config-prettier": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz",
-      "integrity": "sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==",
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.2.0.tgz",
+      "integrity": "sha512-rV4Qu0C3nfJKPOAhFujFxB7RMP+URFyQqqOZW9DMRD7ZDTFyjaIlETU3xzHELt++4ugC0+Jm084HQYkkJe+Ivg==",
       "dev": true
     },
     "eslint-config-react-app": {
@@ -8505,9 +8513,9 @@
           }
         },
         "qs": {
-          "version": "6.9.4",
-          "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
-          "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==",
+          "version": "6.9.6",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz",
+          "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==",
           "dev": true
         },
         "semver": {
@@ -9020,16 +9028,16 @@
       }
     },
     "firebase": {
-      "version": "8.2.2",
-      "resolved": "https://registry.npmjs.org/firebase/-/firebase-8.2.2.tgz",
-      "integrity": "sha512-a07aW2TTAA9S7p4mx5pu8hvtVokJEjAQlAocHKOWwmRJRIduE9Vvr/3i50FtujT5gGNr0Qm+EyWyB+/7TJiwnw==",
+      "version": "8.2.5",
+      "resolved": "https://registry.npmjs.org/firebase/-/firebase-8.2.5.tgz",
+      "integrity": "sha512-x9KUJR8PvqLUNzNKWHjAnO7rJVgK546G0F+vjlJTNl+J/8oFTdWh8X4PvYda0z0XM68A2Y9xPGf3blz5qHCn0A==",
       "requires": {
         "@firebase/analytics": "0.6.2",
-        "@firebase/app": "0.6.13",
+        "@firebase/app": "0.6.14",
         "@firebase/app-types": "0.6.1",
-        "@firebase/auth": "0.16.1",
-        "@firebase/database": "0.8.2",
-        "@firebase/firestore": "2.1.2",
+        "@firebase/auth": "0.16.2",
+        "@firebase/database": "0.9.1",
+        "@firebase/firestore": "2.1.4",
         "@firebase/functions": "0.6.1",
         "@firebase/installations": "0.4.19",
         "@firebase/messaging": "0.7.3",
@@ -9041,9 +9049,9 @@
       }
     },
     "firebase-tools": {
-      "version": "9.1.2",
-      "resolved": "https://registry.npmjs.org/firebase-tools/-/firebase-tools-9.1.2.tgz",
-      "integrity": "sha512-YUiqMuQ+nbdCNpahSO0eyKxxVfT0nDdijkUEUplTGArkDwqdOKPIxVqHj1edq7GEPXTRWlk7zibnbOnCCHaedw==",
+      "version": "9.2.2",
+      "resolved": "https://registry.npmjs.org/firebase-tools/-/firebase-tools-9.2.2.tgz",
+      "integrity": "sha512-AFjf7S9NjEM+u8ZByJEKASxRG1g+LLg/A0CrzA3V91P92MN+8cyrCigEs7mCdtFknLaShrCgzROyo/OEwd4xdA==",
       "dev": true,
       "requires": {
         "@google-cloud/pubsub": "^2.7.0",
@@ -10146,9 +10154,9 @@
       },
       "dependencies": {
         "@types/node": {
-          "version": "13.13.39",
-          "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.39.tgz",
-          "integrity": "sha512-wct+WgRTTkBm2R3vbrFOqyZM5w0g+D8KnhstG9463CJBVC3UVZHMToge7iMBR1vDl/I+NWFHUeK9X+JcF0rWKw==",
+          "version": "13.13.40",
+          "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.40.tgz",
+          "integrity": "sha512-eKaRo87lu1yAXrzEJl0zcJxfUMDT5/mZalFyOkT44rnQps41eS2pfWzbaulSPpQLFNy29bFqn+Y5lOTL8ATlEQ==",
           "dev": true
         },
         "duplexify": {
@@ -10784,9 +10792,9 @@
       "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="
     },
     "husky": {
-      "version": "4.3.7",
-      "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.7.tgz",
-      "integrity": "sha512-0fQlcCDq/xypoyYSJvEuzbDPHFf8ZF9IXKJxlrnvxABTSzK1VPT2RKYQKrcgJ+YD39swgoB6sbzywUqFxUiqjw==",
+      "version": "4.3.8",
+      "resolved": "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz",
+      "integrity": "sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow==",
       "dev": true,
       "requires": {
         "chalk": "^4.0.0",
@@ -11515,9 +11523,9 @@
       },
       "dependencies": {
         "ip-regex": {
-          "version": "4.2.0",
-          "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.2.0.tgz",
-          "integrity": "sha512-n5cDDeTWWRwK1EBoWwRti+8nP4NbytBBY0pldmnIkq6Z55KNFmWofh4rl9dPZpj+U/nVq7gweR3ylrvMt4YZ5A==",
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz",
+          "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==",
           "dev": true
         }
       }
@@ -14254,9 +14262,9 @@
       }
     },
     "localforage": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.8.1.tgz",
-      "integrity": "sha512-azSSJJfc7h4bVpi0PGi+SmLQKJl2/8NErI+LhJsrORNikMZnhaQ7rv9fHj+ofwgSHrKRlsDCL/639a6nECIKuQ==",
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.9.0.tgz",
+      "integrity": "sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==",
       "requires": {
         "lie": "3.1.1"
       }
@@ -15263,9 +15271,9 @@
       "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
     },
     "nanoid": {
-      "version": "2.1.11",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
-      "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
+      "version": "3.1.20",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
+      "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw=="
     },
     "nanomatch": {
       "version": "1.2.13",
@@ -17653,9 +17661,9 @@
       }
     },
     "proxy-agent": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-4.0.0.tgz",
-      "integrity": "sha512-8P0Y2SkwvKjiGU1IkEfYuTteioMIDFxPL4/j49zzt5Mz3pG1KO+mIrDG1qH0PQUHTTczjwGcYl+EzfXiFj5vUQ==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-4.0.1.tgz",
+      "integrity": "sha512-ODnQnW2jc/FUVwHHuaZEfN5otg/fMbvMxz9nMSUQfJ9JU7q2SZvSULSsjLloVgJOiv9yhc8GlNMKc4GkFmcVEA==",
       "dev": true,
       "requires": {
         "agent-base": "^6.0.0",
diff --git a/package.json b/package.json
index 4c5771e7d8..3af13c1697 100644
--- a/package.json
+++ b/package.json
@@ -19,21 +19,20 @@
     ]
   },
   "dependencies": {
-    "@sentry/browser": "5.29.2",
-    "@sentry/integrations": "5.29.2",
-    "@testing-library/jest-dom": "5.11.8",
+    "@sentry/browser": "6.0.3",
+    "@sentry/integrations": "6.0.3",
+    "@testing-library/jest-dom": "5.11.9",
     "@testing-library/react": "11.2.3",
     "@types/jest": "26.0.20",
-    "@types/nanoid": "2.1.0",
     "@types/react": "17.0.0",
     "@types/react-dom": "17.0.0",
-    "@types/socket.io-client": "1.4.34",
-    "browser-nativefs": "0.12.0",
+    "@types/socket.io-client": "1.4.35",
+    "browser-fs-access": "0.13.0",
     "clsx": "1.1.1",
-    "firebase": "8.2.2",
+    "firebase": "8.2.5",
     "i18next-browser-languagedetector": "6.0.1",
     "lodash.throttle": "4.1.1",
-    "nanoid": "2.1.11",
+    "nanoid": "3.1.20",
     "node-sass": "4.14.1",
     "open-color": "1.8.0",
     "pako": "1.0.11",
@@ -52,10 +51,10 @@
   "devDependencies": {
     "@types/lodash.throttle": "4.1.6",
     "@types/pako": "1.0.1",
-    "eslint-config-prettier": "7.1.0",
+    "eslint-config-prettier": "7.2.0",
     "eslint-plugin-prettier": "3.3.1",
-    "firebase-tools": "9.1.2",
-    "husky": "4.3.7",
+    "firebase-tools": "9.2.2",
+    "husky": "4.3.8",
     "jest-canvas-mock": "2.3.0",
     "lint-staged": "10.5.3",
     "pepjs": "0.5.3",
@@ -73,7 +72,7 @@
   },
   "jest": {
     "transformIgnorePatterns": [
-      "node_modules/(?!(roughjs|points-on-curve|path-data-parser|points-on-path|browser-nativefs)/)"
+      "node_modules/(?!(roughjs|points-on-curve|path-data-parser|points-on-path|browser-fs-access)/)"
     ],
     "resetMocks": false
   },
@@ -82,7 +81,7 @@
   "scripts": {
     "build-node": "node ./scripts/build-node.js",
     "build:app:docker": "REACT_APP_DISABLE_SENTRY=true react-scripts build",
-    "build:app": "REACT_APP_GIT_SHA=$NOW_GITHUB_COMMIT_SHA react-scripts build",
+    "build:app": "REACT_APP_GIT_SHA=$VERCEL_GIT_COMMIT_SHA react-scripts build",
     "build:version": "node ./scripts/build-version.js",
     "build": "npm run build:app && npm run build:version",
     "eject": "react-scripts eject",
diff --git a/public/og-image-sm.png b/public/og-image-sm.png
new file mode 100644
index 0000000000..5e88dba059
Binary files /dev/null and b/public/og-image-sm.png differ
diff --git a/scripts/locales-coverage-description.js b/scripts/locales-coverage-description.js
index df5794409c..7eb5fe457c 100644
--- a/scripts/locales-coverage-description.js
+++ b/scripts/locales-coverage-description.js
@@ -18,6 +18,7 @@ const crowdinMap = {
   "id-ID": "en-id",
   "it-IT": "en-it",
   "ja-JP": "en-ja",
+  "kab-KAB": "en-kab",
   "ko-KR": "en-ko",
   "my-MM": "en-my",
   "nb-NO": "en-nb",
@@ -40,7 +41,7 @@ const crowdinMap = {
 const flags = {
   "ar-SA": "🇸🇦",
   "bg-BG": "🇧🇬",
-  "ca-ES": "🇪🇸",
+  "ca-ES": "🏳",
   "de-DE": "🇩🇪",
   "el-GR": "🇬🇷",
   "es-ES": "🇪🇸",
@@ -53,6 +54,7 @@ const flags = {
   "id-ID": "🇮🇩",
   "it-IT": "🇮🇹",
   "ja-JP": "🇯🇵",
+  "kab-KAB": "🏳",
   "ko-KR": "🇰🇷",
   "my-MM": "🇲🇲",
   "nb-NO": "🇳🇴",
@@ -88,6 +90,7 @@ const languages = {
   "id-ID": "Bahasa Indonesia",
   "it-IT": "Italiano",
   "ja-JP": "日本語",
+  "kab-KAB": "Taqbaylit",
   "ko-KR": "한국어",
   "my-MM": "Burmese",
   "nb-NO": "Norsk bokmål",
diff --git a/src/actions/actionAddToLibrary.ts b/src/actions/actionAddToLibrary.ts
index a0abbf5ca1..8fb7eac931 100644
--- a/src/actions/actionAddToLibrary.ts
+++ b/src/actions/actionAddToLibrary.ts
@@ -17,6 +17,5 @@ export const actionAddToLibrary = register({
     });
     return false;
   },
-  contextMenuOrder: 6,
   contextItemLabel: "labels.addToLibrary",
 });
diff --git a/src/actions/actionCanvas.tsx b/src/actions/actionCanvas.tsx
index 872874a5ba..866f1ce291 100644
--- a/src/actions/actionCanvas.tsx
+++ b/src/actions/actionCanvas.tsx
@@ -3,7 +3,7 @@ import { getDefaultAppState } from "../appState";
 import { ColorPicker } from "../components/ColorPicker";
 import { resetZoom, trash, zoomIn, zoomOut } from "../components/icons";
 import { ToolButton } from "../components/ToolButton";
-import { GRID_SIZE } from "../constants";
+import { GRID_SIZE, ZOOM_STEP } from "../constants";
 import { getCommonBounds, getNonDeletedElements } from "../element";
 import { newElementWith } from "../element/mutateElement";
 import { ExcalidrawElement } from "../element/types";
@@ -76,8 +76,6 @@ export const actionClearCanvas = register({
   ),
 });
 
-const ZOOM_STEP = 0.1;
-
 export const actionZoomIn = register({
   name: "zoomIn",
   perform: (_elements, appState) => {
diff --git a/src/actions/actionClipboard.tsx b/src/actions/actionClipboard.tsx
new file mode 100644
index 0000000000..70e4af17d5
--- /dev/null
+++ b/src/actions/actionClipboard.tsx
@@ -0,0 +1,114 @@
+import { CODES, KEYS } from "../keys";
+import { register } from "./register";
+import { copyToClipboard } from "../clipboard";
+import { actionDeleteSelected } from "./actionDeleteSelected";
+import { getSelectedElements } from "../scene/selection";
+import { exportCanvas } from "../data/index";
+import { getNonDeletedElements } from "../element";
+import { t } from "../i18n";
+
+export const actionCopy = register({
+  name: "copy",
+  perform: (elements, appState) => {
+    copyToClipboard(getNonDeletedElements(elements), appState);
+
+    return {
+      commitToHistory: false,
+    };
+  },
+  contextItemLabel: "labels.copy",
+  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.C,
+});
+
+export const actionCut = register({
+  name: "cut",
+  perform: (elements, appState, data, app) => {
+    actionCopy.perform(elements, appState, data, app);
+    return actionDeleteSelected.perform(elements, appState, data, app);
+  },
+  contextItemLabel: "labels.cut",
+  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.X,
+});
+
+export const actionCopyAsSvg = register({
+  name: "copyAsSvg",
+  perform: async (elements, appState, _data, app) => {
+    if (!app.canvas) {
+      return {
+        commitToHistory: false,
+      };
+    }
+    const selectedElements = getSelectedElements(
+      getNonDeletedElements(elements),
+      appState,
+    );
+    try {
+      await exportCanvas(
+        "clipboard-svg",
+        selectedElements.length
+          ? selectedElements
+          : getNonDeletedElements(elements),
+        appState,
+        app.canvas,
+        appState,
+      );
+      return {
+        commitToHistory: false,
+      };
+    } catch (error) {
+      console.error(error);
+      return {
+        appState: {
+          ...appState,
+          errorMessage: error.message,
+        },
+        commitToHistory: false,
+      };
+    }
+  },
+  contextItemLabel: "labels.copyAsSvg",
+});
+
+export const actionCopyAsPng = register({
+  name: "copyAsPng",
+  perform: async (elements, appState, _data, app) => {
+    if (!app.canvas) {
+      return {
+        commitToHistory: false,
+      };
+    }
+    const selectedElements = getSelectedElements(
+      getNonDeletedElements(elements),
+      appState,
+    );
+    try {
+      await exportCanvas(
+        "clipboard",
+        selectedElements.length
+          ? selectedElements
+          : getNonDeletedElements(elements),
+        appState,
+        app.canvas,
+        appState,
+      );
+      return {
+        appState: {
+          ...appState,
+          toastMessage: t("toast.copyToClipboardAsPng"),
+        },
+        commitToHistory: false,
+      };
+    } catch (error) {
+      console.error(error);
+      return {
+        appState: {
+          ...appState,
+          errorMessage: error.message,
+        },
+        commitToHistory: false,
+      };
+    }
+  },
+  contextItemLabel: "labels.copyAsPng",
+  keyTest: (event) => event.code === CODES.C && event.altKey && event.shiftKey,
+});
diff --git a/src/actions/actionDeleteSelected.tsx b/src/actions/actionDeleteSelected.tsx
index 88e0145035..dd2ad42cd3 100644
--- a/src/actions/actionDeleteSelected.tsx
+++ b/src/actions/actionDeleteSelected.tsx
@@ -136,7 +136,6 @@ export const actionDeleteSelected = register({
     };
   },
   contextItemLabel: "labels.delete",
-  contextMenuOrder: 999999,
   keyTest: (event) => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE,
   PanelComponent: ({ elements, appState, updateData }) => (
     <ToolButton
diff --git a/src/actions/actionGroup.tsx b/src/actions/actionGroup.tsx
index b8d839e595..a5d8e226e5 100644
--- a/src/actions/actionGroup.tsx
+++ b/src/actions/actionGroup.tsx
@@ -125,7 +125,6 @@ export const actionGroup = register({
       commitToHistory: true,
     };
   },
-  contextMenuOrder: 4,
   contextItemLabel: "labels.group",
   contextItemPredicate: (elements, appState) =>
     enableActionGroup(elements, appState),
@@ -174,7 +173,6 @@ export const actionUngroup = register({
   },
   keyTest: (event) =>
     event.shiftKey && event[KEYS.CTRL_OR_CMD] && event.code === CODES.G,
-  contextMenuOrder: 5,
   contextItemLabel: "labels.ungroup",
   contextItemPredicate: (elements, appState) =>
     getSelectedGroupIds(appState).length > 0,
diff --git a/src/actions/actionHistory.tsx b/src/actions/actionHistory.tsx
index 9e3f62321b..c0971c2ef0 100644
--- a/src/actions/actionHistory.tsx
+++ b/src/actions/actionHistory.tsx
@@ -6,7 +6,7 @@ import { t } from "../i18n";
 import { SceneHistory, HistoryEntry } from "../history";
 import { ExcalidrawElement } from "../element/types";
 import { AppState } from "../types";
-import { KEYS } from "../keys";
+import { isWindows, KEYS } from "../keys";
 import { getElementMap } from "../element";
 import { newElementWith } from "../element/mutateElement";
 import { fixBindingsAfterDeletion } from "../element/binding";
@@ -59,16 +59,16 @@ const writeData = (
   return { commitToHistory };
 };
 
-const testUndo = (shift: boolean) => (event: KeyboardEvent) =>
-  event[KEYS.CTRL_OR_CMD] && /z/i.test(event.key) && event.shiftKey === shift;
-
 type ActionCreator = (history: SceneHistory) => Action;
 
 export const createUndoAction: ActionCreator = (history) => ({
   name: "undo",
   perform: (elements, appState) =>
     writeData(elements, appState, () => history.undoOnce()),
-  keyTest: testUndo(false),
+  keyTest: (event) =>
+    event[KEYS.CTRL_OR_CMD] &&
+    event.key.toLowerCase() === KEYS.Z &&
+    !event.shiftKey,
   PanelComponent: ({ updateData }) => (
     <ToolButton
       type="button"
@@ -84,7 +84,11 @@ export const createRedoAction: ActionCreator = (history) => ({
   name: "redo",
   perform: (elements, appState) =>
     writeData(elements, appState, () => history.redoOnce()),
-  keyTest: testUndo(true),
+  keyTest: (event) =>
+    (event[KEYS.CTRL_OR_CMD] &&
+      event.shiftKey &&
+      event.key.toLowerCase() === KEYS.Z) ||
+    (isWindows && event.ctrlKey && !event.shiftKey && event.key === KEYS.Y),
   PanelComponent: ({ updateData }) => (
     <ToolButton
       type="button"
diff --git a/src/actions/actionMenu.tsx b/src/actions/actionMenu.tsx
index 6ae29e5194..8cfae39814 100644
--- a/src/actions/actionMenu.tsx
+++ b/src/actions/actionMenu.tsx
@@ -74,13 +74,13 @@ export const actionShortcuts = register({
     return {
       appState: {
         ...appState,
-        showShortcutsDialog: true,
+        showHelpDialog: !appState.showHelpDialog,
       },
       commitToHistory: false,
     };
   },
   PanelComponent: ({ updateData }) => (
-    <HelpIcon title={t("shortcutsDialog.title")} onClick={updateData} />
+    <HelpIcon title={t("helpDialog.title")} onClick={updateData} />
   ),
   keyTest: (event) => event.key === KEYS.QUESTION_MARK,
 });
diff --git a/src/actions/actionStyles.ts b/src/actions/actionStyles.ts
index 991f62b953..4a8b54afc1 100644
--- a/src/actions/actionStyles.ts
+++ b/src/actions/actionStyles.ts
@@ -4,6 +4,7 @@ import {
   redrawTextBoundingBox,
 } from "../element";
 import { CODES, KEYS } from "../keys";
+import { t } from "../i18n";
 import { register } from "./register";
 import { mutateElement, newElementWith } from "../element/mutateElement";
 import {
@@ -23,13 +24,16 @@ export const actionCopyStyles = register({
       copiedStyles = JSON.stringify(element);
     }
     return {
+      appState: {
+        ...appState,
+        toastMessage: t("toast.copyStyles"),
+      },
       commitToHistory: false,
     };
   },
   contextItemLabel: "labels.copyStyles",
   keyTest: (event) =>
     event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.C,
-  contextMenuOrder: 0,
 });
 
 export const actionPasteStyles = register({
@@ -69,5 +73,4 @@ export const actionPasteStyles = register({
   contextItemLabel: "labels.pasteStyles",
   keyTest: (event) =>
     event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V,
-  contextMenuOrder: 1,
 });
diff --git a/src/actions/actionToggleGridMode.tsx b/src/actions/actionToggleGridMode.tsx
new file mode 100644
index 0000000000..152a9da9aa
--- /dev/null
+++ b/src/actions/actionToggleGridMode.tsx
@@ -0,0 +1,22 @@
+import { CODES, KEYS } from "../keys";
+import { register } from "./register";
+import { GRID_SIZE } from "../constants";
+import { AppState } from "../types";
+import { trackEvent } from "../analytics";
+
+export const actionToggleGridMode = register({
+  name: "gridMode",
+  perform(elements, appState) {
+    trackEvent("view", "mode", "grid");
+    return {
+      appState: {
+        ...appState,
+        gridSize: this.checked!(appState) ? null : GRID_SIZE,
+      },
+      commitToHistory: false,
+    };
+  },
+  checked: (appState: AppState) => appState.gridSize !== null,
+  contextItemLabel: "labels.gridMode",
+  keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE,
+});
diff --git a/src/actions/actionToggleStats.tsx b/src/actions/actionToggleStats.tsx
new file mode 100644
index 0000000000..3c03b80c63
--- /dev/null
+++ b/src/actions/actionToggleStats.tsx
@@ -0,0 +1,16 @@
+import { register } from "./register";
+
+export const actionToggleStats = register({
+  name: "stats",
+  perform(elements, appState) {
+    return {
+      appState: {
+        ...appState,
+        showStats: !this.checked!(appState),
+      },
+      commitToHistory: false,
+    };
+  },
+  checked: (appState) => appState.showStats,
+  contextItemLabel: "stats.title",
+});
diff --git a/src/actions/actionToggleViewMode.tsx b/src/actions/actionToggleViewMode.tsx
new file mode 100644
index 0000000000..0808a5d539
--- /dev/null
+++ b/src/actions/actionToggleViewMode.tsx
@@ -0,0 +1,22 @@
+import { CODES, KEYS } from "../keys";
+import { register } from "./register";
+import { trackEvent } from "../analytics";
+
+export const actionToggleViewMode = register({
+  name: "viewMode",
+  perform(elements, appState) {
+    trackEvent("view", "mode", "view");
+    return {
+      appState: {
+        ...appState,
+        viewModeEnabled: !this.checked!(appState),
+        selectedElementIds: {},
+      },
+      commitToHistory: false,
+    };
+  },
+  checked: (appState) => appState.viewModeEnabled,
+  contextItemLabel: "labels.viewMode",
+  keyTest: (event) =>
+    !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.R,
+});
diff --git a/src/actions/actionToggleZenMode.tsx b/src/actions/actionToggleZenMode.tsx
new file mode 100644
index 0000000000..5ff494ea28
--- /dev/null
+++ b/src/actions/actionToggleZenMode.tsx
@@ -0,0 +1,22 @@
+import { CODES, KEYS } from "../keys";
+import { register } from "./register";
+import { trackEvent } from "../analytics";
+
+export const actionToggleZenMode = register({
+  name: "zenMode",
+  perform(elements, appState) {
+    trackEvent("view", "mode", "zen");
+
+    return {
+      appState: {
+        ...appState,
+        zenModeEnabled: !this.checked!(appState),
+      },
+      commitToHistory: false,
+    };
+  },
+  checked: (appState) => appState.zenModeEnabled,
+  contextItemLabel: "buttons.zenMode",
+  keyTest: (event) =>
+    !event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.Z,
+});
diff --git a/src/actions/index.ts b/src/actions/index.ts
index c5c4444828..b335e44e6d 100644
--- a/src/actions/index.ts
+++ b/src/actions/index.ts
@@ -65,3 +65,15 @@ export {
   distributeHorizontally,
   distributeVertically,
 } from "./actionDistribute";
+
+export {
+  actionCopy,
+  actionCut,
+  actionCopyAsPng,
+  actionCopyAsSvg,
+} from "./actionClipboard";
+
+export { actionToggleGridMode } from "./actionToggleGridMode";
+export { actionToggleZenMode } from "./actionToggleZenMode";
+
+export { actionToggleStats } from "./actionToggleStats";
diff --git a/src/actions/manager.tsx b/src/actions/manager.tsx
index 236a14414a..71eccf14f8 100644
--- a/src/actions/manager.tsx
+++ b/src/actions/manager.tsx
@@ -3,14 +3,15 @@ import {
   Action,
   ActionsManagerInterface,
   UpdaterFn,
-  ActionFilterFn,
   ActionName,
   ActionResult,
 } from "./types";
 import { ExcalidrawElement } from "../element/types";
-import { AppState } from "../types";
-import { t } from "../i18n";
-import { ShortcutName } from "./shortcuts";
+import { AppState, ExcalidrawProps } from "../types";
+
+// This is the <App> component, but for now we don't care about anything but its
+// `canvas` state.
+type App = { canvas: HTMLCanvasElement | null; props: ExcalidrawProps };
 
 export class ActionManager implements ActionsManagerInterface {
   actions = {} as ActionsManagerInterface["actions"];
@@ -18,13 +19,14 @@ export class ActionManager implements ActionsManagerInterface {
   updater: (actionResult: ActionResult | Promise<ActionResult>) => void;
 
   getAppState: () => Readonly<AppState>;
-
   getElementsIncludingDeleted: () => readonly ExcalidrawElement[];
+  app: App;
 
   constructor(
     updater: UpdaterFn,
     getAppState: () => AppState,
     getElementsIncludingDeleted: () => readonly ExcalidrawElement[],
+    app: App,
   ) {
     this.updater = (actionResult) => {
       if (actionResult && "then" in actionResult) {
@@ -37,6 +39,7 @@ export class ActionManager implements ActionsManagerInterface {
     };
     this.getAppState = getAppState;
     this.getElementsIncludingDeleted = getElementsIncludingDeleted;
+    this.app = app;
   }
 
   registerAction(action: Action) {
@@ -63,6 +66,12 @@ export class ActionManager implements ActionsManagerInterface {
     if (data.length === 0) {
       return false;
     }
+    const { viewModeEnabled } = this.getAppState();
+    if (viewModeEnabled) {
+      if (data[0].name !== "viewMode") {
+        return false;
+      }
+    }
 
     event.preventDefault();
     this.updater(
@@ -70,6 +79,7 @@ export class ActionManager implements ActionsManagerInterface {
         this.getElementsIncludingDeleted(),
         this.getAppState(),
         null,
+        this.app,
       ),
     );
     return true;
@@ -81,43 +91,11 @@ export class ActionManager implements ActionsManagerInterface {
         this.getElementsIncludingDeleted(),
         this.getAppState(),
         null,
+        this.app,
       ),
     );
   }
 
-  getContextMenuItems(actionFilter: ActionFilterFn = (action) => action) {
-    return Object.values(this.actions)
-      .filter(actionFilter)
-      .filter((action) => "contextItemLabel" in action)
-      .filter((action) =>
-        action.contextItemPredicate
-          ? action.contextItemPredicate(
-              this.getElementsIncludingDeleted(),
-              this.getAppState(),
-            )
-          : true,
-      )
-      .sort(
-        (a, b) =>
-          (a.contextMenuOrder !== undefined ? a.contextMenuOrder : 999) -
-          (b.contextMenuOrder !== undefined ? b.contextMenuOrder : 999),
-      )
-      .map((action) => ({
-        // take last bit of the label  "labels.<shortcutName>"
-        shortcutName: action.contextItemLabel?.split(".").pop() as ShortcutName,
-        label: action.contextItemLabel ? t(action.contextItemLabel) : "",
-        action: () => {
-          this.updater(
-            action.perform(
-              this.getElementsIncludingDeleted(),
-              this.getAppState(),
-              null,
-            ),
-          );
-        },
-      }));
-  }
-
   // Id is an attribute that we can use to pass in data like keys.
   // This is needed for dynamically generated action components
   // like the user list. We can use this key to extract more
@@ -132,6 +110,7 @@ export class ActionManager implements ActionsManagerInterface {
             this.getElementsIncludingDeleted(),
             this.getAppState(),
             formState,
+            this.app,
           ),
         );
       };
diff --git a/src/actions/shortcuts.ts b/src/actions/shortcuts.ts
index e2fcf595ac..4c9bc60c4f 100644
--- a/src/actions/shortcuts.ts
+++ b/src/actions/shortcuts.ts
@@ -9,7 +9,7 @@ export type ShortcutName =
   | "copyStyles"
   | "pasteStyles"
   | "selectAll"
-  | "delete"
+  | "deleteSelectedElements"
   | "duplicateSelection"
   | "sendBackward"
   | "bringForward"
@@ -22,7 +22,8 @@ export type ShortcutName =
   | "gridMode"
   | "zenMode"
   | "stats"
-  | "addToLibrary";
+  | "addToLibrary"
+  | "viewMode";
 
 const shortcutMap: Record<ShortcutName, string[]> = {
   cut: [getShortcutKey("CtrlOrCmd+X")],
@@ -31,10 +32,10 @@ const shortcutMap: Record<ShortcutName, string[]> = {
   copyStyles: [getShortcutKey("CtrlOrCmd+Alt+C")],
   pasteStyles: [getShortcutKey("CtrlOrCmd+Alt+V")],
   selectAll: [getShortcutKey("CtrlOrCmd+A")],
-  delete: [getShortcutKey("Del")],
+  deleteSelectedElements: [getShortcutKey("Del")],
   duplicateSelection: [
     getShortcutKey("CtrlOrCmd+D"),
-    getShortcutKey(`Alt+${t("shortcutsDialog.drag")}`),
+    getShortcutKey(`Alt+${t("helpDialog.drag")}`),
   ],
   sendBackward: [getShortcutKey("CtrlOrCmd+[")],
   bringForward: [getShortcutKey("CtrlOrCmd+]")],
@@ -56,6 +57,7 @@ const shortcutMap: Record<ShortcutName, string[]> = {
   zenMode: [getShortcutKey("Alt+Z")],
   stats: [],
   addToLibrary: [],
+  viewMode: [getShortcutKey("Alt+R")],
 };
 
 export const getShortcutFromShortcutName = (name: ShortcutName) => {
diff --git a/src/actions/types.ts b/src/actions/types.ts
index ca30f56781..9d90efd883 100644
--- a/src/actions/types.ts
+++ b/src/actions/types.ts
@@ -16,12 +16,18 @@ type ActionFn = (
   elements: readonly ExcalidrawElement[],
   appState: Readonly<AppState>,
   formData: any,
+  app: { canvas: HTMLCanvasElement | null },
 ) => ActionResult | Promise<ActionResult>;
 
 export type UpdaterFn = (res: ActionResult) => void;
 export type ActionFilterFn = (action: Action) => void;
 
 export type ActionName =
+  | "copy"
+  | "cut"
+  | "paste"
+  | "copyAsPng"
+  | "copyAsSvg"
   | "sendBackward"
   | "bringForward"
   | "sendToBack"
@@ -29,6 +35,9 @@ export type ActionName =
   | "copyStyles"
   | "selectAll"
   | "pasteStyles"
+  | "gridMode"
+  | "zenMode"
+  | "stats"
   | "changeStrokeColor"
   | "changeBackgroundColor"
   | "changeFillStyle"
@@ -75,7 +84,8 @@ export type ActionName =
   | "alignVerticallyCentered"
   | "alignHorizontallyCentered"
   | "distributeHorizontally"
-  | "distributeVertically";
+  | "distributeVertically"
+  | "viewMode";
 
 export interface Action {
   name: ActionName;
@@ -93,19 +103,16 @@ export interface Action {
     elements: readonly ExcalidrawElement[],
   ) => boolean;
   contextItemLabel?: string;
-  contextMenuOrder?: number;
   contextItemPredicate?: (
     elements: readonly ExcalidrawElement[],
     appState: AppState,
   ) => boolean;
+  checked?: (appState: Readonly<AppState>) => boolean;
 }
 
 export interface ActionsManagerInterface {
   actions: Record<ActionName, Action>;
   registerAction: (action: Action) => void;
   handleKeyDown: (event: KeyboardEvent) => boolean;
-  getContextMenuItems: (
-    actionFilter: ActionFilterFn,
-  ) => { label: string; action: () => void }[];
   renderAction: (name: ActionName) => React.ReactElement | null;
 }
diff --git a/src/analytics.ts b/src/analytics.ts
index 30a3887e3b..a48a0a1f44 100644
--- a/src/analytics.ts
+++ b/src/analytics.ts
@@ -1,5 +1,6 @@
 export const trackEvent =
-  process.env.REACT_APP_GOOGLE_ANALYTICS_ID &&
+  typeof process !== "undefined" &&
+  process.env?.REACT_APP_GOOGLE_ANALYTICS_ID &&
   typeof window !== "undefined" &&
   window.gtag
     ? (category: string, name: string, label?: string, value?: number) => {
@@ -9,7 +10,7 @@ export const trackEvent =
           value,
         });
       }
-    : typeof process !== "undefined" && process?.env?.JEST_WORKER_ID
+    : typeof process !== "undefined" && process.env?.JEST_WORKER_ID
     ? (category: string, name: string, label?: string, value?: number) => {}
     : (category: string, name: string, label?: string, value?: number) => {
         // Uncomment the next line to track locally
diff --git a/src/appState.ts b/src/appState.ts
index 676bb21e15..77662730f4 100644
--- a/src/appState.ts
+++ b/src/appState.ts
@@ -6,7 +6,7 @@ import {
   GRID_SIZE,
 } from "./constants";
 import { t } from "./i18n";
-import { AppState, FlooredNumber, NormalizedZoomValue } from "./types";
+import { AppState, NormalizedZoomValue } from "./types";
 import { getDateTime } from "./utils";
 
 export const getDefaultAppState = (): Omit<
@@ -57,22 +57,24 @@ export const getDefaultAppState = (): Omit<
     previousSelectedElementIds: {},
     resizingElement: null,
     scrolledOutside: false,
-    scrollX: 0 as FlooredNumber,
-    scrollY: 0 as FlooredNumber,
+    scrollX: 0,
+    scrollY: 0,
     selectedElementIds: {},
     selectedGroupIds: {},
     selectionElement: null,
     shouldAddWatermark: false,
     shouldCacheIgnoreZoom: false,
     showGrid: false,
-    showShortcutsDialog: false,
+    showHelpDialog: false,
     showStats: false,
     startBoundElement: null,
     suggestedBindings: [],
+    toastMessage: null,
     viewBackgroundColor: oc.white,
     width: window.innerWidth,
     zenModeEnabled: false,
     zoom: { value: 1 as NormalizedZoomValue, translation: { x: 0, y: 0 } },
+    viewModeEnabled: false,
   };
 };
 
@@ -144,14 +146,16 @@ const APP_STATE_STORAGE_CONF = (<
   selectionElement: { browser: false, export: false },
   shouldAddWatermark: { browser: true, export: false },
   shouldCacheIgnoreZoom: { browser: true, export: false },
-  showShortcutsDialog: { browser: false, export: false },
+  showHelpDialog: { browser: false, export: false },
   showStats: { browser: true, export: false },
   startBoundElement: { browser: false, export: false },
   suggestedBindings: { browser: false, export: false },
+  toastMessage: { browser: false, export: false },
   viewBackgroundColor: { browser: true, export: true },
   width: { browser: false, export: false },
   zenModeEnabled: { browser: true, export: false },
   zoom: { browser: true, export: false },
+  viewModeEnabled: { browser: false, export: false },
 });
 
 const _clearAppStateForStorage = <ExportType extends "export" | "browser">(
diff --git a/src/charts.ts b/src/charts.ts
index 3b2bbb38bc..f36a076e47 100644
--- a/src/charts.ts
+++ b/src/charts.ts
@@ -1,4 +1,3 @@
-import { trackEvent } from "./analytics";
 import colors from "./colors";
 import { DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, ENV } from "./constants";
 import { newElement, newLinearElement, newTextElement } from "./element";
@@ -473,7 +472,6 @@ export const renderSpreadsheet = (
   x: number,
   y: number,
 ): ChartElements => {
-  trackEvent("magic", "chart", chartType, spreadsheet.values.length);
   if (chartType === "line") {
     return chartTypeLine(spreadsheet, x, y);
   }
diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx
index 177fd61c2b..2798caccd6 100644
--- a/src/components/Actions.tsx
+++ b/src/components/Actions.tsx
@@ -163,9 +163,9 @@ export const ShapesSwitcher = ({
     {SHAPES.map(({ value, icon, key }, index) => {
       const label = t(`toolBar.${value}`);
       const letter = typeof key === "string" ? key : key[0];
-      const shortcut = `${capitalizeString(letter)} ${t(
-        "shortcutsDialog.or",
-      )} ${index + 1}`;
+      const shortcut = `${capitalizeString(letter)} ${t("helpDialog.or")} ${
+        index + 1
+      }`;
       return (
         <ToolButton
           className="Shape"
diff --git a/src/components/App.tsx b/src/components/App.tsx
index acc08fb059..801c050790 100644
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -2,8 +2,31 @@ import { Point, simplify } from "points-on-curve";
 import React from "react";
 import { RoughCanvas } from "roughjs/bin/canvas";
 import rough from "roughjs/bin/rough";
+import clsx from "clsx";
+
 import "../actions";
-import { actionDeleteSelected, actionFinalize } from "../actions";
+import {
+  actionAddToLibrary,
+  actionBringForward,
+  actionBringToFront,
+  actionCopy,
+  actionCopyAsPng,
+  actionCopyAsSvg,
+  actionCopyStyles,
+  actionCut,
+  actionDeleteSelected,
+  actionDuplicateSelection,
+  actionFinalize,
+  actionGroup,
+  actionPasteStyles,
+  actionSelectAll,
+  actionSendBackward,
+  actionSendToBack,
+  actionToggleGridMode,
+  actionToggleStats,
+  actionToggleZenMode,
+  actionUngroup,
+} from "../actions";
 import { createRedoAction, createUndoAction } from "../actions/actionHistory";
 import { ActionManager } from "../actions/manager";
 import { actions } from "../actions/register";
@@ -18,7 +41,6 @@ import {
 } from "../clipboard";
 import {
   APP_NAME,
-  CANVAS_ONLY_ACTIONS,
   CURSOR_TYPE,
   DEFAULT_VERTICAL_ALIGN,
   DRAGGING_THRESHOLD,
@@ -32,8 +54,9 @@ import {
   TAP_TWICE_TIMEOUT,
   TEXT_TO_CENTER_SNAP_THRESHOLD,
   TOUCH_CTX_MENU_TIMEOUT,
+  ZOOM_STEP,
 } from "../constants";
-import { exportCanvas, loadFromBlob } from "../data";
+import { loadFromBlob } from "../data";
 import { isValidLibrary } from "../data/json";
 import { Library } from "../data/library";
 import { restore } from "../data/restore";
@@ -126,7 +149,6 @@ import {
   getSelectedElements,
   isOverScrollBars,
   isSomeElementSelected,
-  normalizeScroll,
 } from "../scene";
 import Scene from "../scene/Scene";
 import { SceneState, ScrollBars } from "../scene/types";
@@ -154,9 +176,12 @@ import {
   viewportCoordsToSceneCoords,
   withBatchedUpdates,
 } from "../utils";
-import ContextMenu from "./ContextMenu";
+import { isMobile } from "../is-mobile";
+import ContextMenu, { ContextMenuOption } from "./ContextMenu";
 import LayerUI from "./LayerUI";
 import { Stats } from "./Stats";
+import { Toast } from "./Toast";
+import { actionToggleViewMode } from "../actions/actionToggleViewMode";
 
 const { history } = createHistory();
 
@@ -246,6 +271,7 @@ export type ExcalidrawImperativeAPI = {
   };
   setScrollToCenter: InstanceType<typeof App>["setScrollToCenter"];
   getSceneElements: InstanceType<typeof App>["getSceneElements"];
+  getAppState: () => InstanceType<typeof App>["state"];
   readyPromise: ResolvablePromise<ExcalidrawImperativeAPI>;
   ready: true;
 };
@@ -272,6 +298,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       offsetLeft,
       offsetTop,
       excalidrawRef,
+      viewModeEnabled = false,
     } = props;
     this.state = {
       ...defaultAppState,
@@ -279,6 +306,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       width,
       height,
       ...this.getCanvasOffsets({ offsetLeft, offsetTop }),
+      viewModeEnabled,
     };
     if (excalidrawRef) {
       const readyPromise =
@@ -296,6 +324,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         },
         setScrollToCenter: this.setScrollToCenter,
         getSceneElements: this.getSceneElements,
+        getAppState: () => this.state,
       } as const;
       if (typeof excalidrawRef === "function") {
         excalidrawRef(api);
@@ -310,6 +339,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       this.syncActionResult,
       () => this.state,
       () => this.scene.getElementsIncludingDeleted(),
+      this,
     );
     this.actionManager.registerAll(actions);
 
@@ -317,6 +347,62 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     this.actionManager.registerAction(createRedoAction(history));
   }
 
+  private renderCanvas() {
+    const canvasScale = window.devicePixelRatio;
+    const {
+      width: canvasDOMWidth,
+      height: canvasDOMHeight,
+      viewModeEnabled,
+    } = this.state;
+    const canvasWidth = canvasDOMWidth * canvasScale;
+    const canvasHeight = canvasDOMHeight * canvasScale;
+    if (viewModeEnabled) {
+      return (
+        <canvas
+          id="canvas"
+          style={{
+            width: canvasDOMWidth,
+            height: canvasDOMHeight,
+            cursor: "grabbing",
+          }}
+          width={canvasWidth}
+          height={canvasHeight}
+          ref={this.handleCanvasRef}
+          onContextMenu={this.handleCanvasContextMenu}
+          onPointerMove={this.handleCanvasPointerMove}
+          onPointerUp={this.removePointer}
+          onPointerCancel={this.removePointer}
+          onTouchMove={this.handleTouchMove}
+          onPointerDown={this.handleCanvasPointerDown}
+        >
+          {t("labels.drawingCanvas")}
+        </canvas>
+      );
+    }
+    return (
+      <canvas
+        id="canvas"
+        style={{
+          width: canvasDOMWidth,
+          height: canvasDOMHeight,
+        }}
+        width={canvasWidth}
+        height={canvasHeight}
+        ref={this.handleCanvasRef}
+        onContextMenu={this.handleCanvasContextMenu}
+        onPointerDown={this.handleCanvasPointerDown}
+        onDoubleClick={this.handleCanvasDoubleClick}
+        onPointerMove={this.handleCanvasPointerMove}
+        onPointerUp={this.removePointer}
+        onPointerCancel={this.removePointer}
+        onTouchMove={this.handleTouchMove}
+        onDrop={this.handleCanvasOnDrop}
+      >
+        {t("labels.drawingCanvas")}
+      </canvas>
+    );
+  }
+
   public render() {
     const {
       zenModeEnabled,
@@ -324,20 +410,19 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       height: canvasDOMHeight,
       offsetTop,
       offsetLeft,
+      viewModeEnabled,
     } = this.state;
 
     const { onCollabButtonClick, onExportToBackend, renderFooter } = this.props;
-    const canvasScale = window.devicePixelRatio;
-
-    const canvasWidth = canvasDOMWidth * canvasScale;
-    const canvasHeight = canvasDOMHeight * canvasScale;
 
     const DEFAULT_PASTE_X = canvasDOMWidth / 2;
     const DEFAULT_PASTE_Y = canvasDOMHeight / 2;
 
     return (
       <div
-        className="excalidraw"
+        className={clsx("excalidraw", {
+          "excalidraw--view-mode": viewModeEnabled,
+        })}
         ref={this.excalidrawContainerRef}
         style={{
           width: canvasDOMWidth,
@@ -367,6 +452,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
           isCollaborating={this.props.isCollaborating || false}
           onExportToBackend={onExportToBackend}
           renderCustomFooter={renderFooter}
+          viewModeEnabled={viewModeEnabled}
         />
         {this.state.showStats && (
           <Stats
@@ -376,28 +462,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
             onClose={this.toggleStats}
           />
         )}
-        <main>
-          <canvas
-            id="canvas"
-            style={{
-              width: canvasDOMWidth,
-              height: canvasDOMHeight,
-            }}
-            width={canvasWidth}
-            height={canvasHeight}
-            ref={this.handleCanvasRef}
-            onContextMenu={this.handleCanvasContextMenu}
-            onPointerDown={this.handleCanvasPointerDown}
-            onDoubleClick={this.handleCanvasDoubleClick}
-            onPointerMove={this.handleCanvasPointerMove}
-            onPointerUp={this.removePointer}
-            onPointerCancel={this.removePointer}
-            onTouchMove={this.handleTouchMove}
-            onDrop={this.handleCanvasOnDrop}
-          >
-            {t("labels.drawingCanvas")}
-          </canvas>
-        </main>
+        {this.state.toastMessage !== null && (
+          <Toast
+            message={this.state.toastMessage}
+            clearToast={this.clearToast}
+          />
+        )}
+        <main>{this.renderCanvas()}</main>
       </div>
     );
   }
@@ -437,6 +508,13 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         if (actionResult.commitToHistory) {
           history.resumeRecording();
         }
+
+        let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false;
+
+        if (typeof this.props.viewModeEnabled !== "undefined") {
+          viewModeEnabled = this.props.viewModeEnabled;
+        }
+
         this.setState(
           (state) => ({
             ...actionResult.appState,
@@ -446,6 +524,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
             height: state.height,
             offsetTop: state.offsetTop,
             offsetLeft: state.offsetLeft,
+            viewModeEnabled,
           }),
           () => {
             if (actionResult.syncHistory) {
@@ -628,7 +707,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     }
 
     this.scene.addCallback(this.onSceneUpdated);
-
     this.addEventListeners();
 
     // optim to avoid extra render on init
@@ -695,25 +773,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
   }
 
   private addEventListeners() {
+    this.removeEventListeners();
     document.addEventListener(EVENT.COPY, this.onCopy);
-    document.addEventListener(EVENT.PASTE, this.pasteFromClipboard);
-    document.addEventListener(EVENT.CUT, this.onCut);
-
     document.addEventListener(EVENT.KEYDOWN, this.onKeyDown, false);
     document.addEventListener(EVENT.KEYUP, this.onKeyUp, { passive: true });
     document.addEventListener(
       EVENT.MOUSE_MOVE,
       this.updateCurrentCursorPosition,
     );
-    window.addEventListener(EVENT.RESIZE, this.onResize, false);
-    window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
-    window.addEventListener(EVENT.BLUR, this.onBlur, false);
-    window.addEventListener(EVENT.DRAG_OVER, this.disableEvent, false);
-    window.addEventListener(EVENT.DROP, this.disableEvent, false);
-
     // rerender text elements on font load to fix #637 && #1553
     document.fonts?.addEventListener?.("loadingdone", this.onFontLoaded);
-
     // Safari-only desktop pinch zoom
     document.addEventListener(
       EVENT.GESTURE_START,
@@ -730,6 +799,18 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       this.onGestureEnd as any,
       false,
     );
+    if (this.state.viewModeEnabled) {
+      return;
+    }
+
+    document.addEventListener(EVENT.PASTE, this.pasteFromClipboard);
+    document.addEventListener(EVENT.CUT, this.onCut);
+
+    window.addEventListener(EVENT.RESIZE, this.onResize, false);
+    window.addEventListener(EVENT.UNLOAD, this.onUnload, false);
+    window.addEventListener(EVENT.BLUR, this.onBlur, false);
+    window.addEventListener(EVENT.DRAG_OVER, this.disableEvent, false);
+    window.addEventListener(EVENT.DROP, this.disableEvent, false);
   }
 
   componentDidUpdate(prevProps: ExcalidrawProps, prevState: AppState) {
@@ -752,6 +833,17 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       });
     }
 
+    if (prevProps.viewModeEnabled !== this.props.viewModeEnabled) {
+      this.setState(
+        { viewModeEnabled: !!this.props.viewModeEnabled },
+        this.addEventListeners,
+      );
+    }
+
+    if (prevState.viewModeEnabled !== this.state.viewModeEnabled) {
+      this.addEventListeners();
+    }
+
     document
       .querySelector(".excalidraw")
       ?.classList.toggle("Appearance_dark", this.state.appearance === "dark");
@@ -899,43 +991,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     copyToClipboard(this.scene.getElements(), this.state);
   };
 
-  private copyToClipboardAsPng = async () => {
-    const elements = this.scene.getElements();
-
-    const selectedElements = getSelectedElements(elements, this.state);
-    try {
-      await exportCanvas(
-        "clipboard",
-        selectedElements.length ? selectedElements : elements,
-        this.state,
-        this.canvas!,
-        this.state,
-      );
-    } catch (error) {
-      console.error(error);
-      this.setState({ errorMessage: error.message });
-    }
-  };
-
-  private copyToClipboardAsSvg = async () => {
-    const selectedElements = getSelectedElements(
-      this.scene.getElements(),
-      this.state,
-    );
-    try {
-      await exportCanvas(
-        "clipboard-svg",
-        selectedElements.length ? selectedElements : this.scene.getElements(),
-        this.state,
-        this.canvas!,
-        this.state,
-      );
-    } catch (error) {
-      console.error(error);
-      this.setState({ errorMessage: error.message });
-    }
-  };
-
   private static resetTapTwice() {
     didTapTwice = false;
   }
@@ -1143,9 +1198,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
   };
 
   toggleZenMode = () => {
-    this.setState({
-      zenModeEnabled: !this.state.zenModeEnabled,
-    });
+    this.actionManager.executeAction(actionToggleZenMode);
   };
 
   toggleGridMode = () => {
@@ -1158,9 +1211,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     if (!this.state.showStats) {
       trackEvent("dialog", "stats");
     }
-    this.setState({
-      showStats: !this.state.showStats,
-    });
+    this.actionManager.executeAction(actionToggleStats);
   };
 
   setScrollToCenter = (remoteElements: readonly ExcalidrawElement[]) => {
@@ -1173,6 +1224,10 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     });
   };
 
+  clearToast = () => {
+    this.setState({ toastMessage: null });
+  };
+
   public updateScene = withBatchedUpdates((sceneData: SceneData) => {
     if (sceneData.commitToHistory) {
       history.resumeRecording();
@@ -1242,29 +1297,20 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 
     if (event.key === KEYS.QUESTION_MARK) {
       this.setState({
-        showShortcutsDialog: true,
+        showHelpDialog: true,
       });
     }
 
-    if (!event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.Z) {
-      this.toggleZenMode();
-    }
-
-    if (event[KEYS.CTRL_OR_CMD] && event.code === CODES.QUOTE) {
-      this.toggleGridMode();
-    }
-    if (event[KEYS.CTRL_OR_CMD]) {
-      this.setState({ isBindingEnabled: false });
+    if (this.actionManager.handleKeyDown(event)) {
+      return;
     }
 
-    if (event.code === CODES.C && event.altKey && event.shiftKey) {
-      this.copyToClipboardAsPng();
-      event.preventDefault();
+    if (this.state.viewModeEnabled) {
       return;
     }
 
-    if (this.actionManager.handleKeyDown(event)) {
-      return;
+    if (event[KEYS.CTRL_OR_CMD]) {
+      this.setState({ isBindingEnabled: false });
     }
 
     if (event.code === CODES.NINE) {
@@ -1771,8 +1817,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       const scaleFactor = distance / gesture.initialDistance;
 
       this.setState(({ zoom, scrollX, scrollY, offsetLeft, offsetTop }) => ({
-        scrollX: normalizeScroll(scrollX + deltaX / zoom.value),
-        scrollY: normalizeScroll(scrollY + deltaY / zoom.value),
+        scrollX: scrollX + deltaX / zoom.value,
+        scrollY: scrollY + deltaY / zoom.value,
         zoom: getNewZoom(
           getNormalizedZoom(initialScale * scaleFactor),
           zoom,
@@ -2074,14 +2120,16 @@ class App extends React.Component<ExcalidrawProps, AppState> {
 
     lastPointerUp = onPointerUp;
 
-    window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
-    window.addEventListener(EVENT.POINTER_UP, onPointerUp);
-    window.addEventListener(EVENT.KEYDOWN, onKeyDown);
-    window.addEventListener(EVENT.KEYUP, onKeyUp);
-    pointerDownState.eventListeners.onMove = onPointerMove;
-    pointerDownState.eventListeners.onUp = onPointerUp;
-    pointerDownState.eventListeners.onKeyUp = onKeyUp;
-    pointerDownState.eventListeners.onKeyDown = onKeyDown;
+    if (!this.state.viewModeEnabled) {
+      window.addEventListener(EVENT.POINTER_MOVE, onPointerMove);
+      window.addEventListener(EVENT.POINTER_UP, onPointerUp);
+      window.addEventListener(EVENT.KEYDOWN, onKeyDown);
+      window.addEventListener(EVENT.KEYUP, onKeyUp);
+      pointerDownState.eventListeners.onMove = onPointerMove;
+      pointerDownState.eventListeners.onUp = onPointerUp;
+      pointerDownState.eventListeners.onKeyUp = onKeyUp;
+      pointerDownState.eventListeners.onKeyDown = onKeyDown;
+    }
   };
 
   private maybeOpenContextMenuAfterPointerDownOnTouchDevices = (
@@ -2131,7 +2179,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       !(
         gesture.pointers.size === 0 &&
         (event.button === POINTER_BUTTON.WHEEL ||
-          (event.button === POINTER_BUTTON.MAIN && isHoldingSpace))
+          (event.button === POINTER_BUTTON.MAIN && isHoldingSpace) ||
+          this.state.viewModeEnabled)
       )
     ) {
       return false;
@@ -2184,12 +2233,8 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       }
 
       this.setState({
-        scrollX: normalizeScroll(
-          this.state.scrollX - deltaX / this.state.zoom.value,
-        ),
-        scrollY: normalizeScroll(
-          this.state.scrollY - deltaY / this.state.zoom.value,
-        ),
+        scrollX: this.state.scrollX - deltaX / this.state.zoom.value,
+        scrollY: this.state.scrollY - deltaY / this.state.zoom.value,
       });
     });
     const teardown = withBatchedUpdates(
@@ -3013,9 +3058,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       const x = event.clientX;
       const dx = x - pointerDownState.lastCoords.x;
       this.setState({
-        scrollX: normalizeScroll(
-          this.state.scrollX - dx / this.state.zoom.value,
-        ),
+        scrollX: this.state.scrollX - dx / this.state.zoom.value,
       });
       pointerDownState.lastCoords.x = x;
       return true;
@@ -3025,9 +3068,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       const y = event.clientY;
       const dy = y - pointerDownState.lastCoords.y;
       this.setState({
-        scrollY: normalizeScroll(
-          this.state.scrollY - dy / this.state.zoom.value,
-        ),
+        scrollY: this.state.scrollY - dy / this.state.zoom.value,
       });
       pointerDownState.lastCoords.y = y;
       return true;
@@ -3593,9 +3634,6 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       transformElements(
         pointerDownState,
         transformHandleType,
-        (newTransformHandle) => {
-          pointerDownState.resize.handleType = newTransformHandle;
-        },
         selectedElements,
         pointerDownState.resize.arrowDirection,
         getRotateWithDiscreteAngleKey(event),
@@ -3625,52 +3663,87 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       this.state,
     );
 
+    const maybeGroupAction = actionGroup.contextItemPredicate!(
+      this.actionManager.getElementsIncludingDeleted(),
+      this.actionManager.getAppState(),
+    );
+
+    const maybeUngroupAction = actionUngroup.contextItemPredicate!(
+      this.actionManager.getElementsIncludingDeleted(),
+      this.actionManager.getAppState(),
+    );
+
+    const separator = "separator";
+
+    const _isMobile = isMobile();
+
     const elements = this.scene.getElements();
     const element = this.getElementAtPosition(x, y);
+    const options: ContextMenuOption[] = [];
+    if (probablySupportsClipboardBlob && elements.length > 0) {
+      options.push(actionCopyAsPng);
+    }
+
+    if (probablySupportsClipboardWriteText && elements.length > 0) {
+      options.push(actionCopyAsSvg);
+    }
     if (!element) {
+      const viewModeOptions: ContextMenuOption[] = [
+        ...options,
+        actionToggleStats,
+      ];
+
+      if (typeof this.props.viewModeEnabled === "undefined") {
+        viewModeOptions.push(actionToggleViewMode);
+      }
+
+      ContextMenu.push({
+        options: viewModeOptions,
+        top: clientY,
+        left: clientX,
+        actionManager: this.actionManager,
+        appState: this.state,
+      });
+
+      if (this.state.viewModeEnabled) {
+        return;
+      }
+
       ContextMenu.push({
         options: [
-          navigator.clipboard && {
-            shortcutName: "paste",
-            label: t("labels.paste"),
-            action: () => this.pasteFromClipboard(null),
-          },
-          probablySupportsClipboardBlob &&
-            elements.length > 0 && {
-              shortcutName: "copyAsPng",
-              label: t("labels.copyAsPng"),
-              action: this.copyToClipboardAsPng,
+          _isMobile &&
+            navigator.clipboard && {
+              name: "paste",
+              perform: (elements, appStates) => {
+                this.pasteFromClipboard(null);
+                return {
+                  commitToHistory: false,
+                };
+              },
+              contextItemLabel: "labels.paste",
             },
+          _isMobile && navigator.clipboard && separator,
+          probablySupportsClipboardBlob &&
+            elements.length > 0 &&
+            actionCopyAsPng,
           probablySupportsClipboardWriteText &&
-            elements.length > 0 && {
-              shortcutName: "copyAsSvg",
-              label: t("labels.copyAsSvg"),
-              action: this.copyToClipboardAsSvg,
-            },
-          ...this.actionManager.getContextMenuItems((action) =>
-            CANVAS_ONLY_ACTIONS.includes(action.name),
-          ),
-          {
-            checked: this.state.showGrid,
-            shortcutName: "gridMode",
-            label: t("labels.gridMode"),
-            action: this.toggleGridMode,
-          },
-          {
-            checked: this.state.zenModeEnabled,
-            shortcutName: "zenMode",
-            label: t("buttons.zenMode"),
-            action: this.toggleZenMode,
-          },
-          {
-            checked: this.state.showStats,
-            shortcutName: "stats",
-            label: t("stats.title"),
-            action: this.toggleStats,
-          },
+            elements.length > 0 &&
+            actionCopyAsSvg,
+          ((probablySupportsClipboardBlob && elements.length > 0) ||
+            (probablySupportsClipboardWriteText && elements.length > 0)) &&
+            separator,
+          actionSelectAll,
+          separator,
+          actionToggleGridMode,
+          actionToggleZenMode,
+          typeof this.props.viewModeEnabled === "undefined" &&
+            actionToggleViewMode,
+          actionToggleStats,
         ],
         top: clientY,
         left: clientX,
+        actionManager: this.actionManager,
+        appState: this.state,
       });
       return;
     }
@@ -3679,39 +3752,55 @@ class App extends React.Component<ExcalidrawProps, AppState> {
       this.setState({ selectedElementIds: { [element.id]: true } });
     }
 
+    if (this.state.viewModeEnabled) {
+      ContextMenu.push({
+        options: [navigator.clipboard && actionCopy, ...options],
+        top: clientY,
+        left: clientX,
+        actionManager: this.actionManager,
+        appState: this.state,
+      });
+      return;
+    }
+
     ContextMenu.push({
       options: [
-        {
-          shortcutName: "cut",
-          label: t("labels.cut"),
-          action: this.cutAll,
-        },
-        navigator.clipboard && {
-          shortcutName: "copy",
-          label: t("labels.copy"),
-          action: this.copyAll,
-        },
-        navigator.clipboard && {
-          shortcutName: "paste",
-          label: t("labels.paste"),
-          action: () => this.pasteFromClipboard(null),
-        },
-        probablySupportsClipboardBlob && {
-          shortcutName: "copyAsPng",
-          label: t("labels.copyAsPng"),
-          action: this.copyToClipboardAsPng,
-        },
-        probablySupportsClipboardWriteText && {
-          shortcutName: "copyAsSvg",
-          label: t("labels.copyAsSvg"),
-          action: this.copyToClipboardAsSvg,
-        },
-        ...this.actionManager.getContextMenuItems(
-          (action) => !CANVAS_ONLY_ACTIONS.includes(action.name),
-        ),
+        _isMobile && actionCut,
+        _isMobile && navigator.clipboard && actionCopy,
+        _isMobile &&
+          navigator.clipboard && {
+            name: "paste",
+            perform: (elements, appStates) => {
+              this.pasteFromClipboard(null);
+              return {
+                commitToHistory: false,
+              };
+            },
+            contextItemLabel: "labels.paste",
+          },
+        _isMobile && separator,
+        ...options,
+        separator,
+        actionCopyStyles,
+        actionPasteStyles,
+        separator,
+        maybeGroupAction && actionGroup,
+        maybeUngroupAction && actionUngroup,
+        (maybeGroupAction || maybeUngroupAction) && separator,
+        actionAddToLibrary,
+        separator,
+        actionSendBackward,
+        actionBringForward,
+        actionSendToBack,
+        actionBringToFront,
+        separator,
+        actionDuplicateSelection,
+        actionDeleteSelected,
       ],
       top: clientY,
       left: clientX,
+      actionManager: this.actionManager,
+      appState: this.state,
     });
   };
 
@@ -3742,9 +3831,15 @@ class App extends React.Component<ExcalidrawProps, AppState> {
         }, 1000);
       }
 
+      let newZoom = this.state.zoom.value - delta / 100;
+      // increase zoom steps the more zoomed-in we are (applies to >100% only)
+      newZoom += Math.log10(Math.max(1, this.state.zoom.value)) * -sign;
+      // round to nearest step
+      newZoom = Math.round(newZoom * ZOOM_STEP * 100) / (ZOOM_STEP * 100);
+
       this.setState(({ zoom, offsetLeft, offsetTop }) => ({
         zoom: getNewZoom(
-          getNormalizedZoom(zoom.value - delta / 100),
+          getNormalizedZoom(newZoom),
           zoom,
           { left: offsetLeft, top: offsetTop },
           {
@@ -3767,14 +3862,14 @@ class App extends React.Component<ExcalidrawProps, AppState> {
     if (event.shiftKey) {
       this.setState(({ zoom, scrollX }) => ({
         // on Mac, shift+wheel tends to result in deltaX
-        scrollX: normalizeScroll(scrollX - (deltaY || deltaX) / zoom.value),
+        scrollX: scrollX - (deltaY || deltaX) / zoom.value,
       }));
       return;
     }
 
     this.setState(({ zoom, scrollX, scrollY }) => ({
-      scrollX: normalizeScroll(scrollX - deltaX / zoom.value),
-      scrollY: normalizeScroll(scrollY - deltaY / zoom.value),
+      scrollX: scrollX - deltaX / zoom.value,
+      scrollY: scrollY - deltaY / zoom.value,
     }));
   });
 
@@ -3834,7 +3929,9 @@ class App extends React.Component<ExcalidrawProps, AppState> {
   };
 
   private resetShouldCacheIgnoreZoomDebounced = debounce(() => {
-    this.setState({ shouldCacheIgnoreZoom: false });
+    if (!this.unmounted) {
+      this.setState({ shouldCacheIgnoreZoom: false });
+    }
   }, 300);
 
   private getCanvasOffsets(offsets?: {
diff --git a/src/components/Avatar.scss b/src/components/Avatar.scss
index 61f084b725..d077d916bf 100644
--- a/src/components/Avatar.scss
+++ b/src/components/Avatar.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
   .Avatar {
diff --git a/src/components/CollabButton.scss b/src/components/CollabButton.scss
index fd51cc055d..5d9a86de3d 100644
--- a/src/components/CollabButton.scss
+++ b/src/components/CollabButton.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
   .CollabButton.is-collaborating {
diff --git a/src/components/ColorPicker.scss b/src/components/ColorPicker.scss
index 23a6aac82e..cb29e66d11 100644
--- a/src/components/ColorPicker.scss
+++ b/src/components/ColorPicker.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
   .color-picker {
diff --git a/src/components/ContextMenu.scss b/src/components/ContextMenu.scss
index fe8059e09d..f4ec1142ea 100644
--- a/src/components/ContextMenu.scss
+++ b/src/components/ContextMenu.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
   .context-menu {
@@ -9,9 +9,10 @@
     list-style: none;
     user-select: none;
     margin: -0.25rem 0 0 0.125rem;
-    padding: 0.25rem 0;
+    padding: 0.5rem 0;
     background-color: var(--popup-secondary-background-color);
     border: 1px solid var(--button-gray-3);
+    cursor: default;
   }
 
   .context-menu button {
@@ -54,6 +55,7 @@
     .context-menu-option__shortcut {
       justify-self: end;
       opacity: 0.6;
+      font-family: inherit;
       font-size: 0.7rem;
     }
   }
@@ -87,4 +89,9 @@
       }
     }
   }
+
+  .context-menu-option-separator {
+    border: none;
+    border-top: 1px solid $oc-gray-5;
+  }
 }
diff --git a/src/components/ContextMenu.tsx b/src/components/ContextMenu.tsx
index 27d14d880d..2a2c9b96e8 100644
--- a/src/components/ContextMenu.tsx
+++ b/src/components/ContextMenu.tsx
@@ -2,28 +2,36 @@ import React from "react";
 import { render, unmountComponentAtNode } from "react-dom";
 import clsx from "clsx";
 import { Popover } from "./Popover";
+import { t } from "../i18n";
 
 import "./ContextMenu.scss";
 import {
   getShortcutFromShortcutName,
   ShortcutName,
 } from "../actions/shortcuts";
+import { Action } from "../actions/types";
+import { ActionManager } from "../actions/manager";
+import { AppState } from "../types";
 
-type ContextMenuOption = {
-  checked?: boolean;
-  shortcutName: ShortcutName;
-  label: string;
-  action(): void;
-};
+export type ContextMenuOption = "separator" | Action;
 
-type Props = {
+type ContextMenuProps = {
   options: ContextMenuOption[];
   onCloseRequest?(): void;
   top: number;
   left: number;
+  actionManager: ActionManager;
+  appState: Readonly<AppState>;
 };
 
-const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => {
+const ContextMenu = ({
+  options,
+  onCloseRequest,
+  top,
+  left,
+  actionManager,
+  appState,
+}: ContextMenuProps) => {
   const isDarkTheme = !!document
     .querySelector(".excalidraw")
     ?.classList.contains("Appearance_dark");
@@ -43,23 +51,34 @@ const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => {
           className="context-menu"
           onContextMenu={(event) => event.preventDefault()}
         >
-          {options.map(({ action, checked, shortcutName, label }, idx) => (
-            <li data-testid={shortcutName} key={idx} onClick={onCloseRequest}>
-              <button
-                className={`context-menu-option 
-                ${shortcutName === "delete" ? "dangerous" : ""}
-                ${checked ? "checkmark" : ""}`}
-                onClick={action}
-              >
-                <div className="context-menu-option__label">{label}</div>
-                <div className="context-menu-option__shortcut">
-                  {shortcutName
-                    ? getShortcutFromShortcutName(shortcutName)
-                    : ""}
-                </div>
-              </button>
-            </li>
-          ))}
+          {options.map((option, idx) => {
+            if (option === "separator") {
+              return <hr key={idx} className="context-menu-option-separator" />;
+            }
+
+            const actionName = option.name;
+            const label = option.contextItemLabel
+              ? t(option.contextItemLabel)
+              : "";
+            return (
+              <li key={idx} data-testid={actionName} onClick={onCloseRequest}>
+                <button
+                  className={clsx("context-menu-option", {
+                    dangerous: actionName === "deleteSelectedElements",
+                    checkmark: option.checked?.(appState),
+                  })}
+                  onClick={() => actionManager.executeAction(option)}
+                >
+                  <div className="context-menu-option__label">{label}</div>
+                  <kbd className="context-menu-option__shortcut">
+                    {actionName
+                      ? getShortcutFromShortcutName(actionName as ShortcutName)
+                      : ""}
+                  </kbd>
+                </button>
+              </li>
+            );
+          })}
         </ul>
       </Popover>
     </div>
@@ -78,8 +97,10 @@ const getContextMenuNode = (): HTMLDivElement => {
 
 type ContextMenuParams = {
   options: (ContextMenuOption | false | null | undefined)[];
-  top: number;
-  left: number;
+  top: ContextMenuProps["top"];
+  left: ContextMenuProps["left"];
+  actionManager: ContextMenuProps["actionManager"];
+  appState: Readonly<AppState>;
 };
 
 const handleClose = () => {
@@ -101,6 +122,8 @@ export default {
           left={params.left}
           options={options}
           onCloseRequest={handleClose}
+          actionManager={params.actionManager}
+          appState={params.appState}
         />,
         getContextMenuNode(),
       );
diff --git a/src/components/Dialog.scss b/src/components/Dialog.scss
index 3586c1cd27..37d19219b6 100644
--- a/src/components/Dialog.scss
+++ b/src/components/Dialog.scss
@@ -1,6 +1,11 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
+  .Dialog {
+    user-select: text;
+    cursor: auto;
+  }
+
   .Dialog__title {
     display: grid;
     align-items: center;
@@ -10,6 +15,7 @@
     padding: calc(var(--space-factor) * 2);
     text-align: center;
     font-variant: small-caps;
+    font-size: 1.2em;
   }
 
   .Dialog__titleContent {
diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx
index aa2ef7c010..507dc17728 100644
--- a/src/components/Dialog.tsx
+++ b/src/components/Dialog.tsx
@@ -1,5 +1,6 @@
 import clsx from "clsx";
-import React, { useCallback, useEffect, useState } from "react";
+import React, { useEffect } from "react";
+import { useCallbackRefState } from "../hooks/useCallbackRefState";
 import { t } from "../i18n";
 import useIsMobile from "../is-mobile";
 import { KEYS } from "../keys";
@@ -8,14 +9,6 @@ import { back, close } from "./icons";
 import { Island } from "./Island";
 import { Modal } from "./Modal";
 
-const useRefState = <T,>() => {
-  const [refValue, setRefValue] = useState<T | null>(null);
-  const refCallback = useCallback((value: T) => {
-    setRefValue(value);
-  }, []);
-  return [refValue, refCallback] as const;
-};
-
 export const Dialog = (props: {
   children: React.ReactNode;
   className?: string;
@@ -24,7 +17,7 @@ export const Dialog = (props: {
   title: React.ReactNode;
   autofocus?: boolean;
 }) => {
-  const [islandNode, setIslandNode] = useRefState<HTMLDivElement>();
+  const [islandNode, setIslandNode] = useCallbackRefState<HTMLDivElement>();
 
   useEffect(() => {
     if (!islandNode) {
@@ -80,7 +73,7 @@ export const Dialog = (props: {
       onCloseRequest={props.onCloseRequest}
     >
       <Island ref={setIslandNode}>
-        <h3 id="dialog-title" className="Dialog__title">
+        <h2 id="dialog-title" className="Dialog__title">
           <span className="Dialog__titleContent">{props.title}</span>
           <button
             className="Modal__close"
@@ -89,7 +82,7 @@ export const Dialog = (props: {
           >
             {useIsMobile() ? back : close}
           </button>
-        </h3>
+        </h2>
         <div className="Dialog__content">{props.children}</div>
       </Island>
     </Modal>
diff --git a/src/components/ExportDialog.scss b/src/components/ExportDialog.scss
index 3086d72b15..c47cdf400a 100644
--- a/src/components/ExportDialog.scss
+++ b/src/components/ExportDialog.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
   .ExportDialog__preview {
diff --git a/src/components/ShortcutsDialog.scss b/src/components/HelpDialog.scss
similarity index 55%
rename from src/components/ShortcutsDialog.scss
rename to src/components/HelpDialog.scss
index 2feb38fbf2..6b5701b846 100644
--- a/src/components/ShortcutsDialog.scss
+++ b/src/components/HelpDialog.scss
@@ -1,23 +1,28 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
-  .ShortcutsDialog-island {
+  .HelpDialog h3 {
+    border-bottom: 1px solid var(--button-gray-2);
+    padding-bottom: 4px;
+  }
+
+  .HelpDialog--island {
     border: 1px solid var(--button-gray-2);
     margin-bottom: 16px;
   }
 
-  .ShortcutsDialog-island-title {
+  .HelpDialog--island-title {
     margin: 0;
     padding: 4px;
     background-color: var(--button-gray-1);
     text-align: center;
   }
 
-  .ShorcutsDialog-shortcut {
+  .HelpDialog--shortcut {
     border-top: 1px solid var(--button-gray-2);
   }
 
-  .ShorcutsDialog-key {
+  .HelpDialog--key {
     word-break: keep-all;
     border: 1px solid var(--button-gray-2);
     padding: 2px 8px;
@@ -29,14 +34,23 @@
     box-sizing: border-box;
     display: flex;
     align-items: center;
+    font-family: inherit;
   }
 
-  .ShortcutsDialog-footer {
+  .HelpDialog--header {
     display: flex;
     flex-direction: row;
     justify-content: space-evenly;
-    border-top: 1px solid var(--button-gray-2);
-    margin-top: 8px;
-    padding-top: 16px;
+    margin-bottom: 32px;
+    padding-bottom: 16px;
+  }
+
+  .HelpDialog--btn {
+    border: 1px solid var(--link-color);
+    padding: 8px 32px;
+    border-radius: 4px;
+  }
+  .HelpDialog--btn:hover {
+    text-decoration: none;
   }
 }
diff --git a/src/components/HelpDialog.tsx b/src/components/HelpDialog.tsx
new file mode 100644
index 0000000000..a66708977d
--- /dev/null
+++ b/src/components/HelpDialog.tsx
@@ -0,0 +1,359 @@
+import React from "react";
+import { t } from "../i18n";
+import { isDarwin, isWindows } from "../keys";
+import { Dialog } from "./Dialog";
+import { getShortcutKey } from "../utils";
+import "./HelpDialog.scss";
+
+const Header = () => (
+  <div className="HelpDialog--header">
+    <a
+      className="HelpDialog--btn"
+      href="https://github.com/excalidraw/excalidraw#documentation"
+      target="_blank"
+      rel="noopener noreferrer"
+    >
+      {t("helpDialog.documentation")}
+    </a>
+    <a
+      className="HelpDialog--btn"
+      href="https://blog.excalidraw.com"
+      target="_blank"
+      rel="noopener noreferrer"
+    >
+      {t("helpDialog.blog")}
+    </a>
+    <a
+      className="HelpDialog--btn"
+      href="https://github.com/excalidraw/excalidraw/issues"
+      target="_blank"
+      rel="noopener noreferrer"
+    >
+      {t("helpDialog.github")}
+    </a>
+  </div>
+);
+
+const Section = (props: { title: string; children: React.ReactNode }) => (
+  <>
+    <h3>{props.title}</h3>
+    {props.children}
+  </>
+);
+
+const Columns = (props: { children: React.ReactNode }) => (
+  <div
+    style={{
+      display: "flex",
+      flexDirection: "row",
+      flexWrap: "wrap",
+      justifyContent: "space-between",
+    }}
+  >
+    {props.children}
+  </div>
+);
+
+const Column = (props: { children: React.ReactNode }) => (
+  <div style={{ width: "49%" }}>{props.children}</div>
+);
+
+const ShortcutIsland = (props: {
+  caption: string;
+  children: React.ReactNode;
+}) => (
+  <div className="HelpDialog--island">
+    <h3 className="HelpDialog--island-title">{props.caption}</h3>
+    {props.children}
+  </div>
+);
+
+const Shortcut = (props: {
+  label: string;
+  shortcuts: string[];
+  isOr: boolean;
+}) => {
+  return (
+    <div className="HelpDialog--shortcut">
+      <div
+        style={{
+          display: "flex",
+          margin: "0",
+          padding: "4px 8px",
+          alignItems: "center",
+        }}
+      >
+        <div
+          style={{
+            lineHeight: 1.4,
+          }}
+        >
+          {props.label}
+        </div>
+        <div
+          style={{
+            display: "flex",
+            flex: "0 0 auto",
+            justifyContent: "flex-end",
+            marginInlineStart: "auto",
+            minWidth: "30%",
+          }}
+        >
+          {props.shortcuts.map((shortcut, index) => (
+            <React.Fragment key={index}>
+              <ShortcutKey>{shortcut}</ShortcutKey>
+              {props.isOr &&
+                index !== props.shortcuts.length - 1 &&
+                t("helpDialog.or")}
+            </React.Fragment>
+          ))}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+Shortcut.defaultProps = {
+  isOr: true,
+};
+
+const ShortcutKey = (props: { children: React.ReactNode }) => (
+  <kbd className="HelpDialog--key" {...props} />
+);
+
+export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
+  const handleClose = React.useCallback(() => {
+    if (onClose) {
+      onClose();
+    }
+  }, [onClose]);
+
+  return (
+    <>
+      <Dialog
+        onCloseRequest={handleClose}
+        title={t("helpDialog.title")}
+        className={"HelpDialog"}
+      >
+        <Header />
+        <Section title={t("helpDialog.shortcuts")}>
+          <Columns>
+            <Column>
+              <ShortcutIsland caption={t("helpDialog.shapes")}>
+                <Shortcut
+                  label={t("toolBar.selection")}
+                  shortcuts={["V", "1"]}
+                />
+                <Shortcut
+                  label={t("toolBar.rectangle")}
+                  shortcuts={["R", "2"]}
+                />
+                <Shortcut label={t("toolBar.diamond")} shortcuts={["D", "3"]} />
+                <Shortcut label={t("toolBar.ellipse")} shortcuts={["E", "4"]} />
+                <Shortcut label={t("toolBar.arrow")} shortcuts={["A", "5"]} />
+                <Shortcut label={t("toolBar.line")} shortcuts={["P", "6"]} />
+                <Shortcut
+                  label={t("toolBar.draw")}
+                  shortcuts={["Shift+P", "7"]}
+                />
+                <Shortcut label={t("toolBar.text")} shortcuts={["T", "8"]} />
+                <Shortcut
+                  label={t("helpDialog.textNewLine")}
+                  shortcuts={[
+                    getShortcutKey("Enter"),
+                    getShortcutKey("Shift+Enter"),
+                  ]}
+                />
+                <Shortcut
+                  label={t("helpDialog.textFinish")}
+                  shortcuts={[
+                    getShortcutKey("Esc"),
+                    getShortcutKey("CtrlOrCmd+Enter"),
+                  ]}
+                />
+                <Shortcut
+                  label={t("helpDialog.curvedArrow")}
+                  shortcuts={[
+                    "A",
+                    t("helpDialog.click"),
+                    t("helpDialog.click"),
+                    t("helpDialog.click"),
+                  ]}
+                  isOr={false}
+                />
+                <Shortcut
+                  label={t("helpDialog.curvedLine")}
+                  shortcuts={[
+                    "L",
+                    t("helpDialog.click"),
+                    t("helpDialog.click"),
+                    t("helpDialog.click"),
+                  ]}
+                  isOr={false}
+                />
+                <Shortcut label={t("toolBar.lock")} shortcuts={["Q"]} />
+                <Shortcut
+                  label={t("helpDialog.preventBinding")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd")]}
+                />
+              </ShortcutIsland>
+              <ShortcutIsland caption={t("helpDialog.view")}>
+                <Shortcut
+                  label={t("buttons.zoomIn")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd++")]}
+                />
+                <Shortcut
+                  label={t("buttons.zoomOut")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+-")]}
+                />
+                <Shortcut
+                  label={t("buttons.resetZoom")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+0")]}
+                />
+                <Shortcut
+                  label={t("helpDialog.zoomToFit")}
+                  shortcuts={["Shift+1"]}
+                />
+                <Shortcut
+                  label={t("helpDialog.zoomToSelection")}
+                  shortcuts={["Shift+2"]}
+                />
+                <Shortcut label={t("buttons.fullScreen")} shortcuts={["F"]} />
+                <Shortcut
+                  label={t("buttons.zenMode")}
+                  shortcuts={[getShortcutKey("Alt+Z")]}
+                />
+                <Shortcut
+                  label={t("labels.gridMode")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
+                />
+                <Shortcut
+                  label={t("labels.viewMode")}
+                  shortcuts={[getShortcutKey("Alt+R")]}
+                />
+              </ShortcutIsland>
+            </Column>
+            <Column>
+              <ShortcutIsland caption={t("helpDialog.editor")}>
+                <Shortcut
+                  label={t("labels.selectAll")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+A")]}
+                />
+                <Shortcut
+                  label={t("labels.multiSelect")}
+                  shortcuts={[getShortcutKey(`Shift+${t("helpDialog.click")}`)]}
+                />
+                <Shortcut
+                  label={t("labels.moveCanvas")}
+                  shortcuts={[
+                    getShortcutKey(`Space+${t("helpDialog.drag")}`),
+                    getShortcutKey(`Wheel+${t("helpDialog.drag")}`),
+                  ]}
+                  isOr={true}
+                />
+                <Shortcut
+                  label={t("labels.cut")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+X")]}
+                />
+                <Shortcut
+                  label={t("labels.copy")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+C")]}
+                />
+                <Shortcut
+                  label={t("labels.paste")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+V")]}
+                />
+                <Shortcut
+                  label={t("labels.copyAsPng")}
+                  shortcuts={[getShortcutKey("Shift+Alt+C")]}
+                />
+                <Shortcut
+                  label={t("labels.copyStyles")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+Alt+C")]}
+                />
+                <Shortcut
+                  label={t("labels.pasteStyles")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+Alt+V")]}
+                />
+                <Shortcut
+                  label={t("labels.delete")}
+                  shortcuts={[getShortcutKey("Del")]}
+                />
+                <Shortcut
+                  label={t("labels.sendToBack")}
+                  shortcuts={[
+                    isDarwin
+                      ? getShortcutKey("CtrlOrCmd+Alt+[")
+                      : getShortcutKey("CtrlOrCmd+Shift+["),
+                  ]}
+                />
+                <Shortcut
+                  label={t("labels.bringToFront")}
+                  shortcuts={[
+                    isDarwin
+                      ? getShortcutKey("CtrlOrCmd+Alt+]")
+                      : getShortcutKey("CtrlOrCmd+Shift+]"),
+                  ]}
+                />
+                <Shortcut
+                  label={t("labels.sendBackward")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+[")]}
+                />
+                <Shortcut
+                  label={t("labels.bringForward")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+]")]}
+                />
+                <Shortcut
+                  label={t("labels.alignTop")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Up")]}
+                />
+                <Shortcut
+                  label={t("labels.alignBottom")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Down")]}
+                />
+                <Shortcut
+                  label={t("labels.alignLeft")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Left")]}
+                />
+                <Shortcut
+                  label={t("labels.alignRight")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Right")]}
+                />
+                <Shortcut
+                  label={t("labels.duplicateSelection")}
+                  shortcuts={[
+                    getShortcutKey("CtrlOrCmd+D"),
+                    getShortcutKey(`Alt+${t("helpDialog.drag")}`),
+                  ]}
+                />
+                <Shortcut
+                  label={t("buttons.undo")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+Z")]}
+                />
+                <Shortcut
+                  label={t("buttons.redo")}
+                  shortcuts={
+                    isWindows
+                      ? [
+                          getShortcutKey("CtrlOrCmd+Y"),
+                          getShortcutKey("CtrlOrCmd+Shift+Z"),
+                        ]
+                      : [getShortcutKey("CtrlOrCmd+Shift+Z")]
+                  }
+                />
+                <Shortcut
+                  label={t("labels.group")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+G")]}
+                />
+                <Shortcut
+                  label={t("labels.ungroup")}
+                  shortcuts={[getShortcutKey("CtrlOrCmd+Shift+G")]}
+                />
+              </ShortcutIsland>
+            </Column>
+          </Columns>
+        </Section>
+      </Dialog>
+    </>
+  );
+};
diff --git a/src/components/HelpIcon.tsx b/src/components/HelpIcon.tsx
index d114117be3..8ec09d4e11 100644
--- a/src/components/HelpIcon.tsx
+++ b/src/components/HelpIcon.tsx
@@ -1,4 +1,5 @@
 import React from "react";
+import { questionCircle } from "../components/icons";
 
 type HelpIconProps = {
   title?: string;
@@ -7,19 +8,8 @@ type HelpIconProps = {
   onClick?(): void;
 };
 
-const ICON = (
-  <svg
-    width="30"
-    height="22"
-    viewBox="0 0 512 512"
-    xmlns="http://www.w3.org/2000/svg"
-  >
-    <path d="M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z" />
-  </svg>
-);
-
 export const HelpIcon = (props: HelpIconProps) => (
   <label title={`${props.title} — ?`} className="help-icon">
-    <div onClick={props.onClick}>{ICON}</div>
+    <div onClick={props.onClick}>{questionCircle}</div>
   </label>
 );
diff --git a/src/components/HintViewer.scss b/src/components/HintViewer.scss
index 87b502b64f..7f87354cb6 100644
--- a/src/components/HintViewer.scss
+++ b/src/components/HintViewer.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 // this is loosely based on the longest hint text
 $wide-viewport-width: 1000px;
diff --git a/src/components/IconPicker.scss b/src/components/IconPicker.scss
index ced5c5c235..284c36526c 100644
--- a/src/components/IconPicker.scss
+++ b/src/components/IconPicker.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
   .picker-container {
diff --git a/src/components/LayerUI.tsx b/src/components/LayerUI.tsx
index fbd5fdee0c..ef245e8ca5 100644
--- a/src/components/LayerUI.tsx
+++ b/src/components/LayerUI.tsx
@@ -36,7 +36,7 @@ import { LockIcon } from "./LockIcon";
 import { MobileMenu } from "./MobileMenu";
 import { PasteChartDialog } from "./PasteChartDialog";
 import { Section } from "./Section";
-import { ShortcutsDialog } from "./ShortcutsDialog";
+import { HelpDialog } from "./HelpDialog";
 import Stack from "./Stack";
 import { ToolButton } from "./ToolButton";
 import { Tooltip } from "./Tooltip";
@@ -61,6 +61,7 @@ interface LayerUIProps {
     canvas: HTMLCanvasElement | null,
   ) => void;
   renderCustomFooter?: (isMobile: boolean) => JSX.Element;
+  viewModeEnabled: boolean;
 }
 
 const useOnClickOutside = (
@@ -299,6 +300,7 @@ const LayerUI = ({
   isCollaborating,
   onExportToBackend,
   renderCustomFooter,
+  viewModeEnabled,
 }: LayerUIProps) => {
   const isMobile = useIsMobile();
 
@@ -358,6 +360,28 @@ const LayerUI = ({
     );
   };
 
+  const renderViewModeCanvasActions = () => {
+    return (
+      <Section
+        heading="canvasActions"
+        className={clsx("zen-mode-transition", {
+          "transition-left": zenModeEnabled,
+        })}
+      >
+        {/* the zIndex ensures this menu has higher stacking order,
+         see https://github.com/excalidraw/excalidraw/pull/1445 */}
+        <Island padding={2} style={{ zIndex: 1 }}>
+          <Stack.Col gap={4}>
+            <Stack.Row gap={1} justifyContent="space-between">
+              {actionManager.renderAction("saveScene")}
+              {actionManager.renderAction("saveAsScene")}
+              {renderExportDialog()}
+            </Stack.Row>
+          </Stack.Col>
+        </Island>
+      </Section>
+    );
+  };
   const renderCanvasActions = () => (
     <Section
       heading="canvasActions"
@@ -448,38 +472,42 @@ const LayerUI = ({
             gap={4}
             className={clsx({ "disable-pointerEvents": zenModeEnabled })}
           >
-            {renderCanvasActions()}
+            {viewModeEnabled
+              ? renderViewModeCanvasActions()
+              : renderCanvasActions()}
             {shouldRenderSelectedShapeActions && renderSelectedShapeActions()}
           </Stack.Col>
-          <Section heading="shapes">
-            {(heading) => (
-              <Stack.Col gap={4} align="start">
-                <Stack.Row gap={1}>
-                  <Island
-                    padding={1}
-                    className={clsx({ "zen-mode": zenModeEnabled })}
-                  >
-                    <HintViewer appState={appState} elements={elements} />
-                    {heading}
-                    <Stack.Row gap={1}>
-                      <ShapesSwitcher
-                        elementType={appState.elementType}
-                        setAppState={setAppState}
-                        isLibraryOpen={appState.isLibraryOpen}
-                      />
-                    </Stack.Row>
-                  </Island>
-                  <LockIcon
-                    zenModeEnabled={zenModeEnabled}
-                    checked={appState.elementLocked}
-                    onChange={onLockToggle}
-                    title={t("toolBar.lock")}
-                  />
-                </Stack.Row>
-                {libraryMenu}
-              </Stack.Col>
-            )}
-          </Section>
+          {!viewModeEnabled && (
+            <Section heading="shapes">
+              {(heading) => (
+                <Stack.Col gap={4} align="start">
+                  <Stack.Row gap={1}>
+                    <Island
+                      padding={1}
+                      className={clsx({ "zen-mode": zenModeEnabled })}
+                    >
+                      <HintViewer appState={appState} elements={elements} />
+                      {heading}
+                      <Stack.Row gap={1}>
+                        <ShapesSwitcher
+                          elementType={appState.elementType}
+                          setAppState={setAppState}
+                          isLibraryOpen={appState.isLibraryOpen}
+                        />
+                      </Stack.Row>
+                    </Island>
+                    <LockIcon
+                      zenModeEnabled={zenModeEnabled}
+                      checked={appState.elementLocked}
+                      onChange={onLockToggle}
+                      title={t("toolBar.lock")}
+                    />
+                  </Stack.Row>
+                  {libraryMenu}
+                </Stack.Col>
+              )}
+            </Section>
+          )}
           <UserList
             className={clsx("zen-mode-transition", {
               "transition-right": zenModeEnabled,
@@ -524,6 +552,20 @@ const LayerUI = ({
     );
   };
 
+  const renderGitHubCorner = () => {
+    return (
+      <aside
+        className={clsx(
+          "layer-ui__wrapper__github-corner zen-mode-transition",
+          {
+            "transition-right": zenModeEnabled,
+          },
+        )}
+      >
+        <GitHubCorner appearance={appState.appearance} />
+      </aside>
+    );
+  };
   const renderFooter = () => (
     <footer role="contentinfo" className="layer-ui__wrapper__footer">
       <div
@@ -566,10 +608,8 @@ const LayerUI = ({
           onClose={() => setAppState({ errorMessage: null })}
         />
       )}
-      {appState.showShortcutsDialog && (
-        <ShortcutsDialog
-          onClose={() => setAppState({ showShortcutsDialog: false })}
-        />
+      {appState.showHelpDialog && (
+        <HelpDialog onClose={() => setAppState({ showHelpDialog: false })} />
       )}
       {appState.pasteDialog.shown && (
         <PasteChartDialog
@@ -601,25 +641,19 @@ const LayerUI = ({
         canvas={canvas}
         isCollaborating={isCollaborating}
         renderCustomFooter={renderCustomFooter}
+        viewModeEnabled={viewModeEnabled}
       />
     </>
   ) : (
-    <div className="layer-ui__wrapper">
+    <div
+      className={clsx("layer-ui__wrapper", {
+        "disable-pointerEvents": appState.cursorButton === "down",
+      })}
+    >
       {dialogs}
       {renderFixedSideContainer()}
       {renderBottomAppMenu()}
-      {
-        <aside
-          className={clsx(
-            "layer-ui__wrapper__github-corner zen-mode-transition",
-            {
-              "transition-right": zenModeEnabled,
-            },
-          )}
-        >
-          <GitHubCorner appearance={appState.appearance} />
-        </aside>
-      }
+      {renderGitHubCorner()}
       {renderFooter()}
     </div>
   );
diff --git a/src/components/MobileMenu.tsx b/src/components/MobileMenu.tsx
index 1500de55f5..6c94dbd58f 100644
--- a/src/components/MobileMenu.tsx
+++ b/src/components/MobileMenu.tsx
@@ -29,6 +29,7 @@ type MobileMenuProps = {
   canvas: HTMLCanvasElement | null;
   isCollaborating: boolean;
   renderCustomFooter?: (isMobile: boolean) => JSX.Element;
+  viewModeEnabled: boolean;
 };
 
 export const MobileMenu = ({
@@ -43,121 +44,164 @@ export const MobileMenu = ({
   canvas,
   isCollaborating,
   renderCustomFooter,
-}: MobileMenuProps) => (
-  <>
-    <FixedSideContainer side="top">
-      <Section heading="shapes">
-        {(heading) => (
-          <Stack.Col gap={4} align="center">
-            <Stack.Row gap={1}>
-              <Island padding={1}>
-                {heading}
-                <Stack.Row gap={1}>
-                  <ShapesSwitcher
-                    elementType={appState.elementType}
-                    setAppState={setAppState}
-                    isLibraryOpen={appState.isLibraryOpen}
-                  />
-                </Stack.Row>
-              </Island>
-              <LockIcon
-                checked={appState.elementLocked}
-                onChange={onLockToggle}
-                title={t("toolBar.lock")}
-              />
-            </Stack.Row>
-            {libraryMenu}
-          </Stack.Col>
-        )}
-      </Section>
-      <HintViewer appState={appState} elements={elements} />
-    </FixedSideContainer>
-    <div
-      className="App-bottom-bar"
-      style={{
-        marginBottom: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
-        marginLeft: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
-        marginRight: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
-      }}
-    >
-      <Island padding={0}>
-        {appState.openMenu === "canvas" ? (
-          <Section className="App-mobile-menu" heading="canvasActions">
-            <div className="panelColumn">
-              <Stack.Col gap={4}>
-                {actionManager.renderAction("loadScene")}
-                {actionManager.renderAction("saveScene")}
-                {actionManager.renderAction("saveAsScene")}
-                {exportButton}
-                {actionManager.renderAction("clearCanvas")}
-                {onCollabButtonClick && (
-                  <CollabButton
-                    isCollaborating={isCollaborating}
-                    collaboratorCount={appState.collaborators.size}
-                    onClick={onCollabButtonClick}
-                  />
-                )}
-                <BackgroundPickerAndDarkModeToggle
-                  actionManager={actionManager}
-                  appState={appState}
-                  setAppState={setAppState}
+  viewModeEnabled,
+}: MobileMenuProps) => {
+  const renderFixedSideContainer = () => {
+    return (
+      <FixedSideContainer side="top">
+        <Section heading="shapes">
+          {(heading) => (
+            <Stack.Col gap={4} align="center">
+              <Stack.Row gap={1}>
+                <Island padding={1}>
+                  {heading}
+                  <Stack.Row gap={1}>
+                    <ShapesSwitcher
+                      elementType={appState.elementType}
+                      setAppState={setAppState}
+                      isLibraryOpen={appState.isLibraryOpen}
+                    />
+                  </Stack.Row>
+                </Island>
+                <LockIcon
+                  checked={appState.elementLocked}
+                  onChange={onLockToggle}
+                  title={t("toolBar.lock")}
                 />
-                {renderCustomFooter?.(true)}
-                <fieldset>
-                  <legend>{t("labels.collaborators")}</legend>
-                  <UserList mobile>
-                    {Array.from(appState.collaborators)
-                      // Collaborator is either not initialized or is actually the current user.
-                      .filter(([_, client]) => Object.keys(client).length !== 0)
-                      .map(([clientId, client]) => (
-                        <React.Fragment key={clientId}>
-                          {actionManager.renderAction(
-                            "goToCollaborator",
-                            clientId,
-                          )}
-                        </React.Fragment>
-                      ))}
-                  </UserList>
-                </fieldset>
-              </Stack.Col>
-            </div>
-          </Section>
-        ) : appState.openMenu === "shape" &&
-          showSelectedShapeActions(appState, elements) ? (
-          <Section className="App-mobile-menu" heading="selectedShapeActions">
-            <SelectedShapeActions
-              appState={appState}
-              elements={elements}
-              renderAction={actionManager.renderAction}
-              elementType={appState.elementType}
-            />
-          </Section>
-        ) : null}
-        <footer className="App-toolbar">
-          <div className="App-toolbar-content">
-            {actionManager.renderAction("toggleCanvasMenu")}
-            {actionManager.renderAction("toggleEditMenu")}
-            {actionManager.renderAction("undo")}
-            {actionManager.renderAction("redo")}
-            {actionManager.renderAction(
-              appState.multiElement ? "finalize" : "duplicateSelection",
-            )}
-            {actionManager.renderAction("deleteSelectedElements")}
-          </div>
-          {appState.scrolledOutside && !appState.openMenu && (
-            <button
-              className="scroll-back-to-content"
-              onClick={() => {
-                setAppState({
-                  ...calculateScrollCenter(elements, appState, canvas),
-                });
-              }}
-            >
-              {t("buttons.scrollBackToContent")}
-            </button>
+              </Stack.Row>
+              {libraryMenu}
+            </Stack.Col>
           )}
-        </footer>
-      </Island>
-    </div>
-  </>
-);
+        </Section>
+        <HintViewer appState={appState} elements={elements} />
+      </FixedSideContainer>
+    );
+  };
+
+  const renderAppToolbar = () => {
+    if (viewModeEnabled) {
+      return (
+        <div className="App-toolbar-content">
+          {actionManager.renderAction("toggleCanvasMenu")}
+        </div>
+      );
+    }
+    return (
+      <div className="App-toolbar-content">
+        {actionManager.renderAction("toggleCanvasMenu")}
+        {actionManager.renderAction("toggleEditMenu")}
+        {actionManager.renderAction("undo")}
+        {actionManager.renderAction("redo")}
+        {actionManager.renderAction(
+          appState.multiElement ? "finalize" : "duplicateSelection",
+        )}
+        {actionManager.renderAction("deleteSelectedElements")}
+      </div>
+    );
+  };
+
+  const renderCanvasActions = () => {
+    if (viewModeEnabled) {
+      return (
+        <>
+          {actionManager.renderAction("saveScene")}
+          {actionManager.renderAction("saveAsScene")}
+          {exportButton}
+        </>
+      );
+    }
+    return (
+      <>
+        {actionManager.renderAction("loadScene")}
+        {actionManager.renderAction("saveScene")}
+        {actionManager.renderAction("saveAsScene")}
+        {exportButton}
+        {actionManager.renderAction("clearCanvas")}
+        {onCollabButtonClick && (
+          <CollabButton
+            isCollaborating={isCollaborating}
+            collaboratorCount={appState.collaborators.size}
+            onClick={onCollabButtonClick}
+          />
+        )}
+        {
+          <BackgroundPickerAndDarkModeToggle
+            actionManager={actionManager}
+            appState={appState}
+            setAppState={setAppState}
+          />
+        }
+      </>
+    );
+  };
+  return (
+    <>
+      {!viewModeEnabled && renderFixedSideContainer()}
+      <div
+        className="App-bottom-bar"
+        style={{
+          marginBottom: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
+          marginLeft: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
+          marginRight: SCROLLBAR_WIDTH + SCROLLBAR_MARGIN * 2,
+        }}
+      >
+        <Island padding={0}>
+          {appState.openMenu === "canvas" ? (
+            <Section className="App-mobile-menu" heading="canvasActions">
+              <div className="panelColumn">
+                <Stack.Col gap={4}>
+                  {renderCanvasActions()}
+                  {renderCustomFooter?.(true)}
+                  <fieldset>
+                    <legend>{t("labels.collaborators")}</legend>
+                    <UserList mobile>
+                      {Array.from(appState.collaborators)
+                        // Collaborator is either not initialized or is actually the current user.
+                        .filter(
+                          ([_, client]) => Object.keys(client).length !== 0,
+                        )
+                        .map(([clientId, client]) => (
+                          <React.Fragment key={clientId}>
+                            {actionManager.renderAction(
+                              "goToCollaborator",
+                              clientId,
+                            )}
+                          </React.Fragment>
+                        ))}
+                    </UserList>
+                  </fieldset>
+                </Stack.Col>
+              </div>
+            </Section>
+          ) : appState.openMenu === "shape" &&
+            !viewModeEnabled &&
+            showSelectedShapeActions(appState, elements) ? (
+            <Section className="App-mobile-menu" heading="selectedShapeActions">
+              <SelectedShapeActions
+                appState={appState}
+                elements={elements}
+                renderAction={actionManager.renderAction}
+                elementType={appState.elementType}
+              />
+            </Section>
+          ) : null}
+          <footer className="App-toolbar">
+            {renderAppToolbar()}
+            {appState.scrolledOutside && !appState.openMenu && (
+              <button
+                className="scroll-back-to-content"
+                onClick={() => {
+                  setAppState({
+                    ...calculateScrollCenter(elements, appState, canvas),
+                  });
+                }}
+              >
+                {t("buttons.scrollBackToContent")}
+              </button>
+            )}
+          </footer>
+        </Island>
+      </div>
+    </>
+  );
+};
diff --git a/src/components/Modal.scss b/src/components/Modal.scss
index 2b34500da4..2666f35144 100644
--- a/src/components/Modal.scss
+++ b/src/components/Modal.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
   .Modal {
diff --git a/src/components/PasteChartDialog.scss b/src/components/PasteChartDialog.scss
index 9d45fb2dfb..dc76306a87 100644
--- a/src/components/PasteChartDialog.scss
+++ b/src/components/PasteChartDialog.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .excalidraw {
   .PasteChartDialog {
diff --git a/src/components/PasteChartDialog.tsx b/src/components/PasteChartDialog.tsx
index 22381d8a44..43607b3caa 100644
--- a/src/components/PasteChartDialog.tsx
+++ b/src/components/PasteChartDialog.tsx
@@ -1,5 +1,6 @@
 import oc from "open-color";
 import React, { useLayoutEffect, useRef, useState } from "react";
+import { trackEvent } from "../analytics";
 import { ChartElements, renderSpreadsheet, Spreadsheet } from "../charts";
 import { ChartType } from "../element/types";
 import { t } from "../i18n";
@@ -86,6 +87,7 @@ export const PasteChartDialog = ({
 
   const handleChartClick = (chartType: ChartType, elements: ChartElements) => {
     onInsertChart(elements);
+    trackEvent("magic", "chart", chartType);
     setAppState({
       currentChartType: chartType,
       pasteDialog: {
diff --git a/src/components/ShortcutsDialog.tsx b/src/components/ShortcutsDialog.tsx
deleted file mode 100644
index ff9327d90d..0000000000
--- a/src/components/ShortcutsDialog.tsx
+++ /dev/null
@@ -1,328 +0,0 @@
-import React from "react";
-import { t } from "../i18n";
-import { isDarwin } from "../keys";
-import { Dialog } from "./Dialog";
-import { getShortcutKey } from "../utils";
-import "./ShortcutsDialog.scss";
-
-const Columns = (props: { children: React.ReactNode }) => (
-  <div
-    style={{
-      display: "flex",
-      flexDirection: "row",
-      flexWrap: "wrap",
-      justifyContent: "space-between",
-    }}
-  >
-    {props.children}
-  </div>
-);
-
-const Column = (props: { children: React.ReactNode }) => (
-  <div style={{ width: "49%" }}>{props.children}</div>
-);
-
-const ShortcutIsland = (props: {
-  caption: string;
-  children: React.ReactNode;
-}) => (
-  <div className="ShortcutsDialog-island">
-    <h3 className="ShortcutsDialog-island-title">{props.caption}</h3>
-    {props.children}
-  </div>
-);
-
-const Shortcut = (props: {
-  label: string;
-  shortcuts: string[];
-  isOr: boolean;
-}) => {
-  return (
-    <div className="ShorcutsDialog-shortcut">
-      <div
-        style={{
-          display: "flex",
-          margin: "0",
-          padding: "4px 8px",
-          alignItems: "center",
-        }}
-      >
-        <div
-          style={{
-            lineHeight: 1.4,
-          }}
-        >
-          {props.label}
-        </div>
-        <div
-          style={{
-            display: "flex",
-            flex: "0 0 auto",
-            justifyContent: "flex-end",
-            marginInlineStart: "auto",
-            minWidth: "30%",
-          }}
-        >
-          {props.shortcuts.map((shortcut, index) => (
-            <React.Fragment key={index}>
-              <ShortcutKey>{shortcut}</ShortcutKey>
-              {props.isOr &&
-                index !== props.shortcuts.length - 1 &&
-                t("shortcutsDialog.or")}
-            </React.Fragment>
-          ))}
-        </div>
-      </div>
-    </div>
-  );
-};
-
-Shortcut.defaultProps = {
-  isOr: true,
-};
-
-const ShortcutKey = (props: { children: React.ReactNode }) => (
-  <span className="ShorcutsDialog-key" {...props} />
-);
-
-const Footer = () => (
-  <div className="ShortcutsDialog-footer">
-    <a
-      href="https://blog.excalidraw.com"
-      target="_blank"
-      rel="noopener noreferrer"
-    >
-      {t("shortcutsDialog.blog")}
-    </a>
-    <a
-      href="https://howto.excalidraw.com"
-      target="_blank"
-      rel="noopener noreferrer"
-    >
-      {t("shortcutsDialog.howto")}
-    </a>
-    <a
-      href="https://github.com/excalidraw/excalidraw/issues"
-      target="_blank"
-      rel="noopener noreferrer"
-    >
-      {t("shortcutsDialog.github")}
-    </a>
-  </div>
-);
-
-export const ShortcutsDialog = ({ onClose }: { onClose?: () => void }) => {
-  const handleClose = React.useCallback(() => {
-    if (onClose) {
-      onClose();
-    }
-  }, [onClose]);
-
-  return (
-    <>
-      <Dialog onCloseRequest={handleClose} title={t("shortcutsDialog.title")}>
-        <Columns>
-          <Column>
-            <ShortcutIsland caption={t("shortcutsDialog.shapes")}>
-              <Shortcut label={t("toolBar.selection")} shortcuts={["V", "1"]} />
-              <Shortcut label={t("toolBar.rectangle")} shortcuts={["R", "2"]} />
-              <Shortcut label={t("toolBar.diamond")} shortcuts={["D", "3"]} />
-              <Shortcut label={t("toolBar.ellipse")} shortcuts={["E", "4"]} />
-              <Shortcut label={t("toolBar.arrow")} shortcuts={["A", "5"]} />
-              <Shortcut label={t("toolBar.line")} shortcuts={["P", "6"]} />
-              <Shortcut
-                label={t("toolBar.draw")}
-                shortcuts={["Shift+P", "7"]}
-              />
-              <Shortcut label={t("toolBar.text")} shortcuts={["T", "8"]} />
-              <Shortcut
-                label={t("shortcutsDialog.textNewLine")}
-                shortcuts={[
-                  getShortcutKey("Enter"),
-                  getShortcutKey("Shift+Enter"),
-                ]}
-              />
-              <Shortcut
-                label={t("shortcutsDialog.textFinish")}
-                shortcuts={[
-                  getShortcutKey("Esc"),
-                  getShortcutKey("CtrlOrCmd+Enter"),
-                ]}
-              />
-              <Shortcut
-                label={t("shortcutsDialog.curvedArrow")}
-                shortcuts={[
-                  "A",
-                  t("shortcutsDialog.click"),
-                  t("shortcutsDialog.click"),
-                  t("shortcutsDialog.click"),
-                ]}
-                isOr={false}
-              />
-              <Shortcut
-                label={t("shortcutsDialog.curvedLine")}
-                shortcuts={[
-                  "L",
-                  t("shortcutsDialog.click"),
-                  t("shortcutsDialog.click"),
-                  t("shortcutsDialog.click"),
-                ]}
-                isOr={false}
-              />
-              <Shortcut label={t("toolBar.lock")} shortcuts={["Q"]} />
-              <Shortcut
-                label={t("shortcutsDialog.preventBinding")}
-                shortcuts={[getShortcutKey("CtrlOrCmd")]}
-              />
-            </ShortcutIsland>
-            <ShortcutIsland caption={t("shortcutsDialog.view")}>
-              <Shortcut
-                label={t("buttons.zoomIn")}
-                shortcuts={[getShortcutKey("CtrlOrCmd++")]}
-              />
-              <Shortcut
-                label={t("buttons.zoomOut")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+-")]}
-              />
-              <Shortcut
-                label={t("buttons.resetZoom")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+0")]}
-              />
-              <Shortcut
-                label={t("shortcutsDialog.zoomToFit")}
-                shortcuts={["Shift+1"]}
-              />
-              <Shortcut
-                label={t("shortcutsDialog.zoomToSelection")}
-                shortcuts={["Shift+2"]}
-              />
-              <Shortcut label={t("buttons.fullScreen")} shortcuts={["F"]} />
-              <Shortcut
-                label={t("buttons.zenMode")}
-                shortcuts={[getShortcutKey("Alt+Z")]}
-              />
-              <Shortcut
-                label={t("labels.gridMode")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+'")]}
-              />
-            </ShortcutIsland>
-          </Column>
-          <Column>
-            <ShortcutIsland caption={t("shortcutsDialog.editor")}>
-              <Shortcut
-                label={t("labels.selectAll")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+A")]}
-              />
-              <Shortcut
-                label={t("labels.multiSelect")}
-                shortcuts={[
-                  getShortcutKey(`Shift+${t("shortcutsDialog.click")}`),
-                ]}
-              />
-              <Shortcut
-                label={t("labels.moveCanvas")}
-                shortcuts={[
-                  getShortcutKey(`Space+${t("shortcutsDialog.drag")}`),
-                  getShortcutKey(`Wheel+${t("shortcutsDialog.drag")}`),
-                ]}
-                isOr={true}
-              />
-              <Shortcut
-                label={t("labels.cut")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+X")]}
-              />
-              <Shortcut
-                label={t("labels.copy")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+C")]}
-              />
-              <Shortcut
-                label={t("labels.paste")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+V")]}
-              />
-              <Shortcut
-                label={t("labels.copyAsPng")}
-                shortcuts={[getShortcutKey("Shift+Alt+C")]}
-              />
-              <Shortcut
-                label={t("labels.copyStyles")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+Alt+C")]}
-              />
-              <Shortcut
-                label={t("labels.pasteStyles")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+Alt+V")]}
-              />
-              <Shortcut
-                label={t("labels.delete")}
-                shortcuts={[getShortcutKey("Del")]}
-              />
-              <Shortcut
-                label={t("labels.sendToBack")}
-                shortcuts={[
-                  isDarwin
-                    ? getShortcutKey("CtrlOrCmd+Alt+[")
-                    : getShortcutKey("CtrlOrCmd+Shift+["),
-                ]}
-              />
-              <Shortcut
-                label={t("labels.bringToFront")}
-                shortcuts={[
-                  isDarwin
-                    ? getShortcutKey("CtrlOrCmd+Alt+]")
-                    : getShortcutKey("CtrlOrCmd+Shift+]"),
-                ]}
-              />
-              <Shortcut
-                label={t("labels.sendBackward")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+[")]}
-              />
-              <Shortcut
-                label={t("labels.bringForward")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+]")]}
-              />
-              <Shortcut
-                label={t("labels.alignTop")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Up")]}
-              />
-              <Shortcut
-                label={t("labels.alignBottom")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Down")]}
-              />
-              <Shortcut
-                label={t("labels.alignLeft")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Left")]}
-              />
-              <Shortcut
-                label={t("labels.alignRight")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Right")]}
-              />
-              <Shortcut
-                label={t("labels.duplicateSelection")}
-                shortcuts={[
-                  getShortcutKey("CtrlOrCmd+D"),
-                  getShortcutKey(`Alt+${t("shortcutsDialog.drag")}`),
-                ]}
-              />
-              <Shortcut
-                label={t("buttons.undo")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+Z")]}
-              />
-              <Shortcut
-                label={t("buttons.redo")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+Shift+Z")]}
-              />
-              <Shortcut
-                label={t("labels.group")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+G")]}
-              />
-              <Shortcut
-                label={t("labels.ungroup")}
-                shortcuts={[getShortcutKey("CtrlOrCmd+Shift+G")]}
-              />
-            </ShortcutIsland>
-          </Column>
-        </Columns>
-        <Footer />
-      </Dialog>
-    </>
-  );
-};
diff --git a/src/components/Stats.scss b/src/components/Stats.scss
index a6849f3bcb..84864f933e 100644
--- a/src/components/Stats.scss
+++ b/src/components/Stats.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 
 .Stats {
   position: fixed;
diff --git a/src/components/TextInput.scss b/src/components/TextInput.scss
index 3ff96c87c5..930372ff04 100644
--- a/src/components/TextInput.scss
+++ b/src/components/TextInput.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables.scss";
+@import "../css/variables.module";
 
 .excalidraw {
   .TextInput {
diff --git a/src/components/Toast.scss b/src/components/Toast.scss
new file mode 100644
index 0000000000..70cc801802
--- /dev/null
+++ b/src/components/Toast.scss
@@ -0,0 +1,32 @@
+@import "../css/variables.module";
+
+.excalidraw {
+  .Toast {
+    animation: fade-in 0.5s;
+    background-color: var(--button-gray-1);
+    border-radius: 4px;
+    bottom: 10px;
+    box-sizing: border-box;
+    cursor: default;
+    left: 50%;
+    margin-left: -150px;
+    padding: 4px 0;
+    position: fixed;
+    text-align: center;
+    width: 300px;
+    z-index: 999999;
+  }
+
+  .Toast__message {
+    color: var(--popup-text-color);
+  }
+
+  @keyframes fade-in {
+    from {
+      opacity: 0;
+    }
+    to {
+      opacity: 1;
+    }
+  }
+}
diff --git a/src/components/Toast.tsx b/src/components/Toast.tsx
new file mode 100644
index 0000000000..d7ae914072
--- /dev/null
+++ b/src/components/Toast.tsx
@@ -0,0 +1,34 @@
+import React, { useCallback, useEffect, useRef } from "react";
+import { TOAST_TIMEOUT } from "../constants";
+import "./Toast.scss";
+
+export const Toast = ({
+  message,
+  clearToast,
+}: {
+  message: string;
+  clearToast: () => void;
+}) => {
+  const timerRef = useRef<number>(0);
+
+  const scheduleTimeout = useCallback(
+    () =>
+      (timerRef.current = window.setTimeout(() => clearToast(), TOAST_TIMEOUT)),
+    [clearToast],
+  );
+
+  useEffect(() => {
+    scheduleTimeout();
+    return () => clearTimeout(timerRef.current);
+  }, [scheduleTimeout, message]);
+
+  return (
+    <div
+      className="Toast"
+      onMouseEnter={() => clearTimeout(timerRef?.current)}
+      onMouseLeave={scheduleTimeout}
+    >
+      <p className="Toast__message">{message}</p>
+    </div>
+  );
+};
diff --git a/src/components/ToolIcon.scss b/src/components/ToolIcon.scss
index 91e8e1bc58..df07ca0257 100644
--- a/src/components/ToolIcon.scss
+++ b/src/components/ToolIcon.scss
@@ -1,5 +1,5 @@
 @import "open-color/open-color.scss";
-@import "../css/variables";
+@import "../css/variables.module";
 
 .excalidraw {
   .ToolIcon {
diff --git a/src/components/Tooltip.scss b/src/components/Tooltip.scss
index 9fe048f6a0..75b79bf56f 100644
--- a/src/components/Tooltip.scss
+++ b/src/components/Tooltip.scss
@@ -1,4 +1,4 @@
-@import "../css/_variables";
+@import "../css/variables.module";
 .excalidraw {
   .Tooltip {
     position: relative;
@@ -48,15 +48,7 @@
     }
   }
 
-  // the following 3 rules ensure that the tooltip doesn't show (nor affect
-  // the cursor) when you drag over when you draw on canvas, but at the same
-  // time it still works when clicking on the link/shield
-
-  body:active & .Tooltip:not(:hover) {
-    pointer-events: none;
-  }
-
-  body:not(:active) & .Tooltip:hover .Tooltip__label {
+  .Tooltip:hover .Tooltip__label {
     visibility: visible;
   }
 
diff --git a/src/constants.ts b/src/constants.ts
index 4f22b35ecd..b037eb2354 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -89,3 +89,7 @@ export const STORAGE_KEYS = {
 export const TAP_TWICE_TIMEOUT = 300;
 export const TOUCH_CTX_MENU_TIMEOUT = 500;
 export const TITLE_TIMEOUT = 10000;
+export const TOAST_TIMEOUT = 5000;
+export const VERSION_TIMEOUT = 30000;
+
+export const ZOOM_STEP = 0.1;
diff --git a/src/createInverseContext.tsx b/src/createInverseContext.tsx
new file mode 100644
index 0000000000..ac6cc223e3
--- /dev/null
+++ b/src/createInverseContext.tsx
@@ -0,0 +1,42 @@
+import React from "react";
+
+export const createInverseContext = <T extends unknown = null>(
+  initialValue: T,
+) => {
+  const Context = React.createContext(initialValue) as React.Context<T> & {
+    _updateProviderValue?: (value: T) => void;
+  };
+
+  class InverseConsumer extends React.Component {
+    state = { value: initialValue };
+    constructor(props: any) {
+      super(props);
+      Context._updateProviderValue = (value: T) => this.setState({ value });
+    }
+    render() {
+      return (
+        <Context.Provider value={this.state.value}>
+          {this.props.children}
+        </Context.Provider>
+      );
+    }
+  }
+
+  class InverseProvider extends React.Component<{ value: T }> {
+    componentDidMount() {
+      Context._updateProviderValue?.(this.props.value);
+    }
+    componentDidUpdate() {
+      Context._updateProviderValue?.(this.props.value);
+    }
+    render() {
+      return <Context.Consumer>{() => this.props.children}</Context.Consumer>;
+    }
+  }
+
+  return {
+    Context,
+    Consumer: InverseConsumer,
+    Provider: InverseProvider,
+  };
+};
diff --git a/src/css/styles.scss b/src/css/styles.scss
index c2fcd527b3..573dba8f52 100644
--- a/src/css/styles.scss
+++ b/src/css/styles.scss
@@ -1,4 +1,4 @@
-@import "./_variables";
+@import "./variables.module";
 @import "./theme";
 
 .excalidraw {
@@ -13,7 +13,7 @@
   a {
     font-weight: 500;
     text-decoration: none;
-    color: $oc-blue-7; /* OC Blue 7 */
+    color: var(--link-color);
 
     &:hover {
       text-decoration: underline;
@@ -282,7 +282,7 @@
     pointer-events: none !important;
   }
 
-  .App-menu_top > * {
+  .layer-ui__wrapper:not(.disable-pointerEvents) .App-menu_top > * {
     pointer-events: all;
   }
 
@@ -323,7 +323,7 @@
     }
   }
 
-  .App-menu_bottom > * {
+  .layer-ui__wrapper:not(.disable-pointerEvents) .App-menu_bottom > * {
     pointer-events: all;
   }
 
@@ -431,6 +431,7 @@
     cursor: pointer;
     fill: $oc-gray-6;
     bottom: 14px;
+    width: 1.5rem;
 
     :root[dir="ltr"] & {
       right: 14px;
@@ -491,6 +492,13 @@
     pointer-events: none !important;
   }
 
+  &.excalidraw--view-mode {
+    .App-menu {
+      display: flex;
+      justify-content: space-between;
+    }
+  }
+
   @media print {
     .App-bottom-bar,
     .FixedSideContainer,
diff --git a/src/css/theme.scss b/src/css/theme.scss
index 4de90d83d9..ca88d84047 100644
--- a/src/css/theme.scss
+++ b/src/css/theme.scss
@@ -32,6 +32,7 @@
   --popup-text-color: #{$oc-black};
   --popup-text-inverted-color: #{$oc-white};
   --dialog-border: #{$oc-gray-6};
+  --link-color: #{$oc-blue-7};
 }
 
 .excalidraw {
diff --git a/src/css/_variables.scss b/src/css/variables.module.scss
similarity index 73%
rename from src/css/_variables.scss
rename to src/css/variables.module.scss
index 4e4ac861dd..5b9ee7a8c0 100644
--- a/src/css/_variables.scss
+++ b/src/css/variables.module.scss
@@ -2,3 +2,7 @@
 
 // keep up to date with is-mobile.tsx
 $is-mobile-query: "(max-width: 600px), (max-height: 500px) and (max-width: 1000px)";
+
+:export {
+  isMobileQuery: unquote($is-mobile-query);
+}
diff --git a/src/data/index.ts b/src/data/index.ts
index 41de22a44a..7dae58be76 100644
--- a/src/data/index.ts
+++ b/src/data/index.ts
@@ -1,4 +1,4 @@
-import { fileSave } from "browser-nativefs";
+import { fileSave } from "browser-fs-access";
 import {
   copyCanvasToClipboardAsPng,
   copyTextToSystemClipboard,
@@ -36,7 +36,7 @@ export const exportCanvas = async (
   },
 ) => {
   if (elements.length === 0) {
-    return window.alert(t("alerts.cannotExportEmptyCanvas"));
+    throw new Error(t("alerts.cannotExportEmptyCanvas"));
   }
   if (type === "svg" || type === "clipboard-svg") {
     const tempSvg = exportToSvg(elements, {
diff --git a/src/data/json.ts b/src/data/json.ts
index d4d3205a1e..65688c32ed 100644
--- a/src/data/json.ts
+++ b/src/data/json.ts
@@ -1,4 +1,4 @@
-import { fileOpen, fileSave } from "browser-nativefs";
+import { fileOpen, fileSave } from "browser-fs-access";
 import { cleanAppStateForExport } from "../appState";
 import { MIME_TYPES } from "../constants";
 import { clearElementsForExport } from "../element";
diff --git a/src/element/index.ts b/src/element/index.ts
index e49bc633e3..63fdcfff49 100644
--- a/src/element/index.ts
+++ b/src/element/index.ts
@@ -34,7 +34,6 @@ export {
 export {
   resizeTest,
   getCursorForResizingElement,
-  normalizeTransformHandleType,
   getElementWithTransformHandleType,
   getTransformHandleTypeFromCoords,
 } from "./resizeTest";
diff --git a/src/element/resizeElements.ts b/src/element/resizeElements.ts
index 199e3a4824..96c216b06e 100644
--- a/src/element/resizeElements.ts
+++ b/src/element/resizeElements.ts
@@ -4,7 +4,6 @@ import { rescalePoints } from "../points";
 import {
   rotate,
   adjustXYWithRotation,
-  getFlipAdjustment,
   centerPoint,
   rotatePoint,
 } from "../math";
@@ -13,21 +12,16 @@ import {
   ExcalidrawTextElement,
   NonDeletedExcalidrawElement,
   NonDeleted,
-  ExcalidrawGenericElement,
-  ExcalidrawElement,
 } from "./types";
 import {
   getElementAbsoluteCoords,
   getCommonBounds,
   getResizedElementAbsoluteCoords,
 } from "./bounds";
-import { isGenericElement, isLinearElement, isTextElement } from "./typeChecks";
+import { isLinearElement, isTextElement } from "./typeChecks";
 import { mutateElement } from "./mutateElement";
 import { getPerfectElementSize } from "./sizeHelpers";
-import {
-  getCursorForResizingElement,
-  normalizeTransformHandleType,
-} from "./resizeTest";
+import { getCursorForResizingElement } from "./resizeTest";
 import { measureText, getFontString } from "../utils";
 import { updateBoundElements } from "./binding";
 import {
@@ -49,7 +43,6 @@ const normalizeAngle = (angle: number): number => {
 export const transformElements = (
   pointerDownState: PointerDownState,
   transformHandleType: MaybeTransformHandleType,
-  setTransformHandle: (nextTransformHandle: MaybeTransformHandleType) => void,
   selectedElements: readonly NonDeletedExcalidrawElement[],
   resizeArrowDirection: "origin" | "end",
   isRotateWithDiscreteAngle: boolean,
@@ -101,36 +94,15 @@ export const transformElements = (
       );
       updateBoundElements(element);
     } else if (transformHandleType) {
-      if (isGenericElement(element)) {
-        resizeSingleGenericElement(
-          pointerDownState.originalElements.get(element.id) as typeof element,
-          shouldKeepSidesRatio,
-          element,
-          transformHandleType,
-          isResizeCenterPoint,
-          pointerX,
-          pointerY,
-        );
-      } else {
-        const keepSquareAspectRatio = shouldKeepSidesRatio;
-        resizeSingleNonGenericElement(
-          element,
-          transformHandleType,
-          isResizeCenterPoint,
-          keepSquareAspectRatio,
-          pointerX,
-          pointerY,
-        );
-        setTransformHandle(
-          normalizeTransformHandleType(element, transformHandleType),
-        );
-        if (element.width < 0) {
-          mutateElement(element, { width: -element.width });
-        }
-        if (element.height < 0) {
-          mutateElement(element, { height: -element.height });
-        }
-      }
+      resizeSingleElement(
+        pointerDownState.originalElements.get(element.id) as typeof element,
+        shouldKeepSidesRatio,
+        element,
+        transformHandleType,
+        isResizeCenterPoint,
+        pointerX,
+        pointerY,
+      );
     }
 
     // update cursor
@@ -414,8 +386,8 @@ const resizeSingleTextElement = (
   }
 };
 
-const resizeSingleGenericElement = (
-  stateAtResizeStart: NonDeleted<ExcalidrawGenericElement>,
+const resizeSingleElement = (
+  stateAtResizeStart: NonDeletedExcalidrawElement,
   shouldKeepSidesRatio: boolean,
   element: NonDeletedExcalidrawElement,
   transformHandleDirection: TransformHandleDirection,
@@ -423,251 +395,184 @@ const resizeSingleGenericElement = (
   pointerX: number,
   pointerY: number,
 ) => {
-  const [x1, y1, x2, y2] = getElementAbsoluteCoords(stateAtResizeStart);
+  // Gets bounds corners
+  const [x1, y1, x2, y2] = getResizedElementAbsoluteCoords(
+    stateAtResizeStart,
+    stateAtResizeStart.width,
+    stateAtResizeStart.height,
+  );
   const startTopLeft: Point = [x1, y1];
   const startBottomRight: Point = [x2, y2];
   const startCenter: Point = centerPoint(startTopLeft, startBottomRight);
 
   // Calculate new dimensions based on cursor position
-  let newWidth = stateAtResizeStart.width;
-  let newHeight = stateAtResizeStart.height;
   const rotatedPointer = rotatePoint(
     [pointerX, pointerY],
     startCenter,
     -stateAtResizeStart.angle,
   );
+
+  //Get bounds corners rendered on screen
+  const [esx1, esy1, esx2, esy2] = getResizedElementAbsoluteCoords(
+    element,
+    element.width,
+    element.height,
+  );
+  const boundsCurrentWidth = esx2 - esx1;
+  const boundsCurrentHeight = esy2 - esy1;
+
+  // It's important we set the initial scale value based on the width and height at resize start,
+  // otherwise previous dimensions affected by modifiers will be taken into account.
+  const atStartBoundsWidth = startBottomRight[0] - startTopLeft[0];
+  const atStartBoundsHeight = startBottomRight[1] - startTopLeft[1];
+  let scaleX = atStartBoundsWidth / boundsCurrentWidth;
+  let scaleY = atStartBoundsHeight / boundsCurrentHeight;
+
   if (transformHandleDirection.includes("e")) {
-    newWidth = rotatedPointer[0] - startTopLeft[0];
+    scaleX = (rotatedPointer[0] - startTopLeft[0]) / boundsCurrentWidth;
   }
   if (transformHandleDirection.includes("s")) {
-    newHeight = rotatedPointer[1] - startTopLeft[1];
+    scaleY = (rotatedPointer[1] - startTopLeft[1]) / boundsCurrentHeight;
   }
   if (transformHandleDirection.includes("w")) {
-    newWidth = startBottomRight[0] - rotatedPointer[0];
+    scaleX = (startBottomRight[0] - rotatedPointer[0]) / boundsCurrentWidth;
   }
   if (transformHandleDirection.includes("n")) {
-    newHeight = startBottomRight[1] - rotatedPointer[1];
+    scaleY = (startBottomRight[1] - rotatedPointer[1]) / boundsCurrentHeight;
   }
+  // Linear elements dimensions differ from bounds dimensions
+  const eleInitialWidth = stateAtResizeStart.width;
+  const eleInitialHeight = stateAtResizeStart.height;
+  // We have to use dimensions of element on screen, otherwise the scaling of the
+  // dimensions won't match the cursor for linear elements.
+  let eleNewWidth = element.width * scaleX;
+  let eleNewHeight = element.height * scaleY;
 
   // adjust dimensions for resizing from center
   if (isResizeFromCenter) {
-    newWidth = 2 * newWidth - stateAtResizeStart.width;
-    newHeight = 2 * newHeight - stateAtResizeStart.height;
+    eleNewWidth = 2 * eleNewWidth - eleInitialWidth;
+    eleNewHeight = 2 * eleNewHeight - eleInitialHeight;
   }
 
   // adjust dimensions to keep sides ratio
   if (shouldKeepSidesRatio) {
-    const widthRatio = Math.abs(newWidth) / stateAtResizeStart.width;
-    const heightRatio = Math.abs(newHeight) / stateAtResizeStart.height;
+    const widthRatio = Math.abs(eleNewWidth) / eleInitialWidth;
+    const heightRatio = Math.abs(eleNewHeight) / eleInitialHeight;
     if (transformHandleDirection.length === 1) {
-      newHeight *= widthRatio;
-      newWidth *= heightRatio;
+      eleNewHeight *= widthRatio;
+      eleNewWidth *= heightRatio;
     }
     if (transformHandleDirection.length === 2) {
       const ratio = Math.max(widthRatio, heightRatio);
-      newWidth = stateAtResizeStart.width * ratio * Math.sign(newWidth);
-      newHeight = stateAtResizeStart.height * ratio * Math.sign(newHeight);
+      eleNewWidth = eleInitialWidth * ratio * Math.sign(eleNewWidth);
+      eleNewHeight = eleInitialHeight * ratio * Math.sign(eleNewHeight);
     }
   }
 
+  const [
+    newBoundsX1,
+    newBoundsY1,
+    newBoundsX2,
+    newBoundsY2,
+  ] = getResizedElementAbsoluteCoords(
+    stateAtResizeStart,
+    eleNewWidth,
+    eleNewHeight,
+  );
+  const newBoundsWidth = newBoundsX2 - newBoundsX1;
+  const newBoundsHeight = newBoundsY2 - newBoundsY1;
+
   // Calculate new topLeft based on fixed corner during resize
-  let newTopLeft = startTopLeft as [number, number];
+  let newTopLeft = [...startTopLeft] as [number, number];
   if (["n", "w", "nw"].includes(transformHandleDirection)) {
     newTopLeft = [
-      startBottomRight[0] - Math.abs(newWidth),
-      startBottomRight[1] - Math.abs(newHeight),
+      startBottomRight[0] - Math.abs(newBoundsWidth),
+      startBottomRight[1] - Math.abs(newBoundsHeight),
     ];
   }
   if (transformHandleDirection === "ne") {
-    const bottomLeft = [
-      stateAtResizeStart.x,
-      stateAtResizeStart.y + stateAtResizeStart.height,
-    ];
-    newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(newHeight)];
+    const bottomLeft = [startTopLeft[0], startBottomRight[1]];
+    newTopLeft = [bottomLeft[0], bottomLeft[1] - Math.abs(newBoundsHeight)];
   }
   if (transformHandleDirection === "sw") {
-    const topRight = [
-      stateAtResizeStart.x + stateAtResizeStart.width,
-      stateAtResizeStart.y,
-    ];
-    newTopLeft = [topRight[0] - Math.abs(newWidth), topRight[1]];
+    const topRight = [startBottomRight[0], startTopLeft[1]];
+    newTopLeft = [topRight[0] - Math.abs(newBoundsWidth), topRight[1]];
   }
 
   // Keeps opposite handle fixed during resize
   if (shouldKeepSidesRatio) {
     if (["s", "n"].includes(transformHandleDirection)) {
-      newTopLeft[0] = startCenter[0] - newWidth / 2;
+      newTopLeft[0] = startCenter[0] - newBoundsWidth / 2;
     }
     if (["e", "w"].includes(transformHandleDirection)) {
-      newTopLeft[1] = startCenter[1] - newHeight / 2;
+      newTopLeft[1] = startCenter[1] - newBoundsHeight / 2;
     }
   }
 
   // Flip horizontally
-  if (newWidth < 0) {
+  if (eleNewWidth < 0) {
     if (transformHandleDirection.includes("e")) {
-      newTopLeft[0] -= Math.abs(newWidth);
+      newTopLeft[0] -= Math.abs(newBoundsWidth);
     }
     if (transformHandleDirection.includes("w")) {
-      newTopLeft[0] += Math.abs(newWidth);
+      newTopLeft[0] += Math.abs(newBoundsWidth);
     }
   }
   // Flip vertically
-  if (newHeight < 0) {
+  if (eleNewHeight < 0) {
     if (transformHandleDirection.includes("s")) {
-      newTopLeft[1] -= Math.abs(newHeight);
+      newTopLeft[1] -= Math.abs(newBoundsHeight);
     }
     if (transformHandleDirection.includes("n")) {
-      newTopLeft[1] += Math.abs(newHeight);
+      newTopLeft[1] += Math.abs(newBoundsHeight);
     }
   }
 
   if (isResizeFromCenter) {
-    newTopLeft[0] = startCenter[0] - Math.abs(newWidth) / 2;
-    newTopLeft[1] = startCenter[1] - Math.abs(newHeight) / 2;
+    newTopLeft[0] = startCenter[0] - Math.abs(newBoundsWidth) / 2;
+    newTopLeft[1] = startCenter[1] - Math.abs(newBoundsHeight) / 2;
   }
 
   // adjust topLeft to new rotation point
   const angle = stateAtResizeStart.angle;
   const rotatedTopLeft = rotatePoint(newTopLeft, startCenter, angle);
   const newCenter: Point = [
-    newTopLeft[0] + Math.abs(newWidth) / 2,
-    newTopLeft[1] + Math.abs(newHeight) / 2,
+    newTopLeft[0] + Math.abs(newBoundsWidth) / 2,
+    newTopLeft[1] + Math.abs(newBoundsHeight) / 2,
   ];
   const rotatedNewCenter = rotatePoint(newCenter, startCenter, angle);
   newTopLeft = rotatePoint(rotatedTopLeft, rotatedNewCenter, -angle);
 
-  const resizedElement = {
-    width: Math.abs(newWidth),
-    height: Math.abs(newHeight),
-    x: newTopLeft[0],
-    y: newTopLeft[1],
-  };
-  updateBoundElements(element, {
-    newSize: { width: resizedElement.width, height: resizedElement.height },
-  });
-  mutateElement(element, resizedElement);
-};
-
-const resizeSingleNonGenericElement = (
-  element: NonDeleted<Exclude<ExcalidrawElement, ExcalidrawGenericElement>>,
-  transformHandleType: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
-  isResizeFromCenter: boolean,
-  keepSquareAspectRatio: boolean,
-  pointerX: number,
-  pointerY: number,
-) => {
-  const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
-  const cx = (x1 + x2) / 2;
-  const cy = (y1 + y2) / 2;
-
-  // rotation pointer with reverse angle
-  const [rotatedX, rotatedY] = rotate(
-    pointerX,
-    pointerY,
-    cx,
-    cy,
-    -element.angle,
+  // Readjust points for linear elements
+  const rescaledPoints = rescalePointsInElement(
+    stateAtResizeStart,
+    eleNewWidth,
+    eleNewHeight,
   );
+  // For linear elements (x,y) are the coordinates of the first drawn point not the top-left corner
+  // So we need to readjust (x,y) to be where the first point should be
+  const newOrigin = [...newTopLeft];
+  newOrigin[0] += stateAtResizeStart.x - newBoundsX1;
+  newOrigin[1] += stateAtResizeStart.y - newBoundsY1;
 
-  let scaleX = 1;
-  let scaleY = 1;
-  if (
-    transformHandleType === "e" ||
-    transformHandleType === "ne" ||
-    transformHandleType === "se"
-  ) {
-    scaleX = (rotatedX - x1) / (x2 - x1);
-  }
-  if (
-    transformHandleType === "s" ||
-    transformHandleType === "sw" ||
-    transformHandleType === "se"
-  ) {
-    scaleY = (rotatedY - y1) / (y2 - y1);
-  }
-  if (
-    transformHandleType === "w" ||
-    transformHandleType === "nw" ||
-    transformHandleType === "sw"
-  ) {
-    scaleX = (x2 - rotatedX) / (x2 - x1);
-  }
-  if (
-    transformHandleType === "n" ||
-    transformHandleType === "nw" ||
-    transformHandleType === "ne"
-  ) {
-    scaleY = (y2 - rotatedY) / (y2 - y1);
-  }
-  let nextWidth = element.width * scaleX;
-  let nextHeight = element.height * scaleY;
-  if (keepSquareAspectRatio) {
-    nextWidth = nextHeight = Math.max(nextWidth, nextHeight);
-  }
-
-  const [nextX1, nextY1, nextX2, nextY2] = getResizedElementAbsoluteCoords(
-    element,
-    nextWidth,
-    nextHeight,
-  );
-  const deltaX1 = (x1 - nextX1) / 2;
-  const deltaY1 = (y1 - nextY1) / 2;
-  const deltaX2 = (x2 - nextX2) / 2;
-  const deltaY2 = (y2 - nextY2) / 2;
-
-  const rescaledPoints = rescalePointsInElement(element, nextWidth, nextHeight);
-
-  updateBoundElements(element, {
-    newSize: { width: nextWidth, height: nextHeight },
-  });
-  const [finalX1, finalY1, finalX2, finalY2] = getResizedElementAbsoluteCoords(
-    {
-      ...element,
-      ...rescaledPoints,
-    },
-    Math.abs(nextWidth),
-    Math.abs(nextHeight),
-  );
-  const [flipDiffX, flipDiffY] = getFlipAdjustment(
-    transformHandleType,
-    nextWidth,
-    nextHeight,
-    nextX1,
-    nextY1,
-    nextX2,
-    nextY2,
-    finalX1,
-    finalY1,
-    finalX2,
-    finalY2,
-    isLinearElement(element),
-    element.angle,
-  );
-  const [nextElementX, nextElementY] = adjustXYWithRotation(
-    getSidesForTransformHandle(transformHandleType, isResizeFromCenter),
-    element.x - flipDiffX,
-    element.y - flipDiffY,
-    element.angle,
-    deltaX1,
-    deltaY1,
-    deltaX2,
-    deltaY2,
-  );
+  const resizedElement = {
+    width: Math.abs(eleNewWidth),
+    height: Math.abs(eleNewHeight),
+    x: newOrigin[0],
+    y: newOrigin[1],
+    ...rescaledPoints,
+  };
 
   if (
-    nextWidth !== 0 &&
-    nextHeight !== 0 &&
-    Number.isFinite(nextElementX) &&
-    Number.isFinite(nextElementY)
+    resizedElement.width !== 0 &&
+    resizedElement.height !== 0 &&
+    Number.isFinite(resizedElement.x) &&
+    Number.isFinite(resizedElement.y)
   ) {
-    mutateElement(element, {
-      width: nextWidth,
-      height: nextHeight,
-      x: nextElementX,
-      y: nextElementY,
-      ...rescaledPoints,
+    updateBoundElements(element, {
+      newSize: { width: resizedElement.width, height: resizedElement.height },
     });
+    mutateElement(element, resizedElement);
   }
 };
 
diff --git a/src/element/resizeTest.ts b/src/element/resizeTest.ts
index 4471244dd3..3a794e2c59 100644
--- a/src/element/resizeTest.ts
+++ b/src/element/resizeTest.ts
@@ -173,57 +173,3 @@ export const getCursorForResizingElement = (resizingElement: {
 
   return cursor ? `${cursor}-resize` : "";
 };
-
-export const normalizeTransformHandleType = (
-  element: ExcalidrawElement,
-  transformHandleType: TransformHandleType,
-): TransformHandleType => {
-  if (element.width >= 0 && element.height >= 0) {
-    return transformHandleType;
-  }
-
-  if (element.width < 0 && element.height < 0) {
-    switch (transformHandleType) {
-      case "nw":
-        return "se";
-      case "ne":
-        return "sw";
-      case "se":
-        return "nw";
-      case "sw":
-        return "ne";
-    }
-  } else if (element.width < 0) {
-    switch (transformHandleType) {
-      case "nw":
-        return "ne";
-      case "ne":
-        return "nw";
-      case "se":
-        return "sw";
-      case "sw":
-        return "se";
-      case "e":
-        return "w";
-      case "w":
-        return "e";
-    }
-  } else {
-    switch (transformHandleType) {
-      case "nw":
-        return "sw";
-      case "ne":
-        return "se";
-      case "se":
-        return "ne";
-      case "sw":
-        return "nw";
-      case "n":
-        return "s";
-      case "s":
-        return "n";
-    }
-  }
-
-  return transformHandleType;
-};
diff --git a/src/element/showSelectedShapeActions.ts b/src/element/showSelectedShapeActions.ts
index d0a7439c9d..545289812c 100644
--- a/src/element/showSelectedShapeActions.ts
+++ b/src/element/showSelectedShapeActions.ts
@@ -7,7 +7,8 @@ export const showSelectedShapeActions = (
   elements: readonly NonDeletedExcalidrawElement[],
 ) =>
   Boolean(
-    appState.editingElement ||
-      getSelectedElements(elements, appState).length ||
-      appState.elementType !== "selection",
+    !appState.viewModeEnabled &&
+      (appState.editingElement ||
+        getSelectedElements(elements, appState).length ||
+        appState.elementType !== "selection"),
   );
diff --git a/src/excalidraw-app/collab/CollabWrapper.tsx b/src/excalidraw-app/collab/CollabWrapper.tsx
index d27adb35de..c308a70d6d 100644
--- a/src/excalidraw-app/collab/CollabWrapper.tsx
+++ b/src/excalidraw-app/collab/CollabWrapper.tsx
@@ -6,10 +6,11 @@ import { APP_NAME, ENV, EVENT } from "../../constants";
 import { ImportedDataState } from "../../data/types";
 import { ExcalidrawElement } from "../../element/types";
 import {
+  getElementMap,
   getSceneVersion,
   getSyncableElements,
 } from "../../packages/excalidraw/index";
-import { AppState, Collaborator, Gesture } from "../../types";
+import { Collaborator, Gesture } from "../../types";
 import { resolvablePromise, withBatchedUpdates } from "../../utils";
 import {
   INITIAL_SCENE_UPDATE_TIMEOUT,
@@ -31,6 +32,7 @@ import {
 } from "../data/localStorage";
 import Portal from "./Portal";
 import RoomDialog from "./RoomDialog";
+import { createInverseContext } from "../../createInverseContext";
 
 interface CollabState {
   isCollaborating: boolean;
@@ -56,17 +58,21 @@ type ReconciledElements = readonly ExcalidrawElement[] & {
 };
 
 interface Props {
-  children: (collab: CollabAPI) => React.ReactNode;
-  // NOTE not type-safe because the refObject may in fact not be initialized
-  // with ExcalidrawImperativeAPI yet
-  excalidrawRef: React.MutableRefObject<ExcalidrawImperativeAPI>;
+  excalidrawAPI: ExcalidrawImperativeAPI;
 }
 
+const {
+  Context: CollabContext,
+  Consumer: CollabContextConsumer,
+  Provider: CollabContextProvider,
+} = createInverseContext<{ api: CollabAPI | null }>({ api: null });
+
+export { CollabContext, CollabContextConsumer };
+
 class CollabWrapper extends PureComponent<Props, CollabState> {
   portal: Portal;
+  excalidrawAPI: Props["excalidrawAPI"];
   private socketInitializationTimer?: NodeJS.Timeout;
-  private excalidrawRef: Props["excalidrawRef"];
-  excalidrawAppState?: AppState;
   private lastBroadcastedOrReceivedSceneVersion: number = -1;
   private collaborators = new Map<string, Collaborator>();
 
@@ -80,7 +86,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
       activeRoomLink: "",
     };
     this.portal = new Portal(this);
-    this.excalidrawRef = props.excalidrawRef;
+    this.excalidrawAPI = props.excalidrawAPI;
   }
 
   componentDidMount() {
@@ -142,7 +148,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
 
   saveCollabRoomToFirebase = async (
     syncableElements: ExcalidrawElement[] = getSyncableElements(
-      this.excalidrawRef.current!.getSceneElementsIncludingDeleted(),
+      this.excalidrawAPI.getSceneElementsIncludingDeleted(),
     ),
   ) => {
     try {
@@ -154,13 +160,13 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
 
   openPortal = async () => {
     window.history.pushState({}, APP_NAME, await generateCollaborationLink());
-    const elements = this.excalidrawRef.current!.getSceneElements();
+    const elements = this.excalidrawAPI.getSceneElements();
     // remove deleted elements from elements array & history to ensure we don't
     // expose potentially sensitive user data in case user manually deletes
     // existing elements (or clears scene), which would otherwise be persisted
     // to database even if deleted before creating the room.
-    this.excalidrawRef.current!.history.clear();
-    this.excalidrawRef.current!.updateScene({
+    this.excalidrawAPI.history.clear();
+    this.excalidrawAPI.updateScene({
       elements,
       commitToHistory: true,
     });
@@ -175,7 +181,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
 
   private destroySocketClient = () => {
     this.collaborators = new Map();
-    this.excalidrawRef.current!.updateScene({
+    this.excalidrawAPI.updateScene({
       collaborators: this.collaborators,
     });
     this.setState({
@@ -265,7 +271,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
               user.selectedElementIds = selectedElementIds;
               user.username = username;
               collaborators.set(socketId, user);
-              this.excalidrawRef.current!.updateScene({
+              this.excalidrawAPI.updateScene({
                 collaborators,
               });
               break;
@@ -300,7 +306,55 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
   private reconcileElements = (
     elements: readonly ExcalidrawElement[],
   ): ReconciledElements => {
-    const newElements = this.portal.reconcileElements(elements);
+    const currentElements = this.getSceneElementsIncludingDeleted();
+    // create a map of ids so we don't have to iterate
+    // over the array more than once.
+    const localElementMap = getElementMap(currentElements);
+
+    const appState = this.excalidrawAPI.getAppState();
+
+    // Reconcile
+    const newElements: readonly ExcalidrawElement[] = elements
+      .reduce((elements, element) => {
+        // if the remote element references one that's currently
+        // edited on local, skip it (it'll be added in the next step)
+        if (
+          element.id === appState.editingElement?.id ||
+          element.id === appState.resizingElement?.id ||
+          element.id === appState.draggingElement?.id
+        ) {
+          return elements;
+        }
+
+        if (
+          localElementMap.hasOwnProperty(element.id) &&
+          localElementMap[element.id].version > element.version
+        ) {
+          elements.push(localElementMap[element.id]);
+          delete localElementMap[element.id];
+        } else if (
+          localElementMap.hasOwnProperty(element.id) &&
+          localElementMap[element.id].version === element.version &&
+          localElementMap[element.id].versionNonce !== element.versionNonce
+        ) {
+          // resolve conflicting edits deterministically by taking the one with the lowest versionNonce
+          if (localElementMap[element.id].versionNonce < element.versionNonce) {
+            elements.push(localElementMap[element.id]);
+          } else {
+            // it should be highly unlikely that the two versionNonces are the same. if we are
+            // really worried about this, we can replace the versionNonce with the socket id.
+            elements.push(element);
+          }
+          delete localElementMap[element.id];
+        } else {
+          elements.push(element);
+          delete localElementMap[element.id];
+        }
+
+        return elements;
+      }, [] as Mutable<typeof elements>)
+      // add local elements that weren't deleted or on remote
+      .concat(...Object.values(localElementMap));
 
     // Avoid broadcasting to the rest of the collaborators the scene
     // we just received!
@@ -319,10 +373,10 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
     }: { init?: boolean; initFromSnapshot?: boolean } = {},
   ) => {
     if (init || initFromSnapshot) {
-      this.excalidrawRef.current!.setScrollToCenter(elements);
+      this.excalidrawAPI.setScrollToCenter(elements);
     }
 
-    this.excalidrawRef.current!.updateScene({
+    this.excalidrawAPI.updateScene({
       elements,
       commitToHistory: !!init,
     });
@@ -331,7 +385,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
     // when we receive any messages from another peer. This UX can be pretty rough -- if you
     // undo, a user makes a change, and then try to redo, your element(s) will be lost. However,
     // right now we think this is the right tradeoff.
-    this.excalidrawRef.current!.history.clear();
+    this.excalidrawAPI.history.clear();
   };
 
   setCollaborators(sockets: string[]) {
@@ -347,7 +401,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
         }
       }
       this.collaborators = collaborators;
-      this.excalidrawRef.current!.updateScene({ collaborators });
+      this.excalidrawAPI.updateScene({ collaborators });
     });
   }
 
@@ -360,7 +414,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
   };
 
   public getSceneElementsIncludingDeleted = () => {
-    return this.excalidrawRef.current!.getSceneElementsIncludingDeleted();
+    return this.excalidrawAPI.getSceneElementsIncludingDeleted();
   };
 
   onPointerUpdate = (payload: {
@@ -373,11 +427,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
       this.portal.broadcastMouseLocation(payload);
   };
 
-  broadcastElements = (
-    elements: readonly ExcalidrawElement[],
-    state: AppState,
-  ) => {
-    this.excalidrawAppState = state;
+  broadcastElements = (elements: readonly ExcalidrawElement[]) => {
     if (
       getSceneVersion(elements) >
       this.getLastBroadcastedOrReceivedSceneVersion()
@@ -396,7 +446,7 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
     this.portal.broadcastScene(
       SCENE.UPDATE,
       getSyncableElements(
-        this.excalidrawRef.current!.getSceneElementsIncludingDeleted(),
+        this.excalidrawAPI.getSceneElementsIncludingDeleted(),
       ),
       true,
     );
@@ -425,8 +475,23 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
     });
   };
 
+  /** PRIVATE. Use `this.getContextValue()` instead. */
+  private contextValue: CollabAPI | null = null;
+
+  /** Getter of context value. Returned object is stable. */
+  getContextValue = (): CollabAPI => {
+    this.contextValue = this.contextValue || ({} as CollabAPI);
+
+    this.contextValue.isCollaborating = this.state.isCollaborating;
+    this.contextValue.username = this.state.username;
+    this.contextValue.onPointerUpdate = this.onPointerUpdate;
+    this.contextValue.initializeSocketClient = this.initializeSocketClient;
+    this.contextValue.onCollabButtonClick = this.onCollabButtonClick;
+    this.contextValue.broadcastElements = this.broadcastElements;
+    return this.contextValue;
+  };
+
   render() {
-    const { children } = this.props;
     const { modalIsShown, username, errorMessage, activeRoomLink } = this.state;
 
     return (
@@ -450,14 +515,11 @@ class CollabWrapper extends PureComponent<Props, CollabState> {
             onClose={() => this.setState({ errorMessage: "" })}
           />
         )}
-        {children({
-          isCollaborating: this.state.isCollaborating,
-          username: this.state.username,
-          onPointerUpdate: this.onPointerUpdate,
-          initializeSocketClient: this.initializeSocketClient,
-          onCollabButtonClick: this.onCollabButtonClick,
-          broadcastElements: this.broadcastElements,
-        })}
+        <CollabContextProvider
+          value={{
+            api: this.getContextValue(),
+          }}
+        />
       </>
     );
   }
diff --git a/src/excalidraw-app/collab/Portal.tsx b/src/excalidraw-app/collab/Portal.tsx
index 2b66b0bd05..92086a27b5 100644
--- a/src/excalidraw-app/collab/Portal.tsx
+++ b/src/excalidraw-app/collab/Portal.tsx
@@ -6,23 +6,20 @@ import {
 
 import CollabWrapper from "./CollabWrapper";
 
-import {
-  getElementMap,
-  getSyncableElements,
-} from "../../packages/excalidraw/index";
+import { getSyncableElements } from "../../packages/excalidraw/index";
 import { ExcalidrawElement } from "../../element/types";
 import { BROADCAST, SCENE } from "../app_constants";
 
 class Portal {
-  app: CollabWrapper;
+  collab: CollabWrapper;
   socket: SocketIOClient.Socket | null = null;
   socketInitialized: boolean = false; // we don't want the socket to emit any updates until it is fully initialized
   roomId: string | null = null;
   roomKey: string | null = null;
   broadcastedElementVersions: Map<string, number> = new Map();
 
-  constructor(app: CollabWrapper) {
-    this.app = app;
+  constructor(collab: CollabWrapper) {
+    this.collab = collab;
   }
 
   open(socket: SocketIOClient.Socket, id: string, key: string) {
@@ -30,7 +27,7 @@ class Portal {
     this.roomId = id;
     this.roomKey = key;
 
-    // Initialize socket listeners (moving from App)
+    // Initialize socket listeners
     this.socket.on("init-room", () => {
       if (this.socket) {
         this.socket.emit("join-room", this.roomId);
@@ -39,12 +36,12 @@ class Portal {
     this.socket.on("new-user", async (_socketId: string) => {
       this.broadcastScene(
         SCENE.INIT,
-        getSyncableElements(this.app.getSceneElementsIncludingDeleted()),
+        getSyncableElements(this.collab.getSceneElementsIncludingDeleted()),
         /* syncAll */ true,
       );
     });
     this.socket.on("room-user-change", (clients: string[]) => {
-      this.app.setCollaborators(clients);
+      this.collab.setCollaborators(clients);
     });
   }
 
@@ -125,10 +122,10 @@ class Portal {
       data as SocketUpdateData,
     );
 
-    if (syncAll && this.app.state.isCollaborating) {
+    if (syncAll && this.collab.state.isCollaborating) {
       await Promise.all([
         broadcastPromise,
-        this.app.saveCollabRoomToFirebase(syncableElements),
+        this.collab.saveCollabRoomToFirebase(syncableElements),
       ]);
     } else {
       await broadcastPromise;
@@ -146,9 +143,9 @@ class Portal {
           socketId: this.socket.id,
           pointer: payload.pointer,
           button: payload.button || "up",
-          selectedElementIds:
-            this.app.excalidrawAppState?.selectedElementIds || {},
-          username: this.app.state.username,
+          selectedElementIds: this.collab.excalidrawAPI.getAppState()
+            .selectedElementIds,
+          username: this.collab.state.username,
         },
       };
       return this._broadcastSocketData(
@@ -157,62 +154,6 @@ class Portal {
       );
     }
   };
-
-  reconcileElements = (
-    sceneElements: readonly ExcalidrawElement[],
-  ): readonly ExcalidrawElement[] => {
-    const currentElements = this.app.getSceneElementsIncludingDeleted();
-    // create a map of ids so we don't have to iterate
-    // over the array more than once.
-    const localElementMap = getElementMap(currentElements);
-
-    // Reconcile
-    return (
-      sceneElements
-        .reduce((elements, element) => {
-          // if the remote element references one that's currently
-          // edited on local, skip it (it'll be added in the next step)
-          if (
-            element.id === this.app.excalidrawAppState?.editingElement?.id ||
-            element.id === this.app.excalidrawAppState?.resizingElement?.id ||
-            element.id === this.app.excalidrawAppState?.draggingElement?.id
-          ) {
-            return elements;
-          }
-
-          if (
-            localElementMap.hasOwnProperty(element.id) &&
-            localElementMap[element.id].version > element.version
-          ) {
-            elements.push(localElementMap[element.id]);
-            delete localElementMap[element.id];
-          } else if (
-            localElementMap.hasOwnProperty(element.id) &&
-            localElementMap[element.id].version === element.version &&
-            localElementMap[element.id].versionNonce !== element.versionNonce
-          ) {
-            // resolve conflicting edits deterministically by taking the one with the lowest versionNonce
-            if (
-              localElementMap[element.id].versionNonce < element.versionNonce
-            ) {
-              elements.push(localElementMap[element.id]);
-            } else {
-              // it should be highly unlikely that the two versionNonces are the same. if we are
-              // really worried about this, we can replace the versionNonce with the socket id.
-              elements.push(element);
-            }
-            delete localElementMap[element.id];
-          } else {
-            elements.push(element);
-            delete localElementMap[element.id];
-          }
-
-          return elements;
-        }, [] as Mutable<typeof sceneElements>)
-        // add local elements that weren't deleted or on remote
-        .concat(...Object.values(localElementMap))
-    );
-  };
 }
 
 export default Portal;
diff --git a/src/excalidraw-app/collab/RoomDialog.scss b/src/excalidraw-app/collab/RoomDialog.scss
index de784d93d2..5a045136ad 100644
--- a/src/excalidraw-app/collab/RoomDialog.scss
+++ b/src/excalidraw-app/collab/RoomDialog.scss
@@ -1,4 +1,4 @@
-@import "../../css/_variables";
+@import "../../css/variables.module";
 
 .excalidraw {
   .RoomDialog-linkContainer {
diff --git a/src/excalidraw-app/components/LanguageList.tsx b/src/excalidraw-app/components/LanguageList.tsx
index 4b707907ea..18f38a320d 100644
--- a/src/excalidraw-app/components/LanguageList.tsx
+++ b/src/excalidraw-app/components/LanguageList.tsx
@@ -25,7 +25,6 @@ export const LanguageList = ({
       <option key={i18n.defaultLang.code} value={i18n.defaultLang.code}>
         {i18n.defaultLang.label}
       </option>
-      <option disabled>{"──────────"}</option>
       {languages.map((lang) => (
         <option key={lang.code} value={lang.code}>
           {lang.label}
diff --git a/src/excalidraw-app/index.tsx b/src/excalidraw-app/index.tsx
index d63eb71b6a..fcc7f45f79 100644
--- a/src/excalidraw-app/index.tsx
+++ b/src/excalidraw-app/index.tsx
@@ -1,6 +1,7 @@
 import LanguageDetector from "i18next-browser-languagedetector";
 import React, {
   useCallback,
+  useContext,
   useEffect,
   useLayoutEffect,
   useRef,
@@ -11,18 +12,19 @@ import { getDefaultAppState } from "../appState";
 import { ExcalidrawImperativeAPI } from "../components/App";
 import { ErrorDialog } from "../components/ErrorDialog";
 import { TopErrorBoundary } from "../components/TopErrorBoundary";
-import { APP_NAME, EVENT, TITLE_TIMEOUT } from "../constants";
+import { APP_NAME, EVENT, TITLE_TIMEOUT, VERSION_TIMEOUT } from "../constants";
 import { ImportedDataState } from "../data/types";
 import {
   ExcalidrawElement,
   NonDeletedExcalidrawElement,
 } from "../element/types";
+import { useCallbackRefState } from "../hooks/useCallbackRefState";
 import { Language, t } from "../i18n";
 import Excalidraw, {
   defaultLang,
   languages,
 } from "../packages/excalidraw/index";
-import { AppState, ExcalidrawAPIRefValue } from "../types";
+import { AppState } from "../types";
 import {
   debounce,
   getVersion,
@@ -30,7 +32,11 @@ import {
   resolvablePromise,
 } from "../utils";
 import { SAVE_TO_LOCAL_STORAGE_TIMEOUT } from "./app_constants";
-import CollabWrapper, { CollabAPI } from "./collab/CollabWrapper";
+import CollabWrapper, {
+  CollabAPI,
+  CollabContext,
+  CollabContextConsumer,
+} from "./collab/CollabWrapper";
 import { LanguageList } from "./components/LanguageList";
 import { exportToBackend, getCollaborationLinkData, loadScene } from "./data";
 import { loadFromFirebase } from "./data/firebase";
@@ -49,15 +55,6 @@ languageDetector.init({
   checkWhitelist: false,
 });
 
-const excalidrawRef: React.MutableRefObject<
-  MarkRequired<ExcalidrawAPIRefValue, "ready" | "readyPromise">
-> = {
-  current: {
-    readyPromise: resolvablePromise(),
-    ready: false,
-  },
-};
-
 const saveDebounced = debounce(
   (elements: readonly ExcalidrawElement[], state: AppState) => {
     saveToLocalStorage(elements, state);
@@ -191,7 +188,7 @@ const initializeScene = async (opts: {
   return null;
 };
 
-function ExcalidrawWrapper(props: { collab: CollabAPI }) {
+function ExcalidrawWrapper() {
   // dimensions
   // ---------------------------------------------------------------------------
 
@@ -226,31 +223,40 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
     initialStatePromiseRef.current.promise = resolvablePromise<ImportedDataState | null>();
   }
 
-  const { collab } = props;
+  useEffect(() => {
+    // Delayed so that the app has a time to load the latest SW
+    setTimeout(() => {
+      trackEvent("load", "version", getVersion());
+    }, VERSION_TIMEOUT);
+  }, []);
+
+  const [
+    excalidrawAPI,
+    excalidrawRefCallback,
+  ] = useCallbackRefState<ExcalidrawImperativeAPI>();
+
+  const collabAPI = useContext(CollabContext)?.api;
 
   useEffect(() => {
-    trackEvent("load", "version", getVersion());
-    excalidrawRef.current!.readyPromise.then((excalidrawApi) => {
-      initializeScene({
-        resetScene: excalidrawApi.resetScene,
-        initializeSocketClient: collab.initializeSocketClient,
-      }).then((scene) => {
-        initialStatePromiseRef.current.promise.resolve(scene);
-      });
+    if (!collabAPI || !excalidrawAPI) {
+      return;
+    }
+
+    initializeScene({
+      resetScene: excalidrawAPI.resetScene,
+      initializeSocketClient: collabAPI.initializeSocketClient,
+    }).then((scene) => {
+      initialStatePromiseRef.current.promise.resolve(scene);
     });
 
     const onHashChange = (_: HashChangeEvent) => {
-      const api = excalidrawRef.current!;
-      if (!api.ready) {
-        return;
-      }
       if (window.location.hash.length > 1) {
         initializeScene({
-          resetScene: api.resetScene,
-          initializeSocketClient: collab.initializeSocketClient,
+          resetScene: excalidrawAPI.resetScene,
+          initializeSocketClient: collabAPI.initializeSocketClient,
         }).then((scene) => {
           if (scene) {
-            api.updateScene(scene);
+            excalidrawAPI.updateScene(scene);
           }
         });
       }
@@ -269,7 +275,7 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
       window.removeEventListener(EVENT.BLUR, onBlur, false);
       clearTimeout(titleTimeout);
     };
-  }, [collab.initializeSocketClient]);
+  }, [collabAPI, excalidrawAPI]);
 
   useEffect(() => {
     languageDetector.cacheUserLanguage(langCode);
@@ -280,8 +286,8 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
     appState: AppState,
   ) => {
     saveDebounced(elements, appState);
-    if (collab.isCollaborating) {
-      collab.broadcastElements(elements, appState);
+    if (collabAPI?.isCollaborating) {
+      collabAPI.broadcastElements(elements);
     }
   };
 
@@ -339,19 +345,20 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
   return (
     <>
       <Excalidraw
-        ref={excalidrawRef}
+        ref={excalidrawRefCallback}
         onChange={onChange}
         width={dimensions.width}
         height={dimensions.height}
         initialData={initialStatePromiseRef.current.promise}
-        user={{ name: collab.username }}
-        onCollabButtonClick={collab.onCollabButtonClick}
-        isCollaborating={collab.isCollaborating}
-        onPointerUpdate={collab.onPointerUpdate}
+        user={{ name: collabAPI?.username }}
+        onCollabButtonClick={collabAPI?.onCollabButtonClick}
+        isCollaborating={collabAPI?.isCollaborating}
+        onPointerUpdate={collabAPI?.onPointerUpdate}
         onExportToBackend={onExportToBackend}
         renderFooter={renderFooter}
         langCode={langCode}
       />
+      {excalidrawAPI && <CollabWrapper excalidrawAPI={excalidrawAPI} />}
       {errorMessage && (
         <ErrorDialog
           message={errorMessage}
@@ -365,13 +372,9 @@ function ExcalidrawWrapper(props: { collab: CollabAPI }) {
 export default function ExcalidrawApp() {
   return (
     <TopErrorBoundary>
-      <CollabWrapper
-        excalidrawRef={
-          excalidrawRef as React.MutableRefObject<ExcalidrawImperativeAPI>
-        }
-      >
-        {(collab) => <ExcalidrawWrapper collab={collab} />}
-      </CollabWrapper>
+      <CollabContextConsumer>
+        <ExcalidrawWrapper />
+      </CollabContextConsumer>
     </TopErrorBoundary>
   );
 }
diff --git a/src/gesture.ts b/src/gesture.ts
index 66b7ef4850..4592338bfb 100644
--- a/src/gesture.ts
+++ b/src/gesture.ts
@@ -1,11 +1,10 @@
 import { PointerCoords } from "./types";
-import { normalizeScroll } from "./scene";
 
 export const getCenter = (pointers: Map<number, PointerCoords>) => {
   const allCoords = Array.from(pointers.values());
   return {
-    x: normalizeScroll(sum(allCoords, (coords) => coords.x) / allCoords.length),
-    y: normalizeScroll(sum(allCoords, (coords) => coords.y) / allCoords.length),
+    x: sum(allCoords, (coords) => coords.x) / allCoords.length,
+    y: sum(allCoords, (coords) => coords.y) / allCoords.length,
   };
 };
 
diff --git a/src/global.d.ts b/src/global.d.ts
index 8bce07a719..5596d3ea0e 100644
--- a/src/global.d.ts
+++ b/src/global.d.ts
@@ -85,6 +85,6 @@ type ForwardRef<T, P = any> = Parameters<
 // --------------------------------------------------------------------------—
 
 interface Blob {
-  handle?: import("browser-nativefs").FileSystemHandle;
+  handle?: import("browser-fs-acces").FileSystemHandle;
   name?: string;
 }
diff --git a/src/hooks/useCallbackRefState.ts b/src/hooks/useCallbackRefState.ts
new file mode 100644
index 0000000000..4a8552b584
--- /dev/null
+++ b/src/hooks/useCallbackRefState.ts
@@ -0,0 +1,7 @@
+import { useCallback, useState } from "react";
+
+export const useCallbackRefState = <T>() => {
+  const [refValue, setRefValue] = useState<T | null>(null);
+  const refCallback = useCallback((value: T | null) => setRefValue(value), []);
+  return [refValue, refCallback] as const;
+};
diff --git a/src/i18n.ts b/src/i18n.ts
index 44b9eeabaa..8b493680c8 100644
--- a/src/i18n.ts
+++ b/src/i18n.ts
@@ -27,6 +27,7 @@ const allLanguages: Language[] = [
   { code: "id-ID", label: "Bahasa Indonesia" },
   { code: "it-IT", label: "Italiano" },
   { code: "ja-JP", label: "日本語" },
+  { code: "kab-KAB", label: "Taqbaylit" },
   { code: "ko-KR", label: "한국어" },
   { code: "my-MM", label: "Burmese" },
   { code: "nb-NO", label: "Norsk bokmål" },
diff --git a/src/is-mobile.tsx b/src/is-mobile.tsx
index c20bf89de1..466fcd0d64 100644
--- a/src/is-mobile.tsx
+++ b/src/is-mobile.tsx
@@ -1,7 +1,18 @@
 import React, { useState, useEffect, useRef, useContext } from "react";
+import variables from "./css/variables.module.scss";
 
 const context = React.createContext(false);
 
+const getIsMobileMatcher = () => {
+  return window.matchMedia
+    ? window.matchMedia(variables.isMobileQuery)
+    : (({
+        matches: false,
+        addListener: () => {},
+        removeListener: () => {},
+      } as any) as MediaQueryList);
+};
+
 export const IsMobileProvider = ({
   children,
 }: {
@@ -9,16 +20,7 @@ export const IsMobileProvider = ({
 }) => {
   const query = useRef<MediaQueryList>();
   if (!query.current) {
-    query.current = window.matchMedia
-      ? window.matchMedia(
-          // keep up to date with _variables.scss
-          "(max-width: 640px), (max-height: 500px) and (max-width: 1000px)",
-        )
-      : (({
-          matches: false,
-          addListener: () => {},
-          removeListener: () => {},
-        } as any) as MediaQueryList);
+    query.current = getIsMobileMatcher();
   }
   const [isMobile, setMobile] = useState(query.current.matches);
 
@@ -31,6 +33,8 @@ export const IsMobileProvider = ({
   return <context.Provider value={isMobile}>{children}</context.Provider>;
 };
 
+export const isMobile = () => getIsMobileMatcher().matches;
+
 export default function useIsMobile() {
   return useContext(context);
 }
diff --git a/src/keys.ts b/src/keys.ts
index 9e0cd2ac9e..31baff5507 100644
--- a/src/keys.ts
+++ b/src/keys.ts
@@ -1,4 +1,5 @@
 export const isDarwin = /Mac|iPod|iPhone|iPad/.test(window.navigator.platform);
+export const isWindows = /^Win/.test(window.navigator.platform);
 
 export const CODES = {
   EQUAL: "Equal",
@@ -18,7 +19,9 @@ export const CODES = {
   F: "KeyF",
   H: "KeyH",
   V: "KeyV",
+  X: "KeyX",
   Z: "KeyZ",
+  R: "KeyR",
 } as const;
 
 export const KEYS = {
@@ -48,6 +51,7 @@ export const KEYS = {
   T: "t",
   V: "v",
   X: "x",
+  Y: "y",
   Z: "z",
 } as const;
 
diff --git a/src/locales/ar-SA.json b/src/locales/ar-SA.json
index 6e69ed5fc8..430dd9c675 100644
--- a/src/locales/ar-SA.json
+++ b/src/locales/ar-SA.json
@@ -80,9 +80,9 @@
     "gridMode": "وضع الشبكة",
     "addToLibrary": "أضف إلى المكتبة",
     "removeFromLibrary": "حذف من المكتبة",
-    "libraryLoadingMessage": "جارٍ تحميل المكتبة...",
+    "libraryLoadingMessage": "جارٍ تحميل المكتبة…",
     "libraries": "تصفح المكتبات",
-    "loadingScene": "جاري تحميل المشهد...",
+    "loadingScene": "جاري تحميل المشهد…",
     "align": "محاذاة",
     "alignTop": "محاذاة إلى اﻷعلى",
     "alignBottom": "محاذاة إلى اﻷسفل",
@@ -91,7 +91,8 @@
     "centerVertically": "توسيط عمودي",
     "centerHorizontally": "توسيط أفقي",
     "distributeHorizontally": "التوزيع الأفقي",
-    "distributeVertically": "التوزيع عمودياً"
+    "distributeVertically": "التوزيع عمودياً",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "إعادة تعيين اللوحة",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "خطأ"
   },
-  "shortcutsDialog": {
-    "title": "اختصارات لوحة المفاتيح",
-    "shapes": "الأشكال",
-    "or": "أو",
-    "click": "انقر",
-    "drag": "اسحب",
-    "curvedArrow": "سهم منحنى",
-    "curvedLine": "خط منحنى",
-    "editor": "المحرر",
-    "view": "المشهد",
-    "blog": "اقرأ مدونتنا",
-    "howto": "اتبع دليلنا",
-    "github": "عثرت على مشكلة؟ إرسال",
-    "textNewLine": "إضافة سطر جديد (نص)",
-    "textFinish": "الانتهاء من تحرير (النص)",
-    "zoomToFit": "تكبير لتلائم جميع العناصر",
-    "zoomToSelection": "تقريب للمحدد",
-    "preventBinding": "منع ربط السهم"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "رسوماتك مشفرة من النهاية إلى النهاية حتى أن خوادم Excalidraw لن تراها أبدا."
@@ -232,5 +235,9 @@
     "title": "إحصائيات للمهووسين",
     "total": "المجموع",
     "width": "العرض"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/bg-BG.json b/src/locales/bg-BG.json
index 2101a395d7..3a4dac74aa 100644
--- a/src/locales/bg-BG.json
+++ b/src/locales/bg-BG.json
@@ -80,9 +80,9 @@
     "gridMode": "Решетъчен режим",
     "addToLibrary": "Добавяне към библиотеката",
     "removeFromLibrary": "Премахване от библиотеката",
-    "libraryLoadingMessage": "Зареждане на библиотеката...",
+    "libraryLoadingMessage": "Зареждане на библиотеката…",
     "libraries": "Разглеждане на библиотеките",
-    "loadingScene": "Зареждане на сцена...",
+    "loadingScene": "Зареждане на сцена…",
     "align": "Подравняване",
     "alignTop": "Подравняване отгоре",
     "alignBottom": "Подравняване отдолу",
@@ -91,7 +91,8 @@
     "centerVertically": "Центрирай вертикално",
     "centerHorizontally": "Центрирай хоризонтално",
     "distributeHorizontally": "Разпредели хоризонтално",
-    "distributeVertically": "Разпредели вертикално"
+    "distributeVertically": "Разпредели вертикално",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "Нулиране на платно",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Грешка"
   },
-  "shortcutsDialog": {
-    "title": "Клавиши за бърз достъп",
-    "shapes": "Фигури",
-    "or": "или",
+  "helpDialog": {
+    "blog": "",
     "click": "клик",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
     "drag": "плъзнете",
-    "curvedArrow": "Извита стрелка",
-    "curvedLine": "Извита линия",
     "editor": "Редактор",
+    "github": "",
+    "howto": "",
+    "or": "или",
+    "preventBinding": "",
+    "shapes": "Фигури",
+    "shortcuts": "Клавиши за бърз достъп",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
     "view": "Преглед",
-    "blog": "Прочетете нашия блог",
-    "howto": "Следвайте нашите ръководства",
-    "github": "Намерихте проблем? Изпратете",
-    "textNewLine": "Добавяне на нов ред (текст)",
-    "textFinish": "Завършете редактиране (текст)",
-    "zoomToFit": "Приближи докато се виждат всички елементи",
-    "zoomToSelection": "Приближи селекцията",
-    "preventBinding": "Спри прилепяне на стрелките"
+    "zoomToFit": "",
+    "zoomToSelection": "Приближи селекцията"
   },
   "encrypted": {
     "tooltip": "Вашите рисунки са криптирани от край до край, така че сървърите на Excalidraw няма да могат да ги виждат."
@@ -232,5 +235,9 @@
     "title": "Статистика за хакери",
     "total": "Общо",
     "width": "Широчина"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/ca-ES.json b/src/locales/ca-ES.json
index 9fedba288f..39962d7075 100644
--- a/src/locales/ca-ES.json
+++ b/src/locales/ca-ES.json
@@ -30,17 +30,17 @@
     "edges": "Vores",
     "sharp": "Agut",
     "round": "Arrodonit",
-    "arrowheads": "Punta de fletxa",
+    "arrowheads": "Puntes de fletxa",
     "arrowhead_none": "Cap",
     "arrowhead_arrow": "Fletxa",
-    "arrowhead_bar": "Línia",
+    "arrowhead_bar": "Barra",
     "arrowhead_dot": "Punt",
     "fontSize": "Mida de lletra",
     "fontFamily": "Tipus de lletra",
     "onlySelected": "Només seleccionats",
     "withBackground": "Amb fons",
     "exportEmbedScene": "Incrustar escena al fitxer exportat",
-    "exportEmbedScene_details": "Les dades de l’escena es desaran al fitxer PNG/SVG exportat de manera que es pugui restaurar l’escena.\nAugmentarà la mida del fitxer exportat.",
+    "exportEmbedScene_details": "Les dades de l’escena es desaran al fitxer PNG/SVG de manera que es pugui restaurar l’escena.\nAugmentarà la mida del fitxer exportat.",
     "addWatermark": "Afegir \"Fet amb Excalidraw\"",
     "handDrawn": "Dibuixat a mà",
     "normal": "Normal",
@@ -61,7 +61,7 @@
     "architect": "Arquitecte",
     "artist": "Artista",
     "cartoonist": "Dibuixant",
-    "fileTitle": "Títol de fitxer",
+    "fileTitle": "Títol del fitxer",
     "colorPicker": "Selector de colors",
     "canvasBackground": "Fons del llenç",
     "drawingCanvas": "Llenç de dibuix",
@@ -82,7 +82,7 @@
     "removeFromLibrary": "Eliminar de la biblioteca",
     "libraryLoadingMessage": "Carregant la biblioteca...",
     "libraries": "Explorar biblioteques",
-    "loadingScene": "Carregant escena...",
+    "loadingScene": "Carregant escena…",
     "align": "Alinear",
     "alignTop": "Alinear a dalt",
     "alignBottom": "Alinear a baix",
@@ -91,7 +91,8 @@
     "centerVertically": "Centrar verticalment",
     "centerHorizontally": "Centrar horitzontalment",
     "distributeHorizontally": "Distribuir horitzontalment",
-    "distributeVertically": "Distribuir verticalment"
+    "distributeVertically": "Distribuir verticalment",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "Netejar el llenç",
@@ -127,7 +128,7 @@
   "alerts": {
     "clearReset": "Tot el llenç s'esborrarà. Estàs segur?",
     "couldNotCreateShareableLink": "No s'ha pogut crear un enllaç per compartir.",
-    "couldNotCreateShareableLinkTooBig": "No s’ha pogut crear un enllaç compartible: l’escena és massa gran",
+    "couldNotCreateShareableLinkTooBig": "No s’ha pogut crear un enllaç per compartir: l’escena és massa gran",
     "couldNotLoadInvalidFile": "No s'ha pogut carregar un fitxer no vàlid",
     "importBackendFailed": "Importació fallida.",
     "cannotExportEmptyCanvas": "No es pot exportar un llenç buit.",
@@ -162,7 +163,7 @@
     "freeDraw": "Fer clic i arrosegar, deixar anar al punt final",
     "text": "Consell: també pots afegir text fent doble clic a qualsevol lloc amb l'eina de selecció",
     "linearElementMulti": "Fer clic a l'ultim punt, o polsar Escape o Enter per acabar",
-    "lockAngle": "Pots restringir l’angle mantenint premuda MAJÚS",
+    "lockAngle": "Per restringir els angles, mantenir premut el majúscul (SHIFT)",
     "resize": "Per restringir les proporcions mentres es canvia la mida, mantenir premut el majúscul (SHIFT); per canviar la mida des del centre, mantenir premut ALT",
     "rotate": "Per restringir els angles mentre gira, mantenir premut el majúscul (SHIFT)",
     "lineEditor_info": "Fes doble clic o premi Enter per editar punts",
@@ -171,7 +172,7 @@
   },
   "canvasError": {
     "cannotShowPreview": "No es pot mostrar la vista prèvia",
-    "canvasTooBig": "El llenç pot ser massa gran.",
+    "canvasTooBig": "Pot ser que el llenç sigui massa gran.",
     "canvasTooBigTip": "Consell: prova d’acostar una mica els elements més allunyats."
   },
   "errorSplash": {
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Error"
   },
-  "shortcutsDialog": {
-    "title": "Dreceres de teclat",
-    "shapes": "Formes",
-    "or": "o",
-    "click": "fer clic",
-    "drag": "arrosegar",
-    "curvedArrow": "Fletxa curva",
-    "curvedLine": "Línea curva",
-    "editor": "Editor",
-    "view": "Vista",
-    "blog": "Llegir el nostre blog",
-    "howto": "Seguir els nostres guies",
-    "github": "Has trobat un problema? Enviar-ho",
-    "textNewLine": "Afegir línea nova (text)",
-    "textFinish": "Acabar d'editar (text)",
-    "zoomToFit": "Zoom per veure tots els elements",
-    "zoomToSelection": "Amplia la selecció",
-    "preventBinding": "Prevenir vinculació de la fletxa"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "Els vostres dibuixos estan xifrats de punta a punta de manera que els servidors d’Excalidraw no els veuran mai."
@@ -232,5 +235,9 @@
     "title": "Estadístiques per nerds",
     "total": "Total",
     "width": "Amplada"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/de-DE.json b/src/locales/de-DE.json
index 15f420b887..cc87fdaab6 100644
--- a/src/locales/de-DE.json
+++ b/src/locales/de-DE.json
@@ -80,9 +80,9 @@
     "gridMode": "Rastermodus",
     "addToLibrary": "Zur Bibliothek hinzufügen",
     "removeFromLibrary": "Aus Bibliothek entfernen",
-    "libraryLoadingMessage": "Lade Bibliothek...",
+    "libraryLoadingMessage": "Lade Bibliothek…",
     "libraries": "Bibliotheken durchsuchen",
-    "loadingScene": "Lade Zeichnung...",
+    "loadingScene": "Lade Zeichnung…",
     "align": "Ausrichten",
     "alignTop": "Obere Kanten",
     "alignBottom": "Untere Kanten",
@@ -91,7 +91,8 @@
     "centerVertically": "Vertikal zentrieren",
     "centerHorizontally": "Horizontal zentrieren",
     "distributeHorizontally": "Horizontal verteilen",
-    "distributeVertically": "Vertikal verteilen"
+    "distributeVertically": "Vertikal verteilen",
+    "viewMode": "Ansichtsmodus"
   },
   "buttons": {
     "clearReset": "Zeichenfläche löschen & Hintergrundfarbe zurücksetzen",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Fehler"
   },
-  "shortcutsDialog": {
-    "title": "Tastaturkürzel",
-    "shapes": "Formen",
-    "or": "oder",
+  "helpDialog": {
+    "blog": "Lies unseren Blog",
     "click": "klicken",
-    "drag": "ziehen",
     "curvedArrow": "Gebogener Pfeil",
     "curvedLine": "Gebogene Linie",
+    "documentation": "Dokumentation",
+    "drag": "ziehen",
     "editor": "Editor",
-    "view": "Ansicht",
-    "blog": "Unseren Blog lesen",
-    "howto": "Folge unseren Anleitungen",
     "github": "Ein Problem gefunden? Informiere uns",
-    "textNewLine": "Neue Zeile hinzufügen (Text)",
+    "howto": "Folge unseren Anleitungen",
+    "or": "oder",
+    "preventBinding": "Pfeil-Bindung verhindern",
+    "shapes": "Formen",
+    "shortcuts": "Tastaturkürzel",
     "textFinish": "Bearbeiten beenden (Text)",
+    "textNewLine": "Neue Zeile hinzufügen (Text)",
+    "title": "Hilfe",
+    "view": "Ansicht",
     "zoomToFit": "Zoomen um alle Elemente einzupassen",
-    "zoomToSelection": "Zoomauswahl",
-    "preventBinding": "Pfeil-Bindung verhindern"
+    "zoomToSelection": "Auf Auswahl zoomen"
   },
   "encrypted": {
     "tooltip": "Da deine Zeichnungen Ende-zu-Ende verschlüsselt werden, sehen auch unsere Excalidraw-Server sie niemals."
@@ -232,5 +235,9 @@
     "title": "Statistiken für Nerds",
     "total": "Gesamt",
     "width": "Breite"
+  },
+  "toast": {
+    "copyStyles": "Formatierung kopiert.",
+    "copyToClipboardAsPng": "In die Zwischenablage als PNG kopiert."
   }
 }
diff --git a/src/locales/el-GR.json b/src/locales/el-GR.json
index 5c91f44b3c..de751d14b8 100644
--- a/src/locales/el-GR.json
+++ b/src/locales/el-GR.json
@@ -80,9 +80,9 @@
     "gridMode": "Εμφάνιση σε πλέγμα",
     "addToLibrary": "Προσθήκη στη βιβλιοθήκη",
     "removeFromLibrary": "Αφαίρεση από τη βιβλιοθήκη",
-    "libraryLoadingMessage": "Φόρτωση βιβλιοθήκης...",
+    "libraryLoadingMessage": "Φόρτωση βιβλιοθήκης…",
     "libraries": "Άλλες βιβλιοθήκες",
-    "loadingScene": "Φόρτωση σκηνής...",
+    "loadingScene": "Φόρτωση σκηνής…",
     "align": "Στοίχιση",
     "alignTop": "Στοίχιση πάνω",
     "alignBottom": "Στοίχιση κάτω",
@@ -91,7 +91,8 @@
     "centerVertically": "Κέντρο κάθετα",
     "centerHorizontally": "Κέντρο οριζόντια",
     "distributeHorizontally": "Οριζόντια κατανομή",
-    "distributeVertically": "Κατακόρυφη κατανομή"
+    "distributeVertically": "Κατακόρυφη κατανομή",
+    "viewMode": "Λειτουργία προβολής"
   },
   "buttons": {
     "clearReset": "Επαναφορά του καμβά",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Σφάλμα"
   },
-  "shortcutsDialog": {
-    "title": "Συντομεύσεις πληκτρολογίου",
-    "shapes": "Σχήματα",
-    "or": "ή",
+  "helpDialog": {
+    "blog": "Διαβάστε το Blog μας",
     "click": "κλικ",
-    "drag": "σύρε",
     "curvedArrow": "Κυρτό βέλος",
     "curvedLine": "Κυρτή γραμμή",
+    "documentation": "Εγχειρίδιο",
+    "drag": "σύρε",
     "editor": "Επεξεργαστής",
-    "view": "Προβολή",
-    "blog": "Διαβάστε το ιστολόγιο μας",
-    "howto": "Ακολουθήστε τους οδηγούς μας",
     "github": "Βρήκατε πρόβλημα; Υποβάλετε το",
-    "textNewLine": "Προσθήκη νέας γραμμής (κείμενο)",
+    "howto": "Ακολουθήστε τους οδηγούς μας",
+    "or": "ή",
+    "preventBinding": "Αποτροπή δέσμευσης βέλων",
+    "shapes": "Σχήματα",
+    "shortcuts": "Συντομεύσεις πληκτρολογίου",
     "textFinish": "Ολοκλήρωση επεξεργασίας (κείμενο)",
+    "textNewLine": "Προσθήκη νέας γραμμής (κείμενο)",
+    "title": "Βοήθεια",
+    "view": "Προβολή",
     "zoomToFit": "Zoom ώστε να χωρέσουν όλα τα στοιχεία",
-    "zoomToSelection": "Εστίαση στην επιλογή",
-    "preventBinding": "Αποτροπή δέσμευσης βέλων"
+    "zoomToSelection": "Ζουμ στην επιλογή"
   },
   "encrypted": {
     "tooltip": "Τα σχέδιά σου είναι κρυπτογραφημένα από άκρο σε άκρο, έτσι δεν θα έιναι ποτέ ορατά μέσα από τους διακομιστές του Excalidraw."
@@ -232,5 +235,9 @@
     "title": "Στατιστικά για σπασίκλες",
     "total": "Σύνολο ",
     "width": "Πλάτος"
+  },
+  "toast": {
+    "copyStyles": "Αντιγράφηκαν στυλ.",
+    "copyToClipboardAsPng": "Αντιγράφτηκε στο πρόχειρο ως PNG."
   }
 }
diff --git a/src/locales/en.json b/src/locales/en.json
index b48b10565b..f6704ec25d 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -80,9 +80,9 @@
     "gridMode": "Grid mode",
     "addToLibrary": "Add to library",
     "removeFromLibrary": "Remove from library",
-    "libraryLoadingMessage": "Loading library...",
+    "libraryLoadingMessage": "Loading library…",
     "libraries": "Browse libraries",
-    "loadingScene": "Loading scene...",
+    "loadingScene": "Loading scene…",
     "align": "Align",
     "alignTop": "Align top",
     "alignBottom": "Align bottom",
@@ -91,7 +91,8 @@
     "centerVertically": "Center vertically",
     "centerHorizontally": "Center horizontally",
     "distributeHorizontally": "Distribute horizontally",
-    "distributeVertically": "Distribute vertically"
+    "distributeVertically": "Distribute vertically",
+    "viewMode": "View mode"
   },
   "buttons": {
     "clearReset": "Reset the canvas",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Error"
   },
-  "shortcutsDialog": {
-    "title": "Keyboard shortcuts",
-    "shapes": "Shapes",
-    "or": "or",
+  "helpDialog": {
+    "blog": "Read our blog",
     "click": "click",
-    "drag": "drag",
     "curvedArrow": "Curved arrow",
     "curvedLine": "Curved line",
+    "documentation": "Documentation",
+    "drag": "drag",
     "editor": "Editor",
-    "view": "View",
-    "blog": "Read our blog",
-    "howto": "Follow our guides",
     "github": "Found an issue? Submit",
-    "textNewLine": "Add new line (text)",
+    "howto": "Follow our guides",
+    "or": "or",
+    "preventBinding": "Prevent arrow binding",
+    "shapes": "Shapes",
+    "shortcuts": "Keyboard shortcuts",
     "textFinish": "Finish editing (text)",
+    "textNewLine": "Add new line (text)",
+    "title": "Help",
+    "view": "View",
     "zoomToFit": "Zoom to fit all elements",
-    "zoomToSelection": "Zoom to selection",
-    "preventBinding": "Prevent arrow binding"
+    "zoomToSelection": "Zoom to selection"
   },
   "encrypted": {
     "tooltip": "Your drawings are end-to-end encrypted so Excalidraw's servers will never see them."
@@ -232,5 +235,9 @@
     "title": "Stats for nerds",
     "total": "Total",
     "width": "Width"
+  },
+  "toast": {
+    "copyStyles": "Copied styles.",
+    "copyToClipboardAsPng": "Copied to clipboard as PNG."
   }
 }
diff --git a/src/locales/es-ES.json b/src/locales/es-ES.json
index f83d779b41..fb54a7cbab 100644
--- a/src/locales/es-ES.json
+++ b/src/locales/es-ES.json
@@ -91,7 +91,8 @@
     "centerVertically": "Centrar verticalmente",
     "centerHorizontally": "Centrar horizontalmente",
     "distributeHorizontally": "Distribuir horizontalmente",
-    "distributeVertically": "Distribuir verticalmente"
+    "distributeVertically": "Distribuir verticalmente",
+    "viewMode": "Modo presentación"
   },
   "buttons": {
     "clearReset": "Limpiar lienzo y reiniciar el color de fondo",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Error"
   },
-  "shortcutsDialog": {
-    "title": "Atajos del teclado",
-    "shapes": "Formas",
-    "or": "o",
-    "click": "clic",
-    "drag": "arrastrar",
+  "helpDialog": {
+    "blog": "Lee nuestro blog",
+    "click": "click",
     "curvedArrow": "Flecha curvada",
     "curvedLine": "Línea curva",
+    "documentation": "Documentación",
+    "drag": "arrastrar",
     "editor": "Editor",
-    "view": "Vista",
-    "blog": "Lee nuestro blog",
-    "howto": "Siga nuestras guías",
     "github": "¿Has encontrado un problema? Envíalo",
-    "textNewLine": "Añadir nueva línea (texto)",
+    "howto": "Siga nuestras guías",
+    "or": "o",
+    "preventBinding": "Evitar yuxtaposición de flechas",
+    "shapes": "Formas",
+    "shortcuts": "Atajos del teclado",
     "textFinish": "Finalizar edición (texto)",
+    "textNewLine": "Añadir nueva línea (texto)",
+    "title": "Ayuda",
+    "view": "Vista",
     "zoomToFit": "Ajustar la vista para mostrar todos los elementos",
-    "zoomToSelection": "Hacer zoom a la selección",
-    "preventBinding": "Evitar yuxtaposición de flechas"
+    "zoomToSelection": "Hacer zoom a la selección"
   },
   "encrypted": {
     "tooltip": "Tus dibujos están cifrados de punto a punto, por lo que los servidores de Excalidraw nunca los verán."
@@ -232,5 +235,9 @@
     "title": "Estadísticas para nerds",
     "total": "Total",
     "width": "Ancho"
+  },
+  "toast": {
+    "copyStyles": "Estilos copiados.",
+    "copyToClipboardAsPng": "Copiado al portapapeles como PNG."
   }
 }
diff --git a/src/locales/fa-IR.json b/src/locales/fa-IR.json
index 2299493012..d29a1e093a 100644
--- a/src/locales/fa-IR.json
+++ b/src/locales/fa-IR.json
@@ -80,9 +80,9 @@
     "gridMode": "حالت شبکه ای",
     "addToLibrary": "افزودن به کتابخانه",
     "removeFromLibrary": "حذف از کتابخانه",
-    "libraryLoadingMessage": "بارگذاری کتابخانه...",
+    "libraryLoadingMessage": "بارگذاری کتابخانه…",
     "libraries": "مرور کردن کتابخانه ها",
-    "loadingScene": "باگذاری صحنه...",
+    "loadingScene": "باگذاری صحنه…",
     "align": "تراز",
     "alignTop": "تراز به بالا",
     "alignBottom": "تراز به پایین",
@@ -91,7 +91,8 @@
     "centerVertically": "وسط قرار دادن به صورت عمودی",
     "centerHorizontally": "وسط قرار دادن به صورت افقی",
     "distributeHorizontally": "توزیع کردن به صورت افقی",
-    "distributeVertically": "توزیع کردن به صورت عمودی"
+    "distributeVertically": "توزیع کردن به صورت عمودی",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "پاکسازی بوم نقاشی",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "خطا"
   },
-  "shortcutsDialog": {
-    "title": "میانبرهای صفحه کلید",
-    "shapes": "شکل‌ها",
-    "or": "یا",
-    "click": "کلیک",
-    "drag": "کشیدن",
+  "helpDialog": {
+    "blog": "بلاگ ما را بخوانید",
+    "click": "",
     "curvedArrow": "فلش خمیده",
     "curvedLine": "منحنی",
+    "documentation": "مستندات",
+    "drag": "",
     "editor": "ویرایشگر",
-    "view": "نمایش",
-    "blog": "بلاگ ما را بخوانید",
-    "howto": "راهنمای ما را دنبال کنید",
     "github": "اشکالی می بینید؟ گزارش دهید",
+    "howto": "راهنمای ما را دنبال کنید",
+    "or": "یا",
+    "preventBinding": "مانع شدن از چسبیدن فلش ها",
+    "shapes": "شکل‌ها",
+    "shortcuts": "میانبرهای صفحه کلید",
+    "textFinish": "",
     "textNewLine": "یک خط جدید اضافه کنید (متن)",
-    "textFinish": "پایان ویرایش (متن)",
+    "title": "راهنما",
+    "view": "مشاهده",
     "zoomToFit": "بزرگنمایی برای دیدن تمام آیتم ها",
-    "zoomToSelection": "بزرگنمایی قسمت انتخاب شده",
-    "preventBinding": "مانع شدن از چسبیدن فلش ها"
+    "zoomToSelection": "بزرگنمایی قسمت انتخاب شده"
   },
   "encrypted": {
     "tooltip": "شما در یک محیط رمزگزاری شده دو طرفه در حال طراحی هستید پس Excalidraw هرگز طرح های شما را نمیبند."
@@ -232,5 +235,9 @@
     "title": "آمار برای نردها",
     "total": "مجموع",
     "width": "عرض"
+  },
+  "toast": {
+    "copyStyles": "کپی سبک.",
+    "copyToClipboardAsPng": "کپی در حافطه موقت به صورت PNG."
   }
 }
diff --git a/src/locales/fi-FI.json b/src/locales/fi-FI.json
index da4e000b31..91b5bd44db 100644
--- a/src/locales/fi-FI.json
+++ b/src/locales/fi-FI.json
@@ -80,9 +80,9 @@
     "gridMode": "Ruudukkotila",
     "addToLibrary": "Lisää kirjastoon",
     "removeFromLibrary": "Poista kirjastosta",
-    "libraryLoadingMessage": "Ladataan kirjastoa...",
+    "libraryLoadingMessage": "Ladataan kirjastoa…",
     "libraries": "Selaa kirjastoja",
-    "loadingScene": "Ladataan työtä...",
+    "loadingScene": "Ladataan työtä…",
     "align": "Tasaa",
     "alignTop": "Tasaa ylös",
     "alignBottom": "Tasaa alas",
@@ -91,7 +91,8 @@
     "centerVertically": "Keskitä pystysuunnassa",
     "centerHorizontally": "Keskitä vaakasuunnassa",
     "distributeHorizontally": "Jaa vaakasuunnassa",
-    "distributeVertically": "Jaa pystysuunnassa"
+    "distributeVertically": "Jaa pystysuunnassa",
+    "viewMode": "Katselutila"
   },
   "buttons": {
     "clearReset": "Tyhjennä piirtoalue",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Virhe"
   },
-  "shortcutsDialog": {
-    "title": "Pikanäppäimet",
-    "shapes": "Muodot",
-    "or": "tai",
+  "helpDialog": {
+    "blog": "Lue blogiamme",
     "click": "klikkaa",
-    "drag": "vedä",
     "curvedArrow": "Kaareva nuoli",
     "curvedLine": "Kaareva viiva",
-    "editor": "Editori",
-    "view": "Näkymä",
-    "blog": "Lue blogiamme",
-    "howto": "Seuraa oppaitamme",
+    "documentation": "Käyttöohjeet",
+    "drag": "vedä",
+    "editor": "Muokkausohjelma",
     "github": "Löysitkö ongelman? Kerro meille",
-    "textNewLine": "Lisää uusi rivi (teksti)",
+    "howto": "Seuraa oppaitamme",
+    "or": "tai",
+    "preventBinding": "Estä nuolten kiinnitys",
+    "shapes": "Muodot",
+    "shortcuts": "Pikanäppäimet",
     "textFinish": "Lopeta muokkaus (teksti)",
-    "zoomToFit": "Zoomaa kaikki elementit näkyviin",
-    "zoomToSelection": "Zoomaa valintaan",
-    "preventBinding": "Estä nuolten sitominen"
+    "textNewLine": "Lisää uusi rivi (teksti)",
+    "title": "Ohjeet",
+    "view": "Näkymä",
+    "zoomToFit": "Näytä kaikki elementit",
+    "zoomToSelection": "Näytä valinta"
   },
   "encrypted": {
     "tooltip": "Piirroksesi ovat päästä päähän salattuja, joten Excalidrawin palvelimet eivät koskaan näe niitä."
@@ -232,5 +235,9 @@
     "title": "Nörttien tilastot",
     "total": "Yhteensä",
     "width": "Leveys"
+  },
+  "toast": {
+    "copyStyles": "Tyylit kopioitu.",
+    "copyToClipboardAsPng": "Kopioitu leikepöydälle PNG-tiedostona."
   }
 }
diff --git a/src/locales/fr-FR.json b/src/locales/fr-FR.json
index 04bc89eae1..3af7e48faa 100644
--- a/src/locales/fr-FR.json
+++ b/src/locales/fr-FR.json
@@ -19,7 +19,7 @@
     "stroke": "Contour",
     "background": "Arrière-plan",
     "fill": "Remplissage",
-    "strokeWidth": "Largeur du contour",
+    "strokeWidth": "Largeur du trait",
     "strokeStyle": "Style du trait",
     "strokeStyle_solid": "Plein",
     "strokeStyle_dashed": "Tirets",
@@ -28,10 +28,10 @@
     "opacity": "Opacité",
     "textAlign": "Alignement du texte",
     "edges": "Angles",
-    "sharp": "Aigu",
-    "round": "Rond",
-    "arrowheads": "Extrémités de ligne",
-    "arrowhead_none": "Aucun",
+    "sharp": "Pointus",
+    "round": "Arrondis",
+    "arrowheads": "Extrémités de flèche",
+    "arrowhead_none": "Aucune",
     "arrowhead_arrow": "Flèche",
     "arrowhead_bar": "Barre",
     "arrowhead_dot": "Point",
@@ -42,7 +42,7 @@
     "exportEmbedScene": "Intégrer la scène au fichier exporté",
     "exportEmbedScene_details": "Les données de scène seront enregistrées dans le fichier PNG/SVG exporté, afin que la scène puisse être restaurée à partir de celui-ci.\nCela augmentera la taille du fichier exporté.",
     "addWatermark": "Ajouter \"Fait avec Excalidraw\"",
-    "handDrawn": "Manuscrite",
+    "handDrawn": "À main levée",
     "normal": "Normale",
     "code": "Code",
     "small": "Petit",
@@ -64,7 +64,7 @@
     "fileTitle": "Titre du fichier",
     "colorPicker": "Sélecteur de couleur",
     "canvasBackground": "Arrière-plan du canevas",
-    "drawingCanvas": "Canvas de dessin",
+    "drawingCanvas": "Zone de dessin",
     "layers": "Calques",
     "actions": "Actions",
     "language": "Langue",
@@ -81,9 +81,9 @@
     "addToLibrary": "Ajouter à la bibliothèque",
     "removeFromLibrary": "Supprimer de la bibliothèque",
     "libraryLoadingMessage": "Chargement de la bibliothèque...",
-    "libraries": "Explorer les bibliothèques",
+    "libraries": "Parcourir les bibliothèques",
     "loadingScene": "Chargement de la scène...",
-    "align": "Alignement",
+    "align": "Aligner",
     "alignTop": "Aligner en haut",
     "alignBottom": "Aligner en bas",
     "alignLeft": "Aligner à gauche",
@@ -91,7 +91,8 @@
     "centerVertically": "Centrer verticalement",
     "centerHorizontally": "Centrer horizontalement",
     "distributeHorizontally": "Distribuer horizontalement",
-    "distributeVertically": "Distribuer verticalement"
+    "distributeVertically": "Distribuer verticalement",
+    "viewMode": "Mode présentation"
   },
   "buttons": {
     "clearReset": "Réinitialiser le canevas",
@@ -99,7 +100,7 @@
     "exportToPng": "Exporter en PNG",
     "exportToSvg": "Exporter en SVG",
     "copyToClipboard": "Copier dans le presse-papier",
-    "copyPngToClipboard": "Copier le PNG dans le presse-papier",
+    "copyPngToClipboard": "Copier le PNG vers le presse-papier",
     "scale": "Échelle",
     "save": "Sauvegarder",
     "saveAs": "Enregistrer sous",
@@ -116,12 +117,12 @@
     "edit": "Modifier",
     "undo": "Annuler",
     "redo": "Rétablir",
-    "roomDialog": "Démarrer le collaboration en temps réel",
-    "createNewRoom": "Créer un nouveau salon",
+    "roomDialog": "Démarrer la collaboration en direct",
+    "createNewRoom": "Créer une nouvelle salle",
     "fullScreen": "Plein écran",
     "darkMode": "Mode sombre",
-    "lightMode": "Mode Clair",
-    "zenMode": "Mode Zen",
+    "lightMode": "Mode clair",
+    "zenMode": "Mode zen",
     "exitZenMode": "Quitter le mode zen"
   },
   "alerts": {
@@ -136,8 +137,8 @@
     "uploadedSecurly": "Le téléchargement a été sécurisé avec un chiffrement de bout en bout, ce qui signifie que ni Excalidraw ni personne d'autre ne peut en lire le contenu.",
     "loadSceneOverridePrompt": "Le chargement d'un dessin externe remplacera votre contenu actuel. Souhaitez-vous continuer ?",
     "errorLoadingLibrary": "Une erreur s'est produite lors du chargement de la bibliothèque tierce.",
-    "confirmAddLibrary": "Cela va ajouter {{numShapes}} forme(s) à votre bibliothèque. Êtes-vous sûr(e) ?",
-    "imageDoesNotContainScene": "L'importation des images n'est pas prise en charge pour le moment.\n\nVoulez-vous importer une scène ? Cette image ne semble pas contenir de données de scène. Avez-vous activé cette option lors de l'exportation ?",
+    "confirmAddLibrary": "Cela va ajouter {{numShapes}} forme(s) à votre bibliothèque. Êtes-vous sûr·e ?",
+    "imageDoesNotContainScene": "L'importation d'images n'est pas prise en charge pour le moment.\n\nVouliez-vous importer une scène ? Cette image ne semble pas contenir de données de scène. Avez-vous activé cette option lors de l'exportation ?",
     "cannotRestoreFromImage": "Impossible de restaurer la scène depuis ce fichier image"
   },
   "toolBar": {
@@ -160,63 +161,65 @@
   "hints": {
     "linearElement": "Cliquez pour démarrer plusieurs points, faites glisser pour une seule ligne",
     "freeDraw": "Cliquez et faites glissez, relâchez quand vous avez terminé",
-    "text": "Astuce : vous pouvez également ajouter du texte en double-cliquant n'importe où avec l'outil de sélection",
+    "text": "Astuce : vous pouvez aussi ajouter du texte en double-cliquant n'importe où avec l'outil de sélection",
     "linearElementMulti": "Cliquez sur le dernier point ou appuyez sur Échap ou Entrée pour terminer",
-    "lockAngle": "Vous pouvez contraindre l'angle en maintenant SHIFT",
-    "resize": "Vous pouvez conserver les proportions en maintenant la touche SHIFT pendant le redimensionnement,\nen maintenant la touche ALT pour redimensionner par rapport au centre",
-    "rotate": "Vous pouvez contraindre les angles en maintenant MAJ enfoncé pendant la rotation",
+    "lockAngle": "Vous pouvez restreindre l'angle en maintenant MAJ",
+    "resize": "Vous pouvez conserver les proportions en maintenant la touche MAJ pendant le redimensionnement,\nmaintenez la touche ALT pour redimensionner par rapport au centre",
+    "rotate": "Vous pouvez restreindre les angles en maintenant MAJ pendant la rotation",
     "lineEditor_info": "Double-cliquez ou appuyez sur Entrée pour éditer les points",
     "lineEditor_pointSelected": "Appuyez sur Supprimer pour supprimer le point, Ctrl ou Cmd+D pour le dupliquer, ou faites-le glisser pour le déplacer",
-    "lineEditor_nothingSelected": "Sélectionnez un point à déplacer ou à supprimer, ou maintenez Alt enfoncé et cliquez pour ajouter de nouveaux points"
+    "lineEditor_nothingSelected": "Sélectionnez un point à déplacer ou supprimer, ou maintenez Alt et cliquez pour ajouter de nouveaux points"
   },
   "canvasError": {
     "cannotShowPreview": "Impossible d’afficher l’aperçu",
     "canvasTooBig": "Le canevas est peut-être trop grand.",
-    "canvasTooBigTip": "Conseil : essayez de rapprocher un peu plus les éléments les plus éloignés."
+    "canvasTooBigTip": "Astuce : essayez de rapprocher un peu les éléments les plus éloignés."
   },
   "errorSplash": {
     "headingMain_pre": "Une erreur est survenue. Essayez ",
-    "headingMain_button": "rechargement de la page.",
+    "headingMain_button": "de recharger la page.",
     "clearCanvasMessage": "Si le rechargement ne résout pas l'erreur, essayez ",
     "clearCanvasMessage_button": "effacement du canevas.",
     "clearCanvasCaveat": " Cela entraînera une perte du travail ",
     "trackedToSentry_pre": "L'erreur avec l'identifiant ",
     "trackedToSentry_post": " a été enregistrée dans notre système.",
-    "openIssueMessage_pre": "Nous avons été très prudents de ne pas inclure les informations de votre scène dans l'erreur. Si votre scène n'est pas privée, veuillez envisager de poursuivre sur notre ",
+    "openIssueMessage_pre": "Nous avons fait très attention à ne pas inclure les informations de votre scène dans l'erreur. Si votre scène n'est pas privée, veuillez envisager de poursuivre sur notre ",
     "openIssueMessage_button": "outil de suivi des bugs.",
     "openIssueMessage_post": " Veuillez inclure les informations ci-dessous en les copiant-collant dans le ticket GitHub.",
     "sceneContent": "Contenu de la scène :"
   },
   "roomDialog": {
-    "desc_intro": "Vous pouvez inviter des personnes dans votre scène actuelle à collaborer avec vous.",
-    "desc_privacy": "Ne vous inquiétez pas, la session utilise le chiffrement de bout en bout, donc tout ce que vous dessinez restera privé. Même notre serveur ne sera pas en mesure de voir ce que vous faites.",
+    "desc_intro": "Vous pouvez inviter des personnes à collaborer avec vous sur votre scène actuelle.",
+    "desc_privacy": "Pas d'inquiétude, la session utilise le chiffrement de bout en bout, donc tout ce que vous dessinez restera privé. Même notre serveur ne pourra voir ce que vous faites.",
     "button_startSession": "Démarrer la session",
     "button_stopSession": "Arrêter la session",
     "desc_inProgressIntro": "La session de collaboration en direct est maintenant en cours.",
-    "desc_shareLink": "Partagez ce lien avec ceux avec qui vous souhaitez collaborer :",
-    "desc_exitSession": "Arrêter la session vous déconnectera du salon, mais vous pourrez continuer à travailler avec la scène, localement. Notez que cela n'affectera pas les autres personnes, et ils seront toujours en mesure de collaborer sur leur version."
+    "desc_shareLink": "Partagez ce lien avec les personnes avec lesquelles vous souhaitez collaborer :",
+    "desc_exitSession": "Arrêter la session vous déconnectera de la salle, mais vous pourrez continuer à travailler avec la scène, localement. Notez que cela n'affectera pas les autres personnes, et ils pourront toujours collaborer sur leur version."
   },
   "errorDialog": {
     "title": "Erreur"
   },
-  "shortcutsDialog": {
-    "title": "Raccourcis clavier",
-    "shapes": "Formes",
-    "or": "ou",
-    "click": "cliquer",
-    "drag": "glisser",
+  "helpDialog": {
+    "blog": "Lire notre blog",
+    "click": "clic",
     "curvedArrow": "Flèche courbée",
     "curvedLine": "Ligne courbée",
+    "documentation": "Documentation",
+    "drag": "glisser",
     "editor": "Éditeur",
-    "view": "Afficher",
-    "blog": "Lisez notre blog",
+    "github": "Problème trouvé ? Soumettre",
     "howto": "Suivez nos guides",
-    "github": "Vous avez trouvé un problème ? Envoyer",
-    "textNewLine": "Ajouter une nouvelle ligne (texte)",
+    "or": "ou",
+    "preventBinding": "Empêcher la liaison de flèche",
+    "shapes": "Formes",
+    "shortcuts": "Raccourcis clavier",
     "textFinish": "Terminer l'édition (texte)",
-    "zoomToFit": "Zoomer pour visualiser tous les éléments",
-    "zoomToSelection": "Zoomer sur la sélection",
-    "preventBinding": "Empêcher la liaison de la flèche"
+    "textNewLine": "Ajouter une nouvelle ligne (texte)",
+    "title": "Aide",
+    "view": "Affichage",
+    "zoomToFit": "Zoomer pour voir tous les éléments",
+    "zoomToSelection": "Zoomer sur la sélection"
   },
   "encrypted": {
     "tooltip": "Vos dessins sont chiffrés de bout en bout, les serveurs d'Excalidraw ne les verront jamais."
@@ -227,10 +230,14 @@
     "elements": "Éléments",
     "height": "Hauteur",
     "scene": "Scène",
-    "selected": "Sélectionné",
+    "selected": "Sélection",
     "storage": "Stockage",
     "title": "Stats pour les nerds",
     "total": "Total",
     "width": "Largeur"
+  },
+  "toast": {
+    "copyStyles": "Styles copiés.",
+    "copyToClipboardAsPng": "Copié vers le presse-papier en PNG."
   }
 }
diff --git a/src/locales/he-IL.json b/src/locales/he-IL.json
index 9d8fb25b21..47db14ccbd 100644
--- a/src/locales/he-IL.json
+++ b/src/locales/he-IL.json
@@ -80,9 +80,9 @@
     "gridMode": "מצב רשת",
     "addToLibrary": "הוסף לספריה",
     "removeFromLibrary": "הסר מספריה",
-    "libraryLoadingMessage": "טוען ספריה...",
+    "libraryLoadingMessage": "טוען ספריה…",
     "libraries": "דפדף בספריות",
-    "loadingScene": "טוען תצוגה...",
+    "loadingScene": "טוען תצוגה…",
     "align": "יישר",
     "alignTop": "יישר למעלה",
     "alignBottom": "יישר למטה",
@@ -91,7 +91,8 @@
     "centerVertically": "מרכז אנכית",
     "centerHorizontally": "מרכז אופקית",
     "distributeHorizontally": "חלוקה אופקית",
-    "distributeVertically": "חלוקה אנכית"
+    "distributeVertically": "חלוקה אנכית",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "אפס את הלוח",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "שגיאה"
   },
-  "shortcutsDialog": {
-    "title": "קיצורי מקלדת",
-    "shapes": "צורות",
-    "or": "או",
-    "click": "לחץ",
-    "drag": "גרור",
-    "curvedArrow": "חץ מעוקל",
-    "curvedLine": "קו מעוקל",
-    "editor": "עורך",
-    "view": "תצוגה",
-    "blog": "קרא את הבלוג שלנו",
-    "howto": "עקוב אחר המדריכים שלנו",
-    "github": "מצאת בעיה? דווח",
-    "textNewLine": "הוסף שורה חדשה (טקסט)",
-    "textFinish": "סיים עריכה (טקסט)",
-    "zoomToFit": "זום להתאמת כל האלמנטים למסך",
-    "zoomToSelection": "התמקד בבחירה",
-    "preventBinding": "מנע השתלבות חצים"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "הרישומים שלך מוצפנים מקצה לקצה כך שהשרתים של Excalidraw לא יראו אותם לעולם."
@@ -232,5 +235,9 @@
     "title": "סטטיסטיקות לחנונים",
     "total": "סה״כ",
     "width": "רוחב"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/hi-IN.json b/src/locales/hi-IN.json
index a87fa75860..d29595eef0 100644
--- a/src/locales/hi-IN.json
+++ b/src/locales/hi-IN.json
@@ -91,7 +91,8 @@
     "centerVertically": "लंबवत केन्द्रित",
     "centerHorizontally": "क्षैतिज केन्द्रित",
     "distributeHorizontally": "क्षैतिज रूप से वितरित करें",
-    "distributeVertically": "खड़ी रूप से वितरित करें"
+    "distributeVertically": "खड़ी रूप से वितरित करें",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "कैनवास रीसेट करें",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "गलती"
   },
-  "shortcutsDialog": {
-    "title": "कीबोर्ड के शॉर्टकट्स",
-    "shapes": "आकृतियाँ",
-    "or": "या",
-    "click": "क्लिक करें",
-    "drag": "खींचें",
-    "curvedArrow": "घुमावदार तीर",
-    "curvedLine": "घुमावदार रेखा",
-    "editor": "संपादक",
-    "view": "दृश्य",
-    "blog": "हमारा ब्लॉग पढे",
-    "howto": "हमारे गाइड का पालन करें",
-    "github": "एक मुद्दा मिला? प्रस्तुत करे",
-    "textNewLine": "नई पंक्ति (पाठ) जोड़ें",
-    "textFinish": "संपादन समाप्त करें (पाठ)",
-    "zoomToFit": "सभी तत्वों को फिट करने के लिए ज़ूम करें",
-    "zoomToSelection": "सिलेक्शन तक ज़ूम करे",
-    "preventBinding": "तीर बंधन रोकें"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "आपके चित्र अंत-से-अंत एन्क्रिप्टेड हैं, इसलिए एक्सक्लूसिव्रॉव के सर्वर उन्हें कभी नहीं देखेंगे।"
@@ -232,5 +235,9 @@
     "title": "बेवकूफ के लिए आँकड़े",
     "total": "कुल",
     "width": "चौड़ाई"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/hu-HU.json b/src/locales/hu-HU.json
index d6c503453e..3c445c4a07 100644
--- a/src/locales/hu-HU.json
+++ b/src/locales/hu-HU.json
@@ -80,9 +80,9 @@
     "gridMode": "Hálómód",
     "addToLibrary": "Hozzáadás a könyvtárhoz",
     "removeFromLibrary": "Eltávólítás a könyvtárból",
-    "libraryLoadingMessage": "Könyvtár betöltése...",
+    "libraryLoadingMessage": "Könyvtár betöltése…",
     "libraries": "Könyvtárak böngészése",
-    "loadingScene": "Jelenet betöltése...",
+    "loadingScene": "Jelenet betöltése…",
     "align": "Igazítás",
     "alignTop": "Felülre igazítás",
     "alignBottom": "Alulra igazítás",
@@ -91,7 +91,8 @@
     "centerVertically": "Függőlegesen középre igazított",
     "centerHorizontally": "Vízszintesen középre igazított",
     "distributeHorizontally": "Vízszintes elosztás",
-    "distributeVertically": "Függőleges elosztás"
+    "distributeVertically": "Függőleges elosztás",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "Vászon törlése",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Hiba"
   },
-  "shortcutsDialog": {
-    "title": "Gyorsbillentyűk",
-    "shapes": "Formák",
-    "or": "vagy",
-    "click": "klikk",
-    "drag": "húzd",
-    "curvedArrow": "Ívelt nyíl",
-    "curvedLine": "Ívelt vonal",
-    "editor": "Szerkesztő",
-    "view": "Nézet",
-    "blog": "Olvasd a blogunkat",
-    "howto": "Kövesd az útmutatóinkat",
-    "github": "Hibát találtál? Küld be",
-    "textNewLine": "Új sor hozzáadása (szöveg)",
-    "textFinish": "Szerkesztés befejezése (szöveg)",
-    "zoomToFit": "Az összes elem látótérbe hozása",
-    "zoomToSelection": "Kijelölésre nagyítás",
-    "preventBinding": "A nyíl ne ragadjon"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "A rajzaidat végpontok közötti titkosítással tároljuk, tehát az Excalidraw szervereiről se tud más belenézni."
@@ -232,5 +235,9 @@
     "title": "Statisztikák",
     "total": "Összesen",
     "width": "Szélesség"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/id-ID.json b/src/locales/id-ID.json
index 76fadab92e..d05928c500 100644
--- a/src/locales/id-ID.json
+++ b/src/locales/id-ID.json
@@ -80,9 +80,9 @@
     "gridMode": "Mode grid",
     "addToLibrary": "Tambahkan ke pustaka",
     "removeFromLibrary": "Hapus dari pustaka",
-    "libraryLoadingMessage": "Memuat pustaka...",
+    "libraryLoadingMessage": "Memuat pustaka…",
     "libraries": "Telusur pustaka",
-    "loadingScene": "Memuat pemandangan...",
+    "loadingScene": "Memuat pemandangan…",
     "align": "Perataan",
     "alignTop": "Rata atas",
     "alignBottom": "Rata bawah",
@@ -91,7 +91,8 @@
     "centerVertically": "Pusatkan secara vertikal",
     "centerHorizontally": "Pusatkan secara horizontal",
     "distributeHorizontally": "Distribusikan horizontal",
-    "distributeVertically": "Distribusikan vertikal"
+    "distributeVertically": "Distribusikan vertikal",
+    "viewMode": "Mode tampilan"
   },
   "buttons": {
     "clearReset": "Setel Ulang Kanvas",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Kesalahan"
   },
-  "shortcutsDialog": {
-    "title": "Pintasan keyboard",
-    "shapes": "Bentuk",
-    "or": "atau",
+  "helpDialog": {
+    "blog": "Baca blog kami",
     "click": "klik",
-    "drag": "seret",
     "curvedArrow": "Panah lengkung",
     "curvedLine": "Garis lengkung",
+    "documentation": "Dokumentasi",
+    "drag": "seret",
     "editor": "Editor",
-    "view": "Tampilan",
-    "blog": "Baca blog kami",
+    "github": "Menemukan masalah? Kirimkan",
     "howto": "Ikuti panduan kami",
-    "github": "Menemukan sebuah masalah? Kirimkan",
-    "textNewLine": "Tambahkan baris baru (teks)",
+    "or": "atau",
+    "preventBinding": "Cegah pengikatan panah",
+    "shapes": "Bentuk",
+    "shortcuts": "Pintasan keyboard",
     "textFinish": "Selesai mengedit (teks)",
+    "textNewLine": "Tambahkan baris baru (teks)",
+    "title": "Bantuan",
+    "view": "Tampilan",
     "zoomToFit": "Perbesar agar sesuai dengan semua elemen",
-    "zoomToSelection": "Perbesar ke seleksi",
-    "preventBinding": "Cegah pengikatan panah"
+    "zoomToSelection": "Perbesar ke seleksi"
   },
   "encrypted": {
     "tooltip": "Gambar anda terenkripsi end-to-end sehingga server Excalidraw tidak akan pernah dapat melihatnya."
@@ -232,5 +235,9 @@
     "title": "Statistik untuk nerd",
     "total": "Total",
     "width": "Lebar"
+  },
+  "toast": {
+    "copyStyles": "Gaya tersalin.",
+    "copyToClipboardAsPng": "Tersalin ke clipboard sebagai PNG."
   }
 }
diff --git a/src/locales/it-IT.json b/src/locales/it-IT.json
index 0faf287fdc..5740d53ec3 100644
--- a/src/locales/it-IT.json
+++ b/src/locales/it-IT.json
@@ -80,9 +80,9 @@
     "gridMode": "Modalità griglia",
     "addToLibrary": "Aggiungi alla libreria",
     "removeFromLibrary": "Rimuovi dalla libreria",
-    "libraryLoadingMessage": "Caricamento della biblioteca...",
+    "libraryLoadingMessage": "Caricamento della biblioteca…",
     "libraries": "Sfoglia librerie",
-    "loadingScene": "Caricamento della scena...",
+    "loadingScene": "Caricamento della scena…",
     "align": "Allinea",
     "alignTop": "Allinea in alto",
     "alignBottom": "Allinea in basso",
@@ -91,7 +91,8 @@
     "centerVertically": "Centra Verticalmente",
     "centerHorizontally": "Centra orizzontalmente",
     "distributeHorizontally": "Distribuisci orizzontalmente",
-    "distributeVertically": "Distribuisci verticalmente"
+    "distributeVertically": "Distribuisci verticalmente",
+    "viewMode": "Modalità visualizzazione"
   },
   "buttons": {
     "clearReset": "Svuota la tela",
@@ -163,7 +164,7 @@
     "text": "Suggerimento: puoi anche aggiungere del testo facendo doppio clic ovunque con lo strumento di selezione",
     "linearElementMulti": "Clicca sull'ultimo punto o premi Esc o Invio per finire",
     "lockAngle": "Puoi limitare l'angolo tenendo premuto SHIFT",
-    "resize": "Per vincolare le proporzioni, tenir premuto MAIUSC durante il ridimensionamento;\nper ridimensionare dal centro, tenir premuto ALT",
+    "resize": "Per vincolare le proporzioni, tieni premuto MAIUSC durante il ridimensionamento;\nper ridimensionare dal centro, tieni premuto ALT",
     "rotate": "Puoi mantenere gli angoli tenendo premuto SHIFT durante la rotazione",
     "lineEditor_info": "Fai doppio click o premi invio per modificare i punti",
     "lineEditor_pointSelected": "Premere Elimina per rimuovere il punto, CtrlOrCmd+D per duplicare o trascinare per spostare",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Errore"
   },
-  "shortcutsDialog": {
-    "title": "Scorciatoie da tastiera",
-    "shapes": "Forme",
-    "or": "oppure",
+  "helpDialog": {
+    "blog": "Leggi il nostro blog",
     "click": "click",
-    "drag": "trascina",
     "curvedArrow": "Freccia curva",
     "curvedLine": "Linea curva",
+    "documentation": "Documentazione",
+    "drag": "trascina",
     "editor": "Editor",
-    "view": "Vista",
-    "blog": "Leggi il nostro blog",
+    "github": "Trovato un problema? Segnalalo",
     "howto": "Segui le nostre guide",
-    "github": "Hai trovato un problema? Segnalalo",
+    "or": "oppure",
+    "preventBinding": "Impedisci legame della freccia",
+    "shapes": "Forme",
+    "shortcuts": "Scorciatoie da tastiera",
+    "textFinish": "Termina la modifica (testo)",
     "textNewLine": "Aggiungi nuova riga (testo)",
-    "textFinish": "Completa la modifica (testo)",
+    "title": "Guida",
+    "view": "Vista",
     "zoomToFit": "Adatta zoom per mostrare tutti gli elementi",
-    "zoomToSelection": "Zoom alla selezione",
-    "preventBinding": "Prevenire l'associazione freccia"
+    "zoomToSelection": "Zoom alla selezione"
   },
   "encrypted": {
     "tooltip": "I tuoi disegni sono crittografati end-to-end in modo che i server di Excalidraw non li possano mai vedere."
@@ -232,5 +235,9 @@
     "title": "Statistiche per nerd",
     "total": "Totale",
     "width": "Larghezza"
+  },
+  "toast": {
+    "copyStyles": "Stili copiati.",
+    "copyToClipboardAsPng": "Copiato negli appunti come PNG."
   }
 }
diff --git a/src/locales/ja-JP.json b/src/locales/ja-JP.json
index 6e85251651..58eb17dae9 100644
--- a/src/locales/ja-JP.json
+++ b/src/locales/ja-JP.json
@@ -30,11 +30,11 @@
     "edges": "角",
     "sharp": "四角",
     "round": "丸",
-    "arrowheads": "",
-    "arrowhead_none": "",
-    "arrowhead_arrow": "",
-    "arrowhead_bar": "",
-    "arrowhead_dot": "",
+    "arrowheads": "線の終点",
+    "arrowhead_none": "なし",
+    "arrowhead_arrow": "矢印",
+    "arrowhead_bar": "バー",
+    "arrowhead_dot": "ドット",
     "fontSize": "フォントの大きさ",
     "fontFamily": "フォントの種類",
     "onlySelected": "選択中のみ",
@@ -80,9 +80,9 @@
     "gridMode": "",
     "addToLibrary": "ライブラリに追加",
     "removeFromLibrary": "ライブラリから削除",
-    "libraryLoadingMessage": "ライブラリを読み込み中...",
+    "libraryLoadingMessage": "ライブラリを読み込み中…",
     "libraries": "",
-    "loadingScene": "シーンを読み込み中...",
+    "loadingScene": "シーンを読み込み中…",
     "align": "整列",
     "alignTop": "上揃え",
     "alignBottom": "下揃え",
@@ -91,7 +91,8 @@
     "centerVertically": "縦方向に中央揃え",
     "centerHorizontally": "横方向に中央揃え",
     "distributeHorizontally": "",
-    "distributeVertically": ""
+    "distributeVertically": "",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "キャンバスのリセット",
@@ -118,9 +119,9 @@
     "redo": "やり直し",
     "roomDialog": "共同編集を開始する",
     "createNewRoom": "新しい部屋を作成する",
-    "fullScreen": "",
-    "darkMode": "",
-    "lightMode": "",
+    "fullScreen": "全画面表示",
+    "darkMode": "ダークモード",
+    "lightMode": "ライトモード",
     "zenMode": "",
     "exitZenMode": "集中モードをやめる"
   },
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "エラー"
   },
-  "shortcutsDialog": {
-    "title": "キーボードショートカット",
-    "shapes": "図形",
-    "or": "または",
-    "click": "クリック",
-    "drag": "ドラッグ",
-    "curvedArrow": "曲がった矢印",
-    "curvedLine": "曲線",
-    "editor": "エディタ",
-    "view": "表示",
-    "blog": "公式ブログを読む",
-    "howto": "ヘルプ・マニュアル",
-    "github": "不具合報告はこちら",
-    "textNewLine": "テキストの改行",
-    "textFinish": "テキストの編集を終える",
-    "zoomToFit": "すべての図形が収まるよう拡大/縮小",
-    "zoomToSelection": "",
-    "preventBinding": "矢印を結合しない"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "描画内容はエンドツーエンド暗号化が施されており、Excalidrawサーバーが内容を見ることはできません。"
@@ -225,12 +228,16 @@
     "angle": "",
     "element": "",
     "elements": "",
-    "height": "",
+    "height": "高さ",
     "scene": "",
     "selected": "",
     "storage": "",
     "title": "",
-    "total": "",
-    "width": ""
+    "total": "合計",
+    "width": "幅"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/kab-KAB.json b/src/locales/kab-KAB.json
new file mode 100644
index 0000000000..0485d3fa73
--- /dev/null
+++ b/src/locales/kab-KAB.json
@@ -0,0 +1,243 @@
+{
+  "labels": {
+    "paste": "Senṭeḍ",
+    "pasteCharts": "Senṭeḍ udlifen",
+    "selectAll": "Fren akk",
+    "multiSelect": "Rnu aferdis ɣer tefrayt",
+    "moveCanvas": "Smutti taɣzut n usuneɣ",
+    "cut": "Gzem",
+    "copy": "Nɣel",
+    "copyAsPng": "Nɣel ɣer tecfawit am PNG",
+    "copyAsSvg": "Nɣel ɣer tecfawit am SVG",
+    "bringForward": "Awi ɣer sdat",
+    "sendToBack": "Awi s agilal",
+    "bringToFront": "Err ɣer deffir",
+    "sendBackward": "Awi ɣer deffir",
+    "delete": "Kkes",
+    "copyStyles": "Nɣel iɣunab",
+    "pasteStyles": "Senṭeḍ iɣunab",
+    "stroke": "Azizdew",
+    "background": "Agilal",
+    "fill": "Taččart",
+    "strokeWidth": "Tehri n yizirig",
+    "strokeStyle": "Aɣanib n tizirig",
+    "strokeStyle_solid": "Aččuran",
+    "strokeStyle_dashed": "S tjerriḍin",
+    "strokeStyle_dotted": "S tenqiḍin",
+    "sloppiness": "",
+    "opacity": "Tiḍullest",
+    "textAlign": "Areyyec n uḍris",
+    "edges": "Leryuf",
+    "sharp": "Yemsed",
+    "round": "Imdewer",
+    "arrowheads": "Ixfawen n tenccabt",
+    "arrowhead_none": "Ulac",
+    "arrowhead_arrow": "Taneccabt",
+    "arrowhead_bar": "Afeggag",
+    "arrowhead_dot": "Tanqiḍt",
+    "fontSize": "Tiddi n tsefsit",
+    "fontFamily": "Tawacult n tsefsiyin",
+    "onlySelected": "Tafrayt kan",
+    "withBackground": "S ugilal",
+    "exportEmbedScene": "Seddu asayes deg ufaylu yettwasifḍen",
+    "exportEmbedScene_details": "Asayes ad yettwasekles deg ufaylu n usifeḍ PNG/SVG akken akken ad yili wamek ara d-yettwarr seg-s usayes. Ayagi ad isimɣur tiddi n ufaylu n usifeḍ.",
+    "addWatermark": "Seddu \"Yettwaxdem s Excalidraw\"",
+    "handDrawn": "Asuneɣ s ufus",
+    "normal": "Amagnu",
+    "code": "Tangalt",
+    "small": "Meẓẓi",
+    "medium": "Alemmas",
+    "large": "Ameqran",
+    "veryLarge": "Meqqer aṭas",
+    "solid": "Aččuran",
+    "hachure": "Azerreg",
+    "crossHatch": "Azerreg anmidag",
+    "thin": "Arqaq",
+    "bold": "Azuran",
+    "left": "Azelmaḍ",
+    "center": "Talemmast",
+    "right": "Ayfus",
+    "extraBold": "Azuran aṭas",
+    "architect": "Amasdag",
+    "artist": "Anaẓur",
+    "cartoonist": "",
+    "fileTitle": "Azwel n ufaylu",
+    "colorPicker": "Amafran n yini",
+    "canvasBackground": "Agilal n teɣzut n usuneɣ",
+    "drawingCanvas": "Taɣzut n usuneɣ",
+    "layers": "Tissiyin",
+    "actions": "Tigawin",
+    "language": "Tutlayt",
+    "createRoom": "Bḍu tiɣimit n umɛawen s srid",
+    "duplicateSelection": "Sisleg",
+    "untitled": "War azwel",
+    "name": "Isem",
+    "yourName": "Isem-ik (im)",
+    "madeWithExcalidraw": "Yettwaxdem s Excalidraw",
+    "group": "Segrew tafrayt",
+    "ungroup": "Kkess asegrew i tefrayt",
+    "collaborators": "Imɛiwnen",
+    "gridMode": "Askar n uferrug",
+    "addToLibrary": "Rnu ɣer temkarḍit",
+    "removeFromLibrary": "Kkes si temkarḍit",
+    "libraryLoadingMessage": "Asali n temkarḍit…",
+    "libraries": "Snirem timkarḍiyin",
+    "loadingScene": "Asali n usayes…",
+    "align": "Reyyec",
+    "alignTop": "Areyyec uksawen",
+    "alignBottom": "Areyyec ukessar",
+    "alignLeft": "Reyyec s azelmaḍ",
+    "alignRight": "Areyyec s ayfus",
+    "centerVertically": "Di tlemmast s ibeddi",
+    "centerHorizontally": "Di tlemmast s uglawi",
+    "distributeHorizontally": "Freq s uglawi",
+    "distributeVertically": "Freq s yibeddi",
+    "viewMode": "Askar n tmuɣli"
+  },
+  "buttons": {
+    "clearReset": "Ales awennez n teɣzut n usuneɣ",
+    "export": "Sifeḍ",
+    "exportToPng": "Sifeḍ ɣer PNG",
+    "exportToSvg": "Sifeḍ ɣer SVG",
+    "copyToClipboard": "Nɣel ɣer tecfawit",
+    "copyPngToClipboard": "Nɣel PNG ɣer tecfawit",
+    "scale": "Taskala",
+    "save": "Sekles",
+    "saveAs": "Sekles am",
+    "load": "Sali-d",
+    "getShareableLink": "Awi-d aseɣwen n beṭṭu",
+    "close": "Mdel",
+    "selectLanguage": "Fren tutlayt",
+    "scrollBackToContent": "Uɣal s agbur",
+    "zoomIn": "Simɣur",
+    "zoomOut": "Simẓi",
+    "resetZoom": "Ales awennez n usemɣer",
+    "menu": "Umuɣ",
+    "done": "Ifukk",
+    "edit": "Ẓreg",
+    "undo": "Sefsex",
+    "redo": "Err-d",
+    "roomDialog": "Bdu amɛawen s srid",
+    "createNewRoom": "Snulfu-d taxxamt tamaynutt",
+    "fullScreen": "Agdil aččuran",
+    "darkMode": "Askar imsulles",
+    "lightMode": "Askar afaw",
+    "zenMode": "Askar Zen",
+    "exitZenMode": "Ffeɣ seg uskar Zen"
+  },
+  "alerts": {
+    "clearReset": "Ayagi ad isfeḍ akk taɣzut n usuneɣ. Tetḥeqqeḍ?",
+    "couldNotCreateShareableLink": "D awezɣi asnulfu n useɣwen n beṭṭu.",
+    "couldNotCreateShareableLinkTooBig": "D awezɣi asnulfu n useɣwen n beṭṭu. Asayes ɣezzif aṭas",
+    "couldNotLoadInvalidFile": "D awezɣi asali n ufaylu armeɣtu",
+    "importBackendFailed": "",
+    "cannotExportEmptyCanvas": "D awezɣi asifeḍ n teɣzut n usuneɣ tilemt.",
+    "couldNotCopyToClipboard": "D awezɣi anɣal ɣer tecfawit. Eɛreḍ ad tesqedceḍ iminig Chrome.",
+    "decryptFailed": "D awezɣi tukksa n uwgelhen i yisefka.",
+    "uploadedSecurly": "Asili yettwasɣelles s uwgelhen ixef s ixef, ayagi yebɣa ad d-yini belli aqeddac n Excalidraw akked medden ur zmiren ara ad ɣren agbur.",
+    "loadSceneOverridePrompt": "Asali n wunuɣ uffiɣ ad isemselsi agbur-inek (m) yellan. Tebɣiḍ ad tkemmeleḍ?",
+    "errorLoadingLibrary": "Teḍra-d tuccḍa deg usali n temkarḍit n wis kraḍ.",
+    "confirmAddLibrary": "Ayagi adirnu talɣa (win) {{numShapes}} ɣer temkarḍit-inek (m). Tetḥeqqeḍ?",
+    "imageDoesNotContainScene": "Taktert n tugniwin ur tettwadhel ara akka tura.\nTebɣiḍ ad tketreḍ asayes? Tugna-agi tettban-d ur tegbir ara isefka n usnas. Tesremdeḍ ayagi deg usifeḍ?",
+    "cannotRestoreFromImage": "Asayes ulamek ara d-yettwarr seg ufaylu-agi n tugna"
+  },
+  "toolBar": {
+    "selection": "Tafrayt",
+    "draw": "Unuɣ ilelli",
+    "rectangle": "Asrem",
+    "diamond": "Ameɣṛun",
+    "ellipse": "Taglayt",
+    "arrow": "Taneccabt",
+    "line": "Izirig",
+    "text": "Aḍris",
+    "library": "Tamkarḍit",
+    "lock": "Eǧǧ afecku n tefrayt yermed mbaɛd asuneɣ"
+  },
+  "headings": {
+    "canvasActions": "Tigawin n teɣzut n usuneɣ",
+    "selectedShapeActions": "Tigawin n talɣa yettwafernen",
+    "shapes": "Talɣiwin"
+  },
+  "hints": {
+    "linearElement": "Ssit akken ad tebduḍ aṭas n tenqiḍin, zuɣer i yiwen n yizirig",
+    "freeDraw": "Ssit yerna zuɣer, serreḥ ticki tfukeḍ",
+    "text": "Tixidest: tzemreḍ daɣen ad ternuḍ aḍris s usiti snat n tikkal anida tebɣiḍ s ufecku n tefrayt",
+    "linearElementMulti": "Ssit ɣef tenqiḍt taneggarut neɣ ssed taqeffalt Escape neɣ taqeffalt Kcem akken ad tfakkeḍ",
+    "lockAngle": "Tzemreḍ ad tḥettmeḍ tiɣmert s tuṭṭfa n tqeffalt SHIFT",
+    "resize": "Tzemreḍ ad tḥettemeḍ assaɣ s tuṭṭfa n tqeffalt SHIFT mi ara tettbeddileḍ tiddi,\nma teṭṭfeḍ ALT abeddel n tiddi ad yili si tlemmast",
+    "rotate": "Tzemreḍ ad tḥettemeḍ tiɣemmar s tuṭṭfa n SHIFT di tuzzya",
+    "lineEditor_info": "Ssit snat n tikkal neɣ ssed taqeffalt Kcem akken ad tẓergeḍ tinqiḍin",
+    "lineEditor_pointSelected": "Ssed taqeffalt kkes akken ad tekkseḍ tanqiḍt, CtrlOrCmd+D akken ad tsiselgeḍ, neɣ zuɣer akken ad tesmuttiḍ",
+    "lineEditor_nothingSelected": "Fren tanqiḍt ara tesmuttiḍ neɣ ara tekkseḍ, neɣ ṭṭef taqeffalt Alt akken ad ternuḍ tinqiḍin timaynutin"
+  },
+  "canvasError": {
+    "cannotShowPreview": "Ulamek abeqqeḍ n teskant",
+    "canvasTooBig": "Taɣzut n usuneɣ tezmer ad tili temeqqer aṭas.",
+    "canvasTooBigTip": "Tixidest: eɛreḍ ad tesqerbeḍ ciṭ iferdisen yembaɛaden."
+  },
+  "errorSplash": {
+    "headingMain_pre": "Teḍra-d tuccḍa. Eɛreḍ ",
+    "headingMain_button": "asali n usebter tikkelt-nniḍen.",
+    "clearCanvasMessage": "Ma yella tulsa n usali ur tefri ara ugur, eɛreḍ ",
+    "clearCanvasMessage_button": "asfaḍ n teɣzut n usuneɣ.",
+    "clearCanvasCaveat": " Ayagi ad d-iglu s usṛuḥu n umahil ",
+    "trackedToSentry_pre": "Tuccḍa akked umesmagi ",
+    "trackedToSentry_post": " tettwasekles deg unagraw-nneɣ.",
+    "openIssueMessage_pre": "",
+    "openIssueMessage_button": "afecku n weḍfar n yibugen.",
+    "openIssueMessage_post": " Ma ulac uɣilif seddu talɣut ukessar-agi s wenɣal akked usenṭeḍ di GitHub issue.",
+    "sceneContent": "Agbur n usayes:"
+  },
+  "roomDialog": {
+    "desc_intro": "Tzemreḍ ad d-teɛerḍeḍ medden ɣer usayes-inek (m) amiran akken ad ttekkin yid-k.",
+    "desc_privacy": "Ur tqelliq ara, tiɣimit tsseqdac awgelhen ixef s ixef, dɣa ayen ara tsunɣeḍ ad iqqim d amaẓlay. Ula d aqeddac-nneɣ ur yezmir ara ad iwali acu txeddemeḍ.",
+    "button_startSession": "Bdu tiɣimit",
+    "button_stopSession": "Ḥbes tiɣimit",
+    "desc_inProgressIntro": "Tiɣimit n umɛawen s srid tetteddu akka tura.",
+    "desc_shareLink": "Bḍu aseɣwen-agi akked medden ukud tebɣiḍ ad temɛawaneḍ:",
+    "desc_exitSession": "Aḥbas n tɣimit ad k (m) yesenser si texxamt, maca ad tizmireḍ ad tkemmeleḍ amahil s usayes, s wudem adigan. Ẓer belli ayagi ur yettḥaz ara imdanen-nniḍen, yerna ad izmiren ad kemmelen ad mɛawanen di tsuffeɣt-nnsen."
+  },
+  "errorDialog": {
+    "title": "Tuccḍa"
+  },
+  "helpDialog": {
+    "blog": "Ɣeṛ ablug-nneɣ",
+    "click": "ssit",
+    "curvedArrow": "Taneccabt izelgen",
+    "curvedLine": "Izirig izelgen",
+    "documentation": "Tasemlit",
+    "drag": "zuɣer",
+    "editor": "Amaẓrag",
+    "github": "Tufiḍ-d ugur? Azen-aɣ-d",
+    "howto": "Ḍfer imniren-nneɣ",
+    "or": "neɣ",
+    "preventBinding": "",
+    "shapes": "Talɣiwin",
+    "shortcuts": "Inegzumen n unasiw",
+    "textFinish": "Fak asiẓreg (aḍris)",
+    "textNewLine": "Rnu ajerriḍ amaynut (aḍris)",
+    "title": "Tallelt",
+    "view": "Tamuɣli",
+    "zoomToFit": "Simɣur akken ad twliḍ akk iferdisen",
+    "zoomToSelection": "Simɣur ɣer tefrayt"
+  },
+  "encrypted": {
+    "tooltip": "Unuɣen-inek (m) ttuwgelhnen seg yixef s ixef dɣa iqeddacen n Excalidraw werǧin ad ten-walin. "
+  },
+  "stats": {
+    "angle": "Tiɣmeṛt",
+    "element": "Aferdis",
+    "elements": "Iferdisen",
+    "height": "Tattayt",
+    "scene": "Asayes",
+    "selected": "Yettwafren",
+    "storage": "Aḥraz",
+    "title": "",
+    "total": "Aɣrud",
+    "width": "Tehri"
+  },
+  "toast": {
+    "copyStyles": "Iɣunab yettwaneɣlen.",
+    "copyToClipboardAsPng": "Yettwanɣel ɣer tecfawit am PNG."
+  }
+}
diff --git a/src/locales/ko-KR.json b/src/locales/ko-KR.json
index 61c026ce04..2cae7b6d87 100644
--- a/src/locales/ko-KR.json
+++ b/src/locales/ko-KR.json
@@ -91,7 +91,8 @@
     "centerVertically": "수직으로 중앙 정렬",
     "centerHorizontally": "수평으로 중앙 정렬",
     "distributeHorizontally": "수평으로 분배",
-    "distributeVertically": "수직으로 분배"
+    "distributeVertically": "수직으로 분배",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "캔버스 초기화",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "오류"
   },
-  "shortcutsDialog": {
-    "title": "키보드 단축키",
-    "shapes": "모양",
-    "or": "또는",
-    "click": "클릭",
-    "drag": "드래그",
-    "curvedArrow": "곡선 화살표",
-    "curvedLine": "곡선",
-    "editor": "편집",
-    "view": "보기",
-    "blog": "블로그 읽어보기",
-    "howto": "가이드 참고하기",
-    "github": "이슈 제보하기",
-    "textNewLine": "줄바꿈 (텍스트)",
-    "textFinish": "편집 완료 (텍스트)",
-    "zoomToFit": "모든 요소가 보이도록 확대/축소",
-    "zoomToSelection": "선택 영역으로 확대/축소",
-    "preventBinding": "화살표가 붙지 않게 하기"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "그림은 종단 간 암호화되므로 Excalidraw의 서버는 절대로 내용을 알 수 없습니다."
@@ -232,5 +235,9 @@
     "title": "덕후들을 위한 통계",
     "total": "합계",
     "width": "너비"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/my-MM.json b/src/locales/my-MM.json
index dd355f5d73..66bdef6784 100644
--- a/src/locales/my-MM.json
+++ b/src/locales/my-MM.json
@@ -80,9 +80,9 @@
     "gridMode": "",
     "addToLibrary": "မှတ်တမ်းတင်",
     "removeFromLibrary": "မှတ်တမ်းမှထုတ်",
-    "libraryLoadingMessage": "မှတ်တမ်းအား တင်သွင်းနေသည်...",
+    "libraryLoadingMessage": "မှတ်တမ်းအား တင်သွင်းနေသည်…",
     "libraries": "စာကြည့်တိုက်တွင်ရှာဖွေပါ",
-    "loadingScene": "မြင်ကွင်းဖော်နေသည်...",
+    "loadingScene": "မြင်ကွင်းဖော်နေသည်…",
     "align": "ချိန်ညှိ",
     "alignTop": "ထိပ်ညှိ",
     "alignBottom": "အခြေညှိ",
@@ -91,7 +91,8 @@
     "centerVertically": "ဒေါင်လိုက်အလယ်ညှိ",
     "centerHorizontally": "အလျားလိုက်အလယ်ညှိ",
     "distributeHorizontally": "အလျားလိုက်",
-    "distributeVertically": "ထောင်လိုက်"
+    "distributeVertically": "ထောင်လိုက်",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "ကားချပ်ရှင်းလင်း",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "ချို့ယွင်းချက်"
   },
-  "shortcutsDialog": {
-    "title": "ကီးဘုတ်ရှော့ကတ်များ",
-    "shapes": "ပုံသဏ္ဌာန်",
-    "or": "(သို့)",
-    "click": "ကလစ်နှိပ်",
-    "drag": "တရွတ်ဆွဲ",
-    "curvedArrow": "မြှားကွေး",
-    "curvedLine": "မျဉ်းကွေး",
-    "editor": "တည်းဖြတ်",
-    "view": "မြင်ကွင်း",
-    "blog": "ဘလော့ဂ်တွင်လေ့လာပါ",
-    "howto": "အညွှန်း",
-    "github": "ချို့ယွင်းမှုအတွက်အသိပေးရန်",
-    "textNewLine": "စာသားဖြည့်သွင်း",
-    "textFinish": "စာသားဖြည့်သွင်းပြီး",
-    "zoomToFit": "ကားချပ်အပြည့်ဖေါ်",
-    "zoomToSelection": "",
-    "preventBinding": "မြှားများမပေါင်းစေရန်"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "ရေးဆွဲထားသောပုံများအား နှစ်ဘက်စွန်းတိုင်လျှို့ဝှက်ထားသဖြင့် Excalidraw ၏ဆာဗာများပင်လျှင်မြင်တွေ့ရမည်မဟုတ်ပါ။"
@@ -232,5 +235,9 @@
     "title": "အက္ခရာများအတွက်အချက်အလက်များ",
     "total": "စုစုပေါင်း",
     "width": "အကျယ်"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/nb-NO.json b/src/locales/nb-NO.json
index 7c00756724..e234c297d7 100644
--- a/src/locales/nb-NO.json
+++ b/src/locales/nb-NO.json
@@ -80,9 +80,9 @@
     "gridMode": "Rutevisning",
     "addToLibrary": "Legg til i bibliotek",
     "removeFromLibrary": "Fjern fra bibliotek",
-    "libraryLoadingMessage": "Laster bibliotek...",
+    "libraryLoadingMessage": "Laster bibliotek…",
     "libraries": "Bla gjennom biblioteker",
-    "loadingScene": "Laster inn scene...",
+    "loadingScene": "Laster inn scene…",
     "align": "Juster",
     "alignTop": "Juster øverst",
     "alignBottom": "Juster nederst",
@@ -91,7 +91,8 @@
     "centerVertically": "Midtstill vertikalt",
     "centerHorizontally": "Midtstill horisontalt",
     "distributeHorizontally": "Distribuer horisontalt",
-    "distributeVertically": "Distribuer vertikalt"
+    "distributeVertically": "Distribuer vertikalt",
+    "viewMode": "Visningsmodus"
   },
   "buttons": {
     "clearReset": "Tøm lerretet og tilbakestill bakgrunnsfargen",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Feil"
   },
-  "shortcutsDialog": {
-    "title": "Tastatursnarveier",
-    "shapes": "Figurer",
-    "or": "eller",
+  "helpDialog": {
+    "blog": "Les bloggen vår",
     "click": "klikk",
-    "drag": "dra",
     "curvedArrow": "Buet pil",
     "curvedLine": "Buet linje",
-    "editor": "Redigering",
-    "view": "Visning",
-    "blog": "Les bloggen vår",
-    "howto": "Følg våre veiledninger",
+    "documentation": "Dokumentasjon",
+    "drag": "dra",
+    "editor": "Redigeringsvisning",
     "github": "Funnet et problem? Send inn",
-    "textNewLine": "Legg til ny linje (tekst)",
+    "howto": "Følg våre veiledninger",
+    "or": "eller",
+    "preventBinding": "Forhindre pilbinding",
+    "shapes": "Former",
+    "shortcuts": "Tastatursnarveier",
     "textFinish": "Fullfør redigering (tekst)",
-    "zoomToFit": "Zoom for å passe alle elementene",
-    "zoomToSelection": "Zoom til utvalg",
-    "preventBinding": "Forhindre pilbinding"
+    "textNewLine": "Legg til ny linje (tekst)",
+    "title": "Hjelp",
+    "view": "Vis",
+    "zoomToFit": "Zoom for å se alle elementer",
+    "zoomToSelection": "Zoom til utvalg"
   },
   "encrypted": {
     "tooltip": "Dine tegninger er ende-til-ende-krypterte slik at Excalidraw sine servere aldri vil se dem."
@@ -232,5 +235,9 @@
     "title": "Statistikk for nerder",
     "total": "Totalt",
     "width": "Bredde"
+  },
+  "toast": {
+    "copyStyles": "Kopierte stiler.",
+    "copyToClipboardAsPng": "Kopiert til utklippstavlen som PNG."
   }
 }
diff --git a/src/locales/nl-NL.json b/src/locales/nl-NL.json
index 59269210c7..7eb3a1c2ae 100644
--- a/src/locales/nl-NL.json
+++ b/src/locales/nl-NL.json
@@ -91,7 +91,8 @@
     "centerVertically": "Verticaal Centreren",
     "centerHorizontally": "Horizontaal Centreren",
     "distributeHorizontally": "Horizontaal verspreiden",
-    "distributeVertically": "Verticaal distribueren"
+    "distributeVertically": "Verticaal distribueren",
+    "viewMode": "Weergavemodus"
   },
   "buttons": {
     "clearReset": "Canvas opnieuw instellen",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Fout"
   },
-  "shortcutsDialog": {
-    "title": "Sneltoetsen",
-    "shapes": "Vormen",
-    "or": "of",
+  "helpDialog": {
+    "blog": "Lees onze blog",
     "click": "klik",
-    "drag": "slepen",
     "curvedArrow": "Gebogen pijl",
-    "curvedLine": "Gebogen lijn",
+    "curvedLine": "Kromme lijn",
+    "documentation": "Documentatie",
+    "drag": "slepen",
     "editor": "Editor",
-    "view": "Weergave",
-    "blog": "Lees onze blog",
+    "github": "Probleem gevonden? Verzenden",
     "howto": "Volg onze handleidingen",
-    "github": "Probleem gevonden? Stuur een nieuwe issue",
-    "textNewLine": "Nieuwe regel toevoegen (tekst)",
+    "or": "of",
+    "preventBinding": "Pijlbinding voorkomen",
+    "shapes": "Vormen",
+    "shortcuts": "Sneltoetsen",
     "textFinish": "Voltooi bewerken (tekst)",
+    "textNewLine": "Nieuwe regel toevoegen (tekst)",
+    "title": "Help",
+    "view": "Weergave",
     "zoomToFit": "Zoom in op alle elementen",
-    "zoomToSelection": "Inzoomen op selectie",
-    "preventBinding": "Pijlbinding voorkomen"
+    "zoomToSelection": "Inzoomen op selectie"
   },
   "encrypted": {
     "tooltip": "Je tekeningen zijn beveiligd met end-to-end encryptie, dus Excalidraw's servers zullen nooit zien wat je tekent."
@@ -232,5 +235,9 @@
     "title": "Statistieken voor nerds",
     "total": "Totaal",
     "width": "Breedte"
+  },
+  "toast": {
+    "copyStyles": "Stijlen gekopieerd.",
+    "copyToClipboardAsPng": "Gekopieerd naar klembord als PNG."
   }
 }
diff --git a/src/locales/nn-NO.json b/src/locales/nn-NO.json
index 5fa2f9971c..6f6c1cfb51 100644
--- a/src/locales/nn-NO.json
+++ b/src/locales/nn-NO.json
@@ -1,7 +1,7 @@
 {
   "labels": {
     "paste": "Lim inn",
-    "pasteCharts": "Lim inn diagrammer",
+    "pasteCharts": "Lim inn diagram",
     "selectAll": "Vel alt",
     "multiSelect": "Legg til element i utval",
     "moveCanvas": "Flytt lerretet",
@@ -80,9 +80,9 @@
     "gridMode": "Rutevisning",
     "addToLibrary": "Legg til i bibliotek",
     "removeFromLibrary": "Fjern frå bibliotek",
-    "libraryLoadingMessage": "Laster bibliotek...",
+    "libraryLoadingMessage": "Laster bibliotek…",
     "libraries": "Blad gjennom bibliotek",
-    "loadingScene": "Laster scene...",
+    "loadingScene": "Laster scene…",
     "align": "Juster",
     "alignTop": "Juster til topp",
     "alignBottom": "Juster til botn",
@@ -91,7 +91,8 @@
     "centerVertically": "Midtstill vertikalt",
     "centerHorizontally": "Midtstill horisontalt",
     "distributeHorizontally": "Sprei horisontalt",
-    "distributeVertically": "Sprei vertikalt"
+    "distributeVertically": "Sprei vertikalt",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "Tilbakestill lerretet",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Feil"
   },
-  "shortcutsDialog": {
-    "title": "Tastatursnarvegar",
-    "shapes": "Figurar",
-    "or": "eller",
+  "helpDialog": {
+    "blog": "",
     "click": "klikk",
-    "drag": "drag",
-    "curvedArrow": "Boga pil",
-    "curvedLine": "Boga linje",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
     "editor": "Redigering",
+    "github": "",
+    "howto": "",
+    "or": "eller",
+    "preventBinding": "",
+    "shapes": "Formar",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "Hjelp",
     "view": "Vising",
-    "blog": "Les bloggen vår",
-    "howto": "Følg vegleiinga vår",
-    "github": "Funne eit problem? Send inn",
-    "textNewLine": "Legg til ny linje (tekst)",
-    "textFinish": "Fullfør redigering (tekst)",
-    "zoomToFit": "Zoom for å sjå alle elementa",
-    "zoomToSelection": "Zoom til utval",
-    "preventBinding": "Hindre pilkobling"
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "Teikningane dine er ende-til-ende-krypterte slik at Excalidraw sine serverar aldri får sjå dei."
@@ -232,5 +235,9 @@
     "title": "Statistikk for nerdar",
     "total": "Totalt",
     "width": "Breidde"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/pa-IN.json b/src/locales/pa-IN.json
index 8cf973761e..34c6fca5c6 100644
--- a/src/locales/pa-IN.json
+++ b/src/locales/pa-IN.json
@@ -91,7 +91,8 @@
     "centerVertically": "ਲੇਟਵੇਂ ਵਿਚਕਾਰ ਕਰੋ",
     "centerHorizontally": "ਖੜ੍ਹਵੇਂ ਵਿਚਕਾਰ ਕਰੋ",
     "distributeHorizontally": "ਖੜ੍ਹਵੇਂ ਇਕਸਾਰ ਵੰਡੋ",
-    "distributeVertically": "ਲੇਟਵੇਂ ਇਕਸਾਰ ਵੰਡੋ"
+    "distributeVertically": "ਲੇਟਵੇਂ ਇਕਸਾਰ ਵੰਡੋ",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "ਕੈਨਵਸ ਰੀਸੈੱਟ ਕਰੋ",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "ਗਲਤੀ"
   },
-  "shortcutsDialog": {
-    "title": "ਕੀਬੋਰਡ ਸ਼ਾਰਟਕੱਟ",
-    "shapes": "ਆਕ੍ਰਿਤੀਆਂ",
-    "or": "ਜਾਂ",
+  "helpDialog": {
+    "blog": "ਸਾਡਾ ਬਲੌਗ ਪੜ੍ਹੋ",
     "click": "ਕਲਿੱਕ",
-    "drag": "ਘਸੀਟੋ",
     "curvedArrow": "ਵਿੰਗਾ ਤੀਰ",
     "curvedLine": "ਵਿੰਗੀ ਲਕੀਰ",
+    "documentation": "ਕਾਗਜ਼ਾਤ",
+    "drag": "ਘਸੀਟੋ",
     "editor": "ਸੋਧਕ",
-    "view": "ਦਿੱਖ",
-    "blog": "ਸਾਡਾ ਬਲੌਗ ਪੜ੍ਹੋ",
-    "howto": "ਸਾਡੀਆਂ ਗਾਈਡਾਂ ਦੀ ਪਾਲਣਾ ਕਰੋ",
     "github": "ਕੋਈ ਸਮੱਸਿਆ ਲੱਭੀ? ਜਮ੍ਹਾਂ ਕਰਵਾਓ",
-    "textNewLine": "ਨਵੀਂ ਪੰਕਤੀ ਜੋੜੋ (ਪਾਠ)",
+    "howto": "ਸਾਡੀਆਂ ਗਾਈਡਾਂ ਦੀ ਪਾਲਣਾ ਕਰੋ",
+    "or": "ਜਾਂ",
+    "preventBinding": "ਤੀਰ ਬੱਝਣਾ ਰੋਕੋ",
+    "shapes": "ਆਕ੍ਰਿਤੀਆਂ",
+    "shortcuts": "ਕੀਬੋਰਡ ਸ਼ਾਰਟਕੱਟ",
     "textFinish": "ਸੋਧ ਮੁਕੰਮਲ ਕਰੋ (ਪਾਠ)",
+    "textNewLine": "ਨਵੀਂ ਪੰਕਤੀ ਜੋੜੋ (ਪਾਠ)",
+    "title": "ਮਦਦ",
+    "view": "ਦਿੱਖ",
     "zoomToFit": "ਸਾਰੇ ਐਲੀਮੈਂਟਾਂ ਨੂੰ ਫਿੱਟ ਕਰਨ ਲਈ ਜ਼ੂਮ ਕਰੋ",
-    "zoomToSelection": "ਚੋਣ ਤੱਕ ਜ਼ੂਮ ਕਰੋ",
-    "preventBinding": "ਤੀਰ ਬੱਝਣਾ ਰੋਕੋ"
+    "zoomToSelection": "ਚੋਣ ਤੱਕ ਜ਼ੂਮ ਕਰੋ"
   },
   "encrypted": {
     "tooltip": "ਤੁਹਾਡੀ ਡਰਾਇੰਗਾਂ ਸਿਰੇ-ਤੋਂ-ਸਿਰੇ ਤੱਕ ਇਨਕਰਿਪਟ ਕੀਤੀਆਂ ਹੋਈਆਂ ਹਨ, ਇਸ ਲਈ Excalidraw ਦੇ ਸਰਵਰ ਉਹਨਾਂ ਨੂੰ ਕਦੇ ਵੀ ਨਹੀਂ ਦੇਖਣਗੇ।"
@@ -232,5 +235,9 @@
     "title": "ਪੜਾਕੂਆਂ ਲਈ ਅੰਕੜੇ",
     "total": "ਕੁੱਲ",
     "width": "ਚੌੜਾਈ"
+  },
+  "toast": {
+    "copyStyles": "ਕਾਪੀ ਕੀਤੇ ਸਟਾਇਲ।",
+    "copyToClipboardAsPng": "ਕਲਿੱਪਬੋਰਡ 'ਤੇ PNG ਵਜੋਂ ਕਾਪੀ ਕੀਤਾ।"
   }
 }
diff --git a/src/locales/percentages.json b/src/locales/percentages.json
index 67431c5637..8adaab9a95 100644
--- a/src/locales/percentages.json
+++ b/src/locales/percentages.json
@@ -1,35 +1,36 @@
 {
-  "ar-SA": 100,
-  "bg-BG": 100,
-  "ca-ES": 100,
+  "ar-SA": 89,
+  "bg-BG": 93,
+  "ca-ES": 89,
   "de-DE": 100,
   "el-GR": 100,
   "en": 100,
   "es-ES": 100,
-  "fa-IR": 100,
+  "fa-IR": 98,
   "fi-FI": 100,
   "fr-FR": 100,
-  "he-IL": 100,
-  "hi-IN": 100,
-  "hu-HU": 100,
+  "he-IL": 89,
+  "hi-IN": 89,
+  "hu-HU": 89,
   "id-ID": 100,
   "it-IT": 100,
-  "ja-JP": 85,
-  "ko-KR": 100,
-  "my-MM": 93,
+  "ja-JP": 81,
+  "kab-KAB": 97,
+  "ko-KR": 89,
+  "my-MM": 83,
   "nb-NO": 100,
   "nl-NL": 100,
-  "nn-NO": 100,
-  "pa-IN": 100,
-  "pl-PL": 100,
+  "nn-NO": 92,
+  "pa-IN": 99,
+  "pl-PL": 90,
   "pt-BR": 100,
-  "pt-PT": 100,
+  "pt-PT": 99,
   "ro-RO": 100,
-  "ru-RU": 100,
+  "ru-RU": 99,
   "sk-SK": 100,
   "sv-SE": 100,
-  "tr-TR": 100,
-  "uk-UA": 100,
-  "zh-CN": 100,
+  "tr-TR": 89,
+  "uk-UA": 99,
+  "zh-CN": 99,
   "zh-TW": 100
 }
diff --git a/src/locales/pl-PL.json b/src/locales/pl-PL.json
index 3a57524e93..f5a9678438 100644
--- a/src/locales/pl-PL.json
+++ b/src/locales/pl-PL.json
@@ -91,7 +91,8 @@
     "centerVertically": "Wyśrodkuj w pionie",
     "centerHorizontally": "Wyśrodkuj w poziomie",
     "distributeHorizontally": "Rozłóż poziomo",
-    "distributeVertically": "Rozłóż pionowo"
+    "distributeVertically": "Rozłóż pionowo",
+    "viewMode": "Tryb widoku"
   },
   "buttons": {
     "clearReset": "Wyczyść dokument i zresetuj kolor dokumentu",
@@ -135,7 +136,7 @@
     "decryptFailed": "Nie udało się odszyfrować danych.",
     "uploadedSecurly": "By zapewnić Ci prywatność, udostępnianie projektu jest zabezpieczone szyfrowaniem end-to-end, co oznacza, że poza tobą i osobą z którą podzielisz się linkiem, nikt nie ma dostępu do tego co udostępniasz.",
     "loadSceneOverridePrompt": "Wczytanie zewnętrznego rysunku zastąpi istniejącą zawartość. Czy chcesz kontynuować?",
-    "errorLoadingLibrary": "Wystąpił błąd podczas ładowania biblioteki stron trzecich.",
+    "errorLoadingLibrary": "Wystąpił błąd podczas ładowania zewnętrznej biblioteki.",
     "confirmAddLibrary": "To doda {{numShapes}} kształtów do twojej biblioteki. Jesteś pewien?",
     "imageDoesNotContainScene": "Importowanie zdjęć nie jest obecnie obsługiwane.\n\nCzy chciałeś zaimportować scenę? Ten obraz nie zawiera żadnych danych sceny. Czy włączyłeś to podczas eksportowania?",
     "cannotRestoreFromImage": "Scena nie mogła zostać przywrócona z pliku obrazu"
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Wystąpił błąd"
   },
-  "shortcutsDialog": {
-    "title": "Skróty klawiszowe",
-    "shapes": "Kształty",
-    "or": "lub",
-    "click": "klik",
-    "drag": "przeciągnij",
-    "curvedArrow": "Zakrzywiona strzałka",
-    "curvedLine": "Zakrzywiona linia",
-    "editor": "Edytor",
-    "view": "Widok",
-    "blog": "Przeczytaj naszego bloga",
-    "howto": "Skorzystaj z instrukcji",
-    "github": "Znalazłeś problem? Zgłoś go",
-    "textNewLine": "Dodaj nową linię (tekst)",
-    "textFinish": "Zakończ edycję (tekst)",
-    "zoomToFit": "Powiększ, aby wyświetlić wszystkie elementy",
-    "zoomToSelection": "Przybliż zaznaczenie",
-    "preventBinding": "Zablokuj przywiązanie strzałek do obiektu"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "Twoje rysunki są zabezpieczone szyfrowaniem end-to-end, tak więc nawet w Excalidraw nie jesteśmy w stanie zobaczyć tego co tworzysz."
@@ -232,5 +235,9 @@
     "title": "Statystyki dla nerdów",
     "total": "Łącznie",
     "width": "Szerokość"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/pt-BR.json b/src/locales/pt-BR.json
index 0f0af47580..461bfb2f3a 100644
--- a/src/locales/pt-BR.json
+++ b/src/locales/pt-BR.json
@@ -91,7 +91,8 @@
     "centerVertically": "Centralizar verticalmente",
     "centerHorizontally": "Centralizar horizontalmente",
     "distributeHorizontally": "Distribuir horizontalmente",
-    "distributeVertically": "Distribuir verticalmente"
+    "distributeVertically": "Distribuir verticalmente",
+    "viewMode": "Modo de visualização"
   },
   "buttons": {
     "clearReset": "Limpar o canvas e redefinir a cor de fundo",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Erro"
   },
-  "shortcutsDialog": {
-    "title": "Atalhos de teclado",
-    "shapes": "Formas",
-    "or": "ou",
+  "helpDialog": {
+    "blog": "Leia o nosso blog",
     "click": "clicar",
-    "drag": "arrastar",
     "curvedArrow": "Seta curva",
     "curvedLine": "Linha curva",
+    "documentation": "Documentação",
+    "drag": "arrastar",
     "editor": "Editor",
-    "view": "Visualizar",
-    "blog": "Leia o nosso blog",
-    "howto": "Siga os nossos guias",
     "github": "Encontrou algum problema? Nos informe",
-    "textNewLine": "Adicionar nova linha (texto)",
+    "howto": "Siga nossos guias",
+    "or": "ou",
+    "preventBinding": "Evitar fixação de seta",
+    "shapes": "Formas",
+    "shortcuts": "Atalhos de teclado",
     "textFinish": "Finalizar edição (texto)",
-    "zoomToFit": "Ajustar para caber todos os elementos",
-    "zoomToSelection": "Ampliar a seleção",
-    "preventBinding": "Prevenir fixação de seta"
+    "textNewLine": "Adicionar nova linha (texto)",
+    "title": "Ajudar",
+    "view": "Visualizar",
+    "zoomToFit": "Ampliar para encaixar todos os elementos",
+    "zoomToSelection": "Ampliar a seleção"
   },
   "encrypted": {
     "tooltip": "Seus desenhos são criptografados de ponta a ponta, então os servidores do Excalidraw nunca os verão."
@@ -232,5 +235,9 @@
     "title": "Estatísticas para nerds",
     "total": "Total",
     "width": "Largura"
+  },
+  "toast": {
+    "copyStyles": "Estilos copiados.",
+    "copyToClipboardAsPng": "Copiado para a área de transferência como PNG."
   }
 }
diff --git a/src/locales/pt-PT.json b/src/locales/pt-PT.json
index d217b41cd9..44928e45c7 100644
--- a/src/locales/pt-PT.json
+++ b/src/locales/pt-PT.json
@@ -80,9 +80,9 @@
     "gridMode": "Modo grade",
     "addToLibrary": "Adicionar à biblioteca",
     "removeFromLibrary": "Remover da biblioteca",
-    "libraryLoadingMessage": "Carregando biblioteca...",
+    "libraryLoadingMessage": "Carregando biblioteca…",
     "libraries": "Procurar bibliotecas",
-    "loadingScene": "Carregando cena...",
+    "loadingScene": "Carregando cena…",
     "align": "Alinhamento",
     "alignTop": "Alinhar ao topo",
     "alignBottom": "Alinhar ao fundo",
@@ -91,7 +91,8 @@
     "centerVertically": "Centralizar verticalmente",
     "centerHorizontally": "Centralizar horizontalmente",
     "distributeHorizontally": "Distribuir horizontalmente",
-    "distributeVertically": "Distribuir verticalmente"
+    "distributeVertically": "Distribuir verticalmente",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "Limpar o canvas e redefinir a cor de fundo",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Erro"
   },
-  "shortcutsDialog": {
-    "title": "Atalhos de teclado",
-    "shapes": "Formas",
-    "or": "ou",
+  "helpDialog": {
+    "blog": "Leia o nosso blog",
     "click": "clicar",
-    "drag": "arrastar",
     "curvedArrow": "Seta curva",
     "curvedLine": "Linha curva",
+    "documentation": "Documentação",
+    "drag": "arrastar",
     "editor": "Editor",
-    "view": "Visualizar",
-    "blog": "Leia o nosso blog",
-    "howto": "Siga os nossos guias",
     "github": "Encontrou algum problema? Nos informe",
-    "textNewLine": "Adicionar nova linha (texto)",
+    "howto": "Siga os nossos guias",
+    "or": "ou",
+    "preventBinding": "Prevenir fixação de seta",
+    "shapes": "Formas",
+    "shortcuts": "Atalhos de teclado",
     "textFinish": "Finalizar edição (texto)",
+    "textNewLine": "Adicionar nova linha (texto)",
+    "title": "Ajuda",
+    "view": "Visualizar",
     "zoomToFit": "Ajustar para caber todos os elementos",
-    "zoomToSelection": "Ampliar a seleção",
-    "preventBinding": "Prevenir fixação de seta"
+    "zoomToSelection": "Ampliar a seleção"
   },
   "encrypted": {
     "tooltip": "Seus desenhos são criptografados de ponta a ponta, então os servidores do Excalidraw nunca os verão."
@@ -232,5 +235,9 @@
     "title": "Estatísticas para nerds",
     "total": "Total",
     "width": "Largura"
+  },
+  "toast": {
+    "copyStyles": "Estilos copiados.",
+    "copyToClipboardAsPng": "Copiado para o clipboard como PNG."
   }
 }
diff --git a/src/locales/ro-RO.json b/src/locales/ro-RO.json
index 8b283af6ac..0601ea01b3 100644
--- a/src/locales/ro-RO.json
+++ b/src/locales/ro-RO.json
@@ -80,9 +80,9 @@
     "gridMode": "Mod grilă",
     "addToLibrary": "Adăugare la bibliotecă",
     "removeFromLibrary": "Eliminare din bibliotecă",
-    "libraryLoadingMessage": "Se încarcă biblioteca...",
+    "libraryLoadingMessage": "Se încarcă biblioteca…",
     "libraries": "Răsfoiește bibliotecile",
-    "loadingScene": "Se încarcă scena...",
+    "loadingScene": "Se încarcă scena…",
     "align": "Aliniere",
     "alignTop": "Aliniere sus",
     "alignBottom": "Aliniere jos",
@@ -91,7 +91,8 @@
     "centerVertically": "Centrare verticală",
     "centerHorizontally": "Centrare orizontală",
     "distributeHorizontally": "Distribuie orizontal",
-    "distributeVertically": "Distribuie vertical"
+    "distributeVertically": "Distribuie vertical",
+    "viewMode": "Mod de vizualizare"
   },
   "buttons": {
     "clearReset": "Resetare pânză",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Eroare"
   },
-  "shortcutsDialog": {
-    "title": "Comenzi rapide de la tastatură",
-    "shapes": "Forme",
-    "or": "sau",
+  "helpDialog": {
+    "blog": "Citește blogul nostru",
     "click": "clic",
-    "drag": "glisare",
     "curvedArrow": "Săgeată curbată",
     "curvedLine": "Linie curbată",
+    "documentation": "Documentație",
+    "drag": "glisare",
     "editor": "Editor",
-    "view": "Vizualizare",
-    "blog": "Citește blogul nostru",
-    "howto": "Urmărește ghidurile noastre",
     "github": "Ai întâmpinat o problemă? Trimite un raport",
-    "textNewLine": "Adaugă o linie nouă (text)",
+    "howto": "Urmărește ghidurile noastre",
+    "or": "sau",
+    "preventBinding": "Împiedică legarea săgeții",
+    "shapes": "Forme",
+    "shortcuts": "Comenzi rapide de la tastatură",
     "textFinish": "Finalizează editarea (text)",
-    "zoomToFit": "Apropiere/depărtare pentru a cuprinde totul",
-    "zoomToSelection": "Panoramare la selecție",
-    "preventBinding": "Împiedică legarea săgeții"
+    "textNewLine": "Adaugă o linie nouă (text)",
+    "title": "Ajutor",
+    "view": "Vizualizare",
+    "zoomToFit": "Panoramare pentru a cuprinde totul",
+    "zoomToSelection": "Panoramare la selecție"
   },
   "encrypted": {
     "tooltip": "Desenele tale sunt criptate integral, astfel că serverele Excalidraw nu le vor vedea niciodată."
@@ -232,5 +235,9 @@
     "title": "Statistici pentru pasionați",
     "total": "Total",
     "width": "Lățime"
+  },
+  "toast": {
+    "copyStyles": "Stiluri copiate.",
+    "copyToClipboardAsPng": "Copiat în memoria temporară ca PNG."
   }
 }
diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json
index 9e95888f2e..0f04dd96be 100644
--- a/src/locales/ru-RU.json
+++ b/src/locales/ru-RU.json
@@ -22,8 +22,8 @@
     "strokeWidth": "Толщина штриха",
     "strokeStyle": "Стиль обводки",
     "strokeStyle_solid": "Сплошная",
-    "strokeStyle_dashed": "Штриховая",
-    "strokeStyle_dotted": "Пунктирная",
+    "strokeStyle_dashed": "Пунктирная",
+    "strokeStyle_dotted": "Точечная",
     "sloppiness": "Стиль обводки",
     "opacity": "Непрозрачность",
     "textAlign": "Выравнивание текста",
@@ -31,9 +31,9 @@
     "sharp": "Острые",
     "round": "Скругленные",
     "arrowheads": "Стрелка",
-    "arrowhead_none": "Без стрелки",
+    "arrowhead_none": "Нет",
     "arrowhead_arrow": "Cтрелка",
-    "arrowhead_bar": "Столбец",
+    "arrowhead_bar": "Черта",
     "arrowhead_dot": "Точка",
     "fontSize": "Размер шрифта",
     "fontFamily": "Семейство шрифтов",
@@ -41,8 +41,8 @@
     "withBackground": "С фоном",
     "exportEmbedScene": "Встроить информацию о сцене в экспортируемый файл",
     "exportEmbedScene_details": "Сцена будет сохранена в PNG/SVG файл так, чтобы всю сцену можно будет восстановить из этого файла. Это увеличит размер файла.",
-    "addWatermark": "Добавить \"Сделано с Excalidraw\"",
-    "handDrawn": "Нарисованный от руки",
+    "addWatermark": "Добавить «Создано в Excalidraw»",
+    "handDrawn": "От руки",
     "normal": "Обычный",
     "code": "Код",
     "small": "Малый",
@@ -64,13 +64,13 @@
     "fileTitle": "Название файла",
     "colorPicker": "Выбор цвета",
     "canvasBackground": "Фон холста",
-    "drawingCanvas": "Холст для рисования",
+    "drawingCanvas": "Полотно",
     "layers": "Слои",
     "actions": "Действия",
     "language": "Язык",
-    "createRoom": "Создать многопользовательскую сессию",
+    "createRoom": "Начать сеанс совместной работы",
     "duplicateSelection": "Дубликат",
-    "untitled": "Без названия",
+    "untitled": "Безымянный",
     "name": "Имя",
     "yourName": "Ваше имя",
     "madeWithExcalidraw": "Сделано в Excalidraw",
@@ -80,9 +80,9 @@
     "gridMode": "Сетка",
     "addToLibrary": "Добавить в библиотеку",
     "removeFromLibrary": "Удалить из библиотеки",
-    "libraryLoadingMessage": "Загрузка библиотеки...",
+    "libraryLoadingMessage": "Загрузка библиотеки…",
     "libraries": "Просмотреть библиотеки",
-    "loadingScene": "Загрузка сцены...",
+    "loadingScene": "Загрузка сцены…",
     "align": "Выровнять",
     "alignTop": "Выровнять по верхнему краю",
     "alignBottom": "Выровнять по нижнему краю",
@@ -91,7 +91,8 @@
     "centerVertically": "Центрировать по вертикали",
     "centerHorizontally": "Центрировать по горизонтали",
     "distributeHorizontally": "Распределить по горизонтали",
-    "distributeVertically": "Распределить по вертикали"
+    "distributeVertically": "Распределить по вертикали",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "Очистить холст и сбросить цвет фона",
@@ -189,34 +190,36 @@
   },
   "roomDialog": {
     "desc_intro": "Вы можете пригласить людей в текущую сцену для совместной работы.",
-    "desc_privacy": "Не беспокойтесь, сессия использует сквозное шифрование, поэтому всё что вы нарисуете останется приватным. Ваша информация не будет доступна даже на наших серверах.",
+    "desc_privacy": "Не беспокойтесь — во время сеанса используется сквозное шифрование. Всё, что вы нарисуете, останется конфиденциальным и не будет доступно даже нашему серверу.",
     "button_startSession": "Начать сеанс",
     "button_stopSession": "Завершить сеанс",
-    "desc_inProgressIntro": "Совместная сессия теперь активна.",
+    "desc_inProgressIntro": "Сеанс совместной работы запущен.",
     "desc_shareLink": "Поделитесь этой ссылкой со всеми участниками:",
     "desc_exitSession": "Завершив сеанс, вы выйдете из комнаты, но сможете продолжить работать с документом локально. Это не повлияет на работу других пользователей — они смогут продолжить совместную работу с их версией документа."
   },
   "errorDialog": {
     "title": "Ошибка"
   },
-  "shortcutsDialog": {
-    "title": "Сочетания клавиш",
-    "shapes": "Фигуры",
-    "or": "или",
+  "helpDialog": {
+    "blog": "Прочитайте наш блог",
     "click": "нажать",
-    "drag": "перетащить",
     "curvedArrow": "Изогнутая стрелка",
     "curvedLine": "Изогнутая линия",
+    "documentation": "Документация",
+    "drag": "перетащить",
     "editor": "Редактор",
-    "view": "Просмотр",
-    "blog": "Прочитайте наш блог",
-    "howto": "Следуйте нашим инструкциям",
     "github": "Нашли проблему? Отправьте",
-    "textNewLine": "Добавить новую строку (текст)",
+    "howto": "Следуйте нашим инструкциям",
+    "or": "или",
+    "preventBinding": "Предотвращать привязку стрелок",
+    "shapes": "Фигуры",
+    "shortcuts": "Горячие клавиши",
     "textFinish": "Закончить редактирование (текст)",
+    "textNewLine": "Добавить новую строку (текст)",
+    "title": "Помощь",
+    "view": "Просмотр",
     "zoomToFit": "Отмастштабировать, чтобы поместились все элементы",
-    "zoomToSelection": "Перейти к выделенному",
-    "preventBinding": "Предотвратить привязку стрелок"
+    "zoomToSelection": "Увеличить до выделенного"
   },
   "encrypted": {
     "tooltip": "Ваши данные защищены сквозным (End-to-end) шифрованием. Серверы Excalidraw никогда не получат доступ к ним."
@@ -232,5 +235,9 @@
     "title": "Статистика для ботаников",
     "total": "Всего",
     "width": "Ширина"
+  },
+  "toast": {
+    "copyStyles": "Скопированы стили.",
+    "copyToClipboardAsPng": "Скопировано в буфер обмена в формате PNG."
   }
 }
diff --git a/src/locales/sk-SK.json b/src/locales/sk-SK.json
index 3b71932085..c30e8fafb8 100644
--- a/src/locales/sk-SK.json
+++ b/src/locales/sk-SK.json
@@ -80,9 +80,9 @@
     "gridMode": "Režim mriežky",
     "addToLibrary": "Pridať do knižnice",
     "removeFromLibrary": "Odstrániť z knižnice",
-    "libraryLoadingMessage": "Načítavanie knižnice...",
+    "libraryLoadingMessage": "Načítavanie knižnice…",
     "libraries": "Prehliadať knižnice",
-    "loadingScene": "Načítavanie scény...",
+    "loadingScene": "Načítavanie scény…",
     "align": "Zarovnanie",
     "alignTop": "Zarovnať nahor",
     "alignBottom": "Zarovnať nadol",
@@ -91,7 +91,8 @@
     "centerVertically": "Zarovnať zvislo na stred",
     "centerHorizontally": "Zarovnať vodorovne na stred",
     "distributeHorizontally": "Rozmiestniť vodorovne",
-    "distributeVertically": "Rozmiestniť zvisle"
+    "distributeVertically": "Rozmiestniť zvisle",
+    "viewMode": "Režim zobrazenia"
   },
   "buttons": {
     "clearReset": "Obnoviť plátno",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Chyba"
   },
-  "shortcutsDialog": {
-    "title": "Klávesové skratky",
-    "shapes": "Tvary",
-    "or": "alebo",
+  "helpDialog": {
+    "blog": "Prečítajte si náš blog",
     "click": "kliknutie",
-    "drag": "potiahnutie",
     "curvedArrow": "Zakrivená šípka",
     "curvedLine": "Zakrivená čiara",
+    "documentation": "Dokumentácia",
+    "drag": "potiahnutie",
     "editor": "Editovanie",
-    "view": "Zobrazenie",
-    "blog": "Prečítajte si náš blog",
-    "howto": "Postupujte podľa naších návodov",
     "github": "Objavili ste problém? Nahláste ho",
-    "textNewLine": "Vložiť nový riadok (text)",
+    "howto": "Postupujte podľa naších návodov",
+    "or": "alebo",
+    "preventBinding": "Zakázať pripájanie šípky",
+    "shapes": "Tvary",
+    "shortcuts": "Klávesové skratky",
     "textFinish": "Ukončenie editovania (text)",
+    "textNewLine": "Vložiť nový riadok (text)",
+    "title": "Pomocník",
+    "view": "Zobrazenie",
     "zoomToFit": "Priblížiť aby boli zahrnuté všetky prvky",
-    "zoomToSelection": "Priblížiť na výber",
-    "preventBinding": "Zakázať pripájanie šípky"
+    "zoomToSelection": "Priblížiť na výber"
   },
   "encrypted": {
     "tooltip": "Vaše kresby používajú end-to-end šifrovanie, takže ich Excalidraw server nedokáže prečítať."
@@ -232,5 +235,9 @@
     "title": "Štatistiky",
     "total": "Celkom",
     "width": "Šírka"
+  },
+  "toast": {
+    "copyStyles": "Štýly skopírované.",
+    "copyToClipboardAsPng": "Skopírované do schránky ako PNG."
   }
 }
diff --git a/src/locales/sv-SE.json b/src/locales/sv-SE.json
index e749a977e0..a305fe33bc 100644
--- a/src/locales/sv-SE.json
+++ b/src/locales/sv-SE.json
@@ -80,9 +80,9 @@
     "gridMode": "Rutnätsläge",
     "addToLibrary": "Lägg till i biblioteket",
     "removeFromLibrary": "Ta bort från bibliotek",
-    "libraryLoadingMessage": "Laddar bibliotek...",
+    "libraryLoadingMessage": "Laddar bibliotek…",
     "libraries": "Bläddra i bibliotek",
-    "loadingScene": "Laddar scen...",
+    "loadingScene": "Laddar scen…",
     "align": "Justera",
     "alignTop": "Justera överkant",
     "alignBottom": "Justera underkant",
@@ -91,7 +91,8 @@
     "centerVertically": "Centrera vertikalt",
     "centerHorizontally": "Centrera horisontellt",
     "distributeHorizontally": "Fördela horisontellt",
-    "distributeVertically": "Fördela vertikalt"
+    "distributeVertically": "Fördela vertikalt",
+    "viewMode": "Visningsläge"
   },
   "buttons": {
     "clearReset": "Återställ canvasen",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Fel"
   },
-  "shortcutsDialog": {
-    "title": "Tangentbordsgenvägar",
-    "shapes": "Former",
-    "or": "eller",
+  "helpDialog": {
+    "blog": "Läs vår blogg",
     "click": "klicka",
-    "drag": "dra",
     "curvedArrow": "Böjd pil",
     "curvedLine": "Böjd linje",
+    "documentation": "Dokumentation",
+    "drag": "dra",
     "editor": "Redigerare",
-    "view": "Visa",
-    "blog": "Läs vår blogg",
-    "howto": "Följ våra guider",
     "github": "Hittat ett problem? Rapportera",
-    "textNewLine": "Lägg till ny rad (text)",
+    "howto": "Följ våra guider",
+    "or": "eller",
+    "preventBinding": "Förhindra pilbindning",
+    "shapes": "Former",
+    "shortcuts": "Tangentbordsgenvägar",
     "textFinish": "Slutför redigering (text)",
+    "textNewLine": "Lägg till ny rad (text)",
+    "title": "Hjälp",
+    "view": "Visa",
     "zoomToFit": "Zooma för att rymma alla element",
-    "zoomToSelection": "Zooma till markering",
-    "preventBinding": "Förhindra pilbindning"
+    "zoomToSelection": "Zooma till markering"
   },
   "encrypted": {
     "tooltip": "Dina skisser är krypterade från ände till ände så Excalidraws servrar kommer aldrig att se dem."
@@ -232,5 +235,9 @@
     "title": "Statistik för nördar",
     "total": "Totalt",
     "width": "Bredd"
+  },
+  "toast": {
+    "copyStyles": "Kopierade stilar.",
+    "copyToClipboardAsPng": "Kopierat till urklipp som PNG."
   }
 }
diff --git a/src/locales/tr-TR.json b/src/locales/tr-TR.json
index 6382029a47..d7b9c52bf0 100644
--- a/src/locales/tr-TR.json
+++ b/src/locales/tr-TR.json
@@ -82,7 +82,7 @@
     "removeFromLibrary": "Kütüphaneden kaldır",
     "libraryLoadingMessage": "Kütüphane yükleniyor...",
     "libraries": "Kütüphanelere gözat",
-    "loadingScene": "Çalışma alanı yükleniyor...",
+    "loadingScene": "Çalışma alanı yükleniyor…",
     "align": "Hizala",
     "alignTop": "Yukarı hizala",
     "alignBottom": "Aşağı hizala",
@@ -91,7 +91,8 @@
     "centerVertically": "Dikeyde ortala",
     "centerHorizontally": "Yatayda ortala",
     "distributeHorizontally": "Yatay dağıt",
-    "distributeVertically": "Dikey dağıt"
+    "distributeVertically": "Dikey dağıt",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "Tuvali sıfırla",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Hata"
   },
-  "shortcutsDialog": {
-    "title": "Klavye kısayolları",
-    "shapes": "Şekiller",
-    "or": "veya",
-    "click": "tıkla",
-    "drag": "sürükle",
-    "curvedArrow": "Eğri ok",
-    "curvedLine": "Eğri çizgi",
-    "editor": "Düzenleyici",
-    "view": "Görüntüle",
-    "blog": "Blog'umuzu okuyun",
-    "howto": "Rehberlerimizi takip edin",
-    "github": "Bir hata mı buldun? Bildir",
-    "textNewLine": "Yeni satır ekle (yazı)",
-    "textFinish": "(Yazıyı) düzenlemeyi bitir",
-    "zoomToFit": "Tüm öğeleri sığdırmak için yakınlaştır",
-    "zoomToSelection": "Seçime yaklaş",
-    "preventBinding": "Ok bağlamayı önleyin"
+  "helpDialog": {
+    "blog": "",
+    "click": "",
+    "curvedArrow": "",
+    "curvedLine": "",
+    "documentation": "",
+    "drag": "",
+    "editor": "",
+    "github": "",
+    "howto": "",
+    "or": "",
+    "preventBinding": "",
+    "shapes": "",
+    "shortcuts": "",
+    "textFinish": "",
+    "textNewLine": "",
+    "title": "",
+    "view": "",
+    "zoomToFit": "",
+    "zoomToSelection": ""
   },
   "encrypted": {
     "tooltip": "Çizimleriniz uçtan-uca şifrelenmiştir, Excalidraw'ın sunucuları bile onları göremez."
@@ -232,5 +235,9 @@
     "title": "İnekler için istatistikler",
     "total": "Toplam",
     "width": "Genişlik"
+  },
+  "toast": {
+    "copyStyles": "",
+    "copyToClipboardAsPng": ""
   }
 }
diff --git a/src/locales/uk-UA.json b/src/locales/uk-UA.json
index a9b9a7fd40..9f3df61058 100644
--- a/src/locales/uk-UA.json
+++ b/src/locales/uk-UA.json
@@ -80,9 +80,9 @@
     "gridMode": "Режим сітки",
     "addToLibrary": "Додати до бібліотеки",
     "removeFromLibrary": "Видалити з бібліотеки",
-    "libraryLoadingMessage": "Завантажити бібліотеку...",
+    "libraryLoadingMessage": "Завантажити бібліотеку…",
     "libraries": "Огляд бібліотек",
-    "loadingScene": "Завантаження сцени...",
+    "loadingScene": "Завантаження сцени…",
     "align": "Вирівнювання",
     "alignTop": "Вирівняти по верхньому краю",
     "alignBottom": "Вирівняти по нижньому краю",
@@ -91,7 +91,8 @@
     "centerVertically": "Центрувати по вертикалі",
     "centerHorizontally": "Центрувати по горизонталі",
     "distributeHorizontally": "Розподілити по горизонталі",
-    "distributeVertically": "Розподілити вертикально"
+    "distributeVertically": "Розподілити вертикально",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "Очистити полотно",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "Помилка"
   },
-  "shortcutsDialog": {
-    "title": "Гарячі клавіші",
-    "shapes": "Фігури",
-    "or": "або",
+  "helpDialog": {
+    "blog": "Наш блог",
     "click": "натиснути",
+    "curvedArrow": "Крива стрілка",
+    "curvedLine": "Крива лінія",
+    "documentation": "Документація",
     "drag": "перетягнути",
-    "curvedArrow": "Вигнута стрілка",
-    "curvedLine": "Вигнута лінія",
     "editor": "Редактор",
-    "view": "Вигляд",
-    "blog": "Читайте наш блог",
+    "github": "Знайшли помилку? Повідомте",
     "howto": "Дотримуйтесь наших інструкцій",
-    "github": "Знайшли помилку? Повідомте!",
-    "textNewLine": "Додати новий рядок (текст)",
+    "or": "або",
+    "preventBinding": "Запобігти зв'язування зі стрілками",
+    "shapes": "Фігури",
+    "shortcuts": "Гарячі клавіші",
     "textFinish": "Завершити редагування (текст)",
-    "zoomToFit": "Збільшити щоб умістити все",
-    "zoomToSelection": "Перейти до виділеного",
-    "preventBinding": "Запобігти зв'язування зі стрілками"
+    "textNewLine": "Додати новий рядок (текст)",
+    "title": "Допомога",
+    "view": "Вигляд",
+    "zoomToFit": "Збільшити щоб умістити всі елементи",
+    "zoomToSelection": "Наблизити вибране"
   },
   "encrypted": {
     "tooltip": "Ваші креслення захищені наскрізним шифруванням — сервери Excalidraw ніколи їх не побачать."
@@ -232,5 +235,9 @@
     "title": "Статистика",
     "total": "Всього",
     "width": "Ширина"
+  },
+  "toast": {
+    "copyStyles": "Скопійовані стилі.",
+    "copyToClipboardAsPng": "Скопійовано в буфер обміну як PNG."
   }
 }
diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json
index 71631fafe0..b32e5bfc46 100644
--- a/src/locales/zh-CN.json
+++ b/src/locales/zh-CN.json
@@ -6,7 +6,7 @@
     "multiSelect": "添加元素到选区",
     "moveCanvas": "移动画布",
     "cut": "剪切",
-    "copy": "复制",
+    "copy": "拷贝",
     "copyAsPng": "复制为 PNG 到剪贴板",
     "copyAsSvg": "复制为 SVG 到剪贴板",
     "bringForward": "上移一层",
@@ -39,8 +39,8 @@
     "fontFamily": "字体",
     "onlySelected": "仅被选中",
     "withBackground": "使用背景",
-    "exportEmbedScene": "将场景嵌入到导出的文件",
-    "exportEmbedScene_details": "场景数据将被保存到导出的 PNG/SVG 文件,以便恢复。\n将会增加导出的文件大小。",
+    "exportEmbedScene": "将画布数据嵌入到导出的文件",
+    "exportEmbedScene_details": "画布数据将被保存到导出的 PNG/SVG 文件,以便恢复。\n将会增加导出的文件大小。",
     "addWatermark": "添加 “使用 Excalidraw 创建” 水印",
     "handDrawn": "手写",
     "normal": "正常",
@@ -60,16 +60,16 @@
     "extraBold": "超粗",
     "architect": "朴素",
     "artist": "艺术",
-    "cartoonist": "漫画师",
+    "cartoonist": "漫画家",
     "fileTitle": "文件标题",
     "colorPicker": "调色盘",
-    "canvasBackground": "Canvas 背景",
+    "canvasBackground": "画布背景",
     "drawingCanvas": "绘制 Canvas",
     "layers": "图层",
     "actions": "操作",
     "language": "语言",
     "createRoom": "分享实时协作会议",
-    "duplicateSelection": "复制所有已选择的元素",
+    "duplicateSelection": "复制",
     "untitled": "无标题",
     "name": "名字",
     "yourName": "您的姓名",
@@ -80,9 +80,9 @@
     "gridMode": "网格模式",
     "addToLibrary": "添加到库中",
     "removeFromLibrary": "从库中移除",
-    "libraryLoadingMessage": "正在加载库...",
+    "libraryLoadingMessage": "正在加载库…",
     "libraries": "浏览库",
-    "loadingScene": "正在加载绘图...",
+    "loadingScene": "正在加载绘图…",
     "align": "对齐",
     "alignTop": "顶部对齐",
     "alignBottom": "底端对齐",
@@ -91,7 +91,8 @@
     "centerVertically": "垂直居中",
     "centerHorizontally": "水平居中",
     "distributeHorizontally": "水平等距分布",
-    "distributeVertically": "垂直等距分布"
+    "distributeVertically": "垂直等距分布",
+    "viewMode": ""
   },
   "buttons": {
     "clearReset": "重置画布",
@@ -121,24 +122,24 @@
     "fullScreen": "全屏",
     "darkMode": "暗色主题",
     "lightMode": "浅色模式",
-    "zenMode": "禅意模式",
+    "zenMode": "禅模式",
     "exitZenMode": "退出禅模式"
   },
   "alerts": {
     "clearReset": "这将会清除整个 画板。您是否要继续?",
     "couldNotCreateShareableLink": "无法创建共享链接",
-    "couldNotCreateShareableLinkTooBig": "无法创建可共享链接:场景过大",
-    "couldNotLoadInvalidFile": "无法加载错误文件",
-    "importBackendFailed": "从后端导入失败",
-    "cannotExportEmptyCanvas": "无法导出空画布。",
-    "couldNotCopyToClipboard": "无法复制到剪贴板。请尝试使用 Chrome 浏览器。",
+    "couldNotCreateShareableLinkTooBig": "无法创建可共享链接:画布过大",
+    "couldNotLoadInvalidFile": "无法加载无效的文件",
+    "importBackendFailed": "从后端导入失败。",
+    "cannotExportEmptyCanvas": "无法导出空白画布。",
+    "couldNotCopyToClipboard": "无法复制到剪贴板,请尝试使用 Chrome 浏览器。",
     "decryptFailed": "无法解密数据。",
     "uploadedSecurly": "上传已被端到端加密保护,这意味着 Excalidraw 的服务器和第三方都无法读取内容。",
     "loadSceneOverridePrompt": "加载外部绘图将取代您现有的内容。您想要继续吗?",
     "errorLoadingLibrary": "加载第三方库时出错。",
     "confirmAddLibrary": "这将添加 {{numShapes}} 个形状到您的库。您确定吗?",
-    "imageDoesNotContainScene": "当前不支持导入图片。\n\n您想要导入场景吗?此图像似乎不包含任何场景数据。您是否在导出过程中启用了这个数据?",
-    "cannotRestoreFromImage": "无法从此图像文件恢复场景"
+    "imageDoesNotContainScene": "当前不支持导入图片。\n\n您想要导入画布数据吗?此图像似乎不包含任何画布数据。您是否在导出过程中启用了嵌入画布的选项?",
+    "cannotRestoreFromImage": "无法从此图像文件恢复画布"
   },
   "toolBar": {
     "selection": "选择",
@@ -182,7 +183,7 @@
     "clearCanvasCaveat": "这会造成当前工作丢失",
     "trackedToSentry_pre": "带有标识符的错误",
     "trackedToSentry_post": "已在我们的系统中跟踪",
-    "openIssueMessage_pre": "我们非常谨慎地不将您的场景信息包含在错误信息中。如果您的场景不是私密的,请考虑跟进我们的",
+    "openIssueMessage_pre": "我们非常谨慎地处理错误信息,您的画布内容不会被包含在错误报告中。如果您的画布内容不需要保持私密,请考虑使用我们的",
     "openIssueMessage_button": "错误追踪器。",
     "openIssueMessage_post": " 请复制并粘贴以下信息到 GitHub Issue 中。",
     "sceneContent": "画布内容:"
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "错误"
   },
-  "shortcutsDialog": {
-    "title": "快捷键列表",
-    "shapes": "形状",
-    "or": "或",
-    "click": "点击",
+  "helpDialog": {
+    "blog": "浏览我们的博客",
+    "click": "单击",
+    "curvedArrow": "曲线箭头",
+    "curvedLine": "曲线",
+    "documentation": "文档",
     "drag": "拖动",
-    "curvedArrow": "曲线(带有箭头)",
-    "curvedLine": "曲线(无箭头)",
     "editor": "编辑器",
-    "view": "视图",
-    "blog": "浏览我们的博客",
-    "howto": "跟随我们的指南",
-    "github": "发现问题?请提出来",
+    "github": "提交问题",
+    "howto": "帮助文档",
+    "or": "或",
+    "preventBinding": "禁用箭头吸附",
+    "shapes": "形状",
+    "shortcuts": "快捷键列表",
+    "textFinish": "完成文本编辑",
     "textNewLine": "文本换行",
-    "textFinish": "完成编辑文本",
+    "title": "帮助",
+    "view": "视图",
     "zoomToFit": "缩放以适应所有元素",
-    "zoomToSelection": "缩放至选择部分",
-    "preventBinding": "防止箭头吸附"
+    "zoomToSelection": "缩放到选区"
   },
   "encrypted": {
     "tooltip": "您的绘图采用的端到端加密,其内容对于Excalidraw服务器是不可见的。"
@@ -226,11 +229,15 @@
     "element": "元素",
     "elements": "元素",
     "height": "高度",
-    "scene": "场景",
+    "scene": "画布",
     "selected": "选中",
     "storage": "存储",
     "title": "详细统计信息",
     "total": "总计",
     "width": "宽度"
+  },
+  "toast": {
+    "copyStyles": "复制样式",
+    "copyToClipboardAsPng": "复制为 PNG 到剪贴板"
   }
 }
diff --git a/src/locales/zh-TW.json b/src/locales/zh-TW.json
index eed021e2d6..fd9fd41bae 100644
--- a/src/locales/zh-TW.json
+++ b/src/locales/zh-TW.json
@@ -91,7 +91,8 @@
     "centerVertically": "垂直置中",
     "centerHorizontally": "水平置中",
     "distributeHorizontally": "水平分布",
-    "distributeVertically": "垂直分布"
+    "distributeVertically": "垂直分布",
+    "viewMode": "檢視模式"
   },
   "buttons": {
     "clearReset": "重置 canvas",
@@ -199,24 +200,26 @@
   "errorDialog": {
     "title": "錯誤"
   },
-  "shortcutsDialog": {
-    "title": "鍵盤快速鍵",
-    "shapes": "形狀",
-    "or": "或",
+  "helpDialog": {
+    "blog": "閱讀部落格",
     "click": "點擊",
-    "drag": "拖曳",
-    "curvedArrow": "箭頭曲線",
+    "curvedArrow": "曲箭頭",
     "curvedLine": "曲線",
+    "documentation": "文件",
+    "drag": "拖曳",
     "editor": "編輯器",
+    "github": "發現異常?回報問題",
+    "howto": "參照我們的說明",
+    "or": "或",
+    "preventBinding": "避免箭號連結",
+    "shapes": "形狀",
+    "shortcuts": "鍵盤快速鍵",
+    "textFinish": "完成編輯 (文字)",
+    "textNewLine": "換行 (文字)",
+    "title": "說明",
     "view": "檢視",
-    "blog": "閱讀部落格",
-    "howto": "官方指南",
-    "github": "發現問題?回報 issue",
-    "textNewLine": "換行(文字)",
-    "textFinish": "完成編輯(文字)",
     "zoomToFit": "放大至填滿畫面",
-    "zoomToSelection": "縮放至選取區",
-    "preventBinding": "防止箭頭綁定"
+    "zoomToSelection": "縮放至選取區"
   },
   "encrypted": {
     "tooltip": "你的作品已使用 end-to-end 方式加密,Excalidraw 的伺服器也無法取得其內容。"
@@ -232,5 +235,9 @@
     "title": "詳細統計",
     "total": "合計",
     "width": "寬度"
+  },
+  "toast": {
+    "copyStyles": "已複製樣式",
+    "copyToClipboardAsPng": "已複製 PNG 至剪貼簿"
   }
 }
diff --git a/src/math.ts b/src/math.ts
index 83c7b8e741..662911649c 100644
--- a/src/math.ts
+++ b/src/math.ts
@@ -70,64 +70,6 @@ export const adjustXYWithRotation = (
   return [x, y];
 };
 
-export const getFlipAdjustment = (
-  side: "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se",
-  nextWidth: number,
-  nextHeight: number,
-  nextX1: number,
-  nextY1: number,
-  nextX2: number,
-  nextY2: number,
-  finalX1: number,
-  finalY1: number,
-  finalX2: number,
-  finalY2: number,
-  needsRotation: boolean,
-  angle: number,
-): [number, number] => {
-  const cos = Math.cos(angle);
-  const sin = Math.sin(angle);
-  let flipDiffX = 0;
-  let flipDiffY = 0;
-  if (nextWidth < 0) {
-    if (side === "e" || side === "ne" || side === "se") {
-      if (needsRotation) {
-        flipDiffX += (finalX2 - nextX1) * cos;
-        flipDiffY += (finalX2 - nextX1) * sin;
-      } else {
-        flipDiffX += finalX2 - nextX1;
-      }
-    }
-    if (side === "w" || side === "nw" || side === "sw") {
-      if (needsRotation) {
-        flipDiffX += (finalX1 - nextX2) * cos;
-        flipDiffY += (finalX1 - nextX2) * sin;
-      } else {
-        flipDiffX += finalX1 - nextX2;
-      }
-    }
-  }
-  if (nextHeight < 0) {
-    if (side === "s" || side === "se" || side === "sw") {
-      if (needsRotation) {
-        flipDiffY += (finalY2 - nextY1) * cos;
-        flipDiffX += (finalY2 - nextY1) * -sin;
-      } else {
-        flipDiffY += finalY2 - nextY1;
-      }
-    }
-    if (side === "n" || side === "ne" || side === "nw") {
-      if (needsRotation) {
-        flipDiffY += (finalY1 - nextY2) * cos;
-        flipDiffX += (finalY1 - nextY2) * -sin;
-      } else {
-        flipDiffY += finalY1 - nextY2;
-      }
-    }
-  }
-  return [flipDiffX, flipDiffY];
-};
-
 export const getPointOnAPath = (point: Point, path: Point[]) => {
   const [px, py] = point;
   const [start, ...other] = path;
diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md
index 865a644702..c9c866e51f 100644
--- a/src/packages/excalidraw/CHANGELOG.md
+++ b/src/packages/excalidraw/CHANGELOG.md
@@ -14,13 +14,62 @@ Please add the latest change on the top under the correct section.
 
 ## [Unreleased]
 
+## Excalidraw API
+
 ### Features
 
-- Add `cmd+o` shortcut to load scene [#2732](https://github.com/excalidraw/excalidraw/pull/2732)
+- Add `viewModeEnabled` prop which enabled the view mode [#2840](https://github.com/excalidraw/excalidraw/pull/2840). When this prop is used, the view mode will not show up in context menu is so it is fully controlled by host.
+- Expose `getAppState` on `excalidrawRef` [#2834](https://github.com/excalidraw/excalidraw/pull/2834).
+
+## Excalidraw Library
+
+### Features
+
+- Add view mode [#2840](https://github.com/excalidraw/excalidraw/pull/2840).
+- Remove `copy`, `cut`, and `paste` actions from contextmenu [#2872](https://github.com/excalidraw/excalidraw/pull/2872)
+- Support `Ctrl-Y` shortcut to redo on Windows [#2831](https://github.com/excalidraw/excalidraw/pull/2831).
+
+### Fixes
+
+- Fix UI pointer-events not disabled when dragging on canvas [#2856](https://github.com/excalidraw/excalidraw/pull/2856).
+- Fix remote pointers not accounting for offset [#2855](https://github.com/excalidraw/excalidraw/pull/2855).
+
+## 0.2.1
+
+## Excalidraw API
+
+### Build
+
+- Bundle css files with js [#2819](https://github.com/excalidraw/excalidraw/pull/2819). The host would not need to import css files separately.
+
+## 0.2.0
+
+## Excalidraw API
+
+### Features
+
+- Exported few [Extra API's](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#extra-apis) which can be used by the host to communicate with Excalidraw.
+
 - Remove language picker, and add `langCode`, `renderFooter` [#2644](https://github.com/excalidraw/excalidraw/pull/2644):
   - BREAKING: removed the language picker from UI. It is now the host app's responsibility to implement a language picker if desirable, using the newly added [`renderFooter`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#renderFooter) prop. The reasoning is that the i18n should be controlled by the app itself, not by the nested Excalidraw component.
   - Added [`langCode`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#langCode) prop to control the UI language.
 - Add support for `exportToBackend` prop to allow host apps to implement shareable links [#2612](https://github.com/excalidraw/excalidraw/pull/2612/files)
+
+### Fixes
+
+- Hide collaboration button when the prop `onCollabButtonClick` is not provided [#2598](https://github.com/excalidraw/excalidraw/pull/2598)
+
+## Excalidraw Library
+
+### Features
+
+- Add toast [#2772](https://github.com/excalidraw/excalidraw/pull/2772)
+- Add `cmd+o` shortcut to load scene [#2732](https://github.com/excalidraw/excalidraw/pull/2732)
+- Require use of a preset dialog size; adjust dialog sizing [#2684](https://github.com/excalidraw/excalidraw/pull/2684)
+- Add line chart and paste dialog selection [#2670](https://github.com/excalidraw/excalidraw/pull/2670)
+- Tweak editing behavior [#2668](https://github.com/excalidraw/excalidraw/pull/2668)
+- Change title to Excalidraw after a timeout
+- Checkmark to toggle context-menu-items [#2645](https://github.com/excalidraw/excalidraw/pull/2645)
 - Add zoom to selection [#2522](https://github.com/excalidraw/excalidraw/pull/2522)
 - Insert Library items in the middle of the screen [#2527](https://github.com/excalidraw/excalidraw/pull/2527)
 - Show shortcut context menu [#2501](https://github.com/excalidraw/excalidraw/pull/2501)
@@ -31,11 +80,16 @@ Please add the latest change on the top under the correct section.
 
 ### Fixes
 
+- Fix compile error [#2685](https://github.com/excalidraw/excalidraw/pull/2685)
+- Center zoom on iPhone and iPad [#2642](https://github.com/excalidraw/excalidraw/pull/2642)
+- Allow text-selecting in dialogs & reset cursor [#2783](https://github.com/excalidraw/excalidraw/pull/2783)
+- Don't render due to zoom after unmount [#2779](https://github.com/excalidraw/excalidraw/pull/2779)
+- Track the chart type correctly [#2773](https://github.com/excalidraw/excalidraw/pull/2773)
+- Fix late-render due to debounced zoom [#2779](https://github.com/excalidraw/excalidraw/pull/2779)
 - Fix initialization when browser tab not focused [#2677](https://github.com/excalidraw/excalidraw/pull/2677)
 - Consistent case for export locale strings [#2622](https://github.com/excalidraw/excalidraw/pull/2622)
 - Remove unnecessary console.error as it was polluting Sentry [#2637](https://github.com/excalidraw/excalidraw/pull/2637)
 - Fix scroll-to-center on init for non-zero canvas offsets [#2445](https://github.com/excalidraw/excalidraw/pull/2445)
-- Hide collab button when onCollabButtonClick not supplied [#2598](https://github.com/excalidraw/excalidraw/pull/2598)
 - Fix resizing the pasted charts [#2586](https://github.com/excalidraw/excalidraw/pull/2586)
 - Fix element visibility and zoom on cursor when canvas offset isn't 0. [#2534](https://github.com/excalidraw/excalidraw/pull/2534)
 - Fix Library Menu Layout [#2502](https://github.com/excalidraw/excalidraw/pull/2502)
@@ -45,6 +99,11 @@ Please add the latest change on the top under the correct section.
 ### Improvements
 
 - Added Zen Mode to the context menu [#2734](https://github.com/excalidraw/excalidraw/pull/2734)
+- Do not reset to selection for draw tool [#2721]((https://github.com/excalidraw/excalidraw/pull/2721)
+- Make dialogs look more like dialogs [#2686](https://github.com/excalidraw/excalidraw/pull/2686)
+- Browse libraries styles fixed [#2694](https://github.com/excalidraw/excalidraw/pull/2694)
+- Change hint for 2-point lines on resize [#2655](https://github.com/excalidraw/excalidraw/pull/2655)
+- Align items in context menu [#2640](https://github.com/excalidraw/excalidraw/pull/2640)
 - Do not reset to selection when using the draw tool [#2721](https://github.com/excalidraw/excalidraw/pull/2721)
 - Display proper tooltip for 2-point lines during resize, and normalize modifier key labels in hints [#2655](https://github.com/excalidraw/excalidraw/pull/2655)
 - Improve error message around importing images [#2619](https://github.com/excalidraw/excalidraw/pull/2619)
@@ -54,9 +113,13 @@ Please add the latest change on the top under the correct section.
 - Hide shortcuts on pickers for mobile [#2508](https://github.com/excalidraw/excalidraw/pull/2508)
 - Hide stats and scrollToContent-button when mobile menus open [#2509](https://github.com/excalidraw/excalidraw/pull/2509)
 
-### Chore
+### Refactor
 
-- Bump ini from 1.3.5 to 1.3.7 in /src/packages/excalidraw [#2500](https://github.com/excalidraw/excalidraw/pull/2500)
+- refactor: Converting span to kbd tag [#2774](https://github.com/excalidraw/excalidraw/pull/2774)
+- Media queries [#2680](https://github.com/excalidraw/excalidraw/pull/2680)
+- Remove duplicate entry from en.json[#2654](https://github.com/excalidraw/excalidraw/pull/2654)
+- Remove the word toggle from labels [#2648](https://github.com/excalidraw/excalidraw/pull/2648)
+-
 
 ### Docs
 
diff --git a/src/packages/excalidraw/README.md b/src/packages/excalidraw/README.md
index 36c76f6687..214eb41296 100644
--- a/src/packages/excalidraw/README.md
+++ b/src/packages/excalidraw/README.md
@@ -31,9 +31,6 @@ import React, { useEffect, useState, createRef } from "react";
 import Excalidraw from "@excalidraw/excalidraw";
 import InitialData from "./initialData";
 
-import "@excalidraw/excalidraw/dist/excalidraw.min.css";
-import "@excalidraw/excalidraw/dist/fonts.min.css";
-
 import "./styles.css";
 
 export default function App() {
@@ -141,6 +138,52 @@ export default function App() {
 | [`onExportToBackend`](#onExportToBackend) | Function |  | Callback triggered when link button is clicked on export dialog |
 | [`langCode`](#langCode) | string | `en` | Language code string |
 | [`renderFooter `](#renderFooter) | Function |  | Function that renders custom UI footer |
+| [`viewModeEnabled`](#viewModeEnabled) | boolean | false | This implies if the app is in view mode. |
+
+### `Extra API's`
+
+#### `getSceneVersion`
+
+**How to use**
+
+<pre>
+import { getSceneVersion } from "@excalidraw/excalidraw";
+getSceneVersion(elements:  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>)
+</pre>
+
+This function returns the current scene version.
+
+#### `getSyncableElements`
+
+**_Signature_**
+
+<pre>
+getSyncableElements(elements:  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>):<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>
+</pre>
+
+**How to use**
+
+```js
+import { getSyncableElements } from "@excalidraw/excalidraw";
+```
+
+This function returns all the deleted elements of the scene.
+
+### `getElementMap`
+
+**_Signature_**
+
+<pre>
+getElementsMap(elements:  <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>): {[id: string]: <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement</a>}
+</pre>
+
+**How to use**
+
+```js
+import { getElementsMap } from "@excalidraw/excalidraw";
+```
+
+This function returns an object where each element is mapped to its id.
 
 #### `width`
 
@@ -178,7 +221,7 @@ This helps to load Excalidraw with `initialData`. It must be an object or a [pro
 
 | name | type |
 | --- | --- |
-| elements | [ExcalidrawElement []](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78) |
+| elements | [ExcalidrawElement[]](https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78) |
 | appState | [AppState](https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37) |
 
 ```json
@@ -226,8 +269,9 @@ You can pass a `ref` when you want to access some excalidraw APIs. We expose the
 | readyPromise | [resolvablePromise](https://github.com/excalidraw/excalidraw/blob/master/src/utils.ts#L317) | This promise will be resolved with the api once excalidraw has rendered. This will be helpful when you want do some action on the host app once this promise resolves. For this to work you will have to pass ref as shown [here](#readyPromise) |
 | updateScene | <pre>(<a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L189">sceneData</a>)) => void </pre> | updates the scene with the sceneData |
 | resetScene | `({ resetLoadingState: boolean }) => void` | Resets the scene. If `resetLoadingState` is passed as true then it will also force set the loading state to false. |
-| getSceneElementsIncludingDeleted | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a></pre> | Returns all the elements including the deleted in the scene |
-| getSceneElements | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement []</a></pre> | Returns all the elements excluding the deleted in the scene |
+| getSceneElementsIncludingDeleted | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a></pre> | Returns all the elements including the deleted in the scene |
+| getSceneElements | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a></pre> | Returns all the elements excluding the deleted in the scene |
+| getAppState | <pre> () => <a href="https://github.com/excalidraw/excalidraw/blob/master/src/types.ts#L37">AppState</a></pre> | Returns current appState |
 | history | `{ clear: () => void }` | This is the history API. `history.clear()` will clear the history |
 | setScrollToCenter | <pre> (<a href="https://github.com/excalidraw/excalidraw/blob/master/src/element/types.ts#L78">ExcalidrawElement[]</a>) => void </pre> | sets the elements to center |
 
@@ -282,8 +326,12 @@ import { defaultLang, languages } from "@excalidraw/excalidraw";
 | name | type |
 | --- | --- |
 | defaultLang | string |
-| languages | [Language []](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L8) |
+| languages | [Language[]](https://github.com/excalidraw/excalidraw/blob/master/src/i18n.ts#L8) |
 
 #### `renderFooter`
 
 A function that renders (returns JSX) custom UI footer. For example, you can use this to render a language picker that was previously being rendered by Excalidraw itself (for now, you'll need to implement your own language picker).
+
+#### `viewModeEnabled`
+
+This prop indicates if the app is in `view mode`. When this prop is used, the `view mode` will not show up in context menu is so it is fully controlled by host. Also the value of this prop if passed will be used over the value of `intialData.appState.viewModeEnabled`
diff --git a/src/packages/excalidraw/entry.js b/src/packages/excalidraw/entry.js
new file mode 100644
index 0000000000..8a0d08d9b8
--- /dev/null
+++ b/src/packages/excalidraw/entry.js
@@ -0,0 +1,6 @@
+import Excalidraw from "./index";
+
+import "../../../public/fonts.css";
+
+export default Excalidraw;
+export * from "./index";
diff --git a/src/packages/excalidraw/index.tsx b/src/packages/excalidraw/index.tsx
index c298fc94ea..0f74413115 100644
--- a/src/packages/excalidraw/index.tsx
+++ b/src/packages/excalidraw/index.tsx
@@ -26,6 +26,7 @@ const Excalidraw = (props: ExcalidrawProps) => {
     onExportToBackend,
     renderFooter,
     langCode = defaultLang.code,
+    viewModeEnabled,
   } = props;
 
   useEffect(() => {
@@ -64,6 +65,7 @@ const Excalidraw = (props: ExcalidrawProps) => {
           onExportToBackend={onExportToBackend}
           renderFooter={renderFooter}
           langCode={langCode}
+          viewModeEnabled={viewModeEnabled}
         />
       </IsMobileProvider>
     </InitializeApp>
@@ -81,7 +83,6 @@ const areEqual = (
 
   const prevKeys = Object.keys(prevProps) as (keyof typeof prev)[];
   const nextKeys = Object.keys(nextProps) as (keyof typeof next)[];
-
   return (
     prevUser?.name === nextUser?.name &&
     prevKeys.length === nextKeys.length &&
@@ -89,6 +90,10 @@ const areEqual = (
   );
 };
 
+Excalidraw.defaultProps = {
+  lanCode: defaultLang.code,
+};
+
 const forwardedRefComp = forwardRef<
   ExcalidrawAPIRefValue,
   PublicExcalidrawProps
diff --git a/src/packages/excalidraw/package-lock.json b/src/packages/excalidraw/package-lock.json
index cb79f61bc7..3bfc8ec80a 100644
--- a/src/packages/excalidraw/package-lock.json
+++ b/src/packages/excalidraw/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "@excalidraw/excalidraw",
-  "version": "0.1.1",
+  "version": "0.2.1",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -1182,9 +1182,9 @@
       }
     },
     "@types/estree": {
-      "version": "0.0.45",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz",
-      "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==",
+      "version": "0.0.46",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
+      "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==",
       "dev": true
     },
     "@types/json-schema": {
@@ -1200,180 +1200,157 @@
       "dev": true
     },
     "@webassemblyjs/ast": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.1.tgz",
-      "integrity": "sha512-uMu1nCWn2Wxyy126LlGqRVlhdTOsO/bsBRI4dNq3+6SiSuRKRQX6ejjKgh82LoGAPSq72lDUiQ4FWVaf0PecYw==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz",
+      "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/helper-module-context": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/wast-parser": "1.9.1"
+        "@webassemblyjs/helper-numbers": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0"
       }
     },
     "@webassemblyjs/floating-point-hex-parser": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.1.tgz",
-      "integrity": "sha512-5VEKu024RySmLKTTBl9q1eO/2K5jk9ZS+2HXDBLA9s9p5IjkaXxWiDb/+b7wSQp6FRdLaH1IVGIfOex58Na2pg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz",
+      "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==",
       "dev": true
     },
     "@webassemblyjs/helper-api-error": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.1.tgz",
-      "integrity": "sha512-y1lGmfm38djrScwpeL37rRR9f1D6sM8RhMpvM7CYLzOlHVboouZokXK/G88BpzW0NQBSvCCOnW5BFhten4FPfA==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz",
+      "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==",
       "dev": true
     },
     "@webassemblyjs/helper-buffer": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.1.tgz",
-      "integrity": "sha512-uS6VSgieHbk/m4GSkMU5cqe/5TekdCzQso4revCIEQ3vpGZgqSSExi4jWpTWwDpAHOIAb1Jfrs0gUB9AA4n71w==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz",
+      "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==",
       "dev": true
     },
-    "@webassemblyjs/helper-code-frame": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.1.tgz",
-      "integrity": "sha512-ZQ2ZT6Evk4DPIfD+92AraGYaFIqGm4U20e7FpXwl7WUo2Pn1mZ1v8VGH8i+Y++IQpxPbQo/UyG0Khs7eInskzA==",
+    "@webassemblyjs/helper-numbers": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz",
+      "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/wast-printer": "1.9.1"
-      }
-    },
-    "@webassemblyjs/helper-fsm": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.1.tgz",
-      "integrity": "sha512-J32HGpveEqqcKFS0YbgicB0zAlpfIxJa5MjxDxhu3i5ltPcVfY5EPvKQ1suRguFPehxiUs+/hfkwPEXom/l0lw==",
-      "dev": true
-    },
-    "@webassemblyjs/helper-module-context": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.1.tgz",
-      "integrity": "sha512-IEH2cMmEQKt7fqelLWB5e/cMdZXf2rST1JIrzWmf4XBt3QTxGdnnLvV4DYoN8pJjOx0VYXsWg+yF16MmJtolZg==",
-      "dev": true,
-      "requires": {
-        "@webassemblyjs/ast": "1.9.1"
+        "@webassemblyjs/floating-point-hex-parser": "1.11.0",
+        "@webassemblyjs/helper-api-error": "1.11.0",
+        "@xtuc/long": "4.2.2"
       }
     },
     "@webassemblyjs/helper-wasm-bytecode": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.1.tgz",
-      "integrity": "sha512-i2rGTBqFUcSXxyjt2K4vm/3kkHwyzG6o427iCjcIKjOqpWH8SEem+xe82jUk1iydJO250/CvE5o7hzNAMZf0dQ==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz",
+      "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==",
       "dev": true
     },
     "@webassemblyjs/helper-wasm-section": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.1.tgz",
-      "integrity": "sha512-FetqzjtXZr2d57IECK+aId3D0IcGweeM0CbAnJHkYJkcRTHP+YcMb7Wmc0j21h5UWBpwYGb9dSkK/93SRCTrGg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz",
+      "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-buffer": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/wasm-gen": "1.9.1"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-buffer": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
+        "@webassemblyjs/wasm-gen": "1.11.0"
       }
     },
     "@webassemblyjs/ieee754": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.1.tgz",
-      "integrity": "sha512-EvTG9M78zP1MmkBpUjGQHZc26DzPGZSLIPxYHCjQsBMo60Qy2W34qf8z0exRDtxBbRIoiKa5dFyWer/7r1aaSQ==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz",
+      "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==",
       "dev": true,
       "requires": {
         "@xtuc/ieee754": "^1.2.0"
       }
     },
     "@webassemblyjs/leb128": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.1.tgz",
-      "integrity": "sha512-Oc04ub0vFfLnF+2/+ki3AE+anmW4sv9uNBqb+79fgTaPv6xJsOT0dhphNfL3FrME84CbX/D1T9XT8tjFo0IIiw==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz",
+      "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==",
       "dev": true,
       "requires": {
         "@xtuc/long": "4.2.2"
       }
     },
     "@webassemblyjs/utf8": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.1.tgz",
-      "integrity": "sha512-llkYtppagjCodFjo0alWOUhAkfOiQPQDIc5oA6C9sFAXz7vC9QhZf/f8ijQIX+A9ToM3c9Pq85X0EX7nx9gVhg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz",
+      "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==",
       "dev": true
     },
     "@webassemblyjs/wasm-edit": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.1.tgz",
-      "integrity": "sha512-S2IaD6+x9B2Xi8BCT0eGsrXXd8UxAh2LVJpg1ZMtHXnrDcsTtIX2bDjHi40Hio6Lc62dWHmKdvksI+MClCYbbw==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz",
+      "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-buffer": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/helper-wasm-section": "1.9.1",
-        "@webassemblyjs/wasm-gen": "1.9.1",
-        "@webassemblyjs/wasm-opt": "1.9.1",
-        "@webassemblyjs/wasm-parser": "1.9.1",
-        "@webassemblyjs/wast-printer": "1.9.1"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-buffer": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
+        "@webassemblyjs/helper-wasm-section": "1.11.0",
+        "@webassemblyjs/wasm-gen": "1.11.0",
+        "@webassemblyjs/wasm-opt": "1.11.0",
+        "@webassemblyjs/wasm-parser": "1.11.0",
+        "@webassemblyjs/wast-printer": "1.11.0"
       }
     },
     "@webassemblyjs/wasm-gen": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.1.tgz",
-      "integrity": "sha512-bqWI0S4lBQsEN5FTZ35vYzfKUJvtjNnBobB1agCALH30xNk1LToZ7Z8eiaR/Z5iVECTlBndoRQV3F6mbEqE/fg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz",
+      "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/ieee754": "1.9.1",
-        "@webassemblyjs/leb128": "1.9.1",
-        "@webassemblyjs/utf8": "1.9.1"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
+        "@webassemblyjs/ieee754": "1.11.0",
+        "@webassemblyjs/leb128": "1.11.0",
+        "@webassemblyjs/utf8": "1.11.0"
       }
     },
     "@webassemblyjs/wasm-opt": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.1.tgz",
-      "integrity": "sha512-gSf7I7YWVXZ5c6XqTEqkZjVs8K1kc1k57vsB6KBQscSagDNbAdxt6MwuJoMjsE1yWY1tsuL+pga268A6u+Fdkg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz",
+      "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-buffer": "1.9.1",
-        "@webassemblyjs/wasm-gen": "1.9.1",
-        "@webassemblyjs/wasm-parser": "1.9.1"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-buffer": "1.11.0",
+        "@webassemblyjs/wasm-gen": "1.11.0",
+        "@webassemblyjs/wasm-parser": "1.11.0"
       }
     },
     "@webassemblyjs/wasm-parser": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.1.tgz",
-      "integrity": "sha512-ImM4N2T1MEIond0MyE3rXvStVxEmivQrDKf/ggfh5pP6EHu3lL/YTAoSrR7shrbKNPpeKpGesW1LIK/L4kqduw==",
-      "dev": true,
-      "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-api-error": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/ieee754": "1.9.1",
-        "@webassemblyjs/leb128": "1.9.1",
-        "@webassemblyjs/utf8": "1.9.1"
-      }
-    },
-    "@webassemblyjs/wast-parser": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.1.tgz",
-      "integrity": "sha512-2xVxejXSvj3ls/o2TR/zI6p28qsGupjHhnHL6URULQRcXmryn3w7G83jQMcT7PHqUfyle65fZtWLukfdLdE7qw==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz",
+      "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/floating-point-hex-parser": "1.9.1",
-        "@webassemblyjs/helper-api-error": "1.9.1",
-        "@webassemblyjs/helper-code-frame": "1.9.1",
-        "@webassemblyjs/helper-fsm": "1.9.1",
-        "@xtuc/long": "4.2.2"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-api-error": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
+        "@webassemblyjs/ieee754": "1.11.0",
+        "@webassemblyjs/leb128": "1.11.0",
+        "@webassemblyjs/utf8": "1.11.0"
       }
     },
     "@webassemblyjs/wast-printer": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.1.tgz",
-      "integrity": "sha512-tDV8V15wm7mmbAH6XvQRU1X+oPGmeOzYsd6h7hlRLz6QpV4Ec/KKxM8OpLtFmQPLCreGxTp+HuxtH4pRIZyL9w==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz",
+      "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/wast-parser": "1.9.1",
+        "@webassemblyjs/ast": "1.11.0",
         "@xtuc/long": "4.2.2"
       }
     },
+    "@webpack-cli/configtest": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.0.tgz",
+      "integrity": "sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw==",
+      "dev": true
+    },
     "@webpack-cli/info": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz",
@@ -1384,9 +1361,9 @@
       }
     },
     "@webpack-cli/serve": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.1.tgz",
-      "integrity": "sha512-Zj1z6AyS+vqV6Hfi7ngCjFGdHV5EwZNIHo6QfFTNe9PyW+zBU1zJ9BiOW1pmUEq950RC4+Dym6flyA/61/vhyw==",
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.2.tgz",
+      "integrity": "sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew==",
       "dev": true
     },
     "@xtuc/ieee754": {
@@ -1408,9 +1385,9 @@
       "dev": true
     },
     "acorn-walk": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.0.tgz",
-      "integrity": "sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA==",
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.1.tgz",
+      "integrity": "sha512-zn/7dYtoTVkG4EoMU55QlQU4F+m+T7Kren6Vj3C2DapWPnakG/DL9Ns5aPAPW5Ixd3uxXrV/BoMKKVFIazPcdg==",
       "dev": true
     },
     "ajv": {
@@ -1740,6 +1717,17 @@
         "tslib": "^1.9.0"
       }
     },
+    "clone-deep": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+      "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+      "dev": true,
+      "requires": {
+        "is-plain-object": "^2.0.4",
+        "kind-of": "^6.0.2",
+        "shallow-clone": "^3.0.0"
+      }
+    },
     "color-convert": {
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -1962,6 +1950,12 @@
         "prr": "~1.0.1"
       }
     },
+    "es-module-lexer": {
+      "version": "0.3.26",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.3.26.tgz",
+      "integrity": "sha512-Va0Q/xqtrss45hWzP8CZJwzGSZJjDM5/MJRE3IXXnUCcVLElR9BRaE9F62BopysASyc4nM3uwhSW7FFB9nlWAA==",
+      "dev": true
+    },
     "escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -2268,6 +2262,15 @@
       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
       "dev": true
     },
+    "is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
     "is-stream": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
@@ -2286,6 +2289,12 @@
       "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
       "dev": true
     },
+    "isobject": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+      "dev": true
+    },
     "jest-worker": {
       "version": "26.6.2",
       "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
@@ -2347,6 +2356,12 @@
         "minimist": "^1.2.5"
       }
     },
+    "kind-of": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+      "dev": true
+    },
     "klona": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.4.tgz",
@@ -2354,9 +2369,9 @@
       "dev": true
     },
     "loader-runner": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.1.0.tgz",
-      "integrity": "sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA==",
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
+      "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==",
       "dev": true
     },
     "loader-utils": {
@@ -2458,9 +2473,9 @@
       }
     },
     "mime": {
-      "version": "2.4.7",
-      "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz",
-      "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==",
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz",
+      "integrity": "sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag==",
       "dev": true
     },
     "mime-db": {
@@ -2485,9 +2500,9 @@
       "dev": true
     },
     "mini-css-extract-plugin": {
-      "version": "1.3.3",
-      "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.3.tgz",
-      "integrity": "sha512-7lvliDSMiuZc81kI+5/qxvn47SCM7BehXex3f2c6l/pR3Goj58IQxZh9nuPQ3AkGQgoETyXuIqLDaO5Oa0TyBw==",
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.5.tgz",
+      "integrity": "sha512-tvmzcwqJJXau4OQE5vT72pRT18o2zF+tQJp8CWchqvfQnTlflkzS+dANYcRdyPRWUWRkfmeNTKltx0NZI/b5dQ==",
       "dev": true,
       "requires": {
         "loader-utils": "^2.0.0",
@@ -2870,9 +2885,9 @@
       "dev": true
     },
     "sass-loader": {
-      "version": "10.1.0",
-      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.1.0.tgz",
-      "integrity": "sha512-ZCKAlczLBbFd3aGAhowpYEy69Te3Z68cg8bnHHl6WnSCvnKpbM6pQrz957HWMa8LKVuhnD9uMplmMAHwGQtHeg==",
+      "version": "10.1.1",
+      "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.1.1.tgz",
+      "integrity": "sha512-W6gVDXAd5hR/WHsPicvZdjAWHBcEJ44UahgxcIE196fW2ong0ZHMPO1kZuI5q0VlvMQZh32gpv69PLWQm70qrw==",
       "dev": true,
       "requires": {
         "klona": "^2.0.4",
@@ -2941,6 +2956,15 @@
         "randombytes": "^2.1.0"
       }
     },
+    "shallow-clone": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+      "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^6.0.2"
+      }
+    },
     "shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3062,17 +3086,17 @@
       }
     },
     "terser-webpack-plugin": {
-      "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz",
-      "integrity": "sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ==",
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz",
+      "integrity": "sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q==",
       "dev": true,
       "requires": {
-        "jest-worker": "^26.6.1",
-        "p-limit": "^3.0.2",
+        "jest-worker": "^26.6.2",
+        "p-limit": "^3.1.0",
         "schema-utils": "^3.0.0",
         "serialize-javascript": "^5.0.1",
         "source-map": "^0.6.1",
-        "terser": "^5.3.8"
+        "terser": "^5.5.1"
       },
       "dependencies": {
         "p-limit": {
@@ -3125,23 +3149,86 @@
       "dev": true
     },
     "ts-loader": {
-      "version": "8.0.13",
-      "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.13.tgz",
-      "integrity": "sha512-1o1nO6aqouA23d2nlcMSEyPMAWRhnYUU0EQUJSc60E0TUyBNX792RHFYUN1ZM29vhMUNayrsbj2UVdZwKhXCDA==",
+      "version": "8.0.14",
+      "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.14.tgz",
+      "integrity": "sha512-Jt/hHlUnApOZjnSjTmZ+AbD5BGlQFx3f1D0nYuNKwz0JJnuDGHJas6az+FlWKwwRTu+26GXpv249A8UAnYUpqA==",
       "dev": true,
       "requires": {
-        "chalk": "^2.3.0",
+        "chalk": "^4.1.0",
         "enhanced-resolve": "^4.0.0",
-        "loader-utils": "^1.0.2",
+        "loader-utils": "^2.0.0",
         "micromatch": "^4.0.0",
-        "semver": "^6.0.0"
+        "semver": "^7.3.4"
       },
       "dependencies": {
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+          "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
           "dev": true
+        },
+        "loader-utils": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
+          "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
+        "semver": {
+          "version": "7.3.4",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
+          "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
         }
       }
     },
@@ -3217,41 +3304,41 @@
       }
     },
     "webpack": {
-      "version": "5.11.1",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.11.1.tgz",
-      "integrity": "sha512-tNUIdAmYJv+nupRs/U/gqmADm6fgrf5xE+rSlSsf2PgsGO7j2WG7ccU6AWNlOJlHFl+HnmXlBmHIkiLf+XA9mQ==",
+      "version": "5.19.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.19.0.tgz",
+      "integrity": "sha512-egX19vAQ8fZ4cVYtA9Y941eqJtcZAK68mQq87MMv+GTXKZOc3TpKBBxdGX+HXUYlquPxiluNsJ1VHvwwklW7CQ==",
       "dev": true,
       "requires": {
         "@types/eslint-scope": "^3.7.0",
-        "@types/estree": "^0.0.45",
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-module-context": "1.9.1",
-        "@webassemblyjs/wasm-edit": "1.9.1",
-        "@webassemblyjs/wasm-parser": "1.9.1",
+        "@types/estree": "^0.0.46",
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/wasm-edit": "1.11.0",
+        "@webassemblyjs/wasm-parser": "1.11.0",
         "acorn": "^8.0.4",
         "browserslist": "^4.14.5",
         "chrome-trace-event": "^1.0.2",
-        "enhanced-resolve": "^5.3.1",
+        "enhanced-resolve": "^5.7.0",
+        "es-module-lexer": "^0.3.26",
         "eslint-scope": "^5.1.1",
         "events": "^3.2.0",
         "glob-to-regexp": "^0.4.1",
         "graceful-fs": "^4.2.4",
         "json-parse-better-errors": "^1.0.2",
-        "loader-runner": "^4.1.0",
+        "loader-runner": "^4.2.0",
         "mime-types": "^2.1.27",
         "neo-async": "^2.6.2",
         "pkg-dir": "^5.0.0",
         "schema-utils": "^3.0.0",
         "tapable": "^2.1.1",
-        "terser-webpack-plugin": "^5.0.3",
+        "terser-webpack-plugin": "^5.1.1",
         "watchpack": "^2.0.0",
         "webpack-sources": "^2.1.1"
       },
       "dependencies": {
         "enhanced-resolve": {
-          "version": "5.4.1",
-          "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.4.1.tgz",
-          "integrity": "sha512-4GbyIMzYktTFoRSmkbgZ1LU+RXwf4AQ8Z+rSuuh1dC8plp0PPeaWvx6+G4hh4KnUJ48VoxKbNyA1QQQIUpXjYA==",
+          "version": "5.7.0",
+          "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz",
+          "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==",
           "dev": true,
           "requires": {
             "graceful-fs": "^4.2.4",
@@ -3340,9 +3427,9 @@
       }
     },
     "webpack-bundle-analyzer": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz",
-      "integrity": "sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz",
+      "integrity": "sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g==",
       "dev": true,
       "requires": {
         "acorn": "^8.0.4",
@@ -3414,14 +3501,15 @@
       }
     },
     "webpack-cli": {
-      "version": "4.3.1",
-      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.3.1.tgz",
-      "integrity": "sha512-/F4+9QNZM/qKzzL9/06Am8NXIkGV+/NqQ62Dx7DSqudxxpAgBqYn6V7+zp+0Y7JuWksKUbczRY3wMTd+7Uj6OA==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.4.0.tgz",
+      "integrity": "sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ==",
       "dev": true,
       "requires": {
         "@discoveryjs/json-ext": "^0.5.0",
+        "@webpack-cli/configtest": "^1.0.0",
         "@webpack-cli/info": "^1.2.1",
-        "@webpack-cli/serve": "^1.2.1",
+        "@webpack-cli/serve": "^1.2.2",
         "colorette": "^1.2.1",
         "commander": "^6.2.0",
         "enquirer": "^2.3.6",
@@ -3431,7 +3519,7 @@
         "interpret": "^2.2.0",
         "rechoir": "^0.7.0",
         "v8-compile-cache": "^2.2.0",
-        "webpack-merge": "^4.2.2"
+        "webpack-merge": "^5.7.3"
       },
       "dependencies": {
         "commander": {
@@ -3443,12 +3531,13 @@
       }
     },
     "webpack-merge": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz",
-      "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==",
+      "version": "5.7.3",
+      "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz",
+      "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==",
       "dev": true,
       "requires": {
-        "lodash": "^4.17.15"
+        "clone-deep": "^4.0.1",
+        "wildcard": "^2.0.0"
       }
     },
     "webpack-sources": {
@@ -3478,10 +3567,16 @@
         "isexe": "^2.0.0"
       }
     },
+    "wildcard": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
+      "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
+      "dev": true
+    },
     "ws": {
-      "version": "7.4.1",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz",
-      "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==",
+      "version": "7.4.2",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
+      "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==",
       "dev": true
     },
     "yallist": {
diff --git a/src/packages/excalidraw/package.json b/src/packages/excalidraw/package.json
index cce5d61638..3927ead561 100644
--- a/src/packages/excalidraw/package.json
+++ b/src/packages/excalidraw/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@excalidraw/excalidraw",
-  "version": "0.1.1",
+  "version": "0.2.1",
   "main": "dist/excalidraw.min.js",
   "files": [
     "dist/*"
@@ -54,13 +54,13 @@
     "cross-env": "7.0.3",
     "css-loader": "5.0.1",
     "file-loader": "6.2.0",
-    "mini-css-extract-plugin": "1.3.3",
-    "sass-loader": "10.1.0",
-    "terser-webpack-plugin": "5.0.3",
-    "ts-loader": "8.0.13",
-    "webpack": "5.11.1",
-    "webpack-bundle-analyzer": "4.3.0",
-    "webpack-cli": "4.3.1"
+    "mini-css-extract-plugin": "1.3.5",
+    "sass-loader": "10.1.1",
+    "terser-webpack-plugin": "5.1.1",
+    "ts-loader": "8.0.14",
+    "webpack": "5.19.0",
+    "webpack-bundle-analyzer": "4.4.0",
+    "webpack-cli": "4.4.0"
   },
   "bugs": "https://github.com/excalidraw/excalidraw/issues",
   "repository": "https://github.com/excalidraw/excalidraw",
diff --git a/src/packages/excalidraw/webpack.prod.config.js b/src/packages/excalidraw/webpack.prod.config.js
index bb04502d0d..434c205992 100644
--- a/src/packages/excalidraw/webpack.prod.config.js
+++ b/src/packages/excalidraw/webpack.prod.config.js
@@ -1,5 +1,4 @@
 const path = require("path");
-const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 const TerserPlugin = require("terser-webpack-plugin");
 const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
   .BundleAnalyzerPlugin;
@@ -7,8 +6,7 @@ const BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
 module.exports = {
   mode: "production",
   entry: {
-    "excalidraw.min": "./index.tsx",
-    "fonts.min": "../../../public/fonts.css",
+    "excalidraw.min": "./entry.js",
   },
   output: {
     path: path.resolve(__dirname, "dist"),
@@ -26,11 +24,7 @@ module.exports = {
       {
         test: /\.(sa|sc|c)ss$/,
         exclude: /node_modules/,
-        use: [
-          MiniCssExtractPlugin.loader,
-          { loader: "css-loader" },
-          "sass-loader",
-        ],
+        use: ["style-loader", { loader: "css-loader" }, "sass-loader"],
       },
       {
         test: /\.(ts|tsx|js|jsx|mjs)$/,
@@ -94,7 +88,6 @@ module.exports = {
     },
   },
   plugins: [
-    new MiniCssExtractPlugin({ filename: "[name].css" }),
     ...(process.env.ANALYZER === "true" ? [new BundleAnalyzerPlugin()] : []),
   ],
   externals: {
diff --git a/src/packages/utils/package-lock.json b/src/packages/utils/package-lock.json
index 9d2f334aca..41d280ad85 100644
--- a/src/packages/utils/package-lock.json
+++ b/src/packages/utils/package-lock.json
@@ -1097,9 +1097,9 @@
       }
     },
     "@types/estree": {
-      "version": "0.0.45",
-      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.45.tgz",
-      "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==",
+      "version": "0.0.46",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz",
+      "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==",
       "dev": true
     },
     "@types/json-schema": {
@@ -1109,186 +1109,163 @@
       "dev": true
     },
     "@types/node": {
-      "version": "14.14.19",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.19.tgz",
-      "integrity": "sha512-4nhBPStMK04rruRVtVc6cDqhu7S9GZai0fpXgPXrFpcPX6Xul8xnrjSdGB4KPBVYG/R5+fXWdCM8qBoiULWGPQ==",
+      "version": "14.14.22",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz",
+      "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==",
       "dev": true
     },
     "@webassemblyjs/ast": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.1.tgz",
-      "integrity": "sha512-uMu1nCWn2Wxyy126LlGqRVlhdTOsO/bsBRI4dNq3+6SiSuRKRQX6ejjKgh82LoGAPSq72lDUiQ4FWVaf0PecYw==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz",
+      "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/helper-module-context": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/wast-parser": "1.9.1"
+        "@webassemblyjs/helper-numbers": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0"
       }
     },
     "@webassemblyjs/floating-point-hex-parser": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.1.tgz",
-      "integrity": "sha512-5VEKu024RySmLKTTBl9q1eO/2K5jk9ZS+2HXDBLA9s9p5IjkaXxWiDb/+b7wSQp6FRdLaH1IVGIfOex58Na2pg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz",
+      "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==",
       "dev": true
     },
     "@webassemblyjs/helper-api-error": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.1.tgz",
-      "integrity": "sha512-y1lGmfm38djrScwpeL37rRR9f1D6sM8RhMpvM7CYLzOlHVboouZokXK/G88BpzW0NQBSvCCOnW5BFhten4FPfA==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz",
+      "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==",
       "dev": true
     },
     "@webassemblyjs/helper-buffer": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.1.tgz",
-      "integrity": "sha512-uS6VSgieHbk/m4GSkMU5cqe/5TekdCzQso4revCIEQ3vpGZgqSSExi4jWpTWwDpAHOIAb1Jfrs0gUB9AA4n71w==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz",
+      "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==",
       "dev": true
     },
-    "@webassemblyjs/helper-code-frame": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.1.tgz",
-      "integrity": "sha512-ZQ2ZT6Evk4DPIfD+92AraGYaFIqGm4U20e7FpXwl7WUo2Pn1mZ1v8VGH8i+Y++IQpxPbQo/UyG0Khs7eInskzA==",
+    "@webassemblyjs/helper-numbers": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz",
+      "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/wast-printer": "1.9.1"
-      }
-    },
-    "@webassemblyjs/helper-fsm": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.1.tgz",
-      "integrity": "sha512-J32HGpveEqqcKFS0YbgicB0zAlpfIxJa5MjxDxhu3i5ltPcVfY5EPvKQ1suRguFPehxiUs+/hfkwPEXom/l0lw==",
-      "dev": true
-    },
-    "@webassemblyjs/helper-module-context": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.1.tgz",
-      "integrity": "sha512-IEH2cMmEQKt7fqelLWB5e/cMdZXf2rST1JIrzWmf4XBt3QTxGdnnLvV4DYoN8pJjOx0VYXsWg+yF16MmJtolZg==",
-      "dev": true,
-      "requires": {
-        "@webassemblyjs/ast": "1.9.1"
+        "@webassemblyjs/floating-point-hex-parser": "1.11.0",
+        "@webassemblyjs/helper-api-error": "1.11.0",
+        "@xtuc/long": "4.2.2"
       }
     },
     "@webassemblyjs/helper-wasm-bytecode": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.1.tgz",
-      "integrity": "sha512-i2rGTBqFUcSXxyjt2K4vm/3kkHwyzG6o427iCjcIKjOqpWH8SEem+xe82jUk1iydJO250/CvE5o7hzNAMZf0dQ==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz",
+      "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==",
       "dev": true
     },
     "@webassemblyjs/helper-wasm-section": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.1.tgz",
-      "integrity": "sha512-FetqzjtXZr2d57IECK+aId3D0IcGweeM0CbAnJHkYJkcRTHP+YcMb7Wmc0j21h5UWBpwYGb9dSkK/93SRCTrGg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz",
+      "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-buffer": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/wasm-gen": "1.9.1"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-buffer": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
+        "@webassemblyjs/wasm-gen": "1.11.0"
       }
     },
     "@webassemblyjs/ieee754": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.1.tgz",
-      "integrity": "sha512-EvTG9M78zP1MmkBpUjGQHZc26DzPGZSLIPxYHCjQsBMo60Qy2W34qf8z0exRDtxBbRIoiKa5dFyWer/7r1aaSQ==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz",
+      "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==",
       "dev": true,
       "requires": {
         "@xtuc/ieee754": "^1.2.0"
       }
     },
     "@webassemblyjs/leb128": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.1.tgz",
-      "integrity": "sha512-Oc04ub0vFfLnF+2/+ki3AE+anmW4sv9uNBqb+79fgTaPv6xJsOT0dhphNfL3FrME84CbX/D1T9XT8tjFo0IIiw==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz",
+      "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==",
       "dev": true,
       "requires": {
         "@xtuc/long": "4.2.2"
       }
     },
     "@webassemblyjs/utf8": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.1.tgz",
-      "integrity": "sha512-llkYtppagjCodFjo0alWOUhAkfOiQPQDIc5oA6C9sFAXz7vC9QhZf/f8ijQIX+A9ToM3c9Pq85X0EX7nx9gVhg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz",
+      "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==",
       "dev": true
     },
     "@webassemblyjs/wasm-edit": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.1.tgz",
-      "integrity": "sha512-S2IaD6+x9B2Xi8BCT0eGsrXXd8UxAh2LVJpg1ZMtHXnrDcsTtIX2bDjHi40Hio6Lc62dWHmKdvksI+MClCYbbw==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz",
+      "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-buffer": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/helper-wasm-section": "1.9.1",
-        "@webassemblyjs/wasm-gen": "1.9.1",
-        "@webassemblyjs/wasm-opt": "1.9.1",
-        "@webassemblyjs/wasm-parser": "1.9.1",
-        "@webassemblyjs/wast-printer": "1.9.1"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-buffer": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
+        "@webassemblyjs/helper-wasm-section": "1.11.0",
+        "@webassemblyjs/wasm-gen": "1.11.0",
+        "@webassemblyjs/wasm-opt": "1.11.0",
+        "@webassemblyjs/wasm-parser": "1.11.0",
+        "@webassemblyjs/wast-printer": "1.11.0"
       }
     },
     "@webassemblyjs/wasm-gen": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.1.tgz",
-      "integrity": "sha512-bqWI0S4lBQsEN5FTZ35vYzfKUJvtjNnBobB1agCALH30xNk1LToZ7Z8eiaR/Z5iVECTlBndoRQV3F6mbEqE/fg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz",
+      "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/ieee754": "1.9.1",
-        "@webassemblyjs/leb128": "1.9.1",
-        "@webassemblyjs/utf8": "1.9.1"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
+        "@webassemblyjs/ieee754": "1.11.0",
+        "@webassemblyjs/leb128": "1.11.0",
+        "@webassemblyjs/utf8": "1.11.0"
       }
     },
     "@webassemblyjs/wasm-opt": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.1.tgz",
-      "integrity": "sha512-gSf7I7YWVXZ5c6XqTEqkZjVs8K1kc1k57vsB6KBQscSagDNbAdxt6MwuJoMjsE1yWY1tsuL+pga268A6u+Fdkg==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz",
+      "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-buffer": "1.9.1",
-        "@webassemblyjs/wasm-gen": "1.9.1",
-        "@webassemblyjs/wasm-parser": "1.9.1"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-buffer": "1.11.0",
+        "@webassemblyjs/wasm-gen": "1.11.0",
+        "@webassemblyjs/wasm-parser": "1.11.0"
       }
     },
     "@webassemblyjs/wasm-parser": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.1.tgz",
-      "integrity": "sha512-ImM4N2T1MEIond0MyE3rXvStVxEmivQrDKf/ggfh5pP6EHu3lL/YTAoSrR7shrbKNPpeKpGesW1LIK/L4kqduw==",
-      "dev": true,
-      "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-api-error": "1.9.1",
-        "@webassemblyjs/helper-wasm-bytecode": "1.9.1",
-        "@webassemblyjs/ieee754": "1.9.1",
-        "@webassemblyjs/leb128": "1.9.1",
-        "@webassemblyjs/utf8": "1.9.1"
-      }
-    },
-    "@webassemblyjs/wast-parser": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.1.tgz",
-      "integrity": "sha512-2xVxejXSvj3ls/o2TR/zI6p28qsGupjHhnHL6URULQRcXmryn3w7G83jQMcT7PHqUfyle65fZtWLukfdLdE7qw==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz",
+      "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/floating-point-hex-parser": "1.9.1",
-        "@webassemblyjs/helper-api-error": "1.9.1",
-        "@webassemblyjs/helper-code-frame": "1.9.1",
-        "@webassemblyjs/helper-fsm": "1.9.1",
-        "@xtuc/long": "4.2.2"
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/helper-api-error": "1.11.0",
+        "@webassemblyjs/helper-wasm-bytecode": "1.11.0",
+        "@webassemblyjs/ieee754": "1.11.0",
+        "@webassemblyjs/leb128": "1.11.0",
+        "@webassemblyjs/utf8": "1.11.0"
       }
     },
     "@webassemblyjs/wast-printer": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.1.tgz",
-      "integrity": "sha512-tDV8V15wm7mmbAH6XvQRU1X+oPGmeOzYsd6h7hlRLz6QpV4Ec/KKxM8OpLtFmQPLCreGxTp+HuxtH4pRIZyL9w==",
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz",
+      "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==",
       "dev": true,
       "requires": {
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/wast-parser": "1.9.1",
+        "@webassemblyjs/ast": "1.11.0",
         "@xtuc/long": "4.2.2"
       }
     },
+    "@webpack-cli/configtest": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.0.tgz",
+      "integrity": "sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw==",
+      "dev": true
+    },
     "@webpack-cli/info": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz",
@@ -1299,9 +1276,9 @@
       }
     },
     "@webpack-cli/serve": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.1.tgz",
-      "integrity": "sha512-Zj1z6AyS+vqV6Hfi7ngCjFGdHV5EwZNIHo6QfFTNe9PyW+zBU1zJ9BiOW1pmUEq950RC4+Dym6flyA/61/vhyw==",
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.2.tgz",
+      "integrity": "sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew==",
       "dev": true
     },
     "@xtuc/ieee754": {
@@ -1323,9 +1300,9 @@
       "dev": true
     },
     "acorn-walk": {
-      "version": "8.0.0",
-      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.0.tgz",
-      "integrity": "sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA==",
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.1.tgz",
+      "integrity": "sha512-zn/7dYtoTVkG4EoMU55QlQU4F+m+T7Kren6Vj3C2DapWPnakG/DL9Ns5aPAPW5Ixd3uxXrV/BoMKKVFIazPcdg==",
       "dev": true
     },
     "ajv": {
@@ -1649,6 +1626,17 @@
         "tslib": "^1.9.0"
       }
     },
+    "clone-deep": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
+      "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
+      "dev": true,
+      "requires": {
+        "is-plain-object": "^2.0.4",
+        "kind-of": "^6.0.2",
+        "shallow-clone": "^3.0.0"
+      }
+    },
     "color-convert": {
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -1812,6 +1800,12 @@
         "prr": "~1.0.1"
       }
     },
+    "es-module-lexer": {
+      "version": "0.3.26",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.3.26.tgz",
+      "integrity": "sha512-Va0Q/xqtrss45hWzP8CZJwzGSZJjDM5/MJRE3IXXnUCcVLElR9BRaE9F62BopysASyc4nM3uwhSW7FFB9nlWAA==",
+      "dev": true
+    },
     "escalade": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@@ -2106,6 +2100,15 @@
       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
       "dev": true
     },
+    "is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "dev": true,
+      "requires": {
+        "isobject": "^3.0.1"
+      }
+    },
     "is-stream": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
@@ -2124,6 +2127,12 @@
       "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
       "dev": true
     },
+    "isobject": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+      "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+      "dev": true
+    },
     "jest-worker": {
       "version": "26.6.2",
       "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz",
@@ -2185,10 +2194,16 @@
         "minimist": "^1.2.5"
       }
     },
+    "kind-of": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+      "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+      "dev": true
+    },
     "loader-runner": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.1.0.tgz",
-      "integrity": "sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA==",
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
+      "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==",
       "dev": true
     },
     "loader-utils": {
@@ -2237,6 +2252,15 @@
         "js-tokens": "^3.0.0 || ^4.0.0"
       }
     },
+    "lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "requires": {
+        "yallist": "^4.0.0"
+      }
+    },
     "make-dir": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -2281,9 +2305,9 @@
       }
     },
     "mime": {
-      "version": "2.4.7",
-      "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz",
-      "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==",
+      "version": "2.5.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz",
+      "integrity": "sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag==",
       "dev": true
     },
     "mime-db": {
@@ -2605,6 +2629,15 @@
         "randombytes": "^2.1.0"
       }
     },
+    "shallow-clone": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
+      "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
+      "dev": true,
+      "requires": {
+        "kind-of": "^6.0.2"
+      }
+    },
     "shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -2726,17 +2759,17 @@
       }
     },
     "terser-webpack-plugin": {
-      "version": "5.0.3",
-      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz",
-      "integrity": "sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ==",
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz",
+      "integrity": "sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q==",
       "dev": true,
       "requires": {
-        "jest-worker": "^26.6.1",
-        "p-limit": "^3.0.2",
+        "jest-worker": "^26.6.2",
+        "p-limit": "^3.1.0",
         "schema-utils": "^3.0.0",
         "serialize-javascript": "^5.0.1",
         "source-map": "^0.6.1",
-        "terser": "^5.3.8"
+        "terser": "^5.5.1"
       },
       "dependencies": {
         "p-limit": {
@@ -2789,23 +2822,86 @@
       "dev": true
     },
     "ts-loader": {
-      "version": "8.0.13",
-      "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.13.tgz",
-      "integrity": "sha512-1o1nO6aqouA23d2nlcMSEyPMAWRhnYUU0EQUJSc60E0TUyBNX792RHFYUN1ZM29vhMUNayrsbj2UVdZwKhXCDA==",
+      "version": "8.0.14",
+      "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.14.tgz",
+      "integrity": "sha512-Jt/hHlUnApOZjnSjTmZ+AbD5BGlQFx3f1D0nYuNKwz0JJnuDGHJas6az+FlWKwwRTu+26GXpv249A8UAnYUpqA==",
       "dev": true,
       "requires": {
-        "chalk": "^2.3.0",
+        "chalk": "^4.1.0",
         "enhanced-resolve": "^4.0.0",
-        "loader-utils": "^1.0.2",
+        "loader-utils": "^2.0.0",
         "micromatch": "^4.0.0",
-        "semver": "^6.0.0"
+        "semver": "^7.3.4"
       },
       "dependencies": {
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+        "ansi-styles": {
+          "version": "4.3.0",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+          "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+          "dev": true,
+          "requires": {
+            "color-convert": "^2.0.1"
+          }
+        },
+        "chalk": {
+          "version": "4.1.0",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+          "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+          "dev": true,
+          "requires": {
+            "ansi-styles": "^4.1.0",
+            "supports-color": "^7.1.0"
+          }
+        },
+        "color-convert": {
+          "version": "2.0.1",
+          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+          "dev": true,
+          "requires": {
+            "color-name": "~1.1.4"
+          }
+        },
+        "color-name": {
+          "version": "1.1.4",
+          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+          "dev": true
+        },
+        "has-flag": {
+          "version": "4.0.0",
+          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
           "dev": true
+        },
+        "loader-utils": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
+          "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
+          "dev": true,
+          "requires": {
+            "big.js": "^5.2.2",
+            "emojis-list": "^3.0.0",
+            "json5": "^2.1.2"
+          }
+        },
+        "semver": {
+          "version": "7.3.4",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz",
+          "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==",
+          "dev": true,
+          "requires": {
+            "lru-cache": "^6.0.0"
+          }
+        },
+        "supports-color": {
+          "version": "7.2.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+          "dev": true,
+          "requires": {
+            "has-flag": "^4.0.0"
+          }
         }
       }
     },
@@ -2875,41 +2971,41 @@
       }
     },
     "webpack": {
-      "version": "5.11.1",
-      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.11.1.tgz",
-      "integrity": "sha512-tNUIdAmYJv+nupRs/U/gqmADm6fgrf5xE+rSlSsf2PgsGO7j2WG7ccU6AWNlOJlHFl+HnmXlBmHIkiLf+XA9mQ==",
+      "version": "5.19.0",
+      "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.19.0.tgz",
+      "integrity": "sha512-egX19vAQ8fZ4cVYtA9Y941eqJtcZAK68mQq87MMv+GTXKZOc3TpKBBxdGX+HXUYlquPxiluNsJ1VHvwwklW7CQ==",
       "dev": true,
       "requires": {
         "@types/eslint-scope": "^3.7.0",
-        "@types/estree": "^0.0.45",
-        "@webassemblyjs/ast": "1.9.1",
-        "@webassemblyjs/helper-module-context": "1.9.1",
-        "@webassemblyjs/wasm-edit": "1.9.1",
-        "@webassemblyjs/wasm-parser": "1.9.1",
+        "@types/estree": "^0.0.46",
+        "@webassemblyjs/ast": "1.11.0",
+        "@webassemblyjs/wasm-edit": "1.11.0",
+        "@webassemblyjs/wasm-parser": "1.11.0",
         "acorn": "^8.0.4",
         "browserslist": "^4.14.5",
         "chrome-trace-event": "^1.0.2",
-        "enhanced-resolve": "^5.3.1",
+        "enhanced-resolve": "^5.7.0",
+        "es-module-lexer": "^0.3.26",
         "eslint-scope": "^5.1.1",
         "events": "^3.2.0",
         "glob-to-regexp": "^0.4.1",
         "graceful-fs": "^4.2.4",
         "json-parse-better-errors": "^1.0.2",
-        "loader-runner": "^4.1.0",
+        "loader-runner": "^4.2.0",
         "mime-types": "^2.1.27",
         "neo-async": "^2.6.2",
         "pkg-dir": "^5.0.0",
         "schema-utils": "^3.0.0",
         "tapable": "^2.1.1",
-        "terser-webpack-plugin": "^5.0.3",
+        "terser-webpack-plugin": "^5.1.1",
         "watchpack": "^2.0.0",
         "webpack-sources": "^2.1.1"
       },
       "dependencies": {
         "enhanced-resolve": {
-          "version": "5.4.1",
-          "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.4.1.tgz",
-          "integrity": "sha512-4GbyIMzYktTFoRSmkbgZ1LU+RXwf4AQ8Z+rSuuh1dC8plp0PPeaWvx6+G4hh4KnUJ48VoxKbNyA1QQQIUpXjYA==",
+          "version": "5.7.0",
+          "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz",
+          "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==",
           "dev": true,
           "requires": {
             "graceful-fs": "^4.2.4",
@@ -2982,9 +3078,9 @@
       }
     },
     "webpack-bundle-analyzer": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz",
-      "integrity": "sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz",
+      "integrity": "sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g==",
       "dev": true,
       "requires": {
         "acorn": "^8.0.4",
@@ -3056,14 +3152,15 @@
       }
     },
     "webpack-cli": {
-      "version": "4.3.1",
-      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.3.1.tgz",
-      "integrity": "sha512-/F4+9QNZM/qKzzL9/06Am8NXIkGV+/NqQ62Dx7DSqudxxpAgBqYn6V7+zp+0Y7JuWksKUbczRY3wMTd+7Uj6OA==",
+      "version": "4.4.0",
+      "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.4.0.tgz",
+      "integrity": "sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ==",
       "dev": true,
       "requires": {
         "@discoveryjs/json-ext": "^0.5.0",
+        "@webpack-cli/configtest": "^1.0.0",
         "@webpack-cli/info": "^1.2.1",
-        "@webpack-cli/serve": "^1.2.1",
+        "@webpack-cli/serve": "^1.2.2",
         "colorette": "^1.2.1",
         "commander": "^6.2.0",
         "enquirer": "^2.3.6",
@@ -3073,7 +3170,7 @@
         "interpret": "^2.2.0",
         "rechoir": "^0.7.0",
         "v8-compile-cache": "^2.2.0",
-        "webpack-merge": "^4.2.2"
+        "webpack-merge": "^5.7.3"
       },
       "dependencies": {
         "commander": {
@@ -3085,12 +3182,13 @@
       }
     },
     "webpack-merge": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz",
-      "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==",
+      "version": "5.7.3",
+      "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz",
+      "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==",
       "dev": true,
       "requires": {
-        "lodash": "^4.17.15"
+        "clone-deep": "^4.0.1",
+        "wildcard": "^2.0.0"
       }
     },
     "webpack-sources": {
@@ -3120,10 +3218,22 @@
         "isexe": "^2.0.0"
       }
     },
+    "wildcard": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz",
+      "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==",
+      "dev": true
+    },
     "ws": {
-      "version": "7.4.1",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz",
-      "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==",
+      "version": "7.4.2",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
+      "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==",
+      "dev": true
+    },
+    "yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
       "dev": true
     },
     "yocto-queue": {
diff --git a/src/packages/utils/package.json b/src/packages/utils/package.json
index 776a30ad5d..2e5d91b0e0 100644
--- a/src/packages/utils/package.json
+++ b/src/packages/utils/package.json
@@ -45,10 +45,10 @@
     "babel-plugin-transform-class-properties": "6.24.1",
     "cross-env": "7.0.3",
     "file-loader": "6.2.0",
-    "ts-loader": "8.0.13",
-    "webpack": "5.11.1",
-    "webpack-bundle-analyzer": "4.3.0",
-    "webpack-cli": "4.3.1"
+    "ts-loader": "8.0.14",
+    "webpack": "5.19.0",
+    "webpack-bundle-analyzer": "4.4.0",
+    "webpack-cli": "4.4.0"
   },
   "bugs": "https://github.com/excalidraw/excalidraw/issues",
   "repository": "https://github.com/excalidraw/excalidraw",
diff --git a/src/random.ts b/src/random.ts
index b0c8b7a08b..3335f729ed 100644
--- a/src/random.ts
+++ b/src/random.ts
@@ -1,5 +1,5 @@
 import { Random } from "roughjs/bin/math";
-import nanoid from "nanoid";
+import { nanoid } from "nanoid";
 
 let random = new Random(Date.now());
 let testIdBase = 0;
diff --git a/src/renderer/renderScene.ts b/src/renderer/renderScene.ts
index a66bf550b6..60ea7a55c1 100644
--- a/src/renderer/renderScene.ts
+++ b/src/renderer/renderScene.ts
@@ -373,12 +373,14 @@ export const renderScene = (
         sceneState.zoom,
         "mouse", // when we render we don't know which pointer type so use mouse
       );
-      renderTransformHandles(
-        context,
-        sceneState,
-        transformHandles,
-        locallySelectedElements[0].angle,
-      );
+      if (!appState.viewModeEnabled) {
+        renderTransformHandles(
+          context,
+          sceneState,
+          transformHandles,
+          locallySelectedElements[0].angle,
+        );
+      }
     } else if (locallySelectedElements.length > 1 && !appState.isRotating) {
       const dashedLinePadding = 4 / sceneState.zoom.value;
       context.fillStyle = oc.white;
@@ -418,7 +420,9 @@ export const renderScene = (
   // Paint remote pointers
   for (const clientId in sceneState.remotePointerViewportCoords) {
     let { x, y } = sceneState.remotePointerViewportCoords[clientId];
-    const username = sceneState.remotePointerUsernames[clientId];
+
+    x -= appState.offsetLeft;
+    y -= appState.offsetTop;
 
     const width = 9;
     const height = 14;
@@ -473,6 +477,8 @@ export const renderScene = (
     context.fill();
     context.stroke();
 
+    const username = sceneState.remotePointerUsernames[clientId];
+
     if (!isOutOfBounds && username) {
       const offsetX = x + width;
       const offsetY = y + height;
diff --git a/src/scene/export.ts b/src/scene/export.ts
index 57d3c18fe7..bf58713a62 100644
--- a/src/scene/export.ts
+++ b/src/scene/export.ts
@@ -5,7 +5,6 @@ import { NonDeletedExcalidrawElement } from "../element/types";
 import { getCommonBounds } from "../element/bounds";
 import { renderScene, renderSceneToSvg } from "../renderer/renderScene";
 import { distance, SVG_NS } from "../utils";
-import { normalizeScroll } from "./scroll";
 import { AppState } from "../types";
 import { t } from "../i18n";
 import { DEFAULT_FONT_FAMILY, DEFAULT_VERTICAL_ALIGN } from "../constants";
@@ -59,8 +58,8 @@ export const exportToCanvas = (
     tempCanvas,
     {
       viewBackgroundColor: exportBackground ? viewBackgroundColor : null,
-      scrollX: normalizeScroll(-minX + exportPadding),
-      scrollY: normalizeScroll(-minY + exportPadding),
+      scrollX: -minX + exportPadding,
+      scrollY: -minY + exportPadding,
       zoom: getDefaultAppState().zoom,
       remotePointerViewportCoords: {},
       remoteSelectedElementIds: {},
diff --git a/src/scene/index.ts b/src/scene/index.ts
index 0b98f6f20e..36cc41619f 100644
--- a/src/scene/index.ts
+++ b/src/scene/index.ts
@@ -6,7 +6,7 @@ export {
   getSelectedElements,
   getTargetElements,
 } from "./selection";
-export { normalizeScroll, calculateScrollCenter } from "./scroll";
+export { calculateScrollCenter } from "./scroll";
 export {
   hasBackground,
   hasStroke,
diff --git a/src/scene/scroll.ts b/src/scene/scroll.ts
index 51cc7da403..c42341fc0e 100644
--- a/src/scene/scroll.ts
+++ b/src/scene/scroll.ts
@@ -1,4 +1,4 @@
-import { AppState, FlooredNumber, PointerCoords, Zoom } from "../types";
+import { AppState, PointerCoords, Zoom } from "../types";
 import { ExcalidrawElement } from "../element/types";
 import { getCommonBounds, getClosestElementBounds } from "../element";
 
@@ -7,9 +7,6 @@ import {
   viewportCoordsToSceneCoords,
 } from "../utils";
 
-export const normalizeScroll = (pos: number) =>
-  Math.floor(pos) as FlooredNumber;
-
 const isOutsideViewPort = (
   appState: AppState,
   canvas: HTMLCanvasElement | null,
@@ -40,16 +37,14 @@ export const centerScrollOn = ({
   zoom: Zoom;
 }) => {
   return {
-    scrollX: normalizeScroll(
+    scrollX:
       (viewportDimensions.width / 2) * (1 / zoom.value) -
-        scenePoint.x -
-        zoom.translation.x * (1 / zoom.value),
-    ),
-    scrollY: normalizeScroll(
+      scenePoint.x -
+      zoom.translation.x * (1 / zoom.value),
+    scrollY:
       (viewportDimensions.height / 2) * (1 / zoom.value) -
-        scenePoint.y -
-        zoom.translation.y * (1 / zoom.value),
-    ),
+      scenePoint.y -
+      zoom.translation.y * (1 / zoom.value),
   };
 };
 
@@ -57,11 +52,11 @@ export const calculateScrollCenter = (
   elements: readonly ExcalidrawElement[],
   appState: AppState,
   canvas: HTMLCanvasElement | null,
-): { scrollX: FlooredNumber; scrollY: FlooredNumber } => {
+): { scrollX: number; scrollY: number } => {
   if (!elements.length) {
     return {
-      scrollX: normalizeScroll(0),
-      scrollY: normalizeScroll(0),
+      scrollX: 0,
+      scrollY: 0,
     };
   }
   let [x1, y1, x2, y2] = getCommonBounds(elements);
diff --git a/src/scene/scrollbars.ts b/src/scene/scrollbars.ts
index 24f10e7d7d..19f3275967 100644
--- a/src/scene/scrollbars.ts
+++ b/src/scene/scrollbars.ts
@@ -1,6 +1,6 @@
 import { ExcalidrawElement } from "../element/types";
 import { getCommonBounds } from "../element";
-import { FlooredNumber, Zoom } from "../types";
+import { Zoom } from "../types";
 import { ScrollBars } from "./types";
 import { getGlobalCSSVariable } from "../utils";
 import { getLanguage } from "../i18n";
@@ -18,8 +18,8 @@ export const getScrollBars = (
     scrollY,
     zoom,
   }: {
-    scrollX: FlooredNumber;
-    scrollY: FlooredNumber;
+    scrollX: number;
+    scrollY: number;
     zoom: Zoom;
   },
 ): ScrollBars => {
diff --git a/src/scene/types.ts b/src/scene/types.ts
index 5bb3f701da..907acf2378 100644
--- a/src/scene/types.ts
+++ b/src/scene/types.ts
@@ -1,9 +1,9 @@
 import { ExcalidrawTextElement } from "../element/types";
-import { FlooredNumber, Zoom } from "../types";
+import { Zoom } from "../types";
 
 export type SceneState = {
-  scrollX: FlooredNumber;
-  scrollY: FlooredNumber;
+  scrollX: number;
+  scrollY: number;
   // null indicates transparent bg
   viewBackgroundColor: string | null;
   zoom: Zoom;
@@ -15,8 +15,8 @@ export type SceneState = {
 };
 
 export type SceneScroll = {
-  scrollX: FlooredNumber;
-  scrollY: FlooredNumber;
+  scrollX: number;
+  scrollY: number;
 };
 
 export interface Scene {
diff --git a/src/scene/zoom.ts b/src/scene/zoom.ts
index c239cc27c1..0a36418dff 100644
--- a/src/scene/zoom.ts
+++ b/src/scene/zoom.ts
@@ -25,6 +25,6 @@ export const getNewZoom = (
 
 export const getNormalizedZoom = (zoom: number): NormalizedZoomValue => {
   const normalizedZoom = parseFloat(zoom.toFixed(2));
-  const clampedZoom = Math.max(0.1, Math.min(normalizedZoom, 2));
+  const clampedZoom = Math.max(0.1, Math.min(normalizedZoom, 10));
   return clampedZoom as NormalizedZoomValue;
 };
diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap
index dc78141e13..14ec87b44c 100644
--- a/src/tests/__snapshots__/regressionTests.test.tsx.snap
+++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap
@@ -71,11 +71,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -457,7 +459,7 @@ Object {
 
 exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of elements 1`] = `3`;
 
-exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of renders 1`] = `25`;
+exports[`given element A and group of elements B and given both are selected when user clicks on B, on pointer up only elements from B should be selected: [end of test] number of renders 1`] = `26`;
 
 exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] appState 1`] = `
 Object {
@@ -537,11 +539,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -922,7 +926,7 @@ Object {
 
 exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] number of elements 1`] = `3`;
 
-exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] number of renders 1`] = `21`;
+exports[`given element A and group of elements B and given both are selected when user shift-clicks on B, on pointer up only element A should be selected: [end of test] number of renders 1`] = `22`;
 
 exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] appState 1`] = `
 Object {
@@ -985,11 +989,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -1696,7 +1702,7 @@ Object {
 
 exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] number of elements 1`] = `3`;
 
-exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] number of renders 1`] = `40`;
+exports[`regression tests Cmd/Ctrl-click exclusively select element under pointer: [end of test] number of renders 1`] = `41`;
 
 exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] appState 1`] = `
 Object {
@@ -1761,11 +1767,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -1898,7 +1906,7 @@ Object {
 
 exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] number of renders 1`] = `9`;
+exports[`regression tests Drags selected element when hitting only bounding box and keeps element selected: [end of test] number of renders 1`] = `10`;
 
 exports[`regression tests adjusts z order when grouping: [end of test] appState 1`] = `
 Object {
@@ -1968,11 +1976,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -2354,7 +2364,7 @@ Object {
 
 exports[`regression tests adjusts z order when grouping: [end of test] number of elements 1`] = `3`;
 
-exports[`regression tests adjusts z order when grouping: [end of test] number of renders 1`] = `19`;
+exports[`regression tests adjusts z order when grouping: [end of test] number of renders 1`] = `20`;
 
 exports[`regression tests alt-drag duplicates an element: [end of test] appState 1`] = `
 Object {
@@ -2419,11 +2429,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -2605,7 +2617,7 @@ Object {
 
 exports[`regression tests alt-drag duplicates an element: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests alt-drag duplicates an element: [end of test] number of renders 1`] = `9`;
+exports[`regression tests alt-drag duplicates an element: [end of test] number of renders 1`] = `10`;
 
 exports[`regression tests arrow keys: [end of test] appState 1`] = `
 Object {
@@ -2667,11 +2679,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -2767,7 +2781,7 @@ Object {
 
 exports[`regression tests arrow keys: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests arrow keys: [end of test] number of renders 1`] = `18`;
+exports[`regression tests arrow keys: [end of test] number of renders 1`] = `19`;
 
 exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] appState 1`] = `
 Object {
@@ -2832,11 +2846,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -3242,7 +3258,7 @@ Object {
 
 exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] number of elements 1`] = `3`;
 
-exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] number of renders 1`] = `17`;
+exports[`regression tests can drag element that covers another element, while another elem is selected: [end of test] number of renders 1`] = `18`;
 
 exports[`regression tests change the properties of a shape: [end of test] appState 1`] = `
 Object {
@@ -3304,11 +3320,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -3548,7 +3566,7 @@ Object {
 
 exports[`regression tests change the properties of a shape: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests change the properties of a shape: [end of test] number of renders 1`] = `10`;
+exports[`regression tests change the properties of a shape: [end of test] number of renders 1`] = `11`;
 
 exports[`regression tests click on an element and drag it: [dragged] appState 1`] = `
 Object {
@@ -3613,11 +3631,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -3750,7 +3770,7 @@ Object {
 
 exports[`regression tests click on an element and drag it: [dragged] number of elements 1`] = `1`;
 
-exports[`regression tests click on an element and drag it: [dragged] number of renders 1`] = `9`;
+exports[`regression tests click on an element and drag it: [dragged] number of renders 1`] = `10`;
 
 exports[`regression tests click on an element and drag it: [end of test] appState 1`] = `
 Object {
@@ -3817,11 +3837,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -3992,7 +4014,7 @@ Object {
 
 exports[`regression tests click on an element and drag it: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests click on an element and drag it: [end of test] number of renders 1`] = `12`;
+exports[`regression tests click on an element and drag it: [end of test] number of renders 1`] = `13`;
 
 exports[`regression tests click to select a shape: [end of test] appState 1`] = `
 Object {
@@ -4057,11 +4079,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -4242,7 +4266,7 @@ Object {
 
 exports[`regression tests click to select a shape: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests click to select a shape: [end of test] number of renders 1`] = `12`;
+exports[`regression tests click to select a shape: [end of test] number of renders 1`] = `13`;
 
 exports[`regression tests click-drag to select a group: [end of test] appState 1`] = `
 Object {
@@ -4308,11 +4332,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -4601,7 +4627,7 @@ Object {
 
 exports[`regression tests click-drag to select a group: [end of test] number of elements 1`] = `3`;
 
-exports[`regression tests click-drag to select a group: [end of test] number of renders 1`] = `18`;
+exports[`regression tests click-drag to select a group: [end of test] number of renders 1`] = `19`;
 
 exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] appState 1`] = `
 Object {
@@ -4709,11 +4735,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -4894,7 +4922,7 @@ Object {
 
 exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `13`;
+exports[`regression tests deselects group of selected elements on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `14`;
 
 exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] appState 1`] = `
 Object {
@@ -4980,11 +5008,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -5199,7 +5229,7 @@ Object {
 
 exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] number of renders 1`] = `14`;
+exports[`regression tests deselects group of selected elements on pointer up when pointer hits common bounding box without hitting any element: [end of test] number of renders 1`] = `15`;
 
 exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] appState 1`] = `
 Object {
@@ -5305,11 +5335,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -5405,7 +5437,7 @@ Object {
 
 exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `7`;
+exports[`regression tests deselects selected element on pointer down when pointer doesn't hit any element: [end of test] number of renders 1`] = `8`;
 
 exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] appState 1`] = `
 Object {
@@ -5489,11 +5521,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -5589,7 +5623,7 @@ Object {
 
 exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] number of renders 1`] = `8`;
+exports[`regression tests deselects selected element, on pointer up, when click hits element bounding box but doesn't hit the element: [end of test] number of renders 1`] = `9`;
 
 exports[`regression tests double click to edit a group: [end of test] appState 1`] = `
 Object {
@@ -5651,11 +5685,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -6040,7 +6076,7 @@ Object {
 
 exports[`regression tests double click to edit a group: [end of test] number of elements 1`] = `3`;
 
-exports[`regression tests double click to edit a group: [end of test] number of renders 1`] = `17`;
+exports[`regression tests double click to edit a group: [end of test] number of renders 1`] = `18`;
 
 exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] appState 1`] = `
 Object {
@@ -6109,11 +6145,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -6356,7 +6394,7 @@ Object {
 
 exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] number of renders 1`] = `15`;
+exports[`regression tests drags selected elements from point inside common bounding box that doesn't hit any element and keeps elements selected after dragging: [end of test] number of renders 1`] = `16`;
 
 exports[`regression tests draw every type of shape: [end of test] appState 1`] = `
 Object {
@@ -6418,11 +6456,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -8388,7 +8428,7 @@ Object {
 
 exports[`regression tests draw every type of shape: [end of test] number of elements 1`] = `8`;
 
-exports[`regression tests draw every type of shape: [end of test] number of renders 1`] = `50`;
+exports[`regression tests draw every type of shape: [end of test] number of renders 1`] = `51`;
 
 exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] appState 1`] = `
 Object {
@@ -8455,11 +8495,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -8748,7 +8790,7 @@ Object {
 
 exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] number of elements 1`] = `3`;
 
-exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] number of renders 1`] = `18`;
+exports[`regression tests given a group of selected elements with an element that is not selected inside the group common bounding box when element that is not selected is clicked should switch selection to not selected element on pointer up: [end of test] number of renders 1`] = `19`;
 
 exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partialy overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] appState 1`] = `
 Object {
@@ -8816,11 +8858,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -9001,7 +9045,7 @@ Object {
 
 exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partialy overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partialy overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] number of renders 1`] = `16`;
+exports[`regression tests given a selected element A and a not selected element B with higher z-index than A and given B partialy overlaps A when there's a shift-click on the overlapped section B is added to the selection: [end of test] number of renders 1`] = `17`;
 
 exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] appState 1`] = `
 Object {
@@ -9067,11 +9111,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -9252,7 +9298,7 @@ Object {
 
 exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of renders 1`] = `16`;
+exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of renders 1`] = `17`;
 
 exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] appState 1`] = `
 Object {
@@ -9319,11 +9365,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -9565,7 +9613,7 @@ Object {
 
 exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of renders 1`] = `17`;
+exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of renders 1`] = `18`;
 
 exports[`regression tests key 2 selects rectangle tool: [end of test] appState 1`] = `
 Object {
@@ -9627,11 +9675,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -9727,7 +9777,7 @@ Object {
 
 exports[`regression tests key 2 selects rectangle tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key 2 selects rectangle tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key 2 selects rectangle tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests key 3 selects diamond tool: [end of test] appState 1`] = `
 Object {
@@ -9789,11 +9839,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -9889,7 +9941,7 @@ Object {
 
 exports[`regression tests key 3 selects diamond tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key 3 selects diamond tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key 3 selects diamond tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests key 4 selects ellipse tool: [end of test] appState 1`] = `
 Object {
@@ -9951,11 +10003,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -10051,7 +10105,7 @@ Object {
 
 exports[`regression tests key 4 selects ellipse tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key 4 selects ellipse tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key 4 selects ellipse tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests key 5 selects arrow tool: [end of test] appState 1`] = `
 Object {
@@ -10113,11 +10167,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -10243,7 +10299,7 @@ Object {
 
 exports[`regression tests key 5 selects arrow tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key 5 selects arrow tool: [end of test] number of renders 1`] = `7`;
+exports[`regression tests key 5 selects arrow tool: [end of test] number of renders 1`] = `8`;
 
 exports[`regression tests key 6 selects line tool: [end of test] appState 1`] = `
 Object {
@@ -10305,11 +10361,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -10435,7 +10493,7 @@ Object {
 
 exports[`regression tests key 6 selects line tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key 6 selects line tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key 6 selects line tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests key 7 selects draw tool: [end of test] appState 1`] = `
 Object {
@@ -10497,11 +10555,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -10627,7 +10687,7 @@ Object {
 
 exports[`regression tests key 7 selects draw tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key 7 selects draw tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key 7 selects draw tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests key a selects arrow tool: [end of test] appState 1`] = `
 Object {
@@ -10689,11 +10749,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -10819,7 +10881,7 @@ Object {
 
 exports[`regression tests key a selects arrow tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key a selects arrow tool: [end of test] number of renders 1`] = `7`;
+exports[`regression tests key a selects arrow tool: [end of test] number of renders 1`] = `8`;
 
 exports[`regression tests key d selects diamond tool: [end of test] appState 1`] = `
 Object {
@@ -10881,11 +10943,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -10981,7 +11045,7 @@ Object {
 
 exports[`regression tests key d selects diamond tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key d selects diamond tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key d selects diamond tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests key e selects ellipse tool: [end of test] appState 1`] = `
 Object {
@@ -11043,11 +11107,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -11143,7 +11209,7 @@ Object {
 
 exports[`regression tests key e selects ellipse tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key e selects ellipse tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key e selects ellipse tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests key l selects line tool: [end of test] appState 1`] = `
 Object {
@@ -11205,11 +11271,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -11335,7 +11403,7 @@ Object {
 
 exports[`regression tests key l selects line tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key l selects line tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key l selects line tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests key r selects rectangle tool: [end of test] appState 1`] = `
 Object {
@@ -11397,11 +11465,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -11497,7 +11567,7 @@ Object {
 
 exports[`regression tests key r selects rectangle tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key r selects rectangle tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key r selects rectangle tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests key x selects draw tool: [end of test] appState 1`] = `
 Object {
@@ -11559,11 +11629,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -11689,7 +11761,7 @@ Object {
 
 exports[`regression tests key x selects draw tool: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests key x selects draw tool: [end of test] number of renders 1`] = `6`;
+exports[`regression tests key x selects draw tool: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests make a group and duplicate it: [end of test] appState 1`] = `
 Object {
@@ -11762,11 +11834,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -12403,7 +12477,7 @@ Object {
 
 exports[`regression tests make a group and duplicate it: [end of test] number of elements 1`] = `6`;
 
-exports[`regression tests make a group and duplicate it: [end of test] number of renders 1`] = `21`;
+exports[`regression tests make a group and duplicate it: [end of test] number of renders 1`] = `22`;
 
 exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] appState 1`] = `
 Object {
@@ -12469,11 +12543,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -12654,7 +12730,7 @@ Object {
 
 exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of renders 1`] = `18`;
+exports[`regression tests noop interaction after undo shouldn't create history entry: [end of test] number of renders 1`] = `19`;
 
 exports[`regression tests pinch-to-zoom works: [end of test] appState 1`] = `
 Object {
@@ -12705,7 +12781,7 @@ Object {
   },
   "previousSelectedElementIds": Object {},
   "resizingElement": null,
-  "scrollX": -6,
+  "scrollX": -5.416666666666667,
   "scrollY": 0,
   "scrolledOutside": false,
   "selectedElementIds": Object {
@@ -12716,16 +12792,18 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": true,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
     "translation": Object {
-      "x": 0.3333333333333357,
+      "x": 0.4166666666666714,
       "y": 0,
     },
     "value": 1,
@@ -12754,7 +12832,7 @@ Object {
 
 exports[`regression tests pinch-to-zoom works: [end of test] number of elements 1`] = `0`;
 
-exports[`regression tests pinch-to-zoom works: [end of test] number of renders 1`] = `8`;
+exports[`regression tests pinch-to-zoom works: [end of test] number of renders 1`] = `9`;
 
 exports[`regression tests rerenders UI on language change: [end of test] appState 1`] = `
 Object {
@@ -12814,11 +12892,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -12852,7 +12932,7 @@ Object {
 
 exports[`regression tests rerenders UI on language change: [end of test] number of elements 1`] = `0`;
 
-exports[`regression tests rerenders UI on language change: [end of test] number of renders 1`] = `7`;
+exports[`regression tests rerenders UI on language change: [end of test] number of renders 1`] = `8`;
 
 exports[`regression tests selecting 'Add to library' in context menu adds element to library: [end of test] appState 1`] = `
 Object {
@@ -12914,11 +12994,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -13014,7 +13096,7 @@ Object {
 
 exports[`regression tests selecting 'Add to library' in context menu adds element to library: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests selecting 'Add to library' in context menu adds element to library: [end of test] number of renders 1`] = `6`;
+exports[`regression tests selecting 'Add to library' in context menu adds element to library: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests selecting 'Bring forward' in context menu brings element forward: [end of test] appState 1`] = `
 Object {
@@ -13076,11 +13158,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -13320,7 +13404,7 @@ Object {
 
 exports[`regression tests selecting 'Bring forward' in context menu brings element forward: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests selecting 'Bring forward' in context menu brings element forward: [end of test] number of renders 1`] = `12`;
+exports[`regression tests selecting 'Bring forward' in context menu brings element forward: [end of test] number of renders 1`] = `13`;
 
 exports[`regression tests selecting 'Bring to front' in context menu brings element to front: [end of test] appState 1`] = `
 Object {
@@ -13382,11 +13466,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -13626,7 +13712,7 @@ Object {
 
 exports[`regression tests selecting 'Bring to front' in context menu brings element to front: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests selecting 'Bring to front' in context menu brings element to front: [end of test] number of renders 1`] = `12`;
+exports[`regression tests selecting 'Bring to front' in context menu brings element to front: [end of test] number of renders 1`] = `13`;
 
 exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] appState 1`] = `
 Object {
@@ -13688,11 +13774,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": "Copied styles.",
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -13788,7 +13876,7 @@ Object {
 
 exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `6`;
+exports[`regression tests selecting 'Copy styles' in context menu copies styles: [end of test] number of renders 1`] = `8`;
 
 exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] appState 1`] = `
 Object {
@@ -13848,11 +13936,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -13982,7 +14072,7 @@ Object {
 
 exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] number of renders 1`] = `7`;
+exports[`regression tests selecting 'Delete' in context menu deletes element: [end of test] number of renders 1`] = `8`;
 
 exports[`regression tests selecting 'Duplicate' in context menu duplicates element: [end of test] appState 1`] = `
 Object {
@@ -14044,11 +14134,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -14229,7 +14321,7 @@ Object {
 
 exports[`regression tests selecting 'Duplicate' in context menu duplicates element: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests selecting 'Duplicate' in context menu duplicates element: [end of test] number of renders 1`] = `7`;
+exports[`regression tests selecting 'Duplicate' in context menu duplicates element: [end of test] number of renders 1`] = `8`;
 
 exports[`regression tests selecting 'Group selection' in context menu groups selected elements: [end of test] appState 1`] = `
 Object {
@@ -14297,11 +14389,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -14551,7 +14645,7 @@ Object {
 
 exports[`regression tests selecting 'Group selection' in context menu groups selected elements: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests selecting 'Group selection' in context menu groups selected elements: [end of test] number of renders 1`] = `13`;
+exports[`regression tests selecting 'Group selection' in context menu groups selected elements: [end of test] number of renders 1`] = `14`;
 
 exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] appState 1`] = `
 Object {
@@ -14613,11 +14707,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": "Copied styles.",
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -15388,7 +15484,7 @@ Object {
 
 exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `21`;
+exports[`regression tests selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `23`;
 
 exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] appState 1`] = `
 Object {
@@ -15450,11 +15546,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -15694,7 +15792,7 @@ Object {
 
 exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] number of renders 1`] = `11`;
+exports[`regression tests selecting 'Send backward' in context menu sends element backward: [end of test] number of renders 1`] = `12`;
 
 exports[`regression tests selecting 'Send to back' in context menu sends element to back: [end of test] appState 1`] = `
 Object {
@@ -15756,11 +15854,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -16000,7 +16100,7 @@ Object {
 
 exports[`regression tests selecting 'Send to back' in context menu sends element to back: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests selecting 'Send to back' in context menu sends element to back: [end of test] number of renders 1`] = `11`;
+exports[`regression tests selecting 'Send to back' in context menu sends element to back: [end of test] number of renders 1`] = `12`;
 
 exports[`regression tests selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] appState 1`] = `
 Object {
@@ -16066,11 +16166,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -16377,7 +16479,7 @@ Object {
 
 exports[`regression tests selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of renders 1`] = `14`;
+exports[`regression tests selecting 'Ungroup selection' in context menu ungroups selected group: [end of test] number of renders 1`] = `15`;
 
 exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] appState 1`] = `
 Object {
@@ -16442,11 +16544,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -16542,7 +16646,7 @@ Object {
 
 exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] number of renders 1`] = `8`;
+exports[`regression tests shift click on selected element should deselect it on pointer up: [end of test] number of renders 1`] = `9`;
 
 exports[`regression tests shift-click to multiselect, then drag: [end of test] appState 1`] = `
 Object {
@@ -16613,11 +16717,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -16861,7 +16967,7 @@ Object {
 
 exports[`regression tests shift-click to multiselect, then drag: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests shift-click to multiselect, then drag: [end of test] number of renders 1`] = `17`;
+exports[`regression tests shift-click to multiselect, then drag: [end of test] number of renders 1`] = `18`;
 
 exports[`regression tests should show fill icons when element has non transparent background: [end of test] appState 1`] = `
 Object {
@@ -16926,11 +17032,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -17098,7 +17206,7 @@ Object {
 
 exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of renders 1`] = `10`;
+exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of renders 1`] = `11`;
 
 exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] appState 1`] = `
 Object {
@@ -17166,11 +17274,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -17351,7 +17461,7 @@ Object {
 
 exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] number of renders 1`] = `14`;
+exports[`regression tests shows 'Group selection' in context menu for multiple selected elements: [end of test] number of renders 1`] = `15`;
 
 exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] appState 1`] = `
 Object {
@@ -17421,11 +17531,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -17676,7 +17788,7 @@ Object {
 
 exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of renders 1`] = `15`;
+exports[`regression tests shows 'Ungroup selection' in context menu for group inside selected elements: [end of test] number of renders 1`] = `16`;
 
 exports[`regression tests shows context menu for canvas: [end of test] appState 1`] = `
 Object {
@@ -17736,11 +17848,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -17774,7 +17888,7 @@ Object {
 
 exports[`regression tests shows context menu for canvas: [end of test] number of elements 1`] = `0`;
 
-exports[`regression tests shows context menu for canvas: [end of test] number of renders 1`] = `2`;
+exports[`regression tests shows context menu for canvas: [end of test] number of renders 1`] = `3`;
 
 exports[`regression tests shows context menu for element: [end of test] appState 1`] = `
 Object {
@@ -17836,11 +17950,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -17936,7 +18052,7 @@ Object {
 
 exports[`regression tests shows context menu for element: [end of test] number of elements 1`] = `1`;
 
-exports[`regression tests shows context menu for element: [end of test] number of renders 1`] = `6`;
+exports[`regression tests shows context menu for element: [end of test] number of renders 1`] = `7`;
 
 exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] appState 1`] = `
 Object {
@@ -18009,11 +18125,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -18755,7 +18873,7 @@ Object {
 
 exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of elements 1`] = `4`;
 
-exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of renders 1`] = `36`;
+exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] number of renders 1`] = `37`;
 
 exports[`regression tests spacebar + drag scrolls the canvas: [end of test] appState 1`] = `
 Object {
@@ -18815,11 +18933,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -18853,7 +18973,7 @@ Object {
 
 exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of elements 1`] = `0`;
 
-exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of renders 1`] = `5`;
+exports[`regression tests spacebar + drag scrolls the canvas: [end of test] number of renders 1`] = `6`;
 
 exports[`regression tests supports nested groups: [end of test] appState 1`] = `
 Object {
@@ -18917,11 +19037,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -19583,7 +19705,7 @@ Object {
 
 exports[`regression tests supports nested groups: [end of test] number of elements 1`] = `3`;
 
-exports[`regression tests supports nested groups: [end of test] number of renders 1`] = `29`;
+exports[`regression tests supports nested groups: [end of test] number of renders 1`] = `30`;
 
 exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] appState 1`] = `
 Object {
@@ -19693,11 +19815,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -19986,7 +20110,7 @@ Object {
 
 exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] number of elements 1`] = `3`;
 
-exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] number of renders 1`] = `17`;
+exports[`regression tests switches from group of selected elements to another element on pointer down: [end of test] number of renders 1`] = `18`;
 
 exports[`regression tests switches selected element on pointer down: [end of test] appState 1`] = `
 Object {
@@ -20094,11 +20218,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -20279,7 +20405,7 @@ Object {
 
 exports[`regression tests switches selected element on pointer down: [end of test] number of elements 1`] = `2`;
 
-exports[`regression tests switches selected element on pointer down: [end of test] number of renders 1`] = `11`;
+exports[`regression tests switches selected element on pointer down: [end of test] number of renders 1`] = `12`;
 
 exports[`regression tests two-finger scroll works: [end of test] appState 1`] = `
 Object {
@@ -20330,7 +20456,7 @@ Object {
   },
   "previousSelectedElementIds": Object {},
   "resizingElement": null,
-  "scrollX": 11,
+  "scrollX": 11.046099290780141,
   "scrollY": -5,
   "scrolledOutside": false,
   "selectedElementIds": Object {
@@ -20341,16 +20467,18 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": true,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
     "translation": Object {
-      "x": -60.420000000000016,
+      "x": -59.425,
       "y": -48.66347517730496,
     },
     "value": 1.99,
@@ -20379,7 +20507,7 @@ Object {
 
 exports[`regression tests two-finger scroll works: [end of test] number of elements 1`] = `0`;
 
-exports[`regression tests two-finger scroll works: [end of test] number of renders 1`] = `10`;
+exports[`regression tests two-finger scroll works: [end of test] number of renders 1`] = `11`;
 
 exports[`regression tests undo/redo drawing an element: [end of test] appState 1`] = `
 Object {
@@ -20441,11 +20569,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -20875,7 +21005,7 @@ Object {
 
 exports[`regression tests undo/redo drawing an element: [end of test] number of elements 1`] = `3`;
 
-exports[`regression tests undo/redo drawing an element: [end of test] number of renders 1`] = `27`;
+exports[`regression tests undo/redo drawing an element: [end of test] number of renders 1`] = `28`;
 
 exports[`regression tests updates fontSize & fontFamily appState: [end of test] appState 1`] = `
 Object {
@@ -20935,11 +21065,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -20973,7 +21105,7 @@ Object {
 
 exports[`regression tests updates fontSize & fontFamily appState: [end of test] number of elements 1`] = `0`;
 
-exports[`regression tests updates fontSize & fontFamily appState: [end of test] number of renders 1`] = `4`;
+exports[`regression tests updates fontSize & fontFamily appState: [end of test] number of renders 1`] = `5`;
 
 exports[`regression tests zoom hotkeys: [end of test] appState 1`] = `
 Object {
@@ -21033,11 +21165,13 @@ Object {
   "shouldAddWatermark": false,
   "shouldCacheIgnoreZoom": false,
   "showGrid": false,
-  "showShortcutsDialog": false,
+  "showHelpDialog": false,
   "showStats": false,
   "startBoundElement": null,
   "suggestedBindings": Array [],
+  "toastMessage": null,
   "viewBackgroundColor": "#ffffff",
+  "viewModeEnabled": false,
   "width": 1024,
   "zenModeEnabled": false,
   "zoom": Object {
@@ -21071,4 +21205,4 @@ Object {
 
 exports[`regression tests zoom hotkeys: [end of test] number of elements 1`] = `0`;
 
-exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `4`;
+exports[`regression tests zoom hotkeys: [end of test] number of renders 1`] = `5`;
diff --git a/src/tests/dragCreate.test.tsx b/src/tests/dragCreate.test.tsx
index ae08977c3c..7a55ed89a4 100644
--- a/src/tests/dragCreate.test.tsx
+++ b/src/tests/dragCreate.test.tsx
@@ -37,7 +37,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
     // finish (position does not matter)
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(6);
+    expect(renderScene).toHaveBeenCalledTimes(7);
     expect(h.state.selectionElement).toBeNull();
 
     expect(h.elements.length).toEqual(1);
@@ -68,7 +68,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
     // finish (position does not matter)
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(6);
+    expect(renderScene).toHaveBeenCalledTimes(7);
     expect(h.state.selectionElement).toBeNull();
 
     expect(h.elements.length).toEqual(1);
@@ -99,7 +99,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
     // finish (position does not matter)
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(6);
+    expect(renderScene).toHaveBeenCalledTimes(7);
     expect(h.state.selectionElement).toBeNull();
 
     expect(h.elements.length).toEqual(1);
@@ -130,7 +130,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
     // finish (position does not matter)
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(6);
+    expect(renderScene).toHaveBeenCalledTimes(7);
     expect(h.state.selectionElement).toBeNull();
 
     expect(h.elements.length).toEqual(1);
@@ -165,7 +165,7 @@ describe("add element to the scene when pointer dragging long enough", () => {
     // finish (position does not matter)
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(6);
+    expect(renderScene).toHaveBeenCalledTimes(7);
     expect(h.state.selectionElement).toBeNull();
 
     expect(h.elements.length).toEqual(1);
@@ -198,7 +198,7 @@ describe("do not add element to the scene if size is too small", () => {
     // finish (position does not matter)
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(5);
+    expect(renderScene).toHaveBeenCalledTimes(6);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(0);
   });
@@ -217,7 +217,7 @@ describe("do not add element to the scene if size is too small", () => {
     // finish (position does not matter)
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(5);
+    expect(renderScene).toHaveBeenCalledTimes(6);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(0);
   });
@@ -236,7 +236,7 @@ describe("do not add element to the scene if size is too small", () => {
     // finish (position does not matter)
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(5);
+    expect(renderScene).toHaveBeenCalledTimes(6);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(0);
   });
@@ -258,7 +258,7 @@ describe("do not add element to the scene if size is too small", () => {
     // we need to finalize it because arrows and lines enter multi-mode
     fireEvent.keyDown(document, { key: KEYS.ENTER });
 
-    expect(renderScene).toHaveBeenCalledTimes(6);
+    expect(renderScene).toHaveBeenCalledTimes(7);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(0);
   });
@@ -280,7 +280,7 @@ describe("do not add element to the scene if size is too small", () => {
     // we need to finalize it because arrows and lines enter multi-mode
     fireEvent.keyDown(document, { key: KEYS.ENTER });
 
-    expect(renderScene).toHaveBeenCalledTimes(6);
+    expect(renderScene).toHaveBeenCalledTimes(7);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(0);
   });
diff --git a/src/tests/move.test.tsx b/src/tests/move.test.tsx
index e97ace31f5..e356f89a38 100644
--- a/src/tests/move.test.tsx
+++ b/src/tests/move.test.tsx
@@ -38,7 +38,7 @@ describe("move element", () => {
       fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
       fireEvent.pointerUp(canvas);
 
-      expect(renderScene).toHaveBeenCalledTimes(6);
+      expect(renderScene).toHaveBeenCalledTimes(7);
       expect(h.state.selectionElement).toBeNull();
       expect(h.elements.length).toEqual(1);
       expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
@@ -77,7 +77,7 @@ describe("move element", () => {
     // select the second rectangles
     new Pointer("mouse").clickOn(rectB);
 
-    expect(renderScene).toHaveBeenCalledTimes(20);
+    expect(renderScene).toHaveBeenCalledTimes(21);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(3);
     expect(h.state.selectedElementIds[rectB.id]).toBeTruthy();
@@ -120,7 +120,7 @@ describe("duplicate element on move when ALT is clicked", () => {
       fireEvent.pointerMove(canvas, { clientX: 60, clientY: 70 });
       fireEvent.pointerUp(canvas);
 
-      expect(renderScene).toHaveBeenCalledTimes(6);
+      expect(renderScene).toHaveBeenCalledTimes(7);
       expect(h.state.selectionElement).toBeNull();
       expect(h.elements.length).toEqual(1);
       expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
diff --git a/src/tests/multiPointCreate.test.tsx b/src/tests/multiPointCreate.test.tsx
index 406c5888be..93bbc6639b 100644
--- a/src/tests/multiPointCreate.test.tsx
+++ b/src/tests/multiPointCreate.test.tsx
@@ -30,7 +30,7 @@ describe("remove shape in non linear elements", () => {
     fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
     fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 });
 
-    expect(renderScene).toHaveBeenCalledTimes(5);
+    expect(renderScene).toHaveBeenCalledTimes(6);
     expect(h.elements.length).toEqual(0);
   });
 
@@ -44,7 +44,7 @@ describe("remove shape in non linear elements", () => {
     fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
     fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 });
 
-    expect(renderScene).toHaveBeenCalledTimes(5);
+    expect(renderScene).toHaveBeenCalledTimes(6);
     expect(h.elements.length).toEqual(0);
   });
 
@@ -58,7 +58,7 @@ describe("remove shape in non linear elements", () => {
     fireEvent.pointerDown(canvas, { clientX: 30, clientY: 20 });
     fireEvent.pointerUp(canvas, { clientX: 30, clientY: 30 });
 
-    expect(renderScene).toHaveBeenCalledTimes(5);
+    expect(renderScene).toHaveBeenCalledTimes(6);
     expect(h.elements.length).toEqual(0);
   });
 });
@@ -88,7 +88,7 @@ describe("multi point mode in linear elements", () => {
     fireEvent.pointerUp(canvas);
     fireEvent.keyDown(document, { key: KEYS.ENTER });
 
-    expect(renderScene).toHaveBeenCalledTimes(12);
+    expect(renderScene).toHaveBeenCalledTimes(13);
     expect(h.elements.length).toEqual(1);
 
     const element = h.elements[0] as ExcalidrawLinearElement;
@@ -129,7 +129,7 @@ describe("multi point mode in linear elements", () => {
     fireEvent.pointerUp(canvas);
     fireEvent.keyDown(document, { key: KEYS.ENTER });
 
-    expect(renderScene).toHaveBeenCalledTimes(12);
+    expect(renderScene).toHaveBeenCalledTimes(13);
     expect(h.elements.length).toEqual(1);
 
     const element = h.elements[0] as ExcalidrawLinearElement;
diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx
index 95fb5662ec..a0bba4f427 100644
--- a/src/tests/regressionTests.test.tsx
+++ b/src/tests/regressionTests.test.tsx
@@ -618,15 +618,17 @@ describe("regression tests", () => {
       clientY: 1,
     });
     const contextMenu = document.querySelector(".context-menu");
+    const contextMenuOptions = document.querySelectorAll(".context-menu li");
     const expectedShortcutNames: ShortcutName[] = [
       "selectAll",
       "gridMode",
       "zenMode",
+      "viewMode",
       "stats",
     ];
 
     expect(contextMenu).not.toBeNull();
-    expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
+    expect(contextMenuOptions.length).toBe(expectedShortcutNames.length);
     expectedShortcutNames.forEach((shortcutName) => {
       expect(
         contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
@@ -645,11 +647,11 @@ describe("regression tests", () => {
       clientY: 1,
     });
     const contextMenu = document.querySelector(".context-menu");
+    const contextMenuOptions = document.querySelectorAll(".context-menu li");
     const expectedShortcutNames: ShortcutName[] = [
-      "cut",
       "copyStyles",
       "pasteStyles",
-      "delete",
+      "deleteSelectedElements",
       "addToLibrary",
       "sendBackward",
       "bringForward",
@@ -659,7 +661,7 @@ describe("regression tests", () => {
     ];
 
     expect(contextMenu).not.toBeNull();
-    expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
+    expect(contextMenuOptions.length).toBe(expectedShortcutNames.length);
     expectedShortcutNames.forEach((shortcutName) => {
       expect(
         contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
@@ -689,11 +691,11 @@ describe("regression tests", () => {
     });
 
     const contextMenu = document.querySelector(".context-menu");
+    const contextMenuOptions = document.querySelectorAll(".context-menu li");
     const expectedShortcutNames: ShortcutName[] = [
-      "cut",
       "copyStyles",
       "pasteStyles",
-      "delete",
+      "deleteSelectedElements",
       "group",
       "addToLibrary",
       "sendBackward",
@@ -704,7 +706,7 @@ describe("regression tests", () => {
     ];
 
     expect(contextMenu).not.toBeNull();
-    expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
+    expect(contextMenuOptions.length).toBe(expectedShortcutNames.length);
     expectedShortcutNames.forEach((shortcutName) => {
       expect(
         contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
@@ -738,11 +740,11 @@ describe("regression tests", () => {
     });
 
     const contextMenu = document.querySelector(".context-menu");
+    const contextMenuOptions = document.querySelectorAll(".context-menu li");
     const expectedShortcutNames: ShortcutName[] = [
-      "cut",
       "copyStyles",
       "pasteStyles",
-      "delete",
+      "deleteSelectedElements",
       "ungroup",
       "addToLibrary",
       "sendBackward",
@@ -753,7 +755,7 @@ describe("regression tests", () => {
     ];
 
     expect(contextMenu).not.toBeNull();
-    expect(contextMenu?.children.length).toBe(expectedShortcutNames.length);
+    expect(contextMenuOptions.length).toBe(expectedShortcutNames.length);
     expectedShortcutNames.forEach((shortcutName) => {
       expect(
         contextMenu?.querySelector(`li[data-testid="${shortcutName}"]`),
diff --git a/src/tests/resize.test.tsx b/src/tests/resize.test.tsx
index 8d207c08ef..70c79fe6b9 100644
--- a/src/tests/resize.test.tsx
+++ b/src/tests/resize.test.tsx
@@ -41,7 +41,7 @@ describe("resize rectangle ellipses and diamond elements", () => {
     ${"s"}  | ${[_, 39]}      | ${[100, 139]} | ${[elemData.x, elemData.x]}
     ${"e"}  | ${[-20, _]}     | ${[80, 100]}  | ${[elemData.x, elemData.y]}
     ${"w"}  | ${[-20, _]}     | ${[120, 100]} | ${[-20, elemData.y]}
-    ${"ne"} | ${[10, 55]}     | ${[110, 45]}  | ${[elemData.x, 55]}
+    ${"ne"} | ${[5, 55]}      | ${[105, 45]}  | ${[elemData.x, 55]}
     ${"se"} | ${[-30, -10]}   | ${[70, 90]}   | ${[elemData.x, elemData.y]}
     ${"nw"} | ${[-300, -200]} | ${[400, 300]} | ${[-300, -200]}
     ${"sw"} | ${[40, -20]}    | ${[60, 80]}   | ${[40, 0]}
diff --git a/src/tests/selection.test.tsx b/src/tests/selection.test.tsx
index 0d2350d1f0..004c4bcd99 100644
--- a/src/tests/selection.test.tsx
+++ b/src/tests/selection.test.tsx
@@ -28,7 +28,7 @@ describe("selection element", () => {
     const canvas = container.querySelector("canvas")!;
     fireEvent.pointerDown(canvas, { clientX: 60, clientY: 100 });
 
-    expect(renderScene).toHaveBeenCalledTimes(3);
+    expect(renderScene).toHaveBeenCalledTimes(4);
     const selectionElement = h.state.selectionElement!;
     expect(selectionElement).not.toBeNull();
     expect(selectionElement.type).toEqual("selection");
@@ -49,7 +49,7 @@ describe("selection element", () => {
     fireEvent.pointerDown(canvas, { clientX: 60, clientY: 100 });
     fireEvent.pointerMove(canvas, { clientX: 150, clientY: 30 });
 
-    expect(renderScene).toHaveBeenCalledTimes(4);
+    expect(renderScene).toHaveBeenCalledTimes(5);
     const selectionElement = h.state.selectionElement!;
     expect(selectionElement).not.toBeNull();
     expect(selectionElement.type).toEqual("selection");
@@ -71,7 +71,7 @@ describe("selection element", () => {
     fireEvent.pointerMove(canvas, { clientX: 150, clientY: 30 });
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(5);
+    expect(renderScene).toHaveBeenCalledTimes(6);
     expect(h.state.selectionElement).toBeNull();
   });
 });
@@ -96,7 +96,7 @@ describe("select single element on the scene", () => {
     fireEvent.pointerDown(canvas, { clientX: 45, clientY: 20 });
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(9);
+    expect(renderScene).toHaveBeenCalledTimes(10);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(1);
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
@@ -123,7 +123,7 @@ describe("select single element on the scene", () => {
     fireEvent.pointerDown(canvas, { clientX: 45, clientY: 20 });
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(9);
+    expect(renderScene).toHaveBeenCalledTimes(10);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(1);
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
@@ -150,7 +150,7 @@ describe("select single element on the scene", () => {
     fireEvent.pointerDown(canvas, { clientX: 45, clientY: 20 });
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(9);
+    expect(renderScene).toHaveBeenCalledTimes(10);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(1);
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
@@ -190,7 +190,7 @@ describe("select single element on the scene", () => {
     fireEvent.pointerDown(canvas, { clientX: 40, clientY: 40 });
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(9);
+    expect(renderScene).toHaveBeenCalledTimes(10);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(1);
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
@@ -229,7 +229,7 @@ describe("select single element on the scene", () => {
     fireEvent.pointerDown(canvas, { clientX: 40, clientY: 40 });
     fireEvent.pointerUp(canvas);
 
-    expect(renderScene).toHaveBeenCalledTimes(9);
+    expect(renderScene).toHaveBeenCalledTimes(10);
     expect(h.state.selectionElement).toBeNull();
     expect(h.elements.length).toEqual(1);
     expect(h.state.selectedElementIds[h.elements[0].id]).toBeTruthy();
diff --git a/src/tests/test-utils.ts b/src/tests/test-utils.ts
index 1822ecc573..f78295be6f 100644
--- a/src/tests/test-utils.ts
+++ b/src/tests/test-utils.ts
@@ -100,5 +100,5 @@ const initLocalStorage = (data: ImportedDataState) => {
 };
 
 export const updateSceneData = (data: SceneData) => {
-  (window.h.collab as any).excalidrawRef.current.updateScene(data);
+  (window.h.collab as any).excalidrawAPI.updateScene(data);
 };
diff --git a/src/types.ts b/src/types.ts
index 0310941edf..018f48d9cc 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -21,7 +21,6 @@ import type { ResolvablePromise } from "./utils";
 import { Spreadsheet } from "./charts";
 import { Language } from "./i18n";
 
-export type FlooredNumber = number & { _brand: "FlooredNumber" };
 export type Point = Readonly<RoughPoint>;
 
 export type Collaborator = {
@@ -68,8 +67,8 @@ export type AppState = {
   currentItemEndArrowhead: Arrowhead | null;
   currentItemLinearStrokeSharpness: ExcalidrawElement["strokeSharpness"];
   viewBackgroundColor: string;
-  scrollX: FlooredNumber;
-  scrollY: FlooredNumber;
+  scrollX: number;
+  scrollY: number;
   cursorButton: "up" | "down";
   scrolledOutside: boolean;
   name: string;
@@ -81,11 +80,13 @@ export type AppState = {
   selectedElementIds: { [id: string]: boolean };
   previousSelectedElementIds: { [id: string]: boolean };
   shouldCacheIgnoreZoom: boolean;
-  showShortcutsDialog: boolean;
+  showHelpDialog: boolean;
+  toastMessage: string | null;
   zenModeEnabled: boolean;
   appearance: "light" | "dark";
   gridSize: number;
   showGrid: boolean;
+  viewModeEnabled: boolean;
 
   /** top-most selected groups (i.e. does not include nested groups) */
   selectedGroupIds: { [groupId: string]: boolean };
@@ -98,7 +99,7 @@ export type AppState = {
   offsetLeft: number;
 
   isLibraryOpen: boolean;
-  fileHandle: import("browser-nativefs").FileSystemHandle | null;
+  fileHandle: import("browser-fs-access").FileSystemHandle | null;
   collaborators: Map<string, Collaborator>;
   showStats: boolean;
   currentChartType: ChartType;
@@ -146,10 +147,7 @@ export type LibraryItems = readonly LibraryItem[];
 // NOTE ready/readyPromise props are optional for host apps' sake (our own
 // implem guarantees existence)
 export type ExcalidrawAPIRefValue =
-  | (ExcalidrawImperativeAPI & {
-      readyPromise?: ResolvablePromise<ExcalidrawImperativeAPI>;
-      ready?: true;
-    })
+  | ExcalidrawImperativeAPI
   | {
       readyPromise?: ResolvablePromise<ExcalidrawImperativeAPI>;
       ready?: false;
@@ -185,6 +183,7 @@ export interface ExcalidrawProps {
   ) => void;
   renderFooter?: (isMobile: boolean) => JSX.Element;
   langCode?: Language["code"];
+  viewModeEnabled?: boolean;
 }
 
 export type SceneData = {
diff --git a/src/utils.ts b/src/utils.ts
index 933765c2f6..936b2850c6 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -364,6 +364,8 @@ export const nFormatter = (num: number, digits: number): string => {
 };
 
 export const getVersion = () => {
-  const version = document.querySelector('meta[name="version"]');
-  return version ? (version as any).content : DEFAULT_VERSION;
+  return (
+    document.querySelector<HTMLMetaElement>('meta[name="version"]')?.content ||
+    DEFAULT_VERSION
+  );
 };
diff --git a/now.json b/vercel.json
similarity index 79%
rename from now.json
rename to vercel.json
index 75a64f33e7..d77c6b002c 100644
--- a/now.json
+++ b/vercel.json
@@ -21,12 +21,5 @@
         }
       ]
     }
-  ],
-  "redirects": [
-    {
-      "source": "/([^.]+)",
-      "destination": "/",
-      "statusCode": 301
-    }
   ]
 }