PageSourceSearch

https://mcit.gov.eg/ScriptPlug.js

js mcit.gov.eg collected 2026-09-25 08:44:44 UTC 121,006 bytes, 3,144 lines download raw bytes

1/* =========================== Daem Accessibility Injector - No iframe (widget) - Injects DOM + CSS + JS =========================== */
2// Capture script base URL immediately (document.currentScript only works at parse time)
3const _daemScriptBase = (() =>
4{
5    const s = document.currentScript;
6    if (!s || !s.src) return '';
7    return s.src.substring(0, s.src.lastIndexOf('/') + 1);
8})();
9
10const plugHostingUrl2020 = "https://mcit.gov.eg"; // For Using Here And applyChange.js
11const youTubeVideoLink = "https://www.youtube.com/embed/4od6UKqJJ0o";
12//const scriptElementsdkURL = "**scriptElementsdkURL**";
13const scriptApplayChanges = "/mcit_gov_eg_version_GAW_V8/Plugs/js/applyChange.js";
14const src = "https://mcit.gov.eg/mcit_gov_eg_version_GAW_V8/Plugs/Languages/en/plug.html";
15const IconclassName = "the-btn Bottom_Right";
16
17const array = scriptApplayChanges.split("/");
18var match = "/" + array[1] + "/";
19
20/**
21 * Optional:
22 * If you want to prefix all widget assets (fonts/images) with hosting base:
23 * Example: plugHostingUrl2020 = https://example.com/
24 * assets will become https://example.com/daem_assets/...
25 */
26const ASSETS_BASE = plugHostingUrl2020 + match + "Plugs/";
27
28function isPageArabic()
29{
30    // Based on your current routing logic:
31    if (src.includes("/en/")) return false;
32    return true;
33}
34
35function initTool()
36{
37    const body = document.getElementsByTagName("body")[0];
38    const head = document.head || document.getElementsByTagName("head")[0];
39
40    // loading the voices
41    let _voicesReady = false;
42    function ensureVoices(cb)
43    {
44        if (!window.speechSynthesis) { cb([]); return; }
45        const voices = window.speechSynthesis.getVoices();
46        if (voices.length > 0) { _voicesReady = true; cb(voices); return; }
47        window.speechSynthesis.onvoiceschanged = () =>
48        {
49            _voicesReady = true;
50            window.speechSynthesis.onvoiceschanged = null;
51            cb(window.speechSynthesis.getVoices());
52        };
53        // Fallback: if voiceschanged never fires (Firefox), proceed after 500ms
54        setTimeout(() => { if (!_voicesReady) { _voicesReady = true; cb(window.speechSynthesis ? window.speechSynthesis.getVoices() : []); } }, 500);
55    }
56    ensureVoices(() => { });
57
58    // Prevent double-injection
59    if (document.getElementById("daem-accessibility-widget")) return;
60
61    const IS_AR = isPageArabic();
62    document.documentElement.lang = IS_AR ? "ar" : "en";
63    //document.documentElement.dir = IS_AR ? "rtl" : "ltr";
64
65    // i18n
66    const I18N = IS_AR
67        ? {
68            dir: "rtl",
69            openMenuAria: "فتح قائمة الإتاحة الرقمية",
70            closeMenuAria: "إغلاق القائمة",
71            resetTitle: "إعادة تعيين الكل",
72            title: "الإتاحة الرقمية من داعم",
73            profiles: "إعدادات مسبقة للإختيار",
74            logo: `${ASSETS_BASE}daem_assets/ar_daem.png`,
75            signHeader: "مترجم لغة الإشارة",
76            signClose: "إغلاق فيديو لغة الإشارة",
77            signNote: "(رابط الفيديو مزود من الخلفية)",
78            profilesLabels: {
79                motor: "إعاقة حركية",
80                visual: "إعاقة بصرية",
81                colorblind: "عمى ألوان",
82                dyslexia: "عسر القراءة",
83                lowvision: "ضعف البصر",
84                cognitive: "التعلم المعرفي",
85                seizure: "الصرع والنوبات",
86                adhd: "فرط الحركة",
87            },
88            featureLabels: {
89                "sign-vid": "فيديو لغة الإشارة",
90                "sign-font": "خط لغة الإشارة",
91                "text-speech": "تحويل النص المحدد إلى كلام",
92                "speech-text": "تحويل الكلام إلى نص",
93                contrast: "تباين عالٍ للألوان",
94                "font-size": "حجم الخط",
95                cursor: "مؤشر كبير",
96                "line-height": "ارتفاع السطر",
97                spacing: "تباعد الأحرف",
98                readable: "خط مقروء",
99                links: "تحديد الروابط",
100                titles: "تحديد العناوين",
101                hint: "تلميحات",
102                "stop-anim": "إيقاف الحركة",
103                "dyslexic-font": "خط عسر القراءة",
104                mask: "قناع القراءة",
105                "hide-images": "إخفاء الصور",
106                "bold-text": "نص غامق",
107                "text-align": "محاذاة النص",
108                saturation: "التشبع",
109                "dark-mode": "الوضع المظلم",
110            },
111            dynamic: {
112                listening: "جاري الاستماع...",
113                readSelection: "قراءة المحدد",
114                textToSpeech: "تحويل النص المحدد إلى كلام",
115                speechToText: "تحويل الكلام إلى نص",
116                slFont: "خط لغة الإشارة",
117                slFontFull: "خط الإشارة (كامل)",
118                slFontSelect: "خط الإشارة (تحديد)",
119                contrast: "تباين عالٍ للألوان",
120                highContrast: "تباين عالٍ",
121                desaturate: "إزالة الألوان",
122                readingMask: "قناع القراءة",
123                readingLine: "خط القراءة",
124                textAlign: "محاذاة النص",
125                alignLeft: "محاذاة لليسار",
126                alignCenter: "محاذاة للوسط",
127                alignRight: "محاذاة لليمين",
128                signVideo: "فيديو لغة الإشارة",
129                hint: "تلميحات / أدوات مساعدة",
130
131                // STT Modal
132                sttTitle: "تحدث إلى نص",
133                sttHelp: "تحدث الآن…",
134                sttStop: "إيقاف",
135                sttResume: "استكمال",
136                sttClear: "مسح",
137                sttCopy: "نسخ",
138                sttCopied: "تم النسخ!",
139                sttClose: "إغلاق",
140                sttStopped: "تم الإيقاف.",
141                sttNoMatch: "لم يتم التعرف على الكلام.",
142                sttError: "حدث خطأ.",
143            },
144            hintPrefixes: {
145                title: "العنوان",
146                alt: "وصف الصورة",
147                placeholder: "نص الحقل",
148                link: "رابط",
149                button: "زر",
150            },
151            alerts: {
152                sttUnsupported:
153                    "المتصفح لا يدعم تحويل الكلام إلى نص. جرّب Chrome أو Edge.",
154                ttsUnsupported: "متصفحك لا يدعم تحويل النص إلى كلام.",
155                micError: "تعذر الوصول إلى الميكروفون.",
156            },
157        }
158        : {
159            dir: "ltr",
160            openMenuAria: "Open Accessibility Menu",
161            closeMenuAria: "Close Menu",
162            resetTitle: "Reset All",
163            title: "Daem Accessibility",
164            profiles: "Accessibility Profiles",
165            logo: `${ASSETS_BASE}daem_assets/en_daem.png`,
166            signHeader: "Sign Language Interpreter",
167            signClose: "Close Sign Language Video",
168            signNote: "(Video URL supplied by backend)",
169            profilesLabels: {
170                motor: "Motor Impairment",
171                visual: "Visual Impairment",
172                colorblind: "Color Blind",
173                dyslexia: "Dyslexia Friendly",
174                lowvision: "Low Vision",
175                cognitive: "Cognitive Learning",
176                seizure: "Seizure / Epileptic",
177                adhd: "ADHD",
178            },
179            featureLabels: {
180                "sign-vid": "Sign Language Video",
181                "sign-font": "Sign Language Font",
182                "text-speech": "Selected Text to Speech",
183                "speech-text": "Speech to Text",
184                contrast: "High Color Contrast",
185                "font-size": "Font Size",
186                cursor: "Large Cursor",
187                "line-height": "Line Height",
188                spacing: "Character Spacing",
189                readable: "Readable Font",
190                links: "Highlight Links",
191                titles: "Highlight Titles",
192                hint: "Hint / Tooltips",
193                "stop-anim": "Stop Animation",
194                "dyslexic-font": "Dyslexic Font",
195                mask: "Reading Mask",
196                "hide-images": "Hide Images",
197                "bold-text": "Bold Text",
198                "text-align": "Text Alignment",
199                saturation: "Saturation",
200                "dark-mode": "Dark Mode",
201            },
202            dynamic: {
203                listening: "Listening...",
204                readSelection: "Read Selection",
205                textToSpeech: "Text to Speech (Select)",
206                speechToText: "Speech to Text",
207                slFont: "Sign Language Font",
208                slFontFull: "SL Font Full",
209                slFontSelect: "SL Font Select",
210                contrast: "High Color Contrast",
211                highContrast: "High Contrast",
212                desaturate: "Desaturate",
213                readingMask: "Reading Mask",
214                readingLine: "Reading Line",
215                textAlign: "Text Alignment",
216                alignLeft: "Align Left",
217                alignCenter: "Align Center",
218                alignRight: "Align Right",
219                signVideo: "Sign Language Video",
220                hint: "Hint / Tooltips",
221
222                // STT Modal
223                sttTitle: "Speech to Text",
224                sttHelp: "Speak now…",
225                sttStop: "Stop",
226                sttResume: "Resume",
227                sttClear: "Clear",
228                sttCopy: "Copy",
229                sttCopied: "Copied!",
230                sttClose: "Close",
231                sttStopped: "Stopped.",
232                sttNoMatch: "Speech could not be recognized.",
233                sttError: "An error occurred.",
234            },
235            hintPrefixes: {
236                title: "Title",
237                alt: "Image Alt Text",
238                placeholder: "Placeholder",
239                link: "Link",
240                button: "Button",
241            },
242            alerts: {
243                sttUnsupported:
244                    "Speech recognition is not supported in this browser. Try Chrome or Edge.",
245                ttsUnsupported: "Your browser does not support Text to Speech.",
246                micError: "Could not access microphone.",
247            },
248        };
249
250    /* --------------------------- 1) SVG icons (no external Font Awesome dependency) --------------------------- */
251    const SVG_ICONS = ASSETS_BASE + "daem_assets/SVG_Icons/";
252
253    /* --------------------------- 2) Inject widget DOM (NO iframe) --------------------------- */
254    const profileShortcuts = {
255        motor: "K",
256        visual: "Y",
257        colorblind: "J",
258        dyslexia: "X",
259        lowvision: "W",
260        cognitive: "N",
261        seizure: "E",
262        adhd: "Z",
263    };
264
265    // Maps each profile to the features it activates and their expected levels
266    const profileFeatureMap = {
267        motor: [{ id: "speech-text", level: 1 }],
268        visual: [{ id: "text-speech", level: 1 }],
269        colorblind: [{ id: "saturation", level: 3 }],
270        dyslexia: [{ id: "dyslexic-font", level: 1 }],
271        lowvision: [{ id: "saturation", level: 3 }, { id: "font-size", level: 2 }, { id: "cursor", level: 1 }],
272        cognitive: [{ id: "saturation", level: 2 }, { id: "font-size", level: 2 }, { id: "mask", level: 2 }],
273        seizure: [{ id: "contrast", level: 1 }, { id: "saturation", level: 4 }],
274        adhd: [{ id: "mask", level: 1 }, { id: "saturation", level: 1 }],
275    };
276
277    const widgetWrapper = document.createElement("div");
278    widgetWrapper.id = "daem-accessibility-widget";
279    widgetWrapper.setAttribute("dir", I18N.dir);
280    if (IS_AR) widgetWrapper.classList.add("daem-acc-ar");
281
282    widgetWrapper.innerHTML = `
283    <div id="daem-acc-filter-overlay"></div>
284
285    <button id="daem-access-trigger" class="${IconclassName}" aria-label="${I18N.openMenuAria}" type="button">
286      <img src="${SVG_ICONS}daem_mainicon.svg" class="daem-btn-icon" alt="">
287    </button>
288
289    <div id="daem-access-panel" aria-hidden="true">
290      <div class="daem-access-header" id="acc-drag-handle">
291        <h3>${I18N.title}</h3>
292        <div class="daem-access-actions">
293          <div class="daem-btn-wrap">
294            <button id="daem-access-reset" title="${I18N.resetTitle}" type="button">
295              <img src="${SVG_ICONS}daem_reset.svg" class="daem-btn-icon" alt="">
296            </button>
297            <span class="daem-shortcut-hint">Alt+R</span>
298          </div>
299          <div class="daem-btn-wrap">
300            <button id="daem-access-close" aria-label="${I18N.closeMenuAria}" type="button">
301              <img src="${SVG_ICONS}daem_close.svg" class="daem-btn-icon" alt="">
302            </button>
303            <span class="daem-shortcut-hint">Alt+O</span>
304          </div>
305        </div>
306      </div>
307
308      <div class="daem-profiles-accordion">
309        <div class="daem-profiles-header" id="profiles-toggle" role="button" tabindex="0">
310          <span>${I18N.profiles}</span>
311          <span class="daem-chevron">&#9660;</span>
312        </div>
313
314        <div class="daem-profiles-content" id="daem-profiles-content">
315          <div class="daem-profiles-grid">
316            <button class="daem-profile-btn" data-profile="motor" type="button">
317              <div class="daem-shortcut-badge">Alt+${profileShortcuts.motor}</div>
318              <img src="${SVG_ICONS}daem_profile_motor.svg" class="daem-btn-icon" alt="">
319              ${I18N.profilesLabels.motor}
320            </button>
321
322            <button class="daem-profile-btn" data-profile="visual" type="button">
323              <div class="daem-shortcut-badge">Alt+${profileShortcuts.visual}</div>
324              <img src="${SVG_ICONS}daem_profile_blind.svg" class="daem-btn-icon" alt="">
325              ${I18N.profilesLabels.visual}
326            </button>
327
328            <button class="daem-profile-btn" data-profile="colorblind" type="button">
329              <div class="daem-shortcut-badge">Alt+${profileShortcuts.colorblind}</div>
330              <img src="${SVG_ICONS}daem_profile_colorblind.svg" class="daem-btn-icon" alt="">
331              ${I18N.profilesLabels.colorblind}
332            </button>
333
334            <button class="daem-profile-btn" data-profile="dyslexia" type="button">
335              <div class="daem-shortcut-badge">Alt+${profileShortcuts.dyslexia}</div>
336              <img src="${SVG_ICONS}daem_profile_dyslexia.svg" class="daem-btn-icon" alt="">
337              ${I18N.profilesLabels.dyslexia}
338            </button>
339
340            <button class="daem-profile-btn" data-profile="lowvision" type="button">
341              <div class="daem-shortcut-badge">Alt+${profileShortcuts.lowvision}</div>
342              <img src="${SVG_ICONS}daem_profile_lowvision.svg" class="daem-btn-icon" alt="">
343              ${I18N.profilesLabels.lowvision}
344            </button>
345
346            <button class="daem-profile-btn" data-profile="cognitive" type="button">
347              <div class="daem-shortcut-badge">Alt+${profileShortcuts.cognitive}</div>
348              <img src="${SVG_ICONS}daem_profile_cognitive.svg" class="daem-btn-icon" alt="">
349              ${I18N.profilesLabels.cognitive}
350            </button>
351
352            <button class="daem-profile-btn" data-profile="seizure" type="button">
353              <div class="daem-shortcut-badge">Alt+${profileShortcuts.seizure}</div>
354              <img src="${SVG_ICONS}daem_profile_epilepsy.svg" class="daem-btn-icon" alt="">
355              ${I18N.profilesLabels.seizure}
356            </button>
357
358            <button class="daem-profile-btn" data-profile="adhd" type="button">
359              <div class="daem-shortcut-badge">Alt+${profileShortcuts.adhd}</div>
360              <img src="${SVG_ICONS}daem_profile_adhd.svg" class="daem-btn-icon" alt="">
361              ${I18N.profilesLabels.adhd}
362            </button>
363          </div>
364        </div>
365      </div>
366
367      <div class="daem-access-grid"></div>
368
369      <div class="daem-access-footer">
370        <img src="${I18N.logo}" alt="Daem Logo" style="max-height: 50px;">
371      </div>
372    </div>
373
374    <div id="daem-reading-mask"></div>
375    <div id="daem-reading-line"></div>
376
377    <div id="daem-sign-lang-modal">
378      <div class="daem-sign-header">
379        <span><img src="${SVG_ICONS}daem_signlanguagevideo.svg" class="daem-btn-icon" alt=""> ${I18N.signHeader}</span>
380        <button class="daem-sign-close" aria-label="${I18N.signClose}" type="button">
381          <img src="${SVG_ICONS}daem_close.svg" class="daem-btn-icon" alt="">
382        </button>
383      </div>
384
385      <div class="daem-sign-video-content">
386        <iframe
387          id="daem-sign-lang-iframe"
388          src=""
389          title="Sign Language Interpreter Video"
390          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
391          allowfullscreen
392        ></iframe>
393
394      </div>
395    </div>
396
397    <!-- Speech-to-Text Popup (NO iframe) -->
398    <div id="daem-stt-modal" aria-hidden="true">
399      <div class="daem-stt-card" role="dialog" aria-modal="true" aria-labelledby="daem-stt-title">
400        <div class="daem-stt-header">
401          <div id="daem-stt-title" class="daem-stt-title">${I18N.dynamic.sttTitle}</div>
402          <button id="daem-stt-close" class="daem-stt-close" type="button" aria-label="${I18N.dynamic.sttClose}">✕</button>
403        </div>
404
405        <div class="daem-stt-body">
406          <div id="daem-stt-help" class="daem-stt-help">${I18N.dynamic.sttHelp}</div>
407          <textarea
408            id="daem-stt-text"
409            class="daem-stt-text"
410            rows="6"           
411          ></textarea>
412           
413
414          <div class="daem-stt-actions">
415            <button id="daem-stt-copy" class="daem-stt-btn" type="button">
416              <span>${I18N.dynamic.sttCopy}</span>
417            </button>
418
419            <button id="daem-stt-clear" class="daem-stt-btn" type="button">
420              <span>${I18N.dynamic.sttClear}</span>
421            </button>
422
423            <button id="daem-stt-stop" class="daem-stt-btn" type="button">
424              <span>${I18N.dynamic.sttStop}</span>
425            </button>
426          </div>
427        </div>
428      </div>
429    </div>
430
431    <div
432      id="daem-hint-tooltip"
433      style="position:fixed;display:none;background:#fffcc2;border:1px solid #f9e29a;padding:5px 8px;border-radius:4px;font-size:12px;pointer-events:none;z-index:10004;max-width:300px;"
434    ></div>
435  `;
436
437    /* --------------------------- 3) Inject CSS (existing + RTL patches + STT modal) --------------------------- */
438    const styleElement = document.createElement("style");
439
440    styleElement.innerHTML = `
441  /* ========== Your Host Floating Button ========== */
442  .the-btn{
443    position: fixed;
444    background-color: #12cd6a;
445    width: 45px;
446    height: 45px;
447    border-radius: 50%;
448    text-align: center;
449    display: inline-block;
450    line-height: 35px !important;
451    z-index: 100000000;
452    background-color: #c5d6e9;
453    border: 4px solid #21c2f8;
454    padding: 0;
455  }
456  .Top_Left {left: 25px;top: 20px;}
457  .Top_Right {right: 25px;top: 20px;}
458  .Top_Center {left: 49%;top: 20px;}
459  .Middle_Left {left: 25px;top: 50%;}
460  .Middle_Right {right: 25px;top: 50%;}
461  .Bottom_Right {right: 25px;bottom: 20px;}
462  .Bottom_Center {left: 49%;bottom: 20px;}
463  .Bottom_Left {left: 25px;bottom: 20px;}
464  .the-btn img{width: 35px;height: auto;}
465
466  /* SVG icon sizes per context */
467  #daem-access-trigger .daem-btn-icon { width: 32px; height: 32px; object-fit: contain; }
468  .daem-access-header button .daem-btn-icon { width: 16px; height: 16px; object-fit: contain; filter: brightness(0) invert(1); }
469  .daem-profiles-header .daem-btn-icon { width: 16px; height: 16px; object-fit: contain; }
470  .daem-profile-btn .daem-btn-icon { width: 28px; height: 28px; object-fit: contain; margin-bottom: 5px; }
471  .daem-access-btn .daem-btn-icon { width: 32px; height: 32px; object-fit: contain; margin-bottom: 12px; }
472  .daem-sign-header .daem-btn-icon { width: 18px; height: 18px; object-fit: contain; vertical-align: middle; }
473  .daem-sign-close .daem-btn-icon { width: 14px; height: 14px; object-fit: contain; filter: brightness(0) invert(1); }
474
475  /* ========== Widget Variables ========== */
476  :root{
477    --daem-acc-primary: #2563eb;
478    --daem-acc-bg: #ffffff;
479    --daem-acc-text: #1f2937;
480    --daem-acc-active-bg: #eff6ff;
481    --daem-acc-active-border: #3b82f6;
482    --daem-reading-mask-height: 100px;
483  }
484
485  /* Custom Fonts */
486  @font-face {
487    font-family: 'AmericanSignLanguage';
488    src: url('${ASSETS_BASE}daem_assets/AmericanSignLanguage.woff2') format('woff2');
489    font-weight: normal;
490    font-style: normal;
491  }
492  @font-face {
493    font-family: 'ArabicSignLanguage';
494    src: url('${ASSETS_BASE}daem_assets/ARFONTS-MANISSI-ABJAD-ISHARA.TTF') format('truetype');
495    font-weight: normal;
496    font-style: normal;
497    unicode-range: U+0600-06FF, U+0750-077F, U+FB50-FDFF, U+FE70-FEFF;
498  }
499  @font-face {
500    font-family: 'OpenDyslexic';
501    src: url('${ASSETS_BASE}daem_assets/OpenDyslexic-Bold.woff2') format('woff2');
502    font-weight: bold;
503    font-style: normal;
504  }
505
506  /* Filter overlay */
507  #daem-acc-filter-overlay{
508    position: fixed;
509    top:0;
510    left:0;
511    width:100vw;
512    height:100vh;
513    pointer-events:none;
514    z-index:9998;
515    display:none;
516  }
517  body.daem-acc-contrast-1 #daem-acc-filter-overlay { display:block; backdrop-filter: contrast(150%); -webkit-backdrop-filter: contrast(150%); }
518  body.daem-acc-saturation-1 #daem-acc-filter-overlay { display:block; backdrop-filter: saturate(150%); -webkit-backdrop-filter: saturate(150%); }
519  body.daem-acc-saturation-2 #daem-acc-filter-overlay { display:block; backdrop-filter: saturate(200%); -webkit-backdrop-filter: saturate(200%); }
520  body.daem-acc-saturation-3 #daem-acc-filter-overlay { display:block; backdrop-filter: saturate(300%); -webkit-backdrop-filter: saturate(300%); }
521  body.daem-acc-saturation-4 #daem-acc-filter-overlay { display:block; backdrop-filter: grayscale(100%); -webkit-backdrop-filter: grayscale(100%); }
522
523  body.daem-acc-contrast-1{
524    background-color:#000 !important;
525    color:#fff !important;
526  }
527  body.daem-acc-contrast-1 *:not(#daem-accessibility-widget, #daem-accessibility-widget *){
528    background-color:#000 !important;
529    color:#fff !important;
530    border-color:#fff !important;
531  }
532
533  /* Dark mode */
534  body.daem-acc-dark-mode {
535    color-scheme: dark;
536  }
537  body.daem-acc-dark-mode,
538  body.daem-acc-dark-mode *:not(
539    #daem-accessibility-widget, #daem-accessibility-widget *,
540    img, svg, canvas, video, picture, source, iframe
541  ) {
542    background-color: #121212 !important;
543    color: #e2e2e2 !important;
544    border-color: #3a3a3a !important;
545  }
546  body.daem-acc-dark-mode *:not(#daem-accessibility-widget, #daem-accessibility-widget *)::before,
547  body.daem-acc-dark-mode *:not(#daem-accessibility-widget, #daem-accessibility-widget *)::after {
548    color: #e2e2e2 !important;
549  }
550  /* Elements with CSS background-image: keep original bg-color so image stays visible */
551  body.daem-acc-dark-mode *:not(#daem-accessibility-widget, #daem-accessibility-widget *).daem-has-bg-image {
552    background-color: initial !important;
553  }
554  body.daem-acc-dark-mode *:not(#daem-accessibility-widget, #daem-accessibility-widget *).daem-has-bg-image::before,
555  body.daem-acc-dark-mode *:not(#daem-accessibility-widget, #daem-accessibility-widget *).daem-has-bg-image::after {
556    background-color: initial !important;
557  }
558  /* Also cover dynamically-added children of bg-image elements (e.g. slick-dots added after JS scan) */
559  body.daem-acc-dark-mode .daem-has-bg-image *:not(#daem-accessibility-widget, #daem-accessibility-widget *) {
560    background-color: initial !important;
561  }
562  body.daem-acc-dark-mode .daem-has-bg-image *:not(#daem-accessibility-widget, #daem-accessibility-widget *)::before,
563  body.daem-acc-dark-mode .daem-has-bg-image *:not(#daem-accessibility-widget, #daem-accessibility-widget *)::after {
564    background-color: initial !important;
565  }
566  body.daem-acc-dark-mode a:not(#daem-accessibility-widget a) {
567    color: #7eb8f7 !important;
568  }
569  body.daem-acc-dark-mode img:not(#daem-accessibility-widget img),
570  body.daem-acc-dark-mode video:not(#daem-accessibility-widget video),
571  body.daem-acc-dark-mode svg:not(#daem-accessibility-widget svg) {
572    filter: brightness(0.85) contrast(1.05) !important;
573    background-color: transparent !important;
574  }
575
576  /* Widget container */
577 #daem-accessibility-widget {
578    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
579    z-index: 99999999999 !important;
580    filter: none !important;
581    position: relative !important;
582    display: block;
583}
584
585  /* Widget trigger (internal) */
586  #daem-access-trigger{
587    position: fixed;
588    bottom: 20px;
589    left: 20px;
590    right: auto;
591    width: 60px;
592    height: 60px;
593    /*background: var(--daem-acc-primary);*/
594    color: white;
595    border: none;
596    border-radius: 50%;
597    font-size: 28px;
598    cursor: pointer;
599    z-index: 10000;
600    background-color: #c5d6e9;
601    border: 4px solid #21c2f8
602  }
603
604  /* Panel (default LTR slide from left) */
605  #daem-access-panel{
606    position: fixed;
607    top: 0;
608    left: -400px;
609    right: auto;
610    width: 380px;
611    height: 100vh;
612    background: rgba(255,255,255,0.85);
613    backdrop-filter: blur(12px);
614    -webkit-backdrop-filter: blur(12px);
615    box-shadow: 10px 0 30px rgba(0,0,0,0.15);
616    transition: left 0.4s;
617    display: flex;
618    flex-direction: column;
619    z-index: 10001;
620    overflow-y: auto;
621  }
622  #daem-access-panel.daem-is-open{ left: 0; right: auto; }
623
624  /* Header */
625.daem-access-header{
626  padding: 12px 20px;        /* ✅ مسافة أعلى/أسفل أوضح */
627  min-height: 52px;          /* ✅ ارتفاع ثابت قريب من التصميم */
628  max-height: none;          /* ✅ الغِ التقييد اللي بيضغط الهيدر */
629  cursor: grab;
630  display: flex;
631  align-items: center;       /* ✅ توسيط رأسي حقيقي */
632  justify-content: space-between;
633  background: #fff;
634}
635
636/* Title */
637.daem-access-header h3{
638  margin: 0 !important;
639  font-size: 17px !important;
640  font-weight: bold !important;
641  line-height: 1.2;          /* ✅ يمنع تقطيع/ضغط */
642}
643
644/* Actions wrapper */
645.daem-access-actions{
646  display: flex;
647  align-items: flex-end;
648  gap: 8px;                  /* ✅ بدل اللعب في margins */
649}
650.daem-btn-wrap{
651  display: flex;
652  flex-direction: column;
653  align-items: center;
654  gap: 2px;
655}
656.daem-shortcut-hint{
657  font-size: 9px;
658  color: #aaa;
659  line-height: 1;
660  white-space: nowrap;
661  user-select: none;
662  letter-spacing: 0.3px;
663}
664
665/* Buttons */
666
667  .daem-access-header button{
668    font-size: 14px;
669    background-color: var(--daem-acc-primary);
670    color: white !important;
671    border: none;
672    border-radius: 5px;
673    padding: 8px 10px;
674    line-height: 1;
675    cursor: pointer;
676    margin-left: 5px;
677    box-shadow: none !important;
678  }
679
680  /* Profiles accordion */
681  .daem-profiles-accordion{ border-bottom: 1px solid rgba(0,0,0,0.1); background:#fff; }
682  .daem-profiles-header{
683    padding: 15px 20px;
684    cursor: pointer;
685    font-weight: 600;
686    color: var(--daem-acc-primary);
687    display:flex;
688    justify-content:space-between;
689    align-items:center;
690    background:#f8fafc;
691  }
692  .daem-profiles-header:hover{ background:#f1f5f9; }
693  .daem-profiles-content{ display:none; padding: 15px 20px; background:#fff; }
694  .daem-profiles-content.daem-active{ display:block; }
695  .daem-profiles-grid{ display:grid; grid-template-columns: 1fr 1fr; gap: 10px; }
696  .daem-profile-btn {
697    background: white;
698    border: 1px solid #e5e7eb;
699    border-radius: 8px;
700    padding: 10px;
701    font-size: 13px;
702    cursor: pointer;
703    text-align: center;
704    color: var(--daem-acc-text) !important;
705    transition: all 0.2s;
706    display: flex;
707    flex-direction: column;
708    align-items: center;
709    justify-content: center;
710    min-height: 60px;
711    box-shadow: none !important;
712    position: relative;
713}
714  .daem-profile-btn:hover{ background:#f9fafb; border-color:#d1d5db; }
715  .daem-profile-btn.daem-active{
716    background: var(--daem-acc-active-bg);
717    border: 2px solid var(--daem-acc-active-border);
718    color: var(--daem-acc-primary);
719    font-weight: bold;
720  }
721  .daem-profile-btn i{ margin-bottom: 5px; font-size: 18px; }
722
723  /* Feature grid */
724  .daem-access-grid{
725    display:grid;
726    grid-template-columns: 1fr 1fr;
727    gap: 15px;
728    padding: 25px;
729    flex-grow: 1;
730  }
731  .daem-access-btn{
732    display:flex;
733    flex-direction:column;
734    align-items:center;
735    justify-content:center;
736    background:white;
737    border:1px solid rgba(0,0,0,0.05);
738    border-radius:16px;
739    padding:15px 10px;
740    cursor:pointer;
741    position:relative;
742    height:110px;
743    color: var(--daem-acc-text);
744    text-align:center;
745    font-size: 14px !important;
746    font-weight: 400 !important;
747  }
748  .daem-access-btn.daem-active{
749    background: var(--daem-acc-active-bg);
750    border:2px solid var(--daem-acc-active-border);
751    color:#1d4ed8;
752  }
753  .daem-access-btn i{
754    font-size:28px;
755    margin-bottom:12px;
756    color: var(--daem-acc-text);
757  }
758  .daem-access-btn.daem-active i{ color: var(--daem-acc-primary); }
759
760  .daem-level-badge{
761    position:absolute;
762    top: 5px;
763    left: 5px;
764    width: 20px;
765    height: 20px;
766    background-color: var(--daem-acc-primary);
767    color: white;
768    border-radius: 50%;
769    font-size: 11px;
770    font-weight: bold;
771    display:none;
772    justify-content:center;
773    align-items:center;
774  }
775  .daem-access-btn.daem-active .daem-level-badge[data-level]:not([data-level="1"]){ display:flex; }
776
777  .daem-shortcut-badge{
778    position:absolute;
779    top:5px;
780    right:5px;
781    font-size: 10px;
782    opacity: 0.7;
783  }
784
785  /* Recording pulse */
786  @keyframes daem-pulse-red {
787    0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
788    70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
789    100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
790  }
791  .daem-access-btn.daem-recording-active{
792    border-color:#ef4444 !important;
793    color:#ef4444 !important;
794    animation: daem-pulse-red 2s infinite;
795  }
796  .daem-access-btn.daem-recording-active i{ color:#ef4444 !important; }
797
798  /* TTS speaking pulse (green) */
799  @keyframes daem-pulse-green {
800    0%   { box-shadow: 0 0 0 0   rgba(16, 185, 129, 0.8); }
801    70%  { box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);  }
802    100% { box-shadow: 0 0 0 0   rgba(16, 185, 129, 0);   }
803  }
804  .daem-access-btn.daem-tts-speaking {
805    border-color: #10b981 !important;
806    color: #10b981 !important;
807    animation: daem-pulse-green 1s infinite !important;
808  }
809  .daem-access-btn.daem-tts-speaking .daem-btn-icon { filter: none !important; }
810
811  /* Footer */
812  .daem-access-footer{
813    height: 50px;
814    min-height: 50px;
815    background:#fff;
816    display:flex;
817    justify-content:center;
818    align-items:center;
819    padding: 5px 0;
820    border-top: 1px solid #d1d5db;
821    flex-shrink: 0;
822    position: sticky;
823    bottom: 0;
824  }
825  .daem-access-footer img{ max-height:100%; max-width:90%; object-fit:contain; }
826
827  /* Cursor */
828  body.daem-acc-cursor *{
829    cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M4,3 L4,37 L13,28 L19,43 L25,40 L19,25 L32,25 Z" fill="white" stroke="black" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/></svg>') 4 3, auto !important;
830  }
831  body.daem-acc-cursor #daem-accessibility-widget button,
832  body.daem-acc-cursor #daem-accessibility-widget [role="button"],
833  body.daem-acc-cursor #daem-accessibility-widget [tabindex] {
834    cursor: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M4,3 L4,37 L13,28 L19,43 L25,40 L19,25 L32,25 Z" fill="white" stroke="black" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/></svg>') 4 3, auto !important;
835    pointer-events: auto !important;
836  }
837
838  /* Sign language modal */
839  #daem-sign-lang-modal{
840    position: fixed;
841    top: 50%;
842    left: 50%;
843    transform: translate(-50%, -50%);
844    width: 680px;
845    max-width: 90vw;
846    /* height: 550px */
847    background: rgba(0,0,0,0.9);
848    border-radius: 10px;
849    box-shadow: 0 10px 25px rgba(0,0,0,0.5);
850    z-index: 10005;
851    display:none;
852    flex-direction:column;
853    overflow:hidden;
854  }
855  #daem-sign-lang-modal.daem-active{ display:flex; }
856  #daem-sign-lang-modal .daem-sign-header{
857    display:flex;
858    justify-content:space-between;
859    align-items:center;
860    padding: 10px 15px;
861    background: var(--daem-acc-primary);
862    color:white;
863    flex-shrink: 0;
864  }
865  #daem-sign-lang-modal .daem-sign-header span i{ margin-right:8px; }
866  #daem-sign-lang-modal .daem-sign-close{
867    background:none;
868    border:none;
869    color:white;
870    font-size:20px;
871    cursor:pointer;
872  }
873  /* Sign language video container */
874#daem-sign-lang-modal .daem-sign-video-content {
875  flex-grow: 1;
876  padding: 8px;
877  display: flex;
878  align-items: center;
879  justify-content: center;
880}
881
882/* Iframe itself */
883#daem-sign-lang-iframe {
884  aspect-ratio: 16 / 9;
885  width: 100%;
886  height: auto;
887  max-height: 100%;
888}
889
890
891  /* الأهم */
892  object-fit: contain;   /* يمنع القص */
893  background: black;
894}
895
896
897  /* Reading mask/line */
898  #daem-reading-mask{
899  display:none;
900  position: fixed;
901  inset: 0;
902  z-index: 9998;
903  pointer-events:none;
904  background: linear-gradient(
905    to bottom,
906    rgba(0,0,0,0.75) 0%,
907    rgba(0,0,0,0.75) var(--rm-top, 40%),
908    rgba(0,0,0,0)     var(--rm-top, 40%),
909    rgba(0,0,0,0)     var(--rm-bottom, 60%),
910    rgba(0,0,0,0.75)  var(--rm-bottom, 60%),
911    rgba(0,0,0,0.75)  100%
912  );
913}
914body.daem-acc-mask-1 #daem-reading-mask{ display:block; }
915
916  #daem-reading-mask.daem-moving{
917    mask-image: linear-gradient(to bottom, black 0%, transparent 0%, transparent var(--daem-reading-mask-height), black var(--daem-reading-mask-height));
918    -webkit-mask-image: linear-gradient(to bottom, black 0%, transparent 0%, transparent var(--daem-reading-mask-height), black var(--daem-reading-mask-height));
919  }
920
921  #daem-reading-mask{
922  display:none;
923  position: fixed;
924  top:0;
925  left:0;
926  width:100%;
927  height:100%;
928  z-index: 9998;
929  background: rgba(0,0,0,0.7);
930  pointer-events:none;
931}
932body.daem-acc-mask-1 #daem-reading-mask{ display:block; }
933#daem-reading-mask.daem-moving{
934  mask-image: linear-gradient(...);
935  -webkit-mask-image: linear-gradient(...);
936}
937
938
939  #daem-reading-line{
940    display:none;
941    position: fixed;
942    width: 30%;
943    height: 5px;
944    background-color: yellow;
945    border-top: 2px solid black;
946    border-bottom: 2px solid black;
947    box-shadow: 0 0 10px rgba(0,0,0,0.3);
948    pointer-events:none;
949    z-index: 9999;
950    transform: translate(-50%, -50%);
951  }
952  #daem-reading-line::after{
953  content:"";
954  position:absolute;
955  left:50%;
956  top:-10px; /* فوق الخط */
957  transform:translateX(-50%);
958  width:0;
959  height:0;
960  border-left: 10px solid transparent;
961  border-right: 10px solid transparent;
962  border-bottom: 10px solid yellow;  /* نفس لون الخط */
963  filter: drop-shadow(0 1px 0 #000); /* حد بسيط */
964}
965  body.daem-acc-mask-2 #daem-reading-line{ display:block; }
966
967  /* Effects */
968
969  /* Default (English/Latin) */
970  body.daem-acc-sign-font-1 *:not(
971    #daem-accessibility-widget, #daem-accessibility-widget *,
972    #daem-sign-lang-modal, #daem-sign-lang-modal *,
973    i, i[class*="fa-"], [class*=" fa-"],
974    .material-icons,
975    [class*="icon-"], [class*="-icon"]
976  ) {
977    font-family: 'AmericanSignLanguage', sans-serif !important;
978  }
979  /* Arabic override */
980  body.daem-acc-sign-font-1 *:lang(ar):not(
981    #daem-accessibility-widget, #daem-accessibility-widget *,
982    #daem-sign-lang-modal, #daem-sign-lang-modal *,
983    i, i[class*="fa-"], [class*=" fa-"],
984    .material-icons,
985    [class*="icon-"], [class*="-icon"]
986  ) {
987    font-family: 'ArabicSignLanguage', sans-serif !important;
988  }
989
990  /* highlight */
991  span.daem-acc-sl-highlight {
992    font-size: inherit !important;
993    background-color: yellow !important;
994    color: black !important;
995    padding: 2px 0;
996    display: inline;
997  }
998  span.daem-acc-sl-highlight:lang(ar) { font-family: 'ArabicSignLanguage', sans-serif !important; }
999  span.daem-acc-sl-highlight:not(:lang(ar)) { font-family: 'AmericanSignLanguage', sans-serif !important; }
1000
1001  /* font-size levels handled via JS inline scaling (applyFontSizeJS) */
1002
1003  body.daem-acc-lineheight-1 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { line-height: 1.75 !important; }
1004  body.daem-acc-lineheight-2 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { line-height: 2 !important; }
1005  body.daem-acc-lineheight-3 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { line-height: 2.25 !important; }
1006  body.daem-acc-lineheight-4 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { line-height: 2.5 !important; }
1007
1008  body.daem-acc-spacing-1 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { letter-spacing: 1px !important; word-spacing: 2px !important; }
1009  body.daem-acc-spacing-2 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { letter-spacing: 2px !important; word-spacing: 4px !important; }
1010  body.daem-acc-spacing-3 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { letter-spacing: 3px !important; word-spacing: 6px !important; }
1011  body.daem-acc-spacing-4 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { letter-spacing: 4px !important; word-spacing: 8px !important; }
1012
1013  body.daem-acc-links a:not(#daem-accessibility-widget a){
1014    text-decoration: underline !important;
1015    text-decoration-color: #f59e0b !important;
1016    text-decoration-thickness: 2px !important;
1017    text-underline-offset: 3px !important;
1018  }
1019  body.daem-acc-links a:not(#daem-accessibility-widget a):not(:has(img)):not(:has(svg)):not(:has([class*="icon"])):not(:has(picture)) {
1020    box-shadow: 0 0 0 2px #f59e0b !important;
1021    border-radius: 3px !important;
1022  }
1023
1024  body.daem-acc-titles h1:not(#daem-accessibility-widget *),
1025  body.daem-acc-titles h2:not(#daem-accessibility-widget *),
1026  body.daem-acc-titles h3:not(#daem-accessibility-widget *),
1027  body.daem-acc-titles h4:not(#daem-accessibility-widget *),
1028  body.daem-acc-titles h5:not(#daem-accessibility-widget *),
1029  body.daem-acc-titles h6:not(#daem-accessibility-widget *){
1030    border: 2px solid var(--daem-acc-primary) !important;
1031    padding: 5px !important;
1032  }
1033
1034  body.daem-acc-dyslexic-font *:not(
1035    #daem-accessibility-widget,
1036    #daem-accessibility-widget *,
1037    i, i[class*="fa-"], [class*=" fa-"],
1038    .material-icons,
1039    [class*="icon-"], [class*="-icon"]
1040  ) {
1041    font-family: 'OpenDyslexic', sans-serif !important;
1042    font-size: inherit !important;
1043    font-weight: normal !important;
1044  }
1045  body.daem-acc-font *:not(
1046    #daem-accessibility-widget, #daem-accessibility-widget *,
1047    i, i[class*="fa-"], [class*=" fa-"],
1048    .material-icons,
1049    [class*="icon-"], [class*="-icon"]
1050  ) { font-family: Arial, sans-serif !important; }
1051
1052  body.daem-acc-stop-anim *:not(#daem-accessibility-widget, #daem-accessibility-widget *),
1053  body.daem-acc-stop-anim *:not(#daem-accessibility-widget, #daem-accessibility-widget *):before,
1054  body.daem-acc-stop-anim *:not(#daem-accessibility-widget, #daem-accessibility-widget *):after{
1055    animation:none !important;
1056    transition:none !important;
1057  }
1058
1059  body.daem-acc-hide-images img:not(#daem-accessibility-widget img){ display:none !important; }
1060
1061  body.daem-acc-bold-1 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { font-weight: 700 !important; }
1062  body.daem-acc-bold-2 *:not(#daem-accessibility-widget, #daem-accessibility-widget *) { font-weight: 900 !important; }
1063
1064  body.daem-acc-align-left *:not(#daem-accessibility-widget *) { text-align:left !important; }
1065  body.daem-acc-align-center *:not(#daem-accessibility-widget *) { text-align:center !important; }
1066  body.daem-acc-align-right *:not(#daem-accessibility-widget *) { text-align:right !important; }
1067
1068  /* ===== RTL patches (Arabic) ===== */
1069  #daem-accessibility-widget.daem-acc-ar #daem-access-trigger{ left: auto; right: 20px; }
1070  #daem-accessibility-widget.daem-acc-ar #daem-access-panel{
1071    left: auto;
1072    right: -400px;
1073    transition: right 0.4s;
1074    box-shadow: -10px 0 30px rgba(0,0,0,0.15);
1075  }
1076  #daem-accessibility-widget.daem-acc-ar #daem-access-panel.daem-is-open{ right: 0; left: auto; }
1077  #daem-accessibility-widget.daem-acc-ar .daem-shortcut-badge{ right: auto; left: 5px; }
1078  #daem-accessibility-widget.daem-acc-ar .daem-level-badge{ left: auto; right: 5px; }
1079
1080  /* ===== Speech-to-Text Modal ===== */
1081  #daem-stt-modal{
1082    position: fixed;
1083    inset: 0;
1084    display: none;
1085    z-index: 10006;
1086    background: rgba(0,0,0,0.55);
1087    align-items: center;
1088    justify-content: center;
1089    padding: 14px;
1090  }
1091  #daem-stt-modal.daem-active{ display:flex; }
1092  #daem-stt-modal .daem-stt-card{
1093    width: 560px;
1094    max-width: 95vw;
1095    background: #fff;
1096    border-radius: 14px;
1097    box-shadow: 0 16px 40px rgba(0,0,0,0.25);
1098    overflow: hidden;
1099  }
1100  #daem-stt-modal .daem-stt-header{
1101    display:flex;
1102    align-items:center;
1103    justify-content:space-between;
1104    padding: 12px 14px;
1105    background: var(--daem-acc-primary);
1106    color: #fff;
1107  }
1108  #daem-stt-modal .daem-stt-title{ font-weight: 700; }
1109  #daem-stt-modal .daem-stt-close{
1110    background: transparent;
1111    border: none;
1112    color: #fff !important;
1113    font-size: 18px;
1114    cursor: pointer;
1115    box-shadow: none !important;
1116    width: auto !important;
1117  }
1118  #daem-stt-modal .daem-stt-body{ padding: 14px; }
1119  #daem-stt-modal .daem-stt-help{
1120    font-size: 13px;
1121    opacity: 0.85;
1122    margin-bottom: 8px;
1123  }
1124  #daem-stt-modal .daem-stt-text{
1125    width: 100% !important;
1126    box-sizing: border-box;
1127    border: 1px solid #e5e7eb;
1128    border-radius: 10px;
1129    padding: 10px;
1130    resize: vertical;
1131    font-size: 14px;
1132    outline: none;
1133    box-shadow: none !important;
1134    background: #fff !important;
1135    max-width: 100% !important;
1136    min-width: 100% !important;
1137  }
1138  #daem-stt-modal .daem-stt-actions{
1139    display:flex;
1140    gap: 10px;
1141    margin-top: 10px;
1142    justify-content: flex-end;
1143  }
1144  #daem-stt-modal .daem-stt-btn{
1145    border: none;
1146    border-radius: 10px;
1147    padding: 10px 12px;
1148    cursor: pointer;
1149    background: #f3f4f6 !important;
1150
1151    display: inline-flex;
1152    align-items: center;
1153    gap: 8px;
1154    box-shadow: none !important;
1155    color: #343a40 !important;
1156  }
1157  #daem-stt-modal .daem-stt-btn i{ font-size: 14px; }
1158  #daem-stt-modal .daem-stt-btn:hover{ background:#e5e7eb; }
1159
1160  #daem-accessibility-widget.daem-acc-ar #daem-stt-modal .daem-stt-card,
1161  #daem-accessibility-widget.daem-acc-ar #daem-stt-modal { direction: rtl; }
1162  `;
1163
1164    /* --------------------------- 4) Inject scripts you already had --------------------------- */
1165    const scriptElement = document.createElement("script");
1166    scriptElement.src = plugHostingUrl2020 + scriptApplayChanges;
1167
1168    // Using native browser APIs for TTS & STT
1169    // const scriptElementsdk = document.createElement("script");
1170    // scriptElementsdk.src = scriptElementsdkURL;
1171
1172    /* --------------------------- 5) Append everything --------------------------- */
1173    body.appendChild(styleElement);
1174    body.appendChild(widgetWrapper);
1175    body.appendChild(scriptElement);
1176    // body.appendChild(scriptElementsdk);
1177
1178    // Patch window.onmessage after applyChange.js loads so that messages
1179    // with unknown keys don't throw an uncaught TypeError (infinite console error).
1180    scriptElement.addEventListener('load', function ()
1181    {
1182        var _orig = window.onmessage;
1183        if (typeof _orig === 'function')
1184        {
1185            window.onmessage = function (mes)
1186            {
1187                try
1188                {
1189                    _orig.call(this, mes);
1190                } catch (e)
1191                {
1192                    // Suppress: excuteChnaged[mes.data] is not a function
1193                    // Fires when the page sends postMessages unrelated to this widget.
1194                }
1195            };
1196        }
1197    });
1198
1199    /* --------------------------- 6) Widget behavior (JS) --------------------------- */
1200    const panel = document.getElementById("daem-access-panel");
1201    const accessTrigger = document.getElementById("daem-access-trigger");
1202    const accessClose = document.getElementById("daem-access-close");
1203    const accessReset = document.getElementById("daem-access-reset");
1204    const container = document.querySelector("#daem-access-panel .daem-access-grid");
1205    const readingMask = document.getElementById("daem-reading-mask");
1206    const readingLine = document.getElementById("daem-reading-line");
1207    const hintTooltip = document.getElementById("daem-hint-tooltip");
1208    const signLangModal = document.getElementById("daem-sign-lang-modal");
1209    const signLangIframe = document.getElementById("daem-sign-lang-iframe");
1210
1211    // STT Modal elements
1212    const sttModal = document.getElementById("daem-stt-modal");
1213    const sttTitle = document.getElementById("daem-stt-title");
1214    const sttHelp = document.getElementById("daem-stt-help");
1215    const sttText = document.getElementById("daem-stt-text");
1216    const sttClose = document.getElementById("daem-stt-close");
1217    const sttStop = document.getElementById("daem-stt-stop");
1218    const sttClear = document.getElementById("daem-stt-clear");
1219    const sttCopy = document.getElementById("daem-stt-copy");
1220
1221    const SIGN_VIDEO_URL = youTubeVideoLink; // provided by backend
1222
1223    // Feature definitions (localized labels)
1224    const features = [
1225        {
1226            id: "sign-vid",
1227            label: I18N.featureLabels["sign-vid"],
1228            icon: `${SVG_ICONS}daem_signlanguagevideo.svg`,
1229            key: "1",
1230        },
1231        {
1232            id: "sign-font",
1233            label: I18N.featureLabels["sign-font"],
1234            icon: `${SVG_ICONS}daem_signlanguagefont.svg`,
1235            key: "2",
1236        },
1237        {
1238            id: "text-speech",
1239            label: I18N.featureLabels["text-speech"],
1240            icon: `${SVG_ICONS}daem_tts.svg`,
1241            key: "3",
1242        },
1243        {
1244            id: "speech-text",
1245            label: I18N.featureLabels["speech-text"],
1246            icon: `${SVG_ICONS}daem_stt.svg`,
1247            key: "4",
1248        },
1249        {
1250            id: "dark-mode",
1251            label: I18N.featureLabels["dark-mode"],
1252            icon: `${SVG_ICONS}daem_darkmode.svg`,
1253            key: "C",
1254        },
1255        {
1256            id: "font-size",
1257            label: I18N.featureLabels["font-size"],
1258            icon: `${SVG_ICONS}daem_fontsize.svg`,
1259            key: "F",
1260        },
1261        {
1262            id: "cursor",
1263            label: I18N.featureLabels.cursor,
1264            icon: `${SVG_ICONS}daem_mouse.svg`,
1265            key: "M",
1266        },
1267        {
1268            id: "line-height",
1269            label: I18N.featureLabels["line-height"],
1270            icon: `${SVG_ICONS}daem_linehight.svg`,
1271            key: "L",
1272        },
1273        {
1274            id: "spacing",
1275            label: I18N.featureLabels.spacing,
1276            icon: `${SVG_ICONS}daem_fontsize.svg`,
1277            key: "S",
1278        },
1279        {
1280            id: "readable",
1281            label: I18N.featureLabels.readable,
1282            icon: `${SVG_ICONS}daem_readablefont.svg`,
1283            key: "R",
1284        },
1285        { id: "links", label: I18N.featureLabels.links, icon: `${SVG_ICONS}daem_linkhighlight.svg`, key: "H" },
1286        {
1287            id: "titles",
1288            label: I18N.featureLabels.titles,
1289            icon: `${SVG_ICONS}daem_heading.svg`,
1290            key: "T",
1291        },
1292        {
1293            id: "hint",
1294            label: I18N.featureLabels.hint,
1295            icon: `${SVG_ICONS}daem_hint.svg`,
1296            key: "Q",
1297        },
1298        {
1299            id: "stop-anim",
1300            label: I18N.featureLabels["stop-anim"],
1301            icon: `${SVG_ICONS}
1301daem_stopanimation.svg`,
1302            key: "P",
1303        },
1304        {
1305            id: "dyslexic-font",
1306            label: I18N.featureLabels["dyslexic-font"],
1307            icon: `${SVG_ICONS}daem_dyslexia.svg`,
1308            key: "D",
1309        },
1310        {
1311            id: "mask",
1312            label: I18N.featureLabels.mask,
1313            icon: `${SVG_ICONS}daem_readingmask.svg`,
1314            key: "V",
1315        },
1316        {
1317            id: "hide-images",
1318            label: I18N.featureLabels["hide-images"],
1319            icon: `${SVG_ICONS}daem_hideimages.svg`,
1320            key: "I",
1321        },
1322        {
1323            id: "bold-text",
1324            label: I18N.featureLabels["bold-text"],
1325            icon: `${SVG_ICONS}daem_bold.svg`,
1326            key: "B",
1327        },
1328        {
1329            id: "text-align",
1330            label: I18N.featureLabels["text-align"],
1331            icon: `${SVG_ICONS}daem_align.svg`,
1332            key: "G",
1333        },
1334        {
1335            id: "saturation",
1336            label: I18N.featureLabels.saturation,
1337            icon: `${SVG_ICONS}daem_saturation.svg`,
1338            key: "U",
1339        },
1340    ];
1341
1342    // Build feature buttons
1343    features.forEach((feat) =>
1344    {
1345        const btn = document.createElement("div");
1346        btn.className = "daem-access-btn";
1347        btn.setAttribute("data-id", feat.id);
1348        btn.setAttribute("role", "button");
1349        btn.setAttribute("aria-pressed", "false");
1350        btn.innerHTML = `
1351      <div class="daem-shortcut-badge">Alt+${feat.key}</div>
1352      <div class="daem-level-badge"></div>
1353      <img src="${feat.icon}" class="daem-btn-icon" alt="">
1354      <span>${feat.label}</span>
1355    `;
1356        btn.addEventListener("click", () => toggleFeature(feat.id, btn));
1357        container.appendChild(btn);
1358    });
1359
1360    //// Remove TTS/STT features and related profiles if AllowPageReaderVoiceToText is disabled
1361    //if (AllowPageReaderVoiceToText === "false" || AllowPageReaderVoiceToText === "False" || AllowPageReaderVoiceToText === "0" || AllowPageReaderVoiceToText === "")
1362    //{
1363    //    // Remove TTS and STT feature buttons
1364    //    const ttsBtn = container.querySelector('[data-id="text-speech"]');
1365    //    const sttBtn = container.querySelector('[data-id="speech-text"]');
1366    //    if (ttsBtn) ttsBtn.remove();
1367    //    if (sttBtn) sttBtn.remove();
1368
1369    //    // Remove visual (uses TTS) and motor (uses STT) profile buttons
1370    //    const visualProfile = widgetWrapper.querySelector('[data-profile="visual"]');
1371    //    const motorProfile = widgetWrapper.querySelector('[data-profile="motor"]');
1372    //    if (visualProfile) visualProfile.remove();
1373    //    if (motorProfile) motorProfile.remove();
1374    //}
1375
1376    // Draggable panel
1377    makePanelDraggable(panel, document.getElementById("acc-drag-handle"));
1378
1379    function makePanelDraggable(draggableElement, handle)
1380    {
1381        let isDragging = false;
1382        let startX;
1383        let initialOffset;
1384        const PANEL_WIDTH = 380;
1385
1386        function getCurrentOffset()
1387        {
1388            if (IS_AR)
1389            {
1390                const styleRight = draggableElement.style.right;
1391                return styleRight && styleRight !== "0px" ? parseFloat(styleRight) : 0;
1392            }
1393            const styleLeft = draggableElement.style.left;
1394            return styleLeft && styleLeft !== "0px" ? parseFloat(styleLeft) : 0;
1395        }
1396
1397        function setOffset(val)
1398        {
1399            if (IS_AR) draggableElement.style.right = `${val}px`;
1400            else draggableElement.style.left = `${val}px`;
1401        }
1402
1403        function onStart(e)
1404        {
1405            if (!draggableElement.classList.contains("daem-is-open")) return;
1406            if (e.button !== 0 && !e.touches) return;
1407            isDragging = true;
1408            document.body.style.userSelect = "none";
1409            initialOffset = getCurrentOffset();
1410            draggableElement.style.transition = "none";
1411            startX = e.clientX || (e.touches && e.touches[0].clientX);
1412        }
1413
1414        function onMove(e)
1415        {
1416            if (!isDragging) return;
1417            e.preventDefault();
1418            const currentX = e.clientX || (e.touches && e.touches[0].clientX);
1419            const dx = currentX - startX;
1420            let newOffset = IS_AR ? initialOffset - dx : initialOffset + dx;
1421            const maxOffset = Math.max(0, window.innerWidth - PANEL_WIDTH);
1422            const minOffset = 0;
1423            newOffset = Math.min(newOffset, maxOffset);
1424            newOffset = Math.max(newOffset, minOffset);
1425            setOffset(newOffset);
1426            draggableElement.classList.add("daem-is-open");
1427        }
1428
1429        function onEnd()
1430        {
1431            if (!isDragging) return;
1432            isDragging = false;
1433            document.body.style.userSelect = "";
1434            if (IS_AR) draggableElement.style.transition = "right 0.4s";
1435            else draggableElement.style.transition = "left 0.4s";
1436            const finalOffset = getCurrentOffset();
1437            const maxOffset = Math.max(0, window.innerWidth - PANEL_WIDTH);
1438            if (finalOffset < 0) setOffset(0);
1439            else if (finalOffset > maxOffset) setOffset(maxOffset);
1440            draggableElement.classList.add("daem-is-open");
1441        }
1442
1443        handle.addEventListener("mousedown", onStart);
1444        document.addEventListener("mousemove", onMove);
1445        document.addEventListener("mouseup", onEnd);
1446
1447        handle.addEventListener("touchstart", (e) => onStart(e), {
1448            passive: false,
1449        });
1450        document.addEventListener("touchmove", (e) => onMove(e), {
1451            passive: false,
1452        });
1453        document.addEventListener("touchend", onEnd);
1454    }
1455
1456    // State
1457    let fontSizeLevel = 0;
1458    let fontSizeOriginals = null; // Map<Element, {inline, computed}> captured on first activation
1459    let contrastLevel = 0;
1460    let lineHeightLevel = 0;
1461    let charSpacingLevel = 0;
1462    let textToSpeechLevel = 0;
1463    let signFontLevel = 0;
1464    let readingMaskLevel = 0;
1465    let boldLevel = 0;
1466    let textAlignLevel = 0;
1467    let saturationLevel = 0;
1468
1469    let isHintModeActive = false;
1470
1471    // Profile state: tracks active presets
1472    let activeProfiles = new Set();
1473    let profileActivating = false; // suppresses sync checks during profile activation
1474
1475    let ttsSynth = null;    // no longer used
1476    let ttsPlayer = null;   // holds current SpeechSynthesisUtterance — MUST keep reference to prevent Chrome GC bug
1477    let _ttsKeepAlive = null; // interval id for Chrome 15s resume workaround
1478    let ttsIsSpeaking = false;
1479
1480    // ---------- Speech-to-Text (native Web Speech API) state ----------
1481    let sttController = null; // { stop(), close() ... }
1482    let sttFinalText = "";
1483    let sttRecognizer = null;
1484    let sttIsRunning = false;
1485    let sttTargetElement = null;
1486    let _sttStartFn = null;    // stored restart function for resume button
1487
1488    // Helper: editable field
1489    function getActiveEditableElement()
1490    {
1491        const el = document.activeElement;
1492        if (!el) return null;
1493        const tag = (el.tagName || "").toUpperCase();
1494        if (tag === "INPUT" || tag === "TEXTAREA") return el;
1495        if (el.isContentEditable) return el;
1496        return null;
1497    }
1498
1499    function writeTextToElement(el, value)
1500    {
1501        if (!el) return;
1502        const tag = (el.tagName || "").toUpperCase();
1503        if (tag === "INPUT" || tag === "TEXTAREA")
1504        {
1505            el.value = value;
1506            el.dispatchEvent(new Event("input", { bubbles: true }));
1507            el.dispatchEvent(new Event("change", { bubbles: true }));
1508            return;
1509        }
1510        if (el.isContentEditable)
1511        {
1512            el.innerText = value;
1513            el.dispatchEvent(new Event("input", { bubbles: true }));
1514        }
1515    }
1516
1517    function setSttBoth(value)
1518    {
1519        sttText.value = value;
1520        writeTextToElement(sttTargetElement, value);
1521    }
1522
1523    function setStopButtonUI(isRunning)
1524    {
1525        const text = sttStop.querySelector("span");
1526        if (isRunning)
1527        {
1528            if (text) text.innerText = I18N.dynamic.sttStop;
1529        } else
1530        {
1531            if (text) text.innerText = I18N.dynamic.sttResume;
1532        }
1533    }
1534
1535    async function copySttText()
1536    {
1537        const value = (sttText.value || "").trim();
1538        if (!value) return;
1539
1540        try
1541        {
1542            await navigator.clipboard.writeText(value);
1543        } catch (e)
1544        {
1545            // Fallback
1546            sttText.focus();
1547            sttText.select();
1548            document.execCommand("copy");
1549            sttText.setSelectionRange(0, 0);
1550        }
1551
1552        const labelSpan = sttCopy.querySelector("span");
1553        const old = labelSpan ? labelSpan.innerText : "";
1554        if (labelSpan) labelSpan.innerText = I18N.dynamic.sttCopied || "Copied!";
1555        setTimeout(() =>
1556        {
1557            if (labelSpan) labelSpan.innerText = I18N.dynamic.sttCopy || old;
1558        }, 900);
1559    }
1560
1561    function openSttModal()
1562    {
1563        sttTitle.textContent = I18N.dynamic.sttTitle;
1564        sttHelp.textContent = I18N.dynamic.sttHelp;
1565
1566        const clearSpan = sttClear.querySelector("span");
1567        const copySpan = sttCopy.querySelector("span");
1568        if (clearSpan) clearSpan.innerText = I18N.dynamic.sttClear;
1569        if (copySpan) copySpan.innerText = I18N.dynamic.sttCopy;
1570
1571        sttClose.setAttribute("aria-label", I18N.dynamic.sttClose);
1572        setStopButtonUI(sttIsRunning);
1573
1574        // Use inline style as well as class — some host-page CSS may override
1575        // the class-based display rule.
1576        sttModal.style.display = 'flex';
1577        sttModal.classList.add("daem-active");
1578        sttModal.setAttribute("aria-hidden", "false");
1579    }
1580
1581    function closeSttModal()
1582    {
1583        sttModal.style.display = '';
1584        sttModal.classList.remove("daem-active");
1585        sttModal.setAttribute("aria-hidden", "true");
1586    }
1587
1588    // Close on overlay click
1589    sttModal.addEventListener("click", (e) =>
1590    {
1591        if (e.target === sttModal)
1592        {
1593            stopSpeechToText(true);
1594        }
1595    });
1596
1597    sttClose.addEventListener("click", () => stopSpeechToText(true));
1598    sttCopy.addEventListener("click", copySttText);
1599
1600    sttClear.addEventListener("click", () =>
1601    {
1602        sttFinalText = "";
1603        setSttBoth("");
1604    });
1605
1606    // Stop/Resume toggle
1607    sttStop.addEventListener("click", () =>
1608    {
1609        if (sttIsRunning)
1610        {
1611            // --- Pause ---
1612            sttIsRunning = false;
1613            _sttStartFn = true;
1614            if (sttRecognizer)
1615            {
1616                const old = sttRecognizer;
1617                sttRecognizer = null;
1618                try { old.abort(); } catch (e) { }
1619            }
1620            sttHelp.textContent = I18N.dynamic.sttStopped;
1621            setStopButtonUI(false);
1622        }
1623        else
1624        {
1625            // --- Resume ---
1626            if (!_sttStartFn) return; // no active session to resume
1627            sttIsRunning = true;
1628            sttHelp.textContent = I18N.dynamic.sttHelp;
1629            setStopButtonUI(true);
1630            speakToText();
1631            //_sttStartFn();
1632        }
1633    });
1634
1635    /**
1636     * speakToText (integrated)
1637     * - Opens popup
1638     * - Starts continuous recognition using native Web Speech API
1639     * - Writes into popup + active field
1640     */
1641    // Write text into the STT textarea, handling all cases:
1642    // 1) Direct assignment (works in plain HTML)
1643    // 2) Native prototype setter (bypasses React/Vue/Angular interceptors)
1644    // 3) Scroll to bottom so latest text is always visible
1645    function _setSttDisplay(text)
1646    {
1647        if (!sttText) return;
1648        // Set value directly — no bubbling events so the host page cannot
1649        // intercept and reset this textarea.
1650        sttText.value = text;
1651        // Fallback: native prototype setter for frameworks that override .value
1652        try
1653        {
1654            Object.getOwnPropertyDescriptor(
1655                window.HTMLTextAreaElement.prototype, 'value'
1656            ).set.call(sttText, text);
1657        } catch (_) { }
1658        // Scroll to newest content
1659        sttText.scrollTop = sttText.scrollHeight;
1660        // Only propagate to the original target field (if one was focused at STT start)
1661        if (sttTargetElement) writeTextToElement(sttTargetElement, text);
1662    }
1663
1664    function speakToText()
1665    {
1666
1667        const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1668        console.log('[STT] speakToText. API=', !!SpeechRecognition);
1669        if (!SpeechRecognition)
1670        {
1671            alert(I18N.alerts.sttUnsupported);
1672            return null;
1673        }
1674
1675        // Capture any currently focused editable field
1676        sttTargetElement = getActiveEditableElement();
1677
1678        // Reset state & UI
1679        if (!sttIsRunning)
1680        {
1681            sttFinalText = "";
1682        }
1683        sttHelp.textContent = I18N.dynamic.sttHelp;
1684        _setSttDisplay(sttFinalText);
1685        openSttModal();
1686
1687        // Cleanly destroy any previous recognizer
1688        const old = sttRecognizer;
1689        sttRecognizer = null;
1690        sttIsRunning = false;
1691        _sttStartFn = null;
1692        if (old) { try { old.abort(); } catch (_) { } }
1693
1694        let _noSpeechStreak = 0; // tracks consecutive no-speech events
1695
1696        // startRec: create a fresh SpeechRecognition instance and start it.
1697        // Each round uses a NEW instance because Chrome's implementation does
1698        // not reliably support calling .start() on an already-ended instance.
1699        function startRec()
1700        {
1701            if (!sttIsRunning) return;
1702
1703            const rec = new SpeechRecognition();
1704            sttRecognizer = rec;
1705
1706            // --- Config ---
1707            rec.lang = IS_AR ? "ar-SA" : "en-US";
1708            // continuous:false = one recognition round per instance (most reliable)
1709            rec.continuous = false;
1710            // interimResults:true = live preview while speaking
1711            rec.interimResults = true;
1712            rec.maxAlternatives = 1;
1713
1714            // --- Handlers ---
1715            rec.onstart = () =>
1716            {
1717                console.log('[STT] onstart lang=', rec.lang);
1718                sttHelp.textContent = I18N.dynamic.sttHelp;
1719            };
1720
1721            rec.onresult = (e) =>
1722            {
1723                // If the modal was accidentally closed while recognition is active, reopen it
1724                if (sttIsRunning && sttModal.style.display !== 'flex')
1725                    openSttModal();
1726
1727                // Case A: continuous:false → e.results always has exactly 1 item per call.
1728                // Case B: in some browsers e.results may accumulate → loop from 0 for safety.
1729                // We collect interim separately so the textarea shows live preview.
1730                let interim = "";
1731                let roundFinal = "";
1732
1733                for (let i = 0; i < e.results.length; i++)
1734                {
1735                    const t = (e.results[i][0].transcript || "").trim();
1736                    if (!t) continue; // skip empty tokens
1737                    if (e.results[i].isFinal)
1738                        roundFinal += (roundFinal ? " " : "") + t;
1739                    else
1740                        interim += (interim ? " " : "") + t;
1741                }
1742
1743                console.log('[STT] onresult final=' + JSON.stringify(roundFinal) + ' interim=' + JSON.stringify(interim));
1744
1745                // Append confirmed text to the session accumulator
1746                if (roundFinal)
1747                {
1748                    sttFinalText = sttFinalText
1749                        ? sttFinalText + " " + roundFinal
1750                        : roundFinal;
1751                    _noSpeechStreak = 0;
1752                }
1753
1754                // Display: confirmed text + live interim preview
1755                const display = interim
1756                    ? sttFinalText + (sttFinalText ? " " : "") + interim
1757                    : sttFinalText;
1758
1759                if (display) _setSttDisplay(display);
1760            };
1761
1762            rec.onerror = (e) =>
1763            {
1764                console.error('[STT] onerror:', e.error);
1765
1766                // Non-fatal — onend will restart
1767                if (e.error === "aborted") return;
1768                if (e.error === "no-speech")
1769                {
1770                    _noSpeechStreak++;
1771                    if (_noSpeechStreak >= 4)
1772                        sttHelp.textContent = IS_AR
1773                            ? "لم أسمعك. تحدّث بوضوح باتجاه الميكروفون."
1774                            : "No speech detected. Speak clearly into the microphone.";
1775                    return; // onend will restart the round
1776                }
1777
1778                // Fatal — stop the session
1779                sttIsRunning = false;
1780                sttRecognizer = null;
1781                _sttStartFn = null;
1782                setStopButtonUI(false);
1783
1784                switch (e.error)
1785                {
1786                    case "not-allowed":
1787                    case "service-not-allowed":
1788                        sttHelp.textContent = IS_AR
1789                            ? "الميكروفون محظور. اضغط على أيقونة القفل في شريط العنوان وافتح إذن الميكروفون."
1790                            : "Microphone blocked. Click the lock icon in the address bar and allow microphone.";
1791                        break;
1792                    case "network":
1793                        sttHelp.textContent = IS_AR
1794                            ? "لا يوجد اتصال بخدمة التعرف على الكلام. تحقق من الإنترنت."
1795                            : "Cannot reach speech service. Check your internet connection.";
1796                        break;
1797                    case "audio-capture":
1798                        sttHelp.textContent = IS_AR
1799                            ? "لا يوجد ميكروفون. تحقق من توصيل الجهاز."
1800                            : "No microphone found. Check device connection.";
1801                        break;
1802                    default:
1803                        sttHelp.textContent = (IS_AR ? "خطأ: " : "Error: ") + e.error;
1804                }
1805            };
1806
1807            rec.onend = () =>
1808            {
1809                console.log('[STT] onend. running=', sttIsRunning, 'same=', sttRecognizer === rec);
1810                // Only restart if this is still the active session
1811                if (sttIsRunning && sttRecognizer === rec)
1812                {
1813                    sttRecognizer = null;
1814                    // 300ms gap prevents Chrome InvalidStateError on fast re-start
1815                    setTimeout(startRec, 300);
1816                }
1817            };
1818
1819            try
1820            {
1821                rec.start();
1822                console.log('[STT] start() called. lang=', rec.lang);
1823            } catch (err)
1824            {
1825                console.error('[STT] start() threw:', err);
1826                // If start() throws, retry once after a longer delay
1827                sttRecognizer = null;
1828                if (sttIsRunning)
1829                    setTimeout(startRec, 800);
1830                else
1831                {
1832                    _sttStartFn = null;
1833                    sttHelp.textContent = I18N.dynamic.sttError;
1834                    setStopButtonUI(false);
1835                }
1836            }
1837        }
1838
1839        _sttStartFn = startRec;
1840        sttIsRunning = true;
1841        setStopButtonUI(true);
1842        startRec();
1843
1844        return {
1845            stop: () => stopSpeechToText(false),
1846            close: () => stopSpeechToText(true),
1847        };
1848    }
1849
1850    function stopSpeechToText(closePopup)
1851    {
1852        sttIsRunning = false;
1853        _sttStartFn = null;
1854
1855        // Null BEFORE abort so onend won't trigger a restart
1856        if (sttRecognizer)
1857        {
1858            const old = sttRecognizer;
1859            sttRecognizer = null;
1860            try { old.abort(); } catch (e) { }
1861        }
1862
1863        sttHelp.textContent = I18N.dynamic.sttStopped;
1864        setStopButtonUI(false);
1865
1866        // Update widget button UI
1867        const sttBtn = document.querySelector('.daem-access-btn[data-id="speech-text"]');
1868        if (sttBtn)
1869        {
1870            sttBtn.classList.remove("daem-recording-active", "daem-active");
1871            const sp = sttBtn.querySelector("span");
1872            if (sp) sp.innerText = I18N.dynamic.speechToText;
1873            syncProfileActiveState();
1874        }
1875
1876        if (closePopup) closeSttModal();
1877        sttController = null;
1878    }
1879
1880    function toggleSpeechToText(btnElement)
1881    {
1882        // If currently active => pause (keep popup open)
1883        if (btnElement.classList.contains("daem-recording-active"))
1884        {
1885            if (sttRecognizer && sttIsRunning)
1886            {
1887                sttIsRunning = false;
1888                try { sttRecognizer.stop(); } catch (e) { }
1889                sttHelp.textContent = I18N.dynamic.sttStopped;
1890                setStopButtonUI(false);
1891            }
1892            btnElement.classList.remove("daem-recording-active");
1893            btnElement.querySelector("span").innerText = I18N.dynamic.speechToText;
1894            return;
1895        }
1896
1897        // Start
1898        sttController = speakToText();
1899        if (!sttController) return;
1900
1901        btnElement.classList.add("daem-recording-active", "daem-active");
1902        btnElement.querySelector("span").innerText = I18N.dynamic.listening;
1903    }
1904
1905    function stopNativeTts()
1906    {
1907        console.log('[TTS] stopNativeTts called. ttsIsSpeaking=', ttsIsSpeaking);
1908        ttsIsSpeaking = false;
1909        ttsPlayer = null;
1910        if (_ttsKeepAlive) { clearInterval(_ttsKeepAlive); _ttsKeepAlive = null; }
1911        if (window.speechSynthesis) window.speechSynthesis.cancel();
1912        const ttsBtn = document.querySelector('.daem-access-btn[data-id="text-speech"]');
1913        if (ttsBtn) ttsBtn.classList.remove('daem-tts-speaking');
1914    }
1915
1916    function speakText(text)
1917    {
1918        console.log('[TTS] speakText called. text=', JSON.stringify(text.substring(0, 80)));
1919        if (!window.speechSynthesis)
1920        {
1921            alert(IS_AR ? "المتصفح لا يدعم تحويل النص إلى كلام." : "Browser does not support text-to-speech.");
1922            return;
1923        }
1924
1925        const synth = window.speechSynthesis;
1926        const ttsBtn = document.querySelector('.daem-access-btn[data-id="text-speech"]');
1927
1928        function _clearSpeakingUI()
1929        {
1930            ttsIsSpeaking = false;
1931            ttsPlayer = null;
1932            if (_ttsKeepAlive) { clearInterval(_ttsKeepAlive); _ttsKeepAlive = null; }
1933            if (ttsBtn) ttsBtn.classList.remove('daem-tts-speaking');
1934        }
1935
1936        // Clear any previous keep-alive and cancel queued speech
1937        if (_ttsKeepAlive) { clearInterval(_ttsKeepAlive); _ttsKeepAlive = null; }
1938        synth.cancel();
1939
1940        const utterance = new SpeechSynthesisUtterance(text);
1941        utterance.rate = 1;
1942        utterance.pitch = 1;
1943
1944        // --- Voice selection ---
1945        const voices = synth.getVoices();
1946        console.log('[TTS] voices available (' + voices.length + '):', voices.map(v => v.name + '[' + v.lang + ']').join(' | '));
1947
1948        let matchedVoice = null;
1949        if (IS_AR)
1950        {
1951            // Try multiple Arabic locale codes in order of preference
1952            matchedVoice =
1953                voices.find(v => v.lang === 'ar-SA') ||
1954                voices.find(v => v.lang === 'ar-EG') ||
1955                voices.find(v => v.lang === 'ar-XA') ||
1956                voices.find(v => v.lang === 'ar') ||
1957                voices.find(v => v.lang.toLowerCase().startsWith('ar'));
1958        }
1959        else
1960        {
1961            matchedVoice =
1962                voices.find(v => v.lang === 'en-US') ||
1963                voices.find(v => v.lang.startsWith('en'));
1964        }
1965
1966        if (matchedVoice)
1967        {
1968            utterance.voice = matchedVoice;
1969            utterance.lang = matchedVoice.lang; // align lang with the actual voice
1970            console.log('[TTS] ✔ voice selected:', matchedVoice.name, '[' + matchedVoice.lang + ']');
1971        }
1972        else
1973        {
1974            // No Arabic voice installed — fall back to first available voice so audio actually plays.
1975            // Chrome silently fires onstart+onend with no audio when lang='ar-SA' has no matching voice.
1976            // Setting an explicit available voice guarantees audible output.
1977            const fallback = voices.find(v => v.lang === 'en-US') || voices[0];
1978            if (fallback)
1979            {
1980                utterance.voice = fallback;
1981                utterance.lang = fallback.lang;
1982                console.warn('[TTS] ⚠ no Arabic voice found — falling back to:', fallback.name, '[' + fallback.lang + ']',
1983                    '| Install Windows Arabic TTS voice (Settings → Time & Language → Speech → Add voices) for proper Arabic speech.');
1984            }
1985            else
1986            {
1987                utterance.lang = IS_AR ? 'ar-SA' : 'en-US';
1988                console.warn('[TTS] ⚠ no voices available at all — attempting with lang=', utterance.lang);
1989            }
1990        }
1991
1992        // CRITICAL: keep strong reference to prevent Chrome GC bug (crbug.com/509488)
1993        ttsPlayer = utterance;
1994        ttsIsSpeaking = true;
1995        if (ttsBtn) ttsBtn.classList.add('daem-tts-speaking');
1996
1997        // Chrome bug: utterances >~15s get silently paused — poke resume() periodically
1998        _ttsKeepAlive = setInterval(() =>
1999        {
2000            if (!ttsIsSpeaking) { clearInterval(_ttsKeepAlive); _ttsKeepAlive = null; return; }
2001            if (synth.paused) { console.log('[TTS] keep-alive: resuming'); synth.resume(); }
2002        }, 5000);
2003
2004        utterance.onstart = () => { console.log('[TTS] ✓ onstart'); };
2005        utterance.onend = () => { console.log('[TTS] ✓ onend'); _clearSpeakingUI(); };
2006        utterance.onerror = (e) => { console.error('[TTS] ✗ onerror:', e.error); _clearSpeakingUI(); };
2007        utterance.onpause = () => { console.warn('[TTS] paused'); };
2008        utterance.onresume = () => { console.log('[TTS] resumed'); };
2009
2010        if (synth.paused) synth.resume();
2011        console.log('[TTS] calling synth.speak()...');
2012        synth.speak(utterance);
2013        console.log('[TTS] after speak(): speaking=', synth.speaking, 'pending=', synth.pending);
2014    }
2015
2016    // Open/close panel
2017    function togglePanel()
2018    {
2019        const isOpen = panel.classList.toggle("daem-is-open");
2020        accessTrigger.setAttribute("aria-expanded", String(isOpen));
2021        panel.setAttribute("aria-hidden", String(!isOpen));
2022
2023        // Slide behavior differs for RTL
2024        if (IS_AR)
2025        {
2026            panel.style.transition = "right 0.4s";
2027            panel.style.right = isOpen ? "0px" : "-400px";
2028            panel.style.left = "auto";
2029        } else
2030        {
2031            panel.style.transition = "left 0.4s";
2032            panel.style.left = isOpen ? "0px" : "-400px";
2033            panel.style.right = "auto";
2034        }
2035    }
2036
2037    function Open_Tool()
2038    {
2039        if (!panel.classList.contains("daem-is-open")) togglePanel();
2040    }
2041
2042    accessTrigger.addEventListener("click", togglePanel);
2043
2044    accessClose.addEventListener("click", () =>
2045    {
2046        panel.classList.remove("daem-is-open");
2047        panel.setAttribute("aria-hidden", "true");
2048        accessTrigger.setAttribute("aria-expanded", "false");
2049        if (IS_AR)
2050        {
2051            panel.style.transition = "right 0.4s";
2052            panel.style.right = "-400px";
2053            panel.style.left = "auto";
2054        } else
2055        {
2056            panel.style.transition = "left 0.4s";
2057            panel.style.left = "-400px";
2058            panel.style.right = "auto";
2059        }
2060    });
2061
2062    // Level badge
2063    function updateLevelBadge(btnElement, level)
2064    {
2065        const badge = btnElement.querySelector(".daem-level-badge");
2066        btnElement.classList.remove("daem-active");
2067
2068        if (level > 1 || (btnElement.dataset.id === "text-align" && level > 0))
2069        {
2070            badge.innerText = String(level);
2071            badge.setAttribute("data-level", String(level));
2072            btnElement.classList.add("daem-active");
2073        } else if (level === 1)
2074        {
2075            badge.removeAttribute("data-level");
2076            badge.innerText = "";
2077            btnElement.classList.add("daem-active");
2078        } else
2079        {
2080            badge.removeAttribute("data-level");
2081            badge.innerText = "";
2082        }
2083        syncProfileActiveState();
2084        saveState();
2085    }
2086
2087    // Returns the current numeric level for a given feature id
2088    function getFeatureLevel(id)
2089    {
2090        switch (id)
2091        {
2092            case "contrast": return contrastLevel;
2093            case "font-size": return fontSizeLevel;
2094            case "line-height": return lineHeightLevel;
2095            case "spacing": return charSpacingLevel;
2096            case "saturation": return saturationLevel;
2097            case "dark-mode": return document.body.classList.contains("daem-acc-dark-mode") ? 1 : 0;
2098            case "sign-font": return signFontLevel;
2099            case "mask": return readingMaskLevel;
2100            case "bold-text": return boldLevel;
2101            case "text-align": return textAlignLevel;
2102            case "text-speech": return textToSpeechLevel;
2103            case "cursor": return document.body.classList.contains("daem-acc-cursor") ? 1 : 0;
2104            case "readable": return document.body.classList.contains("daem-acc-font") ? 1 : 0;
2105            case "dyslexic-font": return document.body.classList.contains("daem-acc-dyslexic-font") ? 1 : 0;
2106            case "links": return document.body.classList.contains("daem-acc-links") ? 1 : 0;
2107            case "titles": return document.body.classList.contains("daem-acc-titles") ? 1 : 0;
2108            case "stop-anim": return document.body.classList.contains("daem-acc-stop-anim") ? 1 : 0;
2109            case "hide-images": return document.body.classList.contains("daem-acc-hide-images") ? 1 : 0;
2110            case "speech-text": {
2111                const b = document.querySelector('.daem-access-btn[data-id="speech-text"]');
2112                return (b && b.classList.contains("daem-active")) ? 1 : 0;
2113            }
2114            default: return 0;
2115        }
2116    }
2117
2118    // Deselects a profile badge if any of its expected features no longer match
2119    function syncProfileActiveState()
2120    {
2121        if (profileActivating) return;
2122        document.querySelectorAll(".daem-profile-btn").forEach(btn =>
2123        {
2124            const profileType = btn.dataset.profile;
2125            if (!activeProfiles.has(profileType)) return;
2126            const expectations = profileFeatureMap[profileType] || [];
2127            const allMatch = expectations.every(({ id, level }) => getFeatureLevel(id) === level);
2128            if (!allMatch)
2129            {
2130                activeProfiles.delete(profileType);
2131                btn.classList.remove("daem-active");
2132            }
2133        });
2134    }
2135
2136    // Scans page elements for CSS background-image and marks them + their descendants to prevent dark-mode bg-color override
2137    function daemScanBgImages()
2138    {
2139        var bgRects = [];
2140
2141        // Pass 1: find elements with background-image, mark them AND all DOM descendants
2142        document.querySelectorAll('*').forEach(function (el)
2143        {
2144            if (el.closest('#daem-accessibility-widget')) return;
2145            try
2146            {
2147                var cs = window.getComputedStyle(el);
2148                if (cs.backgroundImage && cs.backgroundImage !== 'none')
2149                {
2150                    el.classList.add('daem-has-bg-image');
2151                    bgRects.push(el.getBoundingClientRect());
2152                    // Mark all DOM descendants so overlay children are preserved too
2153                    el.querySelectorAll('*').forEach(function (child)
2154                    {
2155                        if (!child.closest('#daem-accessibility-widget'))
2156                        {
2157                            child.classList.add('daem-has-bg-image');
2158                        }
2159                    });
2160                }
2161            } catch (e) { }
2162        });
2163
2164        if (bgRects.length === 0) return;
2165
2166        // Pass 2: find transparent/semi-transparent elements that visually overlap bg-image areas
2167        // (handles cases where text overlay is a sibling, not a child, of the bg-image element)
2168        document.querySelectorAll('div, section, article, header, footer, nav, aside, span, figure, [class*="overlay"], [class*="caption"], [class*="content"], [class*="slide"], [class*="hero"], [class*="banner"]').forEach(function (el)
2169        {
2170            if (el.closest('#daem-accessibility-widget')) return;
2171            if (el.classList.contains('daem-has-bg-image')) return;
2172            try
2173            {
2174                var bgColor = window.getComputedStyle(el).backgroundColor;
2175                var isTransparent = bgColor === 'rgba(0, 0, 0, 0)' ||
2176                    (bgColor.indexOf('rgba') === 0 && !bgColor.endsWith(', 1)'));
2177                if (!isTransparent) return;
2178                var rect = el.getBoundingClientRect();
2179                var overlaps = bgRects.some(function (bgRect)
2180                {
2181                    return rect.right > bgRect.left && rect.left < bgRect.right &&
2182                        rect.bottom > bgRect.top && rect.top < bgRect.bottom;
2183                });
2184                if (overlaps) el.classList.add('daem-has-bg-image');
2185            } catch (e) { }
2186        });
2187    }
2188
2189    // Drives a feature to an exact target level (0 = off) by cycling via toggleFeature
2190    function setFeatureToLevel(id, targetLevel)
2191    {
2192        // First go to 0
2193        let safety = 20;
2194        while (getFeatureLevel(id) !== 0 && safety-- >
2194 0) toggleFeature(id);
2195        // Then step up to targetLevel
2196        safety = 20;
2197        while (getFeatureLevel(id) !== targetLevel && targetLevel > 0 && safety-- > 0) toggleFeature(id);
2198    }
2199
2200    // Selection handler (TTS + sign font select)
2201    function handleSelectionAction()
2202    {
2203        if (textToSpeechLevel === 1) readSelection();
2204        if (signFontLevel === 2) applySignFontToSelection();
2205    }
2206
2207    function readSelection()
2208    {
2209        const selectedText = window.getSelection().toString().trim();
2210        console.log('[TTS] readSelection called. selectedText=', JSON.stringify(selectedText.substring(0, 60)));
2211        if (!selectedText) { console.log('[TTS] readSelection: empty, skipping'); return; }
2212        speakText(selectedText);
2213    }
2214
2215    function applySignFontToSelection()
2216    {
2217        const selection = window.getSelection();
2218        if (!selection || selection.rangeCount === 0) return;
2219        const selectedText = selection.toString().trim();
2220
2221        if (selectedText.length === 0)
2222        {
2223            removeSignFontHighlight();
2224            return;
2225        }
2226
2227        removeSignFontHighlight();
2228
2229        const range = selection.getRangeAt(0);
2230        const commonAncestor = range.commonAncestorContainer;
2231        const root = commonAncestor.nodeType === Node.TEXT_NODE
2232            ? commonAncestor.parentNode
2233            : commonAncestor;
2234
2235        // Walk every text node within the range and wrap each one individually.
2236        // This avoids the block-inside-inline invalidity that breaks across headings/links.
2237        const textNodes = [];
2238        const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null);
2239        let node;
2240        while ((node = walker.nextNode()))
2241        {
2242            // Skip widget-internal nodes
2243            if (node.parentElement && node.parentElement.closest("#daem-accessibility-widget")) continue;
2244            if (range.intersectsNode(node)) textNodes.push(node);
2245        }
2246
2247        textNodes.forEach(textNode =>
2248        {
2249            const nodeRange = document.createRange();
2250            if (textNode === range.startContainer && textNode === range.endContainer)
2251            {
2252                nodeRange.setStart(textNode, range.startOffset);
2253                nodeRange.setEnd(textNode, range.endOffset);
2254            } else if (textNode === range.startContainer)
2255            {
2256                nodeRange.setStart(textNode, range.startOffset);
2257                nodeRange.setEndAfter(textNode);
2258            } else if (textNode === range.endContainer)
2259            {
2260                nodeRange.setStart(textNode, 0);
2261                nodeRange.setEnd(textNode, range.endOffset);
2262            } else
2263            {
2264                nodeRange.selectNodeContents(textNode);
2265            }
2266
2267            if (!nodeRange.toString().trim()) return;
2268
2269            const wrapper = document.createElement("span");
2270            wrapper.setAttribute("lang", IS_AR ? "ar" : "en");
2271            wrapper.setAttribute("dir", IS_AR ? "rtl" : "ltr");
2272            wrapper.className = "daem-acc-sl-highlight";
2273
2274            try
2275            {
2276                nodeRange.surroundContents(wrapper);
2277            } catch (e)
2278            {
2279                try
2280                {
2281                    const frag = nodeRange.extractContents();
2282                    wrapper.appendChild(frag);
2283                    nodeRange.insertNode(wrapper);
2284                } catch (e2) { /* skip malformed node */ }
2285            }
2286        });
2287
2288        selection.removeAllRanges();
2289    }
2290
2291    function removeSignFontHighlight()
2292    {
2293        document.querySelectorAll("span.daem-acc-sl-highlight").forEach((span) =>
2294        {
2295            const parent = span.parentNode;
2296            while (span.firstChild) parent.insertBefore(span.firstChild, span);
2297            parent.removeChild(span);
2298        });
2299    }
2300
2301    // Text to speech toggle
2302    function toggleTextToSpeech(btnElement)
2303    {
2304        console.log('[TTS] toggleTextToSpeech called. current textToSpeechLevel=', textToSpeechLevel);
2305        if (!window.speechSynthesis)
2306        {
2307            console.error('[TTS] window.speechSynthesis NOT available!');
2308            alert(
2309                IS_AR
2310                    ? "المتصفح لا يدعم تحويل النص إلى كلام."
2311                    : "Browser does not support text-to-speech."
2312            );
2313            return;
2314        }
2315
2316        // Stop any ongoing TTS first
2317        stopNativeTts();
2318
2319        // Toggle level (on/off)
2320        textToSpeechLevel = textToSpeechLevel === 1 ? 0 : 1;
2321
2322        // Remove previous listeners
2323        document.removeEventListener("mouseup", handleSelectionAction);
2324        document.removeEventListener("touchend", handleSelectionAction);
2325        document.removeEventListener("selectionchange", handleTtsSelectionChange);
2326
2327        if (textToSpeechLevel === 1)
2328        {
2329            // ENABLE TTS: read selection on mouse/touch release
2330            document.addEventListener("mouseup", handleSelectionAction);
2331            document.addEventListener("touchend", handleSelectionAction);
2332            document.addEventListener("selectionchange", handleTtsSelectionChange);
2333            btnElement.classList.add("daem-active");
2334            console.log('[TTS] ENABLED. Listening for mouseup/touchend. Voices available:', window.speechSynthesis.getVoices().length);
2335
2336            // Check if Arabic voice is available; if not, show install banner
2337            if (IS_AR)
2338            {
2339                const voices = window.speechSynthesis.getVoices();
2340                const hasArabicVoice = voices.some(v => v.lang.toLowerCase().startsWith('ar'));
2341                if (!hasArabicVoice)
2342                {
2343                    alert(
2344                        'لا يوجد صوت عربي مثبت على هذا الجهاز.\n\n' +
2345                        'لتفعيل النطق بالعربية:\n' +
2346                        '1. افتح إعدادات Windows\n' +
2347                        '2. الوقت واللغة ← الكلام\n' +
2348                        '3. إدارة الأصوات ← إضافة صوت\n' +
2349                        '4. ابحث عن "Arabic (Egypt)" وثبّته\n' +
2350                        '5. أعد تشغيل المتصفح'
2351                    );
2352                }
2353            }
2354
2355            // UX fix: if text is already selected when TTS is activated, read it immediately
2356            const existingSel = window.getSelection();
2357            if (existingSel && !existingSel.isCollapsed)
2358            {
2359                const existingText = existingSel.toString().trim();
2360                console.log('[TTS] existing selection on activate:', JSON.stringify(existingText.substring(0, 60)));
2361                if (existingText) speakText(existingText);
2362            }
2363        } else
2364        {
2365            // DISABLE TTS
2366            btnElement.classList.remove("daem-active");
2367
2368            // If sign-font selection mode is still active, keep selection handler
2369            if (signFontLevel === 2)
2370            {
2371                document.addEventListener("mouseup", handleSelectionAction);
2372                document.addEventListener("touchend", handleSelectionAction);
2373            }
2374        }
2375
2376        updateLevelBadge(btnElement, textToSpeechLevel);
2377    }
2378
2379    // Stop TTS immediately when the user collapses / clears their selection
2380    function handleTtsSelectionChange()
2381    {
2382        if (textToSpeechLevel !== 1) return;
2383        // Never cancel while TTS is actively speaking — selectionchange fires right after
2384        // mouseup triggers speak(), and cancelling here would kill the utterance instantly.
2385        if (ttsIsSpeaking) return;
2386        const sel = window.getSelection();
2387        if (!sel || sel.isCollapsed || sel.toString().trim() === "")
2388        {
2389            stopNativeTts();
2390        }
2391    }
2392
2393    // Sign font toggle
2394    function toggleSignFont(btnElement)
2395    {
2396        signFontLevel = (signFontLevel + 1) % 3;
2397
2398        document.body.classList.remove("daem-acc-sign-font-1");
2399        document.removeEventListener("mouseup", handleSelectionAction);
2400        document.removeEventListener("touchend", handleSelectionAction);
2401        removeSignFontHighlight();
2402
2403        if (signFontLevel === 1)
2404        {
2405            document.body.classList.add("daem-acc-sign-font-1");
2406            btnElement.querySelector("span").innerText = I18N.dynamic.slFontFull;
2407        } else if (signFontLevel === 2)
2408        {
2409            document.addEventListener("mouseup", handleSelectionAction);
2410            document.addEventListener("touchend", handleSelectionAction);
2411            btnElement.querySelector("span").innerText = I18N.dynamic.slFontSelect;
2412        } else
2413        {
2414            btnElement.querySelector("span").innerText = I18N.dynamic.slFont;
2415        }
2416
2417        // If TTS is active, ensure handler still exists
2418        if (textToSpeechLevel === 1 && signFontLevel !== 2)
2419        {
2420            document.addEventListener("mouseup", handleSelectionAction);
2421            document.addEventListener("touchend", handleSelectionAction);
2422        }
2423
2424        updateLevelBadge(btnElement, signFontLevel);
2425    }
2426
2427    // Sign video modal
2428    function closeSignLangModal()
2429    {
2430        signLangModal.classList.remove("daem-active");
2431        signLangIframe.src = "";
2432        const btnElement = document.querySelector(
2433            '.daem-access-btn[data-id="sign-vid"]'
2434        );
2435        if (btnElement) btnElement.classList.remove("daem-active");
2436    }
2437
2438    signLangModal
2439        .querySelector(".daem-sign-close")
2440        .addEventListener("click", closeSignLangModal);
2441
2442    function toggleSignLanguage(btnElement)
2443    {
2444        const isActive = btnElement.classList.contains("daem-active");
2445        if (!isActive)
2446        {
2447            signLangIframe.src = SIGN_VIDEO_URL || "";
2448            signLangModal.classList.add("daem-active");
2449            btnElement.classList.add("daem-active");
2450        } else
2451        {
2452            closeSignLangModal();
2453        }
2454    }
2455
2456    const MASK_HALF = 50; // = 100px total height like your design
2457
2458    function moveMask(e)
2459    {
2460        const mouseY = e.clientY || (e.touches ? e.touches[0].clientY : 0);
2461        const mouseX = e.clientX || (e.touches ? e.touches[0].clientX : 0);
2462
2463        if (readingMaskLevel === 1)
2464        {
2465            // Clamp in pixels so gradient is ALWAYS valid
2466            const topPx = Math.max(0, mouseY - MASK_HALF);
2467            const bottomPx = Math.min(window.innerHeight, mouseY + MASK_HALF);
2468
2469            const maskStyle =
2470                `linear-gradient(to bottom,
2471        rgba(0,0,0,1) 0px,
2472        rgba(0,0,0,1) ${topPx}px,
2473        rgba(0,0,0,0) ${topPx}px,
2474        rgba(0,0,0,0) ${bottomPx}px,
2475        rgba(0,0,0,1) ${bottomPx}px,
2476        rgba(0,0,0,1) 100%)`;
2477
2478            readingMask.style.webkitMaskImage = maskStyle;
2479            readingMask.style.maskImage = maskStyle;
2480        } else if (readingMaskLevel === 2)
2481        {
2482            readingLine.style.top = `${mouseY}px`;
2483            readingLine.style.left = `${mouseX}px`;
2484        }
2485    }
2486
2487    function toggleReadingMask(btnElement)
2488    {
2489        readingMaskLevel = (readingMaskLevel + 1) % 3;
2490
2491        document.body.classList.remove("daem-acc-mask-1", "daem-acc-mask-2");
2492
2493        // مهم: شيل listeners من window (هنستخدم window بدل document)
2494        window.removeEventListener("mousemove", moveMask);
2495        window.removeEventListener("touchmove", moveMask);
2496
2497        let buttonText = I18N.dynamic.readingMask;
2498
2499        if (readingMaskLevel === 1)
2500        {
2501            document.body.classList.add("daem-acc-mask-1");
2502            readingMask.classList.add("daem-moving");
2503            document.addEventListener("mousemove", moveMask);
2504            document.addEventListener("touchmove", moveMask);
2505
2506            // initialize immediately
2507            moveMask({ clientY: window.innerHeight / 2 });
2508        } else if (readingMaskLevel === 2)
2509        {
2510            document.body.classList.add("daem-acc-mask-2");
2511
2512            // ✅ تحديث فوري
2513            moveMask({ clientX: window.innerWidth / 2, clientY: window.innerHeight / 2 });
2514
2515            window.addEventListener("mousemove", moveMask);
2516            window.addEventListener("touchmove", moveMask, { passive: true });
2517
2518            btnElement.classList.add("daem-active");
2519            buttonText = I18N.dynamic.readingLine;
2520        } else
2521        {
2522            btnElement.classList.remove("daem-active");
2523        }
2524
2525        btnElement.querySelector("span").innerText = buttonText;
2526        updateLevelBadge(btnElement, readingMaskLevel);
2527    }
2528
2529
2530    // Font-size JS scaler (works on any page regardless of px/rem/em usage)
2531    const FONT_SIZE_SCALES = [1, 1.1, 1.25, 1.45, 1.7];
2532    function applyFontSizeJS(level)
2533    {
2534        if (level === 0)
2535        {
2536            if (fontSizeOriginals)
2537            {
2538                fontSizeOriginals.forEach((orig, el) =>
2539                {
2540                    if (!document.contains(el)) return;
2541                    if (orig.inline === null) el.style.removeProperty('font-size');
2542                    else el.style.fontSize = orig.inline;
2543                });
2544                fontSizeOriginals = null;
2545            }
2546            return;
2547        }
2548        const scale = FONT_SIZE_SCALES[level];
2549        // Capture original computed sizes once
2550        if (!fontSizeOriginals)
2551        {
2552            fontSizeOriginals = new Map();
2553            document.querySelectorAll(
2554                '*:not(#daem-accessibility-widget):not(#daem-accessibility-widget *)'
2555            ).forEach(el =>
2556            {
2557                fontSizeOriginals.set(el, {
2558                    inline: el.style.fontSize || null,
2559                    computed: parseFloat(window.getComputedStyle(el).fontSize)
2560                });
2561            });
2562        }
2563        // Apply scale from captured computed values
2564        fontSizeOriginals.forEach((orig, el) =>
2565        {
2566            if (!document.contains(el)) return;
2567            el.style.setProperty('font-size', (orig.computed * scale) + 'px', 'important');
2568        });
2569    }
2570
2571    // Hint mode
2572    function getHintText(element)
2573    {
2574        if (element.hasAttribute && element.hasAttribute("tab-hint"))
2575            return element.getAttribute("tab-hint");
2576        if (element.title) return `${I18N.hintPrefixes.title}: ${element.title}`;
2577        if (element.alt) return `${I18N.hintPrefixes.alt}: ${element.alt}`;
2578        if (element.placeholder)
2579            return `${I18N.hintPrefixes.placeholder}: ${element.placeholder}`;
2580        if (element.tagName === "A" && element.innerText)
2581            return `${I18N.hintPrefixes.link}: ${element.innerText}`;
2582        if (element.tagName === "BUTTON" && element.innerText)
2583            return `${I18N.hintPrefixes.button}: ${element.innerText}`;
2584        return null;
2585    }
2586
2587    function showHint(e)
2588    {
2589        const element = e.target;
2590        const hintText = getHintText(element);
2591
2592        if (
2593            hintText &&
2594            isHintModeActive &&
2595            !element.closest("#daem-accessibility-widget")
2596        )
2597        {
2598            hintTooltip.innerText = hintText;
2599            hintTooltip.style.display = "block";
2600            const rect = element.getBoundingClientRect();
2601            hintTooltip.style.left = `${rect.left + window.scrollX + 10}px`;
2602            hintTooltip.style.top = `${rect.top + 5}px`;
2603            /*hintTooltip.style.top = `${rect.top + window.scrollY + 5}px`;*/
2604        }
2605    }
2606
2607    function hideHint()
2608    {
2609        hintTooltip.style.display = "none";
2610    }
2611
2612    function toggleHintMode(btnElement)
2613    {
2614        isHintModeActive = !isHintModeActive;
2615
2616        if (isHintModeActive)
2617        {
2618            document.addEventListener("mouseover", showHint);
2619            document.addEventListener("focusin", showHint);
2620            document.addEventListener("mouseout", hideHint);
2621            document.addEventListener("focusout", hideHint);
2622            btnElement.classList.add("daem-active");
2623        } else
2624        {
2625            document.removeEventListener("mouseover", showHint);
2626            document.removeEventListener("focusin", showHint);
2627            document.removeEventListener("mouseout", hideHint);
2628            document.removeEventListener("focusout", hideHint);
2629            hideHint();
2630            btnElement.classList.remove("daem-active");
2631        }
2632    }
2633
2634    // Toggle dispatcher
2635    function toggleFeature(id, btnElement)
2636    {
2637        if (!btnElement)
2638        {
2639            btnElement = document.querySelector(`.daem-access-btn[data-id="${id}"]`);
2640            if (!btnElement) return;
2641        }
2642
2643        if (id === "speech-text") return toggleSpeechToText(btnElement);
2644        if (id === "text-speech") return toggleTextToSpeech(btnElement);
2645        if (id === "sign-font") return toggleSignFont(btnElement);
2646        if (id === "sign-vid") return toggleSignLanguage(btnElement);
2647        if (id === "mask") return toggleReadingMask(btnElement);
2648        if (id === "hint") return toggleHintMode(btnElement);
2649
2650        let currentLevel = 0;
2651
2652        switch (id)
2653        {
2654            case "contrast":
2655                contrastLevel = (contrastLevel + 1) % 2;
2656                currentLevel = contrastLevel;
2657                document.body.classList.remove("daem-acc-contrast-1");
2658                if (contrastLevel === 1) document.body.classList.add("daem-acc-contrast-1");
2659                break;
2660
2661            case "line-height":
2662                lineHeightLevel = (lineHeightLevel + 1) % 5;
2663                currentLevel = lineHeightLevel;
2664                document.body.classList.remove(
2665                    "daem-acc-lineheight-1",
2666                    "daem-acc-lineheight-2",
2667                    "daem-acc-lineheight-3",
2668                    "daem-acc-lineheight-4"
2669                );
2670                if (lineHeightLevel > 0)
2671                    document.body.classList.add(`daem-acc-lineheight-${lineHeightLevel}`);
2672                break;
2673
2674            case "font-size":
2675                fontSizeLevel = (fontSizeLevel + 1) % 5;
2676                currentLevel = fontSizeLevel;
2677                applyFontSizeJS(fontSizeLevel);
2678                break;
2679
2680            case "spacing":
2681                charSpacingLevel = (charSpacingLevel + 1) % 5;
2682                currentLevel = charSpacingLevel;
2683                document.body.classList.remove(
2684                    "daem-acc-spacing-1",
2685                    "daem-acc-spacing-2",
2686                    "daem-acc-spacing-3",
2687                    "daem-acc-spacing-4"
2688                );
2689                if (charSpacingLevel > 0)
2690                    document.body.classList.add(`daem-acc-spacing-${charSpacingLevel}`);
2691                break;
2692
2693            case "cursor":
2694                document.body.classList.toggle("daem-acc-cursor");
2695                currentLevel = document.body.classList.contains("daem-acc-cursor") ? 1 : 0;
2696                break;
2697
2698            case "readable":
2699                document.body.classList.toggle("daem-acc-font");
2700                currentLevel = document.body.classList.contains("daem-acc-font") ? 1 : 0;
2701                break;
2702
2703            case "dyslexic-font":
2704                document.body.classList.toggle("daem-acc-dyslexic-font");
2705                currentLevel = document.body.classList.contains("daem-acc-dyslexic-font")
2706                    ? 1
2707                    : 0;
2708                break;
2709
2710            case "links":
2711                document.body.classList.toggle("daem-acc-links");
2712                currentLevel = document.body.classList.contains("daem-acc-links") ? 1 : 0;
2713                break;
2714
2715            case "titles":
2716                document.body.classList.toggle("daem-acc-titles");
2717                currentLevel = document.body.classList.contains("daem-acc-titles") ? 1 : 0;
2718                break;
2719
2720            case "stop-anim":
2721                document.body.classList.toggle("daem-acc-stop-anim");
2722                currentLevel = document.body.classList.contains("daem-acc-stop-anim")
2723                    ? 1
2724                    : 0;
2725                break;
2726
2727            case "hide-images":
2728                document.body.classList.toggle("daem-acc-hide-images");
2729                currentLevel = document.body.classList.contains("daem-acc-hide-images")
2730                    ? 1
2731                    : 0;
2732                break;
2733
2734            case "bold-text":
2735                boldLevel = (boldLevel + 1) % 2;
2736                currentLevel = boldLevel;
2737                document.body.classList.remove("daem-acc-bold-1");
2738                if (boldLevel > 0) document.body.classList.add(`daem-acc-bold-${boldLevel}`);
2739                break;
2740
2741            case "text-align":
2742                textAlignLevel = (textAlignLevel + 1) % 4;
2743                currentLevel = textAlignLevel;
2744
2745                document.body.classList.remove(
2746                    "daem-acc-align-left",
2747                    "daem-acc-align-center",
2748                    "daem-acc-align-right"
2749                );
2750                let alignLabel = I18N.dynamic.textAlign;
2751
2752                if (textAlignLevel === 1)
2753                {
2754                    document.body.classList.add("daem-acc-align-left");
2755                    alignLabel = I18N.dynamic.alignLeft;
2756                } else if (textAlignLevel === 2)
2757                {
2758                    document.body.classList.add("daem-acc-align-center");
2759                    alignLabel = I18N.dynamic.alignCenter;
2760                } else if (textAlignLevel === 3)
2761                {
2762                    document.body.classList.add("daem-acc-align-right");
2763                    alignLabel = I18N.dynamic.alignRight;
2764                }
2765
2766                btnElement.querySelector("span").innerText = alignLabel;
2767                break;
2768
2769            case "saturation":
2770                saturationLevel = (saturationLevel + 1) % 5;
2771                currentLevel = saturationLevel;
2772                document.body.classList.remove(
2773                    "daem-acc-saturation-1",
2774                    "daem-acc-saturation-2",
2775                    "daem-acc-saturation-3",
2776                    "daem-acc-saturation-4"
2777                );
2778                if (saturationLevel > 0)
2779                    document.body.classList.add(`daem-acc-saturation-${saturationLevel}`);
2780                btnElement.querySelector("span").innerText =
2781                    saturationLevel === 4
2782                        ? I18N.dynamic.desaturate
2783                        : I18N.featureLabels.saturation;
2784                break;
2785
2786            case "dark-mode":
2787                var turningOn = !document.body.classList.contains("daem-acc-dark-mode");
2788                if (turningOn)
2789                {
2790                    daemScanBgImages(); // scan BEFORE dark mode class is added so original styles are readable
2791                    document.body.classList.add("daem-acc-dark-mode");
2792                    currentLevel = 1;
2793                } else
2794                {
2795                    document.body.classList.remove("daem-acc-dark-mode");
2796                    document.querySelectorAll('.daem-has-bg-image').forEach(el => el.classList.remove('daem-has-bg-image'));
2797                    currentLevel = 0;
2798                }
2799                break;
2800        }
2801
2802        updateLevelBadge(btnElement, currentLevel);
2803    }
2804
2805    // All CSS classes the widget adds to <body>
2806    const WIDGET_BODY_CLASSES = [
2807        "daem-acc-contrast-1",
2808        "daem-acc-dark-mode",
2809        "daem-acc-saturation-1", "daem-acc-saturation-2", "daem-acc-saturation-3", "daem-acc-saturation-4",
2810        "daem-acc-fontsize-1", "daem-acc-fontsize-2", "daem-acc-fontsize-3", "daem-acc-fontsize-4",
2811        "daem-acc-lineheight-1", "daem-acc-lineheight-2", "daem-acc-lineheight-3", "daem-acc-lineheight-4",
2812        "daem-acc-spacing-1", "daem-acc-spacing-2", "daem-acc-spacing-3", "daem-acc-spacing-4",
2813        "daem-acc-cursor",
2814        "daem-acc-sign-font-1",
2815        "daem-acc-links",
2816        "daem-acc-titles",
2817        "daem-acc-dyslexic-font",
2818        "daem-acc-font",
2819        "daem-acc-stop-anim",
2820        "daem-acc-hide-images",
2821        "daem-acc-bold-1", "daem-acc-bold-2",
2822        "daem-acc-align-left", "daem-acc-align-center", "daem-acc-align-right",
2823        "daem-acc-mask-1", "daem-acc-mask-2",
2824    ];
2825
2826    // Reset function
2827    function resetAll()
2828    {
2829        applyFontSizeJS(0); // restore all inline font-size overrides before clearing body classes
2830        // Remove only the widget's own body classes — leave all site classes untouched
2831        document.body.classList.remove(...WIDGET_BODY_CLASSES);
2832        // Do NOT wipe document.body.className or removeAttribute("style")
2833
2834        fontSizeLevel = 0;
2835        contrastLevel = 0;
2836        lineHeightLevel = 0;
2837        charSpacingLevel = 0;
2838        textToSpeechLevel = 0;
2839        signFontLevel = 0;
2840        readingMaskLevel = 0;
2841        boldLevel = 0;
2842        textAlignLevel = 0;
2843        saturationLevel = 0;
2844
2845        activeProfiles.clear();
2846
2847        // Stop STT and close popup on reset
2848        stopSpeechToText(true);
2849
2850        document.querySelectorAll(".daem-access-btn").forEach((btn) =>
2851        {
2852            btn.classList.remove("daem-active", "daem-recording-active");
2853
2854            const badge = btn.querySelector(".daem-level-badge");
2855            if (badge)
2856            {
2857                badge.removeAttribute("data-level");
2858                badge.innerText = "";
2859            }
2860
2861            const id = btn.dataset.id;
2862
2863            // Restore defaults
2864            if (id && I18N.featureLabels[id])
2865                btn.querySelector("span").innerText = I18N.featureLabels[id];
2866
2867            // Special ones that change dynamically
2868            if (id === "contrast")
2869                btn.querySelector("span").innerText = I18N.dynamic.contrast;
2870            if (id === "sign-font")
2871                btn.querySelector("span").innerText = I18N.dynamic.slFont;
2872            if (id === "text-speech")
2873                btn.querySelector("span").innerText = I18N.dynamic.textToSpeech;
2874            if (id === "speech-text")
2875                btn.querySelector("span").innerText = I18N.dynamic.speechToText;
2876            if (id === "mask")
2877                btn.querySelector("span").innerText = I18N.dynamic.readingMask;
2878            if (id === "text-align")
2879                btn.querySelector("span").innerText = I18N.dynamic.textAlign;
2880            if (id === "sign-vid")
2881                btn.querySelector("span").innerText = I18N.dynamic.signVideo;
2882            if (id === "hint")
2883                btn.querySelector("span").innerText = I18N.dynamic.hint;
2884        });
2885
2886        document
2887            .querySelectorAll(".daem-profile-btn")
2888            .forEach((pBtn) => pBtn.classList.remove("daem-active"));
2889
2890        window.speechSynthesis && stopNativeTts();
2891
2892        document.removeEventListener("mouseup", handleSelectionAction);
2893        document.removeEventListener("touchend", handleSelectionAction);
2894        document.removeEventListener("selectionchange", handleTtsSelectionChange);
2895
2896        removeSignFontHighlight();
2897
2898        document.body.classList.remove("daem-acc-mask-1", "daem-acc-mask-2");
2899        //readingMask.classList.remove("moving");
2900        document.removeEventListener("mousemove", moveMask);
2901        document.removeEventListener("touchmove", moveMask);
2902
2903        if (isHintModeActive)
2904        {
2905            // Turn off hint mode cleanly
2906            isHintModeActive = true;
2907            toggleHintMode(document.querySelector('.daem-access-btn[data-id="hint"]'));
2908        }
2909
2910        closeSignLangModal();
2911        hintTooltip.style.display = "none";
2912        localStorage.removeItem("daem_widget_state");
2913    }
2914
2915    // ---- Persistence helpers ----
2916    const DAEM_STORAGE_KEY = "daem_widget_state";
2917
2918    function saveState()
2919    {
2920        try
2921        {
2922            const state = {
2923                fontSizeLevel,
2924                contrastLevel,
2925                lineHeightLevel,
2926                charSpacingLevel,
2927                signFontLevel,
2928                readingMaskLevel,
2929                boldLevel,
2930                textAlignLevel,
2931                saturationLevel,
2932                darkMode: document.body.classList.contains("daem-acc-dark-mode"),
2933                cursor: document.body.classList.contains("daem-acc-cursor"),
2934                font: document.body.classList.contains("daem-acc-font"),
2935                dyslexicFont: document.body.classList.contains("daem-acc-dyslexic-font"),
2936                links: document.body.classList.contains("daem-acc-links"),
2937                titles: document.body.classList.contains("daem-acc-titles"),
2938                stopAnim: document.body.classList.contains("daem-acc-stop-anim"),
2939                hideImages: document.body.classList.contains("daem-acc-hide-images"),
2940                activeProfiles: [...activeProfiles],
2941            };
2942            localStorage.setItem(DAEM_STORAGE_KEY, JSON.stringify(state));
2943        } catch (e) { /* localStorage unavailable */ }
2944    }
2945
2946    function loadState()
2947    {
2948        let saved;
2949        try { saved = JSON.parse(localStorage.getItem(DAEM_STORAGE_KEY)); }
2950        catch (e) { return; }
2951        if (!saved) return;
2952
2953        // Numeric levels — step up via setFeatureToLevel
2954        const numericFeatures = [
2955            ["contrast", saved.contrastLevel],
2956            ["font-size", saved.fontSizeLevel],
2957            ["line-height", saved.lineHeightLevel],
2958            ["spacing", saved.charSpacingLevel],
2959            ["saturation", saved.saturationLevel],
2960            ["sign-font", saved.signFontLevel],
2961            ["mask", saved.readingMaskLevel],
2962            ["bold-text", saved.boldLevel],
2963            ["text-align", saved.textAlignLevel],
2964        ];
2965        numericFeatures.forEach(([id, level]) =>
2966        {
2967            if (level && level > 0) setFeatureToLevel(id, level);
2968        });
2969
2970        // Boolean toggles
2971        if (saved.darkMode) { daemScanBgImages(); setFeatureToLevel("dark-mode", 1); }
2972        if (saved.cursor) setFeatureToLevel("cursor", 1);
2973        if (saved.font) setFeatureToLevel("readable", 1);
2974        if (saved.dyslexicFont) setFeatureToLevel("dyslexic-font", 1);
2975        if (saved.links) setFeatureToLevel("links", 1);
2976        if (saved.titles) setFeatureToLevel("titles", 1);
2977        if (saved.stopAnim) setFeatureToLevel("stop-anim", 1);
2978        if (saved.hideImages) setFeatureToLevel("hide-images", 1);
2979
2980        // Restore active profile badges (visual only, features already applied above)
2981        if (Array.isArray(saved.activeProfiles))
2982        {
2983            saved.activeProfiles.forEach(profileType =>
2984            {
2985                activeProfiles.add(profileType);
2986                const btn = document.querySelector(`.daem-profile-btn[data-profile="${profileType}"]`);
2987                if (btn) btn.classList.add("daem-active");
2988            });
2989        }
2990    }
2991
2992    // Load persisted state once the widget DOM and feature definitions are ready
2993    loadState();
2994
2995    accessReset.addEventListener("click", resetAll);
2996
2997    // Keyboard shortcuts: Alt+A / Alt+O — toggle panel, Alt+R — reset, Alt+<key> — features, Alt+K/Y/J/X/W/N/E/Z — profiles
2998    document.addEventListener("keydown", (e) =>
2999    {
3000        if (e.altKey && e.key && e.key.toUpperCase() === "A")
3001        {
3002            e.preventDefault();
3003            togglePanel();
3004            return;
3005        }
3006        if (e.altKey && e.code === "KeyO")
3007        {
3008            e.preventDefault();
3009            togglePanel();
3010            return;
3011        }
3012        if (e.altKey && e.code === "KeyR")
3013        {
3014            e.preventDefault();
3015            resetAll();
3016            return;
3017        }
3018        if (e.altKey && e.key)
3019        {
3020            const key = e.key.toUpperCase();
3021            // Check profile shortcuts
3022            const profileEntry = Object.entries(profileShortcuts).find(
3023                ([, k]) => k === key
3024            );
3025            if (profileEntry)
3026            {
3027                e.preventDefault();
3028                const profileBtn = document.querySelector(
3029                    '.daem-profile-btn[data-profile="' + profileEntry[0] + '"]'
3030                );
3031                if (profileBtn) profileBtn.click();
3032                return;
3033            }
3034            // Check feature shortcuts
3035            const feature = features.find((f) => f.key === key);
3036            if (feature)
3037            {
3038                e.preventDefault();
3039                toggleFeature(feature.id);
3040            }
3041        }
3042    });
3043
3044    // Profiles accordion logic
3045    const profilesToggle = document.getElementById("profiles-toggle");
3046    const profilesContent = document.getElementById("daem-profiles-content");
3047
3048    profilesToggle.addEventListener("click", () =>
3049    {
3050        profilesContent.classList.toggle("daem-active");
3051        const chevron = profilesToggle.querySelector(".daem-chevron");
3052        if (chevron)
3053        {
3054            chevron.innerHTML = profilesContent.classList.contains("daem-active")
3055                ? "&#9650;"
3056                : "&#9660;";
3057        }
3058    });
3059
3060    // Profiles presets
3061    const profileBtns = document.querySelectorAll(".daem-profile-btn");
3062
3063    profileBtns.forEach((btn) =>
3064    {
3065        btn.addEventListener("click", () =>
3066        {
3067            const profileType = btn.dataset.profile;
3068            const wasActive = btn.classList.contains("daem-active");
3069
3070            if (wasActive)
3071            {
3072                btn.classList.remove("daem-active");
3073                activeProfiles.delete(profileType);
3074
3075                // For each feature this profile activated, set it to the highest level
3076                // still required by any remaining active profile (or 0 if none need it)
3077                const features = profileFeatureMap[profileType] || [];
3078                features.forEach(({ id }) =>
3079                {
3080                    const targetLevel = [...activeProfiles].reduce((max, pid) =>
3081                    {
3082                        const f = (profileFeatureMap[pid] || []).find(f => f.id === id);
3083                        return f ? Math.max(max, f.level) : max;
3084                    }, 0);
3085                    setFeatureToLevel(id, targetLevel);
3086                });
3087                return;
3088            }
3089
3090            // Activate this profile on top of any existing state (no resetAll)
3091            profileActivating = true;
3092            btn.classList.add("daem-active");
3093            activeProfiles.add(profileType);
3094
3095            const setFeature = (id, targetLevel) =>
3096            {
3097                for (let i = 0; i < targetLevel; i++) toggleFeature(id);
3098            };
3099
3100            switch (profileType)
3101            {
3102                case "motor":
3103                    toggleFeature("speech-text");
3104                    break;
3105                case "visual":
3106                    toggleFeature("text-speech");
3107                    break;
3108                case "colorblind":
3109                    setFeature("saturation", 3);
3110                    break;
3111                case "dyslexia":
3112                    toggleFeature("dyslexic-font");
3113                    break;
3114                case "lowvision":
3115                    setFeature("saturation", 3);
3116                    setFeature("font-size", 2);
3117                    toggleFeature("cursor");
3118                    break;
3119                case "cognitive":
3120                    setFeature("saturation", 2);
3121                    setFeature("font-size", 2);
3122                    setFeature("mask", 2);
3123                    break;
3124                case "seizure":
3125                    setFeature("contrast", 1);
3126                    setFeature("saturation", 4);
3127                    break;
3128                case "adhd":
3129                    setFeature("mask", 1);
3130                    setFeature("saturation", 1);
3131                    break;
3132            }
3133
3134            // Commit active profile — sync checks are now live
3135            profileActivating = false;
3136        });
3137    });
3138}
3139if (document.readyState === "complete") initTool();
3140else
3141    document.addEventListener("readystatechange", () =>
3142    {
3143        if (document.readyState === "complete") initTool();
3144    });

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.