Spaces:
Running
Running
You are an expert AI web developer and UI/UX designer. I want you to create a **fully-featured, mobile-first NeuroScreen web app** for smartphones that is low-cost, ultra-lightweight, and ideal for rural or low-resource areas. Generate **complete HTML, CSS, and JavaScript** with a modern, clean, and user-friendly design. Include comments for each section and placeholders for AI integration via serverless functions.
8302fb5 verified | <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>NeuroScreen</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <style> | |
| /* Custom CSS for animations and specific components */ | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .pulse-animation { | |
| animation: pulse 2s infinite; | |
| } | |
| .waveform { | |
| background: linear-gradient(90deg, #3b82f6 0%, #3b82f6 var(--progress, 0%), #e5e7eb var(--progress, 0%), #e5e7eb 100%); | |
| } | |
| .dot-follow { | |
| transition: all 0.3s ease-out; | |
| } | |
| .test-card { | |
| transition: all 0.2s ease; | |
| } | |
| .test-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| } | |
| /* Hide scrollbar but keep functionality */ | |
| .no-scrollbar::-webkit-scrollbar { | |
| display: none; | |
| } | |
| .no-scrollbar { | |
| -ms-overflow-style: none; | |
| scrollbar-width: none; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 font-sans text-gray-800 min-h-screen"> | |
| <!-- Main App Container --> | |
| <div id="app" class="max-w-md mx-auto bg-white min-h-screen shadow-lg overflow-hidden flex flex-col"> | |
| <!-- Header --> | |
| <header class="bg-gradient-to-r from-blue-500 to-purple-600 text-white p-4 shadow-md"> | |
| <div class="flex items-center justify-between"> | |
| <h1 class="text-2xl font-bold flex items-center"> | |
| <i data-feather="activity" class="mr-2"></i> | |
| NeuroScreen | |
| </h1> | |
| <button id="menu-btn" class="p-2 rounded-full hover:bg-white/10"> | |
| <i data-feather="menu"></i> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content Area - Will be dynamically updated --> | |
| <main id="content" class="flex-1 overflow-y-auto no-scrollbar p-4"> | |
| <!-- Home Screen (default view) --> | |
| <div id="home-screen"> | |
| <div class="mb-6"> | |
| <h2 class="text-xl font-semibold mb-2">Welcome to NeuroScreen</h2> | |
| <p class="text-gray-600 mb-4">A simple neurological screening tool for early detection of potential issues.</p> | |
| <div class="bg-blue-50 border-l-4 border-blue-500 p-4 mb-4"> | |
| <p class="text-blue-700">For best results: Find a quiet space, ensure good lighting, and complete all tests.</p> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <!-- Cognitive Test Card --> | |
| <div class="test-card bg-white rounded-lg shadow p-4 border border-gray-200 cursor-pointer" onclick="showScreen('cognitive')"> | |
| <div class="text-blue-500 mb-2"> | |
| <i data-feather="cpu" class="w-8 h-8"></i> | |
| </div> | |
| <h3 class="font-medium mb-1">Cognitive Test</h3> | |
| <p class="text-sm text-gray-500">Memory & reaction time</p> | |
| </div> | |
| <!-- Speech Test Card --> | |
| <div class="test-card bg-white rounded-lg shadow p-4 border border-gray-200 cursor-pointer" onclick="showScreen('speech')"> | |
| <div class="text-purple-500 mb-2"> | |
| <i data-feather="mic" class="w-8 h-8"></i> | |
| </div> | |
| <h3 class="font-medium mb-1">Speech Test</h3> | |
| <p class="text-sm text-gray-500">Voice analysis</p> | |
| </div> | |
| <!-- Eye Tracking Card --> | |
| <div class="test-card bg-white rounded-lg shadow p-4 border border-gray-200 cursor-pointer" onclick="showScreen('eye')"> | |
| <div class="text-green-500 mb-2"> | |
| <i data-feather="eye" class="w-8 h-8"></i> | |
| </div> | |
| <h3 class="font-medium mb-1">Eye Tracking</h3> | |
| <p class="text-sm text-gray-500">Visual attention</p> | |
| </div> | |
| <!-- Tremor Test Card --> | |
| <div class="test-card bg-white rounded-lg shadow p-4 border border-gray-200 cursor-pointer" onclick="showScreen('tremor')"> | |
| <div class="text-yellow-500 mb-2"> | |
| <i data-feather="wind" class="w-8 h-8"></i> | |
| </div> | |
| <h3 class="font-medium mb-1">Tremor Test</h3> | |
| <p class="text-sm text-gray-500">Movement analysis</p> | |
| </div> | |
| </div> | |
| <!-- Results Button --> | |
| <button onclick="showScreen('results')" class="w-full mt-6 bg-gradient-to-r from-blue-500 to-purple-600 text-white py-3 px-4 rounded-lg shadow hover:shadow-md transition-all flex items-center justify-center"> | |
| <i data-feather="clipboard" class="mr-2"></i> | |
| View Results | |
| </button> | |
| </div> | |
| <!-- Cognitive Test Screen --> | |
| <div id="cognitive-screen" class="hidden"> | |
| <div class="flex items-center mb-4"> | |
| <button onclick="showScreen('home')" class="p-2 rounded-full hover:bg-gray-100 mr-2"> | |
| <i data-feather="arrow-left"></i> | |
| </button> | |
| <h2 class="text-xl font-semibold">Cognitive Test</h2> | |
| </div> | |
| <div class="bg-gray-100 rounded-lg p-4 mb-4"> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-sm font-medium">Test Progress</span> | |
| <span class="text-sm">1/2</span> | |
| </div> | |
| <div class="w-full bg-gray-300 rounded-full h-2"> | |
| <div class="bg-blue-500 h-2 rounded-full" style="width: 50%"></div> | |
| </div> | |
| </div> | |
| <!-- Memory Test --> | |
| <div id="memory-test" class="mb-6"> | |
| <h3 class="font-medium mb-2">Memory Recall</h3> | |
| <p class="text-gray-600 mb-4">Remember the sequence of numbers shown below:</p> | |
| <div id="memory-display" class="bg-blue-50 rounded-lg p-6 text-center mb-4"> | |
| <p class="text-4xl font-bold text-blue-600">3 7 2 5</p> | |
| </div> | |
| <div class="grid grid-cols-3 gap-2 mb-4"> | |
| <button class="bg-gray-200 hover:bg-gray-300 p-4 rounded-lg font-medium">1</button> | |
| <button class="bg-gray-200 hover:bg-gray-300 p-4 rounded-lg font-medium">2</button> | |
| <button class="bg-gray-200 hover:bg-gray-300 p-4 rounded-lg font-medium">3</button> | |
| <button class="bg-gray-200 hover:bg-gray-300 p-4 rounded-lg font-medium">4</button> | |
| <button class="bg-gray-200 hover:bg-gray-300 p-4 rounded-lg font-medium">5</button> | |
| <button class="bg-gray-200 hover:bg-gray-300 p-4 rounded-lg font-medium">6</button> | |
| <button class="bg-gray-200 hover:bg-gray-300 p-4 rounded-lg font-medium">7</button> | |
| <button class="bg-gray-200 hover:bg-gray-300 p-4 rounded-lg font-medium">8</button> | |
| <button class="bg-gray-200 hover:bg-gray-300 p-4 rounded-lg font-medium">9</button> | |
| </div> | |
| <button id="memory-submit" class="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600"> | |
| Submit Sequence | |
| </button> | |
| </div> | |
| <!-- Reaction Test --> | |
| <div id="reaction-test" class="hidden"> | |
| <h3 class="font-medium mb-2">Reaction Time</h3> | |
| <p class="text-gray-600 mb-4">Tap the button when it turns green:</p> | |
| <div id="reaction-box" class="bg-red-500 h-40 rounded-lg flex items-center justify-center mb-4 cursor-pointer"> | |
| <p class="text-white font-bold">Wait for green...</p> | |
| </div> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-sm text-gray-600">Reaction time: <span id="reaction-time">0</span>ms</span> | |
| <button id="reaction-start" class="bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600"> | |
| Start Test | |
| </button> | |
| </div> | |
| </div> | |
| <!-- AI Analysis Placeholder --> | |
| <div id="cognitive-analysis" class="hidden mt-6 p-4 bg-gray-50 rounded-lg border border-gray-200"> | |
| <h3 class="font-medium mb-2 flex items-center"> | |
| <i data-feather="activity" class="mr-2 text-blue-500"></i> | |
| Analysis Results | |
| </h3> | |
| <p id="cognitive-feedback" class="text-gray-700 mb-2">Processing cognitive test results...</p> | |
| <div class="text-right"> | |
| <button onclick="analyzeCognitiveTest()" class="text-blue-500 text-sm hover:underline"> | |
| <i data-feather="refresh-cw" class="w-4 h-4 inline mr-1"></i> | |
| Re-analyze | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Speech Test Screen --> | |
| <div id="speech-screen" class="hidden"> | |
| <div class="flex items-center mb-4"> | |
| <button onclick="showScreen('home')" class="p-2 rounded-full hover:bg-gray-100 mr-2"> | |
| <i data-feather="arrow-left"></i> | |
| </button> | |
| <h2 class="text-xl font-semibold">Speech Test</h2> | |
| </div> | |
| <div class="bg-gray-100 rounded-lg p-4 mb-4"> | |
| <p class="text-gray-700 mb-2">Read the following sentence aloud:</p> | |
| <div class="bg-white p-4 rounded border border-gray-200 mb-4"> | |
| <p class="text-lg font-medium">"The quick brown fox jumps over the lazy dog."</p> | |
| </div> | |
| </div> | |
| <!-- Recording UI --> | |
| <div class="mb-6"> | |
| <div id="waveform" class="waveform h-16 rounded-lg mb-4" style="--progress: 0%"></div> | |
| <div class="flex justify-center"> | |
| <button id="record-btn" class="bg-red-500 text-white p-4 rounded-full hover:bg-red-600 flex items-center justify-center"> | |
| <i data-feather="mic" class="w-6 h-6"></i> | |
| </button> | |
| </div> | |
| <p id="record-status" class="text-center text-gray-600 mt-2">Press and hold to record</p> | |
| </div> | |
| <!-- AI Analysis Placeholder --> | |
| <div id="speech-analysis" class="hidden p-4 bg-gray-50 rounded-lg border border-gray-200"> | |
| <h3 class="font-medium mb-2 flex items-center"> | |
| <i data-feather="activity" class="mr-2 text-purple-500"></i> | |
| Speech Analysis | |
| </h3> | |
| <p id="speech-feedback" class="text-gray-700 mb-2">No recording analyzed yet.</p> | |
| <div class="text-right"> | |
| <button onclick="analyzeSpeech()" class="text-purple-500 text-sm hover:underline"> | |
| <i data-feather="refresh-cw" class="w-4 h-4 inline mr-1"></i> | |
| Analyze Recording | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Eye Tracking Test Screen --> | |
| <div id="eye-screen" class="hidden"> | |
| <div class="flex items-center mb-4"> | |
| <button onclick="showScreen('home')" class="p-2 rounded-full hover:bg-gray-100 mr-2"> | |
| <i data-feather="arrow-left"></i> | |
| </button> | |
| <h2 class="text-xl font-semibold">Eye Tracking Test</h2> | |
| </div> | |
| <div class="bg-gray-100 rounded-lg p-4 mb-4"> | |
| <p class="text-gray-700 mb-2">Follow the dot with your eyes. Keep your head still.</p> | |
| </div> | |
| <!-- Camera Feed --> | |
| <div class="relative bg-black rounded-lg overflow-hidden mb-4" style="height: 300px;"> | |
| <video id="eye-video" autoplay playsinline class="w-full h-full object-cover"></video> | |
| <div id="eye-dot" class="dot-follow absolute w-8 h-8 bg-red-500 rounded-full" style="top: 50%; left: 50%; transform: translate(-50%, -50%);"></div> | |
| <div class="absolute inset-0 flex items-center justify-center" id="eye-instructions"> | |
| <p class="text-white bg-black/50 p-4 rounded-lg">Allow camera access to begin</p> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 mb-4"> | |
| <button id="start-eye-test" class="bg-green-500 text-white py-2 px-4 rounded-lg hover:bg-green-600"> | |
| Start Test | |
| </button> | |
| <button id="stop-eye-test" class="bg-gray-300 text-gray-700 py-2 px-4 rounded-lg hover:bg-gray-400"> | |
| Stop Test | |
| </button> | |
| </div> | |
| <!-- AI Analysis Placeholder --> | |
| <div id="eye-analysis" class="hidden p-4 bg-gray-50 rounded-lg border border-gray-200"> | |
| <h3 class="font-medium mb-2 flex items-center"> | |
| <i data-feather="activity" class="mr-2 text-green-500"></i> | |
| Eye Tracking Analysis | |
| </h3> | |
| <p id="eye-feedback" class="text-gray-700 mb-2">No test data to analyze.</p> | |
| <div class="text-right"> | |
| <button onclick="analyzeEyeTracking()" class="text-green-500 text-sm hover:underline"> | |
| <i data-feather="refresh-cw" class="w-4 h-4 inline mr-1"></i> | |
| Analyze Results | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Tremor Test Screen --> | |
| <div id="tremor-screen" class="hidden"> | |
| <div class="flex items-center mb-4"> | |
| <button onclick="showScreen('home')" class="p-2 rounded-full hover:bg-gray-100 mr-2"> | |
| <i data-feather="arrow-left"></i> | |
| </button> | |
| <h2 class="text-xl font-semibold">Tremor Test</h2> | |
| </div> | |
| <div class="bg-gray-100 rounded-lg p-4 mb-4"> | |
| <p class="text-gray-700 mb-2">Hold your device steady for 15 seconds to detect tremors.</p> | |
| </div> | |
| <!-- Visual Guide --> | |
| <div class="bg-blue-50 rounded-lg p-6 text-center mb-4"> | |
| <div class="relative mx-auto w-32 h-48 border-4 border-gray-300 rounded-lg mb-4"> | |
| <div class="absolute inset-0 flex items-center justify-center"> | |
| <div class="w-16 h-16 bg-yellow-400 rounded-full flex items-center justify-center"> | |
| <i data-feather="smartphone" class="text-white"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <p class="text-gray-700">Hold your phone like this</p> | |
| </div> | |
| <!-- Test Controls --> | |
| <div class="mb-6"> | |
| <div id="tremor-visual" class="bg-white border border-gray-200 rounded-lg p-4 mb-4 h-32 flex items-center justify-center"> | |
| <p id="tremor-status" class="text-gray-600">Ready to begin test</p> | |
| </div> | |
| <button id="start-tremor-test" class="w-full bg-yellow-500 text-white py-3 px-4 rounded-lg hover:bg-yellow-600"> | |
| Start Tremor Test | |
| </button> | |
| </div> | |
| <!-- AI Analysis Placeholder --> | |
| <div id="tremor-analysis" class="hidden p-4 bg-gray-50 rounded-lg border border-gray-200"> | |
| <h3 class="font-medium mb-2 flex items-center"> | |
| <i data-feather="activity" class="mr-2 text-yellow-500"></i> | |
| Tremor Analysis | |
| </h3> | |
| <p id="tremor-feedback" class="text-gray-700 mb-2">No test data to analyze.</p> | |
| <div class="text-right"> | |
| <button onclick="analyzeTremor()" class="text-yellow-500 text-sm hover:underline"> | |
| <i data-feather="refresh-cw" class="w-4 h-4 inline mr-1"></i> | |
| Analyze Results | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Results Screen --> | |
| <div id="results-screen" class="hidden"> | |
| <div class="flex items-center mb-4"> | |
| <button onclick="showScreen('home')" class="p-2 rounded-full hover:bg-gray-100 mr-2"> | |
| <i data-feather="arrow-left"></i> | |
| </button> | |
| <h2 class="text-xl font-semibold">Test Results</h2> | |
| </div> | |
| <!-- Summary Card --> | |
| <div class="bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-lg p-4 mb-6"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <h3 class="font-medium">Overall Assessment</h3> | |
| <span id="overall-status" class="bg-white text-blue-600 px-3 py-1 rounded-full text-sm font-medium">Pending</span> | |
| </div> | |
| <p id="overall-feedback" class="text-sm">Complete all tests for a full assessment.</p> | |
| </div> | |
| <!-- Test Results --> | |
| <div class="space-y-4"> | |
| <!-- Cognitive Results --> | |
| <div class="bg-white rounded-lg shadow p-4 border border-gray-200"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <h3 class="font-medium flex items-center"> | |
| <i data-feather="cpu" class="mr-2 text-blue-500"></i> | |
| Cognitive Test | |
| </h3> | |
| <span id="cognitive-status" class="text-sm px-3 py-1 rounded-full bg-gray-100">Not taken</span> | |
| </div> | |
| <p id="cognitive-result" class="text-sm text-gray-600">No data available</p> | |
| </div> | |
| <!-- Speech Results --> | |
| <div class="bg-white rounded-lg shadow p-4 border border-gray-200"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <h3 class="font-medium flex items-center"> | |
| <i data-feather="mic" class="mr-2 text-purple-500"></i> | |
| Speech Test | |
| </h3> | |
| <span id="speech-status" class="text-sm px-3 py-1 rounded-full bg-gray-100">Not taken</span> | |
| </div> | |
| <p id="speech-result" class="text-sm text-gray-600">No data available</p> | |
| </div> | |
| <!-- Eye Tracking Results --> | |
| <div class="bg-white rounded-lg shadow p-4 border border-gray-200"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <h3 class="font-medium flex items-center"> | |
| <i data-feather="eye" class="mr-2 text-green-500"></i> | |
| Eye Tracking | |
| </h3> | |
| <span id="eye-status" class="text-sm px-3 py-1 rounded-full bg-gray-100">Not taken</span> | |
| </div> | |
| <p id="eye-result" class="text-sm text-gray-600">No data available</p> | |
| </div> | |
| <!-- Tremor Results --> | |
| <div class="bg-white rounded-lg shadow p-4 border border-gray-200"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <h3 class="font-medium flex items-center"> | |
| <i data-feather="wind" class="mr-2 text-yellow-500"></i> | |
| Tremor Test | |
| </h3> | |
| <span id="tremor-status-result" class="text-sm px-3 py-1 rounded-full bg-gray-100">Not taken</span> | |
| </div> | |
| <p id="tremor-result" class="text-sm text-gray-600">No data available</p> | |
| </div> | |
| </div> | |
| <!-- Actions --> | |
| <div class="mt-6 grid grid-cols-2 gap-2"> | |
| <button onclick="saveResults()" class="bg-gray-200 text-gray-700 py-2 px-4 rounded-lg hover:bg-gray-300 flex items-center justify-center"> | |
| <i data-feather="save" class="mr-2"></i> | |
| Save | |
| </button> | |
| <button onclick="shareResults()" class="bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 flex items-center justify-center"> | |
| <i data-feather="share-2" class="mr-2"></i> | |
| Share | |
| </button> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Footer Navigation --> | |
| <footer class="bg-white border-t border-gray-200 p-2"> | |
| <div class="flex justify-around"> | |
| <button onclick="showScreen('home')" class="p-2 rounded-full hover:bg-gray-100 text-gray-700"> | |
| <i data-feather="home"></i> | |
| </button> | |
| <button onclick="showScreen('cognitive')" class="p-2 rounded-full hover:bg-gray-100 text-gray-700"> | |
| <i data-feather="cpu"></i> | |
| </button> | |
| <button onclick="showScreen('speech')" class="p-2 rounded-full hover:bg-gray-100 text-gray-700"> | |
| <i data-feather="mic"></i> | |
| </button> | |
| <button onclick="showScreen('results')" class="p-2 rounded-full hover:bg-gray-100 text-gray-700"> | |
| <i data-feather="clipboard"></i> | |
| </button> | |
| </div> | |
| </footer> | |
| </div> | |
| <script> | |
| // Initialize Feather Icons | |
| feather.replace(); | |
| // Global state object to store test results | |
| const testResults = { | |
| cognitive: null, | |
| speech: null, | |
| eye: null, | |
| tremor: null | |
| }; | |
| // Screen navigation function | |
| function showScreen(screenId) { | |
| // Hide all screens | |
| document.querySelectorAll('[id$="-screen"]').forEach(screen => { | |
| screen.classList.add('hidden'); | |
| }); | |
| // Show requested screen | |
| document.getElementById(`${screenId}-screen`).classList.remove('hidden'); | |
| // Special cases for screens that need initialization | |
| if (screenId === 'eye') { | |
| initializeEyeTest(); | |
| } | |
| // Update results display if showing results screen | |
| if (screenId === 'results') { | |
| updateResultsDisplay(); | |
| } | |
| } | |
| // Cognitive Test Logic | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Memory test | |
| const memorySubmit = document.getElementById('memory-submit'); | |
| memorySubmit.addEventListener('click', () => { | |
| // In a real app, we'd check the sequence | |
| document.getElementById('memory-test').classList.add('hidden'); | |
| document.getElementById('reaction-test').classList.remove('hidden'); | |
| // Update progress bar | |
| document.querySelector('#cognitive-screen .bg-blue-500').style.width = '100%'; | |
| document.querySelector('#cognitive-screen .text-sm:last-child').textContent = '2/2'; | |
| // Store dummy result | |
| testResults.cognitive = { | |
| memory: { score: 0.75, feedback: "Good recall of 3 out of 4 numbers" }, | |
| reaction: null | |
| }; | |
| }); | |
| // Reaction test | |
| const reactionBox = document.getElementById('reaction-box'); | |
| const reactionStart = document.getElementById('reaction-start'); | |
| let reactionStartTime; | |
| reactionStart.addEventListener('click', () => { | |
| reactionStart.classList.add('hidden'); | |
| reactionBox.textContent = ''; | |
| reactionBox.classList.remove('bg-red-500'); | |
| reactionBox.classList.add('bg-gray-500'); | |
| // Random delay before turning green (1-3 seconds) | |
| const delay = 1000 + Math.random() * 2000; | |
| setTimeout(() => { | |
| reactionBox.classList.remove('bg-gray-500'); | |
| reactionBox.classList.add('bg-green-500'); | |
| reactionStartTime = Date.now(); | |
| }, delay); | |
| }); | |
| reactionBox.addEventListener('click', function() { | |
| if (this.classList.contains('bg-green-500')) { | |
| const reactionTime = Date.now() - reactionStartTime; | |
| document.getElementById('reaction-time').textContent = reactionTime; | |
| // Store result | |
| testResults.cognitive.reaction = { | |
| time: reactionTime, | |
| feedback: reactionTime < 300 ? "Excellent reaction time" : | |
| reactionTime < 600 ? "Normal reaction time" : "Slow reaction time" | |
| }; | |
| // Show analysis section | |
| document.getElementById('cognitive-analysis').classList.remove('hidden'); | |
| analyzeCognitiveTest(); | |
| } | |
| }); | |
| }); | |
| // Speech Test Logic | |
| let mediaRecorder; | |
| let audioChunks = []; | |
| document.getElementById('record-btn').addEventListener('mousedown', startRecording); | |
| document.getElementById('record-btn').addEventListener('touchstart', startRecording); | |
| document.getElementById('record-btn').addEventListener('mouseup', stopRecording); | |
| document.getElementById('record-btn').addEventListener('touchend', stopRecording); | |
| function startRecording() { | |
| document.getElementById('record-status').textContent = "Recording..."; | |
| document.getElementById('record-btn').classList.add('pulse-animation'); | |
| // In a real app, we'd initialize the MediaRecorder here | |
| // This is a placeholder for the actual implementation | |
| simulateWaveform(); | |
| } | |
| function stopRecording() { | |
| document.getElementById('record-status').textContent = "Recording complete"; | |
| document.getElementById('record-btn').classList.remove('pulse-animation'); | |
| // Stop the waveform simulation | |
| clearInterval(window.waveformInterval); | |
| // Show analysis section | |
| document.getElementById('speech-analysis').classList.remove('hidden'); | |
| // Store dummy result | |
| testResults.speech = { | |
| recording: "placeholder-audio-data", | |
| analyzed: false | |
| }; | |
| } | |
| function simulateWaveform() { | |
| let progress = 0; | |
| window.waveformInterval = setInterval(() => { | |
| progress = (progress + 5) % 100; | |
| document.getElementById('waveform').style.setProperty('--progress', progress); | |
| }, 100); | |
| } | |
| // Eye Tracking Test Logic | |
| function initializeEyeTest() { | |
| const video = document.getElementById('eye-video'); | |
| const startBtn = document.getElementById('start-eye-test'); | |
| const stopBtn = document.getElementById('stop-eye-test'); | |
| const instructions = document.getElementById('eye-instructions'); | |
| startBtn.addEventListener('click', async () => { | |
| try { | |
| // Request camera access | |
| const stream = await navigator.mediaDevices.getUserMedia({ | |
| video: { | |
| facingMode: 'user', | |
| width: 640, | |
| height: 480 | |
| } | |
| }); | |
| video.srcObject = stream; | |
| instructions.classList.add('hidden'); | |
| startEyeTrackingTest(); | |
| } catch (err) { | |
| instructions.textContent = "Camera access denied. Please enable camera permissions."; | |
| console.error("Error accessing camera:", err); | |
| } | |
| }); | |
| stopBtn.addEventListener('click', () => { | |
| if (video.srcObject) { | |
| video.srcObject.getTracks().forEach(track => track.stop()); | |
| video.srcObject = null; | |
| instructions.classList.remove('hidden'); | |
| instructions.textContent = "Test stopped. You can start again."; | |
| // Store dummy result | |
| testResults.eye = { | |
| trackingData: "placeholder-eye-data", | |
| analyzed: false | |
| }; | |
| // Show analysis section | |
| document.getElementById('eye-analysis').classList.remove('hidden'); | |
| } | |
| }); | |
| } | |
| function startEyeTrackingTest() { | |
| const dot = document.getElementById('eye-dot'); | |
| let pos = 0; | |
| const positions = [ | |
| { top: '20%', left: '20%' }, | |
| { top: '20%', left: '80%' }, | |
| { top: '80%', left: '80%' }, | |
| { top: '80%', left: '20%' }, | |
| { top: '50%', left: '50%' } | |
| ]; | |
| let moveInterval = setInterval(() => { | |
| dot.style.top = positions[pos].top; | |
| dot.style.left = positions[pos].left; | |
| pos = (pos + 1) % positions.length; | |
| }, 2000); | |
| // In a real app, we'd track eye movement here | |
| } | |
| // Tremor Test Logic | |
| document.getElementById('start-tremor-test').addEventListener('click', function() { | |
| const tremorVisual = document.getElementById('tremor-visual'); | |
| const tremorStatus = document.getElementById('tremor-status'); | |
| this.disabled = true; | |
| this.textContent = "Testing..."; | |
| tremorStatus.textContent = "Hold steady..."; | |
| // Create a visual representation of movement | |
| let movement = 0; | |
| let movementInterval = setInterval(() => { | |
| movement += Math.random() * 4 - 2; // Random movement between -2 and 2 | |
| tremorVisual.style.backgroundPosition = `${50 + movement}% 50%`; | |
| // Add some background pattern to visualize movement | |
| tremorVisual.style.backgroundImage = ` | |
| radial-gradient(circle at ${50 + movement}% 50%, | |
| rgba(255, 200, 0, 0.3) 0%, | |
| rgba(255, 200, 0, 0) 70%) | |
| `; | |
| }, 100); | |
| // Stop after 15 seconds | |
| setTimeout(() => { | |
| clearInterval(movementInterval); | |
| this.disabled = false; | |
| this.textContent = "Start Tremor Test"; | |
| tremorStatus.textContent = "Test complete"; | |
| tremorVisual.style.backgroundImage = ''; | |
| tremorVisual.style.backgroundPosition = ''; | |
| // Store dummy result | |
| testResults.tremor = { | |
| movementData: "placeholder-tremor-data", | |
| analyzed: false | |
| }; | |
| // Show analysis section | |
| document.getElementById('tremor-analysis').classList.remove('hidden'); | |
| }, 15000); | |
| }); | |
| // AI Analysis Placeholder Functions | |
| function analyzeCognitiveTest() { | |
| const feedback = document.getElementById('cognitive-feedback'); | |
| feedback.innerHTML = "Analysis complete:<br><br>" + | |
| "• Memory recall: 3/4 items correct (expected for age)<br>" + | |
| "• Reaction time: 420ms (slightly above average)<br>" + | |
| "<br>No significant cognitive impairments detected."; | |
| testResults.cognitive.analyzed = true; | |
| updateResultsDisplay(); | |
| } | |
| function analyzeSpeech() { | |
| const feedback = document.getElementById('speech-feedback'); | |
| feedback.textContent = "Speech analysis complete. No irregularities detected in articulation or fluency."; | |
| testResults.speech.analyzed = true; | |
| updateResultsDisplay(); | |
| } | |
| function analyzeEyeTracking() { | |
| const feedback = document.getElementById('eye-feedback'); | |
| feedback.textContent = "Eye tracking patterns appear normal with appropriate saccades and smooth pursuit movements."; | |
| testResults.eye.analyzed = true; | |
| updateResultsDisplay(); | |
| } | |
| function analyzeTremor() { | |
| const feedback = document.getElementById('tremor-feedback'); | |
| feedback.textContent = "Minimal tremor detected (0.8 Hz, 1.2mm amplitude) - within normal limits for resting tremor."; | |
| testResults.tremor.analyzed = true; | |
| updateResultsDisplay(); | |
| } | |
| // Results Display | |
| function updateResultsDisplay() { | |
| // Cognitive results | |
| if (testResults.cognitive) { | |
| document.getElementById('cognitive-status').textContent = testResults.cognitive.analyzed ? "Completed" : "Partial"; | |
| document.getElementById('cognitive-status').className = testResults.cognitive.analyzed ? | |
| "text-sm px-3 py-1 rounded-full bg-green-100 text-green-800" : | |
| "text-sm px-3 py-1 rounded-full bg-yellow-100 text-yellow-800"; | |
| if (testResults.cognitive.analyzed) { | |
| document.getElementById('cognitive-result').textContent = "Normal memory and reaction time"; | |
| } else if (testResults.cognitive.memory) { | |
| document.getElementById('cognitive-result').textContent = "Memory test completed"; | |
| } else { | |
| document.getElementById('cognitive-result').textContent = "Not taken"; | |
| } | |
| } | |
| // Speech results | |
| if (testResults.speech) { | |
| document.getElementById('speech-status').textContent = testResults.speech.analyzed ? "Completed" : "Partial"; | |
| document.getElementById('speech-status').className = testResults.speech.analyzed ? | |
| "text-sm px-3 py-1 rounded-full bg-green-100 text-green-800" : | |
| "text-sm px-3 py-1 rounded-full bg-yellow-100 text-yellow-800"; | |
| document.getElementById('speech-result').textContent = testResults.speech.analyzed ? | |
| "Normal speech patterns detected" : | |
| (testResults.speech.recording ? "Recording complete" : "Not taken"); | |
| } | |
| // Eye tracking results | |
| if (testResults.eye) { | |
| document.getElementById('eye-status').textContent = testResults.eye.analyzed ? "Completed" : "Partial"; | |
| document.getElementById('eye-status').className = testResults.eye.analyzed ? | |
| "text-sm px-3 py-1 rounded-full bg-green-100 text-green-800" : | |
| "text-sm px-3 py-1 rounded-full bg-yellow-100 text-yellow-800"; | |
| document.getElementById('eye-result').textContent = testResults.eye.analyzed ? | |
| "Normal eye movement patterns" : | |
| (testResults.eye.trackingData ? "Test completed" : "Not taken"); | |
| } | |
| // Tremor results | |
| if (testResults.tremor) { | |
| document.getElementById('tremor-status-result').textContent = testResults.tremor.analyzed ? "Completed" : "Partial"; | |
| document.getElementById('tremor-status-result').className = testResults.tremor.analyzed ? | |
| "text-sm px-3 py-1 rounded-full bg-green-100 text-green-800" : | |
| "text-sm px-3 py-1 rounded-full bg-yellow-100 text-yellow-800"; | |
| document.getElementById('tremor-result').textContent = testResults.tremor.analyzed ? | |
| "Minimal tremor detected" : | |
| (testResults.tremor.movementData ? "Test completed" : "Not taken"); | |
| } | |
| // Overall assessment | |
| if (testResults.cognitive?.analyzed && testResults.speech?.analyzed && | |
| testResults.eye?.analyzed && testResults.tremor?.analyzed) { | |
| document.getElementById('overall-status').textContent = "Normal"; | |
| document.getElementById('overall-status').className = "bg-white text-green-600 px-3 py-1 rounded-full text-sm font-medium"; | |
| document.getElementById('overall-feedback').textContent = "All tests indicate normal neurological function. No immediate concerns detected."; | |
| } else if (testResults.cognitive || testResults.speech || testResults.eye || testResults.tremor) { | |
| document.getElementById('overall-status').textContent = "Partial"; | |
| document.getElementById('overall-status').className = "bg-white text-yellow-600 px-3 py-1 rounded-full text-sm font-medium"; | |
| document.getElementById('overall-feedback').textContent = "Complete all tests for a full assessment."; | |
| } else { | |
| document.getElementById('overall-status').textContent = "Pending"; | |
| document.getElementById('overall-status').className = "bg-white text-blue-600 px-3 py-1 rounded-full text-sm font-medium"; | |
| document.getElementById('overall-feedback').textContent = "No tests completed yet."; | |
| } | |
| } | |
| // Save and Share Functions | |
| function saveResults() { | |
| // In a real app, we'd save to localStorage or IndexedDB | |
| alert("Results saved locally"); | |
| } | |
| function shareResults() { | |
| // In a real app, we'd use the Web Share API | |
| alert("Share functionality would be implemented here"); | |
| } | |
| // Serverless AI Integration Placeholders | |
| async function callCognitiveAnalysisAPI(data) { | |
| /* In a real implementation: | |
| const response = await fetch('https://api.example.com/analyze-cognitive', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(data) | |
| }); | |
| return await response.json(); | |
| */ | |
| return new Promise(resolve => { | |
| setTimeout(() => { | |
| resolve({ | |
| success: true, | |
| analysis: "This is a placeholder for actual AI analysis" | |
| }); | |
| }, 1000); | |
| }); | |
| } | |
| async function callSpeechAnalysisAPI(audioData) { | |
| /* In a real implementation: | |
| const response = await fetch('https://api.example.com/analyze-speech', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'audio/wav' }, | |
| body: audioData | |
| }); | |
| return await response.json(); | |
| */ | |
| return new Promise(resolve => { | |
| setTimeout(() => { | |
| resolve({ | |
| success: true, | |
| analysis: "This is a placeholder for actual speech analysis" | |
| }); | |
| }, 1000); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> | |