/* Correctif visuel Dona : titre d'accueil et dégagement de la reconnaissance vocale. Ne touche ni à Hermes, ni à Vosk, ni aux réglages du mot d'appel. */ (() => { 'use strict'; const LABEL = 'RECONNAISSANCE VOCALE'; const COMPOSER = 'Écrire une demande à'; let pending = false; let lastStatus = null; let lastDock = null; let resizeObserver = null; const offsets = new WeakMap(); function visible(el) { if (!el || el.nodeType !== 1) return false; const s = getComputedStyle(el); const r = el.getBoundingClientRect(); return s.display !== 'none' && s.visibility !== 'hidden' && r.width > 0 && r.height > 0; } function textNodes() { const root = document.body; if (!root) return []; const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(n) { const p = n.parentElement; if (!p || /^(SCRIPT|STYLE|NOSCRIPT|TEXTAREA)$/i.test(p.tagName)) return NodeFilter.FILTER_REJECT; return n.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; } }); const nodes = []; while (walker.nextNode()) nodes.push(walker.currentNode); return nodes; } function correctTitle(nodes) { for (const n of nodes) { if (n.textContent.trim() !== 'Alfred' || !visible(n.parentElement)) continue; const el = n.parentElement; const size = parseFloat(getComputedStyle(el).fontSize) || 0; const heading = el.closest('h1,h2,[role="heading"]'); // Uniquement le grand titre d'accueil ; ne pas renommer les autres mentions. if (size >= 32 || (heading && parseFloat(getComputedStyle(heading).fontSize) >= 32)) { n.textContent = n.textContent.replace(/Alfred/, 'Dona'); } } } function findDock(nodes) { // V6: the compose label is intentionally hidden; locate the compact bar by ID. const compact = document.querySelector('.dona-compose-compact'); if (visible(compact)) return compact; const found = nodes.find(n => n.textContent.includes(COMPOSER) && visible(n.parentElement)); if (!found) return null; let best = null; let bestScore = -Infinity; let el = found.parentElement; for (let i = 0; i < 8 && el && el !== document.body; i++, el = el.parentElement) { const r = el.getBoundingClientRect(); if (!visible(el) || r.height < 45 || r.height > 265 || r.width < 250 || r.top < window.innerHeight * 0.45) continue; const css = getComputedStyle(el); const fixed = /^(fixed|sticky)$/.test(css.position); const radius = parseFloat(css.borderTopLeftRadius) || 0; const score = (fixed ? 30 : 0) + (radius > 12 ? 14 : 0) + (r.width > window.innerWidth * 0.55 ? 10 : 0) - Math.abs(r.bottom - (window.innerHeight - 16)) / 12 - Math.abs(r.height - 85) / 10; if (score > bestScore) { bestScore = score; best = el; } } return best; } function findVoiceRegion(nodes, dock) { const found = nodes.find(n => n.textContent.includes(LABEL) && visible(n.parentElement)); if (!found) return null; let el = found.parentElement; let candidate = el; for (let i = 0; i < 9 && el && el !== document.body; i++, el = el.parentElement) { const r = el.getBoundingClientRect(); if (dock && el.contains(dock)) break; const text = (el.innerText || '').trim(); if (r.height > 300 || text.includes('Prêt à vous aider') || text.length > 1100) break; // Prendre le groupe entier (statut Vosk + transcription), pas la seule étiquette. if (r.height >= 25 && visible(el)) candidate = el; } return candidate; } function update() { pending = false; const nodes = textNodes(); correctTitle(nodes); const dock = findDock(nodes); const status = findVoiceRegion(nodes, dock); if (resizeObserver) { if (lastStatus && lastStatus !== status) resizeObserver.unobserve(lastStatus); if (lastDock && lastDock !== dock) resizeObserver.unobserve(lastDock); if (status && lastStatus !== status) resizeObserver.observe(status); if (dock && lastDock !== dock) resizeObserver.observe(dock); } lastStatus = status; lastDock = dock; if (!status) return; const prev = offsets.get(status) || 0; const rect = status.getBoundingClientRect(); const originalBottom = rect.bottom + prev; const topLimit = dock ? dock.getBoundingClientRect().top - 24 : window.innerHeight - 22; const offset = dock ? Math.max(0, Math.ceil(originalBottom - topLimit)) : 0; const safeOffset = Math.min(offset, Math.max(0, rect.top + prev - 70)); if (Math.abs(safeOffset - prev) >= 1) { status.style.translate = safeOffset ? `0 -${safeOffset}px` : ''; offsets.set(status, safeOffset); } } function schedule() { if (pending) return; pending = true; requestAnimationFrame(update); } function init() { if ('ResizeObserver' in window) resizeObserver = new ResizeObserver(schedule); const observer = new MutationObserver(schedule); observer.observe(document.body, { subtree: true, childList: true, characterData: true }); window.addEventListener('resize', schedule, {passive: true}); window.addEventListener('orientationchange', schedule, {passive: true}); if (window.visualViewport) window.visualViewport.addEventListener('resize', schedule, {passive:true}); schedule(); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init, {once:true}); else init(); })();