/** * Stark Industries - Full Arc Keyboard MK.83 * Designed for ergonomic polar-coordinate typing experience. */ document.addEventListener('DOMContentLoaded', () => { const arcContainer = document.getElementById('arcKeysContainer'); const svg = document.getElementById('backgroundSvg'); const outputArea = document.getElementById('outputArea'); const systemTimeLabel = document.getElementById('systemTime'); const hub = document.getElementById('arcCenterHub'); // Remove any existing SVG content svg.innerHTML = ''; // --- Layout Configuration --- // The center of the concentric arcs let ARC_CENTER_X = window.innerWidth / 2; let ARC_CENTER_Y = window.innerHeight * 0.8; // Define rings from closest to furthest. Angles are in degrees. 0 is pointing right, 180 is pointing left, 270 is pointing up. // We want the arc to go from roughly 190 degrees to 350 degrees // Actually, mathematically, if we use standard unit circle: // right = 0, up = -90 / 270, left = 180, down = 90 // So for an arch over the center, we want angles between roughly 190 and 350 degrees. // We will organize keys into rings, from bottom (closest to hub) to top (furthest). const RINGS = [ { radius: 200, keys: ['SPACE'], // for space, we just put it at top-center (-90 degrees) angleRange: [-90, -90], keyWidth: 280, keyHeight: 50 }, { radius: 280, keys: ['Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/'], angleRange: [205, 335], keyWidth: 50, keyHeight: 50 }, { radius: 360, keys: ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', "'", 'ENTER'], angleRange: [200, 340], keyWidth: 50, keyHeight: 50 }, { radius: 440, keys: ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']', '\\'], angleRange: [195, 345], keyWidth: 50, keyHeight: 50 }, { radius: 520, keys: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'BKSP'], angleRange: [190, 350], keyWidth: 50, keyHeight: 50 }, { radius: 600, keys: ['ESC', 'FILE', 'PROG', 'SYS', 'LOG', 'CALC', 'DATA', 'NET', 'PWR'], angleRange: [185, 355], keyWidth: 60, keyHeight: 40 } ]; // --- Helper Functions --- const toRad = (deg) => deg * (Math.PI / 180); function createKeyNode(label, parent) { const btn = document.createElement('div'); btn.className = 'key-node'; btn.textContent = label; btn.addEventListener('click', () => { if (label === 'SPACE') outputArea.value += ' '; else if (label === 'ENTER') outputArea.value += '\n'; else if (label === 'BKSP') outputArea.value = outputArea.value.slice(0, -1); else if (label.length > 1) outputArea.value += `\n>> [SYSTEM] EXECUTE_${label}\n`; else outputArea.value += label; outputArea.scrollTop = outputArea.scrollHeight; }); parent.appendChild(btn); return btn; } function drawRings() { const rect = arcContainer.getBoundingClientRect(); // Since arcContainer is inside blueprint-viewport which has padding, its size might not match window perfectly. // It's safer to base centers on the arcContainer's dimensions. ARC_CENTER_X = rect.width / 2; ARC_CENTER_Y = rect.height - 50; // Near bottom // Position Hub hub.style.left = `${ARC_CENTER_X - 100}px`; hub.style.top = `${ARC_CENTER_Y - 100}px`; RINGS.forEach((ring) => { // Draw SVG guide arc const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); const startRad = toRad(ring.angleRange[0]); const endRad = toRad(ring.angleRange[1]); // To draw arc in SVG, we need absolute coordinates relative to document/svg. // SVG is fixed over entire window. const svgRect = svg.getBoundingClientRect(); // Offset arcContainer's coordinates to SVG window coordinates const globalCenterX = rect.left + ARC_CENTER_X; const globalCenterY = rect.top + ARC_CENTER_Y; const x1 = globalCenterX + ring.radius * Math.cos(startRad); const y1 = globalCenterY + ring.radius * Math.sin(startRad); const x2 = globalCenterX + ring.radius * Math.cos(endRad); const y2 = globalCenterY + ring.radius * Math.sin(endRad); // Large arc flag = 0 if angle difference < 180, 1 if > 180 // Since our range is e.g. 190 to 350 (160 deg diff), largeArcFlag = 0 const largeArcFlag = 0; const sweepFlag = 1; // 1 means clockwise from start to end const d = `M ${x1} ${y1} A ${ring.radius} ${ring.radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`; path.setAttribute('d', d); path.setAttribute('fill', 'none'); path.setAttribute('stroke', 'rgba(0, 229, 255, 0.15)'); path.setAttribute('stroke-width', '1'); path.setAttribute('stroke-dasharray', '5,5'); svg.appendChild(path); // Calculate angle step let angleStep = 0; if (ring.keys.length > 1) { angleStep = (ring.angleRange[1] - ring.angleRange[0]) / (ring.keys.length - 1); } ring.keys.forEach((key, kIdx) => { const angle = ring.keys.length === 1 ? (ring.angleRange[0] + ring.angleRange[1])/2 : ring.angleRange[0] + (kIdx * angleStep); const rad = toRad(angle); // Position relative to arcContainer const x = ARC_CENTER_X + ring.radius * Math.cos(rad); const y = ARC_CENTER_Y + ring.radius * Math.sin(rad); const btn = createKeyNode(key, arcContainer); btn.style.width = `${ring.keyWidth}px`; btn.style.height = `${ring.keyHeight}px`; btn.style.left = `${x}px`; btn.style.top = `${y}px`; // Rotate key to face center. // Since angle is right=0, up=-90, tangent angle is angle + 90 const rotation = angle + 90; btn.style.transform = `translate(-50%, -50%) rotate(${rotation}deg)`; // Handle special width buttons if (key === 'ENTER' || key === 'BKSP') { btn.style.width = '80px'; } }); }); } function updateSystemTime() { const now = new Date(); const timeStr = now.toLocaleTimeString('en-US', { hour12: false }); if (systemTimeLabel) systemTimeLabel.textContent = `[ ${timeStr} ]`; } // --- Init --- // Debounce resize handling let resizeTimeout; window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { // Re-render svg.innerHTML = ''; // keep the hub, but remove keys Array.from(arcContainer.children).forEach(child => { if (child.id !== 'arcCenterHub') { arcContainer.removeChild(child); } }); drawRings(); }, 100); }); drawRings(); setInterval(updateSystemTime, 1000); updateSystemTime(); // Global Handlers document.getElementById('clearBtn').onclick = () => { outputArea.value = ''; }; document.getElementById('saveBtn').onclick = () => { if (!outputArea.value.trim()) return; outputArea.value += "\n>> [STORAGE] DATA_COMMITTED\n"; outputArea.scrollTop = outputArea.scrollHeight; }; });