mirror of
https://github.com/litruv/lit.ruv.wtf.git
synced 2026-07-24 02:36:02 +10:00
Refactor code structure for improved readability and maintainability
This commit is contained in:
53
chromakey.js
Normal file
53
chromakey.js
Normal file
@@ -0,0 +1,53 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Using Jimp for image processing
|
||||
const { Jimp } = require('jimp');
|
||||
|
||||
async function chromakeyImage() {
|
||||
try {
|
||||
const inputPath = path.join(__dirname, 'website', 'screenborder.jpg');
|
||||
const outputPath = path.join(__dirname, 'website', 'screenborder.png');
|
||||
|
||||
console.log('Loading image...');
|
||||
const image = await Jimp.read(inputPath);
|
||||
|
||||
console.log('Removing green screen...');
|
||||
|
||||
// Scan through each pixel
|
||||
image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y, idx) {
|
||||
const red = this.bitmap.data[idx + 0];
|
||||
const green = this.bitmap.data[idx + 1];
|
||||
const blue = this.bitmap.data[idx + 2];
|
||||
const alpha = this.bitmap.data[idx + 3];
|
||||
|
||||
// Very aggressive green detection
|
||||
const isGreen = green > red * 1.05 && green > blue * 1.05 && green > 30;
|
||||
|
||||
// Catch any bright greens
|
||||
const isBrightGreen = green > 120 && green > red && green > blue;
|
||||
|
||||
if (isGreen || isBrightGreen) {
|
||||
// Make pixel transparent
|
||||
this.bitmap.data[idx + 3] = 0;
|
||||
} else if (green > red || green > blue) {
|
||||
// Aggressively reduce alpha for any greenish tint
|
||||
const greenness = Math.max(
|
||||
(green - red) / 255,
|
||||
(green - blue) / 255
|
||||
);
|
||||
this.bitmap.data[idx + 3] = Math.floor(alpha * (1 - greenness));
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Saving PNG...');
|
||||
await image.write(outputPath);
|
||||
|
||||
console.log('✓ Chromakey complete! Saved to website/screenborder.png');
|
||||
} catch (error) {
|
||||
console.error('✗ Chromakey failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
chromakeyImage();
|
||||
46
create-bulge-map.js
Normal file
46
create-bulge-map.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const { Jimp } = require('jimp');
|
||||
const path = require('path');
|
||||
|
||||
async function createDisplacementMap() {
|
||||
const size = 512;
|
||||
const image = new Jimp({ width: size, height: size });
|
||||
|
||||
// Create a gentle CRT bulge displacement map
|
||||
// Center appears to bulge outward (magnified)
|
||||
// R channel = horizontal displacement
|
||||
// G channel = vertical displacement
|
||||
// 128 = no displacement, <128 = pull toward 0, >128 = push toward max
|
||||
|
||||
for (let y = 0; y < size; y++) {
|
||||
for (let x = 0; x < size; x++) {
|
||||
// Normalize to -1 to 1 range (center is 0,0)
|
||||
const nx = (x / (size - 1)) * 2 - 1;
|
||||
const ny = (y / (size - 1)) * 2 - 1;
|
||||
|
||||
// Use smooth cubic falloff for gentle curve
|
||||
// Pull edges slightly toward center (pincushion)
|
||||
const distSq = nx * nx + ny * ny;
|
||||
const strength = distSq * 0.15; // Gentle effect
|
||||
|
||||
// Direction away from center (positive = away from center)
|
||||
const dx = nx * strength;
|
||||
const dy = ny * strength;
|
||||
|
||||
// Convert to 0-255 range (128 = no displacement)
|
||||
const r = Math.floor(128 + dx * 127);
|
||||
const g = Math.floor(128 + dy * 127);
|
||||
|
||||
const idx = (y * size + x) * 4;
|
||||
image.bitmap.data[idx + 0] = Math.max(0, Math.min(255, r));
|
||||
image.bitmap.data[idx + 1] = Math.max(0, Math.min(255, g));
|
||||
image.bitmap.data[idx + 2] = 128;
|
||||
image.bitmap.data[idx + 3] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
const outputPath = path.join(__dirname, 'website', 'bulge-map.png');
|
||||
await image.write(outputPath);
|
||||
console.log('✓ Displacement map created: website/bulge-map.png');
|
||||
}
|
||||
|
||||
createDisplacementMap();
|
||||
904
package-lock.json
generated
Normal file
904
package-lock.json
generated
Normal file
@@ -0,0 +1,904 @@
|
||||
{
|
||||
"name": "lit.ruv.wtf",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "lit.ruv.wtf",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jimp": "^1.6.0"
|
||||
},
|
||||
"devDependencies": {}
|
||||
},
|
||||
"node_modules/@jimp/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/file-ops": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"await-to-js": "^3.0.0",
|
||||
"exif-parser": "^0.1.12",
|
||||
"file-type": "^16.0.0",
|
||||
"mime": "3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/diff": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.6.0.tgz",
|
||||
"integrity": "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/plugin-resize": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"pixelmatch": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/file-ops": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.6.0.tgz",
|
||||
"integrity": "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-bmp": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.6.0.tgz",
|
||||
"integrity": "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"bmp-ts": "^1.0.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-gif": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.6.0.tgz",
|
||||
"integrity": "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"gifwrap": "^0.10.1",
|
||||
"omggif": "^1.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-jpeg": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.6.0.tgz",
|
||||
"integrity": "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"jpeg-js": "^0.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-png": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.6.0.tgz",
|
||||
"integrity": "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"pngjs": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/js-tiff": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.6.0.tgz",
|
||||
"integrity": "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"utif2": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-blit": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.6.0.tgz",
|
||||
"integrity": "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-blur": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.6.0.tgz",
|
||||
"integrity": "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/utils": "1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-circle": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.6.0.tgz",
|
||||
"integrity": "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-color": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.6.0.tgz",
|
||||
"integrity": "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"tinycolor2": "^1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-contain": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.6.0.tgz",
|
||||
"integrity": "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/plugin-blit": "1.6.0",
|
||||
"@jimp/plugin-resize": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-cover": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.6.0.tgz",
|
||||
"integrity": "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/plugin-crop": "1.6.0",
|
||||
"@jimp/plugin-resize": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-crop": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.6.0.tgz",
|
||||
"integrity": "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-displace": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.6.0.tgz",
|
||||
"integrity": "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-dither": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.6.0.tgz",
|
||||
"integrity": "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-fisheye": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.6.0.tgz",
|
||||
"integrity": "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-flip": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.6.0.tgz",
|
||||
"integrity": "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-hash": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.6.0.tgz",
|
||||
"integrity": "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/js-bmp": "1.6.0",
|
||||
"@jimp/js-jpeg": "1.6.0",
|
||||
"@jimp/js-png": "1.6.0",
|
||||
"@jimp/js-tiff": "1.6.0",
|
||||
"@jimp/plugin-color": "1.6.0",
|
||||
"@jimp/plugin-resize": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"any-base": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-mask": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.6.0.tgz",
|
||||
"integrity": "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-print": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.6.0.tgz",
|
||||
"integrity": "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/js-jpeg": "1.6.0",
|
||||
"@jimp/js-png": "1.6.0",
|
||||
"@jimp/plugin-blit": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"parse-bmfont-ascii": "^1.0.6",
|
||||
"parse-bmfont-binary": "^1.0.6",
|
||||
"parse-bmfont-xml": "^1.1.6",
|
||||
"simple-xml-to-json": "^1.2.2",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-quantize": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.6.0.tgz",
|
||||
"integrity": "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"image-q": "^4.0.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-resize": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.6.0.tgz",
|
||||
"integrity": "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-rotate": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.6.0.tgz",
|
||||
"integrity": "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/plugin-crop": "1.6.0",
|
||||
"@jimp/plugin-resize": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/plugin-threshold": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.6.0.tgz",
|
||||
"integrity": "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/plugin-color": "1.6.0",
|
||||
"@jimp/plugin-hash": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/types": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz",
|
||||
"integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@jimp/utils": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.6.0.tgz",
|
||||
"integrity": "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/types": "1.6.0",
|
||||
"tinycolor2": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@tokenizer/token": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz",
|
||||
"integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"event-target-shim": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.5"
|
||||
}
|
||||
},
|
||||
"node_modules/any-base": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
|
||||
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/await-to-js": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/await-to-js/-/await-to-js-3.0.0.tgz",
|
||||
"integrity": "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bmp-ts": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/bmp-ts/-/bmp-ts-1.0.9.tgz",
|
||||
"integrity": "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/event-target-shim": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/exif-parser": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
|
||||
"integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="
|
||||
},
|
||||
"node_modules/file-type": {
|
||||
"version": "16.5.4",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz",
|
||||
"integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readable-web-to-node-stream": "^3.0.0",
|
||||
"strtok3": "^6.2.4",
|
||||
"token-types": "^4.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/gifwrap": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz",
|
||||
"integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"image-q": "^4.0.0",
|
||||
"omggif": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/image-q": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz",
|
||||
"integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "16.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jimp": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jimp/-/jimp-1.6.0.tgz",
|
||||
"integrity": "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jimp/core": "1.6.0",
|
||||
"@jimp/diff": "1.6.0",
|
||||
"@jimp/js-bmp": "1.6.0",
|
||||
"@jimp/js-gif": "1.6.0",
|
||||
"@jimp/js-jpeg": "1.6.0",
|
||||
"@jimp/js-png": "1.6.0",
|
||||
"@jimp/js-tiff": "1.6.0",
|
||||
"@jimp/plugin-blit": "1.6.0",
|
||||
"@jimp/plugin-blur": "1.6.0",
|
||||
"@jimp/plugin-circle": "1.6.0",
|
||||
"@jimp/plugin-color": "1.6.0",
|
||||
"@jimp/plugin-contain": "1.6.0",
|
||||
"@jimp/plugin-cover": "1.6.0",
|
||||
"@jimp/plugin-crop": "1.6.0",
|
||||
"@jimp/plugin-displace": "1.6.0",
|
||||
"@jimp/plugin-dither": "1.6.0",
|
||||
"@jimp/plugin-fisheye": "1.6.0",
|
||||
"@jimp/plugin-flip": "1.6.0",
|
||||
"@jimp/plugin-hash": "1.6.0",
|
||||
"@jimp/plugin-mask": "1.6.0",
|
||||
"@jimp/plugin-print": "1.6.0",
|
||||
"@jimp/plugin-quantize": "1.6.0",
|
||||
"@jimp/plugin-resize": "1.6.0",
|
||||
"@jimp/plugin-rotate": "1.6.0",
|
||||
"@jimp/plugin-threshold": "1.6.0",
|
||||
"@jimp/types": "1.6.0",
|
||||
"@jimp/utils": "1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/jpeg-js": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz",
|
||||
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/omggif": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
|
||||
"integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||
"license": "(MIT AND Zlib)"
|
||||
},
|
||||
"node_modules/parse-bmfont-ascii": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz",
|
||||
"integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parse-bmfont-binary": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz",
|
||||
"integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/parse-bmfont-xml": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz",
|
||||
"integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml-parse-from-string": "^1.0.0",
|
||||
"xml2js": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/peek-readable": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz",
|
||||
"integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/pixelmatch": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.3.0.tgz",
|
||||
"integrity": "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"pngjs": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pixelmatch": "bin/pixelmatch"
|
||||
}
|
||||
},
|
||||
"node_modules/pixelmatch/node_modules/pngjs": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
|
||||
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz",
|
||||
"integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"process": "^0.11.10",
|
||||
"string_decoder": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-web-to-node-stream": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz",
|
||||
"integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readable-stream": "^4.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz",
|
||||
"integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-xml-to-json": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz",
|
||||
"integrity": "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.12.2"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strtok3": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz",
|
||||
"integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"peek-readable": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/tinycolor2": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
|
||||
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/token-types": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz",
|
||||
"integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tokenizer/token": "^0.3.0",
|
||||
"ieee754": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Borewit"
|
||||
}
|
||||
},
|
||||
"node_modules/utif2": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz",
|
||||
"integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/xml-parse-from-string": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz",
|
||||
"integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xml2js": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
|
||||
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.25.76",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,9 @@
|
||||
],
|
||||
"author": "Max Litruv Boonzaayer",
|
||||
"license": "MIT",
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"dependencies": {
|
||||
"jimp": "^1.6.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/litruv/lit.ruv.wtf"
|
||||
|
||||
BIN
website/bulge-map.png
Normal file
BIN
website/bulge-map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
238
website/crt-bulge.js
Normal file
238
website/crt-bulge.js
Normal 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);
|
||||
});
|
||||
459
website/crt-canvas.js
Normal file
459
website/crt-canvas.js
Normal file
@@ -0,0 +1,459 @@
|
||||
/**
|
||||
* CRT Canvas Renderer
|
||||
* Renders content to a canvas with WebGL barrel distortion shader
|
||||
* Handles input coordinate transformation automatically
|
||||
*/
|
||||
class CRTCanvasRenderer {
|
||||
/**
|
||||
* @param {HTMLElement} sourceElement - Element to render (will be hidden)
|
||||
* @param {Object} options - Configuration options
|
||||
*/
|
||||
constructor(sourceElement, options = {}) {
|
||||
this.source = sourceElement;
|
||||
this.options = {
|
||||
bulgeStrength: options.bulgeStrength ?? 0.08,
|
||||
scanlineIntensity: options.scanlineIntensity ?? 0.1,
|
||||
vignetteStrength: options.vignetteStrength ?? 0.2,
|
||||
fps: options.fps ?? 60,
|
||||
...options
|
||||
};
|
||||
|
||||
this.canvas = null;
|
||||
this.gl = null;
|
||||
this.program = null;
|
||||
this.animationId = null;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the canvas and WebGL context
|
||||
*/
|
||||
init() {
|
||||
// Create canvas
|
||||
this.canvas = document.createElement('canvas');
|
||||
this.canvas.id = 'crt-canvas';
|
||||
this.canvas.style.cssText = `
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
// Insert canvas before source
|
||||
this.source.parentNode.insertBefore(this.canvas, this.source);
|
||||
|
||||
// Hide source but keep it functional
|
||||
this.source.style.position = 'absolute';
|
||||
this.source.style.opacity = '0';
|
||||
this.source.style.pointerEvents = 'none';
|
||||
|
||||
// Get WebGL context
|
||||
this.gl = this.canvas.getContext('webgl', {
|
||||
alpha: true,
|
||||
antialias: false,
|
||||
preserveDrawingBuffer: true
|
||||
});
|
||||
|
||||
if (!this.gl) {
|
||||
console.error('WebGL not supported, falling back to source element');
|
||||
this.source.style.opacity = '1';
|
||||
this.source.style.pointerEvents = 'auto';
|
||||
this.canvas.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupShaders();
|
||||
this.setupGeometry();
|
||||
this.setupTexture();
|
||||
this.setupEventListeners();
|
||||
this.resize();
|
||||
|
||||
window.addEventListener('resize', () => this.resize());
|
||||
|
||||
this.startRenderLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and compile WebGL shaders
|
||||
*/
|
||||
setupShaders() {
|
||||
const gl = this.gl;
|
||||
|
||||
// Vertex shader
|
||||
const vertexSource = `
|
||||
attribute vec2 a_position;
|
||||
attribute vec2 a_texCoord;
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
||||
v_texCoord = a_texCoord;
|
||||
}
|
||||
`;
|
||||
|
||||
// Fragment shader with barrel distortion
|
||||
const fragmentSource = `
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec2 u_resolution;
|
||||
uniform float u_bulgeStrength;
|
||||
uniform float u_scanlineIntensity;
|
||||
uniform float u_vignetteStrength;
|
||||
uniform float u_time;
|
||||
|
||||
varying vec2 v_texCoord;
|
||||
|
||||
vec2 barrelDistort(vec2 uv) {
|
||||
// Center UV around origin
|
||||
vec2 centered = uv * 2.0 - 1.0;
|
||||
|
||||
// Calculate distance from center
|
||||
float distSq = dot(centered, centered);
|
||||
|
||||
// Apply barrel distortion
|
||||
float distortion = 1.0 + u_bulgeStrength * distSq;
|
||||
centered *= distortion;
|
||||
|
||||
// Convert back to 0-1 range
|
||||
return centered * 0.5 + 0.5;
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Apply barrel distortion
|
||||
vec2 distortedUV = barrelDistort(v_texCoord);
|
||||
|
||||
// Check if UV is out of bounds
|
||||
if (distortedUV.x < 0.0 || distortedUV.x > 1.0 ||
|
||||
distortedUV.y < 0.0 || distortedUV.y > 1.0) {
|
||||
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Sample texture
|
||||
vec4 color = texture2D(u_texture, distortedUV);
|
||||
|
||||
// Scanlines
|
||||
float scanline = sin(distortedUV.y * u_resolution.y * 1.5) * 0.5 + 0.5;
|
||||
color.rgb *= 1.0 - (u_scanlineIntensity * (1.0 - scanline));
|
||||
|
||||
// Subtle RGB shift for chromatic aberration
|
||||
float shift = u_bulgeStrength * 0.002;
|
||||
float r = texture2D(u_texture, distortedUV + vec2(shift, 0.0)).r;
|
||||
float b = texture2D(u_texture, distortedUV - vec2(shift, 0.0)).b;
|
||||
color.r = mix(color.r, r, 0.5);
|
||||
color.b = mix(color.b, b, 0.5);
|
||||
|
||||
// Vignette
|
||||
vec2 vignetteUV = v_texCoord * 2.0 - 1.0;
|
||||
float vignette = 1.0 - dot(vignetteUV, vignetteUV) * u_vignetteStrength;
|
||||
color.rgb *= vignette;
|
||||
|
||||
gl_FragColor = color;
|
||||
}
|
||||
`;
|
||||
|
||||
// Compile shaders
|
||||
const vertexShader = this.compileShader(gl.VERTEX_SHADER, vertexSource);
|
||||
const fragmentShader = this.compileShader(gl.FRAGMENT_SHADER, fragmentSource);
|
||||
|
||||
// Create program
|
||||
this.program = gl.createProgram();
|
||||
gl.attachShader(this.program, vertexShader);
|
||||
gl.attachShader(this.program, fragmentShader);
|
||||
gl.linkProgram(this.program);
|
||||
|
||||
if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
|
||||
console.error('Shader program failed to link:', gl.getProgramInfoLog(this.program));
|
||||
}
|
||||
|
||||
gl.useProgram(this.program);
|
||||
|
||||
// Get uniform locations
|
||||
this.uniforms = {
|
||||
texture: gl.getUniformLocation(this.program, 'u_texture'),
|
||||
resolution: gl.getUniformLocation(this.program, 'u_resolution'),
|
||||
bulgeStrength: gl.getUniformLocation(this.program, 'u_bulgeStrength'),
|
||||
scanlineIntensity: gl.getUniformLocation(this.program, 'u_scanlineIntensity'),
|
||||
vignetteStrength: gl.getUniformLocation(this.program, 'u_vignetteStrength'),
|
||||
time: gl.getUniformLocation(this.program, 'u_time')
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile a shader
|
||||
*/
|
||||
compileShader(type, source) {
|
||||
const gl = this.gl;
|
||||
const shader = gl.createShader(type);
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
console.error('Shader compile error:', gl.getShaderInfoLog(shader));
|
||||
gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up geometry (full-screen quad)
|
||||
*/
|
||||
setupGeometry() {
|
||||
const gl = this.gl;
|
||||
|
||||
// Position attribute
|
||||
const positionBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
||||
-1, -1, 1, -1, -1, 1,
|
||||
-1, 1, 1, -1, 1, 1
|
||||
]), gl.STATIC_DRAW);
|
||||
|
||||
const positionLoc = gl.getAttribLocation(this.program, 'a_position');
|
||||
gl.enableVertexAttribArray(positionLoc);
|
||||
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Texture coordinate attribute
|
||||
const texCoordBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
||||
0, 1, 1, 1, 0, 0,
|
||||
0, 0, 1, 1, 1, 0
|
||||
]), gl.STATIC_DRAW);
|
||||
|
||||
const texCoordLoc = gl.getAttribLocation(this.program, 'a_texCoord');
|
||||
gl.enableVertexAttribArray(texCoordLoc);
|
||||
gl.vertexAttribPointer(texCoordLoc, 2, gl.FLOAT, false, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up texture for rendering source element
|
||||
*/
|
||||
setupTexture() {
|
||||
const gl = this.gl;
|
||||
|
||||
this.texture = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||
|
||||
// Set texture parameters
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle canvas resize
|
||||
*/
|
||||
resize() {
|
||||
const rect = this.source.getBoundingClientRect();
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
|
||||
this.canvas.width = rect.width * dpr;
|
||||
this.canvas.height = rect.height * dpr;
|
||||
this.canvas.style.width = rect.width + 'px';
|
||||
this.canvas.style.height = rect.height + 'px';
|
||||
|
||||
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply inverse barrel distortion to get source coordinates from canvas coordinates
|
||||
*/
|
||||
inverseDistort(canvasX, canvasY) {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
|
||||
// Normalize to 0-1
|
||||
let u = canvasX / rect.width;
|
||||
let v = canvasY / rect.height;
|
||||
|
||||
// Center around 0
|
||||
let x = u * 2 - 1;
|
||||
let y = v * 2 - 1;
|
||||
|
||||
// Iteratively solve inverse (Newton-Raphson)
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const distSq = x * x + y * y;
|
||||
const distortion = 1 + this.options.bulgeStrength * distSq;
|
||||
|
||||
// The forward transform is: distorted = original * distortion
|
||||
// So inverse is approximately: original = distorted / distortion
|
||||
x = (u * 2 - 1) / distortion;
|
||||
y = (v * 2 - 1) / distortion;
|
||||
}
|
||||
|
||||
// Convert back to pixel coordinates
|
||||
const sourceX = ((x + 1) / 2) * rect.width;
|
||||
const sourceY = ((y + 1) / 2) * rect.height;
|
||||
|
||||
return { x: sourceX, y: sourceY };
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up event listeners for mouse/touch input
|
||||
*/
|
||||
setupEventListeners() {
|
||||
const forwardEvent = (e, type) => {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const canvasX = e.clientX - rect.left;
|
||||
const canvasY = e.clientY - rect.top;
|
||||
|
||||
const sourceCoords = this.inverseDistort(canvasX, canvasY);
|
||||
const sourceX = rect.left + sourceCoords.x;
|
||||
const sourceY = rect.top + sourceCoords.y;
|
||||
|
||||
// Find element at transformed position in source
|
||||
this.source.style.pointerEvents = 'auto';
|
||||
this.source.style.opacity = '0.001'; // Nearly invisible but still there
|
||||
const element = document.elementFromPoint(sourceX, sourceY);
|
||||
this.source.style.pointerEvents = 'none';
|
||||
this.source.style.opacity = '0';
|
||||
|
||||
if (element && this.source.contains(element)) {
|
||||
const newEvent = new MouseEvent(type, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: sourceX,
|
||||
clientY: sourceY,
|
||||
button: e.button,
|
||||
buttons: e.buttons
|
||||
});
|
||||
element.dispatchEvent(newEvent);
|
||||
|
||||
// Update cursor
|
||||
this.canvas.style.cursor = window.getComputedStyle(element).cursor;
|
||||
}
|
||||
};
|
||||
|
||||
// Mouse events
|
||||
this.canvas.addEventListener('click', (e) => forwardEvent(e, 'click'));
|
||||
this.canvas.addEventListener('mousedown', (e) => forwardEvent(e, 'mousedown'));
|
||||
this.canvas.addEventListener('mouseup', (e) => forwardEvent(e, 'mouseup'));
|
||||
this.canvas.addEventListener('mousemove', (e) => forwardEvent(e, 'mousemove'));
|
||||
this.canvas.addEventListener('dblclick', (e) => forwardEvent(e, 'dblclick'));
|
||||
this.canvas.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
forwardEvent(e, 'contextmenu');
|
||||
});
|
||||
|
||||
// Wheel events
|
||||
this.canvas.addEventListener('wheel', (e) => {
|
||||
const rect = this.canvas.getBoundingClientRect();
|
||||
const canvasX = e.clientX - rect.left;
|
||||
const canvasY = e.clientY - rect.top;
|
||||
|
||||
const sourceCoords = this.inverseDistort(canvasX, canvasY);
|
||||
const sourceX = rect.left + sourceCoords.x;
|
||||
const sourceY = rect.top + sourceCoords.y;
|
||||
|
||||
this.source.style.pointerEvents = 'auto';
|
||||
const element = document.elementFromPoint(sourceX, sourceY);
|
||||
this.source.style.pointerEvents = 'none';
|
||||
|
||||
if (element && this.source.contains(element)) {
|
||||
element.dispatchEvent(new WheelEvent('wheel', {
|
||||
bubbles: true,
|
||||
clientX: sourceX,
|
||||
clientY: sourceY,
|
||||
deltaX: e.deltaX,
|
||||
deltaY: e.deltaY,
|
||||
deltaMode: e.deltaMode
|
||||
}));
|
||||
}
|
||||
}, { passive: true });
|
||||
|
||||
// Keyboard events should go to focused elements naturally
|
||||
// Focus management
|
||||
this.canvas.addEventListener('mousedown', () => {
|
||||
// Focus the terminal when clicking canvas
|
||||
const terminal = this.source.querySelector('.xterm-helper-textarea');
|
||||
if (terminal) {
|
||||
terminal.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture source element to texture using html2canvas
|
||||
*/
|
||||
async captureSource() {
|
||||
const gl = this.gl;
|
||||
|
||||
// Use html2canvas to render the source element
|
||||
try {
|
||||
const canvas = await html2canvas(this.source, {
|
||||
backgroundColor: null,
|
||||
scale: window.devicePixelRatio || 1,
|
||||
logging: false,
|
||||
useCORS: true
|
||||
});
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
|
||||
} catch (err) {
|
||||
console.error('Failed to capture source:', err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render frame
|
||||
*/
|
||||
render(time) {
|
||||
const gl = this.gl;
|
||||
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
// Set uniforms
|
||||
gl.uniform1i(this.uniforms.texture, 0);
|
||||
gl.uniform2f(this.uniforms.resolution, this.canvas.width, this.canvas.height);
|
||||
gl.uniform1f(this.uniforms.bulgeStrength, this.options.bulgeStrength);
|
||||
gl.uniform1f(this.uniforms.scanlineIntensity, this.options.scanlineIntensity);
|
||||
gl.uniform1f(this.uniforms.vignetteStrength, this.options.vignetteStrength);
|
||||
gl.uniform1f(this.uniforms.time, time * 0.001);
|
||||
|
||||
// Draw
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the render loop
|
||||
*/
|
||||
startRenderLoop() {
|
||||
const frameInterval = 1000 / this.options.fps;
|
||||
let lastCapture = 0;
|
||||
|
||||
const loop = async (time) => {
|
||||
// Capture at reduced rate to save performance
|
||||
if (time - lastCapture > frameInterval) {
|
||||
await this.captureSource();
|
||||
lastCapture = time;
|
||||
}
|
||||
|
||||
this.render(time);
|
||||
this.animationId = requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
this.animationId = requestAnimationFrame(loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop rendering
|
||||
*/
|
||||
destroy() {
|
||||
if (this.animationId) {
|
||||
cancelAnimationFrame(this.animationId);
|
||||
}
|
||||
this.canvas.remove();
|
||||
this.source.style.opacity = '1';
|
||||
this.source.style.pointerEvents = 'auto';
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use
|
||||
window.CRTCanvasRenderer = CRTCanvasRenderer;
|
||||
@@ -30,6 +30,7 @@
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="crt-border-overlay"></div>
|
||||
<div class="container">
|
||||
<div class="terminal-header">
|
||||
<div class="header-left">
|
||||
|
||||
BIN
website/screenborder.png
Normal file
BIN
website/screenborder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 329 KiB |
@@ -10,16 +10,31 @@ body {
|
||||
color: #0f0;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.crt-border-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border: 80px solid transparent;
|
||||
border-image-source: url('screenborder.png');
|
||||
border-image-slice: 200 95 fill;
|
||||
border-image-width: 80px;
|
||||
border-image-repeat: stretch;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 95vw;
|
||||
max-width: 1400px;
|
||||
height: 90vh;
|
||||
background: #001800;
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
left: 80px;
|
||||
right: 80px;
|
||||
bottom:100px;
|
||||
background: transparent;
|
||||
border: 3px solid #0f0;
|
||||
border-radius: 8px;
|
||||
box-shadow:
|
||||
@@ -28,10 +43,11 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.terminal-header {
|
||||
background: #002200;
|
||||
background: transparent;
|
||||
border-bottom: 2px solid #0f0;
|
||||
padding: 8px 16px;
|
||||
display: flex;
|
||||
@@ -99,8 +115,30 @@ body {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
/* Inline terminal input */
|
||||
.terminal-inline-input {
|
||||
position: absolute;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #0f0;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
caret-color: #0f0;
|
||||
text-shadow: 0 0 3px #0f0;
|
||||
z-index: 10;
|
||||
min-width: 50%;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.terminal-inline-input::placeholder {
|
||||
color: rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: #002200;
|
||||
background: transparent;
|
||||
border-top: 2px solid #0f0;
|
||||
padding: 6px 16px;
|
||||
display: flex;
|
||||
@@ -117,7 +155,7 @@ body {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* CRT screen effect */
|
||||
/* CRT scanlines effect */
|
||||
.container::before {
|
||||
content: " ";
|
||||
display: block;
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
// Version
|
||||
const VERSION = '1.0.0';
|
||||
|
||||
// Create inline input element
|
||||
const inlineInput = document.createElement('input');
|
||||
inlineInput.type = 'text';
|
||||
inlineInput.className = 'terminal-inline-input';
|
||||
inlineInput.autocomplete = 'off';
|
||||
inlineInput.autocorrect = 'on';
|
||||
inlineInput.autocapitalize = 'off';
|
||||
inlineInput.spellcheck = false;
|
||||
|
||||
// Initialize xterm.js terminal
|
||||
const term = new Terminal({
|
||||
cursorBlink: true,
|
||||
cursorStyle: 'block',
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'bar',
|
||||
fontFamily: '"Courier New", Courier, monospace',
|
||||
fontSize: 14,
|
||||
theme: {
|
||||
background: '#001800',
|
||||
foreground: '#00ff00',
|
||||
cursor: '#00ff00',
|
||||
cursor: 'transparent',
|
||||
cursorAccent: '#001800',
|
||||
selection: 'rgba(0, 255, 0, 0.3)',
|
||||
black: '#000000',
|
||||
@@ -31,7 +40,8 @@ const term = new Terminal({
|
||||
brightWhite: '#ffffff'
|
||||
},
|
||||
allowTransparency: true,
|
||||
scrollback: 1000
|
||||
scrollback: 1000,
|
||||
disableStdin: true
|
||||
});
|
||||
|
||||
// Add addons
|
||||
@@ -211,7 +221,8 @@ async function enterChatMode() {
|
||||
// Add separator and initial prompt
|
||||
term.writeln('');
|
||||
term.writeln('─'.repeat(term.cols || 60));
|
||||
term.write(`\x1b[1;32m>\x1b[0m `);
|
||||
term.write('\x1b[1;32m>\x1b[0m ');
|
||||
showInlineInput();
|
||||
}
|
||||
|
||||
// Exit chat mode
|
||||
@@ -224,7 +235,8 @@ function exitChatMode() {
|
||||
chatMode.displayNames = {}; // Clear display name cache
|
||||
term.clear();
|
||||
term.writeln(' Exited chat mode.\r\n');
|
||||
term.write(prompt);
|
||||
term.write(promptColored);
|
||||
showInlineInput();
|
||||
}
|
||||
|
||||
// Sync messages from Matrix
|
||||
@@ -889,238 +901,212 @@ function getWelcomeBanner() {
|
||||
}
|
||||
}
|
||||
|
||||
// Prompt
|
||||
const prompt = '\r\n\x1b[1;32muser@lit.ruv.wtf\x1b[0m $ ';
|
||||
// Prompt (plain version for display, we handle colors separately)
|
||||
const promptText = 'user@lit.ruv.wtf $ ';
|
||||
const promptColored = '\r\n\x1b[1;32muser@lit.ruv.wtf\x1b[0m $ ';
|
||||
|
||||
// Initialize terminal
|
||||
function init() {
|
||||
term.writeln(getWelcomeBanner());
|
||||
term.write(prompt);
|
||||
/**
|
||||
* Position the inline input at the cursor location
|
||||
*/
|
||||
function positionInlineInput() {
|
||||
const terminalEl = document.getElementById('terminal');
|
||||
const xtermEl = terminalEl.querySelector('.xterm-screen');
|
||||
|
||||
// Handle input
|
||||
term.onData(data => {
|
||||
handleInput(data);
|
||||
});
|
||||
if (!xtermEl) return;
|
||||
|
||||
// Get terminal dimensions
|
||||
const charWidth = term._core._renderService.dimensions.css.cell.width;
|
||||
const charHeight = term._core._renderService.dimensions.css.cell.height;
|
||||
|
||||
// Get cursor position (rows from bottom for scrollback)
|
||||
const cursorY = term.buffer.active.cursorY;
|
||||
const cursorX = term.buffer.active.cursorX;
|
||||
|
||||
// Position input
|
||||
inlineInput.style.left = (cursorX * charWidth) + 'px';
|
||||
inlineInput.style.top = (cursorY * charHeight) + 'px';
|
||||
inlineInput.style.height = charHeight + 'px';
|
||||
inlineInput.style.fontSize = term.options.fontSize + 'px';
|
||||
inlineInput.style.lineHeight = charHeight + 'px';
|
||||
}
|
||||
|
||||
// Handle terminal input
|
||||
async function handleInput(data) {
|
||||
const code = data.charCodeAt(0);
|
||||
/**
|
||||
* Show the inline input and position it
|
||||
*/
|
||||
function showInlineInput() {
|
||||
const terminalEl = document.getElementById('terminal');
|
||||
const xtermEl = terminalEl.querySelector('.xterm-screen');
|
||||
|
||||
// Chat mode input handling
|
||||
if (!xtermEl.contains(inlineInput)) {
|
||||
xtermEl.appendChild(inlineInput);
|
||||
}
|
||||
|
||||
inlineInput.style.display = 'block';
|
||||
positionInlineInput();
|
||||
inlineInput.focus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the inline input
|
||||
*/
|
||||
function hideInlineInput() {
|
||||
inlineInput.style.display = 'none';
|
||||
inlineInput.value = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit current input
|
||||
*/
|
||||
async function submitInlineInput() {
|
||||
const cmd = inlineInput.value.trim();
|
||||
const rawValue = inlineInput.value;
|
||||
inlineInput.value = '';
|
||||
|
||||
// Handle chat mode
|
||||
if (chatMode.active) {
|
||||
if (code === 13) { // Enter
|
||||
const msg = chatMode.inputLine.trim();
|
||||
chatMode.inputLine = '';
|
||||
|
||||
// Check for quit command
|
||||
if (msg === '/quit' || msg === '/exit') {
|
||||
exitChatMode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for help command
|
||||
if (msg === '/help') {
|
||||
// Insert help above separator
|
||||
term.write('\x1b[1A\x1b[2K\r'); // Move up 1 line, clear
|
||||
term.writeln('\x1b[33mChat Commands:\x1b[0m');
|
||||
term.writeln(' /help - Show this help message');
|
||||
term.writeln(' /nick [name] - Change your display name');
|
||||
term.writeln(' /quit - Exit chat mode');
|
||||
term.writeln('─'.repeat(term.cols || 60));
|
||||
term.write(`\x1b[1;32m>\x1b[0m `);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for nick command
|
||||
if (msg.startsWith('/nick ')) {
|
||||
const newNick = msg.substring(6).trim();
|
||||
term.write('\x1b[1A\x1b[2K\r'); // Move up 1 line, clear
|
||||
if (!newNick) {
|
||||
term.writeln('\x1b[31mError: /nick [name]\x1b[0m');
|
||||
} else {
|
||||
try {
|
||||
// Check if nickname is already taken
|
||||
const isTaken = await isDisplayNameTaken(newNick);
|
||||
if (isTaken) {
|
||||
term.writeln('\x1b[31mError: Nickname already in use\x1b[0m');
|
||||
if (cmd === '/quit' || cmd === '/exit') {
|
||||
hideInlineInput();
|
||||
exitChatMode();
|
||||
return;
|
||||
}
|
||||
|
||||
// Write what user typed
|
||||
term.write(rawValue + '\r\n');
|
||||
|
||||
if (cmd === '/help') {
|
||||
term.writeln('\x1b[33mChat Commands:\x1b[0m');
|
||||
term.writeln(' /help - Show this help message');
|
||||
term.writeln(' /nick [name] - Change your display name');
|
||||
term.writeln(' /quit - Exit chat mode');
|
||||
term.writeln('─'.repeat(term.cols || 60));
|
||||
term.write('\x1b[1;32m>\x1b[0m ');
|
||||
positionInlineInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd.startsWith('/nick ')) {
|
||||
const newNick = cmd.substring(6).trim();
|
||||
if (!newNick) {
|
||||
term.writeln('\x1b[31mError: /nick [name]\x1b[0m');
|
||||
} else {
|
||||
try {
|
||||
const isTaken = await isDisplayNameTaken(newNick);
|
||||
if (isTaken) {
|
||||
term.writeln('\x1b[31mError: Nickname already in use\x1b[0m');
|
||||
} else {
|
||||
const encodedUserId = encodeURIComponent(window.matrixSession.userId);
|
||||
const putResponse = await matrixApi(`/profile/${encodedUserId}/displayname`, 'PUT', {
|
||||
displayname: newNick
|
||||
});
|
||||
|
||||
if (putResponse && putResponse.errcode) {
|
||||
term.writeln(`\x1b[31mError: ${putResponse.error || 'Nickname rejected by server'}\x1b[0m`);
|
||||
} else {
|
||||
// Attempt to change nickname
|
||||
const encodedUserId = encodeURIComponent(window.matrixSession.userId);
|
||||
const putResponse = await matrixApi(`/profile/${encodedUserId}/displayname`, 'PUT', {
|
||||
displayname: newNick
|
||||
});
|
||||
const verifyData = await matrixApi(`/profile/${encodedUserId}/displayname`, 'GET');
|
||||
const actualName = verifyData && verifyData.displayname;
|
||||
|
||||
// Check if PUT request returned an error
|
||||
if (putResponse && putResponse.errcode) {
|
||||
term.writeln(`\x1b[31mError: ${putResponse.error || 'Nickname rejected by server'}\x1b[0m`);
|
||||
if (actualName === newNick) {
|
||||
chatMode.displayNames[window.matrixSession.userId] = newNick;
|
||||
term.writeln(`\x1b[32mNickname changed to: ${newNick}\x1b[0m`);
|
||||
} else {
|
||||
// Verify the change was accepted by fetching it back
|
||||
const verifyData = await matrixApi(`/profile/${encodedUserId}/displayname`, 'GET');
|
||||
const actualName = verifyData && verifyData.displayname;
|
||||
|
||||
if (actualName === newNick) {
|
||||
// Success - update local cache
|
||||
chatMode.displayNames[window.matrixSession.userId] = newNick;
|
||||
term.writeln(`\x1b[32mNickname changed to: ${newNick}\x1b[0m`);
|
||||
} else {
|
||||
// Backend silently changed or rejected it
|
||||
if (actualName) {
|
||||
term.writeln(`\x1b[31mError: Server changed nickname to: ${actualName}\x1b[0m`);
|
||||
} else {
|
||||
term.writeln(`\x1b[31mError: Nickname rejected by server (invalid format)\x1b[0m`);
|
||||
}
|
||||
}
|
||||
term.writeln(`\x1b[31mError: Nickname rejected by server\x1b[0m`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
term.writeln(`\x1b[31mError: Failed to change nickname - ${error.message}\x1b[0m`);
|
||||
}
|
||||
} catch (error) {
|
||||
term.writeln(`\x1b[31mError: Failed to change nickname\x1b[0m`);
|
||||
}
|
||||
term.writeln('─'.repeat(term.cols || 60));
|
||||
term.write(`\x1b[1;32m>\x1b[0m `);
|
||||
return;
|
||||
}
|
||||
|
||||
// Send message
|
||||
if (msg && !msg.startsWith('/')) {
|
||||
await sendChatMessage(msg);
|
||||
} else if (msg.startsWith('/')) {
|
||||
term.write('\x1b[1A\x1b[2K\r'); // Move up 1 line, clear
|
||||
term.writeln(`\x1b[31mUnknown command: ${msg.split(' ')[0]}. Type /help\x1b[0m`);
|
||||
term.writeln('─'.repeat(term.cols || 60));
|
||||
term.write(`\x1b[1;32m>\x1b[0m `);
|
||||
} else {
|
||||
// Empty input, just redraw prompt
|
||||
renderChatPrompt();
|
||||
}
|
||||
} else if (code === 127) { // Backspace
|
||||
if (chatMode.inputLine.length > 0) {
|
||||
chatMode.inputLine = chatMode.inputLine.slice(0, -1);
|
||||
term.write('\b \b'); // Move back, write space, move back again
|
||||
}
|
||||
} else if (code === 4) { // Ctrl+D
|
||||
exitChatMode();
|
||||
} else if (code >= 32 && code < 127) { // Printable characters
|
||||
chatMode.inputLine += data;
|
||||
term.write(data); // Just write the character directly
|
||||
term.writeln('─'.repeat(term.cols || 60));
|
||||
term.write('\x1b[1;32m>\x1b[0m ');
|
||||
positionInlineInput();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd && !cmd.startsWith('/')) {
|
||||
await sendChatMessage(cmd);
|
||||
positionInlineInput();
|
||||
} else if (cmd.startsWith('/')) {
|
||||
term.writeln(`\x1b[31mUnknown command: ${cmd.split(' ')[0]}. Type /help\x1b[0m`);
|
||||
term.writeln('─'.repeat(term.cols || 60));
|
||||
term.write('\x1b[1;32m>\x1b[0m ');
|
||||
positionInlineInput();
|
||||
} else {
|
||||
term.write('\x1b[1;32m>\x1b[0m ');
|
||||
positionInlineInput();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Normal mode input handling
|
||||
// Handle special keys
|
||||
if (code === 13) { // Enter
|
||||
term.write('\r\n');
|
||||
await executeCommand(currentLine.trim());
|
||||
currentLine = '';
|
||||
cursorPosition = 0;
|
||||
// Only show prompt if not in chat mode (chat command enters chat mode)
|
||||
if (!chatMode.active) {
|
||||
term.write(prompt);
|
||||
}
|
||||
} else if (code === 127) { // Backspace
|
||||
if (cursorPosition > 0) {
|
||||
currentLine = currentLine.slice(0, cursorPosition - 1) + currentLine.slice(cursorPosition);
|
||||
cursorPosition--;
|
||||
term.write('\b \b');
|
||||
// Redraw rest of line if needed
|
||||
if (cursorPosition < currentLine.length) {
|
||||
const remaining = currentLine.slice(cursorPosition);
|
||||
term.write(remaining + ' ');
|
||||
for (let i = 0; i <= remaining.length; i++) {
|
||||
term.write('\b');
|
||||
// Normal command mode
|
||||
term.write(rawValue + '\r\n');
|
||||
|
||||
if (cmd) {
|
||||
await executeCommand(cmd);
|
||||
}
|
||||
|
||||
// Show prompt if not in chat mode
|
||||
if (!chatMode.active) {
|
||||
term.write(promptColored);
|
||||
showInlineInput();
|
||||
}
|
||||
|
||||
term.scrollToBottom();
|
||||
}
|
||||
|
||||
// Initialize terminal
|
||||
function init() {
|
||||
term.writeln(getWelcomeBanner());
|
||||
term.write(promptColored);
|
||||
|
||||
// Add inline input after a short delay to ensure terminal is rendered
|
||||
setTimeout(() => {
|
||||
showInlineInput();
|
||||
}, 100);
|
||||
|
||||
// Handle inline input events
|
||||
inlineInput.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
submitInlineInput();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (commandHistory.length > 0) {
|
||||
if (historyIndex === -1 || historyIndex >= commandHistory.length) {
|
||||
historyIndex = commandHistory.length - 1;
|
||||
} else if (historyIndex > 0) {
|
||||
historyIndex--;
|
||||
}
|
||||
inlineInput.value = commandHistory[historyIndex] || '';
|
||||
}
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (historyIndex < commandHistory.length - 1) {
|
||||
historyIndex++;
|
||||
inlineInput.value = commandHistory[historyIndex] || '';
|
||||
} else {
|
||||
historyIndex = commandHistory.length;
|
||||
inlineInput.value = '';
|
||||
}
|
||||
}
|
||||
} else if (code === 27) { // Escape sequences (arrows)
|
||||
if (data === '\x1b[A') { // Up arrow
|
||||
navigateHistory(-1);
|
||||
} else if (data === '\x1b[B') { // Down arrow
|
||||
navigateHistory(1);
|
||||
} else if (data === '\x1b[C') { // Right arrow
|
||||
if (cursorPosition < currentLine.length) {
|
||||
cursorPosition++;
|
||||
term.write('\x1b[C');
|
||||
}
|
||||
} else if (data === '\x1b[D') { // Left arrow
|
||||
if (cursorPosition > 0) {
|
||||
cursorPosition--;
|
||||
term.write('\x1b[D');
|
||||
}
|
||||
} else if (data === '\x1b[H') { // Home
|
||||
while (cursorPosition > 0) {
|
||||
cursorPosition--;
|
||||
term.write('\x1b[D');
|
||||
}
|
||||
} else if (data === '\x1b[F') { // End
|
||||
while (cursorPosition < currentLine.length) {
|
||||
cursorPosition++;
|
||||
term.write('\x1b[C');
|
||||
}
|
||||
}
|
||||
} else if (code === 3) { // Ctrl+C
|
||||
term.write('^C\r\n');
|
||||
currentLine = '';
|
||||
cursorPosition = 0;
|
||||
term.write(prompt);
|
||||
} else if (code === 12) { // Ctrl+L
|
||||
term.clear();
|
||||
term.write(prompt + currentLine);
|
||||
const backspaces = currentLine.length - cursorPosition;
|
||||
for (let i = 0; i < backspaces; i++) {
|
||||
term.write('\b');
|
||||
}
|
||||
} else if (code >= 32 && code < 127) { // Printable characters
|
||||
currentLine = currentLine.slice(0, cursorPosition) + data + currentLine.slice(cursorPosition);
|
||||
cursorPosition++;
|
||||
term.write(data);
|
||||
// Redraw rest of line if in middle
|
||||
if (cursorPosition < currentLine.length) {
|
||||
const remaining = currentLine.slice(cursorPosition);
|
||||
term.write(remaining);
|
||||
for (let i = 0; i < remaining.length; i++) {
|
||||
term.write('\b');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Navigate command history
|
||||
function navigateHistory(direction) {
|
||||
if (commandHistory.length === 0) return;
|
||||
});
|
||||
|
||||
historyIndex += direction;
|
||||
// Click on terminal focuses input
|
||||
document.getElementById('terminal').addEventListener('click', () => {
|
||||
if (inlineInput.style.display !== 'none') {
|
||||
inlineInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
if (historyIndex < 0) {
|
||||
historyIndex = -1;
|
||||
clearCurrentLine();
|
||||
currentLine = '';
|
||||
cursorPosition = 0;
|
||||
} else if (historyIndex >= commandHistory.length) {
|
||||
historyIndex = commandHistory.length - 1;
|
||||
} else {
|
||||
clearCurrentLine();
|
||||
currentLine = commandHistory[historyIndex];
|
||||
cursorPosition = currentLine.length;
|
||||
term.write(currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear current line
|
||||
function clearCurrentLine() {
|
||||
// Move to start of input
|
||||
for (let i = 0; i < cursorPosition; i++) {
|
||||
term.write('\b');
|
||||
}
|
||||
// Clear line
|
||||
for (let i = 0; i < currentLine.length; i++) {
|
||||
term.write(' ');
|
||||
}
|
||||
// Move back to start
|
||||
for (let i = 0; i < currentLine.length; i++) {
|
||||
term.write('\b');
|
||||
}
|
||||
// Reposition on resize
|
||||
window.addEventListener('resize', () => {
|
||||
setTimeout(positionInlineInput, 50);
|
||||
});
|
||||
|
||||
// Handle scroll to keep input positioned
|
||||
term.onScroll(() => {
|
||||
positionInlineInput();
|
||||
});
|
||||
}
|
||||
|
||||
// Execute command
|
||||
|
||||
Reference in New Issue
Block a user