diff --git a/README.md b/README.md index 0d5a7ee..d1cfc36 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,6 @@ A comprehensive, lightweight accessibility tool that enhances web accessibility ### Advanced Features - **Screen Reader Support** - Built-in text-to-speech functionality - **Voice Control** - Voice commands to control accessibility features -- **Reading Mode** - Simplified, distraction-free reading experience -- **Enhanced Focus Indicators** - Improved visual focus indicators for keyboard navigation - **Reduced Motion** - Respects user preferences for reduced motion - **Font Selection** - Choose between Arial, Times New Roman, and Verdana - **Color Blindness Filters** - Filters for Protanopia, Deuteranopia, Tritanopia, and Grayscale @@ -86,8 +84,6 @@ window.ACCESSIBILITY_WIDGET_CONFIG = { enableTextAlign: true, enableScreenReader: true, enableVoiceControl: true, - enableReadingMode: true, - enableEnhancedFocus: true, enableReducedMotion: true, enableFontSelection: true, enableColorFilter: true, @@ -95,8 +91,10 @@ window.ACCESSIBILITY_WIDGET_CONFIG = { // Widget dimensions and position widgetWidth: '440px', widgetPosition: { - right: '20px', - bottom: '20px' + side: 'right', // 'left' or 'right' - which side to position widget + right: '20px', // distance from right edge when side is 'right' + left: '20px', // distance from left edge when side is 'left' + bottom: '20px' // distance from bottom }, // Color scheme @@ -146,6 +144,36 @@ window.ACCESSIBILITY_WIDGET_CONFIG = { animation: { transition: '0.2s', hoverScale: '1.05' + }, + + // Language/Text Configuration - Customize all text strings + lang: { + accessibilityMenu: 'Accessibility Menu', + closeAccessibilityMenu: 'Close Accessibility Menu', + accessibilityTools: 'Accessibility Tools', + resetAllSettings: 'Reset All Settings', + screenReader: 'Screen Reader', + voiceCommand: 'Voice Command', + textSpacing: 'Text Spacing', + pauseAnimations: 'Pause Animations', + hideImages: 'Hide Images', + dyslexiaFriendly: 'Dyslexia Friendly', + biggerCursor: 'Bigger Cursor', + lineHeight: 'Line Height', + reducedMotion: 'Reduced Motion', + fontSelection: 'Font Selection', + colorFilter: 'Color Filter', + textAlign: 'Text Align', + textSize: 'Text Size', + highContrast: 'High Contrast', + defaultFont: 'Default Font', + noFilter: 'No Filter', + default: 'Default', + close: 'Close', + screenReaderOn: 'Screen reader on', + screenReaderOff: 'Screen reader off', + voiceControlActivated: 'Voice control activated', + notSupportedBrowser: 'is not supported in this browser' } }; @@ -169,6 +197,42 @@ window.ACCESSIBILITY_WIDGET_CONFIG = { ``` +### Widget Positioning +The widget can be positioned on either side of the screen: + +```html + +``` + +### Internationalization (i18n) +The widget supports full internationalization. You can customize all text strings: + +```html + +``` + ## Voice Commands When voice control is enabled, users can activate features using these commands: @@ -200,6 +264,10 @@ When voice control is enabled, users can activate features using these commands: - **Performance Optimized** - DOM caching and efficient event handling - **Error Handling** - Robust error handling for browser compatibility - **Responsive Design** - Works on desktop and mobile devices +- **Flexible Positioning** - Support for left/right side positioning +- **Full Internationalization** - Complete text customization for any language +- **Configurable Colors** - Dynamic color theming including SVG icon colors +- **Screen Reader Optimized** - Proper text labels for all interactive elements ## Usage Examples @@ -269,3 +337,14 @@ This project is open source and available under the GPL License. ## Support For support or feature requests, please check the browser console for any error messages and ensure your browser supports the required APIs for advanced features like speech synthesis and recognition. + +## Recent Changes + +### Latest Updates +- **Widget Positioning**: Added support for left/right side positioning with configurable distances +- **Internationalization**: Full i18n support with customizable text strings for all languages +- **Color Theming**: SVG icons now use configurable primary color instead of hardcoded values +- **Screen Reader Improvements**: Fixed close button text to read properly ("Close" instead of "times") +- **Removed Features**: Removed Reading Mode and Enhanced Focus features for better performance +- **UI Improvements**: Updated header background to black, removed gradient from accessibility button +- **Configuration**: Enhanced configuration system with deep merging for partial overrides diff --git a/widget.js b/widget.js index 2df5df0..bb3e442 100644 --- a/widget.js +++ b/widget.js @@ -25,8 +25,6 @@ const DEFAULT_WIDGET_CONFIG = { // Advanced Features enableScreenReader: true, enableVoiceControl: true, - enableReadingMode: true, - enableEnhancedFocus: true, enableReducedMotion: true, enableFontSelection: true, enableColorFilter: true, @@ -66,7 +64,7 @@ const DEFAULT_WIDGET_CONFIG = { menu: { headerHeight: '55px', padding: '0 10px 10px 10px', - optionPadding: '14px 10px', + optionPadding: '20px 10px', optionMargin: '10px', borderRadius: '8px', fontSize: '16px', @@ -87,6 +85,36 @@ const DEFAULT_WIDGET_CONFIG = { animation: { transition: '0.2s', hoverScale: '1.05' + }, + + // Language/Text Configuration + lang: { + accessibilityMenu: 'Accessibility Menu', + closeAccessibilityMenu: 'Close Accessibility Menu', + accessibilityTools: 'Accessibility Tools', + resetAllSettings: 'Reset All Settings', + screenReader: 'Screen Reader', + voiceCommand: 'Voice Command', + textSpacing: 'Text Spacing', + pauseAnimations: 'Pause Animations', + hideImages: 'Hide Images', + dyslexiaFriendly: 'Dyslexia Friendly', + biggerCursor: 'Bigger Cursor', + lineHeight: 'Line Height', + reducedMotion: 'Reduced Motion', + fontSelection: 'Font Selection', + colorFilter: 'Color Filter', + textAlign: 'Text Align', + textSize: 'Text Size', + highContrast: 'High Contrast', + defaultFont: 'Default Font', + noFilter: 'No Filter', + default: 'Default', + screenReaderOn: 'Screen reader on', + screenReaderOff: 'Screen reader off', + voiceControlActivated: 'Voice control activated', + notSupportedBrowser: 'is not supported in this browser', + close: 'Close' } }; @@ -239,7 +267,7 @@ const styles = ` margin-bottom: 10px; padding: ${WIDGET_CONFIG.menu.optionPadding}; width: 100%; - background-color: #ff4444; + background-color: #343434; color: ${WIDGET_CONFIG.colors.textLight}; border: none; cursor: pointer; @@ -350,30 +378,6 @@ const styles = ` cursor: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDgiIGhlaWdodD0iNzIiIHZpZXdCb3g9IjAgMCA0OCA3MiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNNCAyVjcwTDIwIDU0SDM2TDQgMloiIGZpbGw9IiMwMDAiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSI0Ii8+PC9zdmc+'), auto !important; } - /* Reading Mode */ - .snn-reading-mode { - background: #f9f9f9 !important; - color: #333 !important; - line-height: 1.6 !important; - font-family: Georgia, serif !important; - } - .snn-reading-mode * { - background: transparent !important; - color: inherit !important; - font-family: inherit !important; - line-height: inherit !important; - } - .snn-reading-mode h1, .snn-reading-mode h2, .snn-reading-mode h3, - .snn-reading-mode h4, .snn-reading-mode h5, .snn-reading-mode h6 { - color: #222 !important; - font-weight: bold !important; - } - .snn-reading-mode img, .snn-reading-mode video, .snn-reading-mode iframe, - .snn-reading-mode aside, .snn-reading-mode nav, .snn-reading-mode footer, - .snn-reading-mode header, .snn-reading-mode .sidebar, .snn-reading-mode .menu { - display: none !important; - } - /* Font Selection */ .snn-font-arial { font-family: Arial, sans-serif !important; @@ -408,13 +412,6 @@ const styles = ` filter: grayscale(100%) !important; } - /* Enhanced Focus */ - .snn-enhanced-focus *:focus { - outline: 3px solid ${WIDGET_CONFIG.colors.focus} !important; - outline-offset: 2px !important; - box-shadow: 0 0 0 5px ${WIDGET_CONFIG.colors.focusGlow} !important; - } - /* Reduced Motion */ .snn-reduced-motion * { animation: none !important; @@ -433,7 +430,7 @@ const styles = ` // SVG icons const icons = { - buttonsvg: ``, + buttonsvg: ``, highContrast: ``, biggerText: ``, textSpacing: ``, @@ -446,10 +443,8 @@ const icons = { screenReader: ``, resetAll: ``, voiceControl: ``, - readingMode: ``, fontSelection: `Aa`, colorFilter: ``, - enhancedFocus: ``, reducedMotion: ``, }; @@ -515,8 +510,6 @@ function applySettings() { { key: 'textAlign', className: 'snn-text-align' }, { key: 'pauseAnimations', className: 'snn-pause-animations' }, { key: 'textSpacing', className: 'snn-text-spacing' }, - { key: 'readingMode', className: 'snn-reading-mode' }, - { key: 'enhancedFocus', className: 'snn-enhanced-focus' }, { key: 'reducedMotion', className: 'snn-reduced-motion' }, ]; @@ -627,7 +620,7 @@ function createAccessibilityButton() { const button = document.createElement('button'); button.id = 'snn-accessibility-button'; button.innerHTML = icons.buttonsvg; - button.setAttribute('aria-label', 'Accessibility Menu'); + button.setAttribute('aria-label', WIDGET_CONFIG.lang.accessibilityMenu); button.addEventListener('click', function () { toggleMenu(); @@ -658,8 +651,6 @@ function resetAccessibilitySettings() { 'textSpacing', 'highContrast', 'voiceControl', - 'readingMode', - 'enhancedFocus', 'reducedMotion', 'fontSelection', 'colorFilter', @@ -675,8 +666,6 @@ function resetAccessibilitySettings() { 'snn-text-spacing', 'snn-line-height', 'snn-text-align', - 'snn-reading-mode', - 'snn-enhanced-focus', 'snn-reduced-motion', 'snn-font-arial', 'snn-font-times', @@ -731,7 +720,7 @@ function createToggleButton( // Check if feature is supported if (requiresFeature && !requiresFeature.isSupported) { button.disabled = true; - button.setAttribute('title', `${buttonText} is not supported in this browser`); + button.setAttribute('title', `${buttonText} ${WIDGET_CONFIG.lang.notSupportedBrowser}`); button.style.opacity = '0.5'; return button; } @@ -788,7 +777,7 @@ function createToggleButton( // Create special action buttons (for cycling through options) function createActionButton(buttonText, actionFunction, iconSVG) { const button = document.createElement('button'); - button.innerHTML = `${iconSVG}${buttonText}: Default`; + button.innerHTML = `${iconSVG}${buttonText}: ${WIDGET_CONFIG.lang.default}`; button.setAttribute('aria-label', buttonText); button.classList.add('snn-accessibility-option'); @@ -823,19 +812,19 @@ function updateActionButtonStatus(button, buttonText, actionFunction) { if (buttonText.includes('Font')) { const currentFont = localStorage.getItem('fontSelection'); - statusSpan.textContent = currentFont ? currentFont.charAt(0).toUpperCase() + currentFont.slice(1) : 'Default'; + statusSpan.textContent = currentFont ? currentFont.charAt(0).toUpperCase() + currentFont.slice(1) : WIDGET_CONFIG.lang.default; } else if (buttonText.includes('Color')) { const currentFilter = localStorage.getItem('colorFilter'); - statusSpan.textContent = currentFilter ? currentFilter.charAt(0).toUpperCase() + currentFilter.slice(1) : 'None'; + statusSpan.textContent = currentFilter ? currentFilter.charAt(0).toUpperCase() + currentFilter.slice(1) : WIDGET_CONFIG.lang.noFilter; } else if (buttonText.includes('Text Align')) { const currentAlign = localStorage.getItem('textAlign'); - statusSpan.textContent = currentAlign ? currentAlign.charAt(0).toUpperCase() + currentAlign.slice(1) : 'Default'; + statusSpan.textContent = currentAlign ? currentAlign.charAt(0).toUpperCase() + currentAlign.slice(1) : WIDGET_CONFIG.lang.default; } else if (buttonText.includes('Text Size')) { const currentSize = localStorage.getItem('biggerText'); - statusSpan.textContent = currentSize ? (currentSize === 'xlarge' ? 'X-Large' : currentSize.charAt(0).toUpperCase() + currentSize.slice(1)) : 'Default'; + statusSpan.textContent = currentSize ? (currentSize === 'xlarge' ? 'X-Large' : currentSize.charAt(0).toUpperCase() + currentSize.slice(1)) : WIDGET_CONFIG.lang.default; } else if (buttonText.includes('High Contrast')) { const currentContrast = localStorage.getItem('highContrast'); - statusSpan.textContent = currentContrast ? currentContrast.charAt(0).toUpperCase() + currentContrast.slice(1) : 'Default'; + statusSpan.textContent = currentContrast ? currentContrast.charAt(0).toUpperCase() + currentContrast.slice(1) : WIDGET_CONFIG.lang.default; } } @@ -865,7 +854,7 @@ function handleFontSelection() { if (nextIndex === fonts.length) { // Default font localStorage.removeItem('fontSelection'); - return 'Default Font'; + return WIDGET_CONFIG.lang.defaultFont; } else { const selectedFont = fonts[nextIndex]; localStorage.setItem('fontSelection', selectedFont); @@ -888,7 +877,7 @@ function handleColorFilter() { if (nextIndex === filters.length) { // No filter localStorage.removeItem('colorFilter'); - return 'No Filter'; + return WIDGET_CONFIG.lang.noFilter; } else { const selectedFilter = filters[nextIndex]; localStorage.setItem('colorFilter', selectedFilter); @@ -911,7 +900,7 @@ function handleTextAlign() { if (nextIndex === alignments.length) { // Default alignment localStorage.removeItem('textAlign'); - return 'Default'; + return WIDGET_CONFIG.lang.default; } else { const selectedAlign = alignments[nextIndex]; localStorage.setItem('textAlign', selectedAlign); @@ -934,7 +923,7 @@ function handleBiggerText() { if (nextIndex === textSizes.length) { // Default text size localStorage.removeItem('biggerText'); - return 'Default'; + return WIDGET_CONFIG.lang.default; } else { const selectedSize = textSizes[nextIndex]; localStorage.setItem('biggerText', selectedSize); @@ -957,7 +946,7 @@ function handleHighContrast() { if (nextIndex === contrastLevels.length) { // Default contrast localStorage.removeItem('highContrast'); - return 'Default'; + return WIDGET_CONFIG.lang.default; } else { const selectedContrast = contrastLevels[nextIndex]; localStorage.setItem('highContrast', selectedContrast); @@ -994,7 +983,7 @@ const screenReader = { }, toggle: function (isActive) { if (!screenReader.isSupported) { - console.warn('Speech synthesis is not supported in this browser.'); + console.warn(`Speech synthesis ${WIDGET_CONFIG.lang.notSupportedBrowser}`); return false; } @@ -1004,7 +993,7 @@ const screenReader = { try { if (isActive) { document.addEventListener('focusin', screenReader.handleFocus); - const feedbackSpeech = new SpeechSynthesisUtterance('Screen reader on'); + const feedbackSpeech = new SpeechSynthesisUtterance(WIDGET_CONFIG.lang.screenReaderOn); feedbackSpeech.lang = 'en-US'; feedbackSpeech.onerror = function(event) { console.warn('Speech synthesis feedback error:', event.error); @@ -1013,7 +1002,7 @@ const screenReader = { } else { document.removeEventListener('focusin', screenReader.handleFocus); window.speechSynthesis.cancel(); - const feedbackSpeech = new SpeechSynthesisUtterance('Screen reader off'); + const feedbackSpeech = new SpeechSynthesisUtterance(WIDGET_CONFIG.lang.screenReaderOff); feedbackSpeech.lang = 'en-US'; feedbackSpeech.onerror = function(event) { console.warn('Speech synthesis feedback error:', event.error); @@ -1038,7 +1027,7 @@ const voiceControl = { maxRetries: 3, toggle: function (isActive) { if (!voiceControl.isSupported) { - console.warn('Speech Recognition API is not supported in this browser.'); + console.warn(`Speech Recognition API ${WIDGET_CONFIG.lang.notSupportedBrowser}`); return false; } @@ -1075,7 +1064,7 @@ const voiceControl = { voiceControl.recognition.continuous = false; voiceControl.recognition.onstart = function () { - console.log('Voice control activated'); + console.log(WIDGET_CONFIG.lang.voiceControlActivated); voiceControl.retryCount = 0; }; @@ -1193,12 +1182,12 @@ function createAccessibilityMenu() { const title = document.createElement('h2'); title.classList.add('snn-title'); title.id = 'snn-accessibility-title'; - title.textContent = 'Accessibility Tools'; + title.textContent = WIDGET_CONFIG.lang.accessibilityTools; const closeButton = document.createElement('button'); closeButton.className = 'snn-close'; - closeButton.innerHTML = '×'; - closeButton.setAttribute('aria-label', 'Close Accessibility Menu'); + closeButton.innerHTML = WIDGET_CONFIG.lang.close; + closeButton.setAttribute('aria-label', WIDGET_CONFIG.lang.closeAccessibilityMenu); closeButton.addEventListener('click', function () { closeMenu(); @@ -1221,8 +1210,8 @@ function createAccessibilityMenu() { // Create reset button (outside grid, full width) const resetButton = document.createElement('button'); - resetButton.innerHTML = `${icons.resetAll}Reset All Settings`; - resetButton.setAttribute('aria-label', 'Reset All Accessibility Settings'); + resetButton.innerHTML = `${icons.resetAll}${WIDGET_CONFIG.lang.resetAllSettings}`; + resetButton.setAttribute('aria-label', WIDGET_CONFIG.lang.resetAllSettings); resetButton.classList.add('snn-reset-button'); resetButton.addEventListener('click', resetAccessibilitySettings); content.appendChild(resetButton); @@ -1234,7 +1223,7 @@ function createAccessibilityMenu() { // Add accessibility options based on configuration const options = [ { - text: 'Screen Reader', + text: WIDGET_CONFIG.lang.screenReader, key: 'screenReader', customToggleFunction: screenReader.toggle, icon: icons.screenReader, @@ -1242,7 +1231,7 @@ function createAccessibilityMenu() { enabled: WIDGET_CONFIG.enableScreenReader, }, { - text: 'Voice Command', + text: WIDGET_CONFIG.lang.voiceCommand, key: 'voiceControl', customToggleFunction: voiceControl.toggle, icon: icons.voiceControl, @@ -1250,63 +1239,49 @@ function createAccessibilityMenu() { enabled: WIDGET_CONFIG.enableVoiceControl, }, { - text: 'Text Spacing', + text: WIDGET_CONFIG.lang.textSpacing, key: 'textSpacing', className: 'snn-text-spacing', icon: icons.textSpacing, enabled: WIDGET_CONFIG.enableTextSpacing, }, { - text: 'Pause Animations', + text: WIDGET_CONFIG.lang.pauseAnimations, key: 'pauseAnimations', className: 'snn-pause-animations', icon: icons.pauseAnimations, enabled: WIDGET_CONFIG.enablePauseAnimations, }, { - text: 'Hide Images', + text: WIDGET_CONFIG.lang.hideImages, key: 'hideImages', icon: icons.hideImages, customToggleFunction: toggleHideImages, enabled: WIDGET_CONFIG.enableHideImages, }, { - text: 'Dyslexia Friendly', + text: WIDGET_CONFIG.lang.dyslexiaFriendly, key: 'dyslexiaFont', className: 'snn-dyslexia-font', icon: icons.dyslexiaFont, enabled: WIDGET_CONFIG.enableDyslexiaFont, }, { - text: 'Bigger Cursor', + text: WIDGET_CONFIG.lang.biggerCursor, key: 'biggerCursor', className: 'snn-bigger-cursor', icon: icons.biggerCursor, enabled: WIDGET_CONFIG.enableBiggerCursor, }, { - text: 'Line Height', + text: WIDGET_CONFIG.lang.lineHeight, key: 'lineHeight', className: 'snn-line-height', icon: icons.lineHeight, enabled: WIDGET_CONFIG.enableLineHeight, }, { - text: 'Reading Mode', - key: 'readingMode', - className: 'snn-reading-mode', - icon: icons.readingMode, - enabled: WIDGET_CONFIG.enableReadingMode, - }, - { - text: 'Enhanced Focus', - key: 'enhancedFocus', - className: 'snn-enhanced-focus', - icon: icons.enhancedFocus, - enabled: WIDGET_CONFIG.enableEnhancedFocus, - }, - { - text: 'Reduced Motion', + text: WIDGET_CONFIG.lang.reducedMotion, key: 'reducedMotion', className: 'snn-reduced-motion', icon: icons.reducedMotion, @@ -1332,27 +1307,27 @@ function createAccessibilityMenu() { // Add action buttons (font selection and color filters) to grid if enabled if (WIDGET_CONFIG.enableFontSelection) { - const fontButton = createActionButton('Font Selection', handleFontSelection, icons.fontSelection); + const fontButton = createActionButton(WIDGET_CONFIG.lang.fontSelection, handleFontSelection, icons.fontSelection); optionsGrid.appendChild(fontButton); } if (WIDGET_CONFIG.enableColorFilter) { - const colorButton = createActionButton('Color Filter', handleColorFilter, icons.colorFilter); + const colorButton = createActionButton(WIDGET_CONFIG.lang.colorFilter, handleColorFilter, icons.colorFilter); optionsGrid.appendChild(colorButton); } if (WIDGET_CONFIG.enableTextAlign) { - const textAlignButton = createActionButton('Text Align', handleTextAlign, icons.textAlign); + const textAlignButton = createActionButton(WIDGET_CONFIG.lang.textAlign, handleTextAlign, icons.textAlign); optionsGrid.appendChild(textAlignButton); } if (WIDGET_CONFIG.enableBiggerText) { - const biggerTextButton = createActionButton('Text Size', handleBiggerText, icons.biggerText); + const biggerTextButton = createActionButton(WIDGET_CONFIG.lang.textSize, handleBiggerText, icons.biggerText); optionsGrid.appendChild(biggerTextButton); } if (WIDGET_CONFIG.enableHighContrast) { - const highContrastButton = createActionButton('High Contrast', handleHighContrast, icons.highContrast); + const highContrastButton = createActionButton(WIDGET_CONFIG.lang.highContrast, handleHighContrast, icons.highContrast); optionsGrid.appendChild(highContrastButton); }