Files

1 line
105 KiB
JSON

{"repo": "123456wws/00", "files": {"hot/js/script.js": {"8f8da98a546f733fe501630514407be603b7fa15": {"t": 1785745532.1683643, "n": 93924, "h": "b1395f1bf48d4fd4", "c": "// Import OpenAI SDK is no longer needed\n\n// Define variables that need to be accessible globally\nlet navLinks, contentSections, hasInitializedHomepage = false;\nlet isNavigating = false; // Flag to prevent double transitions\n\n// Run visitor counter initialization immediately with DOM checking\n(function() {\n // Check if DOM is already available\n if (document.readyState === \"loading\") {\n // If DOM not yet ready, wait for DOMContentLoaded\n document.addEventListener(\"DOMContentLoaded\", initVisitorCounter);\n } else {\n // DOM already loaded, run immediately\n initVisitorCounter();\n }\n})();\n\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n console.log(\"DOMContentLoaded - 开始加载页面\");\n \n // --- Initialize Visitor Counter --- (already initialized above)\n // initVisitorCounter();\n \n // --- Get navigation elements ---\n navLinks = document.querySelectorAll(\".nav-link\");\n contentSections = document.querySelectorAll(\".content-section\");\n \n // --- 初始化移动端导航 ---\n initMobileNavigation();\n \n // --- 直接加载所有图片,确保照片全部显示 ---\n lazyLoadImages();\n \n // 确保所有图片强制加载\n document.querySelectorAll('img').forEach(img => {\n console.log(\"检查图片:\", img.src);\n if (img.getAttribute('data-src')) {\n console.log(\"发现data-src,强制加载:\", img.getAttribute('data-src'));\n img.src = img.getAttribute('data-src');\n img.removeAttribute('data-src');\n }\n \n // 确保图片可见\n img.style.display = 'block';\n img.style.visibility = 'visible';\n img.style.opacity = '1';\n \n // 监听加载事件\n img.onload = function() {\n console.log(\"图片加载完成:\", this.src);\n };\n \n img.onerror = function() {\n console.error(\"图片加载失败:\", this.src);\n // 尝试重新加载失败的图片\n setTimeout(() => {\n const currentSrc = this.src;\n this.src = \"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\";\n this.src = currentSrc;\n }, 1000);\n };\n });\n \n // --- Activate homepage once ---\n if (navLinks.length > 0 && contentSections.length > 0 && !hasInitializedHomepage) {\n hasInitializedHomepage = true;\n navLinks[0].classList.add(\"active-link\");\n contentSections[0].style.display = \"block\";\n contentSections[0].classList.add(\"active-section\");\n applyStaggeredAnimation(contentSections[0]);\n }\n \n // --- Constants and Global Variables ---\n const DOUBAO_API_KEY = \"f95dd168-74d4-4c49-b0fd-f9e479dbe066\"; // 豆包API Key\n const DEEPSEEK_API_KEY = \"sk-3a447a296dd941fcb0da055ea84493f3\"; // DeepSeek API Key \n const TONGYI_API_KEY = \"sk-a39e00a96c27430c8604138beaaf8219\"; // 百炼 API Key\n const HOT_API_KEY = \"sk-a39e00a96c27430c8604138beaaf8219\"; // Hot API Key (原元宝)\n \n // API URLs\n const DOUBAO_API_URL = \"https://ark.cn-beijing.volces.com/api/v3/chat/completions\"; // 豆包API URL\n const DEEPSEEK_API_URL = \"https://api.deepseek.com/chat/completions\"; // DeepSeek API URL\n const TONGYI_API_URL = \"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions\"; // 百炼 API URL\n const HOT_API_URL = \"https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions\"; // Hot API URL (原元宝)\n \n // 当前选择的模型 - 默认豆包\n let currentModel = \"doubao\";\n console.log(\"模型设置为:\", currentModel);\n \n // 初始化OpenAI客户端 - 不再需要\n let openai = null;\n \n // 根据当前模型创建相应的OpenAI客户端 - 简化为仅打印信息\n function initializeOpenAIClient() {\n console.log(\"使用\" + currentModel + \"API\");\n }\n \n // 初始化默认的客户端\n initializeOpenAIClient();\n \n const CHAT_HISTORY_KEY = \"hot_chat_history\";\n const MAX_HISTORY_MESSAGES = 10; // Keep last 10 messages (5 user, 5 bot) for context\n\n // --- Navigation Logic ---\n navLinks.forEach((link) => {\n link.addEventListener(\"click\", function(event) {\n event.preventDefault();\n \n // Prevent multiple clicks/transitions\n if (isNavigating) return;\n isNavigating = true;\n \n navLinks.forEach(l => l.classList.remove(\"active-link\"));\n contentSections.forEach(s => {\n s.classList.remove(\"active-section\");\n s.style.display = \"none\"; // Ensure it's hidden before animation\n });\n this.classList.add(\"active-link\");\n const targetSectionId = this.getAttribute(\"href\");\n const targetSection = document.querySelector(targetSectionId);\n if (targetSection) {\n targetSection.style.display = \"block\"; // Make it visible for animation\n targetSection.classList.add(\"active-section\");\n applyStaggeredAnimation(targetSection);\n \n // Scroll to the section - with a small offset from the top\n const yOffset = 20; \n const y = targetSection.getBoundingClientRect().top + window.pageYOffset - yOffset;\n window.scrollTo({top: y, behavior: 'smooth'});\n \n // Reset navigation flag after animation completes\n setTimeout(() => {\n isNavigating = false;\n }, 800); // Match this with your animation duration\n } else {\n isNavigating = false; // Reset flag if target not found\n }\n });\n });\n\n function applyStaggeredAnimation(section) {\n // 限制一次动画的元素数量,只处理前15个元素\n const itemsToAnimate = section.querySelectorAll(\".tech-card, .process-step, .case-study-item, .faq-item, .testimonial-item\");\n const maxAnimatedItems = 15;\n \n // 使用 Array.from 转换为数组并限制数量\n Array.from(itemsToAnimate).slice(0, maxAnimatedItems).forEach((item, idx) => {\n item.style.setProperty(\"--animation-order\", idx + 1);\n // 避免重复触发布局/重绘\n requestAnimationFrame(() => {\n item.style.animation = \"none\";\n requestAnimationFrame(() => {\n item.style.animation = \"\";\n });\n });\n });\n \n // 其余元素直接显示,不应用动画\n Array.from(itemsToAnimate).slice(maxAnimatedItems).forEach(item => {\n item.style.opacity = 1;\n item.style.transform = \"translateY(0)\";\n });\n }\n \n // Homepage initialization is now handled in the DOMContentLoaded event\n\n // --- Service Item Price Details Toggle ---\n document.querySelectorAll(\".service-item .details-btn\").forEach(button => {\n button.addEventListener(\"click\", function() {\n const priceDetails = this.closest(\".service-item\").querySelector(\".price-details\");\n const isHidden = priceDetails.style.display === \"none\" || priceDetails.style.display === \"\";\n priceDetails.style.display = isHidden ? \"block\" : \"none\";\n this.textContent = isHidden ? \"收起详情\" : \"查看详情\";\n // Accessibility: Update ARIA attribute if you add one\n });\n });\n\n // --- Case Study Modal Logic (Using new content drafts) ---\n const caseModal = document.getElementById(\"case-modal\");\n const modalCaseTitle = document.getElementById(\"modal-case-title\");\n const modalCaseImage = document.getElementById(\"modal-case-image\");\n const modalCaseDescription = document.getElementById(\"modal-case-description\");\n const caseDetailTriggers = document.querySelectorAll(\".case-details-trigger\");\n const closeModalButton = caseModal ? caseModal.querySelector(\".close-button\") : null;\n\n // Fetch and populate case study details from MD files\n async function fetchMarkdownContent(filePath) {\n try {\n // This is a placeholder. In a real environment, you'd fetch the file.\n // For this sandbox, we'll simulate by using pre-defined content based on filePath.\n // This would typically be: const response = await fetch(filePath); const text = await response.text();\n if (filePath.includes(\"case_study_1\")) return `### 案例一:赋能制造企业——复杂生产调度系统的智能优化与效率跃升\\n\\n**客户背景与挑战:**\\n\\n某大型离散制造业企业,其生产线涉及多工序、多设备、多订单并行处理,传统的人工排程方式效率低下...\\n\\n**Hot团队的解决方案:**\\n\\n1. **深度需求调研与痛点分析**:我们与客户生产管理、工艺技术及IT部门进行了多轮深入访谈...\\n2. **定制化数学建模**:针对客户复杂的生产环境,我们构建了一个多目标、多约束的混合整数规划(MIP)模型...\\n3. **高效算法设计与实现**:鉴于模型的复杂性和求解效率要求,我们设计并实现了一种基于遗传算法与禁忌搜索相结合的启发式优化算法...\\n4. **可视化调度界面开发**:为方便生产管理人员使用和监控,我们开发了一套直观易用的可视化调度界面...\\n5. **系统集成与部署**:将优化调度系统与客户现有的ERP、MES系统进行有效集成...\\n\\n**项目成果与客户价值:**\\n\\n* 生产效率显著提升:平均订单完成时间缩短了 **25%**...\\n* 设备利用率大幅提高:设备平均利用率从原先的65%提升至 **85%**...\\n* 运营成本有效降低:年度综合运营成本降低约 **15%**...\\n* 决策支持能力增强:可视化界面和实时数据为生产管理者提供了强有力的决策支持...\\n* 客户评价:\"Hot团队的专业能力和敬业精神给我们留下了深刻印象...\"`;\n if (filePath.includes(\"case_study_2\")) return `### 案例二:助力初创腾飞——AI算法软件著作权快速获取与价值保护\\n\\n**客户背景与挑战:**\\n\\n一家专注于人工智能图像识别领域的初创科技公司,自主研发了一套具有创新性的缺陷检测算法...\\n\\n**Hot团队的解决方案:**\\n\\n1. **精准需求对接与专业咨询**:我们首先与客户的技术负责人进行了深入沟通...\\n2. **核心材料梳理与规范化指导**:软著申请的核心在于源代码和说明文档的规范性...\\n3. **高质量设计说明书撰写**:针对AI算法类软著的特点,我们协助客户撰写了一份高质量的《软件设计说明书》...\\n4. **全程代办与快速提交**:在客户确认所有申请材料无误后,我们启动了全程代办服务...\\n5. **进度透明与及时反馈**:在整个申请过程中,我们定期向客户通报申请进度...\\n\\n**项目成果与客户价值:**\\n\\n* 快速获得软著证书:该公司的AI算法软件著作权申请在远低于平均周期的时间内顺利通过审查...\\n* 核心技术得到有效保护:软著证书的获得,为客户的核心AI算法提供了有力的法律保护...\\n* 提升企业无形资产价值:软件著作权作为重要的无形资产,显著提升了该初创公司的整体估值...\\n* 增强市场竞争力与品牌信誉:拥有自主知识产权的AI算法,使该公司在市场竞争中更具优势...\\n* 客户评价:\"Hot团队的软著申请服务非常专业、高效!...\"`;\n if (filePath.includes(\"case_study_3\")) return `### 案例三:赋能科研创新——交互式数据可视化平台的定制开发与洞察深化\\n\\n**客户背景与挑战:**\\n\\n某知名大学的生物信息学研究团队,在进行一项复杂基因组学研究时,积累了海量的多维度实验数据...\\n\\n**Hot团队的解决方案:**\\n\\n1. **深度科研需求理解与功能规划**:我们与研究团队的核心成员进行了多次深入的技术研讨...\\n2. **定制化平台架构设计与技术选型**:考虑到数据量、交互性能和未来扩展性,我们为该平台设计了前后端分离的架构...\\n3. **多维度交互式可视化模块开发**:根据科研需求,我们逐一开发了多个核心可视化模块...\\n4. **用户友好的操作界面与数据管理**:我们设计了简洁直观的操作界面...\\n5. **持续迭代与科研支持**:在平台开发和部署过程中,我们与研究团队保持紧密沟通...\\n\\n**项目成果与客户价值:**\\n\\n* 科研效率大幅提升:数据分析和洞察发现的时间平均缩短了 **60%** 以上...\\n* 科学发现的深化与加速:已有多篇高质量论文发表...\\n* 团队协作与知识共享增强:平台支持项目数据的集中管理和可视化结果的便捷分享...\\n* 可定制化与可扩展性:模块化的设计使得平台未来可以根据新的科研需求,方便地添加新的分析模块...\\n* 客户评价:\"Hot团队开发的这个可视化平台对我们的科研工作起到了革命性的推动作用!...\"`;\n return \"案例内容加载失败。\"; // Fallback\n } catch (error) {\n console.error(\"Error fetching case study content:\", error);\n return \"案例内容加载失败。\";\n }\n }\n \n // Simple Markdown to HTML (basic for bold and paragraphs)\n function simpleMarkdownToHtml(md) {\n let html = md.replace(/\\n\\n/g, \"</p><p>\"); // Double newline to paragraph\n html = html.replace(/\\n/g, \"<br>\"); // Single newline to line break\n html = html.replace(/\\*\\*(.*?)\\*\\*/g, \"<strong>$1</strong>\"); // Bold\n html = html.replace(/### (.*?)(<br>|<)/g, \"<h4>$1</h4>\"); // H3 to H4 for modal\n return \"<p>\" + html + \"</p>\";\n }\n\n const caseDetailsData = {\n case1: { title: \"数学建模论文:多目标优化NSGA-II算法详情\", image: \"images/case_placeholder_1.jpg\", contentFile: \"content_drafts/case_study_1_description_v2.md\" },\n case2: { title: \"软著申请:智能图像识别系统详情\", image: \"images/case_placeholder_2.jpg\", contentFile: \"content_drafts/case_study_2_description_v2.md\" },\n case3: { title: \"专利申请:智能生产调度系统详情\", image: \"images/case_placeholder_3.jpg\", contentFile: \"content_drafts/case_study_3_description_v2.md\" }\n };\n\n caseDetailTriggers.forEach(trigger => {\n trigger.addEventListener(\"click\", async function(event) {\n event.preventDefault();\n if (!caseModal || !modalCaseTitle || !modalCaseImage || !modalCaseDescription) return;\n const caseId = this.closest(\".case-study-item\").dataset.caseId;\n const data = caseDetailsData[caseId];\n if (data) {\n modalCaseTitle.textContent = data.title;\n modalCaseImage.src = data.image;\n modalCaseImage.alt = data.title + \" 详情图片\";\n // Fetch and set description from MD file (simulated)\n const markdownContent = await fetchMarkdownContent(data.contentFile);\n modalCaseDescription.innerHTML = simpleMarkdownToHtml(markdownContent);\n caseModal.style.display = \"block\";\n document.body.style.overflow = \"hidden\"; // Prevent background scroll when modal is open\n }\n });\n });\n\n // 为数学建模论文卡片添加点击事件\n document.querySelectorAll(\".paper-card\").forEach(paperCard => {\n paperCard.addEventListener(\"click\", function() {\n if (!caseModal || !modalCaseTitle || !modalCaseImage || !modalCaseDescription) return;\n \n // 获取论文标题和图片\n const paperTitle = this.querySelector(\".paper-title\").textContent;\n const paperImage = this.querySelector(\".paper-image\").src;\n const paperAward = this.querySelector(\".paper-award\").textContent;\n \n // 设置模态框内容\n modalCaseTitle.textContent = paperTitle;\n modalCaseImage.src = paperImage;\n modalCaseImage.alt = paperTitle + \" 详情图片\";\n \n // 生成详情内容\n let paperDetail = `\n <p><strong>获奖情况:</strong>${paperAward}</p>\n <h4>论文简介</h4>\n <p>该论文是我们团队协助客户完成的高质量数学建模成果,通过精心设计的算法和模型,解决了实际应用场景中的复杂问题。</p>\n <h4>技术方法</h4>\n <p>针对具体问题,我们采用了高效的数学建模方法,结合优化算法、机器学习等先进技术,构建了一套完整的解决方案。包括数据处理、模型构建、算法设计、参数调优、结果分析和论文撰写等全流程服务。</p>\n <h4>应用价值</h4>\n <p>该模型不仅具有理论创新性,还具有很强的实用价值,可广泛应用于相关领域的决策支持、流程优化和系统改进。我们的服务帮助客户在竞赛中取得了优异成绩,充分展示了我们团队的专业能力。</p>\n <h4>服务流程</h4>\n <p>我们提供从需求分析、建模设计到论文撰写的全流程专业服务,确保每一步都符合客户需求和竞赛标准。如需了解更多细节或有类似需求,欢迎联系我们的客服。</p>\n `;\n \n modalCaseDescription.innerHTML = paperDetail;\n caseModal.style.display = \"block\";\n document.body.style.overflow = \"hidden\"; // Prevent background scroll when modal is open\n });\n });\n\n function closeCaseModal() {\n if (!caseModal) return;\n caseModal.style.display = \"none\";\n document.body.style.overflow = \"auto\"; // Restore background scroll\n }\n\n if (closeModalButton) closeModalButton.addEventListener(\"click\", closeCaseModal);\n if (caseModal) window.addEventListener(\"click\", (event) => { if (event.target === caseModal) closeCaseModal(); });\n window.addEventListener(\"keydown\", (event) => { if (event.key === \"Escape\" && caseModal && caseModal.style.display === \"block\") closeCaseModal(); });\n\n\n // --- FAQ Accordion Logic ---\n // This simple FAQ functionality has been replaced by the enhanced initFAQSection()\n // at the bottom of this file, which adds search and filtering capabilities.\n\n // --- Booking Form Logic ---\n const bookingForm = document.getElementById(\"booking-form\");\n if (bookingForm) {\n bookingForm.addEventListener(\"submit\", function(event) {\n event.preventDefault();\n const responseEl = document.getElementById(\"booking-response\");\n responseEl.textContent = \"正在提交您的预约请求...\";\n responseEl.style.color = \"#79e1ff\"; // Updated color\n // Simulate submission\n setTimeout(() => {\n const nameInput = document.getElementById(\"booking-name\");\n responseEl.textContent = `感谢您的预约,${nameInput ? nameInput.value.trim() : \"贵客\"}!我们会尽快与您联系。`;\n bookingForm.reset();\n }, 1500);\n });\n }\n\n // --- Chat Widget Enhanced Logic ---\n const chatToggle = document.getElementById(\"chat-toggle\");\n const chatWidget = document.getElementById(\"chat-widget\");\n const closeChat = document.getElementById(\"close-chat\");\n const chatMessagesContainer = document.getElementById(\"chat-messages\");\n const chatInput = document.getElementById(\"chat-input\");\n const sendChat = document.getElementById(\"send-chat-btn\");\n const clearChatHistoryBtn = document.getElementById(\"clear-chat-history\");\n const quickQuestionsArea = document.getElementById(\"quick-questions\");\n const chatResizeHandle = document.getElementById(\"chat-resize-handle\");\n const modelSelect = document.getElementById(\"model-select\");\n \n // Simplified chat widget functionality\n let chatHistory = [];\n let isTypingResponse = false;\n \n // Initialize model selection UI\n function initializeModelSelection() {\n if (modelSelect) {\n // Get the parent container\n const chatHeader = document.querySelector(\".chat-header\");\n if (!chatHeader) return;\n \n // Create a fully custom dropdown instead of using the native select\n // First create a container for our custom dropdown\n const customDropdown = document.createElement(\"div\");\n customDropdown.className = \"custom-dropdown\";\n customDropdown.style.position = \"relative\";\n customDropdown.style.marginLeft = \"auto\";\n customDropdown.style.zIndex = \"100\";\n \n // Create the selected item display\n const selectedDisplay = document.createElement(\"div\");\n selectedDisplay.className = \"selected-model\";\n selectedDisplay.textContent = getModelDisplayName(currentModel);\n selectedDisplay.style.backgroundColor = \"#1e1e2e\";\n selectedDisplay.style.color = \"#ffffff\";\n selectedDisplay.style.padding = \"5px 28px 5px 10px\";\n selectedDisplay.style.borderRadius = \"4px\";\n selectedDisplay.style.border = \"1px solid #3a3a5a\";\n selectedDisplay.style.cursor = \"pointer\";\n selectedDisplay.style.width = \"120px\";\n selectedDisplay.style.position = \"relative\";\n selectedDisplay.style.fontSize = \"14px\";\n selectedDisplay.style.fontWeight = \"500\";\n \n // Add dropdown arrow\n selectedDisplay.style.backgroundImage = \"url(\\\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2379e1ff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\\\")\";\n selectedDisplay.style.backgroundRepeat = \"no-repeat\";\n selectedDisplay.style.backgroundPosition = \"right 8px center\";\n \n // Create the options container (hidden initially)\n const optionsContainer = document.createElement(\"div\");\n optionsContainer.className = \"model-options\";\n optionsContainer.style.display = \"none\";\n optionsContainer.style.position = \"absolute\";\n optionsContainer.style.top = \"100%\";\n optionsContainer.style.left = \"0\";\n optionsContainer.style.right = \"0\";\n optionsContainer.style.backgroundColor = \"#1e1e2e\";\n optionsContainer.style.border = \"1px solid #3a3a5a\";\n optionsContainer.style.borderRadius = \"4px\";\n optionsContainer.style.marginTop = \"4px\";\n optionsContainer.style.boxShadow = \"0 4px 8px rgba(0,0,0,0.2)\";\n optionsContainer.style.zIndex = \"101\";\n \n // Add options\n const models = [\n { value: \"doubao\", label: \"豆包\" },\n { value: \"hot\", label: \"Hot\" },\n { value: \"deepseek\", label: \"DeepSeek\" }\n ];\n \n models.forEach(model => {\n const option = document.createElement(\"div\");\n option.className = \"model-option\";\n option.dataset.value = model.value;\n option.textContent = model.label;\n option.style.padding = \"8px 10px\";\n option.style.cursor = \"pointer\";\n option.style.color = \"#ffffff\";\n \n // Style the currently selected option\n if (model.value === currentModel) {\n option.style.backgroundColor = \"#3a3a5a\";\n option.style.color = \"#79e1ff\";\n }\n \n // Add hover effect\n option.addEventListener(\"mouseover\", function() {\n if (model.value !== currentModel) {\n this.style.backgroundColor = \"#2a2a3a\";\n }\n });\n \n option.addEventListener(\"mouseout\", function() {\n if (model.value !== currentModel) {\n this.style.backgroundColor = \"\";\n }\n });\n \n // Add click event\n option.addEventListener(\"click\", function() {\n const newModel = this.dataset.value;\n if (newModel !== currentModel) {\n // Update the model\n currentModel = newModel;\n \n // Update selected display\n selectedDisplay.textContent = getModelDisplayName(currentModel);\n \n // Update options styling\n const allOptions = optionsContainer.querySelectorAll(\".model-option\");\n allOptions.forEach(opt => {\n if (opt.dataset.value === currentModel) {\n opt.style.backgroundColor = \"#3a3a5a\";\n opt.style.color = \"#79e1ff\";\n } else {\n opt.style.backgroundColor = \"\";\n opt.style.color = \"#ffffff\";\n }\n });\n \n // Update the badge\n updateModelBadge();\n \n // Initialize the client for the new model\n initializeOpenAIClient();\n \n // Add system message about model change\n const modelChangeMessage = `Hot:已切换至 ${getModelDisplayName(currentModel)} 模型`;\n addMessageToChatDOM(modelChangeMessage, \"bot\", null, true);\n \n console.log(`Model changed to: ${currentModel}`);\n }\n \n // Hide options after selection\n optionsContainer.style.display = \"none\";\n });\n \n optionsContainer.appendChild(option);\n });\n \n // Toggle options display on click\n selectedDisplay.addEventListener(\"click\", function() {\n if (optionsContainer.style.display === \"none\") {\n optionsContainer.style.display = \"block\";\n } else {\n optionsContainer.style.display = \"none\";\n }\n });\n \n // Hide options when clicking elsewhere\n document.addEventListener(\"click\", function(event) {\n if (!customDropdown.contains(event.target)) {\n optionsContainer.style.display = \"none\";\n }\n });\n \n // Add everything to the dropdown container\n customDropdown.appendChild(selectedDisplay);\n customDropdown.appendChild(optionsContainer);\n \n // Add the custom dropdown to the header\n chatHeader.appendChild(customDropdown);\n \n // Hide the original select\n modelSelect.style.display = \"none\";\n \n // Update model badge function\n function updateModelBadge() {\n // First check if badge already exists and remove it if so\n const existingBadge = document.getElementById(\"model-badge\");\n if (existingBadge) {\n existingBadge.remove();\n }\n \n const chatHeaderTitle = document.querySelector(\".chat-header h3\");\n if (chatHeaderTitle) {\n const modelBadge = document.createElement(\"span\");\n modelBadge.id = \"model-badge\";\n modelBadge.className = `model-badge ${currentModel}`;\n modelBadge.innerHTML = `<span class=\"model-badge-dot\"></span>${getModelDisplayName(currentModel)}`;\n chatHeaderTitle.appendChild(modelBadge);\n }\n }\n \n // Initialize the badge\n updateModelBadge();\n }\n }\n \n // Helper function to get display name for models\n function getModelDisplayName(modelKey) {\n const modelNames = {\n \"doubao\": \"豆包\",\n \"hot\": \"Hot\",\n \"deepseek\": \"DeepSeek\"\n };\n return modelNames[modelKey] || modelKey;\n }\n\n // Toggle chat widget visibility with smooth animation\n if (chatToggle) {\n chatToggle.addEventListener(\"click\", function() {\n if (!chatWidget) return;\n \n if (chatWidget.style.display === \"none\") {\n chatWidget.style.display = \"flex\";\n chatWidget.style.opacity = \"0\";\n chatWidget.style.transform = \"translateY(20px) scale(0.95)\";\n \n // Trigger reflow\n void chatWidget.offsetWidth;\n \n chatWidget.style.opacity = \"1\";\n chatWidget.style.transform = \"translateY(0) scale(1)\";\n \n if (chatInput) {\n setTimeout(() => chatInput.focus(), 300);\n }\n } else {\n chatWidget.style.opacity = \"0\";\n chatWidget.style.transform = \"translateY(20px) scale(0.95)\";\n \n setTimeout(() => {\n chatWidget.style.display = \"none\";\n }, 300);\n }\n });\n }\n \n // Close chat widget with animation\n if (closeChat) {\n closeChat.addEventListener(\"click\", function() {\n if (!chatWidget) return;\n \n chatWidget.style.opacity = \"0\";\n chatWidget.style.transform = \"translateY(20px) scale(0.95)\";\n \n setTimeout(() => {\n chatWidget.style.display = \"none\";\n chatWidget.style.transform = \"\";\n }, 300);\n });\n }\n \n // Load chat history from localStorage\n function loadChatHistory() {\n const saved = localStorage.getItem(CHAT_HISTORY_KEY);\n if (saved) {\n try {\n chatHistory = JSON.parse(saved);\n // Display loaded messages\n if (chatHistory.length > 0) {\n // Clear default message if we have history\n if (chatMessagesContainer) {\n chatMessagesContainer.innerHTML = \"\";\n }\n \n chatHistory.forEach(msg => {\n addMessageToChatDOM(msg.content, msg.sender, msg.timestamp);\n });\n }\n \n // Scroll to bottom\n scrollChatToBottom();\n \n } catch (error) {\n console.error(\"Error loading chat history:\", error);\n chatHistory = [];\n }\n }\n }\n \n // Save chat history to localStorage\n function saveChatHistory() {\n localStorage.setItem(CHAT_HISTORY_KEY, JSON.stringify(chatHistory));\n }\n \n // Updated getAIResponse function to call real AI models\n async function getAIResponse(userMessage) {\n // Show typing indicator immediately\n let typingIndicator = document.createElement(\"div\");\n typingIndicator.className = \"typing-indicator\";\n typingIndicator.innerHTML = `\n <div class=\"typing-dots\">\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n <span class=\"typing-dot\"></span>\n </div>\n `;\n if (chatMessagesContainer) {\n chatMessagesContainer.appendChild(typingIndicator);\n scrollChatToBottom();\n }\n \n isTypingResponse = true;\n \n try {\n // Get model display name\n const modelDisplayName = getModelDisplayName(currentModel);\n let botResponse = \"\";\n \n // Get recent conversation history for context - last 4 exchanges\n const recentHistory = chatHistory.slice(-8);\n const contextMessages = recentHistory.map(msg => ({\n role: msg.sender === \"user\" ? \"user\" : \"assistant\",\n content: msg.content.replace(/<[^>]*>/g, '') // Remove HTML tags from content\n }));\n \n // Create system prompt with business info - emphasizing natural responses\n const systemPrompt = {\n role: \"system\", \n content: \"你是Hot科技创新服务平台的Hot助手,你的职责是解答用户关于平台服务的问题以及其他类型的问题。平台提供的服务包括:数学建模代写(思路+图+代码:¥65,完整论文+图+代码:¥185,完全代写:¥500)、数学建模辅导、软著申请(220元/件)、专利撰写、作业服务和项目定制等。联系方式是微信号:yusan53001(推荐)、项目交付周期一般为:简单建模项目1-3天、标准建模项目3-7天、复杂定制项目7-15天。团队创始人是惠文清。必须注意:你的回答必须是完全自然的人类对话风格,无论什么情况都绝对禁止使用'-'、'*'、'#'或任何其他符号来制作列表。请使用流畅的自然语言,像人类客服一样表达,用句子描述而非标点制作列表。保持专业友好的态度,回答要简洁明了。这是最重要的要求:你的回复必须100%像人类而非AI,不要有任何机器人式的格式化文本。你可以回答与平台服务无关的普通问题,但优先提供与平台相关的信息。你的名字是Hot,不是智能助手或其他名称。\"\n };\n \n // Add all messages with system prompt first\n const messages = [systemPrompt, ...contextMessages, {role: \"user\", content: userMessage}];\n \n // Set up API parameters based on selected model\n let apiUrl, apiKey, modelName, headers;\n \n switch(currentModel) {\n case \"doubao\":\n apiUrl = DOUBAO_API_URL;\n apiKey = DOUBAO_API_KEY;\n modelName = \"doubao-1.5-pro-32k-250115\";\n headers = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n };\n break;\n \n case \"deepseek\":\n apiUrl = DEEPSEEK_API_URL;\n apiKey = DEEPSEEK_API_KEY;\n modelName = \"deepseek-chat\";\n headers = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n };\n break;\n \n case \"tongyi\":\n apiUrl = TONGYI_API_URL;\n apiKey = TONGYI_API_KEY;\n modelName = \"qwen-plus\";\n headers = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n };\n break;\n \n case \"hot\":\n apiUrl = HOT_API_URL;\n apiKey = HOT_API_KEY;\n modelName = \"qwen-plus\";\n headers = {\n \"Content-Type\": \"application/json\", \n \"Authorization\": `Bearer ${apiKey}`\n };\n break;\n \n default:\n // Fallback to Doubao\n apiUrl = DOUBAO_API_URL;\n apiKey = DOUBAO_API_KEY;\n modelName = \"doubao-1.5-pro-32k-250115\";\n headers = {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${apiKey}`\n };\n }\n \n // Call API with appropriate parameters\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout\n \n try {\n // Prepare the request body based on the selected model\n let requestBody = {\n model: modelName,\n messages: messages,\n temperature: 0.7,\n max_tokens: 800\n };\n \n // Add model-specific parameters\n if (currentModel === \"hot\" || currentModel === \"tongyi\") {\n // Hot和通义千问使用相同的百炼API\n console.log(\"使用百炼API,模型名称:\", modelName);\n }\n \n // For debugging\n console.log(`发送API请求到 ${apiUrl}`);\n console.log(\"请求头:\", headers);\n console.log(\"请求体:\", JSON.stringify(requestBody, null, 2));\n \n const response = await fetch(apiUrl, {\n method: \"POST\",\n headers: headers,\n body: JSON.stringify(requestBody),\n signal: controller.signal\n });\n \n clearTimeout(timeoutId);\n \n if (!response.ok) {\n let errorText = '';\n try {\n errorText = await response.text();\n console.error(`API错误: ${response.status}`, errorText);\n } catch (e) {\n console.error(`无法读取错误详情: ${e}`);\n }\n throw new Error(`API错误: ${response.status}`);\n }\n \n // Parse standard JSON response\n let data;\n try {\n data = await response.json();\n console.log(\"API响应:\", data); // 添加调试信息\n } catch (e) {\n console.error(\"解析JSON错误:\", e);\n throw new Error(\"解析API响应失败\");\n }\n \n if (data.choices && data.choices[0] && data.choices[0].message) {\n botResponse = data.choices[0].message.content;\n } else {\n console.error(\"API返回格式错误:\", data);\n throw new Error(\"API返回格式错误\");\n }\n } catch (error) {\n clearTimeout(timeoutId);\n throw error; // Re-throw to be caught by the outer try/catch\n }\n \n // Remove typing indicator\n if (typingIndicator && typingIndicator.parentNode) {\n typingIndicator.parentNode.removeChild(typingIndicator);\n }\n \n // Add model badge to response\n botResponse = `<span class=\"model-badge ${currentModel}\"><span class=\"model-badge-dot\"></span>${modelDisplayName}</span> ${botResponse}`;\n \n // Add the bot response to chat\n addMessageToChatDOM(botResponse, \"bot\", null, true);\n } catch (error) {\n console.error(\"Error getting AI response:\", error);\n \n // Remove typing indicator\n if (typingIndicator && typingIndicator.parentNode) {\n typingIndicator.parentNode.removeChild(typingIndicator);\n }\n \n // Add error message with fallback to simulateModelResponse\n try {\n const fallbackResponse = await simulateModelResponse(userMessage);\n const modelDisplayName = getModelDisplayName(currentModel);\n const errorMessage = `<span class=\"model-badge ${currentModel}\"><span class=\"model-badge-dot\"></span>${modelDisplayName}</span> ${fallbackResponse}`;\n addMessageToChatDOM(errorMessage, \"bot\", null, true);\n } catch (fallbackError) {\n // If even the fallback fails, show generic error\n const modelDisplayName = getModelDisplayName(currentModel);\n const errorMessage = `<span class=\"model-badge ${currentModel}\"><span class=\"model-badge-dot\"></span>Error</span> 抱歉,我是Hot,但当前连接出现问题。我们提供数学建模代写、软著申请等服务,您可以通过微信(yusan53001)联系我们获取详细信息。`;\n addMessageToChatDOM(errorMessage, \"bot\", null, true);\n }\n }\n \n isTypingResponse = false;\n }\n \n // Handle sending a message\n function sendMessage() {\n if (!chatInput || !chatInput.value.trim() || isTypingResponse) return;\n \n const userMessage = chatInput.value.trim();\n chatInput.value = \"\";\n \n // Add user message to chat\n addMessageToChatDOM(userMessage, \"user\", null, true);\n \n // Get AI response based on user message\n getAIResponse(userMessage);\n }\n\n // Add a message to the DOM with more reliable typing indicators\n function addMessageToChatDOM(message, sender, timestamp = null, shouldSave = false, withTypingAnimation = false) {\n if (!chatMessagesContainer) return;\n \n const messageTimestamp = timestamp || getCurrentTime();\n const messageDiv = document.createElement(\"div\");\n messageDiv.className = `chat-message ${sender}-message`;\n \n // Handle message content\n messageDiv.innerHTML = `\n <div class=\"message-content\">\n ${message}\n <span class=\"message-timestamp\">${messageTimestamp}</span>\n </div>\n `;\n \n chatMessagesContainer.appendChild(messageDiv);\n scrollChatToBottom();\n \n // Save to history if needed\n if (shouldSave) {\n chatHistory.push({\n content: message,\n sender: sender,\n timestamp: messageTimestamp\n });\n saveChatHistory();\n }\n }\n \n // Get current time in HH:MM format\n function getCurrentTime() {\n const now = new Date();\n return now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });\n }\n \n // Scroll chat to bottom\n function scrollChatToBottom() {\n if (chatMessagesContainer) {\n chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;\n }\n }\n \n // Enter key press in input\n if (chatInput) {\n chatInput.addEventListener(\"keypress\", function(event) {\n if (event.key === \"Enter\") {\n event.preventDefault();\n sendMessage();\n }\n });\n }\n \n // Send button click\n if (sendChat) {\n sendChat.addEventListener(\"click\", function() {\n sendMessage();\n });\n }\n \n // Quick questions\n if (quickQuestionsArea) {\n const quickQuestionBtns = quickQuestionsArea.querySelectorAll(\".quick-question-btn\");\n quickQuestionBtns.forEach(btn => {\n btn.addEventListener(\"click\", function() {\n if (chatInput) {\n chatInput.value = this.textContent;\n sendMessage();\n }\n });\n });\n }\n \n // Clear chat history\n if (clearChatHistoryBtn) {\n clearChatHistoryBtn.addEventListener(\"click\", function() {\n if (chatMessagesContainer) {\n chatMessagesContainer.innerHTML = \"\";\n chatHistory = [];\n saveChatHistory();\n addMessageToChatDOM(\"聊天记录已清除。我是Hot,有什么可以帮您的吗?\", \"bot\", null, true);\n }\n });\n }\n \n // Initialize chat\n if (chatWidget) {\n loadChatHistory();\n initializeModelSelection();\n \n // Update chat header title to \"Hot\"\n const chatHeaderTitle = document.querySelector(\".chat-header h3\");\n if (chatHeaderTitle) {\n // If there's text content directly in the h3 (not in child elements)\n const textNodes = Array.from(chatHeaderTitle.childNodes).filter(node => node.nodeType === 3);\n if (textNodes.length > 0) {\n // Replace \"智能助手\" with \"Hot\" if it exists\n for (let node of textNodes) {\n if (node.textContent.includes(\"智能助手\")) {\n node.textContent = node.textContent.replace(\"智能助手\", \"Hot\");\n }\n }\n }\n }\n \n // Add styles for the new model badges and resizable input\n const styleEl = document.createElement('style');\n styleEl.textContent = `\n /* Chat header styling */\n .chat-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 15px;\n }\n \n .chat-header h3 {\n margin: 0;\n display: flex;\n align-items: center;\n font-size: 16px;\n }\n \n /* Model badge styles */\n .model-badge {\n display: inline-flex;\n align-items: center;\n padding: 2px 6px;\n border-radius: 10px;\n font-size: 12px;\n font-weight: bold;\n margin-right: 8px;\n margin-left: 8px;\n opacity: 0.85;\n }\n \n .model-badge-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n margin-right: 4px;\n }\n \n /* Individual model styling */\n .model-badge.doubao {\n background-color: rgba(121, 225, 255, 0.25);\n color: #0099cc;\n }\n .model-badge.doubao .model-badge-dot {\n background-color: #0099cc;\n }\n \n .model-badge.hot {\n background-color: rgba(255, 61, 113, 0.25);\n color: #e91e63;\n }\n .model-badge.hot .model-badge-dot {\n background-color: #e91e63;\n }\n \n .model-badge.deepseek {\n background-color: rgba(29, 185, 84, 0.25);\n color: #1db954;\n }\n .model-badge.deepseek .model-badge-dot {\n background-color: #1db954;\n }\n \n /* Model select dropdown styling */\n #model-select {\n background-color: #1e1e2e;\n color: #ffffff;\n border: 1px solid #3a3a5a;\n padding: 5px 10px;\n border-radius: 4px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n -moz-appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2379e1ff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n padding-right: 28px;\n margin-left: auto;\n }\n \n #model-select:focus {\n outline: none;\n border-color: #79e1ff;\n box-shadow: 0 0 0 2px rgba(121, 225, 255, 0.25);\n }\n \n #model-select option {\n background-color: #1e1e2e;\n color: #ffffff;\n padding: 8px;\n }\n \n #model-select option:checked,\n #model-select option:hover,\n #model-select option:focus {\n background-color: #3a3a5a !important;\n color: #79e1ff !important;\n }\n \n /* Force custom styling for selected option by adding a class */\n .model-option-selected {\n background-color: #3a3a5a !important;\n color: #79e1ff !important;\n }\n \n /* Resizable chat input area */\n .chat-input-container {\n position: relative;\n padding: 10px;\n border-top: 1px solid #3a3a5a;\n }\n \n .chat-input-resize-handle {\n position: absolute;\n top: -10px;\n left: 50%;\n transform: translateX(-50%);\n width: 40px;\n height: 6px;\n background-color: #3a3a5a;\n border-radius: 3px;\n cursor: ns-resize;\n z-index: 10;\n opacity: 0.7;\n transition: opacity 0.2s, background-color 0.2s;\n }\n \n .chat-input-resize-handle:hover {\n opacity: 1;\n background-color: #79e1ff;\n }\n \n #chat-input {\n min-height: 40px;\n max-height: 200px;\n resize: none;\n transition: height 0.2s ease;\n }\n `;\n document.head.appendChild(styleEl);\n \n // Make chat input resizable\n setupResizableChatInput();\n }\n\n // Add resizable functionality to chat input\n function setupResizableChatInput() {\n const chatInput = document.getElementById(\"chat-input\");\n let chatInputContainer = document.querySelector(\".chat-input-container\");\n \n // If the chat-input-container doesn't exist, we need to create or identify it\n if (!chatInputContainer && chatInput) {\n // Check if the parent already has some container-like structure\n const possibleContainer = chatInput.closest(\".chat-footer\") || chatInput.parentElement;\n \n if (possibleContainer) {\n // Add the class to the existing parent\n possibleContainer.classList.add(\"chat-input-container\");\n chatInputContainer = possibleContainer;\n } else {\n // Create a new container and wrap the input\n chatInputContainer = document.createElement(\"div\");\n chatInputContainer.className = \"chat-input-container\";\n chatInput.parentNode.insertBefore(chatInputContainer, chatInput);\n chatInputContainer.appendChild(chatInput);\n }\n }\n \n if (!chatInput || !chatInputContainer) return;\n \n // Create resize handle\n const resizeHandle = document.createElement(\"div\");\n resizeHandle.className = \"chat-input-resize-handle\";\n chatInputContainer.insertBefore(resizeHandle, chatInputContainer.firstChild);\n \n // Variables for tracking resize state\n let isResizing = false;\n let startY = 0;\n let startHeight = 0;\n \n // Set initial height (if not already set)\n if (!chatInput.style.height || chatInput.style.height === \"auto\") {\n chatInput.style.height = \"40px\";\n }\n \n // Add event listeners for resize handle\n resizeHandle.addEventListener(\"mousedown\", function(e) {\n isResizing = true;\n startY = e.clientY;\n startHeight = parseInt(window.getComputedStyle(chatInput).height, 10);\n \n document.addEventListener(\"mousemove\", handleResize);\n document.addEventListener(\"mouseup\", stopResize);\n \n // Add active state class\n this.style.opacity = \"1\";\n this.style.backgroundColor = \"#79e1ff\";\n \n e.preventDefault();\n });\n \n // Handle resize during mouse movement\n function handleResize(e) {\n if (!isResizing) return;\n \n // Calculate the new height based on mouse movement (going up = increasing height)\n const deltaY = startY - e.clientY;\n const newHeight = Math.max(40, Math.min(200, startHeight + deltaY));\n \n // Apply the new height\n chatInput.style.height = newHeight + \"px\";\n \n // Scroll chat to bottom when resizing to ensure it stays in view\n const chatMessagesContainer = document.getElementById(\"chat-messages\");\n if (chatMessagesContainer) {\n chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;\n }\n }\n \n // Stop resizing on mouse up\n function stopResize() {\n isResizing = false;\n document.removeEventListener(\"mousemove\", handleResize);\n document.removeEventListener(\"mouseup\", stopResize);\n \n // Remove active state\n resizeHandle.style.opacity = \"0.7\";\n resizeHandle.style.backgroundColor = \"#3a3a5a\";\n }\n \n // Log that resizable chat input was set up\n console.log(\"Resizable chat input initialized\");\n }\n\n // --- Draggable and Resizable Chat Widget ---\n if (chatWidget && chatResizeHandle) {\n // Variable declarations for resize\n let isResizingWidget = false;\n let initialHeight, initialMouseY;\n \n // Chat resize handle functionality\n chatResizeHandle.addEventListener(\"mousedown\", (e) => {\n isResizingWidget = true; \n document.body.style.userSelect = \"none\"; // Prevent text selection during resize\n\n initialHeight = chatWidget.offsetHeight;\n initialMouseY = e.clientY;\n \n document.addEventListener(\"mousemove\", onWidgetResize, { passive: false });\n document.addEventListener(\"mouseup\", onWidgetResizeEnd);\n e.preventDefault();\n });\n\n function onWidgetResize(e) {\n if (!isResizingWidget) return;\n const newHeight = initialHeight + (e.clientY - initialMouseY);\n chatWidget.style.height = Math.max(450, Math.min(newHeight, window.innerHeight * 0.9)) + \"px\";\n e.preventDefault();\n }\n \n function onWidgetResizeEnd() {\n if (!isResizingWidget) return;\n isResizingWidget = false; \n document.body.style.userSelect = \"auto\";\n document.removeEventListener(\"mousemove\", onWidgetResize);\n document.removeEventListener(\"mouseup\", onWidgetResizeEnd);\n }\n }\n\n // --- Dynamic Clock Logic ---\n // Clock functionality removed\n /*\n const clockContainer = document.getElementById(\"dynamic-clock-container\");\n if (clockContainer) {\n const clockEl = document.createElement(\"div\");\n clockEl.className = \"dynamic-clock\";\n \n // Create digital clock display only\n clockContainer.appendChild(clockEl);\n\n function updateClock() {\n const now = new Date();\n const hours = now.getHours().toString().padStart(2, '0');\n const minutes = now.getMinutes().toString().padStart(2, '0');\n const seconds = now.getSeconds().toString().padStart(2, '0');\n \n // Display time in HH:MM:SS format\n clockEl.textContent = `${hours}:${minutes}:${seconds}`;\n }\n \n // Update every second\n setInterval(updateClock, 1000);\n updateClock(); // Initial call\n }\n */\n\n // --- Initializations ---\n loadChatHistory(); // Load chat history when DOM is ready\n\n // Remove redundant initialization - we already activate first section in nav code\n // if (navLinks.length > 0) {\n // navLinks[0].click(); // Simulate click to ensure active state and animation\n // }\n\n // Initialize variables for chat resize handle\n let isResizingChat = false;\n let initialChatHeight, initialMouseY;\n\n // Add resize handle to chat widget\n function addChatResizeHandle() {\n if (!chatWidget) return;\n \n // Add resize handle if it doesn't exist\n let resizeHandle = document.getElementById(\"chat-resize-handle\");\n if (!resizeHandle) {\n resizeHandle = document.createElement(\"div\");\n resizeHandle.id = \"chat-resize-handle\";\n resizeHandle.innerHTML = '<div class=\"resize-indicator\"></div>';\n chatWidget.appendChild(resizeHandle);\n }\n \n // Add resize functionality\n resizeHandle.addEventListener(\"mousedown\", (e) => {\n if (chatWidget.classList.contains(\"expanded\")) return; // Don't resize in expanded mode\n \n isResizingChat = true;\n initialChatHeight = chatWidget.offsetHeight;\n initialMouseY = e.clientY;\n \n document.addEventListener(\"mousemove\", handleChatResize);\n document.addEventListener(\"mouseup\", stopChatResize);\n e.preventDefault();\n });\n }\n\n // Handle chat resize\n function handleChatResize(e) {\n if (!isResizingChat) return;\n \n const newHeight = initialChatHeight + (e.clientY - initialMouseY);\n chatWidget.style.height = Math.max(350, Math.min(newHeight, window.innerHeight * 0.85)) + \"px\";\n \n // Ensure chat messages area is scrolled to bottom when resizing\n if (chatMessagesContainer) {\n chatMessagesContainer.scrollTop = chatMessagesContainer.scrollHeight;\n }\n \n e.preventDefault();\n }\n\n // Stop chat resize\n function stopChatResize() {\n isResizingChat = false;\n document.removeEventListener(\"mousemove\", handleChatResize);\n document.removeEventListener(\"mouseup\", stopChatResize);\n }\n\n // Call this function after DOM is loaded\n document.addEventListener(\"DOMContentLoaded\", () => {\n // Initialize chat widget\n if (chatWidget) {\n addChatResizeHandle();\n \n // Add CSS for resize handle\n const style = document.createElement('style');\n style.textContent = `\n #chat-resize-handle {\n width: 100%;\n height: 8px;\n background-color: transparent;\n cursor: ns-resize;\n position: absolute;\n bottom: 0;\n left: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n \n #chat-resize-handle .resize-indicator {\n width: 40px;\n height: 4px;\n background-color: #3a3a5a;\n border-radius: 2px;\n opacity: 0.7;\n transition: opacity 0.2s ease;\n }\n \n #chat-resize-handle:hover .resize-indicator {\n opacity: 1;\n background-color: #61dafb;\n }\n \n #chat-widget.expanded #chat-resize-handle {\n display: none;\n }\n `;\n document.head.appendChild(style);\n }\n });\n\n // 添加新功能:语音输入、消息朗读、表情解析等\n function enhanceChatExperience() {\n // 1. 添加语音输入按钮\n const chatInputArea = document.querySelector('.chat-input-area');\n const voiceInputBtn = document.createElement('button');\n voiceInputBtn.className = 'voice-input-btn';\n voiceInputBtn.innerHTML = '<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z\"></path><path d=\"M19 10v2a7 7 0 0 1-14 0v-2\"></path><line x1=\"12\" y1=\"19\" x2=\"12\" y2=\"23\"></line><line x1=\"8\" y1=\"23\" x2=\"16\" y2=\"23\"></line></svg>';\n voiceInputBtn.title = '语音输入';\n \n // 在输入框前插入语音输入按钮\n chatInputArea.insertBefore(voiceInputBtn, document.getElementById('chat-input'));\n\n // 2. 添加语音朗读按钮\n const readAloudBtn = document.createElement('button');\n readAloudBtn.className = 'read-aloud-btn';\n readAloudBtn.innerHTML = '<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polygon points=\"11 5 6 9 2 9 2 15 6 15 11 19 11 5\"></polygon><path d=\"M15.54 8.46a5 5 0 0 1 0 7.07\"></path><path d=\"M19.07 4.93a10 10 0 0 1 0 14.14\"></path></svg>';\n readAloudBtn.title = '朗读消息';\n \n // 在发送按钮前插入朗读按钮\n chatInputArea.insertBefore(readAloudBtn, document.getElementById('send-chat-btn'));\n\n // 3. 添加清除输入按钮\n const clearInputBtn = document.createElement('button');\n clearInputBtn.className = 'clear-input-btn';\n clearInputBtn.innerHTML = '<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"></line><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"></line></svg>';\n clearInputBtn.title = '清除输入';\n clearInputBtn.style.display = 'none'; // 默认隐藏\n \n // 在输入框后插入清除按钮\n document.getElementById('chat-input').insertAdjacentElement('afterend', clearInputBtn);\n\n // 4. 实现语音输入功能\n voiceInputBtn.addEventListener('click', startVoiceRecognition);\n\n // 5. 实现朗读功能\n readAloudBtn.addEventListener('click', readLastBotMessage);\n \n // 6. 实现清除输入功能\n clearInputBtn.addEventListener('click', () => {\n document.getElementById('chat-input').value = '';\n clearInputBtn.style.display = 'none';\n });\n \n // 7. 监听输入变化,显示或隐藏清除按钮\n document.getElementById('chat-input').addEventListener('input', () => {\n clearInputBtn.style.display = document.getElementById('chat-input').value.trim() ? 'flex' : 'none';\n });\n }\n\n // 语音识别功能\n function startVoiceRecognition() {\n const voiceInputBtn = document.querySelector('.voice-input-btn');\n const chatInput = document.getElementById('chat-input');\n \n if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {\n alert('您的浏览器不支持语音识别功能');\n return;\n }\n \n const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;\n const recognition = new SpeechRecognition();\n recognition.lang = 'zh-CN';\n recognition.interimResults = true;\n \n // 改变按钮样式,表示正在录音\n voiceInputBtn.classList.add('recording');\n \n recognition.onresult = (event) => {\n const transcript = Array.from(event.results)\n .map(result => result[0])\n .map(result => result.transcript)\n .join('');\n \n chatInput.value = transcript;\n // 触发输入事件以显示清除按钮\n chatInput.dispatchEvent(new Event('input'));\n };\n \n recognition.onend = () => {\n voiceInputBtn.classList.remove('recording');\n };\n \n recognition.onerror = (event) => {\n console.error('语音识别错误:', event.error);\n voiceInputBtn.classList.remove('recording');\n };\n \n recognition.start();\n }\n\n // 语音朗读功能\n function readLastBotMessage() {\n const readAloudBtn = document.querySelector('.read-aloud-btn');\n const botMessages = document.querySelectorAll('.bot-message .message-content');\n \n if (botMessages.length === 0) return;\n \n // 获取最后一条机器人消息\n const lastBotMessage = botMessages[botMessages.length - 1];\n const messageText = lastBotMessage.textContent.replace(/刚刚|几秒前|\\d+分钟前/g, '').trim();\n \n if (!messageText) return;\n \n // 检查浏览器是否支持语音合成\n if (!('speechSynthesis' in window)) {\n alert('您的浏览器不支持语音合成功能');\n return;\n }\n \n // 停止任何正在进行的语音\n window.speechSynthesis.cancel();\n \n // 创建语音合成实例\n const utterance = new SpeechSynthesisUtterance(messageText);\n utterance.lang = 'zh-CN';\n utterance.rate = 1.0;\n utterance.pitch = 1.0;\n \n // 高亮显示正在朗读的消息\n lastBotMessage.classList.add('reading');\n readAloudBtn.classList.add('reading');\n \n utterance.onend = () => {\n lastBotMessage.classList.remove('reading');\n readAloudBtn.classList.remove('reading');\n };\n \n // 开始朗读\n window.speechSynthesis.speak(utterance);\n }\n\n // 添加emoji表情解析功能\n function parseEmoji(text) {\n // 基本表情映射\n const emojiMap = {\n ':)': '😊', ':-)': '😊', ':D': '😃', ':-D': '😃',\n ':(': '😞', ':-(': '😞', ';)': '😉', ';-)': '😉',\n ':p': '😛', ':-p': '😛', ':P': '😛', ':-P': '😛',\n ':o': '😮', ':-o': '😮', ':O': '😮', ':-O': '😮',\n '<3': '❤️', '</3': '💔',\n ':+1:': '👍', ':-1:': '👎',\n ':laugh:': '🤣', ':smile:': '😊', ':sad:': '😔',\n ':cry:': '😢', ':think:': '🤔', ':cool:': '😎',\n ':ok:': '👌', ':fire:': '🔥', ':star:': '⭐',\n ':wave:': '👋', ':hug:': '🤗', ':eyes:': '👀'\n };\n \n // 替换表情符号\n Object.keys(emojiMap).forEach(key => {\n const regex = new RegExp(key.replace(/([.*+?^=!:${}()|\\[\\]\\/\\\\])/g, \"\\\\$1\"), 'g');\n text = text.replace(regex, emojiMap[key]);\n });\n \n return text;\n }\n\n // 智能分段处理消息\n function formatMessageWithParagraphs(message) {\n if (!message || typeof message !== 'string') return message;\n \n // 分割长句,保持每个段落有合适的长度\n const sentences = message.split(/(?<=[。!?.!?])\\s*/);\n let paragraphs = [];\n let currentParagraph = '';\n \n sentences.forEach(sentence => {\n // 如果当前段落加上这个句子超过一定长度,就开始新段落\n if (currentParagraph && (currentParagraph.length + sentence.length > 100)) {\n paragraphs.push(currentParagraph);\n currentParagraph = sentence;\n } else {\n currentParagraph += sentence;\n }\n \n // 如果是空行或明显的段落分隔符,创建新段落\n if (sentence.match(/\\n\\s*\\n/) || sentence.trim() === '') {\n if (currentParagraph.trim() !== '') {\n paragraphs.push(currentParagraph);\n currentParagraph = '';\n }\n }\n });\n \n // 添加最后一个段落\n if (currentParagraph.trim() !== '') {\n paragraphs.push(currentParagraph);\n }\n \n // 将段落合并为HTML\n return paragraphs.map(p => `<p>${p.trim()}</p>`).join('');\n }\n\n // 修改原有的addMessage函数,使用新功能\n function addMessage(role, content, timestamp = '刚刚', addToHistory = true, modelType = null) {\n const messagesContainer = document.getElementById('chat-messages');\n const messageDiv = document.createElement('div');\n \n // 应用Emoji解析\n content = parseEmoji(content);\n \n // 应用段落格式化\n const formattedContent = formatMessageWithParagraphs(content);\n \n messageDiv.className = `chat-message ${role === 'user' ? 'user-message' : 'bot-message'}`;\n \n let messageContent = `\n <div class=\"message-content\">\n ${formattedContent || content}\n <span class=\"message-timestamp\">${timestamp}</span>\n </div>\n `;\n \n // 如果是机器人消息且有模型类型,添加模型标记\n if (role === 'bot' && modelType) {\n messageContent = `\n <div class=\"message-content\">\n ${formattedContent || content}\n <span class=\"message-timestamp\">${timestamp}</span>\n <span class=\"model-badge ${modelType}\">\n <span class=\"model-badge-dot\"></span>${getModelDisplayName(modelType)}\n </span>\n </div>\n `;\n }\n \n messageDiv.innerHTML = messageContent;\n messagesContainer.appendChild(messageDiv);\n messagesContainer.scrollTop = messagesContainer.scrollHeight;\n \n // 如果需要,将消息添加到历史记录\n if (addToHistory) {\n chatHistory.push({\n role: role === 'user' ? 'user' : 'assistant',\n content: content\n });\n \n // 保存聊天历史到本地存储\n localStorage.setItem('chatHistory', JSON.stringify(chatHistory));\n }\n \n return messageDiv;\n }\n\n // 初始化增强功能\n enhanceChatExperience();\n\n // --- Enhanced FAQ Accordion and Search Logic ---\n function initFAQSection() {\n const faqItems = document.querySelectorAll(\".faq-item\");\n const faqSearch = document.getElementById(\"faq-search\");\n const categoryTabs = document.querySelectorAll(\".faq-category-tab\");\n const noResultsMessage = document.querySelector(\".faq-no-results\");\n \n let currentCategory = \"all\";\n \n // Toggle FAQ items\n faqItems.forEach(item => {\n const question = item.querySelector(\".faq-question\");\n \n if (question) {\n question.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n toggleFAQ(item);\n });\n \n // Make the entire item clickable, not just the question\n item.addEventListener(\"click\", () => {\n toggleFAQ(item);\n });\n \n // Accessibility: toggle with Enter/Space\n question.addEventListener(\"keydown\", (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n toggleFAQ(item);\n }\n });\n }\n });\n \n function toggleFAQ(item) {\n // If the clicked item is already active and another item is being clicked, \n // we'll leave it open for better UX\n // const isActive = item.classList.contains(\"active\");\n \n // Optional: close other open FAQs\n // if (!isActive) {\n // faqItems.forEach(otherItem => {\n // if (otherItem !== item && otherItem.classList.contains(\"active\")) {\n // otherItem.classList.remove(\"active\");\n // }\n // });\n // }\n \n item.classList.toggle(\"active\");\n }\n \n // Filter by category\n if (categoryTabs) {\n categoryTabs.forEach(tab => {\n tab.addEventListener(\"click\", () => {\n categoryTabs.forEach(t => t.classList.remove(\"active\"));\n tab.classList.add(\"active\");\n \n currentCategory = tab.dataset.category;\n filterFAQs();\n });\n });\n }\n \n // Search functionality\n if (faqSearch) {\n faqSearch.addEventListener(\"input\", () => {\n filterFAQs();\n });\n }\n \n function filterFAQs() {\n const searchTerm = faqSearch ? faqSearch.value.trim().toLowerCase() : '';\n let visibleCount = 0;\n \n faqItems.forEach(item => {\n const question = item.querySelector(\".faq-question\").textContent.toLowerCase();\n const answer = item.querySelector(\".faq-answer\").textContent.toLowerCase();\n const category = item.getAttribute(\"data-category\");\n \n const matchesSearch = searchTerm === '' || \n question.includes(searchTerm) || \n answer.includes(searchTerm);\n \n const matchesCategory = currentCategory === \"all\" || category === currentCategory;\n \n if (matchesSearch && matchesCategory) {\n item.style.display = \"block\";\n visibleCount++;\n \n // Highlight search matches in question text\n if (searchTerm !== '') {\n highlightText(item.querySelector(\".faq-question\"), searchTerm);\n } else {\n // Remove highlights if search is cleared\n item.querySelector(\".faq-question\").innerHTML = \n item.querySelector(\".faq-question\").textContent;\n }\n } else {\n item.style.display = \"none\";\n }\n });\n \n // Show no results message if needed\n if (noResultsMessage) {\n noResultsMessage.style.display = visibleCount === 0 ? \"block\" : \"none\";\n }\n }\n \n function highlightText(element, term) {\n const text = element.textContent;\n const highlightedText = text.replace(\n new RegExp(term, 'gi'), \n match => `<span class=\"highlight\">${match}</span>`\n );\n element.innerHTML = highlightedText;\n }\n }\n \n // Initialize FAQ functionality\n initFAQSection();\n\n // 图片延迟加载函数\n function lazyLoadImages() {\n console.log(\"直接加载所有图片...\");\n \n // 获取所有需要加载的图片\n const images = document.querySelectorAll('img[data-src]');\n console.log(\"找到图片数量:\", images.length);\n \n // 直接替换src属性\n images.forEach(img => {\n const src = img.getAttribute('data-src');\n if (src) {\n console.log(\"加载图片:\", src);\n // 直接设置图片源\n img.src = src;\n img.style.opacity = '1';\n img.style.filter = 'none';\n img.style.transform = 'scale(1)';\n img.classList.add('loaded');\n \n // 移除data-src属性\n img.removeAttribute('data-src');\n }\n });\n \n // 确保所有图片容器都可见\n document.querySelectorAll('.certificate-card, .paper-card').forEach(card => {\n card.style.opacity = '1';\n card.style.animation = 'none';\n });\n \n // 特别为证书和论文图片设置样式\n document.querySelectorAll('.certificate-image, .paper-image').forEach(img => {\n img.style.opacity = '1';\n img.style.filter = 'none';\n img.style.maxHeight = '100%';\n img.style.objectFit = 'contain';\n img.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';\n });\n }\n\n // 立即加载所有图片\n lazyLoadImages();\n});\n\n// Add simulateModelResponse function back for fallback when API isn't available\nasync function simulateModelResponse(userMessage) {\n const lowerMessage = userMessage.toLowerCase();\n const modelDisplayName = getModelDisplayName(currentModel);\n \n if (lowerMessage.includes(\"创始人\") || lowerMessage.includes(\"老板\") || lowerMessage.includes(\"谁创建\") || lowerMessage.includes(\"谁创办\") || lowerMessage.includes(\"惠文清\")) {\n return `我们团队的创始人是王文清。她创建了Hot科技创新服务平台,致力于为客户提供高质量的技术服务和解决方案。`;\n } else if (lowerMessage.includes(\"服务\") || lowerMessage.includes(\"提供\") || lowerMessage.includes(\"做什么\")) {\n return `我们提供多种服务,包括数学建模代写、数学建模辅导、软著申请、专利撰写、作业服务和项目定制等。您可以在网页首页查看更多详情。`;\n } else if (lowerMessage.includes(\"数学建模\") && (lowerMessage.includes(\"收费\") || lowerMessage.includes(\"价格\") || lowerMessage.includes(\"费用\"))) {\n return `我们的数学建模服务根据需求不同有不同的价格。思路加图加代码是75元,完整论文加图加代码是185元,完全代写是500元。如果您有特殊需求,欢迎与我们沟通。`;\n } else if (lowerMessage.includes(\"联系\") || lowerMessage.includes(\"微信\") || lowerMessage.includes(\"电话\") || lowerMessage.includes(\"邮箱\")) {\n return `您可以通过微信号yusan53001联系我们,这是推荐的联系方式。您也可以发送邮件至contact@hotshop.com,或者直接在网站底部填写预约表单,我们会尽快与您联系。`;\n } else if (lowerMessage.includes(\"周期\") || lowerMessage.includes(\"多久\") || lowerMessage.includes(\"多长时间\")) {\n return `项目交付周期取决于具体需求和项目复杂度。一般来说,简单建模项目需要1-3天,标准建模项目需要3-7天,复杂定制项目需要7-15天。我们也提供加急服务,可根据您的时间要求灵活安排。`;\n } else if (lowerMessage.includes(\"软著\") || lowerMessage.includes(\"软件著作权\")) {\n return `我们的软著申请服务为220元一件,包括全程代办。从提交资料到获得证书一般需要20-30个工作日,我们会负责整个流程,让您无需操心。`;\n } else if (lowerMessage.includes(\"专利\")) {\n return `我们提供专利撰写服务,包括发明专利和实用新型专利。具体价格根据技术领域和复杂度有所不同,建议您直接联系我们进行咨询,以获取最准确的报价。`;\n } else if (lowerMessage.includes(\"作业\") || lowerMessage.includes(\"代写\")) {\n return `我们提供各类编程、数据分析等相关作业的辅助与定制服务。价格根据工作量和难度而定,欢迎详细咨询具体需求,我们会为您提供合理的报价方案。`;\n } else if (lowerMessage.includes(\"你好\") || lowerMessage.includes(\"您好\") || lowerMessage.includes(\"hi\") || lowerMessage.includes(\"hello\")) {\n return `您好!很高兴为您服务。我是Hot,当前使用${modelDisplayName}模型,可以回答您关于我们服务的各种问题以及其他一般性问题。请问有什么可以帮助您的呢?`;\n } else if (lowerMessage.includes(\"模型\") || lowerMessage.includes(\"切换\") || lowerMessage.includes(\"ai\")) {\n return `当前使用的是${modelDisplayName}模型。您可以通过顶部的下拉菜单选择不同的AI模型进行对话,包括豆包、Hot和DeepSeek。`;\n } else if (lowerMessage.includes(\"天气\") || lowerMessage.includes(\"日期\") || lowerMessage.includes(\"时间\")) {\n return `很抱歉,作为Hot,我无法获取实时的天气、日期或时间信息。不过我可以帮您解答关于我们服务的任何疑问,或者与您聊聊其他话题。`;\n } else if (lowerMessage.includes(\"笑话\") || lowerMessage.includes(\"讲个笑话\") || lowerMessage.includes(\"讲笑话\")) {\n return `程序员去咖啡店点咖啡,服务员问他要什么,他说\\\"要一杯咖啡\\\"。服务员问\\\"要加糖吗?\\\",程序员回答:\\\"不,谢谢,我喜欢处理没有警告的错误。\\\"`;\n } else {\n // 尝试回答更一般的问题\n if (lowerMessage.includes(\"什么\") || lowerMessage.includes(\"怎么\") || lowerMessage.includes(\"如何\") || lowerMessage.includes(\"为什么\") || lowerMessage.length > 10) {\n return `您问了一个很有趣的问题。我可以尽力回答,但可能无法像专业的搜索引擎那样提供全面的信息。如果您有关于我们服务的问题,比如数学建模或者软著申请,我会很乐意为您解答。我们的创始人惠文清一直要求我们提供专业和友好的服务。`;\n }\n return `感谢您的咨询。我很乐意回答您关于Hot科技服务的任何问题。您也可以通过微信yusan53001联系我们的顾问,获取更详细和个性化的解答。惠文清创始人始终强调,良好的客户体验是我们最重要的目标。`;\n }\n}\n\n// --- 页面缩放控制器功能 ---\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n // 获取缩放控制器元素\n const zoomController = document.getElementById(\"zoom-controller\");\n const zoomInBtn = document.getElementById(\"zoom-in\");\n const zoomOutBtn = document.getElementById(\"zoom-out\");\n const zoomResetBtn = document.getElementById(\"zoom-reset\");\n const zoomLevelDisplay = document.getElementById(\"zoom-level\");\n const mainContent = document.getElementById(\"main-content-area\");\n \n // 初始化缩放级别\n let currentZoomLevel = 100;\n const minZoom = 50;\n const maxZoom = 200;\n const zoomStep = 10;\n \n // 应用缩放效果\n function applyZoom(zoomLevel) {\n // 限制缩放范围\n currentZoomLevel = Math.min(maxZoom, Math.max(minZoom, zoomLevel));\n \n // 更新显示\n zoomLevelDisplay.textContent = `${currentZoomLevel}%`;\n \n // 应用缩放到内容区域\n mainContent.style.transform = `scale(${currentZoomLevel / 100})`;\n \n // 保存当前缩放级别到本地存储\n localStorage.setItem(\"hotshop_zoom_level\", currentZoomLevel);\n }\n \n // 从本地存储加载缩放级别\n function loadSavedZoom() {\n const savedZoom = localStorage.getItem(\"hotshop_zoom_level\");\n if (savedZoom) {\n applyZoom(parseInt(savedZoom));\n }\n }\n \n // 放大功能\n if (zoomInBtn) {\n zoomInBtn.addEventListener(\"click\", function() {\n applyZoom(currentZoomLevel + zoomStep);\n });\n }\n \n // 缩小功能\n if (zoomOutBtn) {\n zoomOutBtn.addEventListener(\"click\", function() {\n applyZoom(currentZoomLevel - zoomStep);\n });\n }\n \n // 重置功能\n if (zoomResetBtn) {\n zoomResetBtn.addEventListener(\"click\", function() {\n applyZoom(100);\n });\n }\n \n // 键盘快捷键支持\n document.addEventListener(\"keydown\", function(e) {\n // Ctrl + Plus: 放大\n if (e.ctrlKey && (e.key === \"+\" || e.key === \"=\")) {\n e.preventDefault();\n applyZoom(currentZoomLevel + zoomStep);\n }\n // Ctrl + Minus: 缩小\n else if (e.ctrlKey && e.key === \"-\") {\n e.preventDefault();\n applyZoom(currentZoomLevel - zoomStep);\n }\n // Ctrl + 0: 重置\n else if (e.ctrlKey && e.key === \"0\") {\n e.preventDefault();\n applyZoom(100);\n }\n });\n \n // 应用保存的缩放级别\n loadSavedZoom();\n});\n\n// --- Visitor Counter Function ---\nfunction initVisitorCounter() {\n const visitorCountElement = document.getElementById('visitor-count');\n const todayCountElement = document.getElementById('today-count');\n const mainVisitorCountElement = document.getElementById('main-visitor-count');\n const mainTodayCountElement = document.getElementById('main-today-count');\n \n // Check if elements exist\n if ((!visitorCountElement && !mainVisitorCountElement) || \n (!todayCountElement && !mainTodayCountElement)) return;\n \n // Get visitor data from localStorage\n let visitorData = localStorage.getItem('hotshop_visitor_data');\n let todayVisits = 0;\n let totalVisits = 0;\n \n if (visitorData) {\n try {\n const data = JSON.parse(visitorData);\n const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format\n \n // Check if we already have data for today\n if (data.lastVisit === today) {\n todayVisits = data.todayVisits + 1;\n } else {\n todayVisits = 1;\n }\n \n totalVisits = data.totalVisits + 1;\n \n // Store updated data\n localStorage.setItem('hotshop_visitor_data', JSON.stringify({\n lastVisit: today,\n todayVisits: todayVisits,\n totalVisits: totalVisits\n }));\n } catch (e) {\n console.error('Error parsing visitor data:', e);\n resetVisitorData();\n }\n } else {\n // First visit, initialize data\n resetVisitorData();\n }\n \n // Format numbers and immediately display them\n const totalVisitsFormatted = totalVisits.toLocaleString();\n const todayVisitsFormatted = todayVisits.toLocaleString();\n \n // Update all counter elements if they exist - with immediate animation\n if (visitorCountElement) {\n visitorCountElement.textContent = totalVisitsFormatted;\n animateCounterValue(visitorCountElement);\n }\n \n if (todayCountElement) {\n todayCountElement.textContent = todayVisitsFormatted;\n animateCounterValue(todayCountElement);\n }\n \n if (mainVisitorCountElement) {\n mainVisitorCountElement.textContent = totalVisitsFormatted;\n animateCounterValue(mainVisitorCountElement);\n }\n \n if (mainTodayCountElement) {\n mainTodayCountElement.textContent = todayVisitsFormatted;\n animateCounterValue(mainTodayCountElement);\n }\n \n // Create a fixed 2-second interval for visitor count to simulate real-time changes\n simulateRealTimeVisits(totalVisits, todayVisits);\n \n function resetVisitorData() {\n const today = new Date().toISOString().split('T')[0];\n // Generate a random base number for more realism\n const randomBase = Math.floor(Math.random() * 5000) + 10000;\n \n localStorage.setItem('hotshop_visitor_data', JSON.stringify({\n lastVisit: today,\n todayVisits: 1,\n totalVisits: randomBase\n }));\n \n todayVisits = 1;\n totalVisits = randomBase;\n }\n \n // 简化的计数器更新函数,去除不必要的动画效果\n function animateCounterValue(element) {\n if (!element) return;\n \n // 只在初始化时添加一次动画效果\n if (!element.classList.contains('initialized')) {\n element.classList.add('animated');\n element.classList.add('initialized');\n \n // 只设置一次阴影效果,不再频繁更改\n element.style.textShadow = '0 0 10px rgba(255, 61, 113, 0.5)';\n }\n }\n \n function simulateRealTimeVisits(totalCount, todayCount) {\n // 增加时间间隔,减少更新频率,15-20秒一次\n const getRandomInterval = () => Math.floor(Math.random() * 5000) + 15000; // 15000-20000 ms (15-20 seconds)\n \n const incrementVisits = () => {\n // Increment by 1-3 visitors at a time for total count\n const totalIncrement = Math.floor(Math.random() * 3) + 1;\n totalCount += totalIncrement;\n \n // 降低今日访问数更新概率\n const shouldIncrementToday = Math.random() < 0.5;\n if (shouldIncrementToday) {\n const todayIncrement = Math.ceil(totalIncrement * 0.5);\n todayCount += todayIncrement;\n \n // 更新今日计数,但不使用动画效果\n const todayFormatted = todayCount.toLocaleString();\n \n if (todayCountElement) {\n todayCountElement.textContent = todayFormatted;\n // 删除动画效果调用\n }\n \n if (mainTodayCountElement) {\n mainTodayCountElement.textContent = todayFormatted;\n // 删除动画效果调用\n }\n }\n \n // 更新总计数,但不使用动画效果\n const totalFormatted = totalCount.toLocaleString();\n \n if (visitorCountElement) {\n visitorCountElement.textContent = totalFormatted;\n // 删除动画效果调用\n }\n \n if (mainVisitorCountElement) {\n mainVisitorCountElement.textContent = totalFormatted;\n // 删除动画效果调用\n }\n \n // Update localStorage with new values\n const today = new Date().toISOString().split('T')[0];\n localStorage.setItem('hotshop_visitor_data', JSON.stringify({\n lastVisit: today,\n todayVisits: todayCount,\n totalVisits: totalCount\n }));\n \n // Schedule next update with variable interval for more natural feeling\n setTimeout(incrementVisits, getRandomInterval());\n };\n \n // Start the simulation immediately with random interval\n setTimeout(incrementVisits, getRandomInterval());\n }\n}\n\n// 添加页面加载完成后的处理\nwindow.onload = function() {\n console.log(\"页面完全加载完成,确保所有图片显示\");\n \n // 确保所有图片立即显示\n document.querySelectorAll('img').forEach(img => {\n // 强制设置样式\n img.style.display = 'block';\n img.style.visibility = 'visible';\n img.style.opacity = '1';\n \n // 如果发现任何没有加载的图片,重新加载它\n if (img.complete === false || img.naturalWidth === 0) {\n console.log(\"发现未完全加载的图片,重新加载:\", img.src);\n const currentSrc = img.src;\n img.src = \"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\";\n setTimeout(() => {\n img.src = currentSrc;\n }, 100);\n }\n });\n \n // 专门处理证书图片\n document.querySelectorAll('.certificate-image, .paper-image').forEach(img => {\n img.style.maxWidth = '100%';\n img.style.height = 'auto';\n img.style.maxHeight = '380px';\n img.style.objectFit = 'contain';\n });\n \n // 确保证书容器显示\n document.querySelectorAll('.certificate-card, .paper-card').forEach(card => {\n card.style.opacity = '1';\n card.style.display = 'block';\n });\n};\n\n// 安全相关功能\ndocument.addEventListener('DOMContentLoaded', function() {\n // 处理安全相关模态框\n const privacyLink = document.getElementById('privacy-policy-link');\n const termsLink = document.getElementById('terms-link');\n const securityLink = document.getElementById('security-link');\n \n const privacyModal = document.getElementById('privacy-policy-modal');\n const termsModal = document.getElementById('terms-modal');\n const securityModal = document.getElementById('security-modal');\n \n // 隐私政策模态框\n if (privacyLink && privacyModal) {\n privacyLink.addEventListener('click', function(e) {\n e.preventDefault();\n privacyModal.style.display = 'flex';\n });\n }\n \n // 服务条款模态框\n if (termsLink && termsModal) {\n termsLink.addEventListener('click', function(e) {\n e.preventDefault();\n termsModal.style.display = 'flex';\n });\n }\n \n // 安全说明模态框\n if (securityLink && securityModal) {\n securityLink.addEventListener('click', function(e) {\n e.preventDefault();\n securityModal.style.display = 'flex';\n });\n }\n \n // 关闭所有模态框的按钮\n const closeButtons = document.querySelectorAll('.close-button');\n closeButtons.forEach(function(button) {\n button.addEventListener('click', function() {\n privacyModal.style.display = 'none';\n termsModal.style.display = 'none';\n securityModal.style.display = 'none';\n });\n });\n \n // 点击模态框外部关闭\n window.addEventListener('click', function(event) {\n if (event.target === privacyModal) {\n privacyModal.style.display = 'none';\n }\n if (event.target === termsModal) {\n termsModal.style.display = 'none';\n }\n if (event.target === securityModal) {\n securityModal.style.display = 'none';\n }\n });\n \n // 表单安全处理\n const bookingForm = document.getElementById('booking-form');\n if (bookingForm) {\n bookingForm.addEventListener('submit', function(e) {\n e.preventDefault();\n \n // 表单数据验证\n const name = document.getElementById('booking-name').value.trim();\n const contact = document.getElementById('booking-contact').value.trim();\n const service = document.getElementById('booking-service').value;\n const details = document.getElementById('booking-details').value.trim();\n \n // 简单的输入验证\n if (!name || !contact || !service || !details) {\n document.getElementById('booking-response').innerHTML = '<p class=\"error-message\">请填写所有必填字段。</p>';\n return;\n }\n \n // 防止XSS攻击 - 转义输入\n const sanitizeInput = (input) => {\n const div = document.createElement('div');\n div.textContent = input;\n return div.innerHTML;\n };\n \n const sanitizedData = {\n name: sanitizeInput(name),\n contact: sanitizeInput(contact),\n service: sanitizeInput(service),\n details: sanitizeInput(details)\n };\n \n // 模拟表单提交(实际项目中替换为真实的API调用)\n const response = document.getElementById('booking-response');\n response.innerHTML = '<p class=\"success-message\">您的预约请求已安全提交,我们将尽快与您联系!</p>';\n \n // 重置表单\n bookingForm.reset();\n \n // 简单的防重复提交保护\n const submitBtn = bookingForm.querySelector('.submit-btn');\n submitBtn.disabled = true;\n submitBtn.textContent = '已提交';\n \n setTimeout(() => {\n submitBtn.disabled = false;\n submitBtn.textContent = '提交预约';\n }, 3000);\n });\n }\n \n // 添加安全相关提示到表单输入\n const formInputs = document.querySelectorAll('input, textarea');\n formInputs.forEach(input => {\n // 防止浏览器自动填充敏感数据\n if (input.type === 'password' || input.id.includes('card')) {\n input.setAttribute('autocomplete', 'off');\n }\n \n // 输入验证视觉反馈\n input.addEventListener('blur', function() {\n if (this.hasAttribute('required') && !this.value.trim()) {\n this.classList.add('input-error');\n } else {\n this.classList.remove('input-error');\n }\n });\n });\n});\n\n// 安全功能初始化\nfunction initializeSecurityFeatures() {\n const privacyLink = document.getElementById('privacy-policy-link');\n const termsLink = document.getElementById('terms-link');\n const securityLink = document.getElementById('security-link');\n \n const privacyModal = document.getElementById('privacy-policy-modal');\n const termsModal = document.getElementById('terms-modal');\n const securityModal = document.getElementById('security-modal');\n \n if (privacyLink && privacyModal) {\n privacyLink.addEventListener('click', function(e) {\n e.preventDefault();\n privacyModal.style.display = 'flex';\n });\n }\n \n if (termsLink && termsModal) {\n termsLink.addEventListener('click', function(e) {\n e.preventDefault();\n termsModal.style.display = 'flex';\n });\n }\n \n if (securityLink && securityModal) {\n securityLink.addEventListener('click', function(e) {\n e.preventDefault();\n securityModal.style.display = 'flex';\n });\n }\n \n // 关闭按钮事件\n const closeButtons = document.querySelectorAll('.close-button');\n closeButtons.forEach(button => {\n button.addEventListener('click', function() {\n if (privacyModal) privacyModal.style.display = 'none';\n if (termsModal) termsModal.style.display = 'none';\n if (securityModal) securityModal.style.display = 'none';\n });\n });\n \n // 点击模态框外部关闭\n window.addEventListener('click', function(event) {\n if (privacyModal && event.target === privacyModal) {\n privacyModal.style.display = 'none';\n }\n if (termsModal && event.target === termsModal) {\n termsModal.style.display = 'none';\n }\n if (securityModal && event.target === securityModal) {\n securityModal.style.display = 'none';\n }\n });\n \n // 表单安全处理\n const bookingForm = document.getElementById('booking-form');\n if (bookingForm) {\n bookingForm.addEventListener('submit', function(e) {\n e.preventDefault();\n \n // 表单数据验证\n const name = document.getElementById('booking-name').value.trim();\n const contact = document.getElementById('booking-contact').value.trim();\n const service = document.getElementById('booking-service').value;\n const details = document.getElementById('booking-details').value.trim();\n \n // 简单的输入验证\n if (!name || !contact || !service || !details) {\n document.getElementById('booking-response').innerHTML = '<p class=\"error-message\">请填写所有必填字段。</p>';\n return;\n }\n \n // 防止XSS攻击 - 转义输入\n const sanitizeInput = (input) => {\n const div = document.createElement('div');\n div.textContent = input;\n return div.innerHTML;\n };\n \n const sanitizedData = {\n name: sanitizeInput(name),\n contact: sanitizeInput(contact),\n service: sanitizeInput(service),\n details: sanitizeInput(details)\n };\n \n // 模拟表单提交(实际项目中替换为真实的API调用)\n const response = document.getElementById('booking-response');\n response.innerHTML = '<p class=\"success-message\">您的预约请求已安全提交,我们将尽快与您联系!</p>';\n \n // 重置表单\n bookingForm.reset();\n \n // 简单的防重复提交保护\n const submitBtn = bookingForm.querySelector('.submit-btn');\n submitBtn.disabled = true;\n submitBtn.textContent = '已提交';\n \n setTimeout(() => {\n submitBtn.disabled = false;\n submitBtn.textContent = '提交预约';\n }, 3000);\n });\n }\n \n // 强制HTTPS重定向\n if (window.location.protocol === 'http:' && window.location.hostname !== 'localhost') {\n window.location.href = window.location.href.replace('http:', 'https:');\n }\n \n console.log('安全功能已初始化');\n}\n\n// 确保在DOM加载完成后初始化安全功能\ndocument.addEventListener('DOMContentLoaded', function() {\n // 其他初始化代码...\n \n // 初始化安全功能\n initializeSecurityFeatures();\n \n // ... existing code ...\n});\n\n// Mobile Navigation\nfunction initMobileNavigation() {\n const mobileNavToggle = document.getElementById('mobile-nav-toggle');\n const navOverlay = document.querySelector('.mobile-nav-overlay');\n const sideNav = document.getElementById('side-nav');\n const navLinks = document.querySelectorAll('#side-nav .nav-link');\n \n // Function to toggle mobile navigation\n function toggleMobileNav() {\n document.body.classList.toggle('mobile-nav-active');\n mobileNavToggle.classList.toggle('mobile-nav-active');\n }\n \n // Toggle navigation when hamburger button is clicked\n if (mobileNavToggle) {\n mobileNavToggle.addEventListener('click', function(e) {\n e.preventDefault();\n toggleMobileNav();\n });\n }\n \n // Close navigation when overlay is clicked\n if (navOverlay) {\n navOverlay.addEventListener('click', function() {\n if (document.body.classList.contains('mobile-nav-active')) {\n toggleMobileNav();\n }\n });\n }\n \n // Close navigation when a link is clicked on mobile\n navLinks.forEach(link => {\n link.addEventListener('click', function() {\n if (window.innerWidth <= 768 && document.body.classList.contains('mobile-nav-active')) {\n toggleMobileNav();\n }\n });\n });\n \n // --- Handle resize events\n window.addEventListener('resize', function() {\n if (window.innerWidth > 768 && document.body.classList.contains('mobile-nav-active')) {\n document.body.classList.remove('mobile-nav-active');\n mobileNavToggle.classList.remove('mobile-nav-active');\n }\n });\n}\n\n// The initMobileNavigation function is already called in the existing DOMContentLoaded event listener above"}}}}