// ==UserScript==
// @name AccesSight Pro
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Suite complète d'outils d'accessibilité pour les malvoyants
// @author YGL, Benjamin Moine
// @include *
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @license GPL-3.0
// ==/UserScript==
(function() {
'use strict';
// Configuration initiale
const defaultConfig = {
textSize: 16,
contrastMode: 'normal',
speechRate: 1,
highlightLinks: true,
darkMode: false,
savedPosition: { x: 20, y: 20 }
};
// État courant
let config = {
...defaultConfig,
...GM_getValue('accessightConfig', {})
};
// Création de l'interface utilisateur
function createAccessibilityPanel() {
const panel = document.createElement('div');
panel.id = 'accessight-panel';
panel.style.cssText = `
position: fixed;
top: ${config.savedPosition.y}px;
left: ${config.savedPosition.x}px;
background: ${config.darkMode ? '#333' : '#FFF'};
color: ${config.darkMode ? '#FFF' : '#000'};
padding: 1rem;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
z-index: 10000;
font-family: Arial, sans-serif;
min-width: 250px;
cursor: move;
`;
// Header avec titre et bouton de fermeture
const header = document.createElement('div');
header.innerHTML = `
<h2 style="margin: 0 0 1rem 0; font-size: 1.2rem;">AccesSight</h2>
<button aria-label="Fermer" style="position: absolute; top: 5px; right: 5px; background: none; border: none; cursor: pointer;">×</button>
`;
panel.appendChild(header);
// Contrôles principaux
const controls = [
{ type: 'range', label: 'Taille du texte', min: 12, max: 24, value: config.textSize, key: 'textSize' },
{ type: 'select', label: 'Contraste', options: ['Normal', 'Élevé', 'Inversé'], value: config.contrastMode, key: 'contrastMode' },
{ type: 'button', label: 'Lecture vocale', action: 'toggleSpeech' },
{ type: 'toggle', label: 'Mode sombre', value: config.darkMode, key: 'darkMode' },
{ type: 'toggle', label: 'Surbrillance liens', value: config.highlightLinks, key: 'highlightLinks' }
];
controls.forEach(control => {
const controlGroup = document.createElement('div');
controlGroup.style.marginBottom = '0.5rem';
switch (control.type) {
case 'range':
controlGroup.innerHTML = `
<label style="display: block; margin-bottom: 0.3rem;">
${control.label}: <output>${control.value}px</output>
</label>
<input type="range"
min="${control.min}"
max="${control.max}"
value="${control.value}"
style="width: 100%;"
data-key="${control.key}">
`;
break;
case 'select':
controlGroup.innerHTML = `
<label style="display: block; margin-bottom: 0.3rem;">${control.label}</label>
<select style="width: 100%;" data-key="${control.key}">
${control.options.map(opt =>
`<option value="${opt.toLowerCase()}" ${opt.toLowerCase() === control.value ? 'selected' : ''}>${opt}</option>`
).join('')}
</select>
`;
break;
case 'toggle':
controlGroup.innerHTML = `
<label style="display: flex; align-items: center; gap: 0.5rem;">
<input type="checkbox" ${control.value ? 'checked' : ''} data-key="${control.key}">
${control.label}
</label>
`;
break;
case 'button':
controlGroup.innerHTML = `
<button style="width: 100%; padding: 0.5rem;" data-action="${control.action}">
${control.label}
</button>
`;
break;
}
panel.appendChild(controlGroup);
});
// Gestion des événements
panel.querySelectorAll('input, select').forEach(element => {
element.addEventListener('change', handleSettingChange);
});
panel.querySelector('[data-action="toggleSpeech"]').addEventListener('click', toggleTextToSpeech);
// Déplacement du panel
let isDragging = false;
let initialX, initialY, currentX, currentY;
panel.addEventListener('mousedown', startDragging);
document.addEventListener('mousemove', dragPanel);
document.addEventListener('mouseup', stopDragging);
function startDragging(e) {
isDragging = true;
initialX = e.clientX - panel.offsetLeft;
initialY = e.clientY - panel.offsetTop;
}
function dragPanel(e) {
if (isDragging) {
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
panel.style.left = `${currentX}px`;
panel.style.top = `${currentY}px`;
}
}
function stopDragging() {
isDragging = false;
GM_setValue('accessightConfig', {
...config,
savedPosition: { x: currentX, y: currentY }
});
}
document.body.appendChild(panel);
applyAccessibilitySettings();
}
// Appliquer les paramètres d'accessibilité
function applyAccessibilitySettings() {
// Taille du texte
document.documentElement.style.fontSize = `${config.textSize}px`;
// Contraste
switch (config.contrastMode) {
case 'élevé':
GM_addStyle(`
body {
filter: contrast(150%);
}
`);
break;
case 'inversé':
GM_addStyle(`
body {
filter: invert(1) hue-rotate(180deg);
}
`);
break;
}
// Mode sombre
if (config.darkMode) {
GM_addStyle(`
body {
background: #111 !important;
color: #FFF !important;
}
`);
}
// Surbrillance des liens
if (config.highlightLinks) {
GM_addStyle(`
a {
outline: 2px solid #FF0000 !important;
padding: 2px !important;
}
`);
}
}
// Gestion des changements de paramètres
function handleSettingChange(e) {
const key = e.target.dataset.key;
let value = e.target.type === 'checkbox' ? e.target.checked : e.target.value;
if (key === 'textSize') value = parseInt(value);
config[key] = value;
GM_setValue('accessightConfig', config);
applyAccessibilitySettings();
if (key === 'textSize') {
e.target.parentNode.querySelector('output').textContent = `${value}px`;
}
}
// Synthèse vocale
let speech = null;
function toggleTextToSpeech() {
if (!speech) {
speech = new window.SpeechSynthesisUtterance();
speech.text = document.body.textContent;
speech.rate = config.speechRate;
window.speechSynthesis.speak(speech);
} else {
window.speechSynthesis.cancel();
speech = null;
}
}
// Initialisation
function init() {
if (!document.getElementById('accessight-panel')) {
createAccessibilityPanel();
}
}
// Démarrage
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();