/* ============================ 郑州晟算科技有限公司 - 交互脚本 v3 自定义光标 · 启动加载屏 · 3D倾斜 · 磁性按钮 · 乱序解码 ============================ */ (function () { "use strict"; /* ======================================== 1. 自定义光标 ======================================== */ const isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0; if (!isTouchDevice) { const dot = document.createElement("div"); dot.className = "cursor-dot"; const ring = document.createElement("div"); ring.className = "cursor-ring"; document.body.appendChild(dot); document.body.appendChild(ring); let dotX = 0, dotY = 0; let ringX = 0, ringY = 0; let mouseX = 0, mouseY = 0; let isVisible = false; let cursorActivated = false; document.addEventListener("mousemove", function (e) { mouseX = e.clientX; mouseY = e.clientY; if (!isVisible) { isVisible = true; dot.style.opacity = "1"; ring.style.opacity = "1"; // 确认自定义光标工作后才隐藏系统光标 if (!cursorActivated) { cursorActivated = true; document.body.classList.add("custom-cursor-active"); } } }); document.addEventListener("mouseleave", function () { isVisible = false; dot.style.opacity = "0"; ring.style.opacity = "0"; }); document.addEventListener("mousedown", function () { ring.classList.add("clicking"); }); document.addEventListener("mouseup", function () { ring.classList.remove("clicking"); }); // 检测可交互元素 const hoverables = document.querySelectorAll("a, button, .card, .teaser-card, .feature-card, .belief-card, .philosophy-item, input, textarea, select, .nav-cta, .structure-item, .vision-item"); hoverables.forEach(function (el) { el.addEventListener("mouseenter", function () { ring.classList.add("hovering"); }); el.addEventListener("mouseleave", function () { ring.classList.remove("hovering"); }); }); function animateCursor() { // 点:即时跟随 dotX += (mouseX - dotX) * 0.35; dotY += (mouseY - dotY) * 0.35; dot.style.left = dotX + "px"; dot.style.top = dotY + "px"; // 环:延迟跟随 ringX += (mouseX - ringX) * 0.12; ringY += (mouseY - ringY) * 0.12; ring.style.left = ringX + "px"; ring.style.top = ringY + "px"; requestAnimationFrame(animateCursor); } animateCursor(); } /* ======================================== 2. 启动加载屏(太空模式下由space.js接管) ======================================== */ const isFirstVisit = !sessionStorage.getItem("visited"); if (!window.SPACE_MODE && isFirstVisit) { const loadingScreen = document.createElement("div"); loadingScreen.className = "loading-screen"; loadingScreen.innerHTML = '' + '
' + '
' + '
' + '
' + '
' + '
shengsuan@tech ~ boot
' + '
' + '
' + '
>Detecting software chaos... [76%]
' + '
>Loading fix modules... [OK]
' + '
>Mounting patches... [3 loaded]
' + '
>Rendering scene... [OK]
' + '
>Welcome, fixer. Let\'s repair some software.
' + '
' + '
'; document.body.appendChild(loadingScreen); setTimeout(function () { loadingScreen.classList.add("hide"); sessionStorage.setItem("visited", "true"); setTimeout(function () { if (loadingScreen.parentNode) loadingScreen.parentNode.removeChild(loadingScreen); }, 700); }, 2000); } else { sessionStorage.setItem("visited", "true"); } /* ======================================== 3. 滚动进度条 ======================================== */ const progressBar = document.createElement("div"); progressBar.className = "scroll-progress"; progressBar.style.width = "0%"; document.body.appendChild(progressBar); window.addEventListener("scroll", function () { const scrollTop = window.scrollY; const docHeight = document.documentElement.scrollHeight - window.innerHeight; const scrollPercent = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0; progressBar.style.width = scrollPercent + "%"; }, { passive: true }); /* ======================================== 4. 粒子背景(太空模式下禁用,由space.js接管) ======================================== */ let canvas = document.getElementById("particlesCanvas"); if (window.SPACE_MODE) { if (canvas) canvas.remove(); } else { if (!canvas) { canvas = document.createElement("canvas"); canvas.id = "particlesCanvas"; document.body.prepend(canvas); } const ctx = canvas.getContext("2d"); let particles = []; const PARTICLE_COUNT = 50; let pmouseX = 0, pmouseY = 0; function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } resizeCanvas(); window.addEventListener("resize", resizeCanvas); for (let i = 0; i < PARTICLE_COUNT; i++) { particles.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, vx: (Math.random() - 0.5) * 0.4, vy: (Math.random() - 0.5) * 0.4, size: Math.random() * 1.5 + 0.5, opacity: Math.random() * 0.35 + 0.08 }); } document.addEventListener("mousemove", function (e) { pmouseX = e.clientX; pmouseY = e.clientY; }); function drawParticles() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let i = 0; i < particles.length; i++) { const p = particles[i]; p.x += p.vx; p.y += p.vy; if (p.x < 0) p.x = canvas.width; if (p.x > canvas.width) p.x = 0; if (p.y < 0) p.y = canvas.height; if (p.y > canvas.height) p.y = 0; const dx = pmouseX - p.x; const dy = pmouseY - p.y; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 180 && dist > 0) { const force = (180 - dist) / 180 * 0.5; p.vx += (dx / dist) * force * 0.03; p.vy += (dy / dist) * force * 0.03; } p.vx *= 0.999; p.vy *= 0.999; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fillStyle = "rgba(0, 230, 118, " + p.opacity + ")"; ctx.fill(); } for (let j = 0; j < particles.length; j++) { for (let k = j + 1; k < particles.length; k++) { const a = particles[j], b = particles[k]; const d = Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)); if (d < 120) { ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(b.x, b.y); ctx.strokeStyle = "rgba(0, 230, 118, " + (0.06 * (1 - d / 120)) + ")"; ctx.lineWidth = 0.5; ctx.stroke(); } } } requestAnimationFrame(drawParticles); } drawParticles(); } /* end else (非太空模式才运行粒子) */ /* ======================================== 5. 导航栏 ======================================== */ const header = document.querySelector(".header"); window.addEventListener("scroll", function () { if (window.scrollY > 10) { header.classList.add("scrolled"); } else { header.classList.remove("scrolled"); } }, { passive: true }); const menuToggle = document.querySelector(".menu-toggle"); const nav = document.querySelector(".nav"); if (menuToggle && nav) { menuToggle.addEventListener("click", function () { menuToggle.classList.toggle("active"); nav.classList.toggle("active"); }); nav.querySelectorAll("a").forEach(function (link) { link.addEventListener("click", function () { menuToggle.classList.remove("active"); nav.classList.remove("active"); }); }); } /* ======================================== 6. 滚动显现 ======================================== */ const revealElements = document.querySelectorAll(".reveal"); if ("IntersectionObserver" in window && revealElements.length > 0) { const observer = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { entry.target.classList.add("visible"); observer.unobserve(entry.target); } }); }, { threshold: 0.1, rootMargin: "0px 0px -30px 0px" }); revealElements.forEach(function (el) { observer.observe(el); }); } else { revealElements.forEach(function (el) { el.classList.add("visible"); }); } /* ======================================== 7. 打字机效果 ======================================== */ const typewriterEl = document.querySelector("#typewriter"); if (typewriterEl) { let words; try { words = typewriterEl.getAttribute("data-words") ? JSON.parse(typewriterEl.getAttribute("data-words")) : [typewriterEl.textContent.trim()]; } catch (e) { words = [typewriterEl.textContent.trim()]; } let wordIndex = 0; let charIndex = 0; let isDeleting = false; const typeSpeed = 80; const deleteSpeed = 40; const pauseTime = 2000; function type() { const currentWord = words[wordIndex]; if (isDeleting) { typewriterEl.textContent = currentWord.substring(0, charIndex - 1); charIndex--; if (charIndex === 0) { isDeleting = false; wordIndex = (wordIndex + 1) % words.length; setTimeout(type, 300); return; } setTimeout(type, deleteSpeed); } else { typewriterEl.textContent = currentWord.substring(0, charIndex + 1); charIndex++; if (charIndex === currentWord.length) { setTimeout(function () { isDeleting = true; type(); }, pauseTime); return; } setTimeout(type, typeSpeed); } } const cursor = document.createElement("span"); cursor.className = "typewriter-cursor"; cursor.textContent = "_"; typewriterEl.parentNode.insertBefore(cursor, typewriterEl.nextSibling); setTimeout(type, 500); } /* ======================================== 8. 3D 倾斜卡片 ======================================== */ const tiltCards = document.querySelectorAll(".card, .teaser-card, .feature-card, .belief-card, .philosophy-item, .teaser-main, .teaser-side-item, .about-visual-block"); tiltCards.forEach(function (card) { card.addEventListener("mousemove", function (e) { const rect = card.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const centerX = rect.width / 2; const centerY = rect.height / 2; const rotateX = (y - centerY) / centerY * -8; const rotateY = (x - centerX) / centerX * 8; card.style.transform = "perspective(800px) rotateX(" + rotateX + "deg) rotateY(" + rotateY + "deg) translateZ(10px)"; // 更新光斑位置 let glow = card.querySelector(".card-glow"); if (!glow) { glow = document.createElement("div"); glow.className = "card-glow"; card.appendChild(glow); } glow.style.left = (x - 100) + "px"; glow.style.top = (y - 100) + "px"; }); card.addEventListener("mouseleave", function () { card.style.transform = "perspective(800px) rotateX(0) rotateY(0) translateZ(0)"; card.style.transition = "transform 0.6s ease"; setTimeout(function () { card.style.transition = ""; }, 600); }); }); /* ======================================== 9. 磁性按钮 ======================================== */ const magneticBtns = document.querySelectorAll(".btn, .nav-cta"); magneticBtns.forEach(function (btn) { btn.classList.add("magnetic"); btn.addEventListener("mousemove", function (e) { const rect = btn.getBoundingClientRect(); const x = e.clientX - rect.left - rect.width / 2; const y = e.clientY - rect.top - rect.height / 2; const strength = 0.3; btn.style.transform = "translate(" + x * strength + "px, " + y * strength + "px)"; }); btn.addEventListener("mouseleave", function () { btn.style.transform = "translate(0, 0)"; btn.style.transition = "transform 0.4s cubic-bezier(0.22, 1, 0.36, 1)"; setTimeout(function () { btn.style.transition = ""; }, 400); }); }); /* ======================================== 10. 文字乱序解码 (Scramble) ======================================== */ const CHARS = "!<>-_\\/[]{}—=+*^?#________"; const scrambleElements = document.querySelectorAll("[data-scramble]"); if (scrambleElements.length > 0 && "IntersectionObserver" in window) { const scrambleObserver = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting && !entry.target.classList.contains("scrambled")) { entry.target.classList.add("scrambled"); scrambleText(entry.target); scrambleObserver.unobserve(entry.target); } }); }, { threshold: 0.5 }); scrambleElements.forEach(function (el) { scrambleObserver.observe(el); }); } function scrambleText(el) { const originalText = el.textContent; const length = originalText.length; let frame = 0; const totalFrames = length * 3; const revealed = new Array(length).fill(false); function update() { let output = ""; let done = true; for (let i = 0; i < length; i++) { if (revealed[i]) { output += originalText[i]; continue; } if (frame > i * 2 + Math.random() * 10) { revealed[i] = true; output += originalText[i]; } else { done = false; output += CHARS[Math.floor(Math.random() * CHARS.length)]; } } el.textContent = output; frame++; if (!done) { requestAnimationFrame(update); } else { el.textContent = originalText; } } requestAnimationFrame(update); } /* ======================================== 11. 视差滚动 (Orbs - 排除hero-orb避免与CSS动画冲突) ======================================== */ const orbs = document.querySelectorAll(".cta-orb, .page-hero-orb"); if (orbs.length > 0) { window.addEventListener("scroll", function () { const scrollY = window.scrollY; orbs.forEach(function (orb, index) { const speed = 0.05 + (index % 3) * 0.03; orb.style.transform = "translateY(" + scrollY * speed + "px)"; }); }, { passive: true }); } /* ======================================== 11.5 编译进度条动画 ======================================== */ const progressFills = document.querySelectorAll(".build-progress-fill[data-progress]"); if (progressFills.length > 0 && "IntersectionObserver" in window) { const progressObserver = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { const fill = entry.target; const target = fill.getAttribute("data-progress"); setTimeout(function () { fill.style.width = target + "%"; }, 200); progressObserver.unobserve(fill); } }); }, { threshold: 0.5 }); progressFills.forEach(function (fill) { fill.style.width = "0%"; progressObserver.observe(fill); }); } /* ======================================== 11.6 失控数字滚动 ======================================== */ const chaosNums = document.querySelectorAll("[data-chaos-count]"); if (chaosNums.length > 0 && "IntersectionObserver" in window) { const chaosObserver = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) { animateChaosNum(entry.target); chaosObserver.unobserve(entry.target); } }); }, { threshold: 0.5 }); chaosNums.forEach(function (el) { chaosObserver.observe(el); }); } function animateChaosNum(el) { const target = parseFloat(el.getAttribute("data-chaos-count")); const decimals = el.getAttribute("data-decimals") ? parseInt(el.getAttribute("data-decimals")) : 0; const duration = 1600; let startTime = null; function step(timestamp) { if (!startTime) startTime = timestamp; const progress = Math.min((timestamp - startTime) / duration, 1); const eased = progress === 1 ? 1 : 1 - Math.pow(2, -10 * progress); const current = eased * target; el.textContent = decimals > 0 ? current.toFixed(decimals) : Math.floor(current).toLocaleString(); if (progress < 1) { requestAnimationFrame(step); } else { el.textContent = decimals > 0 ? target.toFixed(decimals) : target.toLocaleString(); } } requestAnimationFrame(step); } /* ======================================== 12.5 KPR风格滚动场景引擎 ======================================== */ const scrollScenes = document.querySelectorAll(".scroll-scene"); if (scrollScenes.length > 0) { const sceneData = []; scrollScenes.forEach(function (scene) { sceneData.push({ el: scene, lines: scene.querySelectorAll(".scene-line"), pctEls: scene.querySelectorAll("[data-scene-pct]"), giantTexts: scene.querySelectorAll(".bg-giant-text") }); }); let sceneTicking = false; function updateScenes() { const vh = window.innerHeight; sceneData.forEach(function (data) { const rect = data.el.getBoundingClientRect(); const scrollable = rect.height - vh; if (scrollable <= 0) return; const progress = Math.min(Math.max(-rect.top / scrollable, 0), 1); // 进度百分比 const pct = Math.floor(progress * 100); data.pctEls.forEach(function (el) { el.textContent = (pct < 10 ? "0" : "") + pct + "%"; }); // 大字背景视差 data.giantTexts.forEach(function (gt) { const shift = (progress - 0.5) * 120; gt.style.transform = "translate(-50%, -50%) translateX(" + shift + "px)"; }); // 逐行揭示 const n = data.lines.length; data.lines.forEach(function (line, i) { const lineStart = (i / n) * 0.9; const lineEnd = ((i + 1) / n) * 0.9 + 0.05; if (progress >= lineStart && progress < lineEnd) { line.classList.add("active"); line.classList.remove("exit"); } else if (progress >= lineEnd) { line.classList.remove("active"); line.classList.add("exit"); } else { line.classList.remove("active", "exit"); } }); }); sceneTicking = false; } window.addEventListener("scroll", function () { if (!sceneTicking) { requestAnimationFrame(updateScenes); sceneTicking = true; } }, { passive: true }); updateScenes(); } /* ======================================== 12.6 持续加密乱码(永不解码) ======================================== */ const SCRAMBLE_CHARS = "01!<>-_/[]{}=+*^?#@%&$ABCDEF"; const loopEncrypted = document.querySelectorAll("[data-scramble-loop]"); if (loopEncrypted.length > 0) { const encStates = []; loopEncrypted.forEach(function (el) { const len = parseInt(el.getAttribute("data-length") || el.textContent.length, 10); encStates.push({ el: el, len: len }); }); let encFrame = 0; function loopScramble() { encFrame++; // 每3帧更新一次,降低频率更有"电流感" if (encFrame % 3 === 0) { encStates.forEach(function (s) { let out = ""; for (let i = 0; i < s.len; i++) { out += SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)]; } s.el.textContent = out; }); } requestAnimationFrame(loopScramble); } // 仅在视口内时运行 const encObserver = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting && !entry.target.dataset.looping) { entry.target.dataset.looping = "1"; } }); }); loopEncrypted.forEach(function (el) { encObserver.observe(el); }); loopScramble(); } /* ======================================== 12.7 数据流下落线条生成(带裁剪层,防页面弹跳) ======================================== */ const streamContainers = document.querySelectorAll("[data-streams]"); streamContainers.forEach(function (container) { // 裁剪层:防止线条溢出改变文档高度导致底部弹跳 const layer = document.createElement("div"); layer.style.cssText = "position:absolute;inset:0;overflow:hidden;pointer-events:none;z-index:0"; const count = parseInt(container.getAttribute("data-streams"), 10) || 5; for (let i = 0; i < count; i++) { const stream = document.createElement("div"); stream.className = "data-stream"; stream.style.left = (10 + Math.random() * 80) + "%"; stream.style.height = (60 + Math.random() * 120) + "px"; stream.style.animationDelay = (Math.random() * 3) + "s"; stream.style.animationDuration = (2.5 + Math.random() * 2) + "s"; layer.appendChild(stream); } container.insertBefore(layer, container.firstChild); }); /* ======================================== 12. 联系表单 ======================================== */ const contactForm = document.querySelector("#contactForm"); if (contactForm) { contactForm.addEventListener("submit", function (e) { e.preventDefault(); const successMsg = document.querySelector(".form-success"); if (successMsg) { successMsg.classList.add("show"); successMsg.scrollIntoView({ behavior: "smooth", block: "center" }); } contactForm.reset(); setTimeout(function () { if (successMsg) successMsg.classList.remove("show"); }, 6000); }); } /* ======================================== 13. 当前页面高亮导航 ======================================== */ const currentPage = window.location.pathname.split("/").pop() || "index.html"; const navLinks = document.querySelectorAll(".nav-link"); navLinks.forEach(function (link) { const href = link.getAttribute("href"); if (href === currentPage || (currentPage === "" && href === "index.html")) { link.classList.add("active"); } }); /* ======================================== 14. 腐化标题特效 —— 标题本身就是bug ======================================== */ const corruptEl = document.querySelector(".corrupt-title[data-text]"); if (corruptEl) { const original = corruptEl.getAttribute("data-text"); const GLITCH_CHARS = "▓█◆#@$%&!?◢◤╳╱╲─│┌┐└┘01"; // 拆分字符 corruptEl.innerHTML = ""; const charSpans = []; for (let ci = 0; ci < original.length; ci++) { const span = document.createElement("span"); span.className = "corrupt-char"; span.textContent = original[ci]; span.setAttribute("data-orig", original[ci]); corruptEl.appendChild(span); charSpans.push(span); } const wrap = corruptEl.closest(".corrupt-wrap"); // 持续轻微腐化: 随机字符短暂变为乱码 setInterval(function () { if (Math.random() > 0.45) { const idx = Math.floor(Math.random() * charSpans.length); const span = charSpans[idx]; span.textContent = GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)]; span.classList.add("glitching"); setTimeout(function () { span.textContent = span.getAttribute("data-orig"); span.classList.remove("glitching"); }, 60 + Math.random() * 120); } }, 140); // 偶发重度腐化: 多个字符同时崩溃 + 屏幕震颤 setInterval(function () { if (Math.random() > 0.6) { const burst = 2 + Math.floor(Math.random() * 3); for (let b = 0; b < burst; b++) { const span = charSpans[Math.floor(Math.random() * charSpans.length)]; span.textContent = GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)]; span.classList.add("glitching"); (function (s) { setTimeout(function () { s.textContent = s.getAttribute("data-orig"); s.classList.remove("glitching"); }, 120 + Math.random() * 200); })(span); } if (wrap) { wrap.classList.remove("shake"); void wrap.offsetWidth; // 重置动画 wrap.classList.add("shake"); } } }, 4200); } })();