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 and a
237 * half from now. This is what the CharmPass app does for
240 const date = new Date();
242 /* Add an hour and a half. We use the low-level get/setTime to
243 * change the number of milliseconds since the epoch that this
244 * date represents. Obviously correct, and avoids all suspicious
245 * corner cases (well, for a few more decades). */
246 date.setTime(date.getTime() + (
90*
60*
1000));
248 /* All
<text> elements produced by inkscape contain a single
<tspan>
249 * that itself contains the actual text. */
250 tt = document.getElementById("tickettime");
251 tt.firstChild.textContent = date.toLocaleTimeString();
253 const td = document.getElementById("ticketdate");
259 td.firstChild.textContent = date.toLocaleDateString("en-US", dateopts);
262 /************************************************************/
263 /* Second, add the onclick handler for the night/day switch */
264 /************************************************************/
266 /* We always start in "day" mode */
270 sky.style.fill = "#efb02f";
273 function set_night() {
274 sky.style.fill = "#
143b66";
277 function swap_colors() {
288 document.body.addEventListener("click", swap_colors);
291 /*************************************************/
292 /* Finally, center the ticket within the browser */
293 /*************************************************/
294 function center_ticket() {
295 /* We're relying on the SVG being the full height of the
296 * viewport already, and on the aspect ratio being
297 * preserved. First, find the center of the ticket. */
298 const r = document.getElementById("ticket").getBoundingClientRect();
299 const c = r.left + (r.width /
2);
301 /* That's the center-line of the ticket. We want to move it to
302 * the center-line of the viewport. */
303 const vc = document.documentElement.clientWidth /
2;
305 /* This is how much we need to translate the SVG */
306 const delta = vc - c;
308 /* But before we can set the absolute left-coordinate of the
309 * SVG, we need to know where it is now. Note: without the
310 * "px" this doesn't default to pixels like CSS does. */
311 const svg = document.querySelector("svg");
312 svg.style.left = (svg.getBoundingClientRect().left + delta) + "px";
315 /* Re-center it when the window is resized */
316 window.addEventListener("resize", center_ticket);
318 /* Center it once when the page has loaded, too */
319 window.addEventListener("load", center_ticket);