website/assets/index.js
elijah d6960d0980
All checks were successful
Deploy / deploy (push) Successful in 20s
Added animations
2024-11-04 12:11:42 +01:00

328 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// this is slop. i know. i'm sorry.
// atleast i'm not a reactfag.
// we add some fancy text typing animation blah blah blah
// it is quite overcomplicated. i have no hobbies
function writeoutnavlabel(label) {
if (label.getAttribute("data-animating") === "true") return;
const text = label.getAttribute("data-text") || label.textContent;
label.setAttribute("data-text", text);
label.textContent = "";
label.style.opacity = "1";
label.setAttribute("data-animating", "true");
let i = 0;
let animationInterval = setInterval(() => {
if (i < text.length) {
label.textContent += text.charAt(i);
i++;
} else {
clearInterval(animationInterval);
label.setAttribute("data-animating", "false");
const icon = label.closest(".nav-icon");
if (icon) {
icon.isAnimating = false;
icon.hoverAnimationDone = true;
}
}
}, 180);
label.setAttribute("data-animation-interval", animationInterval);
}
function eraselabel(label) {
if (label.getAttribute("data-animating") === "true") {
// clear the existing interval
const existingInterval = label.getAttribute("data-animation-interval");
if (existingInterval) {
clearInterval(existingInterval);
}
}
label.setAttribute("data-animating", "true");
let eraseInterval = setInterval(() => {
const currentText = label.textContent;
if (currentText.length > 0) {
label.textContent = currentText.slice(0, -1);
} else {
clearInterval(eraseInterval);
label.setAttribute("data-animating", "false");
label.style.opacity = "0";
}
}, 100);
// store the interval so we can clear it if needed
// this is a terrible way to do this like horrible
label.setAttribute("data-animation-interval", eraseInterval);
}
function updateTime() {
const now = new Date();
const timeString = now
.toLocaleString("en-US", {
month: "2-digit",
day: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
fractionalSecondDigits: 3,
hour12: true,
})
.toUpperCase();
document.getElementById("current-time").textContent = timeString;
}
updateTime();
// this is a terrible way to update the time
setInterval(updateTime, 1);
function calculateAverageUptime(services) {
if (!services || services.length === 0) return 0;
// did i ever mention that i hate math?
const totalUptime = services.reduce(
(sum, service) => sum + service.online_24_hours,
0,
);
return (totalUptime / services.length).toFixed(2);
}
// projects
const projectItems = document.querySelectorAll(".project-item");
projectItems.forEach((item) => {
item.addEventListener("click", function () {
const content = this.querySelector(".project-content");
const icon = this.querySelector(".expand-icon");
// close other expanded items
projectItems.forEach((otherItem) => {
if (otherItem !== item) {
otherItem
.querySelector(".project-content")
.classList.remove("expanded");
otherItem.querySelector(".expand-icon").textContent = "+";
}
});
content.classList.toggle("expanded");
icon.textContent = content.classList.contains("expanded") ? "" : "+";
});
});
// status updater
function formatLatency(latency) {
return (Math.round((latency / 1000) * 10) / 10).toFixed(1);
}
function formatUptime(uptime) {
return uptime.toFixed(2);
}
function getStatusClass(service) {
if (!service.online) return "status-offline";
if (service.latency > 500000) return "status-warning";
return "status-online";
}
// not sure if this is the best way to do this
function getGroupName(groupId) {
const groupNames = {
4: "INFRASTRUCTURE",
5: "SERVICES",
6: "API",
};
return groupNames[groupId] || `GROUP ${groupId}`;
}
// populate status page first
function createStatusHTML(services) {
const groups = services.reduce((acc, service) => {
const groupId = service.group_id;
if (!acc[groupId]) acc[groupId] = [];
acc[groupId].push(service);
return acc;
}, {});
return Object.entries(groups)
.map(
([groupId, groupServices]) => `
<div class="status-group">
<div class="subsection-title">${getGroupName(parseInt(groupId))}</div>
${groupServices
.map(
(service) => `
<div class="status-item">
<div class="entry" data-service="${service.permalink}">
<span class="status-indicator ${getStatusClass(service)}"></span>
<span class="service-name">${service.name.toUpperCase()}</span>
<span class="service-status">${service.online ? "OPERATIONAL" : "OFFLINE"}</span>
</div>
<div class="status-details">
<div class="status-metric">Latency: ${formatLatency(service.latency)}ms</div>
<div class="status-metric">Uptime 24h: ${formatUptime(service.online_24_hours)}%</div>
<div class="status-metric">7-day Uptime: ${formatUptime(service.online_7_days)}%</div>
</div>
</div>
`,
)
.join("")}
</div>
`,
)
.join("");
}
// status page func
function updateStatus() {
const statusContainer = document.getElementById("status-container");
const averageUptimeElement = document.getElementById("average-uptime");
fetch("https://status.elia.network/api/services")
.then((response) => response.json())
.then((data) => {
statusContainer.innerHTML = createStatusHTML(data);
const averageUptime = calculateAverageUptime(data);
averageUptimeElement.textContent = `UPTIME ${averageUptime}%`;
document.querySelectorAll(".status-item").forEach((item) => {
item.addEventListener("click", function () {
const details = this.querySelector(".status-details");
details.classList.toggle("expanded");
});
});
})
.catch((error) => {
statusContainer.innerHTML = `
<div class="error-message">
UNABLE TO FETCH STATUS DATA
<div style="font-size: 0.8em; margin-top: 5px;">
${error.message}
</div>
</div>
`;
averageUptimeElement.textContent = "UPTIME ERROR";
});
}
// initial update
updateStatus();
// status details toggle
document.querySelectorAll(".status-item .entry").forEach((item) => {
const details = item.nextElementSibling;
if (details && details.classList.contains("status-details")) {
item.addEventListener("click", function () {
details.classList.toggle("expanded");
});
}
});
// navigation
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", function (e) {
e.preventDefault();
document.querySelector(this.getAttribute("href")).scrollIntoView({
behavior: "smooth",
});
});
});
// update active nav icon
window.addEventListener("scroll", () => {
let current = "";
document.querySelectorAll("section").forEach((section) => {
const sectionTop = section.offsetTop;
if (scrollY >= sectionTop - 60) {
current = section.getAttribute("id");
}
});
document.querySelectorAll(".nav-icon").forEach((icon) => {
const label = icon.querySelector(".nav-label");
const isCurrentSection = icon.getAttribute("href") === `#${current}`;
const wasActive = icon.classList.contains("active");
if (isCurrentSection) {
if (!wasActive) {
icon.classList.add("active");
// complete instantly
if (icon.isAnimating) {
const text = label.getAttribute("data-text") || label.textContent;
label.textContent = text;
label.setAttribute("data-animating", "false");
clearInterval(
parseInt(label.getAttribute("data-animation-interval")),
);
}
// only start if not currently animating
if (!icon.hoverAnimationDone) {
writeoutnavlabel(label);
}
icon.hoverAnimationDone = true;
}
} else {
if (wasActive) {
eraselabel(label);
icon.classList.remove("active");
icon.hoverAnimationDone = false;
icon.isAnimating = false;
}
}
});
});
document.querySelectorAll(".nav-icon").forEach((icon) => {
icon.hoverAnimationDone = false;
icon.isAnimating = false;
icon.addEventListener("mouseenter", () => {
if (!icon.classList.contains("active") && !icon.isAnimating) {
const label = icon.querySelector(".nav-label");
writeoutnavlabel(label);
icon.isAnimating = true;
}
});
icon.addEventListener("mouseleave", () => {
if (!icon.classList.contains("active")) {
const label = icon.querySelector(".nav-label");
eraselabel(label);
icon.isAnimating = false;
icon.hoverAnimationDone = false;
}
});
icon.addEventListener("click", (e) => {
const label = icon.querySelector(".nav-label");
// continue if already active
// prevent mouseleave from erasing label
if (icon.isAnimating) {
icon.hoverAnimationDone = true;
}
// if no animation, start one
else if (!icon.hoverAnimationDone) {
writeoutnavlabel(label);
icon.isAnimating = true;
icon.hoverAnimationDone = true;
}
label.style.opacity = "1";
});
});
// dom (196 reference) content event listener
document.addEventListener("DOMContentLoaded", () => {
// write out active nav icon label instantly
const activeNavIcon = document.querySelector(".nav-icon.active");
if (activeNavIcon) {
const label = activeNavIcon.querySelector(".nav-label");
if (label) writeoutnavlabel(label);
}
// fancy console log
console.log(
"%cELIA.NETWORK",
"color: #2a5a5a; font-size: 24px; font-weight: bold; text-shadow: 2px 2px 4px rgba(42, 90, 90, 0.4), 4px 4px 8px rgba(42, 90, 90, 0.2), 6px 6px 12px rgba(42, 90, 90, 0.1), 0 0 20px rgba(42, 90, 90, 0.3); transform: perspective(1000px) rotateX(20deg) rotateY(-10deg) translateZ(50px); animation: console3d 8s infinite cubic-bezier(0.4, 0, 0.2, 1), glow 2s infinite alternate; @keyframes console3d { 0% { transform: perspective(1000px) rotateX(20deg) rotateY(-10deg) translateZ(50px); } 25% { transform: perspective(1000px) rotateX(-15deg) rotateY(15deg) translateZ(75px); } 50% { transform: perspective(1000px) rotateX(10deg) rotateY(20deg) translateZ(100px); } 75% { transform: perspective(1000px) rotateX(-20deg) rotateY(-15deg) translateZ(75px); } 100% { transform: perspective(1000px) rotateX(20deg) rotateY(-10deg) translateZ(50px); } } @keyframes glow { from { text-shadow: 0 0 20px rgba(42, 90, 90, 0.3), 0 0 40px rgba(42, 90, 90, 0.2); } to { text-shadow: 0 0 40px rgba(42, 90, 90, 0.5), 0 0 80px rgba(42, 90, 90, 0.3); } }",
);
});