[go: up one dir, main page]

0% found this document useful (0 votes)
36 views39 pages

User Script 3

Uploaded by

faustinodima
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
36 views39 pages

User Script 3

Uploaded by

faustinodima
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 39

// ==UserScript==

// @name Enhanced Fake Camera for VFS Global (Anti-Detection)

// @namespace http://tampermonkey.net/

// @version 3.0

// @description Replaces real camera with photo or video with enhanced anti-detection measures

// @author Original: Brazuca! (Modified for anti-detection)

// @match https://idnvui.vfsglobal.com/*

// @match https://visa.vfsglobal.com/*

// @grant none

// ==/UserScript==

(function() {

'use strict';

// ==================== STATE VARIABLES ====================

let cameraActive = false;

let mediaURL = null;

let videoStream = null;

let canvas = null;

let animationFrameId = null;

let videoElement = null;

let isPaused = false;

// ==================== ADVANCED MOCK VARIABLES ====================

// Store original methods to restore later


const originalGetUserMedia = navigator.mediaDevices.getUserMedia;

const originalEnumerateDevices = navigator.mediaDevices.enumerateDevices;

const originalMediaStreamTrack = window.MediaStreamTrack;

// Mock camera device info

const mockCameraDeviceId = 'mock-camera-' + Math.random().toString(36).substring(2, 15);

const mockCameraGroupId = 'mock-group-' + Math.random().toString(36).substring(2, 15);

const mockCameraLabel = ['HD Webcam', 'USB Camera', 'Integrated Camera']


[Math.floor(Math.random() * 3)];

// Zoom control variables

let zoomLevel = 1.0;

const zoomIncrement = 0.1;

const maxZoom = 3.0;

const minZoom = 0.5;

// Position control variables

let offsetX = 0;

let offsetY = 0;

// Reference to UI elements

let zoomContainer = null;

let pauseContainer = null;

let pauseButton = null;

let zoomInButton = null;

let zoomOutButton = null;


let zoomCounter = null;

let directionContainer = null;

// ==================== HELPER FUNCTIONS ====================

function createElement(tagName, styles) {

const element = document.createElement(tagName);

Object.assign(element.style, styles);

return element;

// Generate a random but realistic camera device ID

function generateMockDeviceId() {

return 'mock-camera-' + Math.random().toString(36).substring(2, 15);

// Function to create realistic camera constraints

function createRealisticConstraints(constraints) {

// Preserve the original constraints where possible

let result = { ...constraints };

// Add realistic video constraints if not already specified

if (!result.video || typeof result.video === 'boolean') {

result.video = {

width: { ideal: 1280, min: 640, max: 1920 },

height: { ideal: 720, min: 480, max: 1080 },


frameRate: { ideal: 30, min: 15 },

facingMode: "user"

};

} else if (typeof result.video === 'object') {

// Enhance existing video constraints

result.video = {

...result.video,

deviceId: { exact: mockCameraDeviceId },

frameRate: result.video.frameRate || { ideal: 30, min: 15 }

};

return result;

// ==================== MOCK IMPLEMENTATION ====================

async function activateFakeCamera(url, isPhoto = false) {

// Clear previous state

if (cameraActive) {

deactivateFakeCamera();

try {

// Reset zoom and position

zoomLevel = 1.0;
offsetX = 0;

offsetY = 0;

isPaused = false;

mediaURL = url;

canvas = document.createElement('canvas');

canvas.width = 1280; // Higher resolution for better quality

canvas.height = 720; // 16:9 aspect ratio

let ctx = canvas.getContext('2d', { alpha: false, desynchronized: true });

ctx.imageSmoothingEnabled = true;

ctx.imageSmoothingQuality = 'high';

if (isPhoto) {

// Handle photo with quality preservation

let image = new Image();

image.crossOrigin = "anonymous";

image.src = url;

await new Promise((resolve, reject) => {

image.onload = resolve;

image.onerror = reject;

setTimeout(() => reject(new Error("Timeout loading image")), 8000);

});
// Calculate ratio to preserve aspect

const ratio = Math.min(

canvas.width / image.width,

canvas.height / image.height

);

const newWidth = image.width * ratio;

const newHeight = image.height * ratio;

// Center image on canvas

const x = (canvas.width - newWidth) / 2;

const y = (canvas.height - newHeight) / 2;

// Clear canvas with black background

ctx.fillStyle = '#000';

ctx.fillRect(0, 0, canvas.width, canvas.height);

// Draw image with high quality

ctx.drawImage(image, x, y, newWidth, newHeight);

// For photos, create a stream with minimal frames (static image)

videoStream = canvas.captureStream(5);

// Update UI controls

updateZoomControls(true);
updatePauseButton(true);

} else {

// Handle video with proper playback

videoElement = document.createElement('video');

videoElement.src = url;

videoElement.loop = true;

videoElement.muted = true;

videoElement.autoplay = true;

videoElement.playsInline = true;

videoElement.crossOrigin = "anonymous";

// Apply additional attributes to make video seem more realistic

videoElement.volume = 0;

videoElement.onloadedmetadata = () => {

// Adjust canvas dimensions to match video if needed

if (videoElement.videoWidth && videoElement.videoHeight) {

// Keep aspect ratio but maintain quality

const aspectRatio = videoElement.videoWidth / videoElement.videoHeight;

if (aspectRatio > 1) {

// Landscape video

canvas.width = Math.min(1280, videoElement.videoWidth);

canvas.height = canvas.width / aspectRatio;

} else {

// Portrait or square video


canvas.height = Math.min(720, videoElement.videoHeight);

canvas.width = canvas.height * aspectRatio;

};

try {

await videoElement.play();

} catch (e) {

console.warn("Warning starting video playback:", e);

// Continue despite error - may be autoplay policy

// Function to draw video frames with zoom and positioning

function drawFrame() {

if (!canvas || !ctx || !videoElement) return;

if (videoElement.videoWidth && videoElement.videoHeight) {

// Calculate base dimensions preserving aspect ratio

const ratio = Math.min(

canvas.width / videoElement.videoWidth,

canvas.height / videoElement.videoHeight

);

const baseWidth = videoElement.videoWidth * ratio;


const baseHeight = videoElement.videoHeight * ratio;

// Apply zoom

const scaledWidth = baseWidth * zoomLevel;

const scaledHeight = baseHeight * zoomLevel;

// Calculate centered position with zoom and offset

const baseX = (canvas.width - baseWidth) / 2;

const baseY = (canvas.height - baseHeight) / 2;

// Offset adjusted for zoom

const x = baseX - ((scaledWidth - baseWidth) / 2) + offsetX;

const y = baseY - ((scaledHeight - baseHeight) / 2) + offsetY;

// Clear with black background

ctx.fillStyle = '#000';

ctx.fillRect(0, 0, canvas.width, canvas.height);

// Draw the video frame with applied transformations

ctx.drawImage(videoElement, x, y, scaledWidth, scaledHeight);

// Update zoom counter if present

if (zoomCounter) {

zoomCounter.textContent = `Zoom: ${zoomLevel.toFixed(1)}x`;

}
} else {

// Fallback if video dimensions aren't available

ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);

// Continue animation loop

animationFrameId = requestAnimationFrame(drawFrame);

// Start drawing frames

drawFrame();

// Create stream with higher framerate for smoother video

videoStream = canvas.captureStream(30);

// Enable UI controls

updateZoomControls(false);

updatePauseButton(false);

// Add realistic tracks metadata to the stream

enhanceStreamWithRealisticTracks(videoStream);

// Override getUserMedia with our enhanced mock implementation

overrideMediaAPIs();
cameraActive = true;

updateButtons();

console.log(isPhoto ? "Fake photo activated!" : "Fake camera activated!");

} catch (error) {

console.error("Error activating fake media:", error);

alert("Error loading media. Check the selected file or try a smaller file.");

// Clean up in case of error

deactivateFakeCamera();

function enhanceStreamWithRealisticTracks(stream) {

if (!stream || !stream.getVideoTracks || stream.getVideoTracks().length === 0) return;

// Get the actual video track

const videoTrack = stream.getVideoTracks()[0];

// Add realistic properties to the track

if (videoTrack) {

// Store original methods to be used in our mocked ones

const originalGetSettings = videoTrack.getSettings;

const originalGetCapabilities = videoTrack.getCapabilities;


const originalGetConstraints = videoTrack.getConstraints;

const originalApplyConstraints = videoTrack.applyConstraints;

// Override getSettings to return realistic camera settings

videoTrack.getSettings = function() {

// Start with original settings if available

let settings = {};

try {

settings = originalGetSettings.call(this);

} catch (e) {

// Ignore errors from original method

// Add realistic camera settings

return {

...settings,

deviceId: mockCameraDeviceId,

groupId: mockCameraGroupId,

aspectRatio: canvas.width / canvas.height,

frameRate: 30,

height: canvas.height,

width: canvas.width,

resizeMode: "none",

facingMode: "user"

};
};

// Override getCapabilities with realistic camera capabilities

videoTrack.getCapabilities = function() {

let capabilities = {};

try {

capabilities = originalGetCapabilities.call(this);

} catch (e) {

// Ignore errors

return {

...capabilities,

deviceId: mockCameraDeviceId,

aspectRatio: {min: 0.5, max: 2.0},

frameRate: {min: 10, max: 60},

height: {min: 240, max: 1080, step: 1},

width: {min: 320, max: 1920, step: 1},

facingMode: ["user", "environment"]

};

};

// Override getConstraints to return fake constraints

videoTrack.getConstraints = function() {

let constraints = {};


try {

constraints = originalGetConstraints.call(this);

} catch (e) {

// Ignore errors

return {

...constraints,

deviceId: {exact: mockCameraDeviceId},

aspectRatio: {ideal: canvas.width / canvas.height},

frameRate: {ideal: 30},

height: {ideal: canvas.height},

width: {ideal: canvas.width},

facingMode: "user"

};

};

// Override applyConstraints to make it appear to work

videoTrack.applyConstraints = async function(constraints) {

try {

// Try original method first

return await originalApplyConstraints.call(this, constraints);

} catch (e) {

// Just return success - we're faking it anyway

return Promise.resolve();
}

};

// Override label and id with realistic values

Object.defineProperties(videoTrack, {

'label': {

get: function() { return mockCameraLabel; },

configurable: true

},

'id': {

get: function() { return mockCameraDeviceId + '-track'; },

configurable: true

},

'kind': {

get: function() { return 'video'; },

configurable: true

},

'enabled': {

get: function() { return true; },

set: function(value) { /* Do nothing */ },

configurable: true

},

'muted': {

get: function() { return false; },

configurable: true
},

'readyState': {

get: function() { return 'live'; },

configurable: true

});

function overrideMediaAPIs() {

// Override getUserMedia to return our fake stream

navigator.mediaDevices.getUserMedia = async function(constraints) {

// If camera is not active or constraints explicitly request something other than video,

// fallback to original implementation

if (!cameraActive || (constraints && constraints.audio && !constraints.video)) {

return originalGetUserMedia.call(this, constraints);

// Apply realistic constraints to make the mocked stream look more legitimate

const enhancedConstraints = createRealisticConstraints(constraints);

console.log("Enhanced constraints:", enhancedConstraints);

// Return our fake video stream

return new Promise((resolve) => {

resolve(videoStream);
});

};

// Override enumerateDevices to include our fake camera

navigator.mediaDevices.enumerateDevices = async function() {

try {

// Get the real devices first

const realDevices = await originalEnumerateDevices.call(this);

// If camera is not active, just return real devices

if (!cameraActive) {

return realDevices;

// Filter out any existing video devices that might conflict with ours

const nonVideoDevices = realDevices.filter(device => device.kind !== 'videoinput');

// Create our fake device

const fakeCamera = {

deviceId: mockCameraDeviceId,

groupId: mockCameraGroupId,

kind: 'videoinput',

label: mockCameraLabel,

toJSON: function() {

return {
deviceId: this.deviceId,

groupId: this.groupId,

kind: this.kind,

label: this.label

};

};

// Add our fake camera to the list

return [...nonVideoDevices, fakeCamera];

} catch (e) {

console.error("Error in enumerateDevices:", e);

// Fallback to original implementation in case of error

return originalEnumerateDevices.call(this);

};

function deactivateFakeCamera() {

// Stop animation frame if active

if (animationFrameId) {

cancelAnimationFrame(animationFrameId);

animationFrameId = null;

}
// Restore original media APIs

navigator.mediaDevices.getUserMedia = originalGetUserMedia;

navigator.mediaDevices.enumerateDevices = originalEnumerateDevices;

// Clean up resources

if (mediaURL && mediaURL.startsWith('blob:')) {

try {

URL.revokeObjectURL(mediaURL);

} catch(e) {

console.warn("Error revoking URL:", e);

// Reset state variables

mediaURL = null;

videoStream = null;

canvas = null;

videoElement = null;

isPaused = false;

cameraActive = false;

// Update UI

updateButtons();

updateZoomControls(true);

updatePauseButton(true);
console.log("Fake camera deactivated!");

// ==================== UI CONTROL FUNCTIONS ====================

function updateButtons() {

if (!videoButton || !photoButton) return;

if (cameraActive) {

videoButton.textContent = '🔴 Disable Media';

videoButton.style.backgroundColor = 'red';

photoButton.textContent = '🚫 Import Media';

photoButton.style.backgroundColor = 'gray';

photoButton.disabled = true;

} else {

videoButton.textContent = '🎥 Activate Video';

videoButton.style.backgroundColor = 'green';

photoButton.textContent = ' Import Photo';

photoButton.style.backgroundColor = 'blue';

photoButton.disabled = false;

}
function togglePause() {

if (!videoElement) return;

if (isPaused) {

videoElement.play();

pauseButton.textContent = ' Pause';

pauseButton.title = "Pause video";

} else {

videoElement.pause();

pauseButton.textContent = '▶️Play';

pauseButton.title = "Play video";

isPaused = !isPaused;

function updatePauseButton(hide) {

if (!pauseContainer) return;

pauseContainer.style.display = hide ? 'none' : 'flex';

// Reset pause button state

if (!hide && pauseButton) {

isPaused = false;

pauseButton.textContent = ' Pause';


pauseButton.title = "Pause video";

function updateZoomControls(hide) {

if (!zoomContainer) return;

zoomContainer.style.display = hide ? 'none' : 'flex';

if (directionContainer) {

directionContainer.style.display = hide ? 'none' : 'flex';

function adjustZoom(increment) {

// Adjust zoom level with limits

const newZoom = Math.min(maxZoom, Math.max(minZoom, zoomLevel + increment));

// Only update if there's a real change

if (newZoom !== zoomLevel) {

zoomLevel = newZoom;

// Reset offset when returning to normal zoom

if (Math.abs(zoomLevel - 1.0) < 0.05) {

offsetX = 0;

offsetY = 0;

}
}

// ==================== UI CREATION FUNCTIONS ====================

function createDirectionControls() {

const container = createElement('div', {

display: 'flex',

flexDirection: 'column',

alignItems: 'center',

gap: '5px',

marginTop: '10px'

});

// Up button

const upButton = createElement('button', {

padding: '5px 10px',

backgroundColor: '#444',

color: 'white',

border: 'none',

borderRadius: '5px',

cursor: 'pointer',

margin: '0',

fontWeight: 'bold',

width: '30px',

textAlign: 'center'
});

upButton.textContent = '↑';

upButton.title = "Move up";

upButton.addEventListener('click', () => { offsetY += 10; });

// Horizontal buttons container

const horizontalContainer = createElement('div', {

display: 'flex',

gap: '15px',

alignItems: 'center'

});

// Left button

const leftButton = createElement('button', {

padding: '5px 10px',

backgroundColor: '#444',

color: 'white',

border: 'none',

borderRadius: '5px',

cursor: 'pointer',

margin: '0',

fontWeight: 'bold',

width: '30px',

textAlign: 'center'

});
leftButton.textContent = '←';

leftButton.title = "Move left";

leftButton.addEventListener('click', () => { offsetX += 10; });

// Center button

const centerButton = createElement('button', {

padding: '5px 10px',

backgroundColor: '#555',

color: 'white',

border: 'none',

borderRadius: '5px',

cursor: 'pointer',

margin: '0',

fontWeight: 'bold',

width: '30px',

textAlign: 'center'

});

centerButton.textContent = '⊙';

centerButton.title = "Reset position";

centerButton.addEventListener('click', () => {

offsetX = 0;

offsetY = 0;

});

// Right button
const rightButton = createElement('button', {

padding: '5px 10px',

backgroundColor: '#444',

color: 'white',

border: 'none',

borderRadius: '5px',

cursor: 'pointer',

margin: '0',

fontWeight: 'bold',

width: '30px',

textAlign: 'center'

});

rightButton.textContent = '→';

rightButton.title = "Move right";

rightButton.addEventListener('click', () => { offsetX -= 10; });

// Down button

const downButton = createElement('button', {

padding: '5px 10px',

backgroundColor: '#444',

color: 'white',

border: 'none',

borderRadius: '5px',

cursor: 'pointer',

margin: '0',
fontWeight: 'bold',

width: '30px',

textAlign: 'center'

});

downButton.textContent = '↓';

downButton.title = "Move down";

downButton.addEventListener('click', () => { offsetY -= 10; });

// Assemble horizontal container

horizontalContainer.appendChild(leftButton);

horizontalContainer.appendChild(centerButton);

horizontalContainer.appendChild(rightButton);

// Assemble direction container

container.appendChild(upButton);

container.appendChild(horizontalContainer);

container.appendChild(downButton);

container.style.display = 'none'; // Hidden initially

return container;

function createControlPanel() {

// Create main container

const controlPanel = createElement('div', {


position: 'fixed',

bottom: '10px',

right: '10px',

backgroundColor: 'rgba(0, 0, 0, 0.7)',

padding: '10px',

borderRadius: '5px',

zIndex: '9999',

color: 'white',

fontFamily: 'Arial, sans-serif',

fontSize: '14px',

boxShadow: '0 0 10px rgba(0, 0, 0, 0.5)',

display: 'flex',

flexDirection: 'column',

gap: '10px'

});

// Title

const title = createElement('div', {

fontWeight: 'bold',

textAlign: 'center',

borderBottom: '1px solid #555',

paddingBottom: '5px',

marginBottom: '5px'

});

title.textContent = 'Camera Controls';


// Container for video and photo buttons

const mediaButtonsContainer = createElement('div', {

display: 'flex',

gap: '5px',

justifyContent: 'center'

});

// Video button

const videoButton = createElement('button', {

padding: '8px 12px',

backgroundColor: 'green',

color: 'white',

border: 'none',

borderRadius: '5px',

cursor: 'pointer',

flex: '1'

});

videoButton.textContent = '🎥 Activate Video';

videoButton.title = "Select and activate a video file";

videoButton.addEventListener('click', handleVideoButtonClick);

// Photo button

const photoButton = createElement('button', {

padding: '8px 12px',


backgroundColor: 'blue',

color: 'white',

border: 'none',

borderRadius: '5px',

cursor: 'pointer',

flex: '1'

});

photoButton.textContent = ' Import Photo';

photoButton.title = "Select and use a photo";

photoButton.addEventListener('click', handlePhotoButtonClick);

// Add file input (hidden)

const fileInput = createElement('input', {

display: 'none',

type: 'file'

});

fileInput.type = 'file';

fileInput.accept = 'image/*,video/*';

// Zoom controls container

zoomContainer = createElement('div', {

display: 'none', // Hidden initially

flexDirection: 'row',

gap: '5px',

alignItems: 'center',
justifyContent: 'center'

});

// Zoom out button

zoomOutButton = createElement('button', {

padding: '5px 10px',

backgroundColor: '#444',

color: 'white',

border: 'none',

borderRadius: '5px',

cursor: 'pointer',

fontWeight: 'bold'

});

zoomOutButton.textContent = '➖';

zoomOutButton.title = "Zoom out";

zoomOutButton.addEventListener('click', () => adjustZoom(-zoomIncrement));

// Zoom counter

zoomCounter = createElement('span', {

minWidth: '80px',

textAlign: 'center'

});

zoomCounter.textContent = 'Zoom: 1.0x';

// Zoom in button
zoomInButton = createElement('button', {

padding: '5px 10px',

backgroundColor: '#444',

color: 'white',

border: 'none',

borderRadius: '5px',

cursor: 'pointer',

fontWeight: 'bold'

});

zoomInButton.textContent = '➕';

zoomInButton.title = "Zoom in";

zoomInButton.addEventListener('click', () => adjustZoom(zoomIncrement));

// Pause controls container

pauseContainer = createElement('div', {

display: 'none', // Hidden initially

justifyContent: 'center'

});

// Pause/play button

pauseButton = createElement('button', {

padding: '5px 10px',

backgroundColor: '#444',

color: 'white',

border: 'none',
borderRadius: '5px',

cursor: 'pointer',

width: '100%'

});

pauseButton.textContent = ' Pause';

pauseButton.title = "Pause video";

pauseButton.addEventListener('click', togglePause);

// Create direction controls

directionContainer = createDirectionControls();

// Close button (X in top right corner)

const closeButton = createElement('button', {

position: 'absolute',

top: '5px',

right: '5px',

background: 'none',

border: 'none',

color: 'white',

cursor: 'pointer',

fontSize: '16px',

padding: '0',

margin: '0',

lineHeight: '1'

});
closeButton.textContent = '×';

closeButton.title = "Hide controls";

closeButton.addEventListener('click', () => {

controlPanel.style.display = 'none';

});

// Minimize button (to show when control panel is hidden)

const minimizeButton = createElement('button', {

position: 'fixed',

bottom: '10px',

right: '10px',

backgroundColor: 'rgba(0, 0, 0, 0.7)',

color: 'white',

border: 'none',

borderRadius: '5px',

padding: '5px 10px',

cursor: 'pointer',

zIndex: '9998',

display: 'none'

});

minimizeButton.textContent = '📷';

minimizeButton.title = "Show camera controls";

minimizeButton.addEventListener('click', () => {

controlPanel.style.display = 'flex';

minimizeButton.style.display = 'none';
});

// When control panel is closed, show minimize button

closeButton.addEventListener('click', () => {

minimizeButton.style.display = 'block';

});

// Add event listener for file selection

fileInput.addEventListener('change', handleFileSelect);

// Assemble zoom container

zoomContainer.appendChild(zoomOutButton);

zoomContainer.appendChild(zoomCounter);

zoomContainer.appendChild(zoomInButton);

// Assemble pause container

pauseContainer.appendChild(pauseButton);

// Assemble media buttons

mediaButtonsContainer.appendChild(videoButton);

mediaButtonsContainer.appendChild(photoButton);

// Assemble control panel

controlPanel.appendChild(closeButton);

controlPanel.appendChild(title);
controlPanel.appendChild(mediaButtonsContainer);

controlPanel.appendChild(zoomContainer);

controlPanel.appendChild(pauseContainer);

controlPanel.appendChild(directionContainer);

controlPanel.appendChild(fileInput);

// Add both elements to the document

document.body.appendChild(controlPanel);

document.body.appendChild(minimizeButton);

// Store references to elements

window.videoButton = videoButton;

window.photoButton = photoButton;

window.fileInput = fileInput;

// ==================== EVENT HANDLERS ====================

function handleVideoButtonClick() {

if (cameraActive) {

deactivateFakeCamera();

} else {

fileInput.setAttribute('accept', 'video/*');

fileInput.click();

}
function handlePhotoButtonClick() {

fileInput.setAttribute('accept', 'image/*');

fileInput.click();

function handleFileSelect(event) {

const file = event.target.files[0];

if (!file) return;

// Clear the input value so the same file can be selected again

event.target.value = '';

const isPhoto = file.type.startsWith('image/');

const url = URL.createObjectURL(file);

activateFakeCamera(url, isPhoto);

// ==================== KEYBOARD SHORTCUTS ====================

function setupKeyboardShortcuts() {

document.addEventListener('keydown', (event) => {

// Only if camera is active

if (!cameraActive) return;
switch(event.key) {

case '+':

case '=': // For keyboards where + requires Shift

adjustZoom(zoomIncrement);

break;

case '-':

adjustZoom(-zoomIncrement);

break;

case ' ': // Space bar

if (videoElement) togglePause();

break;

case 'ArrowUp':

offsetY += 10;

break;

case 'ArrowDown':

offsetY -= 10;

break;

case 'ArrowLeft':

offsetX += 10;

break;

case 'ArrowRight':

offsetX -= 10;

break;

case '0': // Reset zoom and position

zoomLevel = 1.0;
offsetX = 0;

offsetY = 0;

break;

});

// ==================== INITIALIZATION ====================

function init() {

// Create control panel when page is loaded

createControlPanel();

// Setup keyboard shortcuts

setupKeyboardShortcuts();

console.log("Enhanced Fake Camera for VFS Global initialized!");

// Initialize when DOM is loaded

if (document.readyState === 'loading') {

document.addEventListener('DOMContentLoaded', init);

} else {

init();

})();

You might also like