Inital Commit
This commit is contained in:
commit
a309e824be
29 changed files with 2856 additions and 0 deletions
249
website/app.js
Normal file
249
website/app.js
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
// 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 = 600;
|
||||
|
||||
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 Team ───
|
||||
async function loadTeam() {
|
||||
try {
|
||||
const response = await fetch("/api/team.json");
|
||||
const team = await response.json();
|
||||
team.sort((a, b) => a.priority - b.priority);
|
||||
|
||||
const container = document.getElementById("team-grid");
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = team
|
||||
.map(
|
||||
(member) => `
|
||||
<div class="team-card">
|
||||
${
|
||||
member.icon
|
||||
? `<img src="/assets/${member.icon}" alt="${member.name}" onerror="this.style.display='none'">`
|
||||
: '<div style="width:100px;height:100px;border-radius:50%;background:#2a2e33;margin:0 auto 1.2rem;border:3px solid #2a2e33;"></div>'
|
||||
}
|
||||
<div class="name">${member.name}</div>
|
||||
<div class="role">${member.role}</div>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
} catch (err) {
|
||||
console.error("Failed to load team:", 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();
|
||||
loadTeam();
|
||||
loadPosts();
|
||||
initVideo();
|
||||
initScrollEffects();
|
||||
});
|
||||
Loading…
Reference in a new issue