{"id":406,"date":"2023-03-23T22:20:04","date_gmt":"2023-03-23T22:20:04","guid":{"rendered":"https:\/\/viloclub.com.ar\/?page_id=406"},"modified":"2023-03-23T22:25:08","modified_gmt":"2023-03-23T22:25:08","slug":"error","status":"publish","type":"page","link":"https:\/\/viloclub.com.ar\/index.php\/error\/","title":{"rendered":"error"},"content":{"rendered":"\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p><style>\/* Copyright 2017 The Chromium Authors\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file. *\/\n\na {\n  color: var(--link-color);\n}\n\nbody {\n  --background-color: #fff;\n  --error-code-color: var(--google-gray-700);\n  --google-blue-100: rgb(210, 227, 252);\n  --google-blue-300: rgb(138, 180, 248);\n  --google-blue-600: rgb(26, 115, 232);\n  --google-blue-700: rgb(25, 103, 210);\n  --google-gray-100: rgb(241, 243, 244);\n  --google-gray-300: rgb(218, 220, 224);\n  --google-gray-500: rgb(154, 160, 166);\n  --google-gray-50: rgb(248, 249, 250);\n  --google-gray-600: rgb(128, 134, 139);\n  --google-gray-700: rgb(95, 99, 104);\n  --google-gray-800: rgb(60, 64, 67);\n  --google-gray-900: rgb(32, 33, 36);\n  --heading-color: var(--google-gray-900);\n  --link-color: rgb(88, 88, 88);\n  --popup-container-background-color: rgba(0,0,0,.65);\n  --primary-button-fill-color-active: var(--google-blue-700);\n  --primary-button-fill-color: var(--google-blue-600);\n  --primary-button-text-color: #fff;\n  --quiet-background-color: rgb(247, 247, 247);\n  --secondary-button-border-color: var(--google-gray-500);\n  --secondary-button-fill-color: #fff;\n  --secondary-button-hover-border-color: var(--google-gray-600);\n  --secondary-button-hover-fill-color: var(--google-gray-50);\n  --secondary-button-text-color: var(--google-gray-700);\n  --small-link-color: var(--google-gray-700);\n  --text-color: var(--google-gray-700);\n  background: var(--background-color);\n  color: var(--text-color);\n  word-wrap: break-word;\n}\n\n.nav-wrapper .secondary-button {\n  background: var(--secondary-button-fill-color);\n  border: 1px solid var(--secondary-button-border-color);\n  color: var(--secondary-button-text-color);\n  float: none;\n  margin: 0;\n  padding: 8px 16px;\n}\n\n.hidden {\n  display: none;\n}\n\nhtml {\n  -webkit-text-size-adjust: 100%;\n  font-size: 125%;\n}\n\n.icon {\n  background-repeat: no-repeat;\n  background-size: 100%;\n}\n\n@media (prefers-color-scheme: dark) {\n  body {\n    --background-color: var(--google-gray-900);\n    --error-code-color: var(--google-gray-500);\n    --heading-color: var(--google-gray-500);\n    --link-color: var(--google-blue-300);\n    --primary-button-fill-color-active: rgb(129, 162, 208);\n    --primary-button-fill-color: var(--google-blue-300);\n    --primary-button-text-color: var(--google-gray-900);\n    --quiet-background-color: var(--background-color);\n    --secondary-button-border-color: var(--google-gray-700);\n    --secondary-button-fill-color: var(--google-gray-900);\n    --secondary-button-hover-fill-color: rgb(48, 51, 57);\n    --secondary-button-text-color: var(--google-blue-300);\n    --small-link-color: var(--google-blue-300);\n    --text-color: var(--google-gray-500);\n  }\n}\n<\/style><style>\/* Copyright 2014 The Chromium Authors\n   Use of this source code is governed by a BSD-style license that can be\n   found in the LICENSE file. *\/\n\nbutton {\n  border: 0;\n  border-radius: 4px;\n  box-sizing: border-box;\n  color: var(--primary-button-text-color);\n  cursor: pointer;\n  float: right;\n  font-size: .875em;\n  margin: 0;\n  padding: 8px 16px;\n  transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1);\n  user-select: none;\n}\n\n[dir='rtl'] button {\n  float: left;\n}\n\n.bad-clock button,\n.captive-portal button,\n.https-only button,\n.insecure-form button,\n.lookalike-url button,\n.main-frame-blocked button,\n.neterror button,\n.pdf button,\n.ssl button,\n.enterprise-block button,\n.enterprise-warn button,\n.safe-browsing-billing button {\n  background: var(--primary-button-fill-color);\n}\n\nbutton:active {\n  background: var(--primary-button-fill-color-active);\n  outline: 0;\n}\n\n#debugging {\n  display: inline;\n  overflow: auto;\n}\n\n.debugging-content {\n  line-height: 1em;\n  margin-bottom: 0;\n  margin-top: 1em;\n}\n\n.debugging-content-fixed-width {\n  display: block;\n  font-family: monospace;\n  font-size: 1.2em;\n  margin-top: 0.5em;\n}\n\n.debugging-title {\n  font-weight: bold;\n}\n\n#details {\n  margin: 0 0 50px;\n}\n\n#details p:not(:first-of-type) {\n  margin-top: 20px;\n}\n\n.secondary-button:active {\n  border-color: white;\n  box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .3),\n      0 2px 6px 2px rgba(60, 64, 67, .15);\n}\n\n.secondary-button:hover {\n  background: var(--secondary-button-hover-fill-color);\n  border-color: var(--secondary-button-hover-border-color);\n  text-decoration: none;\n}\n\n.error-code {\n  color: var(--error-code-color);\n  font-size: .8em;\n  margin-top: 12px;\n  text-transform: uppercase;\n}\n\n#error-debugging-info {\n  font-size: 0.8em;\n}\n\nh1 {\n  color: var(--heading-color);\n  font-size: 1.6em;\n  font-weight: normal;\n  line-height: 1.25em;\n  margin-bottom: 16px;\n}\n\nh2 {\n  font-size: 1.2em;\n  font-weight: normal;\n}\n\n.icon {\n  height: 72px;\n  margin: 0 0 40px;\n  width: 72px;\n}\n\ninput[type=checkbox] {\n  opacity: 0;\n}\n\ninput[type=checkbox]:focus ~ .checkbox::after {\n  outline: -webkit-focus-ring-color auto 5px;\n}\n\n.interstitial-wrapper {\n  box-sizing: border-box;\n  font-size: 1em;\n  line-height: 1.6em;\n  margin: 14vh auto 0;\n  max-width: 600px;\n  width: 100%;\n}\n\n#main-message > p {\n  display: inline;\n}\n\n#extended-reporting-opt-in {\n  font-size: .875em;\n  margin-top: 32px;\n}\n\n#extended-reporting-opt-in label {\n  display: grid;\n  grid-template-columns: 1.8em 1fr;\n  position: relative;\n}\n\n#enhanced-protection-message {\n  border-radius: 4px;\n  font-size: 1em;\n  margin-top: 32px;\n  padding: 10px 5px;\n}\n\n#enhanced-protection-message label {\n  display: grid;\n  grid-template-columns: 2.5em 1fr;\n  position: relative;\n}\n\n#enhanced-protection-message div {\n  margin: 0.5em;\n}\n\n#enhanced-protection-message .icon {\n  height: 1.5em;\n  vertical-align: middle;\n  width: 1.5em;\n}\n\n.nav-wrapper {\n  margin-top: 51px;\n}\n\n.nav-wrapper::after {\n  clear: both;\n  content: '';\n  display: table;\n  width: 100%;\n}\n\n.small-link {\n  color: var(--small-link-color);\n  font-size: .875em;\n}\n\n.checkboxes {\n  flex: 0 0 24px;\n}\n\n.checkbox {\n  --padding: .9em;\n  background: transparent;\n  display: block;\n  height: 1em;\n  left: -1em;\n  padding-inline-start: var(--padding);\n  position: absolute;\n  right: 0;\n  top: -.5em;\n  width: 1em;\n}\n\n.checkbox::after {\n  border: 1px solid white;\n  border-radius: 2px;\n  content: '';\n  height: 1em;\n  left: var(--padding);\n  position: absolute;\n  top: var(--padding);\n  width: 1em;\n}\n\n.checkbox::before {\n  background: transparent;\n  border: 2px solid white;\n  border-inline-end-width: 0;\n  border-top-width: 0;\n  content: '';\n  height: .2em;\n  left: calc(.3em + var(--padding));\n  opacity: 0;\n  position: absolute;\n  top: calc(.3em  + var(--padding));\n  transform: rotate(-45deg);\n  width: .5em;\n}\n\ninput[type=checkbox]:checked ~ .checkbox::before {\n  opacity: 1;\n}\n\n#recurrent-error-message {\n  background: #ededed;\n  border-radius: 4px;\n  margin-bottom: 16px;\n  margin-top: 12px;\n  padding: 12px 16px;\n}\n\n.showing-recurrent-error-message #extended-reporting-opt-in {\n  margin-top: 16px;\n}\n\n.showing-recurrent-error-message #enhanced-protection-message {\n  margin-top: 16px;\n}\n\n@media (max-width: 700px) {\n  .interstitial-wrapper {\n    padding: 0 10%;\n  }\n\n  #error-debugging-info {\n    overflow: auto;\n  }\n}\n\n@media (max-width: 420px) {\n  button,\n  [dir='rtl'] button,\n  .small-link {\n    float: none;\n    font-size: .825em;\n    font-weight: 500;\n    margin: 0;\n    width: 100%;\n  }\n\n  button {\n    padding: 16px 24px;\n  }\n\n  #details {\n    margin: 20px 0 20px 0;\n  }\n\n  #details p:not(:first-of-type) {\n    margin-top: 10px;\n  }\n\n  .secondary-button:not(.hidden) {\n    display: block;\n    margin-top: 20px;\n    text-align: center;\n    width: 100%;\n  }\n\n  .interstitial-wrapper {\n    padding: 0 5%;\n  }\n\n  #extended-reporting-opt-in {\n    margin-top: 24px;\n  }\n\n  #enhanced-protection-message {\n    margin-top: 24px;\n  }\n\n  .nav-wrapper {\n    margin-top: 30px;\n  }\n}\n\n\/**\n * Mobile specific styling.\n * Navigation buttons are anchored to the bottom of the screen.\n * Details message replaces the top content in its own scrollable area.\n *\/\n\n@media (max-width: 420px) {\n  .nav-wrapper .secondary-button {\n    border: 0;\n    margin: 16px 0 0;\n    margin-inline-end: 0;\n    padding-bottom: 16px;\n    padding-top: 16px;\n  }\n}\n\n\/* Fixed nav. *\/\n@media (min-width: 240px) and (max-width: 420px) and\n       (min-height: 401px),\n       (min-width: 421px) and (min-height: 240px) and\n       (max-height: 560px) {\n  body .nav-wrapper {\n    background: var(--background-color);\n    bottom: 0;\n    box-shadow: 0 -12px 24px var(--background-color);\n    left: 0;\n    margin: 0 auto;\n    max-width: 736px;\n    padding-inline-end: 24px;\n    padding-inline-start: 24px;\n    position: fixed;\n    right: 0;\n    width: 100%;\n    z-index: 2;\n  }\n\n  .interstitial-wrapper {\n    max-width: 736px;\n  }\n\n  #details,\n  #main-content {\n    padding-bottom: 40px;\n  }\n\n  #details {\n    padding-top: 5.5vh;\n  }\n\n  button.small-link {\n    color: var(--google-blue-600);\n  }\n}\n\n@media (max-width: 420px) and (orientation: portrait),\n       (max-height: 560px) {\n  body {\n    margin: 0 auto;\n  }\n\n  button,\n  [dir='rtl'] button,\n  button.small-link,\n  .nav-wrapper .secondary-button {\n    font-family: Roboto-Regular,Helvetica;\n    font-size: .933em;\n    margin: 6px 0;\n    transform: translatez(0);\n  }\n\n  .nav-wrapper {\n    box-sizing: border-box;\n    padding-bottom: 8px;\n    width: 100%;\n  }\n\n  #details {\n    box-sizing: border-box;\n    height: auto;\n    margin: 0;\n    opacity: 1;\n    transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);\n  }\n\n  #details.hidden,\n  #main-content.hidden {\n    height: 0;\n    opacity: 0;\n    overflow: hidden;\n    padding-bottom: 0;\n    transition: none;\n  }\n\n  h1 {\n    font-size: 1.5em;\n    margin-bottom: 8px;\n  }\n\n  .icon {\n    margin-bottom: 5.69vh;\n  }\n\n  .interstitial-wrapper {\n    box-sizing: border-box;\n    margin: 7vh auto 12px;\n    padding: 0 24px;\n    position: relative;\n  }\n\n  .interstitial-wrapper p {\n    font-size: .95em;\n    line-height: 1.61em;\n    margin-top: 8px;\n  }\n\n  #main-content {\n    margin: 0;\n    transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1);\n  }\n\n  .small-link {\n    border: 0;\n  }\n\n  .suggested-left > #control-buttons,\n  .suggested-right > #control-buttons {\n    float: none;\n    margin: 0;\n  }\n}\n\n@media (min-width: 421px) and (min-height: 500px) and (max-height: 560px) {\n  .interstitial-wrapper {\n    margin-top: 10vh;\n  }\n}\n\n@media (min-height: 400px) and (orientation:portrait) {\n  .interstitial-wrapper {\n    margin-bottom: 145px;\n  }\n}\n\n@media (min-height: 299px) {\n  .nav-wrapper {\n    padding-bottom: 16px;\n  }\n}\n\n@media (max-height: 560px) and (min-height: 240px) and (orientation:landscape) {\n  .extended-reporting-has-checkbox #details {\n    padding-bottom: 80px;\n  }\n}\n\n@media (min-height: 500px) and (max-height: 650px) and (max-width: 414px) and\n       (orientation: portrait) {\n  .interstitial-wrapper {\n    margin-top: 7vh;\n  }\n}\n\n@media (min-height: 650px) and (max-width: 414px) and (orientation: portrait) {\n  .interstitial-wrapper {\n    margin-top: 10vh;\n  }\n}\n\n\/* Small mobile screens. No fixed nav. *\/\n@media (max-height: 400px) and (orientation: portrait),\n       (max-height: 239px) and (orientation: landscape),\n       (max-width: 419px) and (max-height: 399px) {\n  .interstitial-wrapper {\n    display: flex;\n    flex-direction: column;\n    margin-bottom: 0;\n  }\n\n  #details {\n    flex: 1 1 auto;\n    order: 0;\n  }\n\n  #main-content {\n    flex: 1 1 auto;\n    order: 0;\n  }\n\n  .nav-wrapper {\n    flex: 0 1 auto;\n    margin-top: 8px;\n    order: 1;\n    padding-inline-end: 0;\n    padding-inline-start: 0;\n    position: relative;\n    width: 100%;\n  }\n\n  button,\n  .nav-wrapper .secondary-button {\n    padding: 16px 24px;\n  }\n\n  button.small-link {\n    color: var(--google-blue-600);\n  }\n}\n\n@media (max-width: 239px) and (orientation: portrait) {\n  .nav-wrapper {\n    padding-inline-end: 0;\n    padding-inline-start: 0;\n  }\n}\n<\/style><style>\/* Copyright 2013 The Chromium Authors\n * Use of this source code is governed by a BSD-style license that can be\n * found in the LICENSE file. *\/\n\n\/* Don't use the main frame div when the error is in a subframe. *\/\nhtml[subframe] #main-frame-error {\n  display: none;\n}\n\n\/* Don't use the subframe error div when the error is in a main frame. *\/\nhtml:not([subframe]) #sub-frame-error {\n  display: none;\n}\n\nh1 {\n  margin-top: 0;\n  word-wrap: break-word;\n}\n\nh1 span {\n  font-weight: 500;\n}\n\na {\n  text-decoration: none;\n}\n\n.icon {\n  -webkit-user-select: none;\n  display: inline-block;\n}\n\n.icon-generic {\n  \/* Can't access chrome:\/\/theme\/IDR_ERROR_NETWORK_GENERIC from an untrusted\n   * renderer process, so embed the resource manually. *\/\n  content: -webkit-image-set(\n      url(data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf\/AAAAAXRSTlMAQObYZgAAAENJREFUeF7tzbEJACEQRNGBLeAasBCza2lLEGx0CxFGG9hBMDDxRy\/72O9FMnIFapGylsu1fgoBdkXfUHLrQgdfrlJN1BdYBjQQm3UAAAAASUVORK5CYII=) 1x,\n      url(data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQAQMAAADdiHD7AAAABlBMVEUAAABTU1OoaSf\/AAAAAXRSTlMAQObYZgAAAFJJREFUeF7t0cENgDAMQ9FwYgxG6WjpaIzCCAxQxVggFuDiCvlLOeRdHR9yzjncHVoq3npu+wQUrUuJHylSTmBaespJyJQoObUeyxDQb3bEm5Au81c0pSCD8HYAAAAASUVORK5CYII=) 2x);\n}\n\n.icon-offline {\n  content: -webkit-image-set(\n      url(data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf\/AAAAAXRSTlMAQObYZgAAAGxJREFUeF7tyMEJwkAQRuFf5ipMKxYQiJ3Z2nSwrWwBA0+DQZcdxEOueaePp9+dQZFB7GpUcURSVU66yVNFj6LFICatThZB6r\/ko\/pbRpUgilY0Cbw5sNmb9txGXUKyuH7eV25x39DtJXUNPQGJtWFV+BT\/QAAAAABJRU5ErkJggg==) 1x,\n      url(data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQBAMAAAAVaP+LAAAAGFBMVEUAAABTU1NNTU1TU1NPT09SUlJSUlJTU1O8B7DEAAAAB3RSTlMAoArVKvVgBuEdKgAAAJ1JREFUeF7t1TEOwyAMQNG0Q6\/UE+RMXD9d\/tC6womIFSL9P+MnAYOXeTIzMzMzMzMzaz8J9Ri6HoITmuHXhISE8nEh9yxDh55aCEUoTGbbQwjqHwIkRAEiIaG0+0AA9VBMaE89Rogeoww936MQrWdBr4GN\/z0IAdQ6nQ\/FIpRXDwHcA+JIJcQowQAlFUA0MfQpXLlVQfkzR4igS6ENjknm\/wiaGhsAAAAASUVORK5CYII=) 2x);\n  position: relative;\n}\n\n.icon-disabled {\n  content: -webkit-image-set(\n      url(data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHAAAABICAMAAAAZF4G5AAAABlBMVEVMaXFTU1OXUj8tAAAAAXRSTlMAQObYZgAAASZJREFUeAHd11Fq7jAMRGGf\/W\/6PoWB67YMqv5DybwG\/CFjRuR8JBw3+ByiRjgV9W\/TJ31P0tBfC6+cj1haUFXKHmVJo5wP98WwQ0ZCbfUc6LQ6VuUBz31ikADkLMkDrfUC4rR6QGW+gF6rx7NaHWCj1Y\/W6lf4L7utvgBSt3rBFSS\/XBMPUILcJINHCBWYUfpWn4NBi1ZfudIc3rf6\/NGEvEA+AsYTJozmXemjXeLZAov+mnkN2HfzXpMSVQDnGw++57qNJ4D1xitA2sJ+VAWMygSEaYf2mYPTjZfk2K8wmP7HLIH5Mg4\/pP+PEcDzUvDMvYbs\/2NWwPO5vBdMZE4EE5UTQLiBFDaUlTDPBRoJ9HdAYIkIo06og3BNXtCzy7zA1aXk5x+tJARq63eAygAAAABJRU5ErkJggg==) 1x,\n      url(data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOAAAACQAQMAAAArwfVjAAAABlBMVEVMaXFTU1OXUj8tAAAAAXRSTlMAQObYZgAAAYdJREFUeF7F1EFqwzAUBNARAmVj0FZe5QoBH6BX+dn4GlY2PYNzGx\/A0CvkCIJuvIraKJKbgBvzf2g62weDGD7CYggpfFReis4J0ey9EGFIiEQQojFSlA9kSIiqd0KkFjKsewgRbStEN19mxUPTtmW9HQ\/h6tyqNQ8NlSMZdzyE6qkoE0trVYGFm0n1WYeBhduzwbwBC7voS+vIxfeMjeaiLxsMMtQNwMPtuew+DjzcTHk8YMfDknEcIUOtf2lVfgVH3K4Xv5PRYAXRVMtItIJ3rfaCIVn9DsTH2NxisAVRex2Hh3hX+\/mRUR08bAwPEYsI51ZxWH4Q0SpicQRXeyEaIug48FEdegARfMz\/tADVsRciwTAxW308ehmC2gLraC+YCbV3QoTZexa+zegAEW5PhhgYfmbvJgcRqngGByOSXdFJcLk2JeDPEN0kxe1JhIt5FiFA+w+ItMELsUyPF2IaJ4aILqb4FbxPwhImwj6JauKgDUCYaxmYIsd4KXdMjIC9ItB5Bn4BNRwsG0XM2nwAAAAASUVORK5CYII=) 2x);\n  width: 112px;\n}\n\n.hidden {\n  display: none;\n}\n\n#suggestions-list a {\n  color: var(--google-blue-600);\n}\n\n#suggestions-list p {\n  margin-block-end: 0;\n}\n\n#suggestions-list ul {\n  margin-top: 0;\n}\n\n.single-suggestion {\n  list-style-type: none;\n  padding-inline-start: 0;\n}\n\n#error-information-button {\n  content: url(data:image\/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJub25lIiBkPSJNMCAwaDI0djI0SDB6Ii8+PHBhdGggZD0iTTExIDE4aDJ2LTJoLTJ2MnptMS0xNkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6bTAtMTRjLTIuMjEgMC00IDEuNzktNCA0aDJjMC0xLjEuOS0yIDItMnMyIC45IDIgMmMwIDItMyAxLjc1LTMgNWgyYzAtMi4yNSAzLTIuNSAzLTUgMC0yLjIxLTEuNzktNC00LTR6Ii8+PC9zdmc+);\n  height: 24px;\n  vertical-align: -.15em;\n  width: 24px;\n}\n\n.use-popup-container#error-information-popup-container\n  #error-information-popup {\n  align-items: center;\n  background-color: var(--popup-container-background-color);\n  display: flex;\n  height: 100%;\n  left: 0;\n  position: fixed;\n  top: 0;\n  width: 100%;\n  z-index: 100;\n}\n\n.use-popup-container#error-information-popup-container\n  #error-information-popup-content > p {\n  margin-bottom: 11px;\n  margin-inline-start: 20px;\n}\n\n.use-popup-container#error-information-popup-container #suggestions-list ul {\n  margin-inline-start: 15px;\n}\n\n.use-popup-container#error-information-popup-container\n  #error-information-popup-box {\n  background-color: var(--background-color);\n  left: 5%;\n  padding-bottom: 15px;\n  padding-top: 15px;\n  position: fixed;\n  width: 90%;\n  z-index: 101;\n}\n\n.use-popup-container#error-information-popup-container div.error-code {\n  margin-inline-start: 20px;\n}\n\n.use-popup-container#error-information-popup-container #suggestions-list p {\n  margin-inline-start: 20px;\n}\n\n:not(.use-popup-container)#error-information-popup-container\n  #error-information-popup-close {\n  display: none;\n}\n\n#error-information-popup-close {\n  margin-bottom: 0;\n  margin-inline-end: 35px;\n  margin-top: 15px;\n  text-align: end;\n}\n\n.link-button {\n  color: rgb(66, 133, 244);\n  display: inline-block;\n  font-weight: bold;\n  text-transform: uppercase;\n}\n\n#sub-frame-error-details {\n\n  color: #8F8F8F;\n\n  \/* Not done on mobile for performance reasons. *\/\n  text-shadow: 0 1px 0 rgba(255,255,255,0.3);\n\n}\n\n[jscontent=hostName],\n[jscontent=failedUrl] {\n  overflow-wrap: break-word;\n}\n\n.secondary-button {\n  background: #d9d9d9;\n  color: #696969;\n  margin-inline-end: 16px;\n}\n\n.snackbar {\n  background: #323232;\n  border-radius: 2px;\n  bottom: 24px;\n  box-sizing: border-box;\n  color: #fff;\n  font-size: .87em;\n  left: 24px;\n  max-width: 568px;\n  min-width: 288px;\n  opacity: 0;\n  padding: 16px 24px 12px;\n  position: fixed;\n  transform: translateY(90px);\n  will-change: opacity, transform;\n  z-index: 999;\n}\n\n.snackbar-show {\n  -webkit-animation:\n    show-snackbar 250ms cubic-bezier(0, 0, 0.2, 1) forwards,\n    hide-snackbar 250ms cubic-bezier(0.4, 0, 1, 1) forwards 5s;\n}\n\n@-webkit-keyframes show-snackbar {\n  100% {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n@-webkit-keyframes hide-snackbar {\n  0% {\n    opacity: 1;\n    transform: translateY(0);\n  }\n  100% {\n    opacity: 0;\n    transform: translateY(90px);\n  }\n}\n\n.suggestions {\n  margin-top: 18px;\n}\n\n.suggestion-header {\n  font-weight: bold;\n  margin-bottom: 4px;\n}\n\n.suggestion-body {\n  color: #777;\n}\n\n\/* Decrease padding at low sizes. *\/\n@media (max-width: 640px), (max-height: 640px) {\n  h1 {\n    margin: 0 0 15px;\n  }\n  .suggestions {\n    margin-top: 10px;\n  }\n  .suggestion-header {\n    margin-bottom: 0;\n  }\n}\n\n#download-link,\n#download-link-clicked {\n  margin-bottom: 30px;\n  margin-top: 30px;\n}\n\n#download-link-clicked {\n  color: #BBB;\n}\n\n#download-link::before,\n#download-link-clicked::before {\n  content: url(data:image\/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxLjJlbSIgaGVpZ2h0PSIxLjJlbSIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNNSAyMGgxNHYtMkg1bTE0LTloLTRWM0g5djZINWw3IDcgNy03eiIgZmlsbD0iIzQyODVGNCIvPjwvc3ZnPg==);\n  display: inline-block;\n  margin-inline-end: 4px;\n  vertical-align: -webkit-baseline-middle;\n}\n\n#download-link-clicked::before {\n  opacity: 0;\n  width: 0;\n}\n\n#offline-content-list-visibility-card {\n  border: 1px solid white;\n  border-radius: 8px;\n  display: flex;\n  font-size: .8em;\n  justify-content: space-between;\n  line-height: 1;\n}\n\n#offline-content-list.list-hidden #offline-content-list-visibility-card {\n  border-color: rgb(218, 220, 224);\n}\n\n#offline-content-list-visibility-card > div {\n  padding: 1em;\n}\n\n#offline-content-list-title {\n  color: var(--google-gray-700);\n}\n\n#offline-content-list-show-text,\n#offline-content-list-hide-text {\n  color: rgb(66, 133, 244);\n}\n\n\/* Hides the \"hide\" text div when the offline content list is collapsed\/hidden\n * and, alternatively, hides the \"show\" text div when the offline content list\n * is expanded\/shown.\n *\/\n#offline-content-list.list-hidden #offline-content-list-hide-text,\n#offline-content-list:not(.list-hidden) #offline-content-list-show-text {\n  display: none;\n}\n\n\/* Controls the animation of the offline content list when it is expanded\/shown.\n *\/\n#offline-content-suggestions {\n  \/* Max-height has to be set for the height animation to work. The chosen value\n   * is a little greater than the maximum height the list will have, when all\n   * suggestions have images, so that it is never clamped. This makes so that\n   * when the actual height is smaller then the animation is not as smooth.\n   *\/\n  max-height: 27em;\n  transition: max-height 200ms ease-in, visibility 0s 200ms,\n              opacity 200ms 200ms linear;\n}\n\n\/* Controls the animation of the offline content list when it is\n * collapsed\/hidden.\n *\/\n#offline-content-list.list-hidden #offline-content-suggestions {\n  max-height: 0;\n  opacity: 0;\n  transition: opacity 200ms linear, visibility 0s 200ms,\n              max-height 200ms 200ms ease-out;\n  visibility: hidden;\n}\n\n#offline-content-list {\n  margin-inline-start: -5%;\n  width: 110%;\n}\n\n\/* The selectors below adjust the \"overflow\" of the suggestion cards contents\n * based on the same screen size based strategy used for the main frame, which\n * is applied by the `interstitial-wrapper` class. *\/\n@media (max-width: 420px)  {\n  #offline-content-list {\n    margin-inline-start: -2.5%;\n    width: 105%;\n  }\n}\n@media (max-width: 420px) and (orientation: portrait),\n       (max-height: 560px) {\n  #offline-content-list {\n    margin-inline-start: -12px;\n    width: calc(100% + 24px);\n  }\n}\n\n.suggestion-with-image .offline-content-suggestion-thumbnail {\n  flex-basis: 8.2em;\n  flex-shrink: 0;\n}\n\n.suggestion-with-image .offline-content-suggestion-thumbnail > img {\n  height: 100%;\n  width: 100%;\n}\n\n.suggestion-with-image #offline-content-list:not(.is-rtl)\n.offline-content-suggestion-thumbnail > img {\n  border-bottom-right-radius: 7px;\n  border-top-right-radius: 7px;\n}\n\n.suggestion-with-image #offline-content-list.is-rtl\n.offline-content-suggestion-thumbnail > img {\n  border-bottom-left-radius: 7px;\n  border-top-left-radius: 7px;\n}\n\n.suggestion-with-icon .offline-content-suggestion-thumbnail {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  min-height: 4.2em;\n  min-width: 4.2em;\n}\n\n.suggestion-with-icon .offline-content-suggestion-thumbnail > div {\n  align-items: center;\n  background-color: rgb(241, 243, 244);\n  border-radius: 50%;\n  display: flex;\n  height: 2.3em;\n  justify-content: center;\n  width: 2.3em;\n}\n\n.suggestion-with-icon .offline-content-suggestion-thumbnail > div > img {\n  height: 1.45em;\n  width: 1.45em;\n}\n\n.offline-content-suggestion-favicon {\n  height: 1em;\n  margin-inline-end: 0.4em;\n  width: 1.4em;\n}\n\n.offline-content-suggestion-favicon > img {\n  height: 1.4em;\n  width: 1.4em;\n}\n\n.no-favicon .offline-content-suggestion-favicon {\n  display: none;\n}\n\n.image-video {\n  content: url(data:image\/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTcgMTAuNVY3YTEgMSAwIDAgMC0xLTFINGExIDEgMCAwIDAtMSAxdjEwYTEgMSAwIDAgMCAxIDFoMTJhMSAxIDAgMCAwIDEtMXYtMy41bDQgNHYtMTFsLTQgNHoiIGZpbGw9IiMzQzQwNDMiLz48L3N2Zz4=);\n}\n\n.image-music-note {\n  content: url(data:image\/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgM3Y5LjI2Yy0uNS0uMTctMS0uMjYtMS41LS4yNkM4IDEyIDYgMTQgNiAxNi41UzggMjEgMTAuNSAyMXM0LjUtMiA0LjUtNC41VjZoNFYzaC03eiIgZmlsbD0iIzNDNDA0MyIvPjwvc3ZnPg==);\n}\n\n.image-earth {\n  content: url(data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMmM1LjUyIDAgMTAgNC40OCAxMCAxMHMtNC40OCAxMC0xMCAxMFMyIDE3LjUyIDIgMTIgNi40OCAyIDEyIDJ6TTQgMTJoNC40YzMuNDA3LjAyMiA0LjkyMiAxLjczIDQuNTQzIDUuMTI3SDkuNDg4djIuNDdhOC4wMDQgOC4wMDQgMCAwIDAgMTAuNDk4LTguMDgzQzE5LjMyNyAxMi41MDQgMTguMzMyIDEzIDE3IDEzYy0yLjEzNyAwLTMuMjA2LS45MTYtMy4yMDYtMi43NWgtMy43NDhjLS4yNzQtMi43MjguNjgzLTQuMDkyIDIuODctNC4wOTIgMC0uOTc1LjMyNy0xLjU5Ny44MTEtMS45N0E4LjAwNCA4LjAwNCAwIDAgMCA0IDEyeiIgZmlsbD0iIzNDNDA0MyIvPjwvc3ZnPg==);\n}\n\n.image-file {\n  content: url(data:image\/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTMgOVYzLjVMMTguNSA5TTYgMmMtMS4xMSAwLTIgLjg5LTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWOGwtNi02SDZ6IiBmaWxsPSIjM0M0MDQzIi8+PC9zdmc+);\n}\n\n.offline-content-suggestion-texts {\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n  line-height: 1.3;\n  padding: .9em;\n  width: 100%;\n}\n\n.offline-content-suggestion-title {\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 3;\n  color: rgb(32, 33, 36);\n  display: -webkit-box;\n  font-size: 1.1em;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\ndiv.offline-content-suggestion {\n  align-items: stretch;\n  border: 1px solid rgb(218, 220, 224);\n  border-radius: 8px;\n  display: flex;\n  justify-content: space-between;\n  margin-bottom: .8em;\n}\n\n.suggestion-with-image {\n  flex-direction: row;\n  height: 8.2em;\n  max-height: 8.2em;\n}\n\n.suggestion-with-icon {\n  flex-direction: row-reverse;\n  height: 4.2em;\n  max-height: 4.2em;\n}\n\n.suggestion-with-icon .offline-content-suggestion-title {\n  -webkit-line-clamp: 1;\n  word-break: break-all;\n}\n\n.suggestion-with-icon .offline-content-suggestion-texts {\n  padding-inline-start: 0;\n}\n\n.offline-content-suggestion-attribution-freshness {\n  color: rgb(95, 99, 104);\n  display: flex;\n  font-size: .8em;\n  line-height: 1.7em;\n}\n\n.offline-content-suggestion-attribution {\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 1;\n  display: -webkit-box;\n  flex-shrink: 1;\n  margin-inline-end: 0.3em;\n  overflow: hidden;\n  overflow-wrap: break-word;\n  text-overflow: ellipsis;\n  word-break: break-all;\n}\n\n.no-attribution .offline-content-suggestion-attribution {\n  display: none;\n}\n\n.offline-content-suggestion-freshness::before {\n  content: '-';\n  display: inline-block;\n  flex-shrink: 0;\n  margin-inline-end: .1em;\n  margin-inline-start: .1em;\n}\n\n.no-attribution .offline-content-suggestion-freshness::before {\n  display: none;\n}\n\n.offline-content-suggestion-freshness {\n  flex-shrink: 0;\n}\n\n.suggestion-with-image .offline-content-suggestion-pin-spacer {\n  flex-grow: 100;\n  flex-shrink: 1;\n}\n\n.suggestion-with-image .offline-content-suggestion-pin {\n  content: url(data:image\/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PGRlZnM+PHBhdGggaWQ9ImEiIGQ9Ik0wIDBoMjR2MjRIMFYweiIvPjwvZGVmcz48Y2xpcFBhdGggaWQ9ImIiPjx1c2UgeGxpbms6aHJlZj0iI2EiIG92ZXJmbG93PSJ2aXNpYmxlIi8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjYikiIGQ9Ik0xMiAyQzYuNSAyIDIgNi41IDIgMTJzNC41IDEwIDEwIDEwIDEwLTQuNSAxMC0xMFMxNy41IDIgMTIgMnptNSAxNkg3di0yaDEwdjJ6bS02LjctNEw3IDEwLjdsMS40LTEuNCAxLjkgMS45IDUuMy01LjNMMTcgNy4zIDEwLjMgMTR6IiBmaWxsPSIjOUFBMEE2Ii8+PC9zdmc+);\n  flex-shrink: 0;\n  height: 1.4em;\n  margin-inline-start: .4em;\n  width: 1.4em;\n}\n\n\/* Controls the animation (and a bit more) of the launch-downloads-home action\n * button when the offline content list is expanded\/shown.\n *\/\n#offline-content-list-action {\n  text-align: center;\n  transition: visibility 0s 200ms, opacity 200ms 200ms linear;\n}\n\n\/* Controls the animation of the launch-downloads-home action button when the\n * offline content list is collapsed\/hidden.\n *\/\n#offline-content-list.list-hidden #offline-content-list-action {\n  opacity: 0;\n  transition: opacity 200ms linear, visibility 0s 200ms;\n  visibility: hidden;\n}\n\n#cancel-save-page-button {\n  background-image: url(data:image\/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48Y2xpcFBhdGggaWQ9Im1hc2siPjxwYXRoIGQ9Ik0xMiAyQzYuNSAyIDIgNi41IDIgMTJzNC41IDEwIDEwIDEwIDEwLTQuNSAxMC0xMFMxNy41IDIgMTIgMnptNSAxNkg3di0yaDEwdjJ6bS02LjctNEw3IDEwLjdsMS40LTEuNCAxLjkgMS45IDUuMy01LjNMMTcgNy4zIDEwLjMgMTR6IiBmaWxsPSIjOUFBMEE2Ii8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjbWFzaykiIGZpbGw9IiM5QUEwQTYiIGQ9Ik0wIDBoMjR2MjRIMHoiLz48cGF0aCBjbGlwLXBhdGg9InVybCgjbWFzaykiIGZpbGw9IiMxQTczRTgiIHN0eWxlPSJhbmltYXRpb246b2ZmbGluZUFuaW1hdGlvbiA0cyBpbmZpbml0ZSIgZD0iTTAgMGgyNHYyNEgweiIvPjxzdHlsZT5Aa2V5ZnJhbWVzIG9mZmxpbmVBbmltYXRpb257MCUsMzUle2hlaWdodDowfTYwJXtoZWlnaHQ6MTAwJX05MCV7ZmlsbC1vcGFjaXR5OjF9dG97ZmlsbC1vcGFjaXR5OjB9fTwvc3R5bGU+PC9zdmc+);\n  background-position: right 27px center;\n  background-repeat: no-repeat;\n  border: 1px solid var(--google-gray-300);\n  border-radius: 5px;\n  color: var(--google-gray-700);\n  margin-bottom: 26px;\n  padding-bottom: 16px;\n  padding-inline-end: 88px;\n  padding-inline-start: 16px;\n  padding-top: 16px;\n  text-align: start;\n}\n\nhtml[dir='rtl'] #cancel-save-page-button {\n  background-position: left 27px center;\n}\n\n#save-page-for-later-button {\n  display: flex;\n  justify-content: start;\n}\n\n#save-page-for-later-button a::before {\n  content: url(data:image\/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxLjJlbSIgaGVpZ2h0PSIxLjJlbSIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNNSAyMGgxNHYtMkg1bTE0LTloLTRWM0g5djZINWw3IDcgNy03eiIgZmlsbD0iIzQyODVGNCIvPjwvc3ZnPg==);\n  display: inline-block;\n  margin-inline-end: 4px;\n  vertical-align: -webkit-baseline-middle;\n}\n\n.hidden#save-page-for-later-button {\n  display: none;\n}\n\n\/* Don't allow overflow when in a subframe. *\/\nhtml[subframe] body {\n  overflow: hidden;\n}\n\n#sub-frame-error {\n  -webkit-align-items: center;\n  -webkit-flex-flow: column;\n  -webkit-justify-content: center;\n  background-color: #DDD;\n  display: -webkit-flex;\n  height: 100%;\n  left: 0;\n  position: absolute;\n  text-align: center;\n  top: 0;\n  transition: background-color 200ms ease-in-out;\n  width: 100%;\n}\n\n#sub-frame-error:hover {\n  background-color: #EEE;\n}\n\n#sub-frame-error .icon-generic {\n  margin: 0 0 16px;\n}\n\n#sub-frame-error-details {\n  margin: 0 10px;\n  text-align: center;\n  visibility: hidden;\n}\n\n\/* Show details only when hovering. *\/\n#sub-frame-error:hover #sub-frame-error-details {\n  visibility: visible;\n}\n\n\/* If the iframe is too small, always hide the error code. *\/\n\/* TODO(mmenke): See if overflow: no-display works better, once supported. *\/\n@media (max-width: 200px), (max-height: 95px) {\n  #sub-frame-error-details {\n    display: none;\n  }\n}\n\n\/* Adjust icon for small embedded frames in apps. *\/\n@media (max-height: 100px) {\n  #sub-frame-error .icon-generic {\n    height: auto;\n    margin: 0;\n    padding-top: 0;\n    width: 25px;\n  }\n}\n\n\/* details-button is special; it's a <button> element that looks like a link. *\/\n#details-button {\n  box-shadow: none;\n  min-width: 0;\n}\n\n\/* Styles for platform dependent separation of controls and details button. *\/\n.suggested-left > #control-buttons,\n.suggested-right > #details-button {\n  float: left;\n}\n\n.suggested-right > #control-buttons,\n.suggested-left > #details-button {\n  float: right;\n}\n\n.suggested-left .secondary-button {\n  margin-inline-end: 0;\n  margin-inline-start: 16px;\n}\n\n#details-button.singular {\n  float: none;\n}\n\n\/* download-button shows both icon and text. *\/\n#download-button {\n  padding-bottom: 4px;\n  padding-top: 4px;\n  position: relative;\n}\n\n#download-button::before {\n  background: -webkit-image-set(\n      url(data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAO0lEQVQ4y2NgGArgPxIY1YChsOE\/LtBAmpYG0mxpIOSDBpKUo2lpIDZxNJCkHKqlYZAla3RAHQ1DFgAARRroHyLNTwwAAAAASUVORK5CYII=) 1x,\n      url(data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAAZElEQVRYw+3Ruw3AMAwDUY3OzZUmRRD4E9iim9wNwAdbEURHyk4AAAAATiCVK8lLyPsKeT9K3lsownnunfkPxO78hKiYHxBV8x2icr5BVM+\/CMf8g3DN34Rzns6ViwHUAUQ\/6wIAd5Km7l6c8AAAAABJRU5ErkJggg==) 2x)\n    no-repeat;\n  content: '';\n  display: inline-block;\n  height: 24px;\n  margin-inline-end: 4px;\n  margin-inline-start: -4px;\n  vertical-align: middle;\n  width: 24px;\n}\n\n#download-button:disabled {\n  background: rgb(180, 206, 249);\n  color: rgb(255, 255, 255);\n}\n\n#buttons::after {\n  clear: both;\n  content: '';\n  display: block;\n  width: 100%;\n}\n\n\/* Offline page *\/\nhtml[dir='rtl'] .runner-container,\nhtml[dir='rtl'].offline .icon-offline {\n  transform: scaleX(-1);\n}\n\n.offline {\n  transition: filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1),\n              background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);\n\n  will-change: filter, background-color;\n\n}\n\n.offline body {\n  transition: background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);\n}\n\n.offline #main-message > p {\n  display: none;\n}\n\n.offline.inverted {\n  background-color: #fff;\n  filter: invert(1);\n}\n\n.offline.inverted body {\n  background-color: #fff;\n}\n\n.offline .interstitial-wrapper {\n  color: var(--text-color);\n  font-size: 1em;\n  line-height: 1.55;\n  margin: 0 auto;\n  max-width: 600px;\n  padding-top: 100px;\n  position: relative;\n  width: 100%;\n}\n\n.offline .runner-container {\n  direction: ltr;\n  height: 150px;\n  max-width: 600px;\n  overflow: hidden;\n  position: absolute;\n  top: 35px;\n  width: 44px;\n}\n\n.offline .runner-container:focus {\n  outline: none;\n}\n\n.offline .runner-container:focus-visible {\n  outline: 3px solid var(--google-blue-300);\n}\n\n.offline .runner-canvas {\n  height: 150px;\n  max-width: 600px;\n  opacity: 1;\n  overflow: hidden;\n  position: absolute;\n  top: 0;\n  z-index: 10;\n}\n\n.offline .controller {\n  height: 100vh;\n  left: 0;\n  position: absolute;\n  top: 0;\n  width: 100vw;\n  z-index: 9;\n}\n\n#offline-resources {\n  display: none;\n}\n\n#offline-instruction {\n  image-rendering: pixelated;\n  left: 0;\n  margin: auto;\n  position: absolute;\n  right: 0;\n  top: 60px;\n  width: fit-content;\n}\n\n.offline-runner-live-region {\n  bottom: 0;\n  clip-path: polygon(0 0, 0 0, 0 0);\n  color: var(--background-color);\n  display: block;\n  font-size: xx-small;\n  overflow: hidden;\n  position: absolute;\n  text-align: center;\n  transition: color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);\n  user-select: none;\n}\n\n\/* Custom toggle *\/\n.slow-speed-option {\n  align-items: center;\n  background: var(--google-gray-50);\n  border-radius: 24px\/50%;\n  bottom: 0;\n  color: var(--error-code-color);\n  display: inline-flex;\n  font-size: 1em;\n  left: 0;\n  line-height: 1.1em;\n  margin: 5px auto;\n  padding: 2px 12px 3px 20px;\n  position: absolute;\n  right: 0;\n  width: max-content;\n  z-index: 999;\n}\n\n.slow-speed-option.hidden {\n  display: none;\n}\n\n.slow-speed-option [type=checkbox] {\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n}\n\n.slow-speed-option .slow-speed-toggle {\n  cursor: pointer;\n  margin-inline-start: 8px;\n  padding: 8px 4px;\n  position: relative;\n}\n\n.slow-speed-option [type=checkbox]:disabled ~ .slow-speed-toggle {\n  cursor: default;\n}\n\n.slow-speed-option-label [type=checkbox] {\n  opacity: 0;\n  pointer-events: none;\n  position: absolute;\n}\n\n.slow-speed-option .slow-speed-toggle::before,\n.slow-speed-option .slow-speed-toggle::after {\n  content: '';\n  display: block;\n  margin: 0 3px;\n  transition: all 100ms cubic-bezier(0.4, 0, 1, 1);\n}\n\n.slow-speed-option .slow-speed-toggle::before {\n  background: rgb(189,193,198);\n  border-radius: 0.65em;\n  height: 0.9em;\n  width: 2em;\n}\n\n.slow-speed-option .slow-speed-toggle::after {\n  background: #fff;\n  border-radius: 50%;\n  box-shadow: 0 1px 3px 0 rgb(0 0 0 \/ 40%);\n  height: 1.2em;\n  position: absolute;\n  top: 51%;\n  transform: translate(-20%, -50%);\n  width: 1.1em;\n}\n\n.slow-speed-option [type=checkbox]:focus + .slow-speed-toggle {\n  box-shadow: 0 0 8px rgb(94, 158, 214);\n  outline: 1px solid rgb(93, 157, 213);\n}\n\n.slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::before {\n  background: var(--google-blue-600);\n  opacity: 0.5;\n}\n\n.slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::after {\n  background: var(--google-blue-600);\n  transform: translate(calc(2em - 90%), -50%);\n}\n\n.slow-speed-option [type=checkbox]:checked:disabled +\n  .slow-speed-toggle::before {\n  background: rgb(189,193,198);\n}\n\n.slow-speed-option [type=checkbox]:checked:disabled +\n  .slow-speed-toggle::after {\n  background: var(--google-gray-50);\n}\n\n@media (max-width: 420px) {\n  #download-button {\n    padding-bottom: 12px;\n    padding-top: 12px;\n  }\n\n  .suggested-left > #control-buttons,\n  .suggested-right > #control-buttons {\n    float: none;\n  }\n\n  .snackbar {\n    border-radius: 0;\n    bottom: 0;\n    left: 0;\n    width: 100%;\n  }\n}\n\n@media (max-height: 350px) {\n  h1 {\n    margin: 0 0 15px;\n  }\n\n  .icon-offline {\n    margin: 0 0 10px;\n  }\n\n  .interstitial-wrapper {\n    margin-top: 5%;\n  }\n\n  .nav-wrapper {\n    margin-top: 30px;\n  }\n}\n\n@media (min-width: 420px) and (max-width: 736px) and\n       (min-height: 240px) and (max-height: 420px) and\n       (orientation:landscape) {\n  .interstitial-wrapper {\n    margin-bottom: 100px;\n  }\n}\n\n@media (max-width: 360px) and (max-height: 480px) {\n  .offline .interstitial-wrapper {\n    padding-top: 60px;\n  }\n\n  .offline .runner-container {\n    top: 8px;\n  }\n}\n\n@media (min-height: 240px) and (orientation: landscape) {\n  .offline .interstitial-wrapper {\n    margin-bottom: 90px;\n  }\n\n  .icon-offline {\n    margin-bottom: 20px;\n  }\n}\n\n@media (max-height: 320px) and (orientation: landscape) {\n  .icon-offline {\n    margin-bottom: 0;\n  }\n\n  .offline .runner-container {\n    top: 10px;\n  }\n}\n\n@media (max-width: 240px) {\n  button {\n    padding-inline-end: 12px;\n    padding-inline-start: 12px;\n  }\n\n  .interstitial-wrapper {\n    overflow: inherit;\n    padding: 0 8px;\n  }\n}\n\n@media (max-width: 120px) {\n  button {\n    width: auto;\n  }\n}\n\n.arcade-mode,\n.arcade-mode .runner-container,\n.arcade-mode .runner-canvas {\n  image-rendering: pixelated;\n  max-width: 100%;\n  overflow: hidden;\n}\n\n.arcade-mode #buttons,\n.arcade-mode #main-content {\n  opacity: 0;\n  overflow: hidden;\n}\n\n.arcade-mode .interstitial-wrapper {\n  height: 100vh;\n  max-width: 100%;\n  overflow: hidden;\n}\n\n.arcade-mode .runner-container {\n  left: 0;\n  margin: auto;\n  right: 0;\n  transform-origin: top center;\n  transition: transform 250ms cubic-bezier(0.4, 0, 1, 1) 400ms;\n  z-index: 2;\n}\n\n@media (prefers-color-scheme: dark) {\n  .icon {\n    filter: invert(1);\n  }\n\n  .offline .runner-canvas {\n    filter: invert(1);\n  }\n\n  .offline.inverted {\n    background-color: var(--background-color);\n    filter: invert(0);\n  }\n\n  .offline.inverted body {\n    background-color: #fff;\n  }\n\n  .offline.inverted .offline-runner-live-region {\n    color: #fff;\n  }\n\n  #suggestions-list a {\n    color: var(--link-color);\n  }\n\n  #error-information-button {\n    filter: invert(0.6);\n  }\n\n  .slow-speed-option {\n    background: var(--google-gray-800);\n    color: var(--google-gray-100);\n  }\n\n  .slow-speed-option .slow-speed-toggle::before,\n  .slow-speed-option [type=checkbox]:checked:disabled +\n    .slow-speed-toggle::before {\n     background: rgb(189,193,198);\n  }\n\n  .slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::after,\n  .slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::before {\n    background: var(--google-blue-300);\n  }\n}\n<\/style><script>\/\/ Copyright 2013 The Chromium Authors\n\/\/ Use of this source code is governed by a BSD-style license that can be\n\/\/ found in the LICENSE file.\n\n\/**\n * @typedef {{\n *   downloadButtonClick: function(),\n *   reloadButtonClick: function(string),\n *   detailsButtonClick: function(),\n *   diagnoseErrorsButtonClick: function(),\n *   portalSigninsButtonClick: function(),\n *   trackEasterEgg: function(),\n *   updateEasterEggHighScore: function(number),\n *   resetEasterEggHighScore: function(),\n *   launchOfflineItem: function(string, string),\n *   savePageForLater: function(),\n *   cancelSavePage: function(),\n *   listVisibilityChange: function(boolean),\n * }}\n *\/\n\/\/ eslint-disable-next-line no-var\nvar errorPageController;\n\nconst HIDDEN_CLASS = 'hidden';\n\n\/\/ Decodes a UTF16 string that is encoded as base64.\nfunction decodeUTF16Base64ToString(encoded_text) {\n  const data = atob(encoded_text);\n  let result = '';\n  for (let i = 0; i < data.length; i += 2) {\n    result +=\n        String.fromCharCode(data.charCodeAt(i) * 256 + data.charCodeAt(i + 1));\n  }\n  return result;\n}\n\nfunction toggleHelpBox() {\n  const helpBoxOuter = document.getElementById('details');\n  helpBoxOuter.classList.toggle(HIDDEN_CLASS);\n  const detailsButton = document.getElementById('details-button');\n  if (helpBoxOuter.classList.contains(HIDDEN_CLASS)) {\n    \/** @suppress {missingProperties} *\/\n    detailsButton.innerText = detailsButton.detailsText;\n  } else {\n    \/** @suppress {missingProperties} *\/\n    detailsButton.innerText = detailsButton.hideDetailsText;\n  }\n\n  \/\/ Details appears over the main content on small screens.\n  if (mobileNav) {\n    document.getElementById('main-content').classList.toggle(HIDDEN_CLASS);\n    const runnerContainer = document.querySelector('.runner-container');\n    if (runnerContainer) {\n      runnerContainer.classList.toggle(HIDDEN_CLASS);\n    }\n  }\n}\n\nfunction diagnoseErrors() {\n  if (window.errorPageController) {\n    errorPageController.diagnoseErrorsButtonClick();\n  }\n}\n\nfunction portalSignin() {\n  if (window.errorPageController) {\n    errorPageController.portalSigninButtonClick();\n  }\n}\n\n\/\/ Subframes use a different layout but the same html file.  This is to make it\n\/\/ easier to support platforms that load the error page via different\n\/\/ mechanisms (Currently just iOS). We also use the subframe style for portals\n\/\/ as they are embedded like subframes and can't be interacted with by the user.\nlet isSubFrame = false;\nif (window.top.location !== window.location || window.portalHost) {\n  document.documentElement.setAttribute('subframe', '');\n  isSubFrame = true;\n}\n\n\/\/ Re-renders the error page using |strings| as the dictionary of values.\n\/\/ Used by NetErrorTabHelper to update DNS error pages with probe results.\nfunction updateForDnsProbe(strings) {\n  const context = new JsEvalContext(strings);\n  jstProcess(context, document.getElementById('t'));\n  onDocumentLoadOrUpdate();\n}\n\n\/\/ Adds an icon class to the list and removes classes previously set.\nfunction updateIconClass(newClass) {\n  const frameSelector = isSubFrame ? '#sub-frame-error' : '#main-frame-error';\n  const iconEl = document.querySelector(frameSelector + ' .icon');\n\n  if (iconEl.classList.contains(newClass)) {\n    return;\n  }\n\n  iconEl.className = 'icon ' + newClass;\n}\n\n\/\/ Implements button clicks.  This function is needed during the transition\n\/\/ between implementing these in trunk chromium and implementing them in iOS.\nfunction reloadButtonClick(url) {\n  if (window.errorPageController) {\n    \/\/ \n\n    \/\/ \n    errorPageController.reloadButtonClick();\n    \/\/ \n  } else {\n    window.location = url;\n  }\n}\n\nfunction downloadButtonClick() {\n  if (window.errorPageController) {\n    errorPageController.downloadButtonClick();\n    const downloadButton = document.getElementById('download-button');\n    downloadButton.disabled = true;\n    \/** @suppress {missingProperties} *\/\n    downloadButton.textContent = downloadButton.disabledText;\n\n    document.getElementById('download-link-wrapper')\n        .classList.add(HIDDEN_CLASS);\n    document.getElementById('download-link-clicked-wrapper')\n        .classList.remove(HIDDEN_CLASS);\n  }\n}\n\nfunction detailsButtonClick() {\n  if (window.errorPageController) {\n    errorPageController.detailsButtonClick();\n  }\n}\n\nlet primaryControlOnLeft = true;\n\/\/ clang-format off\n\/\/ \n\nfunction setAutoFetchState(scheduled, can_schedule) {\n  document.getElementById('cancel-save-page-button')\n      .classList.toggle(HIDDEN_CLASS, !scheduled);\n  document.getElementById('save-page-for-later-button')\n      .classList.toggle(HIDDEN_CLASS, scheduled || !can_schedule);\n}\n\nfunction savePageLaterClick() {\n  errorPageController.savePageForLater();\n  \/\/ savePageForLater will eventually trigger a call to setAutoFetchState() when\n  \/\/ it completes.\n}\n\nfunction cancelSavePageClick() {\n  errorPageController.cancelSavePage();\n  \/\/ setAutoFetchState is not called in response to cancelSavePage(), so do it\n  \/\/ now.\n  setAutoFetchState(false, true);\n}\n\nfunction toggleErrorInformationPopup() {\n  document.getElementById('error-information-popup-container')\n      .classList.toggle(HIDDEN_CLASS);\n}\n\nfunction launchOfflineItem(itemID, name_space) {\n  errorPageController.launchOfflineItem(itemID, name_space);\n}\n\nfunction launchDownloadsPage() {\n  errorPageController.launchDownloadsPage();\n}\n\nfunction getIconForSuggestedItem(item) {\n  \/\/ Note: |item.content_type| contains the enum values from\n  \/\/ chrome::mojom::AvailableContentType.\n  switch (item.content_type) {\n    case 1:  \/\/ kVideo\n      return 'image-video';\n    case 2:  \/\/ kAudio\n      return 'image-music-note';\n    case 0:  \/\/ kPrefetchedPage\n    case 3:  \/\/ kOtherPage\n      return 'image-earth';\n  }\n  return 'image-file';\n}\n\nfunction getSuggestedContentDiv(item, index) {\n  \/\/ Note: See AvailableContentToValue in available_offline_content_helper.cc\n  \/\/ for the data contained in an |item|.\n  \/\/ TODO(carlosk): Present |snippet_base64| when that content becomes\n  \/\/ available.\n  let thumbnail = '';\n  const extraContainerClasses = [];\n  \/\/ html_inline.py will try to replace src attributes with data URIs using a\n  \/\/ simple regex. The following is obfuscated slightly to avoid that.\n  const source = 'src';\n  if (item.thumbnail_data_uri) {\n    extraContainerClasses.push('suggestion-with-image');\n    thumbnail = `<img ${source}=\"${item.thumbnail_data_uri}\">`;\n  } else {\n    extraContainerClasses.push('suggestion-with-icon');\n    const iconClass = getIconForSuggestedItem(item);\n    thumbnail = `<div><img class=\"${iconClass}\"><\/div>`;\n  }\n\n  let favicon = '';\n  if (item.favicon_data_uri) {\n    favicon = `<img ${source}=\"${item.favicon_data_uri}\">`;\n  } else {\n    extraContainerClasses.push('no-favicon');\n  }\n\n  if (!item.attribution_base64) {\n    extraContainerClasses.push('no-attribution');\n  }\n\n  return `\n  <div class=\"offline-content-suggestion ${extraContainerClasses.join(' ')}\"\n    onclick=\"launchOfflineItem('${item.ID}', '${item.name_space}')\">\n      <div class=\"offline-content-suggestion-texts\">\n        <div id=\"offline-content-suggestion-title-${index}\"\n             class=\"offline-content-suggestion-title\">\n        <\/div>\n        <div class=\"offline-content-suggestion-attribution-freshness\">\n          <div id=\"offline-content-suggestion-favicon-${index}\"\n               class=\"offline-content-suggestion-favicon\">\n            ${favicon}\n          <\/div>\n          <div id=\"offline-content-suggestion-attribution-${index}\"\n               class=\"offline-content-suggestion-attribution\">\n          <\/div>\n          <div class=\"offline-content-suggestion-freshness\">\n            ${item.date_modified}\n          <\/div>\n          <div class=\"offline-content-suggestion-pin-spacer\"><\/div>\n          <div class=\"offline-content-suggestion-pin\"><\/div>\n        <\/div>\n      <\/div>\n      <div class=\"offline-content-suggestion-thumbnail\">\n        ${thumbnail}\n      <\/div>\n  <\/div>`;\n}\n\n\/**\n * @typedef {{\n *   ID: string,\n *   name_space: string,\n *   title_base64: string,\n *   snippet_base64: string,\n *   date_modified: string,\n *   attribution_base64: string,\n *   thumbnail_data_uri: string,\n *   favicon_data_uri: string,\n *   content_type: number,\n * }}\n *\/\nlet AvailableOfflineContent;\n\n\/\/ Populates a list of suggested offline content.\n\/\/ Note: For security reasons all content downloaded from the web is considered\n\/\/ unsafe and must be securely handled to be presented on the dino page. Images\n\/\/ have already been safely re-encoded but textual content -- like title and\n\/\/ attribution -- must be properly handled here.\n\/\/ @param {boolean} isShown\n\/\/ @param {Array<AvailableOfflineContent>} suggestions\nfunction offlineContentAvailable(isShown, suggestions) {\n  if (!suggestions || !loadTimeData.valueExists('offlineContentList')) {\n    return;\n  }\n\n  const suggestionsHTML = [];\n  for (let index = 0; index < suggestions.length; index++) {\n    suggestionsHTML.push(getSuggestedContentDiv(suggestions[index], index));\n  }\n\n  document.getElementById('offline-content-suggestions').innerHTML =\n      suggestionsHTML.join('\\n');\n\n  \/\/ Sets textual web content using |textContent| to make sure it's handled as\n  \/\/ plain text.\n  for (let index = 0; index < suggestions.length; index++) {\n    document.getElementById(`offline-content-suggestion-title-${index}`)\n        .textContent =\n        decodeUTF16Base64ToString(suggestions[index].title_base64);\n    document.getElementById(`offline-content-suggestion-attribution-${index}`)\n        .textContent =\n        decodeUTF16Base64ToString(suggestions[index].attribution_base64);\n  }\n\n  const contentListElement = document.getElementById('offline-content-list');\n  if (document.dir === 'rtl') {\n    contentListElement.classList.add('is-rtl');\n  }\n  contentListElement.hidden = false;\n  \/\/ The list is configured as hidden by default. Show it if needed.\n  if (isShown) {\n    toggleOfflineContentListVisibility(false);\n  }\n}\n\nfunction toggleOfflineContentListVisibility(updatePref) {\n  if (!loadTimeData.valueExists('offlineContentList')) {\n    return;\n  }\n\n  const contentListElement = document.getElementById('offline-content-list');\n  const isVisible = !contentListElement.classList.toggle('list-hidden');\n\n  if (updatePref &#038;&#038; window.errorPageController) {\n    errorPageController.listVisibilityChanged(isVisible);\n  }\n}\n\n\/\/ Called on document load, and from updateForDnsProbe().\nfunction onDocumentLoadOrUpdate() {\n  const downloadButtonVisible = loadTimeData.valueExists('downloadButton') &#038;&#038;\n      loadTimeData.getValue('downloadButton').msg;\n  const detailsButton = document.getElementById('details-button');\n\n  \/\/ If offline content suggestions will be visible, the usual buttons will not\n  \/\/ be presented.\n  const offlineContentVisible =\n      loadTimeData.valueExists('suggestedOfflineContentPresentation');\n  if (offlineContentVisible) {\n    document.querySelector('.nav-wrapper').classList.add(HIDDEN_CLASS);\n    detailsButton.classList.add(HIDDEN_CLASS);\n\n    document.getElementById('download-link').hidden = !downloadButtonVisible;\n    document.getElementById('download-links-wrapper')\n        .classList.remove(HIDDEN_CLASS);\n    document.getElementById('error-information-popup-container')\n        .classList.add('use-popup-container', HIDDEN_CLASS);\n    document.getElementById('error-information-button')\n        .classList.remove(HIDDEN_CLASS);\n  }\n\n  const attemptAutoFetch = loadTimeData.valueExists('attemptAutoFetch') &#038;&#038;\n      loadTimeData.getValue('attemptAutoFetch');\n\n  const reloadButtonVisible = loadTimeData.valueExists('reloadButton') &#038;&#038;\n      loadTimeData.getValue('reloadButton').msg;\n\n  const reloadButton = document.getElementById('reload-button');\n  const downloadButton = document.getElementById('download-button');\n  if (reloadButton.style.display === 'none' &#038;&#038;\n      downloadButton.style.display === 'none') {\n    detailsButton.classList.add('singular');\n  }\n\n  \/\/ Show or hide control buttons.\n  const controlButtonDiv = document.getElementById('control-buttons');\n  controlButtonDiv.hidden =\n      offlineContentVisible || !(reloadButtonVisible || downloadButtonVisible);\n\n  const iconClass = loadTimeData.valueExists('iconClass') &#038;&#038;\n      loadTimeData.getValue('iconClass');\n\n  updateIconClass(iconClass);\n\n  if (!isSubFrame &#038;&#038; iconClass === 'icon-offline') {\n    document.documentElement.classList.add('offline');\n    new Runner('.interstitial-wrapper');\n  }\n}\n\nfunction onDocumentLoad() {\n  \/\/ Sets up the proper button layout for the current platform.\n  const buttonsDiv = document.getElementById('buttons');\n  if (primaryControlOnLeft) {\n    buttonsDiv.classList.add('suggested-left');\n  } else {\n    buttonsDiv.classList.add('suggested-right');\n  }\n\n  onDocumentLoadOrUpdate();\n}\n\ndocument.addEventListener('DOMContentLoaded', onDocumentLoad);\n<\/script><script>\/\/ Copyright 2015 The Chromium Authors\n\/\/ Use of this source code is governed by a BSD-style license that can be\n\/\/ found in the LICENSE file.\n\nlet mobileNav = false;\n\n\/**\n * For small screen mobile the navigation buttons are moved\n * below the advanced text.\n *\/\nfunction onResize() {\n  const helpOuterBox = document.querySelector('#details');\n  const mainContent = document.querySelector('#main-content');\n  const mediaQuery = '(min-width: 240px) and (max-width: 420px) and ' +\n      '(min-height: 401px), ' +\n      '(max-height: 560px) and (min-height: 240px) and ' +\n      '(min-width: 421px)';\n\n  const detailsHidden = helpOuterBox.classList.contains(HIDDEN_CLASS);\n  const runnerContainer = document.querySelector('.runner-container');\n\n  \/\/ Check for change in nav status.\n  if (mobileNav !== window.matchMedia(mediaQuery).matches) {\n    mobileNav = !mobileNav;\n\n    \/\/ Handle showing the top content \/ details sections according to state.\n    if (mobileNav) {\n      mainContent.classList.toggle(HIDDEN_CLASS, !detailsHidden);\n      helpOuterBox.classList.toggle(HIDDEN_CLASS, detailsHidden);\n      if (runnerContainer) {\n        runnerContainer.classList.toggle(HIDDEN_CLASS, !detailsHidden);\n      }\n    } else if (!detailsHidden) {\n      \/\/ Non mobile nav with visible details.\n      mainContent.classList.remove(HIDDEN_CLASS);\n      helpOuterBox.classList.remove(HIDDEN_CLASS);\n      if (runnerContainer) {\n        runnerContainer.classList.remove(HIDDEN_CLASS);\n      }\n    }\n  }\n}\n\nfunction setupMobileNav() {\n  window.addEventListener('resize', onResize);\n  onResize();\n}\n\ndocument.addEventListener('DOMContentLoaded', setupMobileNav);\n<\/script><script>\/\/ Copyright 2014 The Chromium Authors\n\/\/ Use of this source code is governed by a BSD-style license that can be\n\/\/ found in the LICENSE file.\n\n\/**\n * T-Rex runner.\n * @param {string} outerContainerId Outer containing element id.\n * @param {!Object=} opt_config\n * @constructor\n * @implements {EventListener}\n * @export\n *\/\nfunction Runner(outerContainerId, opt_config) {\n  \/\/ Singleton\n  if (Runner.instance_) {\n    return Runner.instance_;\n  }\n  Runner.instance_ = this;\n\n  this.outerContainerEl = document.querySelector(outerContainerId);\n  this.containerEl = null;\n  this.snackbarEl = null;\n  \/\/ A div to intercept touch events. Only set while (playing && useTouch).\n  this.touchController = null;\n\n  this.config = opt_config || Object.assign(Runner.config, Runner.normalConfig);\n  \/\/ Logical dimensions of the container.\n  this.dimensions = Runner.defaultDimensions;\n\n  this.gameType = null;\n  Runner.spriteDefinition = Runner.spriteDefinitionByType['original'];\n\n  this.altGameImageSprite = null;\n  this.altGameModeActive = false;\n  this.altGameModeFlashTimer = null;\n  this.fadeInTimer = 0;\n\n  this.canvas = null;\n  this.canvasCtx = null;\n\n  this.tRex = null;\n\n  this.distanceMeter = null;\n  this.distanceRan = 0;\n\n  this.highestScore = 0;\n  this.syncHighestScore = false;\n\n  this.time = 0;\n  this.runningTime = 0;\n  this.msPerFrame = 1000 \/ FPS;\n  this.currentSpeed = this.config.SPEED;\n  Runner.slowDown = false;\n\n  this.obstacles = [];\n\n  this.activated = false; \/\/ Whether the easter egg has been activated.\n  this.playing = false; \/\/ Whether the game is currently in play state.\n  this.crashed = false;\n  this.paused = false;\n  this.inverted = false;\n  this.invertTimer = 0;\n  this.resizeTimerId_ = null;\n\n  this.playCount = 0;\n\n  \/\/ Sound FX.\n  this.audioBuffer = null;\n\n  \/** @type {Object} *\/\n  this.soundFx = {};\n  this.generatedSoundFx = null;\n\n  \/\/ Global web audio context for playing sounds.\n  this.audioContext = null;\n\n  \/\/ Images.\n  this.images = {};\n  this.imagesLoaded = 0;\n\n  \/\/ Gamepad state.\n  this.pollingGamepads = false;\n  this.gamepadIndex = undefined;\n  this.previousGamepad = null;\n\n  if (this.isDisabled()) {\n    this.setupDisabledRunner();\n  } else {\n    if (Runner.isAltGameModeEnabled()) {\n      this.initAltGameType();\n      Runner.gameType = this.gameType;\n    }\n    this.loadImages();\n\n    window['initializeEasterEggHighScore'] =\n        this.initializeHighScore.bind(this);\n  }\n}\n\n\/**\n * Default game width.\n * @const\n *\/\nconst DEFAULT_WIDTH = 600;\n\n\/**\n * Frames per second.\n * @const\n *\/\nconst FPS = 60;\n\n\/** @const *\/\nconst IS_HIDPI = window.devicePixelRatio > 1;\n\n\/** @const *\/\nconst IS_IOS = \/CriOS\/.test(window.navigator.userAgent);\n\n\/** @const *\/\nconst IS_MOBILE = \/Android\/.test(window.navigator.userAgent) || IS_IOS;\n\n\/** @const *\/\nconst IS_RTL = document.querySelector('html').dir == 'rtl';\n\n\/** @const *\/\nconst ARCADE_MODE_URL = 'chrome:\/\/dino\/';\n\n\/** @const *\/\nconst RESOURCE_POSTFIX = 'offline-resources-';\n\n\/** @const *\/\nconst A11Y_STRINGS = {\n  ariaLabel: 'dinoGameA11yAriaLabel',\n  description: 'dinoGameA11yDescription',\n  gameOver: 'dinoGameA11yGameOver',\n  highScore: 'dinoGameA11yHighScore',\n  jump: 'dinoGameA11yJump',\n  started: 'dinoGameA11yStartGame',\n  speedLabel: 'dinoGameA11ySpeedToggle',\n};\n\n\/**\n * Default game configuration.\n * Shared config for all  versions of the game. Additional parameters are\n * defined in Runner.normalConfig and Runner.slowConfig.\n *\/\nRunner.config = {\n  AUDIOCUE_PROXIMITY_THRESHOLD: 190,\n  AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y: 250,\n  BG_CLOUD_SPEED: 0.2,\n  BOTTOM_PAD: 10,\n  \/\/ Scroll Y threshold at which the game can be activated.\n  CANVAS_IN_VIEW_OFFSET: -10,\n  CLEAR_TIME: 3000,\n  CLOUD_FREQUENCY: 0.5,\n  FADE_DURATION: 1,\n  FLASH_DURATION: 1000,\n  GAMEOVER_CLEAR_TIME: 1200,\n  INITIAL_JUMP_VELOCITY: 12,\n  INVERT_FADE_DURATION: 12000,\n  MAX_BLINK_COUNT: 3,\n  MAX_CLOUDS: 6,\n  MAX_OBSTACLE_LENGTH: 3,\n  MAX_OBSTACLE_DUPLICATION: 2,\n  RESOURCE_TEMPLATE_ID: 'audio-resources',\n  SPEED: 6,\n  SPEED_DROP_COEFFICIENT: 3,\n  ARCADE_MODE_INITIAL_TOP_POSITION: 35,\n  ARCADE_MODE_TOP_POSITION_PERCENT: 0.1,\n};\n\nRunner.normalConfig = {\n  ACCELERATION: 0.001,\n  AUDIOCUE_PROXIMITY_THRESHOLD: 190,\n  AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y: 250,\n  GAP_COEFFICIENT: 0.6,\n  INVERT_DISTANCE: 700,\n  MAX_SPEED: 13,\n  MOBILE_SPEED_COEFFICIENT: 1.2,\n  SPEED: 6,\n};\n\n\nRunner.slowConfig = {\n  ACCELERATION: 0.0005,\n  AUDIOCUE_PROXIMITY_THRESHOLD: 170,\n  AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y: 220,\n  GAP_COEFFICIENT: 0.3,\n  INVERT_DISTANCE: 350,\n  MAX_SPEED: 9,\n  MOBILE_SPEED_COEFFICIENT: 1.5,\n  SPEED: 4.2,\n};\n\n\n\/**\n * Default dimensions.\n *\/\nRunner.defaultDimensions = {\n  WIDTH: DEFAULT_WIDTH,\n  HEIGHT: 150,\n};\n\n\n\/**\n * CSS class names.\n * @enum {string}\n *\/\nRunner.classes = {\n  ARCADE_MODE: 'arcade-mode',\n  CANVAS: 'runner-canvas',\n  CONTAINER: 'runner-container',\n  CRASHED: 'crashed',\n  ICON: 'icon-offline',\n  INVERTED: 'inverted',\n  SNACKBAR: 'snackbar',\n  SNACKBAR_SHOW: 'snackbar-show',\n  TOUCH_CONTROLLER: 'controller',\n};\n\n\n\/**\n * Sound FX. Reference to the ID of the audio tag on interstitial page.\n * @enum {string}\n *\/\nRunner.sounds = {\n  BUTTON_PRESS: 'offline-sound-press',\n  HIT: 'offline-sound-hit',\n  SCORE: 'offline-sound-reached',\n};\n\n\n\/**\n * Key code mapping.\n * @enum {Object}\n *\/\nRunner.keycodes = {\n  JUMP: {'38': 1, '32': 1},  \/\/ Up, spacebar\n  DUCK: {'40': 1},           \/\/ Down\n  RESTART: {'13': 1},        \/\/ Enter\n};\n\n\n\/**\n * Runner event names.\n * @enum {string}\n *\/\nRunner.events = {\n  ANIM_END: 'webkitAnimationEnd',\n  CLICK: 'click',\n  KEYDOWN: 'keydown',\n  KEYUP: 'keyup',\n  POINTERDOWN: 'pointerdown',\n  POINTERUP: 'pointerup',\n  RESIZE: 'resize',\n  TOUCHEND: 'touchend',\n  TOUCHSTART: 'touchstart',\n  VISIBILITY: 'visibilitychange',\n  BLUR: 'blur',\n  FOCUS: 'focus',\n  LOAD: 'load',\n  GAMEPADCONNECTED: 'gamepadconnected',\n};\n\nRunner.prototype = {\n  \/**\n   * Initialize alternative game type.\n   *\/\n  initAltGameType() {\n    if (GAME_TYPE.length > 0) {\n      this.gameType = loadTimeData && loadTimeData.valueExists('altGameType') ?\n          GAME_TYPE[parseInt(loadTimeData.getValue('altGameType'), 10) - 1] :\n          '';\n    }\n  },\n\n  \/**\n   * Whether the easter egg has been disabled. CrOS enterprise enrolled devices.\n   * @return {boolean}\n   *\/\n  isDisabled() {\n    return loadTimeData && loadTimeData.valueExists('disabledEasterEgg');\n  },\n\n  \/**\n   * For disabled instances, set up a snackbar with the disabled message.\n   *\/\n  setupDisabledRunner() {\n    this.containerEl = document.createElement('div');\n    this.containerEl.className = Runner.classes.SNACKBAR;\n    this.containerEl.textContent = loadTimeData.getValue('disabledEasterEgg');\n    this.outerContainerEl.appendChild(this.containerEl);\n\n    \/\/ Show notification when the activation key is pressed.\n    document.addEventListener(Runner.events.KEYDOWN, function(e) {\n      if (Runner.keycodes.JUMP[e.keyCode]) {\n        this.containerEl.classList.add(Runner.classes.SNACKBAR_SHOW);\n        document.querySelector('.icon').classList.add('icon-disabled');\n      }\n    }.bind(this));\n  },\n\n  \/**\n   * Setting individual settings for debugging.\n   * @param {string} setting\n   * @param {number|string} value\n   *\/\n  updateConfigSetting(setting, value) {\n    if (setting in this.config && value !== undefined) {\n      this.config[setting] = value;\n\n      switch (setting) {\n        case 'GRAVITY':\n        case 'MIN_JUMP_HEIGHT':\n        case 'SPEED_DROP_COEFFICIENT':\n          this.tRex.config[setting] = value;\n          break;\n        case 'INITIAL_JUMP_VELOCITY':\n          this.tRex.setJumpVelocity(value);\n          break;\n        case 'SPEED':\n          this.setSpeed(\/** @type {number} *\/ (value));\n          break;\n      }\n    }\n  },\n\n  \/**\n   * Creates an on page image element from the base 64 encoded string source.\n   * @param {string} resourceName Name in data object,\n   * @return {HTMLImageElement} The created element.\n   *\/\n  createImageElement(resourceName) {\n    const imgSrc = loadTimeData && loadTimeData.valueExists(resourceName) ?\n        loadTimeData.getString(resourceName) :\n        null;\n\n    if (imgSrc) {\n      const el =\n          \/** @type {HTMLImageElement} *\/ (document.createElement('img'));\n      el.id = resourceName;\n      el.src = imgSrc;\n      document.getElementById('offline-resources').appendChild(el);\n      return el;\n    }\n    return null;\n  },\n\n  \/**\n   * Cache the appropriate image sprite from the page and get the sprite sheet\n   * definition.\n   *\/\n  loadImages() {\n    let scale = '1x';\n    this.spriteDef = Runner.spriteDefinition.LDPI;\n    if (IS_HIDPI) {\n      scale = '2x';\n      this.spriteDef = Runner.spriteDefinition.HDPI;\n    }\n\n    Runner.imageSprite = \/** @type {HTMLImageElement} *\/\n        (document.getElementById(RESOURCE_POSTFIX + scale));\n\n    if (this.gameType) {\n      Runner.altGameImageSprite = \/** @type {HTMLImageElement} *\/\n          (this.createImageElement('altGameSpecificImage' + scale));\n      Runner.altCommonImageSprite = \/** @type {HTMLImageElement} *\/\n          (this.createImageElement('altGameCommonImage' + scale));\n    }\n    Runner.origImageSprite = Runner.imageSprite;\n\n    \/\/ Disable the alt game mode if the sprites can't be loaded.\n    if (!Runner.altGameImageSprite || !Runner.altCommonImageSprite) {\n      Runner.isAltGameModeEnabled = () => false;\n      this.altGameModeActive = false;\n    }\n\n    if (Runner.imageSprite.complete) {\n      this.init();\n    } else {\n      \/\/ If the images are not yet loaded, add a listener.\n      Runner.imageSprite.addEventListener(Runner.events.LOAD,\n          this.init.bind(this));\n    }\n  },\n\n  \/**\n   * Load and decode base 64 encoded sounds.\n   *\/\n  loadSounds() {\n    if (!IS_IOS) {\n      this.audioContext = new AudioContext();\n\n      const resourceTemplate =\n          document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content;\n\n      for (const sound in Runner.sounds) {\n        let soundSrc =\n            resourceTemplate.getElementById(Runner.sounds[sound]).src;\n        soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1);\n        const buffer = decodeBase64ToArrayBuffer(soundSrc);\n\n        \/\/ Async, so no guarantee of order in array.\n        this.audioContext.decodeAudioData(buffer, function(index, audioData) {\n            this.soundFx[index] = audioData;\n          }.bind(this, sound));\n      }\n    }\n  },\n\n  \/**\n   * Sets the game speed. Adjust the speed accordingly if on a smaller screen.\n   * @param {number=} opt_speed\n   *\/\n  setSpeed(opt_speed) {\n    const speed = opt_speed || this.currentSpeed;\n\n    \/\/ Reduce the speed on smaller mobile screens.\n    if (this.dimensions.WIDTH < DEFAULT_WIDTH) {\n      const mobileSpeed = Runner.slowDown ? speed :\n                                            speed * this.dimensions.WIDTH \/\n              DEFAULT_WIDTH * this.config.MOBILE_SPEED_COEFFICIENT;\n      this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed;\n    } else if (opt_speed) {\n      this.currentSpeed = opt_speed;\n    }\n  },\n\n  \/**\n   * Game initialiser.\n   *\/\n  init() {\n    \/\/ Hide the static icon.\n    document.querySelector('.' + Runner.classes.ICON).style.visibility =\n        'hidden';\n\n    this.adjustDimensions();\n    this.setSpeed();\n\n    const ariaLabel = getA11yString(A11Y_STRINGS.ariaLabel);\n    this.containerEl = document.createElement('div');\n    this.containerEl.setAttribute('role', IS_MOBILE ? 'button' : 'application');\n    this.containerEl.setAttribute('tabindex', '0');\n    this.containerEl.setAttribute('title', ariaLabel);\n\n    this.containerEl.className = Runner.classes.CONTAINER;\n\n    \/\/ Player canvas container.\n    this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH,\n        this.dimensions.HEIGHT);\n\n    \/\/ Live region for game status updates.\n    this.a11yStatusEl = document.createElement('span');\n    this.a11yStatusEl.className = 'offline-runner-live-region';\n    this.a11yStatusEl.setAttribute('aria-live', 'assertive');\n    this.a11yStatusEl.textContent = '';\n    Runner.a11yStatusEl = this.a11yStatusEl;\n\n    \/\/ Add checkbox to slow down the game.\n    this.slowSpeedCheckboxLabel = document.createElement('label');\n    this.slowSpeedCheckboxLabel.className = 'slow-speed-option hidden';\n    this.slowSpeedCheckboxLabel.textContent =\n        getA11yString(A11Y_STRINGS.speedLabel);\n\n    this.slowSpeedCheckbox = document.createElement('input');\n    this.slowSpeedCheckbox.setAttribute('type', 'checkbox');\n    this.slowSpeedCheckbox.setAttribute(\n        'title', getA11yString(A11Y_STRINGS.speedLabel));\n    this.slowSpeedCheckbox.setAttribute('tabindex', '0');\n    this.slowSpeedCheckbox.setAttribute('checked', 'checked');\n\n    this.slowSpeedToggleEl = document.createElement('span');\n    this.slowSpeedToggleEl.className = 'slow-speed-toggle';\n\n    this.slowSpeedCheckboxLabel.appendChild(this.slowSpeedCheckbox);\n    this.slowSpeedCheckboxLabel.appendChild(this.slowSpeedToggleEl);\n\n    if (IS_IOS) {\n      this.outerContainerEl.appendChild(this.a11yStatusEl);\n    } else {\n      this.containerEl.appendChild(this.a11yStatusEl);\n    }\n\n    announcePhrase(getA11yString(A11Y_STRINGS.description));\n\n    this.generatedSoundFx = new GeneratedSoundFx();\n\n    this.canvasCtx =\n        \/** @type {CanvasRenderingContext2D} *\/ (this.canvas.getContext('2d'));\n    this.canvasCtx.fillStyle = '#f7f7f7';\n    this.canvasCtx.fill();\n    Runner.updateCanvasScaling(this.canvas);\n\n    \/\/ Horizon contains clouds, obstacles and the ground.\n    this.horizon = new Horizon(this.canvas, this.spriteDef, this.dimensions,\n        this.config.GAP_COEFFICIENT);\n\n    \/\/ Distance meter\n    this.distanceMeter = new DistanceMeter(this.canvas,\n          this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH);\n\n    \/\/ Draw t-rex\n    this.tRex = new Trex(this.canvas, this.spriteDef.TREX);\n\n    this.outerContainerEl.appendChild(this.containerEl);\n    this.outerContainerEl.appendChild(this.slowSpeedCheckboxLabel);\n\n    this.startListening();\n    this.update();\n\n    window.addEventListener(Runner.events.RESIZE,\n        this.debounceResize.bind(this));\n\n    \/\/ Handle dark mode\n    const darkModeMediaQuery =\n        window.matchMedia('(prefers-color-scheme: dark)');\n    this.isDarkMode = darkModeMediaQuery && darkModeMediaQuery.matches;\n    darkModeMediaQuery.addListener((e) => {\n      this.isDarkMode = e.matches;\n    });\n  },\n\n  \/**\n   * Create the touch controller. A div that covers whole screen.\n   *\/\n  createTouchController() {\n    this.touchController = document.createElement('div');\n    this.touchController.className = Runner.classes.TOUCH_CONTROLLER;\n    this.touchController.addEventListener(Runner.events.TOUCHSTART, this);\n    this.touchController.addEventListener(Runner.events.TOUCHEND, this);\n    this.outerContainerEl.appendChild(this.touchController);\n  },\n\n  \/**\n   * Debounce the resize event.\n   *\/\n  debounceResize() {\n    if (!this.resizeTimerId_) {\n      this.resizeTimerId_ =\n          setInterval(this.adjustDimensions.bind(this), 250);\n    }\n  },\n\n  \/**\n   * Adjust game space dimensions on resize.\n   *\/\n  adjustDimensions() {\n    clearInterval(this.resizeTimerId_);\n    this.resizeTimerId_ = null;\n\n    const boxStyles = window.getComputedStyle(this.outerContainerEl);\n    const padding = Number(boxStyles.paddingLeft.substr(0,\n        boxStyles.paddingLeft.length - 2));\n\n    this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2;\n    if (this.isArcadeMode()) {\n      this.dimensions.WIDTH = Math.min(DEFAULT_WIDTH, this.dimensions.WIDTH);\n      if (this.activated) {\n        this.setArcadeModeContainerScale();\n      }\n    }\n\n    \/\/ Redraw the elements back onto the canvas.\n    if (this.canvas) {\n      this.canvas.width = this.dimensions.WIDTH;\n      this.canvas.height = this.dimensions.HEIGHT;\n\n      Runner.updateCanvasScaling(this.canvas);\n\n      this.distanceMeter.calcXPos(this.dimensions.WIDTH);\n      this.clearCanvas();\n      this.horizon.update(0, 0, true);\n      this.tRex.update(0);\n\n      \/\/ Outer container and distance meter.\n      if (this.playing || this.crashed || this.paused) {\n        this.containerEl.style.width = this.dimensions.WIDTH + 'px';\n        this.containerEl.style.height = this.dimensions.HEIGHT + 'px';\n        this.distanceMeter.update(0, Math.ceil(this.distanceRan));\n        this.stop();\n      } else {\n        this.tRex.draw(0, 0);\n      }\n\n      \/\/ Game over panel.\n      if (this.crashed && this.gameOverPanel) {\n        this.gameOverPanel.updateDimensions(this.dimensions.WIDTH);\n        this.gameOverPanel.draw(this.altGameModeActive, this.tRex);\n      }\n    }\n  },\n\n  \/**\n   * Play the game intro.\n   * Canvas container width expands out to the full width.\n   *\/\n  playIntro() {\n    if (!this.activated && !this.crashed) {\n      this.playingIntro = true;\n      this.tRex.playingIntro = true;\n\n      \/\/ CSS animation definition.\n      const keyframes = '@-webkit-keyframes intro { ' +\n            'from { width:' + Trex.config.WIDTH + 'px }' +\n            'to { width: ' + this.dimensions.WIDTH + 'px }' +\n          '}';\n      document.styleSheets[0].insertRule(keyframes, 0);\n\n      this.containerEl.addEventListener(Runner.events.ANIM_END,\n          this.startGame.bind(this));\n\n      this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';\n      this.containerEl.style.width = this.dimensions.WIDTH + 'px';\n\n      this.setPlayStatus(true);\n      this.activated = true;\n    } else if (this.crashed) {\n      this.restart();\n    }\n  },\n\n\n  \/**\n   * Update the game status to started.\n   *\/\n  startGame() {\n    if (this.isArcadeMode()) {\n      this.setArcadeMode();\n    }\n    this.toggleSpeed();\n    this.runningTime = 0;\n    this.playingIntro = false;\n    this.tRex.playingIntro = false;\n    this.containerEl.style.webkitAnimation = '';\n    this.playCount++;\n    this.generatedSoundFx.background();\n    announcePhrase(getA11yString(A11Y_STRINGS.started));\n\n    if (Runner.audioCues) {\n      this.containerEl.setAttribute('title', getA11yString(A11Y_STRINGS.jump));\n    }\n\n    \/\/ Handle tabbing off the page. Pause the current game.\n    document.addEventListener(Runner.events.VISIBILITY,\n          this.onVisibilityChange.bind(this));\n\n    window.addEventListener(Runner.events.BLUR,\n          this.onVisibilityChange.bind(this));\n\n    window.addEventListener(Runner.events.FOCUS,\n          this.onVisibilityChange.bind(this));\n  },\n\n  clearCanvas() {\n    this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH,\n        this.dimensions.HEIGHT);\n  },\n\n  \/**\n   * Checks whether the canvas area is in the viewport of the browser\n   * through the current scroll position.\n   * @return boolean.\n   *\/\n  isCanvasInView() {\n    return this.containerEl.getBoundingClientRect().top >\n        Runner.config.CANVAS_IN_VIEW_OFFSET;\n  },\n\n  \/**\n   * Enable the alt game mode. Switching out the sprites.\n   *\/\n  enableAltGameMode() {\n    Runner.imageSprite = Runner.altGameImageSprite;\n    Runner.spriteDefinition = Runner.spriteDefinitionByType[Runner.gameType];\n\n    if (IS_HIDPI) {\n      this.spriteDef = Runner.spriteDefinition.HDPI;\n    } else {\n      this.spriteDef = Runner.spriteDefinition.LDPI;\n    }\n\n    this.altGameModeActive = true;\n    this.tRex.enableAltGameMode(this.spriteDef.TREX);\n    this.horizon.enableAltGameMode(this.spriteDef);\n    this.generatedSoundFx.background();\n  },\n\n  \/**\n   * Update the game frame and schedules the next one.\n   *\/\n  update() {\n    this.updatePending = false;\n\n    const now = getTimeStamp();\n    let deltaTime = now - (this.time || now);\n\n    \/\/ Flashing when switching game modes.\n    if (this.altGameModeFlashTimer < 0 || this.altGameModeFlashTimer === 0) {\n      this.altGameModeFlashTimer = null;\n      this.tRex.setFlashing(false);\n      this.enableAltGameMode();\n    } else if (this.altGameModeFlashTimer > 0) {\n      this.altGameModeFlashTimer -= deltaTime;\n      this.tRex.update(deltaTime);\n      deltaTime = 0;\n    }\n\n    this.time = now;\n\n    if (this.playing) {\n      this.clearCanvas();\n\n      \/\/ Additional fade in - Prevents jump when switching sprites\n      if (this.altGameModeActive &&\n          this.fadeInTimer <= this.config.FADE_DURATION) {\n        this.fadeInTimer += deltaTime \/ 1000;\n        this.canvasCtx.globalAlpha = this.fadeInTimer;\n      } else {\n        this.canvasCtx.globalAlpha = 1;\n      }\n\n      if (this.tRex.jumping) {\n        this.tRex.updateJump(deltaTime);\n      }\n\n      this.runningTime += deltaTime;\n      const hasObstacles = this.runningTime > this.config.CLEAR_TIME;\n\n      \/\/ First jump triggers the intro.\n      if (this.tRex.jumpCount === 1 && !this.playingIntro) {\n        this.playIntro();\n      }\n\n      \/\/ The horizon doesn't move until the intro is over.\n      if (this.playingIntro) {\n        this.horizon.update(0, this.currentSpeed, hasObstacles);\n      } else if (!this.crashed) {\n        const showNightMode = this.isDarkMode ^ this.inverted;\n        deltaTime = !this.activated ? 0 : deltaTime;\n        this.horizon.update(\n            deltaTime, this.currentSpeed, hasObstacles, showNightMode);\n      }\n\n      \/\/ Check for collisions.\n      let collision = hasObstacles &&\n          checkForCollision(this.horizon.obstacles[0], this.tRex);\n\n      \/\/ For a11y, audio cues.\n      if (Runner.audioCues && hasObstacles) {\n        const jumpObstacle =\n            this.horizon.obstacles[0].typeConfig.type != 'COLLECTABLE';\n\n        if (!this.horizon.obstacles[0].jumpAlerted) {\n          const threshold = Runner.isMobileMouseInput ?\n              Runner.config.AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y :\n              Runner.config.AUDIOCUE_PROXIMITY_THRESHOLD;\n          const adjProximityThreshold = threshold +\n              (threshold * Math.log10(this.currentSpeed \/ Runner.config.SPEED));\n\n          if (this.horizon.obstacles[0].xPos < adjProximityThreshold) {\n            if (jumpObstacle) {\n              this.generatedSoundFx.jump();\n            }\n            this.horizon.obstacles[0].jumpAlerted = true;\n          }\n        }\n      }\n\n      \/\/ Activated alt game mode.\n      if (Runner.isAltGameModeEnabled() &#038;&#038; collision &#038;&#038;\n          this.horizon.obstacles[0].typeConfig.type == 'COLLECTABLE') {\n        this.horizon.removeFirstObstacle();\n        this.tRex.setFlashing(true);\n        collision = false;\n        this.altGameModeFlashTimer = this.config.FLASH_DURATION;\n        this.runningTime = 0;\n        this.generatedSoundFx.collect();\n      }\n\n      if (!collision) {\n        this.distanceRan += this.currentSpeed * deltaTime \/ this.msPerFrame;\n\n        if (this.currentSpeed < this.config.MAX_SPEED) {\n          this.currentSpeed += this.config.ACCELERATION;\n        }\n      } else {\n        this.gameOver();\n      }\n\n      const playAchievementSound = this.distanceMeter.update(deltaTime,\n          Math.ceil(this.distanceRan));\n\n      if (!Runner.audioCues &#038;&#038; playAchievementSound) {\n        this.playSound(this.soundFx.SCORE);\n      }\n\n      \/\/ Night mode.\n      if (!Runner.isAltGameModeEnabled()) {\n        if (this.invertTimer > this.config.INVERT_FADE_DURATION) {\n          this.invertTimer = 0;\n          this.invertTrigger = false;\n          this.invert(false);\n        } else if (this.invertTimer) {\n          this.invertTimer += deltaTime;\n        } else {\n          const actualDistance =\n              this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan));\n\n          if (actualDistance > 0) {\n            this.invertTrigger =\n                !(actualDistance % this.config.INVERT_DISTANCE);\n\n            if (this.invertTrigger && this.invertTimer === 0) {\n              this.invertTimer += deltaTime;\n              this.invert(false);\n            }\n          }\n        }\n      }\n    }\n\n    if (this.playing || (!this.activated &&\n        this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) {\n      this.tRex.update(deltaTime);\n      this.scheduleNextUpdate();\n    }\n  },\n\n  \/**\n   * Event handler.\n   * @param {Event} e\n   *\/\n  handleEvent(e) {\n    return (function(evtType, events) {\n      switch (evtType) {\n        case events.KEYDOWN:\n        case events.TOUCHSTART:\n        case events.POINTERDOWN:\n          this.onKeyDown(e);\n          break;\n        case events.KEYUP:\n        case events.TOUCHEND:\n        case events.POINTERUP:\n          this.onKeyUp(e);\n          break;\n        case events.GAMEPADCONNECTED:\n          this.onGamepadConnected(e);\n          break;\n      }\n    }.bind(this))(e.type, Runner.events);\n  },\n\n  \/**\n   * Initialize audio cues if activated by focus on the canvas element.\n   * @param {Event} e\n   *\/\n  handleCanvasKeyPress(e) {\n    if (!this.activated &#038;&#038; !Runner.audioCues) {\n      this.toggleSpeed();\n      Runner.audioCues = true;\n      this.generatedSoundFx.init();\n      Runner.generatedSoundFx = this.generatedSoundFx;\n      Runner.config.CLEAR_TIME *= 1.2;\n    } else if (e.keyCode &#038;&#038; Runner.keycodes.JUMP[e.keyCode]) {\n      this.onKeyDown(e);\n    }\n  },\n\n  \/**\n   * Prevent space key press from scrolling.\n   * @param {Event} e\n   *\/\n  preventScrolling(e) {\n    if (e.keyCode === 32) {\n      e.preventDefault();\n    }\n  },\n\n  \/**\n   * Toggle speed setting if toggle is shown.\n   *\/\n  toggleSpeed() {\n    if (Runner.audioCues) {\n      const speedChange = Runner.slowDown != this.slowSpeedCheckbox.checked;\n\n      if (speedChange) {\n        Runner.slowDown = this.slowSpeedCheckbox.checked;\n        const updatedConfig =\n            Runner.slowDown ? Runner.slowConfig : Runner.normalConfig;\n\n        Runner.config = Object.assign(Runner.config, updatedConfig);\n        this.currentSpeed = updatedConfig.SPEED;\n        this.tRex.enableSlowConfig();\n        this.horizon.adjustObstacleSpeed();\n      }\n      if (this.playing) {\n        this.disableSpeedToggle(true);\n      }\n    }\n  },\n\n  \/**\n   * Show the speed toggle.\n   * From focus event or when audio cues are activated.\n   * @param {Event=} e\n   *\/\n  showSpeedToggle(e) {\n    const isFocusEvent = e &#038;&#038; e.type == 'focus';\n    if (Runner.audioCues || isFocusEvent) {\n      this.slowSpeedCheckboxLabel.classList.toggle(\n          HIDDEN_CLASS, isFocusEvent ? false : !this.crashed);\n    }\n  },\n\n  \/**\n   * Disable the speed toggle.\n   * @param {boolean} disable\n   *\/\n  disableSpeedToggle(disable) {\n    if (disable) {\n      this.slowSpeedCheckbox.setAttribute('disabled', 'disabled');\n    } else {\n      this.slowSpeedCheckbox.removeAttribute('disabled');\n    }\n  },\n\n  \/**\n   * Bind relevant key \/ mouse \/ touch listeners.\n   *\/\n  startListening() {\n    \/\/ A11y keyboard \/ screen reader activation.\n    this.containerEl.addEventListener(\n        Runner.events.KEYDOWN, this.handleCanvasKeyPress.bind(this));\n    if (!IS_MOBILE) {\n      this.containerEl.addEventListener(\n          Runner.events.FOCUS, this.showSpeedToggle.bind(this));\n    }\n    this.canvas.addEventListener(\n        Runner.events.KEYDOWN, this.preventScrolling.bind(this));\n    this.canvas.addEventListener(\n        Runner.events.KEYUP, this.preventScrolling.bind(this));\n\n    \/\/ Keys.\n    document.addEventListener(Runner.events.KEYDOWN, this);\n    document.addEventListener(Runner.events.KEYUP, this);\n\n    \/\/ Touch \/ pointer.\n    this.containerEl.addEventListener(Runner.events.TOUCHSTART, this);\n    document.addEventListener(Runner.events.POINTERDOWN, this);\n    document.addEventListener(Runner.events.POINTERUP, this);\n\n    if (this.isArcadeMode()) {\n      \/\/ Gamepad\n      window.addEventListener(Runner.events.GAMEPADCONNECTED, this);\n    }\n  },\n\n  \/**\n   * Remove all listeners.\n   *\/\n  stopListening() {\n    document.removeEventListener(Runner.events.KEYDOWN, this);\n    document.removeEventListener(Runner.events.KEYUP, this);\n\n    if (this.touchController) {\n      this.touchController.removeEventListener(Runner.events.TOUCHSTART, this);\n      this.touchController.removeEventListener(Runner.events.TOUCHEND, this);\n    }\n\n    this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this);\n    document.removeEventListener(Runner.events.POINTERDOWN, this);\n    document.removeEventListener(Runner.events.POINTERUP, this);\n\n    if (this.isArcadeMode()) {\n      window.removeEventListener(Runner.events.GAMEPADCONNECTED, this);\n    }\n  },\n\n  \/**\n   * Process keydown.\n   * @param {Event} e\n   *\/\n  onKeyDown(e) {\n    \/\/ Prevent native page scrolling whilst tapping on mobile.\n    if (IS_MOBILE &#038;&#038; this.playing) {\n      e.preventDefault();\n    }\n\n    if (this.isCanvasInView()) {\n      \/\/ Allow toggling of speed toggle.\n      if (Runner.keycodes.JUMP[e.keyCode] &#038;&#038;\n          e.target == this.slowSpeedCheckbox) {\n        return;\n      }\n\n      if (!this.crashed &#038;&#038; !this.paused) {\n        \/\/ For a11y, screen reader activation.\n        const isMobileMouseInput = IS_MOBILE &#038;&#038;\n                e.type === Runner.events.POINTERDOWN &#038;&#038;\n                e.pointerType == 'mouse' &#038;&#038; e.target == this.containerEl ||\n            (IS_IOS &#038;&#038; e.pointerType == 'touch' &#038;&#038;\n             document.activeElement == this.containerEl);\n\n        if (Runner.keycodes.JUMP[e.keyCode] ||\n            e.type === Runner.events.TOUCHSTART || isMobileMouseInput ||\n            (Runner.keycodes.DUCK[e.keyCode] &#038;&#038; this.altGameModeActive)) {\n          e.preventDefault();\n          \/\/ Starting the game for the first time.\n          if (!this.playing) {\n            \/\/ Started by touch so create a touch controller.\n            if (!this.touchController &#038;&#038; e.type === Runner.events.TOUCHSTART) {\n              this.createTouchController();\n            }\n\n            if (isMobileMouseInput) {\n              this.handleCanvasKeyPress(e);\n            }\n            this.loadSounds();\n            this.setPlayStatus(true);\n            this.update();\n            if (window.errorPageController) {\n              errorPageController.trackEasterEgg();\n            }\n          }\n          \/\/ Start jump.\n          if (!this.tRex.jumping &#038;&#038; !this.tRex.ducking) {\n            if (Runner.audioCues) {\n              this.generatedSoundFx.cancelFootSteps();\n            } else {\n              this.playSound(this.soundFx.BUTTON_PRESS);\n            }\n            this.tRex.startJump(this.currentSpeed);\n          }\n          \/\/ Ducking is disabled on alt game modes.\n        } else if (\n            !this.altGameModeActive &#038;&#038; this.playing &#038;&#038;\n            Runner.keycodes.DUCK[e.keyCode]) {\n          e.preventDefault();\n          if (this.tRex.jumping) {\n            \/\/ Speed drop, activated only when jump key is not pressed.\n            this.tRex.setSpeedDrop();\n          } else if (!this.tRex.jumping &#038;&#038; !this.tRex.ducking) {\n            \/\/ Duck.\n            this.tRex.setDuck(true);\n          }\n        }\n      }\n    }\n  },\n\n  \/**\n   * Process key up.\n   * @param {Event} e\n   *\/\n  onKeyUp(e) {\n    const keyCode = String(e.keyCode);\n    const isjumpKey = Runner.keycodes.JUMP[keyCode] ||\n        e.type === Runner.events.TOUCHEND || e.type === Runner.events.POINTERUP;\n\n    if (this.isRunning() &#038;&#038; isjumpKey) {\n      this.tRex.endJump();\n    } else if (Runner.keycodes.DUCK[keyCode]) {\n      this.tRex.speedDrop = false;\n      this.tRex.setDuck(false);\n    } else if (this.crashed) {\n      \/\/ Check that enough time has elapsed before allowing jump key to restart.\n      const deltaTime = getTimeStamp() - this.time;\n\n      if (this.isCanvasInView() &#038;&#038;\n          (Runner.keycodes.RESTART[keyCode] || this.isLeftClickOnCanvas(e) ||\n          (deltaTime >= this.config.GAMEOVER_CLEAR_TIME &&\n          Runner.keycodes.JUMP[keyCode]))) {\n        this.handleGameOverClicks(e);\n      }\n    } else if (this.paused && isjumpKey) {\n      \/\/ Reset the jump state\n      this.tRex.reset();\n      this.play();\n    }\n  },\n\n  \/**\n   * Process gamepad connected event.\n   * @param {Event} e\n   *\/\n  onGamepadConnected(e) {\n    if (!this.pollingGamepads) {\n      this.pollGamepadState();\n    }\n  },\n\n  \/**\n   * rAF loop for gamepad polling.\n   *\/\n  pollGamepadState() {\n    const gamepads = navigator.getGamepads();\n    this.pollActiveGamepad(gamepads);\n\n    this.pollingGamepads = true;\n    requestAnimationFrame(this.pollGamepadState.bind(this));\n  },\n\n  \/**\n   * Polls for a gamepad with the jump button pressed. If one is found this\n   * becomes the \"active\" gamepad and all others are ignored.\n   * @param {!Array<Gamepad>} gamepads\n   *\/\n  pollForActiveGamepad(gamepads) {\n    for (let i = 0; i < gamepads.length; ++i) {\n      if (gamepads[i] &#038;&#038; gamepads[i].buttons.length > 0 &&\n          gamepads[i].buttons[0].pressed) {\n        this.gamepadIndex = i;\n        this.pollActiveGamepad(gamepads);\n        return;\n      }\n    }\n  },\n\n  \/**\n   * Polls the chosen gamepad for button presses and generates KeyboardEvents\n   * to integrate with the rest of the game logic.\n   * @param {!Array<Gamepad>} gamepads\n   *\/\n  pollActiveGamepad(gamepads) {\n    if (this.gamepadIndex === undefined) {\n      this.pollForActiveGamepad(gamepads);\n      return;\n    }\n\n    const gamepad = gamepads[this.gamepadIndex];\n    if (!gamepad) {\n      this.gamepadIndex = undefined;\n      this.pollForActiveGamepad(gamepads);\n      return;\n    }\n\n    \/\/ The gamepad specification defines the typical mapping of physical buttons\n    \/\/ to button indicies: https:\/\/w3c.github.io\/gamepad\/#remapping\n    this.pollGamepadButton(gamepad, 0, 38);  \/\/ Jump\n    if (gamepad.buttons.length >= 2) {\n      this.pollGamepadButton(gamepad, 1, 40);  \/\/ Duck\n    }\n    if (gamepad.buttons.length >= 10) {\n      this.pollGamepadButton(gamepad, 9, 13);  \/\/ Restart\n    }\n\n    this.previousGamepad = gamepad;\n  },\n\n  \/**\n   * Generates a key event based on a gamepad button.\n   * @param {!Gamepad} gamepad\n   * @param {number} buttonIndex\n   * @param {number} keyCode\n   *\/\n  pollGamepadButton(gamepad, buttonIndex, keyCode) {\n    const state = gamepad.buttons[buttonIndex].pressed;\n    let previousState = false;\n    if (this.previousGamepad) {\n      previousState = this.previousGamepad.buttons[buttonIndex].pressed;\n    }\n    \/\/ Generate key events on the rising and falling edge of a button press.\n    if (state !== previousState) {\n      const e = new KeyboardEvent(state ? Runner.events.KEYDOWN\n                                      : Runner.events.KEYUP,\n                                { keyCode: keyCode });\n      document.dispatchEvent(e);\n    }\n  },\n\n  \/**\n   * Handle interactions on the game over screen state.\n   * A user is able to tap the high score twice to reset it.\n   * @param {Event} e\n   *\/\n  handleGameOverClicks(e) {\n    if (e.target != this.slowSpeedCheckbox) {\n      e.preventDefault();\n      if (this.distanceMeter.hasClickedOnHighScore(e) && this.highestScore) {\n        if (this.distanceMeter.isHighScoreFlashing()) {\n          \/\/ Subsequent click, reset the high score.\n          this.saveHighScore(0, true);\n          this.distanceMeter.resetHighScore();\n        } else {\n          \/\/ First click, flash the high score.\n          this.distanceMeter.startHighScoreFlashing();\n        }\n      } else {\n        this.distanceMeter.cancelHighScoreFlashing();\n        this.restart();\n      }\n    }\n  },\n\n  \/**\n   * Returns whether the event was a left click on canvas.\n   * On Windows right click is registered as a click.\n   * @param {Event} e\n   * @return {boolean}\n   *\/\n  isLeftClickOnCanvas(e) {\n    return e.button != null && e.button < 2 &#038;&#038;\n        e.type === Runner.events.POINTERUP &#038;&#038;\n        (e.target === this.canvas ||\n         (IS_MOBILE &#038;&#038; Runner.audioCues &#038;&#038; e.target === this.containerEl));\n  },\n\n  \/**\n   * RequestAnimationFrame wrapper.\n   *\/\n  scheduleNextUpdate() {\n    if (!this.updatePending) {\n      this.updatePending = true;\n      this.raqId = requestAnimationFrame(this.update.bind(this));\n    }\n  },\n\n  \/**\n   * Whether the game is running.\n   * @return {boolean}\n   *\/\n  isRunning() {\n    return !!this.raqId;\n  },\n\n  \/**\n   * Set the initial high score as stored in the user's profile.\n   * @param {number} highScore\n   *\/\n  initializeHighScore(highScore) {\n    this.syncHighestScore = true;\n    highScore = Math.ceil(highScore);\n    if (highScore < this.highestScore) {\n      if (window.errorPageController) {\n        errorPageController.updateEasterEggHighScore(this.highestScore);\n      }\n      return;\n    }\n    this.highestScore = highScore;\n    this.distanceMeter.setHighScore(this.highestScore);\n  },\n\n  \/**\n   * Sets the current high score and saves to the profile if available.\n   * @param {number} distanceRan Total distance ran.\n   * @param {boolean=} opt_resetScore Whether to reset the score.\n   *\/\n  saveHighScore(distanceRan, opt_resetScore) {\n    this.highestScore = Math.ceil(distanceRan);\n    this.distanceMeter.setHighScore(this.highestScore);\n\n    \/\/ Store the new high score in the profile.\n    if (this.syncHighestScore &#038;&#038; window.errorPageController) {\n      if (opt_resetScore) {\n        errorPageController.resetEasterEggHighScore();\n      } else {\n        errorPageController.updateEasterEggHighScore(this.highestScore);\n      }\n    }\n  },\n\n  \/**\n   * Game over state.\n   *\/\n  gameOver() {\n    this.playSound(this.soundFx.HIT);\n    vibrate(200);\n\n    this.stop();\n    this.crashed = true;\n    this.distanceMeter.achievement = false;\n\n    this.tRex.update(100, Trex.status.CRASHED);\n\n    \/\/ Game over panel.\n    if (!this.gameOverPanel) {\n      const origSpriteDef = IS_HIDPI ?\n          Runner.spriteDefinitionByType.original.HDPI :\n          Runner.spriteDefinitionByType.original.LDPI;\n\n      if (this.canvas) {\n        if (Runner.isAltGameModeEnabled) {\n          this.gameOverPanel = new GameOverPanel(\n              this.canvas, origSpriteDef.TEXT_SPRITE, origSpriteDef.RESTART,\n              this.dimensions, origSpriteDef.ALT_GAME_END,\n              this.altGameModeActive);\n        } else {\n          this.gameOverPanel = new GameOverPanel(\n              this.canvas, origSpriteDef.TEXT_SPRITE, origSpriteDef.RESTART,\n              this.dimensions);\n        }\n      }\n    }\n\n    this.gameOverPanel.draw(this.altGameModeActive, this.tRex);\n\n    \/\/ Update the high score.\n    if (this.distanceRan > this.highestScore) {\n      this.saveHighScore(this.distanceRan);\n    }\n\n    \/\/ Reset the time clock.\n    this.time = getTimeStamp();\n\n    if (Runner.audioCues) {\n      this.generatedSoundFx.stopAll();\n      announcePhrase(\n          getA11yString(A11Y_STRINGS.gameOver)\n              .replace(\n                  '$1',\n                  this.distanceMeter.getActualDistance(this.distanceRan)\n                      .toString()) +\n          ' ' +\n          getA11yString(A11Y_STRINGS.highScore)\n              .replace(\n                  '$1',\n\n                  this.distanceMeter.getActualDistance(this.highestScore)\n                      .toString()));\n      this.containerEl.setAttribute(\n          'title', getA11yString(A11Y_STRINGS.ariaLabel));\n    }\n    this.showSpeedToggle();\n    this.disableSpeedToggle(false);\n  },\n\n  stop() {\n    this.setPlayStatus(false);\n    this.paused = true;\n    cancelAnimationFrame(this.raqId);\n    this.raqId = 0;\n    this.generatedSoundFx.stopAll();\n  },\n\n  play() {\n    if (!this.crashed) {\n      this.setPlayStatus(true);\n      this.paused = false;\n      this.tRex.update(0, Trex.status.RUNNING);\n      this.time = getTimeStamp();\n      this.update();\n      this.generatedSoundFx.background();\n    }\n  },\n\n  restart() {\n    if (!this.raqId) {\n      this.playCount++;\n      this.runningTime = 0;\n      this.setPlayStatus(true);\n      this.toggleSpeed();\n      this.paused = false;\n      this.crashed = false;\n      this.distanceRan = 0;\n      this.setSpeed(this.config.SPEED);\n      this.time = getTimeStamp();\n      this.containerEl.classList.remove(Runner.classes.CRASHED);\n      this.clearCanvas();\n      this.distanceMeter.reset();\n      this.horizon.reset();\n      this.tRex.reset();\n      this.playSound(this.soundFx.BUTTON_PRESS);\n      this.invert(true);\n      this.flashTimer = null;\n      this.update();\n      this.gameOverPanel.reset();\n      this.generatedSoundFx.background();\n      this.containerEl.setAttribute('title', getA11yString(A11Y_STRINGS.jump));\n      announcePhrase(getA11yString(A11Y_STRINGS.started));\n    }\n  },\n\n  setPlayStatus(isPlaying) {\n    if (this.touchController) {\n      this.touchController.classList.toggle(HIDDEN_CLASS, !isPlaying);\n    }\n    this.playing = isPlaying;\n  },\n\n  \/**\n   * Whether the game should go into arcade mode.\n   * @return {boolean}\n   *\/\n  isArcadeMode() {\n    \/\/ In RTL languages the title is wrapped with the left to right mark\n    \/\/ control characters &#x202A; and &#x202C but are invisible.\n    return IS_RTL ? document.title.indexOf(ARCADE_MODE_URL) == 1 :\n                    document.title === ARCADE_MODE_URL;\n  },\n\n  \/**\n   * Hides offline messaging for a fullscreen game only experience.\n   *\/\n  setArcadeMode() {\n    document.body.classList.add(Runner.classes.ARCADE_MODE);\n    this.setArcadeModeContainerScale();\n  },\n\n  \/**\n   * Sets the scaling for arcade mode.\n   *\/\n  setArcadeModeContainerScale() {\n    const windowHeight = window.innerHeight;\n    const scaleHeight = windowHeight \/ this.dimensions.HEIGHT;\n    const scaleWidth = window.innerWidth \/ this.dimensions.WIDTH;\n    const scale = Math.max(1, Math.min(scaleHeight, scaleWidth));\n    const scaledCanvasHeight = this.dimensions.HEIGHT * scale;\n    \/\/ Positions the game container at 10% of the available vertical window\n    \/\/ height minus the game container height.\n    const translateY = Math.ceil(Math.max(0, (windowHeight - scaledCanvasHeight -\n        Runner.config.ARCADE_MODE_INITIAL_TOP_POSITION) *\n        Runner.config.ARCADE_MODE_TOP_POSITION_PERCENT)) *\n        window.devicePixelRatio;\n\n    const cssScale = IS_RTL ? -scale + ',' + scale : scale;\n    this.containerEl.style.transform =\n        'scale(' + cssScale + ') translateY(' + translateY + 'px)';\n  },\n\n  \/**\n   * Pause the game if the tab is not in focus.\n   *\/\n  onVisibilityChange(e) {\n    if (document.hidden || document.webkitHidden || e.type === 'blur' ||\n        document.visibilityState !== 'visible') {\n      this.stop();\n    } else if (!this.crashed) {\n      this.tRex.reset();\n      this.play();\n    }\n  },\n\n  \/**\n   * Play a sound.\n   * @param {AudioBuffer} soundBuffer\n   *\/\n  playSound(soundBuffer) {\n    if (soundBuffer) {\n      const sourceNode = this.audioContext.createBufferSource();\n      sourceNode.buffer = soundBuffer;\n      sourceNode.connect(this.audioContext.destination);\n      sourceNode.start(0);\n    }\n  },\n\n  \/**\n   * Inverts the current page \/ canvas colors.\n   * @param {boolean} reset Whether to reset colors.\n   *\/\n  invert(reset) {\n    const htmlEl = document.firstElementChild;\n\n    if (reset) {\n      htmlEl.classList.toggle(Runner.classes.INVERTED,\n          false);\n      this.invertTimer = 0;\n      this.inverted = false;\n    } else {\n      this.inverted = htmlEl.classList.toggle(\n          Runner.classes.INVERTED, this.invertTrigger);\n    }\n  },\n};\n\n\n\/**\n * Updates the canvas size taking into\n * account the backing store pixel ratio and\n * the device pixel ratio.\n *\n * See article by Paul Lewis:\n * http:\/\/www.html5rocks.com\/en\/tutorials\/canvas\/hidpi\/\n *\n * @param {HTMLCanvasElement} canvas\n * @param {number=} opt_width\n * @param {number=} opt_height\n * @return {boolean} Whether the canvas was scaled.\n *\/\nRunner.updateCanvasScaling = function(canvas, opt_width, opt_height) {\n  const context =\n      \/** @type {CanvasRenderingContext2D} *\/ (canvas.getContext('2d'));\n\n  \/\/ Query the various pixel ratios\n  const devicePixelRatio = Math.floor(window.devicePixelRatio) || 1;\n  \/** @suppress {missingProperties} *\/\n  const backingStoreRatio =\n      Math.floor(context.webkitBackingStorePixelRatio) || 1;\n  const ratio = devicePixelRatio \/ backingStoreRatio;\n\n  \/\/ Upscale the canvas if the two ratios don't match\n  if (devicePixelRatio !== backingStoreRatio) {\n    const oldWidth = opt_width || canvas.width;\n    const oldHeight = opt_height || canvas.height;\n\n    canvas.width = oldWidth * ratio;\n    canvas.height = oldHeight * ratio;\n\n    canvas.style.width = oldWidth + 'px';\n    canvas.style.height = oldHeight + 'px';\n\n    \/\/ Scale the context to counter the fact that we've manually scaled\n    \/\/ our canvas element.\n    context.scale(ratio, ratio);\n    return true;\n  } else if (devicePixelRatio === 1) {\n    \/\/ Reset the canvas width \/ height. Fixes scaling bug when the page is\n    \/\/ zoomed and the devicePixelRatio changes accordingly.\n    canvas.style.width = canvas.width + 'px';\n    canvas.style.height = canvas.height + 'px';\n  }\n  return false;\n};\n\n\n\/**\n * Whether events are enabled.\n * @return {boolean}\n *\/\nRunner.isAltGameModeEnabled = function() {\n  return loadTimeData && loadTimeData.valueExists('enableAltGameMode');\n};\n\n\n\/**\n * Generated sound FX class for audio cues.\n * @constructor\n *\/\nfunction GeneratedSoundFx() {\n  this.audioCues = false;\n  this.context = null;\n  this.panner = null;\n}\n\nGeneratedSoundFx.prototype = {\n  init() {\n    this.audioCues = true;\n    if (!this.context) {\n      \/\/ iOS only supports the webkit version.\n      this.context = window.webkitAudioContext ? new webkitAudioContext() :\n                                                 new AudioContext();\n      if (IS_IOS) {\n        this.context.onstatechange = (function() {\n                                       if (this.context.state != 'running') {\n                                         this.context.resume();\n                                       }\n                                     }).bind(this);\n        this.context.resume();\n      }\n      this.panner = this.context.createStereoPanner ?\n          this.context.createStereoPanner() :\n          null;\n    }\n  },\n\n  stopAll() {\n    this.cancelFootSteps();\n  },\n\n  \/**\n   * Play oscillators at certain frequency and for a certain time.\n   * @param {number} frequency\n   * @param {number} startTime\n   * @param {number} duration\n   * @param {?number=} opt_vol\n   * @param {number=} opt_pan\n   *\/\n  playNote(frequency, startTime, duration, opt_vol, opt_pan) {\n    const osc1 = this.context.createOscillator();\n    const osc2 = this.context.createOscillator();\n    const volume = this.context.createGain();\n\n    \/\/ Set oscillator wave type\n    osc1.type = 'triangle';\n    osc2.type = 'triangle';\n    volume.gain.value = 0.1;\n\n    \/\/ Set up node routing\n    if (this.panner) {\n      this.panner.pan.value = opt_pan || 0;\n      osc1.connect(volume).connect(this.panner);\n      osc2.connect(volume).connect(this.panner);\n      this.panner.connect(this.context.destination);\n    } else {\n      osc1.connect(volume);\n      osc2.connect(volume);\n      volume.connect(this.context.destination);\n    }\n\n    \/\/ Detune oscillators for chorus effect\n    osc1.frequency.value = frequency + 1;\n    osc2.frequency.value = frequency - 2;\n\n    \/\/ Fade out\n    volume.gain.setValueAtTime(opt_vol || 0.01, startTime + duration - 0.05);\n    volume.gain.linearRampToValueAtTime(0.00001, startTime + duration);\n\n    \/\/ Start oscillators\n    osc1.start(startTime);\n    osc2.start(startTime);\n    \/\/ Stop oscillators\n    osc1.stop(startTime + duration);\n    osc2.stop(startTime + duration);\n  },\n\n  background() {\n    if (this.audioCues) {\n      const now = this.context.currentTime;\n      this.playNote(493.883, now, 0.116);\n      this.playNote(659.255, now + 0.116, 0.232);\n      this.loopFootSteps();\n    }\n  },\n\n  loopFootSteps() {\n    if (this.audioCues && !this.bgSoundIntervalId) {\n      this.bgSoundIntervalId = setInterval(function() {\n        this.playNote(73.42, this.context.currentTime, 0.05, 0.16);\n        this.playNote(69.30, this.context.currentTime + 0.116, 0.116, 0.16);\n      }.bind(this), 280);\n    }\n  },\n\n  cancelFootSteps() {\n    if (this.audioCues && this.bgSoundIntervalId) {\n      clearInterval(this.bgSoundIntervalId);\n      this.bgSoundIntervalId = null;\n      this.playNote(103.83, this.context.currentTime, 0.232, 0.02);\n      this.playNote(116.54, this.context.currentTime + 0.116, 0.232, 0.02);\n    }\n  },\n\n  collect() {\n    if (this.audioCues) {\n      this.cancelFootSteps();\n      const now = this.context.currentTime;\n      this.playNote(830.61, now, 0.116);\n      this.playNote(1318.51, now + 0.116, 0.232);\n    }\n  },\n\n  jump() {\n    if (this.audioCues) {\n      const now = this.context.currentTime;\n      this.playNote(659.25, now, 0.116, 0.3, -0.6);\n      this.playNote(880, now + 0.116, 0.232, 0.3, -0.6);\n    }\n  },\n};\n\n\n\/**\n * Speak a phrase using Speech Synthesis API for a11y.\n * @param {string} phrase Sentence to speak.\n *\/\nfunction speakPhrase(phrase) {\n  if ('speechSynthesis' in window) {\n    const msg = new SpeechSynthesisUtterance(phrase);\n    const voices = window.speechSynthesis.getVoices();\n    msg.text = phrase;\n    speechSynthesis.speak(msg);\n  }\n}\n\n\n\/**\n * For screen readers make an announcement to the live region.\n * @param {string} phrase Sentence to speak.\n *\/\nfunction announcePhrase(phrase) {\n  if (Runner.a11yStatusEl) {\n    Runner.a11yStatusEl.textContent = '';\n    Runner.a11yStatusEl.textContent = phrase;\n  }\n}\n\n\n\/**\n * Returns a string from loadTimeData data object.\n * @param {string} stringName\n * @return {string}\n *\/\nfunction getA11yString(stringName) {\n  return loadTimeData && loadTimeData.valueExists(stringName) ?\n      loadTimeData.getString(stringName) :\n      '';\n}\n\n\n\/**\n * Get random number.\n * @param {number} min\n * @param {number} max\n *\/\nfunction getRandomNum(min, max) {\n  return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\n\n\/**\n * Vibrate on mobile devices.\n * @param {number} duration Duration of the vibration in milliseconds.\n *\/\nfunction vibrate(duration) {\n  if (IS_MOBILE && window.navigator.vibrate) {\n    window.navigator.vibrate(duration);\n  }\n}\n\n\n\/**\n * Create canvas element.\n * @param {Element} container Element to append canvas to.\n * @param {number} width\n * @param {number} height\n * @param {string=} opt_classname\n * @return {HTMLCanvasElement}\n *\/\nfunction createCanvas(container, width, height, opt_classname) {\n  const canvas =\n      \/** @type {!HTMLCanvasElement} *\/ (document.createElement('canvas'));\n  canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' +\n      opt_classname : Runner.classes.CANVAS;\n  canvas.width = width;\n  canvas.height = height;\n  container.appendChild(canvas);\n\n  return canvas;\n}\n\n\n\/**\n * Decodes the base 64 audio to ArrayBuffer used by Web Audio.\n * @param {string} base64String\n *\/\nfunction decodeBase64ToArrayBuffer(base64String) {\n  const len = (base64String.length \/ 4) * 3;\n  const str = atob(base64String);\n  const arrayBuffer = new ArrayBuffer(len);\n  const bytes = new Uint8Array(arrayBuffer);\n\n  for (let i = 0; i < len; i++) {\n    bytes[i] = str.charCodeAt(i);\n  }\n  return bytes.buffer;\n}\n\n\n\/**\n * Return the current timestamp.\n * @return {number}\n *\/\nfunction getTimeStamp() {\n  return IS_IOS ? new Date().getTime() : performance.now();\n}\n\n\n\/\/******************************************************************************\n\n\n\/**\n * Game over panel.\n * @param {!HTMLCanvasElement} canvas\n * @param {Object} textImgPos\n * @param {Object} restartImgPos\n * @param {!Object} dimensions Canvas dimensions.\n * @param {Object=} opt_altGameEndImgPos\n * @param {boolean=} opt_altGameActive\n * @constructor\n *\/\nfunction GameOverPanel(\n    canvas, textImgPos, restartImgPos, dimensions, opt_altGameEndImgPos,\n    opt_altGameActive) {\n  this.canvas = canvas;\n  this.canvasCtx =\n      \/** @type {CanvasRenderingContext2D} *\/ (canvas.getContext('2d'));\n  this.canvasDimensions = dimensions;\n  this.textImgPos = textImgPos;\n  this.restartImgPos = restartImgPos;\n  this.altGameEndImgPos = opt_altGameEndImgPos;\n  this.altGameModeActive = opt_altGameActive;\n\n  \/\/ Retry animation.\n  this.frameTimeStamp = 0;\n  this.animTimer = 0;\n  this.currentFrame = 0;\n\n  this.gameOverRafId = null;\n\n  this.flashTimer = 0;\n  this.flashCounter = 0;\n  this.originalText = true;\n}\n\nGameOverPanel.RESTART_ANIM_DURATION = 875;\nGameOverPanel.LOGO_PAUSE_DURATION = 875;\nGameOverPanel.FLASH_ITERATIONS = 5;\n\n\/**\n * Animation frames spec.\n *\/\nGameOverPanel.animConfig = {\n  frames: [0, 36, 72, 108, 144, 180, 216, 252],\n  msPerFrame: GameOverPanel.RESTART_ANIM_DURATION \/ 8,\n};\n\n\/**\n * Dimensions used in the panel.\n * @enum {number}\n *\/\nGameOverPanel.dimensions = {\n  TEXT_X: 0,\n  TEXT_Y: 13,\n  TEXT_WIDTH: 191,\n  TEXT_HEIGHT: 11,\n  RESTART_WIDTH: 36,\n  RESTART_HEIGHT: 32,\n};\n\n\nGameOverPanel.prototype = {\n  \/**\n   * Update the panel dimensions.\n   * @param {number} width New canvas width.\n   * @param {number} opt_height Optional new canvas height.\n   *\/\n  updateDimensions(width, opt_height) {\n    this.canvasDimensions.WIDTH = width;\n    if (opt_height) {\n      this.canvasDimensions.HEIGHT = opt_height;\n    }\n    this.currentFrame = GameOverPanel.animConfig.frames.length - 1;\n  },\n\n  drawGameOverText(dimensions, opt_useAltText) {\n    const centerX = this.canvasDimensions.WIDTH \/ 2;\n    let textSourceX = dimensions.TEXT_X;\n    let textSourceY = dimensions.TEXT_Y;\n    let textSourceWidth = dimensions.TEXT_WIDTH;\n    let textSourceHeight = dimensions.TEXT_HEIGHT;\n\n    const textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH \/ 2));\n    const textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) \/ 3);\n    const textTargetWidth = dimensions.TEXT_WIDTH;\n    const textTargetHeight = dimensions.TEXT_HEIGHT;\n\n    if (IS_HIDPI) {\n      textSourceY *= 2;\n      textSourceX *= 2;\n      textSourceWidth *= 2;\n      textSourceHeight *= 2;\n    }\n\n    if (!opt_useAltText) {\n      textSourceX += this.textImgPos.x;\n      textSourceY += this.textImgPos.y;\n    }\n\n    const spriteSource =\n        opt_useAltText ? Runner.altCommonImageSprite : Runner.origImageSprite;\n\n    this.canvasCtx.save();\n\n    if (IS_RTL) {\n      this.canvasCtx.translate(this.canvasDimensions.WIDTH, 0);\n      this.canvasCtx.scale(-1, 1);\n    }\n\n    \/\/ Game over text from sprite.\n    this.canvasCtx.drawImage(\n        spriteSource, textSourceX, textSourceY, textSourceWidth,\n        textSourceHeight, textTargetX, textTargetY, textTargetWidth,\n        textTargetHeight);\n\n    this.canvasCtx.restore();\n  },\n\n  \/**\n   * Draw additional adornments for alternative game types.\n   *\/\n  drawAltGameElements(tRex) {\n    \/\/ Additional adornments.\n    if (this.altGameModeActive &#038;&#038; Runner.spriteDefinition.ALT_GAME_END_CONFIG) {\n      const altGameEndConfig = Runner.spriteDefinition.ALT_GAME_END_CONFIG;\n\n      let altGameEndSourceWidth = altGameEndConfig.WIDTH;\n      let altGameEndSourceHeight = altGameEndConfig.HEIGHT;\n      const altGameEndTargetX = tRex.xPos + altGameEndConfig.X_OFFSET;\n      const altGameEndTargetY = tRex.yPos + altGameEndConfig.Y_OFFSET;\n\n      if (IS_HIDPI) {\n        altGameEndSourceWidth *= 2;\n        altGameEndSourceHeight *= 2;\n      }\n\n      this.canvasCtx.drawImage(\n          Runner.altCommonImageSprite, this.altGameEndImgPos.x,\n          this.altGameEndImgPos.y, altGameEndSourceWidth,\n          altGameEndSourceHeight, altGameEndTargetX, altGameEndTargetY,\n          altGameEndConfig.WIDTH, altGameEndConfig.HEIGHT);\n    }\n  },\n\n  \/**\n   * Draw restart button.\n   *\/\n  drawRestartButton() {\n    const dimensions = GameOverPanel.dimensions;\n    let framePosX = GameOverPanel.animConfig.frames[this.currentFrame];\n    let restartSourceWidth = dimensions.RESTART_WIDTH;\n    let restartSourceHeight = dimensions.RESTART_HEIGHT;\n    const restartTargetX =\n        (this.canvasDimensions.WIDTH \/ 2) - (dimensions.RESTART_WIDTH \/ 2);\n    const restartTargetY = this.canvasDimensions.HEIGHT \/ 2;\n\n    if (IS_HIDPI) {\n      restartSourceWidth *= 2;\n      restartSourceHeight *= 2;\n      framePosX *= 2;\n    }\n\n    this.canvasCtx.save();\n\n    if (IS_RTL) {\n      this.canvasCtx.translate(this.canvasDimensions.WIDTH, 0);\n      this.canvasCtx.scale(-1, 1);\n    }\n\n    this.canvasCtx.drawImage(\n        Runner.origImageSprite, this.restartImgPos.x + framePosX,\n        this.restartImgPos.y, restartSourceWidth, restartSourceHeight,\n        restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,\n        dimensions.RESTART_HEIGHT);\n    this.canvasCtx.restore();\n  },\n\n\n  \/**\n   * Draw the panel.\n   * @param {boolean} opt_altGameModeActive\n   * @param {!Trex} opt_tRex\n   *\/\n  draw(opt_altGameModeActive, opt_tRex) {\n    if (opt_altGameModeActive) {\n      this.altGameModeActive = opt_altGameModeActive;\n    }\n\n    this.drawGameOverText(GameOverPanel.dimensions, false);\n    this.drawRestartButton();\n    this.drawAltGameElements(opt_tRex);\n    this.update();\n  },\n\n  \/**\n   * Update animation frames.\n   *\/\n  update() {\n    const now = getTimeStamp();\n    const deltaTime = now - (this.frameTimeStamp || now);\n\n    this.frameTimeStamp = now;\n    this.animTimer += deltaTime;\n    this.flashTimer += deltaTime;\n\n    \/\/ Restart Button\n    if (this.currentFrame == 0 &#038;&#038;\n        this.animTimer > GameOverPanel.LOGO_PAUSE_DURATION) {\n      this.animTimer = 0;\n      this.currentFrame++;\n      this.drawRestartButton();\n    } else if (\n        this.currentFrame > 0 &&\n        this.currentFrame < GameOverPanel.animConfig.frames.length) {\n      if (this.animTimer >= GameOverPanel.animConfig.msPerFrame) {\n        this.currentFrame++;\n        this.drawRestartButton();\n      }\n    } else if (\n        !this.altGameModeActive &&\n        this.currentFrame == GameOverPanel.animConfig.frames.length) {\n      this.reset();\n      return;\n    }\n\n    \/\/ Game over text\n    if (this.altGameModeActive &&\n        Runner.spriteDefinitionByType.original.ALT_GAME_OVER_TEXT_CONFIG) {\n      const altTextConfig =\n          Runner.spriteDefinitionByType.original.ALT_GAME_OVER_TEXT_CONFIG;\n\n      if (this.flashCounter < GameOverPanel.FLASH_ITERATIONS &#038;&#038;\n          this.flashTimer > altTextConfig.FLASH_DURATION) {\n        this.flashTimer = 0;\n        this.originalText = !this.originalText;\n\n        this.clearGameOverTextBounds();\n        if (this.originalText) {\n          this.drawGameOverText(GameOverPanel.dimensions, false);\n          this.flashCounter++;\n        } else {\n          this.drawGameOverText(altTextConfig, true);\n        }\n      } else if (this.flashCounter >= GameOverPanel.FLASH_ITERATIONS) {\n        this.reset();\n        return;\n      }\n    }\n\n    this.gameOverRafId = requestAnimationFrame(this.update.bind(this));\n  },\n\n  \/**\n   * Clear game over text.\n   *\/\n  clearGameOverTextBounds() {\n    this.canvasCtx.save();\n\n    this.canvasCtx.clearRect(\n        Math.round(\n            this.canvasDimensions.WIDTH \/ 2 -\n            (GameOverPanel.dimensions.TEXT_WIDTH \/ 2)),\n        Math.round((this.canvasDimensions.HEIGHT - 25) \/ 3),\n        GameOverPanel.dimensions.TEXT_WIDTH,\n        GameOverPanel.dimensions.TEXT_HEIGHT + 4);\n    this.canvasCtx.restore();\n  },\n\n  reset() {\n    if (this.gameOverRafId) {\n      cancelAnimationFrame(this.gameOverRafId);\n      this.gameOverRafId = null;\n    }\n    this.animTimer = 0;\n    this.frameTimeStamp = 0;\n    this.currentFrame = 0;\n    this.flashTimer = 0;\n    this.flashCounter = 0;\n    this.originalText = true;\n  },\n};\n\n\n\/\/******************************************************************************\n\n\/**\n * Check for a collision.\n * @param {!Obstacle} obstacle\n * @param {!Trex} tRex T-rex object.\n * @param {CanvasRenderingContext2D=} opt_canvasCtx Optional canvas context for\n *    drawing collision boxes.\n * @return {Array<CollisionBox>|undefined}\n *\/\nfunction checkForCollision(obstacle, tRex, opt_canvasCtx) {\n  const obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos;\n\n  \/\/ Adjustments are made to the bounding box as there is a 1 pixel white\n  \/\/ border around the t-rex and obstacles.\n  const tRexBox = new CollisionBox(\n      tRex.xPos + 1,\n      tRex.yPos + 1,\n      tRex.config.WIDTH - 2,\n      tRex.config.HEIGHT - 2);\n\n  const obstacleBox = new CollisionBox(\n      obstacle.xPos + 1,\n      obstacle.yPos + 1,\n      obstacle.typeConfig.width * obstacle.size - 2,\n      obstacle.typeConfig.height - 2);\n\n  \/\/ Debug outer box\n  if (opt_canvasCtx) {\n    drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);\n  }\n\n  \/\/ Simple outer bounds check.\n  if (boxCompare(tRexBox, obstacleBox)) {\n    const collisionBoxes = obstacle.collisionBoxes;\n    let tRexCollisionBoxes = [];\n\n    if (Runner.isAltGameModeEnabled()) {\n      tRexCollisionBoxes = Runner.spriteDefinition.TREX.COLLISION_BOXES;\n    } else {\n      tRexCollisionBoxes = tRex.ducking ? Trex.collisionBoxes.DUCKING :\n                                          Trex.collisionBoxes.RUNNING;\n    }\n\n    \/\/ Detailed axis aligned box check.\n    for (let t = 0; t < tRexCollisionBoxes.length; t++) {\n      for (let i = 0; i < collisionBoxes.length; i++) {\n        \/\/ Adjust the box to actual positions.\n        const adjTrexBox =\n            createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox);\n        const adjObstacleBox =\n            createAdjustedCollisionBox(collisionBoxes[i], obstacleBox);\n        const crashed = boxCompare(adjTrexBox, adjObstacleBox);\n\n        \/\/ Draw boxes for debug.\n        if (opt_canvasCtx) {\n          drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox);\n        }\n\n        if (crashed) {\n          return [adjTrexBox, adjObstacleBox];\n        }\n      }\n    }\n  }\n}\n\n\n\/**\n * Adjust the collision box.\n * @param {!CollisionBox} box The original box.\n * @param {!CollisionBox} adjustment Adjustment box.\n * @return {CollisionBox} The adjusted collision box object.\n *\/\nfunction createAdjustedCollisionBox(box, adjustment) {\n  return new CollisionBox(\n      box.x + adjustment.x,\n      box.y + adjustment.y,\n      box.width,\n      box.height);\n}\n\n\n\/**\n * Draw the collision boxes for debug.\n *\/\nfunction drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) {\n  canvasCtx.save();\n  canvasCtx.strokeStyle = '#f00';\n  canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height);\n\n  canvasCtx.strokeStyle = '#0f0';\n  canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y,\n      obstacleBox.width, obstacleBox.height);\n  canvasCtx.restore();\n}\n\n\n\/**\n * Compare two collision boxes for a collision.\n * @param {CollisionBox} tRexBox\n * @param {CollisionBox} obstacleBox\n * @return {boolean} Whether the boxes intersected.\n *\/\nfunction boxCompare(tRexBox, obstacleBox) {\n  let crashed = false;\n  const tRexBoxX = tRexBox.x;\n  const tRexBoxY = tRexBox.y;\n\n  const obstacleBoxX = obstacleBox.x;\n  const obstacleBoxY = obstacleBox.y;\n\n  \/\/ Axis-Aligned Bounding Box method.\n  if (tRexBox.x < obstacleBoxX + obstacleBox.width &#038;&#038;\n      tRexBox.x + tRexBox.width > obstacleBoxX &&\n      tRexBox.y < obstacleBox.y + obstacleBox.height &#038;&#038;\n      tRexBox.height + tRexBox.y > obstacleBox.y) {\n    crashed = true;\n  }\n\n  return crashed;\n}\n\n\n\/\/******************************************************************************\n\n\/**\n * Collision box object.\n * @param {number} x X position.\n * @param {number} y Y Position.\n * @param {number} w Width.\n * @param {number} h Height.\n * @constructor\n *\/\nfunction CollisionBox(x, y, w, h) {\n  this.x = x;\n  this.y = y;\n  this.width = w;\n  this.height = h;\n}\n\n\n\/\/******************************************************************************\n\n\/**\n * Obstacle.\n * @param {CanvasRenderingContext2D} canvasCtx\n * @param {ObstacleType} type\n * @param {Object} spriteImgPos Obstacle position in sprite.\n * @param {Object} dimensions\n * @param {number} gapCoefficient Mutipler in determining the gap.\n * @param {number} speed\n * @param {number=} opt_xOffset\n * @param {boolean=} opt_isAltGameMode\n * @constructor\n *\/\nfunction Obstacle(\n    canvasCtx, type, spriteImgPos, dimensions, gapCoefficient, speed,\n    opt_xOffset, opt_isAltGameMode) {\n  this.canvasCtx = canvasCtx;\n  this.spritePos = spriteImgPos;\n  this.typeConfig = type;\n  this.gapCoefficient = Runner.slowDown ? gapCoefficient * 2 : gapCoefficient;\n  this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH);\n  this.dimensions = dimensions;\n  this.remove = false;\n  this.xPos = dimensions.WIDTH + (opt_xOffset || 0);\n  this.yPos = 0;\n  this.width = 0;\n  this.collisionBoxes = [];\n  this.gap = 0;\n  this.speedOffset = 0;\n  this.altGameModeActive = opt_isAltGameMode;\n  this.imageSprite = this.typeConfig.type == 'COLLECTABLE' ?\n      Runner.altCommonImageSprite :\n      this.altGameModeActive ? Runner.altGameImageSprite : Runner.imageSprite;\n\n  \/\/ For animated obstacles.\n  this.currentFrame = 0;\n  this.timer = 0;\n\n  this.init(speed);\n}\n\n\/**\n * Coefficient for calculating the maximum gap.\n *\/\nObstacle.MAX_GAP_COEFFICIENT = 1.5;\n\n\/**\n * Maximum obstacle grouping count.\n *\/\nObstacle.MAX_OBSTACLE_LENGTH = 3;\n\n\nObstacle.prototype = {\n  \/**\n   * Initialise the DOM for the obstacle.\n   * @param {number} speed\n   *\/\n  init(speed) {\n    this.cloneCollisionBoxes();\n\n    \/\/ Only allow sizing if we're at the right speed.\n    if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {\n      this.size = 1;\n    }\n\n    this.width = this.typeConfig.width * this.size;\n\n    \/\/ Check if obstacle can be positioned at various heights.\n    if (Array.isArray(this.typeConfig.yPos)) {\n      const yPosConfig =\n          IS_MOBILE ? this.typeConfig.yPosMobile : this.typeConfig.yPos;\n      this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];\n    } else {\n      this.yPos = this.typeConfig.yPos;\n    }\n\n    this.draw();\n\n    \/\/ Make collision box adjustments,\n    \/\/ Central box is adjusted to the size as one box.\n    \/\/      ____        ______        ________\n    \/\/    _|   |-|    _|     |-|    _|       |-|\n    \/\/   | |<->| |   | |<--->| |   | |<----->| |\n    \/\/   | | 1 | |   | |  2  | |   | |   3   | |\n    \/\/   |_|___|_|   |_|_____|_|   |_|_______|_|\n    \/\/\n    if (this.size > 1) {\n      this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -\n          this.collisionBoxes[2].width;\n      this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;\n    }\n\n    \/\/ For obstacles that go at a different speed from the horizon.\n    if (this.typeConfig.speedOffset) {\n      this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :\n                                               -this.typeConfig.speedOffset;\n    }\n\n    this.gap = this.getGap(this.gapCoefficient, speed);\n\n    \/\/ Increase gap for audio cues enabled.\n    if (Runner.audioCues) {\n      this.gap *= 2;\n    }\n  },\n\n  \/**\n   * Draw and crop based on size.\n   *\/\n  draw() {\n    let sourceWidth = this.typeConfig.width;\n    let sourceHeight = this.typeConfig.height;\n\n    if (IS_HIDPI) {\n      sourceWidth = sourceWidth * 2;\n      sourceHeight = sourceHeight * 2;\n    }\n\n    \/\/ X position in sprite.\n    let sourceX =\n        (sourceWidth * this.size) * (0.5 * (this.size - 1)) + this.spritePos.x;\n\n    \/\/ Animation frames.\n    if (this.currentFrame > 0) {\n      sourceX += sourceWidth * this.currentFrame;\n    }\n\n    this.canvasCtx.drawImage(\n        this.imageSprite, sourceX, this.spritePos.y, sourceWidth * this.size,\n        sourceHeight, this.xPos, this.yPos, this.typeConfig.width * this.size,\n        this.typeConfig.height);\n  },\n\n  \/**\n   * Obstacle frame update.\n   * @param {number} deltaTime\n   * @param {number} speed\n   *\/\n  update(deltaTime, speed) {\n    if (!this.remove) {\n      if (this.typeConfig.speedOffset) {\n        speed += this.speedOffset;\n      }\n      this.xPos -= Math.floor((speed * FPS \/ 1000) * deltaTime);\n\n      \/\/ Update frame\n      if (this.typeConfig.numFrames) {\n        this.timer += deltaTime;\n        if (this.timer >= this.typeConfig.frameRate) {\n          this.currentFrame =\n              this.currentFrame === this.typeConfig.numFrames - 1 ?\n              0 :\n              this.currentFrame + 1;\n          this.timer = 0;\n        }\n      }\n      this.draw();\n\n      if (!this.isVisible()) {\n        this.remove = true;\n      }\n    }\n  },\n\n  \/**\n   * Calculate a random gap size.\n   * - Minimum gap gets wider as speed increses\n   * @param {number} gapCoefficient\n   * @param {number} speed\n   * @return {number} The gap size.\n   *\/\n  getGap(gapCoefficient, speed) {\n    const minGap = Math.round(\n        this.width * speed + this.typeConfig.minGap * gapCoefficient);\n    const maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);\n    return getRandomNum(minGap, maxGap);\n  },\n\n  \/**\n   * Check if obstacle is visible.\n   * @return {boolean} Whether the obstacle is in the game area.\n   *\/\n  isVisible() {\n    return this.xPos + this.width > 0;\n  },\n\n  \/**\n   * Make a copy of the collision boxes, since these will change based on\n   * obstacle type and size.\n   *\/\n  cloneCollisionBoxes() {\n    const collisionBoxes = this.typeConfig.collisionBoxes;\n\n    for (let i = collisionBoxes.length - 1; i >= 0; i--) {\n      this.collisionBoxes[i] = new CollisionBox(\n          collisionBoxes[i].x, collisionBoxes[i].y, collisionBoxes[i].width,\n          collisionBoxes[i].height);\n    }\n  },\n};\n\n\n\/\/******************************************************************************\n\/**\n * T-rex game character.\n * @param {HTMLCanvasElement} canvas\n * @param {Object} spritePos Positioning within image sprite.\n * @constructor\n *\/\nfunction Trex(canvas, spritePos) {\n  this.canvas = canvas;\n  this.canvasCtx =\n      \/** @type {CanvasRenderingContext2D} *\/ (canvas.getContext('2d'));\n  this.spritePos = spritePos;\n  this.xPos = 0;\n  this.yPos = 0;\n  this.xInitialPos = 0;\n  \/\/ Position when on the ground.\n  this.groundYPos = 0;\n  this.currentFrame = 0;\n  this.currentAnimFrames = [];\n  this.blinkDelay = 0;\n  this.blinkCount = 0;\n  this.animStartTime = 0;\n  this.timer = 0;\n  this.msPerFrame = 1000 \/ FPS;\n  this.config = Object.assign(Trex.config, Trex.normalJumpConfig);\n  \/\/ Current status.\n  this.status = Trex.status.WAITING;\n  this.jumping = false;\n  this.ducking = false;\n  this.jumpVelocity = 0;\n  this.reachedMinHeight = false;\n  this.speedDrop = false;\n  this.jumpCount = 0;\n  this.jumpspotX = 0;\n  this.altGameModeEnabled = false;\n  this.flashing = false;\n\n  this.init();\n}\n\n\n\/**\n * T-rex player config.\n *\/\nTrex.config = {\n  DROP_VELOCITY: -5,\n  FLASH_OFF: 175,\n  FLASH_ON: 100,\n  HEIGHT: 47,\n  HEIGHT_DUCK: 25,\n  INTRO_DURATION: 1500,\n  SPEED_DROP_COEFFICIENT: 3,\n  SPRITE_WIDTH: 262,\n  START_X_POS: 50,\n  WIDTH: 44,\n  WIDTH_DUCK: 59,\n};\n\nTrex.slowJumpConfig = {\n  GRAVITY: 0.25,\n  MAX_JUMP_HEIGHT: 50,\n  MIN_JUMP_HEIGHT: 45,\n  INITIAL_JUMP_VELOCITY: -20,\n};\n\nTrex.normalJumpConfig = {\n  GRAVITY: 0.6,\n  MAX_JUMP_HEIGHT: 30,\n  MIN_JUMP_HEIGHT: 30,\n  INITIAL_JUMP_VELOCITY: -10,\n};\n\n\/**\n * Used in collision detection.\n * @enum {Array<CollisionBox>}\n *\/\nTrex.collisionBoxes = {\n  DUCKING: [new CollisionBox(1, 18, 55, 25)],\n  RUNNING: [\n    new CollisionBox(22, 0, 17, 16),\n    new CollisionBox(1, 18, 30, 9),\n    new CollisionBox(10, 35, 14, 8),\n    new CollisionBox(1, 24, 29, 5),\n    new CollisionBox(5, 30, 21, 4),\n    new CollisionBox(9, 34, 15, 4),\n  ],\n};\n\n\n\/**\n * Animation states.\n * @enum {string}\n *\/\nTrex.status = {\n  CRASHED: 'CRASHED',\n  DUCKING: 'DUCKING',\n  JUMPING: 'JUMPING',\n  RUNNING: 'RUNNING',\n  WAITING: 'WAITING',\n};\n\n\/**\n * Blinking coefficient.\n * @const\n *\/\nTrex.BLINK_TIMING = 7000;\n\n\n\/**\n * Animation config for different states.\n * @enum {Object}\n *\/\nTrex.animFrames = {\n  WAITING: {\n    frames: [44, 0],\n    msPerFrame: 1000 \/ 3,\n  },\n  RUNNING: {\n    frames: [88, 132],\n    msPerFrame: 1000 \/ 12,\n  },\n  CRASHED: {\n    frames: [220],\n    msPerFrame: 1000 \/ 60,\n  },\n  JUMPING: {\n    frames: [0],\n    msPerFrame: 1000 \/ 60,\n  },\n  DUCKING: {\n    frames: [264, 323],\n    msPerFrame: 1000 \/ 8,\n  },\n};\n\n\nTrex.prototype = {\n  \/**\n   * T-rex player initaliser.\n   * Sets the t-rex to blink at random intervals.\n   *\/\n  init() {\n    this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -\n        Runner.config.BOTTOM_PAD;\n    this.yPos = this.groundYPos;\n    this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;\n\n    this.draw(0, 0);\n    this.update(0, Trex.status.WAITING);\n  },\n\n  \/**\n   * Assign the appropriate jump parameters based on the game speed.\n   *\/\n  enableSlowConfig: function() {\n    const jumpConfig =\n        Runner.slowDown ? Trex.slowJumpConfig : Trex.normalJumpConfig;\n    Trex.config = Object.assign(Trex.config, jumpConfig);\n\n    this.adjustAltGameConfigForSlowSpeed();\n  },\n\n  \/**\n   * Enables the alternative game. Redefines the dino config.\n   * @param {Object} spritePos New positioning within image sprite.\n   *\/\n  enableAltGameMode: function(spritePos) {\n    this.altGameModeEnabled = true;\n    this.spritePos = spritePos;\n    const spriteDefinition = Runner.spriteDefinition['TREX'];\n\n    \/\/ Update animation frames.\n    Trex.animFrames.RUNNING.frames =\n        [spriteDefinition.RUNNING_1.x, spriteDefinition.RUNNING_2.x];\n    Trex.animFrames.CRASHED.frames = [spriteDefinition.CRASHED.x];\n\n    if (typeof spriteDefinition.JUMPING.x == 'object') {\n      Trex.animFrames.JUMPING.frames = spriteDefinition.JUMPING.x;\n    } else {\n      Trex.animFrames.JUMPING.frames = [spriteDefinition.JUMPING.x];\n    }\n\n    Trex.animFrames.DUCKING.frames =\n        [spriteDefinition.RUNNING_1.x, spriteDefinition.RUNNING_2.x];\n\n    \/\/ Update Trex config\n    Trex.config.GRAVITY = spriteDefinition.GRAVITY || Trex.config.GRAVITY;\n    Trex.config.HEIGHT = spriteDefinition.RUNNING_1.h,\n    Trex.config.INITIAL_JUMP_VELOCITY = spriteDefinition.INITIAL_JUMP_VELOCITY;\n    Trex.config.MAX_JUMP_HEIGHT = spriteDefinition.MAX_JUMP_HEIGHT;\n    Trex.config.MIN_JUMP_HEIGHT = spriteDefinition.MIN_JUMP_HEIGHT;\n    Trex.config.WIDTH = spriteDefinition.RUNNING_1.w;\n    Trex.config.WIDTH_JUMP = spriteDefinition.JUMPING.w;\n    Trex.config.INVERT_JUMP = spriteDefinition.INVERT_JUMP;\n\n    this.adjustAltGameConfigForSlowSpeed(spriteDefinition.GRAVITY);\n    this.config = Trex.config;\n\n    \/\/ Adjust bottom horizon placement.\n    this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -\n        Runner.spriteDefinition['BOTTOM_PAD'];\n    this.yPos = this.groundYPos;\n    this.reset();\n  },\n\n  \/**\n   * Slow speeds adjustments for the alt game modes.\n   * @param {number=} opt_gravityValue\n   *\/\n  adjustAltGameConfigForSlowSpeed: function(opt_gravityValue) {\n    if (Runner.slowDown) {\n      if (opt_gravityValue) {\n        Trex.config.GRAVITY = opt_gravityValue \/ 1.5;\n      }\n      Trex.config.MIN_JUMP_HEIGHT *= 1.5;\n      Trex.config.MAX_JUMP_HEIGHT *= 1.5;\n      Trex.config.INITIAL_JUMP_VELOCITY =\n          Trex.config.INITIAL_JUMP_VELOCITY * 1.5;\n    }\n  },\n\n  \/**\n   * Setter whether dino is flashing.\n   * @param {boolean} status\n   *\/\n  setFlashing: function(status) {\n    this.flashing = status;\n  },\n\n  \/**\n   * Setter for the jump velocity.\n   * The approriate drop velocity is also set.\n   * @param {number} setting\n   *\/\n  setJumpVelocity(setting) {\n    this.config.INITIAL_JUMP_VELOCITY = -setting;\n    this.config.DROP_VELOCITY = -setting \/ 2;\n  },\n\n  \/**\n   * Set the animation status.\n   * @param {!number} deltaTime\n   * @param {Trex.status=} opt_status Optional status to switch to.\n   *\/\n  update(deltaTime, opt_status) {\n    this.timer += deltaTime;\n\n    \/\/ Update the status.\n    if (opt_status) {\n      this.status = opt_status;\n      this.currentFrame = 0;\n      this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;\n      this.currentAnimFrames = Trex.animFrames[opt_status].frames;\n\n      if (opt_status === Trex.status.WAITING) {\n        this.animStartTime = getTimeStamp();\n        this.setBlinkDelay();\n      }\n    }\n    \/\/ Game intro animation, T-rex moves in from the left.\n    if (this.playingIntro && this.xPos < this.config.START_X_POS) {\n      this.xPos += Math.round((this.config.START_X_POS \/\n          this.config.INTRO_DURATION) * deltaTime);\n      this.xInitialPos = this.xPos;\n    }\n\n    if (this.status === Trex.status.WAITING) {\n      this.blink(getTimeStamp());\n    } else {\n      this.draw(this.currentAnimFrames[this.currentFrame], 0);\n    }\n\n    \/\/ Update the frame position.\n    if (!this.flashing &#038;&#038; this.timer >= this.msPerFrame) {\n      this.currentFrame = this.currentFrame ==\n          this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;\n      this.timer = 0;\n    }\n\n    if (!this.altGameModeEnabled) {\n      \/\/ Speed drop becomes duck if the down key is still being pressed.\n      if (this.speedDrop && this.yPos === this.groundYPos) {\n        this.speedDrop = false;\n        this.setDuck(true);\n      }\n    }\n  },\n\n  \/**\n   * Draw the t-rex to a particular position.\n   * @param {number} x\n   * @param {number} y\n   *\/\n  draw(x, y) {\n    let sourceX = x;\n    let sourceY = y;\n    let sourceWidth = this.ducking && this.status !== Trex.status.CRASHED ?\n        this.config.WIDTH_DUCK :\n        this.config.WIDTH;\n    let sourceHeight = this.config.HEIGHT;\n    const outputHeight = sourceHeight;\n\n    let jumpOffset = Runner.spriteDefinition.TREX.JUMPING.xOffset;\n\n    \/\/ Width of sprite changes on jump.\n    if (this.altGameModeEnabled && this.jumping &&\n        this.status !== Trex.status.CRASHED) {\n      sourceWidth = this.config.WIDTH_JUMP;\n    }\n\n    if (IS_HIDPI) {\n      sourceX *= 2;\n      sourceY *= 2;\n      sourceWidth *= 2;\n      sourceHeight *= 2;\n      jumpOffset *= 2;\n    }\n\n    \/\/ Adjustments for sprite sheet position.\n    sourceX += this.spritePos.x;\n    sourceY += this.spritePos.y;\n\n    \/\/ Flashing.\n    if (this.flashing) {\n      if (this.timer < this.config.FLASH_ON) {\n        this.canvasCtx.globalAlpha = 0.5;\n      } else if (this.timer > this.config.FLASH_OFF) {\n        this.timer = 0;\n      }\n    }\n\n    \/\/ Ducking.\n    if (!this.altGameModeEnabled && this.ducking &&\n        this.status !== Trex.status.CRASHED) {\n      this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,\n          sourceWidth, sourceHeight,\n          this.xPos, this.yPos,\n          this.config.WIDTH_DUCK, outputHeight);\n    } else if (\n        this.altGameModeEnabled && this.jumping &&\n        this.status !== Trex.status.CRASHED) {\n      \/\/ Jumping with adjustments.\n      this.canvasCtx.drawImage(\n          Runner.imageSprite, sourceX, sourceY, sourceWidth, sourceHeight,\n          this.xPos - jumpOffset, this.yPos, this.config.WIDTH_JUMP,\n          outputHeight);\n    } else {\n      \/\/ Crashed whilst ducking. Trex is standing up so needs adjustment.\n      if (this.ducking && this.status === Trex.status.CRASHED) {\n        this.xPos++;\n      }\n      \/\/ Standing \/ running\n      this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,\n          sourceWidth, sourceHeight,\n          this.xPos, this.yPos,\n          this.config.WIDTH, outputHeight);\n    }\n    this.canvasCtx.globalAlpha = 1;\n  },\n\n  \/**\n   * Sets a random time for the blink to happen.\n   *\/\n  setBlinkDelay() {\n    this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);\n  },\n\n  \/**\n   * Make t-rex blink at random intervals.\n   * @param {number} time Current time in milliseconds.\n   *\/\n  blink(time) {\n    const deltaTime = time - this.animStartTime;\n\n    if (deltaTime >= this.blinkDelay) {\n      this.draw(this.currentAnimFrames[this.currentFrame], 0);\n\n      if (this.currentFrame === 1) {\n        \/\/ Set new random delay to blink.\n        this.setBlinkDelay();\n        this.animStartTime = time;\n        this.blinkCount++;\n      }\n    }\n  },\n\n  \/**\n   * Initialise a jump.\n   * @param {number} speed\n   *\/\n  startJump(speed) {\n    if (!this.jumping) {\n      this.update(0, Trex.status.JUMPING);\n      \/\/ Tweak the jump velocity based on the speed.\n      this.jumpVelocity = this.config.INITIAL_JUMP_VELOCITY - (speed \/ 10);\n      this.jumping = true;\n      this.reachedMinHeight = false;\n      this.speedDrop = false;\n\n      if (this.config.INVERT_JUMP) {\n        this.minJumpHeight = this.groundYPos + this.config.MIN_JUMP_HEIGHT;\n      }\n    }\n  },\n\n  \/**\n   * Jump is complete, falling down.\n   *\/\n  endJump() {\n    if (this.reachedMinHeight &&\n        this.jumpVelocity < this.config.DROP_VELOCITY) {\n      this.jumpVelocity = this.config.DROP_VELOCITY;\n    }\n  },\n\n  \/**\n   * Update frame for a jump.\n   * @param {number} deltaTime\n   *\/\n  updateJump(deltaTime) {\n    const msPerFrame = Trex.animFrames[this.status].msPerFrame;\n    const framesElapsed = deltaTime \/ msPerFrame;\n\n    \/\/ Speed drop makes Trex fall faster.\n    if (this.speedDrop) {\n      this.yPos += Math.round(this.jumpVelocity *\n          this.config.SPEED_DROP_COEFFICIENT * framesElapsed);\n    } else if (this.config.INVERT_JUMP) {\n      this.yPos -= Math.round(this.jumpVelocity * framesElapsed);\n    } else {\n      this.yPos += Math.round(this.jumpVelocity * framesElapsed);\n    }\n\n    this.jumpVelocity += this.config.GRAVITY * framesElapsed;\n\n    \/\/ Minimum height has been reached.\n    if (this.config.INVERT_JUMP &#038;&#038; (this.yPos > this.minJumpHeight) ||\n        !this.config.INVERT_JUMP && (this.yPos < this.minJumpHeight) ||\n        this.speedDrop) {\n      this.reachedMinHeight = true;\n    }\n\n    \/\/ Reached max height.\n    if (this.config.INVERT_JUMP &#038;&#038; (this.yPos > -this.config.MAX_JUMP_HEIGHT) ||\n        !this.config.INVERT_JUMP && (this.yPos < this.config.MAX_JUMP_HEIGHT) ||\n        this.speedDrop) {\n      this.endJump();\n    }\n\n    \/\/ Back down at ground level. Jump completed.\n    if ((this.config.INVERT_JUMP &#038;&#038; this.yPos) < this.groundYPos ||\n        (!this.config.INVERT_JUMP &#038;&#038; this.yPos) > this.groundYPos) {\n      this.reset();\n      this.jumpCount++;\n\n      if (Runner.audioCues) {\n        Runner.generatedSoundFx.loopFootSteps();\n      }\n    }\n  },\n\n  \/**\n   * Set the speed drop. Immediately cancels the current jump.\n   *\/\n  setSpeedDrop() {\n    this.speedDrop = true;\n    this.jumpVelocity = 1;\n  },\n\n  \/**\n   * @param {boolean} isDucking\n   *\/\n  setDuck(isDucking) {\n    if (isDucking && this.status !== Trex.status.DUCKING) {\n      this.update(0, Trex.status.DUCKING);\n      this.ducking = true;\n    } else if (this.status === Trex.status.DUCKING) {\n      this.update(0, Trex.status.RUNNING);\n      this.ducking = false;\n    }\n  },\n\n  \/**\n   * Reset the t-rex to running at start of game.\n   *\/\n  reset() {\n    this.xPos = this.xInitialPos;\n    this.yPos = this.groundYPos;\n    this.jumpVelocity = 0;\n    this.jumping = false;\n    this.ducking = false;\n    this.update(0, Trex.status.RUNNING);\n    this.midair = false;\n    this.speedDrop = false;\n    this.jumpCount = 0;\n  },\n};\n\n\n\/\/******************************************************************************\n\n\/**\n * Handles displaying the distance meter.\n * @param {!HTMLCanvasElement} canvas\n * @param {Object} spritePos Image position in sprite.\n * @param {number} canvasWidth\n * @constructor\n *\/\nfunction DistanceMeter(canvas, spritePos, canvasWidth) {\n  this.canvas = canvas;\n  this.canvasCtx =\n      \/** @type {CanvasRenderingContext2D} *\/ (canvas.getContext('2d'));\n  this.image = Runner.imageSprite;\n  this.spritePos = spritePos;\n  this.x = 0;\n  this.y = 5;\n\n  this.currentDistance = 0;\n  this.maxScore = 0;\n  this.highScore = '0';\n  this.container = null;\n\n  this.digits = [];\n  this.achievement = false;\n  this.defaultString = '';\n  this.flashTimer = 0;\n  this.flashIterations = 0;\n  this.invertTrigger = false;\n  this.flashingRafId = null;\n  this.highScoreBounds = {};\n  this.highScoreFlashing = false;\n\n  this.config = DistanceMeter.config;\n  this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS;\n  this.canvasWidth = canvasWidth;\n  this.init(canvasWidth);\n}\n\n\n\/**\n * @enum {number}\n *\/\nDistanceMeter.dimensions = {\n  WIDTH: 10,\n  HEIGHT: 13,\n  DEST_WIDTH: 11,\n};\n\n\n\/**\n * Y positioning of the digits in the sprite sheet.\n * X position is always 0.\n * @type {Array<number>}\n *\/\nDistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];\n\n\n\/**\n * Distance meter config.\n * @enum {number}\n *\/\nDistanceMeter.config = {\n  \/\/ Number of digits.\n  MAX_DISTANCE_UNITS: 5,\n\n  \/\/ Distance that causes achievement animation.\n  ACHIEVEMENT_DISTANCE: 100,\n\n  \/\/ Used for conversion from pixel distance to a scaled unit.\n  COEFFICIENT: 0.025,\n\n  \/\/ Flash duration in milliseconds.\n  FLASH_DURATION: 1000 \/ 4,\n\n  \/\/ Flash iterations for achievement animation.\n  FLASH_ITERATIONS: 3,\n\n  \/\/ Padding around the high score hit area.\n  HIGH_SCORE_HIT_AREA_PADDING: 4,\n};\n\n\nDistanceMeter.prototype = {\n  \/**\n   * Initialise the distance meter to '00000'.\n   * @param {number} width Canvas width in px.\n   *\/\n  init(width) {\n    let maxDistanceStr = '';\n\n    this.calcXPos(width);\n    this.maxScore = this.maxScoreUnits;\n    for (let i = 0; i < this.maxScoreUnits; i++) {\n      this.draw(i, 0);\n      this.defaultString += '0';\n      maxDistanceStr += '9';\n    }\n\n    this.maxScore = parseInt(maxDistanceStr, 10);\n  },\n\n  \/**\n   * Calculate the xPos in the canvas.\n   * @param {number} canvasWidth\n   *\/\n  calcXPos(canvasWidth) {\n    this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *\n        (this.maxScoreUnits + 1));\n  },\n\n  \/**\n   * Draw a digit to canvas.\n   * @param {number} digitPos Position of the digit.\n   * @param {number} value Digit value 0-9.\n   * @param {boolean=} opt_highScore Whether drawing the high score.\n   *\/\n  draw(digitPos, value, opt_highScore) {\n    let sourceWidth = DistanceMeter.dimensions.WIDTH;\n    let sourceHeight = DistanceMeter.dimensions.HEIGHT;\n    let sourceX = DistanceMeter.dimensions.WIDTH * value;\n    let sourceY = 0;\n\n    const targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;\n    const targetY = this.y;\n    const targetWidth = DistanceMeter.dimensions.WIDTH;\n    const targetHeight = DistanceMeter.dimensions.HEIGHT;\n\n    \/\/ For high DPI we 2x source values.\n    if (IS_HIDPI) {\n      sourceWidth *= 2;\n      sourceHeight *= 2;\n      sourceX *= 2;\n    }\n\n    sourceX += this.spritePos.x;\n    sourceY += this.spritePos.y;\n\n    this.canvasCtx.save();\n\n    if (IS_RTL) {\n      if (opt_highScore) {\n        this.canvasCtx.translate(\n            this.canvasWidth -\n                (DistanceMeter.dimensions.WIDTH * (this.maxScoreUnits + 3)),\n            this.y);\n      } else {\n        this.canvasCtx.translate(\n            this.canvasWidth - DistanceMeter.dimensions.WIDTH, this.y);\n      }\n      this.canvasCtx.scale(-1, 1);\n    } else {\n      const highScoreX =\n          this.x - (this.maxScoreUnits * 2) * DistanceMeter.dimensions.WIDTH;\n      if (opt_highScore) {\n        this.canvasCtx.translate(highScoreX, this.y);\n      } else {\n        this.canvasCtx.translate(this.x, this.y);\n      }\n    }\n\n    this.canvasCtx.drawImage(\n        this.image,\n        sourceX,\n        sourceY,\n        sourceWidth,\n        sourceHeight,\n        targetX,\n        targetY,\n        targetWidth,\n        targetHeight,\n    );\n\n    this.canvasCtx.restore();\n  },\n\n  \/**\n   * Covert pixel distance to a 'real' distance.\n   * @param {number} distance Pixel distance ran.\n   * @return {number} The 'real' distance ran.\n   *\/\n  getActualDistance(distance) {\n    return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;\n  },\n\n  \/**\n   * Update the distance meter.\n   * @param {number} distance\n   * @param {number} deltaTime\n   * @return {boolean} Whether the acheivement sound fx should be played.\n   *\/\n  update(deltaTime, distance) {\n    let paint = true;\n    let playSound = false;\n\n    if (!this.achievement) {\n      distance = this.getActualDistance(distance);\n      \/\/ Score has gone beyond the initial digit count.\n      if (distance > this.maxScore && this.maxScoreUnits ==\n        this.config.MAX_DISTANCE_UNITS) {\n        this.maxScoreUnits++;\n        this.maxScore = parseInt(this.maxScore + '9', 10);\n      } else {\n        this.distance = 0;\n      }\n\n      if (distance > 0) {\n        \/\/ Achievement unlocked.\n        if (distance % this.config.ACHIEVEMENT_DISTANCE === 0) {\n          \/\/ Flash score and play sound.\n          this.achievement = true;\n          this.flashTimer = 0;\n          playSound = true;\n        }\n\n        \/\/ Create a string representation of the distance with leading 0.\n        const distanceStr = (this.defaultString +\n            distance).substr(-this.maxScoreUnits);\n        this.digits = distanceStr.split('');\n      } else {\n        this.digits = this.defaultString.split('');\n      }\n    } else {\n      \/\/ Control flashing of the score on reaching acheivement.\n      if (this.flashIterations <= this.config.FLASH_ITERATIONS) {\n        this.flashTimer += deltaTime;\n\n        if (this.flashTimer < this.config.FLASH_DURATION) {\n          paint = false;\n        } else if (this.flashTimer > this.config.FLASH_DURATION * 2) {\n          this.flashTimer = 0;\n          this.flashIterations++;\n        }\n      } else {\n        this.achievement = false;\n        this.flashIterations = 0;\n        this.flashTimer = 0;\n      }\n    }\n\n    \/\/ Draw the digits if not flashing.\n    if (paint) {\n      for (let i = this.digits.length - 1; i >= 0; i--) {\n        this.draw(i, parseInt(this.digits[i], 10));\n      }\n    }\n\n    this.drawHighScore();\n    return playSound;\n  },\n\n  \/**\n   * Draw the high score.\n   *\/\n  drawHighScore() {\n    if (parseInt(this.highScore, 10) > 0) {\n      this.canvasCtx.save();\n      this.canvasCtx.globalAlpha = .8;\n      for (let i = this.highScore.length - 1; i >= 0; i--) {\n        this.draw(i, parseInt(this.highScore[i], 10), true);\n      }\n      this.canvasCtx.restore();\n    }\n  },\n\n  \/**\n   * Set the highscore as a array string.\n   * Position of char in the sprite: H - 10, I - 11.\n   * @param {number} distance Distance ran in pixels.\n   *\/\n  setHighScore(distance) {\n    distance = this.getActualDistance(distance);\n    const highScoreStr = (this.defaultString +\n        distance).substr(-this.maxScoreUnits);\n\n    this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));\n  },\n\n\n  \/**\n   * Whether a clicked is in the high score area.\n   * @param {Event} e Event object.\n   * @return {boolean} Whether the click was in the high score bounds.\n   *\/\n  hasClickedOnHighScore(e) {\n    let x = 0;\n    let y = 0;\n\n    if (e.touches) {\n      \/\/ Bounds for touch differ from pointer.\n      const canvasBounds = this.canvas.getBoundingClientRect();\n      x = e.touches[0].clientX - canvasBounds.left;\n      y = e.touches[0].clientY - canvasBounds.top;\n    } else {\n      x = e.offsetX;\n      y = e.offsetY;\n    }\n\n    this.highScoreBounds = this.getHighScoreBounds();\n    return x >= this.highScoreBounds.x && x <=\n        this.highScoreBounds.x + this.highScoreBounds.width &#038;&#038;\n        y >= this.highScoreBounds.y && y <=\n        this.highScoreBounds.y + this.highScoreBounds.height;\n  },\n\n  \/**\n   * Get the bounding box for the high score.\n   * @return {Object} Object with x, y, width and height properties.\n   *\/\n  getHighScoreBounds() {\n    return {\n      x: (this.x - (this.maxScoreUnits * 2) * DistanceMeter.dimensions.WIDTH) -\n          DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING,\n      y: this.y,\n      width: DistanceMeter.dimensions.WIDTH * (this.highScore.length + 1) +\n          DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING,\n      height: DistanceMeter.dimensions.HEIGHT +\n          (DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING * 2),\n    };\n  },\n\n  \/**\n   * Animate flashing the high score to indicate ready for resetting.\n   * The flashing stops following this.config.FLASH_ITERATIONS x 2 flashes.\n   *\/\n  flashHighScore() {\n    const now = getTimeStamp();\n    const deltaTime = now - (this.frameTimeStamp || now);\n    let paint = true;\n    this.frameTimeStamp = now;\n\n    \/\/ Reached the max number of flashes.\n    if (this.flashIterations > this.config.FLASH_ITERATIONS * 2) {\n      this.cancelHighScoreFlashing();\n      return;\n    }\n\n    this.flashTimer += deltaTime;\n\n    if (this.flashTimer < this.config.FLASH_DURATION) {\n      paint = false;\n    } else if (this.flashTimer > this.config.FLASH_DURATION * 2) {\n      this.flashTimer = 0;\n      this.flashIterations++;\n    }\n\n    if (paint) {\n      this.drawHighScore();\n    } else {\n      this.clearHighScoreBounds();\n    }\n    \/\/ Frame update.\n    this.flashingRafId =\n        requestAnimationFrame(this.flashHighScore.bind(this));\n  },\n\n  \/**\n   * Draw empty rectangle over high score.\n   *\/\n  clearHighScoreBounds() {\n    this.canvasCtx.save();\n    this.canvasCtx.fillStyle = '#fff';\n    this.canvasCtx.rect(this.highScoreBounds.x, this.highScoreBounds.y,\n        this.highScoreBounds.width, this.highScoreBounds.height);\n    this.canvasCtx.fill();\n    this.canvasCtx.restore();\n  },\n\n  \/**\n   * Starts the flashing of the high score.\n   *\/\n  startHighScoreFlashing() {\n    this.highScoreFlashing = true;\n    this.flashHighScore();\n  },\n\n  \/**\n   * Whether high score is flashing.\n   * @return {boolean}\n   *\/\n  isHighScoreFlashing() {\n    return this.highScoreFlashing;\n  },\n\n  \/**\n   * Stop flashing the high score.\n   *\/\n  cancelHighScoreFlashing() {\n    if (this.flashingRafId) {\n      cancelAnimationFrame(this.flashingRafId);\n    }\n    this.flashIterations = 0;\n    this.flashTimer = 0;\n    this.highScoreFlashing = false;\n    this.clearHighScoreBounds();\n    this.drawHighScore();\n  },\n\n  \/**\n   * Clear the high score.\n   *\/\n  resetHighScore() {\n    this.setHighScore(0);\n    this.cancelHighScoreFlashing();\n  },\n\n  \/**\n   * Reset the distance meter back to '00000'.\n   *\/\n  reset() {\n    this.update(0, 0);\n    this.achievement = false;\n  },\n};\n\n\n\/\/******************************************************************************\n\n\/**\n * Cloud background item.\n * Similar to an obstacle object but without collision boxes.\n * @param {HTMLCanvasElement} canvas Canvas element.\n * @param {Object} spritePos Position of image in sprite.\n * @param {number} containerWidth\n * @constructor\n *\/\nfunction Cloud(canvas, spritePos, containerWidth) {\n  this.canvas = canvas;\n  this.canvasCtx =\n      \/** @type {CanvasRenderingContext2D} *\/ (this.canvas.getContext('2d'));\n  this.spritePos = spritePos;\n  this.containerWidth = containerWidth;\n  this.xPos = containerWidth;\n  this.yPos = 0;\n  this.remove = false;\n  this.gap =\n      getRandomNum(Cloud.config.MIN_CLOUD_GAP, Cloud.config.MAX_CLOUD_GAP);\n\n  this.init();\n}\n\n\n\/**\n * Cloud object config.\n * @enum {number}\n *\/\nCloud.config = {\n  HEIGHT: 14,\n  MAX_CLOUD_GAP: 400,\n  MAX_SKY_LEVEL: 30,\n  MIN_CLOUD_GAP: 100,\n  MIN_SKY_LEVEL: 71,\n  WIDTH: 46,\n};\n\n\nCloud.prototype = {\n  \/**\n   * Initialise the cloud. Sets the Cloud height.\n   *\/\n  init() {\n    this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL,\n        Cloud.config.MIN_SKY_LEVEL);\n    this.draw();\n  },\n\n  \/**\n   * Draw the cloud.\n   *\/\n  draw() {\n    this.canvasCtx.save();\n    let sourceWidth = Cloud.config.WIDTH;\n    let sourceHeight = Cloud.config.HEIGHT;\n    const outputWidth = sourceWidth;\n    const outputHeight = sourceHeight;\n    if (IS_HIDPI) {\n      sourceWidth = sourceWidth * 2;\n      sourceHeight = sourceHeight * 2;\n    }\n\n    this.canvasCtx.drawImage(Runner.imageSprite, this.spritePos.x,\n        this.spritePos.y,\n        sourceWidth, sourceHeight,\n        this.xPos, this.yPos,\n        outputWidth, outputHeight);\n\n    this.canvasCtx.restore();\n  },\n\n  \/**\n   * Update the cloud position.\n   * @param {number} speed\n   *\/\n  update(speed) {\n    if (!this.remove) {\n      this.xPos -= Math.ceil(speed);\n      this.draw();\n\n      \/\/ Mark as removeable if no longer in the canvas.\n      if (!this.isVisible()) {\n        this.remove = true;\n      }\n    }\n  },\n\n  \/**\n   * Check if the cloud is visible on the stage.\n   * @return {boolean}\n   *\/\n  isVisible() {\n    return this.xPos + Cloud.config.WIDTH > 0;\n  },\n};\n\n\n\/**\n * Background item.\n * Similar to cloud, without random y position.\n * @param {HTMLCanvasElement} canvas Canvas element.\n * @param {Object} spritePos Position of image in sprite.\n * @param {number} containerWidth\n * @param {string} type Element type.\n * @constructor\n *\/\nfunction BackgroundEl(canvas, spritePos, containerWidth, type) {\n  this.canvas = canvas;\n  this.canvasCtx =\n      \/** @type {CanvasRenderingContext2D} *\/ (this.canvas.getContext('2d'));\n  this.spritePos = spritePos;\n  this.containerWidth = containerWidth;\n  this.xPos = containerWidth;\n  this.yPos = 0;\n  this.remove = false;\n  this.type = type;\n  this.gap =\n      getRandomNum(BackgroundEl.config.MIN_GAP, BackgroundEl.config.MAX_GAP);\n  this.animTimer = 0;\n  this.switchFrames = false;\n\n  this.spriteConfig = {};\n  this.init();\n}\n\n\/**\n * Background element object config.\n * Real values assigned when game type changes.\n * @enum {number}\n *\/\nBackgroundEl.config = {\n  MAX_BG_ELS: 0,\n  MAX_GAP: 0,\n  MIN_GAP: 0,\n  POS: 0,\n  SPEED: 0,\n  Y_POS: 0,\n  MS_PER_FRAME: 0,  \/\/ only needed when BACKGROUND_EL.FIXED is true\n};\n\n\nBackgroundEl.prototype = {\n  \/**\n   * Initialise the element setting the y position.\n   *\/\n  init() {\n    this.spriteConfig = Runner.spriteDefinition.BACKGROUND_EL[this.type];\n    if (this.spriteConfig.FIXED) {\n      this.xPos = this.spriteConfig.FIXED_X_POS;\n    }\n    this.yPos = BackgroundEl.config.Y_POS - this.spriteConfig.HEIGHT +\n        this.spriteConfig.OFFSET;\n    this.draw();\n  },\n\n  \/**\n   * Draw the element.\n   *\/\n  draw() {\n    this.canvasCtx.save();\n    let sourceWidth = this.spriteConfig.WIDTH;\n    let sourceHeight = this.spriteConfig.HEIGHT;\n    let sourceX = this.spriteConfig.X_POS;\n    const outputWidth = sourceWidth;\n    const outputHeight = sourceHeight;\n\n    if (IS_HIDPI) {\n      sourceWidth *= 2;\n      sourceHeight *= 2;\n      sourceX *= 2;\n    }\n\n    this.canvasCtx.drawImage(\n        Runner.imageSprite, sourceX, this.spritePos.y, sourceWidth,\n        sourceHeight, this.xPos, this.yPos, outputWidth, outputHeight);\n\n    this.canvasCtx.restore();\n  },\n\n  \/**\n   * Update the background element position.\n   * @param {number} speed\n   *\/\n  update(speed) {\n    if (!this.remove) {\n      if (this.spriteConfig.FIXED) {\n        this.animTimer += speed;\n        if (this.animTimer > BackgroundEl.config.MS_PER_FRAME) {\n          this.animTimer = 0;\n          this.switchFrames = !this.switchFrames;\n        }\n\n        if (this.spriteConfig.FIXED_Y_POS_1 &&\n            this.spriteConfig.FIXED_Y_POS_2) {\n          this.yPos = this.switchFrames ? this.spriteConfig.FIXED_Y_POS_1 :\n                                          this.spriteConfig.FIXED_Y_POS_2;\n        }\n      } else {\n        \/\/ Fixed speed, regardless of actual game speed.\n        this.xPos -= BackgroundEl.config.SPEED;\n      }\n      this.draw();\n\n      \/\/ Mark as removable if no longer in the canvas.\n      if (!this.isVisible()) {\n        this.remove = true;\n      }\n    }\n  },\n\n  \/**\n   * Check if the element is visible on the stage.\n   * @return {boolean}\n   *\/\n  isVisible() {\n    return this.xPos + this.spriteConfig.WIDTH > 0;\n  },\n};\n\n\n\n\/\/******************************************************************************\n\n\/**\n * Nightmode shows a moon and stars on the horizon.\n * @param {HTMLCanvasElement} canvas\n * @param {number} spritePos\n * @param {number} containerWidth\n * @constructor\n *\/\nfunction NightMode(canvas, spritePos, containerWidth) {\n  this.spritePos = spritePos;\n  this.canvas = canvas;\n  this.canvasCtx =\n      \/** @type {CanvasRenderingContext2D} *\/ (canvas.getContext('2d'));\n  this.xPos = containerWidth - 50;\n  this.yPos = 30;\n  this.currentPhase = 0;\n  this.opacity = 0;\n  this.containerWidth = containerWidth;\n  this.stars = [];\n  this.drawStars = false;\n  this.placeStars();\n}\n\n\/**\n * @enum {number}\n *\/\nNightMode.config = {\n  FADE_SPEED: 0.035,\n  HEIGHT: 40,\n  MOON_SPEED: 0.25,\n  NUM_STARS: 2,\n  STAR_SIZE: 9,\n  STAR_SPEED: 0.3,\n  STAR_MAX_Y: 70,\n  WIDTH: 20,\n};\n\nNightMode.phases = [140, 120, 100, 60, 40, 20, 0];\n\nNightMode.prototype = {\n  \/**\n   * Update moving moon, changing phases.\n   * @param {boolean} activated Whether night mode is activated.\n   *\/\n  update(activated) {\n    \/\/ Moon phase.\n    if (activated && this.opacity === 0) {\n      this.currentPhase++;\n\n      if (this.currentPhase >= NightMode.phases.length) {\n        this.currentPhase = 0;\n      }\n    }\n\n    \/\/ Fade in \/ out.\n    if (activated && (this.opacity < 1 || this.opacity === 0)) {\n      this.opacity += NightMode.config.FADE_SPEED;\n    } else if (this.opacity > 0) {\n      this.opacity -= NightMode.config.FADE_SPEED;\n    }\n\n    \/\/ Set moon positioning.\n    if (this.opacity > 0) {\n      this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED);\n\n      \/\/ Update stars.\n      if (this.drawStars) {\n        for (let i = 0; i < NightMode.config.NUM_STARS; i++) {\n          this.stars[i].x =\n              this.updateXPos(this.stars[i].x, NightMode.config.STAR_SPEED);\n        }\n      }\n      this.draw();\n    } else {\n      this.opacity = 0;\n      this.placeStars();\n    }\n    this.drawStars = true;\n  },\n\n  updateXPos(currentPos, speed) {\n    if (currentPos < -NightMode.config.WIDTH) {\n      currentPos = this.containerWidth;\n    } else {\n      currentPos -= speed;\n    }\n    return currentPos;\n  },\n\n  draw() {\n    let moonSourceWidth = this.currentPhase === 3 ? NightMode.config.WIDTH * 2 :\n                                                    NightMode.config.WIDTH;\n    let moonSourceHeight = NightMode.config.HEIGHT;\n    let moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase];\n    const moonOutputWidth = moonSourceWidth;\n    let starSize = NightMode.config.STAR_SIZE;\n    let starSourceX = Runner.spriteDefinitionByType.original.LDPI.STAR.x;\n\n    if (IS_HIDPI) {\n      moonSourceWidth *= 2;\n      moonSourceHeight *= 2;\n      moonSourceX = this.spritePos.x +\n          (NightMode.phases[this.currentPhase] * 2);\n      starSize *= 2;\n      starSourceX = Runner.spriteDefinitionByType.original.HDPI.STAR.x;\n    }\n\n    this.canvasCtx.save();\n    this.canvasCtx.globalAlpha = this.opacity;\n\n    \/\/ Stars.\n    if (this.drawStars) {\n      for (let i = 0; i < NightMode.config.NUM_STARS; i++) {\n        this.canvasCtx.drawImage(\n            Runner.origImageSprite, starSourceX, this.stars[i].sourceY,\n            starSize, starSize, Math.round(this.stars[i].x), this.stars[i].y,\n            NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE);\n      }\n    }\n\n    \/\/ Moon.\n    this.canvasCtx.drawImage(\n        Runner.origImageSprite, moonSourceX, this.spritePos.y, moonSourceWidth,\n        moonSourceHeight, Math.round(this.xPos), this.yPos, moonOutputWidth,\n        NightMode.config.HEIGHT);\n\n    this.canvasCtx.globalAlpha = 1;\n    this.canvasCtx.restore();\n  },\n\n  \/\/ Do star placement.\n  placeStars() {\n    const segmentSize = Math.round(this.containerWidth \/\n        NightMode.config.NUM_STARS);\n\n    for (let i = 0; i < NightMode.config.NUM_STARS; i++) {\n      this.stars[i] = {};\n      this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1));\n      this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y);\n\n      if (IS_HIDPI) {\n        this.stars[i].sourceY =\n            Runner.spriteDefinitionByType.original.HDPI.STAR.y +\n            NightMode.config.STAR_SIZE * 2 * i;\n      } else {\n        this.stars[i].sourceY =\n            Runner.spriteDefinitionByType.original.LDPI.STAR.y +\n            NightMode.config.STAR_SIZE * i;\n      }\n    }\n  },\n\n  reset() {\n    this.currentPhase = 0;\n    this.opacity = 0;\n    this.update(false);\n  },\n\n};\n\n\n\/\/******************************************************************************\n\n\/**\n * Horizon Line.\n * Consists of two connecting lines. Randomly assigns a flat \/ bumpy horizon.\n * @param {HTMLCanvasElement} canvas\n * @param {Object} lineConfig Configuration object.\n * @constructor\n *\/\nfunction HorizonLine(canvas, lineConfig) {\n  let sourceX = lineConfig.SOURCE_X;\n  let sourceY = lineConfig.SOURCE_Y;\n\n  if (IS_HIDPI) {\n    sourceX *= 2;\n    sourceY *= 2;\n  }\n\n  this.spritePos = {x: sourceX, y: sourceY};\n  this.canvas = canvas;\n  this.canvasCtx =\n      \/** @type {CanvasRenderingContext2D} *\/ (canvas.getContext('2d'));\n  this.sourceDimensions = {};\n  this.dimensions = lineConfig;\n\n  this.sourceXPos = [this.spritePos.x, this.spritePos.x +\n      this.dimensions.WIDTH];\n  this.xPos = [];\n  this.yPos = 0;\n  this.bumpThreshold = 0.5;\n\n  this.setSourceDimensions(lineConfig);\n  this.draw();\n}\n\n\n\/**\n * Horizon line dimensions.\n * @enum {number}\n *\/\nHorizonLine.dimensions = {\n  WIDTH: 600,\n  HEIGHT: 12,\n  YPOS: 127,\n};\n\n\nHorizonLine.prototype = {\n  \/**\n   * Set the source dimensions of the horizon line.\n   *\/\n  setSourceDimensions(newDimensions) {\n    for (const dimension in newDimensions) {\n      if (dimension !== 'SOURCE_X' &#038;&#038; dimension !== 'SOURCE_Y') {\n        if (IS_HIDPI) {\n          if (dimension !== 'YPOS') {\n            this.sourceDimensions[dimension] = newDimensions[dimension] * 2;\n          }\n        } else {\n          this.sourceDimensions[dimension] = newDimensions[dimension];\n        }\n        this.dimensions[dimension] = newDimensions[dimension];\n      }\n    }\n\n    this.xPos = [0, newDimensions.WIDTH];\n    this.yPos = newDimensions.YPOS;\n  },\n\n  \/**\n   * Return the crop x position of a type.\n   *\/\n  getRandomType() {\n    return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;\n  },\n\n  \/**\n   * Draw the horizon line.\n   *\/\n  draw() {\n    this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0],\n        this.spritePos.y,\n        this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,\n        this.xPos[0], this.yPos,\n        this.dimensions.WIDTH, this.dimensions.HEIGHT);\n\n    this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[1],\n        this.spritePos.y,\n        this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,\n        this.xPos[1], this.yPos,\n        this.dimensions.WIDTH, this.dimensions.HEIGHT);\n  },\n\n  \/**\n   * Update the x position of an indivdual piece of the line.\n   * @param {number} pos Line position.\n   * @param {number} increment\n   *\/\n  updateXPos(pos, increment) {\n    const line1 = pos;\n    const line2 = pos === 0 ? 1 : 0;\n\n    this.xPos[line1] -= increment;\n    this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;\n\n    if (this.xPos[line1] <= -this.dimensions.WIDTH) {\n      this.xPos[line1] += this.dimensions.WIDTH * 2;\n      this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;\n      this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x;\n    }\n  },\n\n  \/**\n   * Update the horizon line.\n   * @param {number} deltaTime\n   * @param {number} speed\n   *\/\n  update(deltaTime, speed) {\n    const increment = Math.floor(speed * (FPS \/ 1000) * deltaTime);\n\n    if (this.xPos[0] <= 0) {\n      this.updateXPos(0, increment);\n    } else {\n      this.updateXPos(1, increment);\n    }\n    this.draw();\n  },\n\n  \/**\n   * Reset horizon to the starting position.\n   *\/\n  reset() {\n    this.xPos[0] = 0;\n    this.xPos[1] = this.dimensions.WIDTH;\n  },\n};\n\n\n\/\/******************************************************************************\n\n\/**\n * Horizon background class.\n * @param {HTMLCanvasElement} canvas\n * @param {Object} spritePos Sprite positioning.\n * @param {Object} dimensions Canvas dimensions.\n * @param {number} gapCoefficient\n * @constructor\n *\/\nfunction Horizon(canvas, spritePos, dimensions, gapCoefficient) {\n  this.canvas = canvas;\n  this.canvasCtx =\n      \/** @type {CanvasRenderingContext2D} *\/ (this.canvas.getContext('2d'));\n  this.config = Horizon.config;\n  this.dimensions = dimensions;\n  this.gapCoefficient = gapCoefficient;\n  this.obstacles = [];\n  this.obstacleHistory = [];\n  this.horizonOffsets = [0, 0];\n  this.cloudFrequency = this.config.CLOUD_FREQUENCY;\n  this.spritePos = spritePos;\n  this.nightMode = null;\n  this.altGameModeActive = false;\n\n  \/\/ Cloud\n  this.clouds = [];\n  this.cloudSpeed = this.config.BG_CLOUD_SPEED;\n\n  \/\/ Background elements\n  this.backgroundEls = [];\n  this.lastEl = null;\n  this.backgroundSpeed = this.config.BG_CLOUD_SPEED;\n\n  \/\/ Horizon\n  this.horizonLine = null;\n  this.horizonLines = [];\n  this.init();\n}\n\n\n\/**\n * Horizon config.\n * @enum {number}\n *\/\nHorizon.config = {\n  BG_CLOUD_SPEED: 0.2,\n  BUMPY_THRESHOLD: .3,\n  CLOUD_FREQUENCY: .5,\n  HORIZON_HEIGHT: 16,\n  MAX_CLOUDS: 6,\n};\n\n\nHorizon.prototype = {\n  \/**\n   * Initialise the horizon. Just add the line and a cloud. No obstacles.\n   *\/\n  init() {\n    Obstacle.types = Runner.spriteDefinitionByType.original.OBSTACLES;\n    this.addCloud();\n    \/\/ Multiple Horizon lines\n    for (let i = 0; i < Runner.spriteDefinition.LINES.length; i++) {\n      this.horizonLines.push(\n          new HorizonLine(this.canvas, Runner.spriteDefinition.LINES[i]));\n    }\n\n    this.nightMode = new NightMode(this.canvas, this.spritePos.MOON,\n        this.dimensions.WIDTH);\n  },\n\n  \/**\n   * Update obstacle definitions based on the speed of the game.\n   *\/\n  adjustObstacleSpeed: function() {\n    for (let i = 0; i < Obstacle.types.length; i++) {\n      if (Runner.slowDown) {\n        Obstacle.types[i].multipleSpeed = Obstacle.types[i].multipleSpeed \/ 2;\n        Obstacle.types[i].minGap *= 1.5;\n        Obstacle.types[i].minSpeed = Obstacle.types[i].minSpeed \/ 2;\n\n        \/\/ Convert variable y position obstacles to fixed.\n        if (typeof (Obstacle.types[i].yPos) == 'object') {\n          Obstacle.types[i].yPos = Obstacle.types[i].yPos[0];\n          Obstacle.types[i].yPosMobile = Obstacle.types[i].yPos[0];\n        }\n      }\n    }\n  },\n\n  \/**\n   * Update sprites to correspond to change in sprite sheet.\n   * @param {number} spritePos\n   *\/\n  enableAltGameMode: function(spritePos) {\n    \/\/ Clear existing horizon objects.\n    this.clouds = [];\n    this.backgroundEls = [];\n\n    this.altGameModeActive = true;\n    this.spritePos = spritePos;\n\n    Obstacle.types = Runner.spriteDefinition.OBSTACLES;\n    this.adjustObstacleSpeed();\n\n    Obstacle.MAX_GAP_COEFFICIENT = Runner.spriteDefinition.MAX_GAP_COEFFICIENT;\n    Obstacle.MAX_OBSTACLE_LENGTH = Runner.spriteDefinition.MAX_OBSTACLE_LENGTH;\n\n    BackgroundEl.config = Runner.spriteDefinition.BACKGROUND_EL_CONFIG;\n\n    this.horizonLines = [];\n    for (let i = 0; i < Runner.spriteDefinition.LINES.length; i++) {\n      this.horizonLines.push(\n          new HorizonLine(this.canvas, Runner.spriteDefinition.LINES[i]));\n    }\n    this.reset();\n  },\n\n  \/**\n   * @param {number} deltaTime\n   * @param {number} currentSpeed\n   * @param {boolean} updateObstacles Used as an override to prevent\n   *     the obstacles from being updated \/ added. This happens in the\n   *     ease in section.\n   * @param {boolean} showNightMode Night mode activated.\n   *\/\n  update(deltaTime, currentSpeed, updateObstacles, showNightMode) {\n    this.runningTime += deltaTime;\n\n    if (this.altGameModeActive) {\n      this.updateBackgroundEls(deltaTime, currentSpeed);\n    }\n\n    for (let i = 0; i < this.horizonLines.length; i++) {\n      this.horizonLines[i].update(deltaTime, currentSpeed);\n    }\n\n    if (!this.altGameModeActive || Runner.spriteDefinition.HAS_CLOUDS) {\n      this.nightMode.update(showNightMode);\n      this.updateClouds(deltaTime, currentSpeed);\n    }\n\n    if (updateObstacles) {\n      this.updateObstacles(deltaTime, currentSpeed);\n    }\n  },\n\n  \/**\n   * Update background element positions. Also handles creating new elements.\n   * @param {number} elSpeed\n   * @param {Array<Object>} bgElArray\n   * @param {number} maxBgEl\n   * @param {Function} bgElAddFunction\n   * @param {number} frequency\n   *\/\n  updateBackgroundEl(elSpeed, bgElArray, maxBgEl, bgElAddFunction, frequency) {\n    const numElements = bgElArray.length;\n\n    if (numElements) {\n      for (let i = numElements - 1; i >= 0; i--) {\n        bgElArray[i].update(elSpeed);\n      }\n\n      const lastEl = bgElArray[numElements - 1];\n\n      \/\/ Check for adding a new element.\n      if (numElements < maxBgEl &#038;&#038;\n          (this.dimensions.WIDTH - lastEl.xPos) > lastEl.gap &&\n          frequency > Math.random()) {\n        bgElAddFunction();\n      }\n    } else {\n      bgElAddFunction();\n    }\n  },\n\n  \/**\n   * Update the cloud positions.\n   * @param {number} deltaTime\n   * @param {number} speed\n   *\/\n  updateClouds(deltaTime, speed) {\n    const elSpeed = this.cloudSpeed \/ 1000 * deltaTime * speed;\n    this.updateBackgroundEl(\n        elSpeed, this.clouds, this.config.MAX_CLOUDS, this.addCloud.bind(this),\n        this.cloudFrequency);\n\n    \/\/ Remove expired elements.\n    this.clouds = this.clouds.filter((obj) => !obj.remove);\n  },\n\n  \/**\n   * Update the background element positions.\n   * @param {number} deltaTime\n   * @param {number} speed\n   *\/\n  updateBackgroundEls(deltaTime, speed) {\n    this.updateBackgroundEl(\n        deltaTime, this.backgroundEls, BackgroundEl.config.MAX_BG_ELS,\n        this.addBackgroundEl.bind(this), this.cloudFrequency);\n\n    \/\/ Remove expired elements.\n    this.backgroundEls = this.backgroundEls.filter((obj) => !obj.remove);\n  },\n\n  \/**\n   * Update the obstacle positions.\n   * @param {number} deltaTime\n   * @param {number} currentSpeed\n   *\/\n  updateObstacles(deltaTime, currentSpeed) {\n    const updatedObstacles = this.obstacles.slice(0);\n\n    for (let i = 0; i < this.obstacles.length; i++) {\n      const obstacle = this.obstacles[i];\n      obstacle.update(deltaTime, currentSpeed);\n\n      \/\/ Clean up existing obstacles.\n      if (obstacle.remove) {\n        updatedObstacles.shift();\n      }\n    }\n    this.obstacles = updatedObstacles;\n\n    if (this.obstacles.length > 0) {\n      const lastObstacle = this.obstacles[this.obstacles.length - 1];\n\n      if (lastObstacle && !lastObstacle.followingObstacleCreated &&\n          lastObstacle.isVisible() &&\n          (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <\n          this.dimensions.WIDTH) {\n        this.addNewObstacle(currentSpeed);\n        lastObstacle.followingObstacleCreated = true;\n      }\n    } else {\n      \/\/ Create new obstacles.\n      this.addNewObstacle(currentSpeed);\n    }\n  },\n\n  removeFirstObstacle() {\n    this.obstacles.shift();\n  },\n\n  \/**\n   * Add a new obstacle.\n   * @param {number} currentSpeed\n   *\/\n  addNewObstacle(currentSpeed) {\n    const obstacleCount =\n        Obstacle.types[Obstacle.types.length - 1].type != 'COLLECTABLE' ||\n            (Runner.isAltGameModeEnabled() &#038;&#038; !this.altGameModeActive ||\n             this.altGameModeActive) ?\n        Obstacle.types.length - 1 :\n        Obstacle.types.length - 2;\n    const obstacleTypeIndex =\n        obstacleCount > 0 ? getRandomNum(0, obstacleCount) : 0;\n    const obstacleType = Obstacle.types[obstacleTypeIndex];\n\n    \/\/ Check for multiples of the same type of obstacle.\n    \/\/ Also check obstacle is available at current speed.\n    if ((obstacleCount > 0 && this.duplicateObstacleCheck(obstacleType.type)) ||\n        currentSpeed < obstacleType.minSpeed) {\n      this.addNewObstacle(currentSpeed);\n    } else {\n      const obstacleSpritePos = this.spritePos[obstacleType.type];\n\n      this.obstacles.push(new Obstacle(\n          this.canvasCtx, obstacleType, obstacleSpritePos, this.dimensions,\n          this.gapCoefficient, currentSpeed, obstacleType.width,\n          this.altGameModeActive));\n\n      this.obstacleHistory.unshift(obstacleType.type);\n\n      if (this.obstacleHistory.length > 1) {\n        this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION);\n      }\n    }\n  },\n\n  \/**\n   * Returns whether the previous two obstacles are the same as the next one.\n   * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION.\n   * @return {boolean}\n   *\/\n  duplicateObstacleCheck(nextObstacleType) {\n    let duplicateCount = 0;\n\n    for (let i = 0; i < this.obstacleHistory.length; i++) {\n      duplicateCount =\n          this.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0;\n    }\n    return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION;\n  },\n\n  \/**\n   * Reset the horizon layer.\n   * Remove existing obstacles and reposition the horizon line.\n   *\/\n  reset() {\n    this.obstacles = [];\n    for (let l = 0; l < this.horizonLines.length; l++) {\n      this.horizonLines[l].reset();\n    }\n\n    this.nightMode.reset();\n  },\n\n  \/**\n   * Update the canvas width and scaling.\n   * @param {number} width Canvas width.\n   * @param {number} height Canvas height.\n   *\/\n  resize(width, height) {\n    this.canvas.width = width;\n    this.canvas.height = height;\n  },\n\n  \/**\n   * Add a new cloud to the horizon.\n   *\/\n  addCloud() {\n    this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD,\n        this.dimensions.WIDTH));\n  },\n\n  \/**\n   * Add a random background element to the horizon.\n   *\/\n  addBackgroundEl() {\n    const backgroundElTypes =\n        Object.keys(Runner.spriteDefinition.BACKGROUND_EL);\n\n    if (backgroundElTypes.length > 0) {\n      let index = getRandomNum(0, backgroundElTypes.length - 1);\n      let type = backgroundElTypes[index];\n\n      \/\/ Add variation if available.\n      while (type == this.lastEl && backgroundElTypes.length > 1) {\n        index = getRandomNum(0, backgroundElTypes.length - 1);\n        type = backgroundElTypes[index];\n      }\n\n      this.lastEl = type;\n      this.backgroundEls.push(new BackgroundEl(\n          this.canvas, this.spritePos.BACKGROUND_EL, this.dimensions.WIDTH,\n          type));\n    }\n  },\n};\n<\/script><script>\/\/ Copyright 2021 The Chromium Authors\n\/\/ Use of this source code is governed by a BSD-style license that can be\n\/\/ found in the LICENSE file.\n\n\/* @const\n * Add matching sprite definition and config to Runner.spriteDefinitionByType.\n *\/\nconst GAME_TYPE = [];\n\n\/**\n * Obstacle definitions.\n * minGap: minimum pixel space between obstacles.\n * multipleSpeed: Speed at which multiples are allowed.\n * speedOffset: speed faster \/ slower than the horizon.\n * minSpeed: Minimum speed which the obstacle can make an appearance.\n *\n * @typedef {{\n *   type: string,\n *   width: number,\n *   height: number,\n *   yPos: number,\n *   multipleSpeed: number,\n *   minGap: number,\n *   minSpeed: number,\n *   collisionBoxes: Array<CollisionBox>,\n * }}\n *\/\nlet ObstacleType;\n\n\/**\n * T-Rex runner sprite definitions.\n *\/\nRunner.spriteDefinitionByType = {\n  original: {\n    LDPI: {\n      BACKGROUND_EL: {x: 86, y: 2},\n      CACTUS_LARGE: {x: 332, y: 2},\n      CACTUS_SMALL: {x: 228, y: 2},\n      OBSTACLE_2: {x: 332, y: 2},\n      OBSTACLE: {x: 228, y: 2},\n      CLOUD: {x: 86, y: 2},\n      HORIZON: {x: 2, y: 54},\n      MOON: {x: 484, y: 2},\n      PTERODACTYL: {x: 134, y: 2},\n      RESTART: {x: 2, y: 68},\n      TEXT_SPRITE: {x: 655, y: 2},\n      TREX: {x: 848, y: 2},\n      STAR: {x: 645, y: 2},\n      COLLECTABLE: {x: 2, y: 2},\n      ALT_GAME_END: {x: 121, y: 2},\n    },\n    HDPI: {\n      BACKGROUND_EL: {x: 166, y: 2},\n      CACTUS_LARGE: {x: 652, y: 2},\n      CACTUS_SMALL: {x: 446, y: 2},\n      OBSTACLE_2: {x: 652, y: 2},\n      OBSTACLE: {x: 446, y: 2},\n      CLOUD: {x: 166, y: 2},\n      HORIZON: {x: 2, y: 104},\n      MOON: {x: 954, y: 2},\n      PTERODACTYL: {x: 260, y: 2},\n      RESTART: {x: 2, y: 130},\n      TEXT_SPRITE: {x: 1294, y: 2},\n      TREX: {x: 1678, y: 2},\n      STAR: {x: 1276, y: 2},\n      COLLECTABLE: {x: 4, y: 4},\n      ALT_GAME_END: {x: 242, y: 4},\n    },\n    MAX_GAP_COEFFICIENT: 1.5,\n    MAX_OBSTACLE_LENGTH: 3,\n    HAS_CLOUDS: 1,\n    BOTTOM_PAD: 10,\n    TREX: {\n      WAITING_1: {x: 44, w: 44, h: 47, xOffset: 0},\n      WAITING_2: {x: 0, w: 44, h: 47, xOffset: 0},\n      RUNNING_1: {x: 88, w: 44, h: 47, xOffset: 0},\n      RUNNING_2: {x: 132, w: 44, h: 47, xOffset: 0},\n      JUMPING: {x: 0, w: 44, h: 47, xOffset: 0},\n      CRASHED: {x: 220, w: 44, h: 47, xOffset: 0},\n      COLLISION_BOXES: [\n        new CollisionBox(22, 0, 17, 16),\n        new CollisionBox(1, 18, 30, 9),\n        new CollisionBox(10, 35, 14, 8),\n        new CollisionBox(1, 24, 29, 5),\n        new CollisionBox(5, 30, 21, 4),\n        new CollisionBox(9, 34, 15, 4),\n      ],\n    },\n    \/** @type {Array<ObstacleType>} *\/\n    OBSTACLES: [\n      {\n        type: 'CACTUS_SMALL',\n        width: 17,\n        height: 35,\n        yPos: 105,\n        multipleSpeed: 4,\n        minGap: 120,\n        minSpeed: 0,\n        collisionBoxes: [\n          new CollisionBox(0, 7, 5, 27),\n          new CollisionBox(4, 0, 6, 34),\n          new CollisionBox(10, 4, 7, 14),\n        ],\n      },\n      {\n        type: 'CACTUS_LARGE',\n        width: 25,\n        height: 50,\n        yPos: 90,\n        multipleSpeed: 7,\n        minGap: 120,\n        minSpeed: 0,\n        collisionBoxes: [\n          new CollisionBox(0, 12, 7, 38),\n          new CollisionBox(8, 0, 7, 49),\n          new CollisionBox(13, 10, 10, 38),\n        ],\n      },\n      {\n        type: 'PTERODACTYL',\n        width: 46,\n        height: 40,\n        yPos: [100, 75, 50],    \/\/ Variable height.\n        yPosMobile: [100, 50],  \/\/ Variable height mobile.\n        multipleSpeed: 999,\n        minSpeed: 8.5,\n        minGap: 150,\n        collisionBoxes: [\n          new CollisionBox(15, 15, 16, 5),\n          new CollisionBox(18, 21, 24, 6),\n          new CollisionBox(2, 14, 4, 3),\n          new CollisionBox(6, 10, 4, 7),\n          new CollisionBox(10, 8, 6, 9),\n        ],\n        numFrames: 2,\n        frameRate: 1000 \/ 6,\n        speedOffset: .8,\n      },\n    ],\n    BACKGROUND_EL: {\n      'CLOUD': {\n        HEIGHT: 14,\n        MAX_CLOUD_GAP: 400,\n        MAX_SKY_LEVEL: 30,\n        MIN_CLOUD_GAP: 100,\n        MIN_SKY_LEVEL: 71,\n        OFFSET: 4,\n        WIDTH: 46,\n        X_POS: 1,\n        Y_POS: 120,\n      },\n    },\n    BACKGROUND_EL_CONFIG: {\n      MAX_BG_ELS: 1,\n      MAX_GAP: 400,\n      MIN_GAP: 100,\n      POS: 0,\n      SPEED: 0.5,\n      Y_POS: 125,\n    },\n    LINES: [\n      {SOURCE_X: 2, SOURCE_Y: 52, WIDTH: 600, HEIGHT: 12, YPOS: 127},\n    ],\n  },\n};\n<\/script><\/p>\n<div id=\"main-frame-error\" class=\"interstitial-wrapper\">\n<div id=\"main-content\">\n<div class=\"icon icon-generic\">&nbsp;<\/div>\n<div id=\"main-message\">\n<h1>No se puede acceder a este sitio web<a id=\"error-information-button\" class=\"hidden\"><\/a><\/h1>\n<p>Comprueba si hay un error de escritura en viloclub.com.ar.<\/p>\n<!--The suggestion list and error code are normally presented inline,\n          in which case error-information-popup-* divs have no effect. When\n          error-information-popup-container has the use-popup-container class, this\n          information is provided in a popup instead.-->\n<div id=\"error-information-popup-container\">\n<div id=\"error-information-popup\">\n<div id=\"error-information-popup-box\">\n<div id=\"error-information-popup-content\">\n<div id=\"suggestions-list\">\n<p>&nbsp;<\/p>\n<ul class=\"single-suggestion\">\n<li>Si est\u00e1 escrito correctamente, <a id=\"diagnose-link\"><\/a>prueba a ejecutar el diagn\u00f3stico de red de Windows.<\/li>\n<\/ul>\n<\/div>\n<div class=\"error-code\">DNS_PROBE_FINISHED_NXDOMAIN<\/div>\n<p id=\"error-information-popup-close\"><a class=\"link-button\">null<\/a><\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div id=\"download-links-wrapper\" class=\"hidden\">\n<div id=\"download-link-wrapper\">&nbsp;<\/div>\n<div id=\"download-link-clicked-wrapper\" class=\"hidden\">\n<div id=\"download-link-clicked\" class=\"link-button\" style=\"display: none;\">&nbsp;<\/div>\n<\/div>\n<\/div>\n<div id=\"save-page-for-later-button\" class=\"hidden\">&nbsp;<\/div>\n<div id=\"cancel-save-page-button\" class=\"hidden\" style=\"display: none;\">&nbsp;<\/div>\n<div id=\"offline-content-list\" class=\"list-hidden\" hidden=\"\">\n<div id=\"offline-content-list-visibility-card\">\n<div id=\"offline-content-list-title\" style=\"display: none;\">&nbsp;<\/div>\n<div>\n<div id=\"offline-content-list-show-text\" style=\"display: none;\">&nbsp;<\/div>\n<div id=\"offline-content-list-hide-text\" style=\"display: none;\">&nbsp;<\/div>\n<\/div>\n<\/div>\n<div id=\"offline-content-suggestions\">&nbsp;<\/div>\n<div id=\"offline-content-list-action\">&nbsp;<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div id=\"buttons\" class=\"nav-wrapper suggested-left\">\n<div id=\"control-buttons\"><button id=\"reload-button\" class=\"blue-button text-button\">Volver a cargar<\/button> <button id=\"download-button\" class=\"blue-button text-button\" style=\"display: none;\"> <\/button><\/div>\n<button id=\"details-button\" class=\"secondary-button text-button small-link\" style=\"display: none;\"><\/button><\/div>\n<div id=\"details\" class=\"hidden\">\n<div class=\"suggestions\" style=\"display: none;\">\n<div class=\"suggestion-header\">&nbsp;<\/div>\n<div class=\"suggestion-body\">&nbsp;<\/div>\n<\/div>\n<\/div>\n<\/div>\n<div id=\"sub-frame-error\"><!-- Show details when hovering over the icon, in case the details are\n         hidden because they're too large. -->\n<div class=\"icon\">&nbsp;<\/div>\n<div id=\"sub-frame-error-details\">Comprueba si hay un error de escritura en viloclub.com.ar.<\/div>\n<\/div>\n<div id=\"offline-resources\"><img decoding=\"async\" id=\"offline-resources-1x\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAABNEAAABkBAMAAABayruYAAAAJFBMVEUAAADa2tr\/\/\/\/\/9\/e6urpTU1O5ubn39\/f\/\/\/9ZWVlfX1\/z8\/O\/OctmAAAACXRSTlMA\/\/\/\/\/\/\/\/\/\/ZO3iNwAAALPElEQVR4AezdwY6bShMF4GP6krX9Bqgk9kiI\/SzyAAir9lnlFfL6N26OWhXckDae9mClj\/L7L1czMMbfbYDMOCgpKSkpwelyRmIEd6mEhTQpDabvu1C7vsf2ALM6cLlctquVtq2YDwC1jrfHEVDV8fagvln7p7XOlUKVi9SKWrncY5GQnN0DhLuZ1HZJa7WZPemU0GCc6hUMBtVue4BZHeD3v1caTn9KIyiPSimIvjw8SqtDVaQlvKrT2e91JEVUsEilOtGTNkkNUglWnFLX1oDrWSwGSOZ8V91CRczFDnBkWVEaKG0WBISZDPOTeeD2MIZK\/Sz4YESUkbxdRhlkTXTrJ74d+aQ1bFRPSRvYjUuLmLOKmNjIch3\/fQesGygrHW\/SyO2WWzWmSyvSHjpVE1WJSWsIqwJk0agmSmsb39gnzbGKSaOXyJTGKmFSA6vvv\/Nh3NQaDpyjPWaCp22mt0+ahkj+LlTzU4tu3Ujjrt4nrZoIq20qlT8brW\/4k7S5sQGq73ZJO+M5aawjc5pHRmmYLxMozY\/64llp8oAeeaQrMWkir5EGnSPLg8aZ6OaIrJ3n8WsX0lptPCy5ldOiYaT5xro0p9cEaa7nAENd99DOrEzIK0btxOrDSKMl0JeyCgugtr2DSWunmDR2Xy7tdF7c7MgmrfmLNDa7LWmOX9pllzbSDac0UBqrpTQOHOboeQBpIWJOjU3Oq8dItu+pNZRWLaWFBg+nnyBt6FhxIMIrVGxfFqGujcuDj\/lkf6S0EeYC9E5aGDiUtAMcPUNkMZ8xl\/Oj0qqJ0tomSFs2xDfkaWlOr1FpZzwrzU5qP3jn1px\/qeroQUGVDyR2q\/hs9X5auSI44T5nLheTJkppdnDpiNJCY1ta3wVQcB2lceBrpH3Dj29F2qdKO50vEWunl0qb6RDUcO0ojQOGYFya6++gnVlRGiubIO1CXgtq+IFPTZF2AeJvBBeT+Ffz8TlpvJnhZTleSTo+NwOB4Iq0QbvPl\/btJz41Rdpanpemf5EWbmZQVheXZgei0m7Fp0v7+Ts\/APteqI6savX\/Y22XCa3NJVlH9qrP092DSROfv3qUOXdt\/t8z0iyo3rjplgMJ0ugkemPjHCobnKK3PPiFnNOOL61Iq95cGq89rZ9aQ6l1MKNYhLqi9XKZX79if0EokqNrk9FZwtZj0EJks01pamYztFYaSz7qXmmue5U0f+0Zs0FpWqR9rbSpIqwGFWEpG0Fau1\/a4Fn1r5rTskv7pV5aJeYwA4hKli4UjFXmh2LhGho8mujW1yNzlFE+R7QdpDWUNgGoOHmxQWnazP090nr\/R\/UV0sLfe2ryGVfcZB1Zkms+qLRKhGki0iTkC6VNglmaNKC0KTSCNAhnvf3SOnT5pW3pwlgnzWnLqwOY9ghKE2nDzuQ7laUL81KMtHlYDC9TtpNIY+xJsrTl1pmnD6I8OeNE1gAsGzZgpIGz3pa0fkvaFe7qpfX5pH18fPyj0sKX6SRipTHKiHyJtIrS0Fppk4ANwgvSpNmW5hOXdu078Cab5pP23\/cZx9oZV6I0qI5RaVC9SVO+dwyd5OlCNXKHQ9QsTF5qy8nY0zRp0a2nUiPO1bY9O6O0RaO10hpsSHPb0oD80vzP3AKqutSVfD+NITS7JAnrQaWRFeulNA35ImmVzLAgbZBmGySnKdIwJEjDkH1Oe4U0+94JnWTqQlUNNARpd5napTob2QYU33qqNEbifUn+3ahbK0Ga25bm\/JzGhTKep+VOTmlFWpMiDcOmtKEbtLs9aNZrz9dIY+z5fKYu1MTc5dDVTBKlliBtsfWUyNpXiG2nSpvENHiJqT1B9To\/dIDjQFSa0+ugvV5d32f7G\/Yi7d2lAVYaQ0zMFeAgB0jwThrglDYzSMMXSIOPZOnGpW1Tm5pK2qelIS2yeptXGOB5aZ0zNaXZAaqLSKPNIm21W6TRCakMpqY0\/8QNlmNcWpfj9wheElEbydxFVBpE1qVhSS2FkOyTlrDsPmlGVxfQXPuO0swAh1gupdHm+0uT3F1EoGWXJjiANCLqezuJMYMZIEGWVhoHcvwW3uupSfYurLRtapPc0iBOTXywFtkpTZBJGvp+CCdmvJIEYwZIkKWRlu932I8vrUjL8KlWhuDwhtLSr+3zdxGDZqnxdi2LBlhSEwlF+qv6XGkQaWZyImmNHZ815HojLfETYFguoeG0+gkwx5ZWpO3Krk+14tVCzk+1ej01kVd0EYHmNf15a2NOw1FLTSBM6qtKjajgYNJ4upb3k\/r+TWki7SRr0iYRlX9Kmh\/su8yfPvqa8MglqiKpXeGBzXYlaQ2khntpLX9AyEuLsOFWU+XYrSdHcDxpbtAuDGT6ROV\/SVollNZULdcd32oSHZ7OcevKvKc0WGmZPiX+ZRFVgaikd3lgW1JLWsOs7F6a\/3yLBmvSBBAh5\/2vKn\/ySztyji8NVZAW1m1CaXNQpL2vNOFDWjcSEUldAxQxaSLSTg3WpBHYQ9IERdpqijQmLi09qkXaYY+eKqndeBLXAFU+RA6gTcKqd7yq40hzFlS3MRCX1uHoKdJqfG2c86AGb6Wbf1b7ejcAx4GINA68c8Jvhqd240lbw3p4hra66vSoLrZ+gAyDhqnLXZUzlB0gwXnAWWl2IH+KtPeOc\/3vdCCoWxYDJEhfHVz4LTwzkJKSEmetDN1ygARvA47\/7OfQud4OJKWkxFJxCQOh5pP3S0lJSUlJSYmq4sipVcdF\/Y4pqcfbnwNHgXFRv2FKagWgOG74D97a+h1Tonw8ZgiLjxo6nxQteV1GzmzK8NlxYkyMz\/lAydGmEEVJSe7Mc0dJrY8uPyaedO4PN5I96Zsr+yp9c6ppKwKjSIuurYAZk48wy4xJb7COO2jU3CIXKPsqcV8dMnXaEjuiO76DL9xLZV\/Va9+T6oP\/LSVN3yO3wMXzRLEnY9lXyUk8dOquw8R4vHNG1T3fmCa90LKv0vfV\/+2dQW6jQBBFEascwyqpL9RSiZO0ejvL4QZDbmB8g\/hy0zXwRUPZ0QiRDfwnJ5aesstTCdNNm7yAEEJaWXE7ztQQEnRFPM6Q04+orftuwLS64XaUacjpR5Q7KyQuRirMBt0QjzLNmSHyr7TNSVuFOJuPYRjGifsw\/GFp+yCtqBHlnemH4XOcKdH9Ymm7IKIT8eYNShvB\/X1p3cYY2RlNznSXKI20CgQmrk2PkWZ8U1remtrBqDddukJpRNxHvxDDaqj1w7hwn0pLKbl5lfOL0pIrzZkuX6A00sYqDwy5sBpq\/edYMZWWsxWTC3VpaWsK6o12G5NgmhPD0uRlaQFmKu05Pp6FL5TW5ZxRydSMqbQ1BXXGulqbDNOcFtKqqMoM7q5FM6Eq7WGlGShNp5lmoBm0B4MQVwYzbW0STENOS1AJUTQKLsuso2ARiBRnprfKvsbCo7zdUVpeLrLiG5O6vDX22pguw5y0NIKurDIJqorSROyXvU+ljVaaUZeWXFfedMmX5kyXLlAaCXNkWpcWA0JAaV\/PbWkp\/09pzmjypek1SmNp0ZWmMEtpoytNfUU7zTVLY2nK0sjPlKa+NGFp5AdKc58INE4\/LI0cWloUe6E0TDjxpT1YGtmLaEFEcD8NJkiA6S2xmRGlZYBmDjENOftWDtFCrEyU9WrUBFajsIqElaajTEOuVFpQZKDx3Qr7Mozwx4eYhpyXsJR2m4wsGbzeNcQ9t2QHLf7pKjD1SPM7IVka2UUruKshMMGEISyNHMe8mh6lMrhuc88RDCyN7Gba9xhvlYlaBJ\/CI8fSBg0qt9pIEYvpkdrdRhpLI57dXw66Mh+\/K3haAuEJMOQ88FQrsoO\/etICpT2ul1QAAAAASUVORK5CYII=\"> <img decoding=\"async\" id=\"offline-resources-2x\" src=\"data:image\/png;base64,iVBORw0KGgoAAAANSUhEUgAACY4AAADCCAMAAADT9DSoAAAANlBMVEUAAADa2tr\/\/\/\/\/9\/e5ubn39\/dTU1P29vbv7+\/+\/v74+Pjw8PD\/\/\/9ZWVlfX1\/z8\/P5+fn\/\/\/9RgilMAAAAEnRSTlMA\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/2\/\/\/\/9gn80juWAAAR\/UlEQVR4AezdAW+jOBPG8QcgVPv9P+xqHQPvu9nrTWWd1enNuY7D\/ydpS+gwdqRq44yN0WUBAAAAAAAA06u\/sVPPbZZ0\/Ie5LNvIEWbRu11msCsK7duYZM4OcaWzf1+rVk13fbTpj1SctXMWZJHluSLYTmxlUBlVxJlkZz\/py2a\/txeV\/o1qls9B3q55\/TALAAAAHa16KeU340nT4+gKZq36LesYPMIsWmR2mbGuqGvZxqkrOsct+wNgOAYA2Gy6bysmEo3N\/71HKhWzg+W1haTCZqdr06Blu5tSvS\/GpLIhAAzHmsxMWyWsqJA980zxKinb+4zWxh4Zs46RIyoVosWqRGNcYRGOrJE2zCTjjzsD+SwysJLTFXdaRCjf+DA7P74yeTvmrdtUKCTWjr2uaZIAoHR7k5a3H+oLANZX+W4zdf4WjFmHP+IyrM616\/ucQ+S1nFO3FWTn\/r6Gsbi50Sb+3l+aykxk5Q5Mu9xstTshK20UL5MAMBwbzsmyXgCF22yD5OVx\/EthAMBw7NSobP1Yh2qV7X4WyjF\/shLMIio5Xrw2tsTrY\/3XjQXiLPYMxFktLZ7v3O04azRYA\/+z9stL3s0Zk\/ibHkqvqUwA2Opzl9ock5B2J2Qtn50t5ky38txW6R8AhmM9xt4w\/mrVnyMpB3I8MjyOKyyimqO9+r2O16sRswdZtv+HNN01KGRJK\/1tmfdhbZ4Xq67AtoS11wDwcLsLAK49HEvhqvrU9O7Po2HudpVAq0Udn0bocfQ4DuRo0NOB7nXsULPrsG7s9MUZ\/zouTV3Wj0lZq6Z7juyclFQe1yYh7ZxxXJvKBJvsd+XvTbKTQHxtc+u8WPXyJp3Fh8kkAAAAhmMxzu\/G\/WHWccF7HesWazVYswOw0l\/L++zAvmP1Oy0BoLr5a8WmIsC9lasdBVgeE8sMgOHYFl4nczZ7lqRsPVez3Nle2\/qxXrvhN8hh903CqmB7uGYX3x\/sDOdzaLj\/2BTNB8Ahf1NerNz+DgAAwHCs\/Vox9hdr2Yp\/tzFqYw1XrZ1C9KmYSdrKab+tOh+42XXldqxJFf8Q95VrN5lUucuzov4+gP5r3TDrwqb\/E4BLur39KI57AYCVfccra7v65Lb1Y4HqU7O9wQbdocvqUezcD3PuR3HcCwCsTGEAYDf+v4+TCkn1M\/Wz9d8l\/7X1vvj7l+wAAMMxoMeu+vErAhW45nVB92O\/JpXOxndVtr+78tTkiiu\/fFlctnqvHXcBAOtYS\/incq\/9oNPyALic27xrmeef6goAVqFc21Vfy9Uot+ptXozVf\/y76nuvWKox8Tbsmn2op23i3MW+eAAYjn11YuOsTlUAgN9ttoHt8jj+JQBgOAb+GOKrvLr0yiIWixngaZvUxd5lgf3jyQuGYw5n5RwANH1wW3LHOyNT5WUtvpBav6n2\/dwcwR0BDMfy06wb8++XewRzG9aPlfWfwBUXqEpNMqczTq3j2t9dGYg7Ncnisuw\/wOkuAGBX\/n4A4CYAoDrWFQ5lrboiIGvVdM\/Vebq6Mn6TNt+F23u8U1JU8aasqzGBftb7M38y7zA7P86y5SBvPG+p2dxNojoGADyzEsD4qI41GtP3Xze2+r8jxHPHOXKuofqY5aAcG9+hHzyzEgBWCQB4ZmVgpvLr85VXAYDhGLIOzZ9G\/HbYfWYNWrFVOtdQ26F\/0TMBz6x81uei5Opv6x9buVNe8to3jOSIKSXnWqpDDURaZe0YAAAA1bEOY++ee56tzv3Bao5GuQ9X1coTYfnmSt9irVj+rPUCxVnboZ\/a2MjKzV0796RDZ+wO0Jb93AQ8S93p6NVqJR4AAACsHUO80neEIoqVYYEcplihVrRyHfv7g6u1qwTAPbNScXTIS94WNVCbI5r\/dSXpGKjVSwKA2zz\/tJ8f+efp3GFFZn\/+pJbqPazP2Mb7WSYHsI783cYh3F52rvEyJlv+JrmPatQh442o1caiOcor5korPSxda2O2O1m3XrHzmP18QQBm5+gjW2yHVg+75noAYHuTljfpJgBogclKnjdpEcH1Z\/5W1kArr10bszrYx9rY0nV3MuS\/\/p3u2b+Va8mCt6EfzFefq03tp0TTp\/eUe+cRskrkbZ+3vvfY5pyyTs62Z2ef7QqvDq0yHAOA2ywbHD+OfwnAeKiOdRh793C41niZLHO0zN20PmYttG\/le+0d60+7ngfO3Y6zXheA1RmTu7Vq8QAAm698IpvKHsbfVHJflVr2s5yvBBg0Yli2m5cjonUr6wB\/XFYfu3Kf8PHvebqrK8SrBtnieuUlb7F+bHMuo9yaDVdW\/7vo1SrPrASA25setrcf6gkA1qG+2wzA1sDF16a5cjt2LLGIAFcrSXN9z31qUdW9+JcufcK5T\/f1URs7\/LNs9cjUOD4itbwqBdImXRpAdQwAbvbzdQFg7RhgtTHqY7YXf3muR5+Qle0nhv94yn3ykjf+2LD4vFn8HXdvdVZHAAAAWIf5bjOALHPE9zYL5u4vh3q7fH4ucMVejVia18aWyrn9S704JU36Y9LpijPt4zzOb42bKnFdAQDVMQC46YUBoDoGHFKz2tiuXYvnCosvrrcIRxvOVmL2IqPvnfyPvXvRkRMHogAKYdT\/\/70ImH3WitHGkTXuCpQ4Z59NsD2iETE3hWGEujHXG\/2m9zvwNH9HJVfVUaVjAADSsYajaJ1YOEbfjdl9fNinPWf\/Rpv+BG6ZxsnGAOqTjgEASMcgaTWwSIiiRXo2tvf\/VL85FYynHP\/5d\/\/TlfEsZv7TlXlPS86eqqyv9Yx5hX7123j3pPox6RgAgHRsfO5dp27suKx2Tj62T3tfi9hvMBv7yzJeaZZSMQfEFVm\/tfpdJ6RjAABqx9pzb+Rj\/VlXTz7WNjBGo0Xs+159Kd+sMqqrygz1Y\/pVP7ZdOKp0rD4AQDqmfkySl+1Xb27ce1sM2L+R2oX0fOyNT0PO0+d4f5e9q3J+c38AascAAKRjcL98bBlokZnaLZ0VcNlA\/dim39x+k+rH1t9WP7Y1JjsfP9nnuHTUS9MxAAA+Kt3btHGcjuRRd48Cqd1ym7xutN4rnsQc70\/dGIDaMQAA6RjXO4rv8YAV1GLbafvy5vX258QkaE5LmGYrjvVSP9ZR8aPf\/H5H6sfWod\/jfnyjkuvoXGfs2lEvTccAAPi4yb2NNcd4bGYW2VjV+rHoR90YcK3ty+RmKzCqdAwAQO0YyMf2+He4dQXZnNDrrGqshfgzlsbnGv3+4+O\/7du\/KcjxvX6jz5sfh6gfa30e89E4CltqzXLre\/1VJnZIxwAApGO9c+8CDnVjpK1ftk\/vE8nV3L9fO0vr769dQfbGGq9ZzRhYmatz\/f5zivbP5yNv1NAY9XnpGACAdOz1zRX3X+Nvt4JC9sjGUkXqVZOqMSLnCNtPntk\/7t9vvPXw5Bh6X2OkL9cfhz5rZv3YBSsfnEfarMoPACAd60yxeubea5H7NKjh86r9CvysQPm8tMN2bnfNqNIxAABPVgJAQr1OjX4\/T\/0eb8yFtvPnAsdhzVsF\/7K6sZF3TkrHAACkY9mzVwCA4zajSscAAKRjr1MqBgDjq0wd7W236neOVdmz0pcCxyFmAmt72+BR+NH+SZPPw17SMQAA6dga8723zr1hmfb6LULiGPkAkI4BAEjH8r0e+75KCdjS+JW\/tu+XtAjtFpliDHiarbGtQL95ChyHtXNblaO9SccAAKRjnV4x\/33b3HudeJjIgRrJ1f7PP\/kt+jO7aDFc4dU\/BgBqxwAAeJt5gjK1Y\/uFLZZGiz1anPbaT59O+8W48SuxtWsMAJ6SjgEA4MlKiDqp9pOF+S36K8rO2\/f\/fQr7lxH209beMQB4cjoGAACwLAVaJIwQbQB4djoGAAAAAAAAYN0xAF5eYEKVM9AZq3YMAEA6BkBCMrF+\/XBPOAOdsdIxAADpGAAJNTtrM3qA689AZ6x0DABAOgZAfs1OO4CAa85AZ2zJdAwAgDmmqABU0C7R6WzabgwJZ+D62JNuvWM6BgCA2jEAz8M9sY4H1I4BACAdA+DVX+UCSMcAAKRj+dULNb0S7iQd1fzvBN+d65Wj6jsh\/7uTjgEAlDRXmre\/prVnteACqxknHIPe1mWOSv5Ryr9H7x+x8qhxtON7zP8ZXK9cr1yv8rleqR0DAFA7RlWvafVzXHt\/XOn4q+bBdcL1yvXKk5UAAMzJM92EWXx+zUh+bUD+\/D7vT9VlKfXvbOURrleuV2RwvZKOAQAUNZ\/v3HJmuu3+3ZlTt0agfX6Pn2PuctvHz\/WK+3K9cr2SjgEAAAAAAADFzN6R3vZHe3ew27YORGH4DDHLbu77P2Q3WQ40FygCI0xpj0xJjST8H9A2qugTZ3cwZqiFnDoHAACwdwwAAODKXLfW5JoXWoocci4NAACmYwAAALDVjW3RvD7n3LOxTmghp8jZBgAAeF2guotlvoi5FG\/mNDaXAwCAu7OXXcjVianG0\/rmF0Vz2q2ONbm2C4mcA+djAADA325CbZkpY95\/x1iT80u2pjdm9\/WHAAAArsTfH0s1LW+VMY\/ht40ipypjD6b88vUvCtkNAABAHWvdrXKwVW8a2zhos+J26qIAAAC8no3ND8ia5FXO7GysY8przscAAAD87RLlkqIcbNWlztcO2kyd+w3IUiYAAMAxsA9t7oWH5dj5Hr6ZqdPKvPjPCAAAdaxNvnJ82zfnmFYyXVdSfgAAoI51166Ce9WjmtzrmDrHtJpduj9lMh8DAIA6doCIuthdj+3byEwAAIA6Nr4salSrY2vxcrHtVWvysz9lKk80I2M+BgAA2pEvdp\/rdfuPmVKZn\/0idaIOBQAA4IPTJ0r1Ute5WI5bW6pn6+N6OZWjHJ54kXM5gyQpD83ZDgAA+GjcFa512vJ6bBYbc2xw0qu96BCWawpUms4CAADA9c+EXCOuUMW0leV+J7IORm2zOT2bzxm98vic7QAAgI9OuQg9xMxYq41K2CPMV+dY96\/likck5Yo+ZtqP6cQAAADTMX+0se8Nz3+w8aRtmo+lCrM5tuf7sR\/LmQcAAHWsPa5C8r45DVtUjMdaTUNdMYuvWa4Y5di3NmB5umdXmgAAALbxw1MfTS4e3ev9KVnxO4wm5VSHsj56fi+aTedIUvYJNpUz\/g1I2zXnwgAAYCu\/y0cb+KN4DynZ3qOvNAEAAJy6jnkMplcuhULyvlK9Fl9iunj\/8nFoEZKy2btjtttRZnbOTz3tTDkAAKDQdCh3H5Q+7xb4P+wHZ2tQAAAA7Vkzi\/j7KUfuLwPGORqK+BbbqrqTuU9JMqVuzexUOQAAoOA\/EuqhCXm\/z\/IYtAEAAK+fR7mNK\/bpY3Qf9o0BAMDescO4CwAAgOnYYfzJ8ysBAADQDhhYub4JjQEAAKDpXwgVKGwAAIA6pkV\/iXhy+epVS0TUc7Y6JzUjBQAAwHTM9U0MLwEAAOAvb8W65YWQFN5fO8dWAAAArOlX8VgSmtX1uvDYv3elAAAAOOjitZB3Ba8TAgAAoI5paeGDI\/VDz4S0qDfOiaJzjXPS9LacX9Aj504AAGA65uM2BgAAAO\/HWk\/vx7i7LRpZWl3JipxuPJard46lnjOlaqYCOVcAAADTseiDn3Q6to4BAAD4eEhVKdYvbZ+ctPe2NOXW+Y+pRg4AANiTPzvXohQ80BIAAGA71wZL\/XHlfM6EVMW0ATkAAOD4OrY0heT1nq8Ytagu57uYaWNpk20MAACAgy5CXnw3AAAA+MpN+F4OtYqcGGZElZPGcAwAANxaG5WsUCGiaFGj214sHEvaGAAAuLU2u69+WbEg9CkihqUuVuQkbQwAANyZj3pUi683Qp2o21iXUy0qpYw2BgAAbsunTnFdVOtzXPHWtrEP\/ZKt3kCWekh9CAAA4Dr8WY8KSfJ+UbxsY0Wv876J+Ts5aYzGAADA7RT769suh7YuiienX0SV08\/HZHUZYzZ2DwAAUMeKQrZoqMgJySVFP2KrC9modHnIpc+\/eh8CAAC4Fi+24v8Rk2WsL3bR\/+e8ePwBAAC4PFOpTRSoI3P+0x+\/VWpybRNaTpfTpFO9HwAAsI2rtGjCgTm\/BQAAcB+mW2vy7bOfk+U0STrZzwUAAOY1ATgnAADTMfrmcracLuEs7wcAADAdAwAAuLD\/AQPLUxmjjeldAAAAAElFTkSuQmCC\"> <audio id=\"offline-sound-press\"><\/audio> <audio id=\"offline-sound-hit\"><\/audio> <audio id=\"offline-sound-reached\"><\/audio><\/div>\n<p><script jstcache=\"0\">(function(){function l(a,b,c){return Function.prototype.call.apply(Array.prototype.slice,arguments)}function m(a,b,c){var e=l(arguments,2);return function(){return b.apply(a,e)}}function n(a,b){var c=new p(b);for(c.h=[a];c.h.length;){var e=c,d=c.h.shift();e.i(d);for(d=d.firstChild;d;d=d.nextSibling)1==d.nodeType&&e.h.push(d)}}function p(a){this.i=a}function q(a){a.style.display=\"\"}function r(a){a.style.display=\"none\"};var t=\/\\s*;\\s*\/;function u(a,b){this.l.apply(this,arguments)}u.prototype.l=function(a,b){this.a||(this.a={});if(b){var c=this.a,e=b.a;for(d in e)c[d]=e[d]}else{var d=this.a;e=v;for(c in e)d[c]=e[c]}this.a.$this=a;this.a.$context=this;this.f=\"undefined\"!=typeof a&&null!=a?a:\"\";b||(this.a.$top=this.f)};var v={$default:null},w=[];function x(a){for(var b in a.a)delete a.a[b];a.f=null;w.push(a)}function y(a,b,c){try{return b.call(c,a.a,a.f)}catch(e){return v.$default}}\nu.prototype.clone=function(a,b,c){if(0<w.length){var e=w.pop();u.call(e,a,this);a=e}else a=new u(a,this);a.a.$index=b;a.a.$count=c;return a};var z;window.trustedTypes&#038;&#038;(z=trustedTypes.createPolicy(\"jstemplate\",{createScript:function(a){return a}}));var A={};function B(a){if(!A[a])try{var b=\"(function(a_, b_) { with (a_) with (b_) return \"+a+\" })\",c=window.trustedTypes?z.createScript(b):b;A[a]=window.eval(c)}catch(e){}return A[a]}\nfunction E(a){var b=[];a=a.split(t);for(var c=0,e=a.length;c<e;++c){var d=a[c].indexOf(\":\");if(!(0>d)){var g=a[c].substr(0,d).replace(\/^\\s+\/,\"\").replace(\/\\s+$\/,\"\");d=B(a[c].substr(d+1));b.push(g,d)}}return b};function F(){}var G=0,H={0:{}},I={},J={},K=[];function L(a){a.__jstcache||n(a,function(b){M(b)})}var N=[[\"jsselect\",B],[\"jsdisplay\",B],[\"jsvalues\",E],[\"jsvars\",E],[\"jseval\",function(a){var b=[];a=a.split(t);for(var c=0,e=a.length;c<e;++c)if(a[c]){var d=B(a[c]);b.push(d)}return b}],[\"transclude\",function(a){return a}],[\"jscontent\",B],[\"jsskip\",B]];\nfunction M(a){if(a.__jstcache)return a.__jstcache;var b=a.getAttribute(\"jstcache\");if(null!=b)return a.__jstcache=H[b];b=K.length=0;for(var c=N.length;b<c;++b){var e=N[b][0],d=a.getAttribute(e);J[e]=d;null!=d&#038;&#038;K.push(e+\"=\"+d)}if(0==K.length)return a.setAttribute(\"jstcache\",\"0\"),a.__jstcache=H[0];var g=K.join(\"&#038;\");if(b=I[g])return a.setAttribute(\"jstcache\",b),a.__jstcache=H[b];var h={};b=0;for(c=N.length;b<c;++b){d=N[b];e=d[0];var f=d[1];d=J[e];null!=d&#038;&#038;(h[e]=f(d))}b=\"\"+ ++G;a.setAttribute(\"jstcache\",\nb);H[b]=h;I[g]=b;return a.__jstcache=h}function P(a,b){a.j.push(b);a.o.push(0)}function Q(a){return a.c.length?a.c.pop():[]}\nF.prototype.g=function(a,b){var c=R(b),e=c.transclude;if(e)(c=S(e))?(b.parentNode.replaceChild(c,b),e=Q(this),e.push(this.g,a,c),P(this,e)):b.parentNode.removeChild(b);else if(c=c.jsselect){c=y(a,c,b);var d=b.getAttribute(\"jsinstance\");var g=!1;d&#038;&#038;(\"*\"==d.charAt(0)?(d=parseInt(d.substr(1),10),g=!0):d=parseInt(d,10));var h=null!=c&#038;&#038;\"object\"==typeof c&#038;&#038;\"number\"==typeof c.length;e=h?c.length:1;var f=h&#038;&#038;0==e;if(h)if(f)d?b.parentNode.removeChild(b):(b.setAttribute(\"jsinstance\",\"*0\"),r(b));else if(q(b),\nnull===d||\"\"===d||g&#038;&#038;d<e-1){g=Q(this);d=d||0;for(h=e-1;d<h;++d){var k=b.cloneNode(!0);b.parentNode.insertBefore(k,b);T(k,c,d);f=a.clone(c[d],d,e);g.push(this.b,f,k,x,f,null)}T(b,c,d);f=a.clone(c[d],d,e);g.push(this.b,f,b,x,f,null);P(this,g)}else d<e?(g=c[d],T(b,c,d),f=a.clone(g,d,e),g=Q(this),g.push(this.b,f,b,x,f,null),P(this,g)):b.parentNode.removeChild(b);else null==c?r(b):(q(b),f=a.clone(c,0,1),g=Q(this),g.push(this.b,f,b,x,f,null),P(this,g))}else this.b(a,b)};\nF.prototype.b=function(a,b){var c=R(b),e=c.jsdisplay;if(e){if(!y(a,e,b)){r(b);return}q(b)}if(e=c.jsvars)for(var d=0,g=e.length;d<g;d+=2){var h=e[d],f=y(a,e[d+1],b);a.a[h]=f}if(e=c.jsvalues)for(d=0,g=e.length;d<g;d+=2)if(f=e[d],h=y(a,e[d+1],b),\"$\"==f.charAt(0))a.a[f]=h;else if(\".\"==f.charAt(0)){f=f.substr(1).split(\".\");for(var k=b,O=f.length,C=0,U=O-1;C<U;++C){var D=f[C];k[D]||(k[D]={});k=k[D]}k[f[O-1]]=h}else f&#038;&#038;(\"boolean\"==typeof h?h?b.setAttribute(f,f):b.removeAttribute(f):b.setAttribute(f,\"\"+h));\nif(e=c.jseval)for(d=0,g=e.length;d<g;++d)y(a,e[d],b);e=c.jsskip;if(!e||!y(a,e,b))if(c=c.jscontent){if(c=\"\"+y(a,c,b),b.innerHTML!=c){for(;b.firstChild;)e=b.firstChild,e.parentNode.removeChild(e);b.appendChild(this.m.createTextNode(c))}}else{c=Q(this);for(e=b.firstChild;e;e=e.nextSibling)1==e.nodeType&#038;&#038;c.push(this.g,a,e);c.length&#038;&#038;P(this,c)}};function R(a){if(a.__jstcache)return a.__jstcache;var b=a.getAttribute(\"jstcache\");return b?a.__jstcache=H[b]:M(a)}\nfunction S(a,b){var c=document;if(b){var e=c.getElementById(a);if(!e){e=b();var d=c.getElementById(\"jsts\");d||(d=c.createElement(\"div\"),d.id=\"jsts\",r(d),d.style.position=\"absolute\",c.body.appendChild(d));var g=c.createElement(\"div\");d.appendChild(g);g.innerHTML=e;e=c.getElementById(a)}c=e}else c=c.getElementById(a);return c?(L(c),c=c.cloneNode(!0),c.removeAttribute(\"id\"),c):null}function T(a,b,c){c==b.length-1?a.setAttribute(\"jsinstance\",\"*\"+c):a.setAttribute(\"jsinstance\",\"\"+c)};window.jstGetTemplate=S;window.JsEvalContext=u;window.jstProcess=function(a,b){var c=new F;L(b);c.m=b?9==b.nodeType?b:b.ownerDocument||document:document;var e=m(c,c.g,a,b),d=c.j=[],g=c.o=[];c.c=[];e();for(var h,f,k;d.length;)h=d[d.length-1],e=g[g.length-1],e>=h.length?(e=c,f=d.pop(),f.length=0,e.c.push(f),g.pop()):(f=h[e++],k=h[e++],h=h[e++],g[g.length-1]=e,f.call(c,k,h))};\n})()<\/script><script jstcache=\"0\">\"use strict\";\n\/\/ Copyright 2012 The Chromium Authors\n\/\/ Use of this source code is governed by a BSD-style license that can be\n\/\/ found in the LICENSE file.\n\/**\n * @fileoverview\n * NOTE: This file is deprecated, and provides only the minimal LoadTimeData\n * functions for places in the code still not using JS modules. Use\n * load_time_data.ts in all new code.\n *\n * This file defines a singleton which provides access to all data\n * that is available as soon as the page's resources are loaded (before DOM\n * content has finished loading). This data includes both localized strings and\n * any data that is important to have ready from a very early stage (e.g. things\n * that must be displayed right away).\n *\n * Note that loadTimeData is not guaranteed to be consistent between page\n * refreshes (https:\/\/crbug.com\/740629) and should not contain values that might\n * change if the page is re-opened later.\n *\/\n\/** @type {!LoadTimeData} *\/\n\/\/ eslint-disable-next-line no-var\nvar loadTimeData;\nclass LoadTimeData {\n    constructor() {\n        \/** @type {?Object} *\/\n        this.data_ = null;\n    }\n    \/**\n     * Sets the backing object.\n     *\n     * Note that there is no getter for |data_| to discourage abuse of the form:\n     *\n     *     var value = loadTimeData.data()['key'];\n     *\n     * @param {Object} value The de-serialized page data.\n     *\/\n    set data(value) {\n        expect(!this.data_, 'Re-setting data.');\n        this.data_ = value;\n    }\n    \/**\n     * @param {string} id An ID of a value that might exist.\n     * @return {boolean} True if |id| is a key in the dictionary.\n     *\/\n    valueExists(id) {\n        return id in this.data_;\n    }\n    \/**\n     * Fetches a value, expecting that it exists.\n     * @param {string} id The key that identifies the desired value.\n     * @return {*} The corresponding value.\n     *\/\n    getValue(id) {\n        expect(this.data_, 'No data. Did you remember to include strings.js?');\n        const value = this.data_[id];\n        expect(typeof value !== 'undefined', 'Could not find value for ' + id);\n        return value;\n    }\n    \/**\n     * As above, but also makes sure that the value is a string.\n     * @param {string} id The key that identifies the desired string.\n     * @return {string} The corresponding string value.\n     *\/\n    getString(id) {\n        const value = this.getValue(id);\n        expectIsType(id, value, 'string');\n        return \/** @type {string} *\/ (value);\n    }\n    \/**\n     * Returns a formatted localized string where $1 to $9 are replaced by the\n     * second to the tenth argument.\n     * @param {string} id The ID of the string we want.\n     * @param {...(string|number)} var_args The extra values to include in the\n     *     formatted output.\n     * @return {string} The formatted string.\n     *\/\n    getStringF(id, var_args) {\n        const value = this.getString(id);\n        if (!value) {\n            return '';\n        }\n        const args = Array.prototype.slice.call(arguments);\n        args[0] = value;\n        return this.substituteString.apply(this, args);\n    }\n    \/**\n     * Returns a formatted localized string where $1 to $9 are replaced by the\n     * second to the tenth argument. Any standalone $ signs must be escaped as\n     * $$.\n     * @param {string} label The label to substitute through.\n     *     This is not an resource ID.\n     * @param {...(string|number)} var_args The extra values to include in the\n     *     formatted output.\n     * @return {string} The formatted string.\n     *\/\n    substituteString(label, var_args) {\n        const varArgs = arguments;\n        return label.replace(\/\\$(.|$|\\n)\/g, function (m) {\n            expect(m.match(\/\\$[$1-9]\/), 'Unescaped $ found in localized string.');\n            return m === '$$' ? '$' : varArgs[m[1]];\n        });\n    }\n    \/**\n     * As above, but also makes sure that the value is a boolean.\n     * @param {string} id The key that identifies the desired boolean.\n     * @return {boolean} The corresponding boolean value.\n     *\/\n    getBoolean(id) {\n        const value = this.getValue(id);\n        expectIsType(id, value, 'boolean');\n        return \/** @type {boolean} *\/ (value);\n    }\n    \/**\n     * As above, but also makes sure that the value is an integer.\n     * @param {string} id The key that identifies the desired number.\n     * @return {number} The corresponding number value.\n     *\/\n    getInteger(id) {\n        const value = this.getValue(id);\n        expectIsType(id, value, 'number');\n        expect(value === Math.floor(value), 'Number isn\\'t integer: ' + value);\n        return \/** @type {number} *\/ (value);\n    }\n    \/**\n     * Override values in loadTimeData with the values found in |replacements|.\n     * @param {Object} replacements The dictionary object of keys to replace.\n     *\/\n    overrideValues(replacements) {\n        expect(typeof replacements === 'object', 'Replacements must be a dictionary object.');\n        for (const key in replacements) {\n            this.data_[key] = replacements[key];\n        }\n    }\n}\n\/**\n * Checks condition, throws error message if expectation fails.\n * @param {*} condition The condition to check for truthiness.\n * @param {string} message The message to display if the check fails.\n *\/\nfunction expect(condition, message) {\n    if (!condition) {\n        throw new Error('Unexpected condition on ' + document.location.href + ': ' + message);\n    }\n}\n\/**\n * Checks that the given value has the given type.\n * @param {string} id The id of the value (only used for error message).\n * @param {*} value The value to check the type on.\n * @param {string} type The type we expect |value| to be.\n *\/\nfunction expectIsType(id, value, type) {\n    expect(typeof value === type, '[' + value + '] (' + id + ') is not a ' + type);\n}\nexpect(!loadTimeData, 'should only include this file once');\nloadTimeData = new LoadTimeData();\n\/\/ Expose |loadTimeData| directly on |window|, since within a JS module the\n\/\/ scope is local and not all files have been updated to import the exported\n\/\/ |loadTimeData| explicitly.\nwindow.loadTimeData = loadTimeData;\nconsole.warn('crbug\/1173575, non-JS module files deprecated.');\n<\/script><script jstcache=\"0\">const pageData = {\"details\":\"Detalles\",\"errorCode\":\"DNS_PROBE_POSSIBLE\",\"fontfamily\":\"'Segoe UI', Tahoma, sans-serif\",\"fontsize\":\"75%\",\"heading\":{\"hostName\":\"viloclub.com.ar\",\"msg\":\"No se puede acceder a este sitio web\"},\"hideDetails\":\"Ocultar detalles\",\"iconClass\":\"icon-generic\",\"language\":\"es\",\"reloadButton\":{\"msg\":\"Volver a cargar\",\"reloadUrl\":\"https:\/\/viloclub.com.ar\/\"},\"suggestionsDetails\":[],\"suggestionsSummaryList\":[{\"summary\":\"\\u003Ca href=\\\"javascript:diagnoseErrors()\\\" id=\\\"diagnose-link\\\">Prueba a ejecutar Diagn\u00f3sticos de red de Windows\\u003C\/a>.\"}],\"summary\":{\"failedUrl\":\"https:\/\/viloclub.com.ar\/\",\"hostName\":\"viloclub.com.ar\",\"msg\":\"No se ha podido encontrar la \\u003Cabbr id=\\\"dnsDefinition\\\">direcci\u00f3n DNS\\u003C\/abbr> de la p\u00e1gina \\u003Cstrong jscontent=\\\"hostName\\\">\\u003C\/strong>. Se est\u00e1 diagnosticando el problema.\"},\"textdirection\":\"ltr\",\"title\":\"viloclub.com.ar\"};loadTimeData.data = pageData;var tp = document.getElementById('t');jstProcess(new JsEvalContext(pageData), tp);<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>&nbsp; &nbsp; &nbsp; No se puede acceder a este sitio web Comprueba si hay un error de escritura en viloclub.com.ar. &nbsp; Si est\u00e1 escrito correctamente, prueba a ejecutar el diagn\u00f3stico de red de Windows. DNS_PROBE_FINISHED_NXDOMAIN null &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Volver a cargar &nbsp; &nbsp; &nbsp; Comprueba si hay un [&#8230;]\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-406","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/viloclub.com.ar\/index.php\/wp-json\/wp\/v2\/pages\/406","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/viloclub.com.ar\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/viloclub.com.ar\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/viloclub.com.ar\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/viloclub.com.ar\/index.php\/wp-json\/wp\/v2\/comments?post=406"}],"version-history":[{"count":5,"href":"https:\/\/viloclub.com.ar\/index.php\/wp-json\/wp\/v2\/pages\/406\/revisions"}],"predecessor-version":[{"id":413,"href":"https:\/\/viloclub.com.ar\/index.php\/wp-json\/wp\/v2\/pages\/406\/revisions\/413"}],"wp:attachment":[{"href":"https:\/\/viloclub.com.ar\/index.php\/wp-json\/wp\/v2\/media?parent=406"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}