From 0911f240bd1e5a7993905cbb4770ac456b9dafbf Mon Sep 17 00:00:00 2001 From: KittenTM Date: Thu, 22 Jan 2026 23:57:55 -0500 Subject: [PATCH] add squid swimming in background --- assets/squids.png | Bin 0 -> 14825 bytes index.html | 46 ++++--- stages.js | 339 +++++++++++++++++++++++++++++----------------- styles.css | 42 ++++-- 4 files changed, 270 insertions(+), 157 deletions(-) create mode 100644 assets/squids.png diff --git a/assets/squids.png b/assets/squids.png new file mode 100644 index 0000000000000000000000000000000000000000..bfa6b512c37eef6b1bef1ca62300339d108e1ca7 GIT binary patch literal 14825 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 - - SplatNet | Stages - - +
+ +
+ + SplatNet | Stages + + +
-
+
+ +
Loading
+
-
- -
Loading
-
- -
+
Stage Info
- Use the stage information to guide your weapon strategy and battle strategy! + Use the stage information to guide your weapon strategy and battle strategy!
-
+
- + - + diff --git a/stages.js b/stages.js index 648f5fc..0ce8e96 100644 --- a/stages.js +++ b/stages.js @@ -1,150 +1,241 @@ const API_URL = "https://api.splatcord.ink/prod/s1rotations"; function stageNames(stages, lang = "en-US") { - return stages.map(s => s.translatedNames[lang]); + return stages.map(s => s.translatedNames[lang]); } function stageImagePath(stageName) { - const firstWord = stageName - .split(" ")[0] - .toLowerCase() - .replace(/['.]/g, ""); - return `/assets/stages/${firstWord}.png`; + 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)); + 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 = []; + 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; + 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]; + 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 []; + 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); - }; + 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 = `

Something went wrong! Try reloading the page. If this problem persists you may be ratelimited.

`; - return; - } - for (const rotation of rotations) { - const optionsDate = { day: "2-digit", month: "2-digit" }; - const optionsTime = { hour: "numeric", minute: "2-digit", hour12: true }; - const timeZone = "Europe/Paris"; - const formattedTime = `${rotation.startTime.toLocaleDateString("en-GB", { ...optionsDate, timeZone })} at ${rotation.startTime.toLocaleTimeString("en-GB", { ...optionsTime, timeZone })} (CEST) ~ ${rotation.endTime.toLocaleDateString("en-GB", { ...optionsDate, timeZone })} at ${rotation.endTime.toLocaleTimeString("en-GB", { ...optionsTime, timeZone })} (CEST)`; + 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 = `

Something went wrong! Try reloading the page. If this problem persists you may be ratelimited.

`; + return; + } - container.innerHTML += ` -
${formattedTime}
+ let html = ""; -
- Regular Battle -
Regular Battle
-
- ${rotation.turf.map(name => ` -
- ${name} -
${name}
-
- `).join("")} -
-
+ for (const rotation of rotations) { + const optionsDate = { day: "2-digit", month: "2-digit" }; + const optionsTime = { hour: "numeric", minute: "2-digit", hour12: true }; + const timeZone = "Europe/Paris"; + const formattedTime = `${rotation.startTime.toLocaleDateString("en-GB", { + ...optionsDate, + timeZone, + })} at ${rotation.startTime.toLocaleTimeString("en-GB", { ...optionsTime, timeZone })} (CEST) ~ ${rotation.endTime.toLocaleDateString("en-GB", { + ...optionsDate, + timeZone, + })} at ${rotation.endTime.toLocaleTimeString("en-GB", { ...optionsTime, timeZone })} (CEST)`; -
- Ranked Battle -
Ranked Battle
-
-
Battle Mode
-
${rotation.ranked_mode}
-
-
- ${rotation.ranked.map(name => ` -
- ${name} -
${name}
-
- `).join("")} -
-
- `; - } + html += ` +
${formattedTime}
+ +
+ Regular Battle +
Regular Battle
+
+ ${rotation.turf + .map( + (name) => ` +
+ ${name} +
${name}
+
+ ` + ) + .join("")} +
+
+ +
+ Ranked Battle +
Ranked Battle
+
+
Battle Mode
+
${rotation.ranked_mode}
+
+
+ ${rotation.ranked + .map( + (name) => ` +
+ ${name} +
${name}
+
+ ` + ) + .join("")} +
+
+ `; + } + + container.innerHTML = html; } animateLoadingCanvas(); -renderStages(); +renderStages(); \ No newline at end of file diff --git a/styles.css b/styles.css index e99d8cb..20bede7 100644 --- a/styles.css +++ b/styles.css @@ -76,9 +76,6 @@ body { flex-direction: column; gap: 12px; width: 100%; -} - -.stages-section { background-color: rgba(0, 0, 0, 0.7); border-radius: 14px; padding: 16px 18px 20px; @@ -103,26 +100,25 @@ body { } .stage-title { - font-family: "regular"; + font-family: "Regular", sans-serif; text-align: center; margin-top: 6px; font-size: 28px; color: white; - margin-top: 6px; } .stages img { width: 100%; height: auto; display: block; - margin-bottom: 20px; - margin-top: 20px; + margin: 20px 0; } .ranked-mode-labels { text-align: center; margin-bottom: 12px; } + .battle-mode-text { font-family: "Splatoon1", sans-serif; color: white; @@ -130,6 +126,7 @@ body { letter-spacing: 1px; margin-bottom: 4px; } + .ranked-mode-text { font-family: "Regular", sans-serif; color: white; @@ -155,15 +152,14 @@ body { .mode-title { display: block; - margin: 4px auto 4px; + margin: 4px auto; max-width: 166px; width: 80%; height: auto; } .mode-text { - margin-top: 0px; - margin-bottom: 28px; + margin: 0 0 28px; text-align: center; color: white; font-family: "Splatoon1", sans-serif; @@ -172,7 +168,6 @@ body { user-select: none; } - .loading-overlay { position: fixed; top: 50%; @@ -194,6 +189,31 @@ body { box-sizing: border-box; } +#squid-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100vh; + overflow: visible; + pointer-events: none; + z-index: 0; +} + +#squid-canvas { + position: absolute; + left: 50%; + top: 0; + transform: translateX(-50%); + width: 1920px; + height: 1080px; + will-change: transform; + pointer-events: none; + user-select: none; + z-index: 0; + display: block; +} + #loading-canvas { image-rendering: pixelated; width: 100px;