]> gitweb.michael.orlitzky.com - charm-bypass.git/blob - index.html.in
doc/TODO: eliminate "transform" attributes
[charm-bypass.git] / index.html.in
1 <!doctype html>
2 <html lang="en-US">
3 <head>
4 <meta name="viewport" content="width=device-width, initial-scale=1" />
5
6 <title>
7 CharmBypass.
8 </title>
9
10 <style>
11 /*
12 * Reset styles for the html and body elements, the only two HTML
13 * elements we use. */
14 html, body {
15 margin: 0;
16 padding: 0;
17 border: 0;
18 font-weight: normal;
19 font-style: inherit;
20 font-size: 100%;
21 line-height: 1.5;
22 font-family: inherit;
23 text-align: inherit;
24 text-decoration: none;
25 vertical-align: baseline;
26 background: transparent;
27 }
28
29 html, body {
30 /* To create our "window" onto the scene, we're going to slide the
31 * SVG off the left-hand side of the screen, and we don't want
32 * scroll bars to appear. */
33 overflow: hidden;
34 }
35
36 svg {
37 /* Set the height to 100% of the screen, which we'll keep; and the
38 * initial position to (0,0), which we're going to change
39 * every time the window is resized, because the exact amount
40 * that we have to slide the SVG to the left to get the ticket
41 * into the center will change. */
42 position: fixed;
43 top: 0;
44 left: 0;
45 height: 100%;
46 }
47
48 /* The blinking fade in/out animation for the ticket date and time */
49 @keyframes blink {
50 25% {
51 opacity: 0.5;
52 }
53 50% {
54 opacity: 0;
55 }
56 75% {
57 opacity: 0.5;
58 }
59 }
60
61 #tickettime, #ticketdate {
62 /* 300 two-second blinks is ten minutes */
63 animation: blink 2s linear 300;
64 }
65
66 /* Define, load, and specify the custom font we use for the ticket
67 * date, time, and service name. */
68 @font-face {
69 font-family: "CharmBypass Regular";
70 src:
71 url("data:font/woff2;base64,@CBPREGULAR@") format("woff2")
72 }
73
74 #servicename, #tickettime, #ticketdate, #codetext {
75 font-family: "CharmBypass Regular", sans-serif;
76 }
77
78 @font-face {
79 font-family: "CharmBypass Bold";
80 src:
81 url("data:font/woff2;base64,@CBPBOLD@") format("woff2")
82 }
83
84 #serviceletter {
85 font-family: "CharmBypass Bold", sans-serif;
86 }
87
88 /************************/
89 /* Scrolling animations */
90 /************************/
91
92 /* Bus */
93 @keyframes busroll {
94 from {
95 transform: translateX(0%);
96 }
97 to {
98 transform: translateX(100%);
99 }
100 }
101
102 #bus {
103 animation: busroll 23s linear infinite;
104 }
105
106
107 /* Tram */
108 @keyframes tramroll {
109 from {
110 transform: translateX(0%);
111 }
112 to {
113 transform: translateX(100%);
114 }
115 }
116
117 #tram {
118 animation: tramroll 17s linear infinite;
119 }
120
121
122 /* Tram */
123 @keyframes trainroll {
124 from {
125 transform: translateX(0%);
126 }
127 to {
128 transform: translateX(100%);
129 }
130 }
131
132 #train {
133 animation: trainroll 11s linear infinite;
134 }
135
136
137 /* Clouds */
138 @keyframes cloudsfloat {
139 from {
140 transform: translateX(0%);
141 }
142 to {
143 transform: translateX(-50%);
144 }
145 }
146
147 #clouds {
148 animation: cloudsfloat 40s linear infinite;
149 }
150
151 @keyframes cloudscopyfloat {
152 from {
153 transform: translateX(0%);
154 }
155 to {
156 transform: translateX(-50%);
157 }
158 }
159
160 #cloudscopy {
161 animation: cloudscopyfloat 40s linear infinite;
162 }
163
164
165 /* Trees */
166 @keyframes treespass {
167 from {
168 transform: translateX(0%);
169 }
170 to {
171 transform: translateX(-50%);
172 }
173 }
174
175 #trees {
176 /* The trees move a little faster than the clouds */
177 animation: treespass 30s linear infinite;
178 }
179
180 @keyframes treescopypass {
181 from {
182 transform: translateX(0%);
183 }
184 to {
185 transform: translateX(-50%);
186 }
187 }
188
189 #treescopy {
190 /* The trees move a little faster than the clouds */
191 animation: treescopypass 30s linear infinite;
192 }
193
194
195 /* City skyline */
196 @keyframes cityscroll {
197 from {
198 transform: translateX(0%);
199 }
200 to {
201 transform: translateX(-50%);
202 }
203 }
204
205 #city {
206 /* The city moves faster than the clouds but slower
207 * than the trees */
208 animation: cityscroll 35s linear infinite;
209 }
210
211 @keyframes citycopyscroll {
212 from {
213 transform: translateX(0%);
214 }
215 to {
216 transform: translateX(-50%);
217 }
218 }
219
220 #citycopy {
221 /* The city moves faster than the clouds but slower
222 * than the trees */
223 animation: citycopyscroll 35s linear infinite;
224 }
225 </style>
226 </head>
227
228 <body>
229 @SVGDATA@
230
231 <script>
232
233 /***********************************************/
234 /* First, center the ticket within the browser */
235 /***********************************************/
236
237 function center_ticket() {
238 /* We're relying on the SVG being the full height of the
239 * viewport already, and on the aspect ratio being
240 * preserved. First, find the center of the ticket. */
241 const r = document.getElementById("ticket").getBoundingClientRect();
242 const c = r.left + (r.width / 2);
243
244 /* That's the center-line of the ticket. We want to move it to
245 * the center-line of the viewport. */
246 const vc = document.documentElement.clientWidth / 2;
247
248 /* This is how much we need to translate the SVG */
249 const hdelta = vc - c;
250
251 /* But before we can set the absolute left-coordinate of the
252 * SVG, we need to know where it is now. Note: without the
253 * "px" this doesn't default to pixels like CSS does. */
254 const svg = document.querySelector("svg");
255 svg.style.left = (svg.getBoundingClientRect().left + hdelta) + "px";
256 }
257
258
259 /****************************************/
260 /* Set and reposition the security code */
261 /****************************************/
262
263 function set_code() {
264 /* All <text> elements produced by inkscape contain a single <tspan>
265 * that itself contains the actual text. This does something real
266 * sneaky, and actually OVERWRITES that tspan with our own text
267 * content. This turns out to be what we need anyway because trying
268 * to center a (display: inline) tspan is a pain in the butt. */
269 const ct = document.getElementById("codetext");
270
271 /* Get the "code" from the querystring if it's there */
272 let params = new URLSearchParams(document.location.search);
273 if (params.get("code")) {
274 ct.textContent = params.get("code");
275 }
276 else {
277 /* Otherwise, use a random code */
278 const bucket = ["0","1","2","3","4","5","6","7","8","9",
279 "A","B","C","D","E","F","G","H","I","J",
280 "K","L","M","N","O","P","Q","R","S","T",
281 "U","V","W","X","Y","Z"];
282
283 /* Two random ints between 0 and 35 */
284 const i1 = Math.floor(Math.random() * 36);
285 const i2 = Math.floor(Math.random() * 36);
286 const d1 = bucket[i1];
287 const d2 = bucket[i2];
288 ct.textContent = d1 + d2;
289 }
290 }
291
292
293 function center_code() {
294 /* Center the security code inside its red box */
295 const ct = document.getElementById("codetext");
296
297 /* First, find the center of the red box */
298 const r1 = document.getElementById("codebg").getBoundingClientRect();
299 const c1 = r1.left + (r1.width / 2);
300
301 /* Now the center of the code text */
302 const r2 = ct.getBoundingClientRect();
303 const c2 = r2.left + (r2.width / 2);
304
305 /* What do we add to c2 to make it equal to c1? */
306 const hdelta = c1 - c2;
307
308 /* Since this <text> element has an "x" attribute it's easier for
309 * us to shift that than it is to mess with the "left" style. */
310 ct.setAttribute("x", parseFloat(ct.getAttribute("x")) + hdelta);
311 }
312
313
314 /*****************************************/
315 /* Next, set up the ticket date and time */
316 /*****************************************/
317
318 function set_ticket_expiry() {
319 /* There are two parameters, time and date, that we store in one
320 * underlying "date" variable. Default both to an hour and a
321 * half from now. This is what the CharmPass app does for
322 * one-way tickets. */
323 const date = new Date();
324
325 /* Add an hour and a half. We use the low-level get/setTime to
326 * change the number of milliseconds since the epoch that this
327 * date represents. Obviously correct, and avoids all suspicious
328 * corner cases (well, for a few more decades). */
329 date.setTime(date.getTime() + (90*60*1000));
330
331 /* All <text> elements produced by inkscape contain a single <tspan>
332 * that itself contains the actual text. */
333 tt = document.getElementById("tickettime").firstChild;
334 tt.textContent = date.toLocaleTimeString();
335
336 const td = document.getElementById("ticketdate");
337 const dateopts = {
338 day: "2-digit",
339 month: "2-digit",
340 year: "2-digit"
341 };
342 td.textContent = date.toLocaleDateString("en-US", dateopts);
343 }
344
345
346 /*********************************************************/
347 /* Finally, the onclick handler for the night/day switch */
348 /*********************************************************/
349
350 /* We always start in "day" mode */
351 is_day = true;
352
353 function set_day() {
354 sky.style.fill = "#efb02f";
355 }
356
357 function set_night() {
358 sky.style.fill = "#143b66";
359 }
360
361 function swap_colors() {
362 if (is_day) {
363 set_night();
364 is_day = false;
365 }
366 else {
367 set_day();
368 is_day = true;
369 }
370 }
371
372
373 /*****************************************************/
374 /* Add event handlers for all of the functions above */
375 /*****************************************************/
376
377 /* Center the ticket once when the page has loaded */
378 window.addEventListener("load", center_ticket);
379
380 /* Re-center the ticket when the window is resized */
381 window.addEventListener("resize", center_ticket);
382
383 /* Set the security code text when the page has loaded */
384 window.addEventListener("load", set_code);
385
386 /* Center the security code text when the page has loaded;
387 in particular, after we set it */
388 window.addEventListener("load", center_code);
389
390 /* Re-center the security code when the window is resized,
391 and in particular after the ticket is re-centered */
392 window.addEventListener("resize", center_code);
393
394 /* Set the ticket expiration date/time upon page load */
395 window.addEventListener("load", set_ticket_expiry);
396
397 /* Swap colors when the screen is tapped */
398 document.body.addEventListener("click", swap_colors);
399
400 </script>
401 </body>
402 </html>