Update 'about.html' to correct team member numbering and enhance 'index.html' with preloading of critical resources, optimized images, and deferred script loading for improved performance. Modify 'package.json' to include minification in the build script and enable JIT mode in 'tailwind.config.js'.

This commit is contained in:
George Birikorang 2025-09-19 10:02:20 -07:00
parent 38db6e6744
commit 84eafb8746
15 changed files with 382 additions and 384 deletions

56
.htaccess Normal file
View file

@ -0,0 +1,56 @@
# KH3 Website Performance Optimization
# Cache static assets for better performance
# Enable compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/xml
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE image/svg+xml
</IfModule>
# Cache static assets
<IfModule mod_expires.c>
ExpiresActive On
# Images
ExpiresByType image/jpg "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType image/webp "access plus 1 month"
# CSS and JavaScript
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType application/x-javascript "access plus 1 month"
# Fonts
ExpiresByType font/woff "access plus 1 year"
ExpiresByType font/woff2 "access plus 1 year"
ExpiresByType application/font-woff "access plus 1 year"
ExpiresByType application/font-woff2 "access plus 1 year"
# HTML (shorter cache for dynamic content)
ExpiresByType text/html "access plus 1 hour"
</IfModule>
# Add cache control headers
<IfModule mod_headers.c>
# Cache static assets
<FilesMatch "\.(css|js|png|jpg|jpeg|gif|svg|woff|woff2)$">
Header set Cache-Control "public, max-age=2592000"
</FilesMatch>
# Cache HTML for shorter time
<FilesMatch "\.(html|htm)$">
Header set Cache-Control "public, max-age=3600"
</FilesMatch>
</IfModule>

View file

@ -625,7 +625,7 @@
class="w-16 h-16 bg-gradient-to-br from-kh3-red to-red-700 rounded-full flex items-center justify-center mx-auto mb-8 shadow-lg group-hover:shadow-red-500/50 transition-all duration-300 transform group-hover:scale-110"
>
<span class="text-white text-2xl font-bold drop-shadow-lg"
>02</span
>01</span
>
</div>
@ -689,7 +689,7 @@
class="w-16 h-16 bg-gradient-to-br from-kh3-red to-red-700 rounded-full flex items-center justify-center mx-auto mb-8 shadow-lg group-hover:shadow-red-500/50 transition-all duration-300 transform group-hover:scale-110"
>
<span class="text-white text-2xl font-bold drop-shadow-lg"
>01</span
>02</span
>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

View file

@ -12,6 +12,15 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Preload critical resources -->
<link
rel="preload"
href="assets/images/optimized/room_optimized.jpg"
as="image"
/>
<link rel="preload" href="styles/main.css" as="style" />
<link rel="preload" href="js/main.js" as="script" />
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700;800;900&display=swap"
rel="stylesheet"
@ -30,7 +39,14 @@
href="index.html"
class="flex items-center gap-3 hover:opacity-80 transition-opacity"
>
<img src="assets/images/kh3_logo.png" alt="KH3" class="w-12 h-12" />
<img
src="assets/images/optimized/kh3_logo_optimized.png"
alt="KH3"
class="w-12 h-12"
loading="lazy"
width="64"
height="64"
/>
</a>
</div>
@ -368,7 +384,14 @@
href="index.html"
class="flex items-center gap-3 animate-logo-float hover:opacity-80 transition-opacity"
>
<img src="assets/images/kh3_logo.png" alt="KH3" class="w-16 h-16" />
<img
src="assets/images/optimized/kh3_logo_optimized.png"
alt="KH3"
class="w-16 h-16"
loading="lazy"
width="64"
height="64"
/>
</a>
</div>
@ -387,9 +410,12 @@
<div class="menu-outline" aria-hidden="true"></div>
<div class="relative" id="menuGrid">
<img
src="assets/icons/closed_petal.png"
src="assets/images/optimized/closed_petal_optimized.png"
alt="closed petal"
class="w-6 h-6"
loading="lazy"
width="24"
height="24"
/>
</div>
<div
@ -397,9 +423,12 @@
class="absolute inset-0 flex items-center justify-center pointer-events-none opacity-0 transition-opacity"
>
<img
src="assets/icons/open_petal.png"
src="assets/images/optimized/open_petal_optimized.png"
alt="petal"
class="w-8 h-8"
loading="lazy"
width="32"
height="32"
/>
</div>
</div>
@ -509,16 +538,22 @@
<!-- Current Image -->
<img
id="currentImg"
src="assets/images/room.png"
src="assets/images/optimized/room_optimized.jpg"
alt="Sample interior fit-out by KH3"
class="absolute inset-0 w-full h-full object-cover"
loading="eager"
width="1920"
height="1080"
/>
<!-- Next Image (hidden initially) -->
<img
id="nextImg"
src="assets/images/chair.png"
src="assets/images/optimized/chair_optimized.jpg"
alt="Sample interior fit-out by KH3"
class="absolute inset-0 w-full h-full object-cover opacity-0"
loading="lazy"
width="1920"
height="1080"
/>
<div class="absolute inset-0 bg-black bg-opacity-20 z-10"></div>
@ -596,7 +631,14 @@
href="index.html"
class="flex items-center gap-3 mb-8 hover:opacity-80 transition-opacity justify-center"
>
<img src="assets/images/kh3_logo.png" alt="KH3" class="w-16 h-16" />
<img
src="assets/images/optimized/kh3_logo_optimized.png"
alt="KH3"
class="w-16 h-16"
loading="lazy"
width="64"
height="64"
/>
</a>
<div class="w-64 h-1 bg-neutral-800 rounded-full overflow-hidden">
<div class="h-full bg-kh3-red rounded-full animate-pulse"></div>
@ -854,381 +896,14 @@
}
</style>
<script src="js/main.js"></script>
<script src="js/main.js" defer></script>
<script src="js/hero-animations.js" defer></script>
<script src="js/page-transitions.js" defer></script>
<script src="js/menu-animations.js" defer></script>
<script src="js/loading-screen.js" defer></script>
<script>
// Top/Bottom curtain transition system
const curtainTop =
document.getElementById("pageCurtainTop") ||
(() => {
const div = document.createElement("div");
div.id = "pageCurtainTop";
div.className =
"fixed inset-x-0 top-0 h-1/2 z-50 transform -translate-y-full transition-transform duration-600 ease-in-out";
div.style.background = "linear-gradient(0deg, #b03037, #8a252a)";
document.body.appendChild(div);
return div;
})();
const curtainBottom =
document.getElementById("pageCurtainBottom") ||
(() => {
const div = document.createElement("div");
div.id = "pageCurtainBottom";
div.className =
"fixed inset-x-0 bottom-0 h-1/2 z-50 transform translate-y-full transition-transform duration-600 ease-in-out";
div.style.background = "linear-gradient(180deg, #b03037, #8a252a)";
document.body.appendChild(div);
return div;
})();
// Add entrance animation class
document.body.style.animation = "pageEnter 0.6s ease-out";
document.querySelectorAll("a[data-trans]").forEach((a) => {
a.addEventListener("click", (e) => {
const href = a.getAttribute("href");
if (!href || href.startsWith("#")) return;
e.preventDefault();
// Collect elements that had delayed entrance and fade them out in reverse order
const animatedEls = Array.from(
document.querySelectorAll('[style*="animation-delay"]')
).map((el) => {
const m = (el.getAttribute("style") || "").match(
/animation-delay:\s*([0-9.]+)s/
);
const delay = m ? parseFloat(m[1]) : 0;
return { el, delay };
});
const sorted = animatedEls
.sort((a, b) => b.delay - a.delay)
.map((item, idx) => ({ ...item, order: idx }));
sorted.forEach((item) => {
setTimeout(() => {
item.el.style.animation =
"staggerFadeOut 260ms ease-out forwards";
}, item.order * 50);
});
const totalStaggerTime = (sorted.length - 1) * 50 + 260 + 130;
setTimeout(() => {
// Crossfade + tiny zoom of the whole page (longer)
document.body.style.animation = "pageExit 900ms ease forwards";
setTimeout(() => {
location.href = href;
}, 920);
}, totalStaggerTime);
});
});
// Hide curtains and close nav just before page is cached (prevents blank back step)
window.addEventListener("pagehide", () => {
if (curtainTop) curtainTop.style.transform = "translateY(-100%)";
if (curtainBottom) curtainBottom.style.transform = "translateY(100%)";
const navEl = document.getElementById("navMenu");
const gridEl = document.getElementById("menuGrid");
if (navEl) navEl.classList.add("hidden");
const wrapEl = document.getElementById("menuWrap");
if (wrapEl) wrapEl.classList.remove("open");
});
// Enhanced slide + zoom hero image transitions
const imgs = [
"assets/images/room.png",
"assets/images/chair.png",
"assets/images/google.png",
];
const currentImg = document.getElementById("currentImg");
const nextImg = document.getElementById("nextImg");
if (currentImg && nextImg) {
let currentIndex = 0;
let isTransitioning = false;
// Alternate slide directions for dynamic effect
const slideDirections = ["left", "right"];
let slideIndex = 0;
function performSlideZoomTransition() {
if (isTransitioning) return;
isTransitioning = true;
// Get next image index
const nextIndex = (currentIndex + 1) % imgs.length;
const slideDirection =
slideDirections[slideIndex % slideDirections.length];
slideIndex++;
// Set up next image
nextImg.src = imgs[nextIndex];
nextImg.style.opacity = "1";
// Apply animations based on slide direction
if (slideDirection === "left") {
// Current slides out left, next zooms in from right
currentImg.style.animation = "slideOutLeft 1s ease-in-out forwards";
nextImg.style.animation = "zoomInFromRight 1s ease-in-out forwards";
} else {
// Current slides out right, next zooms in from left
currentImg.style.animation =
"slideOutRight 1s ease-in-out forwards";
nextImg.style.animation = "zoomInFromLeft 1s ease-in-out forwards";
}
// After animation completes, swap the images
setTimeout(() => {
// Swap the images
currentImg.src = imgs[nextIndex];
currentImg.style.animation = "";
currentImg.style.transform = "";
currentImg.style.opacity = "1";
// Reset next image
nextImg.style.animation = "";
nextImg.style.transform = "";
nextImg.style.opacity = "0";
currentIndex = nextIndex;
isTransitioning = false;
}, 1000); // Match animation duration
}
// Start the transition cycle
setInterval(performSlideZoomTransition, 10000); // Every 10 seconds for better viewing
}
// Menu toggle functionality
const menuToggle = document.getElementById("menuToggle");
const navMenu = document.getElementById("navMenu");
const menuWrap = document.getElementById("menuWrap");
const menuGrid = document.getElementById("menuGrid");
const menuGridMobile = document.getElementById("menuGridMobile");
if (menuToggle && navMenu) {
let isMenuOpen = false;
menuToggle.addEventListener("click", () => {
if (isMenuOpen) {
// Close menu
navMenu.classList.add("hidden");
if (menuWrap) menuWrap.classList.remove("open");
isMenuOpen = false;
} else {
// Open menu
navMenu.classList.remove("hidden");
if (menuWrap) menuWrap.classList.add("open");
isMenuOpen = true;
}
});
// Close menu when clicking outside
document.addEventListener("click", (e) => {
if (!menuToggle.contains(e.target) && !navMenu.contains(e.target)) {
navMenu.classList.add("hidden");
if (menuWrap) menuWrap.classList.remove("open");
isMenuOpen = false;
}
});
}
// Page transition functionality
function handlePageTransition(e) {
e.preventDefault();
const targetUrl = e.currentTarget.href;
const transitionType =
e.currentTarget.getAttribute("data-trans") || "crossfade";
// Get all elements to animate out
const elementsToAnimate = [
document.querySelector(".hero-section"),
document.querySelector(".dream-design-deliver"),
document.querySelector("main"),
].filter((el) => el);
// Animate elements out in order
elementsToAnimate.forEach((element, index) => {
setTimeout(() => {
if (element) {
element.style.animation =
"staggerFadeOut 0.6s ease-in-out forwards";
}
}, index * 100);
});
// Navigate after animations complete
setTimeout(() => {
window.location.href = targetUrl;
}, elementsToAnimate.length * 100 + 600);
}
// Add event listeners for page transitions
document.addEventListener("DOMContentLoaded", function () {
const transitionLinks = document.querySelectorAll("[data-trans]");
transitionLinks.forEach((link) => {
link.addEventListener("click", handlePageTransition);
});
});
// Handle back/forward navigation
window.addEventListener("pageshow", function (event) {
if (event.persisted) {
// Page was loaded from cache (back/forward)
window.location.reload();
}
});
// Loading screen with improved timing
window.addEventListener("load", () => {
const loadingScreen = document.getElementById("loadingScreen");
if (loadingScreen) {
// Show loading screen briefly, then fade out just before animations start
setTimeout(() => {
loadingScreen.style.transition = "opacity 0.3s ease-out";
loadingScreen.style.opacity = "0";
setTimeout(() => {
loadingScreen.style.display = "none";
}, 300);
}, 400); // Reduced from 1000ms to 400ms to sync with animations
}
});
// Ensure back/forward navigation (bfcache) shows a clean page
window.addEventListener("pageshow", () => {
const loadingScreen = document.getElementById("loadingScreen");
if (loadingScreen) {
loadingScreen.style.display = "none";
loadingScreen.style.opacity = "0";
}
const ct = document.getElementById("pageCurtainTop");
const cb = document.getElementById("pageCurtainBottom");
if (ct) ct.style.transform = "translateY(-100%)";
if (cb) cb.style.transform = "translateY(100%)";
// Close nav if it was left open and reset grid icon
const navMenuEl = document.getElementById("navMenu");
const wrapEl2 = document.getElementById("menuWrap");
if (navMenuEl) navMenuEl.classList.add("hidden");
if (wrapEl2) wrapEl2.classList.remove("open");
});
// Menu grid animation sequence
function startMenuGridAnimation() {
const menuGrid = document.getElementById("menuGrid");
if (!menuGrid) return;
// Remove CSS animation to prevent conflicts
menuGrid.style.animation = "none";
let phase = 0;
let isEnlarged = false;
let isSpinning = true;
let isResting = false;
const animationInterval = setInterval(() => {
phase += 0.02;
if (isSpinning) {
// Spinning animation
const rotation = phase * 360;
const floatY = Math.sin(phase * 2) * 6;
const rippleScale = 1 + Math.sin(phase * 3) * 0.08;
const sizeScale = isEnlarged ? 1.5 : 1;
menuGrid.style.transform = `rotate(${rotation}deg) scale(${
rippleScale * sizeScale
}) translateY(${floatY}px)`;
// Handle color changes
const spans = menuGrid.querySelectorAll("span");
spans.forEach((span) => {
span.style.backgroundColor = isEnlarged ? "#B03037" : "#ffffff";
});
} else if (isResting) {
// Resting animation - just floating with upward ripple effect
const floatY = Math.sin(phase * 1.5) * 8;
const rippleScale = 1 + Math.sin(phase * 2) * 0.1;
menuGrid.style.transform = `scale(${rippleScale}) translateY(${floatY}px)`;
// Keep white color during rest
const spans = menuGrid.querySelectorAll("span");
spans.forEach((span) => {
span.style.backgroundColor = "#ffffff";
});
}
}, 50);
function startCycle() {
// Step 1: Default spinning for 7 seconds
isSpinning = true;
isEnlarged = false;
isResting = false;
// Step 2: After 7 seconds, change to red and bigger, spin once
setTimeout(() => {
isEnlarged = true;
// After one spin (3 seconds), go to rest
setTimeout(() => {
isEnlarged = false;
isSpinning = false;
isResting = true;
// Step 3: Rest for 7 seconds (just floating)
setTimeout(() => {
// Step 4: Start spinning again (repeat cycle)
startCycle();
}, 7000);
}, 3000);
}, 7000);
}
// Start the cycle
startCycle();
}
// Menu button animations disabled on homepage per request
// Custom cursor functionality
const customCursor = document.getElementById("customCursor");
// Hide default cursor
document.body.style.cursor = "none";
// Follow mouse movement
document.addEventListener("mousemove", (e) => {
customCursor.style.left = e.clientX - 12 + "px";
customCursor.style.top = e.clientY - 12 + "px";
});
// Menu grid hover effects
if (menuGrid) {
menuGrid.addEventListener("mouseenter", () => {
customCursor.style.borderColor = "#B03037";
customCursor.style.transform = "scale(1.5)";
customCursor.style.animation = "spin 0.5s linear infinite";
});
menuGrid.addEventListener("mouseleave", () => {
customCursor.style.borderColor = "#B03037";
customCursor.style.transform = "scale(1)";
customCursor.style.animation = "none";
});
}
// Add spin animation for cursor
const style = document.createElement("style");
style.textContent = `
@keyframes spin {
from { transform: scale(1.5) rotate(0deg); }
to { transform: scale(1.5) rotate(360deg); }
}
`;
document.head.appendChild(style);
// Minimal inline script for critical functionality
console.log("KH3 Website loaded successfully");
</script>
</body>
</html>

67
js/hero-animations.js Normal file
View file

@ -0,0 +1,67 @@
// Hero image transitions and animations
document.addEventListener("DOMContentLoaded", function () {
// Enhanced slide + zoom hero image transitions
const imgs = [
"assets/images/optimized/room_optimized.jpg",
"assets/images/optimized/chair_optimized.jpg",
"assets/images/optimized/google_optimized.jpg",
];
const currentImg = document.getElementById("currentImg");
const nextImg = document.getElementById("nextImg");
if (currentImg && nextImg) {
let currentIndex = 0;
let isTransitioning = false;
// Alternate slide directions for dynamic effect
const slideDirections = ["left", "right"];
let slideIndex = 0;
function performSlideZoomTransition() {
if (isTransitioning) return;
isTransitioning = true;
// Get next image index
const nextIndex = (currentIndex + 1) % imgs.length;
const slideDirection =
slideDirections[slideIndex % slideDirections.length];
slideIndex++;
// Set up next image
nextImg.src = imgs[nextIndex];
nextImg.style.opacity = "1";
// Apply animations based on slide direction
if (slideDirection === "left") {
// Current slides out left, next zooms in from right
currentImg.style.animation = "slideOutLeft 1s ease-in-out forwards";
nextImg.style.animation = "zoomInFromRight 1s ease-in-out forwards";
} else {
// Current slides out right, next zooms in from left
currentImg.style.animation = "slideOutRight 1s ease-in-out forwards";
nextImg.style.animation = "zoomInFromLeft 1s ease-in-out forwards";
}
// After animation completes, swap the images
setTimeout(() => {
// Swap the images
currentImg.src = imgs[nextIndex];
currentImg.style.animation = "";
currentImg.style.transform = "";
currentImg.style.opacity = "1";
// Reset next image
nextImg.style.animation = "";
nextImg.style.transform = "";
nextImg.style.opacity = "0";
currentIndex = nextIndex;
isTransitioning = false;
}, 1000); // Match animation duration
}
// Start the transition cycle
setInterval(performSlideZoomTransition, 10000); // Every 10 seconds for better viewing
}
});

17
js/loading-screen.js Normal file
View file

@ -0,0 +1,17 @@
// Loading screen functionality
document.addEventListener("DOMContentLoaded", function () {
// Loading screen with improved timing
window.addEventListener("load", () => {
const loadingScreen = document.getElementById("loadingScreen");
if (loadingScreen) {
// Show loading screen briefly, then fade out just before animations start
setTimeout(() => {
loadingScreen.style.transition = "opacity 0.3s ease-out";
loadingScreen.style.opacity = "0";
setTimeout(() => {
loadingScreen.style.display = "none";
}, 300);
}, 400); // Reduced from 1000ms to 400ms to sync with animations
}
});
});

74
js/menu-animations.js Normal file
View file

@ -0,0 +1,74 @@
// Menu toggle functionality and animations
document.addEventListener("DOMContentLoaded", function () {
const menuToggle = document.getElementById("menuToggle");
const navMenu = document.getElementById("navMenu");
const menuWrap = document.getElementById("menuWrap");
const menuGrid = document.getElementById("menuGrid");
const menuGridMobile = document.getElementById("menuGridMobile");
if (menuToggle && navMenu) {
let isMenuOpen = false;
menuToggle.addEventListener("click", () => {
if (isMenuOpen) {
// Close menu
navMenu.classList.add("hidden");
if (menuWrap) menuWrap.classList.remove("open");
isMenuOpen = false;
} else {
// Open menu
navMenu.classList.remove("hidden");
if (menuWrap) menuWrap.classList.add("open");
isMenuOpen = true;
}
});
// Close menu when clicking outside
document.addEventListener("click", (e) => {
if (!menuToggle.contains(e.target) && !navMenu.contains(e.target)) {
navMenu.classList.add("hidden");
if (menuWrap) menuWrap.classList.remove("open");
isMenuOpen = false;
}
});
}
// Custom cursor functionality
const customCursor = document.getElementById("customCursor");
if (customCursor) {
// Hide default cursor
document.body.style.cursor = "none";
// Follow mouse movement
document.addEventListener("mousemove", (e) => {
customCursor.style.left = e.clientX - 12 + "px";
customCursor.style.top = e.clientY - 12 + "px";
});
// Menu grid hover effects
if (menuGrid) {
menuGrid.addEventListener("mouseenter", () => {
customCursor.style.borderColor = "#B03037";
customCursor.style.transform = "scale(1.5)";
customCursor.style.animation = "spin 0.5s linear infinite";
});
menuGrid.addEventListener("mouseleave", () => {
customCursor.style.borderColor = "#B03037";
customCursor.style.transform = "scale(1)";
customCursor.style.animation = "none";
});
}
}
// Add spin animation for cursor
const style = document.createElement("style");
style.textContent = `
@keyframes spin {
from { transform: scale(1.5) rotate(0deg); }
to { transform: scale(1.5) rotate(360deg); }
}
`;
document.head.appendChild(style);
});

107
js/page-transitions.js Normal file
View file

@ -0,0 +1,107 @@
// Page transition functionality
document.addEventListener("DOMContentLoaded", function () {
// Top/Bottom curtain transition system
const curtainTop =
document.getElementById("pageCurtainTop") ||
(() => {
const div = document.createElement("div");
div.id = "pageCurtainTop";
div.className =
"fixed inset-x-0 top-0 h-1/2 z-50 transform -translate-y-full transition-transform duration-600 ease-in-out";
div.style.background = "linear-gradient(0deg, #b03037, #8a252a)";
document.body.appendChild(div);
return div;
})();
const curtainBottom =
document.getElementById("pageCurtainBottom") ||
(() => {
const div = document.createElement("div");
div.id = "pageCurtainBottom";
div.className =
"fixed inset-x-0 bottom-0 h-1/2 z-50 transform translate-y-full transition-transform duration-600 ease-in-out";
div.style.background = "linear-gradient(180deg, #b03037, #8a252a)";
document.body.appendChild(div);
return div;
})();
// Add entrance animation class
document.body.style.animation = "pageEnter 0.6s ease-out";
document.querySelectorAll("a[data-trans]").forEach((a) => {
a.addEventListener("click", (e) => {
const href = a.getAttribute("href");
if (!href || href.startsWith("#")) return;
e.preventDefault();
// Collect elements that had delayed entrance and fade them out in reverse order
const animatedEls = Array.from(
document.querySelectorAll('[style*="animation-delay"]')
).map((el) => {
const m = (el.getAttribute("style") || "").match(
/animation-delay:\s*([0-9.]+)s/
);
const delay = m ? parseFloat(m[1]) : 0;
return { el, delay };
});
const sorted = animatedEls
.sort((a, b) => b.delay - a.delay)
.map((item, idx) => ({ ...item, order: idx }));
sorted.forEach((item) => {
setTimeout(() => {
item.el.style.animation = "staggerFadeOut 260ms ease-out forwards";
}, item.order * 50);
});
const totalStaggerTime = (sorted.length - 1) * 50 + 260 + 130;
setTimeout(() => {
// Crossfade + tiny zoom of the whole page (longer)
document.body.style.animation = "pageExit 900ms ease forwards";
setTimeout(() => {
location.href = href;
}, 920);
}, totalStaggerTime);
});
});
// Hide curtains and close nav just before page is cached (prevents blank back step)
window.addEventListener("pagehide", () => {
if (curtainTop) curtainTop.style.transform = "translateY(-100%)";
if (curtainBottom) curtainBottom.style.transform = "translateY(100%)";
const navEl = document.getElementById("navMenu");
const gridEl = document.getElementById("menuGrid");
if (navEl) navEl.classList.add("hidden");
const wrapEl = document.getElementById("menuWrap");
if (wrapEl) wrapEl.classList.remove("open");
});
// Handle back/forward navigation
window.addEventListener("pageshow", function (event) {
if (event.persisted) {
// Page was loaded from cache (back/forward)
window.location.reload();
}
});
// Ensure back/forward navigation (bfcache) shows a clean page
window.addEventListener("pageshow", () => {
const loadingScreen = document.getElementById("loadingScreen");
if (loadingScreen) {
loadingScreen.style.display = "none";
loadingScreen.style.opacity = "0";
}
const ct = document.getElementById("pageCurtainTop");
const cb = document.getElementById("pageCurtainBottom");
if (ct) ct.style.transform = "translateY(-100%)";
if (cb) cb.style.transform = "translateY(100%)";
// Close nav if it was left open and reset grid icon
const navMenuEl = document.getElementById("navMenu");
const wrapEl2 = document.getElementById("menuWrap");
if (navMenuEl) navMenuEl.classList.add("hidden");
if (wrapEl2) wrapEl2.classList.remove("open");
});
});

View file

@ -4,7 +4,8 @@
"description": "KH3 - Building Inspiring Spaces",
"main": "index.html",
"scripts": {
"build": "tailwindcss -i ./src/input.css -o ./styles/main.css",
"build": "tailwindcss -i ./src/input.css -o ./styles/main.css --minify",
"build-dev": "tailwindcss -i ./src/input.css -o ./styles/main.css",
"watch": "tailwindcss -i ./src/input.css -o ./styles/main.css --watch",
"dev": "tailwindcss -i ./src/input.css -o ./styles/main.css --watch"
},

View file

@ -2,6 +2,7 @@
module.exports = {
content: ["./*.html", "./js/*.js"],
darkMode: "class",
mode: "jit",
theme: {
extend: {
colors: {