Venus_Studios/assets/js/app.js

216 lines
6.4 KiB
JavaScript

const qs = (selector, scope = document) => scope.querySelector(selector);
const qsa = (selector, scope = document) => [...scope.querySelectorAll(selector)];
const initHeader = () => {
const header = qs(".site-header");
const toggle = qs("[data-menu-toggle]");
const panel = qs("[data-mobile-panel]");
const page = document.body.dataset.page;
let scrollTicking = false;
if (page) {
qsa(`[data-nav="${page}"]`).forEach((link) => link.classList.add("is-active"));
}
const updateHeader = () => {
if (!header) return;
header.classList.toggle("is-scrolled", window.scrollY > 24);
scrollTicking = false;
};
const onScroll = () => {
if (scrollTicking) return;
scrollTicking = true;
window.requestAnimationFrame(updateHeader);
};
updateHeader();
window.addEventListener("scroll", onScroll, { passive: true });
if (toggle && panel) {
toggle.addEventListener("click", () => {
const isOpen = panel.classList.toggle("is-open");
document.body.classList.toggle("menu-open", isOpen);
toggle.setAttribute("aria-expanded", String(isOpen));
const icon = qs("[data-menu-icon]", toggle);
if (icon) icon.setAttribute("data-lucide", isOpen ? "x" : "menu");
if (window.lucide) window.lucide.createIcons();
});
qsa("a", panel).forEach((link) => {
link.addEventListener("click", () => {
panel.classList.remove("is-open");
document.body.classList.remove("menu-open");
toggle.setAttribute("aria-expanded", "false");
const icon = qs("[data-menu-icon]", toggle);
if (icon) icon.setAttribute("data-lucide", "menu");
if (window.lucide) window.lucide.createIcons();
});
});
}
};
const initSmoothAnchors = () => {
const smoothMotion = window.matchMedia("(prefers-reduced-motion: no-preference)").matches && window.matchMedia("(min-width: 768px)").matches;
qsa('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", (event) => {
const target = qs(anchor.getAttribute("href"));
if (!target) return;
event.preventDefault();
target.scrollIntoView({ behavior: smoothMotion ? "smooth" : "auto", block: "start" });
});
});
};
const initAnimations = () => {
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (window.AOS) {
AOS.init({
once: true,
duration: 560,
easing: "ease-out-cubic",
offset: 70,
disable: () => window.innerWidth < 640 || reducedMotion
});
}
};
const initHeroReveal = () => {
qsa(".reveal-text").forEach((item, index) => {
item.style.animationDelay = `${Math.min(index * 70, 420)}ms`;
});
};
const initGallery = () => {
const buttons = qsa("[data-filter]");
const cards = qsa("[data-category]");
if (!buttons.length || !cards.length) return;
buttons.forEach((button) => {
button.addEventListener("click", () => {
const filter = button.dataset.filter;
buttons.forEach((item) => item.classList.remove("is-active"));
button.classList.add("is-active");
cards.forEach((card) => {
const show = filter === "all" || card.dataset.category === filter;
card.style.display = show ? "inline-block" : "none";
});
if (window.AOS) AOS.refreshHard();
});
});
};
const initImageHints = () => {
const fallback = "assets/images/gallery/studio-fallback.svg";
const applyFallback = (img) => {
if (img.dataset.fallbackApplied === "true") return;
img.dataset.fallbackApplied = "true";
img.src = fallback;
img.alt = img.alt || "Venus Photo Studio And Lab image preview";
const lightboxTrigger = img.closest("[data-lightbox]");
if (lightboxTrigger) {
lightboxTrigger.dataset.lightbox = fallback;
}
};
qsa("img").forEach((img) => {
img.decoding = "async";
if (!img.hasAttribute("loading") && !img.closest(".site-header")) {
img.loading = "lazy";
}
img.addEventListener("error", () => applyFallback(img), { once: true });
if (img.complete && img.naturalWidth === 0) {
applyFallback(img);
}
});
};
const initLightbox = () => {
const triggers = qsa("[data-lightbox]");
if (!triggers.length) return;
const lightbox = document.createElement("div");
lightbox.className = "lightbox";
lightbox.setAttribute("role", "dialog");
lightbox.setAttribute("aria-modal", "true");
lightbox.innerHTML = `
<button class="lightbox-close" type="button" aria-label="Close preview">
<i data-lucide="x"></i>
</button>
<img alt="">
`;
document.body.appendChild(lightbox);
const image = qs("img", lightbox);
const close = qs(".lightbox-close", lightbox);
const closeLightbox = () => {
lightbox.classList.remove("is-open");
document.body.classList.remove("menu-open");
image.removeAttribute("src");
image.alt = "";
};
triggers.forEach((trigger) => {
trigger.addEventListener("click", () => {
const source = trigger.dataset.lightbox || qs("img", trigger)?.src;
const caption = trigger.dataset.caption || qs(".gallery-caption", trigger)?.textContent?.trim() || "Venus Photo Studio And Lab gallery preview";
if (!source) return;
image.src = source;
image.alt = caption;
lightbox.classList.add("is-open");
document.body.classList.add("menu-open");
if (window.lucide) window.lucide.createIcons();
});
});
close.addEventListener("click", closeLightbox);
lightbox.addEventListener("click", (event) => {
if (event.target === lightbox) closeLightbox();
});
window.addEventListener("keydown", (event) => {
if (event.key === "Escape") closeLightbox();
});
};
const initContactForm = () => {
const form = qs("[data-contact-form]");
const note = qs("[data-form-note]");
if (!form || !note) return;
form.addEventListener("submit", (event) => {
event.preventDefault();
note.textContent = "Thank you. Your enquiry is ready to send through the connected contact channel.";
note.classList.remove("hidden");
form.reset();
});
};
const initFooterYear = () => {
qsa("[data-year]").forEach((item) => {
item.textContent = String(new Date().getFullYear());
});
};
document.addEventListener("DOMContentLoaded", () => {
initHeader();
initSmoothAnchors();
initHeroReveal();
initAnimations();
initGallery();
initLightbox();
initContactForm();
initImageHints();
initFooterYear();
if (window.lucide) window.lucide.createIcons();
});