Skip to main content
Glama

MCPControl

test-panel.html19.5 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; media-src data:; script-src 'self' 'unsafe-inline';"> <title>MCPControl Test Panel</title> <style> @import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;600&display=swap'); body { font-family: 'Fira Code', monospace; margin: 0; padding: 20px; background-color: #1a1b26; color: #c0caf5; min-height: 100vh; display: flex; } .main-content { flex: 3; padding-right: 20px; } .sidebar { flex: 1; padding-left: 20px; border-left: 1px solid #414868; max-width: 300px; } .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 15px; margin-bottom: 30px; } .button { height: 90px; border-radius: 8px; display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer; user-select: none; background-color: #24283b; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5), inset 0 1px rgba(255, 255, 255, 0.1); transition: all 0.3s ease; border: 2px solid #414868; position: relative; overflow: hidden; } .button::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.05); opacity: 0; pointer-events: none; } .button:hover::before { opacity: 1; } .button:nth-child(4n+1) { border-color: #bb9af7; } .button:nth-child(4n+2) { border-color: #7aa2f7; } .button:nth-child(4n+3) { border-color: #9ece6a; } .button:nth-child(4n+4) { border-color: #f7768e; } .button:hover { transform: translateY(-3px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.6), inset 0 1px rgba(255, 255, 255, 0.1); } .button.active { transform: scale(0.95); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.8), inset 0 1px 1px rgba(255, 255, 255, 0.05); } .counter { font-size: 24px; margin-top: 5px; font-weight: bold; color: #a9b1d6; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); } .stats { margin-top: 20px; padding: 20px; background-color: #1e202e; border-radius: 8px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4); border-left: 3px solid #7aa2f7; } h1, h2 { text-align: center; color: #c0caf5; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.4); } h1 { font-size: 2.2em; margin-bottom: 30px; border-bottom: 2px solid #414868; padding-bottom: 10px; } h2 { font-size: 1.5em; margin-bottom: 20px; } .controls { display: flex; justify-content: center; gap: 15px; margin-bottom: 30px; } button { padding: 12px 24px; cursor: pointer; background-color: #24283b; border: 2px solid #414868; border-radius: 8px; font-family: 'Fira Code', monospace; font-size: 14px; color: #c0caf5; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4); transition: all 0.3s ease; } button:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); background-color: #2a2e42; } button:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); } .background-animation { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; overflow: hidden; } .background-animation::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: repeating-linear-gradient( 0deg, rgba(67, 70, 90, 0.05) 0px, rgba(67, 70, 90, 0.05) 1px, transparent 1px, transparent 4px ); z-index: -1; } @keyframes fadeBack { 0% { filter: brightness(1.5); } 100% { filter: brightness(1); } } .log-container { height: calc(100vh - 100px); padding: 15px; background-color: #1e202e; border-radius: 8px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4); border-left: 3px solid #e0af68; display: flex; flex-direction: column; } .log-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #414868; } .log-entries { flex: 1; overflow-y: auto; padding-right: 5px; } .log-entry { margin: 5px 0; font-family: 'Fira Code', monospace; font-size: 12px; color: #a9b1d6; padding: 4px 0; border-bottom: 1px solid #414868; } .log-timestamp { color: #7aa2f7; margin-right: 8px; } .log-button { color: #9ece6a; margin-right: 8px; } @media (max-width: 1100px) { body { flex-direction: column; } .main-content { max-width: 100%; padding-right: 0; flex: initial; } .sidebar { width: 100%; padding-left: 0; border-left: none; margin-top: 20px; border-top: 1px solid #414868; padding-top: 20px; max-width: none; flex: initial; } .log-container { height: 300px; } } </style> </head> <body> <div class="main-content"> <h1>Chalkboard Button Panel</h1> <div class="controls"> <button id="resetAll">Reset All Counters</button> <button id="randomButton">Click Random Button</button> </div> <div class="grid" id="buttonGrid"></div> <div class="stats"> <h2>Test Statistics</h2> <p>Total Clicks: <span id="totalClicks">0</span></p> <p>Last Button Clicked: <span id="lastClicked">None</span></p> <p>Most Clicked Button: <span id="mostClicked">None</span></p> <p>Click Sequence: <span id="clickSequenceDisplay" style="font-weight: bold; font-family: monospace;">None</span></p> </div> </div> <div class="sidebar"> <div class="log-container"> <div class="log-header"> <h2>Activity Log</h2> <button id="clearLog">Clear Log</button> </div> <div class="log-entries" id="log-entries"></div> </div> </div> <!-- Animated background --> <div class="background-animation"></div> <script> // Configuration const GRID_SIZE = 16; // 4x4 grid const ACTIVE_DURATION = 500; // ms to show active state const FADE_DURATION = 1000; // ms for fade back animation const MAX_LOG_ENTRIES = 100; // Maximum number of log entries to keep // State let totalClicks = 0; let buttonCounts = {}; let mostClickedButton = null; let mostClickedCount = 0; // Add a clickSequence array to track the exact order of clicks let clickSequence = []; // Special function to output test data for automation function outputTestState() { console.log("TEST_DATA_BEGIN"); const testData = { totalClicks, buttonCounts, clickSequence, mostClickedButton, timestamp: new Date().toISOString() }; console.log(JSON.stringify(testData, null, 2)); console.log("TEST_DATA_END"); // For test automation, output in a simple format that's easy to parse console.log(`MCPTEST_FINAL_SEQUENCE|${clickSequence.join('')}`); // Also write to a special div for scraping const testDataDiv = document.getElementById('test-data') || document.createElement('div'); testDataDiv.id = 'test-data'; testDataDiv.setAttribute('data-sequence', clickSequence.join('')); testDataDiv.setAttribute('data-total-clicks', totalClicks); testDataDiv.style.display = 'none'; document.body.appendChild(testDataDiv); // Send the final data to our test server if available if (window.testAPI) { window.testAPI.reportFinalResult(clickSequence); } try { // Save data to localStorage as a backup localStorage.setItem('mcpTestData', JSON.stringify(testData)); } catch (e) { console.error("Failed to save test data:", e); } } // Output initial state window.addEventListener('load', () => { setTimeout(outputTestState, 1000); }); // Create buttons const grid = document.getElementById('buttonGrid'); const logEntries = document.getElementById('log-entries'); // Debounce function to prevent rapid-fire clicking function debounce(func, wait) { let timeout; return function(...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), wait); }; } // Create debounce function once, outside the loop const handleButtonClickDebounced = debounce(handleButtonClick, 100); // Create button labels 0-9 and A-F (hexadecimal) const getButtonLabel = (i) => { if (i < 10) return String(i); return String.fromCharCode(65 + (i - 10)); // A-F for 10-15 }; for (let i = 0; i < GRID_SIZE; i++) { const buttonLabel = getButtonLabel(i); const button = document.createElement('div'); button.className = 'button'; button.id = `button-${buttonLabel}`; button.setAttribute('data-id', buttonLabel); button.setAttribute('role', 'button'); button.setAttribute('tabindex', '0'); button.setAttribute('aria-label', `Button ${buttonLabel}`); const label = document.createElement('div'); label.textContent = `Button ${buttonLabel}`; const counter = document.createElement('div'); counter.className = 'counter'; counter.textContent = '0'; button.appendChild(label); button.appendChild(counter); grid.appendChild(button); // Initialize counter buttonCounts[buttonLabel] = 0; // Add click event with debouncing (100ms) button.addEventListener('click', function() { handleButtonClickDebounced(buttonLabel); }); // Add keyboard accessibility button.addEventListener('keydown', function(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleButtonClick(buttonLabel); } }); } // Handle visual animation efficiently function animateButton(button) { // Show active state button.classList.add('active'); button.style.filter = 'brightness(1.5)'; // Use requestAnimationFrame for better performance requestAnimationFrame(() => { // Schedule removal of active class setTimeout(() => { button.classList.remove('active'); // Start fade animation button.style.animation = `fadeBack ${FADE_DURATION}ms ease forwards`; // Clean up after animation completes setTimeout(() => { button.style.animation = ''; button.style.filter = ''; }, FADE_DURATION); }, ACTIVE_DURATION); }); } function handleButtonClick(buttonId) { // Update counter buttonCounts[buttonId]++; totalClicks++; // Add to click sequence clickSequence.push(buttonId); // Update UI const button = document.getElementById(`button-${buttonId}`); const counter = button.querySelector('.counter'); counter.textContent = buttonCounts[buttonId]; // Log the action addLogEntry(`Button ${buttonId} clicked (count: ${buttonCounts[buttonId]})`); // Update the click sequence display updateClickSequenceDisplay(); // Log to console in machine-parseable format for test automation const timestamp = new Date().toISOString(); console.log(`MCPTEST_CLICK|${timestamp}|${buttonId}|${buttonCounts[buttonId]}|${totalClicks}`); console.log(`MCPTEST_SEQUENCE|${clickSequence.join('')}`); // Send the data to our test server if available if (window.testAPI) { window.testAPI.reportClick(buttonId, buttonCounts[buttonId]); window.testAPI.reportSequence(clickSequence); } // Audio removed // Handle animations animateButton(button); // Update stats document.getElementById('totalClicks').textContent = totalClicks; document.getElementById('lastClicked').textContent = `Button ${buttonId}`; // Check if this is the most clicked button if (buttonCounts[buttonId] > mostClickedCount) { mostClickedButton = buttonId; mostClickedCount = buttonCounts[buttonId]; document.getElementById('mostClicked').textContent = `Button ${buttonId} (${mostClickedCount} clicks)`; } // Output the state for testing outputTestState(); } // Helper to update the click sequence display function updateClickSequenceDisplay() { const display = document.getElementById('clickSequenceDisplay'); if (display) { if (clickSequence.length > 0) { display.textContent = clickSequence.join(''); } else { display.textContent = 'None'; } } } function addLogEntry(message) { const now = new Date(); const timestamp = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}.${now.getMilliseconds().toString().padStart(3, '0')}`; const logEntry = document.createElement('div'); logEntry.className = 'log-entry'; const timestampSpan = document.createElement('span'); timestampSpan.className = 'log-timestamp'; timestampSpan.textContent = timestamp; logEntry.appendChild(timestampSpan); logEntry.appendChild(document.createTextNode(message)); logEntries.appendChild(logEntry); // Trim log if it gets too long while (logEntries.children.length > MAX_LOG_ENTRIES) { logEntries.removeChild(logEntries.firstChild); } // Auto-scroll to bottom logEntries.scrollTop = logEntries.scrollHeight; } // Reset all counters document.getElementById('resetAll').addEventListener('click', function() { totalClicks = 0; mostClickedButton = null; mostClickedCount = 0; clickSequence = []; // Reset click sequence // Loop through all buttons using hexadecimal IDs (0-9, A-F) for (let i = 0; i < GRID_SIZE; i++) { const buttonLabel = getButtonLabel(i); buttonCounts[buttonLabel] = 0; const counter = document.querySelector(`#button-${buttonLabel} .counter`); if (counter) { counter.textContent = '0'; } } document.getElementById('totalClicks').textContent = '0'; document.getElementById('lastClicked').textContent = 'None'; document.getElementById('mostClicked').textContent = 'None'; addLogEntry('All counters reset'); // Output reset state console.log('MCPTEST_RESET'); outputTestState(); }); // Click random button (useful for testing) document.getElementById('randomButton').addEventListener('click', function() { const randomId = Math.floor(Math.random() * GRID_SIZE) + 1; const button = document.getElementById(`button-${randomId}`); addLogEntry(`Random click triggered for Button ${randomId}`); button.click(); }); // Clear log document.getElementById('clearLog').addEventListener('click', function() { logEntries.innerHTML = ''; addLogEntry('Log cleared'); }); // Initial log entry addLogEntry('Test panel initialized'); </script> </body> </html>

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/claude-did-this/MCPControl'

If you have feedback or need assistance with the MCP directory API, please join our Discord server