309 lines
10 KiB
JavaScript
309 lines
10 KiB
JavaScript
// Smooth scroll for nav links
|
|
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
|
anchor.addEventListener("click", function (e) {
|
|
e.preventDefault();
|
|
const target = document.querySelector(this.getAttribute("href"));
|
|
if (target) {
|
|
target.scrollIntoView({ behavior: "smooth" });
|
|
}
|
|
});
|
|
});
|
|
|
|
// ─── Parallax ───
|
|
function initParallax() {
|
|
const sections = document.querySelectorAll(".bg-image-section:not(#hero)");
|
|
const heroLayer = document.getElementById("hero-slideshow");
|
|
|
|
function update() {
|
|
sections.forEach((section) => {
|
|
const rect = section.getBoundingClientRect();
|
|
const yPos = -(rect.top * 0.4);
|
|
section.style.backgroundPositionY = `${yPos}px`;
|
|
});
|
|
|
|
if (heroLayer) {
|
|
const hero = document.getElementById("hero");
|
|
if (hero) {
|
|
const rect = hero.getBoundingClientRect();
|
|
const yPos = -(rect.top * 0.4);
|
|
heroLayer.style.transform = `translateY(${yPos}px) scale(1.1)`;
|
|
}
|
|
}
|
|
}
|
|
|
|
window.addEventListener("scroll", update, { passive: true });
|
|
update();
|
|
}
|
|
|
|
// ─── Hero Slideshow with Destructive Theme ───
|
|
let currentBackdrop = null;
|
|
const NEAR_TOP_THRESHOLD = 1800;
|
|
|
|
function checkDestructiveTheme() {
|
|
if (!currentBackdrop) return;
|
|
const nearTop = window.scrollY < NEAR_TOP_THRESHOLD;
|
|
if (currentBackdrop.destructive && nearTop) {
|
|
document.body.classList.add("destructive-mode");
|
|
} else {
|
|
document.body.classList.remove("destructive-mode");
|
|
}
|
|
}
|
|
|
|
function initHeroSlideshow(allBackdrops, usedNames) {
|
|
const hero = document.getElementById("hero");
|
|
const layer = document.getElementById("hero-slideshow");
|
|
if (!hero || !layer) return;
|
|
|
|
const slides = allBackdrops.filter((b) => !usedNames.includes(b.name));
|
|
|
|
if (slides.length === 0) {
|
|
console.warn("No unused backdrops for hero slideshow");
|
|
return;
|
|
}
|
|
|
|
let idx = 0;
|
|
|
|
function showSlide(index) {
|
|
const backdrop = slides[index];
|
|
currentBackdrop = backdrop;
|
|
|
|
layer.style.opacity = "0";
|
|
|
|
setTimeout(() => {
|
|
layer.style.backgroundImage = `url('/assets/${backdrop.name}.png')`;
|
|
layer.style.opacity = "1";
|
|
checkDestructiveTheme();
|
|
}, 600);
|
|
|
|
const delayMs = (backdrop.time || 6) * 1000;
|
|
idx = (index + 1) % slides.length;
|
|
setTimeout(() => showSlide(idx), delayMs);
|
|
}
|
|
|
|
currentBackdrop = slides[0];
|
|
layer.style.backgroundImage = `url('/assets/${slides[0].name}.png')`;
|
|
layer.style.opacity = "1";
|
|
checkDestructiveTheme();
|
|
|
|
idx = 1;
|
|
const initialDelay = (slides[0].time || 6) * 1000;
|
|
setTimeout(() => showSlide(idx), initialDelay);
|
|
}
|
|
|
|
// ─── Load Backdrops ───
|
|
async function loadBackdrops() {
|
|
try {
|
|
const response = await fetch("/api/backdrops.json");
|
|
const backdrops = await response.json();
|
|
|
|
const safeBackdrops = backdrops.filter((b) => !b.destructive);
|
|
const sections = document.querySelectorAll(".bg-image-section:not(#hero)");
|
|
const used = [];
|
|
|
|
sections.forEach((section) => {
|
|
let pick;
|
|
let safety = 0;
|
|
do {
|
|
pick = safeBackdrops[Math.floor(Math.random() * safeBackdrops.length)];
|
|
safety++;
|
|
} while (
|
|
used.includes(pick.name) &&
|
|
used.length < safeBackdrops.length &&
|
|
safety < 50
|
|
);
|
|
used.push(pick.name);
|
|
section.style.backgroundImage = `url('/assets/${pick.name}.png')`;
|
|
});
|
|
|
|
initParallax();
|
|
initHeroSlideshow(backdrops, used);
|
|
|
|
window.addEventListener("scroll", checkDestructiveTheme, { passive: true });
|
|
} catch (err) {
|
|
console.error("Failed to load backdrops:", err);
|
|
}
|
|
}
|
|
|
|
// ─── Load Staff (in Socials section) ───
|
|
async function loadStaff() {
|
|
try {
|
|
const response = await fetch("/api/staff.json");
|
|
const staff = await response.json();
|
|
|
|
const container = document.getElementById("staff-grid");
|
|
if (!container) return;
|
|
|
|
container.innerHTML = staff
|
|
.map(
|
|
(member) => `
|
|
<div class="team-card staff-card">
|
|
${
|
|
member.icon
|
|
? `<img src="/assets/${member.icon}" alt="${member.name}" onerror="this.style.display='none'">`
|
|
: '<div class="staff-placeholder"></div>'
|
|
}
|
|
<div class="name">${member.name}</div>
|
|
<div class="role">${member.role}</div>
|
|
</div>
|
|
`,
|
|
)
|
|
.join("");
|
|
} catch (err) {
|
|
console.error("Failed to load staff:", err);
|
|
}
|
|
}
|
|
// ─── Load Teams ───
|
|
async function loadTeams() {
|
|
try {
|
|
const response = await fetch("/api/teams.json");
|
|
const teams = await response.json();
|
|
|
|
const container = document.getElementById("teams-grid");
|
|
if (!container) return;
|
|
|
|
container.innerHTML = teams
|
|
.map((team) => {
|
|
const acronym = team.acronym || "";
|
|
const vips = team.vips || "";
|
|
const hasDiscord = team.discord && team.discord.trim() !== "";
|
|
|
|
return `
|
|
<div class="team-card-large">
|
|
<div class="team-card-image" style="background-image: url('/assets/${team.image}')">
|
|
<div class="team-card-overlay"></div>
|
|
<div class="team-card-header">
|
|
<div class="team-name">${team.title}</div>
|
|
${acronym ? `<div class="team-acronym">${acronym}</div>` : ""}
|
|
</div>
|
|
</div>
|
|
<div class="team-card-body">
|
|
<div class="fade-line"></div>
|
|
<div class="team-meta-row">
|
|
<span class="meta-label">${team.leader}</span>
|
|
</div>
|
|
${
|
|
vips
|
|
? `
|
|
<div class="team-meta-row">
|
|
<span class="meta-value vips">${vips}</span>
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
<div class="fade-line"></div>
|
|
<p class="team-description">${team.description}</p>
|
|
${
|
|
hasDiscord
|
|
? `
|
|
<a href="${team.discord}" target="_blank" rel="noopener" class="team-discord-btn">
|
|
<svg viewBox="0 0 24 24" fill="currentColor" class="discord-mini">
|
|
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/>
|
|
</svg>
|
|
Discord
|
|
</a>
|
|
`
|
|
: ""
|
|
}
|
|
</div>
|
|
</div>
|
|
`;
|
|
})
|
|
.join("");
|
|
} catch (err) {
|
|
console.error("Failed to load teams:", err);
|
|
}
|
|
}
|
|
// ─── Load Posts ───
|
|
async function loadPosts() {
|
|
try {
|
|
const response = await fetch("/api/posts.json");
|
|
const posts = await response.json();
|
|
|
|
const container = document.getElementById("posts-grid");
|
|
if (!container) return;
|
|
|
|
container.innerHTML = posts
|
|
.map(
|
|
(post) => `
|
|
<a href="/post/${post.file}" class="post-card">
|
|
<div class="post-date">${post.date}</div>
|
|
<div class="post-title">${post.title}</div>
|
|
<div class="post-desc">${post.description}</div>
|
|
</a>
|
|
`,
|
|
)
|
|
.join("");
|
|
} catch (err) {
|
|
console.error("Failed to load posts:", err);
|
|
}
|
|
}
|
|
|
|
// ─── Video Play/Pause ───
|
|
function initVideo() {
|
|
const playBtn = document.getElementById("play-btn");
|
|
const video = document.getElementById("trailer-video");
|
|
const container = document.querySelector(".video-container");
|
|
|
|
if (!playBtn || !video || !container) return;
|
|
|
|
playBtn.addEventListener("click", () => {
|
|
if (video.paused) {
|
|
video.play();
|
|
container.classList.add("playing");
|
|
} else {
|
|
video.pause();
|
|
container.classList.remove("playing");
|
|
}
|
|
});
|
|
|
|
video.addEventListener("ended", () => {
|
|
container.classList.remove("playing");
|
|
});
|
|
}
|
|
|
|
// ─── Scroll Effects ───
|
|
function initScrollEffects() {
|
|
const header = document.querySelector("header");
|
|
const footer = document.querySelector("footer");
|
|
const scrollThreshold = 200;
|
|
|
|
function update() {
|
|
const scrollY = window.scrollY;
|
|
const docHeight = document.documentElement.scrollHeight;
|
|
const winHeight = window.innerHeight;
|
|
const nearBottom = scrollY + winHeight >= docHeight - 80;
|
|
const shortPage = docHeight <= winHeight + 100;
|
|
|
|
if (scrollY > 10) {
|
|
header.classList.add("scrolled");
|
|
} else {
|
|
header.classList.remove("scrolled");
|
|
}
|
|
|
|
if (scrollY < scrollThreshold && !shortPage) {
|
|
footer.classList.remove("visible");
|
|
} else {
|
|
footer.classList.add("visible");
|
|
if (nearBottom || shortPage) {
|
|
footer.classList.remove("glass");
|
|
footer.classList.add("solid");
|
|
} else {
|
|
footer.classList.add("glass");
|
|
footer.classList.remove("solid");
|
|
}
|
|
}
|
|
}
|
|
|
|
window.addEventListener("scroll", update, { passive: true });
|
|
update();
|
|
}
|
|
|
|
// ─── Initialize ───
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
loadBackdrops();
|
|
loadStaff();
|
|
loadTeams();
|
|
loadPosts();
|
|
initVideo();
|
|
initScrollEffects();
|
|
});
|