// 全局变量 let stockChart = null; let isProcessing = false; // DOM加载完成后初始化 document.addEventListener('DOMContentLoaded', function() { initScrollEffects(); initAIDemo(); initSmoothScroll(); }); // 股票图表初始化 function initChart() { const canvas = document.getElementById('stockChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); // 设置高DPI显示 const devicePixelRatio = window.devicePixelRatio || 1; canvas.width = 400 * devicePixelRatio; canvas.height = 250 * devicePixelRatio; canvas.style.width = '400px'; canvas.style.height = '250px'; ctx.scale(devicePixelRatio, devicePixelRatio); // 生成模拟股价数据 const data = generateStockData(); drawChart(ctx, data); // 定期更新图表 setInterval(() => { const newData = generateStockData(); drawChart(ctx, newData); }, 5000); } // 生成模拟股价数据 function generateStockData() { const points = 30; const data = []; let price = 2800 + Math.random() * 200; for (let i = 0; i < points; i++) { const change = (Math.random() - 0.5) * 20; price += change; price = Math.max(2600, Math.min(3200, price)); data.push({ x: i, y: price, time: new Date(Date.now() - (points - i) * 3600000).toLocaleTimeString('ja-JP', { hour: '2-digit', minute: '2-digit' }) }); } return data; } // 图表绘制 function drawChart(ctx, data) { const width = 400; const height = 250; const padding = 40; // 清除画布 ctx.clearRect(0, 0, width, height); // 计算价格范围 const prices = data.map(d => d.y); const minPrice = Math.min(...prices); const maxPrice = Math.max(...prices); const priceRange = maxPrice - minPrice; // 绘制网格线 ctx.strokeStyle = '#e2e8f0'; ctx.lineWidth = 1; // 水平网格线 for (let i = 0; i <= 5; i++) { const y = padding + (height - 2 * padding) * i / 5; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); // 价格标签 const price = maxPrice - (priceRange * i / 5); ctx.fillStyle = '#64748b'; ctx.font = '12px Noto Sans JP'; ctx.fillText(price.toFixed(0), 5, y + 4); } // 垂直网格线 for (let i = 0; i <= 6; i++) { const x = padding + (width - 2 * padding) * i / 6; ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, height - padding); ctx.stroke(); } // 绘制股价线 ctx.strokeStyle = '#3b82f6'; ctx.lineWidth = 3; ctx.beginPath(); data.forEach((point, index) => { const x = padding + (width - 2 * padding) * index / (data.length - 1); const y = padding + (height - 2 * padding) * (maxPrice - point.y) / priceRange; if (index === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); // 绘制数据点 ctx.fillStyle = '#1e40af'; data.forEach((point, index) => { const x = padding + (width - 2 * padding) * index / (data.length - 1); const y = padding + (height - 2 * padding) * (maxPrice - point.y) / priceRange; ctx.beginPath(); ctx.arc(x, y, 3, 0, 2 * Math.PI); ctx.fill(); }); // 绘制当前价格指示器 const lastPoint = data[data.length - 1]; const lastX = padding + (width - 2 * padding); const lastY = padding + (height - 2 * padding) * (maxPrice - lastPoint.y) / priceRange; ctx.fillStyle = '#10b981'; ctx.beginPath(); ctx.arc(lastX, lastY, 5, 0, 2 * Math.PI); ctx.fill(); // 当前价格标签 ctx.fillStyle = '#ffffff'; ctx.fillRect(lastX + 10, lastY - 10, 60, 20); ctx.strokeStyle = '#10b981'; ctx.strokeRect(lastX + 10, lastY - 10, 60, 20); ctx.fillStyle = '#10b981'; ctx.font = 'bold 12px Noto Sans JP'; ctx.fillText(`¥${lastPoint.y.toFixed(0)}`, lastX + 15, lastY + 3); } // 滚动效果初始化 function initScrollEffects() { // 导航栏滚动效果 window.addEventListener('scroll', function() { const header = document.querySelector('.header'); if (window.scrollY > 100) { header.style.background = 'rgba(255, 255, 255, 0.98)'; header.style.boxShadow = '0 2px 20px rgba(0, 0, 0, 0.1)'; } else { header.style.background = 'rgba(255, 255, 255, 0.95)'; header.style.boxShadow = 'none'; } }); // 元素出现动画 const observerOptions = { threshold: 0.1, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.style.opacity = '1'; entry.target.style.transform = 'translateY(0)'; } }); }, observerOptions); // 为需要动画的元素添加初始样式和观察 const animatedElements = document.querySelectorAll('.feature-card, .step, .pricing-card, .testimonial'); animatedElements.forEach(el => { el.style.opacity = '0'; el.style.transform = 'translateY(30px)'; el.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; observer.observe(el); }); } // 平滑滚动 function initSmoothScroll() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); } // AI デモ初期化 function initAIDemo() { // モーダル内のAI体験エリア const demoPredictBtn = document.getElementById('demoPredictBtn'); const demoStockInput = document.getElementById('demoStockInput'); if (demoPredictBtn && demoStockInput) { demoPredictBtn.addEventListener('click', () => startPrediction(demoStockInput, 'demoChatMessages')); demoStockInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { startPrediction(demoStockInput, 'demoChatMessages'); } }); } } // AI予測開始 async function startPrediction(inputElement, messagesContainerId) { if (isProcessing) return; const query = inputElement.value.trim(); if (!query) return; isProcessing = true; const messagesContainer = document.getElementById(messagesContainerId); // ユーザーメッセージを追加 addMessage(messagesContainer, query, 'user'); inputElement.value = ''; // ボタンを無効化 const predictBtn = inputElement.nextElementSibling; if (predictBtn) { predictBtn.disabled = true; predictBtn.textContent = '分析中...'; } // タイピングインジケーターを表示 const typingIndicator = addTypingIndicator(messagesContainer); try { // AI分析をシミュレート await simulateAIAnalysis(query); // タイピングインジケーターを削除 typingIndicator.remove(); // AI応答を追加 const response = generateAIResponse(query); addMessage(messagesContainer, response, 'ai'); // 予測完了後にLINE推広を表示 setTimeout(() => { showLinePromotion(); }, 1000); } catch (error) { console.error('予測エラー:', error); typingIndicator.remove(); addMessage(messagesContainer, 'エラーが発生しました。もう一度お試しください。', 'ai'); } finally { isProcessing = false; if (predictBtn) { predictBtn.disabled = false; predictBtn.textContent = '予測開始'; } } } // メッセージ追加 function addMessage(container, content, type) { const messageDiv = document.createElement('div'); messageDiv.className = `message ${type}-message`; const avatar = document.createElement('div'); avatar.className = `${type}-avatar-small`; avatar.textContent = type === 'ai' ? '🤖' : '👤'; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; if (type === 'ai' && typeof content === 'object') { // AI予測結果の場合 contentDiv.innerHTML = content.html; } else { contentDiv.textContent = content; } messageDiv.appendChild(avatar); messageDiv.appendChild(contentDiv); container.appendChild(messageDiv); // スクロールを下に container.scrollTop = container.scrollHeight; } // タイピングインジケーター追加 function addTypingIndicator(container) { const messageDiv = document.createElement('div'); messageDiv.className = 'message ai-message'; const avatar = document.createElement('div'); avatar.className = 'ai-avatar-small'; avatar.textContent = '🤖'; const typingDiv = document.createElement('div'); typingDiv.className = 'typing-indicator'; const dotsDiv = document.createElement('div'); dotsDiv.className = 'typing-dots'; for (let i = 0; i < 3; i++) { const dot = document.createElement('div'); dot.className = 'typing-dot'; dotsDiv.appendChild(dot); } typingDiv.appendChild(dotsDiv); messageDiv.appendChild(avatar); messageDiv.appendChild(typingDiv); container.appendChild(messageDiv); container.scrollTop = container.scrollHeight; return messageDiv; } // AI分析シミュレート function simulateAIAnalysis(query) { return new Promise(resolve => { setTimeout(resolve, 2000 + Math.random() * 2000); // 2-4秒のランダムな遅延 }); } // AI応答生成 function generateAIResponse(query) { // 簡単な銘柄データベース const stockData = { 'トヨタ': { code: '7203', name: 'トヨタ自動車', price: 2850 }, '7203': { code: '7203', name: 'トヨタ自動車', price: 2850 }, 'ソニー': { code: '6758', name: 'ソニーグループ', price: 12400 }, '6758': { code: '6758', name: 'ソニーグループ', price: 12400 }, 'ソフトバンク': { code: '9984', name: 'ソフトバンクグループ', price: 6200 }, '9984': { code: '9984', name: 'ソフトバンクグループ', price: 6200 }, 'KDDI': { code: '9433', name: 'KDDI', price: 4100 }, '9433': { code: '9433', name: 'KDDI', price: 4100 } }; // クエリを正規化 const normalizedQuery = query.replace(/\s+/g, '').toUpperCase(); let stockInfo = null; // 銘柄を検索 for (const [key, value] of Object.entries(stockData)) { if (key.toUpperCase().includes(normalizedQuery) || normalizedQuery.includes(key.toUpperCase())) { stockInfo = value; break; } } if (stockInfo) { // 模拟预测数据 const currentPrice = stockInfo.price; const weekChange = (Math.random() - 0.5) * 10; // -5% to +5% const monthChange = (Math.random() - 0.5) * 20; // -10% to +10% const confidence = 75 + Math.random() * 20; // 75-95% const weekPrice = Math.round(currentPrice * (1 + weekChange / 100)); const monthPrice = Math.round(currentPrice * (1 + monthChange / 100)); return { html: `
📈 ${stockInfo.name} (${stockInfo.code}) の予測結果
現在価格
¥${currentPrice.toLocaleString()}
1週間後
¥${weekPrice.toLocaleString()} (${weekChange >= 0 ? '+' : ''}${weekChange.toFixed(1)}%)
1ヶ月後
¥${monthPrice.toLocaleString()} (${monthChange >= 0 ? '+' : ''}${monthChange.toFixed(1)}%)
信頼度
${confidence.toFixed(0)}%

※ これはデモ用の模擬データです。実際の投資判断にはご利用いただけません。

` }; } else { return `申し訳ございませんが、「${query}」に該当する銘柄が見つかりませんでした。\n\n以下の形式でお試しください:\n• 銘柄名:トヨタ、ソニー、ソフトバンク等\n• 銘柄コード:7203、6758、9984等`; } } // AI デモモーダル開閉 function openAIDemo() { const modal = document.getElementById('aiDemoModal'); if (modal) { modal.style.display = 'block'; document.body.style.overflow = 'hidden'; // モーダル外クリックで閉じる modal.addEventListener('click', function(e) { if (e.target === modal) { closeAIDemo(); } }); } } function closeAIDemo() { const modal = document.getElementById('aiDemoModal'); if (modal) { modal.style.display = 'none'; document.body.style.overflow = 'auto'; // リセット resetDemo(); } } // LINE推広を表示 function showLinePromotion() { const demoNotice = document.getElementById('demoNotice'); if (demoNotice) { demoNotice.style.display = 'block'; // スムーズにスクロール setTimeout(() => { demoNotice.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }, 100); } } // デモを続ける function continueDemo() { const demoNotice = document.getElementById('demoNotice'); const demoStockInput = document.getElementById('demoStockInput'); if (demoNotice) { demoNotice.style.display = 'none'; } if (demoStockInput) { demoStockInput.focus(); } } // デモをリセット function resetDemo() { const demoNotice = document.getElementById('demoNotice'); const demoChatMessages = document.getElementById('demoChatMessages'); const demoStockInput = document.getElementById('demoStockInput'); if (demoNotice) { demoNotice.style.display = 'none'; } if (demoChatMessages) { // 初期メッセージ以外を削除 const messages = demoChatMessages.querySelectorAll('.message'); for (let i = 1; i < messages.length; i++) { messages[i].remove(); } } if (demoStockInput) { demoStockInput.value = ''; } isProcessing = false; } // ESCキーでモーダルを閉じる document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { closeAIDemo(); } }); // サインアップフォーム処理 function handleSignup(e) { e.preventDefault(); const formData = new FormData(e.target); const email = formData.get('email'); const name = formData.get('name'); // バリデーション if (!email || !name) { showNotification('すべての項目を入力してください。', 'error'); return; } if (!isValidEmail(email)) { showNotification('有効なメールアドレスを入力してください。', 'error'); return; } // 送信アニメーション const submitBtn = e.target.querySelector('button[type="submit"]'); const originalText = submitBtn.textContent; submitBtn.textContent = '送信中...'; submitBtn.disabled = true; // 模拟API调用 setTimeout(() => { showNotification('登録が完了しました!確認メールを送信いたします。', 'success'); closeModal(); e.target.reset(); submitBtn.textContent = originalText; submitBtn.disabled = false; // 成功後の処理(実際の実装では適切なリダイレクトなど) setTimeout(() => { showWelcomeMessage(); }, 1000); }, 2000); } // メールアドレス验证 function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } // 通知表示 function showNotification(message, type = 'info') { const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.textContent = message; // 通知样式 Object.assign(notification.style, { position: 'fixed', top: '20px', right: '20px', padding: '1rem 1.5rem', borderRadius: '8px', color: 'white', fontSize: '14px', fontWeight: '500', zIndex: '3000', opacity: '0', transform: 'translateX(100%)', transition: 'all 0.3s ease', maxWidth: '400px', wordWrap: 'break-word' }); // 类型特定样式 if (type === 'success') { notification.style.background = '#10b981'; } else if (type === 'error') { notification.style.background = '#ef4444'; } else { notification.style.background = '#3b82f6'; } document.body.appendChild(notification); // 显示动画 setTimeout(() => { notification.style.opacity = '1'; notification.style.transform = 'translateX(0)'; }, 100); // 自动隐藏 setTimeout(() => { notification.style.opacity = '0'; notification.style.transform = 'translateX(100%)'; setTimeout(() => { document.body.removeChild(notification); }, 300); }, 4000); } // 欢迎消息 function showWelcomeMessage() { const welcomeOverlay = document.createElement('div'); welcomeOverlay.innerHTML = `

ようこそ!

日本株式AI予測システムへの登録が完了しました。
まもなく確認メールをお送りいたします。

`; document.body.appendChild(welcomeOverlay); // 自动隐藏 setTimeout(() => { welcomeOverlay.style.display = 'none'; document.body.removeChild(welcomeOverlay); }, 8000); } // パフォーマンス最適化 function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // リサイズ処理 window.addEventListener('resize', debounce(() => { // 图表重绘 if (stockChart) { initChart(); } }, 250)); // ページ離脱前の警告(フォーム入力中の場合) window.addEventListener('beforeunload', function(e) { const modal = document.getElementById('signupModal'); const form = document.querySelector('.signup-form'); if (modal.style.display === 'block' && form) { const inputs = form.querySelectorAll('input[type="email"], input[type="text"]'); const hasContent = Array.from(inputs).some(input => input.value.trim() !== ''); if (hasContent) { e.preventDefault(); e.returnValue = '入力中のデータが失われる可能性があります。ページを離れますか?'; } } }); // Google Analytics統合(実際の実装時に使用) function trackEvent(eventName, parameters = {}) { // Google Analytics 4 イベント追跡 if (typeof gtag !== 'undefined') { gtag('event', eventName, parameters); } // コンソールログ(開発用) console.log('Event tracked:', eventName, parameters); } // 주요 이벤트 추적 document.addEventListener('DOMContentLoaded', function() { // CTA 버튼 클릭 추적 document.querySelectorAll('.btn-primary').forEach(btn => { btn.addEventListener('click', function() { trackEvent('cta_click', { button_text: this.textContent.trim(), button_location: this.closest('section')?.className || 'unknown' }); }); }); // 料金プラン선택 추적 document.querySelectorAll('.pricing-card .btn').forEach(btn => { btn.addEventListener('click', function() { const planName = this.closest('.pricing-card').querySelector('h3').textContent; trackEvent('plan_selected', { plan_name: planName }); }); }); // フォーム送信 추적 const signupForm = document.querySelector('.signup-form'); if (signupForm) { signupForm.addEventListener('submit', function() { trackEvent('form_submit', { form_type: 'signup' }); }); } }); // PWA対応(Progressive Web App) if ('serviceWorker' in navigator) { window.addEventListener('load', function() { navigator.serviceWorker.register('/sw.js') .then(function(registration) { console.log('ServiceWorker registration successful'); }) .catch(function(err) { console.log('ServiceWorker registration failed'); }); }); }