212 lines
8.1 KiB
JavaScript
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;
|
|
};
|
|
}); |