[
  {
    "path": ".browserslistrc",
    "content": "> 1%\nlast 3 versions\nnot ie <= 8\nios >= 9"
  },
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  env: {\n    node: true\n  },\n  extends: ['plugin:vue/essential', '@vue/prettier'],\n  rules: {\n    'vue/component-name-in-template-casing': ['error', 'PascalCase'],\n    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',\n    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'\n  },\n  parserOptions: {\n    parser: 'babel-eslint'\n  }\n}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules\n/dist\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Editor directories and files\n.idea\n.vscode\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw*\n\nstats.json\n/cert\n"
  },
  {
    "path": ".nvmrc",
    "content": "12.18.3\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n  trailingComma: 'none',\n  tabWidth: 2,\n  semi: false,\n  singleQuote: true\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Jan Hug (dulnan)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![Logo](public/drawmote-teaser.jpg)\n# drawmote\n*Draw remotely with your phone*\n\n### **[Try it out on drawmote.app](https://drawmote.app)**\n\n## What is drawmote?\ndrawmote is a browser app that allows you to use your phone as an input device\nto point at and draw on your computer screen. It works by establishing a WebRTC\nconnection between a phone and computer, using the phone's gyroscope to calculate\nwhere the phone is pointing at on the screen and simulating mouse movement to\ndraw on a canvas.\n\n## How it's built\nSome of the things used to build drawmote:\n\n### Frameworks and libraries\n- **[Vue.js](https://github.com/vuejs/vue)**\\\n  as the JavaScript framework\n- **[simple-peer](https://github.com/feross/simple-peer)**\\\n  to establish a WebRTC connection\n- **[gyronorm](https://github.com/dorukeker/gyronorm.js)**\\\n  for cross-browser reading of a gyroscope\n\n### Custom libraries\nDuring the development of drawmote some functionality has been extracted to\nseparate repositories and libraries:\n\n| Name | Description | Demo |\n| ------------- | ------------- | ------------- |\n| **[peersox](https://github.com/dulnan/peersox)** | Client and server to generate pairing codes and hashes, establish WebRTC data connection and WebSocket server as a fallback. | |\n| **[Vuetamin](https://github.com/dulnan/vuetamin)** | Combine animation loops from multiple components into a single requestAnimationFrame loop and provide a consistent state. | |\n| **[lazy-brush](https://github.com/dulnan/lazy-brush)** | Smooth drawing by pulling the brush with a rope connected to the brush and pointer | [Demo](https://lazybrush.dulnan.net) |\n| **[catenary-curve](https://github.com/dulnan/catenary-curve)** | Calculate and draw a cantary curve on a canvas | [Demo](https://lazybrush.dulnan.net) |\n| **[gymote](https://github.com/dulnan/gymote)** | Easy way to use a phone as a remote pointing device for a desktop screen. | |\n| **[gyro-plane](https://github.com/thormeier/gyro-plane)** | Using alpha and beta angles from a gyroscope, calculate where its pointing at on a screen |  |\n\n### History\nThe app has been fundamentally changed and refactored several times during\ndevelopment. It started out as a [hacky VanillaJS proof-of-concept](https://github.com/dulnan/drawmote-server/tree/f7fa7327cec66f5647fbd948d3e31eeb5cf8cf02), then got\nrefactored into an [OOP-style codebase (horrible!)](https://github.com/dulnan/drawmote-server/tree/07334b0e5c2909eb67ef5476e4ac19c4727ec514). After that, a complete rewrite using\nVue.js happened. At first Vuex was used as a way to store and share data.\nPretty soon it was clear that this increases the latency from gyroscope to\ncanvas draw. So I switched to an event-based approach, with an event bus\nnotifying components about new orientation data from the gyroscope. That worked\nquite well, but was still measurably introducing a lag.\n\n## How low latency was achieved\nUntil quite late in the project, every component had its own animation loop\nusing requestAnimationFrame. In total there were 7 loops running at the same\ntime. The problem was that these loops ran at different speeds, had different\nstates and sometimes were interfering with each other. The solution was to\ncompletely remove Vuex and manage state manually. A Vue plugin was created that\nallows for every component to define an animation function. The plugin (called\n[Vuetamin](https://github.com/dulnan/vuetamin)) takes all these functions and\nruns them in a single requestAnimationFrame loop.\n\nWith this approach, the time passing from when new orientation data is received\nand when the last draw function has been done, is on average just 8ms, which is\nnot really noticeable. If phone and desktop are in the same network, the total\ndelay from when gyroscope values are read out and the brush is moving on the\nscreen is higher, but still not close to a range where drawing becomes annoying.\nEven when both devices are in seprate networks with good network connetions,\nit's still useable.\n\nAfter a few seconds, our brains can compensate easily for the delay introduced\nbetween what the hand is doing and what the eyes are seeing.\n\n## Run locally\nYou need both the client and\n[drawmote-server](https://github.com/dulnan/drawmote-server) to run it locally.\n\nInstall dependencies for the client:\n```\nnpm install\n```\n\n### Certificates\nSince iOS 12.2 it is required to request permission to access the\nDeviceMotionEvent. For this to work the connection can not be insecure and\nthus https is required. To get the certificates needed, follow this tutorial\nand put the files in ./cert.\nhttps://engineering.circle.com/https-authorized-certs-with-node-js-315e548354a2\n\nStart drawmote-server and set the IP address of the server in `.env.development`.\nThen you can start running development mode for the client:\n```\nnpm run serve\n```\n"
  },
  {
    "path": "babel.config.js",
    "content": "module.exports = {\n  presets: [['@vue/app', { useBuiltIns: 'entry', modules: false, loose: true }]]\n}\n"
  },
  {
    "path": "netlify.toml",
    "content": "[Settings]\n  ID = \"f278814b-e1d8-4982-b6b7-8f8235c7e97a\"\n\n[build]\n  Publish = \"dist/\"\n  Functions = \"\"\n\n[context.develop]\n  command = \"npm run build-develop\"\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"drawmote\",\n  \"version\": \"1.4.1\",\n  \"private\": true,\n  \"scripts\": {\n    \"serve\": \"vue-cli-service serve\",\n    \"build\": \"vue-cli-service build\",\n    \"build-develop\": \"vue-cli-service build --mode develop\",\n    \"lint\": \"vue-cli-service lint\",\n    \"stats\": \"webpack --profile --json > stats.json --config ./node_modules/@vue/cli-service/webpack.config.js\"\n  },\n  \"dependencies\": {\n    \"@sentry/browser\": \"^5.20.1\",\n    \"@sentry/integrations\": \"^5.20.1\",\n    \"animejs\": \"^3.2.0\",\n    \"catenary-curve\": \"^1.0.1\",\n    \"core-js\": \"^3.6.5\",\n    \"dat.gui\": \"^0.7.7\",\n    \"debounced-resize\": \"^1.1.1\",\n    \"gymote\": \"^1.0.0\",\n    \"html-webpack-inline-source-plugin\": \"0.0.10\",\n    \"input-range-scss\": \"^1.5.2\",\n    \"lazy-brush\": \"^1.0.1\",\n    \"peersox\": \"^0.3.0\",\n    \"prerender-spa-plugin\": \"^3.4.0\",\n    \"three\": \"^0.118.3\",\n    \"universal-cookie\": \"^4.0.3\",\n    \"vue\": \"^2.6.11\",\n    \"vue-i18n\": \"^8.19.0\",\n    \"vue-resize\": \"^0.5.0\",\n    \"vuetamin\": \"^0.0.3\",\n    \"vuex\": \"^3.5.1\",\n    \"whatwg-fetch\": \"^3.2.0\"\n  },\n  \"devDependencies\": {\n    \"@vue/cli-plugin-babel\": \"^4.4.6\",\n    \"@vue/cli-plugin-eslint\": \"^4.4.6\",\n    \"@vue/cli-service\": \"^4.4.6\",\n    \"@vue/eslint-config-prettier\": \"^6.0.0\",\n    \"babel-eslint\": \"^10.1.0\",\n    \"eslint\": \"^7.5.0\",\n    \"eslint-plugin-prettier\": \"^3.1.4\",\n    \"eslint-plugin-vue\": \"^6.2.2\",\n    \"prettier\": \"^2.0.5\",\n    \"kanbasu\": \"^2.5.0\",\n    \"sass\": \"^1.26.10\",\n    \"sass-loader\": \"^9.0.2\",\n    \"style-resources-loader\": \"^1.3.3\",\n    \"terser-webpack-plugin\": \"^3.0.7\",\n    \"vue-cli-plugin-i18n\": \"^1.0.1\",\n    \"vue-cli-plugin-style-resources-loader\": \"^0.1.4\",\n    \"vue-svg-loader\": \"^0.16.0\",\n    \"vue-template-compiler\": \"^2.6.11\"\n  },\n  \"author\": \"Jan Hug <me@dulnan.net>\",\n  \"description\": \"client application for drawmote\"\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    autoprefixer: {}\n  }\n}\n"
  },
  {
    "path": "public/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\" style=\"background: #34152b\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0,user-scalable=no\">\n\n    <title>drawmote - draw remotely with your phone</title>\n\n    <meta name=\"keywords\" content=\"draw remote drawing phone webrtc websockets\">\n    <meta name=\"description\" content=\"Use your phone as a pointing device to remotely draw on your computer screen.\">\n    <meta name=\"robots\" content=\"all\">\n\n    <meta property=\"og:url\" content=\"<%= VUE_APP_URL %>\">\n    <meta property=\"og:type\" content=\"website\">\n    <meta property=\"og:title\" content=\"drawmote.app\">\n    <meta property=\"og:description\" content=\"Use your phone to draw on your computer screen. Visit drawmote.app on your phone and computer (or tablet) to start drawing.\">\n    <meta property=\"og:image\" content=\"<%= VUE_APP_URL %><%= BASE_URL %>drawmote-teaser.jpg\">\n\n    <meta name=\"twitter:card\" content=\"summary_large_image\">\n    <meta name=\"twitter:site\" content=\"@dulnan\">\n    <meta name=\"twitter:creator\" content=\"@dulnan\">\n    <meta name=\"twitter:image\" content=\"<%= VUE_APP_URL %><%= BASE_URL %>drawmote-teaser-twitter.jpg\">\n\n    <link rel=\"apple-touch-icon\" sizes=\"57x57\" href=\"<%= BASE_URL %>apple-icon-57x57.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"60x60\" href=\"<%= BASE_URL %>apple-icon-60x60.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"<%= BASE_URL %>apple-icon-72x72.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"<%= BASE_URL %>apple-icon-76x76.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"114x114\" href=\"<%= BASE_URL %>apple-icon-114x114.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"120x120\" href=\"<%= BASE_URL %>apple-icon-120x120.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"144x144\" href=\"<%= BASE_URL %>apple-icon-144x144.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"152x152\" href=\"<%= BASE_URL %>apple-icon-152x152.png\">\n    <link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"<%= BASE_URL %>apple-icon-180x180.png\">\n\n    <link rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"<%= BASE_URL %>android-icon-192x192.png\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"<%= BASE_URL %>favicon-32x32.png\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"<%= BASE_URL %>favicon-96x96.png\">\n    <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"<%= BASE_URL %>favicon-16x16.png\">\n\n    <link rel=\"apple-touch-startup-image\" href=\"<%= BASE_URL %>launch-640x1136.png\" media=\"(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)\">\n    <link rel=\"apple-touch-startup-image\" href=\"<%= BASE_URL %>launch-750x1294.png\" media=\"(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)\">\n    <link rel=\"apple-touch-startup-image\" href=\"<%= BASE_URL %>launch-1242x2148.png\" media=\"(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)\">\n    <link rel=\"apple-touch-startup-image\" href=\"<%= BASE_URL %>launch-1125x2436.png\" media=\"(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)\">\n    <link rel=\"apple-touch-startup-image\" href=\"<%= BASE_URL %>launch-1536x2048.png\" media=\"(min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)\">\n    <link rel=\"apple-touch-startup-image\" href=\"<%= BASE_URL %>launch-1668x2224.png\" media=\"(min-device-width: 834px) and (max-device-width: 834px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)\">\n    <link rel=\"apple-touch-startup-image\" href=\"<%= BASE_URL %>launch-2048x2732.png\" media=\"(min-device-width: 1024px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait)\">\n\n    <meta name=\"msapplication-TileColor\" content=\"#ffffff\">\n    <meta name=\"msapplication-TileImage\" content=\"/ms-icon-144x144.png\">\n    <meta name=\"theme-color\" content=\"#ffffff\">\n\n    <meta name=\"apple-mobile-web-app-title\" content=\"drawmote\">\n    <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n    <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\">\n    <meta name=\"format-detection\" content=\"telephone=no\">\n    <link href=\"https://fonts.googleapis.com/css?family=Barlow:400,600,700\" rel=\"stylesheet\">\n  </head>\n  <body>\n    <div id=\"drawmote\"></div>\n    <noscript>\n      <div class=\"no-js-overlay\">\n        <h1>Unfortunately, drawmote doesn't work without JavaScript. :(</h1>\n      </div>\n    </noscript>\n\n    <!-- Matomo -->\n    <script type=\"text/javascript\">\n      if (!window.__PRERENDERING) {\n        var _paq = _paq || [];\n        /* tracker methods like \"setCustomDimension\" should be called before \"trackPageView\" */\n        _paq.push(['trackPageView']);\n        _paq.push(['enableLinkTracking']);\n        (function() {\n          var u=\"https://stats.dulnan.net/\";\n          _paq.push(['setTrackerUrl', u+'piwik.php']);\n          _paq.push(['setSiteId', '6']);\n          var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];\n          g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);\n        })();\n      }\n    </script>\n    <!-- End Matomo Code -->\n  </body>\n</html>\n"
  },
  {
    "path": "src/App.vue",
    "content": "<template>\n  <div id=\"drawmote\" class=\"relative\" :class=\"{ 'is-ready': isReady }\">\n    <Mobile v-if=\"isMobile\" />\n    <Desktop v-else />\n    <RestoreConnection />\n    <TheFooter :is-mobile=\"isMobile\" />\n    <ConnectionTimeout :is-mobile=\"isMobile\" />\n    <transition name=\"appear\">\n      <Attribution v-if=\"attributionVisible\" />\n    </transition>\n  </div>\n</template>\n\n<script>\nimport { mapState } from 'vuex'\nimport PeerSox from 'peersox'\n\nimport { BREAKPOINT_REMOTE } from '@/settings'\nimport Desktop from '@/components/Desktop.vue'\nimport Mobile from '@/components/Mobile.vue'\nimport TheFooter from '@/components/Common/Footer/Footer.vue'\nimport RestoreConnection from '@/components/Common/RestoreConnection.vue'\nimport ConnectionTimeout from '@/components/Common/ConnectionTimeout.vue'\nimport Attribution from '@/components/Common/Attribution.vue'\n\nlet peersoxHandlers = []\n\nexport default {\n  name: 'App',\n\n  components: {\n    Desktop,\n    Mobile,\n    TheFooter,\n    RestoreConnection,\n    ConnectionTimeout,\n    Attribution\n  },\n\n  data() {\n    return {\n      isMobile: true,\n      isReady: false\n    }\n  },\n\n  computed: {\n    ...mapState(['attributionVisible'])\n  },\n\n  beforeMount() {\n    this.isMobile = window.innerWidth < BREAKPOINT_REMOTE\n  },\n\n  mounted() {\n    this.$nextTick(() => {\n      if (!window.__PRERENDERING) {\n        peersoxHandlers = new Array(\n          PeerSox.EVENT_SERVER_READY,\n          PeerSox.EVENT_CONNECTION_ESTABLISHED,\n          PeerSox.EVENT_CONNECTION_CLOSED,\n          PeerSox.EVENT_PEER_CONNECTED,\n          PeerSox.EVENT_PEER_TIMEOUT,\n          PeerSox.EVENT_PEER_WEBRTC_CLOSED\n        ).map((event) => {\n          const fn = () => {\n            this.$sentry.logInfo('peersox', event)\n          }\n\n          this.$peersox.on(event, fn)\n\n          return {\n            event,\n            fn\n          }\n        })\n        this.isReady = true\n      }\n\n      document.dispatchEvent(new Event('render-event'))\n      this.$forceUpdate()\n\n      this.$peersox.init().catch((e) => {\n        this.$store.commit('setServerStatus', e)\n      })\n\n      const mode = this.isMobile ? 'mobile' : 'desktop'\n      this.$sentry.setMode(mode)\n    })\n  },\n\n  beforeDestroy() {\n    peersoxHandlers.forEach((handler) => {\n      this.$peersox.off(handler.event, handler.fn)\n    })\n  }\n}\n</script>\n\n<style lang=\"scss\">\n#drawmote {\n  background: $alt-color-darker;\n  opacity: 0;\n  transition: 0.9s;\n  &.is-ready {\n    opacity: 1;\n  }\n  @include media('sm') {\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    z-index: 100;\n    overflow: hidden;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/assets/scss/components/_btn.scss",
    "content": "/*----------------------------------------*\\\n  BUTTON\n\\*----------------------------------------*/\n\n.btn,\n%btn {\n\n  display: inline-block;\n  overflow: hidden;\n  padding: 0.75rem 1rem;\n\n  font-family: $btn-font-family;\n  color: $btn-color;\n\n  text-decoration: none;\n  text-align: center;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  vertical-align: middle;\n  font-weight: 600;\n\n  border: $btn-border;\n  border-radius: $btn-border-radius;\n  background: $btn-bkg;\n  cursor: pointer;\n\n  // Cleaner font rendering\n  // <button> doesn’t inherit\n  -moz-osx-font-smoothing: grayscale;\n  -webkit-font-smoothing: antialiased;\n\n  &:hover,\n  &:focus,\n  &:active {\n    text-decoration: none;\n  }\n\n  &:focus {\n    outline: none;\n  }\n}\n\n\n/**\n * Variants\n */\n\n// Use all the width available\n.btn--block,\n%btn--block {\n  display: block;\n  width: 100%;\n}\n\n// Remove default styling for special buttons\n.btn--bare,\n%btn--bare {\n  padding: 0;\n  border: 0;\n  border-radius: 0;\n}\n\n\n/**\n * Styles\n */\n\n.btn--link {\n  color: $text-color;\n  &:hover {\n    color: $brand-color;\n  }\n}\n\n.btn--default,\n.btn--primary {\n  @include font-values($font-btn);\n}\n\n.btn--default,\n%btn--default {\n  color: $alt-color-dark;\n\n  background-color: white;\n\n  box-shadow: 0 4px 9px rgba($alt-color-light, .24),\n              0 1px 2px rgba($alt-color-light, .71);\n\n\n  &:hover,\n  &:focus {\n    background-color: $btn-default-hover-bkg-color;\n  }\n\n  &:active {\n    background-color: $btn-default-active-bkg-color;\n  }\n}\n\n.btn--primary,\n%btn--primary {\n  color: lighten($btn-primary-bkg-color, 50%);\n\n  background-color: $btn-primary-bkg-color;\n\n  box-shadow: 0 1px 1px rgba(darken($brand-color, 40%), .19),\n              0 3px 7px rgba(darken($brand-color, 70%), .16);\n\n  &:hover {\n    background-color: $btn-primary-hover-bkg-color;\n  }\n\n  &:active, &:focus {\n    background-color: $btn-primary-bkg-color;\n  }\n}\n\n\n/**\n * States\n */\n\n.btn--disabled,\n.btn[disabled],\n%btn--disabled {\n  opacity: .5;\n\n  cursor: not-allowed;\n}\n\n\n/**\n * Sizes\n */\n\n.btn--small,\n%btn--small {\n  padding: $btn-small-padding;\n\n  @include font-values($font-btn-small);\n\n  border: $btn-small-border;\n  border-radius: $btn-small-border-radius;\n}\n\n.btn--large,\n%btn--large {\n  padding: $btn-large-padding;\n\n  font-size: $btn-large-font-size;\n\n  border: $btn-large-border;\n  border-radius: $btn-large-border-radius;\n}"
  },
  {
    "path": "src/assets/scss/components/_check.scss",
    "content": ".check--small {\n  font-size: .8125rem;\n  line-height: 1.230769231;\n}\n\n.check__title {\n  font-weight: 700;\n  position: relative;\n  &:before {\n    content: \"\";\n    display: block;\n    width: 10px;\n    height: 10px;\n    margin-right: 6px;\n    margin-top: 0.25em;\n    border-radius: 100%;\n    float: left;\n    background: $alt-color;\n    .unsupported & {\n      background: $color-red;\n    }\n    .supported & {\n      background: $color-green;\n    }\n    .partial & {\n      background: $color-yellow;\n    }\n  }\n}\n"
  },
  {
    "path": "src/assets/scss/components/_code.scss",
    "content": "$code-size-xs: 4rem;\n$code-size-sm: 3rem;\n$code-size-md: 4rem;\n$code-size-lg: 5rem;\n\n$code-numbers: (\n  0: ($color-yellow, 20%),\n  1: ($color-green, 20%),\n  2: ($color-greendark, 20%),\n  3: ($color-bluelight, 20%),\n  4: ($color-bluedark, 40%),\n  5: ($color-orange, 20%),\n  6: ($color-lavender, 20%),\n  7: ($color-turquoise, 25%),\n  8: ($color-red, 20%),\n  9: ($color-pink, 20%)\n);\n\n.code__content {\n  display: flex;\n}\n\n.code-circle {\n  color: white;\n  width: 1em;\n  height: 1em;\n  text-transform: uppercase;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  overflow: hidden;\n  border-radius: 0.05em;\n\n  line-height: 1;\n  text-align: center;\n  font-weight: 700;\n  // border: 1px solid;\n  position: relative;\n  border-radius: 0.1em;\n  // background: rgba(white, 0.1);\n  background: rgba(black, 0.1);\n  // box-shadow: 0 3px 4px rgba($alt-color, 0.1),\n  //             0 0px 2px rgba($alt-color, 0.3);\n\n  &:before {\n    content: \"\";\n    position: absolute;\n    bottom: 0.05em;\n    left: 0.05em;\n    right: 0.05em;\n    height: 0.1em;\n    border-radius: 1em;\n    transform-origin: top left;\n    // width: 0.15em;\n    background: $alt-color-light;\n  }\n  \n  &.contains {\n    // border: 1px solid rgba(white, 0.3);\n  }\n  &.contains.invalid {\n    color: $alt-color-light !important;\n    border-color: $color-red;\n  }\n\n  @each $index, $color in $code-numbers {\n    &.code-circle--#{$index} {\n      color: lighten(nth($color, 1), nth($color, 2));\n      background: rgba(nth($color, 1), 0.1);\n      &:before {\n        background: nth($color, 1);\n      }\n    }\n  }\n  span {\n    font-size: 0.50em;\n    display: inline-block;\n    margin-top: -0.2em;\n  }\n}\n\n.code {\n  font-size: $code-size-xs;\n  @include media('sm') {\n    font-size: $code-size-sm;\n  }\n  @include media('md') {\n    font-size: $code-size-md;\n  }\n  @include media('lg') {\n    font-size: $code-size-lg;\n  }\n}\n\n.code__item {\n  margin-right: 0.2em;\n\n  @include media('sm') {\n    margin-right: $code-margin;\n  }\n\n  &:last-child {\n    margin-right: 0;\n  }\n}\n"
  },
  {
    "path": "src/assets/scss/components/_icon.scss",
    "content": ".icon {\n  width: 1em;\n  height: 1em;\n  fill: currentColor;\n  vertical-align: -0.2em;\n}\n\n.icon--large {\n  width: 1.2em;\n  height: 1.2em;\n}\n"
  },
  {
    "path": "src/assets/scss/components/_no-js-overlay.scss",
    "content": "/*----------------------------------------*\\\n  NO JS OVERLAY\n\\*----------------------------------------*/\n\n.no-js-overlay {\n  position: fixed;\n  z-index: 1000;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  background: rgba(white, 0.95);\n  font-weight: bold;\n  h1 {\n    max-width: 50rem;\n  }\n}"
  },
  {
    "path": "src/assets/scss/components/_range.scss",
    "content": "$track-color: $alt-color-darkest;\n$thumb-color: $alt-color;\n\n$thumb-radius: 4px;\n$thumb-height: 12px;\n$thumb-width: 12px;\n$thumb-shadow-size: 0px;\n$thumb-shadow-blur: 0px;\n$thumb-shadow-color: rgba(0, 0, 0, 0.2);\n$thumb-border-width: 0px;\n$thumb-border-color: transparent;\n\n$track-width: 100%;\n$track-height: 4px;\n$track-shadow-size: 0px;\n$track-shadow-blur: 0px;\n$track-shadow-color: rgba(0, 0, 0, 0.2);\n$track-border-width: 0px;\n$track-border-color: $alt-color-light;\n\n$track-radius: 0px;\n$contrast: 0%;\n\n$ie-bottom-track-color: darken($track-color, $contrast);\n\n@import 'input-range-scss/_inputrange';\n"
  },
  {
    "path": "src/assets/scss/defaults/_typography.scss",
    "content": "/*----------------------------------------*\\\n  TYPOGRAPHY SCAFFOLDING\n\\*----------------------------------------*/\n\nhtml {\n  font-family: $font-family-default;\n  // Use percentage value for root font-size\n  // see https://github.com/liip/kanbasu/issues/29\n  font-size: 100%;\n  line-height: 1.5;\n  color: $text-color;\n\n  font-weight: 400;\n\n  background: $alt-color-darker;\n\n  // Cleaner font rendering\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\nbody {\n  // overflow: hidden;\n}\n\n/**\n * Links\n */\n\na {\n  color: $text-color;\n  text-decoration: none;\n\n  &:hover,\n  &:focus {\n    color: $brand-color;\n    text-decoration: none;\n  }\n}\n\n.link {\n  border-bottom: 1px solid rgba($alt-color-lighter, 0.6);\n}\n\n/**\n * Headings\n */\n\n@include headings {\n  margin: 0;\n  font-weight: inherit;\n  line-height: 1.2;\n}\n\nh1,\n.h1 {\n  margin-top: 0;\n  @include font-values($font-h1);\n}\n\nh2,\n.h2 {\n  @include font-values($font-h2);\n}\n\nh3,\n.h3 {\n  @include font-values($font-h3);\n}\n\nh4,\n.h4 {\n  @include font-values($font-h4);\n}\n\nh5,\n.h5 {\n  @include font-values($font-h5);\n}\n\nh6,\n.h6 {\n  @include font-values($font-h6);\n}\n\n\n/**\n * Paragraphs\n */\n\np {\n  margin-top: 1em;\n  margin-bottom: 1.3em;\n}\n\n\n/**\n * Preformatted text\n */\n\npre,\ncode {\n  font-family: Monaco, monospace;\n  font-weight: 300;\n  tab-size: 4;\n\n  background-color: #f5f5f5;\n}\n\npre {\n  padding: $spacing-unit-default;\n  margin: 0 0 2em;\n  overflow: auto;\n\n  font-size: rem(14px);\n\n  border-radius: $border-radius-default;\n}\n\ncode {\n  display: inline-block;\n  padding: 1px 5px;\n\n  pre & {\n    display: block;\n    padding: 0;\n  }\n}\n\n\n/**\n * Lists\n */\n\nul,\nol {\n  #{$padding-left}: $spacing-unit-default;\n  margin: 1em 0;\n\n  ul,\n  ol {\n    margin: 0;\n  }\n}\n\ndl {\n  @include clearfix;\n}\n\n  dt {\n    font-weight: 500;\n  }\n\n  dd {\n    #{$margin-left}: 0;\n    margin-bottom: .5em;\n  }\n\n  .dl--inline {\n    dt {\n      float: flip(left, right);\n      width: 100px;\n    }\n\n    dd {\n      @include clearfix;\n      #{$margin-left}: calc(100px + #{$spacing-unit-small});\n    }\n  }\n\n\n/**\n * Miscellaenous\n */\n\nabbr {\n  cursor: help;\n}"
  },
  {
    "path": "src/assets/scss/helpers/_flex.scss",
    "content": ".flex {\n  display: flex;\n}\n\n.flex--column {\n  flex-direction: column;\n}\n\n.flex--align-stretch {\n  align-items: stretch !important;\n}\n\n.flex-1 {\n  flex: 1;\n}\n\n.flex--center {\n  align-items: center;\n  justify-content: center;\n}\n\n.mrgla {\n  margin-left: auto;\n}\n\n.w-100 {\n  width: 100% !important;\n}"
  },
  {
    "path": "src/assets/scss/helpers/_helpers.scss",
    "content": ".block {\n  display: block;\n}\n\n.h-100 {\n  height: 100%;\n}\n\n.arrow-after {\n  &:after {\n    content: \"\";\n    width: 0;\n    height: 0;\n    border-style: solid;\n    border-width: 6px 4px 0 4px;\n    border-color: currentColor transparent transparent transparent;\n    display: inline-block;\n    margin-left: 0.5em;\n    vertical-align: middle;\n  }\n}\n\n.relative {\n  position: relative;\n}\n\n.fixed {\n  position: fixed;\n  top: 0;\n  left: 0;\n}\n\n.absolute {\n  position: absolute;\n  top: 0;\n  left: 0;\n}\n\n.overlay {\n  width: 100%;\n  height: 100%;\n}"
  },
  {
    "path": "src/assets/scss/helpers/_typography.scss",
    "content": ".text-brand {\n  color: $brand-color;\n}\n\n.text-light {\n  font-weight: 400 !important;\n}\n\n.text-bold {\n  font-weight: 700 !important;\n}\n\n.text-heavy {\n  font-weight: 700;\n}\n\n.text-hyphens {\n  hyphens: auto;\n}\n\n.text-white {\n  color: $text-color;\n}\n\n.label {\n  font-weight: 700;\n  font-size: rem(12px);\n  text-transform: uppercase;\n  letter-spacing: 0.06em;\n  .is-drawing & {\n    @include media('sm') {\n      font-size: rem(12px);\n    }\n    @include media('md') {\n      font-size: rem(14px);\n    }\n    @include media('lg') {\n      font-size: rem(14px);\n    }\n  }\n}\n\n.mobile-font-size {\n  font-size: calc((100vw - 4rem) / 7);\n}\n"
  },
  {
    "path": "src/assets/scss/main.scss",
    "content": "/*!\n *  カンバス KANBASU\n *  Distributed under the MIT License\n *  Copyright (c) 2015 Liip AG\n */\n\n/**\n * Settings\n */\n\n@import '../../../node_modules/kanbasu/src/scss/settings/settings';\n@import 'settings/settings';\n\n\n/**\n * Tools\n */\n\n@import '../../../node_modules/kanbasu/src/scss/tools/functions';\n@import '../../../node_modules/kanbasu/src/scss/tools/mixins';\n@import '../../../node_modules/kanbasu/src/scss/tools/rtl';\n\n\n/**\n * Vendors\n */\n\n@import '../../../node_modules/kanbasu/src/scss/vendor/normalize';\n\n\n/**\n * Defaults\n */\n\n@import '../../../node_modules/kanbasu/src/scss/defaults/box-model';\n@import '../../../node_modules/kanbasu/src/scss/defaults/elements';\n@import 'defaults/typography';\n// @import '../../../node_modules/kanbasu/src/scss/defaults/table';\n@import '../../../node_modules/kanbasu/src/scss/defaults/forms';\n\n\n/**\n * Helpers\n */\n\n@import '../../../node_modules/kanbasu/src/scss/helpers/text';\n// @import '../../../node_modules/kanbasu/src/scss/helpers/text-responsive';\n// @import '../../../node_modules/kanbasu/src/scss/helpers/float';\n@import '../../../node_modules/kanbasu/src/scss/helpers/spacings';\n@import '../../../node_modules/kanbasu/src/scss/helpers/spacings-responsive';\n// @import '../../../node_modules/kanbasu/src/scss/helpers/images';\n@import '../../../node_modules/kanbasu/src/scss/helpers/positionning';\n@import '../../../node_modules/kanbasu/src/scss/helpers/display';\n// @import '../../../node_modules/kanbasu/src/scss/helpers/align';\n// @import '../../../node_modules/kanbasu/src/scss/helpers/align-responsive';\n@import 'helpers/typography';\n@import 'helpers/helpers';\n@import 'helpers/flex';\n\n\n/**\n * Components\n */\n\n// @import '../../../node_modules/kanbasu/src/scss/components/grid';\n// @import '../../../node_modules/kanbasu/src/scss/components/widths';\n// @import '../../../node_modules/kanbasu/src/scss/components/widths-responsive';\n@import 'components/btn';\n// @import '../../../node_modules/kanbasu/src/scss/components/box';\n// @import '../../../node_modules/kanbasu/src/scss/components/media';\n// @import '../../../node_modules/kanbasu/src/scss/components/media-responsive';\n@import '../../../node_modules/kanbasu/src/scss/components/list';\n@import '../../../node_modules/kanbasu/src/scss/components/list-inline';\n// @import '../../../node_modules/kanbasu/src/scss/components/list-stacked';\n// @import '../../../node_modules/kanbasu/src/scss/components/embed-responsive';\n// @import '../../../node_modules/kanbasu/src/scss/components/container';\n// @import '../../../node_modules/kanbasu/src/scss/components/pusher';\n// @import '../../../node_modules/kanbasu/src/scss/components/table-responsive';\n@import 'components/_range';\n@import 'components/_icon';\n@import 'components/_no-js-overlay';\n"
  },
  {
    "path": "src/assets/scss/settings/_settings.scss",
    "content": "@import 'typography';\n\n$color-red:                   #F06D31;\n$color-yellow:                #ffd52b;\n$color-orange:                #f39a2d;\n\n$color-pink:                  #db50b4;\n$color-lavender:              #7059d3;\n\n$color-bluelight:             #48bec5;\n$color-bluedark:              #2b67c2;\n\n$color-green:                 #97d779;\n$color-greendark:             #5da83a;\n\n$color-turquoise:             #478bb3;\n\n\n$code-margin:                 0.2em;\n$code-radius:                 $code-margin;\n\n$index-canvas-main:           100;\n$index-canvas-temp:           110;\n$index-canvas-interface:      710;\n\n$index-brush:                 300;\n$index-color-picker:          400;\n$index-header:                460;\n$index-overlay:               470;\n$index-toolbar:               500;\n$index-brush-toolbar:         500;\n\n$index-mobile-pairing:        700;\n\n$index-background-animation:  800;\n$index-footer:                1200;\n$index-drawing:               1100;\n$index-pairing:               700;\n\n$index-modal:                1000;\n\n$toolbar-height:              5rem;\n\n$footer-height-xs:            54px;\n\n$toolbar-button-width-xs:     1.75rem;\n$toolbar-button-width-sm:     1.75rem;\n$toolbar-button-width-md:     2.25rem;\n$toolbar-button-width-lg:     4rem;\n\n$shadow-s:                    0 3px 10px rgba($alt-color, 0.13),\n                              0 2px 4px 1px rgba($alt-color, 0.1);\n\n/*----------------------------------------*\\\n  GLOBAL SETTINGS\n\\*----------------------------------------*/\n\n// Use this setting to prefix all the components classes\n$namespace:                         '';\n\n// Flip all left/right properties for right-to-left languages\n$rtl:                               false;\n\n\n/**\n * Colors\n */\n\n$brand-color:                       #fb6131;\n$brand-color-darker:                darken($brand-color, 10%);\n\n$alt-color-lightest:                #dfdde0;\n$alt-color-lighter:                 #a7a0a8;\n$alt-color-light:                   #867c88;\n$alt-color:                         #554757;\n$alt-color-dark:                    #39293c;\n$alt-color-darker:                  #2a192d;\n$alt-color-darkest:                 #201123;\n\n\n$color-translucent-dark:      rgba($alt-color-darkest, 0.9);\n\n/**\n * Typography\n */\n\n$font-family-default:               \"Barlow\", 'Heebo', -apple-system, BlinkMacSystemFont,\n                                    'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',\n                                    'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n                                    sans-serif;\n\n$font-size-default:                 16px;\n$font-size-small:                   .875rem;\n$font-size-large:                   1.5rem;\n\n$line-height-default:               1.45;\n\n$text-color:                        $alt-color-lightest;\n$link-color:                        $brand-color;\n$muted-color:                       $alt-color-lighter;\n\n\n/**\n * Spacings\n */\n\n$ratio:                             1.61803398875;\n\n$spacing-unit-tiny:                 4px;\n$spacing-unit-small:                8px;\n$spacing-unit-default:              16px;\n$spacing-unit-large:                24px;\n$spacing-unit-huge:                 48px;\n\n$spacings:                          (\n                                      'tight' 0,\n                                      'tiny' $spacing-unit-tiny,\n                                      'small' $spacing-unit-small,\n                                      'large' $spacing-unit-large,\n                                      'huge' $spacing-unit-huge\n                                    );\n\n\n/**\n * Responsiveness\n */\n\n// Use EM media-queries for better browser consistency\n// See http://zellwk.com/blog/media-query-units\n$screen-xs-max:                      699px / 16px * 1em;\n$screen-sm-min:                      700px / 16px * 1em;\n$screen-sm-max:                     1024px / 16px * 1em;\n$screen-md-min:                     1025px / 16px * 1em;\n$screen-md-max:                     1440px / 16px * 1em;\n$screen-lg-min:                     1441px / 16px * 1em;\n$screen-lg-max:                     1679px / 16px * 1em;\n$screen-xl-min:                     1680px / 16px * 1em;\n\n$breakpoints-default:               (\n                                      'sm' '(min-width: #{$screen-sm-min})',\n                                      'md' '(min-width: #{$screen-md-min})',\n                                      'lg' '(min-width: #{$screen-lg-min})'\n                                    );\n$breakpoints-desc:                  (\n                                      'xs' '(max-width: #{$screen-xs-max})',\n                                      'sm' '(max-width: #{$screen-sm-max})',\n                                      'md' '(max-width: #{$screen-md-max})'\n                                    );\n$breakpoints-extra:               (\n                                      'xl' '(min-width: #{$screen-xl-min})',\n                                    );\n\n/**\n * Miscellaneous\n */\n\n$border-radius-default:             4px;\n$border-radius-small:               2px;\n$border-radius-large:               6px;\n\n\n/*----------------------------------------*\\\n  COMPONENTS\n\\*----------------------------------------*/\n\n/**\n * Buttons\n */\n\n$btn-padding:                       1rem;\n$btn-color:                         inherit;\n$btn-font-size:                     inherit;\n$btn-font-family:                   inherit;\n$btn-border:                        0;\n$btn-bkg:                           transparent;\n$btn-border-radius:                 $border-radius-default;\n\n$btn-small-padding:                 $spacing-unit-tiny/$ratio $spacing-unit-tiny;\n$btn-small-font-size:               $font-size-small;\n$btn-small-border:                  $btn-border;\n$btn-small-border-radius:           $border-radius-small;\n\n$btn-large-padding:                 $spacing-unit-default/$ratio $spacing-unit-default;\n$btn-large-font-size:               $font-size-large;\n$btn-large-border:                  $btn-border;\n$btn-large-border-radius:           $border-radius-large;\n\n$btn-default-color:                 $text-color;\n$btn-default-bkg-color:             $alt-color-lighter;\n$btn-default-hover-bkg-color:       lighten($btn-default-bkg-color, 3%);\n$btn-default-active-bkg-color:      darken($btn-default-bkg-color, 5%);\n\n$btn-primary-color:                 white;\n$btn-primary-bkg-color:             $brand-color;\n$btn-primary-hover-bkg-color:       lighten($btn-primary-bkg-color, 8%);\n$btn-primary-active-bkg-color:      darken($btn-primary-bkg-color, 5%);\n\n\n/**\n * Forms\n */\n\n$field-padding:                     $spacing-unit-small/$ratio $spacing-unit-small;\n\n$field-color:                       inherit;\n$field-font-size:                   inherit;\n$field-font-family:                 inherit;\n$field-line-height:                 $line-height-default;\n\n$field-bkg-color:                   white;\n$field-border:                      1px solid $alt-color-light;\n$field-focus-border-color:          $alt-color-darker;\n$field-border-radius:               $border-radius-default;\n\n$field-disabled-bkg-color:          $alt-color-lighter;\n$field-disabled-color:              $alt-color;\n\n$field-small-padding:               $spacing-unit-tiny/$ratio $spacing-unit-tiny;\n$field-small-font-size:             $font-size-small;\n$field-small-border:                $field-border;\n$field-small-border-radius:         $border-radius-small;\n\n$field-large-padding:               $spacing-unit-default/$ratio $spacing-unit-default;\n$field-large-font-size:             $font-size-large;\n$field-large-border:                $field-border;\n$field-large-border-radius:         $border-radius-large;\n\n$field-help-color:                  $alt-color-light;\n\n$form-group-spacing:                $spacing-unit-small;\n\n/**\n * Lists\n */\n\n$list-separator-style:              1px solid $alt-color-dark;\n\n\n/**\n * Box\n */\n\n$box-default-color:                 inherit;\n$box-default-bkg-color:             $alt-color-lighter;\n\n$box-primary-color:                 white;\n$box-primary-bkg-color:             $brand-color;\n\n\n/**\n * Widths\n */\n\n$widths-columns:                    6,5,4,3,2,1;\n$widths-breakpoints:                $breakpoints-default;\n\n\n/**\n * Media responsive\n */\n\n$media-collapse:                    $screen-sm-max;\n\n\n/**\n * Container\n */\n\n$container-gutter-width:            $spacing-unit-small;\n$container-max-width:               1200px;\n\n\n/**\n * Table responsive\n */\n\n$table-responsive-collapse:         $screen-sm-max;\n"
  },
  {
    "path": "src/assets/scss/settings/_typography.scss",
    "content": "$rhythm-spacing-base: 16;\n\n@mixin font-values($font, $force: null) {\n  @if ($force == force) {\n    $force: !important;\n  }\n\n  @each $breakpoint, $values in $font {\n    $fontsize: #{map-get($values, size) / $rhythm-spacing-base}rem $force;\n    $lineheight: #{map-get($values, leading)}rem $force;\n\n    @if ($breakpoint == xs) {\n      font-size: $fontsize;\n      line-height: $lineheight;\n    } @else {\n      @include media($breakpoint) {\n        font-size: $fontsize;\n        line-height: $lineheight;\n      }\n    }\n  }\n}\n\n$rhythm-size-xxxl: (\n  size: 50,\n  leading: 4\n);\n\n$rhythm-size-xxl: (\n  size: 42,\n  leading: 3\n);\n\n$rhythm-size-xl: (\n  size: 36,\n  leading: 2\n);\n\n$rhythm-size-l: (\n  size: 30,\n  leading: 2\n);\n\n$rhythm-size-m: (\n  size: 23,\n  leading: 2\n);\n\n$rhythm-size-s: (\n  size: 21,\n  leading: 1.75\n);\n\n$rhythm-size-xs: (\n  size: 18,\n  leading: 1.5\n);\n\n$rhythm-size-xxs: (\n  size: 15,\n  leading: 1.25\n);\n\n$rhythm-size-xxxs: (\n  size: 13,\n  leading: 1\n);\n\n\n$font-h1: (\n  xs: $rhythm-size-m,\n  sm: $rhythm-size-l,\n  md: $rhythm-size-xl,\n  lg: $rhythm-size-xxxl\n);\n\n$font-h2: (\n  xs: $rhythm-size-xs,\n  sm: $rhythm-size-xxs,\n  md: $rhythm-size-s,\n  lg: $rhythm-size-m,\n);\n\n$font-h3: (\n  xs: $rhythm-size-xs,\n  lg: $rhythm-size-s\n);\n\n$font-h4: (\n  xs: $rhythm-size-xxs,\n  sm: $rhythm-size-xs\n);\n\n$font-h5: (\n  xs: $rhythm-size-xs,\n);\n\n$font-h6: (\n  xs: $rhythm-size-xs,\n);\n\n$font-default: (\n  xs: $rhythm-size-xs,\n);\n\n$font-btn: (\n  xs: $rhythm-size-xs,\n  md: $rhythm-size-s,\n  lg: $rhythm-size-s\n);\n\n$font-btn-small: (\n  xs: $rhythm-size-xxs,\n  md: $rhythm-size-xs,\n);"
  },
  {
    "path": "src/assets/scss/vue_include.scss",
    "content": "@import '../../../node_modules/kanbasu/src/scss/settings/settings';\n@import 'settings/settings';\n@import '../../../node_modules/kanbasu/src/scss/tools/functions';\n@import '../../../node_modules/kanbasu/src/scss/tools/mixins';\n@import '../../../node_modules/kanbasu/src/scss/tools/rtl';\n"
  },
  {
    "path": "src/classes/Brush.js",
    "content": "import {\n  DEFAULT_COLOR,\n  RADIUS_DEFAULT,\n  HARDNESS_DEFAULT,\n  OPACITY_DEFAULT\n} from '@/settings'\n\nimport Color from '@/classes/Color'\n\nexport default class Brush {\n  constructor({\n    color = DEFAULT_COLOR,\n    radius = RADIUS_DEFAULT,\n    hardness = HARDNESS_DEFAULT,\n    opacity = OPACITY_DEFAULT\n  } = {}) {\n    this.color = new Color(color)\n    this.radius = radius\n    this.hardness = hardness\n    this.opacity = opacity\n    this.style = 'smudge'\n\n    this.useFilter = false\n  }\n\n  /**\n   * Return the current brush state. This can be used to instanciate a\n   * new Brush later.\n   *\n   * @returns {Object} The brush properties.\n   */\n  get state() {\n    return {\n      color: this.color,\n      radius: this.radius,\n      hardness: this.hardness,\n      opacity: this.opacity,\n      style: this.style\n    }\n  }\n\n  /**\n   * Calculate and return the brush radius based on the amount\n   * of bluriness is used.\n   *\n   * @returns {Number} The radius in pixels.\n   */\n  get canvasRadius() {\n    if (!this.useFilter) {\n      return this.radius\n    }\n    return (this.hardness / 100 + 1) * this.radius\n  }\n\n  /**\n   * Calculate and return the blur amount for canvas filters.\n   *\n   * @returns {Number} The blur amount in pixels.\n   */\n  get canvasBlur() {\n    return (1 - this.hardness / 100) * this.radius\n  }\n\n  /**\n   * Return the brush color as a string to be used for canvas values.\n   *\n   * @returns {String} The color as a rgba string.\n   */\n  get canvasColor() {\n    return this.color.getRgbaString(this.opacity)\n  }\n\n  /**\n   * Returns the canvas properties required to draw with this brush.\n   *\n   * @returns {Object}\n   */\n  get canvasProperties() {\n    const properties = {\n      lineJoin: 'round',\n      lineCap: 'round',\n      lineWidth: this.canvasRadius * 2,\n      globalAlpha: 1,\n      strokeStyle: this.canvasColor,\n      fillStyle: this.canvasColor\n    }\n\n    if (this.useFilter) {\n      properties.filter = `blur(${this.canvasBlur}px)`\n    }\n\n    return properties\n  }\n\n  /**\n   * Sets the brush color.\n   *\n   * @param {Color} color The new color.\n   */\n  setColor(color) {\n    this.color = color\n  }\n\n  /**\n   * Sets the brush radius.\n   *\n   * @param {Number} radius The new radius.\n   */\n  setRadius(radius) {\n    this.radius = radius\n  }\n\n  /**\n   * Sets the brush hardness.\n   *\n   * @param {Number} hardness The new hardness.\n   */\n  setHardness(hardness) {\n    this.hardness = hardness\n  }\n\n  /**\n   * Sets the brush opacity.\n   *\n   * @param {Number} opacity The new opacity.\n   */\n  setOpacity(opacity) {\n    this.opacity = opacity\n  }\n\n  /**\n   * Sets the brush style.\n   *\n   * @param {String} style The new style.\n   */\n  setStyle(style) {\n    this.style = style\n  }\n\n  /**\n   * Sets the flag to use canvas filters or not.\n   *\n   * @param {Boolean} isSupported Whether canvas filters are supported.\n   */\n  setFilterSupport(isSupported) {\n    this.useFilter = isSupported\n  }\n}\n"
  },
  {
    "path": "src/classes/Canvas/Action.js",
    "content": "import { clearCanvas } from '@/tools/canvas'\n\n/**\n * A canvas action.\n */\nexport default class Action {\n  constructor(type) {\n    this.type = type\n  }\n\n  /**\n   * Run the action on a canvas.\n   *\n   * @param {HTMLCanvasElement} canvas\n   * @param {Object} size\n   */\n  do(canvas, size) {\n    switch (this.type) {\n      case 'erase':\n        clearCanvas(canvas, size)\n        break\n    }\n  }\n}\n"
  },
  {
    "path": "src/classes/Canvas/DrawAction.js",
    "content": "import Action from './Action'\nimport { midPointBetween } from '@/tools/helpers.js'\n\n/**\n * A canvas drawing action.\n */\nexport default class DrawAction extends Action {\n  constructor(canvasProperties) {\n    super('stroke')\n    this.canvasProperties = canvasProperties\n    this.points = []\n  }\n\n  /**\n   * Set the properties on the canvas.\n   *\n   * @param {Object} props An object with canvas properties and the desired values.\n   */\n  setCanvasProperties(canvas) {\n    const context = canvas.getContext('2d')\n\n    Object.keys(this.canvasProperties).forEach((p) => {\n      context[p] = this.canvasProperties[p]\n    })\n  }\n\n  /**\n   * Draws the action to the canvas.\n   *\n   * @param {HTMLCanvasElement} canvas The canvas to draw on to.\n   */\n  do(canvas) {\n    // First prepare the canvas with the properties from the action.\n    this.setCanvasProperties(canvas)\n\n    const c = canvas.getContext('2d')\n    let p1 = this.points[0]\n    let p2 = this.points[1]\n\n    c.beginPath()\n    c.moveTo(p1.x, p1.y)\n\n    for (let i = 1; i < this.points.length; i++) {\n      // we pick the point between pi+1 & pi+2 as the\n      // end point and p1 as our control point\n      const midPoint = midPointBetween(p1, p2)\n      c.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y)\n      p1 = this.points[i]\n      p2 = this.points[i + 1]\n    }\n    // Draw last line as a straight line while\n    // we wait for the next point to be able to calculate\n    // the bezier control point\n    c.lineTo(p1.x, p1.y)\n    c.stroke()\n  }\n}\n"
  },
  {
    "path": "src/classes/Canvas/index.js",
    "content": "import Action from './Action'\nimport DrawAction from './DrawAction'\nimport { clearCanvas } from '@/tools/canvas'\n\nexport default class {\n  /**\n   * Handle drawing using { x, y } coordinates. Supports various brush\n   * settings and offers undo and redo functionality.\n   *\n   * @param {HTMLCanvasElement} canvasMain The canvas where the drawing is.\n   * @param {HTMLCanvasElement} canvasTemp Temporary canvas for the current stroke.\n   */\n  constructor(canvasMain, canvasTemp) {\n    this.actions = []\n\n    this._canvasMain = canvasMain\n    this._canvasTemp = canvasTemp\n\n    this._size = {\n      width: 0,\n      height: 0\n    }\n\n    this.currentAction = null\n    this._historyIndex = 0\n    this.lastActionType = ''\n\n    this.init()\n  }\n\n  /**\n   * Initialize the canvases.\n   */\n  init() {\n    this.setSizes({ width: window.innerWidth, height: window.innerHeight })\n  }\n\n  /**\n   * Returns the current state about what actions are available.\n   * Used to show or hide buttons that interact with CanvasState.\n   *\n   * @returns {Object} Current state.\n   */\n  get state() {\n    const undoPossible = this.actions.length > 0 && this._historyIndex > 0\n    const redoPossible =\n      this.actions.length > 0 && this._historyIndex < this.actions.length\n    const clearPossible =\n      this.lastActionType !== 'erase' || !this.lastActionType\n\n    return { undoPossible, redoPossible, clearPossible }\n  }\n\n  /**\n   * Set the sizes of the canvas.\n   *\n   * @param {Object} size\n   * @param {Number} size.width\n   * @param {Number} size.height\n   */\n  setSizes({ width, height }) {\n    this._size.width = width\n    this._size.height = height\n  }\n\n  /**\n   * Update the sizes of the canvas and redraw the current state.\n   *\n   * @param {Object} viewport\n   */\n  updateSizes(viewport) {\n    this.setSizes(viewport)\n    this.redraw()\n  }\n\n  /**\n   * Start a new drawing action.\n   *\n   * @param {Object} canvasProperties\n   */\n  start(canvasProperties) {\n    this.currentAction = new DrawAction(canvasProperties)\n  }\n\n  /**\n   * If an action is being pushed while the undo functionality has been used,\n   * remove all actions after that. This basically removes the ability to use\n   * redo at this point, until another undo step is done.\n   */\n  updateHistoryState() {\n    if (this._historyIndex !== this.actions.length) {\n      this.actions.splice(this._historyIndex)\n    }\n  }\n\n  /**\n   * Push an action to the history stack und update some variables related to\n   * that. Also update the history index.\n   *\n   * @param {DrawAction} action The action to add to the history.\n   */\n  pushAction(action) {\n    this.updateHistoryState()\n    this.actions.push(action)\n    this.lastActionType = action.type\n    this._historyIndex = this.actions.length\n  }\n\n  /**\n   * Handles the release of the mouse or touchend.\n   */\n  release() {\n    this.copy(this._canvasTemp, this._canvasMain)\n    clearCanvas(this._canvasTemp, this._size)\n\n    this.pushAction(this.currentAction)\n  }\n\n  /**\n   * Push the point to the current action points array and then draw the current\n   * action to the temporary canvas.\n   *\n   * @param {Point} point The x and y coordinates of the next point.\n   */\n  move(point) {\n    this.currentAction.points.push(point)\n\n    clearCanvas(this._canvasTemp, this._size)\n\n    this.currentAction.do(this._canvasTemp)\n  }\n\n  /**\n   * Copy the contents of the source canvas to the target canvas.\n   *\n   * @param {HTMLCanvasElement} source The canvas to copy from.\n   * @param {HTMLCanvasElement} target The canvas to copy the source to.\n   */\n  copy(source, target) {\n    target\n      .getContext('2d')\n      .drawImage(source, 0, 0, this._size.width, this._size.height)\n  }\n\n  /**\n   * Undo the last action.\n   */\n  undo() {\n    clearCanvas(this._canvasMain, this._size)\n    this._historyIndex = Math.max(this._historyIndex - 1, 0)\n\n    if (this._historyIndex >= 0) {\n      this.drawActions()\n    }\n  }\n\n  /**\n   * Redo the previous action.\n   */\n  redo() {\n    this._historyIndex = Math.min(this._historyIndex + 1, this.actions.length)\n\n    if (this._historyIndex <= this.actions.length) {\n      clearCanvas(this._canvasMain, this._size)\n      this.drawActions()\n    }\n  }\n\n  /**\n   * Clear the main canvas and draw all current actions.\n   */\n  redraw() {\n    clearCanvas(this._canvasMain, this._size)\n    this.drawActions()\n  }\n\n  /**\n   * Draw all current actions to the main canvas.\n   */\n  drawActions() {\n    // First, figure out the range of actions to draw.\n    // This loop will find the last occurence of an erase action.\n    let startIndex = 0\n    for (let i = 0; i < this._historyIndex; i++) {\n      const action = this.actions[i]\n      if (action.type === 'erase') {\n        startIndex = i\n      }\n    }\n\n    // Draw all actions from the last erase up to the most recent action.\n    for (let i = startIndex; i < this._historyIndex; i++) {\n      const action = this.actions[i]\n      action.do(this._canvasMain, this._size)\n      this.lastActionType = action.type\n    }\n  }\n\n  /**\n   * Erase the contents of the main canvas and push the action for it.\n   */\n  erase() {\n    if (this.lastActionType === 'erase') {\n      return\n    }\n\n    this.pushAction(new Action('erase'))\n    clearCanvas(this._canvasMain, this._size)\n  }\n}\n"
  },
  {
    "path": "src/classes/Color.js",
    "content": "import { getRgbaString, hexToRgb } from '@/tools/helpers.js'\n\n/**\n * Manages a color.\n */\nexport default class Color {\n  constructor({ name, rgb = [0, 0, 0], hex } = {}) {\n    this.name = name\n    this.rgb = hex ? hexToRgb(hex) : rgb\n  }\n\n  /**\n   * Set the color.\n   *\n   * @param {Array} rgb The rgb values as an array.\n   */\n  setColor(rgb) {\n    this.rgb = rgb\n  }\n\n  /**\n   * Build an rgba string given the alpha value.\n   *\n   * @param {Number} alpha The alpha value to be used (0 - 100).\n   */\n  getRgbaString(alpha) {\n    return getRgbaString(this.rgb, alpha / 100)\n  }\n}\n"
  },
  {
    "path": "src/classes/Rectangle.js",
    "content": "import { Point } from 'lazy-brush'\n\nexport default class Rectangle {\n  constructor(x = 0, y = 0, width = 0, height = 0) {\n    this.p1 = new Point(x, y)\n    this.p2 = new Point(x + width, y + height)\n  }\n\n  /**\n   * @returns {Number} The width of the rectangle.\n   */\n  get width() {\n    return this.p2.x - this.p1.x\n  }\n\n  /**\n   * @returns {Number} The height of the rectangle.\n   */\n  get height() {\n    return this.p2.y - this.p1.y\n  }\n\n  /**\n   * Set the rectangles points based on the values from a DOMRect.\n   *\n   * @param {DOMRect} domRect\n   */\n  setFromDOMRect(domRect) {\n    this.p1.x = domRect.left\n    this.p1.y = domRect.top\n    this.p2.x = domRect.left + domRect.width\n    this.p2.y = domRect.top + domRect.height\n  }\n\n  /**\n   * Set the rectangles points based on the width, height and distance to parent.\n   *\n   * @param {HTMLElement} element\n   */\n  setFromElement(element) {\n    this.p1.x = Number(element.offsetLeft)\n    this.p1.y = Number(element.offsetTop)\n    this.p2.x = Number(element.offsetLeft + element.offsetWidth)\n    this.p2.y = Number(element.offsetTop + element.offsetHeight)\n  }\n\n  /**\n   * Check if the given point is within this rectangle.\n   *\n   * @param {Point} point An object containing x and y properties.\n   * @returns {Boolean}\n   */\n  containsPoint(point) {\n    return (\n      this.p1.x <= point.x &&\n      point.x <= this.p2.x &&\n      this.p1.y <= point.y &&\n      point.y <= this.p2.y\n    )\n  }\n}\n"
  },
  {
    "path": "src/classes/Smoothing.js",
    "content": "/**\n * Slowly ease a value to a new value.\n */\nexport default class Smoothing {\n  constructor() {\n    this.prev = 0\n    this.prevRaw = 0\n    this.smoothing = 0.08\n  }\n\n  /**\n   * Get the next eased value.\n   *\n   * @param {Number} input The input value\n   * @param {Boolean} force If true, the value will be updated immediately.\n   */\n  next(input, force) {\n    if (Math.abs(input - this.prev) > 0.1 || force) {\n      const value = this.prev + this.smoothing * (input - this.prev)\n      this.prev = value\n\n      return value\n    } else {\n      return this.prev\n    }\n  }\n}\n"
  },
  {
    "path": "src/components/Common/Animation/Animation.vue",
    "content": "<template>\n  <div\n    class=\"animation\"\n    :class=\"{\n      'is-desktop': isDesktop,\n      'is-mobile': !isDesktop,\n      'is-fallback': useFallback\n    }\"\n  >\n    <div ref=\"container\" class=\"three-animation\"></div>\n    <slot></slot>\n    <div v-if=\"debug\" class=\"ratio\">{{ ratio }}</div>\n    <div v-if=\"debug\" class=\"debug-range\">\n      <input\n        type=\"range\"\n        min=\"0\"\n        max=\"100\"\n        step=\"0.001\"\n        value=\"0\"\n        @input=\"handleRange\"\n      />\n    </div>\n  </div>\n</template>\n\n<script>\nimport { mapState } from 'vuex'\n\nimport Drawing from '@/components/Desktop/Drawing.vue'\nimport ThreeAnimation from '@/tools/animation'\n\nimport webglIsSupported from '@/tools/animation/webglSupport'\n\nimport { ANIMATION_SCREEN_VIEWPORT } from '@/settings'\nimport threads from '@/store/vuetamin/threads'\n\nimport debouncedResize from 'debounced-resize'\n\nexport default {\n  name: 'Animation',\n\n  props: {\n    isDrawing: Boolean,\n    isDesktop: Boolean,\n    desktopAnimation: {\n      type: Boolean,\n      default: false\n    }\n  },\n\n  data() {\n    return {\n      isRendered: false,\n\n      x: 180,\n      y: 0,\n\n      windowWidth: 958,\n      windowHeight: 542,\n\n      count: 0,\n\n      mouseEnabled: true,\n      sceneVisible: true,\n\n      useFallback: false,\n\n      debug: false\n    }\n  },\n\n  computed: {\n    ...mapState(['isConnected', 'isSkipped', 'introPlayed']),\n\n    ratio() {\n      return this.windowWidth / this.windowHeight\n    }\n  },\n\n  watch: {\n    x(x) {\n      this.updateRotation(x, this.y)\n    },\n\n    y(y) {\n      this.updateRotation(this.x, y)\n    }\n  },\n\n  destroyed() {\n    this.destroy()\n  },\n\n  mounted() {\n    if (!webglIsSupported()) {\n      this.useFallback = true\n      this.isRendered = true\n      this.$store.commit('setIntroPlayed', true)\n      return\n    }\n\n    const pairingEl = this.$slots.default[0].elm\n    this.updateSizes()\n\n    debouncedResize(() => {\n      if (this.isDesktop) {\n        this.updateSizes()\n      }\n    })\n\n    this.animation = new ThreeAnimation(\n      this.$refs.container,\n      ANIMATION_SCREEN_VIEWPORT,\n      this.desktopAnimation,\n      this.debug,\n      pairingEl\n    )\n\n    const screen = this.animation.getScreen()\n    const DrawingCtor = this.$root.constructor.extend(Drawing)\n    this.instance = new DrawingCtor({\n      parent: this,\n      propsData: {\n        showToolbar: true,\n        isDrawing: false\n      }\n    }).$mount(screen)\n\n    this.animation.setSize(this.windowWidth, this.windowHeight)\n\n    window.addEventListener('mousemove', this.onMouseMove)\n    window.addEventListener('mousedown', this.onMouseDown)\n    window.addEventListener('mouseup', this.onMouseUp)\n\n    window.addEventListener('touchstart', this.onMouseDown)\n    window.addEventListener('touchend', this.onMouseUp)\n    window.addEventListener('touchcancel', this.onMouseUp)\n\n    if (!this.isDesktop) {\n      this.loop()\n    }\n\n    if (!this.introPlayed) {\n      this.animation.animateEnter()\n    } else {\n      this.animation.setFinalCameraState()\n    }\n\n    this.animation.on('animationEnd', () => {\n      this.$vuetamin.trigger(threads.SIZES)\n      this.$store.commit('setIntroPlayed', true)\n    })\n\n    this.animation.on('slowPerformance', () => {\n      this.$store.commit('setIntroPlayed', true)\n      this.$sentry.logInfo('animation', 'slow performance')\n      this.$track('Animation', 'performance', 'slow')\n      this.useFallback = true\n      this.isRendered = true\n      this.destroy()\n\n      const pairingEl = this.$slots.default[0].elm\n      pairingEl.removeAttribute('style')\n    })\n\n    this.animation.refresh()\n    this.animation.setSize(this.windowWidth, this.windowHeight)\n\n    this.isRendered = true\n\n    this.animation.play()\n  },\n\n  methods: {\n    destroy() {\n      if (this.instance) {\n        this.instance.$destroy()\n        this.instance.$el.remove()\n        this.instance = null\n      }\n\n      window.removeEventListener('mousemove', this.onMouseMove)\n      window.removeEventListener('mousedown', this.onMouseDown)\n      window.removeEventListener('mouseup', this.onMouseUp)\n\n      window.removeEventListener('touchstart', this.onMouseDown)\n      window.removeEventListener('touchend', this.onMouseUp)\n      window.removeEventListener('touchcancel', this.onMouseUp)\n    },\n\n    loop() {\n      const orientation = this.$mote.gyroscope.getOrientation(100)\n\n      this.animation.setPhoneRotationFromGyro(orientation)\n      this.getIntersection()\n\n      window.requestAnimationFrame(this.loop)\n    },\n\n    handleRange(e) {\n      this.animation.seekAnimation(e.target.value)\n    },\n\n    updateRotation(x, y) {\n      this.animation.setPhoneRotationFromMouse(x, y)\n      this.getIntersection()\n    },\n\n    getIntersection() {\n      const coordinates = this.animation.getIntersection()\n      if (!coordinates) return\n      this.$vuetamin.store.mutate('updatePointer', { coordinates })\n    },\n\n    onMouseMove(e) {\n      if (!this.mouseEnabled) {\n        return\n      }\n      // e.preventDefault()\n      this.setOrientation(e.pageX, e.pageY)\n    },\n\n    onMouseDown() {\n      if (!this.mouseEnabled) {\n        return\n      }\n      // e.preventDefault()\n      this.$vuetamin.store.mutate('updateIsPressing', { isPressing: true })\n    },\n\n    onMouseUp() {\n      if (!this.mouseEnabled) {\n        return\n      }\n      // e.preventDefault()\n      this.$vuetamin.store.mutate('updateIsPressing', { isPressing: false })\n    },\n\n    setOrientation(x, y) {\n      this.x = x / this.windowWidth\n      this.y = y / this.windowHeight\n    },\n\n    updateSizes() {\n      this.windowWidth = window.innerWidth\n      this.windowHeight = window.innerHeight\n      if (this.animation) {\n        this.animation.setSize(this.windowWidth, this.windowHeight)\n      }\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.debug-range {\n  position: fixed;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  z-index: 99999;\n  padding: 1rem;\n  padding-bottom: 4rem;\n  // display: none;\n}\n\n.animation {\n  &.is-mobile {\n    &.component-fade-enter-active,\n    &.component-fade-leave-active {\n      transition: 1.5s;\n    }\n    &.component-fade-enter,\n    &.component-fade-leave-to {\n      opacity: 0;\n      transform: translateY(-4rem);\n    }\n  }\n  &.is-desktop {\n    user-select: none;\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n\n    &.component-fade-enter-active,\n    &.component-fade-leave-active {\n      transition: 1.5s;\n    }\n    &.component-fade-enter,\n    &.component-fade-leave-to {\n      transform: scale(1.1);\n    }\n  }\n}\n\n.three-animation {\n  position: absolute;\n  top: 0;\n  left: 0;\n  overflow: hidden;\n  z-index: 0;\n\n  .is-fallback & {\n    background-image: url('/fallback-mobile.jpg');\n    background-size: cover;\n    background-position: top center;\n\n    @include media('lg') {\n      background-image: url('/fallback-desktop.jpg');\n      background-position: center;\n    }\n  }\n  .is-desktop & {\n    width: 100%;\n    height: 100%;\n  }\n}\n\n.renderer-webgl {\n  position: relative;\n  top: 0;\n  left: 0;\n  z-index: 10;\n}\n\n.renderer-webgl {\n  z-index: 15;\n}\n\n.renderer-css {\n  &,\n  canvas {\n    image-rendering: -moz-crisp-edges;\n    image-rendering: -webkit-crisp-edges;\n    image-rendering: pixelated;\n    image-rendering: crisp-edges;\n  }\n  > div {\n    z-index: 10;\n  }\n}\n\n.dg.ac {\n  z-index: 999999;\n  @include media('sm', $breakpoints-desc) {\n    top: 100vw;\n  }\n}\n\n#screen {\n  background: red;\n}\n\n.screen-wrapper {\n  transform-origin: top left;\n  position: relative;\n  height: 100%;\n  width: 100%;\n}\n\n.screen-container {\n  z-index: 10;\n}\n\n.ratio {\n  position: fixed;\n  top: 0;\n  left: 0;\n  padding: 2rem;\n  font-size: 2rem;\n  font-weight: bold;\n  z-index: 99999999;\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/Attribution.vue",
    "content": "<template>\n  <div class=\"attribution\">\n    <div class=\"attribution-background\"></div>\n    <div class=\"attribution-overlay pdg md-pdg+ lg-pdg++\">\n      <div class=\"md-pdg++ attribution-content\">\n        <h2 class=\"text-heavy h1 mrgb\">Attribution</h2>\n        <p class=\"h2 mrgb+\">\n          drawmote was only made possible thanks to the contributions of the\n          following people and projects.\n        </p>\n\n        <template v-for=\"section in sections\">\n          <div :key=\"section.name\">\n            <h3 class=\"h2 text-bold text-uppercase mrgb- mrgt++\">\n              {{ section.name }}\n            </h3>\n            <ul class=\"list\">\n              <li v-for=\"item in section.items\" :key=\"item.name\" class=\"mrgb\">\n                <div v-if=\"section.name === 'Media'\">\n                  <h4 class=\"h4 text-bold\">\n                    <a :href=\"item.link\" class=\"link\"\n                      >\"{{ item.description }}\"</a\n                    >\n                  </h4>\n                  by\n                  <a :href=\"item.linkUser\" class=\"link\">{{ item.name }}</a> is\n                  licensed under\n                  <a\n                    href=\"https://creativecommons.org/licenses/by/4.0/\"\n                    class=\"link\"\n                    >CC BY 4.0</a\n                  >\n                </div>\n                <div v-else>\n                  <h4 class=\"text-bold\">\n                    <a :href=\"item.link\" class=\"link\">{{ item.name }}</a>\n                  </h4>\n                  <div>{{ item.description }}</div>\n                </div>\n              </li>\n            </ul>\n          </div>\n        </template>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'Attribution',\n\n  data() {\n    return {\n      sections: [\n        {\n          name: 'Contributions',\n          items: [\n            {\n              name: 'Pascal Thormeier',\n              description:\n                'Figured out and implemented the math for gyro-plane.',\n              link: 'http://thormeier.github.io/'\n            },\n            {\n              name: 'Sascha Aeppli',\n              description: 'Support for various math and JavaScript questions.',\n              link: 'https://github.com/munxar'\n            }\n          ]\n        },\n\n        {\n          name: 'Libraries',\n          items: [\n            {\n              name: 'simple-peer',\n              description: 'Simple WebRTC video/voice and data channels',\n              link: 'https://github.com/feross/simple-peer'\n            },\n            {\n              name: 'gyronorm.js',\n              description:\n                'JavaScript project for accessing and normalizing the accelerometer and gyroscope data on mobile devices',\n              link: 'https://github.com/dorukeker/gyronorm.js'\n            },\n            {\n              name: 'Vue.js',\n              description:\n                'a progressive, incrementally-adoptable JavaScript framework for building UI on the web. ',\n              link: 'https://github.com/vuejs/vue'\n            },\n            {\n              name: 'three.js',\n              description: 'JavaScript 3D library',\n              link: 'https://github.com/mrdoob/three.js/'\n            }\n          ]\n        },\n        {\n          name: 'Media',\n          items: [\n            {\n              name: 'BenGreen',\n              linkUser: 'https://sketchfab.com/antonystix',\n              description: 'Smartphone | Low Poly | Free Download',\n              link:\n                'https://sketchfab.com/3d-models/smartphone-low-poly-free-download-36e8a127e2b9481ea7b4ed4187f2b765'\n            },\n            {\n              name: \"Daniel O'Neil\",\n              linkUser: 'https://sketchfab.com/doneil',\n              description: 'Monitor w/Star Wars Animation: Household Props 4',\n              link:\n                'https://sketchfab.com/3d-models/monitor-wstar-wars-animation-household-props-4-8ca4aaaf438a44189443e34962a18cd5'\n            }\n          ]\n        }\n      ]\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.attribution {\n  z-index: $index-footer - 2;\n  position: fixed;\n  top: 0;\n  right: 0;\n  left: 0;\n  bottom: 0;\n  &.appear-enter-active,\n  &.appear-leave-active {\n    transition: 0.7s ease-in-out;\n    .attribution-overlay {\n      transition: 0.7s ease-in-out;\n    }\n  }\n  &.appear-enter,\n  &.appear-leave-to {\n    opacity: 0;\n    .attribution-overlay {\n      transform: translateY(30%);\n      @include media('sm') {\n        transform: translateY(10%);\n      }\n    }\n  }\n}\n\n.attribution-background {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n}\n\n.attribution-background {\n  background: rgba($alt-color-darkest, 0.99);\n}\n\n.attribution-overlay {\n  position: relative;\n  overflow: auto;\n  -webkit-overflow-scrolling: touch;\n  z-index: $index-footer + 1;\n  padding-bottom: 5rem !important;\n  height: 100%;\n  @include media('sm') {\n    display: flex;\n  }\n  > div {\n    max-width: 80rem;\n    margin: 0 auto;\n    @include media('sm') {\n      overflow: hidden;\n    }\n  }\n\n  table {\n    tr {\n      td {\n        padding-left: 0;\n        padding-right: 0;\n      }\n      td:first-child {\n        width: 10rem;\n      }\n    }\n  }\n\n  > div {\n  }\n}\n\n.attribution-content {\n  overflow: auto;\n\n  @include media('sm') {\n    .list {\n      display: flex;\n      flex-wrap: wrap;\n      li {\n        flex: 0 0 50%;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/BrowserSupport.vue",
    "content": "<template lang=\"html\">\n  <transition name=\"appear\">\n    <div class=\"browser-support\" :class=\"{ done: allDone }\">\n      <div class=\"browser-support__content pdg relative\">\n        <template v-if=\"!needsPermission\">\n          <button\n            class=\"btn btn--bare browser-support__close\"\n            @click=\"$emit('close')\"\n          >\n            <div class=\"pdg\">\n              <IconClose class=\"icon block\" />\n            </div>\n          </button>\n          <h3 class=\"label\">{{ $t('browserSupport.title') }}</h3>\n          <ul class=\"list check-list\">\n            <li\n              v-for=\"check in doneChecks\"\n              :key=\"check.check\"\n              class=\"check check--small\"\n              :class=\"check.state\"\n            >\n              <div class=\"check__title\">\n                {{ $t(`browserSupport.${check.check}.label`) }}\n              </div>\n              <div class=\"check__notice\">\n                {{ $t(`browserSupport.${check.check}.${check.state}`) }}\n              </div>\n            </li>\n          </ul>\n        </template>\n\n        <div v-if=\"needsPermission\">\n          <p class=\"h3\">\n            {{ $t('browserSupport.requestPermission.text') }}\n          </p>\n          <button\n            class=\"btn btn--default btn--block mrgt\"\n            @click=\"requestPermission\"\n          >\n            {{ $t('browserSupport.requestPermission.cta') }}\n          </button>\n        </div>\n      </div>\n    </div>\n  </transition>\n</template>\n\n<script>\nimport IconClose from '@/assets/icons/icon-close.svg'\n\nconst CHECKS = ['webRTC', 'webSocket', 'gyroscope', 'canvasFilter']\nlet watchers = []\n\nconst SUPPORT_STATE = {\n  SUPPORTED: 'supported',\n  UNSUPPORTED: 'unsupported',\n  PARTIAL: 'partial',\n  CHECKING: 'checking'\n}\n\nconst CHECK_STATE = {\n  TRUE: 'supported',\n  FALSE: 'unsupported',\n  NOT_REQUIRED: 'not_required',\n  WAITING: 'waiting',\n  CHECKING: 'checking'\n}\n\nfunction getCheckStateFromBoolean(isSupported) {\n  return isSupported ? CHECK_STATE.TRUE : CHECK_STATE.FALSE\n}\n\nfunction filterRelevantCheck(check) {\n  return check !== CHECK_STATE.CHECKING && check !== CHECK_STATE.NOT_REQUIRED\n}\n\nexport default {\n  name: 'BrowserSupport',\n\n  components: {\n    IconClose\n  },\n\n  props: {\n    isMobile: {\n      type: Boolean,\n      default: false\n    }\n  },\n\n  data() {\n    let data = {\n      needsPermission: false\n    }\n\n    CHECKS.forEach((check) => {\n      data[check] = CHECK_STATE.CHECKING\n    })\n\n    return data\n  },\n\n  computed: {\n    /**\n     * @returns {Array} Return the checks with their support status.\n     */\n    doneChecks() {\n      return this.relevantChecks.map((check) => {\n        return {\n          check: check,\n          state: this[check]\n        }\n      })\n    },\n\n    relevantChecks() {\n      return CHECKS.filter((check) => filterRelevantCheck(this[check]))\n    },\n\n    allDone() {\n      return this.relevantChecks.length === this.doneChecks.length\n    },\n\n    supportState() {\n      if (!this.allDone) {\n        return SUPPORT_STATE.CHECKING\n      }\n\n      if (\n        (this.webRTC === CHECK_STATE.FALSE &&\n          this.webSocket === CHECK_STATE.FALSE) ||\n        (this.isMobile && this.gyroscope === CHECK_STATE.FALSE)\n      ) {\n        return SUPPORT_STATE.UNSUPPORTED\n      }\n\n      if (\n        this.relevantChecks.filter((check) => this[check] === CHECK_STATE.FALSE)\n          .length > 0\n      ) {\n        return SUPPORT_STATE.PARTIAL\n      }\n\n      if (this.needsPermission) {\n        return SUPPORT_STATE.PARTIAL\n      }\n\n      return SUPPORT_STATE.SUPPORTED\n    }\n  },\n\n  watch: {\n    supportState(state) {\n      this.$emit('supportState', state)\n    }\n  },\n\n  mounted() {\n    if (!this.$settings.isPrerendering) {\n      this.$emit('supportState', this.supportState)\n\n      watchers = CHECKS.map((check) => {\n        return this.$watch(check, (checkState) => {\n          this.$sentry.setSupport(check, checkState)\n        })\n      })\n\n      this.runCheck()\n\n      this.$peersox.on('usingFallback', () => {\n        this.webRTC = CHECK_STATE.FALSE\n      })\n    }\n  },\n\n  beforeDestroy() {\n    watchers.forEach((watcher) => watcher())\n  },\n\n  methods: {\n    /**\n     * Checks if canvas filters are supported. This is needed for the hardness\n     * property of the brush.\n     */\n    supportsCanvasFilter() {\n      const ctx = document.createElement('canvas').getContext('2d')\n      return getCheckStateFromBoolean(typeof ctx.filter !== 'undefined')\n    },\n\n    /**\n     * Checks if WebSockets are supported.\n     */\n    supportsWebSocket() {\n      return getCheckStateFromBoolean(\n        this.$peersox.getDeviceSupport().WEBSOCKET\n      )\n    },\n\n    /**\n     * Checks if WebRTC is supported.\n     */\n    supportsWebRTC() {\n      return getCheckStateFromBoolean(this.$peersox.getDeviceSupport().WEBRTC)\n    },\n\n    /**\n     * Checks if the device has a gyroscope.\n     */\n    supportsGyroscope() {\n      return this.$mote.deviceHasGyroscope().then(getCheckStateFromBoolean)\n    },\n\n    /**\n     * Asynchronously run the checks. Issue tracking events, supportState event\n     * and modify the Vuetamin store if canvas filters are supported.\n     */\n    runCheck() {\n      this.webRTC = this.supportsWebRTC()\n      this.webSocket = this.supportsWebSocket()\n\n      // Checks gyroscope availability.\n      if (this.isMobile) {\n        if (this.$mote.gyroscope.needsPermission()) {\n          this.needsPermission = true\n        } else {\n          this.supportsGyroscope().then((hasGyroscope) => {\n            this.gyroscope = hasGyroscope\n          })\n          this.$mote.gyroscope.initGyroNorm()\n        }\n      } else {\n        this.gyroscope = CHECK_STATE.NOT_REQUIRED\n      }\n\n      // Checks if canvas filter is available. For mobile this is only relevant\n      // for the demo inside the 3D screen, that's why the user does not need to\n      // be informed about that. It is set however in the Vuetamin store, so\n      // that the canvas inside the demo doesn't attempt to use a canvas filter.\n      const canvasFilter = this.supportsCanvasFilter()\n\n      if (!this.isMobile) {\n        this.canvasFilter = canvasFilter\n      }\n\n      this.$vuetamin.store.mutate('updateCanvasFilterSupport', canvasFilter)\n    },\n\n    requestPermission() {\n      this.$mote.gyroscope.requestPermission().then((isGranted) => {\n        this.needsPermission = false\n        this.gyroscope = CHECK_STATE.TRUE\n      })\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n@import '@/assets/scss/components/_check.scss';\n\n.browser-support {\n  display: block;\n  text-align: left;\n  position: absolute;\n  bottom: 100%;\n  left: 0;\n  width: 100vw;\n  max-width: 24rem;\n  z-index: -1;\n  background: $color-translucent-dark;\n\n  &.appear-enter-active,\n  &.appear-leave-active {\n    transition: 0.5s;\n    .browser-support__content {\n      transition: 0.2s;\n      transition-delay: 0.3s;\n    }\n  }\n  &.appear-enter,\n  &.appear-leave-to {\n    transform: translateY(130%);\n    @include media('md') {\n      transform: translateX(-100%);\n    }\n    .browser-support__content {\n      opacity: 0;\n    }\n  }\n  h3 {\n    text-transform: uppercase;\n  }\n}\n\n.browser-support__close {\n  position: absolute;\n  top: 0;\n  right: 0;\n  .icon {\n    margin-top: rem(9px);\n  }\n}\n\n.browser-support__content {\n}\n\n.check-list {\n  li:not(:last-child) {\n    margin-bottom: 0.75rem;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/ConnectionTimeout.vue",
    "content": "<template>\n  <transition name=\"fade\">\n    <div v-if=\"isVisible\" class=\"connection-timeout\">\n      <div>\n        <h3 class=\"h2 text-bold\">\n          {{ $t('connectionTimeout.title') }}\n        </h3>\n        <p class=\"h2 mrgb+\">\n          {{ $t('connectionTimeout.text ') }}\n        </p>\n        <ul class=\"list-inline\">\n          <li>\n            <button class=\"btn btn--primary relative\" @click=\"backToPairing\">\n              {{ $t('connectionTimeout.toPairing') }}\n            </button>\n          </li>\n          <li v-if=\"!isMobile\">\n            <button class=\"btn btn--default relative\" @click=\"continueDrawing\">\n              {{ $t('connectionTimeout.continueDrawing') }}\n            </button>\n          </li>\n        </ul>\n      </div>\n    </div>\n  </transition>\n</template>\n\n<script>\n/**\n * Provides a way to restore a previously made connection.\n */\nexport default {\n  name: 'ConnectionTimeout',\n\n  props: {\n    isMobile: {\n      type: Boolean,\n      default: false\n    }\n  },\n\n  data() {\n    return {\n      isVisible: false\n    }\n  },\n\n  mounted() {\n    this.$peersox.on('connected', this.handleConnected)\n    this.$peersox.on('connectionTimeout', this.handleConnectionTimeout)\n  },\n\n  beforeDestroy() {\n    this.$peersox.off('connected', this.handleConnected)\n    this.$peersox.off('connectionTimeout', this.handleConnectionTimeout)\n  },\n\n  methods: {\n    handleConnected() {\n      this.isVisible = false\n    },\n\n    handleConnectionTimeout() {\n      this.isVisible = true\n    },\n\n    continueDrawing() {\n      this.isVisible = false\n    },\n\n    backToPairing() {\n      this.$mote.disconnect()\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.connection-timeout {\n  position: fixed;\n  z-index: 1000;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  padding: 1rem;\n  background: rgba($alt-color-lighter, 0.95);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n\n  &.fade-enter-active,\n  &.fade-leave-active {\n    &,\n    > div {\n      transition: 0.5s;\n    }\n  }\n  &.fade-enter, &.fade-leave-to /* .fade-leave-active below version 2.1.8 */ {\n    opacity: 0;\n    > div {\n      transform: scale(1.1);\n    }\n  }\n\n  @include media('sm') {\n    text-align: left;\n  }\n  > div {\n    max-width: 60rem;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/Footer/Footer.vue",
    "content": "<template>\n  <div ref=\"footer\" class=\"footer\">\n    <div class=\"footer__content\">\n      <ul class=\"list-inline list-inline--tight text-small footer__list\">\n        <FooterBrowserSupport :is-mobile=\"isMobile\" />\n        <FooterLanguage />\n        <FooterConnection v-if=\"!isMobile\" />\n        <FooterGithub />\n        <FooterCopyright />\n        <FooterAttribution />\n      </ul>\n    </div>\n  </div>\n</template>\n\n<script>\nimport debouncedResize from 'debounced-resize'\n\nimport FooterBrowserSupport from '@/components/Common/Footer/FooterBrowserSupport.vue'\nimport FooterCopyright from '@/components/Common/Footer/FooterCopyright.vue'\nimport FooterConnection from '@/components/Common/Footer/FooterConnection.vue'\nimport FooterGithub from '@/components/Common/Footer/FooterGithub.vue'\nimport FooterLanguage from '@/components/Common/Footer/FooterLanguage.vue'\nimport FooterAttribution from '@/components/Common/Footer/FooterAttribution.vue'\n\nexport default {\n  name: 'Footer',\n\n  components: {\n    FooterBrowserSupport,\n    FooterCopyright,\n    FooterConnection,\n    FooterGithub,\n    FooterLanguage,\n    FooterAttribution\n  },\n\n  props: {\n    isMobile: {\n      type: Boolean,\n      default: false\n    }\n  },\n\n  mounted() {\n    this.updateSizes()\n    debouncedResize(() => {\n      this.updateSizes()\n    })\n  },\n\n  methods: {\n    updateSizes() {\n      const footer = this.$refs.footer\n      this.$vuetamin.store.mutate('updateFooterRect', footer)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.footer {\n  position: fixed;\n  right: 0;\n  left: 0;\n  bottom: 0;\n  user-select: none;\n  z-index: $index-footer;\n  @include media('xs', $breakpoints-desc) {\n    &:before {\n      content: '';\n      position: absolute;\n      pointer-events: none;\n      top: -1.5rem;\n      left: 0;\n      width: 100%;\n      bottom: 0;\n      background: linear-gradient(\n        0deg,\n        $alt-color-darkest,\n        rgba($alt-color-darkest, 0.9),\n        rgba($alt-color-darkest, 0)\n      );\n    }\n  }\n}\n\n.footer__content {\n  position: relative;\n  z-index: $index-footer;\n}\n\n.footer__list {\n  align-items: stretch !important;\n  @include media('sm') {\n    .hover {\n      &:hover {\n        background: $color-translucent-dark;\n      }\n    }\n  }\n}\n\n.footer-text {\n  @include media('xs', $breakpoints-desc) {\n    font-size: 11px;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/Footer/FooterAttribution.vue",
    "content": "<template>\n  <li class=\"footer-attribution\">\n    <button\n      class=\"btn btn--bare text-bold pdg lg-pdg h-100 hover footer-text\"\n      @click=\"$store.dispatch('toggleAttributionVisibility')\"\n    >\n      <span class=\"arrow-after\">Attribution</span>\n    </button>\n  </li>\n</template>\n\n<script>\nexport default {\n  name: 'FooterAttribution'\n}\n</script>\n\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "src/components/Common/Footer/FooterBrowserSupport.vue",
    "content": "<template>\n  <li class=\"relative footer-browser-support\">\n    <button\n      class=\"btn btn--bare text-bold check pdg lg-pdg h-100 hover footer-text\"\n      :class=\"[supportState, { 'is-open': browserSupportVisible }]\"\n      @click=\"toggleBrowserSupport\"\n    >\n      <div class=\"check__title\">\n        <span class=\"arrow-after\">{{\n          $t(`browserSupport.footer.${supportState}`)\n        }}</span>\n      </div>\n    </button>\n    <BrowserSupport\n      v-show=\"browserSupportVisible\"\n      :is-mobile=\"isMobile\"\n      @supportState=\"handleBrowserSupportState\"\n      @close=\"closeBrowserSupport\"\n    />\n  </li>\n</template>\n\n<script>\nimport { mapGetters } from 'vuex'\nimport BrowserSupport from '@/components/Common/BrowserSupport.vue'\n\nexport default {\n  name: 'FooterBrowserSupport',\n\n  components: {\n    BrowserSupport\n  },\n\n  props: {\n    isMobile: {\n      type: Boolean,\n      default: false\n    }\n  },\n\n  data() {\n    return {\n      supportState: 'checking',\n      browserSupportVisible: false\n    }\n  },\n\n  computed: {\n    ...mapGetters(['isDrawing'])\n  },\n\n  watch: {\n    isDrawing(isDrawing) {\n      if (isDrawing) {\n        this.browserSupportVisible = false\n      }\n    }\n  },\n\n  methods: {\n    toggleBrowserSupport() {\n      this.browserSupportVisible = !this.browserSupportVisible\n      this.$track('BrowserSupport', 'show', 1)\n    },\n\n    handleBrowserSupportState(state) {\n      this.supportState = state\n\n      if (state !== 'supported') {\n        this.browserSupportVisible = true\n      } else {\n        this.browserSupportVisible = false\n      }\n    },\n\n    closeBrowserSupport() {\n      this.browserSupportVisible = false\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n@import '@/assets/scss/components/_check.scss';\n\n.footer-browser-support {\n  flex: 1;\n  z-index: 1;\n  > .btn {\n    width: 100%;\n    text-align: left;\n    &.is-open {\n      background: $color-translucent-dark;\n    }\n  }\n  @include media('sm') {\n    flex: 0 0 auto;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/Footer/FooterConnection.vue",
    "content": "<template>\n  <li class=\"footer-connection\">\n    <button\n      v-if=\"isConnected\"\n      class=\"btn btn--bare text-bold check pdg lg-pdg h-100 hover footer-text\"\n      @click=\"$store.dispatch('disconnect')\"\n    >\n      {{ $t('footer.disconnect') }}\n    </button>\n\n    <button\n      v-if=\"isSkipped\"\n      class=\"btn btn--bare text-bold check pdg lg-pdg h-100 hover footer-text\"\n      @click=\"$store.dispatch('unskip')\"\n    >\n      {{ $t('footer.toPairing') }}\n    </button>\n  </li>\n</template>\n\n<script>\nimport { mapState } from 'vuex'\n\nexport default {\n  name: 'FooterConnection',\n\n  computed: {\n    ...mapState(['isConnected', 'isSkipped'])\n  }\n}\n</script>\n\n<style lang=\"scss\"></style>\n"
  },
  {
    "path": "src/components/Common/Footer/FooterCopyright.vue",
    "content": "<template>\n  <li class=\"text-right hidden-xs-down footer-copyright\">\n    <div class=\"pdg footer-text\">\n      Made by <a href=\"http://www.janhug.info\" class=\"text-bold\">Jan Hug</a> at\n      <a href=\"https://www.liip.ch\"><LiipLogo /></a>\n    </div>\n  </li>\n</template>\n\n<script>\nimport LiipLogo from '@/assets/icons/liip-logo.svg'\n\nexport default {\n  name: 'FooterCopyright',\n\n  components: {\n    LiipLogo\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.footer-copyright {\n  svg {\n    height: 0.7em;\n    margin-left: 0.1rem;\n    fill: currentColor;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/Footer/FooterGithub.vue",
    "content": "<template>\n  <li class=\"text-bold mrgla hover\">\n    <a\n      class=\"pdg block text-white footer-text\"\n      href=\"https://github.com/dulnan/drawmote\"\n    >\n      <IconGithub class=\"icon icon--large\" />\n      <span class=\"hidden-sm-down\">GitHub</span>\n    </a>\n  </li>\n</template>\n\n<script>\nimport IconGithub from '@/assets/icons/icon-github.svg'\n\nexport default {\n  name: 'FooterCopyright',\n\n  components: {\n    IconGithub\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Common/Footer/FooterLanguage.vue",
    "content": "<template>\n  <li class=\"text-bold hover relative language\">\n    <select\n      v-model=\"$i18n.locale\"\n      class=\"language__select\"\n      @change=\"handleLanguageChange\"\n    >\n      <option\n        v-for=\"(lang, i) in languages\"\n        :key=\"`Lang${i}`\"\n        :value=\"lang.key\"\n        >{{ lang.label }}</option\n      >\n    </select>\n    <div class=\"text-bold pdg h-100 language__button footer-text\">\n      <span class=\"arrow-after hidden-sm-down\">{{\n        currentLanguage.label\n      }}</span>\n      <span class=\"arrow-after hidden-md-up text-uppercase\">{{\n        currentLanguage.key\n      }}</span>\n    </div>\n  </li>\n</template>\n\n<script>\nimport { setLocale } from '@/tools/cookies'\n\nexport default {\n  name: 'FooterLanguage',\n\n  props: {\n    isMobile: {\n      type: Boolean,\n      default: false\n    }\n  },\n\n  data() {\n    return {\n      languages: [\n        {\n          key: 'de',\n          label: 'Deutsch'\n        },\n        {\n          key: 'en',\n          label: 'English'\n        }\n      ]\n    }\n  },\n\n  computed: {\n    currentLanguage() {\n      const currentLanguage = this.languages.find(\n        (l) => l.key === this.$i18n.locale\n      )\n\n      // Return English as a fallback if for some reason i18n doesn't return\n      // anything.\n      return currentLanguage || this.languages[1]\n    }\n  },\n\n  methods: {\n    handleLanguageChange(e) {\n      setLocale(e.target.value)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.language__select {\n  position: absolute;\n  top: 0;\n  left: 0;\n  cursor: pointer;\n  width: 100%;\n  height: 100%;\n  opacity: 0;\n  &:focus {\n    outline: none;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/Logo.vue",
    "content": "<template>\n  <div class=\"logo mrgt+\">\n    <div class=\"logo__app\">\n      <img src=\"drawmote-logo.png\" />\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'Logo'\n}\n</script>\n\n<style lang=\"scss\">\n$logo-base: 768;\n\n@keyframes pulse {\n  0% {\n    transform: scale(1);\n  }\n\n  100% {\n    transform: scale(0.95);\n  }\n}\n\n.logo {\n  font-size: 9rem;\n\n  @include media('sm') {\n    font-size: 11rem;\n  }\n  @include media('md') {\n    font-size: 8rem;\n    margin-left: -6rem;\n  }\n  @include media('lg') {\n    // font-size: 16rem;\n  }\n}\n\n.logo__app {\n  position: relative;\n  z-index: 1;\n  width: 1em;\n  height: 1em;\n  background: white;\n  border-radius: 0.22em;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  box-shadow: 0 ((30 / $logo-base) * 1em) ((100 / $logo-base) * 1em)\n      ((7 / $logo-base) * 1em) rgba(0, 0, 0, 0.04),\n    0 ((1 / $logo-base) * 1em) ((4 / $logo-base) * 1em) ((2 / $logo-base) * 1em)\n      rgba(0, 0, 0, 0.01);\n  /* animation: 2s pulse cubic-bezier(1, -0.04, 0.63, 1.01) infinite alternate; */\n\n  img {\n    display: block;\n    width: 100%;\n    height: 100%;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/RestoreConnection.vue",
    "content": "<template>\n  <transition name=\"appear\">\n    <div v-show=\"isVisible\" class=\"connection pdg lg-pdg+\">\n      <div class=\"flex-1 flex\">\n        <div class=\"connection__icon hidden-md-down mrgr lg-mrgr+\">\n          <IconRestore />\n        </div>\n        <div>\n          <h3 class=\"text-bold\">{{ $t('connection.title') }}</h3>\n          <p class=\"mrg0 h4 text-light text-hyphens mrgb sm-mrgb0 sm-pdgr-\">\n            {{ $t('connection.text') }}\n          </p>\n        </div>\n      </div>\n      <div class=\"connection__buttons flex md-mrgl\">\n        <div>\n          <button\n            class=\"btn btn--default connection__button-clear\"\n            @click=\"deleteConnection\"\n          >\n            <span>{{ $t('connection.delete') }}</span>\n          </button>\n        </div>\n        <div class=\"flex-1 pdgl\">\n          <button\n            class=\"btn btn--primary connection__button connection__button--restore relative btn--block\"\n            :class=\"{ restoring: isRestoring, 'is-restored': isRestored }\"\n            @click=\"restoreConnection\"\n          >\n            <span>{{ $t('connection.reconnect') }}</span>\n            <div class=\"connection__button-animation\">\n              <IconRestore />\n            </div>\n          </button>\n        </div>\n      </div>\n    </div>\n  </transition>\n</template>\n\n<script>\nimport IconRestore from '@/assets/icons/icon-restore.svg'\nimport { mapGetters } from 'vuex'\n\n/**\n * Provides a way to restore a previously made connection.\n */\nexport default {\n  name: 'RestoreConnection',\n\n  components: {\n    IconRestore\n  },\n\n  data() {\n    return {\n      connectionRestorable: false,\n      pairing: {},\n      isRestoring: false,\n      isRestored: false,\n      connectionTimeout: false,\n      windowTimeout: null\n    }\n  },\n\n  computed: {\n    ...mapGetters(['isDrawing']),\n\n    isVisible() {\n      return this.connectionRestorable && !this.isDrawing\n    }\n  },\n\n  mounted() {\n    this.$peersox.on('connected', this.handleConnected)\n\n    this.$peersox\n      .restorePairing()\n      .then((pairing) => {\n        if (pairing) {\n          this.pairing = pairing\n          this.isRestoring = false\n          this.isRestored = false\n          this.connectionRestorable = true\n          this.$sentry.logInfo('pairing', 'restoring:found')\n        }\n      })\n      .catch(() => {\n        this.$sentry.logInfo('pairing', 'restoring:failed')\n      })\n  },\n\n  beforeDestroy() {\n    this.$peersox.off('connected', this.handleConnected)\n  },\n\n  methods: {\n    /**\n     * Initialize a peering connection given the stored code and hash.\n     */\n    restoreConnection() {\n      if (this.isRestoring) {\n        return\n      }\n\n      this.$sentry.logInfo('pairing', 'restoring:start')\n\n      this.isRestoring = true\n\n      this.$peersox\n        .close()\n        .then(this.$peersox.connect(this.pairing))\n        .catch(() => {\n          this.$sentry.logInfo('pairing', 'restoring:failed')\n        })\n\n      window.clearTimeout(this.windowTimeout)\n\n      this.windowTimeout = window.setTimeout(() => {\n        this.connectionTimeout = true\n        this.isRestoring = false\n      }, 20000)\n    },\n\n    /**\n     * Delete the stored connection from the history.\n     */\n    deleteConnection() {\n      this.connectionRestorable = false\n      this.$peersox.deletePairing()\n    },\n\n    handleConnected() {\n      this.isRestored = true\n      this.connectionRestorable = false\n      this.connectionTimeout = false\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.connection {\n  position: relative;\n  position: sticky;\n  bottom: $footer-height-xs;\n  z-index: $index-footer - 1;\n  background: $color-translucent-dark;\n  margin-top: 4rem;\n\n  opacity: 1;\n\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n\n  text-align: left;\n\n  @include media('sm') {\n    position: absolute;\n    left: 1rem;\n    right: 1rem;\n    flex-direction: row;\n    border-radius: $border-radius-default;\n    bottom: calc(#{$footer-height-xs} + 0.5rem);\n  }\n\n  &.appear-enter-active,\n  &.appear-leave-active {\n    transition: 0.5s;\n  }\n  &.appear-enter,\n  &.appear-leave-to {\n    transform: translateY(100%);\n    opacity: 0;\n  }\n}\n\n.connection__icon {\n  background: $brand-color;\n  border-radius: $border-radius-default;\n  font-size: 3.75rem;\n  flex: 0 0 1em;\n  width: 1em;\n  height: 1em;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  svg {\n    height: 0.7em;\n    width: 0.7em;\n    fill: white;\n    display: block;\n  }\n}\n\n@keyframes iconrotate {\n  0% {\n    transform: rotate(0);\n  }\n  100% {\n    transform: rotate(1turn);\n  }\n}\n\n.connection__buttons {\n  align-items: center;\n}\n\n.btn.connection__button--restore {\n  position: relative;\n  span {\n    transition: 0.3s;\n  }\n  &:before {\n    content: '';\n    display: block;\n    background: rgba($brand-color-darker, 0.7);\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    transform-origin: left;\n    transform: scaleX(0);\n  }\n  &.restoring {\n    &:before {\n      transform: none;\n      transition: 20s linear;\n    }\n    span {\n      opacity: 0;\n    }\n  }\n}\n\n.connection__button-animation {\n  display: block;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  height: 2rem;\n  width: 2rem;\n  transform: translate(-50%, -50%);\n  transition: 0.3s;\n  opacity: 0;\n\n  .restoring & {\n    opacity: 1;\n  }\n  svg {\n    fill: white;\n    display: block;\n    width: 2rem;\n    height: 2rem;\n    animation: 2s iconrotate infinite linear;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Common/ServerStatus.vue",
    "content": "<template>\n  <div class=\"server-status\">\n    <div class=\"server-status__code\">{{ serverStatus.status }}</div>\n    <div class=\"server-status__text\">\n      <span class=\"text-bold\">{{ serverStatus.statusText }}:</span>\n      {{ description }}\n      <a href=\"https://github.com/dulnan/drawmote/issues/new\" class=\"link\">\n        create an issue on GitHub.\n      </a>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { mapState } from 'vuex'\n\nexport default {\n  name: 'ServerStatus',\n\n  computed: {\n    ...mapState(['serverStatus']),\n\n    description() {\n      if (this.serverStatus.status === 429) {\n        return 'If you think something is fishy about that, '\n      }\n\n      return 'The server might be down or something else went wrong. If you want you can '\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.server-status {\n  background: rgba($color-red, 0.2);\n  border: 1px solid $color-red;\n  width: 7em;\n  max-width: 100%;\n  z-index: 10;\n  border-radius: 0.1em;\n  display: flex;\n  align-items: center;\n  height: 1em;\n  padding: 0 0.2em;\n  @include media('sm') {\n  }\n}\n\n.server-status__text {\n  font-size: 0.25em;\n  line-height: 1.25;\n}\n\n.server-status__code {\n  font-weight: bold;\n  font-size: 0.65em;\n  line-height: 1.2;\n  margin-top: -0.05em;\n  margin-right: 0.3em;\n}\n</style>\n"
  },
  {
    "path": "src/components/Desktop/Canvas/CanvasDrawing.vue",
    "content": "<template>\n  <div>\n    <canvas\n      ref=\"canvas_main\"\n      class=\"absolute overlay canvas canvas--main\"\n    ></canvas>\n    <canvas\n      ref=\"canvas_temp\"\n      class=\"absolute overlay canvas canvas--temp\"\n    ></canvas>\n  </div>\n</template>\n\n<script>\nimport { setupCanvases } from '@/tools/canvas'\n\nimport { EventBus } from '@/events'\n\nimport Canvas from '@/classes/Canvas'\nimport { isSamePoint } from '@/tools/helpers.js'\nimport threads from '@/store/vuetamin/threads'\n\nexport default {\n  name: 'CanvasDrawing',\n\n  vuetamin: {\n    handleSizes: [threads.SIZES],\n    handlePoint: threads.POINT\n  },\n\n  data() {\n    return {\n      wasPressingBefore: false,\n      previousPoint: {}\n    }\n  },\n\n  beforeDestroy() {\n    EventBus.$off('clearCanvas', this.handleErase)\n    EventBus.$off('undoCanvas', this.handleUndo)\n    EventBus.$off('redoCanvas', this.handleRedo)\n  },\n\n  mounted() {\n    this.wasPressingBefore = false\n    this.setCanvasSizes()\n\n    const canvasMain = this.$refs['canvas_main']\n    const canvasTemp = this.$refs['canvas_temp']\n\n    this.canvasState = new Canvas(canvasMain, canvasTemp)\n\n    EventBus.$on('clearCanvas', this.handleErase)\n    EventBus.$on('undoCanvas', this.handleUndo)\n    EventBus.$on('redoCanvas', this.handleRedo)\n  },\n\n  methods: {\n    handleSizes(state) {\n      this.setCanvasSizes()\n      this.canvasState.updateSizes(state.sizes.viewport)\n    },\n\n    handlePoint(state) {\n      if (!state.isPressing && this.wasPressingBefore) {\n        this.canvasState.release()\n        this.wasPressingBefore = false\n        this.updateCanvasState()\n      }\n\n      if (\n        state.isPressing &&\n        !isSamePoint(state.points.brush, this.previousPoint) &&\n        !state.pointingAtToolbar\n      ) {\n        if (!this.wasPressingBefore) {\n          this.canvasState.start(state.brush.canvasProperties)\n          this.wasPressingBefore = true\n        }\n\n        this.canvasState.move(state.points.brush)\n      }\n\n      this.previousPoint = state.points.brush\n    },\n\n    handleErase() {\n      this.canvasState.erase()\n      this.updateCanvasState()\n    },\n\n    handleUndo() {\n      this.canvasState.undo()\n      this.updateCanvasState()\n    },\n\n    handleRedo() {\n      this.canvasState.redo()\n      this.updateCanvasState()\n    },\n\n    updateCanvasState() {\n      const state = this.canvasState.state\n      EventBus.$emit('canvasState', state)\n    },\n\n    setCanvasSizes() {\n      setupCanvases(this.$vuetamin.store.getState().sizes.viewport, [\n        this.$refs.canvas_main,\n        this.$refs.canvas_temp\n      ])\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.app-drawing-canvas {\n  background: white;\n  height: 100%;\n  width: 100%;\n}\n\n.canvas--main {\n  z-index: $index-canvas-main;\n}\n\n.canvas--temp {\n  z-index: $index-canvas-temp;\n}\n</style>\n"
  },
  {
    "path": "src/components/Desktop/Canvas/CanvasInterface.vue",
    "content": "<template>\n  <canvas\n    ref=\"canvas_interface\"\n    class=\"absolute overlay canvas canvas--interface\"\n  ></canvas>\n</template>\n\n<script>\n/**\n * Draws the moving interface parts of the app on a 2D canvas.\n */\nimport { setupCanvases, clearCanvas } from '@/tools/canvas'\nimport { Catenary } from 'catenary-curve'\n\nimport threads from '@/store/vuetamin/threads'\nimport { RADIUS_MAX } from '@/settings'\n\nconst BRUSH_PREVIEW_PADDING = 30\n\nexport default {\n  name: 'CanvasInterface',\n\n  vuetamin: {\n    setCanvasSizes: [threads.SIZES],\n    drawToCanvas: [threads.BRUSH, threads.POINT, threads.SLIDE]\n  },\n\n  created() {\n    this.catenary = new Catenary()\n  },\n\n  methods: {\n    /**\n     * Clears the area outside a rectangle.\n     *\n     * @param {CanvasRenderingContext2D} context\n     * @param {Rectangle} rectangle\n     */\n    clearOutside(context, rectangle) {\n      const x = rectangle.p1.x\n      const y = rectangle.p1.y\n\n      const width = rectangle.p2.x - x\n      const height = rectangle.p2.y - y\n\n      context.beginPath()\n      context.rect(x, y, width, height)\n      context.clip()\n    },\n\n    /**\n     * Call the function to set the width and height of the canvas elements.\n     */\n    setCanvasSizes(state) {\n      setupCanvases(state.sizes.viewport, [this.$refs.canvas_interface])\n    },\n\n    drawToCanvas(state) {\n      const canvas = this.$refs.canvas_interface\n      const context = canvas.getContext('2d')\n\n      clearCanvas(canvas, state.sizes.viewport)\n\n      // Save current context before clipping.\n      context.save()\n\n      // Clip region outside actual drawing area\n      this.clearOutside(context, state.sizes.canvasRect)\n\n      // Draw brush point\n      context.beginPath()\n      context.fillStyle = state.brush.canvasColor\n      if (state.brush.useFilter) {\n        context.filter = `blur(${state.brush.canvasBlur}px)`\n      }\n      context.arc(\n        state.points.brush.x,\n        state.points.brush.y,\n        state.brush.canvasRadius,\n        0,\n        Math.PI * 2,\n        true\n      )\n      context.fill()\n\n      if (state.brush.useFilter) {\n        context.filter = 'none'\n      }\n\n      /**\n       * Draw the catenary.\n       */\n      context.beginPath()\n      context.lineWidth = 1\n      context.lineCap = 'round'\n      context.strokeStyle = 'rgba(255,255,255,0.8)'\n      context.setLineDash([2, 4])\n      this.catenary.drawToCanvas(\n        context,\n        state.points.pointer,\n        state.points.brush,\n        state.lazyRadius\n      )\n      context.stroke()\n\n      /**\n       * Brush anchor\n       */\n      context.beginPath()\n      context.fillStyle = 'white'\n      context.arc(\n        state.points.brush.x,\n        state.points.brush.y,\n        2,\n        0,\n        Math.PI * 2,\n        true\n      )\n      context.fill()\n\n      /**\n       * Brush preview\n       */\n      if (state.pointingAtToolbar) {\n        const backgroundRadius = RADIUS_MAX * 2 + 2 * BRUSH_PREVIEW_PADDING\n        const brushX = state.points.brush.x\n        const brushY = state.sizes.toolbarRect.height + backgroundRadius + 24\n\n        context.beginPath()\n        context.fillStyle = '#2a192d'\n        context.strokeStyle = '#39293c'\n        context.lineWidth = 1\n        context.setLineDash([])\n        context.arc(brushX, brushY, backgroundRadius, 0, Math.PI * 2, true)\n        context.fill()\n        context.stroke()\n\n        context.beginPath()\n        context.fillStyle = state.brush.canvasColor\n\n        if (state.brush.useFilter) {\n          context.filter = `blur(${state.brush.canvasBlur}px)`\n        }\n\n        context.arc(\n          brushX,\n          brushY,\n          state.brush.canvasRadius,\n          0,\n          Math.PI * 2,\n          true\n        )\n        context.fill()\n      }\n\n      // Restore the saved context.\n      context.restore()\n\n      /**\n       * Pointer cross and dot.\n       */\n      context.beginPath()\n      context.fillStyle = 'rgba(255,255,255,0.2)'\n      context.arc(\n        state.points.pointer.x,\n        state.points.pointer.y,\n        4,\n        0,\n        Math.PI * 2,\n        true\n      )\n      context.fill()\n\n      context.beginPath()\n      context.strokeStyle = 'rgba(255,255,255,1)'\n      context.lineWidth = 1\n      context.moveTo(state.points.pointer.x - 10, state.points.pointer.y)\n      context.lineTo(state.points.pointer.x + 10, state.points.pointer.y)\n      context.moveTo(state.points.pointer.x, state.points.pointer.y - 10)\n      context.lineTo(state.points.pointer.x, state.points.pointer.y + 10)\n      context.stroke()\n\n      /**\n       * Out of bounds indicator.\n       */\n      // Find out the highest possible x and y coordinates before it's out of view.\n      const maxX = state.sizes.canvasRect.width\n      const maxY =\n        state.sizes.canvasRect.height +\n        state.sizes.toolbarRect.height -\n        state.sizes.footerRect.height\n      const maxXHalf = maxX / 2\n      const maxYHalf = maxY / 2\n\n      // Calculate the min and max coordinates.\n      const pointerDotX = Math.max(Math.min(state.points.pointer.x, maxX), 0)\n      const pointerDotY = Math.max(Math.min(state.points.pointer.y, maxY), 0)\n\n      // Calculate how much outside the pointer is from the max and min amount.\n      const offsetX =\n        Math.max(\n          Math.max(Math.abs(state.points.pointer.x - maxXHalf) - maxXHalf, 0) -\n            10,\n          0\n        ) / 7\n      const offsetY =\n        Math.max(\n          Math.max(Math.abs(state.points.pointer.y - maxYHalf) - maxYHalf, 0) -\n            10,\n          0\n        ) / 7\n\n      // The radius increases the more the pointer is outside the view.\n      const radius = Math.min(Math.max(offsetX, offsetY), 30)\n\n      // Draw the circle.\n      context.beginPath()\n      context.lineWidth = 2\n      context.strokeStyle = 'rgba(20,20,20,0.2)'\n      context.fillStyle = 'rgba(50,50,50,0.1)'\n      context.arc(pointerDotX, pointerDotY, radius, 0, Math.PI * 2, true)\n      context.fill()\n      context.stroke()\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.canvas--interface {\n  z-index: $index-canvas-interface;\n  top: 0;\n  left: 0;\n  pointer-events: none;\n}\n</style>\n"
  },
  {
    "path": "src/components/Desktop/Drawing.vue",
    "content": "<template>\n  <div class=\"drawing\" :class=\"{ 'is-drawing': isDrawing }\">\n    <Toolbar v-if=\"showToolbar\" ref=\"toolbar\" />\n    <div\n      ref=\"canvasContainer\"\n      class=\"drawing-area\"\n      :style=\"drawingAreaStyle\"\n    ></div>\n    <CanvasDrawing />\n    <CanvasInterface />\n    <resize-observer @notify=\"getElementSizes\" />\n  </div>\n</template>\n\n<script>\nimport threads from '@/store/vuetamin/threads'\n\nimport Toolbar from '@/components/Desktop/Toolbar/Toolbar.vue'\nimport CanvasDrawing from '@/components/Desktop/Canvas/CanvasDrawing.vue'\nimport CanvasInterface from '@/components/Desktop/Canvas/CanvasInterface.vue'\n\nimport PointerEvents from '@/mixins/PointerEvents.js'\n\nexport default {\n  name: 'Drawing',\n\n  components: {\n    Toolbar,\n    CanvasDrawing,\n    CanvasInterface\n  },\n\n  mixins: [PointerEvents],\n\n  props: {\n    showToolbar: {\n      type: Boolean,\n      default: true\n    },\n    isDrawing: {\n      type: Boolean,\n      default: true\n    }\n  },\n\n  data() {\n    return {\n      toolbarHeight: 0\n    }\n  },\n\n  computed: {\n    drawingAreaStyle() {\n      return {\n        top: `${this.toolbarHeight}px`\n      }\n    }\n  },\n\n  vuetamin: {\n    getElementSizes: [threads.SIZES]\n  },\n\n  mounted() {\n    this.getElementSizes()\n\n    if (this.$mote.on) {\n      this.$mote.on('pointermove', this.handlePointerMove)\n      this.$mote.on('pointerdown', this.handlePointerDown)\n      this.$mote.on('pointerup', this.handlePointerUp)\n      this.$mote.on('touch', this.handleTouch)\n      this.$mote.on('calibrated', this.handleCalibrated)\n    }\n  },\n\n  beforeDestroy() {\n    if (this.$mote.on) {\n      this.$mote.off('pointermove', this.handlePointerMove)\n      this.$mote.off('pointerdown', this.handlePointerDown)\n      this.$mote.off('pointerup', this.handlePointerUp)\n      this.$mote.off('touch', this.handleTouch)\n      this.$mote.off('calibrated', this.handleCalibrated)\n    }\n  },\n\n  methods: {\n    getElementSizes() {\n      if (this.$refs.canvasContainer) {\n        const canvasContainer = this.$refs.canvasContainer\n        this.$vuetamin.store.mutate('updateCanvasRect', canvasContainer)\n      }\n\n      if (this.$refs.toolbar) {\n        const toolbar = this.$refs.toolbar.$el\n        this.$vuetamin.store.mutate('updateToolbarRect', toolbar)\n        this.toolbarHeight = toolbar.offsetHeight\n      }\n    },\n\n    handlePointerMove(coordinates) {\n      this.$vuetamin.store.mutate('updatePointer', { coordinates })\n    },\n\n    handlePointerDown() {\n      this.$vuetamin.store.mutate('updateIsPressing', { isPressing: true })\n    },\n\n    handlePointerUp() {\n      this.$vuetamin.store.mutate('updateIsPressing', { isPressing: false })\n    },\n\n    handleTouch(touch) {\n      this.$vuetamin.store.mutate('updateTouch', touch)\n    },\n\n    handleCalibrated() {\n      this.$vuetamin.store.mutate('updateCalibration')\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.drawing {\n  overflow: hidden;\n  position: absolute;\n  user-select: none;\n  z-index: $index-drawing;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  background: $alt-color-darker;\n  &.component-fade-enter-active,\n  &.component-fade-leave-active {\n    transition: 1.5s;\n  }\n  &.component-fade-enter,\n  &.component-fade-leave-to {\n    transform: scale(1.2);\n    opacity: 0;\n  }\n}\n\n.drawing-area {\n  position: absolute;\n  left: 0;\n  right: 0;\n  bottom: 0;\n}\n</style>\n"
  },
  {
    "path": "src/components/Desktop/Pairing.vue",
    "content": "<template>\n  <div\n    class=\"overlay pairing-desktop absolute flex\"\n    :style=\"transformOriginStyle\"\n    :class=\"{ 'is-desktop-animation': desktopAnimation }\"\n  >\n    <div class=\"pairing-container\">\n      <h1 class=\"text-heavy h1\">drawmote</h1>\n      <p class=\"h2 text-bold mrgb+ text-muted\">{{ $t('subtitle') }}</p>\n      <p class=\"text-muted mrgt0 h3 pairing-lead\">{{ $t('desktop.lead') }}</p>\n      <div class=\"code code--desktop sm-mrgt md-mrgt+\">\n        <ServerStatus v-if=\"hasServerError\" />\n        <div v-else class=\"code__content\">\n          <div\n            v-for=\"(number, index) in pairingCodeNumbers\"\n            :key=\"index\"\n            class=\"code__item\"\n            :class=\"{ visible: hasCode && introPlayed }\"\n          >\n            <div class=\"code-circle contains\" :class=\"'code-circle--' + number\">\n              <span>{{ number }}</span>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div class=\"pairing__actions mrgt\">\n        <p class=\"text-muted text-light mrgv0 pairing-skip\">\n          <button class=\"btn btn--bare\" @click=\"skipPairing\">\n            {{ $t('desktop.nophone') }}\n          </button>\n        </p>\n        <p\n          v-if=\"isBlocked\"\n          class=\"text-muted text-light pairing-lead mrg0 text-brand\"\n        >\n          {{ $t('desktop.tooManyAttempts') }}\n        </p>\n        <p\n          class=\"code-timeout text-muted text-light mrg0\"\n          :class=\"{ visible: hasCode && countdown < 60 }\"\n        >\n          <span\n            >{{ $t('desktop.countdownPrefix') }}\n            {{\n              $tc('desktop.countdownSeconds', countdown, { count: countdown })\n            }}\n            {{ $t('desktop.countdownSuffix') }}</span\n          >\n        </p>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport { mapGetters, mapState } from 'vuex'\nimport ServerStatus from '@/components/Common/ServerStatus.vue'\n\nconst PAIRING_TIMEOUT = 120\nlet interval = null\nlet transitionTimeout = null\n\nexport default {\n  name: 'Pairing',\n\n  components: {\n    ServerStatus\n  },\n\n  props: {\n    pairing: {\n      type: Object,\n      required: true\n    },\n    isBlocked: {\n      type: Boolean,\n      default: false\n    },\n    desktopAnimation: {\n      type: Boolean,\n      default: false\n    }\n  },\n\n  data() {\n    return {\n      showModal: false,\n      hasAppeared: false,\n      showCode: false,\n      countdown: PAIRING_TIMEOUT,\n      center: { x: 0, y: 0 }\n    }\n  },\n\n  computed: {\n    ...mapState(['introPlayed']),\n    ...mapGetters(['hasServerError']),\n\n    pairingCodeNumbers: function () {\n      if (this.isBlocked) {\n        return new Array(6).fill('•')\n      }\n\n      if (this.hasCode) {\n        return this.pairing.code.split('')\n      }\n\n      return new Array(6).fill(' ')\n    },\n\n    hasCode: function () {\n      return (\n        this.pairing &&\n        this.pairing.code &&\n        this.pairing.code.length > 0 &&\n        this.showCode\n      )\n    },\n\n    transformOriginStyle() {\n      return {\n        transformOrigin: `${this.center.x}px ${this.center.y}px`\n      }\n    }\n  },\n\n  watch: {\n    pairing(pairing) {\n      if (pairing) {\n        this.startTimer()\n      } else {\n        this.stopTimer()\n      }\n    }\n  },\n\n  mounted() {\n    window.clearTimeout(transitionTimeout)\n    transitionTimeout = window.setTimeout(() => {\n      this.showCode = true\n    }, 1500)\n  },\n\n  beforeDestroy() {\n    window.clearTimeout(transitionTimeout)\n  },\n\n  methods: {\n    updateCenter(center) {\n      this.center = center\n    },\n\n    skipPairing() {\n      this.$store.dispatch('skip')\n      this.$track('Pairing', 'skip', 1)\n    },\n\n    startTimer() {\n      this.stopTimer()\n      this.countdown = PAIRING_TIMEOUT\n\n      interval = window.setInterval(() => {\n        this.countdown--\n\n        if (this.countdown <= 0) {\n          this.$emit('pairingTimeout')\n          this.stopTimer()\n        }\n      }, 1000)\n    },\n\n    stopTimer() {\n      if (interval) {\n        window.clearInterval(interval)\n      }\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n@import '@/assets/scss/components/_code.scss';\n\n.pairing-desktop {\n  overflow: hidden;\n  padding: 2rem 3vw;\n  padding-top: 84vw;\n  justify-content: center;\n  text-align: center;\n  transform-origin: center center;\n  &.is-desktop-animation {\n    text-align: left;\n    padding: 2rem 3vw;\n    z-index: 800;\n    align-items: center;\n    width: 50%;\n  }\n  @include media('md') {\n    padding: 0;\n  }\n  @include media('lg') {\n    padding: 0;\n  }\n}\n\n.pairing-container {\n  padding-bottom: 7vh;\n  position: relative;\n}\n\n.pairing__actions {\n  @include media('md') {\n    display: flex;\n    align-items: center;\n  }\n}\n\n.code--desktop {\n  display: flex;\n  justify-content: flex-start;\n  .code__item {\n    opacity: 0;\n    transform: scale(1.3);\n    transition: 0.55s cubic-bezier(0.79, -1.26, 0.21, 1.99);\n    span {\n      opacity: 0;\n      transition: 0.4s cubic-bezier(0.64, 0.1, 0.61, 1.18);\n      transform: scale(0.8);\n    }\n    .code-circle:before {\n      transition: 0.5s cubic-bezier(0.57, -0.26, 0.24, 1.08);\n      transform-origin: center;\n      transform: scaleX(0);\n    }\n    @for $i from 1 through 6 {\n      &:nth-child(#{$i}) {\n        transition-delay: ($i / 8) * 1s;\n        span {\n          transition-delay: (($i / 8) * 1s) + 0.32s;\n        }\n        .code-circle:before {\n          transition-delay: (($i / 8) * 1s) + 0.1s;\n        }\n      }\n    }\n    &.visible {\n      &,\n      span,\n      .code-circle:before {\n        opacity: 1;\n        transform: none;\n      }\n    }\n  }\n}\n\n.pairing-lead {\n  max-width: 23rem;\n  text-align: justify;\n  @include media('md') {\n    max-width: 29rem;\n  }\n  @include media('lg') {\n    max-width: 35rem;\n    margin: 0;\n  }\n}\n\n.code-timeout {\n  opacity: 0;\n  transition: 0.3s;\n  transition-delay: 0.3s;\n  span {\n    display: inline-block;\n    vertical-align: middle;\n  }\n  &.visible {\n    opacity: 1;\n  }\n}\n\n.pairing-skip {\n  margin-right: auto;\n  &:hover {\n    .btn {\n      color: $brand-color;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Button/Button.vue",
    "content": "<template>\n  <button\n    :class=\"classes\"\n    class=\"btn btn--bare pointer-area flex\"\n    @click=\"handleClick\"\n  >\n    <div class=\"toolbar-button\" :style=\"style\">\n      <icon v-if=\"hasIcon\" />\n    </div>\n  </button>\n</template>\n\n<script>\nimport { EventBus } from '@/events'\n\nimport ToolbarItem from '@/components/Desktop/Toolbar/Item.vue'\n\nexport default {\n  name: 'BrushToolbarTool',\n  extends: ToolbarItem,\n\n  data() {\n    return {\n      isActive: false\n    }\n  },\n\n  created() {\n    const eventName = 'pointerOver_' + this.itemKey\n    EventBus.$on(eventName, this.handleClick)\n  },\n\n  methods: {\n    handleClick() {\n      // Components extending this component implement this method.\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.toolbar-item {\n  .toolbar-button {\n    position: relative;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    overflow: visible;\n    font-size: $toolbar-button-width-xs;\n    color: $text-color;\n    width: 1em;\n    height: 100%;\n    margin: auto 0;\n\n    .is-drawing & {\n      @include media('sm') {\n        font-size: $toolbar-button-width-sm;\n      }\n\n      @include media('md') {\n        font-size: $toolbar-button-width-md;\n      }\n\n      @include media('lg') {\n        font-size: $toolbar-button-width-lg;\n      }\n    }\n\n    svg {\n      width: 0.6em;\n      max-height: 100%;\n      fill: currentColor;\n      .is-drawing & {\n        @include media('lg') {\n          width: 0.4em;\n        }\n      }\n    }\n  }\n  &.disabled {\n    opacity: 0.2;\n    cursor: default;\n  }\n  &.hover:not(.disabled):not(.toolbar-item--colors) {\n    background: $alt-color-dark !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Button/ButtonClear.vue",
    "content": "<script>\nimport { EventBus } from '@/events'\n\nimport Button from '@/components/Desktop/Toolbar/Button/Button.vue'\nimport Icon from '@/assets/icons/icon-delete.svg'\n\nexport default {\n  name: 'ButtonClear',\n\n  components: {\n    Icon\n  },\n  extends: Button,\n\n  data() {\n    return {\n      hasIcon: true,\n      possible: false\n    }\n  },\n\n  computed: {\n    additionalClasses() {\n      return this.possible ? [] : ['disabled']\n    }\n  },\n\n  beforeDestroy() {\n    EventBus.$on('canvasState', this.updateCanvasState)\n  },\n\n  mounted() {\n    EventBus.$on('canvasState', this.updateCanvasState)\n  },\n\n  methods: {\n    handleClick() {\n      EventBus.$emit('clearCanvas')\n      this.$track('Toolbar', 'history', 'clear')\n    },\n\n    updateCanvasState({ clearPossible }) {\n      this.possible = clearPossible\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Button/ButtonColor.vue",
    "content": "<script>\nimport Button from '@/components/Desktop/Toolbar/Button/Button.vue'\n\nimport { getRgbaString, shadeRgbColor } from '@/tools/helpers.js'\n\nimport threads from '@/store/vuetamin/threads'\n\nexport default {\n  name: 'ToolbarButtonColor',\n  extends: Button,\n\n  vuetamin: {\n    handleColorChange: [threads.BRUSH_COLOR]\n  },\n\n  computed: {\n    style() {\n      if (!this.tool.color) {\n        return {}\n      }\n\n      return {\n        background: this.tool.color.getRgbaString(100),\n        color: getRgbaString(shadeRgbColor(this.tool.color.rgb, -0.2), 0.5)\n      }\n    },\n\n    additionalClasses() {\n      return ['toolbar-item--color-' + this.tool.color.name]\n    }\n  },\n\n  methods: {\n    handleColorChange(state) {\n      this.isActive = this.tool.color.name === state.brush.color.name\n    },\n\n    handleClick() {\n      this.$vuetamin.store.mutate('updateBrushColor', this.tool.color)\n      this.$track('Toolbar', 'setColor', this.tool.color.name)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.toolbar-item--colors {\n  overflow: visible;\n  .toolbar-button {\n    border-radius: 100%;\n    width: 0.75em;\n    height: 0.75em;\n    @include media('lg') {\n      width: 0.5em;\n      height: 0.5em;\n    }\n\n    &:hover,\n    .hover & {\n      opacity: 0.75;\n    }\n  }\n\n  &.toolbar-item--color-black .toolbar-button {\n    box-shadow: 0 0 0 1px $alt-color;\n  }\n\n  &.active .toolbar-button {\n    box-shadow: 0 0px 0px 2px $alt-color-darker,\n      0 0px 0px 4px $alt-color-lighter;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Button/ButtonRedo.vue",
    "content": "<script>\nimport { EventBus } from '@/events'\n\nimport Button from '@/components/Desktop/Toolbar/Button/Button.vue'\nimport Icon from '@/assets/icons/icon-redo.svg'\n\nexport default {\n  name: 'ButtonRedo',\n\n  components: {\n    Icon\n  },\n  extends: Button,\n\n  data() {\n    return {\n      hasIcon: true,\n      possible: false\n    }\n  },\n\n  computed: {\n    additionalClasses() {\n      return this.possible ? [] : ['disabled']\n    }\n  },\n\n  beforeDestroy() {\n    EventBus.$on('canvasState', this.updateCanvasState)\n  },\n\n  mounted() {\n    EventBus.$on('canvasState', this.updateCanvasState)\n  },\n\n  methods: {\n    handleClick() {\n      if (this.possible) {\n        EventBus.$emit('redoCanvas')\n        this.$track('Toolbar', 'history', 'redo')\n      }\n    },\n    updateCanvasState({ redoPossible }) {\n      this.possible = redoPossible\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Button/ButtonUndo.vue",
    "content": "<script>\nimport { EventBus } from '@/events'\n\nimport Button from '@/components/Desktop/Toolbar/Button/Button.vue'\nimport Icon from '@/assets/icons/icon-undo.svg'\n\nexport default {\n  name: 'ButtonUndo',\n\n  components: {\n    Icon\n  },\n  extends: Button,\n\n  data() {\n    return {\n      hasIcon: true,\n      possible: false\n    }\n  },\n\n  computed: {\n    additionalClasses() {\n      return this.possible ? [] : ['disabled']\n    }\n  },\n\n  beforeDestroy() {\n    EventBus.$on('canvasState', this.updateCanvasState)\n  },\n\n  mounted() {\n    EventBus.$on('canvasState', this.updateCanvasState)\n  },\n\n  methods: {\n    handleClick() {\n      if (this.possible) {\n        EventBus.$emit('undoCanvas')\n        this.$track('Toolbar', 'history', 'undo')\n      }\n    },\n\n    updateCanvasState({ undoPossible }) {\n      this.possible = undoPossible\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Item.vue",
    "content": "<script>\nimport Rectangle from '@/classes/Rectangle'\n\nexport default {\n  name: 'ToolbarItem',\n\n  props: {\n    tool: {\n      type: Object,\n      required: true\n    },\n    action: {\n      type: String,\n      required: true\n    },\n    hoveredKey: {\n      type: String,\n      required: true\n    },\n    groupId: {\n      type: String,\n      required: true\n    }\n  },\n\n  data() {\n    return {\n      hasIcon: false\n    }\n  },\n\n  computed: {\n    itemKey() {\n      return `${this.action}${this.tool.id}`\n    },\n\n    isHovered() {\n      return this.itemKey === this.hoveredKey\n    },\n\n    classes() {\n      return [\n        {\n          hover: this.isHovered,\n          active: this.isActive\n        },\n        'toolbar-item',\n        'toolbar-item--' + this.groupId,\n        ...this.additionalClasses\n      ]\n    },\n\n    additionalClasses() {\n      return []\n    },\n\n    style() {\n      return {}\n    }\n  },\n\n  methods: {\n    getRectangle() {\n      const el = this.$el\n\n      // Easiest quick solution for getting the left offset in the toolbar.\n      const group = el.parentElement.parentElement.parentElement\n      const parentOffsetLeft = group.offsetLeft\n\n      const x = el.offsetLeft + parentOffsetLeft\n      const y = 0\n      const width = el.offsetWidth\n      const height = el.offsetHeight\n\n      const rectangle = new Rectangle(x, y, width, height)\n\n      return {\n        coords: rectangle,\n        key: this.itemKey,\n        el: this.$el\n      }\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Slider/Slider.vue",
    "content": "<template>\n  <div\n    class=\"btn btn--bare tool-slider pointer-area sm-pdg-- md-pdg- lg-pdg w-100\"\n    :class=\"classes\"\n    :style=\"style\"\n    @wheel=\"handleWheel\"\n  >\n    <div class=\"\">\n      <div class=\"tool-slider__text\">\n        <div class=\"tool-slider__label label pdg0 tool-slider__label\">\n          {{ $t('tools.' + tool.id) }}\n        </div>\n        <span\n          class=\"tool-slider__value label flex-1 text-muted text-light pdg0\"\n          >{{ roundedValue }}</span\n        >\n      </div>\n      <input\n        type=\"range\"\n        :min=\"min\"\n        :max=\"max\"\n        :step=\"step\"\n        :value=\"value\"\n        @input=\"handleInput\"\n      />\n    </div>\n  </div>\n</template>\n\n<script>\nimport ToolbarItem from '@/components/Desktop/Toolbar/Item.vue'\n\nlet timeout = null\n\nexport default {\n  name: 'ToolbarSlider',\n  extends: ToolbarItem,\n\n  data() {\n    return {\n      min: 0,\n      max: 100,\n      value: 0,\n      step: 1,\n      multiplier: 0.5\n    }\n  },\n\n  computed: {\n    roundedValue() {\n      return Math.round(this.value)\n    }\n  },\n\n  methods: {\n    handleWheel(e) {\n      const delta = Math.max(Math.min(e.deltaY, 6), -6)\n      const newValue =\n        Math.round(\n          Math.max(\n            Math.min(this.value - delta * this.multiplier, this.max),\n            this.min\n          ) * 100\n        ) / 100\n      this.handleValueChange(newValue)\n\n      if (!timeout) {\n        timeout = window.setTimeout(() => {\n          this.$track('Toolbar', this.tool.id, this.value)\n        }, 3000)\n      }\n    },\n\n    handleInput(e) {\n      this.handleValueChange(parseFloat(e.target.value))\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.btn.tool-slider {\n  text-align: left;\n  transition: 0.15s transform;\n  position: relative;\n  overflow: visible;\n  display: flex;\n  > div {\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n  }\n  input {\n    display: block;\n    margin: 0;\n    height: 4px;\n  }\n}\n\n.tool-slider__text {\n  margin-bottom: auto;\n  .is-drawing & {\n    @include media('lg') {\n      display: flex;\n    }\n  }\n}\n\n.tool-slider__value {\n  .is-drawing & {\n    @include media('lg') {\n      text-align: right;\n    }\n    @include media('xl', $breakpoints-extra) {\n      font-size: 1.5rem;\n      line-height: 1.15;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Slider/SliderBrushHardness.vue",
    "content": "<script>\nimport { HARDNESS_MIN, HARDNESS_MAX } from '@/settings'\nimport threads from '@/store/vuetamin/threads'\n\nimport Slider from '@/components/Desktop/Toolbar/Slider/Slider.vue'\n\nexport default {\n  name: 'SliderBrushHardness',\n  extends: Slider,\n\n  vuetamin: {\n    handleHardnessChange: [threads.BRUSH_HARDNESS]\n  },\n\n  data() {\n    return {\n      min: HARDNESS_MIN,\n      max: HARDNESS_MAX\n    }\n  },\n\n  methods: {\n    handleHardnessChange(state) {\n      if (this.value !== state.brush.hardness) {\n        this.value = state.brush.hardness\n      }\n    },\n\n    handleValueChange(value) {\n      this.$vuetamin.store.mutate('updateBrushHardness', value)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Slider/SliderBrushOpacity.vue",
    "content": "<script>\nimport { OPACITY_MIN, OPACITY_MAX } from '@/settings'\nimport threads from '@/store/vuetamin/threads'\n\nimport Slider from '@/components/Desktop/Toolbar/Slider/Slider.vue'\n\nexport default {\n  name: 'SliderBrushOpacity',\n  extends: Slider,\n\n  vuetamin: {\n    handleOpacityChange: [threads.BRUSH_OPACITY, threads.BRUSH]\n  },\n\n  data() {\n    return {\n      min: OPACITY_MIN,\n      max: OPACITY_MAX\n    }\n  },\n\n  methods: {\n    handleOpacityChange(state) {\n      if (this.value !== state.brush.opacity) {\n        this.value = state.brush.opacity\n      }\n    },\n\n    handleStateChange(state) {\n      if (this.value !== state.brush.opacity) {\n        this.value = state.brush.opacity\n      }\n    },\n\n    handleValueChange(value) {\n      this.$vuetamin.store.mutate('updateBrushOpacity', value)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Slider/SliderBrushRadius.vue",
    "content": "<script>\nimport { RADIUS_MIN, RADIUS_MAX } from '@/settings'\nimport threads from '@/store/vuetamin/threads'\n\nimport Slider from '@/components/Desktop/Toolbar/Slider/Slider.vue'\n\nexport default {\n  name: 'SliderBrushRadius',\n  extends: Slider,\n\n  vuetamin: {\n    handleRadiusChange: [threads.BRUSH_RADIUS]\n  },\n\n  data() {\n    return {\n      min: RADIUS_MIN,\n      max: RADIUS_MAX\n    }\n  },\n\n  methods: {\n    handleRadiusChange(state) {\n      if (this.value !== state.brush.radius) {\n        this.value = state.brush.radius\n      }\n    },\n\n    handleValueChange(value) {\n      this.$vuetamin.store.mutate('updateBrushRadius', value)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Slider/SliderDistance.vue",
    "content": "<script>\nimport threads from '@/store/vuetamin/threads'\n\nimport Slider from '@/components/Desktop/Toolbar/Slider/Slider.vue'\n\nimport { encodeEventMessage } from '@/tools/helpers'\n\nexport default {\n  name: 'SliderDistance',\n  extends: Slider,\n\n  vuetamin: {\n    handleSizesChange: [threads.SIZES, threads.DISTANCE]\n  },\n\n  data() {\n    return {\n      min: 0,\n      max: 100,\n      multiplier: 7\n    }\n  },\n\n  mounted() {\n    this.$peersox.send(\n      encodeEventMessage('distance', this.$vuetamin.store.data.gymoteDistance)\n    )\n  },\n\n  methods: {\n    handleSizesChange(state) {\n      this.min = state.sizes.viewport.width / 4\n      this.max = state.sizes.viewport.width * 2\n\n      if (this.value !== state.gymoteDistance) {\n        this.value = state.gymoteDistance\n      }\n    },\n\n    handleValueChange(value) {\n      this.value = value\n      this.$peersox.send(encodeEventMessage('distance', value))\n      this.$vuetamin.store.mutate('updateGymoteDistance', value)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Slider/SliderLazyRadius.vue",
    "content": "<script>\nimport { LAZY_RADIUS_MIN, LAZY_RADIUS_MAX } from '@/settings'\nimport threads from '@/store/vuetamin/threads'\n\nimport Slider from '@/components/Desktop/Toolbar/Slider/Slider.vue'\n\nexport default {\n  name: 'SliderLazyRadius',\n  extends: Slider,\n\n  vuetamin: {\n    handleLazyRadiusChange: [threads.LAZYRADIUS]\n  },\n\n  data() {\n    return {\n      min: LAZY_RADIUS_MIN,\n      max: LAZY_RADIUS_MAX,\n      multiplier: 1\n    }\n  },\n\n  methods: {\n    handleLazyRadiusChange(state) {\n      if (this.value !== state.lazyRadius) {\n        this.value = state.lazyRadius\n      }\n    },\n\n    handleValueChange(value) {\n      this.$vuetamin.store.mutate('updateLazyRadius', value)\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/components/Desktop/Toolbar/Toolbar.vue",
    "content": "<template>\n  <div ref=\"toolbar\" class=\"toolbar\">\n    <ul\n      class=\"list-inline list-inline--tight list-inline--divided toolbar-list flex--align-stretch\"\n    >\n      <li\n        v-for=\"group in toolGroups\"\n        :key=\"group.id\"\n        class=\"toolbar-group flex h-100\"\n        :class=\"[\n          'toolbar-group--' + group.id,\n          { 'flex-1': group.id === 'sliders' }\n        ]\"\n      >\n        <ul\n          class=\"list-inline list-inline--tight toolbar-group-list flex--align-stretch\"\n          :class=\"{\n            'flex-1 list-inline--divided': group.id === 'sliders'\n          }\"\n        >\n          <li\n            v-for=\"tool in group.items\"\n            :key=\"group.action + tool.id\"\n            class=\"flex flex--align-stretch h-100\"\n            :class=\"{ 'flex-1': group.id === 'sliders' }\"\n          >\n            <component\n              :is=\"tool.component\"\n              ref=\"items\"\n              :tool=\"tool\"\n              :group-id=\"group.id\"\n              :action=\"group.action\"\n              :hovered-key=\"toolBeingHovered\"\n            />\n          </li>\n        </ul>\n      </li>\n    </ul>\n  </div>\n</template>\n\n<script>\nimport ButtonClear from '@/components/Desktop/Toolbar/Button/ButtonClear.vue'\nimport ButtonUndo from '@/components/Desktop/Toolbar/Button/ButtonUndo.vue'\nimport ButtonRedo from '@/components/Desktop/Toolbar/Button/ButtonRedo.vue'\nimport ButtonColor from '@/components/Desktop/Toolbar/Button/ButtonColor.vue'\n\nimport SliderBrushRadius from '@/components/Desktop/Toolbar/Slider/SliderBrushRadius.vue'\nimport SliderBrushOpacity from '@/components/Desktop/Toolbar/Slider/SliderBrushOpacity.vue'\nimport SliderBrushHardness from '@/components/Desktop/Toolbar/Slider/SliderBrushHardness.vue'\nimport SliderLazyRadius from '@/components/Desktop/Toolbar/Slider/SliderLazyRadius.vue'\nimport SliderDistance from '@/components/Desktop/Toolbar/Slider/SliderDistance.vue'\n\nimport { COLORS, TOOLBAR_TOOLS, TOOLBAR_SLIDERS } from '@/settings'\nimport threads from '@/store/vuetamin/threads'\n\nimport { mapState } from 'vuex'\n\nimport Color from '@/classes/Color'\n\nexport default {\n  name: 'BrushToolbar',\n\n  components: {\n    ButtonColor,\n    ButtonClear,\n    ButtonUndo,\n    ButtonRedo,\n    SliderBrushRadius,\n    SliderBrushOpacity,\n    SliderBrushHardness,\n    SliderLazyRadius,\n    SliderDistance\n  },\n\n  vuetamin: {\n    calculatePointerAreas: [threads.SIZES],\n    handleToolsChange: [threads.TOOLS]\n  },\n\n  data() {\n    return {\n      pointerAreas: [],\n      toolBeingHovered: '',\n      lastItemClick: '',\n      wasPressingBefore: false,\n      wheelDelta: 0,\n      canvasFilterSupported: false\n    }\n  },\n\n  computed: {\n    ...mapState(['isSkipped', 'isConnected']),\n\n    toolGroups() {\n      return [\n        {\n          id: 'tools',\n          type: 'button',\n          action: 'tool',\n          items: TOOLBAR_TOOLS\n        },\n        {\n          id: 'colors',\n          type: 'button',\n          action: 'color',\n          items: COLORS.map((color) => {\n            return {\n              id: color.name,\n              component: 'ButtonColor',\n              color: new Color(color)\n            }\n          })\n        },\n        {\n          id: 'sliders',\n          type: 'slider',\n          action: 'brush',\n          items: TOOLBAR_SLIDERS.filter((tool) => {\n            if (tool.id === 'brushHardness' && !this.canvasFilterSupported) {\n              return false\n            }\n\n            if (tool.id === 'distance' && !this.isConnected) {\n              return false\n            }\n\n            return true\n          })\n        }\n      ]\n    }\n  },\n\n  mounted() {\n    this.calculatePointerAreas()\n  },\n\n  beforeDestroy() {},\n\n  methods: {\n    handleConnection({ connection }) {\n      this.connectionDevice = connection.device\n    },\n\n    handleToolsChange(state) {\n      let tool = this.getToolAtPoint(state.points.pointer)\n\n      this.toolBeingHovered = tool ? tool.key : ''\n\n      if (tool && state.isPressing) {\n        if (\n          this.lastItemClick !== this.toolBeingHovered &&\n          !this.wasPressingBefore\n        ) {\n          tool.el.click()\n          this.lastItemClick = tool.key\n        }\n\n        const wheel = state.touch.y - this.wheelDelta\n\n        if (wheel !== 0) {\n          let event = new WheelEvent('wheel', {\n            bubbles: false,\n            cancelable: true,\n            deltaX: 0,\n            deltaY: wheel / 2\n          })\n          tool.el.dispatchEvent(event)\n        }\n\n        this.wheelDelta = state.touch.y\n      } else {\n        this.wheelDelta = 0\n        this.lastItemClick = ''\n      }\n\n      this.wasPressingBefore = state.isPressing\n    },\n\n    getToolAtPoint(point) {\n      for (let i = 0; i < this.pointerAreas.length; i++) {\n        const area = this.pointerAreas[i]\n        if (area.coords.containsPoint(point)) {\n          return area\n        }\n      }\n    },\n    calculatePointerAreas() {\n      this.canvasFilterSupported = this.$vuetamin.store.data.canvasFilterSupported\n\n      let items = []\n      this.$refs.items.forEach((item) => {\n        items.push(item.getRectangle())\n      })\n\n      this.pointerAreas = items\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.toolbar {\n  border-bottom: $list-separator-style;\n  z-index: $index-toolbar;\n  position: absolute;\n  left: 0;\n  top: 0;\n  right: 0;\n  height: $toolbar-height - 1rem;\n  user-select: none;\n  background: $alt-color-darker;\n  @include media('md') {\n    height: $toolbar-height;\n  }\n}\n\n.toolbar-list {\n  height: 100%;\n}\n\n.toolbar-group-list {\n  position: relative;\n}\n\n.toolbar-group--colors {\n  .is-drawing & {\n    li {\n      @include media('sm') {\n        margin-right: 0.125rem;\n      }\n      @include media('md') {\n        margin-right: 0.5rem;\n      }\n      @include media('lg') {\n        margin-right: 0.5rem;\n      }\n      &:first-child {\n        @include media('sm') {\n          margin-left: rem(7px);\n        }\n        @include media('md') {\n          margin-left: rem(10px);\n        }\n        @include media('lg') {\n          margin-left: rem(13px);\n        }\n      }\n      &:last-child {\n        @include media('sm') {\n          margin-right: rem(7px);\n        }\n        @include media('md') {\n          margin-right: rem(10px);\n        }\n        @include media('lg') {\n          margin-right: rem(13px);\n        }\n      }\n    }\n  }\n}\n\n.toolbar-group--sliders {\n  li {\n    max-width: 18rem;\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Desktop.vue",
    "content": "<template>\n  <div class=\"desktop relative\">\n    <div class=\"desktop-container relative overlay material\">\n      <transition name=\"component-fade\">\n        <component\n          :is=\"visibleComponent\"\n          :is-desktop=\"true\"\n          :desktop-animation=\"desktopAnimation\"\n        >\n          <Pairing\n            :pairing=\"pairing\"\n            v-if=\"!isDrawing\"\n            :desktop-animation=\"desktopAnimation\"\n            @pairingTimeout=\"handleTimeout\"\n          />\n        </component>\n      </transition>\n    </div>\n  </div>\n</template>\n\n<script>\nimport debouncedResize from 'debounced-resize'\nimport { mapState } from 'vuex'\nimport { BREAKPOINT_REMOTE, ANIMATION_SCREEN_VIEWPORT } from '@/settings'\n\nimport Pairing from '@/components/Desktop/Pairing.vue'\nimport Animation from '@/components/Common/Animation/Animation.vue'\nimport Drawing from '@/components/Desktop/Drawing.vue'\nimport { getViewportSize, encodeEventMessage } from '@/tools/helpers'\n\nexport default {\n  name: 'Desktop',\n\n  components: {\n    Pairing,\n    Drawing,\n    Animation\n  },\n\n  data() {\n    return {\n      pairing: {},\n      viewportWidth: 700\n    }\n  },\n\n  computed: {\n    ...mapState(['isConnected', 'isSkipped']),\n\n    visibleComponent() {\n      return this.isDrawing ? 'Drawing' : 'Animation'\n    },\n\n    hasPairing() {\n      return this.pairing && this.pairing.code && this.pairing.hash\n    },\n\n    isDrawing() {\n      return this.isConnected || this.isSkipped\n    },\n\n    desktopAnimation() {\n      return this.viewportWidth >= 1024\n    }\n  },\n\n  watch: {\n    isConnected(isConnected) {\n      this.updateViewport()\n\n      if (!isConnected && !this.isSkipped) {\n        this.pairing = {}\n        this.getPairingCode()\n      }\n    },\n\n    isSkipped(isSkipped) {\n      this.updateViewport()\n\n      if (isSkipped && this.$peersox.isConnected()) {\n        this.$peersox.close()\n      }\n    }\n  },\n\n  beforeMount() {\n    this.viewportWidth = getViewportSize().width\n  },\n\n  mounted() {\n    this.updateViewport()\n\n    debouncedResize(() => {\n      this.updateViewport()\n    })\n\n    if (!this.$settings.isPrerendering) {\n      this.$peersox.on('serverReady', this.getPairingCode)\n    }\n\n    this.$peersox.on('peerConnected', this.handleConnected)\n    this.$peersox.on('peerTimeout', this.handlePeerTimeout)\n    this.$peersox.on('connectionClosed', this.handleDisconnected)\n\n    this.$peersox.onBinary = this.$mote.handleRemoteData.bind(this.$mote)\n  },\n\n  beforeDestroy() {\n    this.$peersox.off('serverReady', this.getPairingCode)\n    this.$peersox.off('peerConnected', this.handleConnected)\n    this.$peersox.off('peerTimeout', this.handlePeerTimeout)\n    this.$peersox.off('connectionClosed', this.handleDisconnected)\n\n    this.$peersox.onBinary = () => {}\n\n    this.$peersox.close()\n  },\n\n  methods: {\n    getPairingCode() {\n      if (this.hasPairing) {\n        return\n      }\n\n      if (this.$peersox.isConnected()) {\n        this.$peersox.close()\n      }\n\n      this.$peersox\n        .createPairing()\n        .then((pairing) => {\n          if (pairing) {\n            this.pairing = pairing\n            this.$peersox.connect(pairing).catch((error) => {\n              this.$store.commit('setServerStatus', error)\n              this.$sentry.logInfo('pairing', 'connect:failed')\n            })\n          } else {\n            this.pairing = {}\n            this.$sentry.logInfo('pairing', 'create:failed')\n          }\n          this.$store.commit('setServerStatus')\n        })\n        .catch((error) => {\n          this.$store.commit('setServerStatus', error)\n          this.$sentry.logInfo('pairing', 'create:failed')\n        })\n    },\n\n    updateViewport() {\n      let viewport = ANIMATION_SCREEN_VIEWPORT\n\n      if (this.isConnected || this.isSkipped) {\n        viewport = getViewportSize()\n      }\n\n      this.$vuetamin.store.mutate('updateViewport', viewport)\n      this.$peersox.send(encodeEventMessage('viewport', viewport))\n\n      if (!this.$peersox.isConnected()) {\n        this.isMobile = viewport.width < BREAKPOINT_REMOTE\n      }\n\n      this.$sentry.logInfo('viewport', JSON.stringify(viewport))\n    },\n\n    handleTimeout() {\n      this.pairing = {}\n      this.getPairingCode()\n    },\n\n    handlePeerTimeout() {\n      this.pairing = {}\n    },\n\n    handleConnected({ pairing }) {\n      this.$store.dispatch('connect')\n\n      this.updateViewport()\n      this.$peersox.storePairing(pairing)\n      this.$sentry.setUser(pairing.hash)\n    },\n\n    handleDisconnected() {\n      this.$store.dispatch('disconnect')\n    },\n\n    handleBinary(intArray) {\n      this.$mote.handleRemoteData(intArray)\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.desktop {\n  height: 100%;\n  overflow: hidden;\n}\n</style>\n"
  },
  {
    "path": "src/components/Mobile/Controlling.vue",
    "content": "<template>\n  <transition name=\"fade\">\n    <div class=\"controlling\">\n      <TouchHandler />\n    </div>\n  </transition>\n</template>\n\n<script>\nimport { DEFAULT_COLOR, RADIUS_DEFAULT } from '@/settings'\n\nimport { getViewportSize } from '@/tools/helpers.js'\n\nimport TouchHandler from '@/components/Mobile/TouchHandler.vue'\n\nexport default {\n  name: 'Mobile',\n  components: {\n    TouchHandler\n  },\n\n  data() {\n    return {\n      brushCoordinates: {\n        x: 0,\n        y: 0\n      },\n      brush: {\n        color: DEFAULT_COLOR,\n        radius: RADIUS_DEFAULT\n      }\n    }\n  },\n\n  mounted() {\n    const viewport = getViewportSize()\n    this.brushCoordinates = {\n      x: viewport.width / 2,\n      y: viewport.height / 2\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.controlling {\n  position: fixed;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  background: $alt-color-darker;\n  z-index: $index-footer - 1;\n  &.component-fade-enter-active,\n  &.component-fade-leave-active {\n    transition: 1.5s;\n  }\n  &.component-fade-enter,\n  &.component-fade-leave-to {\n    opacity: 0;\n    transform: translateY(6rem);\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Mobile/Pairing.vue",
    "content": "<template>\n  <div class=\"mobile-pairing\">\n    <div class=\"mobile-pairing__content relative pdgh\">\n      <h1 class=\"text-heavy mrgt\">drawmote</h1>\n      <p class=\"h2 text-bold mrgb text-muted\">{{ $t('subtitle') }}</p>\n      <p\n        class=\"h4 text-muted text-light text-hyphens mrgb md-mrgb+ mrgt- md-mrgt\"\n      >\n        {{ $t('mobile.lead') }}\n      </p>\n      <div class=\"code code--mobile relative\">\n        <ServerStatus v-if=\"hasServerError\" />\n        <div v-else class=\"code__circles flex\">\n          <div\n            v-for=\"(char, index) in inputChars\"\n            :key=\"char + index\"\n            class=\"code__item\"\n          >\n            <div\n              class=\"code-circle\"\n              :class=\"[\n                { contains: char !== ' ', invalid: char.search(/[0-9]/g) },\n                'code-circle--' + char\n              ]\"\n            >\n              <span>{{ char }}</span>\n            </div>\n          </div>\n        </div>\n\n        <form\n          v-if=\"!hasServerError\"\n          class=\"code__form absolute\"\n          @submit.prevent=\"onSubmit\"\n        >\n          <input\n            ref=\"pairing_id\"\n            v-model=\"inputValue\"\n            maxlength=\"6\"\n            class=\"code__input absolute\"\n            type=\"tel\"\n            pattern=\"[0-9]*\"\n            novalidate\n          />\n        </form>\n\n        <button\n          v-show=\"inputValue.length === 6\"\n          class=\"btn btn--primary btn--block mrgt+\"\n          @click.prevent=\"onSubmit\"\n        >\n          <span>{{ $t('mobile.pairButton') }}</span>\n        </button>\n\n        <transition name=\"appear\">\n          <div v-if=\"codeInvalid\" class=\"code__error\">\n            {{ $t('mobile.codeInvalid') }}\n          </div>\n        </transition>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script>\nimport ServerStatus from '@/components/Common/ServerStatus.vue'\nimport { mapGetters } from 'vuex'\n\nexport default {\n  name: 'Pairing',\n\n  components: {\n    ServerStatus\n  },\n\n  data() {\n    return {\n      inputValue: '',\n      codeInvalid: false\n    }\n  },\n\n  computed: {\n    ...mapGetters(['hasServerError']),\n\n    inputChars: function () {\n      return String(this.inputValue + '      ')\n        .slice(0, 6)\n        .split('')\n    }\n  },\n\n  watch: {\n    inputValue: function () {\n      this.codeInvalid = false\n    }\n  },\n\n  methods: {\n    onSubmit() {\n      const code = this.$refs.pairing_id.value\n      this.validateCode(code)\n    },\n\n    validateCode(code) {\n      this.$peersox\n        .joinPairing(code)\n        .then((pairing) => {\n          this.$peersox\n            .connect(pairing)\n            .then(() => {\n              this.codeInvalid = false\n              this.$track('Pairing', 'valid', '1')\n              this.$sentry.logInfo('pairing', 'code:valid')\n              this.$peersox.storePairing(pairing)\n              this.$store.commit('setServerStatus')\n            })\n            .catch((error) => {\n              this.$store.commit('setServerStatus', error)\n              this.$sentry.logInfo('pairing', 'connect:failed')\n            })\n        })\n        .catch((error) => {\n          this.$store.commit('setServerStatus', error)\n          this.$sentry.logInfo('pairing', 'code:invalid')\n          this.codeInvalid = true\n          this.$track('Pairing', 'valid', '0')\n        })\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n@import '@/assets/scss/components/_code.scss';\n\n.mobile-pairing {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  // text-align: center;\n  margin-bottom: $footer-height-xs;\n  user-select: none;\n  // min-height: calc(100vh - #{$footer-height-xs});\n  overflow: hidden;\n  padding-top: 80vw;\n}\n\n.mobile-pairing__content {\n  z-index: $index-mobile-pairing;\n  margin-bottom: auto;\n  padding-bottom: 2rem;\n}\n\n.code--mobile {\n  font-size: calc((100vw - 2rem) / 7);\n\n  .code__circles {\n    div {\n      font-size: 1em;\n    }\n  }\n\n  .code__form {\n    height: 1em;\n    right: -1rem;\n  }\n\n  .code__input {\n    background: none;\n    opacity: 0;\n    font-size: 0.7em;\n    line-height: 0;\n    width: 100%;\n    font-family: monospace;\n    letter-spacing: 1.12em;\n    padding-left: 0.1em;\n    margin-right: -1em;\n  }\n\n  .code__error {\n    margin-top: 1em;\n    font-weight: 600;\n    color: $brand-color;\n    font-size: 0.875rem;\n    letter-spacing: 0.5px;\n    padding: 0.25rem 0;\n    &.appear-enter-active,\n    &.appear-leave-active {\n      transition: 0.5s;\n    }\n    &.appear-enter,\n    &.appear-leave-to {\n      opacity: 0;\n      transform: translateY(50%);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Mobile/TouchHandler.vue",
    "content": "<template>\n  <div class=\"mobile-controller\">\n    <div\n      class=\"mobile-touch pdg\"\n      @touchstart=\"handleMainTouchStart\"\n      @touchmove=\"handleMainTouchMove\"\n      @touchend=\"handleMainTouchEnd\"\n      @touchcancel=\"handleMainTouchCancel\"\n    >\n      <div class=\"h3 text-muted\">{{ $t('mobile.controllingInfo') }}</div>\n      <div class=\"click-area\" :class=\"{ 'is-clicking': isClicking }\">\n        <div class=\"click-area__circle\"></div>\n      </div>\n    </div>\n\n    <div class=\"calibration pdg\">\n      <button class=\"btn btn--primary btn--block\" @click=\"calibrate\">\n        <span>{{ $t('mobile.calibrationButton') }}</span>\n      </button>\n    </div>\n  </div>\n</template>\n\n<script>\nexport default {\n  name: 'TouchHandler',\n\n  data() {\n    return {\n      isClicking: false,\n      touchStartY: 0,\n      touchStartTime: {},\n      touchDiffY: 0\n    }\n  },\n\n  watch: {\n    isClicking(isClicking) {\n      this.$mote.updateClick(isClicking)\n    },\n\n    touchDiffY(diffY) {\n      this.$mote.updateTouch({ x: 0, y: diffY })\n    }\n  },\n\n  methods: {\n    handleMainTouchStart(e) {\n      e.preventDefault()\n\n      this.touchDiffY = 0\n      this.touchStartY = 0\n      this.touchStartTime = new Date().getTime()\n\n      this.isClicking = true\n    },\n\n    handleMainTouchMove(e) {\n      e.preventDefault()\n\n      const diffTime = new Date().getTime() - this.touchStartTime\n      const touch = e.changedTouches[0]\n\n      if (diffTime > 50) {\n        if (this.touchStartY === 0) {\n          this.touchStartY = touch.pageY\n        }\n        this.touchDiffY = touch.pageY - this.touchStartY\n      }\n    },\n\n    handleMainTouchEnd(e) {\n      e.preventDefault()\n\n      this.isClicking = false\n    },\n\n    handleMainTouchCancel(e) {\n      e.preventDefault()\n\n      this.isClicking = false\n      this.touchDiffY = 0\n    },\n\n    calibrate() {\n      this.$mote.calibrate()\n    }\n  }\n}\n</script>\n\n<style lang=\"scss\">\n.mobile-controller {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n  padding-bottom: 53px;\n}\n.mobile-touch {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n}\n.calibration {\n  margin-top: auto;\n}\n\n.click-area {\n  display: flex;\n  align-items: center;\n  flex: 1;\n}\n\n.click-area__circle {\n  width: 70vw;\n  height: 70vw;\n  padding: 1rem;\n  margin: 0 auto;\n  border-radius: 100%;\n  border: 1px solid rgba($alt-color-darkest, 0.5);\n  background: linear-gradient(\n    lighten($alt-color-darkest, 0%),\n    lighten($alt-color-darkest, 2%)\n  );\n  position: relative;\n\n  &:before {\n    content: '';\n    position: absolute;\n    top: 1rem;\n    left: 1rem;\n    bottom: 1rem;\n    right: 1rem;\n    border-radius: inherit;\n\n    background: linear-gradient(\n      0deg,\n      lighten($alt-color-darkest, 2%),\n      lighten($alt-color-darker, 4%)\n    );\n    box-shadow: inset 0 3px 10px rgba($alt-color-dark, 0.15),\n      inset 0 4px 2px rgba($alt-color-dark, 0.1);\n  }\n  .is-clicking & {\n    &:before {\n      background: linear-gradient(\n        lighten($alt-color-darkest, 1%),\n        lighten($alt-color-darker, 9%)\n      );\n      box-shadow: 0 3px 10px rgba($alt-color-dark, 0.35),\n        0 4px 3px rgba($alt-color-dark, 0.5),\n        inset 0 4px 3px rgba($alt-color, 0.5);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "src/components/Mobile.vue",
    "content": "<template>\n  <div class=\"mobile h-100\">\n    <transition name=\"component-fade\">\n      <component :is=\"visibleComponent\">\n        <Pairing v-if=\"!isConnected\" />\n      </component>\n    </transition>\n  </div>\n</template>\n\n<script>\nimport Pairing from '@/components/Mobile/Pairing.vue'\nimport Animation from '@/components/Common/Animation/Animation.vue'\nimport Controlling from '@/components/Mobile/Controlling.vue'\n\nimport { mapState } from 'vuex'\nimport { decodeEventMessage } from '@/tools/helpers'\n\nexport default {\n  name: 'Mobile',\n\n  components: {\n    Pairing,\n    Animation,\n    Controlling\n  },\n\n  computed: {\n    ...mapState(['isConnected']),\n\n    visibleComponent() {\n      return this.isConnected ? 'Controlling' : 'Animation'\n    }\n  },\n\n  mounted() {\n    this.$peersox.on('peerConnected', this.handleConnected.bind(this))\n    this.$peersox.on('connectionClosed', this.handleConnectionClosed.bind(this))\n\n    this.$mote.start()\n\n    this.$peersox.onString = this.handleMessage.bind(this)\n    this.$mote._onDataChange = this.handleDataChange.bind(this)\n  },\n\n  beforeDestroy() {\n    this.$peersox.off('peerConnected', this.handleConnected)\n    this.$peersox.off('connectionClosed', this.handleConnectionClosed)\n\n    this.$peersox.onString = () => {}\n    this.$mote._onDataChange = () => {}\n  },\n\n  methods: {\n    handleConnected({ pairing }) {\n      this.$store.dispatch('connect')\n      this.$sentry.setUser(pairing.hash)\n    },\n\n    handleConnectionClosed() {\n      this.$store.dispatch('disconnect')\n      this.$mote.stop()\n    },\n\n    handleMessage(rawMessage) {\n      if (!this.isConnected) {\n        return\n      }\n\n      const { event, data } = decodeEventMessage(rawMessage)\n\n      if (event === 'viewport') {\n        this.$mote.updateScreenViewport(data)\n      }\n\n      if (event === 'distance') {\n        this.$mote.updateScreenDistance(data)\n      }\n    },\n\n    handleDataChange(data) {\n      if (this.isConnected) {\n        this.$peersox.send(data)\n      }\n    }\n  }\n}\n</script>\n"
  },
  {
    "path": "src/events/index.js",
    "content": "import Vue from 'vue'\nexport const EventBus = new Vue()\n"
  },
  {
    "path": "src/i18n.js",
    "content": "import { getLocale } from '@/tools/cookies'\n\nimport Vue from 'vue'\nimport VueI18n from 'vue-i18n'\n\nVue.use(VueI18n)\n\nfunction loadLocaleMessages() {\n  const locales = require.context(\n    './locales',\n    true,\n    /[A-Za-z0-9-_,\\s]+\\.json$/i\n  )\n  const messages = {}\n  locales.keys().forEach((key) => {\n    const matched = key.match(/([a-z0-9]+)\\./i)\n    if (matched && matched.length > 1) {\n      const locale = matched[1]\n      messages[locale] = locales(key)\n    }\n  })\n  return messages\n}\n\nfunction detectLanguage() {\n  const locale = getLocale()\n  if (locale) {\n    return locale\n  }\n\n  return window.navigator.language.split('-')[0] || 'en'\n}\n\nexport default new VueI18n({\n  locale: detectLanguage(),\n  fallbackLocale: 'en',\n  messages: loadLocaleMessages()\n})\n"
  },
  {
    "path": "src/locales/de.json",
    "content": "{\n  \"subtitle\": \"Telemalen mit deinem Telefon.\",\n  \"desktop\": {\n    \"lead\": \"Benutze dein Smartphone als Fernbedienung um hier zu malen. Öffne drawmote.app auf deinem Smartphone und gib den folgenden Code ein:\",\n    \"countdownPrefix\": \"Code ist noch \",\n    \"countdownSeconds\": \"0 Sekunden | 1 Sekunde | {count} Sekunden\",\n    \"countdownSuffix\": \" gültig.\",\n    \"nophone\": \"Ohne Smartphone verwenden\",\n    \"tooManyAttempts\": \"Es konnte kein Code generiert werden. Das kann passieren wenn zu viele Anfragen in kurzer Zeit ausgeführt werden. Bitte warte einen Moment und probiere es nochmal.\"\n  },\n  \"mobile\": {\n    \"lead\": \"Öffne drawmote.app auf deinem Desktop Browser um einen Verbindungscode zu generieren.\",\n    \"pairButton\": \"Mit Desktop verbinden\",\n    \"codeInvalid\": \"Der eingegebene Code ist leider ungültig :(\",\n    \"controllingInfo\": \"Bewege dein Smartphone und richte es auf deinen Bildschirm. Drücke den Kreis um zu malen. Bewege den Finger nach oben oder unten um Sliderwerte anzupassen.\",\n    \"calibrationButton\": \"Pinsel zentrieren\"\n  },\n  \"tools\": {\n    \"brushOpacity\": \"Deckkraft\",\n    \"brushRadius\": \"Radius\",\n    \"brushHardness\": \"Härte\",\n    \"lazyRadius\": \"Trägheit\",\n    \"distance\": \"Distanz\"\n  },\n  \"browserSupport\": {\n    \"title\": \"Kompatibilität\",\n    \"footer\": {\n      \"checking\": \"Prüfe Kompatibilität...\",\n      \"supported\": \"Gerät wird unterstützt\",\n      \"partial\": \"Reduzierte Funktionen\",\n      \"unsupported\": \"Wird nicht unterstützt\"\n    },\n    \"webRTC\": {\n      \"label\": \"WebRTC\",\n      \"supported\": \"Geringe Latenz zwischen beiden Geräten.\",\n      \"unsupported\": \"Dein Browser unterstützt WebRTC nicht. Das ist Voraussetzung für ein optimales Erlebnis.\"\n    },\n    \"webSocket\": {\n      \"label\": \"WebSocket\",\n      \"supported\": \"Dies wird als Alternative zu WebRTC verwendet. Die Latenz ist höher, was eine spürbare Verzögerung zur Folge hat.\",\n      \"unsupported\": \"Dein Browser unterstützt keine nativen WebSocket-Verbindungen. Wahrscheinlich wirst du diese App nicht nutzen können.\"\n    },\n    \"gyroscope\": {\n      \"label\": \"Gyroskop\",\n      \"supported\": \"Dieses Gerät kann zum Malen auf dem Computer verwendet werden.\",\n      \"unsupported\": \"Kein Gyroskop gefunden. Malen wird nicht möglich sein.\"\n    },\n    \"canvasFilter\": {\n      \"label\": \"Canvas Filter\",\n      \"supported\": \"Alle Pinsel können verwendet werden.\",\n      \"unsupported\": \"Dir stehen nicht alle Pinseloptionen zur Verfügung.\"\n    },\n    \"requestPermission\": {\n      \"cta\": \"Zugriff auf Gyroskop erlauben...\",\n      \"text\": \"drawmote benötigt Zugriff auf das Gyroskop deines Smartphones. Bitte klicke auf den folgenden Button und erlaube den Zugriff.\"\n    }\n  },\n  \"connection\": {\n    \"title\": \"Letzte Verbindung wiederherstellen\",\n    \"text\": \"Klicke bei beiden Geräten den grossen roten Button um die Verbindung wiederherzustellen.\",\n    \"delete\": \"Entfernen\",\n    \"reconnect\": \"Geräte verbinden\"\n  },\n  \"connectionTimeout\": {\n    \"title\": \"Zeitüberschreitung bei der Verbindung\",\n    \"text\": \"Smartphone und Computer scheinen nicht mehr verbunden zu sein.\",\n    \"toPairing\": \"Neue Verbindung\",\n    \"continueDrawing\": \"Ohne Smartphone fortfahren\"\n  },\n  \"footer\": {\n    \"copyright\": \"Entwickelt von\",\n    \"toPairing\": \"Zurück zum Start\",\n    \"disconnect\": \"Verbindung trennen\"\n  }\n}\n"
  },
  {
    "path": "src/locales/en.json",
    "content": "{\n  \"subtitle\": \"Draw remotely with your phone\",\n  \"desktop\": {\n    \"lead\": \"Visit drawmote.app on your phone, enter the following code and start drawing! For the best experience both devices should be in the same network.\",\n    \"countdownPrefix\": \"New code will be generated in \",\n    \"countdownSeconds\": \"0 seconds | 1 second | {count} seconds\",\n    \"countdownSuffix\": \".\",\n    \"nophone\": \"Use without phone\",\n    \"tooManyAttempts\": \"There was an error generating a pairing code. This can happen if too many requests to generate a code were made. Please wait a bit and try again later.\"\n  },\n  \"mobile\": {\n    \"lead\": \"Visit drawmote.app on your computer or tablet to get a pairing code and start drawing.\",\n    \"pairButton\": \"Pair with desktop\",\n    \"codeInvalid\": \"The entered code is not valid.\",\n    \"controllingInfo\": \"Point your phone at your screen and press the circle to draw or click a button. Slide up or down to change a slider.\",\n    \"calibrationButton\": \"Reset to center\"\n  },\n  \"tools\": {\n    \"brushOpacity\": \"opacity\",\n    \"brushRadius\": \"radius\",\n    \"brushHardness\": \"hardness\",\n    \"lazyRadius\": \"lazy radius\",\n    \"distance\": \"distance\"\n  },\n  \"browserSupport\": {\n    \"title\": \"Browser support\",\n    \"footer\": {\n      \"checking\": \"Checking compatibility...\",\n      \"supported\": \"Device is supported\",\n      \"partial\": \"Reduced functionality\",\n      \"unsupported\": \"Device not supported\"\n    },\n    \"webRTC\": {\n      \"label\": \"WebRTC\",\n      \"supported\": \"Low latency connection between both devices.\",\n      \"unsupported\": \"Unfortunately your browser doesn't support WebRTC. This is required for the best experience.\"\n    },\n    \"webSocket\": {\n      \"label\": \"WebSocket\",\n      \"supported\": \"This is used as a fallback to WebRTC. High latency is likely, resulting in a noticeable delay.\",\n      \"unsupported\": \"Your browser doesn't support native WebSocket connections. It's likely that you won't be able to use this app.\"\n    },\n    \"gyroscope\": {\n      \"label\": \"Gyroscope\",\n      \"supported\": \"Your phone can be used to draw on a screen.\",\n      \"unsupported\": \"No gyroscope could be detected. Drawing won't be possible.\"\n    },\n    \"canvasFilter\": {\n      \"label\": \"Canvas Filter\",\n      \"supported\": \"All brush features can be used.\",\n      \"unsupported\": \"You won't be able to use all brush features.\"\n    },\n    \"requestPermission\": {\n      \"cta\": \"Allow access to gyroscope...\",\n      \"text\": \"drawmote needs to access your device's gyroscope in order to word. Please click the button below and grant access.\"\n    }\n  },\n  \"connection\": {\n    \"title\": \"Restore your last connection\",\n    \"text\": \"Click the big red button on both devices to quickly reestablish a connection between them.\",\n    \"delete\": \"Remove\",\n    \"reconnect\": \"Reconnect devices\"\n  },\n  \"connectionTimeout\": {\n    \"title\": \"Connection Timeout\",\n    \"text\": \"Looks like the connection between your phone and computer has timed out.\",\n    \"toPairing\": \"Back to pairing\",\n    \"continueDrawing\": \"Continue drawing\"\n  },\n  \"footer\": {\n    \"copyright\": \"Made by\",\n    \"toPairing\": \"Back to pairing\",\n    \"disconnect\": \"Disconnect\"\n  }\n}\n"
  },
  {
    "path": "src/main.js",
    "content": "import 'es6-promise/auto'\nimport './assets/scss/main.scss'\n\nimport Vue from 'vue'\nimport App from './App.vue'\n\nimport Vuetamin from 'vuetamin'\nimport VueResize from 'vue-resize'\nimport Track from './plugins/Track'\nimport Settings from './plugins/Settings'\nimport PeerSox from './plugins/PeerSox'\nimport Sentry from './plugins/Sentry'\n\nimport vuetaminStore from './store/vuetamin'\nimport vuexStore from './store/vuex'\nimport i18n from './i18n'\n\nimport { getServerUrls } from '@/tools/helpers.js'\nimport { BREAKPOINT_REMOTE } from '@/settings'\n\nfunction getGymote() {\n  if (window.innerWidth > BREAKPOINT_REMOTE) {\n    return import('./plugins/GymoteScreen')\n  } else {\n    return import('./plugins/GymoteRemote')\n  }\n}\n\nVue.use(Sentry)\n\ngetGymote().then(({ default: Gymote }) => {\n  const serverUrls = getServerUrls()\n  Vue.use(Vuetamin, { store: vuetaminStore })\n  Vue.use(Gymote)\n  Vue.use(PeerSox, serverUrls)\n  Vue.use(Track)\n  Vue.use(Settings)\n  Vue.use(VueResize)\n\n  Vue.config.productionTip = false\n\n  new Vue({\n    store: vuexStore,\n    i18n,\n    render: (h) => h(App)\n  }).$mount('#drawmote')\n})\n"
  },
  {
    "path": "src/mixins/PointerEvents.js",
    "content": "import { EventBus } from '@/events'\n\nexport default {\n  name: 'PointerEvents',\n\n  methods: {\n    handleWheel(e) {\n      e.preventDefault()\n\n      if (e.deltaY > 0) {\n        EventBus.$emit('touchUp')\n      } else {\n        EventBus.$emit('touchDown')\n      }\n    },\n\n    handleMouseMove(e) {\n      this.preventEventIfRequired(e)\n\n      this.$vuetamin.store.mutate('updatePointer', {\n        coordinates: {\n          x: e.clientX,\n          y: e.clientY\n        }\n      })\n    },\n\n    handleMouseDown() {\n      this.$vuetamin.store.mutate('updateIsPressing', {\n        isPressing: true,\n        fromMouse: true\n      })\n    },\n\n    handleMouseUp() {\n      this.$vuetamin.store.mutate('updateIsPressing', {\n        isPressing: false,\n        fromMouse: true\n      })\n    },\n\n    handleTouchStart(e) {\n      this.preventEventIfRequired(e)\n      const touch = e.changedTouches[0]\n\n      this.$vuetamin.store.mutate('updatePointer', {\n        both: true,\n        coordinates: {\n          x: touch.pageX,\n          y: touch.pageY\n        }\n      })\n\n      this.handleMouseDown()\n    },\n\n    handleTouchMove(e) {\n      this.preventEventIfRequired(e)\n\n      const touch = e.changedTouches[0]\n\n      this.$vuetamin.store.mutate('updatePointer', {\n        coordinates: {\n          x: touch.pageX,\n          y: touch.pageY\n        }\n      })\n    },\n\n    handleTouchEnd(e) {\n      this.preventEventIfRequired(e)\n      this.handleMouseUp()\n    },\n\n    preventEventIfRequired(e) {\n      if (!this.$vuetamin.store.getState().pointingAtToolbar) {\n        e.preventDefault()\n      }\n    }\n  },\n\n  mounted() {\n    this.$el.addEventListener('wheel', this.handleWheel)\n\n    this.$el.addEventListener('mousedown', this.handleMouseDown)\n    this.$el.addEventListener('mousemove', this.handleMouseMove)\n    this.$el.addEventListener('mouseup', this.handleMouseUp)\n\n    this.$el.addEventListener('touchstart', this.handleTouchStart)\n    this.$el.addEventListener('touchmove', this.handleTouchMove)\n    this.$el.addEventListener('touchend', this.handleTouchEnd)\n  },\n\n  destroyed() {\n    this.$el.removeEventListener('wheel', this.handleWheel)\n\n    this.$el.removeEventListener('mousedown', this.handleMouseDown)\n    this.$el.removeEventListener('mousemove', this.handleMouseMove)\n    this.$el.removeEventListener('mouseup', this.handleMouseUp)\n\n    this.$el.removeEventListener('touchstart', this.handleTouchStart)\n    this.$el.removeEventListener('touchmove', this.handleTouchMove)\n    this.$el.removeEventListener('touchend', this.handleTouchEnd)\n  }\n}\n"
  },
  {
    "path": "src/plugins/GymoteRemote.js",
    "content": "import { GymoteRemote } from 'gymote'\n\nexport default {\n  install(Vue) {\n    Vue.prototype.$mote = new GymoteRemote()\n  }\n}\n"
  },
  {
    "path": "src/plugins/GymoteScreen.js",
    "content": "import { GymoteScreen } from 'gymote'\n\nexport default {\n  install(Vue) {\n    Vue.prototype.$mote = new GymoteScreen()\n  }\n}\n"
  },
  {
    "path": "src/plugins/PeerSox.js",
    "content": "import 'whatwg-fetch'\nimport PeerSox from 'peersox'\n\nexport default {\n  install(Vue, { api, wss }) {\n    Vue.prototype.$peersox = new PeerSox(api, {\n      debug: process.env.VUE_APP_SERVER_ENV !== 'production',\n      autoUpgrade: true,\n      socketServerUrl: wss\n    })\n  }\n}\n"
  },
  {
    "path": "src/plugins/Sentry.js",
    "content": "import * as Sentry from '@sentry/browser'\nimport * as Integrations from '@sentry/integrations'\n\nimport dependencies from '@/tools/dependencies'\nimport { trackUser, trackDimension } from '@/plugins/Track'\n\nconst isLocal = process.env.VUE_APP_SERVER_ENV === 'local'\nconst VERSION = `drawmote@${process.env.PKG_VERSION}`\n\nfunction log(category, data) {\n  // eslint-disable-next-line\n  console.log(`[${category}]`, data)\n}\n\nexport default {\n  install(Vue) {\n    const handler = {\n      setUser(user) {\n        log('user', user)\n      },\n      setMode(mode) {\n        log('mode', mode)\n      },\n      setSupport(feature, supportState) {\n        log('support', { feature, supportState })\n      },\n      logError(category, message) {\n        log('error', { category, message })\n      },\n      logInfo(category, message) {\n        log('info', { category, message })\n      }\n    }\n\n    if (!window.__PRERENDERING && !isLocal) {\n      Sentry.init({\n        dsn: 'https://b0df1bd1d041480f9e8e4dd2c3b56ed5@sentry.io/1342499',\n        release: VERSION,\n        environment: process.env.VUE_APP_SERVER_ENV,\n        integrations: [new Integrations.Vue({ Vue, attachProps: true })]\n      })\n\n      trackDimension('version', VERSION)\n\n      Sentry.configureScope((scope) => {\n        Object.keys(dependencies).forEach((key) => {\n          scope.setTag('library_' + key, dependencies[key])\n        })\n      })\n\n      handler.setUser = function (id) {\n        Sentry.configureScope((scope) => {\n          scope.setUser({ id: id })\n        })\n        trackUser(id)\n      }\n\n      handler.setMode = function (mode) {\n        Sentry.configureScope((scope) => {\n          scope.setTag('mode', mode)\n        })\n        trackDimension('mode', mode)\n      }\n\n      handler.setSupport = function (feature, supportState) {\n        Sentry.configureScope((scope) => {\n          scope.setTag('supports_' + feature, supportState)\n        })\n\n        if (feature === 'webRTC') {\n          trackDimension('supportsWebRTC', supportState)\n        }\n\n        if (feature === 'webSocket') {\n          trackDimension('supportsWebSocket', supportState)\n        }\n      }\n\n      handler.logError = function (category, message) {\n        Sentry.addBreadcrumb({\n          category: category,\n          message: message,\n          level: Sentry.Severity.Error\n        })\n      }\n\n      handler.logInfo = function (category, message) {\n        Sentry.addBreadcrumb({\n          category: category,\n          message: message,\n          level: Sentry.Severity.Info\n        })\n      }\n    }\n\n    Vue.prototype.$sentry = handler\n  }\n}\n"
  },
  {
    "path": "src/plugins/Settings.js",
    "content": "export default {\n  install(Vue) {\n    Vue.prototype.$settings = {\n      isPrerendering: window.__PRERENDERING === true\n    }\n  }\n}\n"
  },
  {
    "path": "src/plugins/Track.js",
    "content": "const DIMENSIONS = {\n  mode: 1,\n  supportsWebRTC: 2,\n  supportsWebSocket: 3,\n  version: 4\n}\n\nexport function trackEvent(category, action, value) {\n  try {\n    window._paq.push(['trackEvent', category, action, value])\n  } catch (e) {\n    // eslint-disable-next-line\n    console.log(e)\n  }\n}\n\nexport function trackUser(hash) {\n  try {\n    window._paq.push(['setUserId', hash])\n  } catch (e) {\n    // eslint-disable-next-line\n    console.log(e)\n  }\n}\n\nexport function trackDimension(dimension, value) {\n  try {\n    window._paq.push(['setCustomDimension', DIMENSIONS[dimension], value])\n  } catch (e) {\n    // eslint-disable-next-line\n    console.log(e)\n  }\n}\n\nexport default {\n  install(Vue) {\n    Vue.prototype.$track = trackEvent\n  }\n}\n"
  },
  {
    "path": "src/settings/index.js",
    "content": "export const COLORS = [\n  {\n    name: 'red',\n    hex: '#F06D31'\n  },\n  {\n    name: 'blue',\n    hex: '#48bec5'\n  },\n  {\n    name: 'green',\n    hex: '#97d779'\n  },\n  {\n    name: 'yellow',\n    hex: '#ffd52b'\n  },\n  {\n    name: 'white',\n    hex: '#f0f1b7'\n  },\n  {\n    name: 'black',\n    hex: '#2a192d'\n  }\n]\n\nexport const DEFAULT_COLOR = COLORS[3]\nexport const RADIUS_DEFAULT = 16\nexport const RADIUS_MIN = 1\nexport const RADIUS_MAX = 36\n\nexport const LAZY_RADIUS_MIN = 0\nexport const LAZY_RADIUS_MAX = 6 * RADIUS_MAX\nexport const LAZY_RADIUS_DEFAULT = 20\n\nexport const HARDNESS_DEFAULT = 100\nexport const HARDNESS_MIN = 0\nexport const HARDNESS_MAX = 100\n\nexport const OPACITY_DEFAULT = 100\nexport const OPACITY_MIN = 1\nexport const OPACITY_MAX = 100\n\n// export const SMOOTHING_INIT = 0.85\nexport const SMOOTHING_INIT = 1.3\n\nexport const SMUDGE_AMOUNT = 0.25\n\nexport const BRUSH_DEFAULT = {\n  color: DEFAULT_COLOR,\n  radius: RADIUS_DEFAULT,\n  hardness: HARDNESS_DEFAULT,\n  opacity: OPACITY_DEFAULT,\n  style: 'smudge'\n}\n\nexport const TOOLBAR_TOOLS = [\n  {\n    id: 'canvasClear',\n    component: 'ButtonClear'\n  },\n  {\n    id: 'undo',\n    component: 'ButtonUndo'\n  },\n  {\n    id: 'redo',\n    component: 'ButtonRedo'\n  }\n]\n\nexport const TOOLBAR_SLIDERS = [\n  {\n    id: 'brushOpacity',\n    component: 'SliderBrushOpacity',\n    icon: ''\n  },\n  {\n    id: 'brushRadius',\n    component: 'SliderBrushRadius',\n    icon: ''\n  },\n  {\n    id: 'brushHardness',\n    component: 'SliderBrushHardness',\n    icon: ''\n  },\n  {\n    id: 'lazyRadius',\n    component: 'SliderLazyRadius',\n    icon: ''\n  },\n  {\n    id: 'distance',\n    component: 'SliderDistance',\n    icon: ''\n  }\n]\n\nexport const BREAKPOINT_REMOTE = 700\nexport const BREAKPOINT_ANIMATION = 1024\n\nexport const ANIMATION_SCREEN_VIEWPORT = {\n  width: 960,\n  height: 540,\n  ratio: 0.75\n}\n"
  },
  {
    "path": "src/store/vuetamin/actions.js",
    "content": "import { setState } from '@/tools/cookies'\n\nexport function storeStateCookie({ data }, { noTimeout } = {}) {\n  const timeout = noTimeout ? 0 : 5000\n\n  window.clearTimeout(data.cookieTimout)\n  data.cookieTimout = window.setTimeout(() => {\n    setState({\n      brush: data.brush.state,\n      lazyRadius: data.lazyBrush.getRadius(),\n      gymoteDistance: data.gymoteDistance\n    })\n  }, timeout)\n}\n"
  },
  {
    "path": "src/store/vuetamin/data.js",
    "content": "import { LazyBrush } from 'lazy-brush'\nimport { getState } from '@/tools/cookies'\nimport Rectangle from '@/classes/Rectangle'\nimport Brush from '@/classes/Brush'\nimport { ANIMATION_SCREEN_VIEWPORT } from '@/settings'\n\n/**\n * Build the Vuetamin data store.\n *\n * @returns {Object}\n */\nexport default function () {\n  const cookieState = getState()\n  let brushOptions = {}\n  let lazyRadius = 80\n  let gymoteDistance = window.innerWidth\n\n  if (cookieState) {\n    brushOptions = cookieState.brush || {}\n    lazyRadius = cookieState.lazyRadius || lazyRadius\n    gymoteDistance = cookieState.gymoteDistance || gymoteDistance\n  }\n\n  return {\n    lazyBrush: new LazyBrush({\n      radius: lazyRadius\n    }),\n\n    brush: new Brush(brushOptions),\n\n    isPressing: false,\n    touch: {\n      x: 0,\n      y: 0\n    },\n\n    viewport: ANIMATION_SCREEN_VIEWPORT,\n\n    canvasRect: new Rectangle(0, 0, 0, 0),\n    toolbarRect: new Rectangle(0, 0, 0, 0),\n    footerRect: new Rectangle(0, 0, 0, 0),\n\n    pointingAtToolbar: false,\n    hasCalibrated: false,\n\n    cookieTimout: null,\n\n    canvasFilterSupported: false,\n\n    gymoteDistance: gymoteDistance\n  }\n}\n"
  },
  {
    "path": "src/store/vuetamin/index.js",
    "content": "import data from './data'\nimport state from './state'\nimport * as actions from './actions'\nimport * as mutations from './mutations'\n\nexport default {\n  data,\n  state,\n  actions,\n  mutations\n}\n"
  },
  {
    "path": "src/store/vuetamin/mutations.js",
    "content": "import threads from './threads'\n\nexport function updatePointer(\n  { data, trigger },\n  { coordinates, both = false } = {}\n) {\n  const updateBoth = both || data.hasCalibrated\n  const hasChanged = data.lazyBrush.update(coordinates, { both: updateBoth })\n\n  data.hasCalibrated = false\n\n  // TODO VERY HARD\n  if (hasChanged || updateBoth) {\n    trigger(threads.POINT)\n    if (\n      data.toolbarRect.containsPoint(data.lazyBrush.pointer) &&\n      (!data.isPressing || data.pointingAtToolbar)\n    ) {\n      data.pointingAtToolbar = true\n      trigger(threads.TOOLS)\n    } else {\n      if (data.pointingAtToolbar) {\n        trigger(threads.TOOLS)\n      }\n      if (!data.isPressing) {\n        data.pointingAtToolbar = false\n      }\n    }\n  }\n}\n\nexport function updateIsPressing(\n  { data, trigger },\n  { isPressing = false, fromMouse = false } = {}\n) {\n  if (data.isPressing !== isPressing) {\n    data.isPressing = isPressing\n    trigger(threads.POINT)\n\n    if (!fromMouse) {\n      trigger(threads.TOOLS)\n    }\n  }\n}\n\nexport function updateTouch({ data, trigger }, touch) {\n  if (data.touch.y !== touch.y && data.pointingAtToolbar) {\n    data.touch = touch\n    trigger(threads.TOOLS)\n  }\n}\n\nexport function updateCalibration({ data }) {\n  data.hasCalibrated = true\n}\n\nexport function updateCanvasRect({ data, trigger }, element) {\n  data.canvasRect.setFromElement(element)\n  trigger(threads.STATE)\n}\n\nexport function updateToolbarRect({ data, trigger }, element) {\n  data.toolbarRect.setFromElement(element)\n  trigger(threads.STATE)\n}\n\nexport function updateFooterRect({ data, trigger }, element) {\n  data.footerRect.setFromElement(element)\n  trigger(threads.STATE)\n}\n\nexport function updateViewport({ data, trigger }, viewport) {\n  data.viewport = viewport\n  trigger(threads.STATE)\n  trigger(threads.SIZES)\n}\n\nexport function updateUseLazyBrush({ data }, useLazyBrush) {\n  if (useLazyBrush) {\n    data.lazyBrush.enable()\n  } else {\n    data.lazyBrush.disable()\n  }\n}\n\nexport function updateLazyRadius({ data, trigger, action }, radius) {\n  data.lazyBrush.setRadius(radius)\n  trigger(threads.BRUSH)\n  trigger(threads.LAZYRADIUS)\n  action('storeStateCookie')\n}\n\nexport function updateBrushColor({ data, trigger, action }, color) {\n  data.brush.setColor(color)\n  trigger(threads.BRUSH)\n  trigger(threads.BRUSH_COLOR)\n  action('storeStateCookie')\n}\n\nexport function updateBrushOpacity({ data, trigger, action }, opacity) {\n  data.brush.setOpacity(opacity)\n  trigger(threads.BRUSH)\n  trigger(threads.BRUSH_OPACITY)\n  action('storeStateCookie')\n}\n\nexport function updateBrushRadius({ data, trigger, action }, radius) {\n  data.brush.setRadius(radius)\n  trigger(threads.BRUSH)\n  trigger(threads.BRUSH_RADIUS)\n  action('storeStateCookie')\n}\n\nexport function updateBrushHardness({ data, trigger, action }, hardness) {\n  data.brush.setHardness(hardness)\n  trigger(threads.BRUSH)\n  trigger(threads.BRUSH_HARDNESS)\n  action('storeStateCookie')\n}\n\nexport function updateGymoteDistance({ data, action }, distance) {\n  data.gymoteDistance = distance\n  action('storeStateCookie')\n}\n\nexport function updateCanvasFilterSupport({ data }, isSupported) {\n  data.canvasFilterSupported = isSupported\n  data.brush.setFilterSupport(isSupported)\n}\n"
  },
  {
    "path": "src/store/vuetamin/state.js",
    "content": "/**\n * Returns the state from the Vuetamin data.\n *\n * @param {Object} data Vuetamin data.\n * @returns {Object} The state.\n */\nexport default function (data) {\n  return {\n    brush: data.brush,\n    isPressing: data.isPressing,\n    lazyRadius: data.lazyBrush.radius,\n    sizes: {\n      viewport: data.viewport,\n      canvasRect: data.canvasRect,\n      toolbarRect: data.toolbarRect,\n      footerRect: data.footerRect\n    },\n    points: {\n      brush: data.lazyBrush.brush.toObject(),\n      pointer: data.lazyBrush.pointer.toObject()\n    },\n    touch: data.touch,\n    pointingAtToolbar: data.pointingAtToolbar,\n    gymoteDistance: data.gymoteDistance\n  }\n}\n"
  },
  {
    "path": "src/store/vuetamin/threads.js",
    "content": "/**\n * Vuetamin thread names.\n */\nexport default {\n  BRUSH: 'brush',\n  POINT: 'point',\n  SLIDE: 'slide',\n  STATE: 'state',\n  TOOLS: 'tools',\n  SIZES: 'sizes',\n  BRUSH_RADIUS: 'brushRadius',\n  BRUSH_OPACITY: 'brushOpacity',\n  BRUSH_HARDNESS: 'brushHardness',\n  BRUSH_COLOR: 'brushColor',\n  LAZYRADIUS: 'lazyRadius',\n  DISTANCE: 'distance',\n  CONNECTION: 'connection'\n}\n"
  },
  {
    "path": "src/store/vuex/index.js",
    "content": "import Vue from 'vue'\nimport Vuex from 'vuex'\n\nVue.use(Vuex)\n\nexport default new Vuex.Store({\n  state: {\n    isSkipped: false,\n    isConnected: false,\n    serverStatus: {\n      ok: true,\n      status: 0,\n      statusText: ''\n    },\n    introPlayed: false,\n    attributionVisible: false\n  },\n  mutations: {\n    setServerStatus(state, status = {}) {\n      state.serverStatus.ok = status.ok || true\n      state.serverStatus.status = status.status || 0\n      state.serverStatus.statusText = status.statusText || ''\n    },\n\n    setSkipped(state, isSkipped) {\n      state.isSkipped = isSkipped\n    },\n\n    setConnected(state, isConnected) {\n      state.isConnected = isConnected\n    },\n\n    setIntroPlayed(state, introPlayed) {\n      state.introPlayed = introPlayed\n    },\n\n    setAttributionVisible(state, isVisible) {\n      state.attributionVisible = isVisible\n    }\n  },\n\n  actions: {\n    connect({ commit }) {\n      commit('setSkipped', false)\n      commit('setConnected', true)\n    },\n\n    disconnect({ commit }) {\n      commit('setConnected', false)\n    },\n\n    skip({ commit }) {\n      commit('setSkipped', true)\n    },\n\n    unskip({ commit }) {\n      commit('setSkipped', false)\n    },\n\n    toggleAttributionVisibility({ state, commit }) {\n      commit('setAttributionVisible', !state.attributionVisible)\n    }\n  },\n\n  getters: {\n    hasServerError(state) {\n      return state.serverStatus.ok === false\n    },\n\n    isDrawing(state) {\n      return state.isConnected || state.isSkipped\n    }\n  }\n})\n"
  },
  {
    "path": "src/tools/animation/app.json",
    "content": "{\n\t\"metadata\": {\n\t\t\"type\": \"App\"\n\t},\n\t\"project\": {\n\t\t\"shadows\": true,\n\t\t\"vr\": false\n\t},\n\t\"camera\": {\n\t\t\"metadata\": {\n\t\t\t\"version\": 4.5,\n\t\t\t\"type\": \"Object\",\n\t\t\t\"generator\": \"Object3D.toJSON\"\n\t\t},\n\t\t\"object\": {\n\t\t\t\"uuid\": \"E51C1918-0B8A-42A7-AC88-5DA9A9103C5A\",\n\t\t\t\"type\": \"PerspectiveCamera\",\n\t\t\t\"name\": \"Camera\",\n\t\t\t\"layers\": 1,\n\t\t\t\"matrix\": [0.778004,0,-0.628258,0,-0.06467,0.99469,-0.080084,0,0.624922,0.102935,0.773872,0,9.725295,0.209866,13.958911,1],\n\t\t\t\"fov\": 50,\n\t\t\t\"zoom\": 1,\n\t\t\t\"near\": 0.36,\n\t\t\t\"far\": 1000,\n\t\t\t\"focus\": 10,\n\t\t\t\"aspect\": 1.469649,\n\t\t\t\"filmGauge\": 35,\n\t\t\t\"filmOffset\": 0\n\t\t}\n\t},\n\t\"scene\": {\n\t\t\"metadata\": {\n\t\t\t\"version\": 4.5,\n\t\t\t\"type\": \"Object\",\n\t\t\t\"generator\": \"Object3D.toJSON\"\n\t\t},\n\t\t\"geometries\": [\n\t\t\t{\n\t\t\t\t\"uuid\": \"D7227094-70CB-489B-B20D-E5ACC790A53A\",\n\t\t\t\t\"type\": \"PlaneBufferGeometry\",\n\t\t\t\t\"width\": 4096,\n\t\t\t\t\"height\": 4096,\n\t\t\t\t\"widthSegments\": 1,\n\t\t\t\t\"heightSegments\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"FB0A18E0-4EC7-403E-B939-867DBFED1864\",\n\t\t\t\t\"type\": \"PlaneBufferGeometry\",\n\t\t\t\t\"width\": 8192,\n\t\t\t\t\"height\": 8192,\n\t\t\t\t\"widthSegments\": 1,\n\t\t\t\t\"heightSegments\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"7199EAA3-8D36-426E-9BB2-5D38D9BFF238\",\n\t\t\t\t\"type\": \"CylinderBufferGeometry\",\n\t\t\t\t\"radiusTop\": 0.01,\n\t\t\t\t\"radiusBottom\": 0.01,\n\t\t\t\t\"height\": 11.22,\n\t\t\t\t\"radialSegments\": 30,\n\t\t\t\t\"heightSegments\": 1,\n\t\t\t\t\"openEnded\": false\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"6FF753BE-ACD3-48F9-8324-819EC5AAAF47\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [-0.600751,0.78998,-0.122596,-0.600751,0.78998,-0.122596,-0.600751,0.78998,-0.122596,-0.600751,0.78998,-0.122596],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [-3.047849,4.350529,74.612076,-2.990534,4.262561,73.764374,-7.832444,0.587624,73.810509,-7.889759,0.675592,74.658211],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,0,2,3]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [-5.440146,2.469076,74.211292],\n\t\t\t\t\t\t\"radius\": 3.069785\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"676CF2FA-78A4-4A8F-B7C9-00A0243C5EBF\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [-0.677632,0.723664,-0.13086,-0.948643,0.300427,-0.099091,-0.885243,0.454288,-0.09984,-0.676472,0.730662,-0.092299,-0.976206,-0.209188,-0.057123,-0.997622,-0.050712,-0.046666,-0.852419,-0.522343,-0.023209,-0.891348,-0.453298,0.004323,-0.676407,-0.736435,0.011717,-0.73207,-0.680088,0.039419,-0.316631,-0.946642,0.060111,-0.463351,-0.883258,0.071851,0.20063,-0.973823,0.106844,0.035388,-0.993473,0.108434,0.550787,-0.828134,0.104055,0.47974,-0.866772,0.136218,-0.892548,0.310258,-0.327259,-0.677632,0.723664,-0.13086,-0.87811,-0.136184,-0.458669,-0.732959,-0.460568,-0.500648,-0.56654,-0.67802,-0.468317,-0.274743,-0.873927,-0.400958,0.148276,-0.962373,-0.227709,0.550787,-0.828134,0.104055,-0.728839,0.399214,-0.556257,-0.677632,0.723664,-0.13086,-0.609277,0.016403,-0.792788,-0.419874,-0.278817,-0.863694,-0.252998,-0.495831,-0.830749,0.012336,-0.703646,-0.710443,0.343456,-0.84289,-0.414215,0.550787,-0.828134,0.104055,-0.532455,0.619026,-0.577321,-0.677632,0.723664,-0.13086,-0.32292,0.267575,-0.907814,-0.101942,-0.041678,-0.993917,0.063114,-0.26281,-0.962781,0.281201,-0.498145,-0.82023,0.516384,-0.724446,-0.456646,0.550787,-0.828134,0.104055,-0.518921,0.841264,-0.151646,-0.440264,0.60909,-0.65968,-0.532455,0.619026,-0.577321,-0.677632,0.723664,-0.13086,-0.161541,0.316805,-0.934633,-0.32292,0.267575,-0.907814,0.070429,0.060437,-0.995684,-0.101942,-0.041678,-0.993917,0.235086,-0.160091,-0.9587,0.063114,-0.26281,-0.962781,0.438187,-0.442249,-0.782565,0.281201,-0.498145,-0.82023,0.596529,-0.716129,-0.36237,0.516384,-0.724446,-0.456646,0.709311,-0.696814,0.106434,0.550787,-0.828134,0.104055,-0.272972,0.736084,-0.619409,-0.518921,0.841264,-0.151646,0.092969,0.544061,-0.833879,0.369144,0.319812,-0.872613,0.535902,0.102979,-0.837976,0.709951,-0.168976,-0.683679,0.791333,-0.49393,-0.360313,0.709311,-0.696814,0.106434,-0.102982,0.89372,-0.436645,-0.518921,0.841264,-0.151646,0.338211,0.777743,-0.529839,0.633661,0.576298,-0.516096,0.799883,0.359146,-0.480835,0.933565,0.052145,-0.354594,0.924024,-0.357956,-0.134342,0.709311,-0.696814,0.106434,-0.199629,0.972303,-0.121551,-0.518921,0.841264,-0.151646,0.318796,0.943361,-0.091863,0.678859,0.732719,-0.047686,0.85498,0.518462,-0.014337,0.977841,0.206708,0.033142,0.949096,-0.301109,0.09247,0.709311,-0.696814,0.106434,-0.517636,0.848098,-0.11306,-0.03584,0.994158,-0.101814,-0.199629,0.972303,-0.121551,-0.518921,0.841264,-0.151646,0.461714,0.885736,-0.04787,0.318796,0.943361,-0.091863,0.72951,0.683967,-0.001873,0.678859,0.732719,-0.047686,0.888897,0.457013,0.031646,0.85498,0.518462,-0.014337,0.995457,0.053994,0.078418,0.977841,0.206708,0.033142,0.88424,-0.452767,0.114547,0.949096,-0.301109,0.09247,0.648135,-0.748432,0.140611,0.709311,-0.696814,0.106434,-0.094387,0.987549,0.125853,-0.517636,0.848098,-0.11306,0.305985,0.888521,0.341912,0.567318,0.678385,0.466846,0.732179,0.460207,0.502119,0.846871,0.121591,0.517711,0.838658,-0.335433,0.429114,0.648135,-0.748432,0.140611,-0.258097,0.898591,0.354851,-0.517636,0.848098,-0.11306,0.037151,0.735935,0.676032,0.254233,0.496636,0.82989,0.418639,0.278015,0.864551,0.559792,-0.048698,0.827201,0.643479,-0.454916,0.615618,0.648135,-0.748432,0.140611,-0.524469,0.770815,0.361632,-0.517636,0.848098,-0.11306,-0.297858,0.548739,0.781131,-0.078482,0.278929,0.957099,0.090074,0.061369,0.994043,0.273771,-0.23163,0.933486,0.476697,-0.581445,0.659304,0.648135,-0.748432,0.140611,-0.676472,0.730662,-0.092299,-0.552814,0.696802,0.457017,-0.524469,0.770815,0.361632,-0.517636,0.848098,-0.11306,-0.393428,0.412974,0.821381,-0.297858,0.548739,0.781131,-0.223617,0.141007,0.964423,-0.078482,0.278929,0.957099,-0.054662,-0.077157,0.995519,0.090074,0.061369,0.994043,0.182592,-0.374068,0.909249,0.273771,-0.23163,0.933486,0.460392,-0.673713,0.578057,0.476697,-0.581445,0.659304,0.47974,-0.866772,0.136218,0.648135,-0.748432,0.140611,-0.713959,0.561725,0.418005,-0.676472,0.730662,-0.092299,-0.665097,0.208269,0.717126,-0.534787,-0.101998,0.838808,-0.370264,-0.320797,0.871776,-0.137827,-0.58336,0.800434,0.195601,-0.803875,0.561716,0.47974,-0.866772,0.136218,-0.883959,0.404081,0.235234,-0.676472,0.730662,-0.092299,-0.910338,-0.025404,0.413085,-0.799302,-0.358482,0.482294,-0.634241,-0.576962,0.514639,-0.361438,-0.804482,0.47135,0.06291,-0.93985,0.335744,0.47974,-0.866772,0.136218,-0.885243,0.454288,-0.09984,-0.676472,0.730662,-0.092299,-0.997622,-0.050712,-0.046666,-0.891348,-0.453298,0.004323,-0.73207,-0.680088,0.039419,-0.463351,-0.883258,0.071851,0.035388,-0.993473,0.108434,0.47974,-0.866772,0.136218,0.550787,-0.828134,0.104055,0.709311,-0.696814,0.106434,0.47974,-0.866772,0.136218,-0.518921,0.841264,-0.151646,-0.677632,0.723664,-0.13086,-0.676472,0.730662,-0.092299],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [8.655041,12.545126,57.205441,8.523679,12.376444,57.213913,8.341802,12.652069,59.881222,8.473164,12.820751,59.872746,8.494052,12.165463,57.233696,8.312175,12.441089,59.901001,8.5741,11.968718,57.259483,8.392223,12.244344,59.926792,8.683826,11.82443,57.281876,8.501949,12.100055,59.949184,8.852099,11.694635,57.306763,8.670222,11.970262,59.974068,9.06351,11.666573,57.324078,8.881633,11.942198,59.991383,9.26141,11.747759,57.329182,9.079533,12.023384,59.99649,8.552728,12.382585,57.111134,8.655041,12.545126,57.205441,8.544367,12.1761,57.055676,8.632199,11.980999,57.053925,8.741925,11.836711,57.076313,8.902414,11.705272,57.128742,9.092559,11.672713,57.221298,9.26141,11.747759,57.329182,8.618106,12.420555,57.035442,8.655041,12.545126,57.205441,8.657606,12.241868,56.924572,8.762956,12.056942,56.902538,8.872682,11.912653,56.924931,9.015654,11.77104,56.997639,9.157937,11.710685,57.145607,9.26141,11.747759,57.329182,8.702295,12.480184,57.007118,8.655041,12.545126,57.205441,8.803426,12.345146,56.875519,8.931334,12.176197,56.845898,9.04106,12.031908,56.868286,9.161473,11.874318,56.948586,9.242126,11.770312,57.117283,9.26141,11.747759,57.329182,8.920732,12.746753,57.202721,8.967987,12.68181,57.004402,8.702295,12.480184,57.007118,8.655041,12.545126,57.205441,9.069118,12.546773,56.872799,8.803426,12.345146,56.875519,9.197025,12.377824,56.843178,8.931334,12.176197,56.845898,9.306752,12.233536,56.86557,9.04106,12.031908,56.868286,9.427165,12.075945,56.945866,9.161473,11.874318,56.948586,9.507818,11.971939,57.114567,9.242126,11.770312,57.117283,9.527102,11.949386,57.326466,9.26141,11.747759,57.329182,9.048429,12.747117,57.03104,8.920732,12.746753,57.202721,9.208447,12.659887,56.918938,9.357909,12.508438,56.896454,9.467635,12.364149,56.918846,9.566494,12.189059,56.992004,9.58826,12.037246,57.141201,9.527102,11.949386,57.326466,9.103569,12.800604,57.105499,8.920732,12.746753,57.202721,9.303951,12.75253,57.047901,9.468188,12.615412,57.045368,9.577915,12.471123,57.06776,9.661999,12.281702,57.120972,9.6434,12.090733,57.21566,9.527102,11.949386,57.326466,9.118632,12.82794,57.207825,8.920732,12.746753,57.202721,9.330042,12.799876,57.225143,9.498316,12.670083,57.250027,9.608042,12.525794,57.272419,9.688089,12.329048,57.29821,9.658463,12.118069,57.317989,9.527102,11.949386,57.326466,8.738855,13.022378,59.870029,8.936755,13.103565,59.875134,9.118632,12.82794,57.207825,8.920732,12.746753,57.202721,9.148165,13.075501,59.892448,9.330042,12.799876,57.225143,9.316438,12.945708,59.917336,9.498316,12.670083,57.250027,9.426165,12.801419,59.939728,9.608042,12.525794,57.272419,9.506212,12.604673,59.965515,9.688089,12.329048,57.29821,9.476586,12.393694,59.985298,9.658463,12.118069,57.317989,9.345223,12.225012,59.993771,9.527102,11.949386,57.326466,8.907705,13.097425,59.977913,8.738855,13.022378,59.870029,9.09785,13.064866,60.070469,9.25834,12.933427,60.122894,9.368066,12.789138,60.145287,9.455897,12.594038,60.143536,9.447536,12.387553,60.088078,9.345223,12.225012,59.993771,8.842327,13.059453,60.053604,8.738855,13.022378,59.870029,8.984612,12.999098,60.201572,9.127583,12.857485,60.274281,9.237309,12.713196,60.296673,9.342659,12.52827,60.274639,9.382158,12.349582,60.163769,9.345223,12.225012,59.993771,8.758138,12.999825,60.081928,8.738855,13.022378,59.870029,8.838792,12.89582,60.250626,8.959205,12.738229,60.330921,9.068931,12.593941,60.353313,9.196839,12.424992,60.323692,9.297969,12.289954,60.192093,9.345223,12.225012,59.993771,8.473164,12.820751,59.872746,8.492446,12.798199,60.084644,8.758138,12.999825,60.081928,8.738855,13.022378,59.870029,8.5731,12.694193,60.253345,8.838792,12.89582,60.250626,8.693513,12.536602,60.333641,8.959205,12.738229,60.330921,8.803239,12.392313,60.356033,9.068931,12.593941,60.353313,8.931148,12.223365,60.326412,9.196839,12.424992,60.323692,9.032278,12.088327,60.194809,9.297969,12.289954,60.192093,9.079533,12.023384,59.99649,9.345223,12.225012,59.993771,8.412004,12.732892,60.05801,8.473164,12.820751,59.872746,8.433771,12.581078,60.207207,8.53263,12.405989,60.280365,8.642356,12.2617,60.302757,8.791819,12.11025,60.280273,8.951836,12.023021,60.168171,9.079533,12.023384,59.99649,8.356865,12.679404,59.983551,8.473164,12.820751,59.872746,8.338265,12.488436,60.078239,8.42235,12.299014,60.131451,8.532076,12.154726,60.153839,8.696313,12.017608,60.15131,8.896696,11.969533,60.093712,9.079533,12.023384,59.99649,8.341802,12.652069,59.881222,8.473164,12.820751,59.872746,8.312175,12.441089,59.901001,8.392223,12.244344,59.926792,8.501949,12.100055,59.949184,8.670222,11.970262,59.974068,8.881633,11.942198,59.991383,9.079533,12.023384,59.99649,9.26141,11.747759,57.329182,9.527102,11.949386,57.326466,9.079533,12.023384,59.99649,8.920732,12.746753,57.202721,8.655041,12.545126,57.205441,8.473164,12.820751,59.872746],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,0,2,3,1,4,5,1,5,2,4,6,7,4,7,5,6,8,9,6,9,7,8,10,11,8,11,9,10,12,13,10,13,11,12,14,15,12,15,13,16,1,17,16,18,4,16,4,1,18,19,6,18,6,4,19,20,8,19,8,6,20,21,10,20,10,8,21,22,12,21,12,10,22,23,12,24,16,25,24,26,18,24,18,16,26,27,19,26,19,18,27,28,20,27,20,19,28,29,21,28,21,20,29,30,22,29,22,21,30,31,22,32,24,33,32,34,26,32,26,24,34,35,27,34,27,26,35,36,28,35,28,27,36,37,29,36,29,28,37,38,30,37,30,29,38,39,30,40,41,42,40,42,43,41,44,45,41,45,42,44,46,47,44,47,45,46,48,49,46,49,47,48,50,51,48,51,49,50,52,53,50,53,51,52,54,55,52,55,53,56,41,57,56,58,44,56,44,41,58,59,46,58,46,44,59,60,48,59,48,46,60,61,50,60,50,48,61,62,52,61,52,50,62,63,52,64,56,65,64,66,58,64,58,56,66,67,59,66,59,58,67,68,60,67,60,59,68,69,61,68,61,60,69,70,62,69,62,61,70,71,62,72,64,73,72,74,66,72,66,64,74,75,67,74,67,66,75,76,68,75,68,67,76,77,69,76,69,68,77,78,70,77,70,69,78,79,70,80,81,82,80,82,83,81,84,85,81,85,82,84,86,87,84,87,85,86,88,89,86,89,87,88,90,91,88,91,89,90,92,93,90,93,91,92,94,95,92,95,93,96,81,97,96,98,84,96,84,81,98,99,86,98,86,84,99,100,88,99,88,86,100,101,90,100,90,88,101,102,92,101,92,90,102,103,92,104,96,105,104,106,98,104,98,96,106,107,99,106,99,98,107,108,100,107,100,99,108,109,101,108,101,100,109,110,102,109,102,101,110,111,102,112,104,113,112,114,106,112,106,104,114,115,107,114,107,106,115,116,108,115,108,107,116,117,109,116,109,108,117,118,110,117,110,109,118,119,110,120,121,122,120,122,123,121,124,125,121,125,122,124,126,127,124,127,125,126,128,129,126,129,127,128,130,131,128,131,129,130,132,133,130,133,131,132,134,135,132,135,133,136,121,137,136,138,124,136,124,121,138,139,126,138,126,124,139,140,128,139,128,126,140,141,130,140,130,128,141,142,132,141,132,130,142,143,132,144,136,145,144,146,138,144,138,136,146,147,139,146,139,138,147,148,140,147,140,139,148,149,141,148,141,140,149,150,142,149,142,141,150,151,142,152,144,153,152,154,146,152,146,144,154,155,147,154,147,146,155,156,148,155,148,147,156,157,149,156,149,148,157,158,150,157,150,149,158,159,150,160,161,135,160,135,162,123,163,164,123,164,165]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [9.000132,12.385069,58.599606],\n\t\t\t\t\t\t\"radius\": 1.767446\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"D4790B01-2C63-4B0C-A567-4F5C662B79E0\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [-0.06331,0.105866,0.992363,-0.063301,0.105862,0.992364,-0.063309,0.105861,0.992364,0.386871,0.91921,-0.073375,0.574866,0.816691,-0.050446,-0.014437,0.994157,-0.106973,-0.227753,0.966586,-0.11764,0.0633,-0.105861,-0.992364,0.063307,-0.105858,-0.992364,0.063313,-0.105861,-0.992363,-0.063315,0.105865,0.992363,0.853724,0.520725,-0.001084,0.944588,0.327278,0.025348,0.063297,-0.10586,-0.992364,-0.063313,0.105854,0.992364,0.994482,-0.076658,0.07162,0.953511,-0.287144,0.091461,0.063297,-0.105863,-0.992364,-0.063307,0.105856,0.992364,0.755384,-0.644758,0.11697,0.598224,-0.791889,0.122638,0.063307,-0.105856,-0.992364,-0.063302,0.105862,0.992364,0.227751,-0.966587,0.117638,0.014437,-0.994157,0.10697,0.063311,-0.105847,-0.992365,-0.06331,0.105866,0.992363,-0.386871,-0.91921,0.073374,-0.574864,-0.816693,0.050446,0.063307,-0.105849,-0.992365,-0.063315,0.105865,0.992363,-0.853723,-0.520727,0.001084,-0.944588,-0.327278,-0.025348,0.06331,-0.105854,-0.992364,-0.063313,0.105854,0.992364,-0.994482,0.076658,-0.071621,-0.95351,0.287146,-0.091461,0.063311,-0.105866,-0.992363,-0.063307,0.105856,0.992364,-0.755382,0.644761,-0.116969,-0.598226,0.791888,-0.122638,0.063317,-0.105866,-0.992362,-0.227753,0.966586,-0.11764,-0.014437,0.994157,-0.106973,-0.598798,0.790886,-0.126257,-0.598798,0.790886,-0.126257,-0.598798,0.790886,-0.126257,0.45236,0.203887,-0.868217,0.583368,0.322696,-0.745352,0.149426,-0.044563,-0.987768,-0.022505,-0.174207,-0.984452,0.598798,-0.790885,0.126259,0.598798,-0.790886,0.126258,0.598797,-0.790886,0.126258,-0.598798,0.790885,-0.126257,0.754443,0.504102,-0.420354,0.7945,0.566698,-0.218228,0.598799,-0.790885,0.126257,-0.598798,0.790886,-0.126258,0.768357,0.611765,0.18807,0.702155,0.59424,0.392247,0.598798,-0.790886,0.126256,-0.598799,0.790885,-0.126258,0.488781,0.48576,0.724659,0.341612,0.394806,0.852895,0.598797,-0.790886,0.126257,-0.598799,0.790885,-0.126257,0.022514,0.174207,0.984452,-0.149413,0.04456,0.98777,0.598798,-0.790886,0.126257,-0.598797,0.790886,-0.126257,-0.452357,-0.203891,0.868217,-0.583374,-0.322702,0.745344,0.598797,-0.790886,0.126257,-0.598797,0.790886,-0.126258,-0.754443,-0.504103,0.420352,-0.7945,-0.566697,0.21823,0.598797,-0.790886,0.126258,-0.598798,0.790885,-0.126258,-0.768358,-0.611764,-0.188073,-0.702158,-0.594236,-0.392247,0.598798,-0.790885,0.126258,-0.598798,0.790885,-0.126257,-0.48879,-0.485755,-0.724656,-0.341613,-0.394803,-0.852895,0.598797,-0.790886,0.126257,-0.022505,-0.174207,-0.984452,0.149426,-0.044563,-0.987768,-0.598797,0.790887,-0.126258,-0.598797,0.790886,-0.126258,-0.598798,0.790886,-0.126258,0.452358,0.203882,-0.868219,0.583375,0.322694,-0.745347,0.149418,-0.044566,-0.987769,-0.022505,-0.174203,-0.984453,0.598798,-0.790885,0.126257,0.598798,-0.790886,0.126258,0.598798,-0.790886,0.126258,-0.598796,0.790887,-0.126259,0.754445,0.504098,-0.420354,0.794502,0.566694,-0.21823,0.598798,-0.790885,0.126258,-0.598797,0.790886,-0.126258,0.768357,0.611765,0.188072,0.702157,0.594239,0.392244,0.598799,-0.790885,0.126258,-0.598797,0.790886,-0.126256,0.488787,0.48576,0.724655,0.341609,0.394802,0.852898,0.598798,-0.790885,0.126258,-0.598797,0.790886,-0.126257,0.022505,0.174203,0.984452,-0.149418,0.044566,0.987769,0.598798,-0.790885,0.126259,-0.598798,0.790886,-0.126257,-0.452358,-0.203883,0.868219,-0.583374,-0.322695,0.745347,0.598798,-0.790885,0.126258,-0.598798,0.790885,-0.126258,-0.754444,-0.5041,0.420354,-0.794501,-0.566697,0.218229,0.598797,-0.790886,0.126256,-0.598799,0.790885,-0.126258,-0.768359,-0.611763,-0.18807,-0.702159,-0.594237,-0.392244,0.598797,-0.790887,0.126257,-0.598798,0.790885,-0.126257,-0.488784,-0.485754,-0.724661,-0.341602,-0.394798,-0.852902,0.598797,-0.790887,0.126258,-0.022505,-0.174203,-0.984453,0.149418,-0.044566,-0.987769,0.59513,-0.791845,0.137121,0.59513,-0.791845,0.137121,0.59513,-0.791845,0.13712,0.188247,0.30095,0.934875,0.274233,0.358574,0.892312,0.009865,0.174845,0.984546,-0.081678,0.106878,0.990912,-0.59513,0.791845,-0.13712,-0.59513,0.791845,-0.13712,-0.59513,0.791845,-0.13712,0.595129,-0.791845,0.137121,0.430219,0.456893,0.778563,0.500214,0.49778,0.70852,-0.59513,0.791845,-0.13712,0.59513,-0.791845,0.137121,0.618387,0.560162,0.551195,0.667274,0.582283,0.464426,-0.595129,0.791845,-0.137121,0.595129,-0.791845,0.13712,0.74187,0.607754,0.283311,0.768345,0.611619,0.188593,-0.59513,0.791845,-0.13712,0.59513,-0.791845,0.13712,0.798485,0.602013,-0.001591,0.802453,0.588623,-0.097942,-0.59513,0.791845,-0.13712,0.59513,-0.791845,0.13712,0.787764,0.54557,-0.285976,0.768772,0.51552,-0.378455,-0.59513,0.791845,-0.13712,0.59513,-0.791845,0.13712,0.707867,0.439972,-0.552584,0.665162,0.393852,-0.634381,-0.59513,0.791845,-0.13712,0.595129,-0.791845,0.13712,0.557402,0.288034,-0.778678,0.491652,0.22794,-0.84043,-0.59513,0.791845,-0.137121,0.595129,-0.791845,0.13712,0.341926,0.09901,-0.934496,0.258022,0.030423,-0.96566,-0.59513,0.791845,-0.13712,0.59513,-0.791845,0.13712,0.081679,-0.106883,-0.990911,-0.00986,-0.174848,-0.984546,-0.595129,0.791845,-0.13712,0.59513,-0.791845,0.13712,-0.188243,-0.300952,-0.934876,-0.274231,-0.358575,-0.892313,-0.59513,0.791845,-0.13712,0.59513,-0.791845,0.13712,-0.430219,-0.456892,-0.778563,-0.500216,-0.497779,-0.708519,-0.59513,0.791845,-0.13712,0.59513,-0.791845,0.13712,-0.618388,-0.560163,-0.551192,-0.667275,-0.582284,-0.464424,-0.595129,0.791845,-0.137121,0.595129,-0.791845,0.137121,-0.741869,-0.607753,-0.283312,-0.768346,-0.611618,-0.188595,-0.59513,0.791845,-0.13712,0.595129,-0.791846,0.13712,-0.798485,-0.602013,0.001591,-0.802453,-0.588623,0.097942,-0.59513,0.791845,-0.13712,0.595129,-0.791845,0.13712,-0.787764,-0.54557,0.285975,-0.768772,-0.51552,0.378455,-0.59513,0.791845,-0.137121,0.595129,-0.791845,0.13712,-0.707867,-0.439974,0.552583,-0.665163,-0.393853,0.63438,-0.595129,0.791845,-0.13712,0.59513,-0.791845,0.13712,-0.557401,-0.288031,0.77868,-0.491649,-0.227936,0.840432,-0.595129,0.791846,-0.13712,0.59513,-0.791845,0.137121,-0.341924,-0.09901,0.934497,-0.258023,-0.03043,0.965659,-0.59513,0.791845,-0.13712,-0.081678,0.106878,0.990912,0.009865,0.174845,0.984546],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0.56747,9.133398,76.623772,0.237094,9.195099,76.596115,0.303555,8.657041,76.657753,0.56747,9.133398,76.623772,0.597501,9.083182,76.15303,0.267126,9.144882,76.125374,0.237094,9.195099,76.596115,0.597501,9.083182,76.15303,0.333586,8.606824,76.187012,0.267126,9.144882,76.125374,0.797039,8.889746,76.664413,0.797039,8.889746,76.664413,0.82707,8.839529,76.193665,0.82707,8.839529,76.193665,0.838114,8.557208,76.702507,0.838114,8.557208,76.702507,0.868145,8.506991,76.231758,0.868145,8.506991,76.231758,0.675005,8.262802,76.723503,0.675005,8.262802,76.723503,0.705037,8.212585,76.252762,0.705037,8.212585,76.252762,0.370015,8.118982,76.719391,0.370015,8.118982,76.719391,0.400047,8.068767,76.248642,0.400047,8.068767,76.248642,0.03964,8.180682,76.691734,0.03964,8.180682,76.691734,0.069671,8.130466,76.220985,0.069671,8.130466,76.220985,-0.189929,8.424335,76.651093,-0.189929,8.424335,76.651093,-0.159898,8.374119,76.180351,-0.159898,8.374119,76.180351,-0.231004,8.756873,76.612999,-0.231004,8.756873,76.612999,-0.200973,8.706657,76.142258,-0.200973,8.706657,76.142258,-0.067896,9.051279,76.592003,-0.067896,9.051279,76.592003,-0.037864,9.001062,76.121254,-0.037864,9.001062,76.121254,0.237094,9.195099,76.596115,0.267126,9.144882,76.125374,-11.236314,-1.44627,73.807961,-11.485709,-1.650805,73.709534,-11.520534,-1.590772,74.250748,-11.236314,-1.44627,73.807961,-10.952264,-1.821439,73.867851,-11.20166,-2.025974,73.769432,-11.485709,-1.650805,73.709534,-10.952264,-1.821439,73.867851,-11.236485,-1.965941,74.310638,-11.20166,-2.025974,73.769432,-11.09548,-1.296929,74.075508,-11.09548,-1.296929,74.075508,-10.811431,-1.672098,74.135399,-10.811431,-1.672098,74.135399,-11.117002,-1.259827,74.409996,-11.117002,-1.259827,74.409996,-10.832952,-1.634996,74.469887,-10.832952,-1.634996,74.469887,-11.29266,-1.349134,74.683647,-11.29266,-1.349134,74.683647,-11.008611,-1.724304,74.743546,-11.008611,-1.724304,74.743546,-11.555357,-1.530739,74.791954,-11.555357,-1.530739,74.791954,-11.271308,-1.905909,74.851845,-11.271308,-1.905909,74.851845,-11.804753,-1.735275,74.693535,-11.804753,-1.735275,74.693535,-11.520704,-2.110444,74.753426,-11.520704,-2.110444,74.753426,-11.945587,-1.884615,74.42598,-11.945587,-1.884615,74.42598,-11.661538,-2.259784,74.48587,-11.661538,-2.259784,74.48587,-11.924065,-1.921718,74.091499,-11.924065,-1.921718,74.091499,-11.640016,-2.296887,74.15139,-11.640016,-2.296887,74.15139,-11.748407,-1.83241,73.817841,-11.748407,-1.83241,73.817841,-11.464358,-2.207579,73.877731,-11.464358,-2.207579,73.877731,-11.485709,-1.650805,73.709534,-11.20166,-2.025974,73.769432,-13.872152,-3.591607,73.936165,-14.053963,-3.740713,73.864418,-14.07935,-3.696949,74.258957,-13.872152,-3.591607,73.936165,-13.665081,-3.865105,73.979828,-13.84689,-4.014212,73.908081,-14.053963,-3.740713,73.864418,-13.665081,-3.865105,73.979828,-13.872277,-3.970448,74.30262,-13.84689,-4.014212,73.908081,-13.769485,-3.482738,74.13121,-13.769485,-3.482738,74.13121,-13.562413,-3.756236,74.174873,-13.562413,-3.756236,74.174873,-13.785174,-3.45569,74.375046,-13.785174,-3.45569,74.375046,-13.578103,-3.729189,74.418709,-13.578103,-3.729189,74.418709,-13.913229,-3.520796,74.574547,-13.913229,-3.520796,74.574547,-13.706158,-3.794294,74.61821,-13.706158,-3.794294,74.61821,-14.104735,-3.653186,74.653496,-14.104735,-3.653186,74.653496,-13.897664,-3.926684,74.697159,-13.897664,-3.926684,74.697159,-14.286546,-3.802292,74.581749,-14.286546,-3.802292,74.581749,-14.079473,-4.07579,74.625412,-14.079473,-4.07579,74.625412,-14.389214,-3.911161,74.386703,-14.389214,-3.911161,74.386703,-14.182141,-4.184659,74.430367,-14.182141,-4.184659,74.430367,-14.373524,-3.938209,74.142868,-14.373524,-3.938209,74.142868,-14.166451,-4.211707,74.186523,-14.166451,-4.211707,74.186523,-14.245469,-3.873104,73.943367,-14.245469,-3.873104,73.943367,-14.038397,-4.146602,73.98703,-14.038397,-4.146602,73.98703,-14.053963,-3.740713,73.864418,-13.84689,-4.014212,73.908081,-5.768764,2.933028,66.273743,-6.214926,2.619189,66.397812,-6.133282,2.349768,64.48761,-5.768764,2.933028,66.273743,-6.234167,3.550042,66.168831,-6.680329,3.236202,66.2929,-6.214926,2.619189,66.397812,-6.172415,3.468839,66.181435,-6.536933,2.885579,64.395294,-6.618577,3.154999,66.305496,-5.358283,3.189774,65.974838,-5.358283,3.189774,65.974838,-5.823686,3.806787,65.869919,-5.761934,3.725585,65.882523,-5.023664,3.364294,65.53035,-5.023664,3.364294,65.53035,-5.489068,3.981308,65.425438,-5.427316,3.900105,65.438034,-4.797663,3.439506,64.983795,-4.797663,3.439506,64.983795,-5.263066,4.05652,64.878876,-5.201314,3.975316,64.891479,-4.702402,3.408046,64.388664,-4.702402,3.408046,64.388664,-5.167805,4.02506,64.283745,-5.106053,3.943857,64.296349,-4.747204,3.272995,63.803223,-4.747204,3.272995,63.803223,-5.212607,3.890009,63.698307,-5.150856,3.808805,63.710907,-4.927686,3.047572,63.284771,-4.927686,3.047572,63.284771,-5.393089,3.664586,63.179855,-5.331337,3.583383,63.192455,-5.22618,2.753843,62.884064,-5.22618,2.753843,62.884064,-5.691583,3.370857,62.779148,-5.629831,3.289654,62.791748,-5.613468,2.420561,62.64032,-5.613468,2.420561,62.64032,-6.078871,3.037574,62.535404,-6.017118,2.956371,62.548008,-6.051638,2.080348,62.577404,-6.051638,2.080348,62.577404,-6.517041,2.697362,62.472488,-6.455289,2.616159,62.485088,-6.4978,1.766509,62.701469,-6.4978,1.766509,62.701469,-6.963203,2.383523,62.596554,-6.901452,2.30232,62.609158,-6.908281,1.509763,63.000378,-6.908281,1.509763,63.000378,-7.373684,2.126777,62.895462,-7.311932,2.045573,62.908066,-7.242899,1.335243,63.444866,-7.242899,1.335243,63.444866,-7.708302,1.952256,63.339951,-7.646551,1.871053,63.352551,-7.468901,1.260031,63.991425,-7.468901,1.260031,63.991425,-7.934304,1.877045,63.886509,-7.872552,1.795842,63.899109,-7.564163,1.291491,64.586548,-7.564163,1.291491,64.586548,-8.029566,1.908504,64.481636,-7.967813,1.827301,64.49424,-7.51936,1.426542,65.171997,-7.51936,1.426542,65.171997,-7.984763,2.043555,65.067078,-7.923011,1.962353,65.079681,-7.338878,1.651965,65.690445,-7.338878,1.651965,65.690445,-7.804281,2.268979,65.585533,-7.742529,2.187776,65.598129,-7.040384,1.945694,66.091156,-7.040384,1.945694,66.091156,-7.505787,2.562707,65.986237,-7.444035,2.481504,65.99884,-6.653097,2.278976,66.334892,-6.653097,2.278976,66.334892,-7.1185,2.89599,66.22998,-7.056747,2.814787,66.242584,-6.214926,2.619189,66.397812,-6.680329,3.236202,66.2929],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,3,4,5,3,5,6,7,8,9,10,0,2,11,12,4,11,4,3,13,8,7,14,10,2,15,16,12,15,12,11,17,8,13,18,14,2,19,20,16,19,16,15,21,8,17,22,18,2,23,24,20,23,20,19,25,8,21,26,22,2,27,28,24,27,24,23,29,8,25,30,26,2,31,32,28,31,28,27,33,8,29,34,30,2,35,36,32,35,32,31,37,8,33,38,34,2,39,40,36,39,36,35,41,8,37,1,38,2,42,43,40,42,40,39,9,8,41,44,45,46,47,48,49,47,49,50,51,52,53,54,44,46,55,56,48,55,48,47,57,52,51,58,54,46,59,60,56,59,56,55,61,52,57,62,58,46,63,64,60,63,60,59,65,52,61,66,62,46,67,68,64,67,64,63,69,52,65,70,66,46,71,72,68,71,68,67,73,52,69,74,70,46,75,76,72,75,72,71,77,52,73,78,74,46,79,80,76,79,76,75,81,52,77,82,78,46,83,84,80,83,80,79,85,52,81,45,82,46,86,87,84,86,84,83,53,52,85,88,89,90,91,92,93,91,93,94,95,96,97,98,88,90,99,100,92,99,92,91,101,96,95,102,98,90,103,104,100,103,100,99,105,96,101,106,102,90,107,108,104,107,104,103,109,96,105,110,106,90,111,112,108,111,108,107,113,96,109,114,110,90,115,116,112,115,112,111,117,96,113,118,114,90,119,120,116,119,116,115,121,96,117,122,118,90,123,124,120,123,120,119,125,96,121,126,122,90,127,128,124,127,124,123,129,96,125,89,126,90,130,131,128,130,128,127,97,96,129,132,133,134,135,136,137,135,137,138,139,140,141,142,132,134,143,144,136,143,136,135,145,140,139,146,142,134,147,148,144,147,144,143,149,140,145,150,146,134,151,152,148,151,148,147,153,140,149,154,150,134,155,156,152,155,152,151,157,140,153,158,154,134,159,160,156,159,156,155,161,140,157,162,158,134,163,164,160,163,160,159,165,140,161,166,162,134,167,168,164,167,164,163,169,140,165,170,166,134,171,172,168,171,168,167,173,140,169,174,170,134,175,176,172,175,172,171,177,140,173,178,174,134,179,180,176,179,176,175,181,140,177,182,178,134,183,184,180,183,180,179,185,140,181,186,182,134,187,188,184,187,184,183,189,140,185,190,186,134,191,192,188,191,188,187,193,140,189,194,190,134,195,196,192,195,192,191,197,140,193,198,194,134,199,200,196,199,196,195,201,140,197,202,198,134,203,204,200,203,200,199,205,140,201,206,202,134,207,208,204,207,204,203,209,140,205,210,206,134,211,212,208,211,208,207,213,140,209,133,210,134,214,215,212,214,212,211,141,140,213]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [-6.760534,2.491696,69.597996],\n\t\t\t\t\t\t\"radius\": 12.164958\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"14DDFFFB-0E99-4A92-BB32-FBF228DB96B4\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [-0.602487,0.788954,-0.12067,-0.517086,0.68663,0.51104,-0.564202,0.750155,0.344884,-0.594573,0.794919,-0.120777,-0.298212,0.399994,0.866645,-0.376778,0.511269,0.772426,-0.023295,0.037508,0.999025,-0.112519,0.166275,0.979639,0.26172,-0.338631,0.903787,0.172699,-0.211665,0.961963,0.501888,-0.656655,0.56295,0.433629,-0.561406,0.704833,0.53084,-0.83536,0.142767,0.629218,-0.760571,0.160053,-0.724748,0.518587,0.453661,-0.602487,0.788954,-0.12067,-0.669396,0.152913,0.726998,-0.47994,-0.233751,0.845587,-0.196302,-0.572349,0.796167,0.133779,-0.801474,0.582875,0.53084,-0.83536,0.142767,-0.907062,0.334053,0.256217,-0.602487,0.788954,-0.12067,-0.907858,-0.095625,0.408227,-0.734675,-0.485089,0.47428,-0.426434,-0.783257,0.452397,-0.018797,-0.940343,0.339709,0.53084,-0.83536,0.142767,-0.912235,0.396728,-0.102143,-0.602487,0.788954,-0.12067,-0.991175,-0.12151,-0.052988,-0.838784,-0.544462,0.001333,-0.524749,-0.84956,0.053715,-0.052404,-0.99362,0.09987,0.53084,-0.83536,0.142767,-0.60689,0.784989,-0.124407,-0.972355,0.216097,-0.08847,-0.912235,0.396728,-0.102143,-0.602487,0.788954,-0.12067,-0.960129,-0.277072,-0.037197,-0.991175,-0.12151,-0.052988,-0.75012,-0.661099,0.016342,-0.838784,-0.544462,0.001333,-0.383078,-0.921189,0.068276,-0.524749,-0.84956,0.053715,0.137919,-0.984231,0.110756,-0.052404,-0.99362,0.09987,0.56692,-0.819831,0.080496,0.53084,-0.83536,0.142767,-0.915229,0.231997,-0.329443,-0.60689,0.784989,-0.124407,-0.87536,-0.204082,-0.438288,-0.671351,-0.581792,-0.459136,-0.341619,-0.852944,-0.394694,0.074646,-0.966118,-0.247071,0.56692,-0.819831,0.080496,-0.743384,0.339373,-0.576373,-0.60689,0.784989,-0.124407,-0.614325,-0.042294,-0.787919,-0.364925,-0.40743,-0.837156,-0.04165,-0.693,-0.719734,0.29913,-0.841923,-0.449095,0.56692,-0.819831,0.080496,-0.50426,0.654858,-0.562923,-0.60689,0.784989,-0.124407,-0.266682,0.334873,-0.903737,0.01778,-0.041694,-0.998972,0.294558,-0.402771,-0.866609,0.516054,-0.687414,-0.511028,0.56692,-0.819831,0.080496,-0.599011,0.790999,-0.124522,-0.432597,0.562191,-0.704841,-0.50426,0.654858,-0.562923,-0.60689,0.784989,-0.124407,-0.169042,0.21444,-0.961998,-0.266682,0.334873,-0.903737,0.118035,-0.16209,-0.979691,0.01778,-0.041694,-0.998972,0.381735,-0.507504,-0.772475,0.294558,-0.402771,-0.866609,0.566572,-0.748355,-0.344908,0.516054,-0.687414,-0.511028,0.679049,-0.72764,0.097118,0.56692,-0.819831,0.080496,-0.223777,0.728711,-0.647228,-0.599011,0.790999,-0.124522,0.148173,0.53249,-0.833366,0.479941,0.233753,-0.845586,0.71514,-0.10991,-0.690286,0.80937,-0.438752,-0.390406,0.679049,-0.72764,0.097118,-0.026989,0.894212,-0.446829,-0.599011,0.790999,-0.124522,0.403458,0.758904,-0.511161,0.734674,0.48509,-0.474281,0.928708,0.122774,-0.349897,0.947807,-0.281291,-0.150126,0.679049,-0.72764,0.097118,-0.137825,0.984087,-0.112145,-0.599011,0.790999,-0.124522,0.383269,0.920898,-0.071068,0.750327,0.660783,-0.019385,0.960266,0.276861,0.035161,0.972396,-0.216158,0.087874,0.679049,-0.72764,0.097118,-0.594573,0.794919,-0.120777,0.052364,0.993681,-0.099274,-0.137825,0.984087,-0.112145,-0.599011,0.790999,-0.124522,0.524611,0.849772,-0.051679,0.383269,0.920898,-0.071068,0.838579,0.544778,0.001709,0.750327,0.660783,-0.019385,0.990986,0.121798,0.055781,0.960266,0.276861,0.035161,0.912142,-0.396584,0.103531,0.972396,-0.216158,0.087874,0.629218,-0.760571,0.160053,0.679049,-0.72764,0.097118,-0.014115,0.99008,0.13979,-0.594573,0.794919,-0.120777,0.372668,0.865114,0.335702,0.671352,0.581789,0.459137,0.846481,0.189058,0.497721,0.859844,-0.262722,0.437773,0.629218,-0.760571,0.160053,-0.200213,0.901445,0.383811,-0.594573,0.794919,-0.120777,0.094988,0.725217,0.681937,0.364927,0.407429,0.837156,0.56334,0.006987,0.826196,0.649754,-0.405846,0.642735,0.629218,-0.760571,0.160053,-0.564202,0.750155,0.344884,-0.594573,0.794919,-0.120777,-0.376778,0.511269,0.772426,-0.112519,0.166275,0.979639,0.172699,-0.211665,0.961963,0.433629,-0.561406,0.704833,0.629218,-0.760571,0.160053,-0.60689,0.784989,-0.124407,-0.602487,0.788954,-0.12067,-0.594573,0.794919,-0.120777,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.56692,-0.819831,0.080496,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.53084,-0.83536,0.142767,0.600751,-0.78998,0.122596,0.629218,-0.760571,0.160053,0.600751,-0.78998,0.122596,-0.253605,0.322689,-0.911897,-0.253576,0.322649,-0.911919,0.793317,0.608734,-0.009532,0.25357,-0.322642,0.911923,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,-0.600751,0.78998,-0.122596,-0.600751,0.78998,-0.122596,-0.600751,0.78998,-0.122596,-0.600751,0.78998,-0.122596,0.068165,-0.10227,-0.992418,0.068122,-0.102214,-0.992427,-0.796614,-0.604423,0.008862,-0.796612,-0.604425,0.008862,-0.068122,0.102215,0.992427,-0.068079,0.102157,0.992436,-0.068122,0.102215,0.992427,-0.068165,0.102272,0.992418,0.796614,0.604423,-0.008862,0.796617,0.60442,-0.008861,-0.67825,0.72395,-0.12599,-0.949106,0.299539,-0.097333,-0.882766,0.458201,-0.10381,-0.677686,0.727654,-0.106123,-0.975853,-0.212465,-0.050699,-0.997438,-0.045541,-0.055166,-0.85164,-0.523952,-0.013592,-0.892166,-0.451681,-0.004922,-0.67495,-0.737566,0.020955,-0.733533,-0.679,0.029809,-0.311489,-0.947773,0.068557,-0.466495,-0.882099,0.065455,0.205116,-0.972452,0.110743,0.034386,-0.993697,0.106691,0.550014,-0.827435,0.113297,0.478596,-0.868447,0.129403,-0.892547,0.310257,-0.327261,-0.67825,0.72395,-0.12599,-0.878111,-0.136182,-0.458667,-0.732958,-0.46057,-0.500647,-0.566539,-0.678021,-0.468317,-0.274748,-0.873926,-0.400957,0.148278,-0.962374,-0.227708,0.550014,-0.827435,0.113297,-0.728835,0.399218,-0.556259,-0.67825,0.72395,-0.12599,-0.609278,0.016408,-0.792787,-0.419873,-0.278821,-0.863693,-0.252997,-0.495832,-0.830749,0.012337,-0.703643,-0.710447,0.343457,-0.84289,-0.414214,0.550014,-0.827435,0.113297,-0.532455,0.619027,-0.577319,-0.67825,0.72395,-0.12599,-0.322924,0.267577,-0.907812,-0.101936,-0.041682,-0.993917,0.063114,-0.262809,-0.962781,0.281195,-0.498142,-0.820234,0.516385,-0.724446,-0.456645,0.550014,-0.827435,0.113297,-0.517765,0.844347,-0.137834,-0.44027,0.609091,-0.659674,-0.532455,0.619027,-0.577319,-0.67825,0.72395,-0.12599,-0.161541,0.316805,-0.934633,-0.322924,0.267577,-0.907812,0.070429,0.060433,-0.995685,-0.101936,-0.041682,-0.993917,0.235081,-0.16009,-0.958701,0.063114,-0.262809,-0.962781,0.438186,-0.442248,-0.782566,0.281195,-0.498142,-0.820234,0.596533,-0.716128,-0.362367,0.516385,-0.724446,-0.456645,0.710214,-0.694825,0.113199,0.550014,-0.827435,0.113297,-0.272977,0.736083,-0.619408,-0.517765,0.844347,-0.137834,0.092974,0.544064,-0.833877,0.36915,0.319817,-0.872608,0.535907,0.102983,-0.837973,0.709953,-0.168973,-0.683677,0.791336,-0.493929,-0.360307,0.710214,-0.694825,0.113199,-0.102976,0.893724,-0.436638,-0.517765,0.844347,-0.137834,0.338216,0.777743,-0.529835,0.633663,0.576297,-0.516094,0.799882,0.35915,-0.480833,0.933565,0.052145,-0.354595,0.924023,-0.357958,-0.134339,0.710214,-0.694825,0.113199,-0.204614,0.971693,-0.118093,-0.517765,0.844347,-0.137834,0.312582,0.946117,-0.084594,0.676193,0.735684,-0.039152,0.852937,0.521986,-0.005443,0.976674,0.211221,0.038658,0.949329,-0.299878,0.094058,0.710214,-0.694825,0.113199,-0.517018,0.847812,-0.11793,-0.03461,0.994036,-0.103416,-0.204614,0.971693,-0.118093,-0.517765,0.844347,-0.137834,0.465675,0.883342,-0.053414,0.312582,0.946117,-0.084594,0.732236,0.680966,-0.010775,0.676193,0.735684,-0.039152,0.890924,0.453564,0.023119,0.852937,0.521986,-0.005443,0.996345,0.047195,0.071203,0.976674,0.211221,0.038658,0.882265,-0.457441,0.111161,0.949329,-0.299878,0.094058,0.649046,-0.749316,0.131397,0.710214,-0.694825,0.113199,-0.094387,0.987548,0.125856,-0.517018,0.847812,-0.11793,0.305984,0.888521,0.341913,0.567316,0.678386,0.466845,0.732178,0.460206,0.502121,0.846871,0.121591,0.517711,0.838658,-0.335433,0.429112,0.649046,-0.749316,0.131397,-0.258096,0.898591,0.354853,-0.517018,0.847812,-0.11793,0.03715,0.735935,0.676032,0.254232,0.496636,0.829891,0.418639,0.278014,0.864552,0.559791,-0.048698,0.827201,0.643478,-0.454916,0.615619,0.649046,-0.749316,0.131397,-0.524468,0.770814,0.361634,-0.517018,0.847812,-0.11793,-0.297858,0.548741,0.78113,-0.078482,0.278929,0.957099,0.090074,0.061369,0.994043,0.273771,-0.231631,0.933486,0.476697,-0.581444,0.659305,0.649046,-0.749316,0.131397,-0.677686,0.727654,-0.106123,-0.552816,0.6968,0.457017,-0.524468,0.770814,0.361634,-0.517018,0.847812,-0.11793,-0.393431,0.412976,0.821379,-0.297858,0.548741,0.78113,-0.223617,0.141007,0.964423,-0.078482,0.278929,0.957099,-0.054662,-0.077157,0.995519,0.090074,0.061369,0.994043,0.182592,-0.374068,0.909249,0.273771,-0.231631,0.933486,0.460391,-0.673714,0.578056,0.476697,-0.581444,0.659305,0.478596,-0.868447,0.129403,0.649046,-0.749316,0.131397,-0.71396,0.561725,0.418002,-0.677686,0.727654,-0.106123,-0.6651,0.208274,0.717123,-0.534788,-0.102003,0.838807,-0.370268,-0.320797,0.871775,-0.137829,-0.583364,0.800431,0.195597,-0.803877,0.561714,0.478596,-0.868447,0.129403,-0.883958,0.404083,0.235233,-0.677686,0.727654,-0.106123,-0.910339,-0.025405,0.413082,-0.799303,-0.358483,0.482291,-0.634242,-0.576964,0.514635,-0.36144,-0.804482,0.471349,0.062911,-0.93985,0.335743,0.478596,-0.868447,0.129403,-0.882766,0.458201,-0.10381,-0.677686,0.727654,-0.106123,-0.997438,-0.045541,-0.055166,-0.892166,-0.451681,-0.004922,-0.733533,-0.679,0.029809,-0.466495,-0.882099,0.065455,0.034386,-0.993697,0.106691,0.478596,-0.868447,0.129403,0.550014,-0.827435,0.113297,0.710214,-0.694825,0.113199,0.478596,-0.868447,0.129403,-0.517765,0.844347,-0.137834,-0.67825,0.72395,-0.12599,-0.677686,0.727654,-0.106123,0.793311,0.608742,-0.009533,-0.253576,0.322649,-0.911919,0.793305,0.608749,-0.009534,-0.600751,0.78998,-0.122596,0.25357,-0.322641,0.911924,0.793311,0.608742,-0.009533,0.253569,-0.322641,0.911924,-0.600751,0.78998,-0.122596,0.068078,-0.102158,-0.992436,0.796614,0.604423,-0.008862,0.796612,0.604426,-0.008863,-0.600751,0.78998,-0.122597,-0.600751,0.78998,-0.122596,0.068122,-0.102214,-0.992427,-0.796616,-0.604421,0.008862,-0.796614,-0.604423,0.008862,-0.600751,0.78998,-0.122597,-0.793318,-0.608733,0.009532,-0.253546,0.32261,-0.911942,-0.793316,-0.608735,0.009533,-0.600751,0.78998,-0.122597,0.600751,-0.78998,0.122596,-0.793316,-0.608735,0.009533,0.25357,-0.322641,0.911924,-0.793315,-0.608737,0.009533],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [-20.494698,-6.026025,75.831726,-20.442572,-6.087613,76.316879,6.917687,14.678411,76.056168,6.865561,14.74,75.571014,-20.236868,-6.353017,76.70623,7.123391,14.413008,76.445518,-19.932705,-6.751124,76.895447,7.427554,14.014901,76.634735,-19.611582,-7.17526,76.833832,7.748676,13.590765,76.57312,-19.359545,-7.511779,76.537895,8.000713,13.254246,76.277184,-19.244125,-7.67051,76.086929,8.116133,13.095514,75.826218,-20.611698,-6.225703,76.255821,-20.494698,-6.026025,75.831726,-20.529804,-6.592196,76.600479,-20.270958,-7.027304,76.773338,-19.904518,-7.414439,76.728081,-19.528671,-7.649869,76.476837,-19.244125,-7.67051,76.086929,-20.727028,-6.339807,76.085709,-20.494698,-6.026025,75.831726,-20.729561,-6.78983,76.305832,-20.501617,-7.255511,76.433113,-20.104275,-7.612072,76.433441,-19.644001,-7.763972,76.306725,-19.244125,-7.67051,76.086929,-20.757658,-6.39935,75.852127,-20.494698,-6.026025,75.831726,-20.782614,-6.892962,75.901245,-20.562878,-7.374598,75.965935,-20.157328,-7.715205,76.028854,-19.674631,-7.823516,76.073135,-19.244125,-7.67051,76.086929,-15.659926,-13.446559,4.324014,-15.922888,-13.819884,4.344414,-20.757658,-6.39935,75.852127,-20.494698,-6.026025,75.831726,-15.947844,-14.313496,4.393538,-20.782614,-6.892962,75.901245,-15.728107,-14.795133,4.458225,-20.562878,-7.374598,75.965935,-15.322557,-15.135738,4.521142,-20.157328,-7.715205,76.028854,-14.839861,-15.24405,4.565429,-19.674631,-7.823516,76.073135,-14.409355,-15.091044,4.57922,-19.244125,-7.67051,76.086929,-15.860611,-13.808912,4.109941,-15.659926,-13.446559,4.324014,-15.839977,-14.294493,3.987419,-15.603554,-14.77319,3.989279,-15.214691,-15.116735,4.115022,-14.777584,-15.233078,4.330956,-14.409355,-15.091044,4.57922,-15.722115,-13.730366,3.937409,-15.659926,-13.446559,4.324014,-15.600094,-14.158446,3.688586,-15.326561,-14.616096,3.644216,-14.974809,-14.980689,3.816189,-14.639088,-15.154531,4.158424,-14.409355,-15.091044,4.57922,-15.544508,-13.60529,3.873049,-15.659926,-13.446559,4.324014,-15.292471,-13.94181,3.577111,-14.971348,-14.365946,3.515496,-14.667185,-14.764051,3.704714,-14.461481,-15.029456,4.094064,-14.409355,-15.091044,4.57922,11.700332,7.319466,4.063301,11.81575,7.160734,3.612335,-15.544508,-13.60529,3.873049,-15.659926,-13.446559,4.324014,12.067787,6.824215,3.316397,-15.292471,-13.94181,3.577111,12.38891,6.400079,3.254782,-14.971348,-14.365946,3.515496,12.693073,6.001973,3.444,-14.667185,-14.764051,3.704714,12.898777,5.736568,3.83335,-14.461481,-15.029456,4.094064,12.950903,5.674981,4.318506,-14.409355,-15.091044,4.57922,11.984877,7.298824,3.673391,11.700332,7.319466,4.063301,12.360723,7.063395,3.422149,12.727163,6.67626,3.376894,12.98601,6.241152,3.549752,13.067904,5.874658,3.894406,12.950903,5.674981,4.318506,12.100206,7.412928,3.843504,11.700332,7.319466,4.063301,12.56048,7.261028,3.716793,12.957823,6.904467,3.71712,13.185766,6.438785,3.844396,13.183233,5.988762,4.064519,12.950903,5.674981,4.318506,12.130836,7.472471,4.077092,11.700332,7.319466,4.063301,12.613534,7.36416,4.121379,13.019083,7.023554,4.184295,13.238819,6.541917,4.248982,13.213863,6.048306,4.298107,12.950903,5.674981,4.318506,6.865561,14.74,75.571014,7.296067,14.893005,75.584801,12.130836,7.472471,4.077092,11.700332,7.319466,4.063301,7.778763,14.784694,75.629089,12.613534,7.36416,4.121379,8.184313,14.444088,75.692009,13.019083,7.023554,4.184295,8.404049,13.962452,75.756691,13.238819,6.541917,4.248982,8.379093,13.46884,75.805817,13.213863,6.048306,4.298107,8.116133,13.095514,75.826218,12.950903,5.674981,4.318506,7.23379,14.882034,75.819275,6.865561,14.74,75.571014,7.670897,14.765691,76.03521,8.05976,14.422145,76.16095,8.296183,13.943448,76.162811,8.316816,13.457868,76.040291,8.116133,13.095514,75.826218,7.095294,14.803487,75.991806,6.865561,14.74,75.571014,7.431014,14.629644,76.334045,7.782767,14.265051,76.506012,8.056299,13.807402,76.461647,8.17832,13.379321,76.212822,8.116133,13.095514,75.826218,6.917687,14.678411,76.056168,6.865561,14.74,75.571014,7.123391,14.413008,76.445518,7.427554,14.014901,76.634735,7.748676,13.590765,76.57312,8.000713,13.254246,76.277184,8.116133,13.095514,75.826218,-15.659926,-13.446559,4.324014,-20.494698,-6.026025,75.831726,6.865561,14.74,75.571014,-3.079532,4.392192,74.605606,-3.009991,4.314536,73.764435,6.756584,11.727219,73.671371,6.885622,11.955595,74.510651,-14.409355,-15.091044,4.57922,-13.687444,-14.425836,5.328121,-18.234619,-7.44671,72.582214,-19.244125,-7.67051,76.086929,12.126726,5.166731,5.082139,8.116133,13.095514,75.826218,7.579551,12.145857,72.336235,-18.268682,-7.401923,72.607536,7.545488,12.190644,72.361549,-13.721506,-14.381048,5.353438,12.092664,5.211518,5.107457,-17.834019,-6.806241,74.746201,-7.921443,0.717255,74.651749,-3.079532,4.392192,74.605606,-17.591249,-6.752421,73.903381,-3.079532,4.392192,74.605606,-7.921443,0.717255,74.651749,-7.900812,0.602476,73.811043,-7.921443,0.717255,74.651749,-7.921443,0.717255,74.651749,-7.864128,0.629287,73.804047,-3.022218,4.304224,73.757904,-3.079532,4.392192,74.605606,-3.079532,4.392192,74.605606,-3.022218,4.304224,73.757904,-7.864128,0.629287,73.804047,-7.921443,0.717255,74.651749,-3.047849,4.350529,74.612076,-7.889759,0.675592,74.658211,-3.022218,4.304224,73.757904,-2.990534,4.262561,73.764374,-7.864128,0.629287,73.804047,-7.832444,0.587624,73.810509,-2.990534,4.262561,73.764374,-3.022218,4.304224,73.757904,-7.832444,0.587624,73.810509,-7.864128,0.629287,73.804047,9.431758,11.356605,45.740791,9.300395,11.187922,45.749264,8.927946,11.752349,51.211384,9.059309,11.921032,51.202911,9.270769,10.976943,45.769047,8.89832,11.541369,51.231167,9.350817,10.780197,45.794834,8.978368,11.344624,51.256954,9.460544,10.635908,45.817226,9.088095,11.200335,51.279346,9.628817,10.506115,45.842113,9.256368,11.070541,51.304234,9.840226,10.478051,45.859428,9.467777,11.042479,51.321548,10.038126,10.559238,45.864532,9.665677,11.123665,51.326656,9.329445,11.194063,45.646484,9.431758,11.356605,45.740791,9.321084,10.987578,45.591026,9.408916,10.792479,45.589275,9.518641,10.64819,45.611668,9.679132,10.51675,45.664093,9.869276,10.484192,45.756649,10.038126,10.559238,45.864532,9.394823,11.232035,45.570793,9.431758,11.356605,45.740791,9.434322,11.053347,45.459923,9.539672,10.868421,45.437889,9.649399,10.724132,45.460281,9.79237,10.582519,45.53299,9.934654,10.522163,45.680958,10.038126,10.559238,45.864532,9.479012,11.291662,45.542473,9.431758,11.356605,45.740791,9.580142,11.156625,45.41087,9.708051,10.987676,45.381248,9.817777,10.843388,45.403641,9.93819,10.685797,45.483936,10.018844,10.581791,45.652634,10.038126,10.559238,45.864532,9.697449,11.558232,45.738075,9.744703,11.493289,45.539753,9.479012,11.291662,45.542473,9.431758,11.356605,45.740791,9.845834,11.358252,45.40815,9.580142,11.156625,45.41087,9.973742,11.189303,45.378529,9.708051,10.987676,45.381248,10.083468,11.045014,45.400921,9.817777,10.843388,45.403641,10.203881,10.887424,45.481216,9.93819,10.685797,45.483936,10.284535,10.783418,45.649918,10.018844,10.581791,45.652634,10.303818,10.760865,45.861816,10.038126,10.559238,45.864532,9.825146,11.558596,45.566391,9.697449,11.558232,45.738075,9.985163,11.471367,45.454288,10.134625,11.319917,45.431805,10.244351,11.175628,45.454197,10.34321,11.000539,45.527355,10.364977,10.848725,45.676556,10.303818,10.760865,45.861816,9.880285,11.612083,45.64085,9.697449,11.558232,45.738075,10.080668,11.564009,45.583256,10.244905,11.42689,45.580723,10.354631,11.282602,45.603115,10.438716,11.093181,45.656322,10.420116,10.902212,45.751015,10.303818,10.760865,45.861816,9.895349,11.639419,45.743179,9.697449,11.558232,45.738075,10.106759,11.611355,45.760494,10.275032,11.481562,45.785381,10.384758,11.337273,45.807774,10.464807,11.140528,45.833561,10.43518,10.929547,45.853344,10.303818,10.760865,45.861816,9.325,12.122659,51.200195,9.5229,12.203845,51.205299,9.895349,11.639419,45.743179,9.697449,11.558232,45.738075,9.73431,12.175782,51.222614,10.106759,11.611355,45.760494,9.902583,12.045988,51.247501,10.275032,11.481562,45.785381,10.01231,11.9017,51.269894,10.384758,11.337273,45.807774,10.092358,11.704954,51.295681,10.464807,11.140528,45.833561,10.062731,11.493974,51.315464,10.43518,10.929547,45.853344,9.931369,11.325292,51.323936,10.303818,10.760865,45.861816,9.493851,12.197704,51.308079,9.325,12.122659,51.200195,9.683995,12.165146,51.400635,9.844484,12.033708,51.45306,9.954211,11.889419,51.475452,10.042043,11.694318,51.473701,10.033682,11.487833,51.418243,9.931369,11.325292,51.323936,9.428473,12.159733,51.38377,9.325,12.122659,51.200195,9.570757,12.099378,51.531738,9.713728,11.957765,51.604446,9.823454,11.813476,51.626839,9.928804,11.62855,51.604805,9.968304,11.449862,51.493935,9.931369,11.325292,51.323936,9.344283,12.100105,51.412094,9.325,12.122659,51.200195,9.424936,11.996099,51.580791,9.545349,11.83851,51.661087,9.655076,11.694221,51.683479,9.782984,11.525271,51.653858,9.884114,11.390234,51.522259,9.931369,11.325292,51.323936,9.059309,11.921032,51.202911,9.078591,11.898479,51.41481,9.344283,12.100105,51.412094,9.325,12.122659,51.200195,9.159245,11.794473,51.583511,9.424936,11.996099,51.580791,9.279658,11.636883,51.663807,9.545349,11.83851,51.661087,9.389384,11.492594,51.686199,9.655076,11.694221,51.683479,9.517292,11.323645,51.656578,9.782984,11.525271,51.653858,9.618423,11.188607,51.524975,9.884114,11.390234,51.522259,9.665677,11.123665,51.326656,9.931369,11.325292,51.323936,8.99815,11.833172,51.388172,9.059309,11.921032,51.202911,9.019916,11.681358,51.537373,9.118774,11.506269,51.610531,9.228501,11.36198,51.632923,9.377963,11.21053,51.610439,9.537981,11.123301,51.498337,9.665677,11.123665,51.326656,8.94301,11.779685,51.313713,9.059309,11.921032,51.202911,8.924411,11.588716,51.408405,9.008494,11.399295,51.461613,9.118221,11.255006,51.484005,9.282458,11.117887,51.481472,9.482841,11.069814,51.423878,9.665677,11.123665,51.326656,8.927946,11.752349,51.211384,9.059309,11.921032,51.202911,8.89832,11.541369,51.231167,8.978368,11.344624,51.256954,9.088095,11.200335,51.279346,9.256368,11.070541,51.304234,9.467777,11.042479,51.321548,9.665677,11.123665,51.326656,10.038126,10.559238,45.864532,10.303818,10.760865,45.861816,9.665677,11.123665,51.326656,9.697449,11.558232,45.738075,9.431758,11.356605,45.740791,9.059309,11.921032,51.202911,-18.268682,-7.401923,72.607536,-18.234619,-7.44671,72.582214,-18.234619,-7.44671,72.582214,-17.591249,-6.752421,73.903381,-13.721506,-14.381048,5.353438,-13.687444,-14.425836,5.328121,-13.687444,-14.425836,5.328121,-7.921443,0.717255,74.651749,-7.921443,0.717255,74.651749,-7.921443,0.717255,74.651749,-7.889759,0.675592,74.658211,-3.079532,4.392192,74.605606,-3.079532,4.392192,74.605606,-3.079532,4.392192,74.605606,-3.079532,4.392192,74.605606,-3.047849,4.350529,74.612076,-3.009991,4.314536,73.764435,7.545488,12.190644,72.361549,7.579551,12.145857,72.336235,7.579551,12.145857,72.336235,7.579551,12.145857,72.336235,7.579551,12.145857,72.336235,12.092664,5.211518,5.107457,12.126726,5.166731,5.082139,12.126726,5.166731,5.082139],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,0,2,3,1,4,5,1,5,2,4,6,7,4,7,5,6,8,9,6,9,7,8,10,11,8,11,9,10,12,13,10,13,11,14,1,15,14,16,4,14,4,1,16,17,6,16,6,4,17,18,8,17,8,6,18,19,10,18,10,8,19,20,10,21,14,22,21,23,16,21,16,14,23,24,17,23,17,16,24,25,18,24,18,17,25,26,19,25,19,18,26,27,19,28,21,29,28,30,23,28,23,21,30,31,24,30,24,23,31,32,25,31,25,24,32,33,26,32,26,25,33,34,26,35,36,37,35,37,38,36,39,40,36,40,37,39,41,42,39,42,40,41,43,44,41,44,42,43,45,46,43,46,44,45,47,48,45,48,46,49,36,50,49,51,39,49,39,36,51,52,41,51,41,39,52,53,43,52,43,41,53,54,45,53,45,43,54,55,45,56,49,57,56,58,51,56,51,49,58,59,52,58,52,51,59,60,53,59,53,52,60,61,54,60,54,53,61,62,54,63,56,64,63,65,58,63,58,56,65,66,59,65,59,58,66,67,60,66,60,59,67,68,61,67,61,60,68,69,61,70,71,72,70,72,73,71,74,75,71,75,72,74,76,77,74,77,75,76,78,79,76,79,77,78,80,81,78,81,79,80,82,83,80,83,81,84,71,85,84,86,74,84,74,71,86,87,76,86,76,74,87,88,78,87,78,76,88,89,80,88,80,78,89,90,80,91,84,92,91,93,86,91,86,84,93,94,87,93,87,86,94,95,88,94,88,87,95,96,89,95,89,88,96,97,89,98,91,99,98,100,93,98,93,91,100,101,94,100,94,93,101,102,95,101,95,94,102,103,96,102,96,95,103,104,96,105,106,107,105,107,108,106,109,110,106,110,107,109,111,112,109,112,110,111,113,114,111,114,112,113,115,116,113,116,114,115,117,118,115,118,116,119,106,120,119,121,109,119,109,106,121,122,111,121,111,109,122,123,113,122,113,111,123,124,115,123,115,113,124,125,115,126,119,127,126,128,121,126,121,119,128,129,122,128,122,121,129,130,123,129,123,122,130,131,124,130,124,123,131,132,124,133,126,134,133,135,128,133,128,126,135,136,129,135,129,128,136,137,130,136,130,129,137,138,131,137,131,130,138,139,131,108,140,141,108,141,142,143,144,145,143,145,146,147,148,149,147,149,150,118,151,148,118,148,147,152,153,151,152,151,118,351,154,155,351,155,368,355,156,350,355,350,352,373,157,354,373,354,356,369,367,372,369,372,374,150,158,159,150,159,160,150,160,146,150,146,152,149,161,158,149,158,150,153,145,144,370,366,361,371,162,163,371,163,164,371,164,161,371,161,149,152,146,145,152,145,153,165,158,161,143,165,161,362,353,357,161,164,165,166,167,168,166,168,169,170,171,172,170,172,173,363,174,175,363,175,358,176,177,365,176,365,364,178,179,180,178,180,181,359,360,182,359,182,183,184,185,186,184,186,187,185,188,189,185,189,186,188,190,191,188,191,189,190,192,193,190,193,191,192,194,195,192,195,193,194,196,197,194,197,195,196,198,199,196,199,197,200,185,201,200,202,188,200,188,185,202,203,190,202,190,188,203,204,192,203,192,190,204,205,194,204,194,192,205,206,196,205,196,194,206,207,196,208,200,209,208,210,202,208,202,200,210,211,203,210,203,202,211,212,204,211,204,203,212,213,205,212,205,204,213,214,206,213,206,205,214,215,206,216,208,217,216,218,210,216,210,208,218,219,211,218,211,210,219,220,212,219,212,211,220,221,213,220,213,212,221,222,214,221,214,213,222,223,214,224,225,226,224,226,227,225,228,229,225,229,226,228,230,231,228,231,229,230,232,233,230,233,231,232,234,235,232,235,233,234,236,237,234,237,235,236,238,239,236,239,237,240,225,241,240,242,228,240,228,225,242,243,230,242,230,228,243,244,232,243,232,230,244,245,234,244,234,232,245,246,236,245,236,234,246,247,236,248,240,249,248,250,242,248,242,240,250,251,243,250,243,242,251,252,244,251,244,243,252,253,245,252,245,244,253,254,246,253,246,245,254,255,246,256,248,257,256,258,250,256,250,248,258,259,251,258,251,250,259,260,252,259,252,251,260,261,253,260,253,252,261,262,254,261,254,253,262,263,254,264,265,266,264,266,267,265,268,269,265,269,266,268,270,271,268,271,269,270,272,273,270,273,271,272,274,275,272,275,273,274,276,277,274,277,275,276,278,279,276,279,277,280,265,281,280,282,268,280,268,265,282,283,270,282,270,268,283,284,272,283,272,270,284,285,274,284,274,272,285,286,276,285,276,274,286,287,276,288,280,289,288,290,282,288,282,280,290,291,283,290,283,282,291,292,284,291,284,283,292,293,285,292,285,284,293,294,286,293,286,285,294,295,286,296,288,297,296,298,290,296,290,288,298,299,291,298,291,290,299,300,292,299,292,291,300,301,293,300,293,292,301,302,294,301,294,293,302,303,294,304,305,306,304,306,307,305,308,309,305,309,306,308,310,311,308,311,309,310,312,313,310,313,311,312,314,315,312,315,313,314,316,317,314,317,315,316,318,319,316,319,317,320,305,321,320,322,308,320,308,305,322,323,310,322,310,308,323,324,312,323,312,310,324,325,314,324,314,312,325,326,316,325,316,314,326,327,316,328,320,329,328,330,322,328,322,320,330,331,323,330,323,322,331,332,324,331,324,323,332,333,325,332,325,324,333,334,326,333,326,325,334,335,326,336,328,337,336,338,330,336,330,328,338,339,331,338,331,330,339,340,332,339,332,331,340,341,333,340,333,332,341,342,334,341,334,333,342,343,334,344,345,319,344,319,346,307,347,348,307,348,349]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [-3.771897,-0.175522,40.075114],\n\t\t\t\t\t\t\"radius\": 40.856465\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"6199E3D0-B1B2-4E2A-BCB9-AF7B5828F61F\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596,0.600751,-0.78998,0.122596],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [-18.268682,-7.401923,72.607536,-13.721506,-14.381048,5.353438,12.092664,5.211518,5.107457,7.545488,12.190644,72.361549],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"uv\": {\n\t\t\t\t\t\t\t\"itemSize\": 2,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0.98253,0.01747,0.98253,0.98253,0.01747,0.98253,0.01747,0.01747],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,0,2,3]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [-3.088009,-1.095202,38.857497],\n\t\t\t\t\t\t\"radius\": 37.578011\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"0213477F-EDA4-4BDC-84B9-CABC9CBC7610\",\n\t\t\t\t\"type\": \"PlaneBufferGeometry\",\n\t\t\t\t\"width\": 1,\n\t\t\t\t\"height\": 2.04,\n\t\t\t\t\"widthSegments\": 1,\n\t\t\t\t\"heightSegments\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"808D8D04-3741-4AE5-82B7-0A422E04AF47\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0,0,1,0,0,1,0,0,1,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [160,-90,0,160,90,0,-160,90,0,-160,-90,0,160,-90,-1,160,90,-1,160,90,0,160,-90,0,-160,-90,-1,-160,90,-1,160,90,-1,160,-90,-1,-160,-90,0,-160,90,0,-160,90,-1,-160,-90,-1,160,90,0,160,90,-1,-160,90,-1,-160,90,0,160,-90,-1,160,-90,0,-160,-90,0,-160,-90,-1],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"uv\": {\n\t\t\t\t\t\t\t\"itemSize\": 2,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [0,0,-0.5],\n\t\t\t\t\t\t\"radius\": 183.576278\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"F46F8127-9D5C-4847-8294-0022B2766A43\",\n\t\t\t\t\"type\": \"PlaneBufferGeometry\",\n\t\t\t\t\"width\": 320,\n\t\t\t\t\"height\": 180,\n\t\t\t\t\"widthSegments\": 1,\n\t\t\t\t\"heightSegments\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"1CA965BA-DB92-46C1-8D50-59DC106DD3A1\",\n\t\t\t\t\"type\": \"PlaneBufferGeometry\",\n\t\t\t\t\"width\": 320,\n\t\t\t\t\"height\": 180,\n\t\t\t\t\"widthSegments\": 1,\n\t\t\t\t\"heightSegments\": 1\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"A48910CF-8696-42E3-A3A3-32223FD0D660\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0,0,1,0,0,1,0,0,1,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0,-0.022991,-0.044164,-0.99876,-0.022991,0.044164,-0.99876,0.022991,0.044164,-0.99876,0.022991,-0.044164,-0.99876,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-0.002506,0.002506,0.999994,-0.002506,0.002506,0.999994,0.002506,0.002506,0.999994,0.002506,0.002506,0.999994,-0.002506,-0.002506,0.999994,-0.002506,-0.002506,0.999994,0.002506,-0.002506,0.999994,0.002506,-0.002506,0.999994,0,1,0,0,1,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,-0.034497,-0.066266,-0.997206,0.034497,-0.066266,-0.997206,-0.034497,0.066266,-0.997206,0.034497,0.066266,-0.997206],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [160,-90,12.449879,160,90,12.449879,-160,90,12.449879,-160,-90,12.449879,170,-100,10,170,100,10,170,100,15,170,-100,15,-25,-25,0,-25,25,0,25,25,0,25,-25,0,-170,-100,15,-170,100,15,-170,100,10,-170,-100,10,170,100,15,170,100,10,-170,100,10,-170,100,15,170,-100,10,170,-100,15,-170,-100,15,-170,-100,10,170,-100,15,160,-90,14.949879,-160,-90,14.949879,-170,-100,15,170,100,15,160,90,14.949879,-170,100,15,-160,90,14.949879,160,-90,14.949879,160,-90,12.449879,-160,-90,12.449879,-160,-90,14.949879,160,90,14.949879,160,90,12.449879,160,-90,12.449879,160,-90,14.949879,-160,90,14.949879,-160,90,12.449879,160,90,12.449879,160,90,14.949879,-160,-90,14.949879,-160,-90,12.449879,-160,90,12.449879,-160,90,14.949879,-170,-100,10,170,-100,10,-170,100,10,170,100,10],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"uv\": {\n\t\t\t\t\t\t\t\"itemSize\": 2,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0.982428,0.017572,0.982428,0.982428,0.017572,0.982428,0.017572,0.017572,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,0.982428,0.017572,0.017572,0.017572,0,0,1,1,0.982428,0.982428,0,1,0.017572,0.982428,0.982428,0.017572,0.982428,0.017572,0.017572,0.017572,0.017572,0.017572,0.982428,0.982428,0.982428,0.982428,0.982428,0.017572,0.982428,0.017572,0.017572,0.982428,0.017572,0.982428,0.982428,0.982428,0.982428,0.982428,0.017572,0.017572,0.017572,0.017572,0.017572,0.982428,0.017572,0.982428,1,0,0,0,1,1,0,1],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23,24,25,26,24,26,27,28,29,25,28,25,24,30,31,29,30,29,28,27,26,31,27,31,30,32,33,34,32,34,35,36,37,38,36,38,39,40,41,42,40,42,43,44,45,46,44,46,47,48,8,11,48,11,49,50,9,8,50,8,48,51,10,9,51,9,50,49,11,10,49,10,51]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [0,0,7.5],\n\t\t\t\t\t\t\"radius\": 197.373377\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"21A34A8C-B420-4A84-9AE9-9B67B05A2E9B\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0,0,1,0,0,1,0,0,1,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,-1,0,0,-1,0,0,-1,0,0,-1,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [12,0,24,12,24,24,-12,24,24,-12,0,24,12,0,0,12,24,0,12,24,24,12,0,24,-12,0,0,-12,24,0,12,24,0,12,0,0,-12,0,24,-12,24,24,-12,24,0,-12,0,0,12,24,24,12,24,0,-12,24,0,-12,24,24,12,0,0,12,0,24,-12,0,24,-12,0,0],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"uv\": {\n\t\t\t\t\t\t\t\"itemSize\": 2,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [0,12,12],\n\t\t\t\t\t\t\"radius\": 20.78461\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"F4081EC8-18BA-4F70-86F3-2A85E0F9E920\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0,-0.062505,0.998045,0,-0.124766,0.992186,0,-0.124766,0.992186,0,-0.062505,0.998045,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0.062096,-0.99807,0,0.062096,-0.99807,0.931271,0.022744,-0.363617,0.930734,0.045477,-0.362857,0.930734,0.045477,-0.362857,0.931271,0.022744,-0.363617,-0.931256,0.02275,-0.363655,-0.930704,0.045488,-0.362934,-0.930704,0.045488,-0.362934,-0.931256,0.02275,-0.363655,0,0.123952,-0.992288,0,0.123952,-0.992288,0.931326,0,-0.364188,0.931326,0,-0.364188,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-0.931326,0,-0.364187,-0.931326,0,-0.364187,0,0.845814,0.533478,0,0.845814,0.533478,0,0.845814,0.533478,0,0.845814,0.533478],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [19.5,13,-75.03331,19.5,180,-54.033306,-19.5,180,-54.033306,-19.5,13,-75.03331,-19.5,0,-75.03331,19.5,0,-75.03331,9.75,0,-99.96669,-9.75,0,-99.96669,-9.75,13,-99.96669,9.75,13,-99.96669,9.75,13,-99.96669,9.75,194.658997,-77.274734,19.5,180,-54.033306,19.5,13,-75.03331,-19.5,13,-75.03331,-19.5,180,-54.033306,-9.75,194.658997,-77.274734,-9.75,13,-99.96669,-9.75,194.658997,-77.274734,9.75,194.658997,-77.274734,19.5,0,-75.03331,9.75,0,-99.96669,-19.5,0,-75.03331,-9.75,0,-99.96669,9.75,0,-99.96669,19.5,0,-75.03331,-9.75,0,-99.96669,-19.5,0,-75.03331,19.5,180,-54.033306,9.75,194.658997,-77.274734,-9.75,194.658997,-77.274734,-19.5,180,-54.033306],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"uv\": {\n\t\t\t\t\t\t\t\"itemSize\": 2,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0.320727,1,0.320727,1,0.699311,1,0.699311,1,0.699311,0,0.320727,0,0.320727,0,0.699311,0,0.699311,1,0.320727,1,0.320727,1,0.320727,1,0.320727,1,0.320727,1,0.699311,1,0.699311,1,0.699311,1,0.699311,1,0.699311,1,0.320727,1,0.320727,0,0.320727,0,0.699311,0,0.699311,0,0.320727,0,0.320727,0,0.699311,0,0.699311,0,0.320727,1,0.320727,1,0.699311,1,0.699311,1],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,0,2,3,0,3,4,0,4,5,6,7,8,6,8,9,10,11,12,10,12,13,14,15,16,14,16,17,8,18,19,8,19,9,20,21,10,20,10,13,22,23,24,22,24,25,14,17,26,14,26,27,28,29,30,28,30,31]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [0,97.329498,-76.999998],\n\t\t\t\t\t\t\"radius\": 100.476677\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"15887E0B-7C66-441B-8371-13B37531F161\",\n\t\t\t\t\"type\": \"BufferGeometry\",\n\t\t\t\t\"data\": {\n\t\t\t\t\t\"attributes\": {\n\t\t\t\t\t\t\"normal\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [0,0.707107,0.707107,0,0.707107,0.707107,0,0.707107,0.707107,0,0.707107,0.707107,0.707107,0.707107,0,0.707107,0.707107,0,0.707107,0.707107,0,0.707107,0.707107,0,0,0.707107,-0.707107,0,0.707107,-0.707107,0,0.707107,-0.707107,0,0.707107,-0.707107,-0.707107,0.707107,0,-0.707107,0.707107,0,-0.707107,0.707107,0,-0.707107,0.707107,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"position\": {\n\t\t\t\t\t\t\t\"itemSize\": 3,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [85,0,0,80,5,-5,-80,5,-5,-85,0,0,85,0,-130,80,5,-125,80,5,-5,85,0,0,-85,0,-130,-80,5,-125,80,5,-125,85,0,-130,-85,0,0,-80,5,-5,-80,5,-125,-85,0,-130,80,5,-5,80,5,-125,-80,5,-125,-80,5,-5,85,0,-130,85,0,0,-85,0,0,-85,0,-130],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"uv\": {\n\t\t\t\t\t\t\t\"itemSize\": 2,\n\t\t\t\t\t\t\t\"type\": \"Float32Array\",\n\t\t\t\t\t\t\t\"array\": [1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0],\n\t\t\t\t\t\t\t\"normalized\": false\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"index\": {\n\t\t\t\t\t\t\"type\": \"Uint32Array\",\n\t\t\t\t\t\t\"array\": [0,1,2,0,2,3,4,5,6,4,6,7,8,9,10,8,10,11,12,13,14,12,14,15,16,17,18,16,18,19,20,21,22,20,22,23]\n\t\t\t\t\t},\n\t\t\t\t\t\"boundingSphere\": {\n\t\t\t\t\t\t\"center\": [0,2.5,-65],\n\t\t\t\t\t\t\"radius\": 107.033873\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}],\n\t\t\"materials\": [\n\t\t\t{\n\t\t\t\t\"uuid\": \"2173F7A1-6849-4A38-9F06-EBE121BD191A\",\n\t\t\t\t\"type\": \"MeshStandardMaterial\",\n\t\t\t\t\"color\": 16777215,\n\t\t\t\t\"roughness\": 0.62,\n\t\t\t\t\"metalness\": 0.02,\n\t\t\t\t\"emissive\": 1381654,\n\t\t\t\t\"depthFunc\": 3,\n\t\t\t\t\"depthTest\": true,\n\t\t\t\t\"depthWrite\": true\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"6FB7F440-A263-4992-88B0-6A4F41A01C9F\",\n\t\t\t\t\"type\": \"MeshStandardMaterial\",\n\t\t\t\t\"name\": \"LaserMaterial\",\n\t\t\t\t\"color\": 16726272,\n\t\t\t\t\"roughness\": 0.5,\n\t\t\t\t\"metalness\": 0.5,\n\t\t\t\t\"emissive\": 16740096,\n\t\t\t\t\"opacity\": 0.5,\n\t\t\t\t\"transparent\": true,\n\t\t\t\t\"depthFunc\": 3,\n\t\t\t\t\"depthTest\": true,\n\t\t\t\t\"depthWrite\": true\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"922D79CF-2713-4818-BD6A-71AB3D8DE137\",\n\t\t\t\t\"type\": \"MeshStandardMaterial\",\n\t\t\t\t\"color\": 12237498,\n\t\t\t\t\"roughness\": 0.34,\n\t\t\t\t\"metalness\": 0.04,\n\t\t\t\t\"emissive\": 460551,\n\t\t\t\t\"depthFunc\": 3,\n\t\t\t\t\"depthTest\": true,\n\t\t\t\t\"depthWrite\": true,\n\t\t\t\t\"skinning\": true\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"BA212800-C855-483B-B6F5-CD93D5F615B2\",\n\t\t\t\t\"type\": \"MeshPhysicalMaterial\",\n\t\t\t\t\"color\": 986895,\n\t\t\t\t\"roughness\": 0.86,\n\t\t\t\t\"metalness\": 0,\n\t\t\t\t\"emissive\": 0,\n\t\t\t\t\"clearCoat\": 0.16,\n\t\t\t\t\"clearCoatRoughness\": 0.24,\n\t\t\t\t\"opacity\": 0.5,\n\t\t\t\t\"transparent\": true,\n\t\t\t\t\"depthFunc\": 3,\n\t\t\t\t\"depthTest\": true,\n\t\t\t\t\"depthWrite\": true\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"0E3C503B-FC51-49C3-8B3D-990ADAC77732\",\n\t\t\t\t\"type\": \"MeshStandardMaterial\",\n\t\t\t\t\"color\": 16711785,\n\t\t\t\t\"roughness\": 0.5,\n\t\t\t\t\"metalness\": 0.5,\n\t\t\t\t\"emissive\": 0,\n\t\t\t\t\"depthFunc\": 3,\n\t\t\t\t\"depthTest\": true,\n\t\t\t\t\"depthWrite\": true\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"ACD47A79-A999-4EB3-9816-D701E05268DA\",\n\t\t\t\t\"type\": \"MeshPhysicalMaterial\",\n\t\t\t\t\"color\": 7500146,\n\t\t\t\t\"roughness\": 0.14,\n\t\t\t\t\"metalness\": 1,\n\t\t\t\t\"emissive\": 0,\n\t\t\t\t\"clearCoat\": 0,\n\t\t\t\t\"clearCoatRoughness\": 0,\n\t\t\t\t\"blending\": 2,\n\t\t\t\t\"transparent\": true,\n\t\t\t\t\"depthFunc\": 3,\n\t\t\t\t\"depthTest\": true,\n\t\t\t\t\"depthWrite\": true\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"92C9B818-59C4-46E9-9CE7-618CA4F8E56E\",\n\t\t\t\t\"type\": \"MeshStandardMaterial\",\n\t\t\t\t\"color\": 0,\n\t\t\t\t\"roughness\": 0.5,\n\t\t\t\t\"metalness\": 0.5,\n\t\t\t\t\"emissive\": 0,\n\t\t\t\t\"blending\": 0,\n\t\t\t\t\"depthFunc\": 3,\n\t\t\t\t\"depthTest\": true,\n\t\t\t\t\"depthWrite\": true\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"uuid\": \"33528C76-5006-4A16-B8CA-D4447E0FF2E8\",\n\t\t\t\t\"type\": \"MeshPhysicalMaterial\",\n\t\t\t\t\"color\": 5921370,\n\t\t\t\t\"roughness\": 0.36,\n\t\t\t\t\"metalness\": 0,\n\t\t\t\t\"emissive\": 0,\n\t\t\t\t\"clearCoat\": 0.6,\n\t\t\t\t\"clearCoatRoughness\": 0.32,\n\t\t\t\t\"blending\": 2,\n\t\t\t\t\"opacity\": 0.5,\n\t\t\t\t\"transparent\": true,\n\t\t\t\t\"depthFunc\": 3,\n\t\t\t\t\"depthTest\": true,\n\t\t\t\t\"depthWrite\": true,\n\t\t\t\t\"skinning\": true\n\t\t\t}],\n\t\t\"object\": {\n\t\t\t\"uuid\": \"31517222-A9A7-4EAF-B5F6-60751C0BABA3\",\n\t\t\t\"type\": \"Scene\",\n\t\t\t\"name\": \"Scene\",\n\t\t\t\"layers\": 1,\n\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\"children\": [\n\t\t\t\t{\n\t\t\t\t\t\"uuid\": \"63587600-49CC-477F-AA94-D453D19BA448\",\n\t\t\t\t\t\"type\": \"Group\",\n\t\t\t\t\t\"name\": \"Wrapper\",\n\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"uuid\": \"735B16FD-856F-4725-8579-792E82920C5D\",\n\t\t\t\t\t\t\t\"type\": \"Group\",\n\t\t\t\t\t\t\t\"name\": \"Group\",\n\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"uuid\": \"3DF2B87C-188B-44DB-8F0B-9524E6BDA146\",\n\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\"name\": \"Plane\",\n\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,-5.165,-2.26,-14.80204,1],\n\t\t\t\t\t\t\t\t\t\"geometry\": \"D7227094-70CB-489B-B20D-E5ACC790A53A\",\n\t\t\t\t\t\t\t\t\t\"material\": \"2173F7A1-6849-4A38-9F06-EBE121BD191A\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"uuid\": \"6DF1A5E2-C0AC-43C2-84A8-1D878C979908\",\n\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\"name\": \"Plane\",\n\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,0,-1,0,0,1,0,0,0,-6.375,-90.72,1],\n\t\t\t\t\t\t\t\t\t\"geometry\": \"FB0A18E0-4EC7-403E-B939-867DBFED1864\",\n\t\t\t\t\t\t\t\t\t\"material\": \"2173F7A1-6849-4A38-9F06-EBE121BD191A\"\n\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"uuid\": \"209721AA-DDA3-46F7-A0B2-A5DF81A023A8\",\n\t\t\t\t\t\t\t\"type\": \"Group\",\n\t\t\t\t\t\t\t\"name\": \"Phone\",\n\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,0.980462,0.196709,0,0,-0.196709,0.980462,0,0,-4.578,7.193,1],\n\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"uuid\": \"FA2FECE9-703B-46B2-A26F-AC5663C11CFB\",\n\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\"name\": \"Laser\",\n\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,0,-1,0,0,1,0,0,0,0,-6.581152,1],\n\t\t\t\t\t\t\t\t\t\"geometry\": \"7199EAA3-8D36-426E-9BB2-5D38D9BFF238\",\n\t\t\t\t\t\t\t\t\t\"material\": \"6FB7F440-A263-4992-88B0-6A4F41A01C9F\"\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"uuid\": \"66C171F1-8E6B-4A51-AFE3-8409045228B9\",\n\t\t\t\t\t\t\t\t\t\"type\": \"Scene\",\n\t\t\t\t\t\t\t\t\t\"name\": \"phone.gltf\",\n\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\"matrix\": [0.03,0,0,0,0,0,-0.03,0,0,0.03,0,0,0.1,-0.1,1.2,1],\n\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"AD4DB785-D4F4-415E-9A7C-57287C218909\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\"name\": \"RootNode_(gltf_orientation_matrix)\",\n\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [0.799674,-0.071099,0.596211,0,0.600412,0.103346,-0.792985,0,-0.005236,0.992101,0.125332,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"A53527F8-6A53-4C23-A118-627B65732AA3\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"RootNode_(model_correction_matrix)\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"33CDA73C-A434-4F49-88CD-7F288E0B1123\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"5754c6a4dce942aa90793c09e21aea35objcleanermaterialmergergles\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"74B50260-9840-44EC-8314-C18D44CD70C1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,-0.089631,0.050364,-0.098647,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"6FF753BE-ACD3-48F9-8324-819EC5AAAF47\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"922D79CF-2713-4818-BD6A-71AB3D8DE137\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"744ABC7B-98A0-4A81-A511-3914682960E7\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"676CF2FA-78A4-4A8F-B7C9-00A0243C5EBF\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"922D79CF-2713-4818-BD6A-71AB3D8DE137\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"EFE07253-64FF-4669-B930-8FF7948D8FB8\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"D4790B01-2C63-4B0C-A567-4F5C662B79E0\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"922D79CF-2713-4818-BD6A-71AB3D8DE137\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"17DB6525-B695-4E82-B6DA-8FB22A641384\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0.003826,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"14DDFFFB-0E99-4A92-BB32-FBF228DB96B4\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"922D79CF-2713-4818-BD6A-71AB3D8DE137\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"2C610D23-0CCC-43A5-ACD4-CC37E200711F\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1.02,0,0,0,0,1.02,0,0,0,0,1.02,0,0.1897,0.260818,-0.695928,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"6199E3D0-B1B2-4E2A-BCB9-AF7B5828F61F\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"BA212800-C855-483B-B6F5-CD93D5F615B2\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"uuid\": \"FB1A9D4A-AA75-4ACC-AF35-4B366A1DF865\",\n\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\"name\": \"PhoneDisplay\",\n\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,0,-1,0,0,1,0,0,0,0.005713,0.038968,1],\n\t\t\t\t\t\t\t\t\t\"geometry\": \"0213477F-EDA4-4BDC-84B9-CABC9CBC7610\",\n\t\t\t\t\t\t\t\t\t\"material\": \"0E3C503B-FC51-49C3-8B3D-990ADAC77732\"\n\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"uuid\": \"AB051D3A-6B97-4773-ACCF-2FFE7194B157\",\n\t\t\t\t\t\t\t\"type\": \"Group\",\n\t\t\t\t\t\t\t\"name\": \"Screen\",\n\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,-2.04,0,1],\n\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"uuid\": \"B1DA7CB4-9700-4CD9-89CB-65FF6708EBEC\",\n\t\t\t\t\t\t\t\t\t\"type\": \"Group\",\n\t\t\t\t\t\t\t\t\t\"name\": \"ScreenContainer\",\n\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,-4.267,-2.3,1],\n\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"B685D636-C9AA-4E16-A056-9D31CB3E158E\",\n\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Scene\",\n\t\t\t\t\t\t\t\t\t\t\t\"name\": \"scene.gltf\",\n\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [0.03,0,0,0,0,0.03,0,0,0,0,0.03,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"CA2237D7-78A2-497F-88BE-60A1A665B8F4\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"RootNode_(gltf_orientation_matrix)\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,0,-1,0,0,1,0,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"53C8700D-6D67-4F1A-8C4F-8032ACC0E57A\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"RootNode_(model_correction_matrix)\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"BC7807EA-6C73-4DD8-8491-2831A57FDC21\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"43877d2b4db942439d9cbde88b41c052fbx\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,0,1,0,0,-1,0,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"CD4566C9-91F2-436C-BDB2-81849F7B9041\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"307D4554-7A57-4CE6-A32A-9C1BDA972546\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"RootNode\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"F4ACD608-882E-4B05-89ED-A926F568F3A1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Screens\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,142.544998,75.5,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"5AC09370-F6D7-4827-AD6D-418A4A202E41\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Screen1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0.01,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"F55CF37B-FEBA-45B7-BB92-5E5A034E729D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Screen1_LeiaR2_0\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"visible\": false,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1.02,0,0,0,0,1.02,0,0,0,0,1.02,0,1.631274,0,1.151288,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"808D8D04-3741-4AE5-82B7-0A422E04AF47\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"ACD47A79-A999-4EB3-9816-D701E05268DA\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"6B3258C0-A532-4151-8B20-B6C89E5C02C9\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Monitor_Flat\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,69.772484,69.125,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"7915DA14-9FA6-498B-B803-AFDDF15AA7CC\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"MonitorContainer\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,0.987688,-0.156434,0,0,0.156434,0.987688,0,0,89.013,-6.125,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"1C9FF041-52F9-4606-8308-1D69559F060C\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"ScreenBackground\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,12.87,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"F46F8127-9D5C-4847-8294-0022B2766A43\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"92C9B818-59C4-46E9-9CE7-618CA4F8E56E\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"1F657FDE-4FA6-46D0-9F8A-506063960E9C\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"DisplayPlane\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0.002,0,12.892,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"1CA965BA-DB92-46C1-8D50-59DC106DD3A1\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"33528C76-5006-4A16-B8CA-D4447E0FF2E8\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"2B16E786-D6E6-4DF0-8F71-AEA1397B9719\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Monitor_Black_0\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"A48910CF-8696-42E3-A3A3-32223FD0D660\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"922D79CF-2713-4818-BD6A-71AB3D8DE137\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"D18AA898-FD1D-4CCF-8F9D-5A2CC7B46700\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Neck\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,66.772446,-37.125,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"7116FF7E-91BB-4CDE-A4AE-EBA8FF37BECA\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Neck_Smaller\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,6,12,1]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"62A11B54-FC58-4C09-9704-E93CB69118DF\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Neck_Larger\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,-6,-12,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"C2F76454-7671-4A05-94CB-761C429BD2B9\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Neck_Larger_Black_0\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"21A34A8C-B420-4A84-9AE9-9B67B05A2E9B\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"922D79CF-2713-4818-BD6A-71AB3D8DE137\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"6FE7191D-0FB8-4123-B6E0-69EF782460F9\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Stem\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,-69.772484,12.375,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"22954CE4-97E5-4416-9C37-7CF75C685DAA\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Stem_Black_0\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"F4081EC8-18BA-4F70-86F3-2A85E0F9E920\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"922D79CF-2713-4818-BD6A-71AB3D8DE137\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"D91252B5-9FBE-4F07-9D49-EE64F5011ABA\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Object3D\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Base\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,-69.772484,30.875,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"uuid\": \"74B81EEE-781B-4C7F-B2F1-2FF1CEFA1ADB\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"type\": \"Mesh\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"name\": \"Base_Black_0\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0.901592,-0.594763,-2.308714,1],\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"geometry\": \"15887E0B-7C66-441B-8371-13B37531F161\",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"material\": \"922D79CF-2713-4818-BD6A-71AB3D8DE137\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t\t\t}]\n\t\t\t\t\t\t}]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"uuid\": \"048D3575-9519-4A93-AC41-9C011563C8DE\",\n\t\t\t\t\t\"type\": \"SpotLight\",\n\t\t\t\t\t\"name\": \"SpotLight\",\n\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,-8.7902,-1.354546,6.176378,1],\n\t\t\t\t\t\"color\": 4672255,\n\t\t\t\t\t\"intensity\": 0.54,\n\t\t\t\t\t\"distance\": 0,\n\t\t\t\t\t\"angle\": 0.494,\n\t\t\t\t\t\"decay\": 0,\n\t\t\t\t\t\"penumbra\": 1,\n\t\t\t\t\t\"shadow\": {\n\t\t\t\t\t\t\"radius\": 2.88,\n\t\t\t\t\t\t\"camera\": {\n\t\t\t\t\t\t\t\"uuid\": \"07F9B6F2-ACEA-4BDF-94EB-E398B420A697\",\n\t\t\t\t\t\t\t\"type\": \"PerspectiveCamera\",\n\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\"fov\": 56.60823,\n\t\t\t\t\t\t\t\"zoom\": 1,\n\t\t\t\t\t\t\t\"near\": 0.5,\n\t\t\t\t\t\t\t\"far\": 0.02,\n\t\t\t\t\t\t\t\"focus\": 10,\n\t\t\t\t\t\t\t\"aspect\": 1,\n\t\t\t\t\t\t\t\"filmGauge\": 35,\n\t\t\t\t\t\t\t\"filmOffset\": 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"uuid\": \"4FD1B9B1-7854-46C5-898A-DC165D851E01\",\n\t\t\t\t\t\"type\": \"SpotLight\",\n\t\t\t\t\t\"name\": \"SpotLight\",\n\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,-8.551407,5.056665,9.496512,1],\n\t\t\t\t\t\"color\": 13238383,\n\t\t\t\t\t\"intensity\": 0.16,\n\t\t\t\t\t\"distance\": 0,\n\t\t\t\t\t\"angle\": 0.774,\n\t\t\t\t\t\"decay\": 1,\n\t\t\t\t\t\"penumbra\": 0.62,\n\t\t\t\t\t\"shadow\": {\n\t\t\t\t\t\t\"radius\": 1.28,\n\t\t\t\t\t\t\"camera\": {\n\t\t\t\t\t\t\t\"uuid\": \"C06E6D74-6E4D-4039-AED9-C13C0A824DFD\",\n\t\t\t\t\t\t\t\"type\": \"PerspectiveCamera\",\n\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\"fov\": 88.693867,\n\t\t\t\t\t\t\t\"zoom\": 1,\n\t\t\t\t\t\t\t\"near\": 0.5,\n\t\t\t\t\t\t\t\"far\": 500,\n\t\t\t\t\t\t\t\"focus\": 10,\n\t\t\t\t\t\t\t\"aspect\": 1,\n\t\t\t\t\t\t\t\"filmGauge\": 35,\n\t\t\t\t\t\t\t\"filmOffset\": 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"uuid\": \"971B9AD9-EC42-4D20-B387-DA2F05875B26\",\n\t\t\t\t\t\"type\": \"HemisphereLight\",\n\t\t\t\t\t\"name\": \"HemisphereLight\",\n\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,10,0,1],\n\t\t\t\t\t\"color\": 12452095,\n\t\t\t\t\t\"intensity\": 0.08,\n\t\t\t\t\t\"groundColor\": 7405812\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"uuid\": \"4CC6CA38-D7DC-43BC-BF9D-D347D4920294\",\n\t\t\t\t\t\"type\": \"SpotLight\",\n\t\t\t\t\t\"name\": \"SpotLightTop\",\n\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,-8.2,3.3,1.1,1],\n\t\t\t\t\t\"color\": 4672255,\n\t\t\t\t\t\"intensity\": 0.16,\n\t\t\t\t\t\"distance\": 0,\n\t\t\t\t\t\"angle\": 0.494,\n\t\t\t\t\t\"decay\": 0,\n\t\t\t\t\t\"penumbra\": 1,\n\t\t\t\t\t\"shadow\": {\n\t\t\t\t\t\t\"radius\": 2.88,\n\t\t\t\t\t\t\"camera\": {\n\t\t\t\t\t\t\t\"uuid\": \"C86580BF-32E7-48CA-95E6-BD43F68E3AF9\",\n\t\t\t\t\t\t\t\"type\": \"PerspectiveCamera\",\n\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\"fov\": 56.60823,\n\t\t\t\t\t\t\t\"zoom\": 1,\n\t\t\t\t\t\t\t\"near\": 0.5,\n\t\t\t\t\t\t\t\"far\": 0.02,\n\t\t\t\t\t\t\t\"focus\": 10,\n\t\t\t\t\t\t\t\"aspect\": 1,\n\t\t\t\t\t\t\t\"filmGauge\": 35,\n\t\t\t\t\t\t\t\"filmOffset\": 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"uuid\": \"DA84844F-EFEF-47BB-A3F4-B2E4A3E5720F\",\n\t\t\t\t\t\"type\": \"SpotLight\",\n\t\t\t\t\t\"name\": \"SpotLight\",\n\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\"matrix\": [1,0,0,0,0,1,0,0,0,0,1,0,0,-0.024428,-0.304986,1],\n\t\t\t\t\t\"color\": 2886473,\n\t\t\t\t\t\"intensity\": 1.56,\n\t\t\t\t\t\"distance\": 0,\n\t\t\t\t\t\"angle\": 1.234,\n\t\t\t\t\t\"decay\": 1,\n\t\t\t\t\t\"penumbra\": 0.6,\n\t\t\t\t\t\"shadow\": {\n\t\t\t\t\t\t\"camera\": {\n\t\t\t\t\t\t\t\"uuid\": \"ADF8A8AE-1058-48BF-BBAD-6C3743BC8118\",\n\t\t\t\t\t\t\t\"type\": \"PerspectiveCamera\",\n\t\t\t\t\t\t\t\"layers\": 1,\n\t\t\t\t\t\t\t\"fov\": 50,\n\t\t\t\t\t\t\t\"zoom\": 1,\n\t\t\t\t\t\t\t\"near\": 0.5,\n\t\t\t\t\t\t\t\"far\": 500,\n\t\t\t\t\t\t\t\"focus\": 10,\n\t\t\t\t\t\t\t\"aspect\": 1,\n\t\t\t\t\t\t\t\"filmGauge\": 35,\n\t\t\t\t\t\t\t\"filmOffset\": 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}],\n\t\t\t\"background\": 2556441,\n\t\t\t\"fog\": {\n\t\t\t\t\"type\": \"Fog\",\n\t\t\t\t\"color\": 2228244,\n\t\t\t\t\"near\": 1.22,\n\t\t\t\t\"far\": 100\n\t\t\t}\n\t\t}\n\t},\n\t\"scripts\": {}\n}"
  },
  {
    "path": "src/tools/animation/debug.js",
    "content": "import * as dat from 'dat.gui'\n// require('three/examples/js/controls/OrbitControls')\nimport { CAMERA, PHONE } from './keyframes'\n\nconst THREE = window.THREE\n\nconst round = (value) => {\n  return Math.round(value * 1000000) / 1000000\n}\n\nexport default class AnimationDebug {\n  constructor(rectLight, cameraAnimation, phoneAnimation) {\n    this.gui = null\n\n    this.rectLight = rectLight\n    this.cameraAnimation = cameraAnimation\n    this.phoneAnimation = phoneAnimation\n\n    window.getCameraState = (init) => {\n      this.camera.updateMatrixWorld()\n      this.camera.updateProjectionMatrix()\n\n      let sourceValues = CAMERA[init]\n\n      let newValues = {\n        offset: sourceValues.offset,\n        options: sourceValues.options,\n        values: {}\n      }\n\n      newValues.values.positionX = round(this.cameraAnimation.positionX)\n      newValues.values.positionY = round(this.cameraAnimation.positionY)\n      newValues.values.positionZ = round(this.cameraAnimation.positionZ)\n\n      newValues.values.targetX = round(this.cameraAnimation.targetX)\n      newValues.values.targetY = round(this.cameraAnimation.targetY)\n      newValues.values.targetZ = round(this.cameraAnimation.targetZ)\n\n      return JSON.stringify(newValues)\n    }\n\n    window.getPhoneState = (init) => {\n      this.objectPhone.updateMatrixWorld()\n\n      let sourceValues = PHONE[init]\n\n      let newValues = {\n        offset: sourceValues.offset,\n        options: sourceValues.options,\n        values: {}\n      }\n\n      newValues.values.positionX = round(this.phoneAnimation.positionX)\n      newValues.values.positionY = round(this.phoneAnimation.positionY)\n      newValues.values.positionZ = round(this.phoneAnimation.positionZ)\n\n      newValues.values.rotationX = round(this.phoneAnimation.rotationX)\n      newValues.values.rotationY = round(this.phoneAnimation.rotationY)\n      newValues.values.rotationZ = round(this.phoneAnimation.rotationZ)\n\n      return JSON.stringify(newValues)\n    }\n  }\n\n  seekAnimation(step) {\n    if (this.animeAnimation) {\n      this.animeAnimation.seek(this.animeAnimation.duration * (step / 100))\n    }\n  }\n\n  addOrbitControls() {\n    if (this.features.orbit) {\n      this.orbit = new THREE.OrbitControls(\n        this.camera,\n        this.webgl.renderer.domElement\n      )\n      this.orbit.minDistance = 1\n      this.orbit.maxDistance = 100\n      this.orbit.enableRotate = true\n      window.orbit = this.orbit\n    }\n  }\n\n  addTargetSphere() {\n    if (this._debug) {\n      var geometry = new THREE.SphereGeometry(0.1, 32, 32)\n      var material = new THREE.MeshBasicMaterial({ color: 0xffff00 })\n      this.sphere = new THREE.Mesh(geometry, material)\n      this.webgl.scene.add(this.sphere)\n    }\n  }\n\n  updateGui() {\n    if (this._debug) {\n      window.clearTimeout(this.guiUpdateTimeline)\n\n      this.guiUpdateTimeline = window.setTimeout(() => {\n        Object.keys(this.gui.__folders).forEach((folderKey) => {\n          this.gui.__folders[folderKey].updateDisplay()\n        })\n      }, 1000)\n    }\n  }\n\n  addGui() {\n    this.gui = new dat.GUI({ width: 300 })\n    this.gui.open()\n    var param = {\n      motion: true,\n      width: this.rectLight.width,\n      height: this.rectLight.height,\n      color: this.rectLight.color.getHex(),\n      intensity: this.rectLight.intensity\n    }\n    this.gui.add(param, 'motion')\n    var lightFolder = this.gui.addFolder('Light')\n\n    lightFolder.addColor(param, 'color').onChange((val) => {\n      this.rectLight.color.setHex(val)\n    })\n\n    lightFolder\n      .add(param, 'intensity', 0.0, 10.0)\n      .step(0.01)\n      .onChange((val) => {\n        this.rectLight.intensity = val\n      })\n\n    let cameraFolder = this.gui.addFolder('Camera')\n\n    Object.keys(this.cameraAnimation).forEach((key) => {\n      cameraFolder\n        .add(this.cameraAnimation, key, -100, 100)\n        .step(0.01)\n        .onChange(this.updateCamera.bind(this))\n    })\n\n    cameraFolder\n      .add(this.camera, 'zoom', 0, 4)\n      .step(0.01)\n      .onChange(() => {\n        this.camera.updateProjectionMatrix()\n        this.updateCamera()\n      })\n\n    let phoneFolder = this.gui.addFolder('Phone')\n\n    Object.keys(this.phoneAnimation).forEach((key) => {\n      phoneFolder\n        .add(this.phoneAnimation, key, -20, 20)\n        .step(0.001)\n        .onChange(this.updatePhone.bind(this))\n    })\n\n    let spotlightFolder = this.gui.addFolder('SpotLight')\n\n    let props = ['x', 'y', 'z']\n    props.forEach((key) => {\n      spotlightFolder\n        .add(this.objectLightTop.position, key, -20, 20)\n        .step(0.001)\n    })\n\n    let actionFolder = this.gui.addFolder('Actions')\n\n    actionFolder.add(this, 'animateEnter')\n\n    actionFolder.open()\n  }\n\n  dispose() {\n    this.gui.destroy()\n  }\n}\n"
  },
  {
    "path": "src/tools/animation/index.js",
    "content": "import EventEmitter from 'eventemitter3'\nimport anime from 'animejs'\n\nimport { ANIMATION_SCREEN_VIEWPORT } from '@/settings'\nimport { PHONE, PHONE_MOBILE, CAMERA, CAMERA_MOBILE } from './keyframes'\nimport { scaleRange } from '@/tools/helpers'\n\nimport {\n  WebGLRenderer,\n  Scene,\n  Raycaster,\n  ObjectLoader,\n  SpotLightHelper,\n  Vector3,\n  Quaternion,\n  PlaneBufferGeometry,\n  TextureLoader,\n  Mesh,\n  Matrix4,\n  Object3D,\n  Math as ThreeMath,\n  MeshStandardMaterial,\n  MeshPhysicalMaterial,\n  MeshBasicMaterial\n} from 'three'\n\nimport * as THREE_CONSTANTS from 'three/src/constants'\n\nwindow.THREE = {\n  THREE_CONSTANTS,\n  Object3D,\n  MeshPhysicalMaterial,\n  MeshBasicMaterial,\n  Matrix4,\n  Vector3\n}\n\nrequire('three/examples/js/renderers/CSS3DRenderer')\n\nconst sceneObject = require('./app.json')\n\nexport default class ThreeAnimation extends EventEmitter {\n  constructor(container, viewport, isDesktop, debug, pairingEl) {\n    super()\n\n    this._debug = debug\n\n    this.pairingEl = pairingEl\n\n    this.camera = null\n\n    this.isDesktop = isDesktop\n\n    this.webgl = {\n      scene: null,\n      renderer: new WebGLRenderer({ antialias: true, alpha: true })\n    }\n\n    this.css = {\n      scene: new Scene(),\n      renderer: null\n    }\n\n    this.css.renderer = new window.THREE.CSS3DRenderer()\n\n    this.objectPhone = null\n    this.objectLightTop = null\n\n    this.objectPhoneDisplay = null\n\n    this.dom = container\n\n    this.container = null\n\n    this.width = 500\n    this.height = 500\n\n    this.cameraAnimation = {\n      positionX: 0,\n      positionY: 0,\n      positionZ: 0,\n      targetX: 0,\n      targetY: 0,\n      targetZ: 0\n    }\n\n    this.phoneAnimation = {\n      positionX: 0,\n      positionY: 0,\n      positionZ: 0,\n      rotationX: 0,\n      rotationY: 0,\n      rotationZ: 0\n    }\n\n    this.animeAnimation = null\n\n    this.raycaster = new Raycaster()\n\n    this.animationFinished = false\n\n    this.lastTick = null\n    this.tickCount = 0\n    this.tickDiffs = []\n    this.lagCount = 0\n\n    this.needsRedraw = true\n\n    this.load(sceneObject)\n  }\n\n  load(json) {\n    const loader = new ObjectLoader()\n\n    let project = json.project\n    if (project.gammaInput) this.webgl.renderer.gammaInput = true\n    if (project.gammaOutput) this.webgl.renderer.gammaOutput = true\n    if (project.shadows) this.webgl.renderer.shadowMap.enabled = true\n\n    loader.parse(json.scene, (scene) => {\n      loader.parse(json.camera, (camera) => {\n        this.camera = camera\n        this.camera.aspect = 1\n        this.camera.updateProjectionMatrix()\n\n        this.webgl.scene = scene\n\n        this.init()\n        this.emit('ready')\n      })\n    })\n  }\n\n  initPhone() {\n    const phone = this.webgl.scene.getObjectByName('PhoneDisplay')\n\n    // instantiate a loader\n    var loader = new TextureLoader()\n\n    // load a resource\n    loader.load(\n      // resource URL\n      '/drawmote-logo-phone.png',\n\n      // onLoad callback\n      (texture) => {\n        // in this example we create the material when the texture is loaded\n        texture.anisotropy = this.webgl.renderer.capabilities.getMaxAnisotropy()\n        var material = new MeshBasicMaterial({\n          map: texture\n        })\n\n        material.map.minFilter = THREE_CONSTANTS.LinearFilter\n\n        phone.material = material\n      },\n\n      // onProgress callback currently not supported\n      undefined,\n\n      // onError callback\n      function (error) {\n        // eslint-disable-next-line\n        console.error('Error loading texture:', error)\n      }\n    )\n\n    this.objectPhoneDisplay = phone\n  }\n\n  init() {\n    const pixelRatio = 1\n    this.webgl.renderer.setPixelRatio(pixelRatio)\n    this.webgl.renderer.setClearColor(0x000000, 0)\n\n    this.webgl.renderer.domElement.classList.add('renderer-webgl')\n    this.dom.appendChild(this.webgl.renderer.domElement)\n\n    this.css.renderer.setSize(window.innerWidth, window.innerHeight)\n    this.css.renderer.domElement.style.position = 'absolute'\n    this.css.renderer.domElement.style.top = 0\n    this.css.renderer.domElement.classList.add('renderer-css')\n\n    this.dom.appendChild(this.css.renderer.domElement)\n\n    const displayScreen = this.webgl.scene.getObjectByName('DisplayPlane')\n    this.objectPhone = this.webgl.scene.getObjectByName('Phone')\n\n    this.objectLightTop = this.webgl.scene.getObjectByName('SpotLightTop')\n    this.objectLightTop.target = this.objectPhone\n    if (this._debug) {\n      this.spotLightHelper = new SpotLightHelper(this.objectLightTop)\n      this.webgl.scene.add(this.spotLightHelper)\n    }\n\n    this.container = this.addCssObject('screen', displayScreen)\n\n    let screenPosition = new Vector3()\n    let screenScale = new Vector3()\n    let screenQuaternion = new Quaternion()\n\n    displayScreen.updateMatrixWorld()\n    displayScreen.getWorldPosition(screenPosition)\n    displayScreen.getWorldScale(screenScale)\n    displayScreen.getWorldQuaternion(screenQuaternion)\n\n    let intersectionPlane = new PlaneBufferGeometry(\n      displayScreen.geometry.parameters.width * screenScale.x * 2,\n      displayScreen.geometry.parameters.height * screenScale.y * 2,\n      8,\n      8\n    )\n\n    var mat = new MeshStandardMaterial({\n      color: 0x000000\n    })\n    mat.opacity = 0\n    mat.transparent = true\n    this.intersectionPlane = new Mesh(intersectionPlane, mat)\n\n    let rotObjectMatrix = new Matrix4()\n    rotObjectMatrix.makeRotationFromQuaternion(screenQuaternion)\n\n    this.intersectionPlane.position.copy(screenPosition)\n    this.intersectionPlane.position.z = this.intersectionPlane.position.z + 0\n    this.intersectionPlane.quaternion.setFromRotationMatrix(rotObjectMatrix)\n\n    this.webgl.scene.add(this.intersectionPlane)\n\n    const bg = this.webgl.scene.getObjectByName('ScreenBackground')\n\n    bg.material.blending = THREE_CONSTANTS.NoBlending\n    bg.material.opacity = 0\n\n    this.initPhone()\n  }\n\n  addCssObject(name, source) {\n    let container = document.createElement('div')\n    container.classList.add(name + '-container')\n\n    this.webgl.scene.updateMatrixWorld()\n    this.css.scene.updateMatrixWorld()\n    source.updateMatrixWorld()\n\n    const multiplier = 3\n\n    const screenWidth = ANIMATION_SCREEN_VIEWPORT.width\n    const screenHeight = ANIMATION_SCREEN_VIEWPORT.height\n    const rotation = new Quaternion()\n    const position = new Vector3()\n    const scale = new Vector3()\n    source.getWorldPosition(position)\n    source.getWorldScale(scale)\n    source.getWorldQuaternion(rotation)\n\n    const screenWrapper = document.createElement('div')\n    screenWrapper.classList.add(name + '-wrapper')\n\n    const drawingElement = document.createElement('div')\n\n    drawingElement.style.width = screenWidth + 'px'\n    drawingElement.style.height = screenHeight + 'px'\n    container.style.width = screenWidth + 'px'\n    container.style.height = screenHeight + 'px'\n\n    screenWrapper.appendChild(drawingElement)\n    container.appendChild(screenWrapper)\n\n    // create the object3d for this element\n    const cssObject = new window.THREE.CSS3DObject(container)\n    // we reference the same position and rotation\n    scale.divide(new Vector3(multiplier, multiplier, multiplier))\n\n    cssObject.quaternion.copy(rotation)\n    cssObject.scale.copy(scale)\n    cssObject.position.copy(position)\n\n    // add it to the css scene\n    this.css.scene.add(cssObject)\n\n    return container\n  }\n\n  getScreen() {\n    return this.container.children[0].children[0]\n  }\n\n  updateCamera() {\n    if (!this.camera) {\n      return\n    }\n\n    this.camera.position.x = this.cameraAnimation.positionX\n    this.camera.position.y = this.cameraAnimation.positionY\n    this.camera.position.z = this.cameraAnimation.positionZ\n\n    this.camera.lookAt(\n      this.cameraAnimation.targetX,\n      this.cameraAnimation.targetY,\n      this.cameraAnimation.targetZ\n    )\n\n    if (this.isDesktop) {\n      this.camera.zoom = Math.min(\n        Math.max(this.width / this.height / 1.9, 0.7),\n        1.3\n      )\n    } else {\n      this.camera.zoom = 1\n    }\n\n    this.camera.updateMatrixWorld()\n    this.camera.updateProjectionMatrix()\n  }\n\n  updatePhone() {\n    this.objectPhone.rotation.x = this.phoneAnimation.rotationX\n    this.objectPhone.rotation.y = this.phoneAnimation.rotationY\n    this.objectPhone.rotation.z = this.phoneAnimation.rotationZ\n\n    this.objectPhone.position.x = this.phoneAnimation.positionX\n    this.objectPhone.position.y = this.phoneAnimation.positionY\n    this.objectPhone.position.z = this.phoneAnimation.positionZ\n\n    this.objectPhone.updateMatrixWorld()\n  }\n\n  setPhoneRotationFromGyro({ alpha, beta }) {\n    this.phoneAnimation.rotationX = ThreeMath.degToRad(beta - 40)\n    this.phoneAnimation.rotationY = ThreeMath.degToRad(alpha)\n\n    this.needsRedraw = true\n  }\n\n  setPhoneRotationFromMouse(x, y) {\n    if (!this.animationFinished) {\n      return\n    }\n\n    const rangeX = [-0.26, 0.82]\n    const rangeY = [-0.95, 0.95]\n\n    this.phoneAnimation.rotationX =\n      scaleRange(1 - y, [0, 1], [0, rangeX[1] - rangeX[0]]) + rangeX[0]\n    this.phoneAnimation.rotationY =\n      scaleRange(1 - x, [0, 1], [0, rangeY[1] - rangeY[0]]) + rangeY[0]\n\n    this.updatePhone()\n\n    this.objectPhone.updateMatrixWorld()\n\n    this.needsRedraw = true\n  }\n\n  getIntersection() {\n    if (!this.animationFinished) {\n      return\n    }\n\n    let direction = new Vector3()\n    let position = new Vector3()\n\n    this.objectPhone.getWorldDirection(direction)\n    this.objectPhone.getWorldPosition(position)\n\n    direction.negate()\n\n    this.raycaster.set(position, direction)\n\n    let intersection\n\n    const w = ANIMATION_SCREEN_VIEWPORT.width\n    const h = ANIMATION_SCREEN_VIEWPORT.height\n\n    var intersects = this.raycaster.intersectObject(this.intersectionPlane)\n    if (intersects.length > 0) {\n      intersection = { x: 0, y: 0 }\n      const ix = intersects[0].uv.x\n      const iy = 1 - intersects[0].uv.y\n\n      intersection.x = scaleRange(ix, [0, 1], [0, w * 2]) - w / 2\n      intersection.y = scaleRange(iy, [0, 1], [0, h * 2]) - h / 2\n    }\n\n    return intersection\n  }\n\n  setFinalCameraState() {\n    if (this.isDesktop) {\n      this.setCameraValues(CAMERA[1])\n      this.setPhoneValues(PHONE[1])\n    } else {\n      this.setCameraValues(CAMERA_MOBILE[2])\n      this.setPhoneValues(PHONE_MOBILE[1])\n    }\n    this.updateCamera()\n    this.animationFinished = true\n  }\n\n  setSize(width, height) {\n    const w = width\n    const h = this.isDesktop ? height : width * 2\n\n    this.width = w\n    this.height = h\n\n    if (this.animationFinished) {\n      this.setFinalCameraState()\n    }\n\n    if (this.camera) {\n      this.camera.aspect = w / h\n      this.updateCamera()\n    }\n\n    if (this.webgl.renderer) {\n      this.webgl.renderer.setSize(w, h)\n    }\n\n    if (this.css.renderer) {\n      this.css.renderer.setSize(w, h)\n    }\n  }\n\n  animate(t) {\n    if (!this.needsRedraw && this.animationFinished) {\n      this.lastTick = t\n      return\n    }\n\n    if (!this.lastTick) {\n      this.lastTick = t\n    }\n\n    const diff = t - this.lastTick\n\n    this.tickDiffs.push(diff)\n    if (this.tickDiffs.length > 10) {\n      this.tickDiffs.shift()\n    }\n\n    const average = this.tickDiffs.reduce((p, a) => p + a) / 10\n\n    if (this.tickDiffs.length === 10) {\n      if (average > 50) {\n        this.lagCount++\n      } else {\n        this.lagCount = Math.max(this.lagCount - 1, 0)\n      }\n    }\n\n    if (this.lagCount > 20) {\n      anime.remove(this.animeAnimation)\n      this.webgl.renderer.setAnimationLoop(null)\n      this.emit('slowPerformance')\n    }\n\n    this.lastTick = t\n\n    if (this.animeAnimation) {\n      this.animeAnimation.tick(t)\n    }\n\n    this.updateCamera()\n    this.updatePhone()\n\n    this.webgl.renderer.render(this.webgl.scene, this.camera)\n    this.css.renderer.render(this.css.scene, this.camera)\n\n    this.needsRedraw = false\n  }\n\n  play() {\n    this.tickCount = 0\n    this.needsRedraw = true\n    this.webgl.renderer.setAnimationLoop(this.animate.bind(this))\n  }\n\n  stop() {\n    this.webgl.renderer.setAnimationLoop(null)\n  }\n\n  animateEnter() {\n    let animeAnimation = anime.timeline({\n      autoplay: false,\n      complete: () => {\n        this.animationFinished = true\n        this.emit('animationEnd')\n        anime.remove(this.animeAnimation)\n        this.animeAnimation = null\n      },\n      update: () => {\n        this.needsRedraw = true\n      },\n      begin: () => {\n        this.needsRedraw = true\n      }\n    })\n\n    if (this.isDesktop) {\n      this.setPhoneValues(PHONE[1])\n      this.setCameraValues(CAMERA[1])\n      this.addToTimeline('camera', animeAnimation, CAMERA)\n      this.addToTimeline('phone', animeAnimation, PHONE)\n      animeAnimation.add(\n        {\n          targets: this.pairingEl,\n          scale: [1.2, 1],\n          opacity: [0, 1],\n          transformOrigin: ['50% 50% 0', '50% 50% 0'],\n          easing: 'easeInOutQuad',\n          duration: 1900\n        },\n        '-=2000'\n      )\n    } else {\n      this.setPhoneValues(PHONE_MOBILE[0])\n      this.setCameraValues(CAMERA_MOBILE[0])\n      this.addToTimeline('camera', animeAnimation, CAMERA_MOBILE)\n      this.addToTimeline('phone', animeAnimation, PHONE_MOBILE)\n      animeAnimation.add(\n        {\n          targets: this.pairingEl,\n          opacity: [0, 1],\n          translateY: ['20%', '0%'],\n          easing: 'easeInOutQuad',\n          duration: 1700\n        },\n        '-=1800'\n      )\n    }\n\n    this.needsRedraw = true\n\n    this.animeAnimation = animeAnimation\n  }\n\n  setObjectValues(object, values) {\n    Object.keys(values).forEach((key) => {\n      object[key] = values[key]\n    })\n  }\n\n  setCameraValues({ values }) {\n    this.setObjectValues(this.cameraAnimation, values)\n  }\n\n  setPhoneValues({ values }) {\n    this.setObjectValues(this.phoneAnimation, values)\n  }\n\n  addToTimeline(target, timeline, frames) {\n    frames.forEach((frame, index) => {\n      if (index === 0) {\n        return\n      }\n\n      let animation = {\n        targets:\n          target === 'camera' ? this.cameraAnimation : this.phoneAnimation,\n        ...frame.options\n      }\n\n      let properties = JSON.parse(JSON.stringify(frame.values))\n\n      Object.keys(properties).forEach((key) => {\n        animation[key] = [frames[index - 1].values[key], properties[key]]\n      })\n\n      timeline.add(animation, frame.offset)\n    })\n  }\n\n  refresh() {\n    const screenContainer = this.container\n\n    screenContainer.style.display = 'none'\n    screenContainer.style.display = 'block'\n  }\n\n  dispose() {\n    this.stop()\n\n    anime.remove(this.animeAnimation)\n\n    while (this.dom.children.length) {\n      this.dom.removeChild(this.dom.firstChild)\n    }\n\n    this.webgl.renderer.dispose()\n  }\n}\n"
  },
  {
    "path": "src/tools/animation/keyframes.js",
    "content": "export const CAMERA_MOBILE = [\n  {\n    offset: 0,\n    options: {\n      duration: 2000,\n      delay: 0,\n      elasticity: 7,\n      endDelay: 0,\n      easing: 'easeInOutCubic'\n    },\n    values: {\n      positionX: 0,\n      positionY: -2.89,\n      positionZ: 7.16,\n      targetX: 0,\n      targetY: -2.87,\n      targetZ: 5.03\n    }\n  },\n  {\n    offset: 1000,\n    options: {\n      duration: 2000,\n      delay: 0,\n      elasticity: 7,\n      endDelay: 0,\n      easing: 'easeInOutCubic'\n    },\n    values: {\n      positionX: 0,\n      positionY: 0.31,\n      positionZ: 7.55,\n      targetX: 0,\n      targetY: -47.71,\n      targetZ: -39.52\n    }\n  },\n  {\n    offset: 3000,\n    options: {\n      duration: 4000,\n      delay: 0,\n      elasticity: 7,\n      endDelay: 0,\n      easing: 'easeInOutCubic'\n    },\n    values: {\n      positionX: 0,\n      positionY: 24.19,\n      positionZ: 10.43,\n      targetX: 0,\n      targetY: -47.7,\n      targetZ: 7.43\n    }\n  }\n]\n\nexport const PHONE_MOBILE = [\n  {\n    offset: 0,\n    options: {\n      duration: 4000,\n      delay: 0,\n      elasticity: 7,\n      endDelay: 0,\n      easing: 'easeInOutBack'\n    },\n    values: {\n      positionX: 0,\n      positionY: -2.835,\n      positionZ: 5.132,\n      rotationX: 1.6,\n      rotationY: 0,\n      rotationZ: 0\n    }\n  },\n  {\n    offset: 0,\n    options: {\n      duration: 6000,\n      delay: 0,\n      elasticity: 7,\n      endDelay: 0,\n      easing: 'easeInOutCubic'\n    },\n    values: {\n      positionX: 0,\n      positionY: -2.835,\n      positionZ: 5.132,\n      rotationX: 0.062,\n      rotationY: 0.062,\n      rotationZ: 0\n    }\n  }\n]\n\nexport const CAMERA = [\n  {\n    offset: 0,\n    options: {\n      duration: 4000,\n      delay: 0,\n      elasticity: 7,\n      endDelay: 0,\n      easing: 'easeInOutCubic'\n    },\n    values: {\n      positionX: -1.46,\n      positionY: -6.06,\n      positionZ: 5.66,\n      targetX: -6.75,\n      targetY: -2,\n      targetZ: -3.24\n    }\n  },\n  {\n    offset: 1000,\n    options: {\n      duration: 4000,\n      delay: 0,\n      elasticity: 7,\n      endDelay: 0,\n      easing: 'easeInOutCubic'\n    },\n    values: {\n      positionX: 6.4,\n      positionY: -2.39,\n      positionZ: 14,\n      targetX: -6.75,\n      targetY: -2,\n      targetZ: 0\n    }\n  }\n]\n\nexport const PHONE = [\n  {\n    offset: 0,\n    options: {\n      duration: 2000,\n      delay: 0,\n      elasticity: 7,\n      endDelay: 0,\n      easing: 'easeInOutBack'\n    },\n    values: {\n      positionX: 0,\n      positionY: -4.56,\n      positionZ: 10.735,\n      rotationX: 0.444,\n      rotationY: 0.445,\n      rotationZ: 0\n    }\n  },\n  {\n    offset: 0,\n    options: {\n      duration: 5000,\n      delay: 0,\n      elasticity: 7,\n      endDelay: 0,\n      easing: 'easeInOutCubic'\n    },\n    values: {\n      positionX: 0,\n      positionY: -4.578,\n      positionZ: 7.193,\n      rotationX: 0.198,\n      rotationY: 0,\n      rotationZ: 0\n    }\n  }\n]\n"
  },
  {
    "path": "src/tools/animation/webglSupport.js",
    "content": "/**\n * @author alteredq / http://alteredqualia.com/\n * @author mr.doob / http://mrdoob.com/\n */\n\nexport default function () {\n  try {\n    var canvas = document.createElement('canvas')\n    return !!(\n      window.WebGLRenderingContext &&\n      (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))\n    )\n  } catch (e) {\n    return false\n  }\n}\n"
  },
  {
    "path": "src/tools/canvas.js",
    "content": "import { BREAKPOINT_ANIMATION } from '@/settings'\n\n/**\n * Sets the given size and scaling of the canvases.\n *\n * @param {Object} size Desired size of the canvases.\n * @param {Number} size.width Desired width.\n * @param {Number} size.height Desired height.\n * @param {HTMLCanvasElement[]} canvases Array of canvases to set the sizes.\n */\nexport function setupCanvases({ width, height }, canvases) {\n  let dpi = 0.5\n\n  if (window.innerWidth > BREAKPOINT_ANIMATION) {\n    dpi = Math.min(window.devicePixelRatio, 1.25)\n  }\n\n  canvases.forEach((canvas) => {\n    let context = canvas.getContext('2d')\n\n    canvas.width = width * dpi\n    canvas.height = height * dpi\n\n    canvas.style.width = `${width}px`\n    canvas.style.height = `${height}px`\n\n    context.scale(dpi, dpi)\n  })\n}\n\n/**\n * Clears the given canvas.\n *\n * @param {HTMLCanvasElement} canvas The context to clear.\n * @param {Object} size The area to clear.\n * @param {Number} size.width Desired width.\n * @param {Number} size.height Desired height.\n * @param {Object} coordinates The starting coordinates of the area to clear.\n * @param {Number} coordinates.x x coordinate.\n * @param {Number} coordinates.y y coordinate.\n */\nexport function clearCanvas(\n  canvas,\n  { width, height } = {},\n  { x = 0, y = 0 } = {}\n) {\n  const canvasWidth = width || canvas.width\n  const canvasHeight = height || canvas.height\n\n  let context = canvas.getContext('2d')\n\n  context.clearRect(x, y, canvasWidth, canvasHeight)\n}\n"
  },
  {
    "path": "src/tools/cookies.js",
    "content": "import Cookies from 'universal-cookie'\n\nconst cookie = new Cookies()\n\nexport function setState(state) {\n  cookie.set('state', state, { path: '/' })\n}\n\nexport function getState() {\n  return cookie.get('state')\n}\n\nexport function setLocale(locale) {\n  cookie.set('locale', locale, { path: '/' })\n}\n\nexport function getLocale() {\n  return cookie.get('locale')\n}\n"
  },
  {
    "path": "src/tools/dependencies.js",
    "content": "// DEPENDENCIES is defined in vue.config.js as a webpack plugin and contains\n// version numbers for a few main dependencies of drawmote.\n\n// eslint-disable-next-line no-undef\nexport default DEPENDENCIES\n"
  },
  {
    "path": "src/tools/helpers.js",
    "content": "export function getViewportSize() {\n  return {\n    width: Math.max(\n      document.documentElement.clientWidth,\n      window.innerWidth || 0\n    ),\n    height: Math.max(\n      document.documentElement.clientHeight,\n      window.innerHeight || 0\n    ),\n    // ratio: window.devicePixelRatio,\n    ratio: 1\n  }\n}\n\nexport function midPointBetween(p1, p2) {\n  return {\n    x: p1.x + (p2.x - p1.x) / 2,\n    y: p1.y + (p2.y - p1.y) / 2\n  }\n}\n\nexport function getRgbaString(rgb, alpha) {\n  return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${alpha})`\n}\n\n/**\n * Convert a hex color string to rgb values.\n *\n * @param {String} hex The hex string to convert.\n * @returns {Array} The converted rgb values as an array.\n */\nexport function hexToRgb(hex) {\n  var result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex)\n  return result\n    ? [\n        parseInt(result[1], 16),\n        parseInt(result[2], 16),\n        parseInt(result[3], 16)\n      ]\n    : null\n}\n\nexport function shadeRgbColor(rgb, percent) {\n  let f = rgb\n  let t = percent < 0 ? 0 : 255\n  let p = percent < 0 ? percent * -1 : percent\n  let R = parseInt(f[0])\n  let G = parseInt(f[1])\n  let B = parseInt(f[2])\n\n  return [\n    Math.round((t - R) * p) + R,\n    Math.round((t - G) * p) + G,\n    Math.round((t - B) * p) + B\n  ]\n}\n\nexport function eraseCookie(name) {\n  document.cookie = name + '=; Max-Age=-99999999;'\n}\n\nexport function isSamePoint(p1, p2) {\n  return p1.x === p2.x && p1.y === p2.y\n}\n\nexport function buildDevServerUrl(hostname, port) {\n  return `http://${hostname}:${port}`\n}\n\nexport function getServerUrls() {\n  if (process.env.VUE_APP_API_URL && process.env.VUE_APP_WSS_URL) {\n    return {\n      api: process.env.VUE_APP_API_URL,\n      wss: process.env.VUE_APP_WSS_URL\n    }\n  } else {\n    return {\n      api: `https://${window.location.hostname}:3000`,\n      wss: `wss://${window.location.hostname}:3000/ws`\n    }\n  }\n}\n\nexport function encodeEventMessage(event, data) {\n  return JSON.stringify({ event, data })\n}\n\nexport function decodeEventMessage(message) {\n  return JSON.parse(message)\n}\n\n/**\n * Scale an input number between an input range proportionally down to be\n * in an output range.\n *\n * @param {number} input - Input number.\n * @param {Array<number, number>} inputRange - The input range.\n * @param {Array<number, number>} outputRange - The output range.\n *\n * @returns {number}\n */\nexport function scaleRange(input, inputRange, outputRange) {\n  const [inMin, inMax] = inputRange\n  const [outMin, outMax] = outputRange\n\n  // return (inMax - inMin) * (input - outMin) / (outMax - outMin) + inMin\n  return ((input - inMin) * (outMax - outMin)) / (inMax - inMin + outMin)\n}\n"
  },
  {
    "path": "vue.config.js",
    "content": "const path = require('path')\nconst fs = require('fs')\nconst webpack = require('webpack')\n\nconst PrerenderSpaPlugin = require('prerender-spa-plugin')\nconst HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin')\nconst TerserPlugin = require('terser-webpack-plugin')\n\nconst packageDependencies = require('./package-lock.json').dependencies\n\nconst webpackPlugins = []\n\nlet certs = {}\n\nif (process.env.NODE_ENV === 'production') {\n  webpackPlugins.push(new HtmlWebpackInlineSourcePlugin())\n  webpackPlugins.push(\n    new PrerenderSpaPlugin({\n      staticDir: path.join(__dirname, 'dist'),\n      routes: ['/'],\n      renderer: new PrerenderSpaPlugin.PuppeteerRenderer({\n        injectProperty: '__PRERENDERING',\n        inject: true,\n        renderAfterDocumentEvent: 'render-event',\n        skipThirdPartyRequests: true\n      }),\n      postProcess(context) {\n        // context.html = context.html.replace('id=\"drawmote\"', 'id=\"drawmote\" data-server-rendered=\"true\"')\n        return context\n      }\n    })\n  )\n} else {\n  certs = {\n    key: fs.readFileSync('./cert/server-key.pem'),\n    cert: fs.readFileSync('./cert/server-crt.pem'),\n    ca: fs.readFileSync('./cert/ca-crt.pem')\n  }\n}\n\nconst dependencies = {}\n\nnew Array('gymote', 'peersox', 'vuetamin', 'lazy-brush').forEach((name) => {\n  dependencies[name] = packageDependencies[name].version\n})\n\nwebpackPlugins.push(\n  new webpack.DefinePlugin({\n    DEPENDENCIES: JSON.stringify(dependencies)\n  })\n)\n\nmodule.exports = {\n  productionSourceMap: true,\n  filenameHashing: true,\n  transpileDependencies: ['debounced-resize', 'peersox'],\n  pluginOptions: {\n    'style-resources-loader': {\n      preProcessor: 'scss',\n      patterns: [path.resolve(__dirname, 'src/assets/scss/vue_include.scss')]\n    },\n    i18n: {\n      locale: 'en',\n      fallbackLocale: 'en',\n      localeDir: 'locales',\n      enableInSFC: false\n    }\n  },\n  devServer: {\n    https: {\n      ...certs\n    }\n  },\n  configureWebpack: {\n    optimization: {\n      minimizer:\n        process.env.NODE_ENV === 'production'\n          ? [\n              new TerserPlugin({\n                sourceMap: true,\n                terserOptions: {\n                  ecma: 5,\n                  module: true,\n                  toplevel: true\n                }\n              })\n            ]\n          : []\n    },\n    resolve: {\n      symlinks: false\n    },\n    plugins: webpackPlugins\n  },\n  chainWebpack: (config) => {\n    config.optimization.delete('splitChunks')\n    config.optimization.splitChunks(false)\n    config.plugin('define').tap((args) => {\n      let v = JSON.stringify(require('./package.json').version)\n      args[0]['process.env']['PKG_VERSION'] = v\n      return args\n    })\n    if (process.env.NODE_ENV === 'production') {\n      config.plugin('preload').tap((args) => {\n        args[0].fileBlacklist.push(/\\.css$/)\n        return args\n      })\n      config.plugin('html').tap((args) => {\n        args[0].inlineSource = '.(css)$'\n        return args\n      })\n    }\n    if (process.env.NODE_ENV === 'development') {\n      config.output.filename('[name].[hash].js').end()\n    }\n    const svgRule = config.module.rule('svg')\n\n    svgRule.uses.clear()\n\n    svgRule\n      .use('babel-loader')\n      .loader('babel-loader')\n      .end()\n      .use('vue-svg-loader')\n      .loader('vue-svg-loader')\n  }\n}\n"
  }
]