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