2026-02-22 23:15:12 +00:00

212 lines
8.1 KiB
JavaScript

/**
* 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;
};
});