Vision Correction PRO v8

Cylindrical lens simulation — curved arc correction, per-eye PD, depth/width/spread/tint controls

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Аўтар
CosmicIndustries
Усталяванняў за дзень
0
Усяго ўсталяванняў
0
Рэйтынг
0 0 0
Версія
8.0
Створаны
31.03.2026
Абноўлены
31.03.2026
Памер
15.7 КБ
Ліцэнзія
Н/Д
Ужываецца на
Усе сайты

// ==UserScript==
// @name Vision Correction PRO v8
// @namespace vision.correction.pro
// @version 8.0
// @description Cylindrical lens simulation — curved arc correction, per-eye PD, depth/width/spread/tint controls
// @match *://*/*
// @run-at document-end
// @grant none
// ==/UserScript==

(function () {
'use strict';

const STORAGE_KEY = "vision_v8";

const DEFAULTS = {
enabled: true,
showTest: false,
panelX: null,
panelY: null,
left: {
axis: 50,
strength: 0.52,
curvature: 60,
width: 1.2,
depth: 3,
spread: 0.85,
falloff: 1.8,
pdOffset: -30,
tint: 0,
},
right: {
axis: 45,
strength: 0.48,
curvature: 55,
width: 1.2,
depth: 3,
spread: 0.85,
falloff: 1.8,
pdOffset: 30,
tint: 0,
},
focusAssist: 0.15,
};

function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); }

function load() {
try {
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};
return {
...DEFAULTS, ...saved,
left: { ...DEFAULTS.left, ...(saved.left || {}) },
right: { ...DEFAULTS.right, ...(saved.right || {}) },
};
} catch { return deepClone(DEFAULTS); }
}

let settings = load();
let dirty = true;

function save() { localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)); }
function markDirty() { dirty = true; }

/* CANVAS */

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

Object.assign(canvas.style, {
position: 'fixed', top: '0', left: '0',
width: '100%', height: '100%',
pointerEvents: 'none', zIndex: '999999',
mixBlendMode: 'screen',
});
document.body.appendChild(canvas);

let dpr = 1, W = 0, H = 0;

function resize() {
dpr = window.devicePixelRatio || 1;
W = window.innerWidth; H = window.innerHeight;
canvas.width = W * dpr; canvas.height = H * dpr;
canvas.style.width = W + 'px'; canvas.style.height = H + 'px';
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
markDirty();
}
window.addEventListener('resize', resize);
resize();

/* CYLINDRICAL LENS ENGINE
─────────────────────────────────────────────────────────
A cylindrical lens corrects refractive error along one meridian.
We simulate the corrective wavefront by drawing iso-phase arcs:
quadratic bezier curves whose sag (bow) models the cylinder power
and reverses sign across the optical center.

Arc at perpendicular position t from optical center (cx, cy):
P1 = (ox, oy) + axisDir * halfLen
P2 = (ox, oy) - axisDir * halfLen
CP = (ox, oy) + normDir * sag ← sag = curvature * strength * falloff
sag sign flips at t=0 → continuous wavefront reversal like a real lens.
*/

function tintColor(tint, alpha) {
if (tint >= 0) {
return `rgba(255,${Math.round(255 - tint*40)},${Math.round(255 - tint*120)},${alpha})`;
} else {
const t = -tint;
return `rgba(${Math.round(255 - t*80)},${Math.round(255 - t*20)},255,${alpha})`;
}
}

function drawCorrectionField(eye) {
const { axis, strength, curvature, width, depth, spread, falloff, pdOffset, tint } = eye;

const angle = axis * Math.PI / 180;
const axisX = Math.cos(angle), axisY = Math.sin(angle);
const normX = -axisY, normY = axisX;

const cx = W / 2 + pdOffset;
const cy = H / 2;
const halfLen = Math.max(W, H) * 0.7;
const spacing = 20;

const count = Math.ceil(Math.max(W, H) / spacing) + 6;

ctx.lineWidth = width;

for (let pass = 0; pass < depth; pass++) {
const ps = (pass - (depth - 1) / 2) * 1.8;

for (let i = -count; i <= count; i++) {
const t = i * spacing;
const ox = cx + normX * t + axisX * ps;
const oy = cy + normY * t + axisY * ps;

const dx = (ox - cx) / (W * 0.5 + 1);
const dy = (oy - cy) / (H * 0.5 + 1);
const dist = Math.sqrt(dx*dx + dy*dy);

if (dist > spread) continue;

const ff = Math.pow(dist / (spread + 0.001), falloff);
const ls = strength * ff;
const sag = (t >= 0 ? 1 : -1) * curvature * ls;

const x1 = ox + axisX * halfLen, y1 = oy + axisY * halfLen;
const x2 = ox - axisX * halfLen, y2 = oy - axisY * halfLen;
const cpx = ox + normX * sag, cpy = oy + normY * sag;

const alpha = (0.007 + ls * 0.044) / Math.max(depth, 1);

ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.quadraticCurveTo(cpx, cpy, x2, y2);
ctx.strokeStyle = tintColor(tint, alpha);
ctx.stroke();
}
}
}

function drawFocusAssist() {
const s = settings.focusAssist;
if (s <= 0) return;
const cx = W / 2, cy = H / 2;
const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, cx * 0.4);
g.addColorStop(0, `rgba(255,255,255,${0.04 * s})`);
g.addColorStop(1, 'rgba(255,255,255,0)');
ctx.globalAlpha = 1;
ctx.fillStyle = g;
ctx.fillRect(0, 0, W, H);
}

/* RENDER LOOP */

function render() {
requestAnimationFrame(render);
if (!settings.enabled) {
if (dirty) { ctx.clearRect(0, 0, canvas.width, canvas.height); dirty = false; }
return;
}
if (!dirty) return;
dirty = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawCorrectionField(settings.left);
drawCorrectionField(settings.right);
drawFocusAssist();
}
render();

/* TEST APPLET */

const testLayer = document.createElement('div');
Object.assign(testLayer.style, {
position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
display: 'none', zIndex: '999998', pointerEvents: 'none',
color: 'white', fontFamily: 'monospace', background: 'rgba(0,0,0,0.72)',
});

(function buildTest() {
const center = document.createElement('div');
Object.assign(center.style, {
position: 'absolute', top: '50%', left: '50%',
transform: 'translate(-50%,-50%)', textAlign: 'center',
});
center.innerHTML = `

AXIS CALIBRATION WHEEL


Adjust axis until lines appear thinnest and sharpest

`;

const wheel = document.createElement('div');
wheel.style.cssText = 'position:relative;width:300px;height:300px;margin:14px auto;';
for (let i = 0; i < 36; i++) {
const deg = i * 5;
const line = document.createElement('div');
Object.assign(line.style, {
position: 'absolute', width: '1px', height: '300px',
background: i % 6 === 0 ? '#fff' : 'rgba(255,255,255,0.4)',
left: '50%', top: '0',
transformOrigin: '50% 50%',
transform: `translateX(-50%) rotate(${deg}deg)`,
});
if (i % 6 === 0) {
const lbl = document.createElement('span');
lbl.style.cssText = 'position:absolute;font-size:9px;color:#aaa;left:50%;top:2px;transform:translateX(-50%);';
lbl.textContent = deg + '\u00b0';
line.appendChild(lbl);
}
wheel.appendChild(line);
}
center.appendChild(wheel);
testLayer.appendChild(center);

const acuity = document.createElement('div');
Object.assign(acuity.style, {
position: 'absolute', bottom: '10%',
left: '50%', transform: 'translateX(-50%)', textAlign: 'center',
});
acuity.innerHTML = `

ACUITY / FOCUS TEST

The quick brown fox jumps over the lazy dog

The quick brown fox jumps over the lazy dog

The quick brown fox jumps over the lazy dog

The quick brown fox jumps over the lazy dog

The quick brown fox jumps over the lazy dog

`;
testLayer.appendChild(acuity);
})();

document.body.appendChild(testLayer);
function syncTest() { testLayer.style.display = settings.showTest ? 'block' : 'none'; }

/* PANEL */

const Sty = {
btn: 'flex:1;padding:4px 0;border:none;border-radius:4px;cursor:pointer;font-size:11px;background:#252535;color:#ccc;',
dbtn:'flex:1;padding:4px 0;border:none;border-radius:4px;cursor:pointer;font-size:11px;background:#5c1111;color:#fbb;',
sec: 'color:#666;font-size:10px;text-transform:uppercase;letter-spacing:.06em;margin:7px 0 2px;',
};

function sliderHTML(id, label, min, max, step, val, title) {
return `


${label}

${val}

`;
}

function eyeHTML(p, e) {
return [
sliderHTML(`${p}_axis`, 'Axis °', 0, 180, 1, e.axis, 'Correction axis meridian (degrees)'),
sliderHTML(`${p}_strength`, 'Strength', 0, 1, 0.01, e.strength, 'Cylinder power / overall intensity'),
sliderHTML(`${p}_curvature`, 'Curvature', 0, 200, 1, e.curvature, 'Arc bow depth — 0=flat, 200=highly curved'),
sliderHTML(`${p}_width`, 'Width', 0.5, 5, 0.1, e.width, 'Stroke width of correction arcs'),
sliderHTML(`${p}_depth`, 'Depth', 1, 8, 1, e.depth, 'Number of overlay passes'),
sliderHTML(`${p}_spread`, 'Spread', 0, 1, 0.01, e.spread, 'Radial extent of correction field'),
sliderHTML(`${p}_falloff`, 'Falloff', 0.5, 4, 0.1, e.falloff, 'Falloff exponent — higher concentrates edges'),
sliderHTML(`${p}_pdOffset`, 'PD Offset', -150, 150, 1, e.pdOffset, 'Horizontal optical center shift'),
sliderHTML(`${p}_tint`, 'Tint', -1, 1, 0.05, e.tint, '-1 cool/blue 0 white +1 warm/amber'),
].join('');
}

const panel = document.createElement('div');
Object.assign(panel.style, {
position: 'fixed', bottom: '10px', right: '10px',
background: 'rgba(10,10,16,0.93)', color: '#ddd',
padding: '10px 13px', fontSize: '12px',
zIndex: '2147483647', borderRadius: '8px',
pointerEvents: 'auto', userSelect: 'none',
boxShadow: '0 3px 18px rgba(0,0,0,0.75)',
minWidth: '268px', lineHeight: '1.6',
});

panel.innerHTML = `


👁 Vision PRO v8


◀ Left Eye
Right Eye ▶
${eyeHTML('l', settings.left)}
${eyeHTML('r', settings.right)}
Global
${sliderHTML('gf', 'Focus Assist', 0, 0.5, 0.01, settings.focusAssist, 'Central focus enhancement')}

Test
On/Off
Reset

Alt+V toggle · hover sliders for tips

`;

document.body.appendChild(panel);

if (settings.panelX !== null) {
panel.style.right = 'auto'; panel.style.bottom = 'auto';
panel.style.left = settings.panelX + 'px';
panel.style.top = settings.panelY + 'px';
}

/* tabs */
panel.querySelector('#tl').addEventListener('click', () => setTab('l'));
panel.querySelector('#tr').addEventListener('click', () => setTab('r'));
function setTab(e) {
panel.querySelector('#el').style.display = e==='l' ? 'block' : 'none';
panel.querySelector('#er').style.display = e==='r' ? 'block' : 'none';
panel.querySelector('#tl').style.cssText = `${Sty.btn};flex:1;` + (e==='l' ? 'background:#161626;color:#8af;' : '');
panel.querySelector('#tr').style.cssText = `${Sty.btn};flex:1;` + (e==='r' ? 'background:#161626;color:#8af;' : '');
}

/* sliders */
function bind(id, setter) {
const inp = panel.querySelector('#'+id);
const lbl = panel.querySelector('#'+id+'_v');
if (!inp) return;
inp.addEventListener('input', () => {
const v = parseFloat(inp.value);
lbl.textContent = v; setter(v); save(); markDirty();
});
}
['axis','strength','curvature','width','depth','spread','falloff','pdOffset','tint'].forEach(k => {
bind('l_'+k, v => settings.left[k] = v);
bind('r_'+k, v => settings.right[k] = v);
});
bind('gf', v => settings.focusAssist = v);

/* buttons */
panel.querySelector('#btst').addEventListener('click', () => {
settings.showTest = !settings.showTest; syncTest(); save();
});
panel.querySelector('#btog').addEventListener('click', toggle);
panel.querySelector('#brst').addEventListener('click', () => {
if (!confirm('Reset all vision settings to defaults?')) return;
Object.assign(settings, deepClone(DEFAULTS));
save(); refreshSliders(); syncTest(); markDirty();
});

function refreshSliders() {
const upd = (id, v) => {
const el = panel.querySelector('#'+id);
const lb = panel.querySelector('#'+id+'_v');
if (el) el.value = v;
if (lb) lb.textContent = v;
};
['axis','strength','curvature','width','depth','spread','falloff','pdOffset','tint'].forEach(k => {
upd('l_'+k, settings.left[k]);
upd('r_'+k, settings.right[k]);
});
upd('gf', settings.focusAssist);
}

/* minimize */
const pb = panel.querySelector('#pb');
const pm = panel.querySelector('#pm');
let mini = false;
pm.addEventListener('click', () => {
mini = !mini;
pb.style.display = mini ? 'none' : 'block';
pm.textContent = mini ? '+' : '–';
});

/* drag */
(function(){
const h = panel.querySelector('#ph');
let drag=false, ox=0, oy=0;
h.addEventListener('mousedown', e => {
drag=true;
const r = panel.getBoundingClientRect();
ox=e.clientX-r.left; oy=e.clientY-r.top;
h.style.cursor='grabbing'; e.preventDefault();
});
document.addEventListener('mousemove', e => {
if (!drag) return;
panel.style.left=(e.clientX-ox)+'px'; panel.style.top=(e.clientY-oy)+'px';
panel.style.right='auto'; panel.style.bottom='auto';
settings.panelX=e.clientX-ox; settings.panelY=e.clientY-oy;
});
document.addEventListener('mouseup', () => {
if (!drag) return; drag=false; h.style.cursor='grab'; save();
});
})();

/* keyboard */
document.addEventListener('keydown', e => { if (e.altKey && e.key==='v') toggle(); });

function toggle() {
settings.enabled = !settings.enabled;
flash(settings.enabled ? 'Correction ON' : 'Correction OFF');
save(); markDirty();
}
function flash(msg) {
const el = panel.querySelector('#st');
el.style.color='#8af'; el.textContent=msg;
setTimeout(()=>{ el.style.color='#555'; el.textContent='Alt+V toggle \u00b7 hover sliders for tips'; }, 1800);
}

syncTest();
})();