const root = document.documentElement; const header = document.querySelector(".site-header"); const nav = document.querySelector(".site-nav"); const navToggle = document.querySelector(".nav-toggle"); const themeToggle = document.querySelector(".theme-toggle"); const typingLine = document.querySelector("#typing-line"); const selectedPrompt = document.querySelector("#selected-prompt"); const prefersReduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const media = { researchVideo: new URL("../assets/showcase-research.mp4", import.meta.url).href, researchPoster: new URL("../assets/showcase-research.jpg", import.meta.url).href, imageVideo: new URL("../assets/showcase-ai-image.mp4", import.meta.url).href, imagePoster: new URL("../assets/showcase-ai-image.jpg", import.meta.url).href, videoVideo: new URL("../assets/showcase-ai-video.mp4", import.meta.url).href, videoPoster: new URL("../assets/showcase-ai-video.jpg", import.meta.url).href, avatarVideo: new URL("../assets/showcase-avatar.mp4", import.meta.url).href, avatarPoster: new URL("../assets/showcase-avatar.jpg", import.meta.url).href, clipVideo: new URL("../assets/showcase-clip.mp4", import.meta.url).href, clipPoster: new URL("../assets/showcase-clip.jpg", import.meta.url).href, publishVideo: new URL("../assets/showcase-publish.mp4", import.meta.url).href, publishPoster: new URL("../assets/showcase-publish.jpg", import.meta.url).href }; const featureData = { research: { kicker: "万象智枢", title: "从全网信号里找到今天值得做的内容战役", text: "聚合网页、视频、社区和开源项目,把趋势、用户痛点和内容机会转成脚本、分镜和提示词。", video: media.researchVideo, poster: media.researchPoster, points: ["趋势研究与需求洞察", "脚本、分镜、提示词生成", "结果可保存到知识与资产链路"] }, image: { kicker: "AI 图片生成", title: "从一句方向到一组可筛选、可复用的视觉资产", text: "支持文生图、图生图、多图参考、局部重绘和批量提示词,适合产品图、封面、视觉风格探索。", video: media.imageVideo, poster: media.imagePoster, points: ["单图与批量出图", "参考图和参数统一管理", "结果进入资产中心与画布"] }, video: { kicker: "AI 视频生成", title: "把选题、脚本和分镜快速推到镜头生产", text: "支持文生视频、图生视频、首尾帧和爆款视频流程,让一人团队也能连续产出内容素材。", video: media.videoVideo, poster: media.videoPoster, points: ["脚本到镜头连续生成", "热门视频与 MV 模式", "多结果对比与二次编辑"] }, avatar: { kicker: "数字人口播", title: "让创始人形象、声音和脚本成为可调度资产", text: "形象、音色、文案和背景组合生成口播视频,适合课程、产品讲解、私域种草和批量答疑。", video: media.avatarVideo, poster: media.avatarPoster, points: ["形象与音色管理", "TTS 配音和字幕生成", "本地模型状态可检查"] }, clip: { kicker: "AI 剪辑中心", title: "把长素材拆成高密度短视频发布弹药", text: "长视频拆条、文字剪辑、说话人选段、字幕和商业短视频包装集中在同一个剪辑工作台。", video: media.clipVideo, poster: media.clipPoster, points: ["长视频智能拆条", "字幕、封面和包装建议", "任务历史与资产中心同步"] }, publish: { kicker: "自动发布", title: "让内容从资产中心直接进入渠道排程", text: "选择图片视频资产,授权多平台账号,批量发布到社交渠道,并把发布动作沉淀回下一轮复盘。", video: media.publishVideo, poster: media.publishPoster, points: ["多平台账号与排程", "素材库批量选择", "发布结果回流到增长复盘"] } }; const typingPhrases = [ "读取全网趋势,拆成可执行内容战役...", "正在为一人公司生成选题、脚本、分镜和发布排程...", "图片、视频、声音、数字人任务已进入统一队列...", "资产中心已同步生成结果,等待下一轮复盘..." ]; function setHeaderState() { header?.setAttribute("data-elevated", String(window.scrollY > 24)); } window.addEventListener("scroll", setHeaderState, { passive: true }); setHeaderState(); navToggle?.addEventListener("click", () => { const isOpen = nav?.classList.toggle("is-open") ?? false; navToggle.setAttribute("aria-expanded", String(isOpen)); }); nav?.addEventListener("click", (event) => { if (event.target instanceof HTMLAnchorElement) { nav.classList.remove("is-open"); navToggle?.setAttribute("aria-expanded", "false"); } }); const savedTheme = localStorage.getItem("truegrowth-official-theme"); if (savedTheme === "light" || savedTheme === "dark") { root.dataset.siteTheme = savedTheme; } themeToggle?.addEventListener("click", () => { const next = root.dataset.siteTheme === "dark" ? "light" : "dark"; root.dataset.siteTheme = next; localStorage.setItem("truegrowth-official-theme", next); }); const revealObserver = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add("is-visible"); revealObserver.unobserve(entry.target); } }); }, { threshold: 0.14 } ); document.querySelectorAll(".reveal").forEach((element) => revealObserver.observe(element)); const counters = document.querySelectorAll("[data-count-to]"); const counterObserver = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) return; const element = entry.target; const target = Number(element.getAttribute("data-count-to") || "0"); const start = performance.now(); const duration = prefersReduced ? 1 : 900; const tick = (time) => { const progress = Math.min(1, (time - start) / duration); element.textContent = String(Math.round(target * progress)); if (progress < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); counterObserver.unobserve(element); }); }, { threshold: 0.4 } ); counters.forEach((counter) => counterObserver.observe(counter)); let phraseIndex = 0; let charIndex = 0; let deleting = false; function typeNext() { if (!typingLine || prefersReduced) return; const phrase = typingPhrases[phraseIndex]; typingLine.textContent = phrase.slice(0, charIndex); if (!deleting && charIndex < phrase.length) { charIndex += 1; window.setTimeout(typeNext, 34); return; } if (!deleting && charIndex === phrase.length) { deleting = true; window.setTimeout(typeNext, 1300); return; } if (deleting && charIndex > 0) { charIndex -= 1; window.setTimeout(typeNext, 18); return; } deleting = false; phraseIndex = (phraseIndex + 1) % typingPhrases.length; window.setTimeout(typeNext, 220); } typeNext(); const featureVideo = document.querySelector("#feature-video"); const featureKicker = document.querySelector("#feature-kicker"); const featureTitle = document.querySelector("#feature-title"); const featureText = document.querySelector("#feature-text"); const featurePoints = document.querySelector("#feature-points"); function setFeatureVideo(data) { if (!featureVideo || !data) return; featureVideo.setAttribute("poster", data.poster); if (featureVideo.currentSrc !== data.video) { featureVideo.setAttribute("src", data.video); featureVideo.load(); } featureVideo.play().catch(() => {}); } setFeatureVideo(featureData.research); document.querySelectorAll(".feature-tab").forEach((button) => { button.addEventListener("click", () => { const key = button.getAttribute("data-feature"); const data = key ? featureData[key] : null; if (!data) return; document.querySelectorAll(".feature-tab").forEach((tab) => { const active = tab === button; tab.classList.toggle("is-active", active); tab.setAttribute("aria-selected", String(active)); }); if (featureKicker) featureKicker.textContent = data.kicker; if (featureTitle) featureTitle.textContent = data.title; if (featureText) featureText.textContent = data.text; if (featurePoints) { featurePoints.innerHTML = data.points.map((point) => `
  • ${point}
  • `).join(""); } setFeatureVideo(data); }); }); document.querySelectorAll(".plaza-filter").forEach((button) => { button.addEventListener("click", () => { const filter = button.getAttribute("data-filter") || "all"; document.querySelectorAll(".plaza-filter").forEach((item) => item.classList.toggle("is-active", item === button)); document.querySelectorAll(".prompt-card").forEach((card) => { const group = card.getAttribute("data-group"); card.classList.toggle("is-hidden", filter !== "all" && group !== filter); }); }); }); document.querySelectorAll(".prompt-card").forEach((card) => { card.addEventListener("click", () => { document.querySelectorAll(".prompt-card").forEach((item) => item.classList.toggle("is-selected", item === card)); selectedPrompt.textContent = card.getAttribute("data-prompt") || ""; }); }); document.querySelector(".prompt-card")?.classList.add("is-selected"); document.querySelector(".console-run")?.addEventListener("click", () => { if (!selectedPrompt) return; selectedPrompt.textContent = `已创建模拟任务:${selectedPrompt.textContent.replace(/^已创建模拟任务:/, "")}`; }); function initSignalCanvas() { const canvas = document.querySelector("#signal-canvas"); if (!(canvas instanceof HTMLCanvasElement) || prefersReduced) return; const context = canvas.getContext("2d"); if (!context) return; const pointer = { x: window.innerWidth / 2, y: window.innerHeight / 2 }; const nodes = []; const maxNodes = Math.min(82, Math.max(38, Math.floor(window.innerWidth / 22))); function resize() { const dpr = Math.min(2, window.devicePixelRatio || 1); canvas.width = Math.floor(window.innerWidth * dpr); canvas.height = Math.floor(window.innerHeight * dpr); canvas.style.width = `${window.innerWidth}px`; canvas.style.height = `${window.innerHeight}px`; context.setTransform(dpr, 0, 0, dpr, 0, 0); } function seed() { nodes.length = 0; for (let i = 0; i < maxNodes; i += 1) { nodes.push({ x: Math.random() * window.innerWidth, y: Math.random() * window.innerHeight, vx: (Math.random() - 0.5) * 0.36, vy: (Math.random() - 0.5) * 0.36, r: 1 + Math.random() * 1.8 }); } } function draw() { context.clearRect(0, 0, window.innerWidth, window.innerHeight); nodes.forEach((node) => { node.x += node.vx; node.y += node.vy; if (node.x < -20 || node.x > window.innerWidth + 20) node.vx *= -1; if (node.y < -20 || node.y > window.innerHeight + 20) node.vy *= -1; }); for (let i = 0; i < nodes.length; i += 1) { for (let j = i + 1; j < nodes.length; j += 1) { const a = nodes[i]; const b = nodes[j]; const dx = a.x - b.x; const dy = a.y - b.y; const distance = Math.hypot(dx, dy); if (distance < 132) { const alpha = (1 - distance / 132) * 0.18; context.strokeStyle = `rgba(255, 91, 0, ${alpha})`; context.lineWidth = 1; context.beginPath(); context.moveTo(a.x, a.y); context.lineTo(b.x, b.y); context.stroke(); } } } nodes.forEach((node) => { const pointerDistance = Math.hypot(node.x - pointer.x, node.y - pointer.y); if (pointerDistance < 190) { context.strokeStyle = `rgba(80, 230, 255, ${(1 - pointerDistance / 190) * 0.28})`; context.beginPath(); context.moveTo(node.x, node.y); context.lineTo(pointer.x, pointer.y); context.stroke(); } context.fillStyle = pointerDistance < 190 ? "rgba(80, 230, 255, 0.72)" : "rgba(255, 91, 0, 0.58)"; context.beginPath(); context.arc(node.x, node.y, node.r, 0, Math.PI * 2); context.fill(); }); requestAnimationFrame(draw); } window.addEventListener("resize", () => { resize(); seed(); }); window.addEventListener("pointermove", (event) => { pointer.x = event.clientX; pointer.y = event.clientY; }, { passive: true }); resize(); seed(); draw(); } initSignalCanvas();