// Mobile Navigation Toggle const hamburger = document.querySelector('.hamburger'); const navMenu = document.querySelector('.nav-menu'); if (hamburger) { hamburger.addEventListener('click', () => { navMenu.classList.toggle('active'); hamburger.classList.toggle('active'); }); } // Auto-position submenus consistently per parent menu level // All child menus within a parent (In/Proc/Out) point in the same direction // If ANY child menu level would equal or exceed the right margin, ALL menus point leftward // // FUTURE-PROOF: This function automatically works for new pages/menus because: // - Uses generic selectors (.nav-menu > .dropdown) - finds any top-level parent // - Dynamically detects parent type from link text/href - no hardcoded names // - Finds ALL child menus at ALL nesting levels - works for any depth // - Uses MutationObserver to detect DOM changes - catches dynamically added menus // - Re-runs on window resize - adapts to viewport changes function initSubmenuPositioning() { // Find all top-level dropdown menus (In, Proc, Out, or any future parents) // This selector is generic and will find any top-level dropdown menu const topLevelDropdowns = document.querySelectorAll('.nav-menu > .dropdown'); topLevelDropdowns.forEach(parentDropdown => { const parentLink = parentDropdown.querySelector('a.nav-link'); if (!parentLink) return; // Determine which parent this is (In, Proc, or Out) const linkText = parentLink.textContent.trim(); const linkHref = parentLink.getAttribute('href') || ''; let parentType = null; if (linkText.includes('In') || linkHref.includes('/in.html') || linkHref.includes('/in/in.html')) { parentType = 'in'; } else if (linkText.includes('Proc') || linkHref.includes('/processing.html') || linkHref.includes('/processing/processing.html')) { parentType = 'processing'; } else if (linkText.includes('Out') || linkHref.includes('/out.html') || linkHref.includes('/out/out.html')) { parentType = 'out'; } // Get the parent dropdown menu const parentDropdownMenu = parentDropdown.querySelector('.dropdown-menu'); if (!parentDropdownMenu) return; // Calculate if this parent menu should point leftward or rightward function determineParentDirection() { const parentRect = parentDropdown.getBoundingClientRect(); const viewportWidth = window.innerWidth; const padding = 10; const safetyMargin = 20; const rightMargin = viewportWidth - padding - safetyMargin; // Find maximum nesting depth let maxDepth = 0; function findMaxDepth(element, currentDepth = 0) { // Get direct child li elements, then their direct child ul elements const directLiChildren = Array.from(element.children).filter(child => child.tagName === 'LI'); const childMenus = []; directLiChildren.forEach(li => { const directUlChild = Array.from(li.children).find(child => child.tagName === 'UL'); if (directUlChild) { childMenus.push(directUlChild); } }); if (childMenus.length === 0) { if (currentDepth > maxDepth) { maxDepth = currentDepth; } return; } childMenus.forEach(childMenu => { findMaxDepth(childMenu, currentDepth + 1); }); } findMaxDepth(parentDropdownMenu); // Estimate submenu width (average width for nested menus) const estimatedSubmenuWidth = 220; // Calculate rightmost edge position if all menus point rightward // This represents the worst-case scenario const rightmostEdgePosition = parentRect.right + (estimatedSubmenuWidth * maxDepth); // Debug logging console.log(`[Menu Direction] ${parentType || 'unknown'}: parent.right=${parentRect.right.toFixed(0)}, maxDepth=${maxDepth}, rightmostEdge=${rightmostEdgePosition.toFixed(0)}, rightMargin=${rightMargin.toFixed(0)}`); // Check if rightmost edge would equal or exceed right margin if (rightmostEdgePosition >= rightMargin) { // ANY menu level would exceed right margin → point ALL leftward console.log(`[Menu Direction] ${parentType || 'unknown'}: OVERFLOW DETECTED → pointing LEFTWARD`); return 'leftward'; } // Also check each menu level individually const allChildMenus = parentDropdownMenu.querySelectorAll('ul'); for (let childMenu of allChildMenus) { // Calculate depth of this menu let depth = 0; let current = childMenu; while (current && current !== parentDropdownMenu) { if (current.tagName === 'UL') { depth++; } current = current.parentElement; } // Get parent menu item const parentMenuItem = childMenu.parentElement; if (parentMenuItem) { const menuItemRect = parentMenuItem.getBoundingClientRect(); // Calculate where this menu's right edge would be const estimatedRightEdge = menuItemRect.right + estimatedSubmenuWidth; // If this menu would equal or exceed right margin, point all leftward if (estimatedRightEdge >= rightMargin) { console.log(`[Menu Direction] ${parentType || 'unknown'}: Individual menu overflow → pointing LEFTWARD`); return 'leftward'; } } } // For Out and Proc, also check if parent is close to right edge if (parentType === 'out' || parentType === 'processing') { const spaceOnRight = viewportWidth - parentRect.right - padding - safetyMargin; const spaceOnLeft = parentRect.left - padding; // If parent is in right 70% of screen or space on right is limited, prefer leftward if (parentRect.right > viewportWidth * 0.7 || spaceOnRight < spaceOnLeft * 1.2) { console.log(`[Menu Direction] ${parentType}: Close to right edge → pointing LEFTWARD`); return 'leftward'; } } // Default: rightward if there's enough space console.log(`[Menu Direction] ${parentType || 'unknown'}: Enough space → pointing RIGHTWARD`); return 'rightward'; } // Determine direction for this parent menu const parentDirection = determineParentDirection(); // Find ALL child menus at ALL levels within this parent const allChildMenus = parentDropdownMenu.querySelectorAll('ul'); // Apply consistent direction to all child menus with STRONG inline styles allChildMenus.forEach(childMenu => { if (parentDirection === 'leftward') { // Point leftward - add class and set styles childMenu.classList.add('submenu-reversed'); childMenu.style.setProperty('left', 'auto', 'important'); childMenu.style.setProperty('right', '100%', 'important'); childMenu.style.setProperty('margin-left', '0', 'important'); childMenu.style.setProperty('margin-right', '2px', 'important'); } else { // Point rightward - remove class and set styles childMenu.classList.remove('submenu-reversed'); childMenu.style.setProperty('left', '100%', 'important'); childMenu.style.setProperty('right', 'auto', 'important'); childMenu.style.setProperty('margin-left', '2px', 'important'); childMenu.style.setProperty('margin-right', '0', 'important'); } }); // Set up hover handlers to maintain consistency const menuItemsWithChildren = parentDropdownMenu.querySelectorAll('li:has(> ul)'); menuItemsWithChildren.forEach(menuItem => { // Get direct child ul (not nested) const submenu = Array.from(menuItem.children).find(child => child.tagName === 'UL'); if (!submenu) return; // On hover, ensure consistent direction is maintained menuItem.addEventListener('mouseenter', () => { if (parentDirection === 'leftward') { submenu.classList.add('submenu-reversed'); submenu.style.setProperty('left', 'auto', 'important'); submenu.style.setProperty('right', '100%', 'important'); submenu.style.setProperty('margin-left', '0', 'important'); submenu.style.setProperty('margin-right', '2px', 'important'); } else { submenu.classList.remove('submenu-reversed'); submenu.style.setProperty('left', '100%', 'important'); submenu.style.setProperty('right', 'auto', 'important'); submenu.style.setProperty('margin-left', '2px', 'important'); submenu.style.setProperty('margin-right', '0', 'important'); } }); }); }); // Re-check on window resize let resizeTimer; window.addEventListener('resize', () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { initSubmenuPositioning(); }, 100); }); } // Initialize submenu positioning when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initSubmenuPositioning); } else { initSubmenuPositioning(); } // Re-initialize after dynamic menus are loaded setTimeout(initSubmenuPositioning, 600); setTimeout(initSubmenuPositioning, 1000); // Also observe DOM changes for dynamically added menus // FUTURE-PROOF: This ensures new menus added via JavaScript are automatically positioned const observer = new MutationObserver((mutations) => { // Only re-run if dropdown menus were added/modified let shouldUpdate = false; mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === 1 && ( node.classList?.contains('dropdown') || node.classList?.contains('dropdown-menu') || node.querySelector?.('.dropdown') || node.querySelector?.('.dropdown-menu') )) { shouldUpdate = true; } }); }); if (shouldUpdate) { setTimeout(initSubmenuPositioning, 100); } }); observer.observe(document.body, { childList: true, subtree: true }); // Close mobile menu when clicking on a link const navLinks = document.querySelectorAll('.nav-link, .dropdown-link, .submenu-link'); navLinks.forEach(link => { link.addEventListener('click', () => { navMenu.classList.remove('active'); hamburger.classList.remove('active'); }); }); // Handle dropdown menus on mobile const dropdowns = document.querySelectorAll('.dropdown'); dropdowns.forEach(dropdown => { const dropdownLink = dropdown.querySelector('.nav-link'); if (dropdownLink) { dropdownLink.addEventListener('click', (e) => { if (window.innerWidth <= 768) { e.preventDefault(); dropdown.classList.toggle('active'); } }); } }); // Theme Toggle Functionality - Helper functions // Inject stylesheet immediately if dark theme is detected (runs before DOMContentLoaded) (function() { if (typeof localStorage !== 'undefined') { const savedTheme = localStorage.getItem('theme') || 'light'; if (savedTheme === 'dark') { // Inject stylesheet immediately in head const darkThemeStyle = document.createElement('style'); darkThemeStyle.id = 'dark-theme-override-style'; darkThemeStyle.textContent = ` /* Override CSS variables at root level */ :root.dark-theme, html.dark-theme, body.dark-theme { --color-text: #ffffff !important; --color-text-light: #ffffff !important; } /* Force all content-text and children to white */ body.dark-theme .content-text, html.dark-theme .content-text, body.dark-theme .content-text *, html.dark-theme .content-text *, body.dark-theme .content-text p, html.dark-theme .content-text p, body.dark-theme .section .content-text, html.dark-theme .section .content-text, body.dark-theme .section .content-text *, html.dark-theme .section .content-text *, body.dark-theme .section .content-text p, html.dark-theme .section .content-text p, body.dark-theme main .content-text, html.dark-theme main .content-text, body.dark-theme main .content-text *, html.dark-theme main .content-text *, body.dark-theme main .content-text p, html.dark-theme main .content-text p, body.dark-theme .container .content-text, html.dark-theme .container .content-text, body.dark-theme .container .content-text *, html.dark-theme .container .content-text *, body.dark-theme .container .content-text p, html.dark-theme .container .content-text p, body.dark-theme .content-grid .content-text, html.dark-theme .content-grid .content-text, body.dark-theme .content-grid .content-text *, html.dark-theme .content-grid .content-text *, body.dark-theme .content-grid .content-text p, html.dark-theme .content-grid .content-text p { color: #ffffff !important; --color-text: #ffffff !important; --color-text-light: #ffffff !important; } /* Force background colors */ body.dark-theme main, html.dark-theme main, body.dark-theme .section, html.dark-theme .section, body.dark-theme .container, html.dark-theme .container, body.dark-theme .content-grid, html.dark-theme .content-grid { background-color: #1a1a1a !important; color: #ffffff !important; --color-text: #ffffff !important; --color-text-light: #ffffff !important; } /* Force h1 in content-grid */ body.dark-theme .content-grid h1, html.dark-theme .content-grid h1, body.dark-theme .content-text h1, html.dark-theme .content-text h1 { color: #ffffff !important; } `; // Insert immediately if head exists, otherwise wait for it if (document.head) { document.head.appendChild(darkThemeStyle); } else { document.addEventListener('DOMContentLoaded', function() { if (!document.getElementById('dark-theme-override-style')) { document.head.appendChild(darkThemeStyle); } }); } } } })(); function applyInlineDarkThemeStyles() { // Inject a dynamic stylesheet to override everything - most aggressive approach let darkThemeStyle = document.getElementById('dark-theme-override-style'); if (!darkThemeStyle) { darkThemeStyle = document.createElement('style'); darkThemeStyle.id = 'dark-theme-override-style'; // Insert at the end of head to ensure it overrides everything document.head.appendChild(darkThemeStyle); } // Use a comprehensive stylesheet that targets every possible selector darkThemeStyle.textContent = ` /* Override CSS variables at root level */ :root.dark-theme, html.dark-theme, body.dark-theme { --color-text: #ffffff !important; --color-text-light: #ffffff !important; } /* Force all content-text and children to white */ body.dark-theme .content-text, html.dark-theme .content-text, body.dark-theme .content-text *, html.dark-theme .content-text *, body.dark-theme .content-text p, html.dark-theme .content-text p, body.dark-theme .section .content-text, html.dark-theme .section .content-text, body.dark-theme .section .content-text *, html.dark-theme .section .content-text *, body.dark-theme .section .content-text p, html.dark-theme .section .content-text p, body.dark-theme main .content-text, html.dark-theme main .content-text, body.dark-theme main .content-text *, html.dark-theme main .content-text *, body.dark-theme main .content-text p, html.dark-theme main .content-text p, body.dark-theme .container .content-text, html.dark-theme .container .content-text, body.dark-theme .container .content-text *, html.dark-theme .container .content-text *, body.dark-theme .container .content-text p, html.dark-theme .container .content-text p, body.dark-theme .content-grid .content-text, html.dark-theme .content-grid .content-text, body.dark-theme .content-grid .content-text *, html.dark-theme .content-grid .content-text *, body.dark-theme .content-grid .content-text p, html.dark-theme .content-grid .content-text p { color: #ffffff !important; --color-text: #ffffff !important; --color-text-light: #ffffff !important; } /* Force background colors */ body.dark-theme main, html.dark-theme main, body.dark-theme .section, html.dark-theme .section, body.dark-theme .container, html.dark-theme .container, body.dark-theme .content-grid, html.dark-theme .content-grid { background-color: #1a1a1a !important; color: #ffffff !important; --color-text: #ffffff !important; --color-text-light: #ffffff !important; } /* Force h1 in content-grid */ body.dark-theme .content-grid h1, html.dark-theme .content-grid h1, body.dark-theme .content-text h1, html.dark-theme .content-text h1 { color: #ffffff !important; } `; // Also force update CSS variables on html and body first document.documentElement.style.setProperty('--color-text', '#ffffff', 'important'); document.documentElement.style.setProperty('--color-text-light', '#ffffff', 'important'); document.documentElement.style.setProperty('background-color', '#1a1a1a', 'important'); document.body.style.setProperty('--color-text', '#ffffff', 'important'); document.body.style.setProperty('--color-text-light', '#ffffff', 'important'); document.body.style.setProperty('background-color', '#1a1a1a', 'important'); // Batch DOM updates for better performance const contentTextElements = document.querySelectorAll('.content-text'); const main = document.querySelector('main'); const section = document.querySelector('.section'); const container = document.querySelector('.container'); const contentGrid = document.querySelector('.content-grid'); // Update main containers first with CSS variables if (main) { main.style.setProperty('background-color', '#1a1a1a', 'important'); main.style.setProperty('color', '#ffffff', 'important'); main.style.setProperty('--color-text', '#ffffff', 'important'); main.style.setProperty('--color-text-light', '#ffffff', 'important'); } if (section) { section.style.setProperty('background-color', '#1a1a1a', 'important'); section.style.setProperty('color', '#ffffff', 'important'); section.style.setProperty('--color-text', '#ffffff', 'important'); section.style.setProperty('--color-text-light', '#ffffff', 'important'); } if (container) { container.style.setProperty('color', '#ffffff', 'important'); container.style.setProperty('--color-text', '#ffffff', 'important'); container.style.setProperty('--color-text-light', '#ffffff', 'important'); } if (contentGrid) { contentGrid.style.setProperty('color', '#ffffff', 'important'); contentGrid.style.setProperty('--color-text', '#ffffff', 'important'); contentGrid.style.setProperty('--color-text-light', '#ffffff', 'important'); } // Update all content-text elements and their children in one pass contentTextElements.forEach(el => { // Set CSS variables on the element itself el.style.setProperty('--color-text', '#ffffff', 'important'); el.style.setProperty('--color-text-light', '#ffffff', 'important'); el.style.setProperty('color', '#ffffff', 'important'); el.style.setProperty('background-color', 'transparent', 'important'); // CRITICAL: Update paragraphs FIRST with direct color override const paragraphs = el.querySelectorAll('p'); paragraphs.forEach(p => { // Force color directly - this overrides CSS variable usage p.style.setProperty('color', '#ffffff', 'important'); p.style.setProperty('--color-text-light', '#ffffff', 'important'); }); // Update ALL other children recursively const allChildren = el.querySelectorAll('*:not(p)'); allChildren.forEach(child => { child.style.setProperty('color', '#ffffff', 'important'); }); const headings = el.querySelectorAll('h1, h2, h3, h4, h5, h6'); headings.forEach(h => { h.style.setProperty('color', '#ffffff', 'important'); }); const textElements = el.querySelectorAll('strong, b, a, span, em, i'); textElements.forEach(t => { t.style.setProperty('color', '#ffffff', 'important'); }); }); // Also update h1 in content-grid const h1Elements = document.querySelectorAll('.content-grid h1, .content-text h1'); h1Elements.forEach(h1 => { h1.style.setProperty('color', '#ffffff', 'important'); }); } function removeInlineDarkThemeStyles() { // Remove the dynamic stylesheet const darkThemeStyle = document.getElementById('dark-theme-override-style'); if (darkThemeStyle) { darkThemeStyle.remove(); } // CRITICAL: Remove dark background from html and body document.documentElement.style.removeProperty('background-color'); document.documentElement.style.setProperty('background-color', '#ffffff', 'important'); document.body.style.removeProperty('background-color'); document.body.style.setProperty('background-color', '#ffffff', 'important'); // Remove CSS variables from html and body document.documentElement.style.removeProperty('--color-text'); document.documentElement.style.removeProperty('--color-text-light'); document.body.style.removeProperty('--color-text'); document.body.style.removeProperty('--color-text-light'); // Batch DOM updates for better performance const contentTextElements = document.querySelectorAll('.content-text'); const main = document.querySelector('main'); const section = document.querySelector('.section'); const container = document.querySelector('.container'); const contentGrid = document.querySelector('.content-grid'); // Update main containers first - explicitly set to white background if (main) { main.style.setProperty('background-color', '#ffffff', 'important'); main.style.removeProperty('color'); main.style.removeProperty('--color-text'); main.style.removeProperty('--color-text-light'); } if (section) { section.style.setProperty('background-color', '#ffffff', 'important'); section.style.removeProperty('color'); section.style.removeProperty('--color-text'); section.style.removeProperty('--color-text-light'); } if (container) { container.style.removeProperty('color'); container.style.removeProperty('--color-text'); container.style.removeProperty('--color-text-light'); } if (contentGrid) { contentGrid.style.removeProperty('color'); contentGrid.style.removeProperty('--color-text'); contentGrid.style.removeProperty('--color-text-light'); } // Remove styles from all content-text elements and their children in one pass contentTextElements.forEach(el => { el.style.removeProperty('--color-text'); el.style.removeProperty('--color-text-light'); el.style.removeProperty('color'); el.style.removeProperty('background-color'); // Remove from paragraphs first const paragraphs = el.querySelectorAll('p'); paragraphs.forEach(p => { p.style.removeProperty('color'); p.style.removeProperty('--color-text-light'); }); // Remove from ALL other children const allChildren = el.querySelectorAll('*:not(p)'); allChildren.forEach(child => { child.style.removeProperty('color'); }); }); // Remove from h1 elements const h1Elements = document.querySelectorAll('.content-grid h1, .content-text h1'); h1Elements.forEach(h1 => { h1.style.removeProperty('color'); }); } function initThemeToggle() { const themeToggle = document.getElementById('themeToggle'); const body = document.body; const html = document.documentElement; if (!themeToggle) { // Retry if element not found yet setTimeout(initThemeToggle, 100); return; } const themeIcon = themeToggle.querySelector('.theme-icon'); // Check for saved theme preference or default to light mode const currentTheme = localStorage.getItem('theme') || 'light'; if (currentTheme === 'dark') { body.classList.add('dark-theme'); html.classList.add('dark-theme'); document.documentElement.classList.add('dark-theme'); if (themeIcon) themeIcon.textContent = '☀️'; // Apply inline styles on load if dark theme - use setTimeout to ensure DOM is ready setTimeout(() => { applyInlineDarkThemeStyles(); }, 0); } else { body.classList.remove('dark-theme'); html.classList.remove('dark-theme'); document.documentElement.classList.remove('dark-theme'); if (themeIcon) themeIcon.textContent = '🌙'; // Ensure no inline styles if light theme removeInlineDarkThemeStyles(); } // Mark that listener is attached if (!themeToggle.hasAttribute('data-listener-attached')) { themeToggle.setAttribute('data-listener-attached', 'true'); // Toggle theme - instant seamless change themeToggle.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); // Disable transitions temporarily for instant theme change const style = document.createElement('style'); style.textContent = ` *, *::before, *::after { transition: none !important; } `; document.head.appendChild(style); // Immediately toggle classes on all elements - ensure class is properly set const currentlyDark = body.classList.contains('dark-theme'); if (currentlyDark) { // Switching to light body.classList.remove('dark-theme'); html.classList.remove('dark-theme'); document.documentElement.classList.remove('dark-theme'); } else { // Switching to dark body.classList.add('dark-theme'); html.classList.add('dark-theme'); document.documentElement.classList.add('dark-theme'); } const isDark = body.classList.contains('dark-theme'); // Update icon immediately if (themeIcon) { themeIcon.textContent = isDark ? '☀️' : '🌙'; } // Save preference (non-blocking) localStorage.setItem('theme', isDark ? 'dark' : 'light'); // Apply or remove inline styles based on theme - do it immediately if (isDark) { applyInlineDarkThemeStyles(); } else { removeInlineDarkThemeStyles(); } // Re-enable transitions after a brief moment (for other interactions) requestAnimationFrame(() => { requestAnimationFrame(() => { if (document.head.contains(style)) { document.head.removeChild(style); } }); }); }); } } // Initialize theme toggle when DOM is ready - multiple fallbacks function initializeThemeToggle() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initThemeToggle); } else if (document.readyState === 'interactive' || document.readyState === 'complete') { // DOM already loaded or nearly loaded initThemeToggle(); } else { // Fallback: try immediately and retry if needed initThemeToggle(); } } // Try multiple initialization methods initializeThemeToggle(); // Also try after a short delay as fallback setTimeout(() => { const themeToggle = document.getElementById('themeToggle'); if (themeToggle && !themeToggle.hasAttribute('data-listener-attached')) { initThemeToggle(); } }, 100); // Chat Agent Functionality function initChatAgent() { // Support both chatButton and chatAgentBtn IDs const chatButton = document.getElementById('chatButton') || document.getElementById('chatAgentBtn'); const chatWindow = document.getElementById('chatWindow'); const chatClose = document.getElementById('chatClose'); const chatInput = document.getElementById('chatInput'); const chatSend = document.getElementById('chatSend'); const chatMessages = document.getElementById('chatMessages'); if (chatButton && chatWindow) { // Mark listener as attached to prevent duplicates if (!chatButton.hasAttribute('data-chat-listener-attached')) { chatButton.setAttribute('data-chat-listener-attached', 'true'); chatButton.addEventListener('click', () => { chatWindow.classList.toggle('active'); }); } } if (chatClose) { chatClose.addEventListener('click', () => { chatWindow.classList.remove('active'); }); } if (chatSend && chatInput) { const handleSend = async () => { const message = chatInput.value.trim(); if (!message) return; // Handle special commands if (message.startsWith('/setkey ')) { const apiKey = message.substring(8).trim(); localStorage.setItem('mistral_api_key', apiKey); addChatMessage('✅ Mistral API key saved! You can now use AI-powered responses.', false); chatInput.value = ''; return; } // Add user message addChatMessage(message, true); chatInput.value = ''; chatInput.disabled = true; chatSend.disabled = true; // Show typing indicator const typingId = addTypingIndicator(); try { // Try Mistral AI first const apiKey = localStorage.getItem('mistral_api_key'); if (apiKey) { const response = await getMistralResponse(message); removeTypingIndicator(typingId); addChatMessage(response, false); } else { // Fallback to rule-based removeTypingIndicator(typingId); const fallbackResponse = getFallbackResponse(message); addChatMessage(fallbackResponse, false); } } catch (error) { console.error('Chat error:', error); removeTypingIndicator(typingId); const fallbackResponse = getFallbackResponse(message); addChatMessage(fallbackResponse, false); } finally { chatInput.disabled = false; chatSend.disabled = false; chatInput.focus(); } }; chatSend.addEventListener('click', handleSend); chatInput.addEventListener('keypress', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSend(); } }); } } // Initialize chat agent when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initChatAgent); } else { initChatAgent(); } // Also try after a short delay as fallback setTimeout(() => { const chatButton = document.getElementById('chatButton') || document.getElementById('chatAgentBtn'); if (chatButton && !chatButton.hasAttribute('data-chat-listener-attached')) { chatButton.setAttribute('data-chat-listener-attached', 'true'); initChatAgent(); } }, 100); // Conversation history for context let conversationHistory = []; function addChatMessage(message, isUser = false) { const chatMessages = document.getElementById('chatMessages'); if (!chatMessages) return; const messageDiv = document.createElement('div'); messageDiv.className = `chat-message ${isUser ? 'user' : 'bot'}-message`; const contentDiv = document.createElement('div'); contentDiv.className = 'message-content'; // Format message (support basic markdown-like formatting) const formattedMessage = formatMessage(message); contentDiv.innerHTML = formattedMessage; messageDiv.appendChild(contentDiv); chatMessages.appendChild(messageDiv); chatMessages.scrollTop = chatMessages.scrollHeight; // Store in history (keep last 10 messages) conversationHistory.push({ role: isUser ? 'user' : 'assistant', content: message }); if (conversationHistory.length > 10) { conversationHistory.shift(); } } function formatMessage(text) { // Convert URLs to links text = text.replace(/(https?:\/\/[^\s]+)/g, '$1'); // Convert page paths to links text = text.replace(/([a-z]+\/[^\.]+\.html)/g, '$1'); return text; } function addTypingIndicator() { const chatMessages = document.getElementById('chatMessages'); if (!chatMessages) return null; const typingDiv = document.createElement('div'); typingDiv.className = 'chat-message bot-message typing-indicator'; typingDiv.id = 'typing-indicator'; typingDiv.innerHTML = '
'; chatMessages.appendChild(typingDiv); chatMessages.scrollTop = chatMessages.scrollHeight; return 'typing-indicator'; } function removeTypingIndicator(id) { const indicator = document.getElementById(id); if (indicator) { indicator.remove(); } } // Mistral AI Integration async function getMistralResponse(userMessage) { const apiKey = localStorage.getItem('mistral_api_key'); if (!apiKey) { throw new Error('No API key'); } // Load knowledge base if not loaded if (!window.MISTRAL_TRAINING || !window.MISTRAL_TRAINING.KNOWLEDGE_BASE.length) { if (window.MISTRAL_TRAINING && window.MISTRAL_TRAINING.loadKnowledgeBase) { await window.MISTRAL_TRAINING.loadKnowledgeBase(); } } // Build context from knowledge base (RAG) const context = window.MISTRAL_TRAINING?.buildContext(userMessage) || ''; // Build messages array with system prompt and context const messages = [ { role: 'system', content: (window.MISTRAL_TRAINING?.SYSTEM_PROMPT || '') + '\n\n' + context }, ...conversationHistory.slice(-8), // Last 8 messages for context { role: 'user', content: userMessage } ]; const response = await fetch('https://api.mistral.ai/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: 'mistral-small', // Can be changed to mistral-medium or mistral-large-latest messages: messages, temperature: 0.7, max_tokens: 1000 }) }); if (!response.ok) { const error = await response.text(); throw new Error(`Mistral API error: ${error}`); } const data = await response.json(); return data.choices[0]?.message?.content || 'I apologize, but I couldn\'t generate a response.'; } // Fallback rule-based responses function getFallbackResponse(message) { const msg = message.toLowerCase(); // Check for common queries if (msg.includes('what is') || msg.includes('explain')) { if (msg.includes('piandt')) { return 'PIANDT is a Proportional Information Architecture for Networked Digital Transactions. It uses a triadic system (In, Processing, Out) to manage information flow. Would you like to know more about a specific stage?'; } if (msg.includes('triadic') || msg.includes('triad')) { return 'The triadic information architecture has three stages: In (incoming signals), Processing (analysis and transformation), and Out (delivered outputs). Each stage maintains proportional relationships (S_In ∝ S_Proc ∝ S_Out).'; } } if (msg.includes('in ') || msg.includes('incoming')) { return 'The In stage handles incoming signals from external stakeholders. You can explore In pages to see how different types of information are received.'; } if (msg.includes('processing') || msg.includes('proc')) { return 'The Processing stage analyzes and transforms incoming signals. Check the Processing pages to see how information is synthesized.'; } if (msg.includes('out ') || msg.includes('output')) { return 'The Out stage delivers processed signals to stakeholders. Visit Out pages to see how information is formatted and delivered.'; } if (msg.includes('miu') || msg.includes('machine intelligence')) { return 'MIU (Machine Intelligence Unit) is part of the Units section. It includes Vision, Products, and Services. Each has In, Processing, and Out variants.'; } if (msg.includes('bidirectional') || msg.includes('both direction')) { return 'Yes! PIANDT supports bidirectional information flow. Signals can flow from organization to external stakeholders and vice versa.'; } if (msg.includes('automation') || msg.includes('automated')) { return 'The structured nature of PIANDT enables full automation of business transactions through machine-readable formats and standardized protocols.'; } // Default response return 'I can help you navigate the PIANDT website! Ask me about:\n- The triadic information architecture (In, Processing, Out)\n- Specific pages or sections\n- MIU (Machine Intelligence Unit)\n- Bidirectionality and automation\n\nTo enable AI-powered responses, type: /setkey your-mistral-api-key'; } // ============================================ // Movie-Like Seamless Page Transitions // ============================================ // Provides invisible, ultra-smooth transitions like a movie // Crossfade technique for seamless navigation (function() { 'use strict'; // Prevent multiple initializations if (window.pageTransitionInitialized) return; window.pageTransitionInitialized = true; // Configuration for movie-like transitions - no blank screen const FADE_IN_DURATION = 80; // milliseconds - very fast const EASING = 'cubic-bezier(0.2, 0, 0.2, 1)'; // Linear-like for seamless feel // Initialize page - subtle fade-in only function initPageFadeIn() { // Start with opacity 0 for subtle fade-in document.body.style.opacity = '0'; document.body.classList.add('page-fade-in'); // Fade in immediately requestAnimationFrame(() => { document.body.style.opacity = '1'; // Remove fade-in class after animation setTimeout(() => { document.body.classList.remove('page-fade-in'); }, FADE_IN_DURATION); }); } // Handle link clicks with movie-like crossfade function handleLinkClicks() { // Get all internal links (same origin) const links = document.querySelectorAll('a[href]'); links.forEach(link => { // Skip if already has transition handler if (link.dataset.transitionHandler === 'true') return; link.dataset.transitionHandler = 'true'; const href = link.getAttribute('href'); // Skip if it's not a same-origin link or special links if (!href || href.startsWith('#') || href.startsWith('javascript:') || href.startsWith('mailto:') || href.startsWith('tel:') || (link.hasAttribute('target') && link.getAttribute('target') === '_blank') || link.hasAttribute('download')) { return; } // Check if it's a same-origin link try { const url = new URL(href, window.location.origin); if (url.origin !== window.location.origin) { return; // External link, skip } } catch (e) { // Relative URL, proceed } // Preload page on hover for instant transition link.addEventListener('mouseenter', function() { if (href && !href.startsWith('#')) { // Prefetch the page for instant loading const linkElement = document.createElement('link'); linkElement.rel = 'prefetch'; linkElement.href = href; document.head.appendChild(linkElement); } }, { passive: true }); // Add click handler - no fade-out to prevent blank screen link.addEventListener('click', function(e) { // Don't interfere with special clicks (Ctrl/Cmd, middle mouse, etc.) if (e.ctrlKey || e.metaKey || e.shiftKey || e.button !== 0) { return; } // Disable pointer events but keep page visible - no blank screen document.body.classList.add('page-transition-out'); // Allow navigation to proceed - browser handles the rest // The new page will fade in seamlessly without blank screen }, { passive: true }); }); } // Handle browser back/forward navigation function handleBrowserNavigation() { // Use pageshow event for seamless back/forward window.addEventListener('pageshow', function(event) { // Remove transition-out class document.body.classList.remove('page-transition-out'); // If page was loaded from cache (back/forward) if (event.persisted) { // Instant appearance - no fade needed document.body.style.opacity = '1'; } else { // Regular page load - subtle fade-in initPageFadeIn(); } }); // Handle page unload - no fade-out to prevent blank screen window.addEventListener('beforeunload', function() { // Just disable pointer events, keep page visible document.body.classList.add('page-transition-out'); }); } // Initialize when DOM is ready function initPageTransitions() { // Remove any existing transition classes document.body.classList.remove('page-transition-out'); // Initialize instant appearance initPageFadeIn(); // Setup link handlers handleLinkClicks(); // Setup browser navigation handlers handleBrowserNavigation(); } // Initialize based on document state if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initPageTransitions); } else { // DOM already loaded initPageTransitions(); } // Also initialize after a short delay to catch dynamically added links setTimeout(handleLinkClicks, 100); // Re-initialize link handlers when new content is added (for dynamic menus) const observer = new MutationObserver(function(mutations) { let shouldReinit = false; mutations.forEach(function(mutation) { if (mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(function(node) { if (node.nodeType === 1 && (node.tagName === 'A' || node.querySelector('a'))) { shouldReinit = true; } }); } }); if (shouldReinit) { handleLinkClicks(); } }); // Observe the document for new links if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); } // Preload pages on menu hover for instant transitions document.addEventListener('mouseover', function(e) { const link = e.target.closest('a[href]'); if (link && link.dataset.transitionHandler === 'true') { const href = link.getAttribute('href'); if (href && !href.startsWith('#') && !href.startsWith('javascript:')) { try { const url = new URL(href, window.location.origin); if (url.origin === window.location.origin) { // Prefetch for instant loading const prefetchLink = document.createElement('link'); prefetchLink.rel = 'prefetch'; prefetchLink.href = href; if (!document.querySelector(`link[rel="prefetch"][href="${href}"]`)) { document.head.appendChild(prefetchLink); } } } catch (e) { // Invalid URL, skip } } } }, { passive: true }); })(); // ============================================ // Hard Refresh on Resize and Column Changes // ============================================ // Automatically hard refreshes page when viewport or column sizes change (function() { 'use strict'; // Prevent multiple initializations if (window.resizeRefreshInitialized) return; window.resizeRefreshInitialized = true; let resizeTimer; let isRefreshing = false; let lastWidth = window.innerWidth; let lastHeight = window.innerHeight; // Get current column count based on viewport width - MUST be defined first function getColumnCount() { const width = window.innerWidth; if (width < 768) return 1; // Mobile if (width < 1024) return 2; // Tablet if (width < 1440) return 3; // Desktop return 4; // Large desktop } let lastColumnCount = getColumnCount(); // Hard refresh the page - simple and direct function hardRefresh() { if (isRefreshing) return; isRefreshing = true; // Force hard refresh - bypass cache window.location.reload(true); } // Handle resize events - refresh on ANY resize function handleResize() { if (isRefreshing) return; const currentWidth = window.innerWidth; const currentHeight = window.innerHeight; const currentColumnCount = getColumnCount(); // Check if column count changed (breakpoint crossed) if (currentColumnCount !== lastColumnCount) { lastColumnCount = currentColumnCount; clearTimeout(resizeTimer); resizeTimer = setTimeout(hardRefresh, 200); return; } // Check if size changed significantly const widthChanged = Math.abs(currentWidth - lastWidth) > 10; const heightChanged = Math.abs(currentHeight - lastHeight) > 10; if (widthChanged || heightChanged) { lastWidth = currentWidth; lastHeight = currentHeight; clearTimeout(resizeTimer); resizeTimer = setTimeout(hardRefresh, 200); } } // Handle orientation changes function handleOrientationChange() { if (isRefreshing) return; setTimeout(hardRefresh, 100); } // Monitor column changes with ResizeObserver function observeColumnChanges() { const contentElements = document.querySelectorAll('main, .content-grid, .section, .container'); if (contentElements.length > 0 && window.ResizeObserver) { const resizeObserver = new ResizeObserver(function(entries) { if (isRefreshing) return; for (let entry of entries) { const currentColumnCount = getColumnCount(); if (currentColumnCount !== lastColumnCount) { lastColumnCount = currentColumnCount; clearTimeout(resizeTimer); resizeTimer = setTimeout(hardRefresh, 200); break; } } }); contentElements.forEach(function(element) { resizeObserver.observe(element); }); } } // Initialize immediately function initResizeRefresh() { // Listen for window resize window.addEventListener('resize', handleResize, { passive: true }); // Listen for orientation changes window.addEventListener('orientationchange', handleOrientationChange, { passive: true }); // Monitor column changes observeColumnChanges(); // Re-observe after delay for dynamic content setTimeout(observeColumnChanges, 500); } // Initialize immediately if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initResizeRefresh); } else { initResizeRefresh(); } // Re-observe when new content is added if (document.body) { const observer = new MutationObserver(function() { observeColumnChanges(); }); observer.observe(document.body, { childList: true, subtree: true }); } })(); /** * Collaboration enquiry form. * Placeholder until ADMIN_EMAIL is configured — then submissions open a mailto: draft. * Set either: * window.PIANDT_ADMIN_EMAIL = 'you@example.org'; * or replace PIANDT_ADMIN_EMAIL below. */ (function() { const PIANDT_ADMIN_EMAIL = (typeof window !== 'undefined' && window.PIANDT_ADMIN_EMAIL) || ''; function initCollaborateForm() { const form = document.getElementById('collaborateForm'); if (!form) return; const statusEl = document.getElementById('collaborateFormStatus'); const emailDisplay = document.getElementById('collabAdminEmailDisplay'); if (emailDisplay) { emailDisplay.textContent = PIANDT_ADMIN_EMAIL ? PIANDT_ADMIN_EMAIL : 'Admin email not set yet (placeholder)'; } form.addEventListener('submit', function(event) { event.preventDefault(); const name = (form.elements.namedItem('name') || {}).value || ''; const organisation = (form.elements.namedItem('organisation') || {}).value || ''; const email = (form.elements.namedItem('email') || {}).value || ''; const scope = (form.elements.namedItem('scope') || {}).value || ''; const message = (form.elements.namedItem('message') || {}).value || ''; if (!name.trim() || !email.trim() || !scope || !message.trim()) { if (statusEl) { statusEl.className = 'form-status is-error'; statusEl.textContent = 'Please complete the required fields.'; } return; } const payload = { name: name.trim(), organisation: organisation.trim(), email: email.trim(), scope: scope, message: message.trim(), submittedAt: new Date().toISOString() }; // Keep a local copy until email forwarding is wired try { const key = 'piandt_collaboration_enquiries'; const existing = JSON.parse(localStorage.getItem(key) || '[]'); existing.push(payload); localStorage.setItem(key, JSON.stringify(existing)); } catch (err) { console.warn('Could not store collaboration enquiry locally:', err); } if (!PIANDT_ADMIN_EMAIL) { if (statusEl) { statusEl.className = 'form-status is-pending'; statusEl.textContent = 'Thank you. Your enquiry was saved locally. Email forwarding is a placeholder until an admin email is configured.'; } form.reset(); return; } const subject = encodeURIComponent('PIANDT collaboration enquiry — ' + payload.name); const body = encodeURIComponent( [ 'Name: ' + payload.name, 'Organisation: ' + (payload.organisation || '(not provided)'), 'Email: ' + payload.email, 'Collaboration type: ' + payload.scope, '', payload.message, '', 'Submitted: ' + payload.submittedAt ].join('\n') ); window.location.href = 'mailto:' + PIANDT_ADMIN_EMAIL + '?subject=' + subject + '&body=' + body; if (statusEl) { statusEl.className = 'form-status is-success'; statusEl.textContent = 'Opening your email client to send this enquiry…'; } form.reset(); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initCollaborateForm); } else { initCollaborateForm(); } })();