4 <meta name=
"viewport" content=
"width=device-width, initial-scale=1" />
12 * Reset styles for the html and body elements, the only two HTML
24 text-decoration: none;
25 vertical-align: baseline;
26 background: transparent;
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. */
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. */
48 /* The blinking fade in/out animation for the ticket date and time */
61 #tickettime, #ticketdate {
62 animation: blink
2s linear infinite;
65 /* Define, load, and specify the custom font we use for the ticket
66 * date, time, and service name. */
68 font-family: "CharmBypass Regular";
70 url("data:font/woff2;base64,@CBPREGULAR@") format("woff2")
73 #servicename, #tickettime, #ticketdate {
74 font-family: "CharmBypass Regular";
78 font-family: "CharmBypass Bold";
80 url("data:font/woff2;base64,@CBPBOLD@") format("woff2")
84 font-family: "CharmBypass Bold";
87 /************************/
88 /* Scrolling animations */
89 /************************/
94 transform: translateX(
0%);
97 transform: translateX(
100%);
102 animation: busroll
23s linear infinite;
107 @keyframes tramroll {
109 transform: translateX(
0%);
112 transform: translateX(
100%);
117 animation: tramroll
17s linear infinite;
122 @keyframes trainroll {
124 transform: translateX(
0%);
127 transform: translateX(
100%);
132 animation: trainroll
11s linear infinite;
137 @keyframes cloudsfloat {
139 transform: translateX(
0%);
142 transform: translateX(-
50%);
147 animation: cloudsfloat
40s linear infinite;
150 @keyframes cloudscopyfloat {
152 transform: translateX(
0%);
155 transform: translateX(-
50%);
160 animation: cloudscopyfloat
40s linear infinite;
165 @keyframes treespass {
167 transform: translateX(
0%);
170 transform: translateX(-
50%);
175 /* The trees move a little faster than the clouds */
176 animation: treespass
30s linear infinite;
179 @keyframes treescopypass {
181 transform: translateX(
0%);
184 transform: translateX(-
50%);
189 /* The trees move a little faster than the clouds */
190 animation: treescopypass
30s linear infinite;
195 @keyframes cityscroll {
197 transform: translateX(
0%);
200 transform: translateX(-
50%);
205 /* The city moves faster than the clouds but slower
207 animation: cityscroll
35s linear infinite;
210 @keyframes citycopyscroll {
212 transform: translateX(
0%);
215 transform: translateX(-
50%);
220 /* The city moves faster than the clouds but slower
222 animation: citycopyscroll
35s linear infinite;
231 /******************************************/
232 /* First, set up the ticket date and time */
233 /******************************************/
235 /* There are two parameters, time and date, that we store in one
236 * underlying "date" variable. Default both to an hour from now. This
237 * is sensible because the date/time shown on your ticket is its
238 * EXPIRATION time, and tickets are valid for two hours. Having it
239 * show one hour in the future means that you didn't just use your
240 * ticket a second ago (if you just got caught on the light rail, for
241 * example) but also means that it's not expiring for a while.
243 const date = new Date();
245 /* Add an hour. We use the low-level get/setTime to change the number
246 * of milliseconds since the epoch that this date represents. Obviously
247 * correct, and avoids all suspicious corner cases (well, for a few more
249 date.setTime(date.getTime() + (
60*
60*
1000));
251 /* All
<text> elements produced by inkscape contain a single
<tspan>
252 * that itself contains the actual text. */
253 tt = document.getElementById("tickettime");
254 tt.firstChild.textContent = date.toLocaleTimeString();
256 const td = document.getElementById("ticketdate");
262 td.firstChild.textContent = date.toLocaleDateString("en-US", dateopts);
265 /************************************************************/
266 /* Second, add the onclick handler for the night/day switch */
267 /************************************************************/
269 /* We always start in "day" mode */
273 sky.style.fill = "#efb02f";
276 function set_night() {
277 sky.style.fill = "#
143b66";
280 function swap_colors() {
291 document.body.addEventListener("click", swap_colors);
294 /*************************************************/
295 /* Finally, center the ticket within the browser */
296 /*************************************************/
297 function center_ticket() {
298 /* We're relying on the SVG being the full height of the
299 * viewport already, and on the aspect ratio being
300 * preserved. First, find the center of the ticket. */
301 const r = document.getElementById("ticket").getBoundingClientRect();
302 const c = r.left + (r.width /
2);
304 /* That's the center-line of the ticket. We want to move it to
305 * the center-line of the viewport. */
306 const vc = document.documentElement.clientWidth /
2;
308 /* This is how much we need to translate the SVG */
309 const delta = vc - c;
311 /* But before we can set the absolute left-coordinate of the
312 * SVG, we need to know where it is now. Note: without the
313 * "px" this doesn't default to pixels like CSS does. */
314 const svg = document.querySelector("svg");
315 svg.style.left = (svg.getBoundingClientRect().left + delta) + "px";
318 /* Re-center it when the window is resized */
319 window.addEventListener("resize", center_ticket);
321 /* Center it once when the page has loaded, too */
322 window.addEventListener("load", center_ticket);