updateweb

This commit is contained in:
Local Server
2026-01-01 22:24:30 -06:00
parent 017c6376fc
commit 1919f6f8bb
185 changed files with 19860 additions and 17603 deletions

View File

@@ -1,143 +1,555 @@
// Smooth Page Transitions for Sky Art Shop
// Provides fade-out/fade-in effects when navigating between pages
/**
* Page Transitions and Smooth Navigation
* Handles page loading, transitions, and history management
*/
(function () {
"use strict";
class PageTransitions {
constructor() {
this.transitionDuration = 300;
this.isTransitioning = false;
this.init();
}
// Add page transition styles (less aggressive approach)
const style = document.createElement("style");
style.textContent = `
body {
transition: opacity 0.25s ease-in-out;
init() {
// Wait for body to exist
if (!document.body) return;
// Add transition wrapper if it doesn't exist
if (!document.getElementById("page-transition")) {
const wrapper = document.createElement("div");
wrapper.id = "page-transition";
wrapper.className = "page-transition";
// Wrap main content
const main = document.querySelector("main") || document.body;
const parent = main.parentNode;
parent.insertBefore(wrapper, main);
wrapper.appendChild(main);
}
body.page-transitioning {
// Add fade-in on page load
this.fadeIn();
// Intercept navigation clicks
this.setupLinkInterception();
// Handle back/forward buttons
window.addEventListener("popstate", (e) => {
if (e.state && e.state.url) {
this.navigate(e.state.url, false);
}
});
// Add scroll restoration
if ("scrollRestoration" in history) {
history.scrollRestoration = "manual";
}
}
fadeIn() {
const wrapper = document.getElementById("page-transition");
if (wrapper) {
wrapper.classList.add("fade-in");
setTimeout(() => {
wrapper.classList.remove("fade-in");
}, this.transitionDuration);
}
}
fadeOut(callback) {
const wrapper = document.getElementById("page-transition");
if (wrapper) {
wrapper.classList.add("fade-out");
setTimeout(() => {
if (callback) callback();
wrapper.classList.remove("fade-out");
}, this.transitionDuration);
} else {
if (callback) callback();
}
}
setupLinkInterception() {
document.addEventListener("click", (e) => {
const link = e.target.closest("a");
// Check if it's a valid internal link
if (!link) return;
if (link.hasAttribute("data-no-transition")) return;
if (link.target === "_blank") return;
if (link.hasAttribute("download")) return;
const href = link.getAttribute("href");
if (
!href ||
href.startsWith("#") ||
href.startsWith("mailto:") ||
href.startsWith("tel:")
)
return;
// Check if it's an external link
const url = new URL(href, window.location.origin);
if (url.origin !== window.location.origin) return;
// Intercept the navigation
e.preventDefault();
this.navigate(href, true);
});
}
navigate(url, updateHistory = true) {
if (this.isTransitioning) return;
this.isTransitioning = true;
this.fadeOut(() => {
if (updateHistory) {
history.pushState({ url }, "", url);
}
window.location.href = url;
});
}
// Scroll to element with smooth animation
scrollTo(selector, offset = 0) {
const element = document.querySelector(selector);
if (!element) return;
const top =
element.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({
top,
behavior: "smooth",
});
}
// Scroll to top
scrollToTop() {
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
}
/**
* Lazy Loading Images
* Improves performance by loading images only when they're visible
*/
class LazyLoader {
constructor() {
this.images = [];
this.observer = null;
this.init();
}
init() {
// Find all lazy images
this.images = document.querySelectorAll(
'img[data-src], img[loading="lazy"]'
);
// Set up Intersection Observer
if ("IntersectionObserver" in window) {
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
}
});
},
{
rootMargin: "50px",
}
);
this.images.forEach((img) => this.observer.observe(img));
} else {
// Fallback for older browsers
this.images.forEach((img) => this.loadImage(img));
}
}
loadImage(img) {
const src = img.getAttribute("data-src");
if (src) {
img.src = src;
img.removeAttribute("data-src");
}
// Add fade-in effect
img.addEventListener("load", () => {
img.classList.add("loaded");
});
if (this.observer) {
this.observer.unobserve(img);
}
}
// Add new images to observer
observe(images) {
if (!images) return;
const imageList = Array.isArray(images) ? images : [images];
imageList.forEach((img) => {
if (this.observer) {
this.observer.observe(img);
} else {
this.loadImage(img);
}
});
}
}
/**
* Smooth Scroll Handler
* Adds smooth scrolling to anchor links
*/
class SmoothScroll {
constructor() {
this.init();
}
init() {
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", (e) => {
const href = anchor.getAttribute("href");
if (href === "#") return;
e.preventDefault();
const target = document.querySelector(href);
if (target) {
const offset = 80; // Account for fixed header
const top =
target.getBoundingClientRect().top + window.pageYOffset - offset;
window.scrollTo({
top,
behavior: "smooth",
});
// Update URL without scrolling
history.pushState(null, "", href);
}
});
});
}
}
/**
* Back to Top Button
* Shows/hides button based on scroll position
*/
class BackToTop {
constructor() {
this.button = null;
this.scrollThreshold = 300;
this.init();
}
init() {
// Wait for body to exist
if (!document.body) return;
// Create button if it doesn't exist
this.button = document.getElementById("back-to-top");
if (!this.button) {
this.button = document.createElement("button");
this.button.id = "back-to-top";
this.button.className = "back-to-top";
this.button.innerHTML = "↑";
this.button.setAttribute("aria-label", "Back to top");
document.body.appendChild(this.button);
}
// Handle scroll
window.addEventListener("scroll", () => {
if (window.pageYOffset > this.scrollThreshold) {
this.button.classList.add("visible");
} else {
this.button.classList.remove("visible");
}
});
// Handle click
this.button.addEventListener("click", () => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
});
}
}
/**
* Loading Overlay
* Shows loading state during async operations
*/
class LoadingOverlay {
constructor() {
this.overlay = null;
this.activeOperations = 0;
this.init();
}
init() {
// Wait for body to exist
if (!document.body) return;
// Create overlay if it doesn't exist
this.overlay = document.getElementById("loading-overlay");
if (!this.overlay) {
this.overlay = document.createElement("div");
this.overlay.id = "loading-overlay";
this.overlay.className = "loading-overlay";
this.overlay.innerHTML = `
<div class="loading-spinner">
<div class="spinner"></div>
<p>Loading...</p>
</div>
`;
document.body.appendChild(this.overlay);
}
}
show() {
this.activeOperations++;
this.overlay.classList.add("active");
document.body.style.overflow = "hidden";
}
hide() {
this.activeOperations = Math.max(0, this.activeOperations - 1);
if (this.activeOperations === 0) {
this.overlay.classList.remove("active");
document.body.style.overflow = "";
}
}
// Force hide regardless of operation count
forceHide() {
this.activeOperations = 0;
this.overlay.classList.remove("active");
document.body.style.overflow = "";
}
}
/**
* Page Visibility Handler
* Handles actions when page becomes visible/hidden
*/
class PageVisibility {
constructor() {
this.callbacks = {
visible: [],
hidden: [],
};
this.init();
}
init() {
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
this.callbacks.hidden.forEach((cb) => cb());
} else {
this.callbacks.visible.forEach((cb) => cb());
}
});
}
onVisible(callback) {
this.callbacks.visible.push(callback);
}
onHidden(callback) {
this.callbacks.hidden.push(callback);
}
}
/**
* Network Status Handler
* Monitors online/offline status
*/
class NetworkStatus {
constructor() {
this.isOnline = navigator.onLine;
this.callbacks = {
online: [],
offline: [],
};
this.init();
}
init() {
window.addEventListener("online", () => {
this.isOnline = true;
this.callbacks.online.forEach((cb) => cb());
this.showNotification("Back online", "success");
});
window.addEventListener("offline", () => {
this.isOnline = false;
this.callbacks.offline.forEach((cb) => cb());
this.showNotification("No internet connection", "error");
});
}
onOnline(callback) {
this.callbacks.online.push(callback);
}
onOffline(callback) {
this.callbacks.offline.push(callback);
}
showNotification(message, type) {
if (window.Utils && window.Utils.notify) {
window.Utils.notify(message, type);
}
}
}
// Initialize when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initPageTransitions);
} else {
initPageTransitions();
}
function initPageTransitions() {
// Initialize all modules
window.pageTransitions = new PageTransitions();
window.lazyLoader = new LazyLoader();
window.smoothScroll = new SmoothScroll();
window.backToTop = new BackToTop();
window.loadingOverlay = new LoadingOverlay();
window.pageVisibility = new PageVisibility();
window.networkStatus = new NetworkStatus();
console.log("Page transitions initialized");
}
// Add CSS if not already present
if (!document.getElementById("page-transitions-styles")) {
const style = document.createElement("style");
style.id = "page-transitions-styles";
style.textContent = `
.page-transition {
opacity: 1;
transition: opacity 300ms ease;
}
.page-transition.fade-in {
opacity: 0;
pointer-events: none;
animation: fadeIn 300ms ease forwards;
}
.page-transition.fade-out {
opacity: 1;
animation: fadeOut 300ms ease forwards;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
img[data-src] {
opacity: 0;
transition: opacity 300ms ease;
}
img.loaded {
opacity: 1;
}
.back-to-top {
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background: #667eea;
color: white;
border: none;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
opacity: 0;
visibility: hidden;
transform: translateY(20px);
transition: all 0.3s ease;
z-index: 999;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.back-to-top.visible {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.back-to-top:hover {
background: #5568d3;
transform: translateY(-2px);
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255,255,255,0.95);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
z-index: 9999;
}
.loading-overlay.active {
opacity: 1;
visibility: visible;
}
.loading-spinner {
text-align: center;
}
.spinner {
width: 60px;
height: 60px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-spinner p {
color: #667eea;
font-size: 16px;
font-weight: 600;
margin: 0;
}
`;
document.head.appendChild(style);
// Fade in page on load (if coming from a transition)
function initPageTransition() {
// Check if we're coming from a transition
const isTransitioning = sessionStorage.getItem("page-transitioning");
if (isTransitioning === "true") {
document.body.style.opacity = "0";
sessionStorage.removeItem("page-transitioning");
// Wait for content to be ready, then fade in
requestAnimationFrame(() => {
requestAnimationFrame(() => {
document.body.style.opacity = "1";
});
});
}
}
// Handle navigation with transitions
function setupNavigationTransitions() {
// Get all internal links
document.addEventListener("click", function (e) {
const link = e.target.closest("a");
if (!link) return;
const href = link.getAttribute("href");
// Skip if:
// - External link
// - Opens in new tab
// - Has download attribute
// - Is a hash link on same page
// - Is a javascript: link
// - Is a mailto: or tel: link
if (
!href ||
link.target === "_blank" ||
link.hasAttribute("download") ||
href.startsWith("javascript:") ||
href.startsWith("mailto:") ||
href.startsWith("tel:") ||
href.startsWith("#") ||
(href.includes("://") && !href.includes(window.location.host))
) {
return;
}
// Prevent default navigation
e.preventDefault();
// Start transition
document.body.classList.add("page-transitioning");
sessionStorage.setItem("page-transitioning", "true");
// Navigate after fade-out completes
setTimeout(() => {
window.location.href = href;
}, 250); // Match CSS transition duration
});
}
// Use View Transitions API if available (Chrome 111+, Safari 18+)
function setupViewTransitions() {
if (!document.startViewTransition) return;
document.addEventListener(
"click",
function (e) {
const link = e.target.closest("a");
if (!link) return;
const href = link.getAttribute("href");
// Same checks as above
if (
!href ||
link.target === "_blank" ||
link.hasAttribute("download") ||
href.startsWith("javascript:") ||
href.startsWith("mailto:") ||
href.startsWith("tel:") ||
href.startsWith("#") ||
(href.includes("://") && !href.includes(window.location.host))
) {
return;
}
e.preventDefault();
// Use View Transitions API for smooth cross-page transitions
sessionStorage.setItem("page-transitioning", "true");
document.startViewTransition(() => {
window.location.href = href;
});
},
true
); // Use capture to run before other handlers
}
// Initialize
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
initPageTransition();
setupNavigationTransitions();
});
} else {
initPageTransition();
setupNavigationTransitions();
}
// For browsers that support View Transitions API (progressive enhancement)
if ("startViewTransition" in document) {
const viewStyle = document.createElement("style");
viewStyle.textContent = `
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.25s;
}
`;
document.head.appendChild(viewStyle);
}
})();
}