PageSourceSearch

https://azlawhelp.org/plugins/system/screenreader/screenreader/libraries/controller/controller.js?e92c5d

js azlawhelp.org collected 2026-09-24 19:46:19 UTC 91,058 bytes, 2,260 lines download raw bytes

1/**
2 * Screen Reader main controller class
3 * 
4 * @package SCREENREADER::plugins::system::screenreader
5 * @subpackage libraries
6 * @subpackage controller
7 * @author Joomla! Extensions Store
8 * @copyright (C) 2015 - Joomla! Extensions Store
9 * @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html  
10 */
11//'use strict';
12(function($) {
13	var MainController = function(options) {
14		/**
15		 * Plugin options
16		 * 
17		 * @access private
18		 * @var Object
19		 */
20		var screenReaderOptions = {};
21		
22		/**
23		 * Storage used by the plugin, session or local
24		 * 
25		 * @access private
26		 * @var Object
27		 */
28		var screenReaderStorage = {};
29		
30		/**
31		 * TTS instance
32		 * 
33		 * @access private
34		 * @var Object
35		 */
36		var frTTSEngineInstance = {};
37		
38		/**
39		 * Font size default as starting point
40		 * 
41		 * @access private
42		 * @var Int
43		 */
44		var fontSizeDefault = 80;
45		
46		/**
47		 * Font size currently set
48		 * 
49		 * @access private
50		 * @var Int
51		 */
52		var fontSizeCurrent = 80;
53		
54		/**
55		 * Spacing size currently set
56		 * 
57		 * @access private
58		 * @var Int
59		 */
60		var spacingSizeCurrent = 0;
61		
62		/**
63		 * Page zoom currently set
64		 * 
65		 * @access private
66		 * @var Int
67		 */
68		var zoomSizeCurrent = 100;
69		
70		/**
71		 * Line height currently set
72		 * 
73		 * @access private
74		 * @var Int
75		 */
76		var lineHeightCurrent = 0;
77		
78		/**
79		 * Set a variable for the current state of high contrast
80		 * 
81		 * @access private
82		 * @var Int
83		 */
84		var highContrast = 0;
85		
86		/**
87		 * Set a variable for the current state of alternate contrast
88		 * 
89		 * @access private
90		 * @var Int
91		 */
92		var highContrastAlternate = 0;
93		
94		/**
95		 * Set the target element for the filter transform
96		 * 
97		 * @access private
98		 * @var Int
99		 */
100		var targetFilterElement = $('body');
101		
102		/**
103		 * Set a variable for the current state of dyslexic font
104		 * 
105		 * @access private
106		 * @var Int
107		 */
108		var dyslexicFont = 0;
109		
110		/**
111		 * Set a variable for the current state of gray hues
112		 * 
113		 * @access private
114		 * @var Int
115		 */
116		var grayHues = 0;
117		
118		/**
119		 * Set a variable for the current state of big cursor
120		 * 
121		 * @access private
122		 * @var Int
123		 */
124		var bigCursor = 0;
125		
126		/**
127		 * Set a variable for the current state of read guides
128		 * 
129		 * @access private
130		 * @var Int
131		 */
132		var readingGuides = 0;
133		
134		/**
135		 * Set a variable to store the original HTML code to restore
136		 * 
137		 * @access private
138		 * @var String
139		 */
140		var originalHtml = '';
141		
142		/**
143		 * Set a variable for the current state of read mode
144		 * 
145		 * @access private
146		 * @var String
147		 */
148		var readabilityMode = 0;
149		
150		/**
151		 * Set a variable for the current state of hide images
152		 * 
153		 * @access private
154		 * @var String
155		 */
156		var hideImages = 0;
157		
158		/**
159		 * Set a variable for the current state of hide images
160		 * 
161		 * @access private
162		 * @var String
163		 */
164		var hideAlsoVideosIFrames = 0;
165		
166		/**
167		 * Set a variable for the current state of custom background color
168		 * 
169		 * @access private
170		 * @var String
171		 */
172		var customBackgroundColor;
173		
174		/**
175		 * Set a variable for the current state of custom text color
176		 * 
177		 * @access private
178		 * @var String
179		 */
180		var customTextColor;
181		
182		/**
183		 * Set a variable to retrieve the base body font family
184		 * 
185		 * @access private
186		 * @var Int
187		 */
188		var originalBodyFontFamily = $('body').css('font-family');
189		
190		/**
191		 * Keep track of the original HTML element styles if any
192		 * 
193		 * @accee private
194		 * @var String
195		 */
196		var originalHTMLStyles = null;
197		
198		// Apply stylesheets programmaticallyfunction rtrim(str, ch)
199		var rtrim = function(str, ch) {
200		    for (i = str.length - 1; i >= 0; i--) {
201		        if (ch != str.charAt(i)) {
202		            str = str.substring(0, i + 1);
203		            break;
204		        }
205		    } 
206		    return str;
207		};
208		
209		/**
210		 * Manage the appliance of the font resizing for headers element with an increment to preserve proportions
211		 * 
212		 * @access private
213		 * @return Void
214		 */
215		var fontResizeHeaders = function() {
216			// Filter header elements h1, h2, h3, h4
217    		$(screenReaderOptions.fontsizeSelector).filter(function(index){
218    			var elementNodeName = this.nodeName.toLowerCase();
219    			var enlargedElement = false;
220    			var enlargementValue = 0;
221    			var headersReduction = 1;
222    			switch (elementNodeName) {
223    				case 'h1':
224    					enlargementValue = 100;
225    					enlargedElement = true;
226    					break;
227    				
228    				case 'h2':
229    					enlargementValue = 80;
230    					headersReduction = 0.8;
231    					enlargedElement = true;
232	    				break;
233	    				
234    				case 'h3':
235    					enlargementValue = 40;
236    					headersReduction = 0.6;
237    					enlargedElement = true;
238	    				break;
239	    				
240    				case 'h4':
241    					enlargementValue = 20;
242    					headersReduction = 0.4;
243    					enlargedElement = true;
244	    				break;
245    			}
246    			// Found a header element to enlarge?
247    			if(enlargedElement) {
248	    			// Append to the current inline style of the element
249	    			var currentInlineStyles = $(this).attr('style') || '';
250	    			$(this).attr('style', currentInlineStyles + ';font-size:' + (parseInt(fontSizeCurrent) + (screenReaderOptions.fontSizeHeadersIncrement * headersReduction) + enlargementValue) + '%');
251    			}
252    		}); 
253		};
254		
255		/**
256		 * Manage the appliance of the alternate high contrast mode
257		 * 
258		 * @access private
259		 * @param Object jqEvent
260		 * @return Void
261		 */
262		var applyAlternateHighContrast = function(jqEvent){
263			// Get alternate ig contrast value
264			highContrastAlternate = parseInt(screenReaderStorage.getItem('scr_highcontrast_alternate')) || 0;
265			
266			if(jqEvent) {
267				$('#fr_screenreader_highcontrast,#fr_screenreader_highcontrast2,#fr_screenreader_highcontrast3').removeClass('active');
268			}
269			
270			// Appliance function
271			var applianceFunction = function() {
272				// Always reset previous default states
273				targetFilterElement.removeClass('scr_highcontrast');
274				highContrast = 0;
275				screenReaderStorage.setItem('scr_highcontrast', highContrast);
276				
277				// Get original styles
278				if(originalHTMLStyles === null) {
279					originalHTMLStyles = targetFilterElement.attr('style') || '';
280				}
281				
282				// Get a hue rotate value if any
283				var hueRotate = '';
284				if($(this).data('rotate')) {
285					hueRotate = ' hue-rotate(' + parseInt($(this).data('rotate')) + 'deg)';
286				}
287				
288				// Get a brightness value if any
289				var brightness = '';
290				if($(this).data('brightness')) {
291					brightness = ' brightness(' + parseInt($(this).data('brightness')) + ')';
292				}
293				
294				targetFilterElement.attr('style', rtrim(originalHTMLStyles, ';') + ';filter: invert(100%)' + hueRotate + brightness);
295			}
296			
297			if(!highContrastAlternate) {
298				applianceFunction.bind(this)();
299				highContrastAlternate = $(this).data('alternate');
300				
301				if(jqEvent) {
302					$(jqEvent.target).addClass('active');
303				}
304			} else if(highContrastAlternate && typeof(jqEvent) !== 'undefined'){
305				// Is the same alternate high contrast or the other one?
306				var currentClickedHighContrastAlternate = $(this).data('alternate');
307				if(currentClickedHighContrastAlternate == highContrastAlternate) {
308					// Disable it, exit from state
309					// Recover original stylesheets
310					targetFilterElement.attr('style', originalHTMLStyles);
311					highContrastAlternate = 0;
312				} else {
313					highContrastAlternate = currentClickedHighContrastAlternate;
314					applianceFunction.bind(this)();
315					
316					if(jqEvent) {
317						$(jqEvent.target).addClass('active');
318					}
319				}
320			} else if(highContrastAlternate && typeof(jqEvent) === 'undefined') {
321				applianceFunction.bind(this)();
322			}
323			
324	    	screenReaderStorage.setItem('scr_highcontrast_alternate', highContrastAlternate);
325		};
326		
327		/**
328		 * Build Screen Reader interface
329		 * 
330		 * @access private
331		 * @param String hexnum
332		 * @return String
333		 */
334		var applyColorsInversion = function(hexnum, isForeGround) {
335
336			hexnum = hexnum.toUpperCase();
337			var splitnum = hexnum.split("");
338			var resultnum = "";
339			var simplenum = "FEDCBA9876".split("");
340			var complexnum = new Array();
341			complexnum.A = "5";
342			complexnum.B = "4";
343			complexnum.C = "3";
344			complexnum.D = "2";
345			complexnum.E = "1";
346			complexnum.F = "0";
347
348			for (i = 0; i < 6; i++) {
349				if (!isNaN(splitnum[i])) {
350					resultnum += simplenum[splitnum[i]];
351				} else if (complexnum[splitnum[i]]) {
352					resultnum += complexnum[splitnum[i]];
353				} else {
354					return false;
355				}
356			}
357			
358			if(isForeGround && screenReaderOptions.ieHighContrast) {
359				var originalIntColor = parseInt(hexnum, 16);
360				var newIntColor = parseInt(resultnum, 16);
361				if(newIntColor < originalIntColor)
362				return hexnum;
363			}
364
365			return resultnum;
366
367		};
368		
369		/**
370		 * Convert rgb color to hex
371		 * 
372		 * @access private
373		 * @param String
374		 *            $rgbcolor
375		 * @return String
376		 */
377		var convertRGBDecimalToHex = function(rgb) {
378		    var regex = /rgba? *\( *([0-9]{1,3}) *, *([0-9]{1,3}) *, *([0-9]{1,3})(, *[0-9]{1,3}[.0-9]*)? *\)/;
379		    var values = regex.exec(rgb);
380		    if(!values) {
381		        return 'FFFFFF'; // fall back to white   
382		    }
383		    var r = Math.round(parseFloat(values[1]));
384		    var g = Math.round(parseFloat(values[2]));
385		    var b = Math.round(parseFloat(values[3]));
386		    var hexColor = (r + 0x10000).toString(16).substring(3).toUpperCase() 
387				         + (g + 0x10000).toString(16).substring(3).toUpperCase()
388				         + (b + 0x10000).toString(16).substring(3).toUpperCase();
389		    
390		    return hexColor;
391		};
392		
393		/**
394		 * Detect if browser has native support for CSS3 filters
395		 * 
396		 * @access private
397		 * @param String selector
398		 * @return String
399		 */
400		var css3FilterFeatureDetect = function(enableWebkit) {
401			var prefixes = ' -webkit- -moz- -o- -ms- '.split(' ');
402		    var el = document.createElement('div');
403		    el.style.cssText = prefixes.join('filter' + ':invert(100%); ');
404		    return !!el.style.length && ((document.documentMode === undefined || document.documentMode > 9));
405		};
406		
407		/**
408		 * Start the legacy IE color inversion
409		 * 
410		 * @access private
411		 * @param String selector
412		 * @return String
413		 */
414		var startLegacyHighcontrast = function(selector) {
415			var collectionOfElements = $(selector);
416			
417			$.fn.skipElement = function(style) {
418			    var original = this.css(style);
419			    this.css(style, 'inherit');
420			    var inherited = this.css(style);
421			    if (original != inherited && convertRGBDecimalToHex(inherited) != '000000') {
422			    	this.css(style, inherited);
423			    	return true;
424			    } else {
425			    	this.css(style, original);
426			    	return false;
427			    }
428			}
429			
430			$(selector).each(function(index, HTMLElement){
431				// Skip untraslated elements
432				if($.inArray(HTMLElement.nodeName.toLowerCase(), ['script', 'embed', 'object']) == -1) {
433					// Advanced mode for !important inline styling without the skipElemen algo
434					if(screenReaderOptions.ieHighContrastAdvanced) {
435						// Get rgb original color
436						var backgroundColor = $(HTMLElement).css('background-color');
437						var foregroundColor = $(HTMLElement).css('color');
438						
439						if(backgroundColor && foregroundColor) {
440							// Invert to hex colors
441							var invertedBackgroundColor = applyColorsInversion(convertRGBDecimalToHex(backgroundColor));
442							var invertedForegroundColor = applyColorsInversion(convertRGBDecimalToHex(foregroundColor), true);
443							
444							// Now apply calculated inverted colors to the target element
445							var currentStyle = $(HTMLElement).attr('style') || '';
446							var newStyle = currentStyle + ' background-color:#' + invertedBackgroundColor + ' !important; color:#' + invertedForegroundColor + ' !important';
447							$(HTMLElement).attr('style', newStyle);
448						}
449					} else {
450						if(!$(HTMLElement).skipElement('background-color')) {
451							// Get rgb original color
452							var backgroundColor = $(HTMLElement).css('background-color');
453							var foregroundColor = $(HTMLElement).css('color');
454							
455							if(backgroundColor && foregroundColor) {
456								// Invert to hex colors
457								var invertedBackgroundColor = applyColorsInversion(convertRGBDecimalToHex(backgroundColor));
458								var invertedForegroundColor = applyColorsInversion(convertRGBDecimalToHex(foregroundColor), true);
459								
460								// Now apply calculated inverted colors to the target element
461								$(HTMLElement).css('background-color', '#' + invertedBackgroundColor);
462								$(HTMLElement).css('color', '#' + invertedForegroundColor);
463							}
464						}
465					}
466				}
467			});
468		};
469		
470		/**
471		 * Manage the appliance of the alternate high contrast mode
472		 * 
473		 * @access private
474		 * @param Object jqEvent
475		 * @return Void
476		 */
477		var skipToContents = function(jqEvent){
478			var targetElement = $(screenReaderOptions.skipToContentsSelector);
479			if(targetElement.length) {
480				$('html, body').animate({
481					scrollTop: targetElement.offset().top
482				}, 500);
483			}
484		};
485		
486		/**
487		 * Build Screen Reader interface
488		 * 
489		 * @access public
490		 * @return Void
491		 */
492		this.buildInterface = function() {
493			// Inject del container
494			var mainContainer =  $('<div/>').attr('id', 'accessibility-links')
495											.addClass(screenReaderOptions.position + ' ' + screenReaderOptions.scrolling + ' ' +  screenReaderOptions.templateOrientation);
496
497			// Switch the target append mode
498			if(screenReaderOptions.targetAppendMode == 'bottom') {
499				mainContainer.appendTo(screenReaderOptions.targetAppendto);
500			}
500 else if(screenReaderOptions.targetAppendMode == 'top') {
501				mainContainer.prependTo(screenReaderOptions.targetAppendto);
502			}
503			
504			// Init dark mode
505			if(screenReaderOptions.enableDarkMode && !!parseInt(screenReaderStorage.getItem('scr_toolbar_darkmode'))) {
506				$('#accessibility-links').addClass('scr-toolbar-darkmode');
507			}
508			
509			if(screenReaderOptions.template == 'accessible.css') {
510				// Inject della extra top toolbar per accessible template di chiusura e informazioni
511				var toolbarDiv = $('<div/>').attr('id', 'toolbar_plugin')
512										 	.html('<span class="scaccessibletoolbar-text">' + (screenReaderOptions.labeltext || fr_screenreader) + '</span>')
513										 	.addClass('scaccessibletoolbar');
514				if(screenReaderOptions.screenreaderIcon == 'wheelchair') {
515					toolbarDiv.addClass('scr_wheelchair');
516				}
517				mainContainer.append(toolbarDiv);
518				
519				// Inject del toolbar closer
520				var toolbarDivCloser = $('<span/>').attr('id', 'toolbar_closer')
521						   						   .html('&#x274c;')
522						   						   .attr('data-title', fr_close_toolbar)
523						   						   .attr('aria-label', fr_close_toolbar)
524						   						   .attr('role', 'button')
525						   						   .addClass('scaccessibletoolbarcloser');
526				$('#toolbar_plugin').append(toolbarDivCloser);
527			}
528											
529			// Inject del left container per il testo
530			var textDiv = $('<div/>').attr('id', 'text_plugin')
531									 .attr('title', ' - ' + (screenReaderOptions.labeltext || fr_screenreader) + ' - ')
532					   				 .addClass('scbasebin screenreader text');
533			if(!screenReaderOptions.showlabel) {
534				textDiv.addClass('scr_nolabel');
535			}
536			if(screenReaderOptions.screenreaderIcon == 'wheelchair') {
537				textDiv.addClass('scr_wheelchair');
538			}
539			if(screenReaderOptions.screenreaderIcon == 'custom') {
540				textDiv.addClass('scr_customicon');
541			}
542			mainContainer.append(textDiv);
543			
544			// Inject del middle container per il volume controls
545			if(screenReaderOptions.screenreader) {
546				var volumeDiv = $('<div/>').attr('id', 'volume_plugin')
547										   .addClass('scbasebin');
548				mainContainer.append(volumeDiv);
549			}
550			
551			// Inject del right container per lo switch speaker
552			var speakerDiv = $('<div/>').attr('id', 'speaker_plugin')
553										.addClass('scbasebin speaker');
554			mainContainer.append(speakerDiv);
555			
556			// Inject del clearer
557			var clearerDiv = $('<div/>').css('clear', 'both').attr('id', 'scr_clearer');
558			mainContainer.append(clearerDiv);
559			
560			// Label
561			if(screenReaderOptions.showlabel) {
562				var labelScreenReader = $('<span/>').addClass('fr_label startapp')
563													 .text(screenReaderOptions.labeltext || fr_screenreader);
564				textDiv.append(labelScreenReader);
565			}
566			
567			if(screenReaderOptions.screenreader) {
568				// Inject del play
569				var playButton = $('<button/>').attr({	'id':'fr_screenreader_play',
570														'title': ' - ' + fr_screenreader_play + ' - ',
571														'aria-label': ' - ' + fr_screenreader_play + ' - ',
572														'role' : 'button',
573														'accesskey':screenReaderOptions.accesskey_play})
574											   .addClass('pinnable')
575											   .text(fr_screenreader_play);
576				speakerDiv.append(playButton);
577				
578				// Inject del pause
579				var pauseButton = $('<button/>').attr({	'id':'fr_screenreader_pause',
580														'title': ' - ' + fr_screenreader_pause + ' - ',
581														'aria-label': ' - ' + fr_screenreader_pause + ' - ',
582														'role' : 'button',
583														'accesskey':screenReaderOptions.accesskey_pause})
584												.addClass('pinnable')
585												.text(fr_screenreader_pause);
586				speakerDiv.append(pauseButton);
587				
588				// Inject dello stop
589				var stopButton = $('<button/>').attr({	'id':'fr_screenreader_stop',
590														'title': ' - ' + fr_screenreader_stop + ' - ',
591														'aria-label': ' - ' + fr_screenreader_stop + ' - ',
592														'role' : 'button',
593														'accesskey':screenReaderOptions.accesskey_stop})
594											   .addClass('pinnable')
595											   .text(fr_screenreader_stop);
596				speakerDiv.append(stopButton);
597			}
598			
599			// Rebuild the section if the accessible template is active
600			if(screenReaderOptions.template == 'accessible.css') {
601				var sectionAudioContainer = $('<div/>').attr({	'id':'sc-section-audio' })
602													   .addClass('sc-section');
603				
604				sectionAudioContainer.append('<div class="sc-section-title" aria-label="' + fr_text_reader + '">' + fr_text_reader + '</div>');
605				speakerDiv.append(sectionAudioContainer);
606				var audioSectionButtons = $('#fr_screenreader_play, #fr_screenreader_pause, #fr_screenreader_stop').clone(true, true).end().remove();
607				sectionAudioContainer.append(audioSectionButtons);
608				
609				var audioSectionVolumeControls = $('#volume_plugin').clone(true, true).end().remove();
610				sectionAudioContainer.append(audioSectionVolumeControls);
611				
612				if(screenReaderOptions.enableDarkMode) {
613    				// Inject del dark mode
614    				var toolbarDivDarkMode = $('<span/>').attr('id', 'toolbar_darkmode')
615        						   						 .html('<svg class="scr-darkmode" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.56 122.88"><defs><style>.cls-1{fill-rule:evenodd;}</style></defs><title>night</title><path class="cls-1" d="M121.85,87.3A64.31,64.31,0,1,1,36.88.4c2.94-1.37,5.92.91,4.47,4.47a56.29,56.29,0,0,0,75.75,77.4l.49-.27a3.41,3.41,0,0,1,4.61,4.61l-.35.69ZM92.46,74.67H92A16.11,16.11,0,0,0,76.2,58.93v-.52a15.08,15.08,0,0,0,11-4.72,15.19,15.19,0,0,0,4.72-11h.51a15.12,15.12,0,0,0,4.72,11,15.12,15.12,0,0,0,11,4.72v.51A16.13,16.13,0,0,0,92.46,74.67Zm10.09-46.59h-.27a7.94,7.94,0,0,0-2.49-5.81A7.94,7.94,0,0,0,94,19.78v-.27A7.94,7.94,0,0,0,99.79,17a8,8,0,0,0,2.49-5.8h.27A8,8,0,0,0,105,17a8,8,0,0,0,5.81,2.49v.27A8,8,0,0,0,105,22.27a7.94,7.94,0,0,0-2.49,5.81Zm-41.5,8h-.41a12.06,12.06,0,0,0-3.78-8.82A12.06,12.06,0,0,0,48,23.5v-.41a12.07,12.07,0,0,0,8.82-3.78,12.09,12.09,0,0,0,3.78-8.82h.41a12.08,12.08,0,0,0,3.77,8.82,12.09,12.09,0,0,0,8.83,3.78v.41a12.09,12.09,0,0,0-8.83,3.78,12.08,12.08,0,0,0-3.77,8.82Z"/></svg>')
616        						   						 .attr('data-title', fr_dark_mode)
617        						   						 .attr('aria-label', fr_dark_mode)
618        						   						 .attr('role', 'button')
619        						   						 .addClass('scaccessibletoolbarcloser');
620    				$('#sc-section-audio').append(toolbarDivDarkMode);
621    				
622    				$('#toolbar_darkmode').on('click', function(jqEvent){
623    					var darkModeState = 0;
624    					var accessibilityLinksToolbar = $('#accessibility-links');
625    					accessibilityLinksToolbar.toggleClass('scr-toolbar-darkmode');
626    					if(accessibilityLinksToolbar.hasClass('scr-toolbar-darkmode')) {
627    						darkModeState = 1;
628    					}
629    					screenReaderStorage.setItem('scr_toolbar_darkmode', darkModeState);
630    				});
631				}
632				if(screenReaderOptions.enableAccessibilityStatement) {
633					// Inject del dark mode
634    				var toolbarDivAccessibilityStatement = $('<a/>').attr('id', 'toolbar_accessibility_statement')
635        						   						 			.text(screenReaderOptions.enableAccessibilityStatementText)
636    						   						 				.attr('href', screenReaderOptions.enableAccessibilityStatementLink)
637    						   						 				.attr('download', screenReaderOptions.enableAccessibilityStatementLink.substring(screenReaderOptions.enableAccessibilityStatementLink.lastIndexOf('/') + 1))
638    						   						 				.addClass('scaccessibletoolbarstatement');
639    				if(screenReaderOptions.enableDarkMode) {
640    					toolbarDivAccessibilityStatement.addClass('darkmode');
641    				}
642    				$('#sc-section-audio').append(toolbarDivAccessibilityStatement);
643				}
644			}
645			
646			if(screenReaderOptions.fontsize) {
647				// Inject del font size +
648				var increaseButton = $('<button/>').attr({'id':'fr_screenreader_font_increase',
649														  'title': ' - ' + fr_increase + ' - ',
650														  'aria-label': ' - ' + fr_increase + ' - ',
651														  'role' : 'button',
652														  'accesskey':screenReaderOptions.accesskey_increase,
653														  'data-value':'1'})
654												   .addClass('sizable')
655												   .text(fr_increase);
656				speakerDiv.append(increaseButton);
657				
658				// Inject del font size -
659				var decreaseButton = $('<button/>').attr({'id':'fr_screenreader_font_decrease',
660														  'title': ' - ' + fr_decrease + ' - ',
661														  'aria-label': ' - ' + fr_decrease + ' - ',
662														  'role' : 'button',
663														  'accesskey':screenReaderOptions.accesskey_decrease,
664														  'data-value':'-1'})
665												   .addClass('sizable')
666												   .text(fr_decrease);
667				speakerDiv.append(decreaseButton);
668				
669				// Inject del font size reset
670				var resetButton = $('<button/>').attr({	'id':'fr_screenreader_font_reset',
671														'title': ' - ' + fr_reset + ' - ',
672														'aria-label': ' - ' + fr_reset + ' - ',
673														'role' : 'button',
674														'accesskey':screenReaderOptions.accesskey_reset,
675														'data-value':'0'})
676												.addClass('resizable')
677												.text(fr_reset);
678				speakerDiv.append(resetButton);
679				
680				// Add extra control labels for the accessible template
681		    	if(screenReaderOptions.template == 'accessible.css') {
682		    		var fontSizeDefault = screenReaderOptions.fontsizeDefault;
683					var sessionFontSizeCurrent = screenReaderStorage.getItem('scr_fontsize');
684		    		var currentCalculatedIncrement = parseInt(fontSizeCurrent - fontSizeDefault);
685		    		var incrementSign = currentCalculatedIncrement > 0 ? '+' : '-';
686		    		if(currentCalculatedIncrement != 0) {
687		    			if(currentCalculatedIncrement > 0) {
688		    				$('#fr_screenreader_font_increase').append('<div class="scfontsizelabel">' + incrementSign + currentCalculatedIncrement + '%</div>');
689		    			} else {
690		    				$('#fr_screenreader_font_decrease').append('<div class="scfontsizelabel">' + currentCalculatedIncrement + '%</div>');
691		    			}
692		    		}
693		    		
694		    		// Rebuild the section if the accessible template is active
695					var sectionFontsizeContainer = $('<div/>').attr({	'id':'sc-section-fontsize' })
696														   	  .addClass('sc-section');
697					
698					sectionFontsizeContainer.append('<div class="sc-section-title" aria-label="' + fr_font_sizing + '">' + fr_font_sizing + '</div>');
699					speakerDiv.append(sectionFontsizeContainer);
700					var fontsizeSectionButtons = $('#fr_screenreader_font_increase, #fr_screenreader_font_decrease, #fr_screenreader_font_reset').clone(true, true).end().remove();
701					sectionFontsizeContainer.append(fontsizeSectionButtons);
702		    	}
703			}
704			
705			// Append del font family toggle
706			if(screenReaderOptions.dyslexicFont) {
707				// Inject del font size reset
708				var fontFamilyButton = $('<button/>').attr({'id':'fr_screenreader_font_family',
709															'title': ' - ' + fr_dyslexic_title + ' - ',
710															'aria-label': ' - ' + fr_dyslexic_title + ' - ',
711															'role' : 'button',
712															'accesskey':screenReaderOptions.accesskey_dyslexic,
713															'data-value':'0'})
714													 .text(fr_dyslexic_title);
715				speakerDiv.append(fontFamilyButton);
716			}
717			
718			// Append del gray hues button
719			if(screenReaderOptions.grayHues) {
720				// Inject del font size reset
721				var grayHuesButton = $('<button/>').attr({'id':'fr_screenreader_gray_hues',
722														  'title': ' - ' + fr_gray_hues + ' - ',
723														  'aria-label': ' - ' + fr_gray_hues + ' - ',
724														  'role' : 'button',
725														  'accesskey':screenReaderOptions.accesskey_grayhues})
726												   .text(fr_gray_hues);
727				speakerDiv.append(grayHuesButton);
728			}
729			
730			// Append del big cursor
731			if(screenReaderOptions.bigCursor) {
732				var bigCursorButton = $('<button/>').attr({ 'id':'fr_screenreader_bigcursor',
733														  	'title': ' - ' + fr_big_cursor + ' - ',
734														  	'aria-label': ' - ' + fr_big_cursor + ' - ',
735															'role' : 'button',
736														  	'accesskey':screenReaderOptions.accesskey_bigcursor})
737												   .text(fr_big_cursor);
738				speakerDiv.append(bigCursorButton);
739			}
740			
741			// Append della reading guide
742			if(screenReaderOptions.readingGuides) {
743				// Inject del font size reset
744				var readingGuidesButton = $('<button/>').attr({'id':'fr_screenreader_reading_guides',
745																'title': ' - ' + fr_reading_guides_title + ' - ',
746																'aria-label': ' - ' + fr_reading_guides_title + ' - ',
747																'role' : 'button',
748																'accesskey':screenReaderOptions.accesskey_reading_guides})
749														 .text(fr_reading_guides_title);
750				speakerDiv.append(readingGuidesButton);
751				
752				// Init reading guides
753		    	readingGuides = !!parseInt(screenReaderStorage.getItem('scr_reading_guides')) || 0;
754		    	if(readingGuides) {
755		    		$('#fr_screenreader_reading_guides').trigger('click', 1);
756		    	}
757			}
758			
759			// Append del read mode
760			if(screenReaderOptions.readability) {
761				// Inject del font size reset
762				var readabilityButton = $('<button/>').attr({'id':'fr_screenreader_readability',
763																'title': ' - ' + fr_readability_title + ' - ',
764																'aria-label': ' - ' + fr_readability_title + ' - ',
765																'role' : 'button',
766																'accesskey':screenReaderOptions.accesskey_readability})
767														 .text(fr_readability_title);
768				speakerDiv.append(readabilityButton);
769			}
770			
771			// Append dell'hide images
772			if(screenReaderOptions.hideImages) {
773				// Inject del font size reset
774				var hideimagesButton = $('<button/>').attr({'id':'fr_screenreader_hideimages',
775															'title': ' - ' + fr_hideimages_title + ' - ',
776															'aria-label': ' - ' + fr_hideimages_title + ' - ',
777															'role' : 'button',
778															'accesskey':screenReaderOptions.accesskey_hideimages})
779													 .text(fr_hideimages_title);
780				speakerDiv.append(hideimagesButton);
781				
782				// Init reading guides
783		    	hideImages = !!parseInt(screenReaderStorage.getItem('scr_hideimages')) || 0;
784		    	if(hideImages) {
785		    		$('#fr_screenreader_hideimages').trigger('click', 1);
786		    	}
787			}
788			
789			// Append dello skip to contents
790			if(screenReaderOptions.showSkipToContents) {
791				// Inject del font size reset
792				var skipToContentsButton = $('<button/>').attr({'id':'fr_screenreader_skiptocontents',
793																'title': ' - ' + fr_showskiptocontents_title + ' - ',
794																'aria-label': ' - ' + fr_showskiptocontents_title + ' - ',
795																'role' : 'button',
796																'accesskey':screenReaderOptions.accesskey_skiptocontents})
797														 .text(fr_showskiptocontents_title);
798				speakerDiv.append(skipToContentsButton);
799			}
800			
801			// Append dello spacing increase/decrease
802			if(screenReaderOptions.spacingSize) {
803				// Inject del font size +
804				var spacingIncreaseButton = $('<button/>').attr({ 'id':'fr_screenreader_spacing_increase',
805																  'title': ' - ' + fr_spacing_increase + ' - ',
806																  'aria-label': ' - ' + fr_spacing_increase + ' - ',
807																  'role' : 'button',
808																  'accesskey':screenReaderOptions.accesskey_spacingsize_increase,
809																  'data-value':'1'})
810														   .addClass('spacingsizable')
811														   .text(fr_spacing_increase);
812				speakerDiv.append(spacingIncreaseButton);
813				
814				// Inject del font size -
815				var spacingDecreaseButton = $('<button/>').attr({ 'id':'fr_screenreader_spacing_decrease',
816																  'title': ' - ' + fr_spacing_decrease + ' - ',
817																  'aria-label': ' - ' + fr_spacing_decrease + ' - ',
818																  'role' : 'button',
819																  'accesskey':screenReaderOptions.accesskey_spacingsize_decrease,
820																  'data-value':'-1'})
821														   .addClass('spacingsizable')
822														   .text(fr_spacing_decrease);
823				speakerDiv.append(spacingDecreaseButton);
824				
825				// Add extra control labels for the accessible template
826		    	if(screenReaderOptions.template == 'accessible.css') {
827					var sessionSpacingSizeCurrent = screenReaderStorage.getItem('scr_spacingsize');
828		    		if(sessionSpacingSizeCurrent) {
829		    			if(sessionSpacingSizeCurrent > 0) {
830		    				$('#fr_screenreader_spacing_increase').append('<div class="scspacingsizelabel">+' + (sessionSpacingSizeCurrent * 10) + '%</div>');
831		    			}
832		    		}
833		    	}
834			}
835			
836			// Add extra control labels for the accessible template
837	    	if(screenReaderOptions.template == 'accessible.css') {
838	    		// Rebuild the section if the accessible template is active
839	    		var enhancementsSectionButtons = $('#fr_screenreader_font_family, #fr_screenreader_gray_hues, #fr_screenreader_bigcursor, #fr_screenreader_reading_guides, #fr_screenreader_readability, #fr_screenreader_hideimages, #fr_screenreader_skiptocontents, #fr_screenreader_spacing_increase, #fr_screenreader_spacing_decrease').clone(true, true).end().remove();
840	    		if(enhancementsSectionButtons.length) {
841	    			var sectionEnhancementContainer = $('<div/>').attr({ 'id':'sc-section-enhancement' })
842	    														 .addClass('sc-section');
843	    			sectionEnhancementContainer.append('<div class="sc-section-title" aria-label="' + fr_accessibility_enhancements + '">' + fr_accessibility_enhancements + '</div>');
844	    			speakerDiv.append(sectionEnhancementContainer);
845	    			sectionEnhancementContainer.append(enhancementsSectionButtons);
846	    		}
847	    	}
848
849			// Append del page zoom
850			if(screenReaderOptions.pageZoom && screenReaderOptions.templateOrientation == 'vertical') {
851				// Increase del page zoom +
852				var pagezoomIncreaseButton = $('<button/>').attr({ 'id':'fr_screenreader_pagezoom_increase',
853																   'title': ' - ' + fr_pagezoom_increase + ' - ',
854																   'aria-label': ' - ' + fr_pagezoom_increase + ' - ',
855																   'role' : 'button',
856																   'accesskey':screenReaderOptions.accesskey_pagezoomsize_increase,
857																   'data-value':'1'})
858														   .addClass('zoomsizable')
859														   .text(fr_pagezoom_increase);
860				speakerDiv.append(pagezoomIncreaseButton);
861				
862				// Decrease del page zoom -
863				var pagezoomDecreaseButton = $('<button/>').attr({ 'id':'fr_screenreader_pagezoom_decrease',
864																   'title': ' - ' + fr_pagezoom_decrease + ' - ',
865																   'aria-label': ' - ' + fr_pagezoom_decrease + ' - ',
866																   'role' : 'button',
867																   'accesskey':screenReaderOptions.accesskey_pagezoomsize_decrease,
868																   'data-value':'-1'})
869														   .addClass('zoomsizable')
870														   .text(fr_pagezoom_decrease);
871				speakerDiv.append(pagezoomDecreaseButton);
872				
873				// Reset del page zoom
874				var pagezoomResetButton = $('<button/>').attr({	'id':'fr_screenreader_pagezoom_reset',
875            													'title': ' - ' + fr_pagezoom_reset + ' - ',
876            													'aria-label': ' - ' + fr_pagezoom_reset + ' - ',
877            													'role' : 'button',
878            													'accesskey':screenReaderOptions.accesskey_pagezoomsize_reset,
879            													'data-value':'0'})
880        												.addClass('zoomresizable')
881        												.text(fr_pagezoom_reset);
882				speakerDiv.append(pagezoomResetButton);
883				
884				// Add extra control labels for the accessible template
885		    	if(screenReaderOptions.template == 'accessible.css') {
886		    		var zoomSizeDefault = 100;
887					var sessionZoomSizeCurrent = screenReaderStorage.getItem('scr_zoomsize');
888		    		var currentCalculatedIncrement = parseInt(zoomSizeCurrent - zoomSizeDefault);
889		    		var incrementSign = currentCalculatedIncrement > 0 ? '+' : '-';
890		    		if(currentCalculatedIncrement != 0) {
891		    			if(currentCalculatedIncrement > 0) {
892		    				$('#fr_screenreader_pagezoom_increase').append('<div class="sczoomsizelabel">' + incrementSign + currentCalculatedIncrement + '%</div>');
893		    			} else {
894		    				$('#fr_screenreader_pagezoom_decrease').append('<div class="sczoomsizelabel">' + currentCalculatedIncrement + '%</div>');
895		    			}
896		    		}
897		    		
898		    		
899					var sessionPageZoomCurrent = screenReaderStorage.getItem('scr_pagezoom');
900		    		if(sessionPageZoomCurrent) {
901		    			if(sessionPageZoomCurrent > 0) {
902		    				$('#fr_screenreader_pagezoom_increase').append('<div class="scspacingsizelabel">+' + (sessionPageZoomCurrent * 10) + '%</div>');
903		    			}
904		    		}
905		    		
906		    		var sectionPageZoomContainer = $('<div/>').attr({ 'id':'sc-section-pagezoom' })
907				   											  .addClass('sc-section');
908
909                    sectionPageZoomContainer.append('<div class="sc-section-title" aria-label="' + fr_page_zoom + '">' + fr_page_zoom + '</div>');
910                    speakerDiv.append(sectionPageZoomContainer);
911                    var pagezoomSectionButtons = $('#fr_screenreader_pagezoom_increase, #fr_screenreader_pagezoom_decrease, #fr_screenreader_pagezoom_reset').clone(true, true).end().remove();
912                    sectionPageZoomContainer.append(pagezoomSectionButtons);
913		    	}
914			}
915			
916			var subtractWidthKonst = 0;
917			if(!screenReaderOptions.screenreader) {
918				subtractWidthKonst = 100;
919			}
920			if(!screenReaderOptions.dyslexicFont) {
921				subtractWidthKonst += 35;
922			}
923			if(!screenReaderOptions.grayHues) {
924				subtractWidthKonst += 35;
925			}
926			if(!screenReaderOptions.bigCursor) {
927				subtractWidthKonst += 35;
928			}
929			if(!screenReaderOptions.readingGuides) {
930				subtractWidthKonst += 35;
931			}
932			if(!screenReaderOptions.readability) {
933				subtractWidthKonst += 35;
934			}
935			if(!screenReaderOptions.showSkipToContents) {
936				subtractWidthKonst += 35;
937			}
938			if(!screenReaderOptions.spacingSize) {
939				subtractWidthKonst += 70;
940			}
941
942			if(screenReaderOptions.highcontrast) {
943				// Inject del default high contrast
944				var highContrastHtml = $('<button/>').attr({'id':'fr_screenreader_highcontrast',
945															'title': ' - ' + fr_highcontrast + ' - ',
946															'aria-label': ' - ' + fr_highcontrast + ' - ',
947															'role' : 'button',
948															'accesskey':screenReaderOptions.accesskey_highcontrast,
949															'data-value':'0'})
950													 .text(fr_highcontrast);
951				if(highContrast) {
952					highContrastHtml.addClass('active');
953				}
954				speakerDiv.append(highContrastHtml);
955				
956				// Check if the alternate high contrast mode is enabled and supported
957				var alternateHighContrast = screenReaderOptions.highcontrastAlternate && css3FilterFeatureDetect();
958				if(alternateHighContrast) {
959					// Inject del mode2 high contrast
960					var highContrast2 = $('<button/>').attr({'id':'fr_screenreader_highcontrast2',
961															 'title': ' - ' + fr_highcontrast + ' - ',
962															 'aria-label': ' - ' + fr_highcontrast + ' - ',
963															 'role' : 'button',
964															 'accesskey':screenReaderOptions.accesskey_highcontrast2,
965															 'data-alternate':'1',
966															 'data-rotate':screenReaderOptions.colorHue})
967												 	  .text(fr_highcontrast);
968					speakerDiv.append(highContrast2);
969					
970					// Inject del mode3 high contrast
971					var highContrast3 = $('<button/>').attr({'id':'fr_screenreader_highcontrast3',
972															 'title': ' - ' + fr_highcontrast + ' - ',
973															 'aria-label': ' - ' + fr_highcontrast + ' - ',
974															 'role' : 'button',
975															 'accesskey':screenReaderOptions.accesskey_highcontrast3,
976															 'data-alternate':'2',
977															 'data-rotate':screenReaderOptions.colorHue,
978															 'data-brightness':screenReaderOptions.colorBrightness})
979												 	  .text(fr_highcontrast);
980					speakerDiv.append(highContrast3);
981					
982					// Init high contrast alternate
983			    	highContrastAlternate = parseInt(screenReaderStorage.getItem('scr_highcontrast_alternate')) || 0;
984			    	if(highContrastAlternate) {
985			    		var targetHightContrastAlternateButtonMode = $('button[data-alternate=' + highContrastAlternate + ']').addClass('active');
986			    		applyAlternateHighContrast.call(targetHightContrastAlternateButtonMode);
987			    	}
988				}
989				
990				// Rule the bar size width
991				if(screenReaderOptions.fontsize && alternateHighContrast) {
992					$('#speaker_plugin').css('width', parseInt(605 - subtractWidthKonst) + 'px');
993				} else if(screenReaderOptions.fontsize){
994					$('#speaker_plugin').css('width', parseInt(540 - subtractWidthKonst) + 'px');
995				} else if(!screenReaderOptions.fontsize && alternateHighContrast) {
996					$('#speaker_plugin').css('width', parseInt(510 - subtractWidthKonst) + 'px');
997				}
998				
999				// Add extra control labels for the accessible template
1000		    	if(screenReaderOptions.template == 'accessible.css') {
1001		    		// Rebuild the section if the accessible template is active
1002					var sectionHighcontrastContainer = $('<div/>').attr({ 'id':'sc-section-highcontrast' })
1003																  .addClass('sc-section');
1004					
1005					sectionHighcontrastContainer.append('<div class="sc-section-title" aria-label="' + fr_high_contrast_color + '">' + fr_high_contrast_color + '</div>');
1006					speakerDiv.append(sectionHighcontrastContainer);
1007					var highcontrastSectionButtons = $('#fr_screenreader_highcontrast, #fr_screenreader_highcontrast2, #fr_screenreader_highcontrast3').clone(true, true).end().remove();
1008					sectionHighcontrastContainer.append(highcontrastSectionButtons);
1009		    	}
1010			} else {
1011				if(screenReaderOptions.fontsize) {
1012					$('#speaker_plugin').css('width', parseInt(425 - subtractWidthKonst) + 'px');
1013				} else {
1014					$('#speaker_plugin').css('width', parseInt(315 - subtractWidthKonst) + 'px');
1015				}
1016			}
1017			
1018			// Add colors palette to the accessibility template
1019			if(screenReaderOptions.customColors && screenReaderOptions.template == 'accessible.css') {
1020				// Inject del font size +
1021				var changeTextColor = $('<button/>').attr({'id':'fr_screenreader_text_color',
1022														   'title': ' - ' + fr_text_color + ' - ',
1023														   'aria-label': ' - ' + fr_text_color + ' - ',
1024														   'role' : 'button',
1025														   'accesskey':screenReaderOptions.accesskey_change_text_color})
1026												   .addClass('scr-custom-color')
1027												   .text(fr_text_color);
1028				speakerDiv.append(changeTextColor);
1029				
1030				// Inject del font size -
1031				var changeBackgroundColor = $('<button/>').attr({'id':'fr_screenreader_background_color',
1032														  		 'title': ' - ' + fr_background_color + ' - ',
1033        														 'aria-label': ' - ' + fr_background_color + ' - ',
1034        														 'role' : 'button',
1035        														 'accesskey':screenReaderOptions.accesskey_change_background_color})
1036												   .addClass('scr-custom-color')
1037												   .text(fr_background_color);
1038				speakerDiv.append(changeBackgroundColor);
1039				
1040				var colorsMiniButtons = `
1041				<div class="scr-color-box">
1042					<span data-scr-color="#0076B4" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #0076B4 !important;"> </span> 
1043					<span data-scr-color="#7A549C" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #7A549C !important;"> </span> 
1044					<span data-scr-color="#C83733" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #C83733 !important;"> </span> 
1045					<span data-scr-color="#D07021" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #D07021 !important;"> </span> 
1046					<span data-scr-color="#26999F" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #26999F !important;"> </span> 
1047					<span data-scr-color="#4D7831" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #4D7831 !important;"> </span> 
1048					<span data-scr-color="#ffffff" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #ffffff !important;"> </span> 
1049					<span data-scr-color="#000000" class="scr-color-selection" role="button" tabindex="0" aria-label="${fr_background_color_desc}" style="background-color: #000000 !important;"> </span> 
1050					<div class="scr-color-selection-reset" role="button" tabindex="0"> ${fr_custom_color_reset} </div>
1051				</div>`;
1052				$('button.scr-custom-color').append(colorsMiniButtons);
1053				
1054	    		// Rebuild the section if the accessible template is active
1055				var sectionCustomcolorsContainer = $('<div/>').attr({'id':'sc-section-customcolors'})
1056													   	  	  .addClass('sc-section');
1057				
1058				sectionCustomcolorsContainer.append('<div class="sc-section-title" aria-label="' + fr_custom_colors + '">' + fr_custom_colors + '</div>');
1059				speakerDiv.append(sectionCustomcolorsContainer);
1060				var customColorsSectionButtons = $('#fr_screenreader_text_color, #fr_screenreader_background_color').clone(true, true).end().remove();
1061				sectionCustomcolorsContainer.append(customColorsSectionButtons);
1062				
1063				// Bind event change color, apply the color based on the container type
1064				$('span.scr-color-selection').on('click', function(jqEvent){
1065					var colorButton = $(this);
1066					var parentType = colorButton.parents('button.scr-custom-color').attr('id');
1067					var selectedColor = colorButton.data('scr-color');
1068					
1069					if(parentType == 'fr_screenreader_text_color') {
1070						if(colorButton.hasClass('scr-color-selection-enabled')) {
1071							document.querySelector('body').style.removeProperty('color');
1072							if(screenReaderOptions.customColorsCssSelectors) {
1073								$(screenReaderOptions.customColorsCssSelectors).each(function(index, element){
1074									element.style.removeProperty('color');
1075								});
1076							}
1077							colorButton.removeClass('scr-color-selection-enabled');
1078						} else {
1079							document.querySelector('body').style.setProperty('color', selectedColor, 'important');
1080							if(screenReaderOptions.customColorsCssSelectors) {
1081								$(screenReaderOptions.customColorsCssSelectors).each(function(index, element){
1082									element.style.setProperty('color', selectedColor, 'important');
1083								});
1084							}
1085							$('#fr_screenreader_text_color span.scr-color-selection').removeClass('scr-color-selection-enabled');
1086							colorButton.addClass('scr-color-selection-enabled');
1087						}
1088						screenReaderStorage.setItem('scr_custom_text_color', selectedColor);
1089					} else if(parentType == 'fr_screenreader_background_color') {
1090						if(colorButton.hasClass('scr-color-selection-enabled')) {
1091							document.querySelector('body').style.removeProperty('background');
1092							if(screenReaderOptions.customColorsCssSelectors) {
1093								$(screenReaderOptions.customColorsCssSelectors).each(function(index, element){
1094									element.style.removeProperty('background');
1095								});
1096							}
1097							colorButton.removeClass('scr-color-selection-enabled');
1098						} else {
1099							document.querySelector('body').style.setProperty('background', selectedColor, 'important');
1100							if(screenReaderOptions.customColorsCssSelectors) {
1101								$(screenReaderOptions.customColorsCssSelectors).each(function(index, element){
1102									element.style.setProperty('background', selectedColor, 'important');
1103								});
1104							}
1105							$('#fr_screenreader_background_color span.scr-color-selection').removeClass('scr-color-selection-enabled');
1106							colorButton.addClass('scr-color-selection-enabled');
1107						}
1108						screenReaderStorage.setItem('scr_custom_background_color', selectedColor);
1109					}
1110				});
1111				
1112				$('div.scr-color-selection-reset').on('click', function(jqEvent){
1113					var resetButton = $(this);
1114					var parentType = resetButton.parents('button.scr-custom-color').attr('id');
1115					
1116					if(parentType == 'fr_screenreader_text_color') {
1117						$('span.scr-color-selection').removeClass('scr-color-selection-enabled');
1118						document.querySelector('body').style.removeProperty('color');
1119						if(screenReaderOptions.customColorsCssSelectors) {
1120							$(screenReaderOptions.customColorsCssSelectors).each(function(index, element){
1121								element.style.removeProperty('color');
1122							});
1123						}
1124						screenReaderStorage.removeItem('scr_custom_text_color');
1125					} else if(parentType == 'fr_screenreader_background_color') {
1126						$('span.scr-color-selection').removeClass('scr-color-selection-enabled');
1127						document.querySelector('body').style.removeProperty('background');
1128						if(screenReaderOptions.customColorsCssSelectors) {
1129							$(screenReaderOptions.customColorsCssSelectors).each(function(index, element){
1130								element.style.removeProperty('background');
1131							});
1132						}
1133						screenReaderStorage.removeItem('scr_custom_background_color');
1134					}
1135				});
1136				
1137				// Rotate button click
1138				$('#fr_screenreader_background_color, #fr_screenreader_text_color').on('click', function(jqEvent){
1139					jqEvent.stopPropagation();
1140					if(!$(jqEvent.target).hasClass('scr-custom-color') && !$(jqEvent.target).hasClass('scr-color-box')) {
1141						return false;
1142					}
1143					if($(jqEvent.target).hasClass('scr-custom-color')) {
1144						var selectedContainerButton = $(jqEvent.target);
1145					} else {
1146						var selectedContainerButton = $(jqEvent.target).parent('button.scr-custom-color');
1147					}
1148					var selectedColorButton = $('span.scr-color-selection.scr-color-selection-enabled', selectedContainerButton);
1149					if(selectedColorButton.length) {
1150						selectedColorButton.next().trigger('click');
1151					} else {
1152						$('div.scr-color-box > span:first-child', selectedContainerButton).trigger('click');
1153					}
1154				});
1155			}
1156			
1157			// Manage background color for the toolbar or buttons only based on the template
1158			if(screenReaderOptions.template == 'custom.css') {
1159				$('#speaker_plugin button').css('background-color', screenReaderOptions.toolbarBgcolor);
1160				$('#accessibility-links').css('background', 'transparent');
1161			} else {
1162				$('#accessibility-links').css('background-color', screenReaderOptions.toolbarBgcolor);
1163			}
1164			
1165			if(screenReaderOptions.templateOrientation == 'vertical' && screenReaderOptions.template != 'custom.css') {
1166				$('#text_plugin').css('background-color', screenReaderOptions.toolbarBgcolor);
1167			}
1168			
1169			var toolbarBgcolorCompare = screenReaderOptions.toolbarBgcolor.toLowerCase();
1170			if(screenReaderOptions.template == 'accessible.css' && (toolbarBgcolorCompare == '#eeeeee' || toolbarBgcolorCompare == '#eee')) {
1171				$('#text_plugin').css('background-color', '#004fce');
1172				$('#toolbar_plugin').css('background-color', '#004fce');
1173				$('#toolbar_closer').css({'color':'transparent', 'text-shadow':'#004fce 0px 0px 0px'});
1174				$('svg.scr-darkmode').css('fill', '#004fce');
1175			}
1176			
1177			if(screenReaderOptions.template == 'accessible.css' && toolbarBgcolorCompare != '#eeeeee' && toolbarBgcolorCompare != '#eee') {
1178				$('#accessibility-links').css('background-color', '#EEEEEE');
1179				$('#text_plugin').css('background-color', screenReaderOptions.toolbarBgcolor);
1180				$('#toolbar_plugin').css('background-color', screenReaderOptions.toolbarBgcolor);
1181				$('#toolbar_closer').css({'color':'transparent', 'text-shadow': (screenReaderOptions.toolbarBgcolor + ' 0px 0px 0px')});
1182				$('svg.scr-darkmode').css('fill', screenReaderOptions.toolbarBgcolor);
1183			}
1184			
1185			if(screenReaderOptions.template == 'accessible.css') {
1186				$('#speaker_plugin > button').removeAttr('title');
1187				var currentLabelTitle = $('#text_plugin').attr('title');
1188				$('#text_plugin').removeAttr('title');
1189				$('#text_plugin').attr('aria-label', currentLabelTitle).attr('role', 'button');				
1190			}
1191			
1192			// Manage the visibility state of the toolbar
1193			if(screenReaderConfigOptions.useMinimizedToolbar && 
1194			  (screenReaderConfigOptions.mobileDeviceDetected || (!screenReaderConfigOptions.mobileDeviceDetected && !screenReaderConfigOptions.minimizedToolbarOnlyMobile))) {
1195				var volumeWidth = $('#volume_plugin').width();
1196				var speakerWidth = $('#speaker_plugin').width();
1197				var containerWidth = $('#accessibility-links').width();
1198				var totalWidthToOffset = parseInt(volumeWidth + speakerWidth);
1199				if(screenReaderOptions.templateOrientation == 'vertical') {
1200					totalWidthToOffset = parseInt(speakerWidth);
1201				}
1202				if(screenReaderOptions.template == 'accessible.css') {
1203					totalWidthToOffset = parseInt(containerWidth);
1204				}
1205				var targetState = 0;
1206				var targetSign = '+';
1207				var windowWidth = $(window).width();
1208				if(windowWidth < totalWidthToOffset && screenReaderOptions.template != 'accessible.css') {
1209					totalWidthToOffset = windowWidth - 80;
1210				}
1211				
1212				$('#text_plugin').attr('accesskey', screenReaderConfigOptions.accesskey_minimized);
1213				
1214				$('#toolbar_closer').on('click', function(jqEvent){
1215					$('#text_plugin').trigger('click');
1216				});
1217				
1218				// Manage the lazy loaded fontsize when opening the minimized toolbar
1219				if(screenReaderOptions.fontsizeMinimizedToolbar && screenReaderConfigOptions.statusMinimizedToolbar == 'closed') {
1220					$('#text_plugin').on('click', function(jqEvent){
1221						$('body').css('font-size', fontSizeCurrent + '%');
1222						
1223						if(screenReaderOptions.fontsizeSelector) {
1224							fontSizeInline = ';font-size:100%';
1225							// Manage header resize increment
1226							fontResizeHeaders();
1227						}
1228						
1229						var fontsizingElements = $(screenReaderOptions.fontsizeSelector);
1230						$.each(fontsizingElements, function(index, elem) {
1231							if($.inArray(elem.nodeName.toLowerCase(), ['h1', 'h2', 'h3', 'h4']) != -1) {
1232								fontSizeInline = '';
1233							} else {
1234								fontSizeInline = ';font-size:100%';
1235							}
1236							var currentInlineStyles = $(elem).attr('style') || '';
1237							$(elem).attr('style', rtrim(currentInlineStyles, ';') + fontSizeInline);
1238						});
1239					});
1240				}
1241				
1242				// Identify the side to offest based on the positionment
1243				switch(screenReaderOptions.position) {
1244					case 'bottomright':
1245					case 'topright':
1246						$('#text_plugin span.fr_label').css({'cursor':'pointer'});
1247						if(screenReaderConfigOptions.statusMinimizedToolbar == 'closed') {
1248							$('#accessibility-links').css({'right':'-' + (totalWidthToOffset + 2) + 'px'}).removeClass('screenreader-open').addClass('screenreader-closed');
1249							
1250							// Reposition the button in the accessible.css template on window resize or orientation change on mobile
1251							if(screenReaderOptions.template == 'accessible.css') {
1252								$(window).on('resize', function() {
1253									let accessibilityLinksMainContainer = $('#accessibility-links.screenreader-closed');
1254									if(accessibilityLinksMainContainer.length) {
1255										let containerWidth = accessibilityLinksMainContainer.width();
1256										totalWidthToOffset = parseInt(containerWidth);
1257										$('#accessibility-links').css({'right':'-' + (totalWidthToOffset + 2) + 'px'});
1258									}
1259								})
1260							}
1261						} else {
1262							$('#speaker_plugin, #volume_plugin').css({'opacity':1});
1263							$('#accessibility-links').removeClass('screenreader-closed').addClass('screenreader-open');
1264							targetState = totalWidthToOffset + 2;
1265							targetSign = '-';
1266						}
1267						
1268						$('#text_plugin').css({'cursor':'pointer'}).on('click', function(jqEvent){
1269							if(targetState) {
1270								$('#speaker_plugin, #volume_plugin').animate({'opacity': 0}, 300);
1271							}
1272							$('#accessibility-links').animate({'right': (targetSign + targetState)}, 500, function(){
1273								targetState = targetState ? 0 : totalWidthToOffset + 2;
1274								targetSign = '+' ? '-' : '+';
1275								if(targetState) {
1276									$(this).removeClass('screenreader-closed').addClass('screenreader-open');
1277								} else {
1278									$(this).removeClass('screenreader-open').addClass('screenreader-closed');
1279								}
1280							});
1281							if(!targetState) {
1282								setTimeout(function(){
1283									$('#speaker_plugin, #volume_plugin').animate({'opacity': 1}, 300);
1284								}, 300);
1285							}
1286						});
1287					break;
1288					
1289					case 'topleft':
1290					case 'bottomleft' :
1291						if(screenReaderConfigOptions.statusMinimizedToolbar == 'closed') {
1292							$('#accessibility-links').removeClass('screenreader-open').addClass('screenreader-closed');
1293							$('#volume_plugin,#speaker_plugin,#toolbar_plugin').hide();
1294						} else {
1295							$('#accessibility-links').removeClass('screenreader-closed').addClass('screenreader-open');
1296						}
1297						$('#text_plugin span.fr_label').css({'cursor':'pointer'});
1298						$('#text_plugin').css({'cursor':'pointer'}).on('click', function(jqEvent){
1299							$('#volume_plugin,#speaker_plugin,#toolbar_plugin').fadeToggle();
1300							if($('#accessibility-links').hasClass('screenreader-open')) {
1301								$('#accessibility-links').removeClass('screenreader-open').addClass('screenreader-closed');
1302							} else {
1303								$('#accessibility-links').removeClass('screenreader-closed').addClass('screenreader-open');
1304							}
1305						});
1306					break;
1307				}
1308			} else {
1309				switch(screenReaderOptions.position) {
1310					case 'bottomright':
1311					case 'topright':
1312						$('#speaker_plugin, #volume_plugin').css({'opacity':1});
1313					break;
1314				}
1315			}
1316			
1317			// Manage the clone moving of controls to the bottom of the toolbar for the sidebar optimal displacement
1318			if(screenReaderOptions.template != 'accessible.css' && screenReaderOptions.templateOrientation == 'vertical') {
1319				var $originalTextPlugin = $('#text_plugin');
1320				var $originalVolumePlugin = $('#volume_plugin');
1321				var $clonedTextPlugin = $originalTextPlugin.clone(true, true);
1322				var $clonedVolumePlugin = $originalVolumePlugin.clone(true, true);
1323				var $targetSpeakerPlugin = $('#scr_clearer').before($clonedTextPlugin).before($clonedVolumePlugin);
1324				$originalTextPlugin.remove();
1325				$originalVolumePlugin.remove();
1326			}
1327		};
1328
1329		/**
1330		 * Start TTS engine
1331		 * 
1332		 * @access public
1333		 * @return Boolean
1334		 */
1335		this.startTTSEngine = function() {
1336			// Instance TTS
1337			frTTSEngineInstance = new jQuery.frTTSEngine(screenReaderOptions);  
1338			
1339			return true;
1340		};
1341		
1342		/**
1343		 * Add plugin buttons event listeners
1344		 * 
1345		 * @access private
1346		 * @return Void
1347		 */
1348		var addListeners = function() {
1349			// Register events for sizable
1350			$(document).on('click', '.sizable', function(jqEvent){
1351				var increment = parseInt($(this).data('value'));
1352				fontSizeCurrent = parseInt(fontSizeCurrent) + parseInt(increment * 5);
1353
1354			    if(fontSizeCurrent > screenReaderOptions.fontsizeMax){
1355			    	fontSizeCurrent = screenReaderOptions.fontsizeMax;
1356			    } else if (fontSizeCurrent < screenReaderOptions.fontsizeMin){
1357			    	fontSizeCurrent = screenReaderOptions.fontsizeMin;
1358			    }
1359			    
1360			    $('body').css('font-size', fontSizeCurrent + '%');
1361			    if(screenReaderOptions.fontsizeSelector) {
1362		    		// Manage header resize increment
1363		    		fontResizeHeaders();
1364		    	}
1365		    	screenReaderStorage.setItem('scr_fontsize', fontSizeCurrent);
1366		    	
1367		    	// Add extra control labels for the accessible template
1368		    	if(screenReaderOptions.template == 'accessible.css') {
1369		    		var currentCalculatedIncrement = parseInt(fontSizeCurrent - fontSizeDefault);
1370		    		var incrementSign = currentCalculatedIncrement > 0 ? '+' : '-';
1371		    		if(currentCalculatedIncrement != 0) {
1372		    			$('div.scfontsizelabel').remove();
1373		    			if(currentCalculatedIncrement > 0) {
1374		    				$('#fr_screenreader_font_increase').append('<div class="scfontsizelabel">' + incrementSign + currentCalculatedIncrement + '%</div>');
1375		    			} else {
1376		    				$('#fr_screenreader_font_decrease').append('<div class="scfontsizelabel">' + currentCalculatedIncrement + '%</div>');
1377		    			}
1378		    		} else {
1379		    			$('div.scfontsizelabel').remove();
1380		    		}
1381		    	}
1382			});
1383				
1384			// Register events for reset
1385			$(document).on('click', '.resizable', function(jqEvent){
1386				if(screenReaderOptions.resetButtonBehavior == 'all') {
1387					var resetHighContrast = !!parseInt(screenReaderStorage.getItem('scr_highcontrast')) || 0;
1388			    	if(resetHighContrast) {
1389			    		$('#fr_screenreader_highcontrast').trigger('click');
1390			    	} else {
1391			    		var resetHighContrastAlternate = parseInt(screenReaderStorage.getItem('scr_highcontrast_alternate')) || 0;
1392			    		if(resetHighContrastAlternate == 1) {
1393			    			$('#fr_screenreader_highcontrast2').trigger('click');
1394			    		}
1395			    		if(resetHighContrastAlternate == 2) {
1396			    			$('#fr_screenreader_highcontrast3').trigger('click');
1397			    		}
1398			    	}
1399			    	
1400			    	var resetDyslexicFont = !!parseInt(screenReaderStorage.getItem('scr_dyslexicfont')) || 0;
1401			    	if(resetDyslexicFont) {
1402			    		$('#fr_screenreader_font_family').trigger('click');
1403			    	}
1404			    	
1405			    	var resetGrayHues = !!parseInt(screenReaderStorage.getItem('scr_grayhues')) || 0;
1406			    	if(resetGrayHues) {
1407			    		$('#fr_screenreader_gray_hues').trigger('click');
1408			    	}
1409			    	
1410			    	var resetBigCursor = !!parseInt(screenReaderStorage.getItem('scr_bigcursor')) || 0;
1411			    	if(resetBigCursor) {
1412			    		$('#fr_screenreader_bigcursor').trigger('click');
1413			    	}
1414			    	
1415			    	var resetPageZoom = !!parseInt(screenReaderStorage.getItem('scr_zoomsize')) || 0;
1416			    	if(resetPageZoom) {
1417			    		$('#fr_screenreader_pagezoom_reset').trigger('click');
1418			    	}
1419				}
1420				
1421				// Remove extra control labels for the accessible template
1422				if(screenReaderOptions.template == 'accessible.css') {
1423					$('div.scfontsizelabel,div.scspacingsizelabel').remove();
1424				}
1425
1426				fontSizeCurrent = fontSizeDefault;
1427				$('body').css('font-size', fontSizeDefault + '%');
1428				
1429				// Reset even text spacing if any
1430				var resetSpacingSize = !!parseInt(screenReaderStorage.getItem('scr_spacingsize')) || !!parseInt(screenReaderStorage.getItem('scr_final_lineheight')) || 0;
1431				var resetSpacingSizeInline = '';
1432		    	if(resetSpacingSize) {
1433		    		$('body').css({'word-spacing':'inherit', 'letter-spacing':'inherit', 'line-height':screenReaderStorage.getItem('scr_initial_lineheight')});
1434		    		resetSpacingSizeInline = ';word-spacing:inherit;letter-spacing:inherit;line-height:inherit';
1435		    		screenReaderStorage.setItem('scr_spacingsize', 0);
1436		    		spacingSizeCurrent = 0;
1437		    		screenReaderStorage.removeItem('scr_final_lineheight');
1438		    	}
1439		    	
1440				if(screenReaderOptions.fontsizeSelector) {
1441		    		// Manage header resize increment
1442		    		fontResizeHeaders();
1443		    	}
1444		    	screenReaderStorage.setItem('scr_fontsize', fontSizeDefault);
1445			});
1446
1447			// Register events for zoomsizable
1448			$(document).on('click', '.zoomsizable', function(jqEvent){
1449				var increment = parseInt($(this).data('value'));
1450				zoomSizeCurrent = parseInt(zoomSizeCurrent) + parseInt(increment * 5);
1451
1452			    $('html').css('zoom', zoomSizeCurrent + '%');
1453		    	screenReaderStorage.setItem('scr_zoomsize', zoomSizeCurrent);
1454		    	
1455		    	// Add extra control labels for the accessible template
1456		    	if(screenReaderOptions.template == 'accessible.css') {
1457		    		var currentCalculatedIncrement = parseInt(zoomSizeCurrent - 100);
1458		    		var incrementSign = currentCalculatedIncrement > 0 ? '+' : '-';
1459		    		if(currentCalculatedIncrement != 0) {
1460		    			$('div.sczoomsizelabel').remove();
1461		    			if(currentCalculatedIncrement > 0) {
1462		    				$('#fr_screenreader_pagezoom_increase').append('<div class="sczoomsizelabel">' + incrementSign + currentCalculatedIncrement + '%</div>');
1463		    			} else {
1464		    				$('#fr_screenreader_pagezoom_decrease').append('<div class="sczoomsizelabel">' + currentCalculatedIncrement + '%</div>');
1465		    			}
1466		    		} else {
1467		    			$('div.sczoomsizelabel').remove();
1468		    		}
1469		    	}
1470			});
1471			
1472			// Register events for reset
1473			$(document).on('click', '.zoomresizable', function(jqEvent){
1474				// Remove extra control labels for the accessible template
1475				if(screenReaderOptions.template == 'accessible.css') {
1476					$('div.sczoomsizelabel').remove();
1477				}
1478
1479				zoomSizeCurrent = 100;
1480				$('html').css('zoom', 100 + '%');
1481				
1482		    	screenReaderStorage.removeItem('scr_zoomsize', 100);
1483			});
1484			
1485			// Register events for sizable
1486			$(document).on('click', '.spacingsizable', function(jqEvent){
1487				var increment = parseInt($(this).data('value'));
1488				var currentLineHeight = $('body').css('line-height');
1489				spacingSizeCurrent = parseInt(spacingSizeCurrent) + parseInt(increment);
1490				if(parseFloat(currentLineHeight)) {
1491					lineHeightCurrent = parseFloat(currentLineHeight) + parseInt(increment);
1492				} else {
1493					lineHeightCurrent = 16 + parseInt(increment);
1494				}
1495
1496				if(spacingSizeCurrent > screenReaderOptions.spacingSizeMax){
1497					spacingSizeCurrent = screenReaderOptions.spacingSizeMax;
1498					return;
1499			    } else if (spacingSizeCurrent < screenReaderOptions.spacingSizeMin){
1500			    	spacingSizeCurrent = screenReaderOptions.spacingSizeMin;
1501			    	return;
1502			    }
1503				
1504				// Store the initial line-height for later reset
1505				if(!screenReaderStorage.getItem('scr_initial_lineheight')) {
1506					screenReaderStorage.setItem('scr_initial_lineheight', currentLineHeight);
1507				}
1508
1509			    $('body').css({'word-spacing': (spacingSizeCurrent * 2) + 'px', 'letter-spacing': spacingSizeCurrent + 'px', 'line-height': lineHeightCurrent + 'px'});
1510		    	screenReaderStorage.setItem('scr_spacingsize', spacingSizeCurrent);
1511		    	screenReaderStorage.setItem('scr_final_lineheight', lineHeightCurrent);
1512		    	
1513		    	// Add extra control labels for the accessible template
1514		    	if(screenReaderOptions.template == 'accessible.css') {
1515	    			if(spacingSizeCurrent > 0) {
1516	    				$('#fr_screenreader_spacing_increase').append('<div class="scspacingsizelabel">+' + (spacingSizeCurrent * 10) + '%</div>');
1517	    			} else {
1518	    				$('div.scspacingsizelabel').remove();
1519	    			}
1520		    	}
1521			});
1522			
1523			// Register events for high contrast button
1524			$(document).on('click', '#fr_screenreader_highcontrast', function(jqEvent){
1525				highContrast = !!parseInt(screenReaderStorage.getItem('scr_highcontrast')) || 0;
1526				if(!highContrast) {
1527					if(highContrastAlternate) {
1528						// Recover original stylesheets
1529						targetFilterElement.attr('style', originalHTMLStyles);
1530						highContrastAlternate = 0;
1531						screenReaderStorage.setItem('scr_highcontrast_alternate', highContrastAlternate);
1532					}
1533					
1534					targetFilterElement.addClass('scr_highcontrast');  
1535					
1536					// If IE detected, fallback on the compatibility inversion color high contrast
1537					if("ActiveXObject" in window) {
1538						startLegacyHighcontrast('body, body *:not(#accessibility-links, #accessibility-links *, div.shapes)');
1539					}
1540					
1541					$('#fr_screenreader_highcontrast2,#fr_screenreader_highcontrast3').removeClass('active');
1542					$(this).addClass('active');
1543					highContrast = 1;
1544				} else {
1545					targetFilterElement.removeClass('scr_highcontrast');
1546					
1547					// If IE detected, fallback on the compatibility inversion color high contrast
1548					if("ActiveXObject" in window) {
1549						highContrast = 0;
1550						window.location.reload();
1551					}
1552					
1553					$(this).removeClass('active');
1554					highContrast = 0;
1555				}
1556				
1557		    	screenReaderStorage.setItem('scr_highcontrast', highContrast);
1558			});
1559			
1560			// Register events for dyslexic font
1561			$(document).on('click', '#fr_screenreader_font_family', function(jqEvent){
1562				dyslexicFont = !!parseInt(screenReaderStorage.getItem('scr_dyslexicfont')) || 0;
1563				if(!dyslexicFont) {
1564					$('body').addClass('scr_dyslexic');
1565					if(screenReaderOptions.fontsizeSelector) {
1566		    			// Append to the current inline style of the element
1567			    		var fontsizingElements = $(screenReaderOptions.fontsizeSelector);
1568			    		$.each(fontsizingElements, function(index, elem){
1569			    			var currentInlineStyles = $(elem).attr('style') || '';
1570			    			$(elem).attr('style', currentInlineStyles + ';font-family:OpenDyslexic');
1571			    		});
1572					}
1573					dyslexicFont = 1;
1574				} else {
1575					$('body').removeClass('scr_dyslexic');
1576					if(screenReaderOptions.fontsizeSelector) {
1577		    			// Append to the current inline style of the element
1578			    		var fontsizingElements = $(screenReaderOptions.fontsizeSelector);
1579			    		$.each(fontsizingElements, function(index, elem){
1580			    			var currentInlineStyles = $(elem).attr('style') || '';
1581			    			$(elem).attr('style', currentInlineStyles.replace(';font-family:OpenDyslexic',''));
1582			    		});
1583					}
1584					dyslexicFont = 0;
1585				}
1586				
1587		    	screenReaderStorage.setItem('scr_dyslexicfont', dyslexicFont);
1588			});
1589			
1590			// Register events for gray hues button
1591			$(document).on('click', '#fr_screenreader_gray_hues', function(jqEvent){
1592				grayHues = !!parseInt(screenReaderStorage.getItem('scr_grayhues')) || 0;
1593				if(!grayHues) {
1594					targetFilterElement.addClass('scr_grayhues');  
1595					
1596					grayHues = 1;
1597				} else {
1598					targetFilterElement.removeClass('scr_grayhues');
1599					
1600					grayHues = 0;
1601				}
1602				
1603		    	screenReaderStorage.setItem('scr_grayhues', grayHues);
1604			});
1605			
1606			// Register events for big cursor button
1607			$(document).on('click', '#fr_screenreader_bigcursor', function(jqEvent){
1608				bigCursor = !!parseInt(screenReaderStorage.getItem('scr_bigcursor')) || 0;
1609				if(!bigCursor) {
1610					$('body').addClass('scr_bigcursor');  
1611					
1612					bigCursor = 1;
1613				} else {
1614					$('body').removeClass('scr_bigcursor');
1615					
1616					bigCursor = 0;
1617				}
1618				
1619		    	screenReaderStorage.setItem('scr_bigcursor', bigCursor);
1620			});
1621			
1622			// Register read guides
1623			$(document).on('click', '#fr_screenreader_reading_guides', function(jqEvent, onlyTrigger){
1624				var updateReadGuide = function(event) {
1625                    var topOffset = 0;
1626                    topOffset = ("touchmove" == event.type) ? event.changedTouches[0].clientY : event.y;
1627                    $("#sc_read_guide_bar").css('top', topOffset - 15);
1628                }
1629				
1630				readingGuides = !!parseInt(screenReaderStorage.getItem('scr_reading_guides')) || 0;
1631				if(!readingGuides || onlyTrigger) {
1632					$('body').addClass('scr_reading_guides');  
1633					
1634					var readingGuideElement = document.createElement("div");
1635					readingGuideElement.id = "sc_read_guide_bar";
1636					readingGuideElement.classList.add("sc_read_guide_bar");
1637					$('body').append(readingGuideElement);
1638			        document.body.addEventListener("touchmove", updateReadGuide, false),
1639			        document.body.addEventListener("mousemove", updateReadGuide, false);
1640					    
1641					readingGuides = 1;
1642				} else {
1643					$('body').removeClass('scr_reading_guides');
1644					
1645					$('#sc_read_guide_bar').remove();
1646					
1647					document.body.removeEventListener("touchmove", updateReadGuide, false);
1648			        document.body.removeEventListener("mousemove", updateReadGuide, false)
1649			        
1650					readingGuides = 0;
1651				}
1652				
1653		    	screenReaderStorage.setItem('scr_reading_guides', readingGuides);
1654			});
1655			
1656			// Register read mode
1657			$(document).on('click', '#fr_screenreader_readability', function(jqEvent){
1658				if(!readabilityMode) {
1659					originalHtml = $('body').clone();
1660					
1661					$('body').addClass('scr_readability');  
1662					
1663					var contentToRead = $(screenReaderOptions.readabilitySelector).html();
1664
1665					// Only if some contents to transform are found, then go on
1666					if(contentToRead) {
1667						$('body').empty();
1668						$('body').append('<div class="sc-readability">' + contentToRead + '</div>');
1669					}
1670
1671					readabilityMode = 1;
1672				} else {
1673					$('body').removeClass('scr_readability');
1674					
1675					$('div.sc-readability').remove();
1676					$('body').replaceWith(originalHtml[0]);
1677					
1678					// Reassign - rebind the body element to the local private variable
1679					targetFilterElement = $('body');
1680					
1681					readabilityMode = 0;
1682				}
1683			});
1684			
1685			// Register hide images
1686			$(document).on('click', '#fr_screenreader_hideimages', function(jqEvent, onlyTrigger){
1687				hideImages = !!parseInt(screenReaderStorage.getItem('scr_hideimages')) || 0;
1688				hideAlsoVideosIFrames = screenReaderOptions.hideAlsoVideosIframes;
1689				
1690				if(!hideImages || onlyTrigger) {
1691					$('body').addClass('scr_hideimages');
1692					if(hideAlsoVideosIFrames) {
1693						$('body').addClass('scr_hidevideosiframes');
1694					}
1695					hideImages = 1;
1696				} else {
1697					$('body').removeClass('scr_hideimages');
1698					if(hideAlsoVideosIFrames) {
1699						$('body').removeClass('scr_hidevideosiframes');
1700					}
1701					hideImages = 0;
1702				}
1703				
1704		    	screenReaderStorage.setItem('scr_hideimages', hideImages);
1705			});
1706			
1707			// Register events for alternate high contrast buttons, use only native CSS3 filters if available
1708			$(document).on('click', '#fr_screenreader_highcontrast2, #fr_screenreader_highcontrast3', applyAlternateHighContrast);
1709
1710			// Register events for skip to contents button
1711			if(screenReaderOptions.showSkipToContents) {
1712				$(document).on('click', '#fr_screenreader_skiptocontents', skipToContents);
1713			}
1714		};
1715		
1716		// Simple hash function (djb2 variant)
1717		var hashCode = function (str) {
1718		    let hash = 5381;
1719		    for (let i = 0; i < str.length; i++) {
1720		        hash = ((hash << 5) + hash) + str.charCodeAt(i); // hash * 33 + c
1721		    }
1722		    // Convert to unsigned 32-bit integer and then to a base-36 string.
1723		    return (hash >>> 0).toString(36);
1724		};
1725		
1726		/**
1727		 * Accessibility enhancement fix
1728		 * Sub-Function to call OpenAI API for alt text generation
1729		 * 
1730		 * @access private
1731		 * @return Void
1732		 */
1733		var generateAltTextAI = async function (imageUrl) {
1734			const cacheMappingKey = "screenReaderAltMapping";
1735		    
1736		    // Retrieve the current mapping from local storage, or use an empty object.
1737		    let mapping = {};
1738		    try {
1739		        mapping = JSON.parse(localStorage.getItem(cacheMappingKey)) || {};
1740		    } catch (e) {
1741		        mapping = {};
1742		    }
1743		    
1744		    // Generate a hash key for the image URL.
1745		    const key = hashCode(imageUrl);
1746		    
1747		    // If a cached alt text exists, return it.
1748		    if (mapping[key]) {
1749		        return mapping[key];
1750		    }
1751		    
1752			const OPENAI_API_KEY = screenReaderOptions.generateMissingImagesAltChatgptApikey; 
1753			if(!OPENAI_API_KEY.trim()) {
1754				return '';
1755			}
1756			
1757		    const prompt = `Describe the content of this image in a short, clear sentence using the language matching the code '${screenReaderConfigOptions.langCode}': ${imageUrl}`;
1758		    const response = await fetch("https://api.openai.com/v1/chat/completions", {
1759		        method: "POST",
1760		        headers: {
1761		            "Content-Type": "application/json",
1762		            "Authorization": `Bearer ${OPENAI_API_KEY}`
1763		        },
1764		        body: JSON.stringify({
1765		            model: screenReaderOptions.generateMissingImagesAltChatgptModel,
1766		            messages: [{ role: "user", content: prompt }],
1767		            max_tokens: 50
1768		        })
1769		    });
1770
1771		    if (!response.ok) {
1772		    	return '';
1773		    }
1774		    
1775		    const data = await response.json();
1776			const altText = data.choices[0].message.content.trim();
1777	        
1778	        // Save the generated alt text in our mapping and update local storage.
1779	        mapping[key] = altText;
1780	        localStorage.setItem(cacheMappingKey, JSON.stringify(mapping));
1781	        
1782	        return altText;
1783		};
1784		
1785		/**
1786		 * Accessibility enhancement fix
1787		 * Function to fix missing alt attributes for images using ChatGPT
1788		 * 
1789		 * @access private
1790		 * @return Void
1791		 */
1792		async function fixMissingAltText() {
1793		    const images = document.querySelectorAll('img:not([alt]), img[alt=""]');
1794		    for (const img of images) {
1795		        try {
1796		            const altText = await generateAltTextAI(img.src);
1797		            img.setAttribute('alt', altText);
1798		        } catch (error) {
1799		        }
1800		    }
1801		};
1802		
1803		/**
1804		 * Accessibility enhancement fix
1805		 * Function to automatically fix heading structure issues
1806		 * 
1807		 * @access private
1808		 * @return Void
1809		 */
1810		var fixHeadingStructure = function() {
1811		    const headings = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
1812		    let lastValidLevel = 1; // Start with H1 as the base
1813
1814		    headings.forEach((heading, index) => {
1815		        let currentLevel = parseInt(heading.tagName.replace('H', ''), 10);
1816
1817		        if (index === 0 && currentLevel !== 1) {
1818		            currentLevel = 1;
1819		        } else if (currentLevel > lastValidLevel + 1) {
1820		            currentLevel = lastValidLevel + 1; // Correct the level
1821		        }
1822
1823		        if (currentLevel !== parseInt(heading.tagName.replace('H', ''), 10)) {
1824		            const newHeading = document.createElement(`h${currentLevel}`);
1825		            
1826		            // Preserve attributes, styles, and classes
1827		            Array.from(heading.attributes).forEach(attr => {
1828		                newHeading.setAttribute(attr.name, attr.value);
1829		            });
1830
1831		            newHeading.innerHTML = heading.innerHTML;
1832		            heading.replaceWith(newHeading);
1833		        }
1834
1835		        lastValidLevel = currentLevel;
1836		    });
1837		};
1838		
1839		/**
1840		 * Accessibility enhancement fix
1841		 * Sub-Function to check if contrast is sufficient
1842		 * 
1843		 * @access private
1844		 * @return Void
1845		 */
1846		var isContrastSufficient = function(bgColor, textColor) {
1847		    // Compute relative luminance for an RGB color
1848		    function luminance(r, g, b) {
1849		        const a = [r, g, b].map(v => {
1850		            v /= 255;
1851		            return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
1852		        });
1853		        return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
1854		    }
1855
1856		    // Extracts the numeric RGB values from a color string like "rgb(255, 255, 255)"
1857		    function getRGB(color) {
1858		        return color.match(/\d+/g).map(Number);
1859		    }
1860
1861		    const [r1, g1, b1] = getRGB(bgColor);
1862		    const [r2, g2, b2] = getRGB(textColor);
1863
1864		    const lum1 = luminance(r1, g1, b1);
1865		    const lum2 = luminance(r2, g2, b2);
1866
1867		    // Determine the lighter and darker luminance values
1868		    const lighter = Math.max(lum1, lum2);
1869		    const darker = Math.min(lum1, lum2);
1870
1871		    const contrastRatio = (lighter + 0.05) / (darker + 0.05);
1872
1873		    return contrastRatio >= 4.5; // WCAG standard for normal text
1874		};
1875		
1876		/**
1877		 * Accessibility enhancement fix
1878		 * Function to check and fix low contrast text
1879		 * 
1880		 * @access private
1881		 * @return Void
1882		 */
1883		var fixLowContrastText = function () {
1884		    const textElements = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, span, div, a, li, td, th, label, button');
1885
1886		    textElements.forEach((el) => {
1887				if (el.classList.contains('screenreader_embed_play_button')) {
1888		            return; // Skip this element
1889		        }
1890
1891		        const bgColor = window.getComputedStyle(el).backgroundColor;
1892		        const textColor = window.getComputedStyle(el).color;
1893
1894		        if (!isContrastSufficient(bgColor, textColor)) {
1895		            el.style.color = "#000"; // Adjust to black for better contrast
1896		        }
1897		    });
1898		};
1899		
1900		/**
1901		 * Accessibility enhancement fix
1902		 * Function to add role="main" to the main element if missing
1903		 * 
1904		 * @access private
1905		 * @return Void
1906		 */
1907		var fixMissingAriaRoles = function () {
1908		  // Add role="navigation" to <nav> elements if missing
1909		  document.querySelectorAll('nav:not([role])').forEach(el => {
1910			  el.setAttribute('role', 'navigation');
1911		  });
1912
1913		  // Add role="main" to <main> elements if missing
1914		  const mainEl = document.querySelector('main:not([role]),#content:not([role])');
1915		  if (mainEl) {
1916			  	mainEl.setAttribute('role', 'main');
1917		  } else {
1918			  	// As a fallback, use the skip to contents selector
1919	    		const customMainContent = $(screenReaderOptions.skipToContentsSelector);
1920	    		if(customMainContent.length && !customMainContent.get(0).hasAttribute('role')) {
1921	    			customMainContent.get(0).setAttribute('role', 'main');
1922	    		}
1923		  	}
1924		};
1925		
1926		/**
1927		 * Accessibility enhancement fix
1928		 * Function to add an aria-label if missing
1929		 * 
1930		 * @access private
1931		 * @return Void
1932		 */
1933		var fixMissingFormLabels = function () {
1934			  const inputs = document.querySelectorAll('input, textarea, select');
1935			  
1936			  inputs.forEach(input => {
1937				  // Determine if a label exists by checking for an associated
1938				  // <label> or if the input is wrapped in one.
1939				  const id = input.getAttribute('id');
1940				  const hasAssociatedLabel = id ? document.querySelector(`label[for="${id}"]`) : null;
1941			    
1942				  if (!hasAssociatedLabel && !input.closest('label')) {
1943					  // Use the placeholder as a hint, if available.
1944					  const ariaText = input.getAttribute('placeholder') || 'Input field';
1945					  input.setAttribute('aria-label', ariaText);
1946				  }
1947			});
1948		};
1949		
1950		/**
1951		 * Accessibility enhancement fix
1952		 * Reorder and fix the sequence of tabindex for elements
1953		 * 
1954		 * @access private
1955		 * @return Void
1956		 */
1957		var validateAndFixFocusOrder = function () {
1958			  // Define a selector for common interactive elements.
1959			  const focusableSelectors = `
1960			    a[href],
1961			    area[href],
1962			    input:not([disabled]):not([type="hidden"]),
1963			    select:not([disabled]),
1964			    textarea:not([disabled]),
1965			    button:not([disabled]),
1966			    iframe,
1967			    [tabindex]:not([tabindex="-1"]),
1968			    [contenteditable="true"]
1969			  `;
1970
1971			  // Get all matching elements in document order
1972			  let focusableElements = Array.from(document.querySelectorAll(focusableSelectors));
1973
1974			  // Filter out elements that are not visible (e.g., display:none)
1975			  focusableElements = focusableElements.filter(el => {
1976			    const style = window.getComputedStyle(el);
1977			    return style.display !== "none" && style.visibility !== "hidden";
1978			  });
1979
1980			  // Reassign tabindex values sequentially, starting at 1.
1981			  // This forces the focus order to follow the DOM order.
1982			  focusableElements.forEach((el, index) => {
1983				  // Set both the property and attribute to ensure compatibility.
1984				  const newTabIndex = index + 1;
1985				  el.tabIndex = newTabIndex;
1986				  el.setAttribute("tabindex", newTabIndex);
1987			  });
1988		};
1989		
1990		/**
1991		 * Run the accessibility fixes
1992		 * 
1993		 * @access private
1994		 * @return Void
1995		 */
1996		var autoFixAccessibility = function () {
1997		    // Fix missing alt attributes in images
1998			if(screenReaderOptions.generateMissingImagesAlt) {
1999				fixMissingAltText();
2000			}
2001
2002		    // Automatically fix heading structure issues
2003			if(screenReaderOptions.fixHeadingsStructure) {
2004				fixHeadingStructure();
2005			}
2006
2007		    // Check and fix low contrast text
2008			if(screenReaderOptions.fixLowContrastText) {
2009				fixLowContrastText();
2010			}
2011		    
2012		    // Add missing aria role attributes
2013			if(screenReaderOptions.fixMissingAriaRoles) {
2014				fixMissingAriaRoles();
2015			}
2016		    
2017		    // Add an aria-label if missing
2018			if(screenReaderOptions.fixMissingFormLabels) {
2019				fixMissingFormLabels();
2020			}
2021		    
2022		    // Fix tabindex order
2023			if(screenReaderOptions.validateAndFixFocusOrder) {
2024				validateAndFixFocusOrder();
2025			}
2026		};
2027		
2028		/**
2029		 * Function dummy constructor
2030		 * 
2031		 * @access private
2032		 * @param String
2033		 *            contextSelector
2034		 * @method <<IIFE>>
2035		 * @return Void
2036		 */
2037		(function __construct() {
2038			// Compatibility language translations fallback
2039			if(typeof(fr_close_toolbar) === 'undefined') {
2040				window.fr_close_toolbar="Close accessibility interface";
2041				window.fr_text_reader="Text Reader";
2042				window.fr_font_sizing="Font sizing";
2043				window.fr_accessibility_enhancements="Accessibility enhancements";
2044				window.fr_high_contrast_color="High contrast color";
2045				window.fr_reading_guides_title="Reading guide";
2046			}
2047			if(typeof(fr_dark_mode) === 'undefined') {
2048				window.fr_volume = 'Volume';
2049				window.fr_dark_mode = 'Toggle dark mode';
2050				window.fr_page_zoom="Page zoom"
2051				window.fr_pagezoom_increase="Increase zoom";
2052				window.fr_pagezoom_decrease="Decrease zoom";
2053				window.fr_pagezoom_reset="Reset zoom";
2054			}
2055			
2056			// Init status on load
2057			$.extend(screenReaderOptions, options);
2058			
2059			// Set the preferred storage
2060			if(screenReaderOptions.selectedStorage == 'session') {
2061				screenReaderStorage = window.sessionStorage;
2062			} else if(screenReaderOptions.selectedStorage == 'local') {
2063				screenReaderStorage = window.localStorage;
2064			}
2065			
2066			// Init target filter element
2067			if(screenReaderOptions.rootTarget) {
2068	    		targetFilterElement = $('html');
2069	    	}
2070			
2071			// ReInject if overridden
2072			if(jQuery.frTTSEngine === undefined) {
2073				jQuery('script[src*="tts.js"]').clone().appendTo('body');
2074			}
2075			
2076			// Ensure that the same background color is applied to both the body and html tag
2077			if(screenReaderOptions.autoBackgroundColor) {
2078				var bodyBGColor = $('body').css('background-color');
2079				$('html').css('background-color', bodyBGColor);
2080			}
2081
2082			// Init font sizing
2083			fontSizeDefault = fontSizeCurrent = screenReaderOptions.fontsizeDefault;
2084			var sessionFontSizeCurrent = screenReaderStorage.getItem('scr_fontsize');
2085			if(sessionFontSizeCurrent) {
2086				fontSizeCurrent = sessionFontSizeCurrent;
2087			}
2088			
2089			// Init zoom size
2090			var sessionZoomSizeCurrent = screenReaderStorage.getItem('scr_zoomsize');
2091			if(sessionZoomSizeCurrent) {
2092				zoomSizeCurrent = sessionZoomSizeCurrent;
2093				$('html').css('zoom', zoomSizeCurrent + '%');
2094			}
2095			
2096			// Only if spacing size is enabled
2097			var spacingSizeInline = '';
2098			if(screenReaderOptions.spacingSize) {
2099				spacingSizeCurrent = parseInt(screenReaderStorage.getItem('scr_spacingsize')) || 0;
2100				if(spacingSizeCurrent > 0) {
2101					$('body').css({'word-spacing': (spacingSizeCurrent * 2) + 'px', 'letter-spacing': spacingSizeCurrent + 'px'});  
2102					
2103				}
2104				
2105				lineHeightCurrent = parseFloat(screenReaderStorage.getItem('scr_final_lineheight')) || 0;
2106				if(lineHeightCurrent > 0) {
2107					$('body').css({'line-height': lineHeightCurrent + 'px'});  
2108					
2109				}
2110				if(screenReaderOptions.fontsizeSelector) {
2111					// Append to the current inline style of the element
2112					spacingSizeInline = ';word-spacing:inherit;letter-spacing:inherit;line-height:inherit';
2113				}
2114			}
2115		    
2116			// Only if font sizing is enabled
2117			var fontSizeInline = '';
2118			var isfontsizeMinimizedToolbar = !!screenReaderConfigOptions.useMinimizedToolbar && !!screenReaderOptions.fontsizeMinimizedToolbar && screenReaderConfigOptions.statusMinimizedToolbar == 'closed';
2119			if(screenReaderOptions.fontsize && !isfontsizeMinimizedToolbar) {
2120				$('body').css('font-size', fontSizeCurrent + '%');
2121				
2122				if(screenReaderOptions.fontsizeSelector) {
2123					fontSizeInline = ';font-size:100%';
2124					// Manage header resize increment
2125					fontResizeHeaders();
2126				}
2127			}
2128			
2129			// Append to the current inline style of the element
2130			if(screenReaderOptions.fontsize || screenReaderOptions.spacingSize) {
2131				var fontsizingElements = $(screenReaderOptions.fontsizeSelector);
2132				$.each(fontsizingElements, function(index, elem) {
2133					if($.inArray(elem.nodeName.toLowerCase(), ['h1', 'h2', 'h3', 'h4']) != -1 || isfontsizeMinimizedToolbar) {
2134						fontSizeInline = '';
2135					} else {
2136						fontSizeInline = ';font-size:100%';
2137					}
2138					var currentInlineStyles = $(elem).attr('style') || '';
2139					$(elem).attr('style', rtrim(currentInlineStyles, ';') + fontSizeInline + spacingSizeInline);
2140				});
2141			}
2142			
2143	    	// Init high contrast
2144	    	highContrast = !!parseInt(screenReaderStorage.getItem('scr_highcontrast')) || 0;
2145	    	if(highContrast) {
2146	    		targetFilterElement.addClass('scr_highcontrast');
2147	    		
2148	    		// If IE detected, fallback on the compatibility inversion color high contrast
2149				if("ActiveXObject" in window) {
2150					startLegacyHighcontrast('body, body *:not(#accessibility-links, #accessibility-links *, div.shapes)');
2151				}
2152	    	}
2153
2154	    	// Init dyslexic font
2155	    	dyslexicFont = !!parseInt(screenReaderStorage.getItem('scr_dyslexicfont')) || 0;
2156	    	if(dyslexicFont) {
2157	    		$('body').addClass('scr_dyslexic');
2158	    		if(screenReaderOptions.fontsizeSelector) {
2159	    			// Append to the current inline style of the element
2160		    		var fontsizingElements = $(screenReaderOptions.fontsizeSelector);
2161		    		$.each(fontsizingElements, function(index, elem){
2162		    			var currentInlineStyles = $(elem).attr('style') || '';
2163		    			$(elem).attr('style', currentInlineStyles + ';font-family:OpenDyslexic');
2164		    		});
2165				}
2166	    	}
2167	    	
2168	    	// Init gray hues
2169	    	grayHues = !!parseInt(screenReaderStorage.getItem('scr_grayhues')) || 0;
2170	    	if(grayHues) {
2171	    		targetFilterElement.addClass('scr_grayhues');
2172	    	}
2173	    	
2174	    	// Init big cursor
2175	    	bigCursor = !!parseInt(screenReaderStorage.getItem('scr_bigcursor')) || 0;
2176	    	if(bigCursor) {
2177	    		$('body').addClass('scr_bigcursor');
2178	    	}
2179	    	
2180	    	// Init custom text color
2181	    	customBackgroundColor = screenReaderStorage.getItem('scr_custom_background_color');
2182	    	if(screenReaderOptions.customColors && screenReaderOptions.template == 'accessible.css' && customBackgroundColor) {
2183	    		document.querySelector('body').style.setProperty('background', customBackgroundColor, 'important');
2184	    		if(screenReaderOptions.customColorsCssSelectors) {
2185					$(screenReaderOptions.customColorsCssSelectors).each(function(index, element){
2186						element.style.setProperty('background', customBackgroundColor, 'important');
2187					});
2188				}
2189	    		setTimeout(function(){
2190	    			$('#fr_screenreader_background_color span.scr-color-selection[data-scr-color="' + customBackgroundColor + '"]').addClass('scr-color-selection-enabled');
2191	    		}, 1);
2192	    	}
2193	    	
2194	    	// Init custom background color
2195	    	customTextColor = screenReaderStorage.getItem('scr_custom_text_color');
2196	    	if(screenReaderOptions.customColors && screenReaderOptions.template == 'accessible.css' && customTextColor) {
2197	    		document.querySelector('body').style.setProperty('color', customTextColor, 'important');
2198	    		if(screenReaderOptions.customColorsCssSelectors) {
2199					$(screenReaderOptions.customColorsCssSelectors).each(function(index, element){
2200						element.style.setProperty('color', customTextColor, 'important');
2201					});
2202				}
2203	    		setTimeout(function(){
2204	    			$('#fr_screenreader_text_color span.scr-color-selection[data-scr-color="' + customTextColor + '"]').addClass('scr-color-selection-enabled');
2205	    		}, 1);
2206	    	}
2207	    	
2208			// Remove links target if enabled
2209			if(screenReaderOptions.removeLinksTarget) {
2210				$('a').removeAttr('target');
2211			};
2212			
2213			// Add listeners for UI
2214			addListeners();
2215			
2216			// Check if AI based auto-fixing accessibility issue is enabled and should run
2217			autoFixAccessibility();
2218		}).call(this);
2219	};
2220		
2221	//On DOM Ready
2222	$(function() {
2223		function scrDetectMobile() {
2224			var windowOnTouch = !!('ontouchstart' in window);
2225			var msTouch = !!(navigator.msMaxTouchPoints);
2226			var hasTouch = windowOnTouch || msTouch;
2227			return hasTouch;
2228		}
2229		var isMobile = scrDetectMobile();
2230		
2231		if(screenReaderConfigOptions.hideOnMobile && isMobile) {
2232			return;
2233		}
2234		
2235		if(isMobile && screenReaderConfigOptions.useMobileReaderEngine) {
2236			if(screenReaderConfigOptions.readerEngine != screenReaderConfigOptions.mobileReaderEngine) {
2237				// Override if different
2238				screenReaderConfigOptions.readerEngine = screenReaderConfigOptions.mobileReaderEngine;
2239			}
2240			
2241			if(screenReaderConfigOptions.readerEngine == 'proxy' && parseInt(screenReaderConfigOptions.chunkLength) > 100) {
2242				screenReaderConfigOptions.chunkLength = 90;
2243			}
2244			if(screenReaderConfigOptions.readerEngine == 'proxy_virtual' && parseInt(screenReaderConfigOptions.chunkLength) >= 300) {
2245				screenReaderConfigOptions.chunkLength = 280;
2246			}
2247		}
2248		
2249		screenReaderConfigOptions.mobileDeviceDetected = isMobile ? true : false;
2250		
2251		// Instance controller
2252		window.ScreenReaderMainController = new MainController(screenReaderConfigOptions);
2253		
2254		// Build user interface
2255		ScreenReaderMainController.buildInterface();
2256		
2257		// Start engine once interface rendering has been completed
2258		ScreenReaderMainController.startTTSEngine();
2259	});
2260})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.