PageSourceSearch

https://scwmri.ac.ir/assets/27ff4590/js/AccessibilityWidget.js?v=1785925960

js scwmri.ac.ir collected 2026-10-02 22:50:51 UTC 40,944 bytes, 1,372 lines download raw bytes

1const textColorPicker = document.getElementById('text-color-picker')
2const backgroundColorPicker = document.getElementById('background-color-picker')
3const fontSizeIncreaseBtn = document.getElementById('font-size-increase')
4const fontSizeDecreaseBtn = document.getElementById('font-size-decrease')
5const hideImagesEnable = document.querySelector(
6	'#settings-hide-images input[value="enable"]',
7)
8const hideImagesDisable = document.querySelector(
9	'#settings-hide-images input[value="disable"]',
10)
11const resetHideImagesBtn = document.getElementById('reset-hide-images')
12const wordSpacingIncreaseBtn = document.getElementById('word-spacing-increase')
13const wordSpacingDecreaseBtn = document.getElementById('word-spacing-decrease')
14const lineHeightIncreaseBtn = document.getElementById('line-height-increase')
15const highlightLinksEnable = document.querySelector(
16	'#settings-highlight-links input[value="enable"]',
17)
18const readingModeEnable = document.getElementById('reading-mode-enable')
19const readingModeDisable = document.getElementById('reading-mode-disable')
20const resetReadingModeBtn = document.getElementById('reset-reading-mode')
21const readingFontIncrease = document.getElementById('reading-font-increase')
22const readingFontDecrease = document.getElementById('reading-font-decrease')
23const readingFontSizeValue = document.getElementById('reading-font-size-value')
24const readingBgSwatches = document.querySelectorAll('.reading-bg-swatch')
25
26let readingFontSize =
27	parseFloat(localStorage.getItem('a11yReadingFontSize')) || 1.125
28const highlightLinksDisable = document.querySelector(
29	'#settings-highlight-links input[value="disable"]',
30)
31const resetHighlightLinksBtn = document.getElementById('reset-highlight-links')
32const lineHeightDecreaseBtn = document.getElementById('line-height-decrease')
33const fontFamilySelect = document.getElementById('font-family-select')
34const colorBlindnessSelect = document.getElementById('color-blindness-select')
35// const cursorSizeSmallBtn = document.getElementById('cursor-size-small')
36// const cursorSizeMediumBtn = document.getElementById('cursor-size-medium')
37// const cursorSizeLargeBtn = document.getElementById('cursor-size-large')
38const accessibilityToggle = document.getElementById('accessibility-toggle')
39const accessibilityWidget = document.getElementById('accessibility-widget')
40const accessibilityClose = document.getElementById('accessibility-close')
41const resetButton = document.getElementById('reset-button')
42
43const resetTextColorBtn = document.getElementById('reset-text-color')
44const resetBackgroundColorBtn = document.getElementById(
45	'reset-background-color',
46)
47const resetFontSizeBtn = document.getElementById('reset-font-size')
48const resetWordSpacingBtn = document.getElementById('reset-word-spacing')
49const resetLineHeightBtn = document.getElementById('reset-line-height')
50// const resetCursorSizeBtn = document.getElementById('reset-cursor-size')
51const resetFontFamilyBtn = document.getElementById('reset-font-family')
52const resetColorBlindnessBtn = document.getElementById('reset-color-blindness')
53const resetContrastBtn = document.getElementById('reset-contrast')
54const resetReduceMotionBtn = document.getElementById('reset-reduce-motion')
55
56const menuButtons = document.querySelectorAll('.menu-button')
57const backButtons = document.querySelectorAll('.back-button')
58const mainMenu = document.getElementById('main-menu')
59
60const contrastButtons = {
61	normal: document.getElementById('contrast-normal'),
62	high: document.getElementById('contrast-high'),
63	inverse: document.getElementById('contrast-inverse'),
64}
65const reduceMotionEnable = document.getElementById('reduce-motion-enable')
66const reduceMotionDisable = document.getElementById('reduce-motion-disable')
67
68// Display spans for "current value"
69const textColorValue = document.getElementById('text-color-value')
70const backgroundColorValue = document.getElementById('background-color-value')
71const fontSizeValue = document.getElementById('font-size-value')
72const wordSpacingValue = document.getElementById('word-spacing-value')
73const lineHeightValue = document.getElementById('line-height-value')
74// const cursorSizeValue = document.getElementById('cursor-size-value')
75const fontFamilyValue = document.getElementById('font-family-value')
76const colorBlindnessValue = document.getElementById('color-blindness-value')
77const contrastValue = document.getElementById('contrast-value')
78const reduceMotionValue = document.getElementById('reduce-motion-value')
79
80// Initial empty values (will keep the site as-is unless changed)
81let fontSize = isNaN(parseInt(getSettings('a11yFontSize')))
82	? null
83	: parseInt(getSettings('a11yFontSize'))
84let wordSpacing = isNaN(parseInt(getSettings('a11yWordSpacing')))
85	? null
86	: parseInt(getSettings('a11yWordSpacing'))
87let lineHeight = isNaN(parseFloat(getSettings('a11yLineHeight')))
88	? null
89	: parseFloat(getSettings('a11yLineHeight'))
90// let cursorSize = null
91
92// Cache all elements except those inside the widget and toggle button
93const allElements = document.querySelectorAll(
94	'body *:not(#accessibility-widget):not(#accessibility-widget *):not(#accessibility-toggle):not(#accessibility-toggle *)',
95)
96const allLinks = document.querySelectorAll(
97	'body a:not(#accessibility-widget a):not(#accessibility-widget * a):not(#accessibility-toggle):not(#accessibility-toggle *)',
98)
99
100function resetHighlightLinks() {
101	localStorage.removeItem('a11yHighlightLinks')
102	document.body.classList.remove('a11y-highlight-links')
103	// Reset radio buttons
104	if (highlightLinksEnable) highlightLinksEnable.checked = false
105	if (highlightLinksDisable) highlightLinksDisable.checked = false
106}
107
108// Get elements with background colors
109function getAllElementsWithBackgroundColor() {
110	const elements = document.querySelectorAll(
111		'body *:not(#accessibility-widget):not(#accessibility-widget *):not(#accessibility-toggle):not(#accessibility-toggle *):not(header):not(header *):not(footer):not(footer *)',
112	)
113	const elementsWithBackground = []
114	for (let el of elements) {
115		const bgColor = window
116			.getComputedStyle(el)
117			.getPropertyValue('background-color')
118		if (
119			bgColor &&
120			bgColor !== 'rgba(0, 0, 0, 0)' &&
121			bgColor !== 'transparent'
122		) {
123			elementsWithBackground.push(el)
124		}
125	}
126	return elementsWithBackground
127}
128
129let elementsWithBackground = getAllElementsWithBackgroundColor()
130
131function saveSettings(key, value) {
132	localStorage.setItem(key, value)
133}
134
135function getSettings(key) {
136	return localStorage.getItem(key)
137}
138
139function resetTextColor() {
140	localStorage.removeItem('a11yTextColor')
141	for (let el of allElements) {
142		el.style.removeProperty('color')
143	}
144	updateTextColorValue()
145}
146
147function resetBackgroundColor() {
148	localStorage.removeItem('a11yBackgroundColor')
149	document.body.style.removeProperty('background-color')
150	for (let el of elementsWithBackground) {
151		el.style.removeProperty('background-color')
152	}
153	// Re-scan possible background elements in case user changed them
154	elementsWithBackground = getAllElementsWithBackgroundColor()
155	updateBackgroundColorValue()
156}
157
158function resetFontSize() {
159	localStorage.removeItem('a11yFontSize')
160	for (let el of allElements) {
161		el.style.removeProperty('font-size')
162	}
163	fontSize = null
164	updateFontSizeValue()
165}
166
167function resetWordSpacing() {
168	localStorage.removeItem('a11yWordSpacing')
169	for (let el of allElements) {
170		el.style.removeProperty('word-spacing')
171	}
172	wordSpacing = null
173	updateWordSpacingValue()
174}
175
176function resetLineHeight() {
177	localStorage.removeItem('a11yLineHeight')
178	for (let el of allElements) {
179		el.style.removeProperty('line-height')
180	}
181	lineHeight = null
182	updateLineHeightValue()
183}
184
185// function resetCursorSize() {
186//   localStorage.removeItem('a11yCursorSize')
187//   document.documentElement.style.removeProperty('--custom-cursor')
188//   document.documentElement.style.removeProperty('--custom-pointer-cursor')
189//   document.documentElement.style.removeProperty('--custom-text-cursor')
190//   cursorSize = null
191//   updateCursorSizeValue()
192// }
193
194function resetFontFamily() {
195	localStorage.removeItem('a11yFontFamily')
196	for (let el of allElements) {
197		el.style.removeProperty('font-family')
198	}
199	fontFamilySelect.value = ''
200	// updateFontFamilyValue()
201}
202
203function resetColorBlindness() {
204	localStorage.removeItem('a11yColorBlindness')
205	// remove filter from content-wrapper / footer
206	const cw = document.getElementById('content-wrapper')
207	if (cw) cw.style.removeProperty('filter')
208	const ft = document.querySelector('footer')
209	if (ft) ft.style.removeProperty('filter')
210
211	colorBlindnessSelect.value = ''
212	// updateColorBlindnessValue()
213}
214
215function resetContrast() {
216	localStorage.removeItem('a11yContrast')
217	const cw = document.getElementById('content-wrapper')
218	if (cw) cw.style.removeProperty('filter')
219	const ft = document.querySelector('footer')
220	if (ft) ft.style.removeProperty('filter')
221
222	// updateContrastValue()
223}
224
225function resetReduceMotion() {
226	localStorage.removeItem('a11yReduceMotion')
227	reduceMotionEnable.checked = false
228	reduceMotionDisable.checked = false
229	applyReduceMotion(false)
230	// updateReduceMotionValue()
231}
232
233function updateTextColorValue() {
234	const saved = getSettings('a11yTextColor')
235	textColorValue.textContent = saved || 'Default'
236}
237function updateBackgroundColorValue() {
238	const saved = getSettings('a11yBackgroundColor')
239	backgroundColorValue.textContent = saved || 'Default'
240}
241function updateFontSizeValue() {
242	const saved = getSettings('a11yFontSize')
243	fontSizeValue.textContent = saved ? saved + 'px' : 'Default (16px)'
244}
245function updateWordSpacingValue() {
246	const saved = getSettings('a11yWordSpacing')
247	wordSpacingValue.textContent = saved ? saved + 'px' : 'Default (0px)'
248}
249function updateLineHeightValue() {
250	const saved = getSettings('a11yLineHeight')
251	lineHeightValue.textContent = saved || 'Default (1.5)'
252}
253// function updateCursorSizeValue() {
254//   const saved = getSettings('a11yCursorSize')
255//   if (!saved) cursorSizeValue.textContent = 'Default'
256//   else cursorSizeValue.textContent = saved
257// }
258function updateFontFamilyValue() {
259	const saved = getSettings('a11yFontFamily')
260	fontFamilyValue.textContent = saved || 'Default'
261}
262function updateColorBlindnessValue() {
263	const saved = getSettings('a11yColorBlindness')
264	colorBlindnessValue.textContent = saved || 'None'
265}
266function updateContrastValue() {
267	const saved = getSettings('a11yContrast')
268	if (!saved || saved === 'none') contrastValue.textContent = 'عادی'
269	else if (saved.includes('contrast')) contrastValue.textContent = 'تشدید'
270	else if (saved.includes('invert')) contrastValue.textContent = 'معکوس'
271	else contrastValue.textContent = 'Unknown'
272}
273function updateReduceMotionValue() {
274	const saved = getSettings('a11yReduceMotion')
275	if (saved === 'true') reduceMotionValue.textContent = 'غیرفعال'
276	else if (saved === 'false') reduceMotionValue.textContent = 'فعال'
277	else reduceMotionValue.textContent = 'Default'
278}
279
280function applyHighlightLinks(enable) {
281	if (enable) {
282		document.body.classList.add('a11y-highlight-links')
283	} else {
284		document.body.classList.remove('a11y-highlight-links')
285	}
286}
287
288function ensureBubble() {
289	var existing = document.getElementById('rm-bubble')
290	if (existing) return existing
291
292	var bubble = document.createElement('div')
293	bubble.id = 'rm-bubble'
294	bubble.setAttribute('role', 'status')
295	bubble.setAttribute('aria-live', 'polite')
296	bubble.innerHTML =
297		'<span class="rm-bubble-icon d-none" aria-hidden="true"><i class="fa fa-book"></i></span>' +
298		'<span class="rm-bubble-text d-none">حالت مطالعه فعال است</span>' +
299		'<button type="button" class="rm-bubble-close d-none" aria-label="خروج از حالت مطالعه">' +
300		'<i class="fa fa-times d-none" aria-hidden="true"></i>' +
301		'</button>'
302
303	document.body.appendChild(bubble)
304
305	bubble
306		.querySelector('.rm-bubble-close')
307		.addEventListener('click', function () {
308			applyReadingMode(false)
309			saveSettings('a11yReadingMode', 'false')
310			if (readingModeDisable) readingModeDisable.checked = true
311			if (readingModeEnable) readingModeEnable.checked = false
312		})
313
314	return bubble
315}
316
317function rmNormHref(href) {
318	try {
319		var u = new URL(href, location.href)
320		u.hash = ''
321		return u.origin + u.pathname
322	} catch (e) {
323		return (href || '').trim()
324	}
325}
326function rmClean(t) {
327	return (t || '').replace(/\s+/g, ' ').trim()
328}
329
330function rmSectionOf(a) {
331	return (
332		a.closest(
333			'section, .news-ticker, .multipurpose-news, .multi-purpos, ' +
334				'.multi-purpos-list, .main-news, .album-news, .album-carousel',
335		) ||
336		document.getElementById('content-wrapper') ||
337		document.body
338	)
339}
340
341function rmSectionHeading(section) {
342	if (!section) return ''
343	var t = section.querySelector('.title')
344	if (t && !t.closest('.news-list') && !t.closest('li')) {
345		var txt = rmClean(t.textContent)
346		if (txt) return txt
347	}
348	var hs = section.querySelectorAll('h1,h2,h3,h4,h5')
349	for (var i = 0; i < hs.length; i++) {
350		var h = hs[i]
351		if (
352			h.closest('.news-list') ||
353			h.closest('.item-title') ||
354			h.closest('.media-heading') ||
355			h.closest('li')
356		)
357			continue
358		var ht = rmClean(h.textContent)
359		if (ht) return ht
360	}
361	return ''
362}
363
364function rmExtractTitle(a) {
365	var h = a.querySelector('h1,h2,h3,h4,h5,h6')
366	if (h) {
367		var ht = rmClean(h.textContent)
368		if (ht) return { text: ht, pr: 4 }
369	}
370
371	var item = a.closest('li, .item, .album-main-item, .services-banner-item')
372	if (item) {
373		var it = item.querySelector('.item-title, .media-heading')
374		if (it) {
375			var itt = rmClean(it.textContent)
376			if (itt) return { text: itt, pr: 3 }
377		}
378	}
379	var own = rmClean(a.textContent)
380	if (own) return { text: own, pr: own.length > 120 ? 1 : 2 }
381
382	var img = a.querySelector('img')
383	if (img && img.alt) return { text: rmClean(img.alt), pr: 2 }
384
385	return { text: '', pr: 0 }
386}
387
388function rmImgOf(a) {
389	var img = a.querySelector('img')
390	if (img) return img.getAttribute('src')
391	var item = a.closest('li, .item, .album-main-item, div')
392	if (item) {
393		var im = item.querySelector('img')
394		if (im) return im.getAttribute('src')
395	}
396	return ''
397}
398
399function rmDateOf(a) {
400	var item = a.closest('li, .item, div')
401	if (!item) return ''
402	var d = item.querySelector('.published-date, time')
403	return d ? rmClean(d.textContent) : ''
404}
405
406function rmDetectArticle() {
407	return document.querySelector(
408		'.news-view, article, .article-view, .single-content, .post-content',
409	)
410}
411
412/* ---- builds the clean article view for detail pages ---- */
413function rmBuildArticle(container, articleEl) {
414	// Title
415	var h1 =
416		articleEl.querySelector('h1') ||
417		document.querySelector('#content-wrapper h1, h1')
418	var titleText = (h1 && rmClean(h1.textContent)) || rmClean(document.title)
419	if (titleText) {
420		var pt = document.createElement('h1')
421		pt.className = 'rm-page-title'
422		pt.textContent = titleText
423		container.appendChild(pt)
424	}
425
426	// Date / meta (optional, single line)
427	var dateEl = articleEl.querySelector('.date-info, .published-date, time')
428	if (dateEl) {
429		var d = document.createElement('p')
430		d.className = 'rm-article-meta'
431		d.textContent = rmClean(dateEl.textContent)
432		container.appendChild(d)
433	}
434
435	var summary = articleEl.querySelector('.summary, .lead, .excerpt')
436	if (summary) {
437		var s = document.createElement('p')
438		s.className = 'rm-article-summary'
439		s.textContent = rmClean(summary.textContent)
440		container.appendChild(s)
441	}
442
443	var body = articleEl.querySelector(
444		'.content, .news-content, .article-body, .post-body',
445	)
446	var art = document.createElement('div')
447	art.className = 'rm-article-body'
448
449	if (body) {
450		var clone = body.cloneNode(true)
451
452		clone
453			.querySelectorAll(
454				'script, style, iframe, .video, #tts-wrapper, #tts-player-icon, ' +
455					'input, button, .share-links, .rate-widget, audio, video',
456			)
457			.forEach(function (n) {
458				n.remove()
459			})
460
461		clone
462			.querySelectorAll('p, h2, h3, h4, h5, li, blockquote')
463			.forEach(function (n) {
464				var txt = rmClean(n.textContent)
465				if (!txt) {
466					return
467				}
468			})
469
470		clone.querySelectorAll('img').forEach(function (img) {
471			img.removeAttribute('width')
472			img.removeAttribute('height')
473			img.removeAttribute('style')
474			img.classList.add('rm-article-img')
475			img.loading = 'lazy'
476			if (!img.alt) img.alt = ''
477		})
478
479		clone.querySelectorAll('[style]').forEach(function (n) {
480			if (n.tagName !== 'IMG') n.removeAttribute('style')
481		})
482
483		art.innerHTML = clone.innerHTML
484	} else {
485		var ps = articleEl.querySelectorAll('p')
486		ps.forEach(function (p) {
487			var txt = rmClean(p.textContent)
488			if (!txt) return
489			var np = document.createElement('p')
490			np.textContent = txt
491			art.appendChild(np)
492		})
493	}
494
495	container.appendChild(art)
496
497	var relWrap = document.querySelector(
498		'.relative-news-wrapper, .related-news, .relative-news-carousel',
499	)
500	if (relWrap) {
501		var links = relWrap.querySelectorAll('a[href]')
502		var seen = {},
503			items = []
504		links.forEach(function (a) {
505			var href = a.getAttribute('href')
506			if (!href || href === '#') return
507			var key = rmNormHref(href)
508			if (seen[key]) return
509			var titleNode = a.querySelector(
510				'.relative-news-title, h5, h4, .item-title',
511			)
512			var t = titleNode
513				? rmClean(titleNode.textContent)
514				: rmClean(a.textContent)
515			if (!t) return
516			seen[key] = true
517			var img = a.querySelector('img')
518			items.push({
519				href: href,
520				title: t,
521				img: img ? img.getAttribute('src') : '',
522			})
523		})
524
525		if (items.length) {
526			var sec = document.createElement('section')
527			sec.className = 'rm-section'
528			var h = document.createElement('h2')
529			h.className = 'rm-section-title'
530			h.textContent = 'اخبار مرتبط'
531			sec.appendChild(h)
532
533			var ul = document.createElement('ul')
534			ul.className = 'rm-list'
535			items.forEach(function (it) {
536				ul.appendChild(rmBuildListItem(it))
537			})
538			sec.appendChild(ul)
539			container.appendChild(sec)
540		}
541	}
542}
543
544function rmBuildListItem(it) {
545	var li = document.createElement('li')
546	li.className = 'rm-item'
547
548	var a = document.createElement('a')
549	a.className = 'rm-item-link'
550	a.href = it.href
551
552	if (it.img) {
553		var im = document.createElement('img')
554		im.className = 'rm-item-img'
555		im.src = it.img
556		im.alt = ''
557		im.loading = 'lazy'
558		a.appendChild(im)
559	}
560	var body = document.createElement('div')
561	body.className = 'rm-item-body'
562	var tt = document.createElement('span')
563	tt.className = 'rm-item-title'
564	tt.textContent = it.title || it.href
565	body.appendChild(tt)
566	if (it.date) {
567		var dd = document.createElement('span')
568		dd.className = 'rm-item-date'
569		dd.textContent = it.date
570		body.appendChild(dd)
571	}
572	a.appendChild(body)
573	li.appendChild(a)
574	return li
575}
576
577function rmBuildListing(container) {
578	var scope = document.getElementById('content-wrapper') || document.body
579	var anchors = scope.querySelectorAll(
580		'a[href*="/news/"], a[href*="/article/"], a[href*="/post/"]',
581	)
582
583	var map = {},
584		order = [],
585		groups = {}
586
587	anchors.forEach(function (a) {
588		var href = a.getAttribute('href')
589		if (!href || href === '#') return
590		var key = rmNormHref(href)
591		if (!key) return
592
593		var t = rmExtractTitle(a)
594		var img = rmImgOf(a)
595		if (!t.text && !img) return
596
597		var ex = map[key]
598		if (ex) {
599			if (t.pr > ex.tpr) {
600				ex.title = t.text
601				ex.tpr = t.pr
602			}
603			if (!ex.img && img) ex.img = img
604			if (!ex.date) {
605				var dd = rmDateOf(a)
606				if (dd) ex.date = dd
607			}
608			return
609		}
610
611		var heading = rmSectionHeading(rmSectionOf(a))
612		var gkey = heading || '__default__'
613		var item = {
614			href: href,
615			title: t.text,
616			tpr: t.pr,
617			img: img,
618			date: rmDateOf(a),
619		}
620		map[key] = item
621
622		if (!groups[gkey]) {
623			groups[gkey] = { heading: heading, items: [] }
624			order.push(gkey)
625		}
626		groups[gkey].items.push(item)
627	})
628
629	var h1 = document.querySelector('#content-wrapper h1, h1')
630	var titleText = (h1 && rmClean(h1.textContent)) || rmClean(document.title)
631	if (titleText) {
632		var pt = document.createElement('h1')
633		pt.className = 'rm-page-title'
634		pt.textContent = titleText
635		container.appendChild(pt)
636	}
637
638	var total = 0
639	order.forEach(function (gkey) {
640		var g = groups[gkey]
641		if (!g.items.length) return
642
643		var sec = document.createElement('section')
644		sec.className = 'rm-section'
645		if (g.heading) {
646			var h = document.createElement('h2')
647			h.className = 'rm-section-title'
648			h.textContent = g.heading
649			sec.appendChild(h)
650		}
651		var ul = document.createElement('ul')
652		ul.className = 'rm-list'
653		g.items.forEach(function (it) {
654			total++
655			ul.appendChild(rmBuildListItem(it))
656		})
657		sec.appendChild(ul)
658		container.appendChild(sec)
659	})
660
661	return total
662}
663
664function buildReadingContent() {
665	if (document.getElementById('rm-root')) return
666
667	var root = document.createElement('div')
668	root.id = 'rm-root'
669	root.setAttribute('role', 'region')
670	root.setAttribute('aria-label', 'نسخه ساده‌شده برای مطالعه')
671
672	var container = document.createElement('div')
673	container.className = 'rm-container'
674	root.appendChild(container)
675
676	var articleEl = rmDetectArticle()
677	var empty = false
678
679	if (articleEl) {
680		rmBuildArticle(container, articleEl)
681	} else {
682		var total = rmBuildListing(container)
683		if (!total) empty = true
684	}
685
686	if (empty) {
687		var p = document.createElement('p')
688		p.className = 'rm-empty'
689		p.textContent = 'محتوای قابل نمایشی برای حالت مطالعه یافت نشد.'
690		container.appendChild(p)
691	}
692
693	document.body.appendChild(root)
694}
695
696function destroyReadingContent() {
697	var r = document.getElementById('rm-root')
698	if (r) r.remove()
699}
700
701function applyReadingMode(enable) {
702	document.body.classList.toggle('a11y-reading-mode', enable)
703
704	if (readingModeEnable) readingModeEnable.checked = enable
705	if (readingModeDisable) readingModeDisable.checked = !enable
706
707	if (enable) {
708		buildReadingContent()
709		window.scrollTo(0, 0)
710		void document.body.offsetHeight // force immediate repaint
711	} else {
712		destroyReadingContent()
713		window.dispatchEvent(new Event('resize')) // let owl carousels recalc
714	}
715
716	var bubble = ensureBubble()
717	if (enable) {
718		requestAnimationFrame(function () {
719			bubble.classList.add('rm-bubble-visible')
720		})
721	} else {
722		bubble.classList.remove('rm-bubble-visible')
723	}
724}
725
726function applyReadingFontSize(size) {
727	document.documentElement.style.setProperty('--rm-font-size', size + 'rem')
728	if (readingFontSizeValue) {
729		readingFontSizeValue.textContent =
730			size.toFixed(3).replace(/\.?0+$/, '') + 'rem'
731	}
732}
733
734function applyReadingBg(bg, text) {
735	document.documentElement.style.setProperty('--rm-bg', bg)
736	document.documentElement.style.setProperty('--rm-text', text)
737
738	var isDark = bg === '#1c1c1e'
739	document.body.classList.toggle('reading-mode-dark', isDark)
740
741	readingBgSwatches.forEach(function (s) {
742		s.classList.toggle('active', s.dataset.bg === bg)
743	})
744}
745
746function resetReadingMode() {
747	localStorage.removeItem('a11yReadingMode')
748	localStorage.removeItem('a11yReadingFontSize')
749	localStorage.removeItem('a11yReadingBg')
750	localStorage.removeItem('a11yReadingText')
751	readingFontSize = 1.125
752	applyReadingMode(false)
753	applyReadingFontSize(readingFontSize)
754	applyReadingBg('#fafaf8', '#1a1a1a')
755}
756
757function applySavedSettings() {
758	const savedFontSize = getSettings('a11yFontSize')
759	if (savedFontSize !== null) {
760		applyFontSize(parseInt(savedFontSize))
761	}
762
763	const savedBackgroundColor = getSettings('a11yBackgroundColor')
764	if (savedBackgroundColor !== null) {
765		applyBackgroundColor(savedBackgroundColor)
766	}
767
768	const savedWordSpacing = getSettings('a11yWordSpacing')
769	if (savedWordSpacing !== null) {
770		applyWordSpacing(parseInt(savedWordSpacing))
771	}
772
773	const savedHideImages = getSettings('a11yHideImages')
774	if (savedHideImages === 'true') {
775		applyHideImages(true)
776		if (hideImagesEnable) hideImagesEnable.checked = true
777	} else if (savedHideImages === 'false') {
778		applyHideImages(false)
779		if (hideImagesDisable) hideImagesDisable.checked = true
780	}
781
782	const savedLineHeight = getSettings('a11yLineHeight')
783	if (savedLineHeight !== null) {
784		applyLineHeight(parseFloat(savedLineHeight))
785	}
786
787	const savedFontFamily = getSettings('a11yFontFamily')
788	if (savedFontFamily !== null) {
789		applyFontFamily(savedFontFamily)
790	}
791	const savedHighlightLinks = getSettings('a11yHighlightLinks')
792	if (savedHighlightLinks === 'true') {
793		applyHighlightLinks(true)
794		if (highlightLinksEnable) highlightLinksEnable.checked = true
795	} else if (savedHighlightLinks === 'false') {
796		applyHighlightLinks(false)
797		if (highlightLinksDisable) highlightLinksDisable.checked = true
798	}
799	const savedColorBlindness = getSettings('a11yColorBlindness')
800	if (savedColorBlindness !== null) {
801		applyColorBlindnessFilter(savedColorBlindness)
802	}
803
804	const savedContrastSetting = getSettings('a11yContrast')
805	if (savedContrastSetting !== null) {
806		applyContrastFilter(savedContrastSetting)
807	}
808
809	// const savedCursorSize = getSettings('a11yCursorSize')
810	// if (savedCursorSize !== null) {
811	//   applyCursorSize(savedCursorSize)
812	// }
813
814	const savedTextColor = getSettings('a11yTextColor')
815	if (savedTextColor !== null) {
816		applyTextColor(savedTextColor)
817	}
818
819	const savedLinkColor = getSettings('a11yLinkColor')
820	if (savedLinkColor !== null) {
821		applyLinkColor(savedLinkColor)
822	}
823
824	const savedReduceMotion = getSettings('a11yReduceMotion')
825	if (savedReduceMotion === 'true') {
826		reduceMotionEnable.checked = true
827		applyReduceMotion(true)
828	} else if (savedReduceMotion === 'false') {
829		reduceMotionDisable.checked = true
830		applyReduceMotion(false)
831	}
832	const savedReadingMode = getSettings('a11yReadingMode')
833	if (savedReadingMode === 'true') {
834		applyReadingMode(true)
835	} else if (savedReadingMode === 'false') {
836		applyReadingMode(false)
837	}
838
839	const savedReadingFontSize = parseFloat(getSettings('a11yReadingFontSize'))
840	if (!isNaN(savedReadingFontSize)) {
841		readingFontSize = savedReadingFontSize
842	}
843	applyReadingFontSize(readingFontSize)
844
845	const savedReadingBg = getSettings('a11yReadingBg')
846	const savedReadingText = getSettings('a11yReadingText')
847	if (savedReadingBg && savedReadingText) {
848		applyReadingBg(savedReadingBg, savedReadingText)
849	} else {
850		applyReadingBg('#fafaf8', '#1a1a1a')
851	}
852	// updateTextColorValue()
853	// updateBackgroundColorValue()
854	updateFontSizeValue()
855	updateWordSpacingValue()
856	updateLineHeightValue()
857	// updateCursorSizeValue()
858	// updateFontFamilyValue()
859	// updateColorBlindnessValue()
860	// updateContrastValue()
861	// updateReduceMotionValue()
862}
863
864function applyReduceMotion(reduceMotion) {
865	if (reduceMotion) {
866		document.documentElement.setAttribute('data-reduce-motion', 'true')
867		pauseAllSliders()
868	} else {
869		document.documentElement.removeAttribute('data-reduce-motion')
870		resumeAllSliders()
871	}
872}
873
874function pauseAllSliders() {
875	if (
876		typeof jQuery !== 'undefined' &&
877		typeof jQuery.fn.owlCarousel !== 'undefined'
878	) {
879		$('.owl-carousel').trigger('stop.owl.autoplay')
880	}
881}
882function resumeAllSliders() {
883	if (
884		typeof jQuery !== 'undefined' &&
885		typeof jQuery.fn.owlCarousel !== 'undefined'
886	) {
887		$('.owl-carousel').trigger('play.owl.autoplay', [1000])
888	}
889}
890
891// Apply styles with !important to override existing
892function applyTextColor(color) {
893	for (let el of allElements) {
894		el.style.setProperty('color', color, 'important')
895	}
896}
897function applyLinkColor(color) {
898	for (let el of allLinks) {
899		el.style.setProperty('color', color, 'important')
900	}
901}
902function applyBackgroundColor(color) {
903	document.body.style.setProperty('background-color', color, 'important')
904	for (let el of elementsWithBackground) {
905		el.style.setProperty('background-color', color, 'important')
906	}
907}
908function applyFontSize(size) {
909	for (let el of allElements) {
910		el.style.setProperty('font-size', size + 'px', 'important')
911	}
912}
913function applyWordSpacing(spacing) {
914	for (let el of allElements) {
915		el.style.setProperty('word-spacing', spacing + 'px', 'important')
916	}
917}
918function applyLineHeight(lineHeight) {
919	for (let el of allElements) {
920		el.style.setProperty('line-height', lineHeight, 'important')
921	}
922}
923function applyFontFamily(fontFamily) {
924	for (let el of allElements) {
925		el.style.setProperty('font-family', fontFamily, 'important')
926	}
927}
928function applyColorBlindnessFilter(filterType) {
929	let filterValue = ''
930	switch (filterType) {
931		case 'protanopia':
932			filterValue =
933				'grayscale(100%) sepia(100%) hue-rotate(-50deg) saturate(50%)'
934			break
935		case 'deuteranopia':
936			filterValue =
937				'grayscale(100%) sepia(100%) hue-rotate(-20deg) saturate(50%)'
938			break
939		case 'tritanopia':
940			filterValue =
941				'grayscale(100%) sepia(100%) hue-rotate(80deg) saturate(50%)'
942			break
943		case 'protanomaly':
944			filterValue =
945				'grayscale(50%) sepia(100%) hue-rotate(-30deg) saturate(70%)'
946			break
947		case 'deuteranomaly':
948			filterValue =
949				'grayscale(50%) sepia(100%) hue-rotate(-20deg) saturate(70%)'
950			break
951		case 'tritanomaly':
952			filterValue = 'grayscale(50%) sepia(100%) hue-rotate(60deg) saturate(70%)'
953			break
954		case 'achromatopsia':
955			filterValue = 'grayscale(100%)'
956			break
957		case 'achromatomaly':
958			filterValue = 'grayscale(80%)'
959			break
960		default:
961			filterValue = 'none'
962	}
963	const cw = document.getElementById('content-wrapper')
964	if (cw) cw.style.setProperty('filter', filterValue, 'important')
965	const ft = document.querySelector('footer')
966	if (ft) ft.style.setProperty('filter', filterValue, 'important')
967}
968// function applyCursorSize(size) {
969//   cursorSize = size
970// }
971function applyContrastFilter(filterValue) {
972	const cw = document.getElementById('content-wrapper')
973	if (cw) cw.style.setProperty('filter', filterValue, 'important')
974	const ft = document.querySelector('footer')
975	if (ft) ft.style.setProperty('filter', filterValue, 'important')
976}
977
978function applyHideImages(enable) {
979	if (enable) {
980		document.body.classList.add('a11y-hide-images')
981	} else {
982		document.body.classList.remove('a11y-hide-images')
983	}
984}
985
986function resetHideImages() {
987	localStorage.removeItem('a11yHideImages')
988	document.body.classList.remove('a11y-hide-images')
989	if (hideImagesEnable) hideImagesEnable.checked = false
990	if (hideImagesDisable) hideImagesDisable.checked = false
991}
992
993function resetAccessibilitySettings() {
994	localStorage.removeItem('a11ySettings')
995	localStorage.removeItem('a11yColorBlindness')
996	// localStorage.removeItem('a11yCursorSize')
997	localStorage.removeItem('a11yFontSize')
998	localStorage.removeItem('a11yLineHeight')
999	localStorage.removeItem('a11yTextColor')
1000	localStorage.removeItem('a11yBackgroundColor')
1001	localStorage.removeItem('a11yWordSpacing')
1002	localStorage.removeItem('a11yFontFamily')
1003	localStorage.removeItem('a11yHighlightLinks')
1004	localStorage.removeItem('a11yReduceMotion')
1005	localStorage.removeItem('a11yContrast')
1006	localStorage.removeItem('a11yReadingMode')
1007	localStorage.removeItem('a11yReadingFontSize')
1008	localStorage.removeItem('a11yReadingBg')
1009	localStorage.removeItem('a11yReadingText')
1010	readingFontSize = 1.125
1011	applyReadingMode(false)
1012	applyReadingFontSize(readingFontSize)
1013	applyReadingBg('#fafaf8', '#1a1a1a')
1014
1015	document.body.classList.remove('a11y-highlight-links')
1016	if (highlightLinksEnable) highlightLinksEnable.checked = false
1017	if (highlightLinksDisable) highlightLinksDisable.checked = false
1018
1019	localStorage.removeItem('a11yHideImages')
1020	document.body.classList.remove('a11y-hide-images')
1021	if (hideImagesEnable) hideImagesEnable.checked = false
1022	if (hideImagesDisable) hideImagesDisable.checked = false
1023
1024	for (let el of allElements) {
1025		el.style.removeProperty('font-size')
1026		el.style.removeProperty('word-spacing')
1027		el.style.removeProperty('line-height')
1028		el.style.removeProperty('font-family')
1029		el.style.removeProperty('color')
1030		el.style.removeProperty('background-color')
1031		// el.style.removeProperty('cursor')
1032	}
1033
1034	document.body.style.removeProperty('background-color')
1035	const cw = document.getElementById('content-wrapper')
1036	if (cw) cw.style.removeProperty('filter')
1037	const ft = document.querySelector('footer')
1038	if (ft) ft.style.removeProperty('filter')
1039
1040	// document.documentElement.style.removeProperty('--custom-cursor')
1041	// document.documentElement.style.removeProperty('--custom-pointer-cursor')
1042	// document.documentElement.style.removeProperty('--custom-text-cursor')
1043
1044	reduceMotionEnable.checked = false
1045	reduceMotionDisable.checked = false
1046	applyReduceMotion(false)
1047
1048	// Re-scan any background changes
1049	elementsWithBackground = getAllElementsWithBackgroundColor()
1050
1051	// Update all dynamic values to defaults
1052	// updateTextColorValue()
1053	// updateBackgroundColorValue()
1054	updateFontSizeValue()
1055	updateWordSpacingValue()
1056	updateLineHeightValue()
1057	// updateCursorSizeValue()
1058	// updateFontFamilyValue()
1059	// updateColorBlindnessValue()
1060	// updateContrastValue()
1061	// updateReduceMotionValue()
1062}
1063
1064resetButton.addEventListener('click', function () {
1065	resetAccessibilitySettings()
1066})
1067
1068// Single-feature reset
1069resetTextColorBtn.addEventListener('click', resetTextColor)
1070resetBackgroundColorBtn.addEventListener('click', resetBackgroundColor)
1071resetFontSizeBtn.addEventListener('click', resetFontSize)
1072resetWordSpacingBtn.addEventListener('click', resetWordSpacing)
1073resetLineHeightBtn.addEventListener('click', resetLineHeight)
1074// resetCursorSizeBtn.addEventListener('click', resetCursorSize)
1075resetFontFamilyBtn.addEventListener('click', resetFontFamily)
1076resetColorBlindnessBtn.addEventListener('click', resetColorBlindness)
1077resetContrastBtn.addEventListener('click', resetContrast)
1078resetReduceMotionBtn.addEventListener('click', resetReduceMotion)
1079
1080// Contrast settings
1081contrastButtons.normal.addEventListener('click', function () {
1082	applyContrastFilter('none')
1083	saveSettings('a11yContrast', 'none')
1084	updateContrastValue()
1085})
1086contrastButtons.high.addEventListener('click', function () {
1087	applyContrastFilter('contrast(150%)')
1088	saveSettings('a11yContrast', 'contrast(150%)')
1089	updateContrastValue()
1090})
1091contrastButtons.inverse.addEventListener('click', function () {
1092	applyContrastFilter('invert(100%)')
1093	saveSettings('a11yContrast', 'invert(100%)')
1094	updateContrastValue()
1095})
1096
1097// Widget open/close
1098accessibilityToggle.addEventListener('click', function (event) {
1099	event.stopPropagation()
1100	accessibilityWidget.classList.add('active')
1101})
1102accessibilityClose.addEventListener('click', function (event) {
1103	event.stopPropagation()
1104	accessibilityWidget.classList.remove('active')
1105})
1106
1107// Close the widget when clicking outside
1108document.addEventListener('click', function (event) {
1109	if (
1110		!accessibilityWidget.contains(event.target) &&
1111		!accessibilityToggle.contains(event.target)
1112	) {
1113		accessibilityWidget.classList.remove('active')
1114	}
1115})
1116accessibilityWidget.addEventListener('click', function (event) {
1117	event.stopPropagation()
1118})
1119document.addEventListener('keydown', function (event) {
1120	if (
1121		event.ctrlKey &&
1122		(event.key === 'm' || event.key === 'M' || event.key === 'Ù¾')
1123	) {
1124		accessibilityWidget.classList.toggle('active')
1125	}
1126})
1127
1128// Navigate between menu and setting pages
1129menuButtons.forEach((button) => {
1130	button.addEventListener('click', function () {
1131		const settingsPageId = 'settings-' + button.dataset.settings
1132		const settingsPage = document.getElementById(settingsPageId)
1133		if (settingsPage) {
1134			mainMenu.classList.remove('active')
1135			settingsPage.classList.add('active')
1136		}
1137	})
1138})
1139backButtons.forEach((button) => {
1140	button.addEventListener('click', function () {
1141		const parentSettingsPage = button.closest('.widget-page')
1142		if (parentSettingsPage) {
1143			parentSettingsPage.classList.remove('active')
1144			mainMenu.classList.add('active')
1145		}
1146	})
1147})
1148
1149// WOW instance (optional)
1150if (typeof WOW !== 'undefined') {
1151	const wowInstance = new WOW()
1152	wowInstance.start = () => {
1153		const elements = document.querySelectorAll('.wow')
1154		elements.forEach((el) => {
1155			el.style.visibility = ''
1156			el.classList.add('wow', 'animated')
1157		})
1158		wowInstance.sync()
1159	}
1160	wowInstance.stop = () => {
1161		const elements = document.querySelectorAll('.wow')
1162		elements.forEach((el) => {
1163			el.classList.remove('wow', 'animated')
1164			el.style.visibility = 'visible'
1165		})
1166	}
1167	wowInstance.init()
1168}
1169
1170if (hideImagesEnable) {
1171	hideImagesEnable.addEventListener('change', function () {
1172		if (this.checked) {
1173			applyHideImages(true)
1174			saveSettings('a11yHideImages', 'true')
1175		}
1176	})
1177}
1178if (hideImagesDisable) {
1179	hideImagesDisable.addEventListener('change', function () {
1180		if (this.checked) {
1181			applyHideImages(false)
1182			saveSettings('a11yHideImages', 'false')
1183		}
1184	})
1185}
1186resetHideImagesBtn.addEventListener('click', resetHideImages)
1187
1188if (readingModeEnable) {
1189	readingModeEnable.addEventListener('change', function () {
1190		if (this.checked) {
1191			applyReadingMode(true)
1192			saveSettings('a11yReadingMode', 'true')
1193		}
1194	})
1195}
1196
1197if (readingModeDisable) {
1198	readingModeDisable.addEventListener('change', function () {
1199		if (this.checked) {
1200			applyReadingMode(false)
1201			saveSettings('a11yReadingMode', 'false')
1202		}
1203	})
1204}
1205
1206if (resetReadingModeBtn) {
1207	resetReadingModeBtn.addEventListener('click', function () {
1208		resetReadingMode()
1209	})
1210}
1211
1212if (readingFontIncrease) {
1213	readingFontIncrease.addEventListener('click', function () {
1214		readingFontSize = Math.min(readingFontSize + 0.125, 2.0)
1215		applyReadingFontSize(readingFontSize)
1216		saveSettings('a11yReadingFontSize', readingFontSize)
1217	})
1218}
1219
1220if (readingFontDecrease) {
1221	readingFontDecrease.addEventListener('click', function () {
1222		readingFontSize = Math.max(readingFontSize - 0.125, 0.75)
1223		applyReadingFontSize(readingFontSize)
1224		saveSettings('a11yReadingFontSize', readingFontSize)
1225	})
1226}
1227
1228readingBgSwatches.forEach(function (swatch) {
1229	swatch.addEventListener('click', function () {
1230		var bg = this.dataset.bg
1231		var text = this.dataset.text
1232		applyReadingBg(bg, text)
1233		saveSettings('a11yReadingBg', bg)
1234		saveSettings('a11yReadingText', text)
1235	})
1236})
1237
1238reduceMotionEnable.addEventListener('change', function () {
1239	if (reduceMotionEnable.checked) {
1240		applyReduceMotion(true)
1241		saveSettings('a11yReduceMotion', 'true')
1242		// updateReduceMotionValue()
1243	}
1244})
1245reduceMotionDisable.addEventListener('change', function () {
1246	if (reduceMotionDisable.checked) {
1247		applyReduceMotion(false)
1248		saveSettings('a11yReduceMotion', 'false')
1249		// updateReduceMotionValue()
1250	}
1251})
1252
1253if (highlightLinksEnable) {
1254	highlightLinksEnable.addEventListener('change', function () {
1255		if (this.checked) {
1256			applyHighlightLinks(true)
1257			saveSettings('a11yHighlightLinks', 'true')
1258		}
1259	})
1260}
1261if (highlightLinksDisable) {
1262	highlightLinksDisable.addEventListener('change', function () {
1263		if (this.checked) {
1264			applyHighlightLinks(false)
1265			saveSettings('a11yHighlightLinks', 'false')
1266		}
1267	})
1268}
1269resetHighlightLinksBtn.addEventListener('click', resetHighlightLinks)
1270
1271// cursorSizeSmallBtn.addEventListener('click', function () {
1272//   applyCursorSize('small')
1273//   saveSettings('a11yCursorSize', 'small')
1274//   updateCursorSizeValue()
1275// })
1276// cursorSizeMediumBtn.addEventListener('click', function () {
1277//   applyCursorSize('medium')
1278//   saveSettings('a11yCursorSize', 'medium')
1279//   updateCursorSizeValue()
1280// })
1281// cursorSizeLargeBtn.addEventListener('click', function () {
1282//   applyCursorSize('large')
1283//   saveSettings('a11yCursorSize', 'large')
1284//   updateCursorSizeValue()
1285// })
1286
1287// Color pickers
1288textColorPicker.addEventListener('change', function () {
1289	applyTextColor(this.value)
1290	saveSettings('a11yTextColor', this.value)
1291	// updateTextColorValue()
1292})
1293backgroundColorPicker.addEventListener('change', function () {
1294	applyBackgroundColor(this.value)
1295	saveSettings('a11yBackgroundColor', this.value)
1296	// updateBackgroundColorValue()
1297})
1298
1299// Font Size
1300fontSizeIncreaseBtn.addEventListener('click', function () {
1301	fontSize = fontSize === null ? 16 : fontSize + 1
1302	applyFontSize(fontSize)
1303	saveSettings('a11yFontSize', fontSize)
1304	updateFontSizeValue()
1305})
1306fontSizeDecreaseBtn.addEventListener('click', function () {
1307	if (fontSize === null) {
1308		fontSize = 16
1309	}
1310	if (fontSize > 1) {
1311		fontSize -= 1
1312		applyFontSize(fontSize)
1313		saveSettings('a11yFontSize', fontSize)
1314		updateFontSizeValue()
1315	}
1316})
1317
1318// Word Spacing
1319wordSpacingIncreaseBtn.addEventListener('click', function () {
1320	wordSpacing = wordSpacing === null ? 0 : wordSpacing + 1
1321	applyWordSpacing(wordSpacing)
1322	saveSettings('a11yWordSpacing', wordSpacing)
1323	updateWordSpacingValue()
1324})
1325wordSpacingDecreaseBtn.addEventListener('click', function () {
1326	if (wordSpacing === null) {
1327		wordSpacing = 0
1328	}
1329	if (wordSpacing > 0) {
1330		wordSpacing -= 1
1331		applyWordSpacing(wordSpacing)
1332		saveSettings('a11yWordSpacing', wordSpacing)
1333		updateWordSpacingValue()
1334	}
1335})
1336
1337// Line Height
1338lineHeightIncreaseBtn.addEventListener('click', function () {
1339	lineHeight = lineHeight === null ? 1.5 : lineHeight + 0.1
1340	applyLineHeight(lineHeight.toFixed(1))
1341	saveSettings('a11yLineHeight', lineHeight)
1342	updateLineHeightValue()
1343})
1344lineHeightDecreaseBtn.addEventListener('click', function () {
1345	if (lineHeight === null) {
1346		lineHeight = 1.5
1347	}
1348	if (lineHeight > 0.1) {
1349		lineHeight -= 0.1
1350		applyLineHeight(lineHeight.toFixed(1))
1351		saveSettings('a11yLineHeight', lineHeight)
1352		updateLineHeightValue()
1353	}
1354})
1355
1356// Font Family
1357fontFamilySelect.addEventListener('change', function () {
1358	applyFontFamily(this.value)
1359	saveSettings('a11yFontFamily', this.value)
1360	// updateFontFamilyValue()
1361})
1362
1363// Color Blindness
1364colorBlindnessSelect.addEventListener('change', function () {
1365	applyColorBlindnessFilter(this.value)
1366	saveSettings('a11yColorBlindness', this.value)
1367	// updateColorBlindnessValue()
1368})
1369
1370document.addEventListener('DOMContentLoaded', function () {
1371	applySavedSettings()
1372})

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.