Rank_Noir / static /script.js
Juek's picture
Upload 5 files
ceb218a verified
Raw History Blame Contribute Delete
8.64 kB
/* ══════════════════════════════════════
RANKNOIR β€” script.js
TensorStax-style interactions
══════════════════════════════════════ */
// ── Canvas Background ──
(function() {
const canvas = document.getElementById('bgCanvas');
if (!canvas) return;
const ctx = canvas.getContext('2d');
let W, H, particles;
function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
function mkP() {
return {
x: Math.random() * W, y: Math.random() * H,
size: Math.random() * 1.2 + 0.3,
vx: (Math.random() - 0.5) * 0.25,
vy: (Math.random() - 0.5) * 0.25,
op: Math.random() * 0.5 + 0.1,
color: ['#5a4fcf','#3b7dd8','#e8532a','rgba(90,79,207,0.8)'][Math.floor(Math.random() * 4)]
};
}
function init() { particles = Array.from({length: 50}, mkP); }
function draw() {
ctx.clearRect(0, 0, W, H);
particles.forEach(p => {
ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = p.color; ctx.globalAlpha = p.op; ctx.fill(); ctx.globalAlpha = 1;
p.x += p.vx; p.y += p.vy;
if (p.x < 0) p.x = W; if (p.x > W) p.x = 0;
if (p.y < 0) p.y = H; if (p.y > H) p.y = 0;
});
// Subtle connections
for (let i = 0; i < particles.length; i++) {
for (let j = i+1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x, dy = particles[i].y - particles[j].y;
const d = Math.sqrt(dx*dx + dy*dy);
if (d < 110) {
ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = 'rgba(90,79,207,0.06)'; ctx.globalAlpha = (1 - d/110) * 0.25; ctx.lineWidth = 0.5; ctx.stroke(); ctx.globalAlpha = 1;
}
}
}
requestAnimationFrame(draw);
}
resize(); init(); draw();
window.addEventListener('resize', () => { resize(); init(); }, { passive: true });
})();
// ── Navbar ──
const navbar = document.getElementById('navbar');
window.addEventListener('scroll', () => {
navbar.classList.toggle('scrolled', window.scrollY > 40);
}, { passive: true });
// ── Hamburger ──
const hamburger = document.getElementById('hamburger');
const mobileMenu = document.getElementById('mobileMenu');
hamburger.addEventListener('click', () => {
const open = mobileMenu.classList.toggle('open');
const s = hamburger.querySelectorAll('span');
s[0].style.transform = open ? 'translateY(6.5px) rotate(45deg)' : '';
s[1].style.opacity = open ? '0' : '';
s[2].style.transform = open ? 'translateY(-6.5px) rotate(-45deg)' : '';
});
mobileMenu.querySelectorAll('a').forEach(a => a.addEventListener('click', () => {
mobileMenu.classList.remove('open');
hamburger.querySelectorAll('span').forEach(s => { s.style.transform = ''; s.style.opacity = ''; });
}));
// ── Blob parallax ──
const blobs = document.querySelectorAll('.blob');
window.addEventListener('scroll', () => {
const y = window.scrollY;
blobs.forEach((b, i) => {
const spd = 0.05 + i * 0.015;
const dir = i % 2 === 0 ? 1 : -1;
b.style.transform = `translateY(${y * spd * dir}px)`;
});
}, { passive: true });
// ── Scroll Reveal ──
function setupReveal() {
const sels = [
'.step-block', '.feat-card', '.gs-card', '.sec-badge',
'.faq-item', '.section-title', '.section-sub',
'.step-h2', '.step-p', '.ui-card',
'.feat-os-card', '.security-wrap', '.cta-h2',
'.logo-badge', '.trust-label', '.trust-sub'
];
document.querySelectorAll(sels.join(',')).forEach(el => {
if (el.closest('.hero')) return;
el.classList.add('reveal');
});
const obs = new IntersectionObserver(entries => {
entries.forEach(e => { if (e.isIntersecting) { e.target.classList.add('visible'); obs.unobserve(e.target); }});
}, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
document.querySelectorAll('.reveal').forEach(el => obs.observe(el));
// Stagger cards
['feat-card', 'gs-card', 'sec-badge'].forEach(cls => {
document.querySelectorAll('.' + cls).forEach((el, i) => { el.style.transitionDelay = `${i * 80}ms`; });
});
document.querySelectorAll('.step-block').forEach((el, i) => { el.style.transitionDelay = `${i * 60}ms`; });
}
setupReveal();
// ── FAQ ──
document.querySelectorAll('.faq-q').forEach(btn => {
btn.addEventListener('click', () => {
const isOpen = btn.getAttribute('aria-expanded') === 'true';
document.querySelectorAll('.faq-q').forEach(b => { b.setAttribute('aria-expanded', 'false'); b.nextElementSibling.classList.remove('open'); });
if (!isOpen) { btn.setAttribute('aria-expanded', 'true'); btn.nextElementSibling.classList.add('open'); }
});
});
// ── Form ──
function handleSubmit(e) {
e.preventDefault();
const input = e.target.querySelector('.email-input');
const btn = e.target.querySelector('button[type="submit"]');
if (!input.value.trim()) return;
const orig = btn.innerHTML;
btn.innerHTML = 'βœ“ You\'re on the list!';
btn.style.background = '#2d8a4e';
btn.style.animation = 'none';
input.disabled = btn.disabled = true;
setTimeout(() => {
btn.innerHTML = orig; btn.style.background = btn.style.animation = '';
input.disabled = btn.disabled = false; input.value = '';
}, 4000);
}
// ── Score counter ──
function animCount(el, from, to, dur = 1400) {
const start = performance.now();
(function tick(t) {
const p = Math.min((t - start) / dur, 1), e = 1 - Math.pow(1 - p, 3);
el.textContent = Math.round(from + (to - from) * e);
if (p < 1) requestAnimationFrame(tick);
})(start);
}
const after = document.querySelector('.vsn.gold'), before = document.querySelector('.vscore.before .vsn');
let counted = false;
if (after && before) {
new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && !counted) { counted = true; animCount(before, 0, 42, 1000); animCount(after, 0, 92, 1600); }
}, { threshold: 0.5 }).observe(after);
}
// ── Progress bar animate ──
const progressBar = document.querySelector('.uic-progress-bar');
if (progressBar) {
new IntersectionObserver(([e]) => {
if (e.isIntersecting) { progressBar.style.width = '72%'; }
}, { threshold: 0.4 }).observe(progressBar);
}
// ── Active nav ──
const sections = document.querySelectorAll('section[id]');
const navItems = document.querySelectorAll('.nav-item');
new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
const id = e.target.id;
navItems.forEach(a => {
a.style.color = a.getAttribute('href') === `#${id}` ? 'var(--white)' : '';
});
}
});
}, { threshold: 0.4 }).observe(document.querySelector('section[id]') || document.body);
// ── Cursor glow ──
const glow = document.createElement('div');
Object.assign(glow.style, {
position:'fixed',width:'250px',height:'250px',borderRadius:'50%',pointerEvents:'none',
zIndex:'9999',background:'radial-gradient(circle,rgba(90,79,207,0.06) 0%,transparent 70%)',
transform:'translate(-50%,-50%)',transition:'opacity 0.3s',opacity:'0'
});
document.body.appendChild(glow);
let mx=0, my=0, gx=0, gy=0, glowActive=false;
document.addEventListener('mousemove', e => { mx=e.clientX; my=e.clientY; if(!glowActive){glowActive=true;glow.style.opacity='1';loop();} });
document.addEventListener('mouseleave', () => { glow.style.opacity='0'; glowActive=false; });
function loop() {
if(!glowActive) return;
gx+=(mx-gx)*0.09; gy+=(my-gy)*0.09;
glow.style.left=gx+'px'; glow.style.top=gy+'px';
requestAnimationFrame(loop);
}
async function pollStream() {
console.log("πŸ”„ pollStream started");
const interval = setInterval(async () => {
try {
const res = await fetch("/stream");
const data = await res.json();
console.log("πŸ“‘ /stream response:", data);
if (data.url) {
clearInterval(interval);
const frame = document.getElementById("agentFrame");
if (!frame) return;
frame.src = data.url; // set once, iframe handles it natively
document.getElementById("agentLoader")?.remove();
console.log("πŸŽ₯ Stream live!");
}
} catch (e) {
console.error("❌ Polling error:", e);
}
}, 2000);
}