fudo

lolicon

May 2nd, 2018
158
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 5 170.20 KB | None | 0 0
  1. <!DOCTYPE html>
  2.  
  3. <html dir="ltr" lang="en" i18n-processed=""><head>
  4.   <meta charset="utf-8">
  5.   <meta name="viewport" content="width=device-width, initial-scale=1.0,
  6.                                 maximum-scale=1.0, user-scalable=no">
  7.   <title>Fudo Tsukiko</title>
  8.   <style>/* Copyright 2017 The Chromium Authors. All rights reserved.
  9.  * Use of this source code is governed by a BSD-style license that can be
  10.  * found in the LICENSE file. */
  11.  
  12. a {
  13.   color: rgb(88, 88, 88);
  14. }
  15.  
  16. body {
  17.   background-color: rgb(247, 247, 247);
  18.   color: rgb(100, 100, 100);
  19. }
  20.  
  21. #details-button {
  22.   background: inherit;
  23.   border: 0;
  24.   float: none;
  25.   margin: 0;
  26.   padding: 10px 0;
  27.   text-transform: uppercase;
  28. }
  29.  
  30. .hidden {
  31.   display: none;
  32. }
  33.  
  34. html {
  35.   -webkit-text-size-adjust: 100%;
  36.   font-size: 125%;
  37. }
  38.  
  39. .icon {
  40.   background-repeat: no-repeat;
  41.   background-size: 100%;
  42. }</style>
  43.   <style>/* Copyright 2014 The Chromium Authors. All rights reserved.
  44.    Use of this source code is governed by a BSD-style license that can be
  45.    found in the LICENSE file. */
  46.  
  47. button {
  48.   border: 0;
  49.   border-radius: 2px;
  50.   box-sizing: border-box;
  51.   color: #fff;
  52.   cursor: pointer;
  53.   float: right;
  54.   font-size: .875em;
  55.   margin: 0;
  56.   padding: 10px 24px;
  57.   transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
  58.   user-select: none;
  59. }
  60.  
  61. [dir='rtl'] button {
  62.   float: left;
  63. }
  64.  
  65. .bad-clock button,
  66. .captive-portal button,
  67. .main-frame-blocked button,
  68. .neterror button,
  69. .offline button,
  70. .ssl button {
  71.   background: rgb(66, 133, 244);
  72. }
  73.  
  74. button:active {
  75.   background: rgb(50, 102, 213);
  76.   outline: 0;
  77. }
  78.  
  79. button:hover {
  80.   box-shadow: 0 1px 3px rgba(0, 0, 0, .50);
  81. }
  82.  
  83. #debugging {
  84.   display: inline;
  85.   overflow: auto;
  86. }
  87.  
  88. .debugging-content {
  89.   line-height: 1em;
  90.   margin-bottom: 0;
  91.   margin-top: 1em;
  92. }
  93.  
  94. .debugging-content-fixed-width {
  95.   display: block;
  96.   font-family: monospace;
  97.   font-size: 1.2em;
  98.   margin-top: 0.5em;
  99. }
  100.  
  101. .debugging-title {
  102.   font-weight: bold;
  103. }
  104.  
  105. #details {
  106.   color: #696969;
  107.   margin: 0 0 50px;
  108. }
  109.  
  110. #details p:not(:first-of-type) {
  111.   margin-top: 20px;
  112. }
  113.  
  114. #details-button:hover {
  115.   box-shadow: inherit;
  116.   text-decoration: underline;
  117. }
  118.  
  119. .error-code {
  120.   color: #646464;
  121.   font-size: .86667em;
  122.   text-transform: uppercase;
  123. }
  124.  
  125. #error-debugging-info {
  126.   font-size: 0.8em;
  127. }
  128.  
  129. h1 {
  130.   color: #333;
  131.   font-size: 1.6em;
  132.   font-weight: normal;
  133.   line-height: 1.25em;
  134.   margin-bottom: 16px;
  135. }
  136.  
  137. h2 {
  138.   font-size: 1.2em;
  139.   font-weight: normal;
  140. }
  141.  
  142. .icon {
  143.   height: 72px;
  144.   margin: 0 0 40px;
  145.   width: 72px;
  146. }
  147.  
  148. input[type=checkbox] {
  149.   opacity: 0;
  150. }
  151.  
  152. input[type=checkbox]:focus ~ .checkbox {
  153.   outline: -webkit-focus-ring-color auto 5px;
  154. }
  155.  
  156. .interstitial-wrapper {
  157.   box-sizing: border-box;
  158.   font-size: 1em;
  159.   line-height: 1.6em;
  160.   margin: 14vh auto 0;
  161.   max-width: 600px;
  162.   width: 100%;
  163. }
  164.  
  165. #main-message > p {
  166.   display: inline;
  167. }
  168.  
  169. #extended-reporting-opt-in {
  170.   font-size: .875em;
  171.   margin-top: 39px;
  172. }
  173.  
  174. #extended-reporting-opt-in label {
  175.   position: relative;
  176.   display: flex;
  177.   align-items: flex-start;
  178. }
  179.  
  180. .nav-wrapper {
  181.   margin-top: 51px;
  182. }
  183.  
  184. .nav-wrapper::after {
  185.   clear: both;
  186.   content: '';
  187.   display: table;
  188.   width: 100%;
  189. }
  190.  
  191. .small-link {
  192.   color: #696969;
  193.   font-size: .875em;
  194. }
  195.  
  196. .checkboxes {
  197.   flex: 0 0 24px;
  198. }
  199.  
  200. .checkbox {
  201.   background: transparent;
  202.   border: 1px solid white;
  203.   border-radius: 2px;
  204.   display: block;
  205.   height: 14px;
  206.   left: 0;
  207.   position: absolute;
  208.   right: 0;
  209.   top: 3px;
  210.   width: 14px;
  211. }
  212.  
  213. .checkbox::before {
  214.   background: transparent;
  215.   border: 2px solid white;
  216.   border-right-width: 0;
  217.   border-top-width: 0;
  218.   content: '';
  219.   height: 4px;
  220.   left: 2px;
  221.   opacity: 0;
  222.   position: absolute;
  223.   top: 3px;
  224.   transform: rotate(-45deg);
  225.   width: 9px;
  226. }
  227.  
  228. input[type=checkbox]:checked ~ .checkbox::before {
  229.   opacity: 1;
  230. }
  231.  
  232. @media (max-width: 700px) {
  233.   .interstitial-wrapper {
  234.     padding: 0 10%;
  235.   }
  236.  
  237.   #error-debugging-info {
  238.     overflow: auto;
  239.   }
  240. }
  241.  
  242. @media (max-height: 600px) {
  243.   .error-code {
  244.     margin-top: 10px;
  245.   }
  246. }
  247.  
  248. @media (max-width: 420px) {
  249.   button,
  250.   [dir='rtl'] button,
  251.   .small-link {
  252.     float: none;
  253.     font-size: .825em;
  254.     font-weight: 400;
  255.     margin: 0;
  256.     text-transform: uppercase;
  257.     width: 100%;
  258.   }
  259.  
  260.   #details {
  261.     margin: 20px 0 20px 0;
  262.   }
  263.  
  264.   #details p:not(:first-of-type) {
  265.     margin-top: 10px;
  266.   }
  267.  
  268.   #details-button {
  269.     display: block;
  270.     margin-top: 20px;
  271.     text-align: center;
  272.     width: 100%;
  273.   }
  274.  
  275.   .interstitial-wrapper {
  276.     padding: 0 5%;
  277.   }
  278.  
  279.   #extended-reporting-opt-in {
  280.     margin-top: 24px;
  281.   }
  282.  
  283.   .nav-wrapper {
  284.     margin-top: 30px;
  285.   }
  286. }
  287.  
  288. /**
  289.  * Mobile specific styling.
  290.  * Navigation buttons are anchored to the bottom of the screen.
  291.  * Details message replaces the top content in its own scrollable area.
  292.  */
  293.  
  294. @media (max-width: 420px) {
  295.   #details-button {
  296.     border: 0;
  297.     margin: 8px 0 0;
  298.   }
  299.  
  300.   .secondary-button {
  301.     -webkit-margin-end: 0;
  302.     margin-top: 16px;
  303.   }
  304. }
  305.  
  306. /* Fixed nav. */
  307. @media (min-width: 240px) and (max-width: 420px) and
  308.        (min-height: 401px),
  309.        (min-width: 421px) and (min-height: 240px) and
  310.        (max-height: 560px) {
  311.   body .nav-wrapper {
  312.     background: #f7f7f7;
  313.     bottom: 0;
  314.     box-shadow: 0 -22px 40px rgb(247, 247, 247);
  315.     left: 0;
  316.     margin: 0 auto;
  317.     max-width: 736px;
  318.     padding-left: 24px;
  319.     padding-right: 24px;
  320.     position: fixed;
  321.     right: 0;
  322.     width: 100%;
  323.     z-index: 2;
  324.   }
  325.  
  326.   .interstitial-wrapper {
  327.     max-width: 736px;
  328.   }
  329.  
  330.   #details,
  331.   #main-content {
  332.     padding-bottom: 40px;
  333.   }
  334.  
  335.   #details {
  336.     padding-top: 5.5vh;
  337.   }
  338.  
  339.   #details-button:hover {
  340.     box-shadow: none;
  341.   }
  342. }
  343.  
  344. @media (max-width: 420px) and (orientation: portrait),
  345.        (max-height: 560px) {
  346.   body {
  347.     margin: 0 auto;
  348.   }
  349.  
  350.   button,
  351.   [dir='rtl'] button,
  352.   button.small-link {
  353.     font-family: Roboto-Regular,Helvetica;
  354.     font-size: .933em;
  355.     font-weight: 600;
  356.     margin: 6px 0;
  357.     text-transform: uppercase;
  358.     transform: translatez(0);
  359.   }
  360.  
  361.   .nav-wrapper {
  362.     box-sizing: border-box;
  363.     padding-bottom: 8px;
  364.     width: 100%;
  365.   }
  366.  
  367.   .error-code {
  368.     margin-top: 0;
  369.   }
  370.  
  371.   #details {
  372.     box-sizing: border-box;
  373.     height: auto;
  374.     margin: 0;
  375.     opacity: 1;
  376.     transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
  377.   }
  378.  
  379.   #details.hidden,
  380.   #main-content.hidden {
  381.     display: block;
  382.     height: 0;
  383.     opacity: 0;
  384.     overflow: hidden;
  385.     padding-bottom: 0;
  386.     transition: none;
  387.   }
  388.  
  389.   #details-button {
  390.     padding-bottom: 16px;
  391.     padding-top: 16px;
  392.   }
  393.  
  394.   h1 {
  395.     font-size: 1.5em;
  396.     margin-bottom: 8px;
  397.   }
  398.  
  399.   .icon {
  400.     margin-bottom: 5.69vh;
  401.   }
  402.  
  403.   .interstitial-wrapper {
  404.     box-sizing: border-box;
  405.     margin: 7vh auto 12px;
  406.     padding: 0 24px;
  407.     position: relative;
  408.   }
  409.  
  410.   .interstitial-wrapper p {
  411.     font-size: .95em;
  412.     line-height: 1.61em;
  413.     margin-top: 8px;
  414.   }
  415.  
  416.   #main-content {
  417.     margin: 0;
  418.     transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1);
  419.   }
  420.  
  421.   .small-link {
  422.     border: 0;
  423.   }
  424.  
  425.   .suggested-left > #control-buttons,
  426.   .suggested-right > #control-buttons {
  427.     float: none;
  428.     margin: 0;
  429.   }
  430. }
  431.  
  432. @media (min-width: 421px) and (min-height: 500px) and (max-height: 560px) {
  433.   .interstitial-wrapper {
  434.     margin-top: 10vh;
  435.   }
  436. }
  437.  
  438. @media (min-height: 400px) and (orientation:portrait) {
  439.   .interstitial-wrapper {
  440.     margin-bottom: 145px;
  441.   }
  442. }
  443.  
  444. @media (min-height: 299px) {
  445.   .nav-wrapper {
  446.     padding-bottom: 16px;
  447.   }
  448. }
  449.  
  450. @media (min-height: 500px) and (max-height: 650px) and (max-width: 414px) and
  451.        (orientation: portrait) {
  452.   .interstitial-wrapper {
  453.     margin-top: 7vh;
  454.   }
  455. }
  456.  
  457. @media (min-height: 650px) and (max-width: 414px) and (orientation: portrait) {
  458.   .interstitial-wrapper {
  459.     margin-top: 10vh;
  460.   }
  461. }
  462.  
  463. /* Small mobile screens. No fixed nav. */
  464. @media (max-height: 400px) and (orientation: portrait),
  465.        (max-height: 239px) and (orientation: landscape),
  466.        (max-width: 419px) and (max-height: 399px) {
  467.   .interstitial-wrapper {
  468.     display: flex;
  469.     flex-direction: column;
  470.     margin-bottom: 0;
  471.   }
  472.  
  473.   #details {
  474.     flex: 1 1 auto;
  475.     order: 0;
  476.   }
  477.  
  478.   #main-content {
  479.     flex: 1 1 auto;
  480.     order: 0;
  481.   }
  482.  
  483.   .nav-wrapper {
  484.     flex: 0 1 auto;
  485.     margin-top: 8px;
  486.     order: 1;
  487.     padding-left: 0;
  488.     padding-right: 0;
  489.     position: relative;
  490.     width: 100%;
  491.   }
  492. }
  493.  
  494. @media (max-width: 239px) and (orientation: portrait) {
  495.   .nav-wrapper {
  496.     padding-left: 0;
  497.     padding-right: 0;
  498.   }
  499. }
  500. </style>
  501.   <style>/* Copyright 2013 The Chromium Authors. All rights reserved.
  502.  * Use of this source code is governed by a BSD-style license that can be
  503.  * found in the LICENSE file. */
  504.  
  505. /* Don't use the main frame div when the error is in a subframe. */
  506. html[subframe] #main-frame-error {
  507.   display: none;
  508. }
  509.  
  510. /* Don't use the subframe error div when the error is in a main frame. */
  511. html:not([subframe]) #sub-frame-error {
  512.   display: none;
  513. }
  514.  
  515. #diagnose-button {
  516.   -webkit-margin-start: 0;
  517.   float: none;
  518.   margin-bottom: 10px;
  519.   margin-top: 20px;
  520. }
  521.  
  522. h1 {
  523.   margin-top: 0;
  524.   word-wrap: break-word;
  525. }
  526.  
  527. h1 span {
  528.   font-weight: 500;
  529. }
  530.  
  531. h2 {
  532.   color: #666;
  533.   font-size: 1.2em;
  534.   font-weight: normal;
  535.   margin: 10px 0;
  536. }
  537.  
  538. a {
  539.   color: rgb(17, 85, 204);
  540.   text-decoration: none;
  541. }
  542.  
  543. .icon {
  544.   -webkit-user-select: none;
  545.   display: inline-block;
  546. }
  547.  
  548. .icon-generic {
  549.   /**
  550.    * Can't access chrome://theme/IDR_ERROR_NETWORK_GENERIC from an untrusted
  551.    * renderer process, so embed the resource manually.
  552.    */
  553.   content: -webkit-image-set(
  554.       url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAENJREFUeF7tzbEJACEQRNGBLeAasBCza2lLEGx0CxFGG9hBMDDxRy/72O9FMnIFapGylsu1fgoBdkXfUHLrQgdfrlJN1BdYBjQQm3UAAAAASUVORK5CYII=) 1x,
  555.       url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQAQMAAADdiHD7AAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAFJJREFUeF7t0cENgDAMQ9FwYgxG6WjpaIzCCAxQxVggFuDiCvlLOeRdHR9yzjncHVoq3npu+wQUrUuJHylSTmBaespJyJQoObUeyxDQb3bEm5Au81c0pSCD8HYAAAAASUVORK5CYII=) 2x);
  556. }
  557.  
  558. .icon-offline {
  559.   content: -webkit-image-set(
  560.       url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAGxJREFUeF7tyMEJwkAQRuFf5ipMKxYQiJ3Z2nSwrWwBA0+DQZcdxEOueaePp9+dQZFB7GpUcURSVU66yVNFj6LFICatThZB6r/ko/pbRpUgilY0Cbw5sNmb9txGXUKyuH7eV25x39DtJXUNPQGJtWFV+BT/QAAAAABJRU5ErkJggg==) 1x,
  561.       url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQBAMAAAAVaP+LAAAAGFBMVEUAAABTU1NNTU1TU1NPT09SUlJSUlJTU1O8B7DEAAAAB3RSTlMAoArVKvVgBuEdKgAAAJ1JREFUeF7t1TEOwyAMQNG0Q6/UE+RMXD9d/tC6womIFSL9P+MnAYOXeTIzMzMzMzMzaz8J9Ri6HoITmuHXhISE8nEh9yxDh55aCEUoTGbbQwjqHwIkRAEiIaG0+0AA9VBMaE89Rogeoww936MQrWdBr4GN/z0IAdQ6nQ/FIpRXDwHcA+JIJcQowQAlFUA0MfQpXLlVQfkzR4igS6ENjknm/wiaGhsAAAAASUVORK5CYII=) 2x);
  562.   position: relative;
  563. }
  564.  
  565. .icon-disabled {
  566.   content: -webkit-image-set(
  567.       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,
  568.       url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOAAAACQAQMAAAArwfVjAAAABlBMVEVMaXFTU1OXUj8tAAAAAXRSTlMAQObYZgAAAYdJREFUeF7F1EFqwzAUBNARAmVj0FZe5QoBH6BX+dn4GlY2PYNzGx/A0CvkCIJuvIraKJKbgBvzf2g62weDGD7CYggpfFReis4J0ey9EGFIiEQQojFSlA9kSIiqd0KkFjKsewgRbStEN19mxUPTtmW9HQ/h6tyqNQ8NlSMZdzyE6qkoE0trVYGFm0n1WYeBhduzwbwBC7voS+vIxfeMjeaiLxsMMtQNwMPtuew+DjzcTHk8YMfDknEcIUOtf2lVfgVH3K4Xv5PRYAXRVMtItIJ3rfaCIVn9DsTH2NxisAVRex2Hh3hX+/mRUR08bAwPEYsI51ZxWH4Q0SpicQRXeyEaIug48FEdegARfMz/tADVsRciwTAxW308ehmC2gLraC+YCbV3QoTZexa+zegAEW5PhhgYfmbvJgcRqngGByOSXdFJcLk2JeDPEN0kxe1JhIt5FiFA+w+ItMELsUyPF2IaJ4aILqb4FbxPwhImwj6JauKgDUCYaxmYIsd4KXdMjIC9ItB5Bn4BNRwsG0XM2nwAAAAASUVORK5CYII=) 2x);
  569.   width: 112px;
  570. }
  571.  
  572. .error-code {
  573.   display: block;
  574.   font-size: .8em;
  575. }
  576.  
  577. #content-top {
  578.   margin: 20px;
  579. }
  580.  
  581. #help-box-inner {
  582.   background-color: #f9f9f9;
  583.   border-top: 1px solid #EEE;
  584.   color: #444;
  585.   padding: 20px;
  586.   text-align: start;
  587. }
  588.  
  589. .hidden {
  590.   display: none;
  591. }
  592.  
  593. #suggestion {
  594.   margin-top: 15px;
  595. }
  596.  
  597. #suggestions-list p {
  598.   -webkit-margin-after: 0;
  599. }
  600.  
  601. #suggestions-list ul {
  602.   margin-top: 0;
  603. }
  604.  
  605. .single-suggestion {
  606.   list-style-type: none;
  607.   padding-left: 0;
  608. }
  609.  
  610. #short-suggestion {
  611.   margin-top: 5px;
  612. }
  613.  
  614. #sub-frame-error-details {
  615.  
  616.   color: #8F8F8F;
  617.  
  618.   /* Not done on mobile for performance reasons. */
  619.   text-shadow: 0 1px 0 rgba(255,255,255,0.3);
  620.  
  621. }
  622.  
  623. [jscontent=hostName],
  624. [jscontent=failedUrl] {
  625.   overflow-wrap: break-word;
  626. }
  627.  
  628. #search-container {
  629.   /* Prevents a space between controls. */
  630.   display: flex;
  631.   margin-top: 20px;
  632. }
  633.  
  634. #search-box {
  635.   border: 1px solid #cdcdcd;
  636.   flex-grow: 1;
  637.   font-size: 1em;
  638.   height: 26px;
  639.   margin-right: 0;
  640.   padding: 1px 9px;
  641. }
  642.  
  643. #search-box:focus {
  644.   border: 1px solid rgb(93, 154, 255);
  645.   outline: none;
  646. }
  647.  
  648. #search-button {
  649.   border: none;
  650.   border-bottom-left-radius: 0;
  651.   border-top-left-radius: 0;
  652.   box-shadow: none;
  653.   display: flex;
  654.   height: 30px;
  655.   margin: 0;
  656.   padding: 0;
  657.   width: 60px;
  658. }
  659.  
  660. #search-image {
  661.   content:
  662.       -webkit-image-set(
  663.           url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAQAAAB+HTb/AAAArElEQVR4Xn3NsUoCUBzG0XvB3U0chR4geo5qihpt6gkCx0bXFsMERWj2KWqIanAvmlUUoQapwU6g4l8H5bd9Z/iSPS0hu/RqZqrncBuzLl7U3Rn4cSpQFTeroejJl1Lgs7f4ceDPdeBMXYp86gaONYJkY83AnqHiGk9wHnjk16PKgo5N9BUCkzPf5j6M0PfuVg5MymoetFwoaKAlB26WdXAvJ7u5mezitqtkT//7Sv/u96CaLQAAAABJRU5ErkJggg==) 1x,
  664.           url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAeCAQAAACVzLYUAAABYElEQVR4Xr3VMUuVURzH8XO98jgkGikENkRD0KRGDUVDQy0h2SiC4IuIiktL4AvQt1CDBJUJwo1KXXS6cWdHw7tcjWwoC5Hrx+UZgnNO5CXiO/75jD/+QZf9MzjskVU7DrU1zRv9G9ir5hsA4Nii83+GA9ZI1nI1D6tWAE1TRlQMuuuFDthzMQefgo4nKr+f3dIGDdUUHPYD1ISoMQdgJgUfgqaKEOcxWE/BVTArJBvwC0cGY7gNLgiZNsD1GP4EPVn4EtyLYRuczcJ34HYMP4E7GdajDS7FcB48z8AJ8FmI4TjouBkzZ2yBuRQMlsButIZ+dfDVUBqOaIHvavpLVHXfFmAqv45r9gEHNr3y3hcAfLSgSMPgiiZR+6Z9AMuKNAwqpjUcA2h55pxgAfBWkYRlQ254YMJloaxPHbCkiGCymL5RlLA7GnRDXyuC7uhicLoKdRyaDE5Pl00K//93nABqPgBDK8sfWgAAAABJRU5ErkJggg==) 2x);
  665.   margin: auto;
  666. }
  667.  
  668. .secondary-button {
  669.   -webkit-margin-end: 16px;
  670.   background: #d9d9d9;
  671.   color: #696969;
  672. }
  673.  
  674. .snackbar {
  675.   background: #323232;
  676.   border-radius: 2px;
  677.   bottom: 24px;
  678.   box-sizing: border-box;
  679.   color: #fff;
  680.   font-size: .87em;
  681.   left: 24px;
  682.   max-width: 568px;
  683.   min-width: 288px;
  684.   opacity: 0;
  685.   padding: 16px 24px 12px;
  686.   position: fixed;
  687.   transform: translateY(90px);
  688.   will-change: opacity, transform;
  689.   z-index: 999;
  690. }
  691.  
  692. .snackbar-show {
  693.   -webkit-animation:
  694.     show-snackbar .25s cubic-bezier(0.0, 0.0, 0.2, 1) forwards,
  695.     hide-snackbar .25s cubic-bezier(0.4, 0.0, 1, 1) forwards 5s;
  696. }
  697.  
  698. @-webkit-keyframes show-snackbar {
  699.   100% {
  700.     opacity: 1;
  701.     transform: translateY(0);
  702.   }
  703. }
  704.  
  705. @-webkit-keyframes hide-snackbar {
  706.   0% {
  707.     opacity: 1;
  708.     transform: translateY(0);
  709.   }
  710.   100% {
  711.     opacity: 0;
  712.     transform: translateY(90px);
  713.   }
  714. }
  715.  
  716. .suggestions {
  717.   margin-top: 18px;
  718. }
  719.  
  720. .suggestion-header {
  721.   font-weight: bold;
  722.   margin-bottom: 4px;
  723. }
  724.  
  725. .suggestion-body {
  726.   color: #777;
  727. }
  728.  
  729. /* Increase line height at higher resolutions. */
  730. @media (min-width: 641px) and (min-height: 641px) {
  731.   #help-box-inner {
  732.     line-height: 18px;
  733.   }
  734. }
  735.  
  736. /* Decrease padding at low sizes. */
  737. @media (max-width: 640px), (max-height: 640px) {
  738.   h1 {
  739.     margin: 0 0 15px;
  740.   }
  741.   #content-top {
  742.     margin: 15px;
  743.   }
  744.   #help-box-inner {
  745.     padding: 20px;
  746.   }
  747.   .suggestions {
  748.     margin-top: 10px;
  749.   }
  750.   .suggestion-header {
  751.     margin-bottom: 0;
  752.   }
  753. }
  754.  
  755. /* Don't allow overflow when in a subframe. */
  756. html[subframe] body {
  757.   overflow: hidden;
  758. }
  759.  
  760. #sub-frame-error {
  761.   -webkit-align-items: center;
  762.   background-color: #DDD;
  763.   display: -webkit-flex;
  764.   -webkit-flex-flow: column;
  765.   height: 100%;
  766.   -webkit-justify-content: center;
  767.   left: 0;
  768.   position: absolute;
  769.   text-align: center;
  770.   top: 0;
  771.   transition: background-color .2s ease-in-out;
  772.   width: 100%;
  773. }
  774.  
  775. #sub-frame-error:hover {
  776.   background-color: #EEE;
  777. }
  778.  
  779. #sub-frame-error .icon-generic {
  780.   margin: 0 0 16px;
  781. }
  782.  
  783. #sub-frame-error-details {
  784.   margin: 0 10px;
  785.   text-align: center;
  786.   visibility: hidden;
  787. }
  788.  
  789. /* Show details only when hovering. */
  790. #sub-frame-error:hover #sub-frame-error-details {
  791.   visibility: visible;
  792. }
  793.  
  794. /* If the iframe is too small, always hide the error code. */
  795. /* TODO(mmenke): See if overflow: no-display works better, once supported. */
  796. @media (max-width: 200px), (max-height: 95px) {
  797.   #sub-frame-error-details {
  798.     display: none;
  799.   }
  800. }
  801.  
  802. /* Adjust icon for small embedded frames in apps. */
  803. @media (max-height: 100px) {
  804.   #sub-frame-error .icon-generic {
  805.     height: auto;
  806.     margin: 0;
  807.     padding-top: 0;
  808.     width: 25px;
  809.   }
  810. }
  811.  
  812. /* details-button is special; it's a <button> element that looks like a link. */
  813. #details-button {
  814.   box-shadow: none;
  815.   min-width: 0;
  816. }
  817.  
  818. /* Styles for platform dependent separation of controls and details button. */
  819. .suggested-left > #control-buttons,
  820. .suggested-left #stale-load-button,
  821. .suggested-right > #details-button {
  822.   float: left;
  823. }
  824.  
  825. .suggested-right > #control-buttons,
  826. .suggested-right #stale-load-button,
  827. .suggested-left > #details-button {
  828.   float: right;
  829. }
  830.  
  831. .suggested-left .secondary-button {
  832.   -webkit-margin-end: 0px;
  833.   -webkit-margin-start: 16px;
  834. }
  835.  
  836. #details-button.singular {
  837.   float: none;
  838. }
  839.  
  840. /* download-button shows both icon and text. */
  841. #download-button {
  842.   box-shadow: none;
  843.   position: relative;
  844. }
  845.  
  846. #download-button:before {
  847.   -webkit-margin-end: 4px;
  848.   background: -webkit-image-set(
  849.       url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAO0lEQVQ4y2NgGArgPxIY1YChsOE/LtBAmpYG0mxpIOSDBpKUo2lpIDZxNJCkHKqlYZAla3RAHQ1DFgAARRroHyLNTwwAAAAASUVORK5CYII=) 1x,
  850.       url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAAZElEQVRYw+3Ruw3AMAwDUY3OzZUmRRD4E9iim9wNwAdbEURHyk4AAAAATiCVK8lLyPsKeT9K3lsownnunfkPxO78hKiYHxBV8x2icr5BVM+/CMf8g3DN34Rzns6ViwHUAUQ/6wIAd5Km7l6c8AAAAABJRU5ErkJggg==) 2x)
  851.     no-repeat;
  852.   content: '';
  853.   display: inline-block;
  854.   width: 24px;
  855.   height: 24px;
  856.   vertical-align: middle;
  857. }
  858.  
  859. #download-button:disabled {
  860.   background: rgb(180, 206, 249);
  861.   color: rgb(255, 255, 255);
  862. }
  863.  
  864. #buttons::after {
  865.   clear: both;
  866.   content: '';
  867.   display: block;
  868.   width: 100%;
  869. }
  870.  
  871. /* Offline page */
  872. .offline {
  873.   transition: -webkit-filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1),
  874.               background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);
  875.   will-change: -webkit-filter, background-color;
  876. }
  877.  
  878. .offline #main-message > p {
  879.   display: none;
  880. }
  881.  
  882. .offline.inverted {
  883.   -webkit-filter: invert(100%);
  884.   background-color: #000;
  885. }
  886.  
  887. .offline .interstitial-wrapper {
  888.   color: #2b2b2b;
  889.   font-size: 1em;
  890.   line-height: 1.55;
  891.   margin: 0 auto;
  892.   max-width: 600px;
  893.   padding-top: 100px;
  894.   width: 100%;
  895. }
  896.  
  897. .offline .runner-container {
  898.   direction: ltr;
  899.   height: 150px;
  900.   max-width: 600px;
  901.   overflow: hidden;
  902.   position: absolute;
  903.   top: 35px;
  904.   width: 44px;
  905. }
  906.  
  907. .offline .runner-canvas {
  908.   height: 150px;
  909.   max-width: 600px;
  910.   opacity: 1;
  911.   overflow: hidden;
  912.   position: absolute;
  913.   top: 0;
  914.   z-index: 2;
  915. }
  916.  
  917. .offline .controller {
  918.   background: rgba(247,247,247, .1);
  919.   height: 100vh;
  920.   left: 0;
  921.   position: absolute;
  922.   top: 0;
  923.   width: 100vw;
  924.   z-index: 1;
  925. }
  926.  
  927. #offline-resources {
  928.   display: none;
  929. }
  930.  
  931. @media (max-width: 420px) {
  932.   .suggested-left > #control-buttons,
  933.   .suggested-right > #control-buttons {
  934.     float: none;
  935.   }
  936.  
  937.   .snackbar {
  938.     left: 0;
  939.     bottom: 0;
  940.     width: 100%;
  941.     border-radius: 0;
  942.   }
  943. }
  944.  
  945. @media (max-height: 350px) {
  946.   h1 {
  947.     margin: 0 0 15px;
  948.   }
  949.  
  950.   .icon-offline {
  951.     margin: 0 0 10px;
  952.   }
  953.  
  954.   .interstitial-wrapper {
  955.     margin-top: 5%;
  956.   }
  957.  
  958.   .nav-wrapper {
  959.     margin-top: 30px;
  960.   }
  961. }
  962.  
  963. @media (min-width: 420px) and (max-width: 736px) and
  964.        (min-height: 240px) and (max-height: 420px) and
  965.        (orientation:landscape) {
  966.   .interstitial-wrapper {
  967.     margin-bottom: 100px;
  968.   }
  969. }
  970.  
  971. @media (max-width: 360px) and (max-height: 480px) {
  972.   .offline .interstitial-wrapper {
  973.     padding-top: 60px;
  974.   }
  975.  
  976.   .offline .runner-container {
  977.     top: 8px;
  978.   }
  979. }
  980.  
  981. @media (min-height: 240px) and (orientation: landscape) {
  982.   .offline .interstitial-wrapper {
  983.     margin-bottom: 90px;
  984.   }
  985.  
  986.   .icon-offline {
  987.     margin-bottom: 20px;
  988.   }
  989. }
  990.  
  991. @media (max-height: 320px) and (orientation: landscape) {
  992.   .icon-offline {
  993.     margin-bottom: 0;
  994.   }
  995.  
  996.   .offline .runner-container {
  997.     top: 10px;
  998.   }
  999. }
  1000.  
  1001. @media (max-width: 240px) {
  1002.   button {
  1003.     padding-left: 12px;
  1004.     padding-right: 12px;
  1005.   }
  1006.  
  1007.   .interstitial-wrapper {
  1008.     overflow: inherit;
  1009.     padding: 0 8px;
  1010.   }
  1011. }
  1012.  
  1013. @media (max-width: 120px) {
  1014.   button {
  1015.     width: auto;
  1016.   }
  1017. }
  1018.  
  1019. .arcade-mode,
  1020. .arcade-mode .runner-container,
  1021. .arcade-mode .runner-canvas {
  1022.   image-rendering: pixelated;
  1023.   max-width: 100%;
  1024.   overflow: hidden;
  1025. }
  1026.  
  1027. .arcade-mode #buttons,
  1028. .arcade-mode #main-content {
  1029.   opacity: 0;
  1030.   overflow: hidden;
  1031. }
  1032.  
  1033. .arcade-mode .interstitial-wrapper {
  1034.   height: 100vh;
  1035.   max-width: 100%;
  1036.   overflow: hidden;
  1037. }
  1038.  
  1039. .arcade-mode .runner-container {
  1040.   left: 0;
  1041.   margin: auto;
  1042.   right: 0;
  1043.   transform-origin: top center;
  1044.   transition: transform 250ms cubic-bezier(0.4, 0.0, 1, 1) .4s;
  1045.   z-index: 2;
  1046. }
  1047. </style>
  1048.   <script>// Copyright 2017 The Chromium Authors. All rights reserved.
  1049. // Use of this source code is governed by a BSD-style license that can be
  1050. // found in the LICENSE file.
  1051.  
  1052. // This is the shared code for security interstitials. It is used for both SSL
  1053. // interstitials and Safe Browsing interstitials.
  1054.  
  1055. // Should match security_interstitials::SecurityInterstitialCommand
  1056. /** @enum| {string} */
  1057. var SecurityInterstitialCommandId = {
  1058.   CMD_DONT_PROCEED: 0,
  1059.   CMD_PROCEED: 1,
  1060.   // Ways for user to get more information
  1061.   CMD_SHOW_MORE_SECTION: 2,
  1062.   CMD_OPEN_HELP_CENTER: 3,
  1063.   CMD_OPEN_DIAGNOSTIC: 4,
  1064.   // Primary button actions
  1065.   CMD_RELOAD: 5,
  1066.   CMD_OPEN_DATE_SETTINGS: 6,
  1067.   CMD_OPEN_LOGIN: 7,
  1068.   // Safe Browsing Extended Reporting
  1069.   CMD_DO_REPORT: 8,
  1070.   CMD_DONT_REPORT: 9,
  1071.   CMD_OPEN_REPORTING_PRIVACY: 10,
  1072.   CMD_OPEN_WHITEPAPER: 11,
  1073.   // Report a phishing error.
  1074.   CMD_REPORT_PHISHING_ERROR: 12
  1075. };
  1076.  
  1077. var HIDDEN_CLASS = 'hidden';
  1078.  
  1079. /**
  1080.  * A convenience method for sending commands to the parent page.
  1081.  * @param {string} cmd  The command to send.
  1082.  */
  1083. function sendCommand(cmd) {
  1084.   if (window.certificateErrorPageController) {
  1085.     switch (cmd) {
  1086.       case SecurityInterstitialCommandId.CMD_DONT_PROCEED:
  1087.         certificateErrorPageController.dontProceed();
  1088.         break;
  1089.       case SecurityInterstitialCommandId.CMD_PROCEED:
  1090.         certificateErrorPageController.proceed();
  1091.         break;
  1092.       case SecurityInterstitialCommandId.CMD_SHOW_MORE_SECTION:
  1093.         certificateErrorPageController.showMoreSection();
  1094.         break;
  1095.       case SecurityInterstitialCommandId.CMD_OPEN_HELP_CENTER:
  1096.         certificateErrorPageController.openHelpCenter();
  1097.         break;
  1098.       case SecurityInterstitialCommandId.CMD_OPEN_DIAGNOSTIC:
  1099.         certificateErrorPageController.openDiagnostic();
  1100.         break;
  1101.       case SecurityInterstitialCommandId.CMD_RELOAD:
  1102.         certificateErrorPageController.reload();
  1103.         break;
  1104.       case SecurityInterstitialCommandId.CMD_OPEN_DATE_SETTINGS:
  1105.         certificateErrorPageController.openDateSettings();
  1106.         break;
  1107.       case SecurityInterstitialCommandId.CMD_OPEN_LOGIN:
  1108.         certificateErrorPageController.openLogin();
  1109.         break;
  1110.       case SecurityInterstitialCommandId.CMD_DO_REPORT:
  1111.         certificateErrorPageController.doReport();
  1112.         break;
  1113.       case SecurityInterstitialCommandId.CMD_DONT_REPORT:
  1114.         certificateErrorPageController.dontReport();
  1115.         break;
  1116.       case SecurityInterstitialCommandId.CMD_OPEN_REPORTING_PRIVACY:
  1117.         certificateErrorPageController.openReportingPrivacy();
  1118.         break;
  1119.       case SecurityInterstitialCommandId.CMD_OPEN_WHITEPAPER:
  1120.         certificateErrorPageController.openWhitepaper();
  1121.         break;
  1122.       case SecurityInterstitialCommandId.CMD_REPORT_PHISHING_ERROR:
  1123.         certificateErrorPageController.reportPhishingError();
  1124.         break;
  1125.     }
  1126.     return;
  1127.   }
  1128. //
  1129.   window.domAutomationController.send(cmd);
  1130. //
  1131. //
  1132. }
  1133.  
  1134. /**
  1135.  * Call this to stop clicks on <a href="#"> links from scrolling to the top of
  1136.  * the page (and possibly showing a # in the link).
  1137.  */
  1138. function preventDefaultOnPoundLinkClicks() {
  1139.   document.addEventListener('click', function(e) {
  1140.     var anchor = findAncestor(/** @type {Node} */ (e.target), function(el) {
  1141.       return el.tagName == 'A';
  1142.     });
  1143.     // Use getAttribute() to prevent URL normalization.
  1144.     if (anchor && anchor.getAttribute('href') == '#')
  1145.      e.preventDefault();
  1146.   });
  1147. }
  1148. </script>
  1149.   <script>// Copyright 2015 The Chromium Authors. All rights reserved.
  1150. // Use of this source code is governed by a BSD-style license that can be
  1151. // found in the LICENSE file.
  1152.  
  1153. var mobileNav = false;
  1154.  
  1155. /**
  1156.  * For small screen mobile the navigation buttons are moved
  1157.  * below the advanced text.
  1158.  */
  1159. function onResize() {
  1160.   var helpOuterBox = document.querySelector('#details');
  1161.   var mainContent = document.querySelector('#main-content');
  1162.   var mediaQuery = '(min-width: 240px) and (max-width: 420px) and ' +
  1163.       '(min-height: 401px), ' +
  1164.       '(max-height: 560px) and (min-height: 240px) and ' +
  1165.       '(min-width: 421px)';
  1166.  
  1167.   var detailsHidden = helpOuterBox.classList.contains(HIDDEN_CLASS);
  1168.   var runnerContainer = document.querySelector('.runner-container');
  1169.  
  1170.   // Check for change in nav status.
  1171.   if (mobileNav != window.matchMedia(mediaQuery).matches) {
  1172.     mobileNav = !mobileNav;
  1173.  
  1174.     // Handle showing the top content / details sections according to state.
  1175.     if (mobileNav) {
  1176.       mainContent.classList.toggle(HIDDEN_CLASS, !detailsHidden);
  1177.       helpOuterBox.classList.toggle(HIDDEN_CLASS, detailsHidden);
  1178.       if (runnerContainer) {
  1179.         runnerContainer.classList.toggle(HIDDEN_CLASS, !detailsHidden);
  1180.       }
  1181.     } else if (!detailsHidden) {
  1182.       // Non mobile nav with visible details.
  1183.       mainContent.classList.remove(HIDDEN_CLASS);
  1184.       helpOuterBox.classList.remove(HIDDEN_CLASS);
  1185.       if (runnerContainer) {
  1186.         runnerContainer.classList.remove(HIDDEN_CLASS);
  1187.       }
  1188.     }
  1189.   }
  1190. }
  1191.  
  1192. function setupMobileNav() {
  1193.   window.addEventListener('resize', onResize);
  1194.   onResize();
  1195. }
  1196.  
  1197. document.addEventListener('DOMContentLoaded', setupMobileNav);
  1198. </script>
  1199.   <script>// Copyright 2013 The Chromium Authors. All rights reserved.
  1200. // Use of this source code is governed by a BSD-style license that can be
  1201. // found in the LICENSE file.
  1202.  
  1203. function toggleHelpBox() {
  1204.   var helpBoxOuter = document.getElementById('details');
  1205.   helpBoxOuter.classList.toggle(HIDDEN_CLASS);
  1206.   var detailsButton = document.getElementById('details-button');
  1207.   if (helpBoxOuter.classList.contains(HIDDEN_CLASS))
  1208.     detailsButton.innerText = detailsButton.detailsText;
  1209.   else
  1210.     detailsButton.innerText = detailsButton.hideDetailsText;
  1211.  
  1212.   // Details appears over the main content on small screens.
  1213.   if (mobileNav) {
  1214.     document.getElementById('main-content').classList.toggle(HIDDEN_CLASS);
  1215.     var runnerContainer = document.querySelector('.runner-container');
  1216.     if (runnerContainer) {
  1217.       runnerContainer.classList.toggle(HIDDEN_CLASS);
  1218.     }
  1219.   }
  1220. }
  1221.  
  1222. function diagnoseErrors() {
  1223. //
  1224.     if (window.errorPageController)
  1225.       errorPageController.diagnoseErrorsButtonClick();
  1226. //
  1227. //
  1228. }
  1229.  
  1230. // Subframes use a different layout but the same html file.  This is to make it
  1231. // easier to support platforms that load the error page via different
  1232. // mechanisms (Currently just iOS).
  1233. if (window.top.location != window.location)
  1234.   document.documentElement.setAttribute('subframe', '');
  1235.  
  1236. // Re-renders the error page using |strings| as the dictionary of values.
  1237. // Used by NetErrorTabHelper to update DNS error pages with probe results.
  1238. function updateForDnsProbe(strings) {
  1239.   var context = new JsEvalContext(strings);
  1240.   jstProcess(context, document.getElementById('t'));
  1241. }
  1242.  
  1243. // Given the classList property of an element, adds an icon class to the list
  1244. // and removes the previously-
  1245. function updateIconClass(classList, newClass) {
  1246.   var oldClass;
  1247.  
  1248.   if (classList.hasOwnProperty('last_icon_class')) {
  1249.     oldClass = classList['last_icon_class'];
  1250.     if (oldClass == newClass)
  1251.       return;
  1252.   }
  1253.  
  1254.   classList.add(newClass);
  1255.   if (oldClass !== undefined)
  1256.     classList.remove(oldClass);
  1257.  
  1258.   classList['last_icon_class'] = newClass;
  1259.  
  1260.   if (newClass == 'icon-offline') {
  1261.     document.body.classList.add('offline');
  1262.     new Runner('.interstitial-wrapper');
  1263.   } else {
  1264.     document.body.classList.add('neterror');
  1265.   }
  1266. }
  1267.  
  1268. // Does a search using |baseSearchUrl| and the text in the search box.
  1269. function search(baseSearchUrl) {
  1270.   var searchTextNode = document.getElementById('search-box');
  1271.   document.location = baseSearchUrl + searchTextNode.value;
  1272.   return false;
  1273. }
  1274.  
  1275. // Use to track clicks on elements generated by the navigation correction
  1276. // service.  If |trackingId| is negative, the element does not come from the
  1277. // correction service.
  1278. function trackClick(trackingId) {
  1279.   // This can't be done with XHRs because XHRs are cancelled on navigation
  1280.   // start, and because these are cross-site requests.
  1281.   if (trackingId >= 0 && errorPageController)
  1282.    errorPageController.trackClick(trackingId);
  1283. }
  1284.  
  1285. // Called when an <a> tag generated by the navigation correction service is
  1286. // clicked.  Separate function from trackClick so the resources don't have to
  1287. // be updated if new data is added to jstdata.
  1288. function linkClicked(jstdata) {
  1289.   trackClick(jstdata.trackingId);
  1290. }
  1291.  
  1292. // Implements button clicks.  This function is needed during the transition
  1293. // between implementing these in trunk chromium and implementing them in
  1294. // iOS.
  1295. function reloadButtonClick(url) {
  1296.   if (window.errorPageController) {
  1297.     errorPageController.reloadButtonClick();
  1298.   } else {
  1299.     location = url;
  1300.   }
  1301. }
  1302.  
  1303. function showSavedCopyButtonClick() {
  1304.   if (window.errorPageController) {
  1305.     errorPageController.showSavedCopyButtonClick();
  1306.   }
  1307. }
  1308.  
  1309. function downloadButtonClick() {
  1310.   if (window.errorPageController) {
  1311.     errorPageController.downloadButtonClick();
  1312.     var downloadButton = document.getElementById('download-button');
  1313.     downloadButton.disabled = true;
  1314.     downloadButton.textContent = downloadButton.disabledText;
  1315.   }
  1316. }
  1317.  
  1318. function detailsButtonClick() {
  1319.   if (window.errorPageController)
  1320.     errorPageController.detailsButtonClick();
  1321. }
  1322.  
  1323. /**
  1324.  * Replace the reload button with the Google cached copy suggestion.
  1325.  */
  1326. function setUpCachedButton(buttonStrings) {
  1327.   var reloadButton = document.getElementById('reload-button');
  1328.  
  1329.   reloadButton.textContent = buttonStrings.msg;
  1330.   var url = buttonStrings.cacheUrl;
  1331.   var trackingId = buttonStrings.trackingId;
  1332.   reloadButton.onclick = function(e) {
  1333.     e.preventDefault();
  1334.     trackClick(trackingId);
  1335.     if (window.errorPageController) {
  1336.       errorPageController.trackCachedCopyButtonClick();
  1337.     }
  1338.     location = url;
  1339.   };
  1340.   reloadButton.style.display = '';
  1341.   document.getElementById('control-buttons').hidden = false;
  1342. }
  1343.  
  1344. var primaryControlOnLeft = true;
  1345. //
  1346.  
  1347. function onDocumentLoad() {
  1348.   var controlButtonDiv = document.getElementById('control-buttons');
  1349.   var reloadButton = document.getElementById('reload-button');
  1350.   var detailsButton = document.getElementById('details-button');
  1351.   var showSavedCopyButton = document.getElementById('show-saved-copy-button');
  1352.   var downloadButton = document.getElementById('download-button');
  1353.  
  1354.   var reloadButtonVisible = loadTimeData.valueExists('reloadButton') &&
  1355.      loadTimeData.getValue('reloadButton').msg;
  1356.   var showSavedCopyButtonVisible =
  1357.       loadTimeData.valueExists('showSavedCopyButton') &&
  1358.      loadTimeData.getValue('showSavedCopyButton').msg;
  1359.   var downloadButtonVisible =
  1360.       loadTimeData.valueExists('downloadButton') &&
  1361.      loadTimeData.getValue('downloadButton').msg;
  1362.  
  1363.   var primaryButton, secondaryButton;
  1364.   if (showSavedCopyButton.primary) {
  1365.     primaryButton = showSavedCopyButton;
  1366.     secondaryButton = reloadButton;
  1367.   } else {
  1368.     primaryButton = reloadButton;
  1369.     secondaryButton = showSavedCopyButton;
  1370.   }
  1371.  
  1372.   // Sets up the proper button layout for the current platform.
  1373.   if (primaryControlOnLeft) {
  1374.     buttons.classList.add('suggested-left');
  1375.     controlButtonDiv.insertBefore(secondaryButton, primaryButton);
  1376.   } else {
  1377.     buttons.classList.add('suggested-right');
  1378.     controlButtonDiv.insertBefore(primaryButton, secondaryButton);
  1379.   }
  1380.  
  1381.   // Check for Google cached copy suggestion.
  1382.   if (loadTimeData.valueExists('cacheButton')) {
  1383.     setUpCachedButton(loadTimeData.getValue('cacheButton'));
  1384.   }
  1385.  
  1386.   if (reloadButton.style.display == 'none' &&
  1387.      showSavedCopyButton.style.display == 'none' &&
  1388.      downloadButton.style.display == 'none') {
  1389.    detailsButton.classList.add('singular');
  1390.   }
  1391.  
  1392.   // Show control buttons.
  1393.   if (reloadButtonVisible || showSavedCopyButtonVisible ||
  1394.       downloadButtonVisible) {
  1395.     controlButtonDiv.hidden = false;
  1396.  
  1397.     // Set the secondary button state in the cases of two call to actions.
  1398.     if ((reloadButtonVisible || downloadButtonVisible) &&
  1399.        showSavedCopyButtonVisible) {
  1400.      secondaryButton.classList.add('secondary-button');
  1401.     }
  1402.   }
  1403. }
  1404.  
  1405. document.addEventListener('DOMContentLoaded', onDocumentLoad);
  1406. </script>
  1407.   <script>// Copyright (c) 2014 The Chromium Authors. All rights reserved.
  1408. // Use of this source code is governed by a BSD-style license that can be
  1409. // found in the LICENSE file.
  1410. (function() {
  1411. 'use strict';
  1412. /**
  1413.  * T-Rex runner.
  1414.  * @param {string} outerContainerId Outer containing element id.
  1415.  * @param {Object} opt_config
  1416.  * @constructor
  1417.  * @export
  1418.  */
  1419. function Runner(outerContainerId, opt_config) {
  1420.   // Singleton
  1421.   if (Runner.instance_) {
  1422.     return Runner.instance_;
  1423.   }
  1424.   Runner.instance_ = this;
  1425.  
  1426.   this.outerContainerEl = document.querySelector(outerContainerId);
  1427.   this.containerEl = null;
  1428.   this.snackbarEl = null;
  1429.  
  1430.   this.config = opt_config || Runner.config;
  1431.   // Logical dimensions of the container.
  1432.   this.dimensions = Runner.defaultDimensions;
  1433.  
  1434.   this.canvas = null;
  1435.   this.canvasCtx = null;
  1436.  
  1437.   this.tRex = null;
  1438.  
  1439.   this.distanceMeter = null;
  1440.   this.distanceRan = 0;
  1441.  
  1442.   this.highestScore = 0;
  1443.  
  1444.   this.time = 0;
  1445.   this.runningTime = 0;
  1446.   this.msPerFrame = 1000 / FPS;
  1447.   this.currentSpeed = this.config.SPEED;
  1448.  
  1449.   this.obstacles = [];
  1450.  
  1451.   this.activated = false; // Whether the easter egg has been activated.
  1452.   this.playing = false; // Whether the game is currently in play state.
  1453.   this.crashed = false;
  1454.   this.paused = false;
  1455.   this.inverted = false;
  1456.   this.invertTimer = 0;
  1457.   this.resizeTimerId_ = null;
  1458.  
  1459.   this.playCount = 0;
  1460.  
  1461.   // Sound FX.
  1462.   this.audioBuffer = null;
  1463.   this.soundFx = {};
  1464.  
  1465.   // Global web audio context for playing sounds.
  1466.   this.audioContext = null;
  1467.  
  1468.   // Images.
  1469.   this.images = {};
  1470.   this.imagesLoaded = 0;
  1471.  
  1472.   if (this.isDisabled()) {
  1473.     this.setupDisabledRunner();
  1474.   } else {
  1475.     this.loadImages();
  1476.   }
  1477. }
  1478. window['Runner'] = Runner;
  1479.  
  1480.  
  1481. /**
  1482.  * Default game width.
  1483.  * @const
  1484.  */
  1485. var DEFAULT_WIDTH = 600;
  1486.  
  1487. /**
  1488.  * Frames per second.
  1489.  * @const
  1490.  */
  1491. var FPS = 60;
  1492.  
  1493. /** @const */
  1494. var IS_HIDPI = window.devicePixelRatio > 1;
  1495.  
  1496. /** @const */
  1497. var IS_IOS = /iPad|iPhone|iPod/.test(window.navigator.platform);
  1498.  
  1499. /** @const */
  1500. var IS_MOBILE = /Android/.test(window.navigator.userAgent) || IS_IOS;
  1501.  
  1502. /** @const */
  1503. var IS_TOUCH_ENABLED = 'ontouchstart' in window;
  1504.  
  1505. /** @const */
  1506. var ARCADE_MODE_URL = 'chrome://dino/';
  1507.  
  1508. /**
  1509.  * Default game configuration.
  1510.  * @enum {number}
  1511.  */
  1512. Runner.config = {
  1513.   ACCELERATION: 0.001,
  1514.   BG_CLOUD_SPEED: 0.2,
  1515.   BOTTOM_PAD: 10,
  1516.   CLEAR_TIME: 3000,
  1517.   CLOUD_FREQUENCY: 0.5,
  1518.   GAMEOVER_CLEAR_TIME: 750,
  1519.   GAP_COEFFICIENT: 0.6,
  1520.   GRAVITY: 0.6,
  1521.   INITIAL_JUMP_VELOCITY: 12,
  1522.   INVERT_FADE_DURATION: 12000,
  1523.   INVERT_DISTANCE: 700,
  1524.   MAX_BLINK_COUNT: 3,
  1525.   MAX_CLOUDS: 6,
  1526.   MAX_OBSTACLE_LENGTH: 3,
  1527.   MAX_OBSTACLE_DUPLICATION: 2,
  1528.   MAX_SPEED: 13,
  1529.   MIN_JUMP_HEIGHT: 35,
  1530.   MOBILE_SPEED_COEFFICIENT: 1.2,
  1531.   RESOURCE_TEMPLATE_ID: 'audio-resources',
  1532.   SPEED: 6,
  1533.   SPEED_DROP_COEFFICIENT: 3,
  1534.   ARCADE_MODE_INITIAL_TOP_POSITION: 35,
  1535.   ARCADE_MODE_TOP_POSITION_PERCENT: 0.1
  1536. };
  1537.  
  1538.  
  1539. /**
  1540.  * Default dimensions.
  1541.  * @enum {string}
  1542.  */
  1543. Runner.defaultDimensions = {
  1544.   WIDTH: DEFAULT_WIDTH,
  1545.   HEIGHT: 150
  1546. };
  1547.  
  1548.  
  1549. /**
  1550.  * CSS class names.
  1551.  * @enum {string}
  1552.  */
  1553. Runner.classes = {
  1554.   ARCADE_MODE: 'arcade-mode',
  1555.   CANVAS: 'runner-canvas',
  1556.   CONTAINER: 'runner-container',
  1557.   CRASHED: 'crashed',
  1558.   ICON: 'icon-offline',
  1559.   INVERTED: 'inverted',
  1560.   SNACKBAR: 'snackbar',
  1561.   SNACKBAR_SHOW: 'snackbar-show',
  1562.   TOUCH_CONTROLLER: 'controller'
  1563. };
  1564.  
  1565.  
  1566. /**
  1567.  * Sprite definition layout of the spritesheet.
  1568.  * @enum {Object}
  1569.  */
  1570. Runner.spriteDefinition = {
  1571.   LDPI: {
  1572.     CACTUS_LARGE: {x: 332, y: 2},
  1573.     CACTUS_SMALL: {x: 228, y: 2},
  1574.     CLOUD: {x: 86, y: 2},
  1575.     HORIZON: {x: 2, y: 54},
  1576.     MOON: {x: 484, y: 2},
  1577.     PTERODACTYL: {x: 134, y: 2},
  1578.     RESTART: {x: 2, y: 2},
  1579.     TEXT_SPRITE: {x: 655, y: 2},
  1580.     TREX: {x: 848, y: 2},
  1581.     STAR: {x: 645, y: 2}
  1582.   },
  1583.   HDPI: {
  1584.     CACTUS_LARGE: {x: 652, y: 2},
  1585.     CACTUS_SMALL: {x: 446, y: 2},
  1586.     CLOUD: {x: 166, y: 2},
  1587.     HORIZON: {x: 2, y: 104},
  1588.     MOON: {x: 954, y: 2},
  1589.     PTERODACTYL: {x: 260, y: 2},
  1590.     RESTART: {x: 2, y: 2},
  1591.     TEXT_SPRITE: {x: 1294, y: 2},
  1592.     TREX: {x: 1678, y: 2},
  1593.     STAR: {x: 1276, y: 2}
  1594.   }
  1595. };
  1596.  
  1597.  
  1598. /**
  1599.  * Sound FX. Reference to the ID of the audio tag on interstitial page.
  1600.  * @enum {string}
  1601.  */
  1602. Runner.sounds = {
  1603.   BUTTON_PRESS: 'offline-sound-press',
  1604.   HIT: 'offline-sound-hit',
  1605.   SCORE: 'offline-sound-reached'
  1606. };
  1607.  
  1608.  
  1609. /**
  1610.  * Key code mapping.
  1611.  * @enum {Object}
  1612.  */
  1613. Runner.keycodes = {
  1614.   JUMP: {'38': 1, '32': 1},  // Up, spacebar
  1615.   DUCK: {'40': 1},  // Down
  1616.   RESTART: {'13': 1}  // Enter
  1617. };
  1618.  
  1619.  
  1620. /**
  1621.  * Runner event names.
  1622.  * @enum {string}
  1623.  */
  1624. Runner.events = {
  1625.   ANIM_END: 'webkitAnimationEnd',
  1626.   CLICK: 'click',
  1627.   KEYDOWN: 'keydown',
  1628.   KEYUP: 'keyup',
  1629.   MOUSEDOWN: 'mousedown',
  1630.   MOUSEUP: 'mouseup',
  1631.   RESIZE: 'resize',
  1632.   TOUCHEND: 'touchend',
  1633.   TOUCHSTART: 'touchstart',
  1634.   VISIBILITY: 'visibilitychange',
  1635.   BLUR: 'blur',
  1636.   FOCUS: 'focus',
  1637.   LOAD: 'load'
  1638. };
  1639.  
  1640. Runner.prototype = {
  1641.   /**
  1642.    * Whether the easter egg has been disabled. CrOS enterprise enrolled devices.
  1643.    * @return {boolean}
  1644.    */
  1645.   isDisabled: function() {
  1646.     return loadTimeData && loadTimeData.valueExists('disabledEasterEgg');
  1647.   },
  1648.  
  1649.   /**
  1650.    * For disabled instances, set up a snackbar with the disabled message.
  1651.    */
  1652.   setupDisabledRunner: function() {
  1653.     this.containerEl = document.createElement('div');
  1654.     this.containerEl.className = Runner.classes.SNACKBAR;
  1655.     this.containerEl.textContent = loadTimeData.getValue('disabledEasterEgg');
  1656.     this.outerContainerEl.appendChild(this.containerEl);
  1657.  
  1658.     // Show notification when the activation key is pressed.
  1659.     document.addEventListener(Runner.events.KEYDOWN, function(e) {
  1660.       if (Runner.keycodes.JUMP[e.keyCode]) {
  1661.         this.containerEl.classList.add(Runner.classes.SNACKBAR_SHOW);
  1662.         document.querySelector('.icon').classList.add('icon-disabled');
  1663.       }
  1664.     }.bind(this));
  1665.   },
  1666.  
  1667.   /**
  1668.    * Setting individual settings for debugging.
  1669.    * @param {string} setting
  1670.    * @param {*} value
  1671.    */
  1672.   updateConfigSetting: function(setting, value) {
  1673.     if (setting in this.config && value != undefined) {
  1674.      this.config[setting] = value;
  1675.  
  1676.       switch (setting) {
  1677.         case 'GRAVITY':
  1678.         case 'MIN_JUMP_HEIGHT':
  1679.         case 'SPEED_DROP_COEFFICIENT':
  1680.           this.tRex.config[setting] = value;
  1681.           break;
  1682.         case 'INITIAL_JUMP_VELOCITY':
  1683.           this.tRex.setJumpVelocity(value);
  1684.           break;
  1685.         case 'SPEED':
  1686.           this.setSpeed(value);
  1687.           break;
  1688.       }
  1689.     }
  1690.   },
  1691.  
  1692.   /**
  1693.    * Cache the appropriate image sprite from the page and get the sprite sheet
  1694.    * definition.
  1695.    */
  1696.   loadImages: function() {
  1697.     if (IS_HIDPI) {
  1698.       Runner.imageSprite = document.getElementById('offline-resources-2x');
  1699.       this.spriteDef = Runner.spriteDefinition.HDPI;
  1700.     } else {
  1701.       Runner.imageSprite = document.getElementById('offline-resources-1x');
  1702.       this.spriteDef = Runner.spriteDefinition.LDPI;
  1703.     }
  1704.  
  1705.     if (Runner.imageSprite.complete) {
  1706.       this.init();
  1707.     } else {
  1708.       // If the images are not yet loaded, add a listener.
  1709.       Runner.imageSprite.addEventListener(Runner.events.LOAD,
  1710.           this.init.bind(this));
  1711.     }
  1712.   },
  1713.  
  1714.   /**
  1715.    * Load and decode base 64 encoded sounds.
  1716.    */
  1717.   loadSounds: function() {
  1718.     if (!IS_IOS) {
  1719.       this.audioContext = new AudioContext();
  1720.  
  1721.       var resourceTemplate =
  1722.           document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content;
  1723.  
  1724.       for (var sound in Runner.sounds) {
  1725.         var soundSrc =
  1726.             resourceTemplate.getElementById(Runner.sounds[sound]).src;
  1727.         soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1);
  1728.         var buffer = decodeBase64ToArrayBuffer(soundSrc);
  1729.  
  1730.         // Async, so no guarantee of order in array.
  1731.         this.audioContext.decodeAudioData(buffer, function(index, audioData) {
  1732.             this.soundFx[index] = audioData;
  1733.           }.bind(this, sound));
  1734.       }
  1735.     }
  1736.   },
  1737.  
  1738.   /**
  1739.    * Sets the game speed. Adjust the speed accordingly if on a smaller screen.
  1740.    * @param {number} opt_speed
  1741.    */
  1742.   setSpeed: function(opt_speed) {
  1743.     var speed = opt_speed || this.currentSpeed;
  1744.  
  1745.     // Reduce the speed on smaller mobile screens.
  1746.     if (this.dimensions.WIDTH < DEFAULT_WIDTH) {
  1747.      var mobileSpeed = speed * this.dimensions.WIDTH / DEFAULT_WIDTH *
  1748.          this.config.MOBILE_SPEED_COEFFICIENT;
  1749.      this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed;
  1750.     } else if (opt_speed) {
  1751.       this.currentSpeed = opt_speed;
  1752.     }
  1753.   },
  1754.  
  1755.   /**
  1756.    * Game initialiser.
  1757.    */
  1758.   init: function() {
  1759.     // Hide the static icon.
  1760.     document.querySelector('.' + Runner.classes.ICON).style.visibility =
  1761.         'hidden';
  1762.  
  1763.     this.adjustDimensions();
  1764.     this.setSpeed();
  1765.  
  1766.     this.containerEl = document.createElement('div');
  1767.     this.containerEl.className = Runner.classes.CONTAINER;
  1768.  
  1769.     // Player canvas container.
  1770.     this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH,
  1771.         this.dimensions.HEIGHT, Runner.classes.PLAYER);
  1772.  
  1773.     this.canvasCtx = this.canvas.getContext('2d');
  1774.     this.canvasCtx.fillStyle = '#f7f7f7';
  1775.     this.canvasCtx.fill();
  1776.     Runner.updateCanvasScaling(this.canvas);
  1777.  
  1778.     // Horizon contains clouds, obstacles and the ground.
  1779.     this.horizon = new Horizon(this.canvas, this.spriteDef, this.dimensions,
  1780.         this.config.GAP_COEFFICIENT);
  1781.  
  1782.     // Distance meter
  1783.     this.distanceMeter = new DistanceMeter(this.canvas,
  1784.           this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH);
  1785.  
  1786.     // Draw t-rex
  1787.     this.tRex = new Trex(this.canvas, this.spriteDef.TREX);
  1788.  
  1789.     this.outerContainerEl.appendChild(this.containerEl);
  1790.  
  1791.     if (IS_MOBILE) {
  1792.       this.createTouchController();
  1793.     }
  1794.  
  1795.     this.startListening();
  1796.     this.update();
  1797.  
  1798.     window.addEventListener(Runner.events.RESIZE,
  1799.         this.debounceResize.bind(this));
  1800.   },
  1801.  
  1802.   /**
  1803.    * Create the touch controller. A div that covers whole screen.
  1804.    */
  1805.   createTouchController: function() {
  1806.     this.touchController = document.createElement('div');
  1807.     this.touchController.className = Runner.classes.TOUCH_CONTROLLER;
  1808.   },
  1809.  
  1810.   /**
  1811.    * Debounce the resize event.
  1812.    */
  1813.   debounceResize: function() {
  1814.     if (!this.resizeTimerId_) {
  1815.       this.resizeTimerId_ =
  1816.           setInterval(this.adjustDimensions.bind(this), 250);
  1817.     }
  1818.   },
  1819.  
  1820.   /**
  1821.    * Adjust game space dimensions on resize.
  1822.    */
  1823.   adjustDimensions: function() {
  1824.     clearInterval(this.resizeTimerId_);
  1825.     this.resizeTimerId_ = null;
  1826.  
  1827.     var boxStyles = window.getComputedStyle(this.outerContainerEl);
  1828.     var padding = Number(boxStyles.paddingLeft.substr(0,
  1829.         boxStyles.paddingLeft.length - 2));
  1830.  
  1831.     this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2;
  1832.     if (this.isArcadeMode()) {
  1833.       this.dimensions.WIDTH = Math.min(DEFAULT_WIDTH, this.dimensions.WIDTH);
  1834.       if (this.activated) {
  1835.         this.setArcadeModeContainerScale();
  1836.       }
  1837.     }
  1838.  
  1839.     // Redraw the elements back onto the canvas.
  1840.     if (this.canvas) {
  1841.       this.canvas.width = this.dimensions.WIDTH;
  1842.       this.canvas.height = this.dimensions.HEIGHT;
  1843.  
  1844.       Runner.updateCanvasScaling(this.canvas);
  1845.  
  1846.       this.distanceMeter.calcXPos(this.dimensions.WIDTH);
  1847.       this.clearCanvas();
  1848.       this.horizon.update(0, 0, true);
  1849.       this.tRex.update(0);
  1850.  
  1851.       // Outer container and distance meter.
  1852.       if (this.playing || this.crashed || this.paused) {
  1853.         this.containerEl.style.width = this.dimensions.WIDTH + 'px';
  1854.         this.containerEl.style.height = this.dimensions.HEIGHT + 'px';
  1855.         this.distanceMeter.update(0, Math.ceil(this.distanceRan));
  1856.         this.stop();
  1857.       } else {
  1858.         this.tRex.draw(0, 0);
  1859.       }
  1860.  
  1861.       // Game over panel.
  1862.       if (this.crashed && this.gameOverPanel) {
  1863.        this.gameOverPanel.updateDimensions(this.dimensions.WIDTH);
  1864.         this.gameOverPanel.draw();
  1865.       }
  1866.     }
  1867.   },
  1868.  
  1869.   /**
  1870.    * Play the game intro.
  1871.    * Canvas container width expands out to the full width.
  1872.    */
  1873.   playIntro: function() {
  1874.     if (!this.activated && !this.crashed) {
  1875.      this.playingIntro = true;
  1876.       this.tRex.playingIntro = true;
  1877.  
  1878.       // CSS animation definition.
  1879.       var keyframes = '@-webkit-keyframes intro { ' +
  1880.             'from { width:' + Trex.config.WIDTH + 'px }' +
  1881.             'to { width: ' + this.dimensions.WIDTH + 'px }' +
  1882.           '}';
  1883.       document.styleSheets[0].insertRule(keyframes, 0);
  1884.  
  1885.       this.containerEl.addEventListener(Runner.events.ANIM_END,
  1886.           this.startGame.bind(this));
  1887.  
  1888.       this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';
  1889.       this.containerEl.style.width = this.dimensions.WIDTH + 'px';
  1890.  
  1891.       if (this.touchController) {
  1892.         this.outerContainerEl.appendChild(this.touchController);
  1893.       }
  1894.       this.playing = true;
  1895.       this.activated = true;
  1896.     } else if (this.crashed) {
  1897.       this.restart();
  1898.     }
  1899.   },
  1900.  
  1901.  
  1902.   /**
  1903.    * Update the game status to started.
  1904.    */
  1905.   startGame: function() {
  1906.     if (this.isArcadeMode()) {
  1907.       this.setArcadeMode();
  1908.     }
  1909.     this.runningTime = 0;
  1910.     this.playingIntro = false;
  1911.     this.tRex.playingIntro = false;
  1912.     this.containerEl.style.webkitAnimation = '';
  1913.     this.playCount++;
  1914.  
  1915.     // Handle tabbing off the page. Pause the current game.
  1916.     document.addEventListener(Runner.events.VISIBILITY,
  1917.           this.onVisibilityChange.bind(this));
  1918.  
  1919.     window.addEventListener(Runner.events.BLUR,
  1920.           this.onVisibilityChange.bind(this));
  1921.  
  1922.     window.addEventListener(Runner.events.FOCUS,
  1923.           this.onVisibilityChange.bind(this));
  1924.   },
  1925.  
  1926.   clearCanvas: function() {
  1927.     this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH,
  1928.         this.dimensions.HEIGHT);
  1929.   },
  1930.  
  1931.   /**
  1932.    * Update the game frame and schedules the next one.
  1933.    */
  1934.   update: function() {
  1935.     this.updatePending = false;
  1936.  
  1937.     var now = getTimeStamp();
  1938.     var deltaTime = now - (this.time || now);
  1939.     this.time = now;
  1940.  
  1941.     if (this.playing) {
  1942.       this.clearCanvas();
  1943.  
  1944.       if (this.tRex.jumping) {
  1945.         this.tRex.updateJump(deltaTime);
  1946.       }
  1947.  
  1948.       this.runningTime += deltaTime;
  1949.       var hasObstacles = this.runningTime > this.config.CLEAR_TIME;
  1950.  
  1951.       // First jump triggers the intro.
  1952.       if (this.tRex.jumpCount == 1 && !this.playingIntro) {
  1953.        this.playIntro();
  1954.       }
  1955.  
  1956.       // The horizon doesn't move until the intro is over.
  1957.       if (this.playingIntro) {
  1958.         this.horizon.update(0, this.currentSpeed, hasObstacles);
  1959.       } else {
  1960.         deltaTime = !this.activated ? 0 : deltaTime;
  1961.         this.horizon.update(deltaTime, this.currentSpeed, hasObstacles,
  1962.             this.inverted);
  1963.       }
  1964.  
  1965.       // Check for collisions.
  1966.       var collision = hasObstacles &&
  1967.          checkForCollision(this.horizon.obstacles[0], this.tRex);
  1968.  
  1969.       if (!collision) {
  1970.         this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;
  1971.  
  1972.         if (this.currentSpeed < this.config.MAX_SPEED) {
  1973.          this.currentSpeed += this.config.ACCELERATION;
  1974.        }
  1975.      } else {
  1976.        this.gameOver();
  1977.      }
  1978.  
  1979.      var playAchievementSound = this.distanceMeter.update(deltaTime,
  1980.          Math.ceil(this.distanceRan));
  1981.  
  1982.      if (playAchievementSound) {
  1983.        this.playSound(this.soundFx.SCORE);
  1984.      }
  1985.  
  1986.      // Night mode.
  1987.      if (this.invertTimer > this.config.INVERT_FADE_DURATION) {
  1988.         this.invertTimer = 0;
  1989.         this.invertTrigger = false;
  1990.         this.invert();
  1991.       } else if (this.invertTimer) {
  1992.         this.invertTimer += deltaTime;
  1993.       } else {
  1994.         var actualDistance =
  1995.             this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan));
  1996.  
  1997.         if (actualDistance > 0) {
  1998.           this.invertTrigger = !(actualDistance %
  1999.               this.config.INVERT_DISTANCE);
  2000.  
  2001.           if (this.invertTrigger && this.invertTimer === 0) {
  2002.            this.invertTimer += deltaTime;
  2003.             this.invert();
  2004.           }
  2005.         }
  2006.       }
  2007.     }
  2008.  
  2009.     if (this.playing || (!this.activated &&
  2010.        this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) {
  2011.      this.tRex.update(deltaTime);
  2012.       this.scheduleNextUpdate();
  2013.     }
  2014.   },
  2015.  
  2016.   /**
  2017.    * Event handler.
  2018.    */
  2019.   handleEvent: function(e) {
  2020.     return (function(evtType, events) {
  2021.       switch (evtType) {
  2022.         case events.KEYDOWN:
  2023.         case events.TOUCHSTART:
  2024.         case events.MOUSEDOWN:
  2025.           this.onKeyDown(e);
  2026.           break;
  2027.         case events.KEYUP:
  2028.         case events.TOUCHEND:
  2029.         case events.MOUSEUP:
  2030.           this.onKeyUp(e);
  2031.           break;
  2032.       }
  2033.     }.bind(this))(e.type, Runner.events);
  2034.   },
  2035.  
  2036.   /**
  2037.    * Bind relevant key / mouse / touch listeners.
  2038.    */
  2039.   startListening: function() {
  2040.     // Keys.
  2041.     document.addEventListener(Runner.events.KEYDOWN, this);
  2042.     document.addEventListener(Runner.events.KEYUP, this);
  2043.  
  2044.     if (IS_MOBILE) {
  2045.       // Mobile only touch devices.
  2046.       this.touchController.addEventListener(Runner.events.TOUCHSTART, this);
  2047.       this.touchController.addEventListener(Runner.events.TOUCHEND, this);
  2048.       this.containerEl.addEventListener(Runner.events.TOUCHSTART, this);
  2049.     } else {
  2050.       // Mouse.
  2051.       document.addEventListener(Runner.events.MOUSEDOWN, this);
  2052.       document.addEventListener(Runner.events.MOUSEUP, this);
  2053.     }
  2054.   },
  2055.  
  2056.   /**
  2057.    * Remove all listeners.
  2058.    */
  2059.   stopListening: function() {
  2060.     document.removeEventListener(Runner.events.KEYDOWN, this);
  2061.     document.removeEventListener(Runner.events.KEYUP, this);
  2062.  
  2063.     if (IS_MOBILE) {
  2064.       this.touchController.removeEventListener(Runner.events.TOUCHSTART, this);
  2065.       this.touchController.removeEventListener(Runner.events.TOUCHEND, this);
  2066.       this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this);
  2067.     } else {
  2068.       document.removeEventListener(Runner.events.MOUSEDOWN, this);
  2069.       document.removeEventListener(Runner.events.MOUSEUP, this);
  2070.     }
  2071.   },
  2072.  
  2073.   /**
  2074.    * Process keydown.
  2075.    * @param {Event} e
  2076.    */
  2077.   onKeyDown: function(e) {
  2078.     // Prevent native page scrolling whilst tapping on mobile.
  2079.     if (IS_MOBILE && this.playing) {
  2080.      e.preventDefault();
  2081.     }
  2082.  
  2083.     if (!this.crashed && !this.paused) {
  2084.      if (Runner.keycodes.JUMP[e.keyCode] ||
  2085.          e.type == Runner.events.TOUCHSTART) {
  2086.        e.preventDefault();
  2087.         // Starting the game for the first time.
  2088.         if (!this.playing) {
  2089.           this.loadSounds();
  2090.           this.playing = true;
  2091.           this.update();
  2092.           if (window.errorPageController) {
  2093.             errorPageController.trackEasterEgg();
  2094.           }
  2095.         }
  2096.         // Start jump.
  2097.         if (!this.tRex.jumping && !this.tRex.ducking) {
  2098.          this.playSound(this.soundFx.BUTTON_PRESS);
  2099.           this.tRex.startJump(this.currentSpeed);
  2100.         }
  2101.       } else if (this.playing && Runner.keycodes.DUCK[e.keyCode]) {
  2102.        e.preventDefault();
  2103.         if (this.tRex.jumping) {
  2104.           // Speed drop, activated only when jump key is not pressed.
  2105.           this.tRex.setSpeedDrop();
  2106.         } else if (!this.tRex.jumping && !this.tRex.ducking) {
  2107.          // Duck.
  2108.          this.tRex.setDuck(true);
  2109.         }
  2110.       }
  2111.     } else if (this.crashed && e.type == Runner.events.TOUCHSTART &&
  2112.        e.currentTarget == this.containerEl) {
  2113.      this.restart();
  2114.     }
  2115.   },
  2116.  
  2117.  
  2118.   /**
  2119.    * Process key up.
  2120.    * @param {Event} e
  2121.    */
  2122.   onKeyUp: function(e) {
  2123.     var keyCode = String(e.keyCode);
  2124.     var isjumpKey = Runner.keycodes.JUMP[keyCode] ||
  2125.        e.type == Runner.events.TOUCHEND ||
  2126.        e.type == Runner.events.MOUSEDOWN;
  2127.  
  2128.     if (this.isRunning() && isjumpKey) {
  2129.      this.tRex.endJump();
  2130.     } else if (Runner.keycodes.DUCK[keyCode]) {
  2131.       this.tRex.speedDrop = false;
  2132.       this.tRex.setDuck(false);
  2133.     } else if (this.crashed) {
  2134.       // Check that enough time has elapsed before allowing jump key to restart.
  2135.       var deltaTime = getTimeStamp() - this.time;
  2136.  
  2137.       if (Runner.keycodes.RESTART[keyCode] || this.isLeftClickOnCanvas(e) ||
  2138.           (deltaTime >= this.config.GAMEOVER_CLEAR_TIME &&
  2139.          Runner.keycodes.JUMP[keyCode])) {
  2140.        this.restart();
  2141.       }
  2142.     } else if (this.paused && isjumpKey) {
  2143.      // Reset the jump state
  2144.      this.tRex.reset();
  2145.       this.play();
  2146.     }
  2147.   },
  2148.  
  2149.   /**
  2150.    * Returns whether the event was a left click on canvas.
  2151.    * On Windows right click is registered as a click.
  2152.    * @param {Event} e
  2153.    * @return {boolean}
  2154.    */
  2155.   isLeftClickOnCanvas: function(e) {
  2156.     return e.button != null && e.button < 2 &&
  2157.        e.type == Runner.events.MOUSEUP && e.target == this.canvas;
  2158.   },
  2159.  
  2160.   /**
  2161.    * RequestAnimationFrame wrapper.
  2162.    */
  2163.   scheduleNextUpdate: function() {
  2164.     if (!this.updatePending) {
  2165.       this.updatePending = true;
  2166.       this.raqId = requestAnimationFrame(this.update.bind(this));
  2167.     }
  2168.   },
  2169.  
  2170.   /**
  2171.    * Whether the game is running.
  2172.    * @return {boolean}
  2173.    */
  2174.   isRunning: function() {
  2175.     return !!this.raqId;
  2176.   },
  2177.  
  2178.   /**
  2179.    * Game over state.
  2180.    */
  2181.   gameOver: function() {
  2182.     this.playSound(this.soundFx.HIT);
  2183.     vibrate(200);
  2184.  
  2185.     this.stop();
  2186.     this.crashed = true;
  2187.     this.distanceMeter.achievement = false;
  2188.  
  2189.     this.tRex.update(100, Trex.status.CRASHED);
  2190.  
  2191.     // Game over panel.
  2192.     if (!this.gameOverPanel) {
  2193.       this.gameOverPanel = new GameOverPanel(this.canvas,
  2194.           this.spriteDef.TEXT_SPRITE, this.spriteDef.RESTART,
  2195.           this.dimensions);
  2196.     } else {
  2197.       this.gameOverPanel.draw();
  2198.     }
  2199.  
  2200.     // Update the high score.
  2201.     if (this.distanceRan > this.highestScore) {
  2202.       this.highestScore = Math.ceil(this.distanceRan);
  2203.       this.distanceMeter.setHighScore(this.highestScore);
  2204.     }
  2205.  
  2206.     // Reset the time clock.
  2207.     this.time = getTimeStamp();
  2208.   },
  2209.  
  2210.   stop: function() {
  2211.     this.playing = false;
  2212.     this.paused = true;
  2213.     cancelAnimationFrame(this.raqId);
  2214.     this.raqId = 0;
  2215.   },
  2216.  
  2217.   play: function() {
  2218.     if (!this.crashed) {
  2219.       this.playing = true;
  2220.       this.paused = false;
  2221.       this.tRex.update(0, Trex.status.RUNNING);
  2222.       this.time = getTimeStamp();
  2223.       this.update();
  2224.     }
  2225.   },
  2226.  
  2227.   restart: function() {
  2228.     if (!this.raqId) {
  2229.       this.playCount++;
  2230.       this.runningTime = 0;
  2231.       this.playing = true;
  2232.       this.paused = false;
  2233.       this.crashed = false;
  2234.       this.distanceRan = 0;
  2235.       this.setSpeed(this.config.SPEED);
  2236.       this.time = getTimeStamp();
  2237.       this.containerEl.classList.remove(Runner.classes.CRASHED);
  2238.       this.clearCanvas();
  2239.       this.distanceMeter.reset(this.highestScore);
  2240.       this.horizon.reset();
  2241.       this.tRex.reset();
  2242.       this.playSound(this.soundFx.BUTTON_PRESS);
  2243.       this.invert(true);
  2244.       this.update();
  2245.     }
  2246.   },
  2247.  
  2248.   /**
  2249.    * Whether the game should go into arcade mode.
  2250.    * @return {boolean}
  2251.    */
  2252.   isArcadeMode: function() {
  2253.     return document.title == ARCADE_MODE_URL;
  2254.   },
  2255.  
  2256.   /**
  2257.    * Hides offline messaging for a fullscreen game only experience.
  2258.    */
  2259.   setArcadeMode: function() {
  2260.     document.body.classList.add(Runner.classes.ARCADE_MODE);
  2261.     this.setArcadeModeContainerScale();
  2262.   },
  2263.  
  2264.   /**
  2265.    * Sets the scaling for arcade mode.
  2266.    */
  2267.   setArcadeModeContainerScale: function() {
  2268.     var windowHeight = window.innerHeight;
  2269.     var scaleHeight = windowHeight / this.dimensions.HEIGHT;
  2270.     var scaleWidth = window.innerWidth / this.dimensions.WIDTH;
  2271.     var scale = Math.max(1, Math.min(scaleHeight, scaleWidth));
  2272.     var scaledCanvasHeight = this.dimensions.HEIGHT * scale;
  2273.     // Positions the game container at 10% of the available vertical window
  2274.     // height minus the game container height.
  2275.     var translateY = Math.ceil(Math.max(0, (windowHeight - scaledCanvasHeight -
  2276.         Runner.config.ARCADE_MODE_INITIAL_TOP_POSITION) *
  2277.         Runner.config.ARCADE_MODE_TOP_POSITION_PERCENT)) *
  2278.         window.devicePixelRatio;
  2279.     this.containerEl.style.transform = 'scale(' + scale + ') translateY(' +
  2280.         translateY + 'px)';
  2281.   },
  2282.  
  2283.   /**
  2284.    * Pause the game if the tab is not in focus.
  2285.    */
  2286.   onVisibilityChange: function(e) {
  2287.     if (document.hidden || document.webkitHidden || e.type == 'blur' ||
  2288.       document.visibilityState != 'visible') {
  2289.       this.stop();
  2290.     } else if (!this.crashed) {
  2291.       this.tRex.reset();
  2292.       this.play();
  2293.     }
  2294.   },
  2295.  
  2296.   /**
  2297.    * Play a sound.
  2298.    * @param {SoundBuffer} soundBuffer
  2299.    */
  2300.   playSound: function(soundBuffer) {
  2301.     if (soundBuffer) {
  2302.       var sourceNode = this.audioContext.createBufferSource();
  2303.       sourceNode.buffer = soundBuffer;
  2304.       sourceNode.connect(this.audioContext.destination);
  2305.       sourceNode.start(0);
  2306.     }
  2307.   },
  2308.  
  2309.   /**
  2310.    * Inverts the current page / canvas colors.
  2311.    * @param {boolean} Whether to reset colors.
  2312.    */
  2313.   invert: function(reset) {
  2314.     if (reset) {
  2315.       document.body.classList.toggle(Runner.classes.INVERTED, false);
  2316.       this.invertTimer = 0;
  2317.       this.inverted = false;
  2318.     } else {
  2319.       this.inverted = document.body.classList.toggle(Runner.classes.INVERTED,
  2320.           this.invertTrigger);
  2321.     }
  2322.   }
  2323. };
  2324.  
  2325.  
  2326. /**
  2327.  * Updates the canvas size taking into
  2328.  * account the backing store pixel ratio and
  2329.  * the device pixel ratio.
  2330.  *
  2331.  * See article by Paul Lewis:
  2332.  * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
  2333.  *
  2334.  * @param {HTMLCanvasElement} canvas
  2335.  * @param {number} opt_width
  2336.  * @param {number} opt_height
  2337.  * @return {boolean} Whether the canvas was scaled.
  2338.  */
  2339. Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) {
  2340.   var context = canvas.getContext('2d');
  2341.  
  2342.   // Query the various pixel ratios
  2343.   var devicePixelRatio = Math.floor(window.devicePixelRatio) || 1;
  2344.   var backingStoreRatio = Math.floor(context.webkitBackingStorePixelRatio) || 1;
  2345.   var ratio = devicePixelRatio / backingStoreRatio;
  2346.  
  2347.   // Upscale the canvas if the two ratios don't match
  2348.   if (devicePixelRatio !== backingStoreRatio) {
  2349.     var oldWidth = opt_width || canvas.width;
  2350.     var oldHeight = opt_height || canvas.height;
  2351.  
  2352.     canvas.width = oldWidth * ratio;
  2353.     canvas.height = oldHeight * ratio;
  2354.  
  2355.     canvas.style.width = oldWidth + 'px';
  2356.     canvas.style.height = oldHeight + 'px';
  2357.  
  2358.     // Scale the context to counter the fact that we've manually scaled
  2359.     // our canvas element.
  2360.     context.scale(ratio, ratio);
  2361.     return true;
  2362.   } else if (devicePixelRatio == 1) {
  2363.     // Reset the canvas width / height. Fixes scaling bug when the page is
  2364.     // zoomed and the devicePixelRatio changes accordingly.
  2365.     canvas.style.width = canvas.width + 'px';
  2366.     canvas.style.height = canvas.height + 'px';
  2367.   }
  2368.   return false;
  2369. };
  2370.  
  2371.  
  2372. /**
  2373.  * Get random number.
  2374.  * @param {number} min
  2375.  * @param {number} max
  2376.  * @param {number}
  2377.  */
  2378. function getRandomNum(min, max) {
  2379.   return Math.floor(Math.random() * (max - min + 1)) + min;
  2380. }
  2381.  
  2382.  
  2383. /**
  2384.  * Vibrate on mobile devices.
  2385.  * @param {number} duration Duration of the vibration in milliseconds.
  2386.  */
  2387. function vibrate(duration) {
  2388.   if (IS_MOBILE && window.navigator.vibrate) {
  2389.    window.navigator.vibrate(duration);
  2390.   }
  2391. }
  2392.  
  2393.  
  2394. /**
  2395.  * Create canvas element.
  2396.  * @param {HTMLElement} container Element to append canvas to.
  2397.  * @param {number} width
  2398.  * @param {number} height
  2399.  * @param {string} opt_classname
  2400.  * @return {HTMLCanvasElement}
  2401.  */
  2402. function createCanvas(container, width, height, opt_classname) {
  2403.   var canvas = document.createElement('canvas');
  2404.   canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' +
  2405.       opt_classname : Runner.classes.CANVAS;
  2406.   canvas.width = width;
  2407.   canvas.height = height;
  2408.   container.appendChild(canvas);
  2409.  
  2410.   return canvas;
  2411. }
  2412.  
  2413.  
  2414. /**
  2415.  * Decodes the base 64 audio to ArrayBuffer used by Web Audio.
  2416.  * @param {string} base64String
  2417.  */
  2418. function decodeBase64ToArrayBuffer(base64String) {
  2419.   var len = (base64String.length / 4) * 3;
  2420.   var str = atob(base64String);
  2421.   var arrayBuffer = new ArrayBuffer(len);
  2422.   var bytes = new Uint8Array(arrayBuffer);
  2423.  
  2424.   for (var i = 0; i < len; i++) {
  2425.    bytes[i] = str.charCodeAt(i);
  2426.  }
  2427.  return bytes.buffer;
  2428. }
  2429.  
  2430.  
  2431. /**
  2432. * Return the current timestamp.
  2433. * @return {number}
  2434. */
  2435. function getTimeStamp() {
  2436.  return IS_IOS ? new Date().getTime() : performance.now();
  2437. }
  2438.  
  2439.  
  2440. //******************************************************************************
  2441.  
  2442.  
  2443. /**
  2444. * Game over panel.
  2445. * @param {!HTMLCanvasElement} canvas
  2446. * @param {Object} textImgPos
  2447. * @param {Object} restartImgPos
  2448. * @param {!Object} dimensions Canvas dimensions.
  2449. * @constructor
  2450. */
  2451. function GameOverPanel(canvas, textImgPos, restartImgPos, dimensions) {
  2452.  this.canvas = canvas;
  2453.  this.canvasCtx = canvas.getContext('2d');
  2454.  this.canvasDimensions = dimensions;
  2455.  this.textImgPos = textImgPos;
  2456.  this.restartImgPos = restartImgPos;
  2457.  this.draw();
  2458. };
  2459.  
  2460.  
  2461. /**
  2462. * Dimensions used in the panel.
  2463. * @enum {number}
  2464. */
  2465. GameOverPanel.dimensions = {
  2466.  TEXT_X: 0,
  2467.  TEXT_Y: 13,
  2468.  TEXT_WIDTH: 191,
  2469.  TEXT_HEIGHT: 11,
  2470.  RESTART_WIDTH: 36,
  2471.  RESTART_HEIGHT: 32
  2472. };
  2473.  
  2474.  
  2475. GameOverPanel.prototype = {
  2476.  /**
  2477.   * Update the panel dimensions.
  2478.   * @param {number} width New canvas width.
  2479.   * @param {number} opt_height Optional new canvas height.
  2480.   */
  2481.  updateDimensions: function(width, opt_height) {
  2482.    this.canvasDimensions.WIDTH = width;
  2483.    if (opt_height) {
  2484.      this.canvasDimensions.HEIGHT = opt_height;
  2485.    }
  2486.  },
  2487.  
  2488.  /**
  2489.   * Draw the panel.
  2490.   */
  2491.  draw: function() {
  2492.    var dimensions = GameOverPanel.dimensions;
  2493.  
  2494.    var centerX = this.canvasDimensions.WIDTH / 2;
  2495.  
  2496.    // Game over text.
  2497.    var textSourceX = dimensions.TEXT_X;
  2498.    var textSourceY = dimensions.TEXT_Y;
  2499.    var textSourceWidth = dimensions.TEXT_WIDTH;
  2500.    var textSourceHeight = dimensions.TEXT_HEIGHT;
  2501.  
  2502.    var textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2));
  2503.    var textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3);
  2504.    var textTargetWidth = dimensions.TEXT_WIDTH;
  2505.    var textTargetHeight = dimensions.TEXT_HEIGHT;
  2506.  
  2507.    var restartSourceWidth = dimensions.RESTART_WIDTH;
  2508.    var restartSourceHeight = dimensions.RESTART_HEIGHT;
  2509.    var restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2);
  2510.    var restartTargetY = this.canvasDimensions.HEIGHT / 2;
  2511.  
  2512.    if (IS_HIDPI) {
  2513.      textSourceY *= 2;
  2514.      textSourceX *= 2;
  2515.      textSourceWidth *= 2;
  2516.      textSourceHeight *= 2;
  2517.      restartSourceWidth *= 2;
  2518.      restartSourceHeight *= 2;
  2519.    }
  2520.  
  2521.    textSourceX += this.textImgPos.x;
  2522.    textSourceY += this.textImgPos.y;
  2523.  
  2524.    // Game over text from sprite.
  2525.    this.canvasCtx.drawImage(Runner.imageSprite,
  2526.        textSourceX, textSourceY, textSourceWidth, textSourceHeight,
  2527.        textTargetX, textTargetY, textTargetWidth, textTargetHeight);
  2528.  
  2529.    // Restart button.
  2530.    this.canvasCtx.drawImage(Runner.imageSprite,
  2531.        this.restartImgPos.x, this.restartImgPos.y,
  2532.        restartSourceWidth, restartSourceHeight,
  2533.        restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,
  2534.        dimensions.RESTART_HEIGHT);
  2535.  }
  2536. };
  2537.  
  2538.  
  2539. //******************************************************************************
  2540.  
  2541. /**
  2542. * Check for a collision.
  2543. * @param {!Obstacle} obstacle
  2544. * @param {!Trex} tRex T-rex object.
  2545. * @param {HTMLCanvasContext} opt_canvasCtx Optional canvas context for drawing
  2546. *    collision boxes.
  2547. * @return {Array<CollisionBox>}
  2548.  */
  2549. function checkForCollision(obstacle, tRex, opt_canvasCtx) {
  2550.   var obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos;
  2551.  
  2552.   // Adjustments are made to the bounding box as there is a 1 pixel white
  2553.   // border around the t-rex and obstacles.
  2554.   var tRexBox = new CollisionBox(
  2555.       tRex.xPos + 1,
  2556.       tRex.yPos + 1,
  2557.       tRex.config.WIDTH - 2,
  2558.       tRex.config.HEIGHT - 2);
  2559.  
  2560.   var obstacleBox = new CollisionBox(
  2561.       obstacle.xPos + 1,
  2562.       obstacle.yPos + 1,
  2563.       obstacle.typeConfig.width * obstacle.size - 2,
  2564.       obstacle.typeConfig.height - 2);
  2565.  
  2566.   // Debug outer box
  2567.   if (opt_canvasCtx) {
  2568.     drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);
  2569.   }
  2570.  
  2571.   // Simple outer bounds check.
  2572.   if (boxCompare(tRexBox, obstacleBox)) {
  2573.     var collisionBoxes = obstacle.collisionBoxes;
  2574.     var tRexCollisionBoxes = tRex.ducking ?
  2575.         Trex.collisionBoxes.DUCKING : Trex.collisionBoxes.RUNNING;
  2576.  
  2577.     // Detailed axis aligned box check.
  2578.     for (var t = 0; t < tRexCollisionBoxes.length; t++) {
  2579.      for (var i = 0; i < collisionBoxes.length; i++) {
  2580.        // Adjust the box to actual positions.
  2581.        var adjTrexBox =
  2582.            createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox);
  2583.        var adjObstacleBox =
  2584.            createAdjustedCollisionBox(collisionBoxes[i], obstacleBox);
  2585.        var crashed = boxCompare(adjTrexBox, adjObstacleBox);
  2586.  
  2587.        // Draw boxes for debug.
  2588.        if (opt_canvasCtx) {
  2589.          drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox);
  2590.        }
  2591.  
  2592.        if (crashed) {
  2593.          return [adjTrexBox, adjObstacleBox];
  2594.        }
  2595.      }
  2596.    }
  2597.  }
  2598.  return false;
  2599. };
  2600.  
  2601.  
  2602. /**
  2603. * Adjust the collision box.
  2604. * @param {!CollisionBox} box The original box.
  2605. * @param {!CollisionBox} adjustment Adjustment box.
  2606. * @return {CollisionBox} The adjusted collision box object.
  2607. */
  2608. function createAdjustedCollisionBox(box, adjustment) {
  2609.  return new CollisionBox(
  2610.      box.x + adjustment.x,
  2611.      box.y + adjustment.y,
  2612.      box.width,
  2613.      box.height);
  2614. };
  2615.  
  2616.  
  2617. /**
  2618. * Draw the collision boxes for debug.
  2619. */
  2620. function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) {
  2621.  canvasCtx.save();
  2622.  canvasCtx.strokeStyle = '#f00';
  2623.  canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height);
  2624.  
  2625.  canvasCtx.strokeStyle = '#0f0';
  2626.  canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y,
  2627.      obstacleBox.width, obstacleBox.height);
  2628.  canvasCtx.restore();
  2629. };
  2630.  
  2631.  
  2632. /**
  2633. * Compare two collision boxes for a collision.
  2634. * @param {CollisionBox} tRexBox
  2635. * @param {CollisionBox} obstacleBox
  2636. * @return {boolean} Whether the boxes intersected.
  2637. */
  2638. function boxCompare(tRexBox, obstacleBox) {
  2639.  var crashed = false;
  2640.  var tRexBoxX = tRexBox.x;
  2641.  var tRexBoxY = tRexBox.y;
  2642.  
  2643.  var obstacleBoxX = obstacleBox.x;
  2644.  var obstacleBoxY = obstacleBox.y;
  2645.  
  2646.  // Axis-Aligned Bounding Box method.
  2647.  if (tRexBox.x < obstacleBoxX + obstacleBox.width &&
  2648.      tRexBox.x + tRexBox.width > obstacleBoxX &&
  2649.      tRexBox.y < obstacleBox.y + obstacleBox.height &&
  2650.      tRexBox.height + tRexBox.y > obstacleBox.y) {
  2651.    crashed = true;
  2652.   }
  2653.  
  2654.   return crashed;
  2655. };
  2656.  
  2657.  
  2658. //******************************************************************************
  2659.  
  2660. /**
  2661.  * Collision box object.
  2662.  * @param {number} x X position.
  2663.  * @param {number} y Y Position.
  2664.  * @param {number} w Width.
  2665.  * @param {number} h Height.
  2666.  */
  2667. function CollisionBox(x, y, w, h) {
  2668.   this.x = x;
  2669.   this.y = y;
  2670.   this.width = w;
  2671.   this.height = h;
  2672. };
  2673.  
  2674.  
  2675. //******************************************************************************
  2676.  
  2677. /**
  2678.  * Obstacle.
  2679.  * @param {HTMLCanvasCtx} canvasCtx
  2680.  * @param {Obstacle.type} type
  2681.  * @param {Object} spritePos Obstacle position in sprite.
  2682.  * @param {Object} dimensions
  2683.  * @param {number} gapCoefficient Mutipler in determining the gap.
  2684.  * @param {number} speed
  2685.  * @param {number} opt_xOffset
  2686.  */
  2687. function Obstacle(canvasCtx, type, spriteImgPos, dimensions,
  2688.     gapCoefficient, speed, opt_xOffset) {
  2689.  
  2690.   this.canvasCtx = canvasCtx;
  2691.   this.spritePos = spriteImgPos;
  2692.   this.typeConfig = type;
  2693.   this.gapCoefficient = gapCoefficient;
  2694.   this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH);
  2695.   this.dimensions = dimensions;
  2696.   this.remove = false;
  2697.   this.xPos = dimensions.WIDTH + (opt_xOffset || 0);
  2698.   this.yPos = 0;
  2699.   this.width = 0;
  2700.   this.collisionBoxes = [];
  2701.   this.gap = 0;
  2702.   this.speedOffset = 0;
  2703.  
  2704.   // For animated obstacles.
  2705.   this.currentFrame = 0;
  2706.   this.timer = 0;
  2707.  
  2708.   this.init(speed);
  2709. };
  2710.  
  2711. /**
  2712.  * Coefficient for calculating the maximum gap.
  2713.  * @const
  2714.  */
  2715. Obstacle.MAX_GAP_COEFFICIENT = 1.5;
  2716.  
  2717. /**
  2718.  * Maximum obstacle grouping count.
  2719.  * @const
  2720.  */
  2721. Obstacle.MAX_OBSTACLE_LENGTH = 3,
  2722.  
  2723.  
  2724. Obstacle.prototype = {
  2725.   /**
  2726.    * Initialise the DOM for the obstacle.
  2727.    * @param {number} speed
  2728.    */
  2729.   init: function(speed) {
  2730.     this.cloneCollisionBoxes();
  2731.  
  2732.     // Only allow sizing if we're at the right speed.
  2733.     if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
  2734.      this.size = 1;
  2735.     }
  2736.  
  2737.     this.width = this.typeConfig.width * this.size;
  2738.  
  2739.     // Check if obstacle can be positioned at various heights.
  2740.     if (Array.isArray(this.typeConfig.yPos))  {
  2741.       var yPosConfig = IS_MOBILE ? this.typeConfig.yPosMobile :
  2742.           this.typeConfig.yPos;
  2743.       this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
  2744.     } else {
  2745.       this.yPos = this.typeConfig.yPos;
  2746.     }
  2747.  
  2748.     this.draw();
  2749.  
  2750.     // Make collision box adjustments,
  2751.     // Central box is adjusted to the size as one box.
  2752.     //      ____        ______        ________
  2753.     //    _|   |-|    _|     |-|    _|       |-|
  2754.     //   | |<->| |   | |<--->| |   | |<----->| |
  2755.     //   | | 1 | |   | |  2  | |   | |   3   | |
  2756.     //   |_|___|_|   |_|_____|_|   |_|_______|_|
  2757.     //
  2758.     if (this.size > 1) {
  2759.       this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
  2760.           this.collisionBoxes[2].width;
  2761.       this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
  2762.     }
  2763.  
  2764.     // For obstacles that go at a different speed from the horizon.
  2765.     if (this.typeConfig.speedOffset) {
  2766.       this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
  2767.           -this.typeConfig.speedOffset;
  2768.     }
  2769.  
  2770.     this.gap = this.getGap(this.gapCoefficient, speed);
  2771.   },
  2772.  
  2773.   /**
  2774.    * Draw and crop based on size.
  2775.    */
  2776.   draw: function() {
  2777.     var sourceWidth = this.typeConfig.width;
  2778.     var sourceHeight = this.typeConfig.height;
  2779.  
  2780.     if (IS_HIDPI) {
  2781.       sourceWidth = sourceWidth * 2;
  2782.       sourceHeight = sourceHeight * 2;
  2783.     }
  2784.  
  2785.     // X position in sprite.
  2786.     var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1)) +
  2787.         this.spritePos.x;
  2788.  
  2789.     // Animation frames.
  2790.     if (this.currentFrame > 0) {
  2791.       sourceX += sourceWidth * this.currentFrame;
  2792.     }
  2793.  
  2794.     this.canvasCtx.drawImage(Runner.imageSprite,
  2795.       sourceX, this.spritePos.y,
  2796.       sourceWidth * this.size, sourceHeight,
  2797.       this.xPos, this.yPos,
  2798.       this.typeConfig.width * this.size, this.typeConfig.height);
  2799.   },
  2800.  
  2801.   /**
  2802.    * Obstacle frame update.
  2803.    * @param {number} deltaTime
  2804.    * @param {number} speed
  2805.    */
  2806.   update: function(deltaTime, speed) {
  2807.     if (!this.remove) {
  2808.       if (this.typeConfig.speedOffset) {
  2809.         speed += this.speedOffset;
  2810.       }
  2811.       this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);
  2812.  
  2813.       // Update frame
  2814.       if (this.typeConfig.numFrames) {
  2815.         this.timer += deltaTime;
  2816.         if (this.timer >= this.typeConfig.frameRate) {
  2817.           this.currentFrame =
  2818.               this.currentFrame == this.typeConfig.numFrames - 1 ?
  2819.               0 : this.currentFrame + 1;
  2820.           this.timer = 0;
  2821.         }
  2822.       }
  2823.       this.draw();
  2824.  
  2825.       if (!this.isVisible()) {
  2826.         this.remove = true;
  2827.       }
  2828.     }
  2829.   },
  2830.  
  2831.   /**
  2832.    * Calculate a random gap size.
  2833.    * - Minimum gap gets wider as speed increses
  2834.    * @param {number} gapCoefficient
  2835.    * @param {number} speed
  2836.    * @return {number} The gap size.
  2837.    */
  2838.   getGap: function(gapCoefficient, speed) {
  2839.     var minGap = Math.round(this.width * speed +
  2840.           this.typeConfig.minGap * gapCoefficient);
  2841.     var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
  2842.     return getRandomNum(minGap, maxGap);
  2843.   },
  2844.  
  2845.   /**
  2846.    * Check if obstacle is visible.
  2847.    * @return {boolean} Whether the obstacle is in the game area.
  2848.    */
  2849.   isVisible: function() {
  2850.     return this.xPos + this.width > 0;
  2851.   },
  2852.  
  2853.   /**
  2854.    * Make a copy of the collision boxes, since these will change based on
  2855.    * obstacle type and size.
  2856.    */
  2857.   cloneCollisionBoxes: function() {
  2858.     var collisionBoxes = this.typeConfig.collisionBoxes;
  2859.  
  2860.     for (var i = collisionBoxes.length - 1; i >= 0; i--) {
  2861.       this.collisionBoxes[i] = new CollisionBox(collisionBoxes[i].x,
  2862.           collisionBoxes[i].y, collisionBoxes[i].width,
  2863.           collisionBoxes[i].height);
  2864.     }
  2865.   }
  2866. };
  2867.  
  2868.  
  2869. /**
  2870.  * Obstacle definitions.
  2871.  * minGap: minimum pixel space betweeen obstacles.
  2872.  * multipleSpeed: Speed at which multiples are allowed.
  2873.  * speedOffset: speed faster / slower than the horizon.
  2874.  * minSpeed: Minimum speed which the obstacle can make an appearance.
  2875.  */
  2876. Obstacle.types = [
  2877.   {
  2878.     type: 'CACTUS_SMALL',
  2879.     width: 17,
  2880.     height: 35,
  2881.     yPos: 105,
  2882.     multipleSpeed: 4,
  2883.     minGap: 120,
  2884.     minSpeed: 0,
  2885.     collisionBoxes: [
  2886.       new CollisionBox(0, 7, 5, 27),
  2887.       new CollisionBox(4, 0, 6, 34),
  2888.       new CollisionBox(10, 4, 7, 14)
  2889.     ]
  2890.   },
  2891.   {
  2892.     type: 'CACTUS_LARGE',
  2893.     width: 25,
  2894.     height: 50,
  2895.     yPos: 90,
  2896.     multipleSpeed: 7,
  2897.     minGap: 120,
  2898.     minSpeed: 0,
  2899.     collisionBoxes: [
  2900.       new CollisionBox(0, 12, 7, 38),
  2901.       new CollisionBox(8, 0, 7, 49),
  2902.       new CollisionBox(13, 10, 10, 38)
  2903.     ]
  2904.   },
  2905.   {
  2906.     type: 'PTERODACTYL',
  2907.     width: 46,
  2908.     height: 40,
  2909.     yPos: [ 100, 75, 50 ], // Variable height.
  2910.     yPosMobile: [ 100, 50 ], // Variable height mobile.
  2911.     multipleSpeed: 999,
  2912.     minSpeed: 8.5,
  2913.     minGap: 150,
  2914.     collisionBoxes: [
  2915.       new CollisionBox(15, 15, 16, 5),
  2916.       new CollisionBox(18, 21, 24, 6),
  2917.       new CollisionBox(2, 14, 4, 3),
  2918.       new CollisionBox(6, 10, 4, 7),
  2919.       new CollisionBox(10, 8, 6, 9)
  2920.     ],
  2921.     numFrames: 2,
  2922.     frameRate: 1000/6,
  2923.     speedOffset: .8
  2924.   }
  2925. ];
  2926.  
  2927.  
  2928. //******************************************************************************
  2929. /**
  2930.  * T-rex game character.
  2931.  * @param {HTMLCanvas} canvas
  2932.  * @param {Object} spritePos Positioning within image sprite.
  2933.  * @constructor
  2934.  */
  2935. function Trex(canvas, spritePos) {
  2936.   this.canvas = canvas;
  2937.   this.canvasCtx = canvas.getContext('2d');
  2938.   this.spritePos = spritePos;
  2939.   this.xPos = 0;
  2940.   this.yPos = 0;
  2941.   // Position when on the ground.
  2942.   this.groundYPos = 0;
  2943.   this.currentFrame = 0;
  2944.   this.currentAnimFrames = [];
  2945.   this.blinkDelay = 0;
  2946.   this.blinkCount = 0;
  2947.   this.animStartTime = 0;
  2948.   this.timer = 0;
  2949.   this.msPerFrame = 1000 / FPS;
  2950.   this.config = Trex.config;
  2951.   // Current status.
  2952.   this.status = Trex.status.WAITING;
  2953.  
  2954.   this.jumping = false;
  2955.   this.ducking = false;
  2956.   this.jumpVelocity = 0;
  2957.   this.reachedMinHeight = false;
  2958.   this.speedDrop = false;
  2959.   this.jumpCount = 0;
  2960.   this.jumpspotX = 0;
  2961.  
  2962.   this.init();
  2963. };
  2964.  
  2965.  
  2966. /**
  2967.  * T-rex player config.
  2968.  * @enum {number}
  2969.  */
  2970. Trex.config = {
  2971.   DROP_VELOCITY: -5,
  2972.   GRAVITY: 0.6,
  2973.   HEIGHT: 47,
  2974.   HEIGHT_DUCK: 25,
  2975.   INIITAL_JUMP_VELOCITY: -10,
  2976.   INTRO_DURATION: 1500,
  2977.   MAX_JUMP_HEIGHT: 30,
  2978.   MIN_JUMP_HEIGHT: 30,
  2979.   SPEED_DROP_COEFFICIENT: 3,
  2980.   SPRITE_WIDTH: 262,
  2981.   START_X_POS: 50,
  2982.   WIDTH: 44,
  2983.   WIDTH_DUCK: 59
  2984. };
  2985.  
  2986.  
  2987. /**
  2988.  * Used in collision detection.
  2989.  * @type {Array<CollisionBox>}
  2990.  */
  2991. Trex.collisionBoxes = {
  2992.   DUCKING: [
  2993.     new CollisionBox(1, 18, 55, 25)
  2994.   ],
  2995.   RUNNING: [
  2996.     new CollisionBox(22, 0, 17, 16),
  2997.     new CollisionBox(1, 18, 30, 9),
  2998.     new CollisionBox(10, 35, 14, 8),
  2999.     new CollisionBox(1, 24, 29, 5),
  3000.     new CollisionBox(5, 30, 21, 4),
  3001.     new CollisionBox(9, 34, 15, 4)
  3002.   ]
  3003. };
  3004.  
  3005.  
  3006. /**
  3007.  * Animation states.
  3008.  * @enum {string}
  3009.  */
  3010. Trex.status = {
  3011.   CRASHED: 'CRASHED',
  3012.   DUCKING: 'DUCKING',
  3013.   JUMPING: 'JUMPING',
  3014.   RUNNING: 'RUNNING',
  3015.   WAITING: 'WAITING'
  3016. };
  3017.  
  3018. /**
  3019.  * Blinking coefficient.
  3020.  * @const
  3021.  */
  3022. Trex.BLINK_TIMING = 7000;
  3023.  
  3024.  
  3025. /**
  3026.  * Animation config for different states.
  3027.  * @enum {Object}
  3028.  */
  3029. Trex.animFrames = {
  3030.   WAITING: {
  3031.     frames: [44, 0],
  3032.     msPerFrame: 1000 / 3
  3033.   },
  3034.   RUNNING: {
  3035.     frames: [88, 132],
  3036.     msPerFrame: 1000 / 12
  3037.   },
  3038.   CRASHED: {
  3039.     frames: [220],
  3040.     msPerFrame: 1000 / 60
  3041.   },
  3042.   JUMPING: {
  3043.     frames: [0],
  3044.     msPerFrame: 1000 / 60
  3045.   },
  3046.   DUCKING: {
  3047.     frames: [262, 321],
  3048.     msPerFrame: 1000 / 8
  3049.   }
  3050. };
  3051.  
  3052.  
  3053. Trex.prototype = {
  3054.   /**
  3055.    * T-rex player initaliser.
  3056.    * Sets the t-rex to blink at random intervals.
  3057.    */
  3058.   init: function() {
  3059.     this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
  3060.         Runner.config.BOTTOM_PAD;
  3061.     this.yPos = this.groundYPos;
  3062.     this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
  3063.  
  3064.     this.draw(0, 0);
  3065.     this.update(0, Trex.status.WAITING);
  3066.   },
  3067.  
  3068.   /**
  3069.    * Setter for the jump velocity.
  3070.    * The approriate drop velocity is also set.
  3071.    */
  3072.   setJumpVelocity: function(setting) {
  3073.     this.config.INIITAL_JUMP_VELOCITY = -setting;
  3074.     this.config.DROP_VELOCITY = -setting / 2;
  3075.   },
  3076.  
  3077.   /**
  3078.    * Set the animation status.
  3079.    * @param {!number} deltaTime
  3080.    * @param {Trex.status} status Optional status to switch to.
  3081.    */
  3082.   update: function(deltaTime, opt_status) {
  3083.     this.timer += deltaTime;
  3084.  
  3085.     // Update the status.
  3086.     if (opt_status) {
  3087.       this.status = opt_status;
  3088.       this.currentFrame = 0;
  3089.       this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
  3090.       this.currentAnimFrames = Trex.animFrames[opt_status].frames;
  3091.  
  3092.       if (opt_status == Trex.status.WAITING) {
  3093.         this.animStartTime = getTimeStamp();
  3094.         this.setBlinkDelay();
  3095.       }
  3096.     }
  3097.  
  3098.     // Game intro animation, T-rex moves in from the left.
  3099.     if (this.playingIntro && this.xPos < this.config.START_X_POS) {
  3100.      this.xPos += Math.round((this.config.START_X_POS /
  3101.          this.config.INTRO_DURATION) * deltaTime);
  3102.     }
  3103.  
  3104.     if (this.status == Trex.status.WAITING) {
  3105.       this.blink(getTimeStamp());
  3106.     } else {
  3107.       this.draw(this.currentAnimFrames[this.currentFrame], 0);
  3108.     }
  3109.  
  3110.     // Update the frame position.
  3111.     if (this.timer >= this.msPerFrame) {
  3112.       this.currentFrame = this.currentFrame ==
  3113.           this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
  3114.       this.timer = 0;
  3115.     }
  3116.  
  3117.     // Speed drop becomes duck if the down key is still being pressed.
  3118.     if (this.speedDrop && this.yPos == this.groundYPos) {
  3119.      this.speedDrop = false;
  3120.       this.setDuck(true);
  3121.     }
  3122.   },
  3123.  
  3124.   /**
  3125.    * Draw the t-rex to a particular position.
  3126.    * @param {number} x
  3127.    * @param {number} y
  3128.    */
  3129.   draw: function(x, y) {
  3130.     var sourceX = x;
  3131.     var sourceY = y;
  3132.     var sourceWidth = this.ducking && this.status != Trex.status.CRASHED ?
  3133.        this.config.WIDTH_DUCK : this.config.WIDTH;
  3134.     var sourceHeight = this.config.HEIGHT;
  3135.  
  3136.     if (IS_HIDPI) {
  3137.       sourceX *= 2;
  3138.       sourceY *= 2;
  3139.       sourceWidth *= 2;
  3140.       sourceHeight *= 2;
  3141.     }
  3142.  
  3143.     // Adjustments for sprite sheet position.
  3144.     sourceX += this.spritePos.x;
  3145.     sourceY += this.spritePos.y;
  3146.  
  3147.     // Ducking.
  3148.     if (this.ducking && this.status != Trex.status.CRASHED) {
  3149.      this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,
  3150.          sourceWidth, sourceHeight,
  3151.          this.xPos, this.yPos,
  3152.          this.config.WIDTH_DUCK, this.config.HEIGHT);
  3153.     } else {
  3154.       // Crashed whilst ducking. Trex is standing up so needs adjustment.
  3155.       if (this.ducking && this.status == Trex.status.CRASHED) {
  3156.        this.xPos++;
  3157.       }
  3158.       // Standing / running
  3159.       this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,
  3160.           sourceWidth, sourceHeight,
  3161.           this.xPos, this.yPos,
  3162.           this.config.WIDTH, this.config.HEIGHT);
  3163.     }
  3164.   },
  3165.  
  3166.   /**
  3167.    * Sets a random time for the blink to happen.
  3168.    */
  3169.   setBlinkDelay: function() {
  3170.     this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
  3171.   },
  3172.  
  3173.   /**
  3174.    * Make t-rex blink at random intervals.
  3175.    * @param {number} time Current time in milliseconds.
  3176.    */
  3177.   blink: function(time) {
  3178.     var deltaTime = time - this.animStartTime;
  3179.  
  3180.     if (deltaTime >= this.blinkDelay) {
  3181.       this.draw(this.currentAnimFrames[this.currentFrame], 0);
  3182.  
  3183.       if (this.currentFrame == 1) {
  3184.         // Set new random delay to blink.
  3185.         this.setBlinkDelay();
  3186.         this.animStartTime = time;
  3187.         this.blinkCount++;
  3188.       }
  3189.     }
  3190.   },
  3191.  
  3192.   /**
  3193.    * Initialise a jump.
  3194.    * @param {number} speed
  3195.    */
  3196.   startJump: function(speed) {
  3197.     if (!this.jumping) {
  3198.       this.update(0, Trex.status.JUMPING);
  3199.       // Tweak the jump velocity based on the speed.
  3200.       this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY - (speed / 10);
  3201.       this.jumping = true;
  3202.       this.reachedMinHeight = false;
  3203.       this.speedDrop = false;
  3204.     }
  3205.   },
  3206.  
  3207.   /**
  3208.    * Jump is complete, falling down.
  3209.    */
  3210.   endJump: function() {
  3211.     if (this.reachedMinHeight &&
  3212.        this.jumpVelocity < this.config.DROP_VELOCITY) {
  3213.      this.jumpVelocity = this.config.DROP_VELOCITY;
  3214.     }
  3215.   },
  3216.  
  3217.   /**
  3218.    * Update frame for a jump.
  3219.    * @param {number} deltaTime
  3220.    * @param {number} speed
  3221.    */
  3222.   updateJump: function(deltaTime, speed) {
  3223.     var msPerFrame = Trex.animFrames[this.status].msPerFrame;
  3224.     var framesElapsed = deltaTime / msPerFrame;
  3225.  
  3226.     // Speed drop makes Trex fall faster.
  3227.     if (this.speedDrop) {
  3228.       this.yPos += Math.round(this.jumpVelocity *
  3229.           this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
  3230.     } else {
  3231.       this.yPos += Math.round(this.jumpVelocity * framesElapsed);
  3232.     }
  3233.  
  3234.     this.jumpVelocity += this.config.GRAVITY * framesElapsed;
  3235.  
  3236.     // Minimum height has been reached.
  3237.     if (this.yPos < this.minJumpHeight || this.speedDrop) {
  3238.      this.reachedMinHeight = true;
  3239.    }
  3240.  
  3241.    // Reached max height
  3242.    if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
  3243.      this.endJump();
  3244.    }
  3245.  
  3246.    // Back down at ground level. Jump completed.
  3247.    if (this.yPos > this.groundYPos) {
  3248.       this.reset();
  3249.       this.jumpCount++;
  3250.     }
  3251.  
  3252.     this.update(deltaTime);
  3253.   },
  3254.  
  3255.   /**
  3256.    * Set the speed drop. Immediately cancels the current jump.
  3257.    */
  3258.   setSpeedDrop: function() {
  3259.     this.speedDrop = true;
  3260.     this.jumpVelocity = 1;
  3261.   },
  3262.  
  3263.   /**
  3264.    * @param {boolean} isDucking.
  3265.    */
  3266.   setDuck: function(isDucking) {
  3267.     if (isDucking && this.status != Trex.status.DUCKING) {
  3268.      this.update(0, Trex.status.DUCKING);
  3269.       this.ducking = true;
  3270.     } else if (this.status == Trex.status.DUCKING) {
  3271.       this.update(0, Trex.status.RUNNING);
  3272.       this.ducking = false;
  3273.     }
  3274.   },
  3275.  
  3276.   /**
  3277.    * Reset the t-rex to running at start of game.
  3278.    */
  3279.   reset: function() {
  3280.     this.yPos = this.groundYPos;
  3281.     this.jumpVelocity = 0;
  3282.     this.jumping = false;
  3283.     this.ducking = false;
  3284.     this.update(0, Trex.status.RUNNING);
  3285.     this.midair = false;
  3286.     this.speedDrop = false;
  3287.     this.jumpCount = 0;
  3288.   }
  3289. };
  3290.  
  3291.  
  3292. //******************************************************************************
  3293.  
  3294. /**
  3295.  * Handles displaying the distance meter.
  3296.  * @param {!HTMLCanvasElement} canvas
  3297.  * @param {Object} spritePos Image position in sprite.
  3298.  * @param {number} canvasWidth
  3299.  * @constructor
  3300.  */
  3301. function DistanceMeter(canvas, spritePos, canvasWidth) {
  3302.   this.canvas = canvas;
  3303.   this.canvasCtx = canvas.getContext('2d');
  3304.   this.image = Runner.imageSprite;
  3305.   this.spritePos = spritePos;
  3306.   this.x = 0;
  3307.   this.y = 5;
  3308.  
  3309.   this.currentDistance = 0;
  3310.   this.maxScore = 0;
  3311.   this.highScore = 0;
  3312.   this.container = null;
  3313.  
  3314.   this.digits = [];
  3315.   this.achievement = false;
  3316.   this.defaultString = '';
  3317.   this.flashTimer = 0;
  3318.   this.flashIterations = 0;
  3319.   this.invertTrigger = false;
  3320.  
  3321.   this.config = DistanceMeter.config;
  3322.   this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS;
  3323.   this.init(canvasWidth);
  3324. };
  3325.  
  3326.  
  3327. /**
  3328.  * @enum {number}
  3329.  */
  3330. DistanceMeter.dimensions = {
  3331.   WIDTH: 10,
  3332.   HEIGHT: 13,
  3333.   DEST_WIDTH: 11
  3334. };
  3335.  
  3336.  
  3337. /**
  3338.  * Y positioning of the digits in the sprite sheet.
  3339.  * X position is always 0.
  3340.  * @type {Array<number>}
  3341.  */
  3342. DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];
  3343.  
  3344.  
  3345. /**
  3346.  * Distance meter config.
  3347.  * @enum {number}
  3348.  */
  3349. DistanceMeter.config = {
  3350.   // Number of digits.
  3351.   MAX_DISTANCE_UNITS: 5,
  3352.  
  3353.   // Distance that causes achievement animation.
  3354.   ACHIEVEMENT_DISTANCE: 100,
  3355.  
  3356.   // Used for conversion from pixel distance to a scaled unit.
  3357.   COEFFICIENT: 0.025,
  3358.  
  3359.   // Flash duration in milliseconds.
  3360.   FLASH_DURATION: 1000 / 4,
  3361.  
  3362.   // Flash iterations for achievement animation.
  3363.   FLASH_ITERATIONS: 3
  3364. };
  3365.  
  3366.  
  3367. DistanceMeter.prototype = {
  3368.   /**
  3369.    * Initialise the distance meter to '00000'.
  3370.    * @param {number} width Canvas width in px.
  3371.    */
  3372.   init: function(width) {
  3373.     var maxDistanceStr = '';
  3374.  
  3375.     this.calcXPos(width);
  3376.     this.maxScore = this.maxScoreUnits;
  3377.     for (var i = 0; i < this.maxScoreUnits; i++) {
  3378.      this.draw(i, 0);
  3379.      this.defaultString += '0';
  3380.      maxDistanceStr += '9';
  3381.    }
  3382.  
  3383.    this.maxScore = parseInt(maxDistanceStr);
  3384.  },
  3385.  
  3386.  /**
  3387.   * Calculate the xPos in the canvas.
  3388.   * @param {number} canvasWidth
  3389.   */
  3390.  calcXPos: function(canvasWidth) {
  3391.    this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
  3392.        (this.maxScoreUnits + 1));
  3393.  },
  3394.  
  3395.  /**
  3396.   * Draw a digit to canvas.
  3397.   * @param {number} digitPos Position of the digit.
  3398.   * @param {number} value Digit value 0-9.
  3399.   * @param {boolean} opt_highScore Whether drawing the high score.
  3400.   */
  3401.  draw: function(digitPos, value, opt_highScore) {
  3402.    var sourceWidth = DistanceMeter.dimensions.WIDTH;
  3403.    var sourceHeight = DistanceMeter.dimensions.HEIGHT;
  3404.    var sourceX = DistanceMeter.dimensions.WIDTH * value;
  3405.    var sourceY = 0;
  3406.  
  3407.    var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
  3408.    var targetY = this.y;
  3409.    var targetWidth = DistanceMeter.dimensions.WIDTH;
  3410.    var targetHeight = DistanceMeter.dimensions.HEIGHT;
  3411.  
  3412.    // For high DPI we 2x source values.
  3413.    if (IS_HIDPI) {
  3414.      sourceWidth *= 2;
  3415.      sourceHeight *= 2;
  3416.      sourceX *= 2;
  3417.    }
  3418.  
  3419.    sourceX += this.spritePos.x;
  3420.    sourceY += this.spritePos.y;
  3421.  
  3422.    this.canvasCtx.save();
  3423.  
  3424.    if (opt_highScore) {
  3425.      // Left of the current score.
  3426.      var highScoreX = this.x - (this.maxScoreUnits * 2) *
  3427.          DistanceMeter.dimensions.WIDTH;
  3428.      this.canvasCtx.translate(highScoreX, this.y);
  3429.    } else {
  3430.      this.canvasCtx.translate(this.x, this.y);
  3431.    }
  3432.  
  3433.    this.canvasCtx.drawImage(this.image, sourceX, sourceY,
  3434.        sourceWidth, sourceHeight,
  3435.        targetX, targetY,
  3436.        targetWidth, targetHeight
  3437.      );
  3438.  
  3439.    this.canvasCtx.restore();
  3440.  },
  3441.  
  3442.  /**
  3443.   * Covert pixel distance to a 'real' distance.
  3444.   * @param {number} distance Pixel distance ran.
  3445.   * @return {number} The 'real' distance ran.
  3446.   */
  3447.  getActualDistance: function(distance) {
  3448.    return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;
  3449.  },
  3450.  
  3451.  /**
  3452.   * Update the distance meter.
  3453.   * @param {number} distance
  3454.   * @param {number} deltaTime
  3455.   * @return {boolean} Whether the acheivement sound fx should be played.
  3456.   */
  3457.  update: function(deltaTime, distance) {
  3458.    var paint = true;
  3459.    var playSound = false;
  3460.  
  3461.    if (!this.achievement) {
  3462.      distance = this.getActualDistance(distance);
  3463.      // Score has gone beyond the initial digit count.
  3464.      if (distance > this.maxScore && this.maxScoreUnits ==
  3465.        this.config.MAX_DISTANCE_UNITS) {
  3466.        this.maxScoreUnits++;
  3467.         this.maxScore = parseInt(this.maxScore + '9');
  3468.       } else {
  3469.         this.distance = 0;
  3470.       }
  3471.  
  3472.       if (distance > 0) {
  3473.         // Acheivement unlocked
  3474.         if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) {
  3475.           // Flash score and play sound.
  3476.           this.achievement = true;
  3477.           this.flashTimer = 0;
  3478.           playSound = true;
  3479.         }
  3480.  
  3481.         // Create a string representation of the distance with leading 0.
  3482.         var distanceStr = (this.defaultString +
  3483.             distance).substr(-this.maxScoreUnits);
  3484.         this.digits = distanceStr.split('');
  3485.       } else {
  3486.         this.digits = this.defaultString.split('');
  3487.       }
  3488.     } else {
  3489.       // Control flashing of the score on reaching acheivement.
  3490.       if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
  3491.        this.flashTimer += deltaTime;
  3492.  
  3493.        if (this.flashTimer < this.config.FLASH_DURATION) {
  3494.          paint = false;
  3495.        } else if (this.flashTimer >
  3496.             this.config.FLASH_DURATION * 2) {
  3497.           this.flashTimer = 0;
  3498.           this.flashIterations++;
  3499.         }
  3500.       } else {
  3501.         this.achievement = false;
  3502.         this.flashIterations = 0;
  3503.         this.flashTimer = 0;
  3504.       }
  3505.     }
  3506.  
  3507.     // Draw the digits if not flashing.
  3508.     if (paint) {
  3509.       for (var i = this.digits.length - 1; i >= 0; i--) {
  3510.         this.draw(i, parseInt(this.digits[i]));
  3511.       }
  3512.     }
  3513.  
  3514.     this.drawHighScore();
  3515.     return playSound;
  3516.   },
  3517.  
  3518.   /**
  3519.    * Draw the high score.
  3520.    */
  3521.   drawHighScore: function() {
  3522.     this.canvasCtx.save();
  3523.     this.canvasCtx.globalAlpha = .8;
  3524.     for (var i = this.highScore.length - 1; i >= 0; i--) {
  3525.       this.draw(i, parseInt(this.highScore[i], 10), true);
  3526.     }
  3527.     this.canvasCtx.restore();
  3528.   },
  3529.  
  3530.   /**
  3531.    * Set the highscore as a array string.
  3532.    * Position of char in the sprite: H - 10, I - 11.
  3533.    * @param {number} distance Distance ran in pixels.
  3534.    */
  3535.   setHighScore: function(distance) {
  3536.     distance = this.getActualDistance(distance);
  3537.     var highScoreStr = (this.defaultString +
  3538.         distance).substr(-this.maxScoreUnits);
  3539.  
  3540.     this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
  3541.   },
  3542.  
  3543.   /**
  3544.    * Reset the distance meter back to '00000'.
  3545.    */
  3546.   reset: function() {
  3547.     this.update(0);
  3548.     this.achievement = false;
  3549.   }
  3550. };
  3551.  
  3552.  
  3553. //******************************************************************************
  3554.  
  3555. /**
  3556.  * Cloud background item.
  3557.  * Similar to an obstacle object but without collision boxes.
  3558.  * @param {HTMLCanvasElement} canvas Canvas element.
  3559.  * @param {Object} spritePos Position of image in sprite.
  3560.  * @param {number} containerWidth
  3561.  */
  3562. function Cloud(canvas, spritePos, containerWidth) {
  3563.   this.canvas = canvas;
  3564.   this.canvasCtx = this.canvas.getContext('2d');
  3565.   this.spritePos = spritePos;
  3566.   this.containerWidth = containerWidth;
  3567.   this.xPos = containerWidth;
  3568.   this.yPos = 0;
  3569.   this.remove = false;
  3570.   this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP,
  3571.       Cloud.config.MAX_CLOUD_GAP);
  3572.  
  3573.   this.init();
  3574. };
  3575.  
  3576.  
  3577. /**
  3578.  * Cloud object config.
  3579.  * @enum {number}
  3580.  */
  3581. Cloud.config = {
  3582.   HEIGHT: 14,
  3583.   MAX_CLOUD_GAP: 400,
  3584.   MAX_SKY_LEVEL: 30,
  3585.   MIN_CLOUD_GAP: 100,
  3586.   MIN_SKY_LEVEL: 71,
  3587.   WIDTH: 46
  3588. };
  3589.  
  3590.  
  3591. Cloud.prototype = {
  3592.   /**
  3593.    * Initialise the cloud. Sets the Cloud height.
  3594.    */
  3595.   init: function() {
  3596.     this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL,
  3597.         Cloud.config.MIN_SKY_LEVEL);
  3598.     this.draw();
  3599.   },
  3600.  
  3601.   /**
  3602.    * Draw the cloud.
  3603.    */
  3604.   draw: function() {
  3605.     this.canvasCtx.save();
  3606.     var sourceWidth = Cloud.config.WIDTH;
  3607.     var sourceHeight = Cloud.config.HEIGHT;
  3608.  
  3609.     if (IS_HIDPI) {
  3610.       sourceWidth = sourceWidth * 2;
  3611.       sourceHeight = sourceHeight * 2;
  3612.     }
  3613.  
  3614.     this.canvasCtx.drawImage(Runner.imageSprite, this.spritePos.x,
  3615.         this.spritePos.y,
  3616.         sourceWidth, sourceHeight,
  3617.         this.xPos, this.yPos,
  3618.         Cloud.config.WIDTH, Cloud.config.HEIGHT);
  3619.  
  3620.     this.canvasCtx.restore();
  3621.   },
  3622.  
  3623.   /**
  3624.    * Update the cloud position.
  3625.    * @param {number} speed
  3626.    */
  3627.   update: function(speed) {
  3628.     if (!this.remove) {
  3629.       this.xPos -= Math.ceil(speed);
  3630.       this.draw();
  3631.  
  3632.       // Mark as removeable if no longer in the canvas.
  3633.       if (!this.isVisible()) {
  3634.         this.remove = true;
  3635.       }
  3636.     }
  3637.   },
  3638.  
  3639.   /**
  3640.    * Check if the cloud is visible on the stage.
  3641.    * @return {boolean}
  3642.    */
  3643.   isVisible: function() {
  3644.     return this.xPos + Cloud.config.WIDTH > 0;
  3645.   }
  3646. };
  3647.  
  3648.  
  3649. //******************************************************************************
  3650.  
  3651. /**
  3652.  * Nightmode shows a moon and stars on the horizon.
  3653.  */
  3654. function NightMode(canvas, spritePos, containerWidth) {
  3655.   this.spritePos = spritePos;
  3656.   this.canvas = canvas;
  3657.   this.canvasCtx = canvas.getContext('2d');
  3658.   this.xPos = containerWidth - 50;
  3659.   this.yPos = 30;
  3660.   this.currentPhase = 0;
  3661.   this.opacity = 0;
  3662.   this.containerWidth = containerWidth;
  3663.   this.stars = [];
  3664.   this.drawStars = false;
  3665.   this.placeStars();
  3666. };
  3667.  
  3668. /**
  3669.  * @enum {number}
  3670.  */
  3671. NightMode.config = {
  3672.   FADE_SPEED: 0.035,
  3673.   HEIGHT: 40,
  3674.   MOON_SPEED: 0.25,
  3675.   NUM_STARS: 2,
  3676.   STAR_SIZE: 9,
  3677.   STAR_SPEED: 0.3,
  3678.   STAR_MAX_Y: 70,
  3679.   WIDTH: 20
  3680. };
  3681.  
  3682. NightMode.phases = [140, 120, 100, 60, 40, 20, 0];
  3683.  
  3684. NightMode.prototype = {
  3685.   /**
  3686.    * Update moving moon, changing phases.
  3687.    * @param {boolean} activated Whether night mode is activated.
  3688.    * @param {number} delta
  3689.    */
  3690.   update: function(activated, delta) {
  3691.     // Moon phase.
  3692.     if (activated && this.opacity == 0) {
  3693.      this.currentPhase++;
  3694.  
  3695.       if (this.currentPhase >= NightMode.phases.length) {
  3696.         this.currentPhase = 0;
  3697.       }
  3698.     }
  3699.  
  3700.     // Fade in / out.
  3701.     if (activated && (this.opacity < 1 || this.opacity == 0)) {
  3702.      this.opacity += NightMode.config.FADE_SPEED;
  3703.     } else if (this.opacity > 0) {
  3704.       this.opacity -= NightMode.config.FADE_SPEED;
  3705.     }
  3706.  
  3707.     // Set moon positioning.
  3708.     if (this.opacity > 0) {
  3709.       this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED);
  3710.  
  3711.       // Update stars.
  3712.       if (this.drawStars) {
  3713.          for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
  3714.            this.stars[i].x = this.updateXPos(this.stars[i].x,
  3715.                NightMode.config.STAR_SPEED);
  3716.         }
  3717.      }
  3718.      this.draw();
  3719.    } else {
  3720.      this.opacity = 0;
  3721.      this.placeStars();
  3722.    }
  3723.    this.drawStars = true;
  3724.  },
  3725.  
  3726.  updateXPos: function(currentPos, speed) {
  3727.    if (currentPos < -NightMode.config.WIDTH) {
  3728.      currentPos = this.containerWidth;
  3729.    } else {
  3730.      currentPos -= speed;
  3731.    }
  3732.    return currentPos;
  3733.  },
  3734.  
  3735.  draw: function() {
  3736.    var moonSourceWidth = this.currentPhase == 3 ? NightMode.config.WIDTH * 2 :
  3737.         NightMode.config.WIDTH;
  3738.    var moonSourceHeight = NightMode.config.HEIGHT;
  3739.    var moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase];
  3740.    var moonOutputWidth = moonSourceWidth;
  3741.    var starSize = NightMode.config.STAR_SIZE;
  3742.    var starSourceX = Runner.spriteDefinition.LDPI.STAR.x;
  3743.  
  3744.    if (IS_HIDPI) {
  3745.      moonSourceWidth *= 2;
  3746.      moonSourceHeight *= 2;
  3747.      moonSourceX = this.spritePos.x +
  3748.          (NightMode.phases[this.currentPhase] * 2);
  3749.      starSize *= 2;
  3750.      starSourceX = Runner.spriteDefinition.HDPI.STAR.x;
  3751.    }
  3752.  
  3753.    this.canvasCtx.save();
  3754.    this.canvasCtx.globalAlpha = this.opacity;
  3755.  
  3756.    // Stars.
  3757.    if (this.drawStars) {
  3758.      for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
  3759.        this.canvasCtx.drawImage(Runner.imageSprite,
  3760.            starSourceX, this.stars[i].sourceY, starSize, starSize,
  3761.            Math.round(this.stars[i].x), this.stars[i].y,
  3762.            NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE);
  3763.      }
  3764.    }
  3765.  
  3766.    // Moon.
  3767.    this.canvasCtx.drawImage(Runner.imageSprite, moonSourceX,
  3768.        this.spritePos.y, moonSourceWidth, moonSourceHeight,
  3769.        Math.round(this.xPos), this.yPos,
  3770.        moonOutputWidth, NightMode.config.HEIGHT);
  3771.  
  3772.    this.canvasCtx.globalAlpha = 1;
  3773.    this.canvasCtx.restore();
  3774.  },
  3775.  
  3776.  // Do star placement.
  3777.  placeStars: function() {
  3778.    var segmentSize = Math.round(this.containerWidth /
  3779.        NightMode.config.NUM_STARS);
  3780.  
  3781.    for (var i = 0; i < NightMode.config.NUM_STARS; i++) {
  3782.      this.stars[i] = {};
  3783.      this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1));
  3784.      this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y);
  3785.  
  3786.      if (IS_HIDPI) {
  3787.        this.stars[i].sourceY = Runner.spriteDefinition.HDPI.STAR.y +
  3788.            NightMode.config.STAR_SIZE * 2 * i;
  3789.      } else {
  3790.        this.stars[i].sourceY = Runner.spriteDefinition.LDPI.STAR.y +
  3791.            NightMode.config.STAR_SIZE * i;
  3792.      }
  3793.    }
  3794.  },
  3795.  
  3796.  reset: function() {
  3797.    this.currentPhase = 0;
  3798.    this.opacity = 0;
  3799.    this.update(false);
  3800.  }
  3801.  
  3802. };
  3803.  
  3804.  
  3805. //******************************************************************************
  3806.  
  3807. /**
  3808. * Horizon Line.
  3809. * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon.
  3810. * @param {HTMLCanvasElement} canvas
  3811. * @param {Object} spritePos Horizon position in sprite.
  3812. * @constructor
  3813. */
  3814. function HorizonLine(canvas, spritePos) {
  3815.  this.spritePos = spritePos;
  3816.  this.canvas = canvas;
  3817.  this.canvasCtx = canvas.getContext('2d');
  3818.  this.sourceDimensions = {};
  3819.  this.dimensions = HorizonLine.dimensions;
  3820.  this.sourceXPos = [this.spritePos.x, this.spritePos.x +
  3821.      this.dimensions.WIDTH];
  3822.  this.xPos = [];
  3823.  this.yPos = 0;
  3824.  this.bumpThreshold = 0.5;
  3825.  
  3826.  this.setSourceDimensions();
  3827.  this.draw();
  3828. };
  3829.  
  3830.  
  3831. /**
  3832. * Horizon line dimensions.
  3833. * @enum {number}
  3834. */
  3835. HorizonLine.dimensions = {
  3836.  WIDTH: 600,
  3837.  HEIGHT: 12,
  3838.  YPOS: 127
  3839. };
  3840.  
  3841.  
  3842. HorizonLine.prototype = {
  3843.  /**
  3844.   * Set the source dimensions of the horizon line.
  3845.   */
  3846.  setSourceDimensions: function() {
  3847.  
  3848.    for (var dimension in HorizonLine.dimensions) {
  3849.      if (IS_HIDPI) {
  3850.        if (dimension != 'YPOS') {
  3851.          this.sourceDimensions[dimension] =
  3852.              HorizonLine.dimensions[dimension] * 2;
  3853.        }
  3854.      } else {
  3855.        this.sourceDimensions[dimension] =
  3856.            HorizonLine.dimensions[dimension];
  3857.      }
  3858.      this.dimensions[dimension] = HorizonLine.dimensions[dimension];
  3859.    }
  3860.  
  3861.    this.xPos = [0, HorizonLine.dimensions.WIDTH];
  3862.    this.yPos = HorizonLine.dimensions.YPOS;
  3863.  },
  3864.  
  3865.  /**
  3866.   * Return the crop x position of a type.
  3867.   */
  3868.  getRandomType: function() {
  3869.    return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
  3870.   },
  3871.  
  3872.   /**
  3873.    * Draw the horizon line.
  3874.    */
  3875.   draw: function() {
  3876.     this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0],
  3877.         this.spritePos.y,
  3878.         this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
  3879.         this.xPos[0], this.yPos,
  3880.         this.dimensions.WIDTH, this.dimensions.HEIGHT);
  3881.  
  3882.     this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[1],
  3883.         this.spritePos.y,
  3884.         this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
  3885.         this.xPos[1], this.yPos,
  3886.         this.dimensions.WIDTH, this.dimensions.HEIGHT);
  3887.   },
  3888.  
  3889.   /**
  3890.    * Update the x position of an indivdual piece of the line.
  3891.    * @param {number} pos Line position.
  3892.    * @param {number} increment
  3893.    */
  3894.   updateXPos: function(pos, increment) {
  3895.     var line1 = pos;
  3896.     var line2 = pos == 0 ? 1 : 0;
  3897.  
  3898.     this.xPos[line1] -= increment;
  3899.     this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;
  3900.  
  3901.     if (this.xPos[line1] <= -this.dimensions.WIDTH) {
  3902.      this.xPos[line1] += this.dimensions.WIDTH * 2;
  3903.      this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
  3904.      this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x;
  3905.    }
  3906.  },
  3907.  
  3908.  /**
  3909.   * Update the horizon line.
  3910.   * @param {number} deltaTime
  3911.   * @param {number} speed
  3912.   */
  3913.  update: function(deltaTime, speed) {
  3914.    var increment = Math.floor(speed * (FPS / 1000) * deltaTime);
  3915.  
  3916.    if (this.xPos[0] <= 0) {
  3917.      this.updateXPos(0, increment);
  3918.    } else {
  3919.      this.updateXPos(1, increment);
  3920.    }
  3921.    this.draw();
  3922.  },
  3923.  
  3924.  /**
  3925.   * Reset horizon to the starting position.
  3926.   */
  3927.  reset: function() {
  3928.    this.xPos[0] = 0;
  3929.    this.xPos[1] = HorizonLine.dimensions.WIDTH;
  3930.  }
  3931. };
  3932.  
  3933.  
  3934. //******************************************************************************
  3935.  
  3936. /**
  3937. * Horizon background class.
  3938. * @param {HTMLCanvasElement} canvas
  3939. * @param {Object} spritePos Sprite positioning.
  3940. * @param {Object} dimensions Canvas dimensions.
  3941. * @param {number} gapCoefficient
  3942. * @constructor
  3943. */
  3944. function Horizon(canvas, spritePos, dimensions, gapCoefficient) {
  3945.  this.canvas = canvas;
  3946.  this.canvasCtx = this.canvas.getContext('2d');
  3947.  this.config = Horizon.config;
  3948.  this.dimensions = dimensions;
  3949.  this.gapCoefficient = gapCoefficient;
  3950.  this.obstacles = [];
  3951.  this.obstacleHistory = [];
  3952.  this.horizonOffsets = [0, 0];
  3953.  this.cloudFrequency = this.config.CLOUD_FREQUENCY;
  3954.  this.spritePos = spritePos;
  3955.  this.nightMode = null;
  3956.  
  3957.  // Cloud
  3958.  this.clouds = [];
  3959.  this.cloudSpeed = this.config.BG_CLOUD_SPEED;
  3960.  
  3961.  // Horizon
  3962.  this.horizonLine = null;
  3963.  this.init();
  3964. };
  3965.  
  3966.  
  3967. /**
  3968. * Horizon config.
  3969. * @enum {number}
  3970. */
  3971. Horizon.config = {
  3972.  BG_CLOUD_SPEED: 0.2,
  3973.  BUMPY_THRESHOLD: .3,
  3974.  CLOUD_FREQUENCY: .5,
  3975.  HORIZON_HEIGHT: 16,
  3976.  MAX_CLOUDS: 6
  3977. };
  3978.  
  3979.  
  3980. Horizon.prototype = {
  3981.  /**
  3982.   * Initialise the horizon. Just add the line and a cloud. No obstacles.
  3983.   */
  3984.  init: function() {
  3985.    this.addCloud();
  3986.    this.horizonLine = new HorizonLine(this.canvas, this.spritePos.HORIZON);
  3987.    this.nightMode = new NightMode(this.canvas, this.spritePos.MOON,
  3988.        this.dimensions.WIDTH);
  3989.  },
  3990.  
  3991.  /**
  3992.   * @param {number} deltaTime
  3993.   * @param {number} currentSpeed
  3994.   * @param {boolean} updateObstacles Used as an override to prevent
  3995.   *     the obstacles from being updated / added. This happens in the
  3996.   *     ease in section.
  3997.   * @param {boolean} showNightMode Night mode activated.
  3998.   */
  3999.  update: function(deltaTime, currentSpeed, updateObstacles, showNightMode) {
  4000.    this.runningTime += deltaTime;
  4001.    this.horizonLine.update(deltaTime, currentSpeed);
  4002.    this.nightMode.update(showNightMode);
  4003.    this.updateClouds(deltaTime, currentSpeed);
  4004.  
  4005.    if (updateObstacles) {
  4006.      this.updateObstacles(deltaTime, currentSpeed);
  4007.    }
  4008.  },
  4009.  
  4010.  /**
  4011.   * Update the cloud positions.
  4012.   * @param {number} deltaTime
  4013.   * @param {number} currentSpeed
  4014.   */
  4015.  updateClouds: function(deltaTime, speed) {
  4016.    var cloudSpeed = this.cloudSpeed / 1000 * deltaTime * speed;
  4017.    var numClouds = this.clouds.length;
  4018.  
  4019.    if (numClouds) {
  4020.      for (var i = numClouds - 1; i >= 0; i--) {
  4021.         this.clouds[i].update(cloudSpeed);
  4022.       }
  4023.  
  4024.       var lastCloud = this.clouds[numClouds - 1];
  4025.  
  4026.       // Check for adding a new cloud.
  4027.       if (numClouds < this.config.MAX_CLOUDS &&
  4028.          (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap &&
  4029.          this.cloudFrequency > Math.random()) {
  4030.        this.addCloud();
  4031.       }
  4032.  
  4033.       // Remove expired clouds.
  4034.       this.clouds = this.clouds.filter(function(obj) {
  4035.         return !obj.remove;
  4036.       });
  4037.     } else {
  4038.       this.addCloud();
  4039.     }
  4040.   },
  4041.  
  4042.   /**
  4043.    * Update the obstacle positions.
  4044.    * @param {number} deltaTime
  4045.    * @param {number} currentSpeed
  4046.    */
  4047.   updateObstacles: function(deltaTime, currentSpeed) {
  4048.     // Obstacles, move to Horizon layer.
  4049.     var updatedObstacles = this.obstacles.slice(0);
  4050.  
  4051.     for (var i = 0; i < this.obstacles.length; i++) {
  4052.      var obstacle = this.obstacles[i];
  4053.      obstacle.update(deltaTime, currentSpeed);
  4054.  
  4055.      // Clean up existing obstacles.
  4056.      if (obstacle.remove) {
  4057.        updatedObstacles.shift();
  4058.      }
  4059.    }
  4060.    this.obstacles = updatedObstacles;
  4061.  
  4062.    if (this.obstacles.length > 0) {
  4063.       var lastObstacle = this.obstacles[this.obstacles.length - 1];
  4064.  
  4065.       if (lastObstacle && !lastObstacle.followingObstacleCreated &&
  4066.          lastObstacle.isVisible() &&
  4067.          (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
  4068.          this.dimensions.WIDTH) {
  4069.        this.addNewObstacle(currentSpeed);
  4070.         lastObstacle.followingObstacleCreated = true;
  4071.       }
  4072.     } else {
  4073.       // Create new obstacles.
  4074.       this.addNewObstacle(currentSpeed);
  4075.     }
  4076.   },
  4077.  
  4078.   removeFirstObstacle: function() {
  4079.     this.obstacles.shift();
  4080.   },
  4081.  
  4082.   /**
  4083.    * Add a new obstacle.
  4084.    * @param {number} currentSpeed
  4085.    */
  4086.   addNewObstacle: function(currentSpeed) {
  4087.     var obstacleTypeIndex = getRandomNum(0, Obstacle.types.length - 1);
  4088.     var obstacleType = Obstacle.types[obstacleTypeIndex];
  4089.  
  4090.     // Check for multiples of the same type of obstacle.
  4091.     // Also check obstacle is available at current speed.
  4092.     if (this.duplicateObstacleCheck(obstacleType.type) ||
  4093.         currentSpeed < obstacleType.minSpeed) {
  4094.      this.addNewObstacle(currentSpeed);
  4095.    } else {
  4096.      var obstacleSpritePos = this.spritePos[obstacleType.type];
  4097.  
  4098.      this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType,
  4099.          obstacleSpritePos, this.dimensions,
  4100.          this.gapCoefficient, currentSpeed, obstacleType.width));
  4101.  
  4102.      this.obstacleHistory.unshift(obstacleType.type);
  4103.  
  4104.      if (this.obstacleHistory.length > 1) {
  4105.         this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION);
  4106.       }
  4107.     }
  4108.   },
  4109.  
  4110.   /**
  4111.    * Returns whether the previous two obstacles are the same as the next one.
  4112.    * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION.
  4113.    * @return {boolean}
  4114.    */
  4115.   duplicateObstacleCheck: function(nextObstacleType) {
  4116.     var duplicateCount = 0;
  4117.  
  4118.     for (var i = 0; i < this.obstacleHistory.length; i++) {
  4119.      duplicateCount = this.obstacleHistory[i] == nextObstacleType ?
  4120.          duplicateCount + 1 : 0;
  4121.    }
  4122.    return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION;
  4123.   },
  4124.  
  4125.   /**
  4126.    * Reset the horizon layer.
  4127.    * Remove existing obstacles and reposition the horizon line.
  4128.    */
  4129.   reset: function() {
  4130.     this.obstacles = [];
  4131.     this.horizonLine.reset();
  4132.     this.nightMode.reset();
  4133.   },
  4134.  
  4135.   /**
  4136.    * Update the canvas width and scaling.
  4137.    * @param {number} width Canvas width.
  4138.    * @param {number} height Canvas height.
  4139.    */
  4140.   resize: function(width, height) {
  4141.     this.canvas.width = width;
  4142.     this.canvas.height = height;
  4143.   },
  4144.  
  4145.   /**
  4146.    * Add a new cloud to the horizon.
  4147.    */
  4148.   addCloud: function() {
  4149.     this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD,
  4150.         this.dimensions.WIDTH));
  4151.   }
  4152. };
  4153. })();
  4154. </script>
  4155. </head>
  4156. <body id="t" style="font-family: 'Segoe UI', Tahoma, sans-serif; font-size: 75%" jstcache="0" class="neterror offline">
  4157.   <div id="main-frame-error" class="interstitial-wrapper" jstcache="0">
  4158.     <div id="main-content" jstcache="0">
  4159.       <div class="icon icon-offline" jseval="updateIconClass(this.classList, iconClass)" alt="" jstcache="1" style="visibility: hidden;"></div>
  4160.       <div id="main-message" jstcache="0">
  4161.         <h1 jsselect="heading" jsvalues=".innerHTML:msg" jstcache="5">There is no Internet connection</h1>
  4162.         <p jsselect="summary" jsvalues=".innerHTML:msg" jstcache="2">There is no Internet connection</p>
  4163.         <div id="suggestions-list" style="" jsdisplay="(suggestionsSummaryList &amp;&amp; suggestionsSummaryList.length)" jstcache="6">
  4164.           <p jsvalues=".innerHTML:suggestionsSummaryListHeader" jstcache="13">Try:</p>
  4165.           <ul jsvalues=".className:suggestionsSummaryList.length == 1 ? 'single-suggestion' : ''" jstcache="14" class="">
  4166.             <li jsselect="suggestionsSummaryList" jsvalues=".innerHTML:summary" jstcache="15" jsinstance="0">Checking the network cables, modem, and router</li><li jsselect="suggestionsSummaryList" jsvalues=".innerHTML:summary" jstcache="15" jsinstance="1">Reconnecting to Wi-Fi</li><li jsselect="suggestionsSummaryList" jsvalues=".innerHTML:summary" jstcache="15" jsinstance="*2"><a href="javascript:diagnoseErrors()" id="diagnose-link" jstcache="0">Running Windows Network Diagnostics</a></li>
  4167.           </ul>
  4168.         </div>
  4169.         <div class="error-code" jscontent="errorCode" jstcache="7">DNS_PROBE_FINISHED_NO_INTERNET</div>
  4170.         <div id="diagnose-frame" class="hidden" jstcache="0"></div>
  4171.       </div>
  4172.     </div>
  4173.     <div id="buttons" class="nav-wrapper suggested-left" jstcache="0">
  4174.       <div id="control-buttons" jstcache="0">
  4175.         <button id="show-saved-copy-button" class="blue-button text-button" onclick="showSavedCopyButtonClick()" jsselect="showSavedCopyButton" jscontent="msg" jsvalues="title:title; .primary:primary" jstcache="9" style="display: none;">
  4176.         </button><button id="reload-button" class="blue-button text-button" onclick="trackClick(this.trackingId);
  4177.                     reloadButtonClick(this.url);" jsselect="reloadButton" jsvalues=".url:reloadUrl; .trackingId:reloadTrackingId" jscontent="msg" jstcache="8" style="display: none;">Reload</button>
  4178.        
  4179.         <button id="download-button" class="blue-button text-button" onclick="downloadButtonClick()" jsselect="downloadButton" jscontent="msg" jsvalues=".disabledText:disabledMsg" jstcache="10" style="display: none;">
  4180.         </button>
  4181.       </div>
  4182.       <button id="details-button" class="text-button small-link" onclick="detailsButtonClick(); toggleHelpBox()" jscontent="details" jsdisplay="(suggestionsDetails &amp;&amp; suggestionsDetails.length > 0) || diagnose" jsvalues=".detailsText:details; .hideDetailsText:hideDetails;" jstcache="3" style="display: none;"></button>
  4183.     </div>
  4184.     <div id="details" class="hidden" jstcache="0">
  4185.       <div class="suggestions" jsselect="suggestionsDetails" jstcache="4" jsinstance="*0" style="display: none;">
  4186.         <div class="suggestion-header" jsvalues=".innerHTML:header" jstcache="11"></div>
  4187.         <div class="suggestion-body" jsvalues=".innerHTML:body" jstcache="12"></div>
  4188.       </div>
  4189.     </div>
  4190.   <div class="runner-container"><canvas class="runner-canvas" width="600" height="150" style="width: 600px; height: 150px;"></canvas></div></div>
  4191.   <div id="sub-frame-error" jstcache="0">
  4192.     <!-- Show details when hovering over the icon, in case the details are
  4193.         hidden because they're too large. -->
  4194.     <div class="icon icon-offline" jseval="updateIconClass(this.classList, iconClass)" jstcache="1"></div>
  4195.     <div id="sub-frame-error-details" jsselect="summary" jsvalues=".innerHTML:msg" jstcache="2">There is no Internet connection</div>
  4196.   </div>
  4197.  
  4198.   <div id="offline-resources" jstcache="0">
  4199.     <img id="offline-resources-1x" src="data:image/png;base64,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" jstcache="0">
  4200.     <img id="offline-resources-2x" src="data:image/png;base64,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" jstcache="0">
  4201.     <template id="audio-resources" jstcache="0">
  4202.       <audio id="offline-sound-press" src="data:audio/mpeg;base64,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" i18n-processed=""></audio>
  4203.       <audio id="offline-sound-hit" src="data:audio/mpeg;base64,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" i18n-processed=""></audio>
  4204.       <audio id="offline-sound-reached" src="data:audio/mpeg;base64,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" i18n-processed=""></audio>
  4205.     </template>
  4206.   </div>
  4207.  
  4208.  
  4209. <script jstcache="0">// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4210. // Use of this source code is governed by a BSD-style license that can be
  4211. // found in the LICENSE file.
  4212.  
  4213. /**
  4214.  * @fileoverview This file defines a singleton which provides access to all data
  4215.  * that is available as soon as the page's resources are loaded (before DOM
  4216.  * content has finished loading). This data includes both localized strings and
  4217.  * any data that is important to have ready from a very early stage (e.g. things
  4218.  * that must be displayed right away).
  4219.  *
  4220.  * Note that loadTimeData is not guaranteed to be consistent between page
  4221.  * refreshes (https://crbug.com/740629) and should not contain values that might
  4222.  * change if the page is re-opened later.
  4223.  */
  4224.  
  4225. /**
  4226.  * @typedef {{
  4227.  *   substitutions: (Array<string>|undefined),
  4228.  *   attrs: (Object<function(Node, string):boolean>|undefined),
  4229.  *   tags: (Array<string>|undefined),
  4230.  * }}
  4231.  */
  4232. var SanitizeInnerHtmlOpts;
  4233.  
  4234. /** @type {!LoadTimeData} */ var loadTimeData;
  4235.  
  4236. // Expose this type globally as a temporary work around until
  4237. // https://github.com/google/closure-compiler/issues/544 is fixed.
  4238. /** @constructor */
  4239. function LoadTimeData(){}
  4240.  
  4241. (function() {
  4242.   'use strict';
  4243.  
  4244.   LoadTimeData.prototype = {
  4245.     /**
  4246.      * Sets the backing object.
  4247.      *
  4248.      * Note that there is no getter for |data_| to discourage abuse of the form:
  4249.      *
  4250.      *     var value = loadTimeData.data()['key'];
  4251.      *
  4252.      * @param {Object} value The de-serialized page data.
  4253.      */
  4254.     set data(value) {
  4255.       expect(!this.data_, 'Re-setting data.');
  4256.       this.data_ = value;
  4257.     },
  4258.  
  4259.     /**
  4260.      * Returns a JsEvalContext for |data_|.
  4261.      * @returns {JsEvalContext}
  4262.      */
  4263.     createJsEvalContext: function() {
  4264.       return new JsEvalContext(this.data_);
  4265.     },
  4266.  
  4267.     /**
  4268.      * @param {string} id An ID of a value that might exist.
  4269.      * @return {boolean} True if |id| is a key in the dictionary.
  4270.      */
  4271.     valueExists: function(id) {
  4272.       return id in this.data_;
  4273.     },
  4274.  
  4275.     /**
  4276.      * Fetches a value, expecting that it exists.
  4277.      * @param {string} id The key that identifies the desired value.
  4278.      * @return {*} The corresponding value.
  4279.      */
  4280.     getValue: function(id) {
  4281.       expect(this.data_, 'No data. Did you remember to include strings.js?');
  4282.       var value = this.data_[id];
  4283.       expect(typeof value != 'undefined', 'Could not find value for ' + id);
  4284.       return value;
  4285.     },
  4286.  
  4287.     /**
  4288.      * As above, but also makes sure that the value is a string.
  4289.      * @param {string} id The key that identifies the desired string.
  4290.      * @return {string} The corresponding string value.
  4291.      */
  4292.     getString: function(id) {
  4293.       var value = this.getValue(id);
  4294.       expectIsType(id, value, 'string');
  4295.       return /** @type {string} */ (value);
  4296.     },
  4297.  
  4298.     /**
  4299.      * Returns a formatted localized string where $1 to $9 are replaced by the
  4300.      * second to the tenth argument.
  4301.      * @param {string} id The ID of the string we want.
  4302.      * @param {...(string|number)} var_args The extra values to include in the
  4303.      *     formatted output.
  4304.      * @return {string} The formatted string.
  4305.      */
  4306.     getStringF: function(id, var_args) {
  4307.       var value = this.getString(id);
  4308.       if (!value)
  4309.         return '';
  4310.  
  4311.       var args = Array.prototype.slice.call(arguments);
  4312.       args[0] = value;
  4313.       return this.substituteString.apply(this, args);
  4314.     },
  4315.  
  4316.     /**
  4317.      * Make a string safe for use with with Polymer bindings that are
  4318.      * inner-h-t-m-l (or other innerHTML use).
  4319.      * @param {string} rawString The unsanitized string.
  4320.      * @param {SanitizeInnerHtmlOpts=} opts Optional additional allowed tags and
  4321.      *     attributes.
  4322.      * @return {string}
  4323.      */
  4324.     sanitizeInnerHtml: function(rawString, opts) {
  4325.       opts = opts || {};
  4326.       return parseHtmlSubset('<b>' + rawString + '</b>', opts.tags, opts.attrs)
  4327.           .firstChild.innerHTML;
  4328.     },
  4329.  
  4330.     /**
  4331.      * Returns a formatted localized string where $1 to $9 are replaced by the
  4332.      * second to the tenth argument. Any standalone $ signs must be escaped as
  4333.      * $$.
  4334.      * @param {string} label The label to substitute through.
  4335.      *     This is not an resource ID.
  4336.      * @param {...(string|number)} var_args The extra values to include in the
  4337.      *     formatted output.
  4338.      * @return {string} The formatted string.
  4339.      */
  4340.     substituteString: function(label, var_args) {
  4341.       var varArgs = arguments;
  4342.       return label.replace(/\$(.|$|\n)/g, function(m) {
  4343.         assert(m.match(/\$[$1-9]/), 'Unescaped $ found in localized string.');
  4344.         return m == '$$' ? '$' : varArgs[m[1]];
  4345.       });
  4346.     },
  4347.  
  4348.     /**
  4349.      * Returns a formatted string where $1 to $9 are replaced by the second to
  4350.      * tenth argument, split apart into a list of pieces describing how the
  4351.      * substitution was performed. Any standalone $ signs must be escaped as $$.
  4352.      * @param {string} label A localized string to substitute through.
  4353.      *     This is not an resource ID.
  4354.      * @param {...(string|number)} var_args The extra values to include in the
  4355.      *     formatted output.
  4356.      * @return {!Array<!{value: string, arg: (null|string)}>} The formatted
  4357.      *     string pieces.
  4358.      */
  4359.     getSubstitutedStringPieces: function(label, var_args) {
  4360.       var varArgs = arguments;
  4361.       // Split the string by separately matching all occurrences of $1-9 and of
  4362.       // non $1-9 pieces.
  4363.       var pieces = (label.match(/(\$[1-9])|(([^$]|\$([^1-9]|$))+)/g) ||
  4364.                     []).map(function(p) {
  4365.         // Pieces that are not $1-9 should be returned after replacing $$
  4366.         // with $.
  4367.         if (!p.match(/^\$[1-9]$/)) {
  4368.           assert(
  4369.               (p.match(/\$/g) || []).length % 2 == 0,
  4370.               'Unescaped $ found in localized string.');
  4371.           return {value: p.replace(/\$\$/g, '$'), arg: null};
  4372.         }
  4373.  
  4374.         // Otherwise, return the substitution value.
  4375.         return {value: varArgs[p[1]], arg: p};
  4376.       });
  4377.  
  4378.       return pieces;
  4379.     },
  4380.  
  4381.     /**
  4382.      * As above, but also makes sure that the value is a boolean.
  4383.      * @param {string} id The key that identifies the desired boolean.
  4384.      * @return {boolean} The corresponding boolean value.
  4385.      */
  4386.     getBoolean: function(id) {
  4387.       var value = this.getValue(id);
  4388.       expectIsType(id, value, 'boolean');
  4389.       return /** @type {boolean} */ (value);
  4390.     },
  4391.  
  4392.     /**
  4393.      * As above, but also makes sure that the value is an integer.
  4394.      * @param {string} id The key that identifies the desired number.
  4395.      * @return {number} The corresponding number value.
  4396.      */
  4397.     getInteger: function(id) {
  4398.       var value = this.getValue(id);
  4399.       expectIsType(id, value, 'number');
  4400.       expect(value == Math.floor(value), 'Number isn\'t integer: ' + value);
  4401.       return /** @type {number} */ (value);
  4402.     },
  4403.  
  4404.     /**
  4405.      * Override values in loadTimeData with the values found in |replacements|.
  4406.      * @param {Object} replacements The dictionary object of keys to replace.
  4407.      */
  4408.     overrideValues: function(replacements) {
  4409.       expect(
  4410.           typeof replacements == 'object',
  4411.           'Replacements must be a dictionary object.');
  4412.       for (var key in replacements) {
  4413.         this.data_[key] = replacements[key];
  4414.       }
  4415.     }
  4416.   };
  4417.  
  4418.   /**
  4419.    * Checks condition, displays error message if expectation fails.
  4420.    * @param {*} condition The condition to check for truthiness.
  4421.    * @param {string} message The message to display if the check fails.
  4422.    */
  4423.   function expect(condition, message) {
  4424.     if (!condition) {
  4425.       console.error(
  4426.           'Unexpected condition on ' + document.location.href + ': ' + message);
  4427.     }
  4428.   }
  4429.  
  4430.   /**
  4431.    * Checks that the given value has the given type.
  4432.    * @param {string} id The id of the value (only used for error message).
  4433.    * @param {*} value The value to check the type on.
  4434.    * @param {string} type The type we expect |value| to be.
  4435.    */
  4436.   function expectIsType(id, value, type) {
  4437.     expect(
  4438.         typeof value == type, '[' + value + '] (' + id + ') is not a ' + type);
  4439.   }
  4440.  
  4441.   expect(!loadTimeData, 'should only include this file once');
  4442.   loadTimeData = new LoadTimeData;
  4443. })();
  4444. </script><script jstcache="0">loadTimeData.data = {"details":"Details","errorCode":"DNS_PROBE_POSSIBLE","fontfamily":"'Segoe UI', Tahoma, sans-serif","fontsize":"75%","heading":{"hostName":"slavtel.com","msg":"This site can’t be reached"},"hideDetails":"Hide details","iconClass":"icon-generic","language":"en","reloadButton":{"msg":"Reload","reloadTrackingId":-1,"reloadUrl":"http://slavtel.com/"},"suggestionsDetails":[],"suggestionsSummaryList":[{"summary":"\u003Ca href=\"javascript:diagnoseErrors()\" id=\"diagnose-link\">Try running Windows Network Diagnostics\u003C/a>."}],"summary":{"failedUrl":"http://slavtel.com/","hostName":"slavtel.com","msg":"\u003Cstrong jscontent=\"hostName\">\u003C/strong>’s \u003Cabbr id=\"dnsDefinition\">DNS address\u003C/abbr> could not be found. Diagnosing the problem."},"textdirection":"ltr","title":"slavtel.com"};</script><script jstcache="0">// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4445. // Use of this source code is governed by a BSD-style license that can be
  4446. // found in the LICENSE file.
  4447.  
  4448. // Note: vulcanize sometimes disables GRIT processing. If you're importing i18n
  4449. // stuff with <link rel="import">, you should probably be using
  4450. // html/i18n_template.html instead of this file.
  4451.  
  4452. // // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4453. // Use of this source code is governed by a BSD-style license that can be
  4454. // found in the LICENSE file.
  4455.  
  4456. /** @typedef {Document|DocumentFragment|Element} */
  4457. var ProcessingRoot;
  4458.  
  4459. /**
  4460.  * @fileoverview This is a simple template engine inspired by JsTemplates
  4461.  * optimized for i18n.
  4462.  *
  4463.  * It currently supports three handlers:
  4464.  *
  4465.  *   * i18n-content which sets the textContent of the element.
  4466.  *
  4467.  *     <span i18n-content="myContent"></span>
  4468.  *
  4469.  *   * i18n-options which generates <option> elements for a <select>.
  4470.  *
  4471.  *     <select i18n-options="myOptionList"></select>
  4472.  *
  4473.  *   * i18n-values is a list of attribute-value or property-value pairs.
  4474.  *     Properties are prefixed with a '.' and can contain nested properties.
  4475.  *
  4476.  *     <span i18n-values="title:myTitle;.style.fontSize:fontSize"></span>
  4477.  *
  4478.  * This file is a copy of i18n_template.js, with minor tweaks to support using
  4479.  * load_time_data.js. It should replace i18n_template.js eventually.
  4480.  */
  4481.  
  4482. var i18nTemplate = (function() {
  4483.   /**
  4484.    * This provides the handlers for the templating engine. The key is used as
  4485.    * the attribute name and the value is the function that gets called for every
  4486.    * single node that has this attribute.
  4487.    * @type {!Object}
  4488.    */
  4489.   var handlers = {
  4490.     /**
  4491.      * This handler sets the textContent of the element.
  4492.      * @param {!HTMLElement} element The node to modify.
  4493.      * @param {string} key The name of the value in |data|.
  4494.      * @param {!LoadTimeData} data The data source to draw from.
  4495.      * @param {!Set<ProcessingRoot>} visited
  4496.      */
  4497.     'i18n-content': function(element, key, data, visited) {
  4498.       element.textContent = data.getString(key);
  4499.     },
  4500.  
  4501.     /**
  4502.      * This handler adds options to a <select> element.
  4503.      * @param {!HTMLElement} select The node to modify.
  4504.      * @param {string} key The name of the value in |data|. It should
  4505.      *     identify an array of values to initialize an <option>. Each value,
  4506.      *     if a pair, represents [content, value]. Otherwise, it should be a
  4507.      *     content string with no value.
  4508.      * @param {!LoadTimeData} data The data source to draw from.
  4509.      * @param {!Set<ProcessingRoot>} visited
  4510.      */
  4511.     'i18n-options': function(select, key, data, visited) {
  4512.       var options = data.getValue(key);
  4513.       options.forEach(function(optionData) {
  4514.         var option = typeof optionData == 'string' ?
  4515.             new Option(optionData) :
  4516.             new Option(optionData[1], optionData[0]);
  4517.         select.appendChild(option);
  4518.       });
  4519.     },
  4520.  
  4521.     /**
  4522.      * This is used to set HTML attributes and DOM properties. The syntax is:
  4523.      *   attributename:key;
  4524.      *   .domProperty:key;
  4525.      *   .nested.dom.property:key
  4526.      * @param {!HTMLElement} element The node to modify.
  4527.      * @param {string} attributeAndKeys The path of the attribute to modify
  4528.      *     followed by a colon, and the name of the value in |data|.
  4529.      *     Multiple attribute/key pairs may be separated by semicolons.
  4530.      * @param {!LoadTimeData} data The data source to draw from.
  4531.      * @param {!Set<ProcessingRoot>} visited
  4532.      */
  4533.     'i18n-values': function(element, attributeAndKeys, data, visited) {
  4534.       var parts = attributeAndKeys.replace(/\s/g, '').split(/;/);
  4535.       parts.forEach(function(part) {
  4536.         if (!part)
  4537.           return;
  4538.  
  4539.         var attributeAndKeyPair = part.match(/^([^:]+):(.+)$/);
  4540.         if (!attributeAndKeyPair)
  4541.           throw new Error('malformed i18n-values: ' + attributeAndKeys);
  4542.  
  4543.         var propName = attributeAndKeyPair[1];
  4544.         var propExpr = attributeAndKeyPair[2];
  4545.  
  4546.         var value = data.getValue(propExpr);
  4547.  
  4548.         // Allow a property of the form '.foo.bar' to assign a value into
  4549.         // element.foo.bar.
  4550.         if (propName[0] == '.') {
  4551.           var path = propName.slice(1).split('.');
  4552.           var targetObject = element;
  4553.           while (targetObject && path.length > 1) {
  4554.            targetObject = targetObject[path.shift()];
  4555.           }
  4556.           if (targetObject) {
  4557.             targetObject[path] = value;
  4558.             // In case we set innerHTML (ignoring others) we need to recursively
  4559.             // check the content.
  4560.             if (path == 'innerHTML') {
  4561.               for (var i = 0; i < element.children.length; ++i) {
  4562.                processWithoutCycles(element.children[i], data, visited, false);
  4563.              }
  4564.            }
  4565.          }
  4566.        } else {
  4567.          element.setAttribute(propName, /** @type {string} */ (value));
  4568.        }
  4569.      });
  4570.    }
  4571.  };
  4572.  
  4573.  var prefixes = [''];
  4574.  
  4575.  // Only look through shadow DOM when it's supported. As of April 2015, iOS
  4576.  // Chrome doesn't support shadow DOM.
  4577.  if (Element.prototype.createShadowRoot)
  4578.    prefixes.push('* /deep/ ');
  4579.  
  4580.  var attributeNames = Object.keys(handlers);
  4581.  var selector = prefixes
  4582.                     .map(function(prefix) {
  4583.                       return prefix + '[' +
  4584.                           attributeNames.join('], ' + prefix + '[') + ']';
  4585.                     })
  4586.                     .join(', ');
  4587.  
  4588.  /**
  4589.   * Processes a DOM tree using a |data| source to populate template values.
  4590.   * @param {!ProcessingRoot} root The root of the DOM tree to process.
  4591.   * @param {!LoadTimeData} data The data to draw from.
  4592.   */
  4593.  function process(root, data) {
  4594.    processWithoutCycles(root, data, new Set(), true);
  4595.  }
  4596.  
  4597.  /**
  4598.   * Internal process() method that stops cycles while processing.
  4599.   * @param {!ProcessingRoot} root
  4600.   * @param {!LoadTimeData} data
  4601.   * @param {!Set<ProcessingRoot>} visited Already visited roots.
  4602.    * @param {boolean} mark Whether nodes should be marked processed.
  4603.    */
  4604.   function processWithoutCycles(root, data, visited, mark) {
  4605.     if (visited.has(root)) {
  4606.       // Found a cycle. Stop it.
  4607.       return;
  4608.     }
  4609.  
  4610.     // Mark the node as visited before recursing.
  4611.     visited.add(root);
  4612.  
  4613.     var importLinks = root.querySelectorAll('link[rel=import]');
  4614.     for (var i = 0; i < importLinks.length; ++i) {
  4615.      var importLink = /** @type {!HTMLLinkElement} */ (importLinks[i]);
  4616.      if (!importLink.import) {
  4617.        // Happens when a <link rel=import> is inside a <template>.
  4618.         // TODO(dbeam): should we log an error if we detect that here?
  4619.         continue;
  4620.       }
  4621.       processWithoutCycles(importLink.import, data, visited, mark);
  4622.     }
  4623.  
  4624.     var templates = root.querySelectorAll('template');
  4625.     for (var i = 0; i < templates.length; ++i) {
  4626.      var template = /** @type {HTMLTemplateElement} */ (templates[i]);
  4627.      if (!template.content)
  4628.        continue;
  4629.      processWithoutCycles(template.content, data, visited, mark);
  4630.    }
  4631.  
  4632.    var isElement = root instanceof Element;
  4633.    if (isElement && root.webkitMatchesSelector(selector))
  4634.      processElement(/** @type {!Element} */ (root), data, visited);
  4635.  
  4636.    var elements = root.querySelectorAll(selector);
  4637.    for (var i = 0; i < elements.length; ++i) {
  4638.      processElement(elements[i], data, visited);
  4639.    }
  4640.  
  4641.    if (mark) {
  4642.      var processed = isElement ? [root] : root.children;
  4643.      if (processed) {
  4644.        for (var i = 0; i < processed.length; ++i) {
  4645.          processed[i].setAttribute('i18n-processed', '');
  4646.        }
  4647.      }
  4648.    }
  4649.  }
  4650.  
  4651.  /**
  4652.   * Run through various [i18n-*] attributes and populate.
  4653.   * @param {!Element} element
  4654.   * @param {!LoadTimeData} data
  4655.   * @param {!Set<ProcessingRoot>} visited
  4656.    */
  4657.   function processElement(element, data, visited) {
  4658.     for (var i = 0; i < attributeNames.length; i++) {
  4659.      var name = attributeNames[i];
  4660.      var attribute = element.getAttribute(name);
  4661.      if (attribute != null)
  4662.        handlers[name](element, attribute, data, visited);
  4663.    }
  4664.  }
  4665.  
  4666.  return {process: process};
  4667. }());
  4668.  
  4669. // // Copyright 2017 The Chromium Authors. All rights reserved.
  4670. // Use of this source code is governed by a BSD-style license that can be
  4671. // found in the LICENSE file.
  4672.  
  4673. i18nTemplate.process(document, loadTimeData);
  4674.  
  4675. </script><script jstcache="0">// Copyright (c) 2012 The Chromium Authors. All rights reserved.
  4676. // Use of this source code is governed by a BSD-style license that can be
  4677. // found in the LICENSE file.
  4678.  
  4679. // This file serves as a proxy to bring the included js file from /third_party
  4680. // into its correct location under the resources directory tree, whence it is
  4681. // delivered via a chrome://resources URL.  See ../webui_resources.grd.
  4682.  
  4683. // Note: this <include> is not behind a single-line comment because the first
  4684. // line of the file is source code (so the first line would be skipped) instead
  4685. // of a licence header.
  4686. // clang-format off
  4687. (function(){var i=null;function k(){return Function.prototype.call.apply(Array.prototype.slice,arguments)}function l(a,b){var c=k(arguments,2);return function(){return b.apply(a,c)}}function m(a,b){var c=new n(b);for(c.f=[a];c.f.length;){var e=c,d=c.f.shift();e.g(d);for(d=d.firstChild;d;d=d.nextSibling)d.nodeType==1&&e.f.push(d)}}function n(a){this.g=a}function o(a){a.style.display=""}function p(a){a.style.display="none"};var q=":",r=/\s*;\s*/;function s(){this.i.apply(this,arguments)}s.prototype.i=function(a,b){if(!this.a)this.a={};if(b){var c=this.a,e=b.a,d;for(d in e)c[d]=e[d]}else for(c in d=this.a,e=t,e)d[c]=e[c];this.a.$this=a;this.a.$context=this;this.d=typeof a!="undefined"&&a!=i?a:"";if(!b)this.a.$top=this.d};var t={$default:i},u=[];function v(a){for(var b in a.a)delete a.a[b];a.d=i;u.push(a)}function w(a,b,c){try{return b.call(c,a.a,a.d)}catch(e){return t.$default}}
  4688. function x(a,b,c,e){if(u.length>0){var d=u.pop();s.call(d,b,a);a=d}else a=new s(b,a);a.a.$index=c;a.a.$count=e;return a}var y="a_",z="b_",A="with (a_) with (b_) return ",D={};function E(a){if(!D[a])try{D[a]=new Function(y,z,A+a)}catch(b){}return D[a]}function F(a){for(var b=[],a=a.split(r),c=0,e=a.length;c<e;++c){var d=a[c].indexOf(q);if(!(d<0)){var f;f=a[c].substr(0,d).replace(/^\s+/,"").replace(/\s+$/,"");d=E(a[c].substr(d+1));b.push(f,d)}}return b};var G="jsinstance",H="jsts",I="*",J="div",K="id";function L(){}var M=0,N={0:{}},P={},Q={},R=[];function S(a){a.__jstcache||m(a,function(a){T(a)})}var U=[["jsselect",E],["jsdisplay",E],["jsvalues",F],["jsvars",F],["jseval",function(a){for(var b=[],a=a.split(r),c=0,e=a.length;c<e;++c)if(a[c]){var d=E(a[c]);b.push(d)}return b}],["transclude",function(a){return a}],["jscontent",E],["jsskip",E]];
  4689. function T(a){if(a.__jstcache)return a.__jstcache;var b=a.getAttribute("jstcache");if(b!=i)return a.__jstcache=N[b];for(var b=R.length=0,c=U.length;b<c;++b){var e=U[b][0],d=a.getAttribute(e);Q[e]=d;d!=i&&R.push(e+"="+d)}if(R.length==0)return a.setAttribute("jstcache","0"),a.__jstcache=N[0];var f=R.join("&");if(b=P[f])return a.setAttribute("jstcache",b),a.__jstcache=N[b];for(var h={},b=0,c=U.length;b<c;++b){var d=U[b],e=d[0],g=d[1],d=Q[e];d!=i&&(h[e]=g(d))}b=""+ ++M;a.setAttribute("jstcache",b);N[b]=
  4690. h;P[f]=b;return a.__jstcache=h}function V(a,b){a.h.push(b);a.k.push(0)}function W(a){return a.c.length?a.c.pop():[]}
  4691. L.prototype.e=function(a,b){var c=X(b),e=c.transclude;if(e)(c=Y(e))?(b.parentNode.replaceChild(c,b),e=W(this),e.push(this.e,a,c),V(this,e)):b.parentNode.removeChild(b);else if(c=c.jsselect){var c=w(a,c,b),d=b.getAttribute(G),f=!1;d&&(d.charAt(0)==I?(d=parseInt(d.substr(1),10),f=!0):d=parseInt(d,10));var h=c!=i&&typeof c=="object"&&typeof c.length=="number",e=h?c.length:1,g=h&&e==0;if(h)if(g)d?b.parentNode.removeChild(b):(b.setAttribute(G,"*0"),p(b));else if(o(b),d===i||d===""||f&&d<e-1){f=W(this);
  4692. d=d||0;for(h=e-1;d<h;++d){var j=b.cloneNode(!0);b.parentNode.insertBefore(j,b);Z(j,c,d);g=x(a,c[d],d,e);f.push(this.b,g,j,v,g,i)}Z(b,c,d);g=x(a,c[d],d,e);f.push(this.b,g,b,v,g,i);V(this,f)}else d<e?(f=c[d],Z(b,c,d),g=x(a,f,d,e),f=W(this),f.push(this.b,g,b,v,g,i),V(this,f)):b.parentNode.removeChild(b);else c==i?p(b):(o(b),g=x(a,c,0,1),f=W(this),f.push(this.b,g,b,v,g,i),V(this,f))}else this.b(a,b)};
  4693. L.prototype.b=function(a,b){var c=X(b),e=c.jsdisplay;if(e){if(!w(a,e,b)){p(b);return}o(b)}if(e=c.jsvars)for(var d=0,f=e.length;d<f;d+=2){var h=e[d],g=w(a,e[d+1],b);a.a[h]=g}if(e=c.jsvalues){d=0;for(f=e.length;d<f;d+=2)if(g=e[d],h=w(a,e[d+1],b),g.charAt(0)=="$")a.a[g]=h;else if(g.charAt(0)=="."){for(var g=g.substr(1).split("."),j=b,O=g.length,B=0,$=O-1;B<$;++B){var C=g[B];j[C]||(j[C]={});j=j[C]}j[g[O-1]]=h}else g&&(typeof h=="boolean"?h?b.setAttribute(g,g):b.removeAttribute(g):b.setAttribute(g,""+
  4694. h))}if(e=c.jseval){d=0;for(f=e.length;d<f;++d)w(a,e[d],b)}e=c.jsskip;if(!e||!w(a,e,b))if(c=c.jscontent){if(c=""+w(a,c,b),b.innerHTML!=c){for(;b.firstChild;)e=b.firstChild,e.parentNode.removeChild(e);b.appendChild(this.j.createTextNode(c))}}else{c=W(this);for(e=b.firstChild;e;e=e.nextSibling)e.nodeType==1&&c.push(this.e,a,e);c.length&&V(this,c)}};function X(a){if(a.__jstcache)return a.__jstcache;var b=a.getAttribute("jstcache");if(b)return a.__jstcache=N[b];return T(a)}
  4695. function Y(a,b){var c=document;if(b){var e=c.getElementById(a);if(!e){var e=b(),d=H,f=c.getElementById(d);if(!f)f=c.createElement(J),f.id=d,p(f),f.style.position="absolute",c.body.appendChild(f);d=c.createElement(J);f.appendChild(d);d.innerHTML=e;e=c.getElementById(a)}c=e}else c=c.getElementById(a);return c?(S(c),c=c.cloneNode(!0),c.removeAttribute(K),c):i}function Z(a,b,c){c==b.length-1?a.setAttribute(G,I+c):a.setAttribute(G,""+c)};window.jstGetTemplate=Y;window.JsEvalContext=s;window.jstProcess=function(a,b){var c=new L;S(b);c.j=b?b.nodeType==9?b:b.ownerDocument||document:document;var e=l(c,c.e,a,b),d=c.h=[],f=c.k=[];c.c=[];e();for(var h,g,j;d.length;)h=d[d.length-1],e=f[f.length-1],e>=h.length?(e=c,g=d.pop(),g.length=0,e.c.push(g),f.pop()):(g=h[e++],j=h[e++],h=h[e++],f[f.length-1]=e,g.call(c,j,h))};
  4696. })()
  4697. </script><script jstcache="0">var tp = document.getElementById('t');jstProcess(loadTimeData.createJsEvalContext(), tp);</script></body></html>
Add Comment
Please, Sign In to add comment