Hi there, young explorer! π
+Testing mobile responsiveness! This should look great on all screen sizes.
+Mobile Conversation Test
+Button Test
+ +Ready to see the answer?
+You've done great thinking! Now let's see what the experts say.
+diff --git a/html/kidsai/mobile-keyboard-handler.js b/html/kidsai/mobile-keyboard-handler.js new file mode 100644 index 0000000..4a8cbea --- /dev/null +++ b/html/kidsai/mobile-keyboard-handler.js @@ -0,0 +1,388 @@ +// Mobile Keyboard Overlay Handler +// This script dynamically adjusts the layout when the virtual keyboard appears + +(function() { + 'use strict'; + + // Only run on mobile devices + if (!/Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { + return; + } + + let initialViewportHeight = window.innerHeight; + let isKeyboardOpen = false; + let activeInputContainer = null; // Track which input container is currently active + let focusedInput = null; // Track the currently focused input element + + // Function to handle viewport changes (keyboard show/hide) + function handleViewportChange() { + const currentHeight = window.innerHeight; + const heightDifference = initialViewportHeight - currentHeight; + const threshold = 150; // Minimum height change to consider keyboard open + + const conversationContainer = document.querySelector('.conversation-container'); + const allChatInputContainers = document.querySelectorAll('.chat-input-container'); + const body = document.body; + + // Update debug counter + const visibleInputs = Array.from(allChatInputContainers).filter( + container => container.style.display !== 'none' + ).length; + body.setAttribute('data-visible-inputs', visibleInputs); + + if (heightDifference > threshold && !isKeyboardOpen) { + // Keyboard opened + isKeyboardOpen = true; + console.log('Keyboard opened, adjusting layout. Found', allChatInputContainers.length, 'input containers'); + + if (conversationContainer) { + conversationContainer.classList.add('keyboard-open'); + conversationContainer.style.height = `${currentHeight * 0.4}px`; + conversationContainer.style.maxHeight = `${currentHeight * 0.4}px`; + } + + // Apply keyboard positioning to ALL chat input containers + allChatInputContainers.forEach((chatInputContainer, index) => { + if (chatInputContainer) { + console.log(`Processing input container ${index + 1}/${allChatInputContainers.length}`); + chatInputContainer.style.position = 'fixed'; + chatInputContainer.style.bottom = '0'; + chatInputContainer.style.left = '0'; + chatInputContainer.style.right = '0'; + chatInputContainer.style.zIndex = '1000'; + chatInputContainer.style.background = 'white'; + chatInputContainer.style.borderTop = '1px solid #e2e8f0'; + chatInputContainer.style.padding = '15px'; + chatInputContainer.style.paddingBottom = '20px'; + chatInputContainer.style.boxShadow = '0 -2px 10px rgba(0, 0, 0, 0.1)'; + chatInputContainer.style.margin = '0'; + chatInputContainer.style.borderRadius = '0'; + chatInputContainer.classList.add('keyboard-fixed'); + + // Ensure the input area is properly styled + const inputArea = chatInputContainer.querySelector('.input-area'); + if (inputArea) { + inputArea.style.margin = '0'; + inputArea.style.borderRadius = '20px'; + inputArea.style.background = '#f8f9fa'; + } + } + }); + + // Hide non-active input containers + if (activeInputContainer) { + allChatInputContainers.forEach(container => { + if (container !== activeInputContainer) { + container.style.display = 'none'; + console.log('Hiding non-active input container'); + } else { + console.log('Keeping active input container visible'); + } + }); + } + + body.classList.add('keyboard-open'); + + // Update debug counter after hiding + const visibleAfter = Array.from(allChatInputContainers).filter( + container => container.style.display !== 'none' + ).length; + body.setAttribute('data-visible-inputs', visibleAfter); + + } else if (heightDifference <= threshold && isKeyboardOpen) { + // Keyboard closed + isKeyboardOpen = false; + console.log('Keyboard closed, restoring layout for', allChatInputContainers.length, 'input containers'); + + if (conversationContainer) { + conversationContainer.classList.remove('keyboard-open'); + conversationContainer.style.removeProperty('height'); + conversationContainer.style.removeProperty('max-height'); + } + + // Reset ALL chat input containers + allChatInputContainers.forEach((chatInputContainer, index) => { + if (chatInputContainer) { + console.log(`Restoring input container ${index + 1}/${allChatInputContainers.length}`); + chatInputContainer.style.removeProperty('position'); + chatInputContainer.style.removeProperty('bottom'); + chatInputContainer.style.removeProperty('left'); + chatInputContainer.style.removeProperty('right'); + chatInputContainer.style.removeProperty('z-index'); + chatInputContainer.style.removeProperty('background'); + chatInputContainer.style.removeProperty('border-top'); + chatInputContainer.style.removeProperty('padding'); + chatInputContainer.style.removeProperty('padding-bottom'); + chatInputContainer.style.removeProperty('box-shadow'); + chatInputContainer.style.removeProperty('margin'); + chatInputContainer.style.removeProperty('border-radius'); + chatInputContainer.style.removeProperty('display'); + chatInputContainer.classList.remove('keyboard-fixed'); + + // Reset input area styles + const inputArea = chatInputContainer.querySelector('.input-area'); + if (inputArea) { + inputArea.style.removeProperty('margin'); + inputArea.style.removeProperty('border-radius'); + inputArea.style.removeProperty('background'); + } + } + }); + + activeInputContainer = null; + body.classList.remove('keyboard-open'); + body.removeAttribute('data-visible-inputs'); + } + } + + // Function to scroll input into view when focused + function scrollInputIntoView(inputElement) { + setTimeout(() => { + // Always ensure input is visible when focused + const elementRect = inputElement.getBoundingClientRect(); + const viewportHeight = window.innerHeight; + const keyboardHeight = initialViewportHeight - viewportHeight; + + // If there's likely a keyboard (height reduced by significant amount) + if (keyboardHeight > 150) { + const availableHeight = viewportHeight; + const inputBottom = elementRect.bottom; + + // If input is in bottom 40% of available viewport, scroll it up + if (inputBottom > availableHeight * 0.6) { + // Scroll the input container's parent into view + const container = inputElement.closest('.conversation-container') || + inputElement.closest('.thinking-section'); + + if (container) { + container.scrollTop = container.scrollHeight - availableHeight * 0.5; + } + + // Also try scrolling the window + window.scrollTo({ + top: window.scrollY + (inputBottom - availableHeight * 0.4), + behavior: 'smooth' + }); + } + } + }, 100); // Shorter delay for more responsive feel + } + + // Enhanced focus handler for chat textarea + function handleChatInputFocus(event) { + const chatTextarea = event.target; + focusedInput = chatTextarea; + + // Add focused class for styling + chatTextarea.classList.add('focused'); + + // Immediately apply keyboard-friendly positioning + const chatInputContainer = chatTextarea.closest('.chat-input-container'); + if (chatInputContainer && window.innerWidth <= 768) { + // Set this as the active input container + activeInputContainer = chatInputContainer; + + // Hide all other input containers immediately + const allInputContainers = document.querySelectorAll('.chat-input-container'); + allInputContainers.forEach(container => { + if (container !== chatInputContainer) { + container.style.display = 'none'; + } + }); + + // Apply keyboard positioning immediately on focus + setTimeout(() => { + chatInputContainer.style.position = 'fixed'; + chatInputContainer.style.bottom = '0'; + chatInputContainer.style.left = '0'; + chatInputContainer.style.right = '0'; + chatInputContainer.style.zIndex = '1001'; + chatInputContainer.style.background = 'white'; + chatInputContainer.style.borderTop = '1px solid #e2e8f0'; + chatInputContainer.style.padding = '15px'; + chatInputContainer.style.paddingBottom = '25px'; + chatInputContainer.style.boxShadow = '0 -2px 10px rgba(0, 0, 0, 0.1)'; + chatInputContainer.style.margin = '0'; + chatInputContainer.style.display = 'block'; // Ensure this one is visible + chatInputContainer.classList.add('input-focused'); + + // Add class to body for CSS targeting (fallback for :has() selector) + document.body.classList.add('input-focused-active'); + + // Adjust conversation container + const conversationContainer = document.querySelector('.conversation-container'); + if (conversationContainer) { + conversationContainer.style.paddingBottom = '80px'; + conversationContainer.style.marginBottom = '0'; + } + }, 50); + } + + // Scroll into view if keyboard is likely to appear + scrollInputIntoView(chatTextarea); + + // For iOS, prevent zoom by ensuring font-size is 16px + if (/iPhone|iPad|iPod/i.test(navigator.userAgent)) { + chatTextarea.style.fontSize = '16px'; + } + } + + function handleChatInputBlur(event) { + event.target.classList.remove('focused'); + + // Clear focused input reference + if (focusedInput === event.target) { + focusedInput = null; + } + + // Clean up keyboard positioning when input loses focus + const chatInputContainer = event.target.closest('.chat-input-container'); + if (chatInputContainer && chatInputContainer.classList.contains('input-focused')) { + setTimeout(() => { + // Only remove if keyboard is not detected as open + if (!isKeyboardOpen) { + // Show all input containers again + const allInputContainers = document.querySelectorAll('.chat-input-container'); + allInputContainers.forEach(container => { + container.style.removeProperty('display'); + }); + + chatInputContainer.style.removeProperty('position'); + chatInputContainer.style.removeProperty('bottom'); + chatInputContainer.style.removeProperty('left'); + chatInputContainer.style.removeProperty('right'); + chatInputContainer.style.removeProperty('z-index'); + chatInputContainer.style.removeProperty('background'); + chatInputContainer.style.removeProperty('border-top'); + chatInputContainer.style.removeProperty('padding'); + chatInputContainer.style.removeProperty('padding-bottom'); + chatInputContainer.style.removeProperty('box-shadow'); + chatInputContainer.style.removeProperty('margin'); + chatInputContainer.classList.remove('input-focused'); + + // Remove body class + document.body.classList.remove('input-focused-active'); + + // Reset conversation container + const conversationContainer = document.querySelector('.conversation-container'); + if (conversationContainer) { + conversationContainer.style.removeProperty('padding-bottom'); + conversationContainer.style.removeProperty('margin-bottom'); + } + + // Clear active input container + if (activeInputContainer === chatInputContainer) { + activeInputContainer = null; + } + } + }, 300); // Delay to avoid flicker + } + } + + // Debounced resize handler + let resizeTimeout; + function debouncedViewportChange() { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(handleViewportChange, 150); + } + + // Initialize when DOM is ready + function initialize() { + console.log('Initializing mobile keyboard handler'); + + // Store initial viewport height + initialViewportHeight = window.innerHeight; + + // Listen for viewport changes + window.addEventListener('resize', debouncedViewportChange); + + // Listen for orientation changes + window.addEventListener('orientationchange', () => { + setTimeout(() => { + initialViewportHeight = window.innerHeight; + handleViewportChange(); + }, 500); // Wait for orientation change to complete + }); + + // Add focus/blur handlers for chat inputs (using event delegation for dynamic content) + document.addEventListener('focus', (event) => { + if (event.target.matches('.chat-textarea, #question-input, .step-thinking-space textarea')) { + handleChatInputFocus(event); + } + }, true); + + document.addEventListener('blur', (event) => { + if (event.target.matches('.chat-textarea, #question-input, .step-thinking-space textarea')) { + handleChatInputBlur(event); + } + }, true); + + // Listen for new chat input containers being added to the DOM + if (window.MutationObserver) { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1) { // Element node + // Check if the added node contains chat input containers + const newInputContainers = node.querySelectorAll ? + node.querySelectorAll('.chat-input-container') : []; + + if (newInputContainers.length > 0) { + console.log('New chat input containers detected:', newInputContainers.length); + // If keyboard is currently open and we have an active input, hide the new ones + if (isKeyboardOpen && activeInputContainer) { + newInputContainers.forEach(container => { + if (container !== activeInputContainer) { + container.style.display = 'none'; + } + }); + } + } + } + }); + }); + }); + + // Observe the conversation container for new additions + const conversationContainer = document.querySelector('.conversation-container'); + if (conversationContainer) { + observer.observe(conversationContainer, { + childList: true, + subtree: true + }); + } + + // Also observe the main content area + const mainContent = document.querySelector('.main-content, .thinking-steps'); + if (mainContent) { + observer.observe(mainContent, { + childList: true, + subtree: true + }); + } + } + + // Visual Viewport API support (newer browsers) + if (window.visualViewport) { + window.visualViewport.addEventListener('resize', () => { + const heightDifference = window.innerHeight - window.visualViewport.height; + if (heightDifference > 150 && !isKeyboardOpen) { + handleViewportChange(); + } else if (heightDifference <= 150 && isKeyboardOpen) { + handleViewportChange(); + } + }); + } + } + + // Wait for DOM to be ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initialize); + } else { + initialize(); + } + + // Add utility class for JavaScript + document.documentElement.classList.add('js-mobile-keyboard-handler'); + +})(); diff --git a/html/kidsai/mobile-test.html b/html/kidsai/mobile-test.html new file mode 100644 index 0000000..8319b58 --- /dev/null +++ b/html/kidsai/mobile-test.html @@ -0,0 +1,203 @@ + + +
+ + +Think, Learn, Discover Together!
+Testing mobile responsiveness! This should look great on all screen sizes.
+You've done great thinking! Now let's see what the experts say.
+