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