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