ClarkKentSG-1

Untitled

Nov 9th, 2026
9
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 25.08 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Jigsaw Explorer - zoom molette
  3. // @namespace local
  4. // @version 0.16
  5. // @description Zoome dans le puzzle avec la molette, sans recharger ni redécouper les pièces (zoom d'affichage). Clic milieu + glisser = déplacer la vue. Touche Z = activer/couper. Bouton loupe dans la barre : réglages.
  6. // @match https://www.jigsawexplorer.com/*
  7. // @run-at document-start
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. var MIN = 1, MAX = 6, edgeSize = 36, edgeSpeed = 14;
  15. var STORE = 'jigex-zoom-on';
  16. var on = true;
  17. try { on = localStorage.getItem(STORE) !== '0'; } catch (e) {}
  18. window.__jigexZoomOn = on;
  19.  
  20. var z = 1, tx = 0, ty = 0; // zoom et décalage de l'affichage (px)
  21. var ctrlOnly = false, gripped = false, hoverPan = true, mX = -1, mY = -1, mOver = false, autoPan = false, held = false, lastX = 0, lastY = 0, baseW = 0, canvas = null, badge = null, panning = null, dragging = false, parentOverflow = null;
  22.  
  23. // La transformation est portée par une feuille de style (et non par le style du canvas) : le site réécrit le style du canvas
  24. // à chaque prise/lâcher de pièce, ce qui effaçait le zoom.
  25. var styleEl = null;
  26. function ensureStyle() {
  27. if (styleEl && styleEl.isConnected) return;
  28. var host = document.head || document.documentElement; if (!host) return;
  29. styleEl = document.createElement('style');
  30. styleEl.textContent = 'html[data-jz] #jigex-canvas{transform-origin:0 0!important;transform:translate(var(--jzx),var(--jzy)) scale(var(--jzz))!important;}' +
  31. 'html[data-jz] :has(>#jigex-canvas){overflow:clip!important;}';
  32. host.appendChild(styleEl);
  33. }
  34.  
  35. function getCanvas() { return canvas && canvas.isConnected ? canvas : (canvas = document.getElementById('jigex-canvas')); }
  36.  
  37. function apply() {
  38. var c = getCanvas(); if (!c) return;
  39. var w = c.offsetWidth, h = c.offsetHeight;
  40. var mx = w * 0.4, my = h * 0.4; // marge libre autour du puzzle
  41. tx = Math.min(mx, Math.max(w * (1 - z) - mx, tx)); ty = Math.min(my, Math.max(h * (1 - z) - my, ty));
  42. var de = document.documentElement; ensureStyle();
  43. if (z > 1.0001) {
  44. de.style.setProperty('--jzx', tx + 'px'); de.style.setProperty('--jzy', ty + 'px'); de.style.setProperty('--jzz', z);
  45. de.setAttribute('data-jz', '1');
  46. } else {
  47. z = 1; tx = ty = 0; de.removeAttribute('data-jz');
  48. }
  49. refreshBadge();
  50. }
  51.  
  52. function reset() { z = 1; tx = ty = 0; apply(); }
  53.  
  54. function setOn(v) {
  55. on = v; window.__jigexZoomOn = on;
  56. try { localStorage.setItem(STORE, on ? '1' : '0'); } catch (e) {}
  57. if (!on) reset(); else refreshBadge();
  58. }
  59.  
  60. window.addEventListener('pointerdown', function (e) { if (e.button === 0 && e.target === getCanvas()) gripped = true; }, true);
  61. ['pointerup', 'pointercancel', 'blur'].forEach(function (t) { window.addEventListener(t, function () { gripped = false; }, true); });
  62. try { ctrlOnly = localStorage.getItem('jigex-zoom-ctrl') === '1'; } catch (e) {}
  63. // ---- Molette : zoom autour du pointeur ----
  64. window.addEventListener('wheel', function (e) {
  65. var c = getCanvas();
  66. if (!on || !c || e.target !== c || e.metaKey || e.altKey || e.shiftKey) return;
  67. if (ctrlOnly ? !e.ctrlKey : (e.ctrlKey || gripped)) return; // sinon la molette sert à tourner les pièces du site
  68. e.preventDefault(); e.stopImmediatePropagation();
  69. var r = c.getBoundingClientRect();
  70. var px = (e.clientX - r.left) / z, py = (e.clientY - r.top) / z; // point visé, en coordonnées du canvas non zoomé
  71. var L = r.left - tx, T = r.top - ty; // position de base du canvas dans la page
  72. var nz = Math.min(MAX, Math.max(MIN, z * Math.pow(1.0022, -e.deltaY)));
  73. tx = e.clientX - L - px * nz; ty = e.clientY - T - py * nz; z = nz;
  74. apply();
  75. }, { capture: true, passive: false });
  76.  
  77. // ---- Bouton de déplacement de la vue (touche P pour changer) ----
  78. var MODES = [
  79. { label: 'clic molette', btn: 1 }, { label: 'clic droit', btn: 2 }, { label: 'bouton précédent', btn: 3 },
  80. { label: 'bouton suivant', btn: 4 }, { label: 'clic gauche + Maj', btn: 0, mod: 'shift' }, { label: 'clic gauche + Espace', btn: 0, mod: 'space' }
  81. ];
  82. var modeI = 0, spaceDown = false;
  83. try { modeI = Math.max(0, Math.min(MODES.length - 1, parseInt(localStorage.getItem('jigex-zoom-panmode') || '0', 10) || 0)); } catch (e) {}
  84. var MASK = { 0: 1, 1: 4, 2: 2, 3: 8, 4: 16 };
  85. function isPanBtn(e) {
  86. var m = MODES[modeI];
  87. if (e.button !== m.btn) return false;
  88. if (m.mod === 'shift') return e.shiftKey;
  89. if (m.mod === 'space') return spaceDown;
  90. return true;
  91. }
  92. window.addEventListener('keydown', function (e) { if (e.code === 'Space') spaceDown = true; }, true);
  93. window.addEventListener('keyup', function (e) { if (e.code === 'Space') spaceDown = false; }, true);
  94. window.addEventListener('blur', function () { spaceDown = false; });
  95. window.addEventListener('keydown', function (e) {
  96. if ((e.key !== 'm' && e.key !== 'M') || e.ctrlKey || e.metaKey || e.altKey) return;
  97. var t = document.activeElement; if (t && /INPUT|TEXTAREA|SELECT/.test(t.tagName)) return;
  98. modeI = (modeI + 1) % MODES.length; panning = null;
  99. try { localStorage.setItem('jigex-zoom-panmode', String(modeI)); } catch (er) {}
  100. refreshBadge();
  101. }, true);
  102.  
  103. // ---- Clic milieu : déplacer la vue (le jeu ne le voit pas) ----
  104. function isMid(e) { return e.button === 1 || (e.buttons & 4) === 4; }
  105. ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'auxclick'].forEach(function (t) {
  106. window.addEventListener(t, function (e) {
  107. if (!on || z <= 1) return;
  108. var down = t === 'pointerdown' || t === 'mousedown';
  109. if ((down && isPanBtn(e)) || (!down && panning) || (e.type === 'auxclick' && e.button === MODES[modeI].btn)) {
  110. e.preventDefault(); e.stopImmediatePropagation();
  111. if (down) panning = { x: e.clientX, y: e.clientY, tx: tx, ty: ty, mask: MASK[e.button] };
  112. if (t === 'pointerup' || t === 'mouseup') panning = null;
  113. }
  114. }, true);
  115. });
  116. window.addEventListener('contextmenu', function (e) { if (on && z > 1 && (MODES[modeI].btn === 2 || panning)) { e.preventDefault(); e.stopImmediatePropagation(); } }, true);
  117. function panMove(e) {
  118. if (!panning) return;
  119. if (!(e.buttons & panning.mask)) { panning = null; return; }
  120. tx = panning.tx + (e.clientX - panning.x); ty = panning.ty + (e.clientY - panning.y); apply();
  121. e.stopImmediatePropagation();
  122. }
  123. window.addEventListener('pointermove', function (e) {
  124. if (panning) panMove(e);
  125.  
  126. }, true);
  127. window.addEventListener('mousemove', panMove, true);
  128.  
  129. // ---- Les clics du jeu : on corrige les coordonnées pour qu'elles visent le bon endroit du canvas zoomé ----
  130. function down0(t) { return t === 'pointerdown' || t === 'mousedown'; }
  131. var REMAP = ['pointerdown', 'pointermove', 'pointerup', 'pointercancel', 'mousedown', 'mousemove', 'mouseup', 'click', 'dblclick', 'contextmenu'];
  132. REMAP.forEach(function (t) {
  133. window.addEventListener(t, function (e) {
  134. var c = getCanvas();
  135. if (!c || z <= 1.0001 || panning || (down0(t) && isPanBtn(e))) return;
  136. var down = t === 'pointerdown' || t === 'mousedown', up = t === 'pointerup' || t === 'mouseup' || t === 'pointercancel';
  137. if (down && e.target === c) { dragging = true; held = true; }
  138. if (t === 'pointerup' && held && e.__ox === undefined && e.target) {
  139. // le jeu reçoit un dernier mouvement exactement à l'endroit du lâcher, juste avant le relâchement
  140. try {
  141. var o = { clientX: e.clientX, clientY: e.clientY, buttons: 1, pointerId: e.pointerId || 1, pointerType: e.pointerType || 'mouse', isPrimary: true, bubbles: true, cancelable: true, view: window };
  142. c.dispatchEvent(new PointerEvent('pointermove', o));
  143. c.dispatchEvent(new MouseEvent('mousemove', o));
  144. } catch (err) {}
  145. }
  146. if (up) held = false;
  147. var apply1 = e.target === c || dragging;
  148. if (up) setTimeout(function () { dragging = false; }, 150);
  149. if (!apply1 || e.__ox !== undefined) return;
  150. lastX = e.clientX; lastY = e.clientY;
  151. var r = c.getBoundingClientRect();
  152. var nx = r.left + (e.clientX - r.left) / z, ny = r.top + (e.clientY - r.top) / z;
  153. e.__ox = e.clientX; e.__oy = e.clientY; // position réelle à l'écran, pour les autres scripts
  154. try {
  155. Object.defineProperty(e, 'clientX', { value: nx, configurable: true });
  156. Object.defineProperty(e, 'clientY', { value: ny, configurable: true });
  157. Object.defineProperty(e, 'x', { value: nx, configurable: true });
  158. Object.defineProperty(e, 'y', { value: ny, configurable: true });
  159. Object.defineProperty(e, 'pageX', { value: nx + (window.pageXOffset || 0), configurable: true });
  160. Object.defineProperty(e, 'pageY', { value: ny + (window.pageYOffset || 0), configurable: true });
  161. } catch (err) {}
  162. }, true);
  163. });
  164.  
  165. window.addEventListener('keydown', function (e) {
  166. if ((e.key !== 'z' && e.key !== 'Z') || e.ctrlKey || e.metaKey || e.altKey) return;
  167. var t = document.activeElement;
  168. if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT')) return;
  169. setOn(!on);
  170. }, true);
  171. window.addEventListener('keydown', function (e) {
  172. if ((e.key !== 'a' && e.key !== 'A') || e.ctrlKey || e.metaKey || e.altKey) return;
  173. var t = document.activeElement;
  174. if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.tagName === 'SELECT')) return;
  175. autoPan = !autoPan; held = false; try { localStorage.setItem('jigex-zoom-autopan', autoPan ? '1' : '0'); } catch (er) {}
  176. refreshBadge();
  177. }, true);
  178. try { autoPan = localStorage.getItem('jigex-zoom-autopan') === '1'; } catch (e) {}
  179. ['pointerup','mouseup','pointercancel','dragend','lostpointercapture','blur','contextmenu'].forEach(function (t) {
  180. window.addEventListener(t, function () { held = false; }, true);
  181. });
  182. document.addEventListener('visibilitychange', function () { held = false; });
  183. window.addEventListener('pointermove', function (e) {
  184. mX = e.__ox !== undefined ? e.__ox : e.clientX; mY = e.__oy !== undefined ? e.__oy : e.clientY;
  185. mOver = e.target === getCanvas();
  186. }, true);
  187. window.addEventListener('mouseleave', function () { mOver = false; }, true);
  188. document.addEventListener('mouseleave', function () { mOver = false; });
  189. window.addEventListener('keydown', function (e) {
  190. if ((e.key !== 'e' && e.key !== 'E') || e.ctrlKey || e.metaKey || e.altKey) return;
  191. var t = document.activeElement; if (t && /INPUT|TEXTAREA|SELECT/.test(t.tagName)) return;
  192. hoverPan = !hoverPan; try { localStorage.setItem('jigex-zoom-hover', hoverPan ? '1' : '0'); } catch (er) {} refreshBadge();
  193. }, true);
  194. try { hoverPan = localStorage.getItem('jigex-zoom-hover') !== '0'; } catch (e) {}
  195.  
  196. // ---- Journal de diagnostic (touche L : copie le journal) ----
  197. var LOG = [], t0 = Date.now(), prevSig = '';
  198. function lg(m) { LOG.push(((Date.now() - t0) / 1000).toFixed(2) + ' ' + m); if (LOG.length > 400) LOG.shift(); }
  199. ['pointerdown', 'pointerup', 'pointercancel', 'mouseup', 'click', 'dblclick', 'resize', 'scroll', 'focusin', 'blur', 'keydown'].forEach(function (t) {
  200. window.addEventListener(t, function (e) { lg('évt ' + t + (e.target && e.target.id ? '#' + e.target.id : '') + (e.clientX !== undefined ? ' @' + Math.round(e.__ox !== undefined ? e.__ox : e.clientX) + ',' + Math.round(e.__oy !== undefined ? e.__oy : e.clientY) : '')); }, true);
  201. });
  202. function sample() {
  203. var c = getCanvas(); if (!c) return;
  204. var r = c.getBoundingClientRect(), p = c.parentElement;
  205. var sig = [Math.round(r.left), Math.round(r.top), Math.round(r.width), c.offsetWidth, c.width, c.style.transform, p ? p.scrollLeft + '/' + p.scrollTop : '', window.pageXOffset + '/' + window.pageYOffset, z.toFixed(2), Math.round(tx), Math.round(ty), c.style.cssText.length].join('|');
  206. if (sig !== prevSig) { lg('état rect=' + Math.round(r.left) + ',' + Math.round(r.top) + ' w=' + Math.round(r.width) + ' off=' + c.offsetWidth + ' px=' + c.width + ' z=' + z.toFixed(2) + ' tx,ty=' + Math.round(tx) + ',' + Math.round(ty) + ' scrollP=' + (p ? p.scrollLeft + '/' + p.scrollTop : '-') + ' scrollW=' + window.pageXOffset + '/' + window.pageYOffset + ' style=' + c.style.cssText); prevSig = sig; }
  207. }
  208. window.addEventListener('keydown', function (e) {
  209. if ((e.key !== 'l' && e.key !== 'L') || e.ctrlKey || e.metaKey || e.altKey) return;
  210. var t = document.activeElement; if (t && /INPUT|TEXTAREA|SELECT/.test(t.tagName)) return;
  211. e.preventDefault(); e.stopImmediatePropagation();
  212. var txt = LOG.join('\n');
  213. try { navigator.clipboard.writeText(txt); } catch (er) {}
  214. var d = document.createElement('textarea'); d.value = txt; d.style.cssText = 'position:fixed;left:10%;top:10%;width:80%;height:70%;z-index:2147483647;font:11px monospace;';
  215. d.addEventListener('keydown', function (ev) { ev.stopPropagation(); if (ev.key === 'Escape') d.remove(); });
  216. document.body.appendChild(d); d.focus(); d.select();
  217. }, true);
  218.  
  219. // Redimensionnement : on ne remet à ×1 que si la taille du canvas a vraiment changé
  220. window.addEventListener('resize', function () {
  221. var c = getCanvas(); if (!c) return;
  222. if (baseW && Math.abs(c.offsetWidth - baseW) > 2) reset();
  223. });
  224.  
  225. // Boucle : (1) remet la transformation si le site l'a effacée, (2) défilement auto du bord pendant le déplacement d'une pièce
  226. var EDGE = 70, SPEED = 22;
  227. (function loop() {
  228. var c = getCanvas();
  229. if (c) {
  230. baseW = c.offsetWidth; sample();
  231. if (on && z > 1.0001) { // le zoom agrandit la zone défilable : on empêche tout défilement parasite (focus, glisser...)
  232. for (var q = c.parentElement; q; q = q.parentElement) { if (q.scrollLeft) q.scrollLeft = 0; if (q.scrollTop) q.scrollTop = 0; }
  233. if (window.pageXOffset || window.pageYOffset) window.scrollTo(0, 0);
  234. }
  235. if (on && z > 1.0001) {
  236. if (hoverPan && !held && !panning && mOver) { // survol du bord de l'écran : la vue glisse dans cette direction
  237. var rh = c.getBoundingClientRect(), Lh = rh.left - tx, Th = rh.top - ty, wh = c.offsetWidth, hh = c.offsetHeight, EH = edgeSize, SH = edgeSpeed, hx = 0, hy = 0;
  238. if (mX < Lh + EH) hx = SH * Math.min(1, (Lh + EH - mX) / EH); else if (mX > Lh + wh - EH) hx = -SH * Math.min(1, (mX - (Lh + wh - EH)) / EH);
  239. if (mY < Th + EH) hy = SH * Math.min(1, (Th + EH - mY) / EH); else if (mY > Th + hh - EH) hy = -SH * Math.min(1, (mY - (Th + hh - EH)) / EH);
  240. if (hx || hy) { tx += hx; ty += hy; apply(); }
  241. }
  242. if (autoPan && held && !panning) {
  243. var r0 = c.getBoundingClientRect();
  244. var L = r0.left - tx, T = r0.top - ty, w = c.offsetWidth, h = c.offsetHeight;
  245. var sx = lastX, sy = lastY; // position réelle du curseur à l'écran
  246. var dx = 0, dy = 0;
  247. if (sx < L + EDGE) dx = SPEED * Math.min(1, (L + EDGE - sx) / EDGE);
  248. else if (sx > L + w - EDGE) dx = -SPEED * Math.min(1, (sx - (L + w - EDGE)) / EDGE);
  249. if (sy < T + EDGE) dy = SPEED * Math.min(1, (T + EDGE - sy) / EDGE);
  250. else if (sy > T + h - EDGE) dy = -SPEED * Math.min(1, (sy - (T + h - EDGE)) / EDGE);
  251. if (dx || dy) {
  252. var ox = tx, oy = ty;
  253. tx += dx; ty += dy; apply();
  254. if (tx !== ox || ty !== oy) {
  255. // le curseur n'a pas bougé mais la vue oui : on prévient le jeu pour que la pièce suive
  256. try {
  257. var ev = new PointerEvent('pointermove', { clientX: sx, clientY: sy, buttons: 1, button: -1, pointerId: 1, pointerType: 'mouse', isPrimary: true, bubbles: true, cancelable: true, view: window });
  258. c.dispatchEvent(ev);
  259. c.dispatchEvent(new MouseEvent('mousemove', { clientX: sx, clientY: sy, buttons: 1, bubbles: true, cancelable: true, view: window }));
  260. } catch (err) {}
  261. }
  262. }
  263. }
  264. }
  265. }
  266. requestAnimationFrame(loop);
  267. })();
  268.  
  269. // ---- Interface : bouton dans la barre du site + panneau de réglages ----
  270. var btn = null, panel = null, ctl = {};
  271. function sv(k, v) { try { localStorage.setItem(k, String(v)); } catch (e) {} }
  272. function ld(k, d) { try { var v = localStorage.getItem(k); return v === null ? d : v; } catch (e) { return d; } }
  273. edgeSize = parseInt(ld('jigex-zoom-esz', '36'), 10) || 36; edgeSpeed = parseInt(ld('jigex-zoom-esp', '14'), 10) || 14;
  274. MAX = parseInt(ld('jigex-zoom-max', '6'), 10) || 6;
  275.  
  276. function refreshBadge() {
  277. if (btn) {
  278. var tip = on ? 'Zoom molette ×' + z.toFixed(1) + ' — réglages' : 'Zoom molette désactivé — réglages';
  279. btn.setAttribute('data-tooltip', tip); btn.title = tip; btn.style.opacity = on ? '1' : '.5';
  280. var q = btn.querySelector('.jz-lvl'); if (q) q.textContent = on && z > 1 ? '×' + z.toFixed(1) : '';
  281. }
  282. if (ctl.on) {
  283. ctl.on.checked = on; ctl.ctrl.checked = ctrlOnly; ctl.mode.value = String(modeI); ctl.hover.checked = hoverPan; ctl.auto.checked = autoPan;
  284. ctl.esz.value = edgeSize; ctl.esp.value = edgeSpeed; ctl.max.value = MAX;
  285. ctl.eszv.textContent = edgeSize + ' px'; ctl.espv.textContent = edgeSpeed; ctl.maxv.textContent = '×' + MAX;
  286. ctl.lvl.textContent = 'Zoom actuel : ×' + z.toFixed(1);
  287. }
  288. }
  289. function mk(tag, css, txt) { var e = document.createElement(tag); if (css) e.style.cssText = css; if (txt) e.textContent = txt; return e; }
  290. function row(label) {
  291. var r = mk('label', 'display:flex;align-items:center;gap:8px;margin:7px 0;justify-content:space-between;cursor:pointer;');
  292. r.appendChild(mk('span', '', label)); return r;
  293. }
  294. function buildPanel() {
  295. panel = mk('div', 'position:fixed;z-index:2147483000;width:290px;padding:12px 14px;border-radius:10px;background:rgb(36,38,42);color:#eee;' +
  296. 'font:13px Arial,sans-serif;box-shadow:0 6px 24px rgba(0,0,0,.45);display:none;');
  297. panel.appendChild(mk('div', 'font-weight:bold;font-size:14px;margin-bottom:4px;', 'Zoom molette'));
  298. var r = row('Activé (Z)'); ctl.on = mk('input'); ctl.on.type = 'checkbox'; r.appendChild(ctl.on); panel.appendChild(r);
  299. ctl.on.addEventListener('change', function () { setOn(ctl.on.checked); refreshBadge(); });
  300. r = row('Déplacer la vue avec (M)'); ctl.mode = mk('select', 'max-width:150px;');
  301. MODES.forEach(function (m, i) { var o = mk('option', '', m.label); o.value = i; ctl.mode.appendChild(o); });
  302. r.appendChild(ctl.mode); panel.appendChild(r);
  303. ctl.mode.addEventListener('change', function () { modeI = parseInt(ctl.mode.value, 10); panning = null; sv('jigex-zoom-panmode', modeI); refreshBadge(); });
  304. r = row('Zoom seulement avec Ctrl + molette'); ctl.ctrl = mk('input'); ctl.ctrl.type = 'checkbox'; r.appendChild(ctl.ctrl); panel.appendChild(r);
  305. r.title = 'Utile si la rotation des pièces est activée : la molette seule tourne alors la pièce tenue';
  306. ctl.ctrl.addEventListener('change', function () { ctrlOnly = ctl.ctrl.checked; sv('jigex-zoom-ctrl', ctrlOnly ? '1' : '0'); refreshBadge(); });
  307. r = row('Défiler en touchant les bords (E)'); ctl.hover = mk('input'); ctl.hover.type = 'checkbox'; r.appendChild(ctl.hover); panel.appendChild(r);
  308. ctl.hover.addEventListener('change', function () { hoverPan = ctl.hover.checked; sv('jigex-zoom-hover', hoverPan ? '1' : '0'); refreshBadge(); });
  309. r = row('… aussi en portant une pièce (A)'); ctl.auto = mk('input'); ctl.auto.type = 'checkbox'; r.appendChild(ctl.auto); panel.appendChild(r);
  310. ctl.auto.addEventListener('change', function () { autoPan = ctl.auto.checked; held = false; sv('jigex-zoom-autopan', autoPan ? '1' : '0'); refreshBadge(); });
  311. function slider(label, min, max, key, set, valKey) {
  312. var rr = mk('div', 'margin:7px 0;'); var top = mk('div', 'display:flex;justify-content:space-between;'); top.appendChild(mk('span', '', label));
  313. ctl[valKey] = mk('span', 'opacity:.8;'); top.appendChild(ctl[valKey]); rr.appendChild(top);
  314. var inp = mk('input', 'width:100%;'); inp.type = 'range'; inp.min = min; inp.max = max; inp.step = 1;
  315. inp.addEventListener('input', function () { set(parseInt(inp.value, 10)); sv(key, inp.value); refreshBadge(); });
  316. rr.appendChild(inp); panel.appendChild(rr); return inp;
  317. }
  318. ctl.esz = slider('Largeur de la zone des bords', 10, 120, 'jigex-zoom-esz', function (v) { edgeSize = v; }, 'eszv');
  319. ctl.esp = slider('Vitesse du défilement', 3, 40, 'jigex-zoom-esp', function (v) { edgeSpeed = v; }, 'espv');
  320. ctl.max = slider('Zoom maximum', 2, 12, 'jigex-zoom-max', function (v) { MAX = v; if (z > MAX) { z = MAX; apply(); } }, 'maxv');
  321. var foot = mk('div', 'display:flex;justify-content:space-between;align-items:center;margin-top:10px;');
  322. ctl.lvl = mk('span', 'opacity:.8;'); foot.appendChild(ctl.lvl);
  323. var rb = mk('button', 'cursor:pointer;padding:4px 10px;border-radius:6px;border:1px solid #666;background:#3a3d44;color:#eee;', 'Revenir à ×1');
  324. rb.addEventListener('click', function () { reset(); }); foot.appendChild(rb); panel.appendChild(foot);
  325. panel.appendChild(mk('div', 'margin-top:8px;font-size:11px;opacity:.65;', 'Molette : zoomer (pas pendant qu\'un bouton est enfoncé) · L : journal'));
  326. ['pointerdown', 'mousedown', 'click', 'wheel', 'keydown', 'contextmenu'].forEach(function (t) { panel.addEventListener(t, function (e) { e.stopPropagation(); }); });
  327. document.body.appendChild(panel);
  328. }
  329. // Reprend l'apparence des panneaux du site (menu du programme) pour que nos panneaux aient le même style
  330. function applyTheme(el) {
  331. try {
  332. var src = document.getElementById('jigex-prog-menu-tb') || document.getElementById('jigex-prog-menu');
  333. if (!src) return;
  334. var cs = getComputedStyle(src), bg = cs.backgroundColor;
  335. if (!bg || /rgba\(.*,\s*0\)$/.test(bg) || bg === 'transparent') { var tb = document.getElementById('jigex-prog-menu-tb'); if (tb) bg = getComputedStyle(tb).backgroundColor; }
  336. if (bg && !/rgba\(.*,\s*0\)$/.test(bg) && bg !== 'transparent') {
  337. var m = bg.match(/[\d.]+/g); // fond toujours opaque, même si celui du site est translucide
  338. el.style.background = m && m.length >= 3 ? 'rgb(' + m[0] + ',' + m[1] + ',' + m[2] + ')' : bg;
  339. }
  340. if (cs.color) el.style.color = cs.color;
  341. if (cs.fontFamily) el.style.fontFamily = cs.fontFamily;
  342. if (cs.borderRadius && cs.borderRadius !== '0px') el.style.borderRadius = cs.borderRadius;
  343. if (cs.boxShadow && cs.boxShadow !== 'none') el.style.boxShadow = cs.boxShadow;
  344. } catch (e) {}
  345. }
  346. function placePanel() {
  347. var br = btn.getBoundingClientRect();
  348. panel.style.top = Math.round(br.bottom + 6) + 'px';
  349. panel.style.left = Math.max(8, Math.min(window.innerWidth - 300, Math.round(br.right - 290))) + 'px';
  350. }
  351. function togglePanel(force) {
  352. if (!panel) buildPanel();
  353. var show = force === undefined ? panel.style.display === 'none' : force;
  354. if (show) { applyTheme(panel); placePanel(); refreshBadge(); }
  355. panel.style.display = show ? 'block' : 'none';
  356. }
  357. function buildBtn() {
  358. var clock = document.getElementById('jigex-clock'), bar = clock && clock.parentElement;
  359. btn = document.createElement('div');
  360. btn.id = 'jigex-zoomcfg-btn'; btn.className = 'niftybar-icon-button main-tb-btn'; btn.style.cssText = 'display:block;position:relative;cursor:pointer;';
  361. btn.innerHTML = '<svg class="niftybar-icon" width="24" height="24" viewBox="0 0 24 24" style="color:rgb(232,230,227)"><g style="fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round">' +
  362. '<circle cx="10" cy="10" r="6"/><path d="M15 15l6 6M7.5 10h5M10 7.5v5"/></g></svg>' +
  363. '<span class="jz-lvl" style="position:absolute;right:1px;bottom:1px;font:bold 9px Arial,sans-serif;color:rgb(232,230,227);pointer-events:none;"></span>';
  364. btn.addEventListener('click', function (e) { e.stopPropagation(); togglePanel(); });
  365. if (bar) bar.insertBefore(btn, clock);
  366. else { btn.style.cssText = 'position:fixed;left:8px;bottom:96px;z-index:100000;width:36px;height:36px;color:#fff;background:rgba(0,0,0,.55);border-radius:18px;'; document.body.appendChild(btn); }
  367. document.addEventListener('mousedown', function (e) { if (panel && panel.style.display !== 'none' && !panel.contains(e.target) && !btn.contains(e.target)) togglePanel(false); }, true);
  368. window.addEventListener('resize', function () { if (panel && panel.style.display !== 'none') placePanel(); });
  369. refreshBadge();
  370. }
  371. function start() {
  372. var tries = 0;
  373. (function wait() {
  374. if (document.getElementById('jigex-clock') || ++tries > 60) { if (!document.getElementById('jigex-zoomcfg-btn')) buildBtn(); }
  375. else setTimeout(wait, 500);
  376. })();
  377. }
  378. if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start();
  379. // Le site peut reconstruire sa barre : on remet le bouton si besoin
  380. setInterval(function () { var clock = document.getElementById('jigex-clock'); if (btn && clock && !btn.isConnected && clock.parentElement) clock.parentElement.insertBefore(btn, clock); }, 2000);
  381. })();
Advertisement
Add Comment
Please, Sign In to add comment