Initial import.
[mlpccg.git] / mlpccg.js
1 /* The person who associated a work with this deed has dedicated the
2 work to the public domain by waiving all of his or her rights to
3 the work worldwide under copyright law, including all related and
4 neighboring rights, to the extent allowed by law.
5
6 You can copy, modify, distribute and perform the work, even for
7 commercial purposes, all without asking permission.
8
9 See https://creativecommons.org/publicdomain/zero/1.0/ for details.
10 */
11
12 "use strict";
13
14 var APP = "com.yukkurigames.mlpccg";
15
16 var THEMES = [
17 "applejack",
18 "cadance",
19 "celestia",
20 "chrysalis",
21 "dash",
22 "discord",
23 "fluttershy",
24 "luna",
25 "maud",
26 "muffins",
27 "pinkie",
28 "pon3",
29 "rarity",
30 "spike",
31 "twilight",
32 ];
33
34 window.requestAnimationFrame = (
35 window.requestAnimationFrame
36 || window.mozRequestAnimationFrame
37 || window.webkitRequestAnimationFrame);
38 window.cancelAnimationFrame = (
39 window.cancelAnimationFrame
40 || window.mozCancelAnimationFrame
41 || window.webkitCancelAnimationFrame);
42
43 function debounce (callback, wait) {
44 wait = wait || 100;
45 var this_ = null;
46 var args = null;
47 var id = null;
48
49 function _ () {
50 callback.apply(this_, args);
51 this_ = null;
52 args = null;
53 id = null;
54 }
55
56 return function () {
57 clearTimeout(id);
58 this_ = this;
59 args = arguments;
60 id = setTimeout(_, wait);
61 };
62 }
63
64 function iclamp (x, lo, hi) {
65 return Math.max(Math.min(x | 0, hi), lo);
66 }
67
68 function broadcast (data, source) {
69 var origin = (!location.origin || location.origin === "null")
70 ? "*" : location.origin;
71 source = source || window;
72 if (window.parent && window.parent !== source && window.parent !== window) {
73 window.parent.postMessage(data, origin);
74 }
75 for (var i = 0; i < window.frames.length; ++i) {
76 var w = window.frames[i].window;
77 if (w !== source) {
78 w.postMessage(data, origin);
79 }
80 }
81 }
82
83 var messages = {};
84
85 window.addEventListener('message', function (event) {
86 broadcast(event.data, event.source);
87 if (messages[event.data.type])
88 messages[event.data.type](event.data);
89 });
90
91 function stop (event) {
92 event.preventDefault();
93 }
94
95 function nextTheme (element) {
96 var nextId = (THEMES.indexOf(element.className) + 1) % THEMES.length;
97 var theme = THEMES[nextId];
98 element.className = theme;
99 var id = element.id ? element.id
100 : element === document.body.parentNode ? "html"
101 : null;
102 if (id) {
103 var key = [APP, "theme", window.name || "window", id].join(".");
104 localStorage[key] = theme;
105 }
106 }
107
108 var repaint = debounce(function () {
109 document.body.removeChild(
110 document.body.appendChild(
111 document.createElement('style')));
112 }, 50);
113
114 window.addEventListener('touchmove', stop);
115
116 // Work around incorrect viewport units in (mostly Mobile) Safari. At
117 // page-load time they are often calculated as 0 (because the viewport
118 // isn't ready yet?) and they are not refreshed when the viewport does
119 // change size.
120 window.addEventListener('load', repaint);
121 window.addEventListener('resize', repaint);
122 window.addEventListener('orientationchange', repaint);
123
124 function randomTheme () {
125 return THEMES[(Math.random() * THEMES.length) | 0];
126 }
127
128 function storedTheme () {
129 return localStorage[
130 [APP, "theme", window.name || "window", "html"].join(".")];
131 }
132
133 function loadTheme () {
134 if (THEMES.indexOf(location.hash.slice(1)) >= 0) {
135 document.body.parentNode.className = location.hash.slice(1);
136 } else if (document.body.parentNode.className === "stored-theme") {
137 var random = THEMES[(Math.random() * THEMES.length) | 0];
138 document.body.parentNode.className = storedTheme() || randomTheme();
139 } else if (document.body.parentNode.className === "random-theme") {
140 document.body.parentNode.className = randomTheme();
141 }
142 }
143
144 window.addEventListener('hashchange', function () {
145 var theme = location.hash.slice(1);
146 if (THEMES.indexOf(theme) >= 0) {
147 document.body.parentNode.className = theme;
148 }
149 });
150
151 window.addEventListener('pageshow', repaint);
152
153 function removeStatusBar () {
154 document.body.className = "";
155 }
156
157 function addStatusBar () {
158 document.body.className = "statusbar";
159 }
160
161 function open (event) {
162 (this.target === "_parent" ? window.parent : window).location.href
163 = this.href;
164 event.preventDefault();
165 }
166
167 window.addEventListener('DOMContentLoaded', function () {
168 var links = document.querySelectorAll("a[href]:not([target=_blank])");
169 for (var i = 0; i < links.length; ++i) {
170 links[i].addEventListener('click', open);
171 }
172
173 FastClick.attach(document.body, { tapDelay: 50 });
174 loadTheme();
175 });
176
177 function afterAnimationFrame (f) {
178 /* DOM class modifications intended to trigger transitions
179 must be delayed for at least one frame after the element is
180 created, i.e. after it has gone through at least one full
181 repaint.
182 */
183 window.requestAnimationFrame(function () {
184 window.requestAnimationFrame(f);
185 });
186 }
187
188 function spin () {
189 var orig = document.getElementById("spinner");
190 var spinner = orig.cloneNode();
191 spinner.className = 'spinner';
192 spinner.style.opacity = 1;
193 spinner.style.transform = spinner.style["-webkit-transform"] = "rotateZ(0deg)";
194 document.body.replaceChild(spinner, orig);
195 afterAnimationFrame(function () {
196 var z = (10 + (Math.random() * 2) | 0) * 180;
197 var transform = "rotateZ(" + z + "deg)";
198 spinner.style.transform = spinner.style["-webkit-transform"] = transform;
199 spinner.style.opacity = 0;
200 });
201 }
202
203 function roll (n) {
204 var die = document.createElement("div");
205 die.className = 'die rolling d' + n;
206 document.getElementById("triggered").appendChild(die);
207 die.setAttribute('data-roll', 1 + (Math.random() * n) | 0);
208 var id = setInterval(function () {
209 die.setAttribute('data-roll', 1 + (Math.random() * n) | 0);
210 }, 430);
211
212 die.addEventListener('animationend', function _ () {
213 die.removeEventListener('animationend', _);
214 die.parentNode.removeChild(die);
215 });
216 die.style.transform = die.style["-webkit-transform"] = "rotateY(-270deg)";
217 afterAnimationFrame(function () {
218 die.style.transform = die.style["-webkit-transform"] = "rotateY(360deg)";
219 function _ () {
220 clearInterval(id);
221 die.removeEventListener('transitionend', _);
222 die.removeEventListener('webkitTransitionEnd', _);
223 }
224 die.addEventListener('transitionend', _);
225 die.addEventListener('webkitTransitionEnd', _);
226 });
227 }
228
229 if (applicationCache && applicationCache.status) {
230 applicationCache.update();
231 applicationCache.addEventListener('updateready', function () {
232 if (applicationCache.status === applicationCache.UPDATEREADY) {
233 applicationCache.swapCache();
234 }
235 });
236 }
237
238 function dismiss () {
239 var overlay = document.getElementById("overlay");
240 if (overlay)
241 overlay.parentNode.removeChild(overlay);
242 broadcast({ type: "dismiss" });
243 }
244
245 function show (id, otherId) {
246 var overlay = document.getElementById("overlay");
247 if (!overlay) {
248 overlay = document.createElement('div');
249 overlay.id = 'overlay';
250 } else {
251 overlay.parentNode.removeChild(overlay);
252 }
253 var target = document.getElementById(id);
254 target.parentNode.insertBefore(overlay, target);
255 if (otherId)
256 broadcast({ type: 'show', id: otherId });
257 }
258