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