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:
parent
38db6e6744
commit
84eafb8746
15 changed files with 382 additions and 384 deletions
56
.htaccess
Normal file
56
.htaccess
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
BIN
assets/images/optimized/chair_optimized.jpg
Normal file
BIN
assets/images/optimized/chair_optimized.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 610 KiB |
BIN
assets/images/optimized/closed_petal_optimized.png
Normal file
BIN
assets/images/optimized/closed_petal_optimized.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/optimized/google_optimized.jpg
Normal file
BIN
assets/images/optimized/google_optimized.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 799 KiB |
BIN
assets/images/optimized/kh3_logo_optimized.png
Normal file
BIN
assets/images/optimized/kh3_logo_optimized.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/images/optimized/open_petal_optimized.png
Normal file
BIN
assets/images/optimized/open_petal_optimized.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 642 B |
BIN
assets/images/optimized/room_optimized.jpg
Normal file
BIN
assets/images/optimized/room_optimized.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 519 KiB |
437
index.html
437
index.html
|
|
@ -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
67
js/hero-animations.js
Normal 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
17
js/loading-screen.js
Normal 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
74
js/menu-animations.js
Normal 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
107
js/page-transitions.js
Normal 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");
|
||||
});
|
||||
});
|
||||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
module.exports = {
|
||||
content: ["./*.html", "./js/*.js"],
|
||||
darkMode: "class",
|
||||
mode: "jit",
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue