<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KJS - Charity & Elite Badminton</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=IBM+Plex+Mono:ital,wght@0,400;0,700;0,900;1,400&display=swap');
* {
font-family: 'Inter', sans-serif;
cursor: none;
}
.font-mono-kjs { font-family: 'IBM Plex Mono', monospace; }
:root {
--orange: #FF8C00;
--dark: #050505;
}
body::-webkit-scrollbar { display: none; }
body {
-ms-overflow-style: none;
scrollbar-width: none;
background-color: var(--dark);
color: #fff;
overscroll-behavior: none;
overflow: hidden;
}
/* NOISE OVERLAY */
.noise-bg {
position: fixed;
inset: 0;
z-index: 9998;
opacity: 0.04;
pointer-events: none;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
}
/* CUSTOM CURSOR */
#custom-cursor {
position: fixed;
top: 0; left: 0;
width: 15px; height: 15px;
background: #FF8C00;
border-radius: 50%;
pointer-events: none;
z-index: 9999;
mix-blend-mode: normal;
transform: translate(-50%, -50%);
transition: width 0.3s cubic-bezier(0.16, 1, 0.3, 1), height 0.3s cubic-bezier(0.16, 1, 0.3, 1), background 0.3s ease;
will-change: transform, width, height;
}
body.hovering #custom-cursor {
width: 60px;
height: 60px;
background: white;
mix-blend-mode: difference;
opacity: 1;
}
/* PRELOADER */
#preloader {
position: fixed;
inset: 0;
background: var(--dark);
z-index: 10000;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
transition: transform 1.2s cubic-bezier(0.7, 0, 0.3, 1);
}
#preloader.hide {
transform: translateY(-100%);
}
/* SMOOTH SCROLL WRAPPERS */
#smooth-wrapper {
position: fixed;
top: 0; left: 0;
width: 100vw; height: 100vh;
overflow: hidden;
pointer-events: none;
z-index: 0;
}
#smooth-content {
width: 100%;
will-change: transform;
pointer-events: auto;
}
/* Animasi Load */
.reveal-up {
opacity: 0;
transform: translateY(40px);
transition: all 1.2s cubic-bezier(0.16, 1, 0.3, 1);
}
.reveal-up.active {
opacity: 1;
transform: translateY(0);
}
.magnetic { display: inline-block; }
/* FLOATING IMAGE REVEAL (EVENTS) */
#hover-preview {
position: fixed;
top: 0; left: 0;
width: 320px; height: 220px;
background-size: cover;
background-position: center;
pointer-events: none;
z-index: 9000;
opacity: 0;
transform: scale(0.8) rotate(0deg);
border-radius: 12px;
transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform, opacity;
box-shadow: 0 30px 60px rgba(0,0,0,0.5);
}
#hover-preview.show {
opacity: 1;
transform: scale(1) rotate(4deg);
}
</style>
</head>
<body>
<div id="preloader">
<img src="KJS.png" alt="Logo KJS" class="h-16 md:h-24 w-auto mb-8 drop-shadow-2xl">
<div class="font-mono-kjs text-sm tracking-[0.4em] text-[#FF8C00] mb-4">LOADING KJS EXPERIENCE</div>
<div id="loader-counter" class="font-mono-kjs font-black text-8xl md:text-9xl text-white">0%</div>
</div>
<div class="noise-bg"></div>
<div id="custom-cursor"></div>
<div id="hover-preview"></div>
<nav class="fixed top-0 left-0 right-0 z-[100] flex items-center justify-between p-6 text-white mix-blend-difference">
<div class="hover-target magnetic cursor-pointer" onclick="scrollToId('hero')">
<img src="KJS.png" alt="Logo KJS" class="h-8 md:h-10 w-auto inline-block pointer-events-none">
</div>
<div class="hidden md:flex bg-white/10 backdrop-blur-md border border-white/20 rounded-full px-2 py-2 items-center gap-2">
<button onclick="scrollToId('vision')" class="hover-target text-[#FF8C00] px-5 py-2 rounded-full text-sm font-bold font-mono-kjs hover:bg-white/10 transition">Vision</button>
<button onclick="scrollToId('squad')" class="hover-target text-white px-5 py-2 rounded-full text-sm font-medium transition hover:bg-white/20">Squad</button>
<button onclick="scrollToId('events')" class="hover-target text-white px-5 py-2 rounded-full text-sm font-medium transition hover:bg-white/20">Events</button>
</div>
<button class="hover-target magnetic bg-white text-black font-mono-kjs font-bold text-sm px-6 py-2.5 rounded-full hover:bg-[#FF8C00] hover:text-white transition-colors">
<span class="inline-block">JOIN US</span>
</button>
</nav>
<div id="smooth-wrapper">
<div id="smooth-content">
<section id="hero" class="relative w-full h-screen overflow-hidden bg-black flex items-center justify-center pt-20">
<div class="absolute inset-0 bg-center bg-cover bg-no-repeat opacity-40 scale-105 transition-transform duration-[3s]" id="hero-img-1"
style="background-image: url('https://images.unsplash.com/photo-1611095567219-8157778b1973?q=80&w=1920&auto=format&fit=crop');">
</div>
<div id="reveal-layer" class="absolute inset-0 bg-center bg-cover bg-no-repeat z-30 pointer-events-none"
style="background-image: url('test.webp'); mask-size: 100% 100%; -webkit-mask-size: 100% 100%;">
</div>
<canvas id="mask-canvas" class="absolute inset-0 pointer-events-none z-20" style="display: none;"></canvas>
<div class="relative z-40 text-center pointer-events-none w-full px-4 flex justify-center items-center">
<img src="FULL.png" alt="Kencana Jingga Society" class="w-[70vw] md:w-[35vw] drop-shadow-[0_20px_50px_rgba(0,0,0,0.5)] reveal-up delay-100">
</div>
</section>
<div class="w-full bg-[#FF8C00] text-black py-5 overflow-hidden relative z-50 border-y border-[#FF8C00]/50">
<div class="flex whitespace-nowrap font-mono-kjs font-black text-5xl uppercase" id="marquee">
<span class="px-8">PLAY FOR FREE</span> ❋
<span class="px-8">GIVE FOR A CAUSE</span> ❋
<span class="px-8">MALANG CITY</span> ❋
<span class="px-8">KJS COLLECTIVE</span> ❋
<span class="px-8">PLAY FOR FREE</span> ❋
<span class="px-8">GIVE FOR A CAUSE</span> ❋
<span class="px-8">MALANG CITY</span> ❋
<span class="px-8">KJS COLLECTIVE</span>
</div>
</div>
<section id="vision" class="relative w-full min-h-screen bg-[#050505] flex items-center py-32 px-6 md:px-20 overflow-hidden">
<div class="absolute inset-0 opacity-10" style="background-image: radial-gradient(circle at right, #FF8C00 0%, transparent 50%);" data-speed="0.2"></div>
<div class="max-w-6xl mx-auto z-10 w-full">
<div class="text-[#FF8C00] font-mono-kjs font-bold tracking-[0.2em] mb-8 reveal-up">OUR VISION</div>
<h2 class="text-white font-medium text-4xl md:text-5xl lg:text-6xl leading-tight reveal-up delay-100">
Every match means more than the final score. Through <span class="text-[#FF8C00] font-mono-kjs italic">free-to-enter</span> tournaments, we bring people together and use the power of sport to <span class="text-white font-mono-kjs italic underline decoration-[#FF8C00]">support those who need it most.</span>
</h2>
<p class="mt-8 text-white/50 text-xl max-w-3xl reveal-up delay-150">
Zero entry fees. 100% voluntary donations. You play your heart out on the court, and if you choose to, you donate to the charities we partner with. That's the KJS way.
</p>
<div class="mt-16 grid grid-cols-1 md:grid-cols-3 gap-12 reveal-up delay-200 border-t border-white/10 pt-12">
<div>
<div class="text-7xl font-mono-kjs font-black text-[#FF8C00]">20</div>
<div class="text-sm text-white/50 tracking-widest mt-2 uppercase font-bold">Members</div>
</div>
<div>
<div class="text-7xl font-mono-kjs font-black text-[#FF8C00]">10</div>
<div class="text-sm text-white/50 tracking-widest mt-2 uppercase font-bold">Matches Played</div>
</div>
<div>
<div class="text-7xl font-mono-kjs font-black text-[#FF8C00]">1</div>
<div class="text-sm text-white/50 tracking-widest mt-2 uppercase font-bold">Tour Held</div>
</div>
</div>
</div>
</section>
<section id="squad" class="relative w-full min-h-screen bg-[#0a0a0a] px-6 md:px-20 py-32 border-t border-white/5">
<div class="text-center mb-24 reveal-up">
<h2 class="font-mono-kjs font-black text-6xl text-white mb-4">THE CREW.</h2>
<p class="text-xl text-white/50">The minds behind the movement.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<a href="https://www.instagram.com/rifqyprdya/" target="_blank" class="block hover-target js-tilt w-full h-[500px] bg-[#111] rounded-2xl overflow-hidden relative cursor-pointer group reveal-up" style="transform-style: preserve-3d;" data-speed="0.1">
<img src="rifqy.webp" class="absolute inset-0 w-full h-full object-cover opacity-50 grayscale group-hover:grayscale-0 group-hover:scale-110 transition-all duration-700">
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/40 to-transparent"></div>
<div class="absolute bottom-8 left-8 z-20 pointer-events-none" style="transform: translateZ(50px);">
<h3 class="font-mono-kjs font-black text-4xl text-white mb-1 group-hover:text-[#FF8C00] transition-colors">RIFQY</h3>
<p class="text-[#FF8C00] font-bold tracking-widest uppercase text-sm">Head of KJS</p>
</div>
</a>
<a href="https://www.instagram.com/farrrelarya/" target="_blank" class="block hover-target js-tilt w-full h-[500px] bg-[#111] rounded-2xl overflow-hidden relative cursor-pointer group reveal-up delay-100" style="transform-style: preserve-3d;" data-speed="0.3">
<img src="ayel.webp" class="absolute inset-0 w-full h-full object-cover opacity-50 grayscale group-hover:grayscale-0 group-hover:scale-110 transition-all duration-700">
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/40 to-transparent"></div>
<div class="absolute bottom-8 left-8 z-20 pointer-events-none" style="transform: translateZ(50px);">
<h3 class="font-mono-kjs font-black text-3xl lg:text-4xl text-white mb-1 group-hover:text-[#FF8C00] transition-colors leading-tight">FARREL ICY APPLE</h3>
<p class="text-[#FF8C00] font-bold tracking-widest uppercase text-sm">Vice Head</p>
</div>
</a>
<a href="https://www.instagram.com/fatyaazhraa/" target="_blank" class="block hover-target js-tilt w-full h-[500px] bg-[#111] rounded-2xl overflow-hidden relative cursor-pointer group reveal-up delay-200" style="transform-style: preserve-3d;" data-speed="0.15">
<img src="aya.webp" class="absolute inset-0 w-full h-full object-cover opacity-50 grayscale group-hover:grayscale-0 group-hover:scale-110 transition-all duration-700">
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/40 to-transparent"></div>
<div class="absolute bottom-8 left-8 z-20 pointer-events-none" style="transform: translateZ(50px);">
<h3 class="font-mono-kjs font-black text-4xl text-white mb-1 group-hover:text-[#FF8C00] transition-colors">AYA</h3>
<p class="text-[#FF8C00] font-bold tracking-widest uppercase text-sm">Secretary</p>
</div>
</a>
<a href="https://www.instagram.com/salsabilasalda/" target="_blank" class="block hover-target js-tilt w-full h-[500px] bg-[#111] rounded-2xl overflow-hidden relative cursor-pointer group reveal-up delay-300" style="transform-style: preserve-3d;" data-speed="0.25">
<img src="salsa.webp" class="absolute inset-0 w-full h-full object-cover opacity-50 grayscale group-hover:grayscale-0 group-hover:scale-110 transition-all duration-700">
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/40 to-transparent"></div>
<div class="absolute bottom-8 left-8 z-20 pointer-events-none" style="transform: translateZ(50px);">
<h3 class="font-mono-kjs font-black text-4xl text-white mb-1 group-hover:text-[#FF8C00] transition-colors">SALSA</h3>
<p class="text-[#FF8C00] font-bold tracking-widest uppercase text-sm">Head of Event</p>
</div>
</a>
<a href="https://www.instagram.com/naysillartha/" target="_blank" class="block hover-target js-tilt w-full h-[500px] bg-[#111] rounded-2xl overflow-hidden relative cursor-pointer group reveal-up delay-400" style="transform-style: preserve-3d;" data-speed="0.1">
<img src="nai.webp" class="absolute inset-0 w-full h-full object-cover opacity-50 grayscale group-hover:grayscale-0 group-hover:scale-110 transition-all duration-700">
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/40 to-transparent"></div>
<div class="absolute bottom-8 left-8 z-20 pointer-events-none" style="transform: translateZ(50px);">
<h3 class="font-mono-kjs font-black text-4xl text-white mb-1 group-hover:text-[#FF8C00] transition-colors">NAI</h3>
<p class="text-[#FF8C00] font-bold tracking-widest uppercase text-sm">Creative Head</p>
</div>
</a>
<a href="https://www.instagram.com/afilardho/" target="_blank" class="block hover-target js-tilt w-full h-[500px] bg-[#111] rounded-2xl overflow-hidden relative cursor-pointer group reveal-up delay-500" style="transform-style: preserve-3d;" data-speed="0.2">
<img src="aldo.webp" class="absolute inset-0 w-full h-full object-cover opacity-50 grayscale group-hover:grayscale-0 group-hover:scale-110 transition-all duration-700">
<div class="absolute inset-0 bg-gradient-to-t from-black via-black/40 to-transparent"></div>
<div class="absolute bottom-8 left-8 z-20 pointer-events-none" style="transform: translateZ(50px);">
<h3 class="font-mono-kjs font-black text-4xl text-white mb-1 group-hover:text-[#FF8C00] transition-colors">ALDO</h3>
<p class="text-[#FF8C00] font-bold tracking-widest uppercase text-sm">Head of PR</p>
</div>
</a>
</div>
</section>
<section id="events" class="relative w-full min-h-screen bg-[#050505] py-32 px-6 md:px-20 border-t border-white/5">
<div class="border-b border-white/20 pb-8 mb-16 reveal-up">
<h2 class="font-mono-kjs font-black text-6xl text-[#FF8C00]">TOURNAMENTS</h2>
</div>
<div class="flex flex-col w-full">
<div class="event-row hover-target group border-b border-white/10 py-12 flex flex-col md:flex-row justify-between md:items-center cursor-pointer transition-colors hover:bg-white/5 px-6 reveal-up"
data-img="batam.webp">
<div class="text-4xl md:text-6xl font-mono-kjs font-bold text-white/40 group-hover:text-white group-hover:translate-x-4 transition-all duration-500">KJS GOES TO BATAM</div>
<div class="text-[#FF8C00] font-mono-kjs mt-4 md:mt-0 tracking-widest text-xl group-hover:-translate-x-4 transition-transform duration-500">JULY 2026</div>
</div>
<div class="event-row hover-target group border-b border-white/10 py-12 flex flex-col md:flex-row justify-between md:items-center cursor-pointer transition-colors hover:bg-white/5 px-6 reveal-up"
data-img="agustus.webp">
<div class="text-4xl md:text-6xl font-mono-kjs font-bold text-white/40 group-hover:text-white group-hover:translate-x-4 transition-all duration-500">KJS AGUSTUSAN</div>
<div class="text-[#FF8C00] font-mono-kjs mt-4 md:mt-0 tracking-widest text-xl group-hover:-translate-x-4 transition-transform duration-500">AUGUST 2026</div>
</div>
<div class="event-row hover-target group border-b border-white/10 py-12 flex flex-col md:flex-row justify-between md:items-center cursor-pointer transition-colors hover:bg-white/5 px-6 reveal-up"
data-img="malang.webp">
<div class="flex items-center gap-4">
<div class="text-4xl md:text-6xl font-mono-kjs font-bold text-white/20 group-hover:text-white/80 group-hover:translate-x-4 transition-all duration-500">KJS BADMINTON VOL. 1</div>
<span class="border border-white/20 text-white/40 text-xs px-3 py-1 rounded-full group-hover:translate-x-4 transition-transform duration-500">COMPLETED</span>
</div>
<div class="text-white/30 font-mono-kjs mt-4 md:mt-0 tracking-widest text-xl group-hover:-translate-x-4 transition-transform duration-500">22 MAY 2026</div>
</div>
</div>
</section>
<footer class="bg-[#050505] py-24 px-6 md:px-20 border-t border-white/10 flex flex-col items-center justify-center">
<img src="KJS.png" alt="KJS Footer Logo" class="hover-target w-[50vw] md:w-[20vw] opacity-20 hover:opacity-100 transition-opacity duration-700 mb-6 cursor-pointer" onclick="scrollToId('hero')">
<p class="text-white/40 mt-2 text-sm tracking-[0.3em] font-mono-kjs uppercase text-center">MALANG BADMINTON CLUB</p>
</footer>
</div>
</div>
<script>
// 1. PRELOADER
const preloader = document.getElementById('preloader');
const counter = document.getElementById('loader-counter');
const heroImg1 = document.getElementById('hero-img-1');
let count = 0;
document.body.style.overflow = 'hidden';
let loaderInterval = setInterval(() => {
count += Math.floor(Math.random() * 12) + 1;
if(count >= 100) {
count = 100;
clearInterval(loaderInterval);
setTimeout(() => {
preloader.classList.add('hide');
document.body.style.overflow = 'auto';
heroImg1.style.transform = 'scale(1)';
document.querySelectorAll('.reveal-up').forEach((el, index) => {
setTimeout(() => el.classList.add('active'), 100 * index);
});
}, 500);
}
counter.innerText = count + '%';
}, 60);
// 2. CUSTOM CURSOR & MAGNETIC BTN
const cursor = document.getElementById('custom-cursor');
let cursorX = window.innerWidth / 2, cursorY = window.innerHeight / 2;
let smoothCursorX = cursorX, smoothCursorY = cursorY;
window.addEventListener('mousemove', (e) => {
cursorX = e.clientX;
cursorY = e.clientY;
});
document.querySelectorAll('.hover-target, button, a').forEach(el => {
el.addEventListener('mouseenter', () => document.body.classList.add('hovering'));
el.addEventListener('mouseleave', () => document.body.classList.remove('hovering'));
});
document.querySelectorAll('.magnetic').forEach(btn => {
const child = btn.querySelector('span') || btn.querySelector('img');
if(child) {
btn.addEventListener('mousemove', (e) => {
const rect = btn.getBoundingClientRect();
const x = e.clientX - rect.left - rect.width / 2;
const y = e.clientY - rect.top - rect.height / 2;
child.style.transform = `translate(${x * 0.4}px, ${y * 0.4}px)`;
});
btn.addEventListener('mouseleave', () => {
child.style.transform = `translate(0px, 0px)`;
child.style.transition = `transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275)`;
setTimeout(() => child.style.transition = '', 300);
});
}
});
// 3. LERP SCROLL
const body = document.body;
const smoothContent = document.getElementById('smooth-content');
const marquee = document.getElementById('marquee');
const parallaxEls = document.querySelectorAll('[data-speed]');
let currentScroll = 0;
let targetScroll = 0;
let ease = 0.08;
function setBodyHeight() {
body.style.height = `${smoothContent.getBoundingClientRect().height}px`;
}
window.addEventListener('resize', setBodyHeight);
setInterval(setBodyHeight, 1000);
window.addEventListener('scroll', () => {
targetScroll = window.scrollY;
});
function scrollToId(id) {
const section = document.getElementById(id);
if(section) {
window.scrollTo({
top: section.offsetTop,
behavior: 'smooth'
});
}
}
// 4. FLOATING EVENT IMAGES
const hoverPreview = document.getElementById('hover-preview');
let previewTargetX = 0, previewTargetY = 0;
let previewCurrentX = 0, previewCurrentY = 0;
let isPreviewVisible = false;
document.querySelectorAll('.event-row').forEach(row => {
row.addEventListener('mouseenter', (e) => {
hoverPreview.style.backgroundImage = `url('${row.dataset.img}')`;
hoverPreview.classList.add('show');
isPreviewVisible = true;
previewCurrentX = e.clientX + 20;
previewCurrentY = e.clientY - 100;
hoverPreview.style.transform = `translate(${previewCurrentX}px, ${previewCurrentY}px) scale(1) rotate(4deg)`;
});
row.addEventListener('mousemove', (e) => {
previewTargetX = e.clientX + 20;
previewTargetY = e.clientY - 100;
});
row.addEventListener('mouseleave', () => {
hoverPreview.classList.remove('show');
isPreviewVisible = false;
});
});
// 5. SPOTLIGHT HERO
const heroSection = document.getElementById('hero');
const canvas = document.getElementById('mask-canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const revealLayer = document.getElementById('reveal-layer');
const SPOTLIGHT_R = 250;
let maskMouseX = -999, maskMouseY = -999;
let smoothMaskX = -999, smoothMaskY = -999;
function resizeCanvas() {
canvas.width = heroSection.offsetWidth;
canvas.height = heroSection.offsetHeight;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
heroSection.addEventListener('mousemove', (e) => {
const rect = heroSection.getBoundingClientRect();
maskMouseX = e.clientX - rect.left;
maskMouseY = e.clientY - rect.top;
});
heroSection.addEventListener('mouseleave', () => {
maskMouseX = -999; maskMouseY = -999;
});
// 6. RENDER LOOP
function render() {
currentScroll += (targetScroll - currentScroll) * ease;
smoothContent.style.transform = `translate3d(0, -${currentScroll}px, 0)`;
parallaxEls.forEach(el => {
let speed = parseFloat(el.getAttribute('data-speed'));
let yPos = (targetScroll - currentScroll) * speed;
el.style.transform = `translate3d(0, ${yPos}px, 0)`;
});
marquee.style.transform = `translate3d(-${(currentScroll * 0.4) % 1000}px, 0, 0)`;
document.querySelectorAll('.reveal-up:not(.active)').forEach(el => {
const rect = el.getBoundingClientRect();
if(rect.top < window.innerHeight * 0.85) {
el.classList.add('active');
}
});
smoothCursorX += (cursorX - smoothCursorX) * 0.2;
smoothCursorY += (cursorY - smoothCursorY) * 0.2;
cursor.style.left = `${smoothCursorX}px`;
cursor.style.top = `${smoothCursorY}px`;
if(isPreviewVisible) {
previewCurrentX += (previewTargetX - previewCurrentX) * 0.1;
previewCurrentY += (previewTargetY - previewCurrentY) * 0.1;
hoverPreview.style.transform = `translate(${previewCurrentX}px, ${previewCurrentY}px) scale(1) rotate(4deg)`;
}
smoothMaskX += (maskMouseX - smoothMaskX) * 0.15;
smoothMaskY += (maskMouseY - smoothMaskY) * 0.15;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (Math.abs(smoothMaskX - maskMouseX) > 0.1 || maskMouseX !== -999) {
const gradient = ctx.createRadialGradient(smoothMaskX, smoothMaskY, 0, smoothMaskX, smoothMaskY, SPOTLIGHT_R);
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
gradient.addColorStop(0.3, 'rgba(255, 255, 255, 0.9)');
gradient.addColorStop(0.6, 'rgba(255, 255, 255, 0.5)');
gradient.addColorStop(0.8, 'rgba(255, 255, 255, 0.1)');
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(smoothMaskX, smoothMaskY, SPOTLIGHT_R, 0, Math.PI * 2);
ctx.fill();
const dataUrl = canvas.toDataURL();
revealLayer.style.maskImage = `url(${dataUrl})`;
revealLayer.style.webkitMaskImage = `url(${dataUrl})`;
}
requestAnimationFrame(render);
}
render();
// 7. 3D TILT
document.querySelectorAll('.js-tilt').forEach(card => {
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left - rect.width / 2;
const y = e.clientY - rect.top - rect.height / 2;
const rotateX = (y / (rect.height / 2)) * -12;
const rotateY = (x / (rect.width / 2)) * 12;
card.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.02, 1.02, 1.02)`;
card.style.zIndex = "50";
});
card.addEventListener('mouseleave', () => {
card.style.transition = 'transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
card.style.transform = `perspective(1000px) rotateX(0) rotateY(0) scale3d(1, 1, 1)`;
card.style.zIndex = "10";
setTimeout(() => card.style.transition = '', 500);
});
});
</script>
</body>
</html>