diff --git a/favicon.ico b/favicon.ico
new file mode 100644
index 0000000..c571957
Binary files /dev/null and b/favicon.ico differ
diff --git a/index.html b/index.html
index c94233f..7dde93e 100644
--- a/index.html
+++ b/index.html
@@ -3,15 +3,20 @@

diff --git a/stages.js b/stages.js
index ed3422f..d45bf33 100644
--- a/stages.js
+++ b/stages.js
@@ -14,40 +14,110 @@ function stageImagePath(stageName) {
}
async function fetchRotations() {
- const res = await fetch(API_URL);
- const data = await res.json();
+ try {
+ const res = await fetch(API_URL);
+ if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
+ const data = await res.json();
- const rotations = data?.pretendo?.rotations ?? {};
- const now = new Date();
+ const rotations = data?.pretendo?.rotations ?? {};
+ const now = new Date();
- const timestamps = Object.keys(rotations).sort(
- (a, b) => Number(a) - Number(b)
- );
+ const timestamps = Object.keys(rotations).sort(
+ (a, b) => Number(a) - Number(b)
+ );
- for (const ts of timestamps) {
- const start = new Date(Number(ts));
- const end = new Date(start.getTime() + 2 * 60 * 60 * 1000);
+ for (const ts of timestamps) {
+ const start = new Date(Number(ts));
+ const end = new Date(start.getTime() + 2 * 60 * 60 * 1000);
- if (end < now) continue;
+ if (end < now) continue;
- const r = rotations[ts];
+ const r = rotations[ts];
- return [{
- turf: stageNames(r.turfStages).map(stageImagePath),
- ranked: stageNames(r.rankedStages).map(stageImagePath),
- ranked_mode: r.rankedMode?.translatedNames?.["en-US"] ?? "Ranked"
- }];
+ return [{
+ turf: stageNames(r.turfStages).map(stageImagePath),
+ ranked: stageNames(r.rankedStages).map(stageImagePath),
+ ranked_mode: r.rankedMode?.translatedNames?.["en-US"] ?? "Ranked"
+ }];
+ }
+
+ return [];
+ } catch (error) {
+ console.error("Failed to fetch rotations:", error);
+ return [];
}
+}
- return [];
+function animateLoadingCanvas() {
+ const canvas = document.getElementById("loading-canvas");
+ const loadingOverlay = document.getElementById("loading-overlay");
+ if (!canvas || !loadingOverlay) return;
+
+ const ctx = canvas.getContext("2d");
+ const image = new Image();
+ image.src = "/assets/loading.png";
+
+ const frameWidth = 100;
+ const frameHeight = 100;
+ const totalFrames = 17;
+
+ let currentFrame = 0;
+ let lastUpdateTime = 0;
+ const frameDuration = 50; // ms per frame
+
+ let animationId;
+
+ image.onload = () => {
+ function animate(timestamp = 0) {
+ if (loadingOverlay.style.display === "none") {
+ cancelAnimationFrame(animationId);
+ return;
+ }
+
+ if (!lastUpdateTime) lastUpdateTime = timestamp;
+ const elapsed = timestamp - lastUpdateTime;
+
+ if (elapsed > frameDuration) {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.drawImage(
+ image,
+ 0, currentFrame * frameHeight,
+ frameWidth, frameHeight,
+ 0, 0,
+ frameWidth, frameHeight
+ );
+
+ currentFrame = (currentFrame + 1) % totalFrames;
+ lastUpdateTime = timestamp;
+ }
+
+ animationId = requestAnimationFrame(animate);
+ }
+
+ animationId = requestAnimationFrame(animate);
+ };
}
async function renderStages() {
+ const loadingOverlay = document.getElementById("loading-overlay");
const container = document.getElementById("stages-container");
- const rotations = await fetchRotations();
+
+ let rotations = [];
+ try {
+ rotations = await fetchRotations();
+ } catch (err) {
+ console.error("Error in renderStages:", err);
+ } finally {
+ loadingOverlay.style.display = "none"; // always hide loading
+ }
if (!rotations.length) {
- container.innerHTML = "
No rotations available
";
+ container.innerHTML = `
+
+ Something went wrong! Try reloading the page. If this problem persists you may be ratelimited.
+
+`;
+
return;
}
diff --git a/styles.css b/styles.css
index 4d796ac..d501591 100644
--- a/styles.css
+++ b/styles.css
@@ -5,6 +5,45 @@
font-style: normal;
}
+.loading-overlay {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background-color: rgba(0,0,0,0.8);
+ border: 3px solid white;
+ border-radius: 12px;
+ padding: 20px 24px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ z-index: 9999;
+ user-select: none;
+ pointer-events: none;
+ width: 200px; /* Updated width */
+ height: 250px; /* Updated height */
+ box-sizing: border-box;
+}
+
+#loading-canvas {
+ image-rendering: pixelated;
+ /* Make sure canvas doesn't get resized by CSS */
+ width: 100px;
+ height: 100px;
+ display: block;
+ margin: 0 auto; /* center horizontally */
+}
+
+#loading-text {
+ margin-top: 12px;
+ color: white;
+ font-family: "Splatoon1", sans-serif;
+ font-size: 20px;
+ letter-spacing: 1.2px;
+ text-align: center;
+}
+
html, body {
margin: 0;
padding: 0;
@@ -93,3 +132,10 @@ body {
display: block;
border-radius: 8px;
}
+
+.error-message {
+ color: white;
+ font-family: "Splatoon1", sans-serif;
+ text-align: center;
+ margin-top: 40px;
+}