diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c67375c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.pem
+server.py
diff --git a/404.html b/404.html
new file mode 100644
index 0000000..f1a5c68
--- /dev/null
+++ b/404.html
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
diff --git a/Splatoon1.otf b/Splatoon1.otf
new file mode 100644
index 0000000..13050f4
Binary files /dev/null and b/Splatoon1.otf differ
diff --git a/app/stages/index.html b/app/stages/index.html
new file mode 100644
index 0000000..6a6ed2e
--- /dev/null
+++ b/app/stages/index.html
@@ -0,0 +1,44 @@
+
+
+
+
+

+
+ Use the stage information to guide your weapon strategy and battle strategy!
+
+
+
+
+
+
+
+
+
+
diff --git a/app/stages/stages.js b/app/stages/stages.js
new file mode 100644
index 0000000..0ce8e96
--- /dev/null
+++ b/app/stages/stages.js
@@ -0,0 +1,241 @@
+const API_URL = "https://api.splatcord.ink/prod/s1rotations";
+
+function stageNames(stages, lang = "en-US") {
+ return stages.map(s => s.translatedNames[lang]);
+}
+
+function stageImagePath(stageName) {
+ const firstWord = stageName
+ .split(" ")[0]
+ .toLowerCase()
+ .replace(/['.]/g, "");
+ return `/assets/stages/${firstWord}.png`;
+}
+
+async function fetchRotations() {
+ 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 timestamps = Object.keys(rotations).sort((a, b) => Number(a) - Number(b));
+
+ const result = [];
+
+ 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;
+
+ const r = rotations[ts];
+
+ result.push({
+ turf: stageNames(r.turfStages),
+ ranked: stageNames(r.rankedStages),
+ ranked_mode: r.rankedMode.replace(/([a-z])([A-Z])/g, "$1 $2"),
+ startTime: start,
+ endTime: end,
+ });
+ }
+
+ return result;
+ } catch (error) {
+ console.error("Failed to fetch rotations:", error);
+ 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;
+ 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 > 50) {
+ 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);
+ };
+}
+
+//pain in the ass to implement. im proud of ts
+const canvas = document.getElementById("squid-canvas");
+const ctx = canvas.getContext("2d");
+const frameWidth = 64;
+const frameHeight = 64;
+const totalFrames = 8;
+const squidCount = 5;
+const squids = [];
+const canvasWidth = canvas.width;
+const canvasHeight = canvas.height;
+const titleYThreshold = 0;
+
+function createSquid() {
+ return {
+ x: Math.random() * canvasWidth,
+ y: canvasHeight + Math.random() * 300,
+ currentFrame: Math.floor(Math.random() * totalFrames),
+ speed: 0.05 + Math.random() * 0.15,
+ lastFrameUpdate: 0
+ };
+}
+
+for (let i = 0; i < squidCount; i++) {
+ squids.push(createSquid());
+}
+
+let lastTimestamp = 0;
+
+const image = new Image();
+image.src = "/assets/squids.png";
+
+image.onload = () => {
+ function animate(timestamp = 0) {
+ if (!lastTimestamp) lastTimestamp = timestamp;
+ const delta = timestamp - lastTimestamp;
+
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+
+ for (const squid of squids) {
+ squid.y -= squid.speed * delta;
+
+ if (squid.y + frameHeight < titleYThreshold) {
+ squid.x = Math.random() * canvasWidth;
+ squid.y = canvasHeight + Math.random() * 300;
+ squid.currentFrame = Math.floor(Math.random() * totalFrames);
+ squid.speed = 0.05 + Math.random() * 0.15;
+ }
+
+ if (timestamp - squid.lastFrameUpdate > 100) {
+ squid.currentFrame = (squid.currentFrame + 1) % totalFrames;
+ squid.lastFrameUpdate = timestamp;
+ }
+
+ ctx.drawImage(
+ image,
+ squid.currentFrame * frameWidth,
+ 0,
+ frameWidth,
+ frameHeight,
+ squid.x,
+ squid.y,
+ frameWidth,
+ frameHeight
+ );
+ }
+
+ lastTimestamp = timestamp;
+ requestAnimationFrame(animate);
+ }
+ requestAnimationFrame(animate);
+};
+
+async function renderStages() {
+ const loadingOverlay = document.getElementById("loading-overlay");
+ const container = document.getElementById("stages-container");
+ let rotations = [];
+ try {
+ rotations = await fetchRotations();
+ } catch (err) {
+ console.error("Error in renderStages:", err);
+ } finally {
+ loadingOverlay.style.display = "none";
+ }
+ if (!rotations.length) {
+ container.innerHTML = `