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 @@ SplatNet | Stages + -
+
+ +
Loading
+
+
Stage Info 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; +}