Untitled


SUBMITTED BY: WesleySantista

DATE: Oct. 19, 2024, 1:17 p.m.

FORMAT: Text only

SIZE: 251.1 kB

HITS: 115

  1. <html dir="ltr" lang="pt"><head>
  2. <meta charset="utf-8">
  3. <meta name="color-scheme" content="light dark">
  4. <meta name="theme-color" content="#fff">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0,
  6. maximum-scale=1.0, user-scalable=no">
  7. <title>uqload.xyz</title>
  8. <style>/* Copyright 2017 The Chromium Authors
  9. * Use of this source code is governed by a BSD-style license that can be
  10. * found in the LICENSE file. */
  11. a {
  12. color: var(--link-color);
  13. }
  14. body {
  15. --background-color: #fff;
  16. --error-code-color: var(--google-gray-700);
  17. --google-blue-50: rgb(232, 240, 254);
  18. --google-blue-100: rgb(210, 227, 252);
  19. --google-blue-300: rgb(138, 180, 248);
  20. --google-blue-600: rgb(26, 115, 232);
  21. --google-blue-700: rgb(25, 103, 210);
  22. --google-gray-100: rgb(241, 243, 244);
  23. --google-gray-300: rgb(218, 220, 224);
  24. --google-gray-500: rgb(154, 160, 166);
  25. --google-gray-50: rgb(248, 249, 250);
  26. --google-gray-600: rgb(128, 134, 139);
  27. --google-gray-700: rgb(95, 99, 104);
  28. --google-gray-800: rgb(60, 64, 67);
  29. --google-gray-900: rgb(32, 33, 36);
  30. --heading-color: var(--google-gray-900);
  31. --link-color: rgb(88, 88, 88);
  32. --popup-container-background-color: rgba(0,0,0,.65);
  33. --primary-button-fill-color-active: var(--google-blue-700);
  34. --primary-button-fill-color: var(--google-blue-600);
  35. --primary-button-text-color: #fff;
  36. --quiet-background-color: rgb(247, 247, 247);
  37. --secondary-button-border-color: var(--google-gray-500);
  38. --secondary-button-fill-color: #fff;
  39. --secondary-button-hover-border-color: var(--google-gray-600);
  40. --secondary-button-hover-fill-color: var(--google-gray-50);
  41. --secondary-button-text-color: var(--google-gray-700);
  42. --small-link-color: var(--google-gray-700);
  43. --text-color: var(--google-gray-700);
  44. background: var(--background-color);
  45. color: var(--text-color);
  46. word-wrap: break-word;
  47. }
  48. .nav-wrapper .secondary-button {
  49. background: var(--secondary-button-fill-color);
  50. border: 1px solid var(--secondary-button-border-color);
  51. color: var(--secondary-button-text-color);
  52. float: none;
  53. margin: 0;
  54. padding: 8px 16px;
  55. }
  56. .hidden {
  57. display: none;
  58. }
  59. html {
  60. -webkit-text-size-adjust: 100%;
  61. font-size: 125%;
  62. }
  63. .icon {
  64. background-repeat: no-repeat;
  65. background-size: 100%;
  66. }
  67. @media (prefers-color-scheme: dark) {
  68. body {
  69. --background-color: var(--google-gray-900);
  70. --error-code-color: var(--google-gray-500);
  71. --heading-color: var(--google-gray-500);
  72. --link-color: var(--google-blue-300);
  73. --primary-button-fill-color-active: rgb(129, 162, 208);
  74. --primary-button-fill-color: var(--google-blue-300);
  75. --primary-button-text-color: var(--google-gray-900);
  76. --quiet-background-color: var(--background-color);
  77. --secondary-button-border-color: var(--google-gray-700);
  78. --secondary-button-fill-color: var(--google-gray-900);
  79. --secondary-button-hover-fill-color: rgb(48, 51, 57);
  80. --secondary-button-text-color: var(--google-blue-300);
  81. --small-link-color: var(--google-blue-300);
  82. --text-color: var(--google-gray-500);
  83. }
  84. }
  85. </style>
  86. <style>/* Copyright 2014 The Chromium Authors
  87. Use of this source code is governed by a BSD-style license that can be
  88. found in the LICENSE file. */
  89. button {
  90. border: 0;
  91. border-radius: 20px;
  92. box-sizing: border-box;
  93. color: var(--primary-button-text-color);
  94. cursor: pointer;
  95. float: right;
  96. font-size: .875em;
  97. margin: 0;
  98. padding: 8px 16px;
  99. transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1);
  100. user-select: none;
  101. }
  102. [dir='rtl'] button {
  103. float: left;
  104. }
  105. .bad-clock button,
  106. .captive-portal button,
  107. .https-only button,
  108. .insecure-form button,
  109. .lookalike-url button,
  110. .main-frame-blocked button,
  111. .neterror button,
  112. .pdf button,
  113. .ssl button,
  114. .enterprise-block button,
  115. .enterprise-warn button,
  116. .safe-browsing-billing button,
  117. .supervised-user-verify button {
  118. background: var(--primary-button-fill-color);
  119. }
  120. button:active {
  121. background: var(--primary-button-fill-color-active);
  122. outline: 0;
  123. }
  124. #debugging {
  125. display: inline;
  126. overflow: auto;
  127. }
  128. .debugging-content {
  129. line-height: 1em;
  130. margin-bottom: 0;
  131. margin-top: 1em;
  132. }
  133. .debugging-content-fixed-width {
  134. display: block;
  135. font-family: monospace;
  136. font-size: 1.2em;
  137. margin-top: 0.5em;
  138. }
  139. .debugging-title {
  140. font-weight: bold;
  141. }
  142. #details {
  143. margin: 0 0 50px;
  144. }
  145. #details p:not(:first-of-type) {
  146. margin-top: 20px;
  147. }
  148. .secondary-button:active {
  149. border-color: white;
  150. box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .3),
  151. 0 2px 6px 2px rgba(60, 64, 67, .15);
  152. }
  153. .secondary-button:hover {
  154. background: var(--secondary-button-hover-fill-color);
  155. border-color: var(--secondary-button-hover-border-color);
  156. text-decoration: none;
  157. }
  158. .error-code {
  159. color: var(--error-code-color);
  160. font-size: .8em;
  161. margin-top: 12px;
  162. text-transform: uppercase;
  163. }
  164. #error-debugging-info {
  165. font-size: 0.8em;
  166. }
  167. h1 {
  168. color: var(--heading-color);
  169. font-size: 1.6em;
  170. font-weight: normal;
  171. line-height: 1.25em;
  172. margin-bottom: 16px;
  173. }
  174. h2 {
  175. font-size: 1.2em;
  176. font-weight: normal;
  177. }
  178. .icon {
  179. height: 72px;
  180. margin: 0 0 40px;
  181. width: 72px;
  182. }
  183. input[type=checkbox] {
  184. opacity: 0;
  185. }
  186. input[type=checkbox]:focus ~ .checkbox::after {
  187. outline: -webkit-focus-ring-color auto 5px;
  188. }
  189. .interstitial-wrapper {
  190. box-sizing: border-box;
  191. font-size: 1em;
  192. line-height: 1.6em;
  193. margin: 14vh auto 0;
  194. max-width: 600px;
  195. width: 100%;
  196. }
  197. #main-message > p {
  198. display: inline;
  199. }
  200. #extended-reporting-opt-in {
  201. font-size: .875em;
  202. margin-top: 32px;
  203. }
  204. #extended-reporting-opt-in label {
  205. display: grid;
  206. grid-template-columns: 1.8em 1fr;
  207. position: relative;
  208. }
  209. #enhanced-protection-message {
  210. border-radius: 20px;
  211. font-size: 1em;
  212. margin-top: 32px;
  213. padding: 10px 5px;
  214. }
  215. #enhanced-protection-message a {
  216. color: var(--google-red-10);
  217. }
  218. #enhanced-protection-message label {
  219. display: grid;
  220. grid-template-columns: 2.5em 1fr;
  221. position: relative;
  222. }
  223. #enhanced-protection-message div {
  224. margin: 0.5em;
  225. }
  226. #enhanced-protection-message .icon {
  227. height: 1.5em;
  228. vertical-align: middle;
  229. width: 1.5em;
  230. }
  231. .nav-wrapper {
  232. margin-top: 51px;
  233. }
  234. .nav-wrapper::after {
  235. clear: both;
  236. content: '';
  237. display: table;
  238. width: 100%;
  239. }
  240. .small-link {
  241. color: var(--small-link-color);
  242. font-size: .875em;
  243. }
  244. .checkboxes {
  245. flex: 0 0 24px;
  246. }
  247. .checkbox {
  248. --padding: .9em;
  249. background: transparent;
  250. display: block;
  251. height: 1em;
  252. left: -1em;
  253. padding-inline-start: var(--padding);
  254. position: absolute;
  255. right: 0;
  256. top: -.5em;
  257. width: 1em;
  258. }
  259. .checkbox::after {
  260. border: 1px solid white;
  261. border-radius: 2px;
  262. content: '';
  263. height: 1em;
  264. left: var(--padding);
  265. position: absolute;
  266. top: var(--padding);
  267. width: 1em;
  268. }
  269. .checkbox::before {
  270. background: transparent;
  271. border: 2px solid white;
  272. border-inline-end-width: 0;
  273. border-top-width: 0;
  274. content: '';
  275. height: .2em;
  276. left: calc(.3em + var(--padding));
  277. opacity: 0;
  278. position: absolute;
  279. top: calc(.3em + var(--padding));
  280. transform: rotate(-45deg);
  281. width: .5em;
  282. }
  283. input[type=checkbox]:checked ~ .checkbox::before {
  284. opacity: 1;
  285. }
  286. #recurrent-error-message {
  287. background: #ededed;
  288. border-radius: 4px;
  289. margin-bottom: 16px;
  290. margin-top: 12px;
  291. padding: 12px 16px;
  292. }
  293. .showing-recurrent-error-message #extended-reporting-opt-in {
  294. margin-top: 16px;
  295. }
  296. .showing-recurrent-error-message #enhanced-protection-message {
  297. margin-top: 16px;
  298. }
  299. @media (max-width: 700px) {
  300. .interstitial-wrapper {
  301. padding: 0 10%;
  302. }
  303. #error-debugging-info {
  304. overflow: auto;
  305. }
  306. }
  307. @media (max-width: 420px) {
  308. button,
  309. [dir='rtl'] button,
  310. .small-link {
  311. float: none;
  312. font-size: .825em;
  313. font-weight: 500;
  314. margin: 0;
  315. width: 100%;
  316. }
  317. button {
  318. padding: 16px 24px;
  319. }
  320. #details {
  321. margin: 20px 0 20px 0;
  322. }
  323. #details p:not(:first-of-type) {
  324. margin-top: 10px;
  325. }
  326. .secondary-button:not(.hidden) {
  327. display: block;
  328. margin-top: 20px;
  329. text-align: center;
  330. width: 100%;
  331. }
  332. .interstitial-wrapper {
  333. padding: 0 5%;
  334. }
  335. #extended-reporting-opt-in {
  336. margin-top: 24px;
  337. }
  338. #enhanced-protection-message {
  339. margin-top: 24px;
  340. }
  341. .nav-wrapper {
  342. margin-top: 30px;
  343. }
  344. }
  345. /**
  346. * Mobile specific styling.
  347. * Navigation buttons are anchored to the bottom of the screen.
  348. * Details message replaces the top content in its own scrollable area.
  349. */
  350. @media (max-width: 420px) {
  351. .nav-wrapper .secondary-button {
  352. border: 0;
  353. margin: 16px 0 0;
  354. margin-inline-end: 0;
  355. padding-bottom: 16px;
  356. padding-top: 16px;
  357. }
  358. }
  359. /* Fixed nav. */
  360. @media (min-width: 240px) and (max-width: 420px) and
  361. (min-height: 401px),
  362. (min-width: 421px) and (min-height: 240px) and
  363. (max-height: 560px) {
  364. body .nav-wrapper {
  365. background: var(--background-color);
  366. bottom: 0;
  367. box-shadow: 0 -12px 24px var(--background-color);
  368. left: 0;
  369. margin: 0 auto;
  370. max-width: 736px;
  371. padding-inline-end: 24px;
  372. padding-inline-start: 24px;
  373. position: fixed;
  374. right: 0;
  375. width: 100%;
  376. z-index: 2;
  377. }
  378. .interstitial-wrapper {
  379. max-width: 736px;
  380. }
  381. #details,
  382. #main-content {
  383. padding-bottom: 40px;
  384. }
  385. #details {
  386. padding-top: 5.5vh;
  387. }
  388. button.small-link {
  389. color: var(--google-blue-600);
  390. }
  391. }
  392. @media (max-width: 420px) and (orientation: portrait),
  393. (max-height: 560px) {
  394. body {
  395. margin: 0 auto;
  396. }
  397. button,
  398. [dir='rtl'] button,
  399. button.small-link,
  400. .nav-wrapper .secondary-button {
  401. font-family: Roboto-Regular,Helvetica;
  402. font-size: .933em;
  403. margin: 6px 0;
  404. transform: translatez(0);
  405. }
  406. .nav-wrapper {
  407. box-sizing: border-box;
  408. padding-bottom: 8px;
  409. width: 100%;
  410. }
  411. #details {
  412. box-sizing: border-box;
  413. height: auto;
  414. margin: 0;
  415. opacity: 1;
  416. transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
  417. }
  418. #details.hidden,
  419. #main-content.hidden {
  420. height: 0;
  421. opacity: 0;
  422. overflow: hidden;
  423. padding-bottom: 0;
  424. transition: none;
  425. }
  426. h1 {
  427. font-size: 1.5em;
  428. margin-bottom: 8px;
  429. }
  430. .icon {
  431. margin-bottom: 5.69vh;
  432. }
  433. .interstitial-wrapper {
  434. box-sizing: border-box;
  435. margin: 7vh auto 12px;
  436. padding: 0 24px;
  437. position: relative;
  438. }
  439. .interstitial-wrapper p {
  440. font-size: .95em;
  441. line-height: 1.61em;
  442. margin-top: 8px;
  443. }
  444. #main-content {
  445. margin: 0;
  446. transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1);
  447. }
  448. .small-link {
  449. border: 0;
  450. }
  451. .suggested-left > #control-buttons,
  452. .suggested-right > #control-buttons {
  453. float: none;
  454. margin: 0;
  455. }
  456. }
  457. @media (min-width: 421px) and (min-height: 500px) and (max-height: 560px) {
  458. .interstitial-wrapper {
  459. margin-top: 10vh;
  460. }
  461. }
  462. @media (min-height: 400px) and (orientation:portrait) {
  463. .interstitial-wrapper {
  464. margin-bottom: 145px;
  465. }
  466. }
  467. @media (min-height: 299px) {
  468. .nav-wrapper {
  469. padding-bottom: 16px;
  470. }
  471. }
  472. @media (max-height: 560px) and (min-height: 240px) and (orientation:landscape) {
  473. .extended-reporting-has-checkbox #details {
  474. padding-bottom: 80px;
  475. }
  476. }
  477. @media (min-height: 500px) and (max-height: 650px) and (max-width: 414px) and
  478. (orientation: portrait) {
  479. .interstitial-wrapper {
  480. margin-top: 7vh;
  481. }
  482. }
  483. @media (min-height: 650px) and (max-width: 414px) and (orientation: portrait) {
  484. .interstitial-wrapper {
  485. margin-top: 10vh;
  486. }
  487. }
  488. /* Small mobile screens. No fixed nav. */
  489. @media (max-height: 400px) and (orientation: portrait),
  490. (max-height: 239px) and (orientation: landscape),
  491. (max-width: 419px) and (max-height: 399px) {
  492. .interstitial-wrapper {
  493. display: flex;
  494. flex-direction: column;
  495. margin-bottom: 0;
  496. }
  497. #details {
  498. flex: 1 1 auto;
  499. order: 0;
  500. }
  501. #main-content {
  502. flex: 1 1 auto;
  503. order: 0;
  504. }
  505. .nav-wrapper {
  506. flex: 0 1 auto;
  507. margin-top: 8px;
  508. order: 1;
  509. padding-inline-end: 0;
  510. padding-inline-start: 0;
  511. position: relative;
  512. width: 100%;
  513. }
  514. button,
  515. .nav-wrapper .secondary-button {
  516. padding: 16px 24px;
  517. }
  518. button.small-link {
  519. color: var(--google-blue-600);
  520. }
  521. }
  522. @media (max-width: 239px) and (orientation: portrait) {
  523. .nav-wrapper {
  524. padding-inline-end: 0;
  525. padding-inline-start: 0;
  526. }
  527. }
  528. </style>
  529. <style>/* Copyright 2013 The Chromium Authors
  530. * Use of this source code is governed by a BSD-style license that can be
  531. * found in the LICENSE file. */
  532. /* Don't use the main frame div when the error is in a subframe. */
  533. html[subframe] #main-frame-error {
  534. display: none;
  535. }
  536. /* Don't use the subframe error div when the error is in a main frame. */
  537. html:not([subframe]) #sub-frame-error {
  538. display: none;
  539. }
  540. h1 {
  541. margin-top: 0;
  542. word-wrap: break-word;
  543. }
  544. h1 span {
  545. font-weight: 500;
  546. }
  547. a {
  548. text-decoration: none;
  549. }
  550. .icon {
  551. -webkit-user-select: none;
  552. display: inline-block;
  553. }
  554. .icon-generic {
  555. /* Can't access chrome://theme/IDR_ERROR_NETWORK_GENERIC from an untrusted
  556. * renderer process, so embed the resource manually. */
  557. content: image-set(
  558. url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAENJREFUeF7tzbEJACEQRNGBLeAasBCza2lLEGx0CxFGG9hBMDDxRy/72O9FMnIFapGylsu1fgoBdkXfUHLrQgdfrlJN1BdYBjQQm3UAAAAASUVORK5CYII=) 1x,
  559. url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQAQMAAADdiHD7AAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAFJJREFUeF7t0cENgDAMQ9FwYgxG6WjpaIzCCAxQxVggFuDiCvlLOeRdHR9yzjncHVoq3npu+wQUrUuJHylSTmBaespJyJQoObUeyxDQb3bEm5Au81c0pSCD8HYAAAAASUVORK5CYII=) 2x);
  560. }
  561. .icon-info {
  562. content: image-set(
  563. url(data:image/png;base64,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) 1x,
  564. url(data:image/png;base64,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) 2x);
  565. }
  566. .icon-offline {
  567. content: image-set(
  568. url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAGxJREFUeF7tyMEJwkAQRuFf5ipMKxYQiJ3Z2nSwrWwBA0+DQZcdxEOueaePp9+dQZFB7GpUcURSVU66yVNFj6LFICatThZB6r/ko/pbRpUgilY0Cbw5sNmb9txGXUKyuH7eV25x39DtJXUNPQGJtWFV+BT/QAAAAABJRU5ErkJggg==) 1x,
  569. url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQBAMAAAAVaP+LAAAAGFBMVEUAAABTU1NNTU1TU1NPT09SUlJSUlJTU1O8B7DEAAAAB3RSTlMAoArVKvVgBuEdKgAAAJ1JREFUeF7t1TEOwyAMQNG0Q6/UE+RMXD9d/tC6womIFSL9P+MnAYOXeTIzMzMzMzMzaz8J9Ri6HoITmuHXhISE8nEh9yxDh55aCEUoTGbbQwjqHwIkRAEiIaG0+0AA9VBMaE89Rogeoww936MQrWdBr4GN/z0IAdQ6nQ/FIpRXDwHcA+JIJcQowQAlFUA0MfQpXLlVQfkzR4igS6ENjknm/wiaGhsAAAAASUVORK5CYII=) 2x);
  570. position: relative;
  571. }
  572. .icon-disabled {
  573. content: image-set(
  574. 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,
  575. url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOAAAACQAQMAAAArwfVjAAAABlBMVEVMaXFTU1OXUj8tAAAAAXRSTlMAQObYZgAAAYdJREFUeF7F1EFqwzAUBNARAmVj0FZe5QoBH6BX+dn4GlY2PYNzGx/A0CvkCIJuvIraKJKbgBvzf2g62weDGD7CYggpfFReis4J0ey9EGFIiEQQojFSlA9kSIiqd0KkFjKsewgRbStEN19mxUPTtmW9HQ/h6tyqNQ8NlSMZdzyE6qkoE0trVYGFm0n1WYeBhduzwbwBC7voS+vIxfeMjeaiLxsMMtQNwMPtuew+DjzcTHk8YMfDknEcIUOtf2lVfgVH3K4Xv5PRYAXRVMtItIJ3rfaCIVn9DsTH2NxisAVRex2Hh3hX+/mRUR08bAwPEYsI51ZxWH4Q0SpicQRXeyEaIug48FEdegARfMz/tADVsRciwTAxW308ehmC2gLraC+YCbV3QoTZexa+zegAEW5PhhgYfmbvJgcRqngGByOSXdFJcLk2JeDPEN0kxe1JhIt5FiFA+w+ItMELsUyPF2IaJ4aILqb4FbxPwhImwj6JauKgDUCYaxmYIsd4KXdMjIC9ItB5Bn4BNRwsG0XM2nwAAAAASUVORK5CYII=) 2x);
  576. width: 112px;
  577. }
  578. .hidden {
  579. display: none;
  580. }
  581. #suggestions-list a {
  582. color: var(--google-blue-600);
  583. }
  584. #suggestions-list p {
  585. margin-block-end: 0;
  586. }
  587. #suggestions-list ul {
  588. margin-top: 0;
  589. }
  590. .single-suggestion {
  591. list-style-type: none;
  592. padding-inline-start: 0;
  593. }
  594. #error-information-button {
  595. content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBmaWxsPSJub25lIiBkPSJNMCAwaDI0djI0SDB6Ii8+PHBhdGggZD0iTTExIDE4aDJ2LTJoLTJ2MnptMS0xNkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMnptMCAxOGMtNC40MSAwLTgtMy41OS04LThzMy41OS04IDgtOCA4IDMuNTkgOCA4LTMuNTkgOC04IDh6bTAtMTRjLTIuMjEgMC00IDEuNzktNCA0aDJjMC0xLjEuOS0yIDItMnMyIC45IDIgMmMwIDItMyAxLjc1LTMgNWgyYzAtMi4yNSAzLTIuNSAzLTUgMC0yLjIxLTEuNzktNC00LTR6Ii8+PC9zdmc+);
  596. height: 24px;
  597. vertical-align: -.15em;
  598. width: 24px;
  599. }
  600. .use-popup-container#error-information-popup-container
  601. #error-information-popup {
  602. align-items: center;
  603. background-color: var(--popup-container-background-color);
  604. display: flex;
  605. height: 100%;
  606. left: 0;
  607. position: fixed;
  608. top: 0;
  609. width: 100%;
  610. z-index: 100;
  611. }
  612. .use-popup-container#error-information-popup-container
  613. #error-information-popup-content > p {
  614. margin-bottom: 11px;
  615. margin-inline-start: 20px;
  616. }
  617. .use-popup-container#error-information-popup-container #suggestions-list ul {
  618. margin-inline-start: 15px;
  619. }
  620. .use-popup-container#error-information-popup-container
  621. #error-information-popup-box {
  622. background-color: var(--background-color);
  623. left: 5%;
  624. padding-bottom: 15px;
  625. padding-top: 15px;
  626. position: fixed;
  627. width: 90%;
  628. z-index: 101;
  629. }
  630. .use-popup-container#error-information-popup-container div.error-code {
  631. margin-inline-start: 20px;
  632. }
  633. .use-popup-container#error-information-popup-container #suggestions-list p {
  634. margin-inline-start: 20px;
  635. }
  636. :not(.use-popup-container)#error-information-popup-container
  637. #error-information-popup-close {
  638. display: none;
  639. }
  640. #error-information-popup-close {
  641. margin-bottom: 0;
  642. margin-inline-end: 35px;
  643. margin-top: 15px;
  644. text-align: end;
  645. }
  646. .link-button {
  647. color: rgb(66, 133, 244);
  648. display: inline-block;
  649. font-weight: bold;
  650. text-transform: uppercase;
  651. }
  652. #sub-frame-error-details {
  653. color: #8F8F8F;
  654. /* Not done on mobile for performance reasons. */
  655. text-shadow: 0 1px 0 rgba(255,255,255,0.3);
  656. }
  657. [jscontent=hostName],
  658. [jscontent=failedUrl] {
  659. overflow-wrap: break-word;
  660. }
  661. .secondary-button {
  662. background: #d9d9d9;
  663. color: #696969;
  664. margin-inline-end: 16px;
  665. }
  666. .snackbar {
  667. background: #323232;
  668. border-radius: 2px;
  669. bottom: 24px;
  670. box-sizing: border-box;
  671. color: #fff;
  672. font-size: .87em;
  673. left: 24px;
  674. max-width: 568px;
  675. min-width: 288px;
  676. opacity: 0;
  677. padding: 16px 24px 12px;
  678. position: fixed;
  679. transform: translateY(90px);
  680. will-change: opacity, transform;
  681. z-index: 999;
  682. }
  683. .snackbar-show {
  684. -webkit-animation:
  685. show-snackbar 250ms cubic-bezier(0, 0, 0.2, 1) forwards,
  686. hide-snackbar 250ms cubic-bezier(0.4, 0, 1, 1) forwards 5s;
  687. }
  688. @-webkit-keyframes show-snackbar {
  689. 100% {
  690. opacity: 1;
  691. transform: translateY(0);
  692. }
  693. }
  694. @-webkit-keyframes hide-snackbar {
  695. 0% {
  696. opacity: 1;
  697. transform: translateY(0);
  698. }
  699. 100% {
  700. opacity: 0;
  701. transform: translateY(90px);
  702. }
  703. }
  704. .suggestions {
  705. margin-top: 18px;
  706. }
  707. .suggestion-header {
  708. font-weight: bold;
  709. margin-bottom: 4px;
  710. }
  711. .suggestion-body {
  712. color: #777;
  713. }
  714. /* Decrease padding at low sizes. */
  715. @media (max-width: 640px), (max-height: 640px) {
  716. h1 {
  717. margin: 0 0 15px;
  718. }
  719. .suggestions {
  720. margin-top: 10px;
  721. }
  722. .suggestion-header {
  723. margin-bottom: 0;
  724. }
  725. }
  726. #download-link,
  727. #download-link-clicked {
  728. margin-bottom: 30px;
  729. margin-top: 30px;
  730. }
  731. #download-link-clicked {
  732. color: #BBB;
  733. }
  734. #download-link::before,
  735. #download-link-clicked::before {
  736. content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxLjJlbSIgaGVpZ2h0PSIxLjJlbSIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNNSAyMGgxNHYtMkg1bTE0LTloLTRWM0g5djZINWw3IDcgNy03eiIgZmlsbD0iIzQyODVGNCIvPjwvc3ZnPg==);
  737. display: inline-block;
  738. margin-inline-end: 4px;
  739. vertical-align: -webkit-baseline-middle;
  740. }
  741. #download-link-clicked::before {
  742. opacity: 0;
  743. width: 0;
  744. }
  745. #offline-content-list-visibility-card {
  746. border: 1px solid white;
  747. border-radius: 8px;
  748. display: flex;
  749. font-size: .8em;
  750. justify-content: space-between;
  751. line-height: 1;
  752. }
  753. #offline-content-list.list-hidden #offline-content-list-visibility-card {
  754. border-color: rgb(218, 220, 224);
  755. }
  756. #offline-content-list-visibility-card > div {
  757. padding: 1em;
  758. }
  759. #offline-content-list-title {
  760. color: var(--google-gray-700);
  761. }
  762. #offline-content-list-show-text,
  763. #offline-content-list-hide-text {
  764. color: rgb(66, 133, 244);
  765. }
  766. /* Hides the "hide" text div when the offline content list is collapsed/hidden
  767. * and, alternatively, hides the "show" text div when the offline content list
  768. * is expanded/shown.
  769. */
  770. #offline-content-list.list-hidden #offline-content-list-hide-text,
  771. #offline-content-list:not(.list-hidden) #offline-content-list-show-text {
  772. display: none;
  773. }
  774. /* Controls the animation of the offline content list when it is expanded/shown.
  775. */
  776. #offline-content-suggestions {
  777. /* Max-height has to be set for the height animation to work. The chosen value
  778. * is a little greater than the maximum height the list will have, when all
  779. * suggestions have images, so that it is never clamped. This makes so that
  780. * when the actual height is smaller then the animation is not as smooth.
  781. */
  782. max-height: 27em;
  783. transition: max-height 200ms ease-in, visibility 0s 200ms,
  784. opacity 200ms 200ms linear;
  785. }
  786. /* Controls the animation of the offline content list when it is
  787. * collapsed/hidden.
  788. */
  789. #offline-content-list.list-hidden #offline-content-suggestions {
  790. max-height: 0;
  791. opacity: 0;
  792. transition: opacity 200ms linear, visibility 0s 200ms,
  793. max-height 200ms 200ms ease-out;
  794. visibility: hidden;
  795. }
  796. #offline-content-list {
  797. margin-inline-start: -5%;
  798. width: 110%;
  799. }
  800. /* The selectors below adjust the "overflow" of the suggestion cards contents
  801. * based on the same screen size based strategy used for the main frame, which
  802. * is applied by the `interstitial-wrapper` class. */
  803. @media (max-width: 420px) {
  804. #offline-content-list {
  805. margin-inline-start: -2.5%;
  806. width: 105%;
  807. }
  808. }
  809. @media (max-width: 420px) and (orientation: portrait),
  810. (max-height: 560px) {
  811. #offline-content-list {
  812. margin-inline-start: -12px;
  813. width: calc(100% + 24px);
  814. }
  815. }
  816. .suggestion-with-image .offline-content-suggestion-thumbnail {
  817. flex-basis: 8.2em;
  818. flex-shrink: 0;
  819. }
  820. .suggestion-with-image .offline-content-suggestion-thumbnail > img {
  821. height: 100%;
  822. width: 100%;
  823. }
  824. .suggestion-with-image #offline-content-list:not(.is-rtl)
  825. .offline-content-suggestion-thumbnail > img {
  826. border-bottom-right-radius: 7px;
  827. border-top-right-radius: 7px;
  828. }
  829. .suggestion-with-image #offline-content-list.is-rtl
  830. .offline-content-suggestion-thumbnail > img {
  831. border-bottom-left-radius: 7px;
  832. border-top-left-radius: 7px;
  833. }
  834. .suggestion-with-icon .offline-content-suggestion-thumbnail {
  835. align-items: center;
  836. display: flex;
  837. justify-content: center;
  838. min-height: 4.2em;
  839. min-width: 4.2em;
  840. }
  841. .suggestion-with-icon .offline-content-suggestion-thumbnail > div {
  842. align-items: center;
  843. background-color: rgb(241, 243, 244);
  844. border-radius: 50%;
  845. display: flex;
  846. height: 2.3em;
  847. justify-content: center;
  848. width: 2.3em;
  849. }
  850. .suggestion-with-icon .offline-content-suggestion-thumbnail > div > img {
  851. height: 1.45em;
  852. width: 1.45em;
  853. }
  854. .offline-content-suggestion-favicon {
  855. height: 1em;
  856. margin-inline-end: 0.4em;
  857. width: 1.4em;
  858. }
  859. .offline-content-suggestion-favicon > img {
  860. height: 1.4em;
  861. width: 1.4em;
  862. }
  863. .no-favicon .offline-content-suggestion-favicon {
  864. display: none;
  865. }
  866. .image-video {
  867. content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTcgMTAuNVY3YTEgMSAwIDAgMC0xLTFINGExIDEgMCAwIDAtMSAxdjEwYTEgMSAwIDAgMCAxIDFoMTJhMSAxIDAgMCAwIDEtMXYtMy41bDQgNHYtMTFsLTQgNHoiIGZpbGw9IiMzQzQwNDMiLz48L3N2Zz4=);
  868. }
  869. .image-music-note {
  870. content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTIgM3Y5LjI2Yy0uNS0uMTctMS0uMjYtMS41LS4yNkM4IDEyIDYgMTQgNiAxNi41UzggMjEgMTAuNSAyMXM0LjUtMiA0LjUtNC41VjZoNFYzaC03eiIgZmlsbD0iIzNDNDA0MyIvPjwvc3ZnPg==);
  871. }
  872. .image-earth {
  873. content: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTIgMmM1LjUyIDAgMTAgNC40OCAxMCAxMHMtNC40OCAxMC0xMCAxMFMyIDE3LjUyIDIgMTIgNi40OCAyIDEyIDJ6TTQgMTJoNC40YzMuNDA3LjAyMiA0LjkyMiAxLjczIDQuNTQzIDUuMTI3SDkuNDg4djIuNDdhOC4wMDQgOC4wMDQgMCAwIDAgMTAuNDk4LTguMDgzQzE5LjMyNyAxMi41MDQgMTguMzMyIDEzIDE3IDEzYy0yLjEzNyAwLTMuMjA2LS45MTYtMy4yMDYtMi43NWgtMy43NDhjLS4yNzQtMi43MjguNjgzLTQuMDkyIDIuODctNC4wOTIgMC0uOTc1LjMyNy0xLjU5Ny44MTEtMS45N0E4LjAwNCA4LjAwNCAwIDAgMCA0IDEyeiIgZmlsbD0iIzNDNDA0MyIvPjwvc3ZnPg==);
  874. }
  875. .image-file {
  876. content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTMgOVYzLjVMMTguNSA5TTYgMmMtMS4xMSAwLTIgLjg5LTIgMnYxNmEyIDIgMCAwIDAgMiAyaDEyYTIgMiAwIDAgMCAyLTJWOGwtNi02SDZ6IiBmaWxsPSIjM0M0MDQzIi8+PC9zdmc+);
  877. }
  878. .offline-content-suggestion-texts {
  879. display: flex;
  880. flex-direction: column;
  881. justify-content: space-between;
  882. line-height: 1.3;
  883. padding: .9em;
  884. width: 100%;
  885. }
  886. .offline-content-suggestion-title {
  887. -webkit-box-orient: vertical;
  888. -webkit-line-clamp: 3;
  889. color: rgb(32, 33, 36);
  890. display: -webkit-box;
  891. font-size: 1.1em;
  892. overflow: hidden;
  893. text-overflow: ellipsis;
  894. }
  895. div.offline-content-suggestion {
  896. align-items: stretch;
  897. border: 1px solid rgb(218, 220, 224);
  898. border-radius: 8px;
  899. display: flex;
  900. justify-content: space-between;
  901. margin-bottom: .8em;
  902. }
  903. .suggestion-with-image {
  904. flex-direction: row;
  905. height: 8.2em;
  906. max-height: 8.2em;
  907. }
  908. .suggestion-with-icon {
  909. flex-direction: row-reverse;
  910. height: 4.2em;
  911. max-height: 4.2em;
  912. }
  913. .suggestion-with-icon .offline-content-suggestion-title {
  914. -webkit-line-clamp: 1;
  915. word-break: break-all;
  916. }
  917. .suggestion-with-icon .offline-content-suggestion-texts {
  918. padding-inline-start: 0;
  919. }
  920. .offline-content-suggestion-attribution-freshness {
  921. color: rgb(95, 99, 104);
  922. display: flex;
  923. font-size: .8em;
  924. line-height: 1.7em;
  925. }
  926. .offline-content-suggestion-attribution {
  927. -webkit-box-orient: vertical;
  928. -webkit-line-clamp: 1;
  929. display: -webkit-box;
  930. flex-shrink: 1;
  931. margin-inline-end: 0.3em;
  932. overflow: hidden;
  933. overflow-wrap: break-word;
  934. text-overflow: ellipsis;
  935. word-break: break-all;
  936. }
  937. .no-attribution .offline-content-suggestion-attribution {
  938. display: none;
  939. }
  940. .offline-content-suggestion-freshness::before {
  941. content: '-';
  942. display: inline-block;
  943. flex-shrink: 0;
  944. margin-inline-end: .1em;
  945. margin-inline-start: .1em;
  946. }
  947. .no-attribution .offline-content-suggestion-freshness::before {
  948. display: none;
  949. }
  950. .offline-content-suggestion-freshness {
  951. flex-shrink: 0;
  952. }
  953. .suggestion-with-image .offline-content-suggestion-pin-spacer {
  954. flex-grow: 100;
  955. flex-shrink: 1;
  956. }
  957. .suggestion-with-image .offline-content-suggestion-pin {
  958. content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCI+PGRlZnM+PHBhdGggaWQ9ImEiIGQ9Ik0wIDBoMjR2MjRIMFYweiIvPjwvZGVmcz48Y2xpcFBhdGggaWQ9ImIiPjx1c2UgeGxpbms6aHJlZj0iI2EiIG92ZXJmbG93PSJ2aXNpYmxlIi8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjYikiIGQ9Ik0xMiAyQzYuNSAyIDIgNi41IDIgMTJzNC41IDEwIDEwIDEwIDEwLTQuNSAxMC0xMFMxNy41IDIgMTIgMnptNSAxNkg3di0yaDEwdjJ6bS02LjctNEw3IDEwLjdsMS40LTEuNCAxLjkgMS45IDUuMy01LjNMMTcgNy4zIDEwLjMgMTR6IiBmaWxsPSIjOUFBMEE2Ii8+PC9zdmc+);
  959. flex-shrink: 0;
  960. height: 1.4em;
  961. margin-inline-start: .4em;
  962. width: 1.4em;
  963. }
  964. /* Controls the animation (and a bit more) of the launch-downloads-home action
  965. * button when the offline content list is expanded/shown.
  966. */
  967. #offline-content-list-action {
  968. text-align: center;
  969. transition: visibility 0s 200ms, opacity 200ms 200ms linear;
  970. }
  971. /* Controls the animation of the launch-downloads-home action button when the
  972. * offline content list is collapsed/hidden.
  973. */
  974. #offline-content-list.list-hidden #offline-content-list-action {
  975. opacity: 0;
  976. transition: opacity 200ms linear, visibility 0s 200ms;
  977. visibility: hidden;
  978. }
  979. #cancel-save-page-button {
  980. background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48Y2xpcFBhdGggaWQ9Im1hc2siPjxwYXRoIGQ9Ik0xMiAyQzYuNSAyIDIgNi41IDIgMTJzNC41IDEwIDEwIDEwIDEwLTQuNSAxMC0xMFMxNy41IDIgMTIgMnptNSAxNkg3di0yaDEwdjJ6bS02LjctNEw3IDEwLjdsMS40LTEuNCAxLjkgMS45IDUuMy01LjNMMTcgNy4zIDEwLjMgMTR6IiBmaWxsPSIjOUFBMEE2Ii8+PC9jbGlwUGF0aD48cGF0aCBjbGlwLXBhdGg9InVybCgjbWFzaykiIGZpbGw9IiM5QUEwQTYiIGQ9Ik0wIDBoMjR2MjRIMHoiLz48cGF0aCBjbGlwLXBhdGg9InVybCgjbWFzaykiIGZpbGw9IiMxQTczRTgiIHN0eWxlPSJhbmltYXRpb246b2ZmbGluZUFuaW1hdGlvbiA0cyBpbmZpbml0ZSIgZD0iTTAgMGgyNHYyNEgweiIvPjxzdHlsZT5Aa2V5ZnJhbWVzIG9mZmxpbmVBbmltYXRpb257MCUsMzUle2hlaWdodDowfTYwJXtoZWlnaHQ6MTAwJX05MCV7ZmlsbC1vcGFjaXR5OjF9dG97ZmlsbC1vcGFjaXR5OjB9fTwvc3R5bGU+PC9zdmc+);
  981. background-position: right 27px center;
  982. background-repeat: no-repeat;
  983. border: 1px solid var(--google-gray-300);
  984. border-radius: 5px;
  985. color: var(--google-gray-700);
  986. margin-bottom: 26px;
  987. padding-bottom: 16px;
  988. padding-inline-end: 88px;
  989. padding-inline-start: 16px;
  990. padding-top: 16px;
  991. text-align: start;
  992. }
  993. html[dir='rtl'] #cancel-save-page-button {
  994. background-position: left 27px center;
  995. }
  996. #save-page-for-later-button {
  997. display: flex;
  998. justify-content: start;
  999. }
  1000. #save-page-for-later-button a::before {
  1001. content: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxLjJlbSIgaGVpZ2h0PSIxLjJlbSIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNNSAyMGgxNHYtMkg1bTE0LTloLTRWM0g5djZINWw3IDcgNy03eiIgZmlsbD0iIzQyODVGNCIvPjwvc3ZnPg==);
  1002. display: inline-block;
  1003. margin-inline-end: 4px;
  1004. vertical-align: -webkit-baseline-middle;
  1005. }
  1006. .hidden#save-page-for-later-button {
  1007. display: none;
  1008. }
  1009. /* Don't allow overflow when in a subframe. */
  1010. html[subframe] body {
  1011. overflow: hidden;
  1012. }
  1013. #sub-frame-error {
  1014. -webkit-align-items: center;
  1015. -webkit-flex-flow: column;
  1016. -webkit-justify-content: center;
  1017. background-color: #DDD;
  1018. display: -webkit-flex;
  1019. height: 100%;
  1020. left: 0;
  1021. position: absolute;
  1022. text-align: center;
  1023. top: 0;
  1024. transition: background-color 200ms ease-in-out;
  1025. width: 100%;
  1026. }
  1027. #sub-frame-error:hover {
  1028. background-color: #EEE;
  1029. }
  1030. #sub-frame-error .icon-generic {
  1031. margin: 0 0 16px;
  1032. }
  1033. #sub-frame-error-details {
  1034. margin: 0 10px;
  1035. text-align: center;
  1036. opacity: 0;
  1037. }
  1038. /* Show details only when hovering. */
  1039. #sub-frame-error:hover #sub-frame-error-details {
  1040. opacity: 1;
  1041. }
  1042. /* If the iframe is too small, always hide the error code. */
  1043. /* TODO(mmenke): See if overflow: no-display works better, once supported. */
  1044. @media (max-width: 200px), (max-height: 95px) {
  1045. #sub-frame-error-details {
  1046. display: none;
  1047. }
  1048. }
  1049. /* Adjust icon for small embedded frames in apps. */
  1050. @media (max-height: 100px) {
  1051. #sub-frame-error .icon-generic {
  1052. height: auto;
  1053. margin: 0;
  1054. padding-top: 0;
  1055. width: 25px;
  1056. }
  1057. }
  1058. /* details-button is special; it's a <button> element that looks like a link. */
  1059. #details-button {
  1060. box-shadow: none;
  1061. min-width: 0;
  1062. }
  1063. /* Styles for platform dependent separation of controls and details button. */
  1064. .suggested-left > #control-buttons,
  1065. .suggested-right > #details-button {
  1066. float: left;
  1067. }
  1068. .suggested-right > #control-buttons,
  1069. .suggested-left > #details-button {
  1070. float: right;
  1071. }
  1072. .suggested-left .secondary-button {
  1073. margin-inline-end: 0;
  1074. margin-inline-start: 16px;
  1075. }
  1076. #details-button.singular {
  1077. float: none;
  1078. }
  1079. /* download-button shows both icon and text. */
  1080. #download-button {
  1081. padding-bottom: 4px;
  1082. padding-top: 4px;
  1083. position: relative;
  1084. }
  1085. #download-button::before {
  1086. background: image-set(
  1087. url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAO0lEQVQ4y2NgGArgPxIY1YChsOE/LtBAmpYG0mxpIOSDBpKUo2lpIDZxNJCkHKqlYZAla3RAHQ1DFgAARRroHyLNTwwAAAAASUVORK5CYII=) 1x,
  1088. url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAQAAAD9CzEMAAAAZElEQVRYw+3Ruw3AMAwDUY3OzZUmRRD4E9iim9wNwAdbEURHyk4AAAAATiCVK8lLyPsKeT9K3lsownnunfkPxO78hKiYHxBV8x2icr5BVM+/CMf8g3DN34Rzns6ViwHUAUQ/6wIAd5Km7l6c8AAAAABJRU5ErkJggg==) 2x)
  1089. no-repeat;
  1090. content: '';
  1091. display: inline-block;
  1092. height: 24px;
  1093. margin-inline-end: 4px;
  1094. margin-inline-start: -4px;
  1095. vertical-align: middle;
  1096. width: 24px;
  1097. }
  1098. #download-button:disabled {
  1099. background: rgb(180, 206, 249);
  1100. color: rgb(255, 255, 255);
  1101. }
  1102. #buttons::after {
  1103. clear: both;
  1104. content: '';
  1105. display: block;
  1106. width: 100%;
  1107. }
  1108. /* Offline page */
  1109. html[dir='rtl'] .runner-container,
  1110. html[dir='rtl'].offline .icon-offline {
  1111. transform: scaleX(-1);
  1112. }
  1113. .offline {
  1114. transition: filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1),
  1115. background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);
  1116. will-change: filter, background-color;
  1117. }
  1118. .offline body {
  1119. transition: background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);
  1120. }
  1121. .offline #main-message > p {
  1122. display: none;
  1123. }
  1124. .offline.inverted {
  1125. background-color: #fff;
  1126. filter: invert(1);
  1127. }
  1128. .offline.inverted body {
  1129. background-color: #fff;
  1130. }
  1131. .offline .interstitial-wrapper {
  1132. color: var(--text-color);
  1133. font-size: 1em;
  1134. line-height: 1.55;
  1135. margin: 0 auto;
  1136. max-width: 600px;
  1137. padding-top: 100px;
  1138. position: relative;
  1139. width: 100%;
  1140. }
  1141. .offline .runner-container {
  1142. direction: ltr;
  1143. height: 150px;
  1144. max-width: 600px;
  1145. overflow: hidden;
  1146. position: absolute;
  1147. top: 35px;
  1148. width: 44px;
  1149. }
  1150. .offline .runner-container:focus {
  1151. outline: none;
  1152. }
  1153. .offline .runner-container:focus-visible {
  1154. outline: 3px solid var(--google-blue-300);
  1155. }
  1156. .offline .runner-canvas {
  1157. height: 150px;
  1158. max-width: 600px;
  1159. opacity: 1;
  1160. overflow: hidden;
  1161. position: absolute;
  1162. top: 0;
  1163. z-index: 10;
  1164. }
  1165. .offline .controller {
  1166. height: 100vh;
  1167. left: 0;
  1168. position: absolute;
  1169. top: 0;
  1170. width: 100vw;
  1171. z-index: 9;
  1172. }
  1173. #offline-resources {
  1174. display: none;
  1175. }
  1176. #offline-instruction {
  1177. image-rendering: pixelated;
  1178. left: 0;
  1179. margin: auto;
  1180. position: absolute;
  1181. right: 0;
  1182. top: 60px;
  1183. width: fit-content;
  1184. }
  1185. .offline-runner-live-region {
  1186. bottom: 0;
  1187. clip-path: polygon(0 0, 0 0, 0 0);
  1188. color: var(--background-color);
  1189. display: block;
  1190. font-size: xx-small;
  1191. overflow: hidden;
  1192. position: absolute;
  1193. text-align: center;
  1194. transition: color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1);
  1195. user-select: none;
  1196. }
  1197. /* Custom toggle */
  1198. .slow-speed-option {
  1199. align-items: center;
  1200. background: var(--google-gray-50);
  1201. border-radius: 24px/50%;
  1202. bottom: 0;
  1203. color: var(--error-code-color);
  1204. display: inline-flex;
  1205. font-size: 1em;
  1206. left: 0;
  1207. line-height: 1.1em;
  1208. margin: 5px auto;
  1209. padding: 2px 12px 3px 20px;
  1210. position: absolute;
  1211. right: 0;
  1212. width: max-content;
  1213. z-index: 999;
  1214. }
  1215. .slow-speed-option.hidden {
  1216. display: none;
  1217. }
  1218. .slow-speed-option [type=checkbox] {
  1219. opacity: 0;
  1220. pointer-events: none;
  1221. position: absolute;
  1222. }
  1223. .slow-speed-option .slow-speed-toggle {
  1224. cursor: pointer;
  1225. margin-inline-start: 8px;
  1226. padding: 8px 4px;
  1227. position: relative;
  1228. }
  1229. .slow-speed-option [type=checkbox]:disabled ~ .slow-speed-toggle {
  1230. cursor: default;
  1231. }
  1232. .slow-speed-option-label [type=checkbox] {
  1233. opacity: 0;
  1234. pointer-events: none;
  1235. position: absolute;
  1236. }
  1237. .slow-speed-option .slow-speed-toggle::before,
  1238. .slow-speed-option .slow-speed-toggle::after {
  1239. content: '';
  1240. display: block;
  1241. margin: 0 3px;
  1242. transition: all 100ms cubic-bezier(0.4, 0, 1, 1);
  1243. }
  1244. .slow-speed-option .slow-speed-toggle::before {
  1245. background: rgb(189,193,198);
  1246. border-radius: 0.65em;
  1247. height: 0.9em;
  1248. width: 2em;
  1249. }
  1250. .slow-speed-option .slow-speed-toggle::after {
  1251. background: #fff;
  1252. border-radius: 50%;
  1253. box-shadow: 0 1px 3px 0 rgb(0 0 0 / 40%);
  1254. height: 1.2em;
  1255. position: absolute;
  1256. top: 51%;
  1257. transform: translate(-20%, -50%);
  1258. width: 1.1em;
  1259. }
  1260. .slow-speed-option [type=checkbox]:focus + .slow-speed-toggle {
  1261. box-shadow: 0 0 8px rgb(94, 158, 214);
  1262. outline: 1px solid rgb(93, 157, 213);
  1263. }
  1264. .slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::before {
  1265. background: var(--google-blue-600);
  1266. opacity: 0.5;
  1267. }
  1268. .slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::after {
  1269. background: var(--google-blue-600);
  1270. transform: translate(calc(2em - 90%), -50%);
  1271. }
  1272. .slow-speed-option [type=checkbox]:checked:disabled +
  1273. .slow-speed-toggle::before {
  1274. background: rgb(189,193,198);
  1275. }
  1276. .slow-speed-option [type=checkbox]:checked:disabled +
  1277. .slow-speed-toggle::after {
  1278. background: var(--google-gray-50);
  1279. }
  1280. @media (max-width: 420px) {
  1281. #download-button {
  1282. padding-bottom: 12px;
  1283. padding-top: 12px;
  1284. }
  1285. .suggested-left > #control-buttons,
  1286. .suggested-right > #control-buttons {
  1287. float: none;
  1288. }
  1289. .snackbar {
  1290. border-radius: 0;
  1291. bottom: 0;
  1292. left: 0;
  1293. width: 100%;
  1294. }
  1295. }
  1296. @media (max-height: 350px) {
  1297. h1 {
  1298. margin: 0 0 15px;
  1299. }
  1300. .icon-offline {
  1301. margin: 0 0 10px;
  1302. }
  1303. .interstitial-wrapper {
  1304. margin-top: 5%;
  1305. }
  1306. .nav-wrapper {
  1307. margin-top: 30px;
  1308. }
  1309. }
  1310. @media (min-width: 420px) and (max-width: 736px) and
  1311. (min-height: 240px) and (max-height: 420px) and
  1312. (orientation:landscape) {
  1313. .interstitial-wrapper {
  1314. margin-bottom: 100px;
  1315. }
  1316. }
  1317. @media (max-width: 360px) and (max-height: 480px) {
  1318. .offline .interstitial-wrapper {
  1319. padding-top: 60px;
  1320. }
  1321. .offline .runner-container {
  1322. top: 8px;
  1323. }
  1324. }
  1325. @media (min-height: 240px) and (orientation: landscape) {
  1326. .offline .interstitial-wrapper {
  1327. margin-bottom: 90px;
  1328. }
  1329. .icon-offline {
  1330. margin-bottom: 20px;
  1331. }
  1332. }
  1333. @media (max-height: 320px) and (orientation: landscape) {
  1334. .icon-offline {
  1335. margin-bottom: 0;
  1336. }
  1337. .offline .runner-container {
  1338. top: 10px;
  1339. }
  1340. }
  1341. @media (max-width: 240px) {
  1342. button {
  1343. padding-inline-end: 12px;
  1344. padding-inline-start: 12px;
  1345. }
  1346. .interstitial-wrapper {
  1347. overflow: inherit;
  1348. padding: 0 8px;
  1349. }
  1350. }
  1351. @media (max-width: 120px) {
  1352. button {
  1353. width: auto;
  1354. }
  1355. }
  1356. .arcade-mode,
  1357. .arcade-mode .runner-container,
  1358. .arcade-mode .runner-canvas {
  1359. image-rendering: pixelated;
  1360. max-width: 100%;
  1361. overflow: hidden;
  1362. }
  1363. .arcade-mode #buttons,
  1364. .arcade-mode #main-content {
  1365. opacity: 0;
  1366. overflow: hidden;
  1367. }
  1368. .arcade-mode .interstitial-wrapper {
  1369. height: 100vh;
  1370. max-width: 100%;
  1371. overflow: hidden;
  1372. }
  1373. .arcade-mode .runner-container {
  1374. left: 0;
  1375. margin: auto;
  1376. right: 0;
  1377. transform-origin: top center;
  1378. transition: transform 250ms cubic-bezier(0.4, 0, 1, 1) 400ms;
  1379. z-index: 2;
  1380. }
  1381. @media (prefers-color-scheme: dark) {
  1382. .icon {
  1383. filter: invert(1);
  1384. }
  1385. .offline .runner-canvas {
  1386. filter: invert(1);
  1387. }
  1388. .offline.inverted {
  1389. background-color: var(--background-color);
  1390. filter: invert(0);
  1391. }
  1392. .offline.inverted body {
  1393. background-color: #fff;
  1394. }
  1395. .offline.inverted .offline-runner-live-region {
  1396. color: #fff;
  1397. }
  1398. #suggestions-list a {
  1399. color: var(--link-color);
  1400. }
  1401. #error-information-button {
  1402. filter: invert(0.6);
  1403. }
  1404. .slow-speed-option {
  1405. background: var(--google-gray-800);
  1406. color: var(--google-gray-100);
  1407. }
  1408. .slow-speed-option .slow-speed-toggle::before,
  1409. .slow-speed-option [type=checkbox]:checked:disabled +
  1410. .slow-speed-toggle::before {
  1411. background: rgb(189,193,198);
  1412. }
  1413. .slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::after,
  1414. .slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::before {
  1415. background: var(--google-blue-300);
  1416. }
  1417. }
  1418. </style>
  1419. <script>(function(){function l(a,b,c){return Function.prototype.call.apply(Array.prototype.slice,arguments)}function m(a,b,c){var e=l(arguments,2);return function(){return b.apply(a,e)}}function n(a,b){var c=new p(b);for(c.h=[a];c.h.length;){var e=c,d=c.h.shift();e.i(d);for(d=d.firstChild;d;d=d.nextSibling)1==d.nodeType&&e.h.push(d);}}function p(a){this.i=a;}function q(a){a.style.display="";}function r(a){a.style.display="none";}var t=/\s*;\s*/;function u(a,b){this.l.apply(this,arguments);}u.prototype.l=function(a,b){this.a||(this.a={});if(b){var c=this.a,e=b.a;for(d in e)c[d]=e[d];}else {var d=this.a;e=v;for(c in e)d[c]=e[c];}this.a.$this=a;this.a.$context=this;this.f="undefined"!=typeof a&&null!=a?a:"";b||(this.a.$top=this.f);};var v={$default:null},w=[];function x(a){for(var b in a.a)delete a.a[b];a.f=null;w.push(a);}function y(a,b,c){try{return b.call(c,a.a,a.f)}catch(e){return v.$default}}
  1420. u.prototype.clone=function(a,b,c){if(0<w.length){var e=w.pop();u.call(e,a,this);a=e;}else a=new u(a,this);a.a.$index=b;a.a.$count=c;return a};var z;window.trustedTypes&&(z=trustedTypes.createPolicy("jstemplate",{createScript:function(a){return a}}));var A={};function B(a){if(!A[a])try{var b="(function(a_, b_) { with (a_) with (b_) return "+a+" })",c=window.trustedTypes?z.createScript(b):b;A[a]=window.eval(c);}catch(e){}return A[a]}
  1421. function E(a){var b=[];a=a.split(t);for(var c=0,e=a.length;c<e;++c){var d=a[c].indexOf(":");if(!(0>d)){var g=a[c].substr(0,d).replace(/^\s+/,"").replace(/\s+$/,"");d=B(a[c].substr(d+1));b.push(g,d);}}return b}function F(){}var G=0,H={0:{}},I={},J={},K=[];function L(a){a.__jstcache||n(a,function(b){M(b);});}var N=[["jsselect",B],["jsdisplay",B],["jsvalues",E],["jsvars",E],["jseval",function(a){var b=[];a=a.split(t);for(var c=0,e=a.length;c<e;++c)if(a[c]){var d=B(a[c]);b.push(d);}return b}],["transclude",function(a){return a}],["jscontent",B],["jsskip",B]];
  1422. function M(a){if(a.__jstcache)return a.__jstcache;var b=a.getAttribute("jstcache");if(null!=b)return a.__jstcache=H[b];b=K.length=0;for(var c=N.length;b<c;++b){var e=N[b][0],d=a.getAttribute(e);J[e]=d;null!=d&&K.push(e+"="+d);}if(0==K.length)return a.setAttribute("jstcache","0"),a.__jstcache=H[0];var g=K.join("&");if(b=I[g])return a.setAttribute("jstcache",b),a.__jstcache=H[b];var h={};b=0;for(c=N.length;b<c;++b){d=N[b];e=d[0];var f=d[1];d=J[e];null!=d&&(h[e]=f(d));}b=""+ ++G;a.setAttribute("jstcache",
  1423. b);H[b]=h;I[g]=b;return a.__jstcache=h}function P(a,b){a.j.push(b);a.o.push(0);}function Q(a){return a.c.length?a.c.pop():[]}
  1424. F.prototype.g=function(a,b){var c=R(b),e=c.transclude;if(e)(c=S(e))?(b.parentNode.replaceChild(c,b),e=Q(this),e.push(this.g,a,c),P(this,e)):b.parentNode.removeChild(b);else if(c=c.jsselect){c=y(a,c,b);var d=b.getAttribute("jsinstance");var g=!1;d&&("*"==d.charAt(0)?(d=parseInt(d.substr(1),10),g=!0):d=parseInt(d,10));var h=null!=c&&"object"==typeof c&&"number"==typeof c.length;e=h?c.length:1;var f=h&&0==e;if(h)if(f)d?b.parentNode.removeChild(b):(b.setAttribute("jsinstance","*0"),r(b));else if(q(b),
  1425. null===d||""===d||g&&d<e-1){g=Q(this);d=d||0;for(h=e-1;d<h;++d){var k=b.cloneNode(!0);b.parentNode.insertBefore(k,b);T(k,c,d);f=a.clone(c[d],d,e);g.push(this.b,f,k,x,f,null);}T(b,c,d);f=a.clone(c[d],d,e);g.push(this.b,f,b,x,f,null);P(this,g);}else d<e?(g=c[d],T(b,c,d),f=a.clone(g,d,e),g=Q(this),g.push(this.b,f,b,x,f,null),P(this,g)):b.parentNode.removeChild(b);else null==c?r(b):(q(b),f=a.clone(c,0,1),g=Q(this),g.push(this.b,f,b,x,f,null),P(this,g));}else this.b(a,b);};
  1426. F.prototype.b=function(a,b){var c=R(b),e=c.jsdisplay;if(e){if(!y(a,e,b)){r(b);return}q(b);}if(e=c.jsvars)for(var d=0,g=e.length;d<g;d+=2){var h=e[d],f=y(a,e[d+1],b);a.a[h]=f;}if(e=c.jsvalues)for(d=0,g=e.length;d<g;d+=2)if(f=e[d],h=y(a,e[d+1],b),"$"==f.charAt(0))a.a[f]=h;else if("."==f.charAt(0)){f=f.substr(1).split(".");for(var k=b,O=f.length,C=0,U=O-1;C<U;++C){var D=f[C];k[D]||(k[D]={});k=k[D];}k[f[O-1]]=h;}else f&&("boolean"==typeof h?h?b.setAttribute(f,f):b.removeAttribute(f):b.setAttribute(f,""+h));
  1427. if(e=c.jseval)for(d=0,g=e.length;d<g;++d)y(a,e[d],b);e=c.jsskip;if(!e||!y(a,e,b))if(c=c.jscontent){if(c=""+y(a,c,b),b.innerHTML!=c){for(;b.firstChild;)e=b.firstChild,e.parentNode.removeChild(e);b.appendChild(this.m.createTextNode(c));}}else {c=Q(this);for(e=b.firstChild;e;e=e.nextSibling)1==e.nodeType&&c.push(this.g,a,e);c.length&&P(this,c);}};function R(a){if(a.__jstcache)return a.__jstcache;var b=a.getAttribute("jstcache");return b?a.__jstcache=H[b]:M(a)}
  1428. function S(a,b){var c=document;if(b){var e=c.getElementById(a);if(!e){e=b();var d=c.getElementById("jsts");d||(d=c.createElement("div"),d.id="jsts",r(d),d.style.position="absolute",c.body.appendChild(d));var g=c.createElement("div");d.appendChild(g);g.innerHTML=e;e=c.getElementById(a);}c=e;}else c=c.getElementById(a);return c?(L(c),c=c.cloneNode(!0),c.removeAttribute("id"),c):null}function T(a,b,c){c==b.length-1?a.setAttribute("jsinstance","*"+c):a.setAttribute("jsinstance",""+c);}window.jstGetTemplate=S;window.JsEvalContext=u;window.jstProcess=function(a,b){var c=new F;L(b);c.m=b?9==b.nodeType?b:b.ownerDocument||document:document;var e=m(c,c.g,a,b),d=c.j=[],g=c.o=[];c.c=[];e();for(var h,f,k;d.length;)h=d[d.length-1],e=g[g.length-1],e>=h.length?(e=c,f=d.pop(),f.length=0,e.c.push(f),g.pop()):(f=h[e++],k=h[e++],h=h[e++],g[g.length-1]=e,f.call(c,k,h));};
  1429. })();
  1430. // Copyright 2017 The Chromium Authors
  1431. // Use of this source code is governed by a BSD-style license that can be
  1432. // found in the LICENSE file.
  1433. const HIDDEN_CLASS$1 = 'hidden';
  1434. //
  1435. //
  1436. // Copyright 2015 The Chromium Authors
  1437. // Use of this source code is governed by a BSD-style license that can be
  1438. // found in the LICENSE file.
  1439. let mobileNav = false;
  1440. /**
  1441. * For small screen mobile the navigation buttons are moved
  1442. * below the advanced text.
  1443. */
  1444. function onResize() {
  1445. const helpOuterBox = document.querySelector('#details');
  1446. const mainContent = document.querySelector('#main-content');
  1447. const mediaQuery = '(min-width: 240px) and (max-width: 420px) and ' +
  1448. '(min-height: 401px), ' +
  1449. '(max-height: 560px) and (min-height: 240px) and ' +
  1450. '(min-width: 421px)';
  1451. const detailsHidden = helpOuterBox.classList.contains(HIDDEN_CLASS$1);
  1452. const runnerContainer = document.querySelector('.runner-container');
  1453. // Check for change in nav status.
  1454. if (mobileNav !== window.matchMedia(mediaQuery).matches) {
  1455. mobileNav = !mobileNav;
  1456. // Handle showing the top content / details sections according to state.
  1457. if (mobileNav) {
  1458. mainContent.classList.toggle(HIDDEN_CLASS$1, !detailsHidden);
  1459. helpOuterBox.classList.toggle(HIDDEN_CLASS$1, detailsHidden);
  1460. if (runnerContainer) {
  1461. runnerContainer.classList.toggle(HIDDEN_CLASS$1, !detailsHidden);
  1462. }
  1463. } else if (!detailsHidden) {
  1464. // Non mobile nav with visible details.
  1465. mainContent.classList.remove(HIDDEN_CLASS$1);
  1466. helpOuterBox.classList.remove(HIDDEN_CLASS$1);
  1467. if (runnerContainer) {
  1468. runnerContainer.classList.remove(HIDDEN_CLASS$1);
  1469. }
  1470. }
  1471. }
  1472. }
  1473. function setupMobileNav() {
  1474. window.addEventListener('resize', onResize);
  1475. onResize();
  1476. }
  1477. document.addEventListener('DOMContentLoaded', setupMobileNav);
  1478. // Copyright 2022 The Chromium Authors
  1479. // Use of this source code is governed by a BSD-style license that can be
  1480. // found in the LICENSE file.
  1481. /**
  1482. * Verify |value| is truthy.
  1483. * @param value A value to check for truthiness. Note that this
  1484. * may be used to test whether |value| is defined or not, and we don't want
  1485. * to force a cast to boolean.
  1486. */
  1487. function assert(value, message) {
  1488. if (value) {
  1489. return;
  1490. }
  1491. throw new Error('Assertion failed' + (message ? `: ${message}` : ''));
  1492. }
  1493. // Copyright 2022 The Chromium Authors
  1494. // Use of this source code is governed by a BSD-style license that can be
  1495. // found in the LICENSE file.
  1496. /**
  1497. * @fileoverview This file defines a singleton which provides access to all data
  1498. * that is available as soon as the page's resources are loaded (before DOM
  1499. * content has finished loading). This data includes both localized strings and
  1500. * any data that is important to have ready from a very early stage (e.g. things
  1501. * that must be displayed right away).
  1502. *
  1503. * Note that loadTimeData is not guaranteed to be consistent between page
  1504. * refreshes (https://crbug.com/740629) and should not contain values that might
  1505. * change if the page is re-opened later.
  1506. */
  1507. class LoadTimeData {
  1508. data_ = null;
  1509. /**
  1510. * Sets the backing object.
  1511. *
  1512. * Note that there is no getter for |data_| to discourage abuse of the form:
  1513. *
  1514. * var value = loadTimeData.data()['key'];
  1515. */
  1516. set data(value) {
  1517. assert(!this.data_, 'Re-setting data.');
  1518. this.data_ = value;
  1519. }
  1520. /**
  1521. * @param id An ID of a value that might exist.
  1522. * @return True if |id| is a key in the dictionary.
  1523. */
  1524. valueExists(id) {
  1525. assert(this.data_, 'No data. Did you remember to include strings.js?');
  1526. return id in this.data_;
  1527. }
  1528. /**
  1529. * Fetches a value, expecting that it exists.
  1530. * @param id The key that identifies the desired value.
  1531. * @return The corresponding value.
  1532. */
  1533. getValue(id) {
  1534. assert(this.data_, 'No data. Did you remember to include strings.js?');
  1535. const value = this.data_[id];
  1536. assert(typeof value !== 'undefined', 'Could not find value for ' + id);
  1537. return value;
  1538. }
  1539. /**
  1540. * As above, but also makes sure that the value is a string.
  1541. * @param id The key that identifies the desired string.
  1542. * @return The corresponding string value.
  1543. */
  1544. getString(id) {
  1545. const value = this.getValue(id);
  1546. assert(typeof value === 'string', `[${value}] (${id}) is not a string`);
  1547. return value;
  1548. }
  1549. /**
  1550. * Returns a formatted localized string where $1 to $9 are replaced by the
  1551. * second to the tenth argument.
  1552. * @param id The ID of the string we want.
  1553. * @param args The extra values to include in the formatted output.
  1554. * @return The formatted string.
  1555. */
  1556. getStringF(id, ...args) {
  1557. const value = this.getString(id);
  1558. if (!value) {
  1559. return '';
  1560. }
  1561. return this.substituteString(value, ...args);
  1562. }
  1563. /**
  1564. * Returns a formatted localized string where $1 to $9 are replaced by the
  1565. * second to the tenth argument. Any standalone $ signs must be escaped as
  1566. * $$.
  1567. * @param label The label to substitute through. This is not an resource ID.
  1568. * @param args The extra values to include in the formatted output.
  1569. * @return The formatted string.
  1570. */
  1571. substituteString(label, ...args) {
  1572. return label.replace(/\$(.|$|\n)/g, function (m) {
  1573. assert(m.match(/\$[$1-9]/), 'Unescaped $ found in localized string.');
  1574. if (m === '$$') {
  1575. return '$';
  1576. }
  1577. const substitute = args[Number(m[1]) - 1];
  1578. if (substitute === undefined || substitute === null) {
  1579. // Not all callers actually provide values for all substitutes. Return
  1580. // an empty value for this case.
  1581. return '';
  1582. }
  1583. return substitute.toString();
  1584. });
  1585. }
  1586. /**
  1587. * Returns a formatted string where $1 to $9 are replaced by the second to
  1588. * tenth argument, split apart into a list of pieces describing how the
  1589. * substitution was performed. Any standalone $ signs must be escaped as $$.
  1590. * @param label A localized string to substitute through.
  1591. * This is not an resource ID.
  1592. * @param args The extra values to include in the formatted output.
  1593. * @return The formatted string pieces.
  1594. */
  1595. getSubstitutedStringPieces(label, ...args) {
  1596. // Split the string by separately matching all occurrences of $1-9 and of
  1597. // non $1-9 pieces.
  1598. const pieces = (label.match(/(\$[1-9])|(([^$]|\$([^1-9]|$))+)/g) ||
  1599. []).map(function (p) {
  1600. // Pieces that are not $1-9 should be returned after replacing $$
  1601. // with $.
  1602. if (!p.match(/^\$[1-9]$/)) {
  1603. assert((p.match(/\$/g) || []).length % 2 === 0, 'Unescaped $ found in localized string.');
  1604. return { value: p.replace(/\$\$/g, '$'), arg: null };
  1605. }
  1606. // Otherwise, return the substitution value.
  1607. const substitute = args[Number(p[1]) - 1];
  1608. if (substitute === undefined || substitute === null) {
  1609. // Not all callers actually provide values for all substitutes. Return
  1610. // an empty value for this case.
  1611. return { value: '', arg: p };
  1612. }
  1613. return { value: substitute.toString(), arg: p };
  1614. });
  1615. return pieces;
  1616. }
  1617. /**
  1618. * As above, but also makes sure that the value is a boolean.
  1619. * @param id The key that identifies the desired boolean.
  1620. * @return The corresponding boolean value.
  1621. */
  1622. getBoolean(id) {
  1623. const value = this.getValue(id);
  1624. assert(typeof value === 'boolean', `[${value}] (${id}) is not a boolean`);
  1625. return value;
  1626. }
  1627. /**
  1628. * As above, but also makes sure that the value is an integer.
  1629. * @param id The key that identifies the desired number.
  1630. * @return The corresponding number value.
  1631. */
  1632. getInteger(id) {
  1633. const value = this.getValue(id);
  1634. assert(typeof value === 'number', `[${value}] (${id}) is not a number`);
  1635. assert(value === Math.floor(value), 'Number isn\'t integer: ' + value);
  1636. return value;
  1637. }
  1638. /**
  1639. * Override values in loadTimeData with the values found in |replacements|.
  1640. * @param replacements The dictionary object of keys to replace.
  1641. */
  1642. overrideValues(replacements) {
  1643. assert(typeof replacements === 'object', 'Replacements must be a dictionary object.');
  1644. assert(this.data_, 'Data must exist before being overridden');
  1645. for (const key in replacements) {
  1646. this.data_[key] = replacements[key];
  1647. }
  1648. }
  1649. /**
  1650. * Reset loadTimeData's data. Should only be used in tests.
  1651. * @param newData The data to restore to, when null restores to unset state.
  1652. */
  1653. resetForTesting(newData = null) {
  1654. this.data_ = newData;
  1655. }
  1656. /**
  1657. * @return Whether loadTimeData.data has been set.
  1658. */
  1659. isInitialized() {
  1660. return this.data_ !== null;
  1661. }
  1662. }
  1663. const loadTimeData = new LoadTimeData();
  1664. // Copyright 2023 The Chromium Authors
  1665. // Use of this source code is governed by a BSD-style license that can be
  1666. // found in the LICENSE file.
  1667. const HIDDEN_CLASS = 'hidden';
  1668. // Copyright 2021 The Chromium Authors
  1669. // Use of this source code is governed by a BSD-style license that can be
  1670. // found in the LICENSE file.
  1671. /* @const
  1672. * Add matching sprite definition and config to spriteDefinitionByType.
  1673. */
  1674. const GAME_TYPE = ['altgame'];
  1675. //******************************************************************************
  1676. /**
  1677. * Collision box object.
  1678. * @param {number} x X position.
  1679. * @param {number} y Y Position.
  1680. * @param {number} w Width.
  1681. * @param {number} h Height.
  1682. * @constructor
  1683. */
  1684. function CollisionBox(x, y, w, h) {
  1685. this.x = x;
  1686. this.y = y;
  1687. this.width = w;
  1688. this.height = h;
  1689. }
  1690. /**
  1691. * T-Rex runner sprite definitions.
  1692. */
  1693. const spriteDefinitionByType = {
  1694. original: {
  1695. LDPI: {
  1696. BACKGROUND_EL: {x: 86, y: 2},
  1697. CACTUS_LARGE: {x: 332, y: 2},
  1698. CACTUS_SMALL: {x: 228, y: 2},
  1699. OBSTACLE_2: {x: 332, y: 2},
  1700. OBSTACLE: {x: 228, y: 2},
  1701. CLOUD: {x: 86, y: 2},
  1702. HORIZON: {x: 2, y: 54},
  1703. MOON: {x: 484, y: 2},
  1704. PTERODACTYL: {x: 134, y: 2},
  1705. RESTART: {x: 2, y: 68},
  1706. TEXT_SPRITE: {x: 655, y: 2},
  1707. TREX: {x: 848, y: 2},
  1708. STAR: {x: 645, y: 2},
  1709. COLLECTABLE: {x: 0, y: 0},
  1710. ALT_GAME_END: {x: 32, y: 0},
  1711. },
  1712. HDPI: {
  1713. BACKGROUND_EL: {x: 166, y: 2},
  1714. CACTUS_LARGE: {x: 652, y: 2},
  1715. CACTUS_SMALL: {x: 446, y: 2},
  1716. OBSTACLE_2: {x: 652, y: 2},
  1717. OBSTACLE: {x: 446, y: 2},
  1718. CLOUD: {x: 166, y: 2},
  1719. HORIZON: {x: 2, y: 104},
  1720. MOON: {x: 954, y: 2},
  1721. PTERODACTYL: {x: 260, y: 2},
  1722. RESTART: {x: 2, y: 130},
  1723. TEXT_SPRITE: {x: 1294, y: 2},
  1724. TREX: {x: 1678, y: 2},
  1725. STAR: {x: 1276, y: 2},
  1726. COLLECTABLE: {x: 0, y: 0},
  1727. ALT_GAME_END: {x: 64, y: 0},
  1728. },
  1729. MAX_GAP_COEFFICIENT: 1.5,
  1730. MAX_OBSTACLE_LENGTH: 3,
  1731. HAS_CLOUDS: 1,
  1732. BOTTOM_PAD: 10,
  1733. TREX: {
  1734. WAITING_1: {x: 44, w: 44, h: 47, xOffset: 0},
  1735. WAITING_2: {x: 0, w: 44, h: 47, xOffset: 0},
  1736. RUNNING_1: {x: 88, w: 44, h: 47, xOffset: 0},
  1737. RUNNING_2: {x: 132, w: 44, h: 47, xOffset: 0},
  1738. JUMPING: {x: 0, w: 44, h: 47, xOffset: 0},
  1739. CRASHED: {x: 220, w: 44, h: 47, xOffset: 0},
  1740. COLLISION_BOXES: [
  1741. new CollisionBox(22, 0, 17, 16),
  1742. new CollisionBox(1, 18, 30, 9),
  1743. new CollisionBox(10, 35, 14, 8),
  1744. new CollisionBox(1, 24, 29, 5),
  1745. new CollisionBox(5, 30, 21, 4),
  1746. new CollisionBox(9, 34, 15, 4),
  1747. ],
  1748. },
  1749. /** @type {Array<ObstacleType>} */
  1750. OBSTACLES: [
  1751. {
  1752. type: 'CACTUS_SMALL',
  1753. width: 17,
  1754. height: 35,
  1755. yPos: 105,
  1756. multipleSpeed: 4,
  1757. minGap: 120,
  1758. minSpeed: 0,
  1759. collisionBoxes: [
  1760. new CollisionBox(0, 7, 5, 27),
  1761. new CollisionBox(4, 0, 6, 34),
  1762. new CollisionBox(10, 4, 7, 14),
  1763. ],
  1764. },
  1765. {
  1766. type: 'CACTUS_LARGE',
  1767. width: 25,
  1768. height: 50,
  1769. yPos: 90,
  1770. multipleSpeed: 7,
  1771. minGap: 120,
  1772. minSpeed: 0,
  1773. collisionBoxes: [
  1774. new CollisionBox(0, 12, 7, 38),
  1775. new CollisionBox(8, 0, 7, 49),
  1776. new CollisionBox(13, 10, 10, 38),
  1777. ],
  1778. },
  1779. {
  1780. type: 'PTERODACTYL',
  1781. width: 46,
  1782. height: 40,
  1783. yPos: [100, 75, 50], // Variable height.
  1784. yPosMobile: [100, 50], // Variable height mobile.
  1785. multipleSpeed: 999,
  1786. minSpeed: 8.5,
  1787. minGap: 150,
  1788. collisionBoxes: [
  1789. new CollisionBox(15, 15, 16, 5),
  1790. new CollisionBox(18, 21, 24, 6),
  1791. new CollisionBox(2, 14, 4, 3),
  1792. new CollisionBox(6, 10, 4, 7),
  1793. new CollisionBox(10, 8, 6, 9),
  1794. ],
  1795. numFrames: 2,
  1796. frameRate: 1000 / 6,
  1797. speedOffset: .8,
  1798. },
  1799. {
  1800. type: 'COLLECTABLE',
  1801. width: 31,
  1802. height: 24,
  1803. yPos: 104,
  1804. multipleSpeed: 1000,
  1805. minGap: 9999,
  1806. minSpeed: 0,
  1807. collisionBoxes: [
  1808. new CollisionBox(0, 0, 32, 25),
  1809. ],
  1810. },
  1811. ],
  1812. BACKGROUND_EL: {
  1813. 'CLOUD': {
  1814. HEIGHT: 14,
  1815. MAX_CLOUD_GAP: 400,
  1816. MAX_SKY_LEVEL: 30,
  1817. MIN_CLOUD_GAP: 100,
  1818. MIN_SKY_LEVEL: 71,
  1819. OFFSET: 4,
  1820. WIDTH: 46,
  1821. X_POS: 1,
  1822. Y_POS: 120,
  1823. },
  1824. },
  1825. BACKGROUND_EL_CONFIG: {
  1826. MAX_BG_ELS: 1,
  1827. MAX_GAP: 400,
  1828. MIN_GAP: 100,
  1829. POS: 0,
  1830. SPEED: 0.5,
  1831. Y_POS: 125,
  1832. },
  1833. LINES: [
  1834. {SOURCE_X: 2, SOURCE_Y: 52, WIDTH: 600, HEIGHT: 12, YPOS: 127},
  1835. ],
  1836. ALT_GAME_OVER_TEXT_CONFIG: {
  1837. TEXT_X: 32,
  1838. TEXT_Y: 0,
  1839. TEXT_WIDTH: 246,
  1840. TEXT_HEIGHT: 17,
  1841. FLASH_DURATION: 1500,
  1842. FLASHING: false,
  1843. },
  1844. },
  1845. altgame: {
  1846. LDPI: {
  1847. BACKGROUND_EL: {x: 260, y: 19},
  1848. OBSTACLE1: {x: 152, y: 65},
  1849. OBSTACLE2: {x: 188, y: 65},
  1850. OBSTACLE3: {x: 152, y: 65},
  1851. OBSTACLE4: {x: 188, y: 65},
  1852. OBSTACLE5: {x: 0, y: 60},
  1853. OBSTACLE6: {x: 42, y: 58},
  1854. OBSTACLE7: {x: 98, y: 58},
  1855. OBSTACLE8: {x: 96, y: 19},
  1856. HORIZON: {x: 0, y: 3},
  1857. TREX: {x: 557, y: 63},
  1858. COLLECTABLE: {x: 193, y: 19},
  1859. },
  1860. HDPI: {
  1861. BACKGROUND_EL: {x: 520, y: 38},
  1862. OBSTACLE1: {x: 304, y: 130},
  1863. OBSTACLE2: {x: 376, y: 130},
  1864. OBSTACLE3: {x: 304, y: 130},
  1865. OBSTACLE4: {x: 376, y: 130},
  1866. OBSTACLE5: {x: 0, y: 120},
  1867. OBSTACLE6: {x: 84, y: 116},
  1868. OBSTACLE7: {x: 196, y: 116},
  1869. OBSTACLE8: {x: 192, y: 38},
  1870. HORIZON: {x: 0, y: 6},
  1871. TREX: {x: 1114, y: 126},
  1872. COLLECTABLE: {x: 386, y: 38},
  1873. },
  1874. MAX_GAP_COEFFICIENT: 1.5,
  1875. MAX_OBSTACLE_LENGTH: 2,
  1876. HAS_CLOUDS: 0,
  1877. BOTTOM_PAD: 10,
  1878. TREX: {
  1879. MAX_JUMP_HEIGHT: 50,
  1880. MIN_JUMP_HEIGHT: 40,
  1881. INITIAL_JUMP_VELOCITY: -10,
  1882. RUNNING_1: {x: 96, w: 49, h: 47, xOffset: 0},
  1883. RUNNING_2: {x: 145, w: 49, h: 47, xOffset: 0},
  1884. JUMPING: {x: 47, w: 49, h: 47, xOffset: 0},
  1885. CRASHED: {x: 194, w: 61, h: 47, xOffset: 0},
  1886. DUCKING_1: {x: 257, w: 55, h: 26, xOffset: 0},
  1887. DUCKING_2: {x: 316, w: 55, h: 26, xOffset: 0},
  1888. COLLISION_BOXES: [
  1889. new CollisionBox(22, 0, 17, 16),
  1890. new CollisionBox(1, 18, 30, 9),
  1891. new CollisionBox(10, 35, 14, 8),
  1892. new CollisionBox(1, 24, 29, 5),
  1893. new CollisionBox(5, 30, 21, 4),
  1894. new CollisionBox(9, 34, 15, 4),
  1895. ],
  1896. },
  1897. /** @type {Array<ObstacleType>} */
  1898. OBSTACLES: [
  1899. {
  1900. type: 'OBSTACLE1',
  1901. width: 36,
  1902. height: 45,
  1903. yPos: 95,
  1904. multipleSpeed: 999,
  1905. minGap: 120,
  1906. minSpeed: 0,
  1907. collisionBoxes: [
  1908. new CollisionBox(0, 17, 8, 28),
  1909. new CollisionBox(6, 3, 24, 42),
  1910. new CollisionBox(28, 17, 8, 28),
  1911. ],
  1912. },
  1913. {
  1914. type: 'OBSTACLE2',
  1915. width: 36,
  1916. height: 45,
  1917. yPos: 95,
  1918. multipleSpeed: 999,
  1919. minGap: 120,
  1920. minSpeed: 0,
  1921. collisionBoxes: [
  1922. new CollisionBox(0, 17, 8, 28),
  1923. new CollisionBox(6, 3, 24, 42),
  1924. new CollisionBox(28, 17, 8, 28),
  1925. ],
  1926. },
  1927. {
  1928. type: 'OBSTACLE3',
  1929. width: 72,
  1930. height: 45,
  1931. yPos: 95,
  1932. multipleSpeed: 999,
  1933. minGap: 120,
  1934. minSpeed: 8,
  1935. collisionBoxes: [
  1936. new CollisionBox(0, 17, 8, 28),
  1937. new CollisionBox(6, 3, 24, 42),
  1938. new CollisionBox(28, 17, 8, 28),
  1939. new CollisionBox(36, 17, 8, 28),
  1940. new CollisionBox(42, 3, 24, 42),
  1941. new CollisionBox(64, 17, 8, 28),
  1942. ],
  1943. },
  1944. {
  1945. type: 'OBSTACLE4',
  1946. width: 72,
  1947. height: 45,
  1948. yPos: 95,
  1949. multipleSpeed: 999,
  1950. minGap: 120,
  1951. minSpeed: 8,
  1952. collisionBoxes: [
  1953. new CollisionBox(0, 17, 8, 28),
  1954. new CollisionBox(6, 3, 24, 42),
  1955. new CollisionBox(28, 17, 8, 28),
  1956. new CollisionBox(36, 17, 8, 28),
  1957. new CollisionBox(42, 3, 24, 42),
  1958. new CollisionBox(64, 17, 8, 28),
  1959. ],
  1960. },
  1961. {
  1962. type: 'OBSTACLE5',
  1963. width: 42,
  1964. height: 50,
  1965. yPos: 95,
  1966. multipleSpeed: 999,
  1967. minGap: 120,
  1968. minSpeed: 5,
  1969. collisionBoxes: [
  1970. new CollisionBox(0, 0, 42, 50),
  1971. ],
  1972. },
  1973. {
  1974. type: 'OBSTACLE6',
  1975. width: 56,
  1976. height: 52,
  1977. yPos: 93,
  1978. multipleSpeed: 999,
  1979. minGap: 120,
  1980. minSpeed: 7,
  1981. collisionBoxes: [
  1982. new CollisionBox(0, 11, 8, 40),
  1983. new CollisionBox(8, 0, 19, 51),
  1984. new CollisionBox(27, 11, 28, 40),
  1985. ],
  1986. },
  1987. {
  1988. type: 'OBSTACLE7',
  1989. width: 54,
  1990. height: 52,
  1991. yPos: 93,
  1992. multipleSpeed: 999,
  1993. minGap: 120,
  1994. minSpeed: 6,
  1995. collisionBoxes: [
  1996. new CollisionBox(0, 11, 19, 40),
  1997. new CollisionBox(19, 0, 19, 51),
  1998. new CollisionBox(38, 14, 15, 37),
  1999. ],
  2000. },
  2001. {
  2002. type: 'OBSTACLE8',
  2003. width: 49,
  2004. height: 20,
  2005. yPos: [100, 75, 50], // Variable height.
  2006. yPosMobile: [100, 50], // Variable height mobile.
  2007. multipleSpeed: 999,
  2008. minSpeed: 8.5,
  2009. minGap: 150,
  2010. collisionBoxes: [
  2011. new CollisionBox(15, 15, 16, 5),
  2012. new CollisionBox(18, 21, 24, 6),
  2013. new CollisionBox(2, 14, 4, 3),
  2014. new CollisionBox(6, 10, 4, 7),
  2015. new CollisionBox(10, 8, 6, 9),
  2016. ],
  2017. numFrames: 2,
  2018. frameRate: 1000 / 6,
  2019. speedOffset: .8,
  2020. },
  2021. ],
  2022. BACKGROUND_EL: {
  2023. 'GROUP1': {
  2024. HEIGHT: 91,
  2025. MAX_CLOUD_GAP: 600,
  2026. MAX_SKY_LEVEL: 0,
  2027. MIN_CLOUD_GAP: 300,
  2028. MIN_SKY_LEVEL: 0,
  2029. OFFSET: 11,
  2030. WIDTH: 131,
  2031. X_POS: 260,
  2032. },
  2033. 'GROUP2': {
  2034. HEIGHT: 91,
  2035. MAX_CLOUD_GAP: 600,
  2036. MAX_SKY_LEVEL: 0,
  2037. MIN_CLOUD_GAP: 300,
  2038. MIN_SKY_LEVEL: 0,
  2039. OFFSET: 11,
  2040. WIDTH: 166,
  2041. X_POS: 391,
  2042. },
  2043. },
  2044. BACKGROUND_EL_CONFIG: {
  2045. MAX_BG_ELS: 8,
  2046. MAX_GAP: 600,
  2047. MIN_GAP: 300,
  2048. POS: 0,
  2049. SPEED: 0.8,
  2050. Y_POS: 122,
  2051. },
  2052. LINES: [
  2053. {SOURCE_X: 2, SOURCE_Y: 3, WIDTH: 600, HEIGHT: 12, YPOS: 128},
  2054. ],
  2055. },
  2056. };
  2057. // Copyright 2014 The Chromium Authors
  2058. // Use of this source code is governed by a BSD-style license that can be
  2059. // found in the LICENSE file.
  2060. /**
  2061. * T-Rex runner.
  2062. * @param {string} outerContainerId Outer containing element id.
  2063. * @param {!Object=} opt_config
  2064. * @constructor
  2065. * @implements {EventListener}
  2066. * @export
  2067. */
  2068. function Runner(outerContainerId, opt_config) {
  2069. // Singleton
  2070. if (Runner.instance_) {
  2071. return Runner.instance_;
  2072. }
  2073. Runner.instance_ = this;
  2074. this.outerContainerEl = document.querySelector(outerContainerId);
  2075. this.containerEl = null;
  2076. this.snackbarEl = null;
  2077. // A div to intercept touch events. Only set while (playing && useTouch).
  2078. this.touchController = null;
  2079. this.config = opt_config || Object.assign(Runner.config, Runner.normalConfig);
  2080. // Logical dimensions of the container.
  2081. this.dimensions = Runner.defaultDimensions;
  2082. this.gameType = null;
  2083. Runner.spriteDefinition = spriteDefinitionByType['original'];
  2084. this.altGameImageSprite = null;
  2085. this.altGameModeActive = false;
  2086. this.altGameModeFlashTimer = null;
  2087. this.fadeInTimer = 0;
  2088. this.canvas = null;
  2089. this.canvasCtx = null;
  2090. this.tRex = null;
  2091. this.distanceMeter = null;
  2092. this.distanceRan = 0;
  2093. this.highestScore = 0;
  2094. this.syncHighestScore = false;
  2095. this.time = 0;
  2096. this.runningTime = 0;
  2097. this.msPerFrame = 1000 / FPS;
  2098. this.currentSpeed = this.config.SPEED;
  2099. Runner.slowDown = false;
  2100. this.obstacles = [];
  2101. this.activated = false; // Whether the easter egg has been activated.
  2102. this.playing = false; // Whether the game is currently in play state.
  2103. this.crashed = false;
  2104. this.paused = false;
  2105. this.inverted = false;
  2106. this.invertTimer = 0;
  2107. this.resizeTimerId_ = null;
  2108. this.playCount = 0;
  2109. // Sound FX.
  2110. this.audioBuffer = null;
  2111. /** @type {Object} */
  2112. this.soundFx = {};
  2113. this.generatedSoundFx = null;
  2114. // Global web audio context for playing sounds.
  2115. this.audioContext = null;
  2116. // Images.
  2117. this.images = {};
  2118. this.imagesLoaded = 0;
  2119. // Gamepad state.
  2120. this.pollingGamepads = false;
  2121. this.gamepadIndex = undefined;
  2122. this.previousGamepad = null;
  2123. if (this.isDisabled()) {
  2124. this.setupDisabledRunner();
  2125. } else {
  2126. if (Runner.isAltGameModeEnabled()) {
  2127. this.initAltGameType();
  2128. Runner.gameType = this.gameType;
  2129. }
  2130. this.loadImages();
  2131. window['initializeEasterEggHighScore'] =
  2132. this.initializeHighScore.bind(this);
  2133. }
  2134. }
  2135. /**
  2136. * Default game width.
  2137. * @const
  2138. */
  2139. const DEFAULT_WIDTH = 600;
  2140. /**
  2141. * Frames per second.
  2142. * @const
  2143. */
  2144. const FPS = 60;
  2145. /** @const */
  2146. const IS_HIDPI = window.devicePixelRatio > 1;
  2147. /** @const */
  2148. const IS_IOS = /CriOS/.test(window.navigator.userAgent);
  2149. /** @const */
  2150. const IS_MOBILE = /Android/.test(window.navigator.userAgent) || IS_IOS;
  2151. /** @const */
  2152. const IS_RTL = document.querySelector('html').dir == 'rtl';
  2153. /** @const */
  2154. const ARCADE_MODE_URL = 'chrome://dino/';
  2155. /** @const */
  2156. const RESOURCE_POSTFIX = 'offline-resources-';
  2157. /** @const */
  2158. const A11Y_STRINGS = {
  2159. ariaLabel: 'dinoGameA11yAriaLabel',
  2160. description: 'dinoGameA11yDescription',
  2161. gameOver: 'dinoGameA11yGameOver',
  2162. highScore: 'dinoGameA11yHighScore',
  2163. jump: 'dinoGameA11yJump',
  2164. started: 'dinoGameA11yStartGame',
  2165. speedLabel: 'dinoGameA11ySpeedToggle',
  2166. };
  2167. /**
  2168. * Default game configuration.
  2169. * Shared config for all versions of the game. Additional parameters are
  2170. * defined in Runner.normalConfig and Runner.slowConfig.
  2171. */
  2172. Runner.config = {
  2173. AUDIOCUE_PROXIMITY_THRESHOLD: 190,
  2174. AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y: 250,
  2175. BG_CLOUD_SPEED: 0.2,
  2176. BOTTOM_PAD: 10,
  2177. // Scroll Y threshold at which the game can be activated.
  2178. CANVAS_IN_VIEW_OFFSET: -10,
  2179. CLEAR_TIME: 3000,
  2180. CLOUD_FREQUENCY: 0.5,
  2181. FADE_DURATION: 1,
  2182. FLASH_DURATION: 1000,
  2183. GAMEOVER_CLEAR_TIME: 1200,
  2184. INITIAL_JUMP_VELOCITY: 12,
  2185. INVERT_FADE_DURATION: 12000,
  2186. MAX_BLINK_COUNT: 3,
  2187. MAX_CLOUDS: 6,
  2188. MAX_OBSTACLE_LENGTH: 3,
  2189. MAX_OBSTACLE_DUPLICATION: 2,
  2190. RESOURCE_TEMPLATE_ID: 'audio-resources',
  2191. SPEED: 6,
  2192. SPEED_DROP_COEFFICIENT: 3,
  2193. ARCADE_MODE_INITIAL_TOP_POSITION: 35,
  2194. ARCADE_MODE_TOP_POSITION_PERCENT: 0.1,
  2195. };
  2196. Runner.normalConfig = {
  2197. ACCELERATION: 0.001,
  2198. AUDIOCUE_PROXIMITY_THRESHOLD: 190,
  2199. AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y: 250,
  2200. GAP_COEFFICIENT: 0.6,
  2201. INVERT_DISTANCE: 700,
  2202. MAX_SPEED: 13,
  2203. MOBILE_SPEED_COEFFICIENT: 1.2,
  2204. SPEED: 6,
  2205. };
  2206. Runner.slowConfig = {
  2207. ACCELERATION: 0.0005,
  2208. AUDIOCUE_PROXIMITY_THRESHOLD: 170,
  2209. AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y: 220,
  2210. GAP_COEFFICIENT: 0.3,
  2211. INVERT_DISTANCE: 350,
  2212. MAX_SPEED: 9,
  2213. MOBILE_SPEED_COEFFICIENT: 1.5,
  2214. SPEED: 4.2,
  2215. };
  2216. /**
  2217. * Default dimensions.
  2218. */
  2219. Runner.defaultDimensions = {
  2220. WIDTH: DEFAULT_WIDTH,
  2221. HEIGHT: 150,
  2222. };
  2223. /**
  2224. * CSS class names.
  2225. * @enum {string}
  2226. */
  2227. Runner.classes = {
  2228. ARCADE_MODE: 'arcade-mode',
  2229. CANVAS: 'runner-canvas',
  2230. CONTAINER: 'runner-container',
  2231. CRASHED: 'crashed',
  2232. ICON: 'icon-offline',
  2233. INVERTED: 'inverted',
  2234. SNACKBAR: 'snackbar',
  2235. SNACKBAR_SHOW: 'snackbar-show',
  2236. TOUCH_CONTROLLER: 'controller',
  2237. };
  2238. /**
  2239. * Sound FX. Reference to the ID of the audio tag on interstitial page.
  2240. * @enum {string}
  2241. */
  2242. Runner.sounds = {
  2243. BUTTON_PRESS: 'offline-sound-press',
  2244. HIT: 'offline-sound-hit',
  2245. SCORE: 'offline-sound-reached',
  2246. };
  2247. /**
  2248. * Key code mapping.
  2249. * @enum {Object}
  2250. */
  2251. Runner.keycodes = {
  2252. JUMP: {'38': 1, '32': 1}, // Up, spacebar
  2253. DUCK: {'40': 1}, // Down
  2254. RESTART: {'13': 1}, // Enter
  2255. };
  2256. /**
  2257. * Runner event names.
  2258. * @enum {string}
  2259. */
  2260. Runner.events = {
  2261. ANIM_END: 'webkitAnimationEnd',
  2262. CLICK: 'click',
  2263. KEYDOWN: 'keydown',
  2264. KEYUP: 'keyup',
  2265. POINTERDOWN: 'pointerdown',
  2266. POINTERUP: 'pointerup',
  2267. RESIZE: 'resize',
  2268. TOUCHEND: 'touchend',
  2269. TOUCHSTART: 'touchstart',
  2270. VISIBILITY: 'visibilitychange',
  2271. BLUR: 'blur',
  2272. FOCUS: 'focus',
  2273. LOAD: 'load',
  2274. GAMEPADCONNECTED: 'gamepadconnected',
  2275. };
  2276. Runner.prototype = {
  2277. /**
  2278. * Initialize alternative game type.
  2279. */
  2280. initAltGameType() {
  2281. if (GAME_TYPE.length > 0) {
  2282. this.gameType = loadTimeData && loadTimeData.valueExists('altGameType') ?
  2283. GAME_TYPE[parseInt(loadTimeData.getValue('altGameType'), 10) - 1] :
  2284. '';
  2285. }
  2286. },
  2287. /**
  2288. * Whether the easter egg has been disabled. CrOS enterprise enrolled devices.
  2289. * @return {boolean}
  2290. */
  2291. isDisabled() {
  2292. return loadTimeData && loadTimeData.valueExists('disabledEasterEgg');
  2293. },
  2294. /**
  2295. * For disabled instances, set up a snackbar with the disabled message.
  2296. */
  2297. setupDisabledRunner() {
  2298. this.containerEl = document.createElement('div');
  2299. this.containerEl.className = Runner.classes.SNACKBAR;
  2300. this.containerEl.textContent = loadTimeData.getValue('disabledEasterEgg');
  2301. this.outerContainerEl.appendChild(this.containerEl);
  2302. // Show notification when the activation key is pressed.
  2303. document.addEventListener(Runner.events.KEYDOWN, function(e) {
  2304. if (Runner.keycodes.JUMP[e.keyCode]) {
  2305. this.containerEl.classList.add(Runner.classes.SNACKBAR_SHOW);
  2306. document.querySelector('.icon').classList.add('icon-disabled');
  2307. }
  2308. }.bind(this));
  2309. },
  2310. /**
  2311. * Setting individual settings for debugging.
  2312. * @param {string} setting
  2313. * @param {number|string} value
  2314. */
  2315. updateConfigSetting(setting, value) {
  2316. if (setting in this.config && value !== undefined) {
  2317. this.config[setting] = value;
  2318. switch (setting) {
  2319. case 'GRAVITY':
  2320. case 'MIN_JUMP_HEIGHT':
  2321. case 'SPEED_DROP_COEFFICIENT':
  2322. this.tRex.config[setting] = value;
  2323. break;
  2324. case 'INITIAL_JUMP_VELOCITY':
  2325. this.tRex.setJumpVelocity(value);
  2326. break;
  2327. case 'SPEED':
  2328. this.setSpeed(/** @type {number} */ (value));
  2329. break;
  2330. }
  2331. }
  2332. },
  2333. /**
  2334. * Creates an on page image element from the base 64 encoded string source.
  2335. * @param {string} resourceName Name in data object,
  2336. * @return {HTMLImageElement} The created element.
  2337. */
  2338. createImageElement(resourceName) {
  2339. const imgSrc = loadTimeData && loadTimeData.valueExists(resourceName) ?
  2340. loadTimeData.getString(resourceName) :
  2341. null;
  2342. if (imgSrc) {
  2343. const el =
  2344. /** @type {HTMLImageElement} */ (document.createElement('img'));
  2345. el.id = resourceName;
  2346. el.src = imgSrc;
  2347. document.getElementById('offline-resources').appendChild(el);
  2348. return el;
  2349. }
  2350. return null;
  2351. },
  2352. /**
  2353. * Cache the appropriate image sprite from the page and get the sprite sheet
  2354. * definition.
  2355. */
  2356. loadImages() {
  2357. let scale = '1x';
  2358. this.spriteDef = Runner.spriteDefinition.LDPI;
  2359. if (IS_HIDPI) {
  2360. scale = '2x';
  2361. this.spriteDef = Runner.spriteDefinition.HDPI;
  2362. }
  2363. Runner.imageSprite = /** @type {HTMLImageElement} */
  2364. (document.getElementById(RESOURCE_POSTFIX + scale));
  2365. if (this.gameType) {
  2366. Runner.altGameImageSprite = /** @type {HTMLImageElement} */
  2367. (this.createImageElement('altGameSpecificImage' + scale));
  2368. Runner.altCommonImageSprite = /** @type {HTMLImageElement} */
  2369. (this.createImageElement('altGameCommonImage' + scale));
  2370. }
  2371. Runner.origImageSprite = Runner.imageSprite;
  2372. // Disable the alt game mode if the sprites can't be loaded.
  2373. if (!Runner.altGameImageSprite || !Runner.altCommonImageSprite) {
  2374. Runner.isAltGameModeEnabled = () => false;
  2375. this.altGameModeActive = false;
  2376. }
  2377. if (Runner.imageSprite.complete) {
  2378. this.init();
  2379. } else {
  2380. // If the images are not yet loaded, add a listener.
  2381. Runner.imageSprite.addEventListener(Runner.events.LOAD,
  2382. this.init.bind(this));
  2383. }
  2384. },
  2385. /**
  2386. * Load and decode base 64 encoded sounds.
  2387. */
  2388. loadSounds() {
  2389. if (!IS_IOS) {
  2390. this.audioContext = new AudioContext();
  2391. const resourceTemplate =
  2392. document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content;
  2393. for (const sound in Runner.sounds) {
  2394. let soundSrc =
  2395. resourceTemplate.getElementById(Runner.sounds[sound]).src;
  2396. soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1);
  2397. const buffer = decodeBase64ToArrayBuffer(soundSrc);
  2398. // Async, so no guarantee of order in array.
  2399. this.audioContext.decodeAudioData(buffer, function(index, audioData) {
  2400. this.soundFx[index] = audioData;
  2401. }.bind(this, sound));
  2402. }
  2403. }
  2404. },
  2405. /**
  2406. * Sets the game speed. Adjust the speed accordingly if on a smaller screen.
  2407. * @param {number=} opt_speed
  2408. */
  2409. setSpeed(opt_speed) {
  2410. const speed = opt_speed || this.currentSpeed;
  2411. // Reduce the speed on smaller mobile screens.
  2412. if (this.dimensions.WIDTH < DEFAULT_WIDTH) {
  2413. const mobileSpeed = Runner.slowDown ? speed :
  2414. speed * this.dimensions.WIDTH /
  2415. DEFAULT_WIDTH * this.config.MOBILE_SPEED_COEFFICIENT;
  2416. this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed;
  2417. } else if (opt_speed) {
  2418. this.currentSpeed = opt_speed;
  2419. }
  2420. },
  2421. /**
  2422. * Game initialiser.
  2423. */
  2424. init() {
  2425. // Hide the static icon.
  2426. document.querySelector('.' + Runner.classes.ICON).style.visibility =
  2427. 'hidden';
  2428. if (this.isArcadeMode()) {
  2429. document.title =
  2430. document.title + ' - ' + getA11yString(A11Y_STRINGS.ariaLabel);
  2431. }
  2432. this.adjustDimensions();
  2433. this.setSpeed();
  2434. const ariaLabel = getA11yString(A11Y_STRINGS.ariaLabel);
  2435. this.containerEl = document.createElement('div');
  2436. this.containerEl.setAttribute('role', IS_MOBILE ? 'button' : 'application');
  2437. this.containerEl.setAttribute('tabindex', '0');
  2438. this.containerEl.setAttribute(
  2439. 'title', getA11yString(A11Y_STRINGS.description));
  2440. this.containerEl.setAttribute('aria-label', ariaLabel);
  2441. this.containerEl.className = Runner.classes.CONTAINER;
  2442. // Player canvas container.
  2443. this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH,
  2444. this.dimensions.HEIGHT);
  2445. // Live region for game status updates.
  2446. this.a11yStatusEl = document.createElement('span');
  2447. this.a11yStatusEl.className = 'offline-runner-live-region';
  2448. this.a11yStatusEl.setAttribute('aria-live', 'assertive');
  2449. this.a11yStatusEl.textContent = '';
  2450. Runner.a11yStatusEl = this.a11yStatusEl;
  2451. // Add checkbox to slow down the game.
  2452. this.slowSpeedCheckboxLabel = document.createElement('label');
  2453. this.slowSpeedCheckboxLabel.className = 'slow-speed-option hidden';
  2454. this.slowSpeedCheckboxLabel.textContent =
  2455. getA11yString(A11Y_STRINGS.speedLabel);
  2456. this.slowSpeedCheckbox = document.createElement('input');
  2457. this.slowSpeedCheckbox.setAttribute('type', 'checkbox');
  2458. this.slowSpeedCheckbox.setAttribute(
  2459. 'title', getA11yString(A11Y_STRINGS.speedLabel));
  2460. this.slowSpeedCheckbox.setAttribute('tabindex', '0');
  2461. this.slowSpeedCheckbox.setAttribute('checked', 'checked');
  2462. this.slowSpeedToggleEl = document.createElement('span');
  2463. this.slowSpeedToggleEl.className = 'slow-speed-toggle';
  2464. this.slowSpeedCheckboxLabel.appendChild(this.slowSpeedCheckbox);
  2465. this.slowSpeedCheckboxLabel.appendChild(this.slowSpeedToggleEl);
  2466. if (IS_IOS) {
  2467. this.outerContainerEl.appendChild(this.a11yStatusEl);
  2468. } else {
  2469. this.containerEl.appendChild(this.a11yStatusEl);
  2470. }
  2471. this.generatedSoundFx = new GeneratedSoundFx();
  2472. this.canvasCtx =
  2473. /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d'));
  2474. this.canvasCtx.fillStyle = '#f7f7f7';
  2475. this.canvasCtx.fill();
  2476. Runner.updateCanvasScaling(this.canvas);
  2477. // Horizon contains clouds, obstacles and the ground.
  2478. this.horizon = new Horizon(this.canvas, this.spriteDef, this.dimensions,
  2479. this.config.GAP_COEFFICIENT);
  2480. // Distance meter
  2481. this.distanceMeter = new DistanceMeter(this.canvas,
  2482. this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH);
  2483. // Draw t-rex
  2484. this.tRex = new Trex(this.canvas, this.spriteDef.TREX);
  2485. this.outerContainerEl.appendChild(this.containerEl);
  2486. this.outerContainerEl.appendChild(this.slowSpeedCheckboxLabel);
  2487. this.startListening();
  2488. this.update();
  2489. window.addEventListener(Runner.events.RESIZE,
  2490. this.debounceResize.bind(this));
  2491. // Handle dark mode
  2492. const darkModeMediaQuery =
  2493. window.matchMedia('(prefers-color-scheme: dark)');
  2494. this.isDarkMode = darkModeMediaQuery && darkModeMediaQuery.matches;
  2495. darkModeMediaQuery.addListener((e) => {
  2496. this.isDarkMode = e.matches;
  2497. });
  2498. },
  2499. /**
  2500. * Create the touch controller. A div that covers whole screen.
  2501. */
  2502. createTouchController() {
  2503. this.touchController = document.createElement('div');
  2504. this.touchController.className = Runner.classes.TOUCH_CONTROLLER;
  2505. this.touchController.addEventListener(Runner.events.TOUCHSTART, this);
  2506. this.touchController.addEventListener(Runner.events.TOUCHEND, this);
  2507. this.outerContainerEl.appendChild(this.touchController);
  2508. },
  2509. /**
  2510. * Debounce the resize event.
  2511. */
  2512. debounceResize() {
  2513. if (!this.resizeTimerId_) {
  2514. this.resizeTimerId_ =
  2515. setInterval(this.adjustDimensions.bind(this), 250);
  2516. }
  2517. },
  2518. /**
  2519. * Adjust game space dimensions on resize.
  2520. */
  2521. adjustDimensions() {
  2522. clearInterval(this.resizeTimerId_);
  2523. this.resizeTimerId_ = null;
  2524. const boxStyles = window.getComputedStyle(this.outerContainerEl);
  2525. const padding = Number(boxStyles.paddingLeft.substr(0,
  2526. boxStyles.paddingLeft.length - 2));
  2527. this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2;
  2528. if (this.isArcadeMode()) {
  2529. this.dimensions.WIDTH = Math.min(DEFAULT_WIDTH, this.dimensions.WIDTH);
  2530. if (this.activated) {
  2531. this.setArcadeModeContainerScale();
  2532. }
  2533. }
  2534. // Redraw the elements back onto the canvas.
  2535. if (this.canvas) {
  2536. this.canvas.width = this.dimensions.WIDTH;
  2537. this.canvas.height = this.dimensions.HEIGHT;
  2538. Runner.updateCanvasScaling(this.canvas);
  2539. this.distanceMeter.calcXPos(this.dimensions.WIDTH);
  2540. this.clearCanvas();
  2541. this.horizon.update(0, 0, true);
  2542. this.tRex.update(0);
  2543. // Outer container and distance meter.
  2544. if (this.playing || this.crashed || this.paused) {
  2545. this.containerEl.style.width = this.dimensions.WIDTH + 'px';
  2546. this.containerEl.style.height = this.dimensions.HEIGHT + 'px';
  2547. this.distanceMeter.update(0, Math.ceil(this.distanceRan));
  2548. this.stop();
  2549. } else {
  2550. this.tRex.draw(0, 0);
  2551. }
  2552. // Game over panel.
  2553. if (this.crashed && this.gameOverPanel) {
  2554. this.gameOverPanel.updateDimensions(this.dimensions.WIDTH);
  2555. this.gameOverPanel.draw(this.altGameModeActive, this.tRex);
  2556. }
  2557. }
  2558. },
  2559. /**
  2560. * Play the game intro.
  2561. * Canvas container width expands out to the full width.
  2562. */
  2563. playIntro() {
  2564. if (!this.activated && !this.crashed) {
  2565. this.playingIntro = true;
  2566. this.tRex.playingIntro = true;
  2567. // CSS animation definition.
  2568. const keyframes = '@-webkit-keyframes intro { ' +
  2569. 'from { width:' + Trex.config.WIDTH + 'px }' +
  2570. 'to { width: ' + this.dimensions.WIDTH + 'px }' +
  2571. '}';
  2572. document.styleSheets[0].insertRule(keyframes, 0);
  2573. this.containerEl.addEventListener(Runner.events.ANIM_END,
  2574. this.startGame.bind(this));
  2575. this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';
  2576. this.containerEl.style.width = this.dimensions.WIDTH + 'px';
  2577. this.setPlayStatus(true);
  2578. this.activated = true;
  2579. } else if (this.crashed) {
  2580. this.restart();
  2581. }
  2582. },
  2583. /**
  2584. * Update the game status to started.
  2585. */
  2586. startGame() {
  2587. if (this.isArcadeMode()) {
  2588. this.setArcadeMode();
  2589. }
  2590. this.toggleSpeed();
  2591. this.runningTime = 0;
  2592. this.playingIntro = false;
  2593. this.tRex.playingIntro = false;
  2594. this.containerEl.style.webkitAnimation = '';
  2595. this.playCount++;
  2596. this.generatedSoundFx.background();
  2597. if (Runner.audioCues) {
  2598. this.containerEl.setAttribute('title', getA11yString(A11Y_STRINGS.jump));
  2599. }
  2600. // Handle tabbing off the page. Pause the current game.
  2601. document.addEventListener(Runner.events.VISIBILITY,
  2602. this.onVisibilityChange.bind(this));
  2603. window.addEventListener(Runner.events.BLUR,
  2604. this.onVisibilityChange.bind(this));
  2605. window.addEventListener(Runner.events.FOCUS,
  2606. this.onVisibilityChange.bind(this));
  2607. },
  2608. clearCanvas() {
  2609. this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH,
  2610. this.dimensions.HEIGHT);
  2611. },
  2612. /**
  2613. * Checks whether the canvas area is in the viewport of the browser
  2614. * through the current scroll position.
  2615. * @return boolean.
  2616. */
  2617. isCanvasInView() {
  2618. return this.containerEl.getBoundingClientRect().top >
  2619. Runner.config.CANVAS_IN_VIEW_OFFSET;
  2620. },
  2621. /**
  2622. * Enable the alt game mode. Switching out the sprites.
  2623. */
  2624. enableAltGameMode() {
  2625. Runner.imageSprite = Runner.altGameImageSprite;
  2626. Runner.spriteDefinition = spriteDefinitionByType[Runner.gameType];
  2627. if (IS_HIDPI) {
  2628. this.spriteDef = Runner.spriteDefinition.HDPI;
  2629. } else {
  2630. this.spriteDef = Runner.spriteDefinition.LDPI;
  2631. }
  2632. this.altGameModeActive = true;
  2633. this.tRex.enableAltGameMode(this.spriteDef.TREX);
  2634. this.horizon.enableAltGameMode(this.spriteDef);
  2635. this.generatedSoundFx.background();
  2636. },
  2637. /**
  2638. * Update the game frame and schedules the next one.
  2639. */
  2640. update() {
  2641. this.updatePending = false;
  2642. const now = getTimeStamp();
  2643. let deltaTime = now - (this.time || now);
  2644. // Flashing when switching game modes.
  2645. if (this.altGameModeFlashTimer < 0 || this.altGameModeFlashTimer === 0) {
  2646. this.altGameModeFlashTimer = null;
  2647. this.tRex.setFlashing(false);
  2648. this.enableAltGameMode();
  2649. } else if (this.altGameModeFlashTimer > 0) {
  2650. this.altGameModeFlashTimer -= deltaTime;
  2651. this.tRex.update(deltaTime);
  2652. deltaTime = 0;
  2653. }
  2654. this.time = now;
  2655. if (this.playing) {
  2656. this.clearCanvas();
  2657. // Additional fade in - Prevents jump when switching sprites
  2658. if (this.altGameModeActive &&
  2659. this.fadeInTimer <= this.config.FADE_DURATION) {
  2660. this.fadeInTimer += deltaTime / 1000;
  2661. this.canvasCtx.globalAlpha = this.fadeInTimer;
  2662. } else {
  2663. this.canvasCtx.globalAlpha = 1;
  2664. }
  2665. if (this.tRex.jumping) {
  2666. this.tRex.updateJump(deltaTime);
  2667. }
  2668. this.runningTime += deltaTime;
  2669. const hasObstacles = this.runningTime > this.config.CLEAR_TIME;
  2670. // First jump triggers the intro.
  2671. if (this.tRex.jumpCount === 1 && !this.playingIntro) {
  2672. this.playIntro();
  2673. }
  2674. // The horizon doesn't move until the intro is over.
  2675. if (this.playingIntro) {
  2676. this.horizon.update(0, this.currentSpeed, hasObstacles);
  2677. } else if (!this.crashed) {
  2678. const showNightMode = this.isDarkMode ^ this.inverted;
  2679. deltaTime = !this.activated ? 0 : deltaTime;
  2680. this.horizon.update(
  2681. deltaTime, this.currentSpeed, hasObstacles, showNightMode);
  2682. }
  2683. // Check for collisions.
  2684. let collision = hasObstacles &&
  2685. checkForCollision(this.horizon.obstacles[0], this.tRex);
  2686. // For a11y, audio cues.
  2687. if (Runner.audioCues && hasObstacles) {
  2688. const jumpObstacle =
  2689. this.horizon.obstacles[0].typeConfig.type != 'COLLECTABLE';
  2690. if (!this.horizon.obstacles[0].jumpAlerted) {
  2691. const threshold = Runner.isMobileMouseInput ?
  2692. Runner.config.AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y :
  2693. Runner.config.AUDIOCUE_PROXIMITY_THRESHOLD;
  2694. const adjProximityThreshold = threshold +
  2695. (threshold * Math.log10(this.currentSpeed / Runner.config.SPEED));
  2696. if (this.horizon.obstacles[0].xPos < adjProximityThreshold) {
  2697. if (jumpObstacle) {
  2698. this.generatedSoundFx.jump();
  2699. }
  2700. this.horizon.obstacles[0].jumpAlerted = true;
  2701. }
  2702. }
  2703. }
  2704. // Activated alt game mode.
  2705. if (Runner.isAltGameModeEnabled() && collision &&
  2706. this.horizon.obstacles[0].typeConfig.type == 'COLLECTABLE') {
  2707. this.horizon.removeFirstObstacle();
  2708. this.tRex.setFlashing(true);
  2709. collision = false;
  2710. this.altGameModeFlashTimer = this.config.FLASH_DURATION;
  2711. this.runningTime = 0;
  2712. this.generatedSoundFx.collect();
  2713. }
  2714. if (!collision) {
  2715. this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;
  2716. if (this.currentSpeed < this.config.MAX_SPEED) {
  2717. this.currentSpeed += this.config.ACCELERATION;
  2718. }
  2719. } else {
  2720. this.gameOver();
  2721. }
  2722. const playAchievementSound = this.distanceMeter.update(deltaTime,
  2723. Math.ceil(this.distanceRan));
  2724. if (!Runner.audioCues && playAchievementSound) {
  2725. this.playSound(this.soundFx.SCORE);
  2726. }
  2727. // Night mode.
  2728. if (!Runner.isAltGameModeEnabled()) {
  2729. if (this.invertTimer > this.config.INVERT_FADE_DURATION) {
  2730. this.invertTimer = 0;
  2731. this.invertTrigger = false;
  2732. this.invert(false);
  2733. } else if (this.invertTimer) {
  2734. this.invertTimer += deltaTime;
  2735. } else {
  2736. const actualDistance =
  2737. this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan));
  2738. if (actualDistance > 0) {
  2739. this.invertTrigger =
  2740. !(actualDistance % this.config.INVERT_DISTANCE);
  2741. if (this.invertTrigger && this.invertTimer === 0) {
  2742. this.invertTimer += deltaTime;
  2743. this.invert(false);
  2744. }
  2745. }
  2746. }
  2747. }
  2748. }
  2749. if (this.playing || (!this.activated &&
  2750. this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) {
  2751. this.tRex.update(deltaTime);
  2752. this.scheduleNextUpdate();
  2753. }
  2754. },
  2755. /**
  2756. * Event handler.
  2757. * @param {Event} e
  2758. */
  2759. handleEvent(e) {
  2760. return (function(evtType, events) {
  2761. switch (evtType) {
  2762. case events.KEYDOWN:
  2763. case events.TOUCHSTART:
  2764. case events.POINTERDOWN:
  2765. this.onKeyDown(e);
  2766. break;
  2767. case events.KEYUP:
  2768. case events.TOUCHEND:
  2769. case events.POINTERUP:
  2770. this.onKeyUp(e);
  2771. break;
  2772. case events.GAMEPADCONNECTED:
  2773. this.onGamepadConnected(e);
  2774. break;
  2775. }
  2776. }.bind(this))(e.type, Runner.events);
  2777. },
  2778. /**
  2779. * Initialize audio cues if activated by focus on the canvas element.
  2780. * @param {Event} e
  2781. */
  2782. handleCanvasKeyPress(e) {
  2783. if (!this.activated && !Runner.audioCues) {
  2784. this.toggleSpeed();
  2785. Runner.audioCues = true;
  2786. this.generatedSoundFx.init();
  2787. Runner.generatedSoundFx = this.generatedSoundFx;
  2788. Runner.config.CLEAR_TIME *= 1.2;
  2789. } else if (e.keyCode && Runner.keycodes.JUMP[e.keyCode]) {
  2790. this.onKeyDown(e);
  2791. }
  2792. },
  2793. /**
  2794. * Prevent space key press from scrolling.
  2795. * @param {Event} e
  2796. */
  2797. preventScrolling(e) {
  2798. if (e.keyCode === 32) {
  2799. e.preventDefault();
  2800. }
  2801. },
  2802. /**
  2803. * Toggle speed setting if toggle is shown.
  2804. */
  2805. toggleSpeed() {
  2806. if (Runner.audioCues) {
  2807. const speedChange = Runner.slowDown != this.slowSpeedCheckbox.checked;
  2808. if (speedChange) {
  2809. Runner.slowDown = this.slowSpeedCheckbox.checked;
  2810. const updatedConfig =
  2811. Runner.slowDown ? Runner.slowConfig : Runner.normalConfig;
  2812. Runner.config = Object.assign(Runner.config, updatedConfig);
  2813. this.currentSpeed = updatedConfig.SPEED;
  2814. this.tRex.enableSlowConfig();
  2815. this.horizon.adjustObstacleSpeed();
  2816. }
  2817. if (this.playing) {
  2818. this.disableSpeedToggle(true);
  2819. }
  2820. }
  2821. },
  2822. /**
  2823. * Show the speed toggle.
  2824. * From focus event or when audio cues are activated.
  2825. * @param {Event=} e
  2826. */
  2827. showSpeedToggle(e) {
  2828. const isFocusEvent = e && e.type == 'focus';
  2829. if (Runner.audioCues || isFocusEvent) {
  2830. this.slowSpeedCheckboxLabel.classList.toggle(
  2831. HIDDEN_CLASS, isFocusEvent ? false : !this.crashed);
  2832. }
  2833. },
  2834. /**
  2835. * Disable the speed toggle.
  2836. * @param {boolean} disable
  2837. */
  2838. disableSpeedToggle(disable) {
  2839. if (disable) {
  2840. this.slowSpeedCheckbox.setAttribute('disabled', 'disabled');
  2841. } else {
  2842. this.slowSpeedCheckbox.removeAttribute('disabled');
  2843. }
  2844. },
  2845. /**
  2846. * Bind relevant key / mouse / touch listeners.
  2847. */
  2848. startListening() {
  2849. // A11y keyboard / screen reader activation.
  2850. this.containerEl.addEventListener(
  2851. Runner.events.KEYDOWN, this.handleCanvasKeyPress.bind(this));
  2852. if (!IS_MOBILE) {
  2853. this.containerEl.addEventListener(
  2854. Runner.events.FOCUS, this.showSpeedToggle.bind(this));
  2855. }
  2856. this.canvas.addEventListener(
  2857. Runner.events.KEYDOWN, this.preventScrolling.bind(this));
  2858. this.canvas.addEventListener(
  2859. Runner.events.KEYUP, this.preventScrolling.bind(this));
  2860. // Keys.
  2861. document.addEventListener(Runner.events.KEYDOWN, this);
  2862. document.addEventListener(Runner.events.KEYUP, this);
  2863. // Touch / pointer.
  2864. this.containerEl.addEventListener(Runner.events.TOUCHSTART, this);
  2865. document.addEventListener(Runner.events.POINTERDOWN, this);
  2866. document.addEventListener(Runner.events.POINTERUP, this);
  2867. if (this.isArcadeMode()) {
  2868. // Gamepad
  2869. window.addEventListener(Runner.events.GAMEPADCONNECTED, this);
  2870. }
  2871. },
  2872. /**
  2873. * Remove all listeners.
  2874. */
  2875. stopListening() {
  2876. document.removeEventListener(Runner.events.KEYDOWN, this);
  2877. document.removeEventListener(Runner.events.KEYUP, this);
  2878. if (this.touchController) {
  2879. this.touchController.removeEventListener(Runner.events.TOUCHSTART, this);
  2880. this.touchController.removeEventListener(Runner.events.TOUCHEND, this);
  2881. }
  2882. this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this);
  2883. document.removeEventListener(Runner.events.POINTERDOWN, this);
  2884. document.removeEventListener(Runner.events.POINTERUP, this);
  2885. if (this.isArcadeMode()) {
  2886. window.removeEventListener(Runner.events.GAMEPADCONNECTED, this);
  2887. }
  2888. },
  2889. /**
  2890. * Process keydown.
  2891. * @param {Event} e
  2892. */
  2893. onKeyDown(e) {
  2894. // Prevent native page scrolling whilst tapping on mobile.
  2895. if (IS_MOBILE && this.playing) {
  2896. e.preventDefault();
  2897. }
  2898. if (this.isCanvasInView()) {
  2899. // Allow toggling of speed toggle.
  2900. if (Runner.keycodes.JUMP[e.keyCode] &&
  2901. e.target == this.slowSpeedCheckbox) {
  2902. return;
  2903. }
  2904. if (!this.crashed && !this.paused) {
  2905. // For a11y, screen reader activation.
  2906. const isMobileMouseInput = IS_MOBILE &&
  2907. e.type === Runner.events.POINTERDOWN && e.pointerType == 'mouse' &&
  2908. (e.target == this.containerEl ||
  2909. (IS_IOS &&
  2910. (e.target == this.touchController || e.target == this.canvas)));
  2911. if (Runner.keycodes.JUMP[e.keyCode] ||
  2912. e.type === Runner.events.TOUCHSTART || isMobileMouseInput) {
  2913. e.preventDefault();
  2914. // Starting the game for the first time.
  2915. if (!this.playing) {
  2916. // Started by touch so create a touch controller.
  2917. if (!this.touchController && e.type === Runner.events.TOUCHSTART) {
  2918. this.createTouchController();
  2919. }
  2920. if (isMobileMouseInput) {
  2921. this.handleCanvasKeyPress(e);
  2922. }
  2923. this.loadSounds();
  2924. this.setPlayStatus(true);
  2925. this.update();
  2926. if (window.errorPageController) {
  2927. errorPageController.trackEasterEgg();
  2928. }
  2929. }
  2930. // Start jump.
  2931. if (!this.tRex.jumping && !this.tRex.ducking) {
  2932. if (Runner.audioCues) {
  2933. this.generatedSoundFx.cancelFootSteps();
  2934. } else {
  2935. this.playSound(this.soundFx.BUTTON_PRESS);
  2936. }
  2937. this.tRex.startJump(this.currentSpeed);
  2938. }
  2939. } else if (this.playing && Runner.keycodes.DUCK[e.keyCode]) {
  2940. e.preventDefault();
  2941. if (this.tRex.jumping) {
  2942. // Speed drop, activated only when jump key is not pressed.
  2943. this.tRex.setSpeedDrop();
  2944. } else if (!this.tRex.jumping && !this.tRex.ducking) {
  2945. // Duck.
  2946. this.tRex.setDuck(true);
  2947. }
  2948. }
  2949. }
  2950. }
  2951. },
  2952. /**
  2953. * Process key up.
  2954. * @param {Event} e
  2955. */
  2956. onKeyUp(e) {
  2957. const keyCode = String(e.keyCode);
  2958. const isjumpKey = Runner.keycodes.JUMP[keyCode] ||
  2959. e.type === Runner.events.TOUCHEND || e.type === Runner.events.POINTERUP;
  2960. if (this.isRunning() && isjumpKey) {
  2961. this.tRex.endJump();
  2962. } else if (Runner.keycodes.DUCK[keyCode]) {
  2963. this.tRex.speedDrop = false;
  2964. this.tRex.setDuck(false);
  2965. } else if (this.crashed) {
  2966. // Check that enough time has elapsed before allowing jump key to restart.
  2967. const deltaTime = getTimeStamp() - this.time;
  2968. if (this.isCanvasInView() &&
  2969. (Runner.keycodes.RESTART[keyCode] || this.isLeftClickOnCanvas(e) ||
  2970. (deltaTime >= this.config.GAMEOVER_CLEAR_TIME &&
  2971. Runner.keycodes.JUMP[keyCode]))) {
  2972. this.handleGameOverClicks(e);
  2973. }
  2974. } else if (this.paused && isjumpKey) {
  2975. // Reset the jump state
  2976. this.tRex.reset();
  2977. this.play();
  2978. }
  2979. },
  2980. /**
  2981. * Process gamepad connected event.
  2982. * @param {Event} e
  2983. */
  2984. onGamepadConnected(e) {
  2985. if (!this.pollingGamepads) {
  2986. this.pollGamepadState();
  2987. }
  2988. },
  2989. /**
  2990. * rAF loop for gamepad polling.
  2991. */
  2992. pollGamepadState() {
  2993. const gamepads = navigator.getGamepads();
  2994. this.pollActiveGamepad(gamepads);
  2995. this.pollingGamepads = true;
  2996. requestAnimationFrame(this.pollGamepadState.bind(this));
  2997. },
  2998. /**
  2999. * Polls for a gamepad with the jump button pressed. If one is found this
  3000. * becomes the "active" gamepad and all others are ignored.
  3001. * @param {!Array<Gamepad>} gamepads
  3002. */
  3003. pollForActiveGamepad(gamepads) {
  3004. for (let i = 0; i < gamepads.length; ++i) {
  3005. if (gamepads[i] && gamepads[i].buttons.length > 0 &&
  3006. gamepads[i].buttons[0].pressed) {
  3007. this.gamepadIndex = i;
  3008. this.pollActiveGamepad(gamepads);
  3009. return;
  3010. }
  3011. }
  3012. },
  3013. /**
  3014. * Polls the chosen gamepad for button presses and generates KeyboardEvents
  3015. * to integrate with the rest of the game logic.
  3016. * @param {!Array<Gamepad>} gamepads
  3017. */
  3018. pollActiveGamepad(gamepads) {
  3019. if (this.gamepadIndex === undefined) {
  3020. this.pollForActiveGamepad(gamepads);
  3021. return;
  3022. }
  3023. const gamepad = gamepads[this.gamepadIndex];
  3024. if (!gamepad) {
  3025. this.gamepadIndex = undefined;
  3026. this.pollForActiveGamepad(gamepads);
  3027. return;
  3028. }
  3029. // The gamepad specification defines the typical mapping of physical buttons
  3030. // to button indicies: https://w3c.github.io/gamepad/#remapping
  3031. this.pollGamepadButton(gamepad, 0, 38); // Jump
  3032. if (gamepad.buttons.length >= 2) {
  3033. this.pollGamepadButton(gamepad, 1, 40); // Duck
  3034. }
  3035. if (gamepad.buttons.length >= 10) {
  3036. this.pollGamepadButton(gamepad, 9, 13); // Restart
  3037. }
  3038. this.previousGamepad = gamepad;
  3039. },
  3040. /**
  3041. * Generates a key event based on a gamepad button.
  3042. * @param {!Gamepad} gamepad
  3043. * @param {number} buttonIndex
  3044. * @param {number} keyCode
  3045. */
  3046. pollGamepadButton(gamepad, buttonIndex, keyCode) {
  3047. const state = gamepad.buttons[buttonIndex].pressed;
  3048. let previousState = false;
  3049. if (this.previousGamepad) {
  3050. previousState = this.previousGamepad.buttons[buttonIndex].pressed;
  3051. }
  3052. // Generate key events on the rising and falling edge of a button press.
  3053. if (state !== previousState) {
  3054. const e = new KeyboardEvent(state ? Runner.events.KEYDOWN
  3055. : Runner.events.KEYUP,
  3056. { keyCode: keyCode });
  3057. document.dispatchEvent(e);
  3058. }
  3059. },
  3060. /**
  3061. * Handle interactions on the game over screen state.
  3062. * A user is able to tap the high score twice to reset it.
  3063. * @param {Event} e
  3064. */
  3065. handleGameOverClicks(e) {
  3066. if (e.target != this.slowSpeedCheckbox) {
  3067. e.preventDefault();
  3068. if (this.distanceMeter.hasClickedOnHighScore(e) && this.highestScore) {
  3069. if (this.distanceMeter.isHighScoreFlashing()) {
  3070. // Subsequent click, reset the high score.
  3071. this.saveHighScore(0, true);
  3072. this.distanceMeter.resetHighScore();
  3073. } else {
  3074. // First click, flash the high score.
  3075. this.distanceMeter.startHighScoreFlashing();
  3076. }
  3077. } else {
  3078. this.distanceMeter.cancelHighScoreFlashing();
  3079. this.restart();
  3080. }
  3081. }
  3082. },
  3083. /**
  3084. * Returns whether the event was a left click on canvas.
  3085. * On Windows right click is registered as a click.
  3086. * @param {Event} e
  3087. * @return {boolean}
  3088. */
  3089. isLeftClickOnCanvas(e) {
  3090. return e.button != null && e.button < 2 &&
  3091. e.type === Runner.events.POINTERUP &&
  3092. (e.target === this.canvas ||
  3093. (IS_MOBILE && Runner.audioCues && e.target === this.containerEl));
  3094. },
  3095. /**
  3096. * RequestAnimationFrame wrapper.
  3097. */
  3098. scheduleNextUpdate() {
  3099. if (!this.updatePending) {
  3100. this.updatePending = true;
  3101. this.raqId = requestAnimationFrame(this.update.bind(this));
  3102. }
  3103. },
  3104. /**
  3105. * Whether the game is running.
  3106. * @return {boolean}
  3107. */
  3108. isRunning() {
  3109. return !!this.raqId;
  3110. },
  3111. /**
  3112. * Set the initial high score as stored in the user's profile.
  3113. * @param {number} highScore
  3114. */
  3115. initializeHighScore(highScore) {
  3116. this.syncHighestScore = true;
  3117. highScore = Math.ceil(highScore);
  3118. if (highScore < this.highestScore) {
  3119. if (window.errorPageController) {
  3120. errorPageController.updateEasterEggHighScore(this.highestScore);
  3121. }
  3122. return;
  3123. }
  3124. this.highestScore = highScore;
  3125. this.distanceMeter.setHighScore(this.highestScore);
  3126. },
  3127. /**
  3128. * Sets the current high score and saves to the profile if available.
  3129. * @param {number} distanceRan Total distance ran.
  3130. * @param {boolean=} opt_resetScore Whether to reset the score.
  3131. */
  3132. saveHighScore(distanceRan, opt_resetScore) {
  3133. this.highestScore = Math.ceil(distanceRan);
  3134. this.distanceMeter.setHighScore(this.highestScore);
  3135. // Store the new high score in the profile.
  3136. if (this.syncHighestScore && window.errorPageController) {
  3137. if (opt_resetScore) {
  3138. errorPageController.resetEasterEggHighScore();
  3139. } else {
  3140. errorPageController.updateEasterEggHighScore(this.highestScore);
  3141. }
  3142. }
  3143. },
  3144. /**
  3145. * Game over state.
  3146. */
  3147. gameOver() {
  3148. this.playSound(this.soundFx.HIT);
  3149. vibrate(200);
  3150. this.stop();
  3151. this.crashed = true;
  3152. this.distanceMeter.achievement = false;
  3153. this.tRex.update(100, Trex.status.CRASHED);
  3154. // Game over panel.
  3155. if (!this.gameOverPanel) {
  3156. const origSpriteDef = IS_HIDPI ? spriteDefinitionByType.original.HDPI :
  3157. spriteDefinitionByType.original.LDPI;
  3158. if (this.canvas) {
  3159. if (Runner.isAltGameModeEnabled) {
  3160. this.gameOverPanel = new GameOverPanel(
  3161. this.canvas, origSpriteDef.TEXT_SPRITE, origSpriteDef.RESTART,
  3162. this.dimensions, origSpriteDef.ALT_GAME_END,
  3163. this.altGameModeActive);
  3164. } else {
  3165. this.gameOverPanel = new GameOverPanel(
  3166. this.canvas, origSpriteDef.TEXT_SPRITE, origSpriteDef.RESTART,
  3167. this.dimensions);
  3168. }
  3169. }
  3170. }
  3171. this.gameOverPanel.draw(this.altGameModeActive, this.tRex);
  3172. // Update the high score.
  3173. if (this.distanceRan > this.highestScore) {
  3174. this.saveHighScore(this.distanceRan);
  3175. }
  3176. // Reset the time clock.
  3177. this.time = getTimeStamp();
  3178. if (Runner.audioCues) {
  3179. this.generatedSoundFx.stopAll();
  3180. announcePhrase(
  3181. getA11yString(A11Y_STRINGS.gameOver)
  3182. .replace(
  3183. '$1',
  3184. this.distanceMeter.getActualDistance(this.distanceRan)
  3185. .toString()) +
  3186. ' ' +
  3187. getA11yString(A11Y_STRINGS.highScore)
  3188. .replace(
  3189. '$1',
  3190. this.distanceMeter.getActualDistance(this.highestScore)
  3191. .toString()));
  3192. this.containerEl.setAttribute(
  3193. 'title', getA11yString(A11Y_STRINGS.ariaLabel));
  3194. }
  3195. this.showSpeedToggle();
  3196. this.disableSpeedToggle(false);
  3197. },
  3198. stop() {
  3199. this.setPlayStatus(false);
  3200. this.paused = true;
  3201. cancelAnimationFrame(this.raqId);
  3202. this.raqId = 0;
  3203. this.generatedSoundFx.stopAll();
  3204. },
  3205. play() {
  3206. if (!this.crashed) {
  3207. this.setPlayStatus(true);
  3208. this.paused = false;
  3209. this.tRex.update(0, Trex.status.RUNNING);
  3210. this.time = getTimeStamp();
  3211. this.update();
  3212. this.generatedSoundFx.background();
  3213. }
  3214. },
  3215. restart() {
  3216. if (!this.raqId) {
  3217. this.playCount++;
  3218. this.runningTime = 0;
  3219. this.setPlayStatus(true);
  3220. this.toggleSpeed();
  3221. this.paused = false;
  3222. this.crashed = false;
  3223. this.distanceRan = 0;
  3224. this.setSpeed(this.config.SPEED);
  3225. this.time = getTimeStamp();
  3226. this.containerEl.classList.remove(Runner.classes.CRASHED);
  3227. this.clearCanvas();
  3228. this.distanceMeter.reset();
  3229. this.horizon.reset();
  3230. this.tRex.reset();
  3231. this.playSound(this.soundFx.BUTTON_PRESS);
  3232. this.invert(true);
  3233. this.flashTimer = null;
  3234. this.update();
  3235. this.gameOverPanel.reset();
  3236. this.generatedSoundFx.background();
  3237. this.containerEl.setAttribute('title', getA11yString(A11Y_STRINGS.jump));
  3238. announcePhrase(getA11yString(A11Y_STRINGS.started));
  3239. }
  3240. },
  3241. setPlayStatus(isPlaying) {
  3242. if (this.touchController) {
  3243. this.touchController.classList.toggle(HIDDEN_CLASS, !isPlaying);
  3244. }
  3245. this.playing = isPlaying;
  3246. },
  3247. /**
  3248. * Whether the game should go into arcade mode.
  3249. * @return {boolean}
  3250. */
  3251. isArcadeMode() {
  3252. // In RTL languages the title is wrapped with the left to right mark
  3253. // control characters &#x202A; and &#x202C but are invisible.
  3254. return IS_RTL ? document.title.indexOf(ARCADE_MODE_URL) == 1 :
  3255. document.title === ARCADE_MODE_URL;
  3256. },
  3257. /**
  3258. * Hides offline messaging for a fullscreen game only experience.
  3259. */
  3260. setArcadeMode() {
  3261. document.body.classList.add(Runner.classes.ARCADE_MODE);
  3262. this.setArcadeModeContainerScale();
  3263. },
  3264. /**
  3265. * Sets the scaling for arcade mode.
  3266. */
  3267. setArcadeModeContainerScale() {
  3268. const windowHeight = window.innerHeight;
  3269. const scaleHeight = windowHeight / this.dimensions.HEIGHT;
  3270. const scaleWidth = window.innerWidth / this.dimensions.WIDTH;
  3271. const scale = Math.max(1, Math.min(scaleHeight, scaleWidth));
  3272. const scaledCanvasHeight = this.dimensions.HEIGHT * scale;
  3273. // Positions the game container at 10% of the available vertical window
  3274. // height minus the game container height.
  3275. const translateY = Math.ceil(Math.max(0, (windowHeight - scaledCanvasHeight -
  3276. Runner.config.ARCADE_MODE_INITIAL_TOP_POSITION) *
  3277. Runner.config.ARCADE_MODE_TOP_POSITION_PERCENT)) *
  3278. window.devicePixelRatio;
  3279. const cssScale = IS_RTL ? -scale + ',' + scale : scale;
  3280. this.containerEl.style.transform =
  3281. 'scale(' + cssScale + ') translateY(' + translateY + 'px)';
  3282. },
  3283. /**
  3284. * Pause the game if the tab is not in focus.
  3285. */
  3286. onVisibilityChange(e) {
  3287. if (document.hidden || document.webkitHidden || e.type === 'blur' ||
  3288. document.visibilityState !== 'visible') {
  3289. this.stop();
  3290. } else if (!this.crashed) {
  3291. this.tRex.reset();
  3292. this.play();
  3293. }
  3294. },
  3295. /**
  3296. * Play a sound.
  3297. * @param {AudioBuffer} soundBuffer
  3298. */
  3299. playSound(soundBuffer) {
  3300. if (soundBuffer) {
  3301. const sourceNode = this.audioContext.createBufferSource();
  3302. sourceNode.buffer = soundBuffer;
  3303. sourceNode.connect(this.audioContext.destination);
  3304. sourceNode.start(0);
  3305. }
  3306. },
  3307. /**
  3308. * Inverts the current page / canvas colors.
  3309. * @param {boolean} reset Whether to reset colors.
  3310. */
  3311. invert(reset) {
  3312. const htmlEl = document.firstElementChild;
  3313. if (reset) {
  3314. htmlEl.classList.toggle(Runner.classes.INVERTED,
  3315. false);
  3316. this.invertTimer = 0;
  3317. this.inverted = false;
  3318. } else {
  3319. this.inverted = htmlEl.classList.toggle(
  3320. Runner.classes.INVERTED, this.invertTrigger);
  3321. }
  3322. },
  3323. };
  3324. /**
  3325. * Updates the canvas size taking into
  3326. * account the backing store pixel ratio and
  3327. * the device pixel ratio.
  3328. *
  3329. * See article by Paul Lewis:
  3330. * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
  3331. *
  3332. * @param {HTMLCanvasElement} canvas
  3333. * @param {number=} opt_width
  3334. * @param {number=} opt_height
  3335. * @return {boolean} Whether the canvas was scaled.
  3336. */
  3337. Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) {
  3338. const context =
  3339. /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
  3340. // Query the various pixel ratios
  3341. const devicePixelRatio = Math.floor(window.devicePixelRatio) || 1;
  3342. /** @suppress {missingProperties} */
  3343. const backingStoreRatio =
  3344. Math.floor(context.webkitBackingStorePixelRatio) || 1;
  3345. const ratio = devicePixelRatio / backingStoreRatio;
  3346. // Upscale the canvas if the two ratios don't match
  3347. if (devicePixelRatio !== backingStoreRatio) {
  3348. const oldWidth = opt_width || canvas.width;
  3349. const oldHeight = opt_height || canvas.height;
  3350. canvas.width = oldWidth * ratio;
  3351. canvas.height = oldHeight * ratio;
  3352. canvas.style.width = oldWidth + 'px';
  3353. canvas.style.height = oldHeight + 'px';
  3354. // Scale the context to counter the fact that we've manually scaled
  3355. // our canvas element.
  3356. context.scale(ratio, ratio);
  3357. return true;
  3358. } else if (devicePixelRatio === 1) {
  3359. // Reset the canvas width / height. Fixes scaling bug when the page is
  3360. // zoomed and the devicePixelRatio changes accordingly.
  3361. canvas.style.width = canvas.width + 'px';
  3362. canvas.style.height = canvas.height + 'px';
  3363. }
  3364. return false;
  3365. };
  3366. /**
  3367. * Whether events are enabled.
  3368. * @return {boolean}
  3369. */
  3370. Runner.isAltGameModeEnabled = function() {
  3371. return loadTimeData && loadTimeData.valueExists('enableAltGameMode');
  3372. };
  3373. /**
  3374. * Generated sound FX class for audio cues.
  3375. * @constructor
  3376. */
  3377. function GeneratedSoundFx() {
  3378. this.audioCues = false;
  3379. this.context = null;
  3380. this.panner = null;
  3381. }
  3382. GeneratedSoundFx.prototype = {
  3383. init() {
  3384. this.audioCues = true;
  3385. if (!this.context) {
  3386. // iOS only supports the webkit version.
  3387. this.context = window.webkitAudioContext ? new webkitAudioContext() :
  3388. new AudioContext();
  3389. if (IS_IOS) {
  3390. this.context.onstatechange = (function() {
  3391. if (this.context.state != 'running') {
  3392. this.context.resume();
  3393. }
  3394. }).bind(this);
  3395. this.context.resume();
  3396. }
  3397. this.panner = this.context.createStereoPanner ?
  3398. this.context.createStereoPanner() :
  3399. null;
  3400. }
  3401. },
  3402. stopAll() {
  3403. this.cancelFootSteps();
  3404. },
  3405. /**
  3406. * Play oscillators at certain frequency and for a certain time.
  3407. * @param {number} frequency
  3408. * @param {number} startTime
  3409. * @param {number} duration
  3410. * @param {?number=} opt_vol
  3411. * @param {number=} opt_pan
  3412. */
  3413. playNote(frequency, startTime, duration, opt_vol, opt_pan) {
  3414. const osc1 = this.context.createOscillator();
  3415. const osc2 = this.context.createOscillator();
  3416. const volume = this.context.createGain();
  3417. // Set oscillator wave type
  3418. osc1.type = 'triangle';
  3419. osc2.type = 'triangle';
  3420. volume.gain.value = 0.1;
  3421. // Set up node routing
  3422. if (this.panner) {
  3423. this.panner.pan.value = opt_pan || 0;
  3424. osc1.connect(volume).connect(this.panner);
  3425. osc2.connect(volume).connect(this.panner);
  3426. this.panner.connect(this.context.destination);
  3427. } else {
  3428. osc1.connect(volume);
  3429. osc2.connect(volume);
  3430. volume.connect(this.context.destination);
  3431. }
  3432. // Detune oscillators for chorus effect
  3433. osc1.frequency.value = frequency + 1;
  3434. osc2.frequency.value = frequency - 2;
  3435. // Fade out
  3436. volume.gain.setValueAtTime(opt_vol || 0.01, startTime + duration - 0.05);
  3437. volume.gain.linearRampToValueAtTime(0.00001, startTime + duration);
  3438. // Start oscillators
  3439. osc1.start(startTime);
  3440. osc2.start(startTime);
  3441. // Stop oscillators
  3442. osc1.stop(startTime + duration);
  3443. osc2.stop(startTime + duration);
  3444. },
  3445. background() {
  3446. if (this.audioCues) {
  3447. const now = this.context.currentTime;
  3448. this.playNote(493.883, now, 0.116);
  3449. this.playNote(659.255, now + 0.116, 0.232);
  3450. this.loopFootSteps();
  3451. }
  3452. },
  3453. loopFootSteps() {
  3454. if (this.audioCues && !this.bgSoundIntervalId) {
  3455. this.bgSoundIntervalId = setInterval(function() {
  3456. this.playNote(73.42, this.context.currentTime, 0.05, 0.16);
  3457. this.playNote(69.30, this.context.currentTime + 0.116, 0.116, 0.16);
  3458. }.bind(this), 280);
  3459. }
  3460. },
  3461. cancelFootSteps() {
  3462. if (this.audioCues && this.bgSoundIntervalId) {
  3463. clearInterval(this.bgSoundIntervalId);
  3464. this.bgSoundIntervalId = null;
  3465. this.playNote(103.83, this.context.currentTime, 0.232, 0.02);
  3466. this.playNote(116.54, this.context.currentTime + 0.116, 0.232, 0.02);
  3467. }
  3468. },
  3469. collect() {
  3470. if (this.audioCues) {
  3471. this.cancelFootSteps();
  3472. const now = this.context.currentTime;
  3473. this.playNote(830.61, now, 0.116);
  3474. this.playNote(1318.51, now + 0.116, 0.232);
  3475. }
  3476. },
  3477. jump() {
  3478. if (this.audioCues) {
  3479. const now = this.context.currentTime;
  3480. this.playNote(659.25, now, 0.116, 0.3, -0.6);
  3481. this.playNote(880, now + 0.116, 0.232, 0.3, -0.6);
  3482. }
  3483. },
  3484. };
  3485. /**
  3486. * For screen readers make an announcement to the live region.
  3487. * @param {string} phrase Sentence to speak.
  3488. */
  3489. function announcePhrase(phrase) {
  3490. if (Runner.a11yStatusEl) {
  3491. Runner.a11yStatusEl.textContent = '';
  3492. Runner.a11yStatusEl.textContent = phrase;
  3493. }
  3494. }
  3495. /**
  3496. * Returns a string from loadTimeData data object.
  3497. * @param {string} stringName
  3498. * @return {string}
  3499. */
  3500. function getA11yString(stringName) {
  3501. return loadTimeData && loadTimeData.valueExists(stringName) ?
  3502. loadTimeData.getString(stringName) :
  3503. '';
  3504. }
  3505. /**
  3506. * Get random number.
  3507. * @param {number} min
  3508. * @param {number} max
  3509. */
  3510. function getRandomNum(min, max) {
  3511. return Math.floor(Math.random() * (max - min + 1)) + min;
  3512. }
  3513. /**
  3514. * Vibrate on mobile devices.
  3515. * @param {number} duration Duration of the vibration in milliseconds.
  3516. */
  3517. function vibrate(duration) {
  3518. if (IS_MOBILE && window.navigator.vibrate) {
  3519. window.navigator.vibrate(duration);
  3520. }
  3521. }
  3522. /**
  3523. * Create canvas element.
  3524. * @param {Element} container Element to append canvas to.
  3525. * @param {number} width
  3526. * @param {number} height
  3527. * @param {string=} opt_classname
  3528. * @return {HTMLCanvasElement}
  3529. */
  3530. function createCanvas(container, width, height, opt_classname) {
  3531. const canvas =
  3532. /** @type {!HTMLCanvasElement} */ (document.createElement('canvas'));
  3533. canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' +
  3534. opt_classname : Runner.classes.CANVAS;
  3535. canvas.width = width;
  3536. canvas.height = height;
  3537. container.appendChild(canvas);
  3538. return canvas;
  3539. }
  3540. /**
  3541. * Decodes the base 64 audio to ArrayBuffer used by Web Audio.
  3542. * @param {string} base64String
  3543. */
  3544. function decodeBase64ToArrayBuffer(base64String) {
  3545. const len = (base64String.length / 4) * 3;
  3546. const str = atob(base64String);
  3547. const arrayBuffer = new ArrayBuffer(len);
  3548. const bytes = new Uint8Array(arrayBuffer);
  3549. for (let i = 0; i < len; i++) {
  3550. bytes[i] = str.charCodeAt(i);
  3551. }
  3552. return bytes.buffer;
  3553. }
  3554. /**
  3555. * Return the current timestamp.
  3556. * @return {number}
  3557. */
  3558. function getTimeStamp() {
  3559. return IS_IOS ? new Date().getTime() : performance.now();
  3560. }
  3561. //******************************************************************************
  3562. /**
  3563. * Game over panel.
  3564. * @param {!HTMLCanvasElement} canvas
  3565. * @param {Object} textImgPos
  3566. * @param {Object} restartImgPos
  3567. * @param {!Object} dimensions Canvas dimensions.
  3568. * @param {Object=} opt_altGameEndImgPos
  3569. * @param {boolean=} opt_altGameActive
  3570. * @constructor
  3571. */
  3572. function GameOverPanel(
  3573. canvas, textImgPos, restartImgPos, dimensions, opt_altGameEndImgPos,
  3574. opt_altGameActive) {
  3575. this.canvas = canvas;
  3576. this.canvasCtx =
  3577. /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
  3578. this.canvasDimensions = dimensions;
  3579. this.textImgPos = textImgPos;
  3580. this.restartImgPos = restartImgPos;
  3581. this.altGameEndImgPos = opt_altGameEndImgPos;
  3582. this.altGameModeActive = opt_altGameActive;
  3583. // Retry animation.
  3584. this.frameTimeStamp = 0;
  3585. this.animTimer = 0;
  3586. this.currentFrame = 0;
  3587. this.gameOverRafId = null;
  3588. this.flashTimer = 0;
  3589. this.flashCounter = 0;
  3590. this.originalText = true;
  3591. }
  3592. GameOverPanel.RESTART_ANIM_DURATION = 875;
  3593. GameOverPanel.LOGO_PAUSE_DURATION = 875;
  3594. GameOverPanel.FLASH_ITERATIONS = 5;
  3595. /**
  3596. * Animation frames spec.
  3597. */
  3598. GameOverPanel.animConfig = {
  3599. frames: [0, 36, 72, 108, 144, 180, 216, 252],
  3600. msPerFrame: GameOverPanel.RESTART_ANIM_DURATION / 8,
  3601. };
  3602. /**
  3603. * Dimensions used in the panel.
  3604. * @enum {number}
  3605. */
  3606. GameOverPanel.dimensions = {
  3607. TEXT_X: 0,
  3608. TEXT_Y: 13,
  3609. TEXT_WIDTH: 191,
  3610. TEXT_HEIGHT: 11,
  3611. RESTART_WIDTH: 36,
  3612. RESTART_HEIGHT: 32,
  3613. };
  3614. GameOverPanel.prototype = {
  3615. /**
  3616. * Update the panel dimensions.
  3617. * @param {number} width New canvas width.
  3618. * @param {number} opt_height Optional new canvas height.
  3619. */
  3620. updateDimensions(width, opt_height) {
  3621. this.canvasDimensions.WIDTH = width;
  3622. if (opt_height) {
  3623. this.canvasDimensions.HEIGHT = opt_height;
  3624. }
  3625. this.currentFrame = GameOverPanel.animConfig.frames.length - 1;
  3626. },
  3627. drawGameOverText(dimensions, opt_useAltText) {
  3628. const centerX = this.canvasDimensions.WIDTH / 2;
  3629. let textSourceX = dimensions.TEXT_X;
  3630. let textSourceY = dimensions.TEXT_Y;
  3631. let textSourceWidth = dimensions.TEXT_WIDTH;
  3632. let textSourceHeight = dimensions.TEXT_HEIGHT;
  3633. const textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2));
  3634. const textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3);
  3635. const textTargetWidth = dimensions.TEXT_WIDTH;
  3636. const textTargetHeight = dimensions.TEXT_HEIGHT;
  3637. if (IS_HIDPI) {
  3638. textSourceY *= 2;
  3639. textSourceX *= 2;
  3640. textSourceWidth *= 2;
  3641. textSourceHeight *= 2;
  3642. }
  3643. if (!opt_useAltText) {
  3644. textSourceX += this.textImgPos.x;
  3645. textSourceY += this.textImgPos.y;
  3646. }
  3647. const spriteSource =
  3648. opt_useAltText ? Runner.altCommonImageSprite : Runner.origImageSprite;
  3649. this.canvasCtx.save();
  3650. if (IS_RTL) {
  3651. this.canvasCtx.translate(this.canvasDimensions.WIDTH, 0);
  3652. this.canvasCtx.scale(-1, 1);
  3653. }
  3654. // Game over text from sprite.
  3655. this.canvasCtx.drawImage(
  3656. spriteSource, textSourceX, textSourceY, textSourceWidth,
  3657. textSourceHeight, textTargetX, textTargetY, textTargetWidth,
  3658. textTargetHeight);
  3659. this.canvasCtx.restore();
  3660. },
  3661. /**
  3662. * Draw additional adornments for alternative game types.
  3663. */
  3664. drawAltGameElements(tRex) {
  3665. // Additional adornments.
  3666. if (this.altGameModeActive && Runner.spriteDefinition.ALT_GAME_END_CONFIG) {
  3667. const altGameEndConfig = Runner.spriteDefinition.ALT_GAME_END_CONFIG;
  3668. let altGameEndSourceWidth = altGameEndConfig.WIDTH;
  3669. let altGameEndSourceHeight = altGameEndConfig.HEIGHT;
  3670. const altGameEndTargetX = tRex.xPos + altGameEndConfig.X_OFFSET;
  3671. const altGameEndTargetY = tRex.yPos + altGameEndConfig.Y_OFFSET;
  3672. if (IS_HIDPI) {
  3673. altGameEndSourceWidth *= 2;
  3674. altGameEndSourceHeight *= 2;
  3675. }
  3676. this.canvasCtx.drawImage(
  3677. Runner.altCommonImageSprite, this.altGameEndImgPos.x,
  3678. this.altGameEndImgPos.y, altGameEndSourceWidth,
  3679. altGameEndSourceHeight, altGameEndTargetX, altGameEndTargetY,
  3680. altGameEndConfig.WIDTH, altGameEndConfig.HEIGHT);
  3681. }
  3682. },
  3683. /**
  3684. * Draw restart button.
  3685. */
  3686. drawRestartButton() {
  3687. const dimensions = GameOverPanel.dimensions;
  3688. let framePosX = GameOverPanel.animConfig.frames[this.currentFrame];
  3689. let restartSourceWidth = dimensions.RESTART_WIDTH;
  3690. let restartSourceHeight = dimensions.RESTART_HEIGHT;
  3691. const restartTargetX =
  3692. (this.canvasDimensions.WIDTH / 2) - (dimensions.RESTART_WIDTH / 2);
  3693. const restartTargetY = this.canvasDimensions.HEIGHT / 2;
  3694. if (IS_HIDPI) {
  3695. restartSourceWidth *= 2;
  3696. restartSourceHeight *= 2;
  3697. framePosX *= 2;
  3698. }
  3699. this.canvasCtx.save();
  3700. if (IS_RTL) {
  3701. this.canvasCtx.translate(this.canvasDimensions.WIDTH, 0);
  3702. this.canvasCtx.scale(-1, 1);
  3703. }
  3704. this.canvasCtx.drawImage(
  3705. Runner.origImageSprite, this.restartImgPos.x + framePosX,
  3706. this.restartImgPos.y, restartSourceWidth, restartSourceHeight,
  3707. restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,
  3708. dimensions.RESTART_HEIGHT);
  3709. this.canvasCtx.restore();
  3710. },
  3711. /**
  3712. * Draw the panel.
  3713. * @param {boolean} opt_altGameModeActive
  3714. * @param {!Trex} opt_tRex
  3715. */
  3716. draw(opt_altGameModeActive, opt_tRex) {
  3717. if (opt_altGameModeActive) {
  3718. this.altGameModeActive = opt_altGameModeActive;
  3719. }
  3720. this.drawGameOverText(GameOverPanel.dimensions, false);
  3721. this.drawRestartButton();
  3722. this.drawAltGameElements(opt_tRex);
  3723. this.update();
  3724. },
  3725. /**
  3726. * Update animation frames.
  3727. */
  3728. update() {
  3729. const now = getTimeStamp();
  3730. const deltaTime = now - (this.frameTimeStamp || now);
  3731. this.frameTimeStamp = now;
  3732. this.animTimer += deltaTime;
  3733. this.flashTimer += deltaTime;
  3734. // Restart Button
  3735. if (this.currentFrame == 0 &&
  3736. this.animTimer > GameOverPanel.LOGO_PAUSE_DURATION) {
  3737. this.animTimer = 0;
  3738. this.currentFrame++;
  3739. this.drawRestartButton();
  3740. } else if (
  3741. this.currentFrame > 0 &&
  3742. this.currentFrame < GameOverPanel.animConfig.frames.length) {
  3743. if (this.animTimer >= GameOverPanel.animConfig.msPerFrame) {
  3744. this.currentFrame++;
  3745. this.drawRestartButton();
  3746. }
  3747. } else if (
  3748. !this.altGameModeActive &&
  3749. this.currentFrame == GameOverPanel.animConfig.frames.length) {
  3750. this.reset();
  3751. return;
  3752. }
  3753. // Game over text
  3754. if (this.altGameModeActive &&
  3755. spriteDefinitionByType.original.ALT_GAME_OVER_TEXT_CONFIG) {
  3756. const altTextConfig =
  3757. spriteDefinitionByType.original.ALT_GAME_OVER_TEXT_CONFIG;
  3758. if (altTextConfig.FLASHING) {
  3759. if (this.flashCounter < GameOverPanel.FLASH_ITERATIONS &&
  3760. this.flashTimer > altTextConfig.FLASH_DURATION) {
  3761. this.flashTimer = 0;
  3762. this.originalText = !this.originalText;
  3763. this.clearGameOverTextBounds();
  3764. if (this.originalText) {
  3765. this.drawGameOverText(GameOverPanel.dimensions, false);
  3766. this.flashCounter++;
  3767. } else {
  3768. this.drawGameOverText(altTextConfig, true);
  3769. }
  3770. } else if (this.flashCounter >= GameOverPanel.FLASH_ITERATIONS) {
  3771. this.reset();
  3772. return;
  3773. }
  3774. } else {
  3775. this.clearGameOverTextBounds(altTextConfig);
  3776. this.drawGameOverText(altTextConfig, true);
  3777. }
  3778. }
  3779. this.gameOverRafId = requestAnimationFrame(this.update.bind(this));
  3780. },
  3781. /**
  3782. * Clear game over text.
  3783. * @param {Object} dimensions Game over text config.
  3784. */
  3785. clearGameOverTextBounds(dimensions) {
  3786. this.canvasCtx.save();
  3787. this.canvasCtx.clearRect(
  3788. Math.round(
  3789. this.canvasDimensions.WIDTH / 2 - (dimensions.TEXT_WIDTH / 2)),
  3790. Math.round((this.canvasDimensions.HEIGHT - 25) / 3),
  3791. dimensions.TEXT_WIDTH, dimensions.TEXT_HEIGHT + 4);
  3792. this.canvasCtx.restore();
  3793. },
  3794. reset() {
  3795. if (this.gameOverRafId) {
  3796. cancelAnimationFrame(this.gameOverRafId);
  3797. this.gameOverRafId = null;
  3798. }
  3799. this.animTimer = 0;
  3800. this.frameTimeStamp = 0;
  3801. this.currentFrame = 0;
  3802. this.flashTimer = 0;
  3803. this.flashCounter = 0;
  3804. this.originalText = true;
  3805. },
  3806. };
  3807. //******************************************************************************
  3808. /**
  3809. * Check for a collision.
  3810. * @param {!Obstacle} obstacle
  3811. * @param {!Trex} tRex T-rex object.
  3812. * @param {CanvasRenderingContext2D=} opt_canvasCtx Optional canvas context for
  3813. * drawing collision boxes.
  3814. * @return {Array<CollisionBox>|undefined}
  3815. */
  3816. function checkForCollision(obstacle, tRex, opt_canvasCtx) {
  3817. Runner.defaultDimensions.WIDTH + obstacle.xPos;
  3818. // Adjustments are made to the bounding box as there is a 1 pixel white
  3819. // border around the t-rex and obstacles.
  3820. const tRexBox = new CollisionBox(
  3821. tRex.xPos + 1,
  3822. tRex.yPos + 1,
  3823. tRex.config.WIDTH - 2,
  3824. tRex.config.HEIGHT - 2);
  3825. const obstacleBox = new CollisionBox(
  3826. obstacle.xPos + 1,
  3827. obstacle.yPos + 1,
  3828. obstacle.typeConfig.width * obstacle.size - 2,
  3829. obstacle.typeConfig.height - 2);
  3830. // Debug outer box
  3831. if (opt_canvasCtx) {
  3832. drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);
  3833. }
  3834. // Simple outer bounds check.
  3835. if (boxCompare(tRexBox, obstacleBox)) {
  3836. const collisionBoxes = obstacle.collisionBoxes;
  3837. let tRexCollisionBoxes = [];
  3838. if (Runner.isAltGameModeEnabled()) {
  3839. tRexCollisionBoxes = Runner.spriteDefinition.TREX.COLLISION_BOXES;
  3840. } else {
  3841. tRexCollisionBoxes = tRex.ducking ? Trex.collisionBoxes.DUCKING :
  3842. Trex.collisionBoxes.RUNNING;
  3843. }
  3844. // Detailed axis aligned box check.
  3845. for (let t = 0; t < tRexCollisionBoxes.length; t++) {
  3846. for (let i = 0; i < collisionBoxes.length; i++) {
  3847. // Adjust the box to actual positions.
  3848. const adjTrexBox =
  3849. createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox);
  3850. const adjObstacleBox =
  3851. createAdjustedCollisionBox(collisionBoxes[i], obstacleBox);
  3852. const crashed = boxCompare(adjTrexBox, adjObstacleBox);
  3853. // Draw boxes for debug.
  3854. if (opt_canvasCtx) {
  3855. drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox);
  3856. }
  3857. if (crashed) {
  3858. return [adjTrexBox, adjObstacleBox];
  3859. }
  3860. }
  3861. }
  3862. }
  3863. }
  3864. /**
  3865. * Adjust the collision box.
  3866. * @param {!CollisionBox} box The original box.
  3867. * @param {!CollisionBox} adjustment Adjustment box.
  3868. * @return {CollisionBox} The adjusted collision box object.
  3869. */
  3870. function createAdjustedCollisionBox(box, adjustment) {
  3871. return new CollisionBox(
  3872. box.x + adjustment.x,
  3873. box.y + adjustment.y,
  3874. box.width,
  3875. box.height);
  3876. }
  3877. /**
  3878. * Draw the collision boxes for debug.
  3879. */
  3880. function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) {
  3881. canvasCtx.save();
  3882. canvasCtx.strokeStyle = '#f00';
  3883. canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height);
  3884. canvasCtx.strokeStyle = '#0f0';
  3885. canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y,
  3886. obstacleBox.width, obstacleBox.height);
  3887. canvasCtx.restore();
  3888. }
  3889. /**
  3890. * Compare two collision boxes for a collision.
  3891. * @param {CollisionBox} tRexBox
  3892. * @param {CollisionBox} obstacleBox
  3893. * @return {boolean} Whether the boxes intersected.
  3894. */
  3895. function boxCompare(tRexBox, obstacleBox) {
  3896. let crashed = false;
  3897. tRexBox.x;
  3898. tRexBox.y;
  3899. const obstacleBoxX = obstacleBox.x;
  3900. obstacleBox.y;
  3901. // Axis-Aligned Bounding Box method.
  3902. if (tRexBox.x < obstacleBoxX + obstacleBox.width &&
  3903. tRexBox.x + tRexBox.width > obstacleBoxX &&
  3904. tRexBox.y < obstacleBox.y + obstacleBox.height &&
  3905. tRexBox.height + tRexBox.y > obstacleBox.y) {
  3906. crashed = true;
  3907. }
  3908. return crashed;
  3909. }
  3910. //******************************************************************************
  3911. /**
  3912. * Obstacle.
  3913. * @param {CanvasRenderingContext2D} canvasCtx
  3914. * @param {ObstacleType} type
  3915. * @param {Object} spriteImgPos Obstacle position in sprite.
  3916. * @param {Object} dimensions
  3917. * @param {number} gapCoefficient Mutipler in determining the gap.
  3918. * @param {number} speed
  3919. * @param {number=} opt_xOffset
  3920. * @param {boolean=} opt_isAltGameMode
  3921. * @constructor
  3922. */
  3923. function Obstacle(
  3924. canvasCtx, type, spriteImgPos, dimensions, gapCoefficient, speed,
  3925. opt_xOffset, opt_isAltGameMode) {
  3926. this.canvasCtx = canvasCtx;
  3927. this.spritePos = spriteImgPos;
  3928. this.typeConfig = type;
  3929. this.gapCoefficient = Runner.slowDown ? gapCoefficient * 2 : gapCoefficient;
  3930. this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH);
  3931. this.dimensions = dimensions;
  3932. this.remove = false;
  3933. this.xPos = dimensions.WIDTH + (opt_xOffset || 0);
  3934. this.yPos = 0;
  3935. this.width = 0;
  3936. this.collisionBoxes = [];
  3937. this.gap = 0;
  3938. this.speedOffset = 0;
  3939. this.altGameModeActive = opt_isAltGameMode;
  3940. this.imageSprite = this.typeConfig.type == 'COLLECTABLE' ?
  3941. Runner.altCommonImageSprite :
  3942. this.altGameModeActive ? Runner.altGameImageSprite : Runner.imageSprite;
  3943. // For animated obstacles.
  3944. this.currentFrame = 0;
  3945. this.timer = 0;
  3946. this.init(speed);
  3947. }
  3948. /**
  3949. * Coefficient for calculating the maximum gap.
  3950. */
  3951. Obstacle.MAX_GAP_COEFFICIENT = 1.5;
  3952. /**
  3953. * Maximum obstacle grouping count.
  3954. */
  3955. Obstacle.MAX_OBSTACLE_LENGTH = 3;
  3956. Obstacle.prototype = {
  3957. /**
  3958. * Initialise the DOM for the obstacle.
  3959. * @param {number} speed
  3960. */
  3961. init(speed) {
  3962. this.cloneCollisionBoxes();
  3963. // Only allow sizing if we're at the right speed.
  3964. if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
  3965. this.size = 1;
  3966. }
  3967. this.width = this.typeConfig.width * this.size;
  3968. // Check if obstacle can be positioned at various heights.
  3969. if (Array.isArray(this.typeConfig.yPos)) {
  3970. const yPosConfig =
  3971. IS_MOBILE ? this.typeConfig.yPosMobile : this.typeConfig.yPos;
  3972. this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)];
  3973. } else {
  3974. this.yPos = this.typeConfig.yPos;
  3975. }
  3976. this.draw();
  3977. // Make collision box adjustments,
  3978. // Central box is adjusted to the size as one box.
  3979. // ____ ______ ________
  3980. // _| |-| _| |-| _| |-|
  3981. // | |<->| | | |<--->| | | |<----->| |
  3982. // | | 1 | | | | 2 | | | | 3 | |
  3983. // |_|___|_| |_|_____|_| |_|_______|_|
  3984. //
  3985. if (this.size > 1) {
  3986. this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
  3987. this.collisionBoxes[2].width;
  3988. this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
  3989. }
  3990. // For obstacles that go at a different speed from the horizon.
  3991. if (this.typeConfig.speedOffset) {
  3992. this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset :
  3993. -this.typeConfig.speedOffset;
  3994. }
  3995. this.gap = this.getGap(this.gapCoefficient, speed);
  3996. // Increase gap for audio cues enabled.
  3997. if (Runner.audioCues) {
  3998. this.gap *= 2;
  3999. }
  4000. },
  4001. /**
  4002. * Draw and crop based on size.
  4003. */
  4004. draw() {
  4005. let sourceWidth = this.typeConfig.width;
  4006. let sourceHeight = this.typeConfig.height;
  4007. if (IS_HIDPI) {
  4008. sourceWidth = sourceWidth * 2;
  4009. sourceHeight = sourceHeight * 2;
  4010. }
  4011. // X position in sprite.
  4012. let sourceX =
  4013. (sourceWidth * this.size) * (0.5 * (this.size - 1)) + this.spritePos.x;
  4014. // Animation frames.
  4015. if (this.currentFrame > 0) {
  4016. sourceX += sourceWidth * this.currentFrame;
  4017. }
  4018. this.canvasCtx.drawImage(
  4019. this.imageSprite, sourceX, this.spritePos.y, sourceWidth * this.size,
  4020. sourceHeight, this.xPos, this.yPos, this.typeConfig.width * this.size,
  4021. this.typeConfig.height);
  4022. },
  4023. /**
  4024. * Obstacle frame update.
  4025. * @param {number} deltaTime
  4026. * @param {number} speed
  4027. */
  4028. update(deltaTime, speed) {
  4029. if (!this.remove) {
  4030. if (this.typeConfig.speedOffset) {
  4031. speed += this.speedOffset;
  4032. }
  4033. this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);
  4034. // Update frame
  4035. if (this.typeConfig.numFrames) {
  4036. this.timer += deltaTime;
  4037. if (this.timer >= this.typeConfig.frameRate) {
  4038. this.currentFrame =
  4039. this.currentFrame === this.typeConfig.numFrames - 1 ?
  4040. 0 :
  4041. this.currentFrame + 1;
  4042. this.timer = 0;
  4043. }
  4044. }
  4045. this.draw();
  4046. if (!this.isVisible()) {
  4047. this.remove = true;
  4048. }
  4049. }
  4050. },
  4051. /**
  4052. * Calculate a random gap size.
  4053. * - Minimum gap gets wider as speed increses
  4054. * @param {number} gapCoefficient
  4055. * @param {number} speed
  4056. * @return {number} The gap size.
  4057. */
  4058. getGap(gapCoefficient, speed) {
  4059. const minGap = Math.round(
  4060. this.width * speed + this.typeConfig.minGap * gapCoefficient);
  4061. const maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
  4062. return getRandomNum(minGap, maxGap);
  4063. },
  4064. /**
  4065. * Check if obstacle is visible.
  4066. * @return {boolean} Whether the obstacle is in the game area.
  4067. */
  4068. isVisible() {
  4069. return this.xPos + this.width > 0;
  4070. },
  4071. /**
  4072. * Make a copy of the collision boxes, since these will change based on
  4073. * obstacle type and size.
  4074. */
  4075. cloneCollisionBoxes() {
  4076. const collisionBoxes = this.typeConfig.collisionBoxes;
  4077. for (let i = collisionBoxes.length - 1; i >= 0; i--) {
  4078. this.collisionBoxes[i] = new CollisionBox(
  4079. collisionBoxes[i].x, collisionBoxes[i].y, collisionBoxes[i].width,
  4080. collisionBoxes[i].height);
  4081. }
  4082. },
  4083. };
  4084. //******************************************************************************
  4085. /**
  4086. * T-rex game character.
  4087. * @param {HTMLCanvasElement} canvas
  4088. * @param {Object} spritePos Positioning within image sprite.
  4089. * @constructor
  4090. */
  4091. function Trex(canvas, spritePos) {
  4092. this.canvas = canvas;
  4093. this.canvasCtx =
  4094. /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
  4095. this.spritePos = spritePos;
  4096. this.xPos = 0;
  4097. this.yPos = 0;
  4098. this.xInitialPos = 0;
  4099. // Position when on the ground.
  4100. this.groundYPos = 0;
  4101. this.currentFrame = 0;
  4102. this.currentAnimFrames = [];
  4103. this.blinkDelay = 0;
  4104. this.blinkCount = 0;
  4105. this.animStartTime = 0;
  4106. this.timer = 0;
  4107. this.msPerFrame = 1000 / FPS;
  4108. this.config = Object.assign(Trex.config, Trex.normalJumpConfig);
  4109. // Current status.
  4110. this.status = Trex.status.WAITING;
  4111. this.jumping = false;
  4112. this.ducking = false;
  4113. this.jumpVelocity = 0;
  4114. this.reachedMinHeight = false;
  4115. this.speedDrop = false;
  4116. this.jumpCount = 0;
  4117. this.jumpspotX = 0;
  4118. this.altGameModeEnabled = false;
  4119. this.flashing = false;
  4120. this.init();
  4121. }
  4122. /**
  4123. * T-rex player config.
  4124. */
  4125. Trex.config = {
  4126. DROP_VELOCITY: -5,
  4127. FLASH_OFF: 175,
  4128. FLASH_ON: 100,
  4129. HEIGHT: 47,
  4130. HEIGHT_DUCK: 25,
  4131. INTRO_DURATION: 1500,
  4132. SPEED_DROP_COEFFICIENT: 3,
  4133. SPRITE_WIDTH: 262,
  4134. START_X_POS: 50,
  4135. WIDTH: 44,
  4136. WIDTH_DUCK: 59,
  4137. };
  4138. Trex.slowJumpConfig = {
  4139. GRAVITY: 0.25,
  4140. MAX_JUMP_HEIGHT: 50,
  4141. MIN_JUMP_HEIGHT: 45,
  4142. INITIAL_JUMP_VELOCITY: -20,
  4143. };
  4144. Trex.normalJumpConfig = {
  4145. GRAVITY: 0.6,
  4146. MAX_JUMP_HEIGHT: 30,
  4147. MIN_JUMP_HEIGHT: 30,
  4148. INITIAL_JUMP_VELOCITY: -10,
  4149. };
  4150. /**
  4151. * Used in collision detection.
  4152. * @enum {Array<CollisionBox>}
  4153. */
  4154. Trex.collisionBoxes = {
  4155. DUCKING: [new CollisionBox(1, 18, 55, 25)],
  4156. RUNNING: [
  4157. new CollisionBox(22, 0, 17, 16),
  4158. new CollisionBox(1, 18, 30, 9),
  4159. new CollisionBox(10, 35, 14, 8),
  4160. new CollisionBox(1, 24, 29, 5),
  4161. new CollisionBox(5, 30, 21, 4),
  4162. new CollisionBox(9, 34, 15, 4),
  4163. ],
  4164. };
  4165. /**
  4166. * Animation states.
  4167. * @enum {string}
  4168. */
  4169. Trex.status = {
  4170. CRASHED: 'CRASHED',
  4171. DUCKING: 'DUCKING',
  4172. JUMPING: 'JUMPING',
  4173. RUNNING: 'RUNNING',
  4174. WAITING: 'WAITING',
  4175. };
  4176. /**
  4177. * Blinking coefficient.
  4178. * @const
  4179. */
  4180. Trex.BLINK_TIMING = 7000;
  4181. /**
  4182. * Animation config for different states.
  4183. * @enum {Object}
  4184. */
  4185. Trex.animFrames = {
  4186. WAITING: {
  4187. frames: [44, 0],
  4188. msPerFrame: 1000 / 3,
  4189. },
  4190. RUNNING: {
  4191. frames: [88, 132],
  4192. msPerFrame: 1000 / 12,
  4193. },
  4194. CRASHED: {
  4195. frames: [220],
  4196. msPerFrame: 1000 / 60,
  4197. },
  4198. JUMPING: {
  4199. frames: [0],
  4200. msPerFrame: 1000 / 60,
  4201. },
  4202. DUCKING: {
  4203. frames: [264, 323],
  4204. msPerFrame: 1000 / 8,
  4205. },
  4206. };
  4207. Trex.prototype = {
  4208. /**
  4209. * T-rex player initaliser.
  4210. * Sets the t-rex to blink at random intervals.
  4211. */
  4212. init() {
  4213. this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
  4214. Runner.config.BOTTOM_PAD;
  4215. this.yPos = this.groundYPos;
  4216. this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
  4217. this.draw(0, 0);
  4218. this.update(0, Trex.status.WAITING);
  4219. },
  4220. /**
  4221. * Assign the appropriate jump parameters based on the game speed.
  4222. */
  4223. enableSlowConfig: function() {
  4224. const jumpConfig =
  4225. Runner.slowDown ? Trex.slowJumpConfig : Trex.normalJumpConfig;
  4226. Trex.config = Object.assign(Trex.config, jumpConfig);
  4227. this.adjustAltGameConfigForSlowSpeed();
  4228. },
  4229. /**
  4230. * Enables the alternative game. Redefines the dino config.
  4231. * @param {Object} spritePos New positioning within image sprite.
  4232. */
  4233. enableAltGameMode: function(spritePos) {
  4234. this.altGameModeEnabled = true;
  4235. this.spritePos = spritePos;
  4236. const spriteDefinition = Runner.spriteDefinition['TREX'];
  4237. // Update animation frames.
  4238. Trex.animFrames.RUNNING.frames =
  4239. [spriteDefinition.RUNNING_1.x, spriteDefinition.RUNNING_2.x];
  4240. Trex.animFrames.CRASHED.frames = [spriteDefinition.CRASHED.x];
  4241. if (typeof spriteDefinition.JUMPING.x == 'object') {
  4242. Trex.animFrames.JUMPING.frames = spriteDefinition.JUMPING.x;
  4243. } else {
  4244. Trex.animFrames.JUMPING.frames = [spriteDefinition.JUMPING.x];
  4245. }
  4246. Trex.animFrames.DUCKING.frames =
  4247. [spriteDefinition.DUCKING_1.x, spriteDefinition.DUCKING_2.x];
  4248. // Update Trex config
  4249. Trex.config.GRAVITY = spriteDefinition.GRAVITY || Trex.config.GRAVITY;
  4250. Trex.config.HEIGHT = spriteDefinition.RUNNING_1.h,
  4251. Trex.config.INITIAL_JUMP_VELOCITY = spriteDefinition.INITIAL_JUMP_VELOCITY;
  4252. Trex.config.MAX_JUMP_HEIGHT = spriteDefinition.MAX_JUMP_HEIGHT;
  4253. Trex.config.MIN_JUMP_HEIGHT = spriteDefinition.MIN_JUMP_HEIGHT;
  4254. Trex.config.WIDTH = spriteDefinition.RUNNING_1.w;
  4255. Trex.config.WIDTH_CRASHED = spriteDefinition.CRASHED.w;
  4256. Trex.config.WIDTH_JUMP = spriteDefinition.JUMPING.w;
  4257. Trex.config.INVERT_JUMP = spriteDefinition.INVERT_JUMP;
  4258. this.adjustAltGameConfigForSlowSpeed(spriteDefinition.GRAVITY);
  4259. this.config = Trex.config;
  4260. // Adjust bottom horizon placement.
  4261. this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
  4262. Runner.spriteDefinition['BOTTOM_PAD'];
  4263. this.yPos = this.groundYPos;
  4264. this.reset();
  4265. },
  4266. /**
  4267. * Slow speeds adjustments for the alt game modes.
  4268. * @param {number=} opt_gravityValue
  4269. */
  4270. adjustAltGameConfigForSlowSpeed: function(opt_gravityValue) {
  4271. if (Runner.slowDown) {
  4272. if (opt_gravityValue) {
  4273. Trex.config.GRAVITY = opt_gravityValue / 1.5;
  4274. }
  4275. Trex.config.MIN_JUMP_HEIGHT *= 1.5;
  4276. Trex.config.MAX_JUMP_HEIGHT *= 1.5;
  4277. Trex.config.INITIAL_JUMP_VELOCITY =
  4278. Trex.config.INITIAL_JUMP_VELOCITY * 1.5;
  4279. }
  4280. },
  4281. /**
  4282. * Setter whether dino is flashing.
  4283. * @param {boolean} status
  4284. */
  4285. setFlashing: function(status) {
  4286. this.flashing = status;
  4287. },
  4288. /**
  4289. * Setter for the jump velocity.
  4290. * The approriate drop velocity is also set.
  4291. * @param {number} setting
  4292. */
  4293. setJumpVelocity(setting) {
  4294. this.config.INITIAL_JUMP_VELOCITY = -setting;
  4295. this.config.DROP_VELOCITY = -setting / 2;
  4296. },
  4297. /**
  4298. * Set the animation status.
  4299. * @param {!number} deltaTime
  4300. * @param {Trex.status=} opt_status Optional status to switch to.
  4301. */
  4302. update(deltaTime, opt_status) {
  4303. this.timer += deltaTime;
  4304. // Update the status.
  4305. if (opt_status) {
  4306. this.status = opt_status;
  4307. this.currentFrame = 0;
  4308. this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
  4309. this.currentAnimFrames = Trex.animFrames[opt_status].frames;
  4310. if (opt_status === Trex.status.WAITING) {
  4311. this.animStartTime = getTimeStamp();
  4312. this.setBlinkDelay();
  4313. }
  4314. }
  4315. // Game intro animation, T-rex moves in from the left.
  4316. if (this.playingIntro && this.xPos < this.config.START_X_POS) {
  4317. this.xPos += Math.round((this.config.START_X_POS /
  4318. this.config.INTRO_DURATION) * deltaTime);
  4319. this.xInitialPos = this.xPos;
  4320. }
  4321. if (this.status === Trex.status.WAITING) {
  4322. this.blink(getTimeStamp());
  4323. } else {
  4324. this.draw(this.currentAnimFrames[this.currentFrame], 0);
  4325. }
  4326. // Update the frame position.
  4327. if (!this.flashing && this.timer >= this.msPerFrame) {
  4328. this.currentFrame = this.currentFrame ==
  4329. this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
  4330. this.timer = 0;
  4331. }
  4332. // Speed drop becomes duck if the down key is still being pressed.
  4333. if (this.speedDrop && this.yPos === this.groundYPos) {
  4334. this.speedDrop = false;
  4335. this.setDuck(true);
  4336. }
  4337. },
  4338. /**
  4339. * Draw the t-rex to a particular position.
  4340. * @param {number} x
  4341. * @param {number} y
  4342. */
  4343. draw(x, y) {
  4344. let sourceX = x;
  4345. let sourceY = y;
  4346. let sourceWidth = this.ducking && this.status !== Trex.status.CRASHED ?
  4347. this.config.WIDTH_DUCK :
  4348. this.config.WIDTH;
  4349. let sourceHeight = this.config.HEIGHT;
  4350. const outputHeight = sourceHeight;
  4351. const outputWidth =
  4352. this.altGameModeEnabled && this.status == Trex.status.CRASHED ?
  4353. this.config.WIDTH_CRASHED :
  4354. this.config.WIDTH;
  4355. let jumpOffset = Runner.spriteDefinition.TREX.JUMPING.xOffset;
  4356. // Width of sprite can change on jump or crashed.
  4357. if (this.altGameModeEnabled) {
  4358. if (this.jumping && this.status !== Trex.status.CRASHED) {
  4359. sourceWidth = this.config.WIDTH_JUMP;
  4360. } else if (this.status == Trex.status.CRASHED) {
  4361. sourceWidth = this.config.WIDTH_CRASHED;
  4362. }
  4363. }
  4364. if (IS_HIDPI) {
  4365. sourceX *= 2;
  4366. sourceY *= 2;
  4367. sourceWidth *= 2;
  4368. sourceHeight *= 2;
  4369. jumpOffset *= 2;
  4370. }
  4371. // Adjustments for sprite sheet position.
  4372. sourceX += this.spritePos.x;
  4373. sourceY += this.spritePos.y;
  4374. // Flashing.
  4375. if (this.flashing) {
  4376. if (this.timer < this.config.FLASH_ON) {
  4377. this.canvasCtx.globalAlpha = 0.5;
  4378. } else if (this.timer > this.config.FLASH_OFF) {
  4379. this.timer = 0;
  4380. }
  4381. }
  4382. // Ducking.
  4383. if (this.ducking && this.status !== Trex.status.CRASHED) {
  4384. this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY,
  4385. sourceWidth, sourceHeight,
  4386. this.xPos, this.yPos,
  4387. this.config.WIDTH_DUCK, outputHeight);
  4388. } else if (
  4389. this.altGameModeEnabled && this.jumping &&
  4390. this.status !== Trex.status.CRASHED) {
  4391. // Jumping with adjustments.
  4392. this.canvasCtx.drawImage(
  4393. Runner.imageSprite, sourceX, sourceY, sourceWidth, sourceHeight,
  4394. this.xPos - jumpOffset, this.yPos, this.config.WIDTH_JUMP,
  4395. outputHeight);
  4396. } else {
  4397. // Crashed whilst ducking. Trex is standing up so needs adjustment.
  4398. if (this.ducking && this.status === Trex.status.CRASHED) {
  4399. this.xPos++;
  4400. }
  4401. // Standing / running
  4402. this.canvasCtx.drawImage(
  4403. Runner.imageSprite, sourceX, sourceY, sourceWidth, sourceHeight,
  4404. this.xPos, this.yPos, outputWidth, outputHeight);
  4405. }
  4406. this.canvasCtx.globalAlpha = 1;
  4407. },
  4408. /**
  4409. * Sets a random time for the blink to happen.
  4410. */
  4411. setBlinkDelay() {
  4412. this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
  4413. },
  4414. /**
  4415. * Make t-rex blink at random intervals.
  4416. * @param {number} time Current time in milliseconds.
  4417. */
  4418. blink(time) {
  4419. const deltaTime = time - this.animStartTime;
  4420. if (deltaTime >= this.blinkDelay) {
  4421. this.draw(this.currentAnimFrames[this.currentFrame], 0);
  4422. if (this.currentFrame === 1) {
  4423. // Set new random delay to blink.
  4424. this.setBlinkDelay();
  4425. this.animStartTime = time;
  4426. this.blinkCount++;
  4427. }
  4428. }
  4429. },
  4430. /**
  4431. * Initialise a jump.
  4432. * @param {number} speed
  4433. */
  4434. startJump(speed) {
  4435. if (!this.jumping) {
  4436. this.update(0, Trex.status.JUMPING);
  4437. // Tweak the jump velocity based on the speed.
  4438. this.jumpVelocity = this.config.INITIAL_JUMP_VELOCITY - (speed / 10);
  4439. this.jumping = true;
  4440. this.reachedMinHeight = false;
  4441. this.speedDrop = false;
  4442. if (this.config.INVERT_JUMP) {
  4443. this.minJumpHeight = this.groundYPos + this.config.MIN_JUMP_HEIGHT;
  4444. }
  4445. }
  4446. },
  4447. /**
  4448. * Jump is complete, falling down.
  4449. */
  4450. endJump() {
  4451. if (this.reachedMinHeight &&
  4452. this.jumpVelocity < this.config.DROP_VELOCITY) {
  4453. this.jumpVelocity = this.config.DROP_VELOCITY;
  4454. }
  4455. },
  4456. /**
  4457. * Update frame for a jump.
  4458. * @param {number} deltaTime
  4459. */
  4460. updateJump(deltaTime) {
  4461. const msPerFrame = Trex.animFrames[this.status].msPerFrame;
  4462. const framesElapsed = deltaTime / msPerFrame;
  4463. // Speed drop makes Trex fall faster.
  4464. if (this.speedDrop) {
  4465. this.yPos += Math.round(this.jumpVelocity *
  4466. this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
  4467. } else if (this.config.INVERT_JUMP) {
  4468. this.yPos -= Math.round(this.jumpVelocity * framesElapsed);
  4469. } else {
  4470. this.yPos += Math.round(this.jumpVelocity * framesElapsed);
  4471. }
  4472. this.jumpVelocity += this.config.GRAVITY * framesElapsed;
  4473. // Minimum height has been reached.
  4474. if (this.config.INVERT_JUMP && (this.yPos > this.minJumpHeight) ||
  4475. !this.config.INVERT_JUMP && (this.yPos < this.minJumpHeight) ||
  4476. this.speedDrop) {
  4477. this.reachedMinHeight = true;
  4478. }
  4479. // Reached max height.
  4480. if (this.config.INVERT_JUMP && (this.yPos > -this.config.MAX_JUMP_HEIGHT) ||
  4481. !this.config.INVERT_JUMP && (this.yPos < this.config.MAX_JUMP_HEIGHT) ||
  4482. this.speedDrop) {
  4483. this.endJump();
  4484. }
  4485. // Back down at ground level. Jump completed.
  4486. if ((this.config.INVERT_JUMP && this.yPos) < this.groundYPos ||
  4487. (!this.config.INVERT_JUMP && this.yPos) > this.groundYPos) {
  4488. this.reset();
  4489. this.jumpCount++;
  4490. if (Runner.audioCues) {
  4491. Runner.generatedSoundFx.loopFootSteps();
  4492. }
  4493. }
  4494. },
  4495. /**
  4496. * Set the speed drop. Immediately cancels the current jump.
  4497. */
  4498. setSpeedDrop() {
  4499. this.speedDrop = true;
  4500. this.jumpVelocity = 1;
  4501. },
  4502. /**
  4503. * @param {boolean} isDucking
  4504. */
  4505. setDuck(isDucking) {
  4506. if (isDucking && this.status !== Trex.status.DUCKING) {
  4507. this.update(0, Trex.status.DUCKING);
  4508. this.ducking = true;
  4509. } else if (this.status === Trex.status.DUCKING) {
  4510. this.update(0, Trex.status.RUNNING);
  4511. this.ducking = false;
  4512. }
  4513. },
  4514. /**
  4515. * Reset the t-rex to running at start of game.
  4516. */
  4517. reset() {
  4518. this.xPos = this.xInitialPos;
  4519. this.yPos = this.groundYPos;
  4520. this.jumpVelocity = 0;
  4521. this.jumping = false;
  4522. this.ducking = false;
  4523. this.update(0, Trex.status.RUNNING);
  4524. this.midair = false;
  4525. this.speedDrop = false;
  4526. this.jumpCount = 0;
  4527. },
  4528. };
  4529. //******************************************************************************
  4530. /**
  4531. * Handles displaying the distance meter.
  4532. * @param {!HTMLCanvasElement} canvas
  4533. * @param {Object} spritePos Image position in sprite.
  4534. * @param {number} canvasWidth
  4535. * @constructor
  4536. */
  4537. function DistanceMeter(canvas, spritePos, canvasWidth) {
  4538. this.canvas = canvas;
  4539. this.canvasCtx =
  4540. /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
  4541. this.image = Runner.imageSprite;
  4542. this.spritePos = spritePos;
  4543. this.x = 0;
  4544. this.y = 5;
  4545. this.currentDistance = 0;
  4546. this.maxScore = 0;
  4547. this.highScore = '0';
  4548. this.container = null;
  4549. this.digits = [];
  4550. this.achievement = false;
  4551. this.defaultString = '';
  4552. this.flashTimer = 0;
  4553. this.flashIterations = 0;
  4554. this.invertTrigger = false;
  4555. this.flashingRafId = null;
  4556. this.highScoreBounds = {};
  4557. this.highScoreFlashing = false;
  4558. this.config = DistanceMeter.config;
  4559. this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS;
  4560. this.canvasWidth = canvasWidth;
  4561. this.init(canvasWidth);
  4562. }
  4563. /**
  4564. * @enum {number}
  4565. */
  4566. DistanceMeter.dimensions = {
  4567. WIDTH: 10,
  4568. HEIGHT: 13,
  4569. DEST_WIDTH: 11,
  4570. };
  4571. /**
  4572. * Y positioning of the digits in the sprite sheet.
  4573. * X position is always 0.
  4574. * @type {Array<number>}
  4575. */
  4576. DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];
  4577. /**
  4578. * Distance meter config.
  4579. * @enum {number}
  4580. */
  4581. DistanceMeter.config = {
  4582. // Number of digits.
  4583. MAX_DISTANCE_UNITS: 5,
  4584. // Distance that causes achievement animation.
  4585. ACHIEVEMENT_DISTANCE: 100,
  4586. // Used for conversion from pixel distance to a scaled unit.
  4587. COEFFICIENT: 0.025,
  4588. // Flash duration in milliseconds.
  4589. FLASH_DURATION: 1000 / 4,
  4590. // Flash iterations for achievement animation.
  4591. FLASH_ITERATIONS: 3,
  4592. // Padding around the high score hit area.
  4593. HIGH_SCORE_HIT_AREA_PADDING: 4,
  4594. };
  4595. DistanceMeter.prototype = {
  4596. /**
  4597. * Initialise the distance meter to '00000'.
  4598. * @param {number} width Canvas width in px.
  4599. */
  4600. init(width) {
  4601. let maxDistanceStr = '';
  4602. this.calcXPos(width);
  4603. this.maxScore = this.maxScoreUnits;
  4604. for (let i = 0; i < this.maxScoreUnits; i++) {
  4605. this.draw(i, 0);
  4606. this.defaultString += '0';
  4607. maxDistanceStr += '9';
  4608. }
  4609. this.maxScore = parseInt(maxDistanceStr, 10);
  4610. },
  4611. /**
  4612. * Calculate the xPos in the canvas.
  4613. * @param {number} canvasWidth
  4614. */
  4615. calcXPos(canvasWidth) {
  4616. this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
  4617. (this.maxScoreUnits + 1));
  4618. },
  4619. /**
  4620. * Draw a digit to canvas.
  4621. * @param {number} digitPos Position of the digit.
  4622. * @param {number} value Digit value 0-9.
  4623. * @param {boolean=} opt_highScore Whether drawing the high score.
  4624. */
  4625. draw(digitPos, value, opt_highScore) {
  4626. let sourceWidth = DistanceMeter.dimensions.WIDTH;
  4627. let sourceHeight = DistanceMeter.dimensions.HEIGHT;
  4628. let sourceX = DistanceMeter.dimensions.WIDTH * value;
  4629. let sourceY = 0;
  4630. const targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
  4631. const targetY = this.y;
  4632. const targetWidth = DistanceMeter.dimensions.WIDTH;
  4633. const targetHeight = DistanceMeter.dimensions.HEIGHT;
  4634. // For high DPI we 2x source values.
  4635. if (IS_HIDPI) {
  4636. sourceWidth *= 2;
  4637. sourceHeight *= 2;
  4638. sourceX *= 2;
  4639. }
  4640. sourceX += this.spritePos.x;
  4641. sourceY += this.spritePos.y;
  4642. this.canvasCtx.save();
  4643. if (IS_RTL) {
  4644. if (opt_highScore) {
  4645. this.canvasCtx.translate(
  4646. this.canvasWidth -
  4647. (DistanceMeter.dimensions.WIDTH * (this.maxScoreUnits + 3)),
  4648. this.y);
  4649. } else {
  4650. this.canvasCtx.translate(
  4651. this.canvasWidth - DistanceMeter.dimensions.WIDTH, this.y);
  4652. }
  4653. this.canvasCtx.scale(-1, 1);
  4654. } else {
  4655. const highScoreX =
  4656. this.x - (this.maxScoreUnits * 2) * DistanceMeter.dimensions.WIDTH;
  4657. if (opt_highScore) {
  4658. this.canvasCtx.translate(highScoreX, this.y);
  4659. } else {
  4660. this.canvasCtx.translate(this.x, this.y);
  4661. }
  4662. }
  4663. this.canvasCtx.drawImage(
  4664. this.image,
  4665. sourceX,
  4666. sourceY,
  4667. sourceWidth,
  4668. sourceHeight,
  4669. targetX,
  4670. targetY,
  4671. targetWidth,
  4672. targetHeight,
  4673. );
  4674. this.canvasCtx.restore();
  4675. },
  4676. /**
  4677. * Covert pixel distance to a 'real' distance.
  4678. * @param {number} distance Pixel distance ran.
  4679. * @return {number} The 'real' distance ran.
  4680. */
  4681. getActualDistance(distance) {
  4682. return distance ? Math.round(distance * this.config.COEFFICIENT) : 0;
  4683. },
  4684. /**
  4685. * Update the distance meter.
  4686. * @param {number} distance
  4687. * @param {number} deltaTime
  4688. * @return {boolean} Whether the acheivement sound fx should be played.
  4689. */
  4690. update(deltaTime, distance) {
  4691. let paint = true;
  4692. let playSound = false;
  4693. if (!this.achievement) {
  4694. distance = this.getActualDistance(distance);
  4695. // Score has gone beyond the initial digit count.
  4696. if (distance > this.maxScore && this.maxScoreUnits ==
  4697. this.config.MAX_DISTANCE_UNITS) {
  4698. this.maxScoreUnits++;
  4699. this.maxScore = parseInt(this.maxScore + '9', 10);
  4700. } else {
  4701. this.distance = 0;
  4702. }
  4703. if (distance > 0) {
  4704. // Achievement unlocked.
  4705. if (distance % this.config.ACHIEVEMENT_DISTANCE === 0) {
  4706. // Flash score and play sound.
  4707. this.achievement = true;
  4708. this.flashTimer = 0;
  4709. playSound = true;
  4710. }
  4711. // Create a string representation of the distance with leading 0.
  4712. const distanceStr = (this.defaultString +
  4713. distance).substr(-this.maxScoreUnits);
  4714. this.digits = distanceStr.split('');
  4715. } else {
  4716. this.digits = this.defaultString.split('');
  4717. }
  4718. } else {
  4719. // Control flashing of the score on reaching acheivement.
  4720. if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
  4721. this.flashTimer += deltaTime;
  4722. if (this.flashTimer < this.config.FLASH_DURATION) {
  4723. paint = false;
  4724. } else if (this.flashTimer > this.config.FLASH_DURATION * 2) {
  4725. this.flashTimer = 0;
  4726. this.flashIterations++;
  4727. }
  4728. } else {
  4729. this.achievement = false;
  4730. this.flashIterations = 0;
  4731. this.flashTimer = 0;
  4732. }
  4733. }
  4734. // Draw the digits if not flashing.
  4735. if (paint) {
  4736. for (let i = this.digits.length - 1; i >= 0; i--) {
  4737. this.draw(i, parseInt(this.digits[i], 10));
  4738. }
  4739. }
  4740. this.drawHighScore();
  4741. return playSound;
  4742. },
  4743. /**
  4744. * Draw the high score.
  4745. */
  4746. drawHighScore() {
  4747. if (parseInt(this.highScore, 10) > 0) {
  4748. this.canvasCtx.save();
  4749. this.canvasCtx.globalAlpha = .8;
  4750. for (let i = this.highScore.length - 1; i >= 0; i--) {
  4751. this.draw(i, parseInt(this.highScore[i], 10), true);
  4752. }
  4753. this.canvasCtx.restore();
  4754. }
  4755. },
  4756. /**
  4757. * Set the highscore as a array string.
  4758. * Position of char in the sprite: H - 10, I - 11.
  4759. * @param {number} distance Distance ran in pixels.
  4760. */
  4761. setHighScore(distance) {
  4762. distance = this.getActualDistance(distance);
  4763. const highScoreStr = (this.defaultString +
  4764. distance).substr(-this.maxScoreUnits);
  4765. this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
  4766. },
  4767. /**
  4768. * Whether a clicked is in the high score area.
  4769. * @param {Event} e Event object.
  4770. * @return {boolean} Whether the click was in the high score bounds.
  4771. */
  4772. hasClickedOnHighScore(e) {
  4773. let x = 0;
  4774. let y = 0;
  4775. if (e.touches) {
  4776. // Bounds for touch differ from pointer.
  4777. const canvasBounds = this.canvas.getBoundingClientRect();
  4778. x = e.touches[0].clientX - canvasBounds.left;
  4779. y = e.touches[0].clientY - canvasBounds.top;
  4780. } else {
  4781. x = e.offsetX;
  4782. y = e.offsetY;
  4783. }
  4784. this.highScoreBounds = this.getHighScoreBounds();
  4785. return x >= this.highScoreBounds.x && x <=
  4786. this.highScoreBounds.x + this.highScoreBounds.width &&
  4787. y >= this.highScoreBounds.y && y <=
  4788. this.highScoreBounds.y + this.highScoreBounds.height;
  4789. },
  4790. /**
  4791. * Get the bounding box for the high score.
  4792. * @return {Object} Object with x, y, width and height properties.
  4793. */
  4794. getHighScoreBounds() {
  4795. return {
  4796. x: (this.x - (this.maxScoreUnits * 2) * DistanceMeter.dimensions.WIDTH) -
  4797. DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING,
  4798. y: this.y,
  4799. width: DistanceMeter.dimensions.WIDTH * (this.highScore.length + 1) +
  4800. DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING,
  4801. height: DistanceMeter.dimensions.HEIGHT +
  4802. (DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING * 2),
  4803. };
  4804. },
  4805. /**
  4806. * Animate flashing the high score to indicate ready for resetting.
  4807. * The flashing stops following this.config.FLASH_ITERATIONS x 2 flashes.
  4808. */
  4809. flashHighScore() {
  4810. const now = getTimeStamp();
  4811. const deltaTime = now - (this.frameTimeStamp || now);
  4812. let paint = true;
  4813. this.frameTimeStamp = now;
  4814. // Reached the max number of flashes.
  4815. if (this.flashIterations > this.config.FLASH_ITERATIONS * 2) {
  4816. this.cancelHighScoreFlashing();
  4817. return;
  4818. }
  4819. this.flashTimer += deltaTime;
  4820. if (this.flashTimer < this.config.FLASH_DURATION) {
  4821. paint = false;
  4822. } else if (this.flashTimer > this.config.FLASH_DURATION * 2) {
  4823. this.flashTimer = 0;
  4824. this.flashIterations++;
  4825. }
  4826. if (paint) {
  4827. this.drawHighScore();
  4828. } else {
  4829. this.clearHighScoreBounds();
  4830. }
  4831. // Frame update.
  4832. this.flashingRafId =
  4833. requestAnimationFrame(this.flashHighScore.bind(this));
  4834. },
  4835. /**
  4836. * Draw empty rectangle over high score.
  4837. */
  4838. clearHighScoreBounds() {
  4839. this.canvasCtx.save();
  4840. this.canvasCtx.fillStyle = '#fff';
  4841. this.canvasCtx.rect(this.highScoreBounds.x, this.highScoreBounds.y,
  4842. this.highScoreBounds.width, this.highScoreBounds.height);
  4843. this.canvasCtx.fill();
  4844. this.canvasCtx.restore();
  4845. },
  4846. /**
  4847. * Starts the flashing of the high score.
  4848. */
  4849. startHighScoreFlashing() {
  4850. this.highScoreFlashing = true;
  4851. this.flashHighScore();
  4852. },
  4853. /**
  4854. * Whether high score is flashing.
  4855. * @return {boolean}
  4856. */
  4857. isHighScoreFlashing() {
  4858. return this.highScoreFlashing;
  4859. },
  4860. /**
  4861. * Stop flashing the high score.
  4862. */
  4863. cancelHighScoreFlashing() {
  4864. if (this.flashingRafId) {
  4865. cancelAnimationFrame(this.flashingRafId);
  4866. }
  4867. this.flashIterations = 0;
  4868. this.flashTimer = 0;
  4869. this.highScoreFlashing = false;
  4870. this.clearHighScoreBounds();
  4871. this.drawHighScore();
  4872. },
  4873. /**
  4874. * Clear the high score.
  4875. */
  4876. resetHighScore() {
  4877. this.setHighScore(0);
  4878. this.cancelHighScoreFlashing();
  4879. },
  4880. /**
  4881. * Reset the distance meter back to '00000'.
  4882. */
  4883. reset() {
  4884. this.update(0, 0);
  4885. this.achievement = false;
  4886. },
  4887. };
  4888. //******************************************************************************
  4889. /**
  4890. * Cloud background item.
  4891. * Similar to an obstacle object but without collision boxes.
  4892. * @param {HTMLCanvasElement} canvas Canvas element.
  4893. * @param {Object} spritePos Position of image in sprite.
  4894. * @param {number} containerWidth
  4895. * @constructor
  4896. */
  4897. function Cloud(canvas, spritePos, containerWidth) {
  4898. this.canvas = canvas;
  4899. this.canvasCtx =
  4900. /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d'));
  4901. this.spritePos = spritePos;
  4902. this.containerWidth = containerWidth;
  4903. this.xPos = containerWidth;
  4904. this.yPos = 0;
  4905. this.remove = false;
  4906. this.gap =
  4907. getRandomNum(Cloud.config.MIN_CLOUD_GAP, Cloud.config.MAX_CLOUD_GAP);
  4908. this.init();
  4909. }
  4910. /**
  4911. * Cloud object config.
  4912. * @enum {number}
  4913. */
  4914. Cloud.config = {
  4915. HEIGHT: 14,
  4916. MAX_CLOUD_GAP: 400,
  4917. MAX_SKY_LEVEL: 30,
  4918. MIN_CLOUD_GAP: 100,
  4919. MIN_SKY_LEVEL: 71,
  4920. WIDTH: 46,
  4921. };
  4922. Cloud.prototype = {
  4923. /**
  4924. * Initialise the cloud. Sets the Cloud height.
  4925. */
  4926. init() {
  4927. this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL,
  4928. Cloud.config.MIN_SKY_LEVEL);
  4929. this.draw();
  4930. },
  4931. /**
  4932. * Draw the cloud.
  4933. */
  4934. draw() {
  4935. this.canvasCtx.save();
  4936. let sourceWidth = Cloud.config.WIDTH;
  4937. let sourceHeight = Cloud.config.HEIGHT;
  4938. const outputWidth = sourceWidth;
  4939. const outputHeight = sourceHeight;
  4940. if (IS_HIDPI) {
  4941. sourceWidth = sourceWidth * 2;
  4942. sourceHeight = sourceHeight * 2;
  4943. }
  4944. this.canvasCtx.drawImage(Runner.imageSprite, this.spritePos.x,
  4945. this.spritePos.y,
  4946. sourceWidth, sourceHeight,
  4947. this.xPos, this.yPos,
  4948. outputWidth, outputHeight);
  4949. this.canvasCtx.restore();
  4950. },
  4951. /**
  4952. * Update the cloud position.
  4953. * @param {number} speed
  4954. */
  4955. update(speed) {
  4956. if (!this.remove) {
  4957. this.xPos -= Math.ceil(speed);
  4958. this.draw();
  4959. // Mark as removeable if no longer in the canvas.
  4960. if (!this.isVisible()) {
  4961. this.remove = true;
  4962. }
  4963. }
  4964. },
  4965. /**
  4966. * Check if the cloud is visible on the stage.
  4967. * @return {boolean}
  4968. */
  4969. isVisible() {
  4970. return this.xPos + Cloud.config.WIDTH > 0;
  4971. },
  4972. };
  4973. /**
  4974. * Background item.
  4975. * Similar to cloud, without random y position.
  4976. * @param {HTMLCanvasElement} canvas Canvas element.
  4977. * @param {Object} spritePos Position of image in sprite.
  4978. * @param {number} containerWidth
  4979. * @param {string} type Element type.
  4980. * @constructor
  4981. */
  4982. function BackgroundEl(canvas, spritePos, containerWidth, type) {
  4983. this.canvas = canvas;
  4984. this.canvasCtx =
  4985. /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d'));
  4986. this.spritePos = spritePos;
  4987. this.containerWidth = containerWidth;
  4988. this.xPos = containerWidth;
  4989. this.yPos = 0;
  4990. this.remove = false;
  4991. this.type = type;
  4992. this.gap =
  4993. getRandomNum(BackgroundEl.config.MIN_GAP, BackgroundEl.config.MAX_GAP);
  4994. this.animTimer = 0;
  4995. this.switchFrames = false;
  4996. this.spriteConfig = {};
  4997. this.init();
  4998. }
  4999. /**
  5000. * Background element object config.
  5001. * Real values assigned when game type changes.
  5002. * @enum {number}
  5003. */
  5004. BackgroundEl.config = {
  5005. MAX_BG_ELS: 0,
  5006. MAX_GAP: 0,
  5007. MIN_GAP: 0,
  5008. POS: 0,
  5009. SPEED: 0,
  5010. Y_POS: 0,
  5011. MS_PER_FRAME: 0, // only needed when BACKGROUND_EL.FIXED is true
  5012. };
  5013. BackgroundEl.prototype = {
  5014. /**
  5015. * Initialise the element setting the y position.
  5016. */
  5017. init() {
  5018. this.spriteConfig = Runner.spriteDefinition.BACKGROUND_EL[this.type];
  5019. if (this.spriteConfig.FIXED) {
  5020. this.xPos = this.spriteConfig.FIXED_X_POS;
  5021. }
  5022. this.yPos = BackgroundEl.config.Y_POS - this.spriteConfig.HEIGHT +
  5023. this.spriteConfig.OFFSET;
  5024. this.draw();
  5025. },
  5026. /**
  5027. * Draw the element.
  5028. */
  5029. draw() {
  5030. this.canvasCtx.save();
  5031. let sourceWidth = this.spriteConfig.WIDTH;
  5032. let sourceHeight = this.spriteConfig.HEIGHT;
  5033. let sourceX = this.spriteConfig.X_POS;
  5034. const outputWidth = sourceWidth;
  5035. const outputHeight = sourceHeight;
  5036. if (IS_HIDPI) {
  5037. sourceWidth *= 2;
  5038. sourceHeight *= 2;
  5039. sourceX *= 2;
  5040. }
  5041. this.canvasCtx.drawImage(
  5042. Runner.imageSprite, sourceX, this.spritePos.y, sourceWidth,
  5043. sourceHeight, this.xPos, this.yPos, outputWidth, outputHeight);
  5044. this.canvasCtx.restore();
  5045. },
  5046. /**
  5047. * Update the background element position.
  5048. * @param {number} speed
  5049. */
  5050. update(speed) {
  5051. if (!this.remove) {
  5052. if (this.spriteConfig.FIXED) {
  5053. this.animTimer += speed;
  5054. if (this.animTimer > BackgroundEl.config.MS_PER_FRAME) {
  5055. this.animTimer = 0;
  5056. this.switchFrames = !this.switchFrames;
  5057. }
  5058. if (this.spriteConfig.FIXED_Y_POS_1 &&
  5059. this.spriteConfig.FIXED_Y_POS_2) {
  5060. this.yPos = this.switchFrames ? this.spriteConfig.FIXED_Y_POS_1 :
  5061. this.spriteConfig.FIXED_Y_POS_2;
  5062. }
  5063. } else {
  5064. // Fixed speed, regardless of actual game speed.
  5065. this.xPos -= BackgroundEl.config.SPEED;
  5066. }
  5067. this.draw();
  5068. // Mark as removable if no longer in the canvas.
  5069. if (!this.isVisible()) {
  5070. this.remove = true;
  5071. }
  5072. }
  5073. },
  5074. /**
  5075. * Check if the element is visible on the stage.
  5076. * @return {boolean}
  5077. */
  5078. isVisible() {
  5079. return this.xPos + this.spriteConfig.WIDTH > 0;
  5080. },
  5081. };
  5082. //******************************************************************************
  5083. /**
  5084. * Nightmode shows a moon and stars on the horizon.
  5085. * @param {HTMLCanvasElement} canvas
  5086. * @param {number} spritePos
  5087. * @param {number} containerWidth
  5088. * @constructor
  5089. */
  5090. function NightMode(canvas, spritePos, containerWidth) {
  5091. this.spritePos = spritePos;
  5092. this.canvas = canvas;
  5093. this.canvasCtx =
  5094. /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
  5095. this.xPos = containerWidth - 50;
  5096. this.yPos = 30;
  5097. this.currentPhase = 0;
  5098. this.opacity = 0;
  5099. this.containerWidth = containerWidth;
  5100. this.stars = [];
  5101. this.drawStars = false;
  5102. this.placeStars();
  5103. }
  5104. /**
  5105. * @enum {number}
  5106. */
  5107. NightMode.config = {
  5108. FADE_SPEED: 0.035,
  5109. HEIGHT: 40,
  5110. MOON_SPEED: 0.25,
  5111. NUM_STARS: 2,
  5112. STAR_SIZE: 9,
  5113. STAR_SPEED: 0.3,
  5114. STAR_MAX_Y: 70,
  5115. WIDTH: 20,
  5116. };
  5117. NightMode.phases = [140, 120, 100, 60, 40, 20, 0];
  5118. NightMode.prototype = {
  5119. /**
  5120. * Update moving moon, changing phases.
  5121. * @param {boolean} activated Whether night mode is activated.
  5122. */
  5123. update(activated) {
  5124. // Moon phase.
  5125. if (activated && this.opacity === 0) {
  5126. this.currentPhase++;
  5127. if (this.currentPhase >= NightMode.phases.length) {
  5128. this.currentPhase = 0;
  5129. }
  5130. }
  5131. // Fade in / out.
  5132. if (activated && (this.opacity < 1 || this.opacity === 0)) {
  5133. this.opacity += NightMode.config.FADE_SPEED;
  5134. } else if (this.opacity > 0) {
  5135. this.opacity -= NightMode.config.FADE_SPEED;
  5136. }
  5137. // Set moon positioning.
  5138. if (this.opacity > 0) {
  5139. this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED);
  5140. // Update stars.
  5141. if (this.drawStars) {
  5142. for (let i = 0; i < NightMode.config.NUM_STARS; i++) {
  5143. this.stars[i].x =
  5144. this.updateXPos(this.stars[i].x, NightMode.config.STAR_SPEED);
  5145. }
  5146. }
  5147. this.draw();
  5148. } else {
  5149. this.opacity = 0;
  5150. this.placeStars();
  5151. }
  5152. this.drawStars = true;
  5153. },
  5154. updateXPos(currentPos, speed) {
  5155. if (currentPos < -NightMode.config.WIDTH) {
  5156. currentPos = this.containerWidth;
  5157. } else {
  5158. currentPos -= speed;
  5159. }
  5160. return currentPos;
  5161. },
  5162. draw() {
  5163. let moonSourceWidth = this.currentPhase === 3 ? NightMode.config.WIDTH * 2 :
  5164. NightMode.config.WIDTH;
  5165. let moonSourceHeight = NightMode.config.HEIGHT;
  5166. let moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase];
  5167. const moonOutputWidth = moonSourceWidth;
  5168. let starSize = NightMode.config.STAR_SIZE;
  5169. let starSourceX = spriteDefinitionByType.original.LDPI.STAR.x;
  5170. if (IS_HIDPI) {
  5171. moonSourceWidth *= 2;
  5172. moonSourceHeight *= 2;
  5173. moonSourceX = this.spritePos.x +
  5174. (NightMode.phases[this.currentPhase] * 2);
  5175. starSize *= 2;
  5176. starSourceX = spriteDefinitionByType.original.HDPI.STAR.x;
  5177. }
  5178. this.canvasCtx.save();
  5179. this.canvasCtx.globalAlpha = this.opacity;
  5180. // Stars.
  5181. if (this.drawStars) {
  5182. for (let i = 0; i < NightMode.config.NUM_STARS; i++) {
  5183. this.canvasCtx.drawImage(
  5184. Runner.origImageSprite, starSourceX, this.stars[i].sourceY,
  5185. starSize, starSize, Math.round(this.stars[i].x), this.stars[i].y,
  5186. NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE);
  5187. }
  5188. }
  5189. // Moon.
  5190. this.canvasCtx.drawImage(
  5191. Runner.origImageSprite, moonSourceX, this.spritePos.y, moonSourceWidth,
  5192. moonSourceHeight, Math.round(this.xPos), this.yPos, moonOutputWidth,
  5193. NightMode.config.HEIGHT);
  5194. this.canvasCtx.globalAlpha = 1;
  5195. this.canvasCtx.restore();
  5196. },
  5197. // Do star placement.
  5198. placeStars() {
  5199. const segmentSize = Math.round(this.containerWidth /
  5200. NightMode.config.NUM_STARS);
  5201. for (let i = 0; i < NightMode.config.NUM_STARS; i++) {
  5202. this.stars[i] = {};
  5203. this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1));
  5204. this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y);
  5205. if (IS_HIDPI) {
  5206. this.stars[i].sourceY = spriteDefinitionByType.original.HDPI.STAR.y +
  5207. NightMode.config.STAR_SIZE * 2 * i;
  5208. } else {
  5209. this.stars[i].sourceY = spriteDefinitionByType.original.LDPI.STAR.y +
  5210. NightMode.config.STAR_SIZE * i;
  5211. }
  5212. }
  5213. },
  5214. reset() {
  5215. this.currentPhase = 0;
  5216. this.opacity = 0;
  5217. this.update(false);
  5218. },
  5219. };
  5220. //******************************************************************************
  5221. /**
  5222. * Horizon Line.
  5223. * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon.
  5224. * @param {HTMLCanvasElement} canvas
  5225. * @param {Object} lineConfig Configuration object.
  5226. * @constructor
  5227. */
  5228. function HorizonLine(canvas, lineConfig) {
  5229. let sourceX = lineConfig.SOURCE_X;
  5230. let sourceY = lineConfig.SOURCE_Y;
  5231. if (IS_HIDPI) {
  5232. sourceX *= 2;
  5233. sourceY *= 2;
  5234. }
  5235. this.spritePos = {x: sourceX, y: sourceY};
  5236. this.canvas = canvas;
  5237. this.canvasCtx =
  5238. /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d'));
  5239. this.sourceDimensions = {};
  5240. this.dimensions = lineConfig;
  5241. this.sourceXPos = [this.spritePos.x, this.spritePos.x +
  5242. this.dimensions.WIDTH];
  5243. this.xPos = [];
  5244. this.yPos = 0;
  5245. this.bumpThreshold = 0.5;
  5246. this.setSourceDimensions(lineConfig);
  5247. this.draw();
  5248. }
  5249. /**
  5250. * Horizon line dimensions.
  5251. * @enum {number}
  5252. */
  5253. HorizonLine.dimensions = {
  5254. WIDTH: 600,
  5255. HEIGHT: 12,
  5256. YPOS: 127,
  5257. };
  5258. HorizonLine.prototype = {
  5259. /**
  5260. * Set the source dimensions of the horizon line.
  5261. */
  5262. setSourceDimensions(newDimensions) {
  5263. for (const dimension in newDimensions) {
  5264. if (dimension !== 'SOURCE_X' && dimension !== 'SOURCE_Y') {
  5265. if (IS_HIDPI) {
  5266. if (dimension !== 'YPOS') {
  5267. this.sourceDimensions[dimension] = newDimensions[dimension] * 2;
  5268. }
  5269. } else {
  5270. this.sourceDimensions[dimension] = newDimensions[dimension];
  5271. }
  5272. this.dimensions[dimension] = newDimensions[dimension];
  5273. }
  5274. }
  5275. this.xPos = [0, newDimensions.WIDTH];
  5276. this.yPos = newDimensions.YPOS;
  5277. },
  5278. /**
  5279. * Return the crop x position of a type.
  5280. */
  5281. getRandomType() {
  5282. return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
  5283. },
  5284. /**
  5285. * Draw the horizon line.
  5286. */
  5287. draw() {
  5288. this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0],
  5289. this.spritePos.y,
  5290. this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
  5291. this.xPos[0], this.yPos,
  5292. this.dimensions.WIDTH, this.dimensions.HEIGHT);
  5293. this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[1],
  5294. this.spritePos.y,
  5295. this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
  5296. this.xPos[1], this.yPos,
  5297. this.dimensions.WIDTH, this.dimensions.HEIGHT);
  5298. },
  5299. /**
  5300. * Update the x position of an indivdual piece of the line.
  5301. * @param {number} pos Line position.
  5302. * @param {number} increment
  5303. */
  5304. updateXPos(pos, increment) {
  5305. const line1 = pos;
  5306. const line2 = pos === 0 ? 1 : 0;
  5307. this.xPos[line1] -= increment;
  5308. this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;
  5309. if (this.xPos[line1] <= -this.dimensions.WIDTH) {
  5310. this.xPos[line1] += this.dimensions.WIDTH * 2;
  5311. this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
  5312. this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x;
  5313. }
  5314. },
  5315. /**
  5316. * Update the horizon line.
  5317. * @param {number} deltaTime
  5318. * @param {number} speed
  5319. */
  5320. update(deltaTime, speed) {
  5321. const increment = Math.floor(speed * (FPS / 1000) * deltaTime);
  5322. if (this.xPos[0] <= 0) {
  5323. this.updateXPos(0, increment);
  5324. } else {
  5325. this.updateXPos(1, increment);
  5326. }
  5327. this.draw();
  5328. },
  5329. /**
  5330. * Reset horizon to the starting position.
  5331. */
  5332. reset() {
  5333. this.xPos[0] = 0;
  5334. this.xPos[1] = this.dimensions.WIDTH;
  5335. },
  5336. };
  5337. //******************************************************************************
  5338. /**
  5339. * Horizon background class.
  5340. * @param {HTMLCanvasElement} canvas
  5341. * @param {Object} spritePos Sprite positioning.
  5342. * @param {Object} dimensions Canvas dimensions.
  5343. * @param {number} gapCoefficient
  5344. * @constructor
  5345. */
  5346. function Horizon(canvas, spritePos, dimensions, gapCoefficient) {
  5347. this.canvas = canvas;
  5348. this.canvasCtx =
  5349. /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d'));
  5350. this.config = Horizon.config;
  5351. this.dimensions = dimensions;
  5352. this.gapCoefficient = gapCoefficient;
  5353. this.obstacles = [];
  5354. this.obstacleHistory = [];
  5355. this.horizonOffsets = [0, 0];
  5356. this.cloudFrequency = this.config.CLOUD_FREQUENCY;
  5357. this.spritePos = spritePos;
  5358. this.nightMode = null;
  5359. this.altGameModeActive = false;
  5360. // Cloud
  5361. this.clouds = [];
  5362. this.cloudSpeed = this.config.BG_CLOUD_SPEED;
  5363. // Background elements
  5364. this.backgroundEls = [];
  5365. this.lastEl = null;
  5366. this.backgroundSpeed = this.config.BG_CLOUD_SPEED;
  5367. // Horizon
  5368. this.horizonLine = null;
  5369. this.horizonLines = [];
  5370. this.init();
  5371. }
  5372. /**
  5373. * Horizon config.
  5374. * @enum {number}
  5375. */
  5376. Horizon.config = {
  5377. BG_CLOUD_SPEED: 0.2,
  5378. BUMPY_THRESHOLD: .3,
  5379. CLOUD_FREQUENCY: .5,
  5380. HORIZON_HEIGHT: 16,
  5381. MAX_CLOUDS: 6,
  5382. };
  5383. Horizon.prototype = {
  5384. /**
  5385. * Initialise the horizon. Just add the line and a cloud. No obstacles.
  5386. */
  5387. init() {
  5388. Obstacle.types = spriteDefinitionByType.original.OBSTACLES;
  5389. this.addCloud();
  5390. // Multiple Horizon lines
  5391. for (let i = 0; i < Runner.spriteDefinition.LINES.length; i++) {
  5392. this.horizonLines.push(
  5393. new HorizonLine(this.canvas, Runner.spriteDefinition.LINES[i]));
  5394. }
  5395. this.nightMode = new NightMode(this.canvas, this.spritePos.MOON,
  5396. this.dimensions.WIDTH);
  5397. },
  5398. /**
  5399. * Update obstacle definitions based on the speed of the game.
  5400. */
  5401. adjustObstacleSpeed: function() {
  5402. for (let i = 0; i < Obstacle.types.length; i++) {
  5403. if (Runner.slowDown) {
  5404. Obstacle.types[i].multipleSpeed = Obstacle.types[i].multipleSpeed / 2;
  5405. Obstacle.types[i].minGap *= 1.5;
  5406. Obstacle.types[i].minSpeed = Obstacle.types[i].minSpeed / 2;
  5407. // Convert variable y position obstacles to fixed.
  5408. if (typeof (Obstacle.types[i].yPos) == 'object') {
  5409. Obstacle.types[i].yPos = Obstacle.types[i].yPos[0];
  5410. Obstacle.types[i].yPosMobile = Obstacle.types[i].yPos[0];
  5411. }
  5412. }
  5413. }
  5414. },
  5415. /**
  5416. * Update sprites to correspond to change in sprite sheet.
  5417. * @param {number} spritePos
  5418. */
  5419. enableAltGameMode: function(spritePos) {
  5420. // Clear existing horizon objects.
  5421. this.clouds = [];
  5422. this.backgroundEls = [];
  5423. this.altGameModeActive = true;
  5424. this.spritePos = spritePos;
  5425. Obstacle.types = Runner.spriteDefinition.OBSTACLES;
  5426. this.adjustObstacleSpeed();
  5427. Obstacle.MAX_GAP_COEFFICIENT = Runner.spriteDefinition.MAX_GAP_COEFFICIENT;
  5428. Obstacle.MAX_OBSTACLE_LENGTH = Runner.spriteDefinition.MAX_OBSTACLE_LENGTH;
  5429. BackgroundEl.config = Runner.spriteDefinition.BACKGROUND_EL_CONFIG;
  5430. this.horizonLines = [];
  5431. for (let i = 0; i < Runner.spriteDefinition.LINES.length; i++) {
  5432. this.horizonLines.push(
  5433. new HorizonLine(this.canvas, Runner.spriteDefinition.LINES[i]));
  5434. }
  5435. this.reset();
  5436. },
  5437. /**
  5438. * @param {number} deltaTime
  5439. * @param {number} currentSpeed
  5440. * @param {boolean} updateObstacles Used as an override to prevent
  5441. * the obstacles from being updated / added. This happens in the
  5442. * ease in section.
  5443. * @param {boolean} showNightMode Night mode activated.
  5444. */
  5445. update(deltaTime, currentSpeed, updateObstacles, showNightMode) {
  5446. this.runningTime += deltaTime;
  5447. if (this.altGameModeActive) {
  5448. this.updateBackgroundEls(deltaTime, currentSpeed);
  5449. }
  5450. for (let i = 0; i < this.horizonLines.length; i++) {
  5451. this.horizonLines[i].update(deltaTime, currentSpeed);
  5452. }
  5453. if (!this.altGameModeActive || Runner.spriteDefinition.HAS_CLOUDS) {
  5454. this.nightMode.update(showNightMode);
  5455. this.updateClouds(deltaTime, currentSpeed);
  5456. }
  5457. if (updateObstacles) {
  5458. this.updateObstacles(deltaTime, currentSpeed);
  5459. }
  5460. },
  5461. /**
  5462. * Update background element positions. Also handles creating new elements.
  5463. * @param {number} elSpeed
  5464. * @param {Array<Object>} bgElArray
  5465. * @param {number} maxBgEl
  5466. * @param {Function} bgElAddFunction
  5467. * @param {number} frequency
  5468. */
  5469. updateBackgroundEl(elSpeed, bgElArray, maxBgEl, bgElAddFunction, frequency) {
  5470. const numElements = bgElArray.length;
  5471. if (numElements) {
  5472. for (let i = numElements - 1; i >= 0; i--) {
  5473. bgElArray[i].update(elSpeed);
  5474. }
  5475. const lastEl = bgElArray[numElements - 1];
  5476. // Check for adding a new element.
  5477. if (numElements < maxBgEl &&
  5478. (this.dimensions.WIDTH - lastEl.xPos) > lastEl.gap &&
  5479. frequency > Math.random()) {
  5480. bgElAddFunction();
  5481. }
  5482. } else {
  5483. bgElAddFunction();
  5484. }
  5485. },
  5486. /**
  5487. * Update the cloud positions.
  5488. * @param {number} deltaTime
  5489. * @param {number} speed
  5490. */
  5491. updateClouds(deltaTime, speed) {
  5492. const elSpeed = this.cloudSpeed / 1000 * deltaTime * speed;
  5493. this.updateBackgroundEl(
  5494. elSpeed, this.clouds, this.config.MAX_CLOUDS, this.addCloud.bind(this),
  5495. this.cloudFrequency);
  5496. // Remove expired elements.
  5497. this.clouds = this.clouds.filter((obj) => !obj.remove);
  5498. },
  5499. /**
  5500. * Update the background element positions.
  5501. * @param {number} deltaTime
  5502. * @param {number} speed
  5503. */
  5504. updateBackgroundEls(deltaTime, speed) {
  5505. this.updateBackgroundEl(
  5506. deltaTime, this.backgroundEls, BackgroundEl.config.MAX_BG_ELS,
  5507. this.addBackgroundEl.bind(this), this.cloudFrequency);
  5508. // Remove expired elements.
  5509. this.backgroundEls = this.backgroundEls.filter((obj) => !obj.remove);
  5510. },
  5511. /**
  5512. * Update the obstacle positions.
  5513. * @param {number} deltaTime
  5514. * @param {number} currentSpeed
  5515. */
  5516. updateObstacles(deltaTime, currentSpeed) {
  5517. const updatedObstacles = this.obstacles.slice(0);
  5518. for (let i = 0; i < this.obstacles.length; i++) {
  5519. const obstacle = this.obstacles[i];
  5520. obstacle.update(deltaTime, currentSpeed);
  5521. // Clean up existing obstacles.
  5522. if (obstacle.remove) {
  5523. updatedObstacles.shift();
  5524. }
  5525. }
  5526. this.obstacles = updatedObstacles;
  5527. if (this.obstacles.length > 0) {
  5528. const lastObstacle = this.obstacles[this.obstacles.length - 1];
  5529. if (lastObstacle && !lastObstacle.followingObstacleCreated &&
  5530. lastObstacle.isVisible() &&
  5531. (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
  5532. this.dimensions.WIDTH) {
  5533. this.addNewObstacle(currentSpeed);
  5534. lastObstacle.followingObstacleCreated = true;
  5535. }
  5536. } else {
  5537. // Create new obstacles.
  5538. this.addNewObstacle(currentSpeed);
  5539. }
  5540. },
  5541. removeFirstObstacle() {
  5542. this.obstacles.shift();
  5543. },
  5544. /**
  5545. * Add a new obstacle.
  5546. * @param {number} currentSpeed
  5547. */
  5548. addNewObstacle(currentSpeed) {
  5549. const obstacleCount =
  5550. Obstacle.types[Obstacle.types.length - 1].type != 'COLLECTABLE' ||
  5551. (Runner.isAltGameModeEnabled() && !this.altGameModeActive ||
  5552. this.altGameModeActive) ?
  5553. Obstacle.types.length - 1 :
  5554. Obstacle.types.length - 2;
  5555. const obstacleTypeIndex =
  5556. obstacleCount > 0 ? getRandomNum(0, obstacleCount) : 0;
  5557. const obstacleType = Obstacle.types[obstacleTypeIndex];
  5558. // Check for multiples of the same type of obstacle.
  5559. // Also check obstacle is available at current speed.
  5560. if ((obstacleCount > 0 && this.duplicateObstacleCheck(obstacleType.type)) ||
  5561. currentSpeed < obstacleType.minSpeed) {
  5562. this.addNewObstacle(currentSpeed);
  5563. } else {
  5564. const obstacleSpritePos = this.spritePos[obstacleType.type];
  5565. this.obstacles.push(new Obstacle(
  5566. this.canvasCtx, obstacleType, obstacleSpritePos, this.dimensions,
  5567. this.gapCoefficient, currentSpeed, obstacleType.width,
  5568. this.altGameModeActive));
  5569. this.obstacleHistory.unshift(obstacleType.type);
  5570. if (this.obstacleHistory.length > 1) {
  5571. this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION);
  5572. }
  5573. }
  5574. },
  5575. /**
  5576. * Returns whether the previous two obstacles are the same as the next one.
  5577. * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION.
  5578. * @return {boolean}
  5579. */
  5580. duplicateObstacleCheck(nextObstacleType) {
  5581. let duplicateCount = 0;
  5582. for (let i = 0; i < this.obstacleHistory.length; i++) {
  5583. duplicateCount =
  5584. this.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0;
  5585. }
  5586. return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION;
  5587. },
  5588. /**
  5589. * Reset the horizon layer.
  5590. * Remove existing obstacles and reposition the horizon line.
  5591. */
  5592. reset() {
  5593. this.obstacles = [];
  5594. for (let l = 0; l < this.horizonLines.length; l++) {
  5595. this.horizonLines[l].reset();
  5596. }
  5597. this.nightMode.reset();
  5598. },
  5599. /**
  5600. * Update the canvas width and scaling.
  5601. * @param {number} width Canvas width.
  5602. * @param {number} height Canvas height.
  5603. */
  5604. resize(width, height) {
  5605. this.canvas.width = width;
  5606. this.canvas.height = height;
  5607. },
  5608. /**
  5609. * Add a new cloud to the horizon.
  5610. */
  5611. addCloud() {
  5612. this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD,
  5613. this.dimensions.WIDTH));
  5614. },
  5615. /**
  5616. * Add a random background element to the horizon.
  5617. */
  5618. addBackgroundEl() {
  5619. const backgroundElTypes =
  5620. Object.keys(Runner.spriteDefinition.BACKGROUND_EL);
  5621. if (backgroundElTypes.length > 0) {
  5622. let index = getRandomNum(0, backgroundElTypes.length - 1);
  5623. let type = backgroundElTypes[index];
  5624. // Add variation if available.
  5625. while (type == this.lastEl && backgroundElTypes.length > 1) {
  5626. index = getRandomNum(0, backgroundElTypes.length - 1);
  5627. type = backgroundElTypes[index];
  5628. }
  5629. this.lastEl = type;
  5630. this.backgroundEls.push(new BackgroundEl(
  5631. this.canvas, this.spritePos.BACKGROUND_EL, this.dimensions.WIDTH,
  5632. type));
  5633. }
  5634. },
  5635. };
  5636. // Copyright 2013 The Chromium Authors
  5637. // Use of this source code is governed by a BSD-style license that can be
  5638. // found in the LICENSE file.
  5639. function toggleHelpBox() {
  5640. const helpBoxOuter = document.getElementById('details');
  5641. helpBoxOuter.classList.toggle(HIDDEN_CLASS);
  5642. const detailsButton = document.getElementById('details-button');
  5643. if (helpBoxOuter.classList.contains(HIDDEN_CLASS)) {
  5644. /** @suppress {missingProperties} */
  5645. detailsButton.innerText = detailsButton.detailsText;
  5646. } else {
  5647. /** @suppress {missingProperties} */
  5648. detailsButton.innerText = detailsButton.hideDetailsText;
  5649. }
  5650. // Details appears over the main content on small screens.
  5651. if (mobileNav) {
  5652. document.getElementById('main-content').classList.toggle(HIDDEN_CLASS);
  5653. const runnerContainer = document.querySelector('.runner-container');
  5654. if (runnerContainer) {
  5655. runnerContainer.classList.toggle(HIDDEN_CLASS);
  5656. }
  5657. }
  5658. }
  5659. function diagnoseErrors() {
  5660. if (window.errorPageController) {
  5661. window.errorPageController.diagnoseErrorsButtonClick();
  5662. }
  5663. }
  5664. function portalSignin() {
  5665. if (window.errorPageController) {
  5666. window.errorPageController.portalSigninButtonClick();
  5667. }
  5668. }
  5669. // Subframes use a different layout but the same html file. This is to make it
  5670. // easier to support platforms that load the error page via different
  5671. // mechanisms (Currently just iOS).
  5672. let isSubFrame = false;
  5673. if (window.top.location !== window.location) {
  5674. document.documentElement.setAttribute('subframe', '');
  5675. isSubFrame = true;
  5676. }
  5677. // Re-renders the error page using |strings| as the dictionary of values.
  5678. // Used by NetErrorTabHelper to update DNS error pages with probe results.
  5679. function updateForDnsProbe(strings) {
  5680. const context = new JsEvalContext(strings);
  5681. jstProcess(context, document.body);
  5682. onDocumentLoadOrUpdate();
  5683. }
  5684. // Adds an icon class to the list and removes classes previously set.
  5685. function updateIconClass(newClass) {
  5686. const frameSelector = isSubFrame ? '#sub-frame-error' : '#main-frame-error';
  5687. const iconEl = document.querySelector(frameSelector + ' .icon');
  5688. if (iconEl.classList.contains(newClass)) {
  5689. return;
  5690. }
  5691. iconEl.className = 'icon ' + newClass;
  5692. }
  5693. // Implements button clicks. This function is needed during the transition
  5694. // between implementing these in trunk chromium and implementing them in iOS.
  5695. function reloadButtonClick(url) {
  5696. if (window.errorPageController) {
  5697. //
  5698. //
  5699. window.errorPageController.reloadButtonClick();
  5700. //
  5701. } else {
  5702. window.location = url;
  5703. }
  5704. }
  5705. function downloadButtonClick() {
  5706. if (window.errorPageController) {
  5707. window.errorPageController.downloadButtonClick();
  5708. const downloadButton = document.getElementById('download-button');
  5709. downloadButton.disabled = true;
  5710. /** @suppress {missingProperties} */
  5711. downloadButton.textContent = downloadButton.disabledText;
  5712. document.getElementById('download-link-wrapper')
  5713. .classList.add(HIDDEN_CLASS);
  5714. document.getElementById('download-link-clicked-wrapper')
  5715. .classList.remove(HIDDEN_CLASS);
  5716. }
  5717. }
  5718. function detailsButtonClick() {
  5719. if (window.errorPageController) {
  5720. window.errorPageController.detailsButtonClick();
  5721. }
  5722. }
  5723. // clang-format off
  5724. //
  5725. function setAutoFetchState(scheduled, can_schedule) {
  5726. document.getElementById('cancel-save-page-button')
  5727. .classList.toggle(HIDDEN_CLASS, !scheduled);
  5728. document.getElementById('save-page-for-later-button')
  5729. .classList.toggle(HIDDEN_CLASS, scheduled || !can_schedule);
  5730. }
  5731. function savePageLaterClick() {
  5732. window.errorPageController.savePageForLater();
  5733. // savePageForLater will eventually trigger a call to setAutoFetchState() when
  5734. // it completes.
  5735. }
  5736. function cancelSavePageClick() {
  5737. window.errorPageController.cancelSavePage();
  5738. // setAutoFetchState is not called in response to cancelSavePage(), so do it
  5739. // now.
  5740. setAutoFetchState(false, true);
  5741. }
  5742. function toggleErrorInformationPopup() {
  5743. document.getElementById('error-information-popup-container')
  5744. .classList.toggle(HIDDEN_CLASS);
  5745. }
  5746. function launchDownloadsPage() {
  5747. window.errorPageController.launchDownloadsPage();
  5748. }
  5749. function toggleOfflineContentListVisibility(updatePref) {
  5750. if (!loadTimeData.valueExists('offlineContentList')) {
  5751. return;
  5752. }
  5753. const contentListElement = document.getElementById('offline-content-list');
  5754. const isVisible = !contentListElement.classList.toggle('list-hidden');
  5755. if (updatePref && window.errorPageController) {
  5756. window.errorPageController.listVisibilityChanged(isVisible);
  5757. }
  5758. }
  5759. // Called on document load, and from updateForDnsProbe().
  5760. function onDocumentLoadOrUpdate() {
  5761. const downloadButtonVisible = loadTimeData.valueExists('downloadButton') &&
  5762. loadTimeData.getValue('downloadButton').msg;
  5763. const detailsButton = document.getElementById('details-button');
  5764. // If offline content suggestions will be visible, the usual buttons will not
  5765. // be presented.
  5766. const offlineContentVisible =
  5767. loadTimeData.valueExists('suggestedOfflineContentPresentation');
  5768. if (offlineContentVisible) {
  5769. document.querySelector('.nav-wrapper').classList.add(HIDDEN_CLASS);
  5770. detailsButton.classList.add(HIDDEN_CLASS);
  5771. document.getElementById('download-link').hidden = !downloadButtonVisible;
  5772. document.getElementById('download-links-wrapper')
  5773. .classList.remove(HIDDEN_CLASS);
  5774. document.getElementById('error-information-popup-container')
  5775. .classList.add('use-popup-container', HIDDEN_CLASS);
  5776. document.getElementById('error-information-button')
  5777. .classList.remove(HIDDEN_CLASS);
  5778. }
  5779. loadTimeData.valueExists('attemptAutoFetch') &&
  5780. loadTimeData.getValue('attemptAutoFetch');
  5781. const reloadButtonVisible = loadTimeData.valueExists('reloadButton') &&
  5782. loadTimeData.getValue('reloadButton').msg;
  5783. const reloadButton = document.getElementById('reload-button');
  5784. const downloadButton = document.getElementById('download-button');
  5785. if (reloadButton.style.display === 'none' &&
  5786. downloadButton.style.display === 'none') {
  5787. detailsButton.classList.add('singular');
  5788. }
  5789. // Show or hide control buttons.
  5790. const controlButtonDiv = document.getElementById('control-buttons');
  5791. controlButtonDiv.hidden =
  5792. offlineContentVisible || !(reloadButtonVisible || downloadButtonVisible);
  5793. const iconClass = loadTimeData.valueExists('iconClass') &&
  5794. loadTimeData.getValue('iconClass');
  5795. updateIconClass(iconClass);
  5796. if (!isSubFrame && iconClass === 'icon-offline') {
  5797. document.documentElement.classList.add('offline');
  5798. new Runner('.interstitial-wrapper');
  5799. }
  5800. }
  5801. function onDocumentLoad() {
  5802. // `loadTimeDataRaw` is injected to the `window` scope from C++.
  5803. loadTimeData.data = window.loadTimeDataRaw;
  5804. jstProcess(new JsEvalContext(window.loadTimeDataRaw), document.body);
  5805. // Sets up the proper button layout for the current platform.
  5806. const buttonsDiv = document.getElementById('buttons');
  5807. {
  5808. buttonsDiv.classList.add('suggested-left');
  5809. }
  5810. onDocumentLoadOrUpdate();
  5811. }
  5812. // Expose methods that are triggered either
  5813. // - By `onclick=...` handlers in the HTML code, OR
  5814. // - By `href="javascript:..."` in localized links.
  5815. // - By inected JS code coming from C++
  5816. //
  5817. // since those need to be available on the 'window' object.
  5818. Object.assign(window, {
  5819. cancelSavePageClick,
  5820. detailsButtonClick,
  5821. diagnoseErrors,
  5822. downloadButtonClick,
  5823. launchDownloadsPage,
  5824. portalSignin,
  5825. reloadButtonClick,
  5826. savePageLaterClick,
  5827. toggleErrorInformationPopup,
  5828. toggleHelpBox,
  5829. toggleOfflineContentListVisibility,
  5830. updateForDnsProbe,
  5831. });
  5832. document.addEventListener('DOMContentLoaded', onDocumentLoad);
  5833. //# sourceMappingURL=neterror.rollup.js.map
  5834. </script>
  5835. </head>
  5836. <body class="neterror" style="font-family: 'Segoe UI', Tahoma, sans-serif; font-size: 75%" jstcache="0">
  5837. <div id="main-frame-error" class="interstitial-wrapper" jstcache="0">
  5838. <div id="main-content" jstcache="0">
  5839. <div class="icon icon-generic" jstcache="0"></div>
  5840. <div id="main-message" jstcache="0">
  5841. <h1 jstcache="0">
  5842. <span jsselect="heading" jsvalues=".innerHTML:msg" jstcache="9">Não é possível acessar esse site</span>
  5843. <a id="error-information-button" class="hidden" onclick="toggleErrorInformationPopup();" jstcache="0"></a>
  5844. </h1>
  5845. <p jsselect="summary" jsvalues=".innerHTML:msg" jstcache="1">A página <strong jscontent="failedUrl" jstcache="22">https://uqload.xyz/administrator/login</strong> pode estar temporariamente indisponível ou pode ter sido movida permanentemente para um novo endereço da Web.</p>
  5846. <!--The suggestion list and error code are normally presented inline,
  5847. in which case error-information-popup-* divs have no effect. When
  5848. error-information-popup-container has the use-popup-container class, this
  5849. information is provided in a popup instead.-->
  5850. <div id="error-information-popup-container" jstcache="0">
  5851. <div id="error-information-popup" jstcache="0">
  5852. <div id="error-information-popup-box" jstcache="0">
  5853. <div id="error-information-popup-content" jstcache="0">
  5854. <div id="suggestions-list" style="display:none" jsdisplay="(suggestionsSummaryList &amp;&amp; suggestionsSummaryList.length)" jstcache="16">
  5855. <p jsvalues=".innerHTML:suggestionsSummaryListHeader" jstcache="18"></p>
  5856. <ul jsvalues=".className:suggestionsSummaryList.length == 1 ? 'single-suggestion' : ''" jstcache="19">
  5857. <li jsselect="suggestionsSummaryList" jsvalues=".innerHTML:summary" jstcache="21"></li>
  5858. </ul>
  5859. </div>
  5860. <div class="error-code" jscontent="errorCode" jstcache="17">ERR_QUIC_PROTOCOL_ERROR</div>
  5861. <p id="error-information-popup-close" jstcache="0">
  5862. <a class="link-button" jscontent="closeDescriptionPopup" onclick="toggleErrorInformationPopup();" jstcache="20">null</a>
  5863. </p>
  5864. </div>
  5865. </div>
  5866. </div>
  5867. </div>
  5868. <div id="download-links-wrapper" class="hidden" jstcache="0">
  5869. <div id="download-link-wrapper" jstcache="0">
  5870. <a id="download-link" class="link-button" onclick="downloadButtonClick()" jsselect="downloadButton" jscontent="msg" jsvalues=".disabledText:disabledMsg" jstcache="6" style="display: none;">
  5871. </a>
  5872. </div>
  5873. <div id="download-link-clicked-wrapper" class="hidden" jstcache="0">
  5874. <div id="download-link-clicked" class="link-button" jsselect="downloadButton" jscontent="disabledMsg" jstcache="11" style="display: none;">
  5875. </div>
  5876. </div>
  5877. </div>
  5878. <div id="save-page-for-later-button" class="hidden" jstcache="0">
  5879. <a class="link-button" onclick="savePageLaterClick()" jsselect="savePageLater" jscontent="savePageMsg" jstcache="10" style="display: none;">
  5880. </a>
  5881. </div>
  5882. <div id="cancel-save-page-button" class="hidden" onclick="cancelSavePageClick()" jsselect="savePageLater" jsvalues=".innerHTML:cancelMsg" jstcache="4" style="display: none;">
  5883. </div>
  5884. <div id="offline-content-list" class="list-hidden" hidden="" jstcache="0">
  5885. <div id="offline-content-list-visibility-card" onclick="toggleOfflineContentListVisibility(true)" jstcache="0">
  5886. <div id="offline-content-list-title" jsselect="offlineContentList" jscontent="title" jstcache="12" style="display: none;">
  5887. </div>
  5888. <div jstcache="0">
  5889. <div id="offline-content-list-show-text" jsselect="offlineContentList" jscontent="showText" jstcache="14" style="display: none;">
  5890. </div>
  5891. <div id="offline-content-list-hide-text" jsselect="offlineContentList" jscontent="hideText" jstcache="15" style="display: none;">
  5892. </div>
  5893. </div>
  5894. </div>
  5895. <div id="offline-content-suggestions" jstcache="0"></div>
  5896. <div id="offline-content-list-action" jstcache="0">
  5897. <a class="link-button" onclick="launchDownloadsPage()" jsselect="offlineContentList" jscontent="actionText" jstcache="13" style="display: none;">
  5898. </a>
  5899. </div>
  5900. </div>
  5901. </div>
  5902. </div>
  5903. <div id="buttons" class="nav-wrapper suggested-left" jstcache="0">
  5904. <div id="control-buttons" hidden="" jstcache="0">
  5905. <button id="reload-button" class="blue-button text-button" onclick="reloadButtonClick(this.url);" jsselect="reloadButton" jsvalues=".url:reloadUrl" jscontent="msg" jstcache="5" style="display: none;"></button>
  5906. <button id="download-button" class="blue-button text-button" onclick="downloadButtonClick()" jsselect="downloadButton" jscontent="msg" jsvalues=".disabledText:disabledMsg" jstcache="6" style="display: none;">
  5907. </button>
  5908. </div>
  5909. <button id="details-button" class="secondary-button text-button small-link singular" onclick="detailsButtonClick(); toggleHelpBox()" jscontent="details" jsdisplay="(suggestionsDetails &amp;&amp; suggestionsDetails.length > 0) || diagnose" jsvalues=".detailsText:details; .hideDetailsText:hideDetails;" jstcache="2" style="display: none;"></button>
  5910. </div>
  5911. <div id="details" class="hidden" jstcache="0">
  5912. <div class="suggestions" jsselect="suggestionsDetails" jstcache="3" jsinstance="*0" style="display: none;">
  5913. <div class="suggestion-header" jsvalues=".innerHTML:header" jstcache="7"></div>
  5914. <div class="suggestion-body" jsvalues=".innerHTML:body" jstcache="8"></div>
  5915. </div>
  5916. </div>
  5917. </div>
  5918. <div id="sub-frame-error" jstcache="0">
  5919. <!-- Show details when hovering over the icon, in case the details are
  5920. hidden because they're too large. -->
  5921. <div class="icon" jstcache="0"></div>
  5922. <div id="sub-frame-error-details" jsselect="summary" jsvalues=".innerHTML:msg" jstcache="1">A página <strong jscontent="failedUrl" jstcache="22">https://uqload.xyz/administrator/login</strong> pode estar temporariamente indisponível ou pode ter sido movida permanentemente para um novo endereço da Web.</div>
  5923. </div>
  5924. <div id="offline-resources" jstcache="0">
  5925. <img id="offline-resources-1x" src="data:image/png;base64,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" jstcache="0">
  5926. <img id="offline-resources-2x" src="data:image/png;base64,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" jstcache="0">
  5927. <template id="audio-resources" jstcache="0">
  5928. <audio id="offline-sound-press" src="data:audio/mpeg;base64,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"></audio>
  5929. <audio id="offline-sound-hit" src="data:audio/mpeg;base64,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"></audio>
  5930. <audio id="offline-sound-reached" src="data:audio/mpeg;base64,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"></audio>
  5931. </template>
  5932. </div>
  5933. <script jstcache="0">var loadTimeDataRaw = {"details":"Saiba mais","errorCode":"ERR_QUIC_PROTOCOL_ERROR","fontfamily":"'Segoe UI', Tahoma, sans-serif","fontfamilyMd":"'Segoe UI', Tahoma, sans-serif","fontsize":"75%","heading":{"hostName":"uqload.xyz","msg":"Não é possível acessar esse site"},"hideDetails":"Ocultar detalhes","iconClass":"icon-generic","language":"pt","suggestionsDetails":[],"suggestionsSummaryList":[],"summary":{"failedUrl":"https://uqload.xyz/administrator/login","hostName":"uqload.xyz","msg":"A página \u003Cstrong jscontent=\"failedUrl\">\u003C/strong> pode estar temporariamente indisponível ou pode ter sido movida permanentemente para um novo endereço da Web."},"textdirection":"ltr","title":"uqload.xyz"};</script></body></html>

comments powered by Disqus