· 6 years ago · Apr 01, 2019, 10:56 AM
1<html i18n-values="dir:textdirection;lang:language" dir="ltr" lang="en"><head>
2 <meta property="og:image" content="https://t2.ftcdn.net/jpg/01/85/41/07/400_F_185410729_WZd2RoOcAwhOQevqHRCRcGSVJDNevJuH.jpg
3">
4 <meta charset="utf-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0,
6 maximum-scale=1.0, user-scalable=no">
7 <title i18n-content="title"> Hacked by Mr.TirexGame</title>
8 <style>/* Copyright 2014 The Chromium Authors. All rights reserved.
9 Use of this source code is governed by a BSD-style license that can be
10 found in the LICENSE file. */
11
12a {
13 color: #585858;
14}
15
16.bad-clock .icon {
17 background-image: -webkit-image-set(
18 url(data:image/png;base64,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) 1x,
19 url(data:image/png;base64,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) 2x);
20}
21
22body {
23 background-color: #f7f7f7;
24 color: #646464;
25}
26
27body.safe-browsing {
28 background-color: rgb(206, 52, 38);
29 color: white;
30}
31
32button {
33 background: rgb(76, 142, 250);
34 border: 0;
35 border-radius: 2px;
36 box-sizing: border-box;
37 color: #fff;
38 cursor: pointer;
39 float: right;
40 font-size: .875em;
41 margin: 0;
42 padding: 10px 24px;
43 transition: box-shadow 200ms cubic-bezier(0.4, 0, 0.2, 1);
44}
45
46[dir='rtl'] button {
47 float: left;
48}
49
50button:active {
51 background: rgb(50, 102, 213);
52 outline: 0;
53}
54
55button:hover {
56 box-shadow: 0 1px 3px rgba(0, 0, 0, .50);
57}
58
59#debugging {
60 display: inline;
61 overflow: auto;
62}
63
64.debugging-content {
65 line-height: 1em;
66 margin-bottom: 0;
67 margin-top: 1em;
68}
69
70.debugging-title {
71 font-weight: bold;
72}
73
74#details {
75 color: #696969;
76 margin: 45px 0 50px;
77}
78
79#details p:not(:first-of-type) {
80 margin-top: 20px;
81}
82
83#details-button {
84 background: inherit;
85 border: 0;
86 float: none;
87 margin: 0;
88 padding: 10px 0;
89 text-decoration: underline;
90}
91
92#details-button:hover {
93 box-shadow: inherit;
94}
95
96.error-code {
97 color: #777;
98 display: inline;
99 font-size: .86667em;
100 margin-top: 15px;
101 opacity: .5;
102 text-transform: uppercase;
103}
104
105#error-debugging-info {
106 font-size: 0.8em;
107}
108
109h1 {
110 color: #333;
111 font-size: 1.6em;
112 font-weight: normal;
113 line-height: 1.25em;
114 margin-bottom: 16px;
115}
116
117h2 {
118 font-size: 1.2em;
119 font-weight: normal;
120}
121
122.hidden {
123 display: none;
124}
125
126html {
127 -webkit-text-size-adjust: 100%;
128 font-size: 125%;
129}
130
131.icon {
132 background-repeat: no-repeat;
133 background-size: 100%;
134 height: 72px;
135 margin: 0 0 40px;
136 width: 72px;
137}
138
139input[type=checkbox] {
140 visibility: hidden;
141}
142
143.interstitial-wrapper {
144 box-sizing: border-box;
145 font-size: 1em;
146 line-height: 1.6em;
147 margin: 100px auto 0;
148 max-width: 600px;
149 width: 100%;
150}
151
152#main-message > p {
153 display: inline;
154}
155
156#malware-opt-in {
157 font-size: .875em;
158 margin-top: 39px;
159}
160
161.nav-wrapper {
162 margin-top: 51px;
163}
164
165.nav-wrapper::after {
166 clear: both;
167 content: '';
168 display: table;
169 width: 100%;
170}
171
172#opt-in-label {
173 -webkit-margin-start: 32px;
174}
175
176.safe-browsing :-webkit-any(
177 a, #details, #details-button, h1, h2, p, .small-link) {
178 color: white;
179}
180
181.safe-browsing button {
182 background-color: rgba(255, 255, 255, .15);
183}
184
185.safe-browsing button:active {
186 background-color: rgba(255, 255, 255, .25);
187}
188
189.safe-browsing button:hover {
190 box-shadow: 0 2px 3px rgba(0, 0, 0, .5);
191}
192
193.safe-browsing .error-code {
194 display: none;
195}
196
197.safe-browsing .icon {
198 background-image: -webkit-image-set(
199 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAABV7bNHAAAGOklEQVR4AezQwQmAQAxE0S3BWj1b4VainixAGUG8B2RH+DAfQm4JvPa9lFJKKaUkaZLU9V/9/dncM+JIjQNG8uPAkcw4fCQzDh/JjMNHMuPwkcw4fCQzDh/JjMNHMuPwkcw4fCQzDh/JjwNHKnCCVOAEqcAJ0nCca191LPOzR3VuN3V21tTGEUUBWD8hfyOxY2c1YLwJbMAGLDB4kfNAHLKUq5JX/8HsArFICJAQQhJaRgv7Viypmz4tnWLcNYxUqSll/HCq7devzr09aspSefcLzo4jBbzG2f7ptdTG+mXrx9eeIAGlODUh2aFeyX4TkqN8zmskdyAvcYBSHetX6cNJJE9wNgZv66y/GvEYqQNAxKkBx8h/RQJOwcDJDPTI+qNuSb8EUvbDAcLOYWvsDaqNt4/khpMhjkpaASGpt1Nyenr6YQBx95gwdqz6D2H5p2K1N1bfTrA1QFFAzeY87JI15MWwrP7+q2QyGSD5GMhE4g4yUgn1iRUKSu37sAKwWjSHOPbWdKk0gFLPn0jyj98kmUxKKpWSjY0NOTs78zWQsYvCjRY126NxngZ1SqMPpDr9ChCtcdgaFbQGZ3LysW4OcdbX13U2NzeB5G8gc2EDhikrGARAxZH7Yr15QSTnncNlzH3Tf0uPlYmDEUPQomKxCCT/AxGprpqE1pR1c4hzTwrDjZTePAcMcS5bAxyk2RxkVTWHY0UcwiC5XE63qFwuy/n5uc+BiFSx9M4p2ZpDnM0ndyX/+I5axpPOO8eGg53j1hzioEGlUklqtRqQ/A7E0bGk+t3L93DyCiencBA9UkOXtxWCRYxgrFrh5PN5HeJYliWVSkXq9TqQfA/E/YKdg9YgTaBe5w9AW3OSxlil02nCEAfNMXEQtIhI/gciUlntnDyaM9Qax2yOicOx4t4hTrVa1Tg4ke3tbSD5H4hI2DnAQcydg7iNlYlTKBRMHEYjsUVAuri48D+QeVuZX8fAQThaxCGQOVaI2RyOF3G2trZ0dnZ2gORbIOI4jhVCoGTf17KqsjI5JGt//0kYx4UMDDuO2RycaA+zu7sLJL8BEeeZEw5hLnGQoAIKfiXLE4OSmYm0O1YIcdge4rBFRPIHkPmeAxhz5wAo2cRBNMyDLyVx/wud5WeDko3OOt5WdhSGMNw9CGCIg+zt7QHpfwfiWNlxuHOQy32DpYzmKBw70NK9zyWukhgfkNzcLMeKMXeO2RxXoP39fSB1Hsj1txXi8J2DYOcAxsRBYnc/k/jYIynFFx13Dv/NhewEY+IcHBwgQEICbvEcyOk9B3lv55jfOZG/9M7ROEgTBlnsvSmLd25KLKSQlmJEMsfKsTkMcRgCHR4eIgG3eArk9p5j/wg0v5BxlWMhrygktgYBzIICmr/9qc5i6KGUE/Erx4rLGOHNRRycbE/ngVq/5yBXvufoqxyZnZGl8QEbzg2ZV5lTOEi0+7osPO0D0pVjRRjGbA0DrI4BtX7Pae+Hp/75sDAPJOAgqjk3JNpzXQdAM13XZH40KJXlBFvjOFIMIBAT6OjoiAm4xRMg693Pnrzn4DsHt1UxtqAXs25OT6M5s93XFM4nErnVSGw63GrnEMdxtDoKdG6VJRMe9eQ9h9d4Mb6od469OREAqURHglJdWXYbK8atOXJ8fIwEXOLdDsIf8/D3Ki/ec3hL4dbCzkF72JzoMMdL42DECIP/E4atcWwOTuCcnJwgAbcAKOIlEt6OvXjPIRIW8txIUOF8bDaHcV3IhLG3x4YTaQfoI2+RsrL2dsqT9xxe51ZiSeLT4X+LtYMUikEgCKL3v6VHCbXIpkESKZtZBP/fPgadaX1xshFkzarhd1ZOAv1+vHAbics8QK7kOTl07irnx2bMf2AS5xuohbTWknnOvs/JDjmqJoGomsCZf4LHPRUgPs8JnOxzDvYc8QSvhwSKzXO+msB3BScrhzVwBFAJCRSb5+SGvJut+FzlDDwk53YBhCt5TgLtKidPKw/URwLB5jmbwVPgCKAWks1z8qQSOAKoiQTCpTxH9TkOqI/k8hzRIXugPhJ5MCAqzwHJn1YKqI8ExEGeIzZkATSNBMhhniMqRwBNIx3mOawCRwANIdVnKw80j+TznD7QPBIoPs/x3wMVVEyov/hylQAAAABJRU5ErkJggg==) 1x,
200 url(data:image/png;base64,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) 2x);
201}
202
203.small-link {
204 color: #696969;
205 font-size: .875em;
206}
207
208.ssl .icon {
209 background-image: -webkit-image-set(
210 url(data:image/png;base64,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) 1x,
211 url(data:image/png;base64,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) 2x);
212}
213
214.captive-portal .icon {
215 background-image: -webkit-image-set(
216 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAQAAAD/5HvMAAAFDElEQVRo3u2afWhWVRzHz2bNkhRDKyRb6WxiK5m1sP5YDXoZT9tzn3u+P8voBVy4iCyaQkqBrkZJRUhQMYPUyjmQcsNeEFs10KEbKr1YA6M0Z24Mp8IM99TjdvvjOc997vu597n3WQt27j9j55x7P8/v/M7v/F4OY5Ntsk22yfY/a8p0NcbX0RZ04TcM0kWkcB59OIp2vMnreKnjpEK1HA1owxH00QhdoOPoQQtWKgtCodDNeAUHkSLN68Fp86zYNViPftfRvfyZ+LTAKGVFWEnd3iDZxyhNvEsj0hnn+Do2xTfM3Veigf70C2MEomo6aeoZxTHaR7uwGwfxh2XW4cRt/pZJtU31CRSbil59YZLYCh6fR9VYgbW0Rn2C36uW4El8jjF9TJsUJj4bXwaFMUpILcEQaUjidZqP59FDl0zjRtCB5YlbsI000ugnZbpsNy3GiVxwjDqUqMR+fiutobOuSn2Cg+5HD26U4HDQX7nhGIEYKyuifbLx2CxbqwLakF3bcECMYa2u0h30IlUrS3Anr0UTHRL/v6TGvHEKsSN3GDsQY/QRaWjhi7AMH+MYhpHEKXyFBtRQJ2l8tWxfNYfDsQOVFan3gVtMgEYaXcRGUiU4aAyLYwdirOoy6nQxiaXe0nkojO64AzGmzqSfSSMNwzhAe3E6bZ0SlZ44iRswFB7HGYixeDGO8LrEUr6KP8VLlQrsx3LZcnVEgeMGxBibgrbMjuOvSe0yfzQaHOtpb/rJL4sxY7xW6uW4uwgBn1ZPC7eLNNJog1Q+eCkinLOYK/nhvdjNCmTH6DSciWCx+qnVG4cxxpQFsRlyJ+OFSJU3fKMfJxSQWh7x9g7b+FviE/9MECD8kP4Af5X+ngBA8dni/BpVZ+KxIGdZKCFsFm95w8k3THcdMrlTeQbiD4u3dNtZ14uuTWLHvTcuEpojbNewvUv4h7xe9xjbx0OHcN7lLRnfFvcYQsMD+VfiTCRsJxXBjnqTSdF/zTcQPnUDEk4Zn2Xin4/BPEtoi9uSCdtTVmQ5BCuskVnE5vidgECMocYc/I4TkPOSiUn1/8GSOSm1obcpoE90ShaHSpXavu0tE7cFROoLu+2thtHS7ric9ubDC3A1jNajw8kPpu+jBvI4OsyHq9v0IHm0kIer0f3weMEiOhephNzdj6yDpj7iGWhXIhkZUIG+tx/0cmG/kInZn/sm50lUZhIyjjnqjJOPFL9WgrQ6GiB8KL64QxIG4W2p9YgAKF6sJ9OrJYEikrJsaBRA+ESMPOmav8+G0mjJN5BantFEvspHsgFjuD0skHguUDevt6cV8LXoH6i6wl86ptMrNxE4AfEt5lhcmkzPs74TVtjoIcn+4EjZH6iW6Dn9w6xQvhn1lB5/3FVCrTlkAZ4W5ZgZejlmVKnwYT4NSc8R3OUCPTeHxGi3yDDu0aXW6NeLy6aFB9wMQO31tB19SFlqO57qbXRZSaNvfCyXQ+J8wE1KwRU8NlW3PRodl50HrqUFJLEiGiD06H8NBi/8mosvm9yroTlkIYeUJbkEBAVoNJQeuxJLI7JJR+Pzco+aTAU8fKYsDAuE9qqrwl0YMJU4kcIH8eLcgdAkzU37yq2ZisAYw3e8TrkuKBDOEEUXXzqVyX+hZnqOP+ALZ5Saa66ONOQNfpHAALNTWZyXxGiwqxbpDY73JdXCCJbP52UU2s4T9lxK3lr6ug62ogu/26/rOJmHYO1faMmeMhUfGLMAAAAASUVORK5CYII=) 1x,
217 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQCAMAAADQmBKKAAACfFBMVEUAAABSUlJSUlIrKytSUlJTU1NAQEBSUlJSUlIAAABTU1NTU1NSUlJTU1NSUlJTU1NQUFBSUlJQUFBTU1NTU1NSUlJTU1MAAABPT09SUlJSUlJQUFBRUVEzMzNSUlJTU1NSUlJTU1NQUFBPT09SUlJSUlJSUlJRUVFSUlJRUVFTU1NSUlJTU1NAQEBTU1NSUlJTU1NTU1NTU1NJSUlPT09OTk5SUlJPT09TU1NRUVFSUlJTU1NKSkpTU1NRUVFPT09RUVFSUlJOTk5SUlJSUlJSUlJRUVFSUlJSUlJGRkZRUVFSUlJOTk5SUlJTU1NRUVFQUFBTU1NSUlJHR0dOTk5SUlJSUlJPT09SUlJTU1NRUVEAAABRUVFSUlJNTU1TU1NTU1NSUlJTU1NTU1NTU1NRUVFRUVFTU1NOTk5SUlJTU1NAQEBRUVFTU1NSUlJTU1NQUFBSUlJSUlJTU1NTU1NSUlJSUlJSUlJTU1NSUlJTU1NQUFBSUlJSUlJTU1NTU1NSUlJSUlJSUlJSUlJQUFBTU1NTU1NTU1NSUlJTU1NLS0tSUlJQUFBSUlJTU1NNTU1SUlJRUVFSUlJSUlJSUlJSUlJTU1NSUlJSUlJSUlJTU1NTU1NTU1NRUVFTU1NSUlJTU1NSUlJSUlJSUlJTU1NTU1NTU1M5OTlQUFBTU1NSUlJSUlJQUFBTU1NQUFBTU1NSUlJRUVFSUlJSUlJSUlJSUlJSUlJTU1NSUlJNTU1SUlJSUlJSUlJERERRUVFRUVFSUlJRUVFSUlJRUVFRUVFRUVFSUlJOTk5RUVFSUlJJSUlSUlJSUlJRUVFSUlJSUlJSUlJMTExSUlJTU1MPbK0hAAAA03RSTlMA/uwG+/kE6doCwszG85X6IEtDudLEiwE3H7IQFgV87d/UST0crLssjDkr+KkM3jiz9CIVHQ2oOoher+oYckIqZfI0iT6rhNm1C1vvJIaOazD9hRIaw90tgsWHA3G9IduwMvzn1RNf9hfAUwh0frhHXOZ52Mhz5WD3ruFWocFKdZxU1ztAl6140L8RtDNOaQoZYeB/Z13w4/W6bOSUjaOWoFen4stvnQk2poPHI5FZmspi6301krHuWh52n+gPWE9qJp4vkEWbJzxwDphkgcmApRtj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) 2x);
218}
219
220.styled-checkbox {
221 float: left;
222 height: 16px;
223 margin-top: .36em;
224 position: relative;
225 width: 16px;
226}
227
228[dir='rtl'] .styled-checkbox {
229 float: right;
230}
231
232.styled-checkbox label {
233 background: transparent;
234 border: white solid 1px;
235 border-radius: 2px;
236 height: 14px;
237 left: 0;
238 position: absolute;
239 right: 0;
240 top: 0;
241 width: 14px;
242}
243
244.styled-checkbox .checkbox-tick {
245 background: transparent;
246 border: 2px solid white;
247 border-right-width: 0;
248 border-top-width: 0;
249 content: '';
250 height: 4px;
251 left: 2px;
252 opacity: 0;
253 position: absolute;
254 top: 3px;
255 transform: rotate(-45deg);
256 width: 9px;
257}
258
259.styled-checkbox input[type=checkbox]:checked ~ .checkbox-tick {
260 opacity: 1;
261}
262
263@media (max-width: 700px) {
264 .interstitial-wrapper {
265 padding: 0 10%;
266 }
267
268 #error-debugging-info {
269 overflow: auto;
270 }
271}
272
273@media (max-height: 600px) {
274 .error-code {
275 margin-top: 10px;
276 }
277
278 .interstitial-wrapper {
279 margin-top: 13%;
280 }
281}
282
283@media (max-width: 420px) {
284 button,
285 [dir='rtl'] button,
286 .small-link {
287 float: none;
288 font-size: .825em;
289 font-weight: 400;
290 margin: 0;
291 text-transform: uppercase;
292 width: 100%;
293 }
294
295 #details {
296 margin: 20px 0 20px 0;
297 }
298
299 #details p:not(:first-of-type) {
300 margin-top: 10px;
301 }
302
303 #details-button {
304 display: block;
305 margin-top: 20px;
306 text-align: center;
307 width: 100%;
308 }
309
310 .interstitial-wrapper {
311 padding: 0 5%;
312 }
313
314 #malware-opt-in {
315 margin-top: 24px;
316 }
317
318 .nav-wrapper {
319 margin-top: 30px;
320 }
321}
322
323/**
324 * Mobile specific styling.
325 * Navigation buttons are anchored to the bottom of the screen.
326 * Details message replaces the top content in its own scrollable area.
327 */
328
329@media (max-width: 420px) and (orientation: portrait) {
330 #details-button {
331 border: 0;
332 margin: 8px 0 0;
333 }
334
335 .secondary-button {
336 -webkit-margin-end: 0;
337 margin-top: 16px;
338 }
339}
340
341/* Fixed nav. */
342@media (min-width: 240px) and (max-width: 420px) and
343 (min-height: 401px) and (orientation:portrait),
344 (min-width: 421px) and (max-width: 736px) and (min-height: 240px) and
345 (max-height: 420px) and (orientation:landscape) {
346
347 body .nav-wrapper {
348 background: #f7f7f7;
349 bottom: 0;
350 left: 0;
351 margin: 0;
352 max-width: 736px;
353 position: fixed;
354 z-index: 1;
355 }
356
357 body.safe-browsing .nav-wrapper {
358 background: rgb(206, 52, 38);
359 }
360
361 .interstitial-wrapper {
362 max-width: 736px;
363 }
364}
365
366@media (max-width: 420px) and (orientation: portrait),
367 (max-width: 736px) and (max-height: 420px) and (orientation: landscape) {
368 body {
369 margin: 0 auto;
370 }
371
372 button,
373 [dir='rtl'] button,
374 button.small-link {
375 font-family: Roboto-Regular,Helvetica;
376 font-size: .933em;
377 font-weight: 600;
378 margin: 6px 0;
379 text-transform: uppercase;
380 }
381
382 .nav-wrapper {
383 box-sizing: border-box;
384 padding: 16px 24px 8px;
385 width: 100%;
386 }
387
388 .error-code {
389 margin-top: 0;
390 }
391
392 #details {
393 box-sizing: border-box;
394 height: auto;
395 margin: 0;
396 opacity: 1;
397 transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1);
398 }
399
400 #details.hidden,
401 #main-content.hidden {
402 display: block;
403 height: 0;
404 opacity: 0;
405 overflow: hidden;
406 }
407
408 #details-button {
409 padding-bottom: 16px;
410 padding-top: 16px;
411 }
412
413 h1 {
414 font-size: 1.5em;
415 margin-bottom: 8px;
416 }
417
418 .icon {
419 margin-bottom: 12px;
420 }
421
422 .interstitial-wrapper {
423 box-sizing: border-box;
424 margin: 24px auto 12px;
425 padding: 0 24px;
426 position: relative;
427 }
428
429 .interstitial-wrapper p {
430 font-size: .95em;
431 line-height: 1.61em;
432 margin-top: 8px;
433 }
434
435 #main-content {
436 margin: 0;
437 transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1);
438 }
439
440 .small-link {
441 border: 0;
442 }
443
444 .suggested-left > #control-buttons,
445 .suggested-right > #control-buttons {
446 float: none;
447 margin: 0;
448 }
449}
450
451@media (min-height: 400px) and (orientation:portrait) {
452 body:not(.safe-browsing-has-checkbox) .interstitial-wrapper {
453 margin-bottom: 145px;
454 }
455}
456
457@media (min-height: 299px) and (orientation:portrait) {
458 .nav-wrapper {
459 padding-bottom: 16px;
460 }
461}
462
463@media (min-height: 405px) and (orientation:portrait) {
464 .icon {
465 margin-bottom: 24px;
466 }
467
468 .interstitial-wrapper {
469 margin-top: 64px;
470 }
471}
472
473@media (min-height: 480px) and (max-width: 420px) and (orientation: portrait),
474 (min-height: 338px) and (max-height: 420px) and (max-width: 736px) and
475 (orientation: landscape) {
476 .icon {
477 margin-bottom: 24px;
478 }
479
480 .nav-wrapper {
481 padding: 16px 24px 24px;
482 }
483}
484
485@media (min-height: 500px) and (max-width: 414px) and (orientation: portrait) {
486 :not(.safe-browsing-has-checkbox) .interstitial-wrapper {
487 margin-top: 96px;
488 }
489}
490
491/* Phablet sizing */
492@media (min-width: 375px) and (min-height: 641px) and
493 (max-width: 414px) and (orientation: portrait) {
494 button,
495 [dir='rtl'] button,
496 .small-link {
497 font-size: 1em;
498 padding-bottom: 12px;
499 padding-top: 12px;
500 }
501
502 body:not(.offline) .icon {
503 height: 80px;
504 width: 80px;
505 }
506
507 #details-button {
508 margin-top: 28px;
509 }
510
511 h1 {
512 font-size: 1.7em;
513 }
514
515 .icon {
516 margin-bottom: 28px;
517 }
518
519 .interstitial-wrapper {
520 padding: 28px;
521 }
522
523 .interstitial-wrapper p {
524 font-size: 1.05em;
525 }
526
527 .nav-wrapper {
528 padding: 28px;
529 }
530}
531
532@media (min-width: 420px) and (max-width: 736px) and
533 (min-height: 240px) and (max-height: 298px) and
534 (orientation:landscape) {
535 body:not(.offline) .icon {
536 height: 50px;
537 width: 50px;
538 }
539
540 .icon {
541 padding-top: 0;
542 }
543
544 .interstitial-wrapper {
545 margin-top: 16px;
546 }
547
548 .nav-wrapper {
549 padding: 0 24px 8px;
550 }
551}
552
553@media (min-width: 420px) and (max-width: 736px) and
554 (min-height: 240px) and (max-height: 420px) and
555 (orientation:landscape) {
556 #details-button {
557 margin: 0;
558 }
559
560 .interstitial-wrapper {
561 margin-bottom: 70px;
562 }
563
564 .nav-wrapper {
565 margin-top: 0;
566 }
567
568 #malware-opt-in {
569 margin-top: 0;
570 }
571}
572
573/* Phablet landscape */
574@media (min-width: 680px) and (max-height: 414px) {
575 .interstitial-wrapper {
576 margin: 24px auto;
577 }
578
579 .nav-wrapper {
580 margin: 16px auto 0;
581 }
582}
583
584@media (max-height: 240px) and (orientation: landscape),
585 (max-height: 480px) and (orientation: portrait),
586 (max-width: 419px) and (max-height: 323px) {
587 body:not(.offline) .icon {
588 height: 56px;
589 width: 56px;
590 }
591
592 .icon {
593 margin-bottom: 16px;
594 }
595}
596
597/* Small mobile screens. No fixed nav. */
598@media (max-height: 400px) and (orientation: portrait),
599 (max-height: 239px) and (orientation: landscape),
600 (max-width: 419px) and (max-height: 360px) {
601 .interstitial-wrapper {
602 display: flex;
603 flex-direction: column;
604 margin-bottom: 0;
605 }
606
607 #details {
608 flex: 1 1 auto;
609 order: 0;
610 }
611
612 #main-content {
613 flex: 1 1 auto;
614 order: 0;
615 }
616
617 .nav-wrapper {
618 flex: 0 1 auto;
619 margin-top: 8px;
620 order: 1;
621 padding-left: 0;
622 padding-right: 0;
623 position: relative;
624 width: 100%;
625 }
626}
627
628/* Malware opt-in. No fixed nav. */
629@media (max-height: 600px) and (orientation: portrait),
630 (max-height: 360px) and (max-width: 680px) and (orientation: landscape) {
631 .safe-browsing-has-checkbox .interstitial-wrapper {
632 display: flex;
633 flex-direction: column;
634 margin-bottom: 0;
635 }
636
637 .safe-browsing-has-checkbox #details {
638 flex: 1 1 auto;
639 order: 0;
640 }
641
642 .safe-browsing-has-checkbox #main-content {
643 flex: 1 1 auto;
644 order: 0;
645 }
646
647 .safe-browsing-has-checkbox #malware-opt-in {
648 margin-bottom: 8px;
649 }
650
651 body.safe-browsing-has-checkbox .nav-wrapper {
652 flex: 0 1 auto;
653 margin-top: 0;
654 order: 1;
655 padding-left: 0;
656 padding-right: 0;
657 position: relative;
658 width: 100%;
659 }
660}
661</style>
662 <style>/* Copyright 2013 The Chromium Authors. All rights reserved.
663 * Use of this source code is governed by a BSD-style license that can be
664 * found in the LICENSE file. */
665
666/* Don't use the main frame div when the error is in a subframe. */
667html[subframe] #main-frame-error {
668 display: none;
669}
670
671/* Don't use the subframe error div when the error is in a main frame. */
672html:not([subframe]) #sub-frame-error {
673 display: none;
674}
675
676#diagnose-button {
677 -webkit-margin-start: 0;
678 float: none;
679 margin-bottom: 10px;
680 margin-top: 20px;
681}
682
683h1 {
684 margin-top: 0;
685}
686
687h2 {
688 color: #666;
689 font-size: 1.2em;
690 font-weight: normal;
691 margin: 10px 0;
692}
693
694a {
695 color: rgb(17, 85, 204);
696 text-decoration: none;
697}
698
699.icon {
700 -webkit-user-select: none;
701 content: '';
702}
703
704.icon-generic {
705 /**
706 * Can't access chrome://theme/IDR_ERROR_NETWORK_GENERIC from an untrusted
707 * renderer process, so embed the resource manually.
708 */
709 content: -webkit-image-set(
710 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAENJREFUeF7tzbEJACEQRNGBLeAasBCza2lLEGx0CxFGG9hBMDDxRy/72O9FMnIFapGylsu1fgoBdkXfUHLrQgdfrlJN1BdYBjQQm3UAAAAASUVORK5CYII=) 1x,
711 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQAQMAAADdiHD7AAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAFJJREFUeF7t0cENgDAMQ9FwYgxG6WjpaIzCCAxQxVggFuDiCvlLOeRdHR9yzjncHVoq3npu+wQUrUuJHylSTmBaespJyJQoObUeyxDQb3bEm5Au81c0pSCD8HYAAAAASUVORK5CYII=) 2x);
712 padding-top: 20px;
713}
714
715.icon-offline {
716 content: -webkit-image-set(
717 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABIAQMAAABvIyEEAAAABlBMVEUAAABTU1OoaSf/AAAAAXRSTlMAQObYZgAAAGxJREFUeF7tyMEJwkAQRuFf5ipMKxYQiJ3Z2nSwrWwBA0+DQZcdxEOueaePp9+dQZFB7GpUcURSVU66yVNFj6LFICatThZB6r/ko/pbRpUgilY0Cbw5sNmb9txGXUKyuH7eV25x39DtJXUNPQGJtWFV+BT/QAAAAABJRU5ErkJggg==) 1x,
718 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJAAAACQBAMAAAAVaP+LAAAAGFBMVEUAAABTU1NNTU1TU1NPT09SUlJSUlJTU1O8B7DEAAAAB3RSTlMAoArVKvVgBuEdKgAAAJ1JREFUeF7t1TEOwyAMQNG0Q6/UE+RMXD9d/tC6womIFSL9P+MnAYOXeTIzMzMzMzMzaz8J9Ri6HoITmuHXhISE8nEh9yxDh55aCEUoTGbbQwjqHwIkRAEiIaG0+0AA9VBMaE89Rogeoww936MQrWdBr4GN/z0IAdQ6nQ/FIpRXDwHcA+JIJcQowQAlFUA0MfQpXLlVQfkzR4igS6ENjknm/wiaGhsAAAAASUVORK5CYII=) 2x);
719 position: relative;
720}
721
722.error-code {
723 display: block;
724}
725
726#content-top {
727 margin: 20px;
728}
729
730#help-box-inner {
731 background-color: #f9f9f9;
732 border-top: 1px solid #EEE;
733 color: #444;
734 padding: 20px;
735 text-align: start;
736}
737
738#suggestion {
739 margin-top: 15px;
740}
741
742#short-suggestion {
743 margin-top: 5px;
744}
745
746#sub-frame-error-details {
747 color: #8F8F8F;
748/* Not done on mobile for performance reasons. */
749 text-shadow: 0 1px 0 rgba(255,255,255,0.3);
750}
751
752[jscontent=failedUrl] {
753 overflow-wrap: break-word;
754}
755
756#search-container {
757 /* Prevents a space between controls. */
758 display: flex;
759 margin-top: 20px;
760}
761
762#search-box {
763 border: 1px solid #cdcdcd;
764 flex-grow: 1;
765 font-size: 16px;
766 height: 26px;
767 margin-right: 0;
768 padding: 1px 9px;
769}
770
771#search-box:focus {
772 border: 1px solid rgb(93, 154, 255);
773 outline: none;
774}
775
776#search-button {
777 border: none;
778 border-bottom-left-radius: 0;
779 border-top-left-radius: 0;
780 box-shadow: none;
781 display: flex;
782 height: 30px;
783 margin: 0;
784 padding: 0;
785 width: 60px;
786}
787
788#search-image {
789 content:
790 -webkit-image-set(
791 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAPCAQAAAB+HTb/AAAArElEQVR4Xn3NsUoCUBzG0XvB3U0chR4geo5qihpt6gkCx0bXFsMERWj2KWqIanAvmlUUoQapwU6g4l8H5bd9Z/iSPS0hu/RqZqrncBuzLl7U3Rn4cSpQFTeroejJl1Lgs7f4ceDPdeBMXYp86gaONYJkY83AnqHiGk9wHnjk16PKgo5N9BUCkzPf5j6M0PfuVg5MymoetFwoaKAlB26WdXAvJ7u5mezitqtkT//7Sv/u96CaLQAAAABJRU5ErkJggg==) 1x,
792 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAeCAQAAACVzLYUAAABYElEQVR4Xr3VMUuVURzH8XO98jgkGikENkRD0KRGDUVDQy0h2SiC4IuIiktL4AvQt1CDBJUJwo1KXXS6cWdHw7tcjWwoC5Hrx+UZgnNO5CXiO/75jD/+QZf9MzjskVU7DrU1zRv9G9ir5hsA4Nii83+GA9ZI1nI1D6tWAE1TRlQMuuuFDthzMQefgo4nKr+f3dIGDdUUHPYD1ISoMQdgJgUfgqaKEOcxWE/BVTArJBvwC0cGY7gNLgiZNsD1GP4EPVn4EtyLYRuczcJ34HYMP4E7GdajDS7FcB48z8AJ8FmI4TjouBkzZ2yBuRQMlsButIZ+dfDVUBqOaIHvavpLVHXfFmAqv45r9gEHNr3y3hcAfLSgSMPgiiZR+6Z9AMuKNAwqpjUcA2h55pxgAfBWkYRlQ254YMJloaxPHbCkiGCymL5RlLA7GnRDXyuC7uhicLoKdRyaDE5Pl00K//93nABqPgBDK8sfWgAAAABJRU5ErkJggg==) 2x);
793 margin: auto;
794}
795
796.secondary-button {
797 -webkit-margin-end: 16px;
798 background: #d9d9d9;
799 color: #696969;
800}
801
802.hidden {
803 display: none;
804}
805
806.suggestions {
807 margin-top: 18px;
808}
809
810.suggestion-header {
811 font-weight: bold;
812 margin-bottom: 4px;
813}
814
815.suggestion-body {
816 color: #777;
817}
818
819/* Increase line height at higher resolutions. */
820@media (min-width: 641px) and (min-height: 641px) {
821 #help-box-inner {
822 line-height: 18px;
823 }
824}
825
826/* Decrease padding at low sizes. */
827@media (max-width: 640px), (max-height: 640px) {
828 h1 {
829 margin: 0 0 15px;
830 }
831 #content-top {
832 margin: 15px;
833 }
834 #help-box-inner {
835 padding: 20px;
836 }
837 .suggestions {
838 margin-top: 10px;
839 }
840 .suggestion-header {
841 margin-bottom: 0;
842 }
843 .error-code {
844 margin-top: 10px;
845 }
846}
847
848/* Don't allow overflow when in a subframe. */
849html[subframe] body {
850 overflow: hidden;
851}
852
853#sub-frame-error {
854 -webkit-align-items: center;
855 background-color: #DDD;
856 display: -webkit-flex;
857 -webkit-flex-flow: column;
858 height: 100%;
859 -webkit-justify-content: center;
860 left: 0;
861 position: absolute;
862 top: 0;
863 transition: background-color .2s ease-in-out;
864 width: 100%;
865}
866
867#sub-frame-error:hover {
868 background-color: #EEE;
869}
870
871#sub-frame-error .icon-generic {
872 margin: 0 0 16px;
873}
874
875#sub-frame-error-details {
876 margin: 0 10px;
877 text-align: center;
878 visibility: hidden;
879}
880
881/* Show details only when hovering. */
882#sub-frame-error:hover #sub-frame-error-details {
883 visibility: visible;
884}
885
886/* If the iframe is too small, always hide the error code. */
887/* TODO(mmenke): See if overflow: no-display works better, once supported. */
888@media (max-width: 200px), (max-height: 95px) {
889 #sub-frame-error-details {
890 display: none;
891 }
892}
893
894/* Adjust icon for small embedded frames in apps. */
895@media (max-height: 100px) {
896 #sub-frame-error .icon-generic {
897 height: auto;
898 margin: 0;
899 padding-top: 0;
900 width: 25px;
901 }
902}
903
904/* details-button is special; it's a <button> element that looks like a link. */
905#details-button {
906 box-shadow: none;
907 min-width: 0;
908}
909
910/* Styles for platform dependent separation of controls and details button. */
911.suggested-left > #control-buttons,
912.suggested-left #stale-load-button,
913.suggested-right > #details-button {
914 float: left;
915}
916
917.suggested-right > #control-buttons,
918.suggested-right #stale-load-button,
919.suggested-left > #details-button {
920 float: right;
921}
922
923.suggested-left .secondary-button {
924 -webkit-margin-end: 0px;
925 -webkit-margin-start: 16px;
926}
927
928#details-button.singular {
929 float: none;
930}
931
932#buttons::after {
933 clear: both;
934 content: '';
935 display: block;
936 width: 100%;
937}
938
939/* Offline page */
940.offline .interstitial-wrapper {
941 color: #2b2b2b;
942 font-size: 1em;
943 line-height: 1.55;
944 margin: 0 auto;
945 max-width: 600px;
946 padding-top: 100px;
947 width: 100%;
948}
949
950.offline .runner-container {
951 height: 150px;
952 max-width: 600px;
953 overflow: hidden;
954 position: absolute;
955 top: 35px;
956 width: 44px;
957}
958
959.offline .runner-canvas {
960 height: 150px;
961 max-width: 600px;
962 opacity: 1;
963 overflow: hidden;
964 position: absolute;
965 top: 0;
966 z-index: 2;
967}
968
969.offline .controller {
970 background: rgba(247,247,247, .1);
971 height: 100vh;
972 left: 0;
973 position: absolute;
974 top: 0;
975 width: 100vw;
976 z-index: 1;
977}
978
979#offline-resources {
980 display: none;
981}
982
983@media (max-width: 420px) {
984 .suggested-left > #control-buttons,
985 .suggested-right > #control-buttons {
986 float: none;
987 }
988}
989
990@media (max-height: 350px) {
991 h1 {
992 margin: 0 0 15px;
993 }
994
995 .icon-offline {
996 margin: 0 0 10px;
997 }
998
999 .interstitial-wrapper {
1000 margin-top: 5%;
1001 }
1002
1003 .nav-wrapper {
1004 margin-top: 30px;
1005 }
1006}
1007
1008@media (min-width: 600px) and (max-width: 736px) and (orientation: landscape) {
1009 .offline .interstitial-wrapper {
1010 margin-left: 0;
1011 margin-right: 0;
1012 }
1013}
1014
1015@media (min-height: 240px) and (orientation: landscape) {
1016 .offline .interstitial-wrapper {
1017 margin-bottom: 90px;
1018 }
1019
1020 .icon-offline {
1021 margin-bottom: 20px;
1022 }
1023}
1024
1025@media (max-height: 320px) and (orientation: landscape) {
1026 .icon-offline {
1027 margin-bottom: 0;
1028 }
1029
1030 .offline .runner-container {
1031 top: 10px;
1032 }
1033}
1034
1035@media (max-width: 240px) {
1036 button {
1037 padding-left: 12px;
1038 padding-right: 12px;
1039 }
1040
1041 .interstitial-wrapper {
1042 overflow: inherit;
1043 padding: 0 8px;
1044 }
1045}
1046
1047@media (max-width: 120px) {
1048 button {
1049 width: auto;
1050 }
1051}</style>
1052 <script>// Copyright 2015 The Chromium Authors. All rights reserved.
1053// Use of this source code is governed by a BSD-style license that can be
1054// found in the LICENSE file.
1055
1056var mobileNav = false;
1057
1058/**
1059 * For small screen mobile the navigation buttons are moved
1060 * below the advanced text.
1061 */
1062function onResize() {
1063 var helpOuterBox = document.querySelector('#details');
1064 var mainContent = document.querySelector('#main-content');
1065 var mediaQuery = '(min-width: 240px) and (max-width: 420px) and ' +
1066 '(orientation: portrait), (max-width: 736px) and ' +
1067 '(max-height: 420px) and (orientation: landscape)';
1068 var detailsHidden = helpOuterBox.classList.contains('hidden');
1069 var runnerContainer = document.querySelector('.runner-container');
1070
1071 // Check for change in nav status.
1072 if (mobileNav != window.matchMedia(mediaQuery).matches) {
1073 mobileNav = !mobileNav;
1074
1075 // Handle showing the top content / details sections according to state.
1076 if (mobileNav) {
1077 mainContent.classList.toggle('hidden', !detailsHidden);
1078 helpOuterBox.classList.toggle('hidden', detailsHidden);
1079 if (runnerContainer) {
1080 runnerContainer.classList.toggle('hidden', !detailsHidden);
1081 }
1082 } else if (!detailsHidden) {
1083 // Non mobile nav with visible details.
1084 mainContent.classList.remove('hidden');
1085 helpOuterBox.classList.remove('hidden');
1086 if (runnerContainer) {
1087 runnerContainer.classList.remove('hidden');
1088 }
1089 }
1090 }
1091}
1092
1093function setupMobileNav() {
1094 window.addEventListener('resize', onResize);
1095 onResize();
1096}
1097
1098document.addEventListener('DOMContentLoaded', setupMobileNav);
1099</script>
1100 <script>// Copyright 2013 The Chromium Authors. All rights reserved.
1101// Use of this source code is governed by a BSD-style license that can be
1102// found in the LICENSE file.
1103
1104function toggleHelpBox() {
1105 var helpBoxOuter = document.getElementById('details');
1106 helpBoxOuter.classList.toggle('hidden');
1107 var detailsButton = document.getElementById('details-button');
1108 if (helpBoxOuter.classList.contains('hidden'))
1109 detailsButton.innerText = detailsButton.detailsText;
1110 else
1111 detailsButton.innerText = detailsButton.hideDetailsText;
1112
1113 // Details appears over the main content on small screens.
1114 if (mobileNav) {
1115 document.getElementById('main-content').classList.toggle('hidden');
1116 var runnerContainer = document.querySelector('.runner-container');
1117 if (runnerContainer) {
1118 runnerContainer.classList.toggle('hidden');
1119 }
1120 }
1121}
1122
1123function diagnoseErrors() {
1124 var extensionId = 'idddmepepmjcgiedknnmlbadcokidhoa';
1125 var diagnoseFrame = document.getElementById('diagnose-frame');
1126 diagnoseFrame.innerHTML =
1127 '<iframe src="chrome-extension://' + extensionId +
1128 '/index.html"></iframe>';
1129}
1130
1131// Subframes use a different layout but the same html file. This is to make it
1132// easier to support platforms that load the error page via different
1133// mechanisms (Currently just iOS).
1134if (window.top.location != window.location)
1135 document.documentElement.setAttribute('subframe', '');
1136
1137// Re-renders the error page using |strings| as the dictionary of values.
1138// Used by NetErrorTabHelper to update DNS error pages with probe results.
1139function updateForDnsProbe(strings) {
1140 var context = new JsEvalContext(strings);
1141 jstProcess(context, document.getElementById('t'));
1142}
1143
1144// Given the classList property of an element, adds an icon class to the list
1145// and removes the previously-
1146function updateIconClass(classList, newClass) {
1147 var oldClass;
1148
1149 if (classList.hasOwnProperty('last_icon_class')) {
1150 oldClass = classList['last_icon_class'];
1151 if (oldClass == newClass)
1152 return;
1153 }
1154
1155 classList.add(newClass);
1156 if (oldClass !== undefined)
1157 classList.remove(oldClass);
1158
1159 classList['last_icon_class'] = newClass;
1160
1161 if (newClass == 'icon-offline') {
1162 document.body.classList.add('offline');
1163 new Runner('.interstitial-wrapper');
1164 } else {
1165 document.body.classList.add('neterror');
1166 }
1167}
1168
1169// Does a search using |baseSearchUrl| and the text in the search box.
1170function search(baseSearchUrl) {
1171 var searchTextNode = document.getElementById('search-box');
1172 document.location = baseSearchUrl + searchTextNode.value;
1173 return false;
1174}
1175
1176// Use to track clicks on elements generated by the navigation correction
1177// service. If |trackingId| is negative, the element does not come from the
1178// correction service.
1179function trackClick(trackingId) {
1180 // This can't be done with XHRs because XHRs are cancelled on navigation
1181 // start, and because these are cross-site requests.
1182 if (trackingId >= 0 && errorPageController)
1183 errorPageController.trackClick(trackingId);
1184}
1185
1186// Called when an <a> tag generated by the navigation correction service is
1187// clicked. Separate function from trackClick so the resources don't have to
1188// be updated if new data is added to jstdata.
1189function linkClicked(jstdata) {
1190 trackClick(jstdata.trackingId);
1191}
1192
1193// Implements button clicks. This function is needed during the transition
1194// between implementing these in trunk chromium and implementing them in
1195// iOS.
1196function reloadButtonClick(url) {
1197 if (window.errorPageController) {
1198 errorPageController.reloadButtonClick();
1199 } else {
1200 location = url;
1201 }
1202}
1203
1204function loadStaleButtonClick() {
1205 if (window.errorPageController) {
1206 errorPageController.loadStaleButtonClick();
1207 }
1208}
1209
1210function detailsButtonClick() {
1211 if (window.errorPageController)
1212 errorPageController.detailsButtonClick();
1213}
1214
1215var primaryControlOnLeft = true;
1216primaryControlOnLeft = false;
1217
1218function onDocumentLoad() {
1219 var buttonsDiv = document.getElementById('buttons');
1220 var controlButtonDiv = document.getElementById('control-buttons');
1221 var reloadButton = document.getElementById('reload-button');
1222 var detailsButton = document.getElementById('details-button');
1223 var staleLoadButton = document.getElementById('stale-load-button');
1224
1225 var primaryButton = reloadButton;
1226 var secondaryButton = staleLoadButton;
1227
1228 // Sets up the proper button layout for the current platform.
1229 if (primaryControlOnLeft) {
1230 buttons.classList.add('suggested-left');
1231 controlButtonDiv.insertBefore(primaryButton, secondaryButton);
1232 } else {
1233 buttons.classList.add('suggested-right');
1234 controlButtonDiv.insertBefore(secondaryButton, primaryButton);
1235 }
1236
1237 if (reloadButton.style.display == 'none' &&
1238 staleLoadButton.style.display == 'none') {
1239 detailsButton.classList.add('singular');
1240 }
1241
1242// Hide the details button if there are no details to show.
1243 if (loadTimeData.valueExists('summary') &&
1244 !loadTimeData.getValue('summary').msg) {
1245 detailsButton.style.display = 'none';
1246 }
1247
1248 // Show control buttons.
1249 if (loadTimeData.valueExists('reloadButton') &&
1250 loadTimeData.getValue('reloadButton').msg ||
1251 loadTimeData.valueExists('staleLoadButton') &&
1252 loadTimeData.getValue('staleLoadButton').msg) {
1253 controlButtonDiv.hidden = false;
1254
1255 // Set the secondary button state in the cases of two call to actions.
1256 // Reload is secondary to stale load.
1257 if (loadTimeData.valueExists('staleLoadButton') &&
1258 loadTimeData.getValue('staleLoadButton').msg) {
1259 reloadButton.classList.add('secondary-button');
1260 }
1261 }
1262
1263 // Add a main message paragraph.
1264 if (loadTimeData.valueExists('primaryParagraph')) {
1265 var p = document.querySelector('#main-message p');
1266 p.innerHTML = loadTimeData.getString('primaryParagraph');
1267 p.hidden = false;
1268 }
1269}
1270
1271document.addEventListener('DOMContentLoaded', onDocumentLoad);
1272</script>
1273 <script>// Copyright (c) 2014 The Chromium Authors. All rights reserved.
1274// Use of this source code is governed by a BSD-style license that can be
1275// found in the LICENSE file.
1276(function() {
1277'use strict';
1278/**
1279 * T-Rex runner.
1280 * @param {string} outerContainerId Outer containing element id.
1281 * @param {object} opt_config
1282 * @constructor
1283 * @export
1284 */
1285function Runner(outerContainerId, opt_config) {
1286 // Singleton
1287 if (Runner.instance_) {
1288 return Runner.instance_;
1289 }
1290 Runner.instance_ = this;
1291
1292 this.outerContainerEl = document.querySelector(outerContainerId);
1293 this.containerEl = null;
1294 this.detailsButton = this.outerContainerEl.querySelector('#details-button');
1295
1296 this.config = opt_config || Runner.config;
1297
1298 this.dimensions = Runner.defaultDimensions;
1299
1300 this.canvas = null;
1301 this.canvasCtx = null;
1302
1303 this.tRex = null;
1304
1305 this.distanceMeter = null;
1306 this.distanceRan = 0;
1307
1308 this.highestScore = 0;
1309
1310 this.time = 0;
1311 this.runningTime = 0;
1312 this.msPerFrame = 1000 / FPS;
1313 this.currentSpeed = this.config.SPEED;
1314
1315 this.obstacles = [];
1316
1317 this.started = false;
1318 this.activated = false;
1319 this.crashed = false;
1320 this.paused = false;
1321
1322 this.resizeTimerId_ = null;
1323
1324 this.playCount = 0;
1325
1326 // Sound FX.
1327 this.audioBuffer = null;
1328 this.soundFx = {};
1329
1330 // Global web audio context for playing sounds.
1331 this.audioContext = null;
1332
1333 // Images.
1334 this.images = {};
1335 this.imagesLoaded = 0;
1336 this.loadImages();
1337}
1338window['Runner'] = Runner;
1339
1340
1341/**
1342 * Default game width.
1343 * @const
1344 */
1345var DEFAULT_WIDTH = 600;
1346
1347/**
1348 * Frames per second.
1349 * @const
1350 */
1351var FPS = 60;
1352
1353/** @const */
1354var IS_HIDPI = window.devicePixelRatio > 1;
1355
1356/** @const */
1357var IS_IOS =
1358 window.navigator.userAgent.indexOf('UIWebViewForStaticFileContent') > -1;
1359
1360/** @const */
1361var IS_MOBILE = window.navigator.userAgent.indexOf('Mobi') > -1 || IS_IOS;
1362
1363/** @const */
1364var IS_TOUCH_ENABLED = 'ontouchstart' in window;
1365
1366/**
1367 * Default game configuration.
1368 * @enum {number}
1369 */
1370Runner.config = {
1371 ACCELERATION: 0.001,
1372 BG_CLOUD_SPEED: 0.2,
1373 BOTTOM_PAD: 10,
1374 CLEAR_TIME: 3000,
1375 CLOUD_FREQUENCY: 0.5,
1376 GAMEOVER_CLEAR_TIME: 750,
1377 GAP_COEFFICIENT: 0.6,
1378 GRAVITY: 0.6,
1379 INITIAL_JUMP_VELOCITY: 12,
1380 MAX_CLOUDS: 6,
1381 MAX_OBSTACLE_LENGTH: 3,
1382 MAX_SPEED: 12,
1383 MIN_JUMP_HEIGHT: 35,
1384 MOBILE_SPEED_COEFFICIENT: 1.2,
1385 RESOURCE_TEMPLATE_ID: 'audio-resources',
1386 SPEED: 6,
1387 SPEED_DROP_COEFFICIENT: 3
1388};
1389
1390
1391/**
1392 * Default dimensions.
1393 * @enum {string}
1394 */
1395Runner.defaultDimensions = {
1396 WIDTH: DEFAULT_WIDTH,
1397 HEIGHT: 150
1398};
1399
1400
1401/**
1402 * CSS class names.
1403 * @enum {string}
1404 */
1405Runner.classes = {
1406 CANVAS: 'runner-canvas',
1407 CONTAINER: 'runner-container',
1408 CRASHED: 'crashed',
1409 ICON: 'icon-offline',
1410 TOUCH_CONTROLLER: 'controller'
1411};
1412
1413
1414/**
1415 * Image source urls.
1416 * @enum {array<object>}
1417 */
1418Runner.imageSources = {
1419 LDPI: [
1420 {name: 'CACTUS_LARGE', id: '1x-obstacle-large'},
1421 {name: 'CACTUS_SMALL', id: '1x-obstacle-small'},
1422 {name: 'CLOUD', id: '1x-cloud'},
1423 {name: 'HORIZON', id: '1x-horizon'},
1424 {name: 'RESTART', id: '1x-restart'},
1425 {name: 'TEXT_SPRITE', id: '1x-text'},
1426 {name: 'TREX', id: '1x-trex'}
1427 ],
1428 HDPI: [
1429 {name: 'CACTUS_LARGE', id: '2x-obstacle-large'},
1430 {name: 'CACTUS_SMALL', id: '2x-obstacle-small'},
1431 {name: 'CLOUD', id: '2x-cloud'},
1432 {name: 'HORIZON', id: '2x-horizon'},
1433 {name: 'RESTART', id: '2x-restart'},
1434 {name: 'TEXT_SPRITE', id: '2x-text'},
1435 {name: 'TREX', id: '2x-trex'}
1436 ]
1437};
1438
1439
1440/**
1441 * Sound FX. Reference to the ID of the audio tag on interstitial page.
1442 * @enum {string}
1443 */
1444Runner.sounds = {
1445 BUTTON_PRESS: 'offline-sound-press',
1446 HIT: 'offline-sound-hit',
1447 SCORE: 'offline-sound-reached'
1448};
1449
1450
1451/**
1452 * Key code mapping.
1453 * @enum {object}
1454 */
1455Runner.keycodes = {
1456 JUMP: {'38': 1, '32': 1}, // Up, spacebar
1457 DUCK: {'40': 1}, // Down
1458 RESTART: {'13': 1} // Enter
1459};
1460
1461
1462/**
1463 * Runner event names.
1464 * @enum {string}
1465 */
1466Runner.events = {
1467 ANIM_END: 'webkitAnimationEnd',
1468 CLICK: 'click',
1469 KEYDOWN: 'keydown',
1470 KEYUP: 'keyup',
1471 MOUSEDOWN: 'mousedown',
1472 MOUSEUP: 'mouseup',
1473 RESIZE: 'resize',
1474 TOUCHEND: 'touchend',
1475 TOUCHSTART: 'touchstart',
1476 VISIBILITY: 'visibilitychange',
1477 BLUR: 'blur',
1478 FOCUS: 'focus',
1479 LOAD: 'load'
1480};
1481
1482
1483Runner.prototype = {
1484 /**
1485 * Setting individual settings for debugging.
1486 * @param {string} setting
1487 * @param {*} value
1488 */
1489 updateConfigSetting: function(setting, value) {
1490 if (setting in this.config && value != undefined) {
1491 this.config[setting] = value;
1492
1493 switch (setting) {
1494 case 'GRAVITY':
1495 case 'MIN_JUMP_HEIGHT':
1496 case 'SPEED_DROP_COEFFICIENT':
1497 this.tRex.config[setting] = value;
1498 break;
1499 case 'INITIAL_JUMP_VELOCITY':
1500 this.tRex.setJumpVelocity(value);
1501 break;
1502 case 'SPEED':
1503 this.setSpeed(value);
1504 break;
1505 }
1506 }
1507 },
1508
1509 /**
1510 * Load and cache the image assets from the page.
1511 */
1512 loadImages: function() {
1513 var imageSources = IS_HIDPI ? Runner.imageSources.HDPI :
1514 Runner.imageSources.LDPI;
1515
1516 var numImages = imageSources.length;
1517
1518 for (var i = numImages - 1; i >= 0; i--) {
1519 var imgSource = imageSources[i];
1520 this.images[imgSource.name] = document.getElementById(imgSource.id);
1521 }
1522 this.init();
1523 },
1524
1525 /**
1526 * Load and decode base 64 encoded sounds.
1527 */
1528 loadSounds: function() {
1529 if (!IS_IOS) {
1530 this.audioContext = new AudioContext();
1531 var resourceTemplate =
1532 document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content;
1533
1534 for (var sound in Runner.sounds) {
1535 var soundSrc =
1536 resourceTemplate.getElementById(Runner.sounds[sound]).src;
1537 soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1);
1538 var buffer = decodeBase64ToArrayBuffer(soundSrc);
1539
1540 // Async, so no guarantee of order in array.
1541 this.audioContext.decodeAudioData(buffer, function(index, audioData) {
1542 this.soundFx[index] = audioData;
1543 }.bind(this, sound));
1544 }
1545 }
1546 },
1547
1548 /**
1549 * Sets the game speed. Adjust the speed accordingly if on a smaller screen.
1550 * @param {number} opt_speed
1551 */
1552 setSpeed: function(opt_speed) {
1553 var speed = opt_speed || this.currentSpeed;
1554
1555 // Reduce the speed on smaller mobile screens.
1556 if (this.dimensions.WIDTH < DEFAULT_WIDTH) {
1557 var mobileSpeed = speed * this.dimensions.WIDTH / DEFAULT_WIDTH *
1558 this.config.MOBILE_SPEED_COEFFICIENT;
1559 this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed;
1560 } else if (opt_speed) {
1561 this.currentSpeed = opt_speed;
1562 }
1563 },
1564
1565 /**
1566 * Game initialiser.
1567 */
1568 init: function() {
1569 // Hide the static icon.
1570 document.querySelector('.' + Runner.classes.ICON).style.visibility =
1571 'hidden';
1572
1573 this.adjustDimensions();
1574 this.setSpeed();
1575
1576 this.containerEl = document.createElement('div');
1577 this.containerEl.className = Runner.classes.CONTAINER;
1578
1579 // Player canvas container.
1580 this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH,
1581 this.dimensions.HEIGHT, Runner.classes.PLAYER);
1582
1583 this.canvasCtx = this.canvas.getContext('2d');
1584 this.canvasCtx.fillStyle = '#f7f7f7';
1585 this.canvasCtx.fill();
1586 Runner.updateCanvasScaling(this.canvas);
1587
1588 // Horizon contains clouds, obstacles and the ground.
1589 this.horizon = new Horizon(this.canvas, this.images, this.dimensions,
1590 this.config.GAP_COEFFICIENT);
1591
1592 // Distance meter
1593 this.distanceMeter = new DistanceMeter(this.canvas,
1594 this.images.TEXT_SPRITE, this.dimensions.WIDTH);
1595
1596 // Draw t-rex
1597 this.tRex = new Trex(this.canvas, this.images.TREX);
1598
1599 this.outerContainerEl.appendChild(this.containerEl);
1600
1601 if (IS_MOBILE) {
1602 this.createTouchController();
1603 }
1604
1605 this.startListening();
1606 this.update();
1607
1608 window.addEventListener(Runner.events.RESIZE,
1609 this.debounceResize.bind(this));
1610 },
1611
1612 /**
1613 * Create the touch controller. A div that covers whole screen.
1614 */
1615 createTouchController: function() {
1616 this.touchController = document.createElement('div');
1617 this.touchController.className = Runner.classes.TOUCH_CONTROLLER;
1618 },
1619
1620 /**
1621 * Debounce the resize event.
1622 */
1623 debounceResize: function() {
1624 if (!this.resizeTimerId_) {
1625 this.resizeTimerId_ =
1626 setInterval(this.adjustDimensions.bind(this), 250);
1627 }
1628 },
1629
1630 /**
1631 * Adjust game space dimensions on resize.
1632 */
1633 adjustDimensions: function() {
1634 clearInterval(this.resizeTimerId_);
1635 this.resizeTimerId_ = null;
1636
1637 var boxStyles = window.getComputedStyle(this.outerContainerEl);
1638 var padding = Number(boxStyles.paddingLeft.substr(0,
1639 boxStyles.paddingLeft.length - 2));
1640
1641 this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2;
1642
1643 // Redraw the elements back onto the canvas.
1644 if (this.canvas) {
1645 this.canvas.width = this.dimensions.WIDTH;
1646 this.canvas.height = this.dimensions.HEIGHT;
1647
1648 Runner.updateCanvasScaling(this.canvas);
1649
1650 this.distanceMeter.calcXPos(this.dimensions.WIDTH);
1651 this.clearCanvas();
1652 this.horizon.update(0, 0, true);
1653 this.tRex.update(0);
1654
1655 // Outer container and distance meter.
1656 if (this.activated || this.crashed) {
1657 this.containerEl.style.width = this.dimensions.WIDTH + 'px';
1658 this.containerEl.style.height = this.dimensions.HEIGHT + 'px';
1659 this.distanceMeter.update(0, Math.ceil(this.distanceRan));
1660 this.stop();
1661 } else {
1662 this.tRex.draw(0, 0);
1663 }
1664
1665 // Game over panel.
1666 if (this.crashed && this.gameOverPanel) {
1667 this.gameOverPanel.updateDimensions(this.dimensions.WIDTH);
1668 this.gameOverPanel.draw();
1669 }
1670 }
1671 },
1672
1673 /**
1674 * Play the game intro.
1675 * Canvas container width expands out to the full width.
1676 */
1677 playIntro: function() {
1678 if (!this.started && !this.crashed) {
1679 this.playingIntro = true;
1680 this.tRex.playingIntro = true;
1681
1682 // CSS animation definition.
1683 var keyframes = '@-webkit-keyframes intro { ' +
1684 'from { width:' + Trex.config.WIDTH + 'px }' +
1685 'to { width: ' + this.dimensions.WIDTH + 'px }' +
1686 '}';
1687 document.styleSheets[0].insertRule(keyframes, 0);
1688
1689 this.containerEl.addEventListener(Runner.events.ANIM_END,
1690 this.startGame.bind(this));
1691
1692 this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both';
1693 this.containerEl.style.width = this.dimensions.WIDTH + 'px';
1694
1695 if (this.touchController) {
1696 this.outerContainerEl.appendChild(this.touchController);
1697 }
1698 this.activated = true;
1699 this.started = true;
1700 } else if (this.crashed) {
1701 this.restart();
1702 }
1703 },
1704
1705
1706 /**
1707 * Update the game status to started.
1708 */
1709 startGame: function() {
1710 this.runningTime = 0;
1711 this.playingIntro = false;
1712 this.tRex.playingIntro = false;
1713 this.containerEl.style.webkitAnimation = '';
1714 this.playCount++;
1715
1716 // Handle tabbing off the page. Pause the current game.
1717 window.addEventListener(Runner.events.VISIBILITY,
1718 this.onVisibilityChange.bind(this));
1719
1720 window.addEventListener(Runner.events.BLUR,
1721 this.onVisibilityChange.bind(this));
1722
1723 window.addEventListener(Runner.events.FOCUS,
1724 this.onVisibilityChange.bind(this));
1725 },
1726
1727 clearCanvas: function() {
1728 this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH,
1729 this.dimensions.HEIGHT);
1730 },
1731
1732 /**
1733 * Update the game frame.
1734 */
1735 update: function() {
1736 this.drawPending = false;
1737
1738 var now = getTimeStamp();
1739 var deltaTime = now - (this.time || now);
1740 this.time = now;
1741
1742 if (this.activated) {
1743 this.clearCanvas();
1744
1745 if (this.tRex.jumping) {
1746 this.tRex.updateJump(deltaTime, this.config);
1747 }
1748
1749 this.runningTime += deltaTime;
1750 var hasObstacles = this.runningTime > this.config.CLEAR_TIME;
1751
1752 // First jump triggers the intro.
1753 if (this.tRex.jumpCount == 1 && !this.playingIntro) {
1754 this.playIntro();
1755 }
1756
1757 // The horizon doesn't move until the intro is over.
1758 if (this.playingIntro) {
1759 this.horizon.update(0, this.currentSpeed, hasObstacles);
1760 } else {
1761 deltaTime = !this.started ? 0 : deltaTime;
1762 this.horizon.update(deltaTime, this.currentSpeed, hasObstacles);
1763 }
1764
1765 // Check for collisions.
1766 var collision = hasObstacles &&
1767 checkForCollision(this.horizon.obstacles[0], this.tRex);
1768
1769 if (!collision) {
1770 this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame;
1771
1772 if (this.currentSpeed < this.config.MAX_SPEED) {
1773 this.currentSpeed += this.config.ACCELERATION;
1774 }
1775 } else {
1776 this.gameOver();
1777 }
1778
1779 if (this.distanceMeter.getActualDistance(this.distanceRan) >
1780 this.distanceMeter.maxScore) {
1781 this.distanceRan = 0;
1782 }
1783
1784 var playAcheivementSound = this.distanceMeter.update(deltaTime,
1785 Math.ceil(this.distanceRan));
1786
1787 if (playAcheivementSound) {
1788 this.playSound(this.soundFx.SCORE);
1789 }
1790 }
1791
1792 if (!this.crashed) {
1793 this.tRex.update(deltaTime);
1794 this.raq();
1795 }
1796 },
1797
1798 /**
1799 * Event handler.
1800 */
1801 handleEvent: function(e) {
1802 return (function(evtType, events) {
1803 switch (evtType) {
1804 case events.KEYDOWN:
1805 case events.TOUCHSTART:
1806 case events.MOUSEDOWN:
1807 this.onKeyDown(e);
1808 break;
1809 case events.KEYUP:
1810 case events.TOUCHEND:
1811 case events.MOUSEUP:
1812 this.onKeyUp(e);
1813 break;
1814 }
1815 }.bind(this))(e.type, Runner.events);
1816 },
1817
1818 /**
1819 * Bind relevant key / mouse / touch listeners.
1820 */
1821 startListening: function() {
1822 // Keys.
1823 document.addEventListener(Runner.events.KEYDOWN, this);
1824 document.addEventListener(Runner.events.KEYUP, this);
1825
1826 if (IS_MOBILE) {
1827 // Mobile only touch devices.
1828 this.touchController.addEventListener(Runner.events.TOUCHSTART, this);
1829 this.touchController.addEventListener(Runner.events.TOUCHEND, this);
1830 this.containerEl.addEventListener(Runner.events.TOUCHSTART, this);
1831 } else {
1832 // Mouse.
1833 document.addEventListener(Runner.events.MOUSEDOWN, this);
1834 document.addEventListener(Runner.events.MOUSEUP, this);
1835 }
1836 },
1837
1838 /**
1839 * Remove all listeners.
1840 */
1841 stopListening: function() {
1842 document.removeEventListener(Runner.events.KEYDOWN, this);
1843 document.removeEventListener(Runner.events.KEYUP, this);
1844
1845 if (IS_MOBILE) {
1846 this.touchController.removeEventListener(Runner.events.TOUCHSTART, this);
1847 this.touchController.removeEventListener(Runner.events.TOUCHEND, this);
1848 this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this);
1849 } else {
1850 document.removeEventListener(Runner.events.MOUSEDOWN, this);
1851 document.removeEventListener(Runner.events.MOUSEUP, this);
1852 }
1853 },
1854
1855 /**
1856 * Process keydown.
1857 * @param {Event} e
1858 */
1859 onKeyDown: function(e) {
1860 if (e.target != this.detailsButton) {
1861 if (!this.crashed && (Runner.keycodes.JUMP[String(e.keyCode)] ||
1862 e.type == Runner.events.TOUCHSTART)) {
1863 if (!this.activated) {
1864 this.loadSounds();
1865 this.activated = true;
1866 }
1867
1868 if (!this.tRex.jumping) {
1869 this.playSound(this.soundFx.BUTTON_PRESS);
1870 this.tRex.startJump();
1871 }
1872 }
1873
1874 if (this.crashed && e.type == Runner.events.TOUCHSTART &&
1875 e.currentTarget == this.containerEl) {
1876 this.restart();
1877 }
1878 }
1879
1880 // Speed drop, activated only when jump key is not pressed.
1881 if (Runner.keycodes.DUCK[e.keyCode] && this.tRex.jumping) {
1882 e.preventDefault();
1883 this.tRex.setSpeedDrop();
1884 }
1885 },
1886
1887
1888 /**
1889 * Process key up.
1890 * @param {Event} e
1891 */
1892 onKeyUp: function(e) {
1893 var keyCode = String(e.keyCode);
1894 var isjumpKey = Runner.keycodes.JUMP[keyCode] ||
1895 e.type == Runner.events.TOUCHEND ||
1896 e.type == Runner.events.MOUSEDOWN;
1897
1898 if (this.isRunning() && isjumpKey) {
1899 this.tRex.endJump();
1900 } else if (Runner.keycodes.DUCK[keyCode]) {
1901 this.tRex.speedDrop = false;
1902 } else if (this.crashed) {
1903 // Check that enough time has elapsed before allowing jump key to restart.
1904 var deltaTime = getTimeStamp() - this.time;
1905
1906 if (Runner.keycodes.RESTART[keyCode] ||
1907 (e.type == Runner.events.MOUSEUP && e.target == this.canvas) ||
1908 (deltaTime >= this.config.GAMEOVER_CLEAR_TIME &&
1909 Runner.keycodes.JUMP[keyCode])) {
1910 this.restart();
1911 }
1912 } else if (this.paused && isjumpKey) {
1913 this.play();
1914 }
1915 },
1916
1917 /**
1918 * RequestAnimationFrame wrapper.
1919 */
1920 raq: function() {
1921 if (!this.drawPending) {
1922 this.drawPending = true;
1923 this.raqId = requestAnimationFrame(this.update.bind(this));
1924 }
1925 },
1926
1927 /**
1928 * Whether the game is running.
1929 * @return {boolean}
1930 */
1931 isRunning: function() {
1932 return !!this.raqId;
1933 },
1934
1935 /**
1936 * Game over state.
1937 */
1938 gameOver: function() {
1939 this.playSound(this.soundFx.HIT);
1940 vibrate(200);
1941
1942 this.stop();
1943 this.crashed = true;
1944 this.distanceMeter.acheivement = false;
1945
1946 this.tRex.update(100, Trex.status.CRASHED);
1947
1948 // Game over panel.
1949 if (!this.gameOverPanel) {
1950 this.gameOverPanel = new GameOverPanel(this.canvas,
1951 this.images.TEXT_SPRITE, this.images.RESTART,
1952 this.dimensions);
1953 } else {
1954 this.gameOverPanel.draw();
1955 }
1956
1957 // Update the high score.
1958 if (this.distanceRan > this.highestScore) {
1959 this.highestScore = Math.ceil(this.distanceRan);
1960 this.distanceMeter.setHighScore(this.highestScore);
1961 }
1962
1963 // Reset the time clock.
1964 this.time = getTimeStamp();
1965 },
1966
1967 stop: function() {
1968 this.activated = false;
1969 this.paused = true;
1970 cancelAnimationFrame(this.raqId);
1971 this.raqId = 0;
1972 },
1973
1974 play: function() {
1975 if (!this.crashed) {
1976 this.activated = true;
1977 this.paused = false;
1978 this.tRex.update(0, Trex.status.RUNNING);
1979 this.time = getTimeStamp();
1980 this.update();
1981 }
1982 },
1983
1984 restart: function() {
1985 if (!this.raqId) {
1986 this.playCount++;
1987 this.runningTime = 0;
1988 this.activated = true;
1989 this.crashed = false;
1990 this.distanceRan = 0;
1991 this.setSpeed(this.config.SPEED);
1992
1993 this.time = getTimeStamp();
1994 this.containerEl.classList.remove(Runner.classes.CRASHED);
1995 this.clearCanvas();
1996 this.distanceMeter.reset(this.highestScore);
1997 this.horizon.reset();
1998 this.tRex.reset();
1999 this.playSound(this.soundFx.BUTTON_PRESS);
2000
2001 this.update();
2002 }
2003 },
2004
2005 /**
2006 * Pause the game if the tab is not in focus.
2007 */
2008 onVisibilityChange: function(e) {
2009 if (document.hidden || document.webkitHidden || e.type == 'blur') {
2010 this.stop();
2011 } else {
2012 this.play();
2013 }
2014 },
2015
2016 /**
2017 * Play a sound.
2018 * @param {SoundBuffer} soundBuffer
2019 */
2020 playSound: function(soundBuffer) {
2021 if (soundBuffer) {
2022 var sourceNode = this.audioContext.createBufferSource();
2023 sourceNode.buffer = soundBuffer;
2024 sourceNode.connect(this.audioContext.destination);
2025 sourceNode.start(0);
2026 }
2027 }
2028};
2029
2030
2031/**
2032 * Updates the canvas size taking into
2033 * account the backing store pixel ratio and
2034 * the device pixel ratio.
2035 *
2036 * See article by Paul Lewis:
2037 * http://www.html5rocks.com/en/tutorials/canvas/hidpi/
2038 *
2039 * @param {HTMLCanvasElement} canvas
2040 * @param {number} opt_width
2041 * @param {number} opt_height
2042 * @return {boolean} Whether the canvas was scaled.
2043 */
2044Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) {
2045 var context = canvas.getContext('2d');
2046
2047 // Query the various pixel ratios
2048 var devicePixelRatio = Math.floor(window.devicePixelRatio) || 1;
2049 var backingStoreRatio = Math.floor(context.webkitBackingStorePixelRatio) || 1;
2050 var ratio = devicePixelRatio / backingStoreRatio;
2051
2052 // Upscale the canvas if the two ratios don't match
2053 if (devicePixelRatio !== backingStoreRatio) {
2054
2055 var oldWidth = opt_width || canvas.width;
2056 var oldHeight = opt_height || canvas.height;
2057
2058 canvas.width = oldWidth * ratio;
2059 canvas.height = oldHeight * ratio;
2060
2061 canvas.style.width = oldWidth + 'px';
2062 canvas.style.height = oldHeight + 'px';
2063
2064 // Scale the context to counter the fact that we've manually scaled
2065 // our canvas element.
2066 context.scale(ratio, ratio);
2067 return true;
2068 } else if (devicePixelRatio == 1) {
2069 // Reset the canvas width / height. Fixes scaling bug when the page is
2070 // zoomed and the devicePixelRatio changes accordingly.
2071 canvas.style.width = canvas.width + 'px';
2072 canvas.style.height = canvas.height + 'px';
2073 }
2074 return false;
2075};
2076
2077
2078/**
2079 * Get random number.
2080 * @param {number} min
2081 * @param {number} max
2082 * @param {number}
2083 */
2084function getRandomNum(min, max) {
2085 return Math.floor(Math.random() * (max - min + 1)) + min;
2086}
2087
2088
2089/**
2090 * Vibrate on mobile devices.
2091 * @param {number} duration Duration of the vibration in milliseconds.
2092 */
2093function vibrate(duration) {
2094 if (IS_MOBILE && window.navigator.vibrate) {
2095 window.navigator.vibrate(duration);
2096 }
2097}
2098
2099
2100/**
2101 * Create canvas element.
2102 * @param {HTMLElement} container Element to append canvas to.
2103 * @param {number} width
2104 * @param {number} height
2105 * @param {string} opt_classname
2106 * @return {HTMLCanvasElement}
2107 */
2108function createCanvas(container, width, height, opt_classname) {
2109 var canvas = document.createElement('canvas');
2110 canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' +
2111 opt_classname : Runner.classes.CANVAS;
2112 canvas.width = width;
2113 canvas.height = height;
2114 container.appendChild(canvas);
2115
2116 return canvas;
2117}
2118
2119
2120/**
2121 * Decodes the base 64 audio to ArrayBuffer used by Web Audio.
2122 * @param {string} base64String
2123 */
2124function decodeBase64ToArrayBuffer(base64String) {
2125 var len = (base64String.length / 4) * 3;
2126 var str = atob(base64String);
2127 var arrayBuffer = new ArrayBuffer(len);
2128 var bytes = new Uint8Array(arrayBuffer);
2129
2130 for (var i = 0; i < len; i++) {
2131 bytes[i] = str.charCodeAt(i);
2132 }
2133 return bytes.buffer;
2134}
2135
2136
2137/**
2138 * Return the current timestamp.
2139 * @return {number}
2140 */
2141function getTimeStamp() {
2142 return IS_IOS ? new Date().getTime() : performance.now();
2143}
2144
2145
2146//******************************************************************************
2147
2148
2149/**
2150 * Game over panel.
2151 * @param {!HTMLCanvasElement} canvas
2152 * @param {!HTMLImage} textSprite
2153 * @param {!HTMLImage} restartImg
2154 * @param {!Object} dimensions Canvas dimensions.
2155 * @constructor
2156 */
2157function GameOverPanel(canvas, textSprite, restartImg, dimensions) {
2158 this.canvas = canvas;
2159 this.canvasCtx = canvas.getContext('2d');
2160 this.canvasDimensions = dimensions;
2161 this.textSprite = textSprite;
2162 this.restartImg = restartImg;
2163 this.draw();
2164};
2165
2166
2167/**
2168 * Dimensions used in the panel.
2169 * @enum {number}
2170 */
2171GameOverPanel.dimensions = {
2172 TEXT_X: 0,
2173 TEXT_Y: 13,
2174 TEXT_WIDTH: 191,
2175 TEXT_HEIGHT: 11,
2176 RESTART_WIDTH: 36,
2177 RESTART_HEIGHT: 32
2178};
2179
2180
2181GameOverPanel.prototype = {
2182 /**
2183 * Update the panel dimensions.
2184 * @param {number} width New canvas width.
2185 * @param {number} opt_height Optional new canvas height.
2186 */
2187 updateDimensions: function(width, opt_height) {
2188 this.canvasDimensions.WIDTH = width;
2189 if (opt_height) {
2190 this.canvasDimensions.HEIGHT = opt_height;
2191 }
2192 },
2193
2194 /**
2195 * Draw the panel.
2196 */
2197 draw: function() {
2198 var dimensions = GameOverPanel.dimensions;
2199
2200 var centerX = this.canvasDimensions.WIDTH / 2;
2201
2202 // Game over text.
2203 var textSourceX = dimensions.TEXT_X;
2204 var textSourceY = dimensions.TEXT_Y;
2205 var textSourceWidth = dimensions.TEXT_WIDTH;
2206 var textSourceHeight = dimensions.TEXT_HEIGHT;
2207
2208 var textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2));
2209 var textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3);
2210 var textTargetWidth = dimensions.TEXT_WIDTH;
2211 var textTargetHeight = dimensions.TEXT_HEIGHT;
2212
2213 var restartSourceWidth = dimensions.RESTART_WIDTH;
2214 var restartSourceHeight = dimensions.RESTART_HEIGHT;
2215 var restartTargetX = centerX - (dimensions.RESTART_WIDTH / 2);
2216 var restartTargetY = this.canvasDimensions.HEIGHT / 2;
2217
2218 if (IS_HIDPI) {
2219 textSourceY *= 2;
2220 textSourceX *= 2;
2221 textSourceWidth *= 2;
2222 textSourceHeight *= 2;
2223 restartSourceWidth *= 2;
2224 restartSourceHeight *= 2;
2225 }
2226
2227 // Game over text from sprite.
2228 this.canvasCtx.drawImage(this.textSprite,
2229 textSourceX, textSourceY, textSourceWidth, textSourceHeight,
2230 textTargetX, textTargetY, textTargetWidth, textTargetHeight);
2231
2232 // Restart button.
2233 this.canvasCtx.drawImage(this.restartImg, 0, 0,
2234 restartSourceWidth, restartSourceHeight,
2235 restartTargetX, restartTargetY, dimensions.RESTART_WIDTH,
2236 dimensions.RESTART_HEIGHT);
2237 }
2238};
2239
2240
2241//******************************************************************************
2242
2243/**
2244 * Check for a collision.
2245 * @param {!Obstacle} obstacle
2246 * @param {!Trex} tRex T-rex object.
2247 * @param {HTMLCanvasContext} opt_canvasCtx Optional canvas context for drawing
2248 * collision boxes.
2249 * @return {Array<CollisionBox>}
2250 */
2251function checkForCollision(obstacle, tRex, opt_canvasCtx) {
2252 var obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos;
2253
2254 // Adjustments are made to the bounding box as there is a 1 pixel white
2255 // border around the t-rex and obstacles.
2256 var tRexBox = new CollisionBox(
2257 tRex.xPos + 1,
2258 tRex.yPos + 1,
2259 tRex.config.WIDTH - 2,
2260 tRex.config.HEIGHT - 2);
2261
2262 var obstacleBox = new CollisionBox(
2263 obstacle.xPos + 1,
2264 obstacle.yPos + 1,
2265 obstacle.typeConfig.width * obstacle.size - 2,
2266 obstacle.typeConfig.height - 2);
2267
2268 // Debug outer box
2269 if (opt_canvasCtx) {
2270 drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox);
2271 }
2272
2273 // Simple outer bounds check.
2274 if (boxCompare(tRexBox, obstacleBox)) {
2275 var collisionBoxes = obstacle.collisionBoxes;
2276 var tRexCollisionBoxes = Trex.collisionBoxes;
2277
2278 // Detailed axis aligned box check.
2279 for (var t = 0; t < tRexCollisionBoxes.length; t++) {
2280 for (var i = 0; i < collisionBoxes.length; i++) {
2281 // Adjust the box to actual positions.
2282 var adjTrexBox =
2283 createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox);
2284 var adjObstacleBox =
2285 createAdjustedCollisionBox(collisionBoxes[i], obstacleBox);
2286 var crashed = boxCompare(adjTrexBox, adjObstacleBox);
2287
2288 // Draw boxes for debug.
2289 if (opt_canvasCtx) {
2290 drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox);
2291 }
2292
2293 if (crashed) {
2294 return [adjTrexBox, adjObstacleBox];
2295 }
2296 }
2297 }
2298 }
2299 return false;
2300};
2301
2302
2303/**
2304 * Adjust the collision box.
2305 * @param {!CollisionBox} box The original box.
2306 * @param {!CollisionBox} adjustment Adjustment box.
2307 * @return {CollisionBox} The adjusted collision box object.
2308 */
2309function createAdjustedCollisionBox(box, adjustment) {
2310 return new CollisionBox(
2311 box.x + adjustment.x,
2312 box.y + adjustment.y,
2313 box.width,
2314 box.height);
2315};
2316
2317
2318/**
2319 * Draw the collision boxes for debug.
2320 */
2321function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) {
2322 canvasCtx.save();
2323 canvasCtx.strokeStyle = '#f00';
2324 canvasCtx.strokeRect(tRexBox.x, tRexBox.y,
2325 tRexBox.width, tRexBox.height);
2326
2327 canvasCtx.strokeStyle = '#0f0';
2328 canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y,
2329 obstacleBox.width, obstacleBox.height);
2330 canvasCtx.restore();
2331};
2332
2333
2334/**
2335 * Compare two collision boxes for a collision.
2336 * @param {CollisionBox} tRexBox
2337 * @param {CollisionBox} obstacleBox
2338 * @return {boolean} Whether the boxes intersected.
2339 */
2340function boxCompare(tRexBox, obstacleBox) {
2341 var crashed = false;
2342 var tRexBoxX = tRexBox.x;
2343 var tRexBoxY = tRexBox.y;
2344
2345 var obstacleBoxX = obstacleBox.x;
2346 var obstacleBoxY = obstacleBox.y;
2347
2348 // Axis-Aligned Bounding Box method.
2349 if (tRexBox.x < obstacleBoxX + obstacleBox.width &&
2350 tRexBox.x + tRexBox.width > obstacleBoxX &&
2351 tRexBox.y < obstacleBox.y + obstacleBox.height &&
2352 tRexBox.height + tRexBox.y > obstacleBox.y) {
2353 crashed = true;
2354 }
2355
2356 return crashed;
2357};
2358
2359
2360//******************************************************************************
2361
2362/**
2363 * Collision box object.
2364 * @param {number} x X position.
2365 * @param {number} y Y Position.
2366 * @param {number} w Width.
2367 * @param {number} h Height.
2368 */
2369function CollisionBox(x, y, w, h) {
2370 this.x = x;
2371 this.y = y;
2372 this.width = w;
2373 this.height = h;
2374};
2375
2376
2377//******************************************************************************
2378
2379/**
2380 * Obstacle.
2381 * @param {HTMLCanvasCtx} canvasCtx
2382 * @param {Obstacle.type} type
2383 * @param {image} obstacleImg Image sprite.
2384 * @param {Object} dimensions
2385 * @param {number} gapCoefficient Mutipler in determining the gap.
2386 * @param {number} speed
2387 */
2388function Obstacle(canvasCtx, type, obstacleImg, dimensions,
2389 gapCoefficient, speed) {
2390
2391 this.canvasCtx = canvasCtx;
2392 this.image = obstacleImg;
2393 this.typeConfig = type;
2394 this.gapCoefficient = gapCoefficient;
2395 this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH);
2396 this.dimensions = dimensions;
2397 this.remove = false;
2398 this.xPos = 0;
2399 this.yPos = this.typeConfig.yPos;
2400 this.width = 0;
2401 this.collisionBoxes = [];
2402 this.gap = 0;
2403
2404 this.init(speed);
2405};
2406
2407/**
2408 * Coefficient for calculating the maximum gap.
2409 * @const
2410 */
2411Obstacle.MAX_GAP_COEFFICIENT = 1.5;
2412
2413/**
2414 * Maximum obstacle grouping count.
2415 * @const
2416 */
2417Obstacle.MAX_OBSTACLE_LENGTH = 3,
2418
2419
2420Obstacle.prototype = {
2421 /**
2422 * Initialise the DOM for the obstacle.
2423 * @param {number} speed
2424 */
2425 init: function(speed) {
2426 this.cloneCollisionBoxes();
2427
2428 // Only allow sizing if we're at the right speed.
2429 if (this.size > 1 && this.typeConfig.multipleSpeed > speed) {
2430 this.size = 1;
2431 }
2432
2433 this.width = this.typeConfig.width * this.size;
2434 this.xPos = this.dimensions.WIDTH - this.width;
2435
2436 this.draw();
2437
2438 // Make collision box adjustments,
2439 // Central box is adjusted to the size as one box.
2440 // ____ ______ ________
2441 // _| |-| _| |-| _| |-|
2442 // | |<->| | | |<--->| | | |<----->| |
2443 // | | 1 | | | | 2 | | | | 3 | |
2444 // |_|___|_| |_|_____|_| |_|_______|_|
2445 //
2446 if (this.size > 1) {
2447 this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width -
2448 this.collisionBoxes[2].width;
2449 this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width;
2450 }
2451
2452 this.gap = this.getGap(this.gapCoefficient, speed);
2453 },
2454
2455 /**
2456 * Draw and crop based on size.
2457 */
2458 draw: function() {
2459 var sourceWidth = this.typeConfig.width;
2460 var sourceHeight = this.typeConfig.height;
2461
2462 if (IS_HIDPI) {
2463 sourceWidth = sourceWidth * 2;
2464 sourceHeight = sourceHeight * 2;
2465 }
2466
2467 // Sprite
2468 var sourceX = (sourceWidth * this.size) * (0.5 * (this.size - 1));
2469 this.canvasCtx.drawImage(this.image,
2470 sourceX, 0,
2471 sourceWidth * this.size, sourceHeight,
2472 this.xPos, this.yPos,
2473 this.typeConfig.width * this.size, this.typeConfig.height);
2474 },
2475
2476 /**
2477 * Obstacle frame update.
2478 * @param {number} deltaTime
2479 * @param {number} speed
2480 */
2481 update: function(deltaTime, speed) {
2482 if (!this.remove) {
2483 this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime);
2484 this.draw();
2485
2486 if (!this.isVisible()) {
2487 this.remove = true;
2488 }
2489 }
2490 },
2491
2492 /**
2493 * Calculate a random gap size.
2494 * - Minimum gap gets wider as speed increses
2495 * @param {number} gapCoefficient
2496 * @param {number} speed
2497 * @return {number} The gap size.
2498 */
2499 getGap: function(gapCoefficient, speed) {
2500 var minGap = Math.round(this.width * speed +
2501 this.typeConfig.minGap * gapCoefficient);
2502 var maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT);
2503 return getRandomNum(minGap, maxGap);
2504 },
2505
2506 /**
2507 * Check if obstacle is visible.
2508 * @return {boolean} Whether the obstacle is in the game area.
2509 */
2510 isVisible: function() {
2511 return this.xPos + this.width > 0;
2512 },
2513
2514 /**
2515 * Make a copy of the collision boxes, since these will change based on
2516 * obstacle type and size.
2517 */
2518 cloneCollisionBoxes: function() {
2519 var collisionBoxes = this.typeConfig.collisionBoxes;
2520
2521 for (var i = collisionBoxes.length - 1; i >= 0; i--) {
2522 this.collisionBoxes[i] = new CollisionBox(collisionBoxes[i].x,
2523 collisionBoxes[i].y, collisionBoxes[i].width,
2524 collisionBoxes[i].height);
2525 }
2526 }
2527};
2528
2529
2530/**
2531 * Obstacle definitions.
2532 * minGap: minimum pixel space betweeen obstacles.
2533 * multipleSpeed: Speed at which multiples are allowed.
2534 */
2535Obstacle.types = [
2536 {
2537 type: 'CACTUS_SMALL',
2538 className: ' cactus cactus-small ',
2539 width: 17,
2540 height: 35,
2541 yPos: 105,
2542 multipleSpeed: 3,
2543 minGap: 120,
2544 collisionBoxes: [
2545 new CollisionBox(0, 7, 5, 27),
2546 new CollisionBox(4, 0, 6, 34),
2547 new CollisionBox(10, 4, 7, 14)
2548 ]
2549 },
2550 {
2551 type: 'CACTUS_LARGE',
2552 className: ' cactus cactus-large ',
2553 width: 25,
2554 height: 50,
2555 yPos: 90,
2556 multipleSpeed: 6,
2557 minGap: 120,
2558 collisionBoxes: [
2559 new CollisionBox(0, 12, 7, 38),
2560 new CollisionBox(8, 0, 7, 49),
2561 new CollisionBox(13, 10, 10, 38)
2562 ]
2563 }
2564];
2565
2566
2567//******************************************************************************
2568/**
2569 * T-rex game character.
2570 * @param {HTMLCanvas} canvas
2571 * @param {HTMLImage} image Character image.
2572 * @constructor
2573 */
2574function Trex(canvas, image) {
2575 this.canvas = canvas;
2576 this.canvasCtx = canvas.getContext('2d');
2577 this.image = image;
2578 this.xPos = 0;
2579 this.yPos = 0;
2580 // Position when on the ground.
2581 this.groundYPos = 0;
2582 this.currentFrame = 0;
2583 this.currentAnimFrames = [];
2584 this.blinkDelay = 0;
2585 this.animStartTime = 0;
2586 this.timer = 0;
2587 this.msPerFrame = 1000 / FPS;
2588 this.config = Trex.config;
2589 // Current status.
2590 this.status = Trex.status.WAITING;
2591
2592 this.jumping = false;
2593 this.jumpVelocity = 0;
2594 this.reachedMinHeight = false;
2595 this.speedDrop = false;
2596 this.jumpCount = 0;
2597 this.jumpspotX = 0;
2598
2599 this.init();
2600};
2601
2602
2603/**
2604 * T-rex player config.
2605 * @enum {number}
2606 */
2607Trex.config = {
2608 DROP_VELOCITY: -5,
2609 GRAVITY: 0.6,
2610 HEIGHT: 47,
2611 INIITAL_JUMP_VELOCITY: -10,
2612 INTRO_DURATION: 1500,
2613 MAX_JUMP_HEIGHT: 30,
2614 MIN_JUMP_HEIGHT: 30,
2615 SPEED_DROP_COEFFICIENT: 3,
2616 SPRITE_WIDTH: 262,
2617 START_X_POS: 50,
2618 WIDTH: 44
2619};
2620
2621
2622/**
2623 * Used in collision detection.
2624 * @type {Array<CollisionBox>}
2625 */
2626Trex.collisionBoxes = [
2627 new CollisionBox(1, -1, 30, 26),
2628 new CollisionBox(32, 0, 8, 16),
2629 new CollisionBox(10, 35, 14, 8),
2630 new CollisionBox(1, 24, 29, 5),
2631 new CollisionBox(5, 30, 21, 4),
2632 new CollisionBox(9, 34, 15, 4)
2633];
2634
2635
2636/**
2637 * Animation states.
2638 * @enum {string}
2639 */
2640Trex.status = {
2641 CRASHED: 'CRASHED',
2642 JUMPING: 'JUMPING',
2643 RUNNING: 'RUNNING',
2644 WAITING: 'WAITING'
2645};
2646
2647/**
2648 * Blinking coefficient.
2649 * @const
2650 */
2651Trex.BLINK_TIMING = 7000;
2652
2653
2654/**
2655 * Animation config for different states.
2656 * @enum {object}
2657 */
2658Trex.animFrames = {
2659 WAITING: {
2660 frames: [44, 0],
2661 msPerFrame: 1000 / 3
2662 },
2663 RUNNING: {
2664 frames: [88, 132],
2665 msPerFrame: 1000 / 12
2666 },
2667 CRASHED: {
2668 frames: [220],
2669 msPerFrame: 1000 / 60
2670 },
2671 JUMPING: {
2672 frames: [0],
2673 msPerFrame: 1000 / 60
2674 }
2675};
2676
2677
2678Trex.prototype = {
2679 /**
2680 * T-rex player initaliser.
2681 * Sets the t-rex to blink at random intervals.
2682 */
2683 init: function() {
2684 this.blinkDelay = this.setBlinkDelay();
2685 this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT -
2686 Runner.config.BOTTOM_PAD;
2687 this.yPos = this.groundYPos;
2688 this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT;
2689
2690 this.draw(0, 0);
2691 this.update(0, Trex.status.WAITING);
2692 },
2693
2694 /**
2695 * Setter for the jump velocity.
2696 * The approriate drop velocity is also set.
2697 */
2698 setJumpVelocity: function(setting) {
2699 this.config.INIITAL_JUMP_VELOCITY = -setting;
2700 this.config.DROP_VELOCITY = -setting / 2;
2701 },
2702
2703 /**
2704 * Set the animation status.
2705 * @param {!number} deltaTime
2706 * @param {Trex.status} status Optional status to switch to.
2707 */
2708 update: function(deltaTime, opt_status) {
2709 this.timer += deltaTime;
2710
2711 // Update the status.
2712 if (opt_status) {
2713 this.status = opt_status;
2714 this.currentFrame = 0;
2715 this.msPerFrame = Trex.animFrames[opt_status].msPerFrame;
2716 this.currentAnimFrames = Trex.animFrames[opt_status].frames;
2717
2718 if (opt_status == Trex.status.WAITING) {
2719 this.animStartTime = getTimeStamp();
2720 this.setBlinkDelay();
2721 }
2722 }
2723
2724 // Game intro animation, T-rex moves in from the left.
2725 if (this.playingIntro && this.xPos < this.config.START_X_POS) {
2726 this.xPos += Math.round((this.config.START_X_POS /
2727 this.config.INTRO_DURATION) * deltaTime);
2728 }
2729
2730 if (this.status == Trex.status.WAITING) {
2731 this.blink(getTimeStamp());
2732 } else {
2733 this.draw(this.currentAnimFrames[this.currentFrame], 0);
2734 }
2735
2736 // Update the frame position.
2737 if (this.timer >= this.msPerFrame) {
2738 this.currentFrame = this.currentFrame ==
2739 this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1;
2740 this.timer = 0;
2741 }
2742 },
2743
2744 /**
2745 * Draw the t-rex to a particular position.
2746 * @param {number} x
2747 * @param {number} y
2748 */
2749 draw: function(x, y) {
2750 var sourceX = x;
2751 var sourceY = y;
2752 var sourceWidth = this.config.WIDTH;
2753 var sourceHeight = this.config.HEIGHT;
2754
2755 if (IS_HIDPI) {
2756 sourceX *= 2;
2757 sourceY *= 2;
2758 sourceWidth *= 2;
2759 sourceHeight *= 2;
2760 }
2761
2762 this.canvasCtx.drawImage(this.image, sourceX, sourceY,
2763 sourceWidth, sourceHeight,
2764 this.xPos, this.yPos,
2765 this.config.WIDTH, this.config.HEIGHT);
2766 },
2767
2768 /**
2769 * Sets a random time for the blink to happen.
2770 */
2771 setBlinkDelay: function() {
2772 this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING);
2773 },
2774
2775 /**
2776 * Make t-rex blink at random intervals.
2777 * @param {number} time Current time in milliseconds.
2778 */
2779 blink: function(time) {
2780 var deltaTime = time - this.animStartTime;
2781
2782 if (deltaTime >= this.blinkDelay) {
2783 this.draw(this.currentAnimFrames[this.currentFrame], 0);
2784
2785 if (this.currentFrame == 1) {
2786 // Set new random delay to blink.
2787 this.setBlinkDelay();
2788 this.animStartTime = time;
2789 }
2790 }
2791 },
2792
2793 /**
2794 * Initialise a jump.
2795 */
2796 startJump: function() {
2797 if (!this.jumping) {
2798 this.update(0, Trex.status.JUMPING);
2799 this.jumpVelocity = this.config.INIITAL_JUMP_VELOCITY;
2800 this.jumping = true;
2801 this.reachedMinHeight = false;
2802 this.speedDrop = false;
2803 }
2804 },
2805
2806 /**
2807 * Jump is complete, falling down.
2808 */
2809 endJump: function() {
2810 if (this.reachedMinHeight &&
2811 this.jumpVelocity < this.config.DROP_VELOCITY) {
2812 this.jumpVelocity = this.config.DROP_VELOCITY;
2813 }
2814 },
2815
2816 /**
2817 * Update frame for a jump.
2818 * @param {number} deltaTime
2819 */
2820 updateJump: function(deltaTime) {
2821 var msPerFrame = Trex.animFrames[this.status].msPerFrame;
2822 var framesElapsed = deltaTime / msPerFrame;
2823
2824 // Speed drop makes Trex fall faster.
2825 if (this.speedDrop) {
2826 this.yPos += Math.round(this.jumpVelocity *
2827 this.config.SPEED_DROP_COEFFICIENT * framesElapsed);
2828 } else {
2829 this.yPos += Math.round(this.jumpVelocity * framesElapsed);
2830 }
2831
2832 this.jumpVelocity += this.config.GRAVITY * framesElapsed;
2833
2834 // Minimum height has been reached.
2835 if (this.yPos < this.minJumpHeight || this.speedDrop) {
2836 this.reachedMinHeight = true;
2837 }
2838
2839 // Reached max height
2840 if (this.yPos < this.config.MAX_JUMP_HEIGHT || this.speedDrop) {
2841 this.endJump();
2842 }
2843
2844 // Back down at ground level. Jump completed.
2845 if (this.yPos > this.groundYPos) {
2846 this.reset();
2847 this.jumpCount++;
2848 }
2849
2850 this.update(deltaTime);
2851 },
2852
2853 /**
2854 * Set the speed drop. Immediately cancels the current jump.
2855 */
2856 setSpeedDrop: function() {
2857 this.speedDrop = true;
2858 this.jumpVelocity = 1;
2859 },
2860
2861 /**
2862 * Reset the t-rex to running at start of game.
2863 */
2864 reset: function() {
2865 this.yPos = this.groundYPos;
2866 this.jumpVelocity = 0;
2867 this.jumping = false;
2868 this.update(0, Trex.status.RUNNING);
2869 this.midair = false;
2870 this.speedDrop = false;
2871 this.jumpCount = 0;
2872 }
2873};
2874
2875
2876//******************************************************************************
2877
2878/**
2879 * Handles displaying the distance meter.
2880 * @param {!HTMLCanvasElement} canvas
2881 * @param {!HTMLImage} spriteSheet Image sprite.
2882 * @param {number} canvasWidth
2883 * @constructor
2884 */
2885function DistanceMeter(canvas, spriteSheet, canvasWidth) {
2886 this.canvas = canvas;
2887 this.canvasCtx = canvas.getContext('2d');
2888 this.image = spriteSheet;
2889 this.x = 0;
2890 this.y = 5;
2891
2892 this.currentDistance = 0;
2893 this.maxScore = 0;
2894 this.highScore = 0;
2895 this.container = null;
2896
2897 this.digits = [];
2898 this.acheivement = false;
2899 this.defaultString = '';
2900 this.flashTimer = 0;
2901 this.flashIterations = 0;
2902
2903 this.config = DistanceMeter.config;
2904 this.init(canvasWidth);
2905};
2906
2907
2908/**
2909 * @enum {number}
2910 */
2911DistanceMeter.dimensions = {
2912 WIDTH: 10,
2913 HEIGHT: 13,
2914 DEST_WIDTH: 11
2915};
2916
2917
2918/**
2919 * Y positioning of the digits in the sprite sheet.
2920 * X position is always 0.
2921 * @type {array<number>}
2922 */
2923DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120];
2924
2925
2926/**
2927 * Distance meter config.
2928 * @enum {number}
2929 */
2930DistanceMeter.config = {
2931 // Number of digits.
2932 MAX_DISTANCE_UNITS: 5,
2933
2934 // Distance that causes achievement animation.
2935 ACHIEVEMENT_DISTANCE: 100,
2936
2937 // Used for conversion from pixel distance to a scaled unit.
2938 COEFFICIENT: 0.025,
2939
2940 // Flash duration in milliseconds.
2941 FLASH_DURATION: 1000 / 4,
2942
2943 // Flash iterations for achievement animation.
2944 FLASH_ITERATIONS: 3
2945};
2946
2947
2948DistanceMeter.prototype = {
2949 /**
2950 * Initialise the distance meter to '00000'.
2951 * @param {number} width Canvas width in px.
2952 */
2953 init: function(width) {
2954 var maxDistanceStr = '';
2955
2956 this.calcXPos(width);
2957 this.maxScore = this.config.MAX_DISTANCE_UNITS;
2958 for (var i = 0; i < this.config.MAX_DISTANCE_UNITS; i++) {
2959 this.draw(i, 0);
2960 this.defaultString += '0';
2961 maxDistanceStr += '9';
2962 }
2963
2964 this.maxScore = parseInt(maxDistanceStr);
2965 },
2966
2967 /**
2968 * Calculate the xPos in the canvas.
2969 * @param {number} canvasWidth
2970 */
2971 calcXPos: function(canvasWidth) {
2972 this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH *
2973 (this.config.MAX_DISTANCE_UNITS + 1));
2974 },
2975
2976 /**
2977 * Draw a digit to canvas.
2978 * @param {number} digitPos Position of the digit.
2979 * @param {number} value Digit value 0-9.
2980 * @param {boolean} opt_highScore Whether drawing the high score.
2981 */
2982 draw: function(digitPos, value, opt_highScore) {
2983 var sourceWidth = DistanceMeter.dimensions.WIDTH;
2984 var sourceHeight = DistanceMeter.dimensions.HEIGHT;
2985 var sourceX = DistanceMeter.dimensions.WIDTH * value;
2986
2987 var targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH;
2988 var targetY = this.y;
2989 var targetWidth = DistanceMeter.dimensions.WIDTH;
2990 var targetHeight = DistanceMeter.dimensions.HEIGHT;
2991
2992 // For high DPI we 2x source values.
2993 if (IS_HIDPI) {
2994 sourceWidth *= 2;
2995 sourceHeight *= 2;
2996 sourceX *= 2;
2997 }
2998
2999 this.canvasCtx.save();
3000
3001 if (opt_highScore) {
3002 // Left of the current score.
3003 var highScoreX = this.x - (this.config.MAX_DISTANCE_UNITS * 2) *
3004 DistanceMeter.dimensions.WIDTH;
3005 this.canvasCtx.translate(highScoreX, this.y);
3006 } else {
3007 this.canvasCtx.translate(this.x, this.y);
3008 }
3009
3010 this.canvasCtx.drawImage(this.image, sourceX, 0,
3011 sourceWidth, sourceHeight,
3012 targetX, targetY,
3013 targetWidth, targetHeight
3014 );
3015
3016 this.canvasCtx.restore();
3017 },
3018
3019 /**
3020 * Covert pixel distance to a 'real' distance.
3021 * @param {number} distance Pixel distance ran.
3022 * @return {number} The 'real' distance ran.
3023 */
3024 getActualDistance: function(distance) {
3025 return distance ?
3026 Math.round(distance * this.config.COEFFICIENT) : 0;
3027 },
3028
3029 /**
3030 * Update the distance meter.
3031 * @param {number} deltaTime
3032 * @param {number} distance
3033 * @return {boolean} Whether the acheivement sound fx should be played.
3034 */
3035 update: function(deltaTime, distance) {
3036 var paint = true;
3037 var playSound = false;
3038
3039 if (!this.acheivement) {
3040 distance = this.getActualDistance(distance);
3041
3042 if (distance > 0) {
3043 // Acheivement unlocked
3044 if (distance % this.config.ACHIEVEMENT_DISTANCE == 0) {
3045 // Flash score and play sound.
3046 this.acheivement = true;
3047 this.flashTimer = 0;
3048 playSound = true;
3049 }
3050
3051 // Create a string representation of the distance with leading 0.
3052 var distanceStr = (this.defaultString +
3053 distance).substr(-this.config.MAX_DISTANCE_UNITS);
3054 this.digits = distanceStr.split('');
3055 } else {
3056 this.digits = this.defaultString.split('');
3057 }
3058 } else {
3059 // Control flashing of the score on reaching acheivement.
3060 if (this.flashIterations <= this.config.FLASH_ITERATIONS) {
3061 this.flashTimer += deltaTime;
3062
3063 if (this.flashTimer < this.config.FLASH_DURATION) {
3064 paint = false;
3065 } else if (this.flashTimer >
3066 this.config.FLASH_DURATION * 2) {
3067 this.flashTimer = 0;
3068 this.flashIterations++;
3069 }
3070 } else {
3071 this.acheivement = false;
3072 this.flashIterations = 0;
3073 this.flashTimer = 0;
3074 }
3075 }
3076
3077 // Draw the digits if not flashing.
3078 if (paint) {
3079 for (var i = this.digits.length - 1; i >= 0; i--) {
3080 this.draw(i, parseInt(this.digits[i]));
3081 }
3082 }
3083
3084 this.drawHighScore();
3085
3086 return playSound;
3087 },
3088
3089 /**
3090 * Draw the high score.
3091 */
3092 drawHighScore: function() {
3093 this.canvasCtx.save();
3094 this.canvasCtx.globalAlpha = .8;
3095 for (var i = this.highScore.length - 1; i >= 0; i--) {
3096 this.draw(i, parseInt(this.highScore[i], 10), true);
3097 }
3098 this.canvasCtx.restore();
3099 },
3100
3101 /**
3102 * Set the highscore as a array string.
3103 * Position of char in the sprite: H - 10, I - 11.
3104 * @param {number} distance Distance ran in pixels.
3105 */
3106 setHighScore: function(distance) {
3107 distance = this.getActualDistance(distance);
3108 var highScoreStr = (this.defaultString +
3109 distance).substr(-this.config.MAX_DISTANCE_UNITS);
3110
3111 this.highScore = ['10', '11', ''].concat(highScoreStr.split(''));
3112 },
3113
3114 /**
3115 * Reset the distance meter back to '00000'.
3116 */
3117 reset: function() {
3118 this.update(0);
3119 this.acheivement = false;
3120 }
3121};
3122
3123
3124//******************************************************************************
3125
3126/**
3127 * Cloud background item.
3128 * Similar to an obstacle object but without collision boxes.
3129 * @param {HTMLCanvasElement} canvas Canvas element.
3130 * @param {Image} cloudImg
3131 * @param {number} containerWidth
3132 */
3133function Cloud(canvas, cloudImg, containerWidth) {
3134 this.canvas = canvas;
3135 this.canvasCtx = this.canvas.getContext('2d');
3136 this.image = cloudImg;
3137 this.containerWidth = containerWidth;
3138 this.xPos = containerWidth;
3139 this.yPos = 0;
3140 this.remove = false;
3141 this.cloudGap = getRandomNum(Cloud.config.MIN_CLOUD_GAP,
3142 Cloud.config.MAX_CLOUD_GAP);
3143
3144 this.init();
3145};
3146
3147
3148/**
3149 * Cloud object config.
3150 * @enum {number}
3151 */
3152Cloud.config = {
3153 HEIGHT: 14,
3154 MAX_CLOUD_GAP: 400,
3155 MAX_SKY_LEVEL: 30,
3156 MIN_CLOUD_GAP: 100,
3157 MIN_SKY_LEVEL: 71,
3158 WIDTH: 46
3159};
3160
3161
3162Cloud.prototype = {
3163 /**
3164 * Initialise the cloud. Sets the Cloud height.
3165 */
3166 init: function() {
3167 this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL,
3168 Cloud.config.MIN_SKY_LEVEL);
3169 this.draw();
3170 },
3171
3172 /**
3173 * Draw the cloud.
3174 */
3175 draw: function() {
3176 this.canvasCtx.save();
3177 var sourceWidth = Cloud.config.WIDTH;
3178 var sourceHeight = Cloud.config.HEIGHT;
3179
3180 if (IS_HIDPI) {
3181 sourceWidth = sourceWidth * 2;
3182 sourceHeight = sourceHeight * 2;
3183 }
3184
3185 this.canvasCtx.drawImage(this.image, 0, 0,
3186 sourceWidth, sourceHeight,
3187 this.xPos, this.yPos,
3188 Cloud.config.WIDTH, Cloud.config.HEIGHT);
3189
3190 this.canvasCtx.restore();
3191 },
3192
3193 /**
3194 * Update the cloud position.
3195 * @param {number} speed
3196 */
3197 update: function(speed) {
3198 if (!this.remove) {
3199 this.xPos -= Math.ceil(speed);
3200 this.draw();
3201
3202 // Mark as removeable if no longer in the canvas.
3203 if (!this.isVisible()) {
3204 this.remove = true;
3205 }
3206 }
3207 },
3208
3209 /**
3210 * Check if the cloud is visible on the stage.
3211 * @return {boolean}
3212 */
3213 isVisible: function() {
3214 return this.xPos + Cloud.config.WIDTH > 0;
3215 }
3216};
3217
3218
3219//******************************************************************************
3220
3221/**
3222 * Horizon Line.
3223 * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon.
3224 * @param {HTMLCanvasElement} canvas
3225 * @param {HTMLImage} bgImg Horizon line sprite.
3226 * @constructor
3227 */
3228function HorizonLine(canvas, bgImg) {
3229 this.image = bgImg;
3230 this.canvas = canvas;
3231 this.canvasCtx = canvas.getContext('2d');
3232 this.sourceDimensions = {};
3233 this.dimensions = HorizonLine.dimensions;
3234 this.sourceXPos = [0, this.dimensions.WIDTH];
3235 this.xPos = [];
3236 this.yPos = 0;
3237 this.bumpThreshold = 0.5;
3238
3239 this.setSourceDimensions();
3240 this.draw();
3241};
3242
3243
3244/**
3245 * Horizon line dimensions.
3246 * @enum {number}
3247 */
3248HorizonLine.dimensions = {
3249 WIDTH: 600,
3250 HEIGHT: 12,
3251 YPOS: 127
3252};
3253
3254
3255HorizonLine.prototype = {
3256 /**
3257 * Set the source dimensions of the horizon line.
3258 */
3259 setSourceDimensions: function() {
3260
3261 for (var dimension in HorizonLine.dimensions) {
3262 if (IS_HIDPI) {
3263 if (dimension != 'YPOS') {
3264 this.sourceDimensions[dimension] =
3265 HorizonLine.dimensions[dimension] * 2;
3266 }
3267 } else {
3268 this.sourceDimensions[dimension] =
3269 HorizonLine.dimensions[dimension];
3270 }
3271 this.dimensions[dimension] = HorizonLine.dimensions[dimension];
3272 }
3273
3274 this.xPos = [0, HorizonLine.dimensions.WIDTH];
3275 this.yPos = HorizonLine.dimensions.YPOS;
3276 },
3277
3278 /**
3279 * Return the crop x position of a type.
3280 */
3281 getRandomType: function() {
3282 return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0;
3283 },
3284
3285 /**
3286 * Draw the horizon line.
3287 */
3288 draw: function() {
3289 this.canvasCtx.drawImage(this.image, this.sourceXPos[0], 0,
3290 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
3291 this.xPos[0], this.yPos,
3292 this.dimensions.WIDTH, this.dimensions.HEIGHT);
3293
3294 this.canvasCtx.drawImage(this.image, this.sourceXPos[1], 0,
3295 this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT,
3296 this.xPos[1], this.yPos,
3297 this.dimensions.WIDTH, this.dimensions.HEIGHT);
3298 },
3299
3300 /**
3301 * Update the x position of an indivdual piece of the line.
3302 * @param {number} pos Line position.
3303 * @param {number} increment
3304 */
3305 updateXPos: function(pos, increment) {
3306 var line1 = pos;
3307 var line2 = pos == 0 ? 1 : 0;
3308
3309 this.xPos[line1] -= increment;
3310 this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH;
3311
3312 if (this.xPos[line1] <= -this.dimensions.WIDTH) {
3313 this.xPos[line1] += this.dimensions.WIDTH * 2;
3314 this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH;
3315 this.sourceXPos[line1] = this.getRandomType();
3316 }
3317 },
3318
3319 /**
3320 * Update the horizon line.
3321 * @param {number} deltaTime
3322 * @param {number} speed
3323 */
3324 update: function(deltaTime, speed) {
3325 var increment = Math.floor(speed * (FPS / 1000) * deltaTime);
3326
3327 if (this.xPos[0] <= 0) {
3328 this.updateXPos(0, increment);
3329 } else {
3330 this.updateXPos(1, increment);
3331 }
3332 this.draw();
3333 },
3334
3335 /**
3336 * Reset horizon to the starting position.
3337 */
3338 reset: function() {
3339 this.xPos[0] = 0;
3340 this.xPos[1] = HorizonLine.dimensions.WIDTH;
3341 }
3342};
3343
3344
3345//******************************************************************************
3346
3347/**
3348 * Horizon background class.
3349 * @param {HTMLCanvasElement} canvas
3350 * @param {Array<HTMLImageElement>} images
3351 * @param {object} dimensions Canvas dimensions.
3352 * @param {number} gapCoefficient
3353 * @constructor
3354 */
3355function Horizon(canvas, images, dimensions, gapCoefficient) {
3356 this.canvas = canvas;
3357 this.canvasCtx = this.canvas.getContext('2d');
3358 this.config = Horizon.config;
3359 this.dimensions = dimensions;
3360 this.gapCoefficient = gapCoefficient;
3361 this.obstacles = [];
3362 this.horizonOffsets = [0, 0];
3363 this.cloudFrequency = this.config.CLOUD_FREQUENCY;
3364
3365 // Cloud
3366 this.clouds = [];
3367 this.cloudImg = images.CLOUD;
3368 this.cloudSpeed = this.config.BG_CLOUD_SPEED;
3369
3370 // Horizon
3371 this.horizonImg = images.HORIZON;
3372 this.horizonLine = null;
3373
3374 // Obstacles
3375 this.obstacleImgs = {
3376 CACTUS_SMALL: images.CACTUS_SMALL,
3377 CACTUS_LARGE: images.CACTUS_LARGE
3378 };
3379
3380 this.init();
3381};
3382
3383
3384/**
3385 * Horizon config.
3386 * @enum {number}
3387 */
3388Horizon.config = {
3389 BG_CLOUD_SPEED: 0.2,
3390 BUMPY_THRESHOLD: .3,
3391 CLOUD_FREQUENCY: .5,
3392 HORIZON_HEIGHT: 16,
3393 MAX_CLOUDS: 6
3394};
3395
3396
3397Horizon.prototype = {
3398 /**
3399 * Initialise the horizon. Just add the line and a cloud. No obstacles.
3400 */
3401 init: function() {
3402 this.addCloud();
3403 this.horizonLine = new HorizonLine(this.canvas, this.horizonImg);
3404 },
3405
3406 /**
3407 * @param {number} deltaTime
3408 * @param {number} currentSpeed
3409 * @param {boolean} updateObstacles Used as an override to prevent
3410 * the obstacles from being updated / added. This happens in the
3411 * ease in section.
3412 */
3413 update: function(deltaTime, currentSpeed, updateObstacles) {
3414 this.runningTime += deltaTime;
3415 this.horizonLine.update(deltaTime, currentSpeed);
3416 this.updateClouds(deltaTime, currentSpeed);
3417
3418 if (updateObstacles) {
3419 this.updateObstacles(deltaTime, currentSpeed);
3420 }
3421 },
3422
3423 /**
3424 * Update the cloud positions.
3425 * @param {number} deltaTime
3426 * @param {number} currentSpeed
3427 */
3428 updateClouds: function(deltaTime, speed) {
3429 var cloudSpeed = this.cloudSpeed / 1000 * deltaTime * speed;
3430 var numClouds = this.clouds.length;
3431
3432 if (numClouds) {
3433 for (var i = numClouds - 1; i >= 0; i--) {
3434 this.clouds[i].update(cloudSpeed);
3435 }
3436
3437 var lastCloud = this.clouds[numClouds - 1];
3438
3439 // Check for adding a new cloud.
3440 if (numClouds < this.config.MAX_CLOUDS &&
3441 (this.dimensions.WIDTH - lastCloud.xPos) > lastCloud.cloudGap &&
3442 this.cloudFrequency > Math.random()) {
3443 this.addCloud();
3444 }
3445
3446 // Remove expired clouds.
3447 this.clouds = this.clouds.filter(function(obj) {
3448 return !obj.remove;
3449 });
3450 }
3451 },
3452
3453 /**
3454 * Update the obstacle positions.
3455 * @param {number} deltaTime
3456 * @param {number} currentSpeed
3457 */
3458 updateObstacles: function(deltaTime, currentSpeed) {
3459 // Obstacles, move to Horizon layer.
3460 var updatedObstacles = this.obstacles.slice(0);
3461
3462 for (var i = 0; i < this.obstacles.length; i++) {
3463 var obstacle = this.obstacles[i];
3464 obstacle.update(deltaTime, currentSpeed);
3465
3466 // Clean up existing obstacles.
3467 if (obstacle.remove) {
3468 updatedObstacles.shift();
3469 }
3470 }
3471 this.obstacles = updatedObstacles;
3472
3473 if (this.obstacles.length > 0) {
3474 var lastObstacle = this.obstacles[this.obstacles.length - 1];
3475
3476 if (lastObstacle && !lastObstacle.followingObstacleCreated &&
3477 lastObstacle.isVisible() &&
3478 (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) <
3479 this.dimensions.WIDTH) {
3480 this.addNewObstacle(currentSpeed);
3481 lastObstacle.followingObstacleCreated = true;
3482 }
3483 } else {
3484 // Create new obstacles.
3485 this.addNewObstacle(currentSpeed);
3486 }
3487 },
3488
3489 /**
3490 * Add a new obstacle.
3491 * @param {number} currentSpeed
3492 */
3493 addNewObstacle: function(currentSpeed) {
3494 var obstacleTypeIndex =
3495 getRandomNum(0, Obstacle.types.length - 1);
3496 var obstacleType = Obstacle.types[obstacleTypeIndex];
3497 var obstacleImg = this.obstacleImgs[obstacleType.type];
3498
3499 this.obstacles.push(new Obstacle(this.canvasCtx, obstacleType,
3500 obstacleImg, this.dimensions, this.gapCoefficient, currentSpeed));
3501 },
3502
3503 /**
3504 * Reset the horizon layer.
3505 * Remove existing obstacles and reposition the horizon line.
3506 */
3507 reset: function() {
3508 this.obstacles = [];
3509 this.horizonLine.reset();
3510 },
3511
3512 /**
3513 * Update the canvas width and scaling.
3514 * @param {number} width Canvas width.
3515 * @param {number} height Canvas height.
3516 */
3517 resize: function(width, height) {
3518 this.canvas.width = width;
3519 this.canvas.height = height;
3520 },
3521
3522 /**
3523 * Add a new cloud to the horizon.
3524 */
3525 addCloud: function() {
3526 this.clouds.push(new Cloud(this.canvas, this.cloudImg,
3527 this.dimensions.WIDTH));
3528 }
3529};
3530})();
3531</script>
3532</head>
3533<body id="t" i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize" class="offline" style="font-family: 'Helvetica Neue', 'Lucida Grande', sans-serif; font-size: 75%;">
3534 <div id="main-frame-error" class="interstitial-wrapper">
3535 <div id="main-content">
3536 <img class="icon icon-offline" jseval="updateIconClass(this.classList, iconClass)" style="visibility: hidden;">
3537 <div id="main-message">
3538 <h1 i18n-content="heading">My Own T-Rex Game YEY!!!!</h1>
3539 <p>Internet is connected, that does not mean Google Chrome can stop you playing this game :P.</p>
3540 <div class="error-code" jscontent="errorCode">THANKS GOOGLE FOR THIS GAME</div>
3541 </div>
3542 </div>
3543
3544 <div class="runner-container" style="width: 600px; height: 150px;"><canvas class="runner-canvas" width="1200" height="300" style="width: 600px; height: 150px;"></canvas></div></div>
3545 <div id="sub-frame-error">
3546 <!-- Show details when hovering over the icon, in case the details are
3547 hidden because they're too large. -->
3548 <img class="icon icon-offline" jseval="updateIconClass(this.classList, iconClass)" jsvalues=".title:errorDetails" title="The Internet connection has been lost.">
3549 <div id="sub-frame-error-details" jsvalues=".innerHTML:errorDetails">The Internet connection has been lost.</div>
3550 </div>
3551
3552 <div id="offline-resources">
3553 <div id="offline-resources-1x">
3554 <img id="1x-obstacle-large" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAAAyCAMAAACJUtIoAAAACVBMVEX////39/dTU1OabbyfAAAAAXRSTlMAQObYZgAAAXhJREFUeF7t2NGqAjEMANGM///RlwvaYQndULuFPJgHUYaEI6IPhgNAOA8HZ+3U6384F5y1U6YzAZTWG+dZamnFEstBFtCKJZSHWMADLJ18z+JqpQeLdKoDC8siC5iFCQs4znIxB5B1t6F3lQWkL4N0JsF+u6GXJdbI+FKW+yWr3lhgCZ2VSag3Nlk/FnRkIRbasLCO0oulikMsvmGpeiGLZ1jOMgtIP5bODivYYUXEIVbwFCt4khVssRgsgidZwQaLd2A8m7MYLGTl4KeQQs2y4kMAMGGlmQViDIb5O6xZnnLD485dIBzqDSE1yyFdL4Iqu4XJqUUWl/NVAFSZq1P6a5aqbAUM2epQbBioWflUBABiUyhYyZoCBev8XyMAObDNOhOAfiyxmHU0YNlldGAphGjFCjA3YkUn1o/1Y3EkZFZ5isCC6NUgwDBn1RuXH96doNfAhDXfsIyJ2AnolcCVhay0kcYbW0HvCO8OwIcJ3GzkORpkFuUP/1Ec8FW1qJkAAAAASUVORK5CYII=">
3555 <img id="1x-obstacle-small" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGYAAAAjCAMAAABRlI+PAAAADFBMVEX////////39/dTU1PhglcSAAAAAXRSTlMAQObYZgAAAPNJREFUeF7tlkEKwzAMBLXr//+5iQhU7gRRQkyhZI+DhwH74jhmO+oIJBVwURljuAXagG5QqkSgBLqg3JnxJ1Cb8SmQ3o6gpO85owGlOB4m2BNKJ11BSd01owGlOHkcIAuHkz6UNpPKgozPM54dADHjJuNhZiJxdQCQgZJeBczgCAAy3yhPJvcnmdC9mZwBIsQMFV5AkzHBNknFgcKM+oyDIFcfCAoy03m+jSMIcmoVZkKqSjr1fghyahRmoKRUHYLiSI1SMlCq5CDgX6BXmKkfn+oQ0KEyyrzoy8GbXJ9xrM/YjhUZgl9nnsyTCe9rgSRdV15CwRcIEu8GGQAAAABJRU5ErkJggg==">
3556 <img id="1x-cloud" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAOCAQAAAD6HOaKAAAAU0lEQVR4XrWSsQkAQAgD3X9El/ELixQpJHCfdApnUCtXz7o49cgagaGPaq4rIwAP9s/C7R7UX3inJ0BDb6qWDC7ScOR/QWjRlFizuPwLtTLj+qkH6DjD2wLtikUAAAAASUVORK5CYII=">
3557 <img id="1x-text" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAL8AAAAYAQMAAACRPb4TAAAABlBMVEX///9TU1NYzE1OAAAAAXRSTlMAQObYZgAAAPBJREFUeF6lzzFqwzAUx+F/0eBN7wIBXcGjQk3V2/QAHZohEAfv7nU6lUKGbD7DAxe62Soe6lAjtRLGiQOGQh8aHnyCHw+ZSD4dmUPntqpaO0yzIzo5Y46d86a5hAeSemV0IspaNzo9Q11gkz6ZCqvuGkQ/fJnWZt60/Rzs8IHWplt1Bcmb9Hmj+TmfNbqCDqV/CbCfwaMgUSb6F4p9dQmZak9Doo6Wd0qu3TL8YxRDAczwDIgeUoIcqF8GDdzm+GYwACnjiwuP3/8ONY/g8wj0GgLybsy+T43QB8gug+ZgPMGNjY0NVITzHbgHudBYgh9W5adHzknZVQAAAABJRU5ErkJggg==">
3558 <img id="1x-horizon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABLAAAAAMAgMAAAAPCKxBAAAABlBMVEX///9TU1NYzE1OAAAAAXRSTlMAQObYZgAAALJJREFUeF7t1EEKAyEMhtEvMNm7sPfJEVyY+1+ltLgYAsrQCtWhbxEhQvgxIJtSZypxa/WGshgzKdbq/UihMFMlt3o/CspEYoihIMaAb6mCvM6C+BTAeyo+wN4yykV/6pVfkdLpVyI1hh7GJ6QunUoLEQlQglNP2nkQkeF8+ei9cLxMue1qxVRfk1Ej0s6AEGWfVOk0QUtnK5Xo0Lac6wpdtnQqB6VxomPaz+dgF1PaqqmeWJlz1jYUaSIAAAAASUVORK5CYII=">
3559 <img id="1x-trex" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQgAAAAvAgMAAABiRrxWAAAADFBMVEX///9TU1P39/f///+TS9URAAAAAXRSTlMAQObYZgAAAPpJREFUeF7d0jFKRkEMhdGLMM307itNLALyVmHvJuzTDMjdn72E95PGFEZSmeoU4YMMgxhskvQec8YSVFX1NhGcS5ywtbmC8khcZeKq+ZWJ4F8Sr2+ZCErjkJFEfcjAc/6/BMlfcz6xHdhRthYzIZhIHMcTVY1scUUiAphK8CMSPUbieTBhvD9Lj0vyV4wklEGzHpciKGOJoBp7XDcFs4kWxxM7Ey3iZ8JbzASAvMS7XLOJHTTvEkEZSeQl7DMuwVyCasqK5+XzQRYLUJlMbPXjFcn3m8eKBSjWZMJwvGIOvViAzCbUj1VEDoqFOEQGE3SyInJQLOQMJL4B7enP1UbLXJQAAAAASUVORK5CYII=">
3560 <img id="1x-restart" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAgCAQAAADQmBIFAAAAZklEQVR4Xu3WMQoAIAxDUe/Y+58jYwV1CwQJWQT5o/DAoaWjV2i/LRym/A5FjEsR41LPQchByEHwIVAEC4gZpghmSDP8egXpr/hQZaAKQFQe+pBOQAblDC336qrlPpSg0MEjInbWTLFFmwc8TpTAAAAAAElFTkSuQmCC">
3561 </div>
3562 <div id="offline-resources-2x">
3563 <img id="2x-obstacle-large" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAABkBAMAAADOLxDzAAAACVBMVEX///9TU1P39/ea77PDAAAAAXRSTlMAQObYZgAAAa9JREFUeF7t1lFqhEAQBuG+wl6h7n/IEEgKlma2R8Vk1O4HWSh++Xzb8AKA8E4IXrlYnsXr+zgh1OdifZbBdFIApdWiWShtVhmQ+jAWMLFollCOsTzgxiyd7GcR01/YLOZf1SwsN2EBozBgAU9l4TAHkDWzCNjKApZlybO4z+GtFwu9bGKZl2TJSyxDxaoX8yyha7LGZRDqxR+ymtUsaNaWhTM+s5rl05tjNUsVz2Kxi6XqhSy4NcvbzgLSnzzvjqzgCCsiHsXSdZwVPIAVHGIhi+ABrOAAi5+Avy7HQhaycpAVpDDBsuKDAOBCrHzjQHgYhl9YsHxf+vRrsQxjVVAsDNMsF6uydBUhq+wWBq/ayCKWZekqA6DKPPEq/ZMsYllWdgGDoMdaLAzMsFwszgoAi1pDxUrWFKhZLlZnpXIkAORAs7YEoFmzQSxmt2NWs+xOP7GapRCiZjUrwFyymhX/xmpWs5rVrGZxQphmsT6LAAsvdgcBhmmWi9VZvN7+x+4K2WtgwBosFmZZvIh9IXsl8M5C1mCxLsvTfizoxfDTAfgdAIPFlVhxRqgHlrVZX9y44aEEvVqmAAAAAElFTkSuQmCC">
3564 <img id="2x-obstacle-small" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMwAAABGBAMAAAByJ2Z/AAAADFBMVEX///9TU1P39/f///+TS9URAAAAAXRSTlMAQObYZgAAARZJREFUeF7t2NEJwzAQg2GtcCtoBe2/W6k5aK8qLgR6ToL9KPzzgR+NPCRRjg2ScjiQ9DKMCE4HRYQOJB2MJyXyQWPQgeSCDD8HnYHh10F6NbJk9KyMwpJ+hkEfnoSyGX1NUmAOqVjSz4zrNgwhm9FbMmEyuS7DpQw/Gf5kOGEYXMgwWBobnGHQmZKsYuyKDcZk8gdmM5uJMzKbgS7I5KENgJzxxN95PUMfAKi8gCXO6BQM4cM4ysEZwplyfxFDErAhmWniDKT3pJEpD2RDMpPEGUt6mOIQ1XFGmiXOZNLIgKUpgzH4lTgDtDIgmY0NznhSnWhk/v2ZkuONGOI2DEn0MNf7ttvMZjazmc2AJDkdJOlQ0sk8AC45t4r28J0GAAAAAElFTkSuQmCC">
3565 <img id="2x-cloud" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFwAAAAcAgMAAACR2TCnAAAABlBMVEUAAADa2to4qB92AAAAAXRSTlMAQObYZgAAAFFJREFUeF6VzTEKAFEIxNA03m+a3P8q2wqi/E35BIdeGXq3q5hnrwBs7mC5vIZzu/nnqI319vRtqHB731blwSHjx+22+Rdn94rzQq0ugKPVlz5onyJcGdu0NgAAAABJRU5ErkJggg==">
3566 <img id="2x-text" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAX4AAAAwAQMAAAAsMYMXAAAABlBMVEX///9TU1NYzE1OAAAAAXRSTlMAQObYZgAAAPxJREFUeF7tk0FqBCEQRf/gwt14BK+RlV4p2Te0OUFfSZiLmFvUQvzpQgYbOqtA7FnkLz7FhydUlQUr8Ck2w3prjsXuVqMYFrEPVpw0AXgLWJO4bPTxhAiXqtzVbPbpEkCc5nG3GByLAKbwBQDbsBYJQGXTKYthJS8FogD6JOrOL/iiVvYqIN7hE3W/CC51oOjirwNsH+GDBcGwxIYFeh6p38ME4Ff6l6GoN63rTWtqovLMh7B7ngD0I/Fb1nQTxaDq/PBnMgEYFUC+BuDkPLsjwG2E1U0Bju1+nHt47hsqNkwA/KecJjiA8S97suYJgOEAfrwHvGM06eTvgW+oln6hOpuT1AAAAABJRU5ErkJggg==">
3567 <img id="2x-horizon" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACWAAAAAYAQMAAABEalRSAAAABlBMVEX///9TU1NYzE1OAAAAAXRSTlMAQObYZgAAAOtJREFUeF7tljEKwzAMRb/J0CWgI/QKOYAh1+pUcjQfpUfw2MFEHVyDQSQmQUNM9AYNcobnh4egU+YVqhAvZSpgsfolPnSv5d0nz3vHslgUdK81RLzyvHcsi+WBNxQh4Ln8pw4Wi7skAg9mXgHMrEACXJnbHIllsbqGAtwXhnYswzFzwPWxWEPc2CexoobkHM4ZpD6s2loWiyIEEwCChIomMiMEHqgP573C9eHkc5VLWh3XsljnGVoLWVl+31bp38piTVVuihtPOAm9kcRLbrFjEvqwamtZLK5eI8sSan9rXEK0LcNFrY5oWawf59S7YSRD7eMAAAAASUVORK5CYII=">
3568 <img id="2x-trex" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAhAAAABeAgMAAAAPo8UvAAAADFBMVEX///9TU1P39/f///+TS9URAAAAAXRSTlMAQObYZgAAASdJREFUeF7t1qFOBEEQRdEyGP7vGQy/hsHc/0MPSe8ylU2vKEIqqQnviRZXdI7pyUQuONda901FGAG6j8aa+6mDEUboHP01sk5EHHWEjt/UY0dk/U+Ir/cdkXUEovV1GFF/HQMR/mLWEUYYYQRrf65XRhgB2595Y80lYRjCCG7AV/IZ0FdDabgDhiKMgE+tAX01ES+ajDBCADpHZw0tRdaZCCNEGhCdNSSlQTEVYUROQGeNxxoxH2EErXU+wohdQXONqyBorDsixiB2Be01JiOM2BXQX1MRUxFGpAL6aypiMsIIJCFBtSK98fFYKd6wFDEbYUQgEYh6hTSkonbDDTAdYQTrKNd9QPWGUFwAYYRYR7U+XemGfB0ajTACWEe1Pl3thtxMhBHfOCEbEnR2KZcAAAAASUVORK5CYII=">
3569 <img id="2x-restart" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABAAgMAAADE0Nm5AAAACVBMVEX////39/dTU1OabbyfAAAAAXRSTlMAQObYZgAAAGNJREFUeF7d1CEOwDAMQ9GS3q/ExPcz8Sm3gYBWVRo0afvwSQl0ax1To22JntKWupfGjriSXiLViCXCmXBHCykJTxaYEeIQGcVrHYklcoX8YYpSUggzcpBTiv5JtQWorUltmS6s4ZKtz2GgjAAAAABJRU5ErkJggg==">
3570 </div>
3571 <template id="audio-resources">
3572 <audio id="offline-sound-press" src="data:audio/mpeg;base64,T2dnUwACAAAAAAAAAABVDxppAAAAABYzHfUBHgF2b3JiaXMAAAAAAkSsAAD/////AHcBAP////+4AU9nZ1MAAAAAAAAAAAAAVQ8aaQEAAAC9PVXbEEf//////////////////+IDdm9yYmlzNwAAAEFPOyBhb1R1ViBiNSBbMjAwNjEwMjRdIChiYXNlZCBvbiBYaXBoLk9yZydzIGxpYlZvcmJpcykAAAAAAQV2b3JiaXMlQkNWAQBAAAAkcxgqRqVzFoQQGkJQGeMcQs5r7BlCTBGCHDJMW8slc5AhpKBCiFsogdCQVQAAQAAAh0F4FISKQQghhCU9WJKDJz0IIYSIOXgUhGlBCCGEEEIIIYQQQgghhEU5aJKDJ0EIHYTjMDgMg+U4+ByERTlYEIMnQegghA9CuJqDrDkIIYQkNUhQgwY56ByEwiwoioLEMLgWhAQ1KIyC5DDI1IMLQoiag0k1+BqEZ0F4FoRpQQghhCRBSJCDBkHIGIRGQViSgwY5uBSEy0GoGoQqOQgfhCA0ZBUAkAAAoKIoiqIoChAasgoAyAAAEEBRFMdxHMmRHMmxHAsIDVkFAAABAAgAAKBIiqRIjuRIkiRZkiVZkiVZkuaJqizLsizLsizLMhAasgoASAAAUFEMRXEUBwgNWQUAZAAACKA4iqVYiqVoiueIjgiEhqwCAIAAAAQAABA0Q1M8R5REz1RV17Zt27Zt27Zt27Zt27ZtW5ZlGQgNWQUAQAAAENJpZqkGiDADGQZCQ1YBAAgAAIARijDEgNCQVQAAQAAAgBhKDqIJrTnfnOOgWQ6aSrE5HZxItXmSm4q5Oeecc87J5pwxzjnnnKKcWQyaCa0555zEoFkKmgmtOeecJ7F50JoqrTnnnHHO6WCcEcY555wmrXmQmo21OeecBa1pjppLsTnnnEi5eVKbS7U555xzzjnnnHPOOeec6sXpHJwTzjnnnKi9uZab0MU555xPxunenBDOOeecc84555xzzjnnnCA0ZBUAAAQAQBCGjWHcKQjS52ggRhFiGjLpQffoMAkag5xC6tHoaKSUOggllXFSSicIDVkFAAACAEAIIYUUUkghhRRSSCGFFGKIIYYYcsopp6CCSiqpqKKMMssss8wyyyyzzDrsrLMOOwwxxBBDK63EUlNtNdZYa+4555qDtFZaa621UkoppZRSCkJDVgEAIAAABEIGGWSQUUghhRRiiCmnnHIKKqiA0JBVAAAgAIAAAAAAT/Ic0REd0REd0REd0REd0fEczxElURIlURIt0zI101NFVXVl15Z1Wbd9W9iFXfd93fd93fh1YViWZVmWZVmWZVmWZVmWZVmWIDRkFQAAAgAAIIQQQkghhRRSSCnGGHPMOegklBAIDVkFAAACAAgAAABwFEdxHMmRHEmyJEvSJM3SLE/zNE8TPVEURdM0VdEVXVE3bVE2ZdM1XVM2XVVWbVeWbVu2dduXZdv3fd/3fd/3fd/3fd/3fV0HQkNWAQASAAA6kiMpkiIpkuM4jiRJQGjIKgBABgBAAACK4iiO4ziSJEmSJWmSZ3mWqJma6ZmeKqpAaMgqAAAQAEAAAAAAAACKpniKqXiKqHiO6IiSaJmWqKmaK8qm7Lqu67qu67qu67qu67qu67qu67qu67qu67qu67qu67qu67quC4SGrAIAJAAAdCRHciRHUiRFUiRHcoDQkFUAgAwAgAAAHMMxJEVyLMvSNE/zNE8TPdETPdNTRVd0gdCQVQAAIACAAAAAAAAADMmwFMvRHE0SJdVSLVVTLdVSRdVTVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVTdM0TRMIDVkJAJABAKAQW0utxdwJahxi0nLMJHROYhCqsQgiR7W3yjGlHMWeGoiUURJ7qihjiknMMbTQKSet1lI6hRSkmFMKFVIOWiA0ZIUAEJoB4HAcQLIsQLI0AAAAAAAAAJA0DdA8D7A8DwAAAAAAAAAkTQMsTwM0zwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQNI0QPM8QPM8AAAAAAAAANA8D/BEEfBEEQAAAAAAAAAszwM80QM8UQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwNE0QPM8QPM8AAAAAAAAALA8D/BEEfA8EQAAAAAAAAA0zwM8UQQ8UQQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAABDgAAAQYCEUGrIiAIgTADA4DjQNmgbPAziWBc+D50EUAY5lwfPgeRBFAAAAAAAAAAAAADTPg6pCVeGqAM3zYKpQVaguAAAAAAAAAAAAAJbnQVWhqnBdgOV5MFWYKlQVAAAAAAAAAAAAAE8UobpQXbgqwDNFuCpcFaoLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAABhwAAAIMKEMFBqyIgCIEwBwOIplAQCA4ziWBQAAjuNYFgAAWJYligAAYFmaKAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAGHAAAAgwoQwUGrISAIgCADAoimUBy7IsYFmWBTTNsgCWBtA8gOcBRBEACAAAKHAAAAiwQVNicYBCQ1YCAFEAAAZFsSxNE0WapmmaJoo0TdM0TRR5nqZ5nmlC0zzPNCGKnmeaEEXPM02YpiiqKhBFVRUAAFDgAAAQYIOmxOIAhYasBABCAgAMjmJZnieKoiiKpqmqNE3TPE8URdE0VdVVaZqmeZ4oiqJpqqrq8jxNE0XTFEXTVFXXhaaJommaommqquvC80TRNE1TVVXVdeF5omiapqmqruu6EEVRNE3TVFXXdV0giqZpmqrqurIMRNE0VVVVXVeWgSiapqqqquvKMjBN01RV15VdWQaYpqq6rizLMkBVXdd1ZVm2Aarquq4ry7INcF3XlWVZtm0ArivLsmzbAgAADhwAAAKMoJOMKouw0YQLD0ChISsCgCgAAMAYphRTyjAmIaQQGsYkhBJCJiWVlEqqIKRSUikVhFRSKiWjklJqKVUQUikplQpCKqWVVAAA2IEDANiBhVBoyEoAIA8AgCBGKcYYYwwyphRjzjkHlVKKMeeck4wxxphzzkkpGWPMOeeklIw555xzUkrmnHPOOSmlc84555yUUkrnnHNOSiklhM45J6WU0jnnnBMAAFTgAAAQYKPI5gQjQYWGrAQAUgEADI5jWZqmaZ4nipYkaZrneZ4omqZmSZrmeZ4niqbJ8zxPFEXRNFWV53meKIqiaaoq1xVF0zRNVVVVsiyKpmmaquq6ME3TVFXXdWWYpmmqquu6LmzbVFXVdWUZtq2aqiq7sgxcV3Vl17aB67qu7Nq2AADwBAcAoAIbVkc4KRoLLDRkJQCQAQBAGIOMQgghhRBCCiGElFIICQAAGHAAAAgwoQwUGrISAEgFAACQsdZaa6211kBHKaWUUkqpcIxSSimllFJKKaWUUkoppZRKSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoppZRSSimllFJKKaWUUkoFAC5VOADoPtiwOsJJ0VhgoSErAYBUAADAGKWYck5CKRVCjDkmIaUWK4QYc05KSjEWzzkHoZTWWiyecw5CKa3FWFTqnJSUWoqtqBQyKSml1mIQwpSUWmultSCEKqnEllprQQhdU2opltiCELa2klKMMQbhg4+xlVhqDD74IFsrMdVaAABmgwMARIINqyOcFI0FFhqyEgAICQAgjFGKMcYYc8455yRjjDHmnHMQQgihZIwx55xzDkIIIZTOOeeccxBCCCGEUkrHnHMOQgghhFBS6pxzEEIIoYQQSiqdcw5CCCGEUkpJpXMQQgihhFBCSSWl1DkIIYQQQikppZRCCCGEEkIoJaWUUgghhBBCKKGklFIKIYRSQgillJRSSimFEEoIpZSSUkkppRJKCSGEUlJJKaUUQggllFJKKimllEoJoYRSSimlpJRSSiGUUEIpBQAAHDgAAAQYQScZVRZhowkXHoBCQ1YCAGQAAJSyUkoorVVAIqUYpNpCR5mDFHOJLHMMWs2lYg4pBq2GyjGlGLQWMgiZUkxKCSV1TCknLcWYSuecpJhzjaVzEAAAAEEAgICQAAADBAUzAMDgAOFzEHQCBEcbAIAgRGaIRMNCcHhQCRARUwFAYoJCLgBUWFykXVxAlwEu6OKuAyEEIQhBLA6ggAQcnHDDE294wg1O0CkqdSAAAAAAAAwA8AAAkFwAERHRzGFkaGxwdHh8gISIjJAIAAAAAAAYAHwAACQlQERENHMYGRobHB0eHyAhIiMkAQCAAAIAAAAAIIAABAQEAAAAAAACAAAABARPZ2dTAARhGAAAAAAAAFUPGmkCAAAAO/2ofAwjXh4fIzYx6uqzbla00kVmK6iQVrrIbAUVUqrKzBmtJH2+gRvgBmJVbdRjKgQGAlI5/X/Ofo9yCQZsoHL6/5z9HuUSDNgAAAAACIDB4P/BQA4NcAAHhzYgQAhyZEChScMgZPzmQwZwkcYjJguOaCaT6Sp/Kand3Luej5yp9HApCHVtClzDUAdARABQMgC00kVNVxCUVrqo6QqCoqpkHqdBZaA+ViWsfXWfDxS00kVNVxDkVrqo6QqCjKoGkDPMI4eZeZZqpq8aZ9AMtNJFzVYQ1Fa6qNkKgqoiGrbSkmkbqXv3aIeKI/3mh4gORh4cy6gShGMZVYJwm9SKkJkzqK64CkyLTGbMGExnzhyrNcyYMQl0nE4rwzDkq0+D/PO1japBzB9E1XqdAUTVep0BnDStQJsDk7gaNQK5UeTMGgwzILIr00nCYH0Gd4wp1aAOEwlvhGwA2nl9c0KAu9LTJUSPIOXVyCVQpPP65oQAd6WnS4geQcqrkUugiC8QZa1eq9eqRUYCAFAWY/oggB0gm5gFWYhtgB6gSIeJS8FxMiAGycBBm2ABURdHBNQRQF0JAJDJ8PhkMplMJtcxH+aYTMhkjut1vXIdkwEAHryuAQAgk/lcyZXZ7Darzd2J3RBRoGf+V69evXJtviwAxOMBNqACAAIoAAAgM2tuRDEpAGAD0Khcc8kAQDgMAKDRbGlmFJENAACaaSYCoJkoAAA6mKlYAAA6TgBwxpkKAIDrBACdBAwA8LyGDACacTIRBoAA/in9zlAB4aA4Vczai/R/roGKBP4+pd8ZKiAcFKeKWXuR/s81UJHAn26QimqtBBQ2MW2QKUBUG+oBegpQ1GslgCIboA3IoId6DZeCg2QgkAyIQR3iYgwursY4RgGEH7/rmjBQwUUVgziioIgrroJRBECGTxaUDEAgvF4nYCagzZa1WbJGkhlJGobRMJpMM0yT0Z/6TFiwa/WXHgAKwAABmgLQiOy5yTVDATQdAACaDYCKrDkyA4A2TgoAAB1mTgpAGycjAAAYZ0yjxAEAmQ6FcQWAR4cHAOhDKACAeGkA0WEaGABQSfYcWSMAHhn9f87rKPpQpe8viN3YXQ08cCAy+v+c11H0oUrfXxC7sbsaeOAAmaAXkPWQ6sBBKRAe/UEYxiuPH7/j9bo+M0cAE31NOzEaVBBMChqRNUdWWTIFGRpCZo7ssuXMUBwgACpJZcmZRQMFQJNxMgoCAGKcjNEAEnoDqEoD1t37wH7KXc7FayXfFzrSQHQ7nxi7yVsKXN6eo7ewMrL+kxn/0wYf0gGXcpEoDSQI4CABFsAJ8AgeGf1/zn9NcuIMGEBk9P85/zXJiTNgAAAAPPz/rwAEHBDgGqgSAgQQAuaOAHj6ELgGOaBqRSpIg+J0EC3U8kFGa5qapr41xuXsTB/BpNn2BcPaFfV5vCYu12wisH/m1IkQmqJLYAKBHAAQBRCgAR75/H/Of01yCQbiZkgoRD7/n/Nfk1yCgbgZEgoAAAAAEADBcPgHQRjEAR4Aj8HFGaAAeIATDng74SYAwgEn8BBHUxA4Tyi3ZtOwTfcbkBQ4DAImJ6AA"></audio>
3573 <audio id="offline-sound-hit" src="data:audio/mpeg;base64,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"></audio>
3574 <audio id="offline-sound-reached" src="data:audio/mpeg;base64,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"></audio>
3575 </template>
3576 </div>
3577
3578
3579<script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
3580// Use of this source code is governed by a BSD-style license that can be
3581// found in the LICENSE file.
3582
3583/**
3584 * @fileoverview This file defines a singleton which provides access to all data
3585 * that is available as soon as the page's resources are loaded (before DOM
3586 * content has finished loading). This data includes both localized strings and
3587 * any data that is important to have ready from a very early stage (e.g. things
3588 * that must be displayed right away).
3589 */
3590
3591var loadTimeData;
3592
3593// Expose this type globally as a temporary work around until
3594// https://github.com/google/closure-compiler/issues/544 is fixed.
3595/** @constructor */
3596function LoadTimeData() {}
3597
3598(function() {
3599 'use strict';
3600
3601 LoadTimeData.prototype = {
3602 /**
3603 * Sets the backing object.
3604 *
3605 * Note that there is no getter for |data_| to discourage abuse of the form:
3606 *
3607 * var value = loadTimeData.data()['key'];
3608 *
3609 * @param {Object} value The de-serialized page data.
3610 */
3611 set data(value) {
3612 expect(!this.data_, 'Re-setting data.');
3613 this.data_ = value;
3614 },
3615
3616 /**
3617 * Returns a JsEvalContext for |data_|.
3618 * @returns {JsEvalContext}
3619 */
3620 createJsEvalContext: function() {
3621 return new JsEvalContext(this.data_);
3622 },
3623
3624 /**
3625 * @param {string} id An ID of a value that might exist.
3626 * @return {boolean} True if |id| is a key in the dictionary.
3627 */
3628 valueExists: function(id) {
3629 return id in this.data_;
3630 },
3631
3632 /**
3633 * Fetches a value, expecting that it exists.
3634 * @param {string} id The key that identifies the desired value.
3635 * @return {*} The corresponding value.
3636 */
3637 getValue: function(id) {
3638 expect(this.data_, 'No data. Did you remember to include strings.js?');
3639 var value = this.data_[id];
3640 expect(typeof value != 'undefined', 'Could not find value for ' + id);
3641 return value;
3642 },
3643
3644 /**
3645 * As above, but also makes sure that the value is a string.
3646 * @param {string} id The key that identifies the desired string.
3647 * @return {string} The corresponding string value.
3648 */
3649 getString: function(id) {
3650 var value = this.getValue(id);
3651 expectIsType(id, value, 'string');
3652 return /** @type {string} */ (value);
3653 },
3654
3655 /**
3656 * Returns a formatted localized string where $1 to $9 are replaced by the
3657 * second to the tenth argument.
3658 * @param {string} id The ID of the string we want.
3659 * @param {...string} var_args The extra values to include in the formatted
3660 * output.
3661 * @return {string} The formatted string.
3662 */
3663 getStringF: function(id, var_args) {
3664 var value = this.getString(id);
3665 if (!value)
3666 return '';
3667
3668 var varArgs = arguments;
3669 return value.replace(/\$[$1-9]/g, function(m) {
3670 return m == '$$' ? '$' : varArgs[m[1]];
3671 });
3672 },
3673
3674 /**
3675 * As above, but also makes sure that the value is a boolean.
3676 * @param {string} id The key that identifies the desired boolean.
3677 * @return {boolean} The corresponding boolean value.
3678 */
3679 getBoolean: function(id) {
3680 var value = this.getValue(id);
3681 expectIsType(id, value, 'boolean');
3682 return /** @type {boolean} */ (value);
3683 },
3684
3685 /**
3686 * As above, but also makes sure that the value is an integer.
3687 * @param {string} id The key that identifies the desired number.
3688 * @return {number} The corresponding number value.
3689 */
3690 getInteger: function(id) {
3691 var value = this.getValue(id);
3692 expectIsType(id, value, 'number');
3693 expect(value == Math.floor(value), 'Number isn\'t integer: ' + value);
3694 return /** @type {number} */ (value);
3695 },
3696
3697 /**
3698 * Override values in loadTimeData with the values found in |replacements|.
3699 * @param {Object} replacements The dictionary object of keys to replace.
3700 */
3701 overrideValues: function(replacements) {
3702 expect(typeof replacements == 'object',
3703 'Replacements must be a dictionary object.');
3704 for (var key in replacements) {
3705 this.data_[key] = replacements[key];
3706 }
3707 }
3708 };
3709
3710 /**
3711 * Checks condition, displays error message if expectation fails.
3712 * @param {*} condition The condition to check for truthiness.
3713 * @param {string} message The message to display if the check fails.
3714 */
3715 function expect(condition, message) {
3716 if (!condition) {
3717 console.error('Unexpected condition on ' + document.location.href + ': ' +
3718 message);
3719 }
3720 }
3721
3722 /**
3723 * Checks that the given value has the given type.
3724 * @param {string} id The id of the value (only used for error message).
3725 * @param {*} value The value to check the type on.
3726 * @param {string} type The type we expect |value| to be.
3727 */
3728 function expectIsType(id, value, type) {
3729 expect(typeof value == type, '[' + value + '] (' + id +
3730 ') is not a ' + type);
3731 }
3732
3733 expect(!loadTimeData, 'should only include this file once');
3734 loadTimeData = new LoadTimeData;
3735})();
3736</script><script>loadTimeData.data = {"details":"Details","errorCode":"ERR_INTERNET_DISCONNECTED","errorDetails":"The Internet connection has been lost.","fontfamily":"'Helvetica Neue', 'Lucida Grande', sans-serif","fontsize":"75%","heading":"Hacked By Mr.Tirex Unable to connect to the Internet","hideDetails":"Hide details","iconClass":"icon-offline","language":"en","primaryParagraph":"Google Chrome can’t display the webpage because your computer isn’t connected to the Internet.","suggestions":[],"summary":{"failedUrl":"https://www.facebook.com/","hostName":"www.facebook.com","msg":"You can try to diagnose the problem by taking the following steps:\n \u003Cbr />\n Go to\n \u003Cstrong>\n Applications > System Preferences > Network > Assist me\n \u003C/strong>\n to test your connection.","productName":"Google Chrome"},"textdirection":"ltr","title":"https://www.facebook.com/ is not available"};</script><script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
3737// Use of this source code is governed by a BSD-style license that can be
3738// found in the LICENSE file.
3739
3740// Copyright (c) 2012 The Chromium Authors. All rights reserved.
3741// Use of this source code is governed by a BSD-style license that can be
3742// found in the LICENSE file.
3743
3744/**
3745 * @fileoverview This is a simple template engine inspired by JsTemplates
3746 * optimized for i18n.
3747 *
3748 * It currently supports three handlers:
3749 *
3750 * * i18n-content which sets the textContent of the element.
3751 *
3752 * <span i18n-content="myContent"></span>
3753 *
3754 * * i18n-options which generates <option> elements for a <select>.
3755 *
3756 * <select i18n-options="myOptionList"></select>
3757 *
3758 * * i18n-values is a list of attribute-value or property-value pairs.
3759 * Properties are prefixed with a '.' and can contain nested properties.
3760 *
3761 * <span i18n-values="title:myTitle;.style.fontSize:fontSize"></span>
3762 *
3763 * This file is a copy of i18n_template.js, with minor tweaks to support using
3764 * load_time_data.js. It should replace i18n_template.js eventually.
3765 */
3766
3767var i18nTemplate = (function() {
3768 /**
3769 * This provides the handlers for the templating engine. The key is used as
3770 * the attribute name and the value is the function that gets called for every
3771 * single node that has this attribute.
3772 * @type {!Object}
3773 */
3774 var handlers = {
3775 /**
3776 * This handler sets the textContent of the element.
3777 * @param {HTMLElement} element The node to modify.
3778 * @param {string} key The name of the value in the dictionary.
3779 * @param {LoadTimeData} dictionary The dictionary of strings to draw from.
3780 */
3781 'i18n-content': function(element, key, dictionary) {
3782 element.textContent = dictionary.getString(key);
3783 },
3784
3785 /**
3786 * This handler adds options to a <select> element.
3787 * @param {HTMLElement} select The node to modify.
3788 * @param {string} key The name of the value in the dictionary. It should
3789 * identify an array of values to initialize an <option>. Each value,
3790 * if a pair, represents [content, value]. Otherwise, it should be a
3791 * content string with no value.
3792 * @param {LoadTimeData} dictionary The dictionary of strings to draw from.
3793 */
3794 'i18n-options': function(select, key, dictionary) {
3795 var options = dictionary.getValue(key);
3796 options.forEach(function(optionData) {
3797 var option = typeof optionData == 'string' ?
3798 new Option(optionData) :
3799 new Option(optionData[1], optionData[0]);
3800 select.appendChild(option);
3801 });
3802 },
3803
3804 /**
3805 * This is used to set HTML attributes and DOM properties. The syntax is:
3806 * attributename:key;
3807 * .domProperty:key;
3808 * .nested.dom.property:key
3809 * @param {HTMLElement} element The node to modify.
3810 * @param {string} attributeAndKeys The path of the attribute to modify
3811 * followed by a colon, and the name of the value in the dictionary.
3812 * Multiple attribute/key pairs may be separated by semicolons.
3813 * @param {LoadTimeData} dictionary The dictionary of strings to draw from.
3814 */
3815 'i18n-values': function(element, attributeAndKeys, dictionary) {
3816 var parts = attributeAndKeys.replace(/\s/g, '').split(/;/);
3817 parts.forEach(function(part) {
3818 if (!part)
3819 return;
3820
3821 var attributeAndKeyPair = part.match(/^([^:]+):(.+)$/);
3822 if (!attributeAndKeyPair)
3823 throw new Error('malformed i18n-values: ' + attributeAndKeys);
3824
3825 var propName = attributeAndKeyPair[1];
3826 var propExpr = attributeAndKeyPair[2];
3827
3828 var value = dictionary.getValue(propExpr);
3829
3830 // Allow a property of the form '.foo.bar' to assign a value into
3831 // element.foo.bar.
3832 if (propName[0] == '.') {
3833 var path = propName.slice(1).split('.');
3834 var targetObject = element;
3835 while (targetObject && path.length > 1) {
3836 targetObject = targetObject[path.shift()];
3837 }
3838 if (targetObject) {
3839 targetObject[path] = value;
3840 // In case we set innerHTML (ignoring others) we need to
3841 // recursively check the content.
3842 if (path == 'innerHTML')
3843 process(element, dictionary);
3844 }
3845 } else {
3846 element.setAttribute(propName, /** @type {string} */(value));
3847 }
3848 });
3849 }
3850 };
3851
3852 var attributeNames = Object.keys(handlers);
3853 var selector = '[' + attributeNames.join('],[') + ']';
3854
3855 /**
3856 * Processes a DOM tree with the {@code dictionary} map.
3857 * @param {HTMLElement} node The root of the DOM tree to process.
3858 * @param {LoadTimeData} dictionary The dictionary to draw from.
3859 */
3860 function process(node, dictionary) {
3861 var elements = node.querySelectorAll(selector);
3862 for (var element, i = 0; element = elements[i]; i++) {
3863 for (var j = 0; j < attributeNames.length; j++) {
3864 var name = attributeNames[j];
3865 var attribute = element.getAttribute(name);
3866 if (attribute != null)
3867 handlers[name](element, attribute, dictionary);
3868 }
3869 }
3870 }
3871
3872 return {
3873 process: process
3874 };
3875}());
3876
3877
3878i18nTemplate.process(document, loadTimeData);
3879</script><font size="1" face="Narkisim" color="#f7f7f7">
3880
3881<script>// Copyright (c) 2012 The Chromium Authors. All rights reserved.
3882// Use of this source code is governed by a BSD-style license that can be
3883// found in the LICENSE file.
3884
3885// This file serves as a proxy to bring the included js file from /third_party
3886// into its correct location under the resources directory tree, whence it is
3887// delivered via a chrome://resources URL. See ../webui_resources.grd.
3888(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}}
3889function 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]];
3890function 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]=
3891h;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():[]}
3892L.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);
3893d=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)};
3894L.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,""+
3895h))}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)}
3896function 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))};
3897})()
3898</script><script>var tp = document.getElementById('t');jstProcess(loadTimeData.createJsEvalContext(), tp);</script></body>
3899</html>