JS refactor and new contact page
All checks were successful
Deploy / deploy (push) Successful in 9s

This commit is contained in:
elijah 2024-11-04 15:42:18 +01:00
parent d6960d0980
commit 2746f73cff
3 changed files with 263 additions and 198 deletions

View File

@ -5,7 +5,6 @@
--text-bright: #cef0f0; --text-bright: #cef0f0;
--glow-color: rgba(42, 90, 90, 0.6); --glow-color: rgba(42, 90, 90, 0.6);
} }
@keyframes pulse { @keyframes pulse {
0% { 0% {
box-shadow: 0 0 5px var(--glow-color); box-shadow: 0 0 5px var(--glow-color);
@ -20,7 +19,6 @@
opacity: 0.5; opacity: 0.5;
} }
} }
@keyframes slideInBorder { @keyframes slideInBorder {
0% { 0% {
width: 0; width: 0;
@ -31,7 +29,6 @@
opacity: 1; opacity: 1;
} }
} }
html { html {
scroll-snap-type: y mandatory; scroll-snap-type: y mandatory;
scroll-behavior: smooth; scroll-behavior: smooth;
@ -40,7 +37,6 @@ html {
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
} }
body { body {
background: var(--bg-color); background: var(--bg-color);
color: var(--text-bright); color: var(--text-bright);
@ -51,7 +47,6 @@ body {
position: relative; position: relative;
overflow-x: hidden; overflow-x: hidden;
} }
body::before { body::before {
content: ""; content: "";
position: fixed; position: fixed;
@ -69,14 +64,12 @@ body::before {
pointer-events: none; pointer-events: none;
z-index: 1000; z-index: 1000;
} }
.terminal { .terminal {
margin-left: 80px; margin-left: 80px;
padding: 20px; padding: 20px;
max-width: 800px; max-width: 800px;
position: relative; position: relative;
} }
.terminal::before { .terminal::before {
content: ""; content: "";
position: absolute; position: absolute;
@ -89,7 +82,6 @@ body::before {
opacity: 0.5; opacity: 0.5;
clip-path: polygon(0 2%, 98% 0, 100% 98%, 2% 100%); clip-path: polygon(0 2%, 98% 0, 100% 98%, 2% 100%);
} }
.sidebar { .sidebar {
position: fixed; position: fixed;
left: 0; left: 0;
@ -106,7 +98,6 @@ body::before {
box-shadow: 1px 0 15px var(--glow-color); box-shadow: 1px 0 15px var(--glow-color);
clip-path: polygon(0 0, 90% 0, 100% 2%, 100% 98%, 90% 100%, 0 100%); clip-path: polygon(0 0, 90% 0, 100% 2%, 100% 98%, 90% 100%, 0 100%);
} }
.nav-icon { .nav-icon {
color: var(--text-muted); color: var(--text-muted);
cursor: pointer; cursor: pointer;
@ -117,12 +108,10 @@ body::before {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
} }
.nav-icon .icon { .nav-icon .icon {
font-size: 24px; font-size: 24px;
transition: color 0.3s ease; transition: color 0.3s ease;
} }
.nav-icon .nav-label { .nav-icon .nav-label {
position: absolute; position: absolute;
top: 100%; top: 100%;
@ -138,35 +127,28 @@ body::before {
pointer-events: none; pointer-events: none;
color: var(--text-muted); color: var(--text-muted);
} }
.nav-icon.active .nav-label { .nav-icon.active .nav-label {
opacity: 1; opacity: 1;
transform: translateX(-50%) translateY(0); transform: translateX(-50%) translateY(0);
color: var(--text-muted); color: var(--text-muted);
} }
.nav-icon:hover .nav-label { .nav-icon:hover .nav-label {
opacity: 1; opacity: 1;
transform: translateX(-50%) translateY(0); transform: translateX(-50%) translateY(0);
} }
.nav-icon:hover .icon { .nav-icon:hover .icon {
color: var(--text-bright); color: var(--text-bright);
} }
.nav-icon.active .icon { .nav-icon.active .icon {
color: var(--text-bright); color: var(--text-bright);
} }
.nav-icon.active { .nav-icon.active {
color: var(--text-bright); color: var(--text-bright);
position: relative; position: relative;
} }
.nav-icon:hover { .nav-icon:hover {
color: var(--text-bright); color: var(--text-bright);
} }
.section { .section {
height: 100vh; height: 100vh;
padding: 20px 0; padding: 20px 0;
@ -175,12 +157,10 @@ body::before {
position: relative; position: relative;
overflow-y: auto; overflow-y: auto;
} }
#contact { #contact {
height: auto; height: auto;
min-height: 100vh; min-height: 100vh;
} }
.section-title { .section-title {
color: var(--text-muted); color: var(--text-muted);
margin-bottom: 20px; margin-bottom: 20px;
@ -190,7 +170,6 @@ body::before {
position: relative; position: relative;
padding-left: 20px; padding-left: 20px;
} }
.section-title::before { .section-title::before {
content: "◈"; content: "◈";
position: absolute; position: absolute;
@ -200,14 +179,12 @@ body::before {
text-shadow: 0 0 10px var(--glow-color); text-shadow: 0 0 10px var(--glow-color);
background: none; background: none;
} }
.header { .header {
border-bottom: none; border-bottom: none;
padding-bottom: 10px; padding-bottom: 10px;
margin-bottom: 20px; margin-bottom: 20px;
position: relative; position: relative;
} }
.header::after { .header::after {
content: ""; content: "";
position: absolute; position: absolute;
@ -225,24 +202,20 @@ body::before {
pulse 2s infinite, pulse 2s infinite,
slideInBorder 1s ease-out; slideInBorder 1s ease-out;
} }
.timestamp { .timestamp {
font-size: 0.9em; font-size: 0.9em;
color: var(--text-muted); color: var(--text-muted);
font-family: "Courier New", monospace; font-family: "Courier New", monospace;
line-height: 1; line-height: 1;
} }
.location { .location {
margin-top: 4px; margin-top: 4px;
color: var(--text-muted); color: var(--text-muted);
padding-bottom: 5px; padding-bottom: 5px;
} }
.entry-list { .entry-list {
margin-top: 10px; margin-top: 10px;
} }
.nested-entries { .nested-entries {
margin-left: 20px; margin-left: 20px;
margin-top: 5px; margin-top: 5px;
@ -250,7 +223,6 @@ body::before {
padding-left: 15px; padding-left: 15px;
position: relative; position: relative;
} }
.nested-entries::before { .nested-entries::before {
content: ""; content: "";
position: absolute; position: absolute;
@ -261,31 +233,26 @@ body::before {
background: var(--border-color); background: var(--border-color);
box-shadow: 0 0 8px var(--glow-color); box-shadow: 0 0 8px var(--glow-color);
} }
.entry { .entry {
margin: 10px 0; margin: 10px 0;
padding-left: 20px; padding-left: 20px;
position: relative; position: relative;
} }
.entry::before { .entry::before {
content: "▹"; content: "▹";
position: absolute; position: absolute;
left: 0; left: 0;
margin-right: 10px; margin-right: 10px;
} }
.entry-link { .entry-link {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
transition: all 0.3s ease; transition: all 0.3s ease;
position: relative; position: relative;
} }
.entry-link:hover { .entry-link:hover {
color: var(--text-bright); color: var(--text-bright);
} }
.entry-link::after { .entry-link::after {
content: ""; content: "";
position: absolute; position: absolute;
@ -306,12 +273,10 @@ body::before {
box-shadow: 0 0 8px var(--glow-color); box-shadow: 0 0 8px var(--glow-color);
opacity: 0.8; opacity: 0.8;
} }
.entry-link:hover::after { .entry-link:hover::after {
transform: scaleX(1); transform: scaleX(1);
transform-origin: left; transform-origin: left;
} }
.project-item, .project-item,
.status-item { .status-item {
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
@ -349,7 +314,6 @@ body::before {
border-left: 3px solid rgba(42, 90, 90, 0.6); border-left: 3px solid rgba(42, 90, 90, 0.6);
border-top: 2px solid rgba(255, 255, 255, 0.15); border-top: 2px solid rgba(255, 255, 255, 0.15);
} }
.project-item:hover, .project-item:hover,
.status-item:hover { .status-item:hover {
border-color: var(--text-bright); border-color: var(--text-bright);
@ -361,35 +325,29 @@ body::before {
transform: translateY(-2px) translateZ(10px); transform: translateY(-2px) translateZ(10px);
animation: pulse 2s infinite; animation: pulse 2s infinite;
} }
.project-header { .project-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
} }
.project-header .entry::before { .project-header .entry::before {
content: "◈"; content: "◈";
color: var(--text-muted); color: var(--text-muted);
text-shadow: 0 0 5px var(--glow-color); text-shadow: 0 0 5px var(--glow-color);
transition: all 0.3s ease; transition: all 0.3s ease;
} }
.project-item:hover .project-header .entry::before { .project-item:hover .project-header .entry::before {
color: var(--text-bright); color: var(--text-bright);
text-shadow: 0 0 10px var(--glow-color); text-shadow: 0 0 10px var(--glow-color);
transform: rotate(45deg); transform: rotate(45deg);
} }
.expand-icon { .expand-icon {
color: var(--text-muted); color: var(--text-muted);
transition: transform 0.3s ease; transition: transform 0.3s ease;
} }
.project-item.expanded .expand-icon { .project-item.expanded .expand-icon {
transform: rotate(45deg); transform: rotate(45deg);
} }
.project-content { .project-content {
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
@ -401,13 +359,11 @@ body::before {
opacity: 0; opacity: 0;
transform: translateY(-10px); transform: translateY(-10px);
} }
.project-content.expanded { .project-content.expanded {
max-height: 500px; max-height: 500px;
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
} }
.project-link { .project-link {
display: inline-block; display: inline-block;
color: var(--text-bright); color: var(--text-bright);
@ -422,17 +378,14 @@ body::before {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.project-link:hover { .project-link:hover {
border-color: var(--text-bright); border-color: var(--text-bright);
background: rgba(42, 90, 90, 0.1); background: rgba(42, 90, 90, 0.1);
} }
.status-grid { .status-grid {
display: grid; display: grid;
gap: 15px; gap: 15px;
} }
.status-indicator { .status-indicator {
display: inline-block; display: inline-block;
width: 8px; width: 8px;
@ -440,22 +393,18 @@ body::before {
border-radius: 50%; border-radius: 50%;
margin-right: 10px; margin-right: 10px;
} }
.status-online { .status-online {
background-color: #4caf50; background-color: #4caf50;
box-shadow: 0 0 10px rgba(76, 175, 80, 0.3); box-shadow: 0 0 10px rgba(76, 175, 80, 0.3);
} }
.status-offline { .status-offline {
background-color: #f44336; background-color: #f44336;
box-shadow: 0 0 10px rgba(244, 67, 54, 0.3); box-shadow: 0 0 10px rgba(244, 67, 54, 0.3);
} }
.status-warning { .status-warning {
background-color: #ff9800; background-color: #ff9800;
box-shadow: 0 0 10px rgba(255, 152, 0, 0.3); box-shadow: 0 0 10px rgba(255, 152, 0, 0.3);
} }
.status-details { .status-details {
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
@ -467,20 +416,17 @@ body::before {
opacity: 0; opacity: 0;
transform: translateY(-10px); transform: translateY(-10px);
} }
.status-details.expanded { .status-details.expanded {
max-height: 500px; max-height: 500px;
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
} }
.contact-links { .contact-links {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
margin-top: 20px; margin-top: 20px;
} }
.contact-link { .contact-link {
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
padding: 15px; padding: 15px;
@ -517,7 +463,6 @@ body::before {
border-left: 3px solid rgba(42, 90, 90, 0.6); border-left: 3px solid rgba(42, 90, 90, 0.6);
border-top: 2px solid rgba(255, 255, 255, 0.15); border-top: 2px solid rgba(255, 255, 255, 0.15);
} }
.contact-link:hover { .contact-link:hover {
border-color: var(--text-bright); border-color: var(--text-bright);
background: rgba(42, 90, 90, 0.1); background: rgba(42, 90, 90, 0.1);
@ -528,53 +473,43 @@ body::before {
transform: translateY(-2px) translateZ(10px); transform: translateY(-2px) translateZ(10px);
animation: pulse 2s infinite; animation: pulse 2s infinite;
} }
.contact-icon { .contact-icon {
margin-right: 15px; margin-right: 15px;
font-size: 24px; font-size: 24px;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.terminal { .terminal {
margin-left: 60px; margin-left: 60px;
padding: 15px; padding: 15px;
} }
.sidebar { .sidebar {
width: 60px; width: 60px;
} }
.nav-icon { .nav-icon {
font-size: 20px; font-size: 20px;
} }
.project-item, .project-item,
.contact-link, .contact-link,
.status-item { .status-item {
padding: 10px; padding: 10px;
} }
} }
@media (max-width: 480px) { @media (max-width: 480px) {
.terminal { .terminal {
margin-left: 50px; margin-left: 50px;
padding: 10px; padding: 10px;
} }
.sidebar { .sidebar {
width: 50px; width: 50px;
} }
.nav-icon { .nav-icon {
font-size: 18px; font-size: 18px;
margin: 15px 0; margin: 15px 0;
} }
.section-title { .section-title {
font-size: 1em; font-size: 1em;
} }
} }
.status-average { .status-average {
font-size: 0.9em; font-size: 0.9em;
color: var(--text-muted); color: var(--text-muted);
@ -582,7 +517,6 @@ body::before {
margin-top: 4px; margin-top: 4px;
color: var(--text-muted); color: var(--text-muted);
} }
.header-metrics { .header-metrics {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
@ -592,7 +526,133 @@ body::before {
font-family: "Courier New", monospace; font-family: "Courier New", monospace;
line-height: 1; line-height: 1;
} }
.separator { .separator {
color: var(--border-color); color: var(--border-color);
} }
.contact-grid {
display: grid;
gap: 20px;
margin-top: 20px;
}
.contact-card {
background: linear-gradient(
135deg,
rgba(42, 90, 90, 0.05) 0%,
rgba(42, 90, 90, 0.1) 100%
);
border: 1px solid var(--border-color);
clip-path: polygon(
0 0,
95% 0,
100% 20px,
100% 100%,
5% 100%,
0 calc(100% - 20px)
);
}
.contact-header {
padding: 15px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.contact-type {
color: var(--text-bright);
font-size: 0.9em;
}
.connection-status {
display: flex;
align-items: center;
font-size: 0.8em;
color: var(--text-muted);
}
.status-dot {
width: 6px;
height: 6px;
background: var(--text-bright);
border-radius: 50%;
margin-right: 8px;
box-shadow: 0 0 8px var(--glow-color);
animation: pulse 2s infinite;
}
.contact-content {
padding: 15px;
}
.contact-method {
display: flex;
align-items: center;
padding: 12px;
margin: 8px 0;
text-decoration: none;
color: var(--text-muted);
border: 1px solid transparent;
background: rgba(42, 90, 90, 0.1);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.contact-method:hover {
color: var(--text-bright);
border-color: var(--border-color);
background: rgba(42, 90, 90, 0.2);
transform: translateX(5px);
}
.contact-method::before {
content: "";
position: absolute;
left: 0;
top: 0;
width: 2px;
height: 100%;
background: var(--text-bright);
box-shadow: 0 0 10px var(--glow-color);
opacity: 0;
transition: opacity 0.3s ease;
}
.contact-method:hover::before {
opacity: 1;
}
.method-icon {
font-size: 1.2em;
margin-right: 15px;
color: var(--text-bright);
}
.method-details {
flex: 1;
display: flex;
flex-direction: column;
}
.method-name {
font-size: 0.9em;
margin-bottom: 4px;
}
.method-address {
font-size: 0.8em;
color: var(--text-muted);
}
.connection-indicator {
margin-left: 10px;
transition: transform 0.3s ease;
}
.contact-method:hover .connection-indicator {
transform: translateX(5px);
}
@media (max-width: 768px) {
.contact-grid {
gap: 15px;
}
.contact-method {
padding: 10px;
}
}
@media (max-width: 480px) {
.contact-header {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.method-details {
font-size: 0.9em;
}
}

View File

@ -1,8 +1,21 @@
// this is slop. i know. i'm sorry. // animation speeds (ms)
// atleast i'm not a reactfag. const TYPING_SPEED = 180;
const ERASE_SPEED = 100;
const STATUS_UPDATE_INTERVAL = 60000; // 1min
const TIME_UPDATE_INTERVAL = 1; // 1ms (still terrible)
// we add some fancy text typing animation blah blah blah // status thresholds
// it is quite overcomplicated. i have no hobbies const LATENCY_WARNING_THRESHOLD = 500000;
// mapping for group names because i'm lazy
const GROUP_NAMES = {
4: "INFRASTRUCTURE",
5: "SERVICES",
6: "API",
};
// fancy text typing animation for nav labels
// overcomplicated as fuck but whatever
function writeoutnavlabel(label) { function writeoutnavlabel(label) {
if (label.getAttribute("data-animating") === "true") return; if (label.getAttribute("data-animating") === "true") return;
@ -26,18 +39,17 @@ function writeoutnavlabel(label) {
icon.hoverAnimationDone = true; icon.hoverAnimationDone = true;
} }
} }
}, 180); }, TYPING_SPEED);
label.setAttribute("data-animation-interval", animationInterval); label.setAttribute("data-animation-interval", animationInterval);
} }
// erase animation for nav labels
// also terrible but consistent with the above
function eraselabel(label) { function eraselabel(label) {
if (label.getAttribute("data-animating") === "true") { if (label.getAttribute("data-animating") === "true") {
// clear the existing interval
const existingInterval = label.getAttribute("data-animation-interval"); const existingInterval = label.getAttribute("data-animation-interval");
if (existingInterval) { if (existingInterval) clearInterval(existingInterval);
clearInterval(existingInterval);
}
} }
label.setAttribute("data-animating", "true"); label.setAttribute("data-animating", "true");
@ -51,13 +63,13 @@ function eraselabel(label) {
label.setAttribute("data-animating", "false"); label.setAttribute("data-animating", "false");
label.style.opacity = "0"; label.style.opacity = "0";
} }
}, 100); }, ERASE_SPEED);
// 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); label.setAttribute("data-animation-interval", eraseInterval);
} }
// time display updater
// still updates way too frequently but whatever
function updateTime() { function updateTime() {
const now = new Date(); const now = new Date();
const timeString = now const timeString = now
@ -75,13 +87,9 @@ function updateTime() {
document.getElementById("current-time").textContent = timeString; document.getElementById("current-time").textContent = timeString;
} }
updateTime(); // status helper functions
// this is a terrible way to update the time
setInterval(updateTime, 1);
function calculateAverageUptime(services) { function calculateAverageUptime(services) {
if (!services || services.length === 0) return 0; if (!services || services.length === 0) return 0;
// did i ever mention that i hate math?
const totalUptime = services.reduce( const totalUptime = services.reduce(
(sum, service) => sum + service.online_24_hours, (sum, service) => sum + service.online_24_hours,
0, 0,
@ -89,28 +97,6 @@ function calculateAverageUptime(services) {
return (totalUptime / services.length).toFixed(2); 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) { function formatLatency(latency) {
return (Math.round((latency / 1000) * 10) / 10).toFixed(1); return (Math.round((latency / 1000) * 10) / 10).toFixed(1);
} }
@ -121,20 +107,16 @@ function formatUptime(uptime) {
function getStatusClass(service) { function getStatusClass(service) {
if (!service.online) return "status-offline"; if (!service.online) return "status-offline";
if (service.latency > 500000) return "status-warning"; if (service.latency > LATENCY_WARNING_THRESHOLD) return "status-warning";
return "status-online"; return "status-online";
} }
// not sure if this is the best way to do this
function getGroupName(groupId) { function getGroupName(groupId) {
const groupNames = { return GROUP_NAMES[groupId] || `GROUP ${groupId}`;
4: "INFRASTRUCTURE",
5: "SERVICES",
6: "API",
};
return groupNames[groupId] || `GROUP ${groupId}`;
} }
// populate status page first // creates the HTML for the status page
// groups services by their group_id
function createStatusHTML(services) { function createStatusHTML(services) {
const groups = services.reduce((acc, service) => { const groups = services.reduce((acc, service) => {
const groupId = service.group_id; const groupId = service.group_id;
@ -146,33 +128,33 @@ function createStatusHTML(services) {
return Object.entries(groups) return Object.entries(groups)
.map( .map(
([groupId, groupServices]) => ` ([groupId, groupServices]) => `
<div class="status-group"> <div class="status-group">
<div class="subsection-title">${getGroupName(parseInt(groupId))}</div> <div class="subsection-title">${getGroupName(parseInt(groupId))}</div>
${groupServices ${groupServices
.map( .map(
(service) => ` (service) => `
<div class="status-item"> <div class="status-item">
<div class="entry" data-service="${service.permalink}"> <div class="entry" data-service="${service.permalink}">
<span class="status-indicator ${getStatusClass(service)}"></span> <span class="status-indicator ${getStatusClass(service)}"></span>
<span class="service-name">${service.name.toUpperCase()}</span> <span class="service-name">${service.name.toUpperCase()}</span>
<span class="service-status">${service.online ? "OPERATIONAL" : "OFFLINE"}</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> </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(""); .join("");
} }
// status page func // main status update function
function updateStatus() { function updateStatus() {
const statusContainer = document.getElementById("status-container"); const statusContainer = document.getElementById("status-container");
const averageUptimeElement = document.getElementById("average-uptime"); const averageUptimeElement = document.getElementById("average-uptime");
@ -193,31 +175,41 @@ function updateStatus() {
}) })
.catch((error) => { .catch((error) => {
statusContainer.innerHTML = ` statusContainer.innerHTML = `
<div class="error-message"> <div class="error-message">
UNABLE TO FETCH STATUS DATA a(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); } }"
<div style="font-size: 0.8em; margin-top: 5px;"> );
${error.message} }); UNABLE TO FETCH STATUS DATA
</div> <div style="font-size: 0.8em; margin-top: 5px;">
</div> ${error.message}
`; </div>
</div>
`;
averageUptimeElement.textContent = "UPTIME ERROR"; averageUptimeElement.textContent = "UPTIME ERROR";
}); });
} }
// initial update // project expansion handlers
updateStatus(); document.querySelectorAll(".project-item").forEach((item) => {
item.addEventListener("click", function () {
const content = this.querySelector(".project-content");
const icon = this.querySelector(".expand-icon");
// status details toggle // close other expanded items
document.querySelectorAll(".status-item .entry").forEach((item) => { document.querySelectorAll(".project-item").forEach((otherItem) => {
const details = item.nextElementSibling; if (otherItem !== item) {
if (details && details.classList.contains("status-details")) { otherItem
item.addEventListener("click", function () { .querySelector(".project-content")
details.classList.toggle("expanded"); .classList.remove("expanded");
otherItem.querySelector(".expand-icon").textContent = "+";
}
}); });
}
content.classList.toggle("expanded");
icon.textContent = content.classList.contains("expanded") ? "" : "+";
});
}); });
// navigation // smooth scroll navigation
document.querySelectorAll('a[href^="#"]').forEach((anchor) => { document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", function (e) { anchor.addEventListener("click", function (e) {
e.preventDefault(); e.preventDefault();
@ -227,7 +219,7 @@ document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
}); });
}); });
// update active nav icon // active nav icon updater
window.addEventListener("scroll", () => { window.addEventListener("scroll", () => {
let current = ""; let current = "";
document.querySelectorAll("section").forEach((section) => { document.querySelectorAll("section").forEach((section) => {
@ -245,7 +237,6 @@ window.addEventListener("scroll", () => {
if (isCurrentSection) { if (isCurrentSection) {
if (!wasActive) { if (!wasActive) {
icon.classList.add("active"); icon.classList.add("active");
// complete instantly
if (icon.isAnimating) { if (icon.isAnimating) {
const text = label.getAttribute("data-text") || label.textContent; const text = label.getAttribute("data-text") || label.textContent;
label.textContent = text; label.textContent = text;
@ -254,7 +245,6 @@ window.addEventListener("scroll", () => {
parseInt(label.getAttribute("data-animation-interval")), parseInt(label.getAttribute("data-animation-interval")),
); );
} }
// only start if not currently animating
if (!icon.hoverAnimationDone) { if (!icon.hoverAnimationDone) {
writeoutnavlabel(label); writeoutnavlabel(label);
} }
@ -271,6 +261,7 @@ window.addEventListener("scroll", () => {
}); });
}); });
// nav icon hover animations
document.querySelectorAll(".nav-icon").forEach((icon) => { document.querySelectorAll(".nav-icon").forEach((icon) => {
icon.hoverAnimationDone = false; icon.hoverAnimationDone = false;
icon.isAnimating = false; icon.isAnimating = false;
@ -292,36 +283,39 @@ document.querySelectorAll(".nav-icon").forEach((icon) => {
} }
}); });
icon.addEventListener("click", (e) => { icon.addEventListener("click", () => {
const label = icon.querySelector(".nav-label"); const label = icon.querySelector(".nav-label");
// continue if already active
// prevent mouseleave from erasing label
if (icon.isAnimating) { if (icon.isAnimating) {
icon.hoverAnimationDone = true; icon.hoverAnimationDone = true;
} } else if (!icon.hoverAnimationDone) {
// if no animation, start one
else if (!icon.hoverAnimationDone) {
writeoutnavlabel(label); writeoutnavlabel(label);
icon.isAnimating = true; icon.isAnimating = true;
icon.hoverAnimationDone = true; icon.hoverAnimationDone = true;
} }
label.style.opacity = "1"; label.style.opacity = "1";
}); });
}); });
// dom (196 reference) content event listener // initialization
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
// write out active nav icon label instantly // start time updates
updateTime();
setInterval(updateTime, TIME_UPDATE_INTERVAL);
// start status updates
updateStatus();
setInterval(updateStatus, STATUS_UPDATE_INTERVAL);
// init active nav icon
const activeNavIcon = document.querySelector(".nav-icon.active"); const activeNavIcon = document.querySelector(".nav-icon.active");
if (activeNavIcon) { if (activeNavIcon) {
const label = activeNavIcon.querySelector(".nav-label"); const label = activeNavIcon.querySelector(".nav-label");
if (label) writeoutnavlabel(label); if (label) writeoutnavlabel(label);
} }
// fancy console log
// fancy console log because why not
console.log( console.log(
"%cELIA.NETWORK", "%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); } }", "color: #2a5a5a; font-size: 24px; font-weight: bold; text-shadow: 2px 2px 4px rgba(42, 90, 90, 0.3);",
); );
}); });

View File

@ -202,42 +202,53 @@
<section id="contact" class="section"> <section id="contact" class="section">
<div class="section-title">CONTACT</div> <div class="section-title">CONTACT</div>
<div class="contact-links"> <div class="contact-grid">
<div class="project-item"> <div class="contact-card">
<div class="project-header"> <div class="contact-header">
<div class="entry">E-Mail</div> <div class="contact-type">NETWORK</div>
<span class="expand-icon">+</span> <div class="connection-status">
</div> <span class="status-dot"></span>
<div class="project-content"> EMAIL
<div class="project-links">
<a
href="mailto:alerts@elia.network"
class="project-link"
>⎔ ALERTS →</a
>
<a
href="mailto:noc@elia.network"
class="project-link"
>⎔ NOC →</a
>
</div> </div>
</div> </div>
<div class="contact-content">
<a href="mailto:alerts@elia.network" class="contact-method">
<span class="method-icon"></span>
<span class="method-details">
<span class="method-name">ALERTS</span>
<span class="method-address">alerts@elia.network</span>
</span>
<span class="connection-indicator"></span>
</a>
<a href="mailto:noc@elia.network" class="contact-method">
<span class="method-icon"></span>
<span class="method-details">
<span class="method-name">NOC</span>
<span class="method-address">noc@elia.network</span>
</span>
<span class="connection-indicator"></span>
</a>
</div>
</div> </div>
<div class="project-item"> <div class="contact-card">
<div class="project-header"> <div class="contact-header">
<div class="entry">TELEGRAM</div> <div class="contact-type">DIRECT</div>
<span class="expand-icon">+</span> <div class="connection-status">
</div> <span class="status-dot"></span>
<div class="project-content"> INSTANT MESSAGING
<div class="project-links">
<a
href="https://t.me/beslikmeister"
class="project-link"
>⎔ T.ME →</a
>
</div> </div>
</div> </div>
<div class="contact-content">
<a href="https://t.me/beslikmeister" class="contact-method">
<span class="method-icon"></span>
<span class="method-details">
<span class="method-name">TELEGRAM</span>
<span class="method-address">@beslikmeister</span>
</span>
<span class="connection-indicator"></span>
</a>
</div>
</div> </div>
</div> </div>
</section> </section>