Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-03-05 02:33:35 +11:00
parent 7f5ae16dea
commit b79cbf0545
11 changed files with 1953 additions and 227 deletions

238
website/crt-bulge.js Normal file
View File

@@ -0,0 +1,238 @@
/**
* CRT Bulge Coordinate Transformer
* Intercepts mouse events and applies inverse distortion to match the visual bulge effect
*/
class CRTBulgeTransformer {
constructor(containerSelector, strength = 0.15) {
this.container = document.querySelector(containerSelector);
this.strength = strength;
this.setupEventListeners();
}
/**
* Apply inverse barrel distortion to transform visual coords to DOM coords
* @param {number} x - Visual X coordinate relative to container
* @param {number} y - Visual Y coordinate relative to container
* @param {number} width - Container width
* @param {number} height - Container height
* @returns {{x: number, y: number}} - Transformed coordinates
*/
inverseDistort(x, y, width, height) {
// Normalize to -1 to 1 (center is 0,0)
const nx = (x / width) * 2 - 1;
const ny = (y / height) * 2 - 1;
// The visual filter pushes pixels outward based on distance squared
// To invert: we need to find where this pixel came FROM
// Using Newton-Raphson iteration to solve the inverse
let ux = nx;
let uy = ny;
// Iterate to find the original position
for (let i = 0; i < 5; i++) {
const distSq = ux * ux + uy * uy;
const factor = 1 + this.strength * distSq;
// Forward distortion: visual = original * factor
// Inverse: original = visual / factor (approximately)
ux = nx / factor;
uy = ny / factor;
}
// Convert back to pixel coordinates
const newX = ((ux + 1) / 2) * width;
const newY = ((uy + 1) / 2) * height;
return { x: newX, y: newY };
}
/**
* Transform mouse event coordinates
* @param {MouseEvent} e - Original mouse event
* @returns {{x: number, y: number}} - Transformed page coordinates
*/
transformEvent(e) {
const rect = this.container.getBoundingClientRect();
// Get position relative to container
const relX = e.clientX - rect.left;
const relY = e.clientY - rect.top;
// Apply inverse distortion
const transformed = this.inverseDistort(relX, relY, rect.width, rect.height);
// Convert back to page coordinates
return {
x: rect.left + transformed.x,
y: rect.top + transformed.y
};
}
/**
* Set up event listeners for mouse interaction
*/
setupEventListeners() {
// Create invisible overlay to capture events
const overlay = document.createElement('div');
overlay.id = 'crt-event-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10000;
cursor: inherit;
`;
document.body.appendChild(overlay);
// Track currently hovered element for hover states
let lastHoveredElement = null;
// Handle mouse movement
overlay.addEventListener('mousemove', (e) => {
const transformed = this.transformEvent(e);
// Temporarily hide overlay to find element underneath
overlay.style.pointerEvents = 'none';
const elementBelow = document.elementFromPoint(transformed.x, transformed.y);
overlay.style.pointerEvents = 'auto';
// Update cursor based on element below
if (elementBelow) {
const computedStyle = window.getComputedStyle(elementBelow);
overlay.style.cursor = computedStyle.cursor;
// Handle hover state changes
if (elementBelow !== lastHoveredElement) {
if (lastHoveredElement) {
lastHoveredElement.dispatchEvent(new MouseEvent('mouseleave', {
bubbles: true,
clientX: transformed.x,
clientY: transformed.y
}));
}
elementBelow.dispatchEvent(new MouseEvent('mouseenter', {
bubbles: true,
clientX: transformed.x,
clientY: transformed.y
}));
lastHoveredElement = elementBelow;
}
// Dispatch mousemove to element
elementBelow.dispatchEvent(new MouseEvent('mousemove', {
bubbles: true,
clientX: transformed.x,
clientY: transformed.y
}));
}
});
// Handle clicks
overlay.addEventListener('click', (e) => {
e.preventDefault();
const transformed = this.transformEvent(e);
overlay.style.pointerEvents = 'none';
const elementBelow = document.elementFromPoint(transformed.x, transformed.y);
overlay.style.pointerEvents = 'auto';
if (elementBelow) {
elementBelow.dispatchEvent(new MouseEvent('click', {
bubbles: true,
clientX: transformed.x,
clientY: transformed.y
}));
// Focus if it's an interactive element
if (elementBelow.tagName === 'INPUT' || elementBelow.tagName === 'TEXTAREA' || elementBelow.tabIndex >= 0) {
elementBelow.focus();
}
}
});
// Handle mousedown
overlay.addEventListener('mousedown', (e) => {
const transformed = this.transformEvent(e);
overlay.style.pointerEvents = 'none';
const elementBelow = document.elementFromPoint(transformed.x, transformed.y);
overlay.style.pointerEvents = 'auto';
if (elementBelow) {
elementBelow.dispatchEvent(new MouseEvent('mousedown', {
bubbles: true,
clientX: transformed.x,
clientY: transformed.y,
button: e.button
}));
}
});
// Handle mouseup
overlay.addEventListener('mouseup', (e) => {
const transformed = this.transformEvent(e);
overlay.style.pointerEvents = 'none';
const elementBelow = document.elementFromPoint(transformed.x, transformed.y);
overlay.style.pointerEvents = 'auto';
if (elementBelow) {
elementBelow.dispatchEvent(new MouseEvent('mouseup', {
bubbles: true,
clientX: transformed.x,
clientY: transformed.y,
button: e.button
}));
}
});
// Handle wheel/scroll
overlay.addEventListener('wheel', (e) => {
const transformed = this.transformEvent(e);
overlay.style.pointerEvents = 'none';
const elementBelow = document.elementFromPoint(transformed.x, transformed.y);
overlay.style.pointerEvents = 'auto';
if (elementBelow) {
elementBelow.dispatchEvent(new WheelEvent('wheel', {
bubbles: true,
clientX: transformed.x,
clientY: transformed.y,
deltaX: e.deltaX,
deltaY: e.deltaY,
deltaMode: e.deltaMode
}));
}
}, { passive: true });
// Handle context menu
overlay.addEventListener('contextmenu', (e) => {
const transformed = this.transformEvent(e);
overlay.style.pointerEvents = 'none';
const elementBelow = document.elementFromPoint(transformed.x, transformed.y);
overlay.style.pointerEvents = 'auto';
if (elementBelow) {
const event = new MouseEvent('contextmenu', {
bubbles: true,
clientX: transformed.x,
clientY: transformed.y
});
if (!elementBelow.dispatchEvent(event)) {
e.preventDefault();
}
}
});
}
}
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
// Match the strength value from create-bulge-map.js
new CRTBulgeTransformer('.container', 0.15);
});