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