const CHILLICOLLECTIVO_DEFAULT_VOICE_HOTKEY = {
  key: 'J',
  altKey: true,
  ctrlKey: false,
  shiftKey: false,
  metaKey: false,
};

const CHILLICOLLECTIVO_DEFAULT_PREVIEW_INSERT_HOTKEY = {
  key: 'Enter',
  altKey: false,
  ctrlKey: false,
  shiftKey: false,
  metaKey: false,
};

const CHILLICOLLECTIVO_DEFAULT_TAB_ENTER_HOTKEY = {
  key: 'ArrowLeft',
  altKey: false,
  ctrlKey: false,
  shiftKey: false,
  metaKey: false,
};

const CHILLICOLLECTIVO_MIC_DIAG_GLOW_GAP_MS = 1400;
const CHILLICOLLECTIVO_MIC_DIAG_LOG_MIN_MS = 2500;

function chillicollectivoGetEffectiveHotkey(customHotkey, fallbackHotkey) {
  return (customHotkey?.key || customHotkey?.code) ? customHotkey : fallbackHotkey;
}

function chillicollectivoGetEffectiveVoiceHotkey(customHotkey) {
  return chillicollectivoGetEffectiveHotkey(customHotkey, CHILLICOLLECTIVO_DEFAULT_VOICE_HOTKEY);
}

function chillicollectivoGetEffectivePreviewInsertHotkey(customHotkey) {
  return chillicollectivoGetEffectiveHotkey(customHotkey, CHILLICOLLECTIVO_DEFAULT_PREVIEW_INSERT_HOTKEY);
}

function chillicollectivoGetEffectiveTabEnterHotkey(customHotkey) {
  return chillicollectivoGetEffectiveHotkey(customHotkey, CHILLICOLLECTIVO_DEFAULT_TAB_ENTER_HOTKEY);
}

function chillicollectivoHotkeyUsesSingleKey(hotkey, fallbackHotkey) {
  const hk = chillicollectivoGetEffectiveHotkey(hotkey, fallbackHotkey);
  return !hk.ctrlKey && !hk.altKey && !hk.shiftKey && !hk.metaKey;
}

function chillicollectivoHotkeysEqual(a, b) {
  const left = chillicollectivoGetEffectiveVoiceHotkey(a?.key || a?.code ? a : null);
  const right = chillicollectivoGetEffectiveVoiceHotkey(b?.key || b?.code ? b : null);
  if (typeof chillicollectivoHotkeysStructurallyEqual === 'function') {
    return chillicollectivoHotkeysStructurallyEqual(left, right);
  }
  return String(left.key || '').toUpperCase() === String(right.key || '').toUpperCase()
    && !!left.altKey === !!right.altKey
    && !!left.ctrlKey === !!right.ctrlKey
    && !!left.shiftKey === !!right.shiftKey
    && !!left.metaKey === !!right.metaKey;
}

function chillicollectivoShouldHandleHotkeyInContent(customHotkey) {
  const hk = chillicollectivoGetEffectiveVoiceHotkey(customHotkey);
  return !!(hk.key || hk.code);
}

let chillicollectivoAltChordUntil = 0;
const CHILLICOLLECTIVO_MAC_ALT_J_CHARS = new Set(['º', '˚', '∆', 'ﬁ', 'j', 'J']);

function chillicollectivoTrackAltChord(event) {
  if (event.type === 'keydown' && (event.key === 'Alt' || event.code === 'AltLeft' || event.code === 'AltRight')) {
    chillicollectivoAltChordUntil = Date.now() + 450;
  }
}

function chillicollectivoModifierActive(event, name) {
  if (name === 'alt') {
    return !!event.altKey
      || (typeof event.getModifierState === 'function' && event.getModifierState('Alt'))
      || Date.now() < chillicollectivoAltChordUntil;
  }
  if (name === 'ctrl') return !!event.ctrlKey || (typeof event.getModifierState === 'function' && event.getModifierState('Control'));
  if (name === 'shift') return !!event.shiftKey || (typeof event.getModifierState === 'function' && event.getModifierState('Shift'));
  if (name === 'meta') return !!event.metaKey || (typeof event.getModifierState === 'function' && event.getModifierState('Meta'));
  return false;
}

function chillicollectivoHotkeyMatchesEvent(hotkey, event, fallbackHotkey) {
  const hk = chillicollectivoGetEffectiveHotkey(hotkey, fallbackHotkey);
  const keyMatch = typeof chillicollectivoHotkeyEventKeyMatches === 'function'
    ? chillicollectivoHotkeyEventKeyMatches(hk, event, CHILLICOLLECTIVO_MAC_ALT_J_CHARS)
    : String(event.key || '').toUpperCase() === String(hk.key || '').toUpperCase();
  if (!keyMatch) return false;

  const modMatch = chillicollectivoModifierActive(event, 'ctrl') === !!hk.ctrlKey
    && chillicollectivoModifierActive(event, 'shift') === !!hk.shiftKey
    && chillicollectivoModifierActive(event, 'alt') === !!hk.altKey
    && chillicollectivoModifierActive(event, 'meta') === !!hk.metaKey;
  return modMatch;
}

function chillicollectivoVoiceHotkeyMatchesEvent(hotkey, event) {
  return chillicollectivoHotkeyMatchesEvent(hotkey, event, CHILLICOLLECTIVO_DEFAULT_VOICE_HOTKEY);
}

function chillicollectivoPreviewInsertHotkeyMatchesEvent(hotkey, event) {
  if (!chillicollectivoHotkeyMatchesEvent(hotkey, event, CHILLICOLLECTIVO_DEFAULT_PREVIEW_INSERT_HOTKEY)) {
    return false;
  }
  const hk = chillicollectivoGetEffectivePreviewInsertHotkey(hotkey);
  if (hk.key === 'Enter' && !hk.shiftKey && !hk.ctrlKey && !hk.altKey && !hk.metaKey && event.shiftKey) {
    return false;
  }
  return true;
}

function chillicollectivoTabEnterHotkeyMatchesEvent(hotkey, event) {
  if (!hotkey?.key) return false;
  return chillicollectivoHotkeyMatchesEvent(hotkey, event, null);
}

function chillicollectivoIsPreviewEditableTarget(node) {
  const el = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
  return !!el?.closest?.('#chillicollectivo-voice-overlay .chillicollectivo-voice-overlay-committed[contenteditable="true"]');
}

function chillicollectivoShouldSkipTabEnterHotkey(hotkey, event) {
  if (!hotkey?.key) return true;
  if (!chillicollectivoHotkeyUsesSingleKey(hotkey, null)) return false;
  // Send-Hotkey nur in der Diktat-Vorschau ausblenden – im Chatfeld soll er senden.
  return chillicollectivoIsPreviewEditableTarget(event.target);
}

function chillicollectivoFormatHotkeyLabel(hotkey, fallbackHotkey) {
  const hk = chillicollectivoGetEffectiveHotkey(hotkey, fallbackHotkey);
  const isMac = navigator.platform.toUpperCase().includes('MAC');
  const keyLabel = (typeof chillicollectivoFormatHotkeyKeyLabel === 'function'
    ? chillicollectivoFormatHotkeyKeyLabel
    : (k) => String(k || ''))(hk.key, hk.code);
  if (isMac) {
    let out = '';
    if (hk.ctrlKey) out += '⌃';
    if (hk.altKey) out += '⌥';
    if (hk.shiftKey) out += '⇧';
    if (hk.metaKey) out += '⌘';
    out += keyLabel;
    return out;
  }
  const parts = [];
  if (hk.ctrlKey) parts.push('Ctrl');
  if (hk.altKey) parts.push('Alt');
  if (hk.shiftKey) parts.push('Shift');
  if (hk.metaKey) parts.push('Meta');
  parts.push(keyLabel);
  return parts.length === 1 ? keyLabel : parts.join('+');
}

function chillicollectivoFormatVoiceHotkeyLabel(hotkey) {
  return chillicollectivoFormatHotkeyLabel(hotkey, CHILLICOLLECTIVO_DEFAULT_VOICE_HOTKEY);
}

function chillicollectivoFormatPreviewInsertHotkeyLabel(hotkey) {
  return chillicollectivoFormatHotkeyLabel(hotkey, CHILLICOLLECTIVO_DEFAULT_PREVIEW_INSERT_HOTKEY);
}

function chillicollectivoFormatTabEnterHotkeyLabel(hotkey) {
  return chillicollectivoFormatHotkeyLabel(hotkey, CHILLICOLLECTIVO_DEFAULT_TAB_ENTER_HOTKEY);
}

function chillicollectivoVoiceHotkeyUsesSingleKey(hotkey) {
  const hk = (hotkey?.key || hotkey?.code) ? hotkey : null;
  if (!hk?.key) return false;
  return !hk.ctrlKey && !hk.altKey && !hk.shiftKey && !hk.metaKey;
}

function chillicollectivoIsEditableTarget(node) {
  const el = node?.nodeType === Node.ELEMENT_NODE ? node : node?.parentElement;
  if (!el) return false;
  if (el.isContentEditable) return true;
  if (el.closest?.('#chillicollectivo-voice-overlay .chillicollectivo-voice-overlay-committed[contenteditable="true"]')) return true;
  const tag = String(el.tagName || '').toLowerCase();
  if (tag === 'textarea') return true;
  if (tag === 'select') return true;
  if (tag === 'input') {
    const type = String(el.type || 'text').toLowerCase();
    return !['button', 'submit', 'reset', 'checkbox', 'radio', 'range', 'file', 'hidden', 'image'].includes(type);
  }
  return !!el.closest?.('textarea, select, input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="hidden"]), [contenteditable="true"], [contenteditable="plaintext-only"]');
}

function chillicollectivoShouldSkipSingleKeyHotkey(hotkey, event) {
  if (!chillicollectivoVoiceHotkeyUsesSingleKey(hotkey)) return false;
  return chillicollectivoIsEditableTarget(event.target);
}

const CHILLICOLLECTIVO_DEFAULT_PREVIEW_COLOR = '#D46E6E';
const CHILLICOLLECTIVO_DEFAULT_TOOL_COLOR = '#D46E6E';

function chillicollectivoNormalizePreviewColor(value) {
  const raw = String(value || '').trim();
  if (/^#[0-9a-f]{6}$/i.test(raw)) return raw.toUpperCase();
  if (/^#[0-9a-f]{3}$/i.test(raw)) {
    const r = raw[1];
    const g = raw[2];
    const b = raw[3];
    return `#${r}${r}${g}${g}${b}${b}`.toUpperCase();
  }
  return CHILLICOLLECTIVO_DEFAULT_PREVIEW_COLOR;
}

function chillicollectivoHexToRgb(hex) {
  const norm = chillicollectivoNormalizePreviewColor(hex).slice(1);
  return {
    r: parseInt(norm.slice(0, 2), 16),
    g: parseInt(norm.slice(2, 4), 16),
    b: parseInt(norm.slice(4, 6), 16),
  };
}

function chillicollectivoMixHexWithWhite(hex, whiteAmount = 0.78) {
  const { r, g, b } = chillicollectivoHexToRgb(hex);
  const mix = (channel) => Math.round(channel + (255 - channel) * whiteAmount);
  const toHex = (channel) => mix(channel).toString(16).padStart(2, '0');
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`.toUpperCase();
}

const CHILLICOLLECTIVO_SEND_BUTTON_TEXTS = ['senden', 'send', 'absenden', 'submit', 'abschicken', 'versenden'];
const CHILLICOLLECTIVO_OUT_COUNTER_KEY = 'chillicollectivoOutCounter';
const CHILLICOLLECTIVO_OUT_COUNTER_TOTAL_KEY = 'chillicollectivoOutCounterTotal';
const CHILLICOLLECTIVO_OUT_TARGET_KEY = 'chillicollectivoOutTarget';
const CHILLICOLLECTIVO_OUT_COUNTER_ENABLED_KEY = 'chillicollectivoOutCounterEnabled';
const CHILLICOLLECTIVO_OUT_PREVIEW_HOLD_MS = 2800;
const CHILLICOLLECTIVO_OUT_PREVIEW_FADE_MS = 1100;
const CHILLICOLLECTIVO_PREVIEW_BASE_WIDTH = 720;
const CHILLICOLLECTIVO_PREVIEW_BASE_HEIGHT = 132;
const CHILLICOLLECTIVO_PREVIEW_BASE_FONT_PX = 22;
const CHILLICOLLECTIVO_PREVIEW_FONT_WEIGHT_MAP = {
  light: 300,
  semibold: 600,
  extrabold: 800,
};
const CHILLICOLLECTIVO_DEFAULT_PREVIEW_FONT_WEIGHT = 'light';
const CHILLICOLLECTIVO_PREVIEW_FONT_FAMILY = 'ChilliPreview';
const CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE = 'sentence';
const CHILLICOLLECTIVO_PREVIEW_TEXT_CASE_MODES = new Set(['sentence', 'spoken', 'lowercase', 'uppercase']);
const CHILLICOLLECTIVO_PREVIEW_MIN_WIDTH = 260;
const CHILLICOLLECTIVO_PREVIEW_MIN_HEIGHT = 72;
const CHILLICOLLECTIVO_PREVIEW_SIZE_STORAGE_KEY = 'chillicollectivoVoicePreviewSize';
const CHILLICOLLECTIVO_OUT_BUMP_DEDUPE_MS = 2800;
const CHILLICOLLECTIVO_OUT_MILESTONE_TIP_MS = 5200;
const CHILLICOLLECTIVO_MIC_TIPS = [
  'PP nicht vergessen',
  'Denk dran Wasser zu trinken!',
  'HOL DIR DIE INS !',
  'Fickpraline!',
  'ASA',
  'Nummer bekommen? Dann klick aufs Kästchen!! (Kundendaten erhalten)',
  'Alles für die INS',
  'Wieviel ASA heute noch? ..-.-',
  'Morgen doppelt soviel!',
  'Scharf wie Chilis!',
  'Lustloch!',
  'Antworte mit Bezug auf die letzte Antwort des Kunden!',
  'Lieber zusammen durch die Decke, als alleine!',
  'Verlier dich nicht in den Chat!',
  'Konzentration!',
  'Mindset ist alles',
  'Lass uns Spass haben!',
  'Lass dich nicht ärgern!',
  'Heute 1000 Ins?',
  'Honigtopf!',
  'Scheiss deine Kollegen nicht an!',
  'Weitergehts!',
  'Lets go, ab in den IN-Regen!',
  'Kein Plan? Geh in die Workshops!',
  'Kunde will Bild? Sei schüchtern!',
  'Gib gas oder ich gehe "mal eben" Zigaretten holen!',
];
const CHILLICOLLECTIVO_MIC_TIP_MIN_MS = 20 * 1000;
const CHILLICOLLECTIVO_MIC_TIP_MAX_MS = 20 * 1000;
const CHILLICOLLECTIVO_MIC_TIP_SHOW_MS = 3800;

function chillicollectivoStripVoiceSendTokens(text) {
  if (typeof chillicollectivoStripGoSendTokens === 'function') {
    return chillicollectivoStripGoSendTokens(text);
  }
  return String(text || '');
}

function chillicollectivoGetLogCustomerContext() {
  return {};
}

function chillicollectivoLogVoiceDictation(kind, text, meta = {}) {
  try {
    const clean = String(text || '').replace(/\s+/g, ' ').trim();
    if (!clean) return;
    window.ChillicollectivoDiagnosticLog?.info?.('voice.dictation-event', kind, {
      kind: String(kind || 'dictation'),
      textLen: clean.length,
      route: meta.route || '',
      reason: meta.reason || '',
    }).catch?.(() => {});
  } catch (_) {}
}

function chillicollectivoFormatOutPreviewLabel(count, target, sendWaitSec) {
  const targetNum = Math.floor(Number(target) || 0);
  const waitSec = Math.floor(Number(sendWaitSec));
  let base = targetNum > 0 ? `${count}/${targetNum}` : (count === 1 ? '1 Out' : `${count} Outs`);
  if (Number.isFinite(waitSec) && waitSec > 0) {
    base += ` · ${waitSec}s`;
  }
  return base;
}

function chillicollectivoIsVisibleElement(el) {
  if (!el || !el.isConnected) return false;
  const rect = el.getBoundingClientRect();
  return rect.width > 0 && rect.height > 0;
}

function chillicollectivoExtractStableFlipDigits(spans) {
  const columns = new Map();
  for (const span of spans) {
    const digit = String(span.textContent || '').trim();
    if (!/^\d$/.test(digit)) continue;
    const left = Math.round(span.getBoundingClientRect().left / 6) * 6;
    if (!columns.has(left)) columns.set(left, []);
    columns.get(left).push(span);
  }
  const digits = [];
  for (const [, group] of [...columns.entries()].sort((a, b) => a[0] - b[0])) {
    const stable = group.find((span) => !/rotateX/i.test(span.getAttribute('style') || ''))
      || group.find((span) => {
        const match = (span.getAttribute('style') || '').match(/rotateX\(([-\d.]+)deg\)/i);
        return match && Math.abs(parseFloat(match[1])) < 20;
      })
      || group[group.length - 1];
    if (stable) digits.push(String(stable.textContent || '').trim());
  }
  if (!digits.length) return null;
  const value = parseInt(digits.join(''), 10);
  return Number.isFinite(value) && value >= 0 && value <= 999 ? value : null;
}

function chillicollectivoParseFlipCountdownContainer(container) {
  if (!container || !chillicollectivoIsVisibleElement(container)) return null;

  const parent = container.parentElement;
  if (parent) {
    const siblingColumns = [...parent.children]
      .filter((el) => el.tagName === 'DIV' && chillicollectivoIsVisibleElement(el))
      .map((div) => {
        const spans = [...div.querySelectorAll(':scope > span')].filter((span) => /^\d$/.test(String(span.textContent || '').trim()));
        if (spans.length < 2) return null;
        if (!spans.some((span) => /rotateX/i.test(span.getAttribute('style') || ''))) return null;
        const digit = chillicollectivoExtractStableFlipDigits(spans);
        if (digit == null || digit < 0 || digit > 9) return null;
        return { left: div.getBoundingClientRect().left, digit: String(digit) };
      })
      .filter(Boolean);
    if (siblingColumns.length >= 2) {
      siblingColumns.sort((a, b) => a.left - b.left);
      const clustered = parseInt(siblingColumns.map((item) => item.digit).join(''), 10);
      if (Number.isFinite(clustered) && clustered >= 0 && clustered <= 999) return clustered;
    }
  }

  const directSpans = [...container.querySelectorAll(':scope > span')].filter((span) => /^\d$/.test(String(span.textContent || '').trim()));
  if (directSpans.length < 2) return null;
  if (directSpans.some((span) => /rotateX/i.test(span.getAttribute('style') || ''))) {
    return chillicollectivoExtractStableFlipDigits(directSpans);
  }
  const joined = parseInt(directSpans.map((span) => span.textContent.trim()).join(''), 10);
  return Number.isFinite(joined) && joined >= 0 && joined <= 999 ? joined : null;
}

function chillicollectivoFindSendCountdownNearField(field) {
  const sendBtn = chillicollectivoFindSendButtonForField(field);
  if (!sendBtn || !chillicollectivoIsVisibleElement(sendBtn)) return null;

  const sendRect = sendBtn.getBoundingClientRect();
  let best = null;
  let bestScore = Infinity;

  let node = sendBtn.parentElement;
  for (let depth = 0; depth < 8 && node; depth++, node = node.parentElement) {
    for (const div of node.querySelectorAll('div')) {
      if (!chillicollectivoIsVisibleElement(div)) continue;
      const rect = div.getBoundingClientRect();
      const dy = Math.abs(rect.top - sendRect.top);
      const dx = Math.abs(rect.left - sendRect.left);
      if (dy > 90 || dx > 260) continue;

      const parsed = chillicollectivoParseFlipCountdownContainer(div);
      if (parsed == null) continue;

      const score = dy * 4 + dx + rect.width;
      if (score < bestScore) {
        bestScore = score;
        best = parsed;
      }
    }
  }

  return best;
}

function chillicollectivoReadTorchwoodSendCountdownSeconds(field) {
  try {
    return chillicollectivoFindSendCountdownNearField(field);
  } catch (_) {
    return null;
  }
}

function chillicollectivoIsTorchwoodSendGateBlocked(field) {
  const sendButton = chillicollectivoResolveSendButtonForField(field);
  const waitSec = chillicollectivoReadTorchwoodSendCountdownSeconds(field);
  return !!(sendButton && sendButton.disabled) || (waitSec != null && waitSec > 0);
}

function chillicollectivoPreviewFontFileUrl(file) {
  if (!chrome?.runtime?.getURL) return '';
  return chrome.runtime.getURL(`fonts/${file}`).replace(/ /g, '%20');
}

function chillicollectivoEnsurePreviewFontsInjected() {
  if (globalThis.__chillicollectivoPreviewFontsInjected) return;
  globalThis.__chillicollectivoPreviewFontsInjected = true;
  if (!chrome?.runtime?.getURL) return;
  if (document.getElementById('chillicollectivo-preview-fonts')) return;
  const fontUrl = chillicollectivoPreviewFontFileUrl;
  const style = document.createElement('style');
  style.id = 'chillicollectivo-preview-fonts';
  style.textContent = `
@font-face{font-family:'${CHILLICOLLECTIVO_PREVIEW_FONT_FAMILY}';src:url('${fontUrl('TYPOGRAPH PRO Light.ttf')}') format('truetype');font-weight:300;font-style:normal;font-display:swap}
@font-face{font-family:'${CHILLICOLLECTIVO_PREVIEW_FONT_FAMILY}';src:url('${fontUrl('TYPOGRAPH PRO Semi Bold.ttf')}') format('truetype');font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'Typograph Pro';src:url('${fontUrl('TYPOGRAPH PRO Light.ttf')}') format('truetype');font-weight:300;font-style:normal;font-display:swap}
@font-face{font-family:'Typograph Pro';src:url('${fontUrl('TYPOGRAPH PRO Semi Bold.ttf')}') format('truetype');font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'Typograph Pro';src:url('${fontUrl('TYPOGRAPH PRO Extra Bold.ttf')}') format('truetype');font-weight:800;font-style:normal;font-display:swap}
`;
  (document.head || document.documentElement).appendChild(style);
}

function chillicollectivoNormalizePreviewTextCaseMode(mode) {
  const raw = String(mode || '').trim().toLowerCase();
  if (CHILLICOLLECTIVO_PREVIEW_TEXT_CASE_MODES.has(raw)) return raw;
  if (raw === 'auto') return 'sentence';
  return CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE;
}

function chillicollectivoProtectDictationCaseTokens(text) {
  const MARK = '\uE000';
  const tokens = [];
  let protectedText = String(text || '');
  protectedText = protectedText.replace(/\bMike\b/g, () => {
    tokens.push('Mike');
    return `${MARK}${tokens.length - 1}${MARK}`;
  });
  protectedText = protectedText.replace(/\bmic\b/gi, () => {
    tokens.push('mic');
    return `${MARK}${tokens.length - 1}${MARK}`;
  });
  return { text: protectedText, tokens, MARK };
}

function chillicollectivoRestoreDictationCaseTokens(text, tokens, MARK) {
  let restored = String(text || '');
  tokens.forEach((tok, i) => {
    restored = restored.split(`${MARK}${i}${MARK}`).join(tok);
  });
  return restored;
}

function chillicollectivoContextAtSentenceStart(contextBefore = '') {
  const ctxRaw = String(contextBefore || '');
  const ctx = ctxRaw.trimEnd();
  if (!ctx) return true;
  if (/[.!?…]\s*$/.test(ctx)) return true;
  return /[\n\r]\s*$/.test(ctxRaw);
}

/** NTB-Stil: STT-Grossschreibung behalten, nur Satzanfang und nach .!? anpassen. */
function chillicollectivoApplyNtbInsertCase(text, contextBefore = '', options = {}) {
  const raw = String(text || '');
  if (!raw) return raw;

  const rawContextBefore = String(contextBefore || '');
  const trimmedBefore = rawContextBefore.trimEnd();
  const hasSelection = options.hasSelection === true;

  let result = raw;

  result = result.replace(
    /([.!?…]+)(\s*)(\p{Ll})/gu,
    (_, punct, space, ch) => punct + (space || ' ') + ch.toLocaleUpperCase('de-CH'),
  );

  if (!result.length) return result;

  const endsWithBreak = /[\n\r]\s*$/.test(rawContextBefore);
  const endsWithSentence = /[.!?…]\s*$/.test(trimmedBefore);
  if (!trimmedBefore.length || endsWithBreak || endsWithSentence) {
    result = result.replace(
      /^(\s*)(\p{Ll})/u,
      (_, ws, ch) => ws + ch.toLocaleUpperCase('de-CH'),
    );
  } else if (hasSelection) {
    result = result.replace(
      /^(\s*)(\p{Lu}|\p{Ll})/u,
      (_, ws, ch) => ws + ch.toLocaleLowerCase('de-CH'),
    );
  }

  return result;
}

function chillicollectivoApplyDictationInsertCase(text, contextBefore = '', options = {}) {
  const raw = String(text || '');
  if (!raw) return raw;
  const mode = chillicollectivoNormalizePreviewTextCaseMode(
    options.mode || CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE,
  );
  if (mode === 'spoken') return raw;
  if (mode === 'uppercase') return raw.toLocaleUpperCase('de-CH');
  if (mode === 'lowercase') return chillicollectivoApplyDictationPreviewCase(raw);

  const atSentenceStart = chillicollectivoContextAtSentenceStart(contextBefore);
  const startsWithPunct = /^\s*[,.!?…:;]/.test(raw);
  const forceLowerStart = options.forceLowerStart === true
    || (!atSentenceStart && !startsWithPunct);

  const { text: protectedText, tokens, MARK } = chillicollectivoProtectDictationCaseTokens(raw);
  let normalized = protectedText.toLocaleLowerCase('de-CH');

  normalized = normalized.replace(
    /([.!?…]+)(\s+)(\p{Ll})/gu,
    (_, punct, space, ch) => punct + space + ch.toLocaleUpperCase('de-CH'),
  );

  if (forceLowerStart) {
    normalized = normalized.replace(
      /^(\s*)(\p{Ll}|\p{Lu})/u,
      (_, ws, ch) => ws + ch.toLocaleLowerCase('de-CH'),
    );
  } else if (atSentenceStart) {
    normalized = normalized.replace(
      /^(\s*)(\p{Ll})/u,
      (_, ws, ch) => ws + ch.toLocaleUpperCase('de-CH'),
    );
  }

  return chillicollectivoRestoreDictationCaseTokens(normalized, tokens, MARK);
}

function chillicollectivoPreviewTextCaseToSentence(value) {
  return chillicollectivoApplyDictationInsertCase(value, '', {
    mode: 'sentence',
    forceLowerStart: false,
  });
}

function chillicollectivoApplyPreviewTextCase(text, mode) {
  const raw = String(text || '');
  if (!raw) return raw;
  const resolved = chillicollectivoNormalizePreviewTextCaseMode(mode);
  if (resolved === 'spoken') return raw;
  if (resolved === 'lowercase') return chillicollectivoApplyDictationPreviewCase(raw);
  if (resolved === 'uppercase') return raw.toLocaleUpperCase('de-CH');
  return chillicollectivoPreviewTextCaseToSentence(raw);
}

function chillicollectivoApplyDictationPreviewCase(text) {
  const src = String(text || '');
  if (!src) return src;
  const { text: protectedText, tokens, MARK } = chillicollectivoProtectDictationCaseTokens(src);
  const lower = protectedText.toLocaleLowerCase('de-CH');
  return chillicollectivoRestoreDictationCaseTokens(lower, tokens, MARK);
}

function chillicollectivoNormalizePreviewDisplayCase(text) {
  return chillicollectivoApplyPreviewTextCase(text, CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE);
}

function chillicollectivoToolLog(level, source, message, detail) {
  try {
    const api = window.ChillicollectivoDiagnosticLog;
    const fn = api?.[level] || api?.info;
    if (typeof fn === 'function') fn(source, message, detail).catch?.(() => {});
  } catch (_) {}
}

function chillicollectivoVoiceDiag(level, source, message, detail, fieldManager) {
  const fm = fieldManager || globalThis.__chillicollectivoFieldManager;
  let ctx = {};
  if (typeof fm?._voiceFlowSnapshot === 'function' && !fm._mergingPreviewDisplay && !fm._buildingVoiceFlowSnapshot) {
    fm._buildingVoiceFlowSnapshot = true;
    try {
      ctx = fm._voiceFlowSnapshot();
    } finally {
      fm._buildingVoiceFlowSnapshot = false;
    }
  } else if (fm) {
    ctx = {
      state: fm.currentState,
      route: fm._currentConversationKey?.() || '',
      committedSnippet: String(fm.previewCommitted || '').slice(0, 100),
      interimSnippet: String(fm._currentInterimText || '').slice(0, 80),
      enterToInsert: !!fm._isPreviewEnterToInsertEnabled?.(),
    };
  }
  const extra = detail && typeof detail === 'object' && !(detail instanceof Error)
    ? detail
    : (detail != null ? { note: detail } : {});
  chillicollectivoToolLog(level || 'info', source, message, { ...ctx, ...extra });
}

function chillicollectivoButtonLooksLikeSend(button) {
  if (!button) return false;
  const t = [
    button.textContent,
    button.value,
    button.getAttribute?.('aria-label'),
    button.getAttribute?.('title'),
  ].join(' ').toLowerCase().trim();
  return CHILLICOLLECTIVO_SEND_BUTTON_TEXTS.some((s) => t.includes(s));
}

function chillicollectivoButtonMatchesSend(button) {
  if (!button) return false;
  if (chillicollectivoButtonLooksLikeSend(button)) return true;
  const extraSelectors = [
    'button[type="submit"]',
    'button[data-testid*="send"]',
    'button[aria-label*="send"]',
    'button[aria-label*="senden"]',
    'button.send-button',
    'button.submit-button',
  ];
  return extraSelectors.some((sel) => button.matches?.(sel));
}

function chillicollectivoIsPrimaryTorchwoodSendButton(button) {
  if (!button || !chillicollectivoIsVisibleElement(button)) return false;
  const label = String(button.textContent || '').replace(/\s+/g, ' ').trim();
  if (/senden\s*&/i.test(label)) return false;
  if (/Conversation__SendButton/i.test(String(button.className || ''))) return true;
  if (!/^senden$/i.test(label)) return false;
  return chillicollectivoButtonMatchesSend(button);
}

function chillicollectivoResolvePrimaryTorchwoodSendButton(target) {
  const button = target?.closest?.('button,[role="button"]');
  return chillicollectivoIsPrimaryTorchwoodSendButton(button) ? button : null;
}

function chillicollectivoShouldIgnoreSendEventForExtensionEcho() {
  const stamp = Number(globalThis.__chillicollectivoExtensionSendAt || 0);
  return stamp > 0 && Date.now() - stamp < 700;
}

function chillicollectivoResolveManualSendButton(target) {
  const button = target?.closest?.('button,[role="button"],input[type="submit"],input[type="button"]');
  if (!button || !chillicollectivoIsVisibleElement(button)) return null;
  if (chillicollectivoIsPrimaryTorchwoodSendButton(button)) return button;
  if (!chillicollectivoButtonMatchesSend(button)) return null;
  const label = String(button.textContent || button.value || button.getAttribute?.('aria-label') || '')
    .replace(/\s+/g, ' ')
    .trim();
  if (/senden\s*&/i.test(label)) return null;
  return button;
}

function chillicollectivoResolveManualSendButtonFromEvent(event) {
  const direct = chillicollectivoResolveManualSendButton(event.target);
  if (direct) return direct;
  if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) return null;
  const hit = document.elementFromPoint(event.clientX, event.clientY);
  return chillicollectivoResolveManualSendButton(hit);
}

function chillicollectivoHandleManualSendIntent(trigger, event, extra = {}) {
  if (chillicollectivoShouldIgnoreSendEventForExtensionEcho()) return false;
  const fm = globalThis.__chillicollectivoFieldManager;
  if (!fm) return false;
  fm._refreshStaleFieldRefs?.();
  fm._adoptFreshComposeField?.();
  const field = fm._resolveTextField?.() || fm._findComposeTextField?.() || null;
  fm._logSendButtonPressed?.(trigger, field, {
    trusted: event?.isTrusted !== false,
    ...extra,
  });
  fm._showSentOutPreview('');
  return true;
}

function chillicollectivoInstallTorchwoodManualSendOutListener() {
  if (globalThis.__chillicollectivoManualSendOutListenerInstalled) return;
  globalThis.__chillicollectivoManualSendOutListenerInstalled = true;

  const onSendButtonEvent = (event, trigger) => {
    const button = chillicollectivoResolveManualSendButtonFromEvent(event);
    if (!button || button.disabled) return;
    chillicollectivoHandleManualSendIntent(trigger, event, {
      sendButtonLabel: String(button.textContent || button.getAttribute?.('aria-label') || '')
        .replace(/\s+/g, ' ')
        .trim(),
    });
  };

  document.addEventListener('pointerdown', (event) => {
    if (event.button !== undefined && event.button !== 0) return;
    onSendButtonEvent(event, 'manual-pointer');
  }, true);

  document.addEventListener('click', (event) => {
    onSendButtonEvent(event, 'manual-user');
  }, true);
}

function chillicollectivoSendButtonDepth(field, node) {
  let depth = 0;
  let probe = field;
  while (probe && probe !== node) {
    depth += 1;
    probe = probe.parentElement;
  }
  return probe === node ? depth : Infinity;
}

function chillicollectivoFindSendButtonForField(field) {
  if (!field) return null;

  let bestEnabled = null;
  let bestEnabledDepth = Infinity;
  let bestDisabled = null;
  let bestDisabledDepth = Infinity;

  const consider = (button, node) => {
    if (!chillicollectivoButtonMatchesSend(button) || !node.contains(field)) return;
    const depth = chillicollectivoSendButtonDepth(field, node);
    if (!Number.isFinite(depth)) return;
    if (button.disabled) {
      if (depth < bestDisabledDepth) {
        bestDisabled = button;
        bestDisabledDepth = depth;
      }
      return;
    }
    if (depth < bestEnabledDepth) {
      bestEnabled = button;
      bestEnabledDepth = depth;
    }
  };

  let node = field.parentElement;
  while (node && node !== document.body) {
    const seen = new Set();
    for (const button of node.querySelectorAll('button, input[type="submit"], input[type="button"]')) {
      if (seen.has(button)) continue;
      seen.add(button);
      consider(button, node);
    }
    node = node.parentElement;
  }

  if (bestEnabled) return bestEnabled;

  const container = field.closest?.('form, div[role="form"], .chat-input, .message-input, .input-container');
  if (container) {
    for (const button of container.querySelectorAll('button, input[type="submit"]')) {
      consider(button, container);
    }
  }

  return bestDisabled;
}

function chillicollectivoFindTorchwoodSendButtonNearField(field) {
  const compose = field
    || window.ChillicollectivoTorchwoodAsa?.findComposeField?.()
    || null;
  if (!compose) return null;

  const root = compose.closest('[class*="Conversation"], [class*="Chat"], [class*="MessageInput"]')
    || compose.parentElement?.parentElement?.parentElement;
  if (!root) return null;

  let disabled = null;
  for (const btn of root.querySelectorAll('button, input[type="submit"]')) {
    if (!chillicollectivoIsVisibleElement(btn)) continue;
    const label = [
      btn.textContent,
      btn.value,
      btn.getAttribute?.('aria-label'),
      btn.getAttribute?.('title'),
    ].join(' ').toLowerCase().trim();
    const looksLikeSend = chillicollectivoButtonMatchesSend(btn)
      || label === 'senden'
      || label.startsWith('senden ')
      || label.includes(' senden');
    if (!looksLikeSend) continue;
    if (!btn.disabled) return btn;
    if (!disabled) disabled = btn;
  }
  return disabled;
}

function chillicollectivoResolveSendButtonForField(field) {
  return chillicollectivoFindSendButtonForField(field)
    || chillicollectivoFindTorchwoodSendButtonNearField(field);
}

function chillicollectivoClickSendButton(sendButton, options = {}) {
  if (!sendButton) return false;
  if (!options.bypassSendLock
    && typeof chillicollectivoIsGlobalSendCooldownLocked === 'function'
    && chillicollectivoIsGlobalSendCooldownLocked()) {
    const lock = chillicollectivoReadGlobalSendLock?.();
    chillicollectivoToolLog('warn', 'voice.send-lock', 'Programm-Senden blockiert – Cooldown nach Senden', {
      route: lock?.route || '',
      source: lock?.source || '',
      lockMsLeft: Math.max(0, (lock?.cooldownUntil || 0) - Date.now()),
    });
    return false;
  }
  try {
    if (sendButton.disabled) sendButton.disabled = false;
    sendButton.focus({ preventScroll: true });
  } catch (_) {}
  globalThis.__chillicollectivoExtensionSendAt = Date.now();
  sendButton.click();
  const fm = globalThis.__chillicollectivoFieldManager;
  fm?._logSendButtonPressed?.(options.source || 'extension-program', null, {
    sendButtonLabel: String(sendButton.textContent || sendButton.getAttribute?.('aria-label') || '').replace(/\s+/g, ' ').trim(),
  });
  return true;
}

const CHILLICOLLECTIVO_POST_SEND_COOLDOWN_MS = 6000;
const CHILLICOLLECTIVO_SEND_INFLIGHT_MAX_MS = 90000;

function chillicollectivoReadGlobalSendLock() {
  return globalThis.__chillicollectivoGlobalSendLock || null;
}

function chillicollectivoIsGlobalSendHardLocked() {
  const lock = chillicollectivoReadGlobalSendLock();
  if (!lock) return false;
  const now = Date.now();
  if (lock.inFlight && now < (lock.inFlightUntil || 0)) return true;
  return now < (lock.cooldownUntil || 0);
}

function chillicollectivoIsGlobalSendCooldownLocked() {
  const lock = chillicollectivoReadGlobalSendLock();
  if (!lock) return false;
  return Date.now() < (lock.cooldownUntil || 0);
}

function chillicollectivoArmGlobalSendLock(meta = {}) {
  const now = Date.now();
  globalThis.__chillicollectivoGlobalSendLock = {
    startedAt: now,
    inFlight: true,
    inFlightUntil: now + CHILLICOLLECTIVO_SEND_INFLIGHT_MAX_MS,
    cooldownUntil: 0,
    route: String(meta.route || ''),
    source: String(meta.source || 'voice'),
  };
}

function chillicollectivoConfirmGlobalSendLock() {
  const lock = chillicollectivoReadGlobalSendLock();
  if (!lock) return;
  const now = Date.now();
  lock.inFlight = false;
  lock.inFlightUntil = now;
  lock.cooldownUntil = now + CHILLICOLLECTIVO_POST_SEND_COOLDOWN_MS;
}

function chillicollectivoReleaseGlobalSendInFlight() {
  const lock = chillicollectivoReadGlobalSendLock();
  if (!lock) return;
  const now = Date.now();
  lock.inFlight = false;
  lock.inFlightUntil = now;
}

globalThis.chillicollectivoFindSendButtonForField = chillicollectivoFindSendButtonForField;
globalThis.chillicollectivoResolveSendButtonForField = chillicollectivoResolveSendButtonForField;
globalThis.chillicollectivoClickSendButton = chillicollectivoClickSendButton;
globalThis.chillicollectivoIsGlobalSendHardLocked = chillicollectivoIsGlobalSendHardLocked;
globalThis.chillicollectivoIsGlobalSendCooldownLocked = chillicollectivoIsGlobalSendCooldownLocked;
globalThis.chillicollectivoArmGlobalSendLock = chillicollectivoArmGlobalSendLock;
globalThis.chillicollectivoConfirmGlobalSendLock = chillicollectivoConfirmGlobalSendLock;
globalThis.chillicollectivoReleaseGlobalSendInFlight = chillicollectivoReleaseGlobalSendInFlight;

class CHILLICOLLECTIVOVoiceFAB {
  constructor(fieldManager) {
    this.fieldManager = fieldManager;
    this.btn = null;
    this.isDragging = false;
    this.didDrag = false;
    this._lastFabVisualState = null;
    this.effects = {
      previewEnabled: true,
      previewOpacity: 100,
      previewColor: CHILLICOLLECTIVO_DEFAULT_PREVIEW_COLOR,
      previewFontWeight: CHILLICOLLECTIVO_DEFAULT_PREVIEW_FONT_WEIGHT,
      previewTextCase: CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE,
      previewFontSize: CHILLICOLLECTIVO_PREVIEW_BASE_FONT_PX,
      toolColor: CHILLICOLLECTIVO_DEFAULT_TOOL_COLOR,
      toolOpacity: 100,
      fabGearIcon: 'leaf',
      sendCrumbleIntensity: 100,
    };
    this.previewLayout = {
      movable: false,
      pinned: false,
      position: null,
      size: null,
    };
    this._previewDragging = false;
    this._previewResizing = false;
    this._previewVisible = false;
    this._previewUserEditing = false;
    this._previewDictationFocus = false;
    this._previewDictationMutating = false;
    this._previewMouseSelecting = false;
    this._previewSelectionLogAt = 0;
    this._previewEditBaseline = '';
    this._previewDom = null;
    this._previewUpdateRAF = null;
    this._pendingPreviewUpdate = null;
    this._lastRenderedInterim = '';
    this._lastRenderedCommitted = '';
    this._lastRenderedSendwordBadge = '';
    this._lastRenderedInsertHint = '';
    this._previewCommittedSelectionRange = null;
    this._previewSelectionOffsets = null;
    this._previewSelectionBound = false;
    this._previewReplaceSpanTimer = null;
    this.hotkey = null;
    this.wrapEl = null;
    this.tipEl = null;
    this._micTipHideTimer = null;

    this._loadSettings();
    this._create();
    requestAnimationFrame(() => this._ensureInBounds());
    this._setupHotkeyListener();
    if (!this._previewResizeBound) {
      this._previewResizeBound = true;
      window.addEventListener('resize', () => {
        this._ensureInBounds();
        const overlay = document.getElementById('chillicollectivo-voice-overlay');
        if (!overlay) return;
        this._ensurePreviewInBounds(overlay);
        this._applyPreviewTypography(overlay);
      });
    }
  }

  _loadSettings() {
    try {
      chrome.storage.local.get([
        'chillicollectivoVoiceEffects',
        'chillicollectivoVoiceHotkey',
        'chillicollectivoVoiceSettings',
        'chillicollectivoVoiceFabPos',
        'chillicollectivoVoicePreviewPos',
        CHILLICOLLECTIVO_PREVIEW_SIZE_STORAGE_KEY,
      ], (data) => {
        if (data.chillicollectivoVoiceEffects) {
          Object.assign(this.effects, data.chillicollectivoVoiceEffects);
          if (this.effects.fabGearIcon !== 'mic') this.effects.fabGearIcon = 'leaf';
        }
        if (data.chillicollectivoVoiceSettings) {
          this._applyVoicePreviewSettings(data.chillicollectivoVoiceSettings);
        }
        if (data.chillicollectivoVoiceSettings?.previewOpacity != null) this.effects.previewOpacity = data.chillicollectivoVoiceSettings.previewOpacity;
        if (data.chillicollectivoVoiceSettings?.previewColor) {
          this.effects.previewColor = data.chillicollectivoVoiceSettings.previewColor;
        }
        if (data.chillicollectivoVoiceSettings?.toolColor) {
          this.effects.toolColor = data.chillicollectivoVoiceSettings.toolColor;
        }
        if (data.chillicollectivoVoiceSettings?.toolOpacity != null) {
          this.effects.toolOpacity = data.chillicollectivoVoiceSettings.toolOpacity;
        }
        if (data.chillicollectivoVoiceSettings?.fabGearIcon) {
          this.effects.fabGearIcon = data.chillicollectivoVoiceSettings.fabGearIcon === 'mic' ? 'mic' : 'leaf';
        }
        if (data.chillicollectivoVoiceSettings?.previewMovable != null) {
          this.previewLayout.movable = data.chillicollectivoVoiceSettings.previewMovable === true;
        }
        if (data.chillicollectivoVoiceSettings?.previewPinned != null) {
          this.previewLayout.pinned = data.chillicollectivoVoiceSettings.previewPinned === true;
        }
        if (data.chillicollectivoVoicePreviewPos) {
          this.previewLayout.position = data.chillicollectivoVoicePreviewPos;
        }
        if (data[CHILLICOLLECTIVO_PREVIEW_SIZE_STORAGE_KEY]) {
          this.previewLayout.size = data[CHILLICOLLECTIVO_PREVIEW_SIZE_STORAGE_KEY];
        }
        this._normalizePreviewLayout();
        this._normalizeEffectIntensities();
        this._normalizePreviewOpacity();
        this._normalizeToolOpacity();
        this._normalizePreviewColor();
        this._normalizeToolColor();
        this._normalizePreviewFontWeight();
        this._normalizePreviewTextCase();
        this._normalizePreviewFontSize();
        this._applyEffects();
        const storedHotkey = data.chillicollectivoVoiceHotkey !== undefined
          ? data.chillicollectivoVoiceHotkey
          : data.chillicollectivoVoiceSettings?.hotkey;
        if (storedHotkey !== undefined) this.hotkey = storedHotkey || null;
        const pos = data.chillicollectivoVoiceFabPos;
        const posTarget = this.wrapEl || this.btn;
        if (pos && posTarget) {
          posTarget.style.left = pos.left;
          posTarget.style.top = pos.top;
          posTarget.style.right = 'auto';
          posTarget.style.bottom = 'auto';
          this._ensureInBounds();
        }
        if (this.btn) this._refreshHotkeyLabel();
        this._applyPreviewLayout();
      });
    } catch (e) { /* extension context not yet ready */ }

    try {
      chrome.storage.onChanged.addListener((changes, area) => {
        if (area !== 'local') return;
        if (changes.chillicollectivoVoiceEffects) {
          Object.assign(this.effects, changes.chillicollectivoVoiceEffects.newValue || {});
          if (this.effects.fabGearIcon !== 'mic') this.effects.fabGearIcon = 'leaf';
          this._normalizePreviewFontWeight();
          this._normalizePreviewTextCase();
          this._normalizePreviewFontSize();
          this._normalizeEffectIntensities();
          this._normalizePreviewOpacity();
          this._normalizeToolOpacity();
          this._normalizePreviewColor();
          this._normalizeToolColor();
          this._applyEffects();
          this._applyPreviewFontOnOverlay();
          this.fieldManager?._refreshDictationPreviewCase?.();
        }
        if (changes.chillicollectivoVoiceHotkey) {
          this.hotkey = changes.chillicollectivoVoiceHotkey.newValue || null;
          this._refreshHotkeyLabel();
        }
        if (changes.chillicollectivoVoiceSettings?.newValue?.hotkey !== undefined) {
          this.hotkey = changes.chillicollectivoVoiceSettings.newValue.hotkey || null;
          this._refreshHotkeyLabel();
        }
        if (changes.chillicollectivoVoiceSettings?.newValue) {
          this._applyVoicePreviewSettings(changes.chillicollectivoVoiceSettings.newValue);
        }
        if (changes.chillicollectivoVoiceSettings?.newValue?.previewOpacity != null) {
          this.effects.previewOpacity = changes.chillicollectivoVoiceSettings.newValue.previewOpacity;
          this._normalizePreviewOpacity();
          this._applyEffects();
        }
        if (changes.chillicollectivoVoiceSettings?.newValue?.previewColor) {
          this.effects.previewColor = changes.chillicollectivoVoiceSettings.newValue.previewColor;
          this._normalizePreviewColor();
          this._applyEffects();
        }
        if (changes.chillicollectivoVoiceSettings?.newValue?.previewFontWeight !== undefined) {
          this.effects.previewFontWeight = changes.chillicollectivoVoiceSettings.newValue.previewFontWeight;
          this._normalizePreviewFontWeight();
          this._applyPreviewFontOnOverlay();
        }
        if (changes.chillicollectivoVoiceSettings?.newValue?.previewTextCase !== undefined) {
          this.effects.previewTextCase = changes.chillicollectivoVoiceSettings.newValue.previewTextCase;
          this._normalizePreviewTextCase();
          this.fieldManager?._refreshDictationPreviewCase?.();
        }
        if (changes.chillicollectivoVoiceSettings?.newValue?.previewFontSize != null) {
          this.effects.previewFontSize = changes.chillicollectivoVoiceSettings.newValue.previewFontSize;
          this._normalizePreviewFontSize();
          this._applyPreviewFontOnOverlay();
        }
        if (changes.chillicollectivoVoiceSettings?.newValue?.toolColor) {
          this.effects.toolColor = changes.chillicollectivoVoiceSettings.newValue.toolColor;
          this._normalizeToolColor();
          this._applyEffects();
        }
        if (changes.chillicollectivoVoiceSettings?.newValue?.toolOpacity != null) {
          this.effects.toolOpacity = changes.chillicollectivoVoiceSettings.newValue.toolOpacity;
          this._normalizeToolOpacity();
          this._applyEffects();
        }
        if (changes.chillicollectivoVoiceSettings?.newValue?.fabGearIcon !== undefined) {
          this.effects.fabGearIcon = changes.chillicollectivoVoiceSettings.newValue.fabGearIcon === 'mic' ? 'mic' : 'leaf';
          this._applyGearIcon();
        }
        const previewSettings = changes.chillicollectivoVoiceSettings?.newValue;
        if (previewSettings?.previewMovable !== undefined) {
          this.previewLayout.movable = previewSettings.previewMovable === true;
          this._normalizePreviewLayout();
          this._applyPreviewLayout();
        }
        if (previewSettings?.previewPinned !== undefined) {
          this.previewLayout.pinned = previewSettings.previewPinned === true;
          this._normalizePreviewLayout();
          this._applyPreviewLayout();
        }
        if (changes.chillicollectivoVoicePreviewPos) {
          this.previewLayout.position = changes.chillicollectivoVoicePreviewPos.newValue || null;
          this._applyPreviewLayout();
        }
        if (changes[CHILLICOLLECTIVO_PREVIEW_SIZE_STORAGE_KEY]) {
          this.previewLayout.size = changes[CHILLICOLLECTIVO_PREVIEW_SIZE_STORAGE_KEY].newValue || null;
          this._applyPreviewLayout();
        }
      });
    } catch (e) {}
  }

  _normalizePreviewOpacity() {
    const value = Number(this.effects.previewOpacity);
    this.effects.previewOpacity = Number.isFinite(value)
      ? Math.min(100, Math.max(0, Math.round(value / 5) * 5))
      : 100;
  }

  _normalizeToolOpacity() {
    const value = Number(this.effects.toolOpacity);
    this.effects.toolOpacity = Number.isFinite(value)
      ? Math.min(100, Math.max(20, Math.round(value / 5) * 5))
      : 100;
  }

  _normalizePreviewColor() {
    this.effects.previewColor = chillicollectivoNormalizePreviewColor(this.effects.previewColor);
  }

  _normalizeToolColor() {
    this.effects.toolColor = chillicollectivoNormalizePreviewColor(
      this.effects.toolColor || this.effects.previewColor || CHILLICOLLECTIVO_DEFAULT_TOOL_COLOR
    );
  }

  _normalizePreviewFontWeight() {
    const raw = String(this.effects.previewFontWeight || '').trim().toLowerCase();
    this.effects.previewFontWeight = CHILLICOLLECTIVO_PREVIEW_FONT_WEIGHT_MAP[raw]
      ? raw
      : CHILLICOLLECTIVO_DEFAULT_PREVIEW_FONT_WEIGHT;
  }

  _normalizePreviewTextCase() {
    if (globalThis.__CHILLICOLLECTIVO_AGENTEN_MIC) {
      this.effects.previewTextCase = 'spoken';
      return;
    }
    this.effects.previewTextCase = chillicollectivoNormalizePreviewTextCaseMode(this.effects.previewTextCase);
  }

  _normalizePreviewFontSize() {
    const value = Number(this.effects.previewFontSize);
    this.effects.previewFontSize = Number.isFinite(value)
      ? Math.min(32, Math.max(16, Math.round(value)))
      : CHILLICOLLECTIVO_PREVIEW_BASE_FONT_PX;
  }

  _applyVoicePreviewSettings(settings = {}) {
    if (settings.previewFontWeight !== undefined) {
      this.effects.previewFontWeight = settings.previewFontWeight;
    }
    if (settings.previewTextCase !== undefined) {
      this.effects.previewTextCase = settings.previewTextCase;
    }
    if (settings.previewFontSize !== undefined) {
      this.effects.previewFontSize = settings.previewFontSize;
    }
    this._normalizePreviewFontWeight();
    this._normalizePreviewTextCase();
    this._normalizePreviewFontSize();
    this._applyPreviewFontOnOverlay();
    this.fieldManager?._refreshDictationPreviewCase?.();
  }

  _getPreviewFontWeightCss() {
    const weight = this.effects.previewFontWeight;
    if (weight === 'extrabold') return CHILLICOLLECTIVO_PREVIEW_FONT_WEIGHT_MAP.semibold;
    return CHILLICOLLECTIVO_PREVIEW_FONT_WEIGHT_MAP[weight]
      || CHILLICOLLECTIVO_PREVIEW_FONT_WEIGHT_MAP[CHILLICOLLECTIVO_DEFAULT_PREVIEW_FONT_WEIGHT];
  }

  _getPreviewFontFamilyCss() {
    return `'${CHILLICOLLECTIVO_PREVIEW_FONT_FAMILY}', Verdana, Geneva, sans-serif`;
  }

  _applyPreviewFontOnOverlay() {
    const overlay = document.getElementById('chillicollectivo-voice-overlay');
    if (overlay) this._applyPreviewTypography(overlay);
  }

  _applyToolThemeVars() {
    this._normalizeToolColor();
    this._normalizeToolOpacity();
    const hex = this.effects.toolColor;
    const { r, g, b } = chillicollectivoHexToRgb(hex);
    const lightHex = chillicollectivoMixHexWithWhite(hex, 0.45);
    const light = chillicollectivoHexToRgb(lightHex);
    this._toolAccentRgb = `${r}, ${g}, ${b}`;
    const toolOpacity = String((this.effects.toolOpacity || 100) / 100);
    const targets = [document.documentElement, this.wrapEl, this.btn, this.gearBtn].filter(Boolean);
    targets.forEach((target) => {
      target.style.setProperty('--chillicollectivo-tool-accent', hex);
      target.style.setProperty('--chillicollectivo-tool-accent-rgb', `${r}, ${g}, ${b}`);
      target.style.setProperty('--chillicollectivo-tool-accent-light', lightHex);
      target.style.setProperty('--chillicollectivo-tool-accent-light-rgb', `${light.r}, ${light.g}, ${light.b}`);
      target.style.setProperty('--chillicollectivo-tool-opacity', toolOpacity);
    });
  }

  _applyPreviewThemeVars(target) {
    if (!target) return;
    const hex = chillicollectivoNormalizePreviewColor(this.effects.previewColor);
    const { r, g, b } = chillicollectivoHexToRgb(hex);
    target.style.setProperty('--chillicollectivo-preview-accent', hex);
    target.style.setProperty('--chillicollectivo-preview-accent-rgb', `${r}, ${g}, ${b}`);
    target.style.setProperty('--chillicollectivo-preview-text', chillicollectivoMixHexWithWhite(hex, 0.78));
    target.style.setProperty('--chillicollectivo-preview-border', `rgba(${r}, ${g}, ${b}, 0.35)`);
    target.style.setProperty('--chillicollectivo-preview-interim', `rgba(${r}, ${g}, ${b}, 0.58)`);
    target.style.setProperty('--chillicollectivo-preview-hint', `rgba(${r}, ${g}, ${b}, 0.88)`);
    target.style.setProperty('--chillicollectivo-preview-focus', `rgba(${r}, ${g}, ${b}, 0.45)`);
  }

  _applyPreviewAppearanceVars(target) {
    if (!target) return;
    this._normalizePreviewOpacity();
    this._normalizePreviewColor();
    this._applyEffectIntensityVars(target);
    target.style.setProperty('--chillicollectivo-preview-opacity', String((this.effects.previewOpacity || 100) / 100));
    this._applyPreviewThemeVars(target);
    this._applyPreviewTypography(target);
  }

  _parsePreviewPx(value, fallback = 0) {
    const n = parseFloat(String(value || '').replace('px', '').trim());
    return Number.isFinite(n) ? n : fallback;
  }

  _clampPreviewSize(width, height) {
    const maxW = Math.min(window.innerWidth * 0.96, 1200);
    const maxH = Math.min(window.innerHeight * 0.72, 640);
    return {
      width: Math.max(CHILLICOLLECTIVO_PREVIEW_MIN_WIDTH, Math.min(width, maxW)),
      height: Math.max(CHILLICOLLECTIVO_PREVIEW_MIN_HEIGHT, Math.min(height, maxH)),
    };
  }

  _hasCustomPreviewSize(target) {
    if (!target) return false;
    if (this._previewResizing
      || target.classList.contains('chillicollectivo-voice-overlay-custom-size')) {
      return true;
    }
    const size = this.previewLayout.size;
    return this.previewLayout.movable === true
      && !!size
      && String(size.width || '').trim()
      && String(size.height || '').trim();
  }

  _clearPreviewTypography(target) {
    if (!target) return;
    target.style.removeProperty('--chillicollectivo-preview-padding-y');
    target.style.removeProperty('--chillicollectivo-preview-padding-x');
  }

  _applyPreviewTypography(target) {
    if (!target) return;
    this._normalizePreviewFontWeight();
    this._normalizePreviewFontSize();
    const baseFontPx = this.effects.previewFontSize || CHILLICOLLECTIVO_PREVIEW_BASE_FONT_PX;
    let fontPx = baseFontPx;
    let auxScale = 1;
    if (this._hasCustomPreviewSize(target)) {
      const width = target.offsetWidth
        || this._parsePreviewPx(target.style.width, CHILLICOLLECTIVO_PREVIEW_BASE_WIDTH);
      const height = target.offsetHeight
        || this._parsePreviewPx(target.style.height, CHILLICOLLECTIVO_PREVIEW_BASE_HEIGHT);
      const scaleW = width / CHILLICOLLECTIVO_PREVIEW_BASE_WIDTH;
      const scaleH = height / CHILLICOLLECTIVO_PREVIEW_BASE_HEIGHT;
      const layoutScale = Math.max(0.5, Math.min(1.28, Math.min(scaleW, scaleH)));
      fontPx = baseFontPx * layoutScale;
      auxScale = layoutScale;
      const padY = Math.max(8, 16 * layoutScale).toFixed(1);
      const padX = Math.max(10, 24 * layoutScale).toFixed(1);
      target.style.setProperty('--chillicollectivo-preview-padding-y', `${padY}px`);
      target.style.setProperty('--chillicollectivo-preview-padding-x', `${padX}px`);
    } else {
      this._clearPreviewTypography(target);
    }
    const auxPx = (14 * auxScale * (baseFontPx / CHILLICOLLECTIVO_PREVIEW_BASE_FONT_PX)).toFixed(2);
    target.style.setProperty('--chillicollectivo-preview-font-size', `${fontPx.toFixed(2)}px`);
    target.style.setProperty('--chillicollectivo-preview-font-weight', String(this._getPreviewFontWeightCss()));
    target.style.setProperty('--chillicollectivo-preview-aux-font-size', `${auxPx}px`);
    target.style.fontFamily = this._getPreviewFontFamilyCss();
    target.style.fontWeight = String(this._getPreviewFontWeightCss());
    target.style.textTransform = 'none';
    target.style.fontVariant = 'normal';
    target.dataset.chillicollectivoPreviewCase = this.effects.previewTextCase || CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE;
    target.dataset.chillicollectivoPreviewWeight = this.effects.previewFontWeight || CHILLICOLLECTIVO_DEFAULT_PREVIEW_FONT_WEIGHT;
    target.querySelectorAll('.chillicollectivo-voice-overlay-committed, .chillicollectivo-voice-overlay-interim').forEach((node) => {
      node.style.fontFamily = this._getPreviewFontFamilyCss();
      node.style.fontWeight = String(this._getPreviewFontWeightCss());
      node.style.textTransform = 'none';
      node.style.fontVariant = 'normal';
    });
  }

  _savePreviewLayoutState(root) {
    if (!root) return;
    const nextPos = { left: root.style.left, top: root.style.top };
    const nextSize = {
      width: `${Math.round(this._parsePreviewPx(root.style.width, root.offsetWidth))}px`,
      height: `${Math.round(this._parsePreviewPx(root.style.height, root.offsetHeight))}px`,
    };
    this.previewLayout.position = nextPos;
    this.previewLayout.size = nextSize;
    try {
      chrome.storage.local.set({
        chillicollectivoVoicePreviewPos: nextPos,
        [CHILLICOLLECTIVO_PREVIEW_SIZE_STORAGE_KEY]: nextSize,
      });
    } catch (_) {}
  }

  _normalizeEffectIntensities() {
    const resolve = (intensity, legacyEnabled, fallback = 100) => {
      if (intensity != null && Number.isFinite(Number(intensity))) {
        return Math.min(100, Math.max(0, Math.round(Number(intensity) / 5) * 5));
      }
      if (legacyEnabled === false) return 0;
      return fallback;
    };
    this.effects.sendCrumbleIntensity = resolve(this.effects.sendCrumbleIntensity, this.effects.sendCrumbleEnabled);
    this.effects.sendCrumbleEnabled = this.effects.sendCrumbleIntensity > 0;
  }

  _effectIntensityFactor(key) {
    return Math.max(0, Math.min(1, Number(this.effects[key] || 0) / 100));
  }

  _applyEffectIntensityVars(target) {
    if (!target) return;
    target.style.setProperty('--chillicollectivo-crumble-intensity', String(this._effectIntensityFactor('sendCrumbleIntensity')));
  }

  _fabRootEl() {
    return this.wrapEl || this.btn;
  }

  _wrapLegacyFab(btn) {
    const wrap = document.createElement('div');
    wrap.id = 'chillicollectivo-fab-wrap';
    wrap.className = 'chillicollectivo-fab-wrap';

    const rect = btn.getBoundingClientRect();
    if (btn.style.left && btn.style.top) {
      wrap.style.left = btn.style.left;
      wrap.style.top = btn.style.top;
    } else {
      wrap.style.left = `${Math.round(rect.left)}px`;
      wrap.style.top = `${Math.round(rect.top)}px`;
    }
    wrap.style.right = 'auto';
    wrap.style.bottom = 'auto';

    const tip = document.getElementById('chillicollectivo-fab-tip');
    btn.parentNode?.insertBefore(wrap, btn);
    if (tip) wrap.appendChild(tip);
    wrap.appendChild(btn);

    this.wrapEl = wrap;
    this.btn = btn;
    this.tipEl = tip || null;
    if (!this.tipEl) {
      const newTip = document.createElement('div');
      newTip.id = 'chillicollectivo-fab-tip';
      newTip.className = 'chillicollectivo-fab-tip';
      newTip.setAttribute('aria-live', 'polite');
      newTip.innerHTML = '<div class="chillicollectivo-fab-tip-bubble"></div><div class="chillicollectivo-fab-tip-tail" aria-hidden="true"></div>';
      wrap.insertBefore(newTip, btn);
      this.tipEl = newTip;
    }
  }

  _create() {
    const existingWrap = document.getElementById('chillicollectivo-fab-wrap');
    const existingBtn = document.getElementById('chillicollectivo-voice-fab');
    if (existingWrap && existingBtn) {
      this.wrapEl = existingWrap;
      this.btn = existingBtn;
      this.tipEl = document.getElementById('chillicollectivo-fab-tip');
      this.gearBtn = existingBtn.querySelector('#chillicollectivo-voice-fab-gear');
      this._setupDrag();
      this._setupClick();
      if (this.gearBtn) this._setupGear(this.gearBtn);
      this._applyGearIcon();
      this._applyEffects();
      this._ensureFabVisible();
      return;
    }
    if (existingWrap && !existingBtn) {
      existingWrap.remove();
    }
    const legacyBtn = document.getElementById('chillicollectivo-voice-fab');
    if (legacyBtn) {
      this._wrapLegacyFab(legacyBtn);
      this._setupDrag();
      this._setupClick();
      this._ensureFabVisible();
      return;
    }

    const wrap = document.createElement('div');
    wrap.id = 'chillicollectivo-fab-wrap';
    wrap.className = 'chillicollectivo-fab-wrap';

    const tip = document.createElement('div');
    tip.id = 'chillicollectivo-fab-tip';
    tip.className = 'chillicollectivo-fab-tip';
    tip.setAttribute('aria-live', 'polite');
    tip.innerHTML = '<div class="chillicollectivo-fab-tip-bubble"></div><div class="chillicollectivo-fab-tip-tail" aria-hidden="true"></div>';
    wrap.appendChild(tip);
    this.tipEl = tip;

    const btn = document.createElement('button');
    btn.id = 'chillicollectivo-voice-fab';
    btn.type = 'button';
    btn.setAttribute('aria-label', 'ChiliColectivo starten');
    btn.className = 'chillicollectivo-fab chillicollectivo-fab-ready';
    btn.innerHTML = this._micOffSVG();
    wrap.appendChild(btn);
    const mountRoot = document.body || document.documentElement;
    mountRoot.appendChild(wrap);

    this.wrapEl = wrap;
    this.btn = btn;
    const gear = document.createElement('button');
    this.gearBtn = gear;
    gear.id = 'chillicollectivo-voice-fab-gear';
    gear.type = 'button';
    gear.className = 'chillicollectivo-fab-gear';
    gear.setAttribute('aria-label', 'ChiliColectivo Einstellungen');
    gear.innerHTML = this._gearIconHTML();
    btn.appendChild(gear);

    this._setupDrag();
    this._setupClick();
    this._setupGear(gear);
    this._applyGearIcon();
    this._applyEffects();
    this._ensureFabVisible();
  }

  _createMicTipBubble() {
    if (this.tipEl) return;
    if (document.getElementById('chillicollectivo-fab-tip')) {
      this.tipEl = document.getElementById('chillicollectivo-fab-tip');
      if (this.wrapEl && this.tipEl.parentElement !== this.wrapEl) {
        this.wrapEl.insertBefore(this.tipEl, this.wrapEl.firstChild);
      }
      return;
    }
    const host = this.wrapEl;
    if (!host) return;
    const tip = document.createElement('div');
    tip.id = 'chillicollectivo-fab-tip';
    tip.className = 'chillicollectivo-fab-tip';
    tip.setAttribute('aria-live', 'polite');
    tip.innerHTML = '<div class="chillicollectivo-fab-tip-bubble"></div><div class="chillicollectivo-fab-tip-tail" aria-hidden="true"></div>';
    host.insertBefore(tip, host.firstChild);
    this.tipEl = tip;
  }

  showMicTipBubble(text, durationMs = CHILLICOLLECTIVO_MIC_TIP_SHOW_MS, animOptions = {}) {
    if (!animOptions.allowWhenDisabled && !this.fieldManager?._isMicTipsEnabled?.()) return;
    if (!this.btn || !text) return;
    this._createMicTipBubble();
    const bubble = this.tipEl?.querySelector('.chillicollectivo-fab-tip-bubble');
    if (!bubble || !this.tipEl) return;

    const milestoneTier = Math.max(0, Math.min(6, Math.floor(Number(animOptions.milestoneTier) || 0)));
    const animateWords = !!animOptions.animateWords;
    const animateLetters = !!animOptions.animateLetters;

    for (let tier = 0; tier <= 6; tier += 1) {
      this.tipEl.classList.remove(`chillicollectivo-fab-tip-milestone-${tier}`);
      bubble.classList.remove(`chillicollectivo-fab-tip-bubble-milestone-${tier}`);
    }
    if (milestoneTier > 0) {
      this.tipEl.classList.add(`chillicollectivo-fab-tip-milestone-${milestoneTier}`);
      bubble.classList.add(`chillicollectivo-fab-tip-bubble-milestone-${milestoneTier}`);
    }

    if (animateLetters) {
      bubble.innerHTML = String(text).split('').map((char, index) => {
        const safe = char === ' ' ? '&nbsp;' : char.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
        return `<span class="chillicollectivo-fab-tip-letter" style="animation-delay:${index * 28}ms">${safe}</span>`;
      }).join('');
    } else if (animateWords) {
      bubble.innerHTML = String(text).split(/(\s+)/).filter((part) => part.length).map((part, index) => {
        if (/^\s+$/.test(part)) return part;
        const safe = part.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
        return `<span class="chillicollectivo-fab-tip-word" style="animation-delay:${index * 90}ms">${safe}</span>`;
      }).join('');
    } else {
      bubble.textContent = String(text);
    }

    this.tipEl.classList.remove('chillicollectivo-fab-tip-visible');
    this.tipEl.style.display = 'block';
    requestAnimationFrame(() => {
      this.tipEl?.classList.add('chillicollectivo-fab-tip-visible');
    });
    if (this._micTipHideTimer) clearTimeout(this._micTipHideTimer);
    this._micTipHideTimer = setTimeout(() => this.hideMicTipBubble(), durationMs);
  }

  hideMicTipBubble() {
    if (this._micTipHideTimer) {
      clearTimeout(this._micTipHideTimer);
      this._micTipHideTimer = null;
    }
    if (!this.tipEl) return;
    const bubble = this.tipEl.querySelector('.chillicollectivo-fab-tip-bubble');
    for (let tier = 0; tier <= 6; tier += 1) {
      this.tipEl.classList.remove(`chillicollectivo-fab-tip-milestone-${tier}`);
      bubble?.classList.remove(`chillicollectivo-fab-tip-bubble-milestone-${tier}`);
    }
    this.tipEl.classList.remove('chillicollectivo-fab-tip-visible');
    setTimeout(() => {
      if (this.tipEl && !this.tipEl.classList.contains('chillicollectivo-fab-tip-visible')) {
        this.tipEl.style.display = 'none';
      }
    }, 220);
  }

  _gearIconMode() {
    return this.effects.fabGearIcon === 'mic' ? 'mic' : 'leaf';
  }

  _gearIconHTML(mode = null) {
    const useMic = (mode || this._gearIconMode()) === 'mic';
    const src = chrome.runtime.getURL(useMic ? 'icons/fab-gear-mic.png' : 'icons/fab-chili-leaf.png');
    return `<img class="chillicollectivo-fab-gear-img" src="${src}" alt="Einstellungen">`;
  }

  _applyGearIcon() {
    if (!this.gearBtn) return;
    const useMic = this._gearIconMode() === 'mic';
    this.gearBtn.classList.toggle('chillicollectivo-fab-gear-mic', useMic);
    this.gearBtn.classList.toggle('chillicollectivo-fab-gear-leaf', !useMic);
    this.gearBtn.innerHTML = this._gearIconHTML();
  }

  _micOffSVG() {
    return `<span class="chillicollectivo-fab-chili-wrap chillicollectivo-fab-chili-wrap-off"><img class="chillicollectivo-fab-icon chillicollectivo-fab-icon-img chillicollectivo-fab-badge-img chillicollectivo-fab-chili-img" src="${chrome.runtime.getURL('icons/mic-off-chili.png')}" alt="Mikro aus"></span>`;
  }
  _micOnSVG() {
    return `<span class="chillicollectivo-fab-chili-wrap chillicollectivo-fab-chili-wrap-on"><img class="chillicollectivo-fab-icon chillicollectivo-fab-icon-img chillicollectivo-fab-badge-img chillicollectivo-fab-chili-img" src="${chrome.runtime.getURL('icons/mic-on-chili.png')}" alt="Mikro an"></span>`;
  }
  _micSVG() {
    return this._micOffSVG();
  }
  _recSVG() {
    return this._micOnSVG() +
      '<span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span>' +
      '<span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span>' +
      '<span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span>' +
      '<span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span><span class="chillicollectivo-spark"></span>';
  }
  _spinSVG() {
    return `<svg class="chillicollectivo-fab-icon chillicollectivo-spin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
      <circle cx="12" cy="12" r="10" opacity="0.25"/>
      <path d="M12 2a10 10 0 0 1 10 10" stroke-linecap="round"/>
    </svg>`;
  }
  _errorSVG() {
    return `<svg class="chillicollectivo-fab-icon" viewBox="0 0 24 24" fill="currentColor">
      <circle cx="12" cy="12" r="10"/>
      <path d="M12 8v4m0 4h.01" stroke="#0A0A0B" stroke-width="2.5" stroke-linecap="round"/>
    </svg>`;
  }

  _setupGear(gear) {
    gear.addEventListener('mousedown', e => e.stopPropagation());
    gear.addEventListener('click', e => {
      e.stopPropagation();
      e.preventDefault();
      const fallbackUrl = chrome.runtime.getURL('options.html');
      try {
        chrome.runtime.sendMessage({ type: 'OPEN_OPTIONS' }, (resp) => {
          if (chrome.runtime.lastError || resp?.success === false) {
            window.open(fallbackUrl, '_blank', 'noopener');
          }
        });
      } catch (_) {
        window.open(fallbackUrl, '_blank', 'noopener');
      }
    });
  }

  _setupDrag() {
    const btn = this.btn;
    const root = this._fabRootEl();
    let isDragging = false, startX = 0, startY = 0, startLeft = 0, startTop = 0;

    btn.addEventListener('mousedown', e => {
      if (e.button !== 0 || e.target.closest('#chillicollectivo-voice-fab-gear')) return;
      isDragging = true;
      this.isDragging = true;
      this.didDrag = false;
      startX = e.clientX;
      startY = e.clientY;
      const rect = root.getBoundingClientRect();
      startLeft = rect.left;
      startTop = rect.top;
      root.style.left = rect.left + 'px';
      root.style.top = rect.top + 'px';
      root.style.right = 'auto';
      root.style.bottom = 'auto';
      e.preventDefault();
      document.addEventListener('mousemove', onMove);
      document.addEventListener('mouseup', onUp);
    });

    const onMove = e => {
      if (!isDragging) return;
      const dx = e.clientX - startX, dy = e.clientY - startY;
      if (Math.abs(dx) > 3 || Math.abs(dy) > 3) this.didDrag = true;
      const m = 4;
      root.style.left = Math.max(m, Math.min(startLeft + dx, window.innerWidth - root.offsetWidth - m)) + 'px';
      root.style.top = Math.max(m, Math.min(startTop + dy, window.innerHeight - root.offsetHeight - m)) + 'px';
    };

    const onUp = () => {
      if (!isDragging) return;
      isDragging = false;
      this.isDragging = false;
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseup', onUp);
      try {
        chrome.storage.local.set({ chillicollectivoVoiceFabPos: { left: root.style.left, top: root.style.top } });
      } catch (_) {}
    };

    window.addEventListener('resize', () => {
      if (!this.isDragging) this._ensureInBounds();
    });
  }

  _ensureFabVisible() {
    const root = this._fabRootEl();
    if (!root) return;
    root.style.display = '';
    root.style.visibility = 'visible';
    root.style.opacity = '';
    if (this.btn) {
      this.btn.style.display = '';
      this.btn.style.visibility = 'visible';
    }
    this._ensureInBounds();
  }

  _ensureInBounds() {
    const root = this._fabRootEl();
    if (!root || (!root.style.left && !root.style.top && !root.style.bottom)) return;
    const m = 4;
    const rect = root.getBoundingClientRect();
    const rootWidth = root.offsetWidth || 56;
    const rootHeight = root.offsetHeight || 56;

    let left = parseFloat(root.style.left);
    let top = parseFloat(root.style.top);
    if (isNaN(left)) left = rect.left;
    if (isNaN(top)) top = rect.top;

    const maxLeft = window.innerWidth - rootWidth - m;
    const maxTop = window.innerHeight - rootHeight - m;

    let updated = false;
    if (left > maxLeft) { left = maxLeft; updated = true; }
    if (left < m) { left = m; updated = true; }
    if (top > maxTop) { top = maxTop; updated = true; }
    if (top < m) { top = m; updated = true; }

    if (updated) {
      root.style.left = left + 'px';
      root.style.top = top + 'px';
    }
  }

  _setupClick() {
    this.btn.addEventListener('click', e => {
      if (this.didDrag) { this.didDrag = false; return; }
      if (e.target.closest('#chillicollectivo-voice-fab-gear')) return;
      this.fieldManager.triggerToggle();
    });
  }

  _setupHotkeyListener() {
    const onKeyDown = (e) => {
      chillicollectivoTrackAltChord(e);
      // Legacy: Enter bei Interim ohne Vorschau-Modus – nicht wenn Einfüge-Hotkey aktiv ist
      if (!this.fieldManager?._isPreviewEnterToInsertEnabled?.()
        && e.key === 'Enter'
        && this.fieldManager?.currentState === 'recording'
        && this.fieldManager?._currentInterimText) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();

        const committed2 = this.fieldManager._currentInterimText;
        this.fieldManager.insertText(committed2, true);

        if (this.fieldManager.browserListening && this.fieldManager.browserRecognition) {
          this.fieldManager._lastInterim = '';
          if (this.fieldManager._browserSilenceTimer) {
            clearTimeout(this.fieldManager._browserSilenceTimer);
            this.fieldManager._browserSilenceTimer = null;
          }
          this.fieldManager._restartRecognitionAfterCommand?.();
        }

        return false;
      }

      if (!chillicollectivoShouldHandleHotkeyInContent(this.hotkey)) return;

      if (chillicollectivoVoiceHotkeyMatchesEvent(this.hotkey, e)) {
        if (chillicollectivoShouldSkipSingleKeyHotkey(this.hotkey, e)) return;
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        this.fieldManager.triggerToggle();
        return false;
      }
    };

    const onBeforeInput = (e) => {
      if (e.inputType !== 'insertText' || e.data !== 'º') return;
      const hk = chillicollectivoGetEffectiveVoiceHotkey(this.hotkey);
      if (!hk.altKey || String(hk.key || '').toUpperCase() !== 'J') return;
      const altLikely = Date.now() < chillicollectivoAltChordUntil
        || (typeof e.getModifierState === 'function' && e.getModifierState('Alt'));
      if (!altLikely) return;
      e.preventDefault();
      e.stopPropagation();
      e.stopImmediatePropagation();
      this.fieldManager.triggerToggle();
    };

    document.addEventListener('keydown', onKeyDown, true);
    window.addEventListener('keydown', onKeyDown, true);
    document.addEventListener('beforeinput', onBeforeInput, true);
  }

  setState(state, errorMsg = '') {
    if (!this.btn) return;
    if (this._lastFabVisualState === state && state !== 'error') return;
    this._lastFabVisualState = state;
    if (state !== 'recording' && state !== 'loading' && !this.fieldManager?._isMicTipsEnabled?.()) {
      this.hideMicTipBubble();
    }
    this.btn.className = `chillicollectivo-fab chillicollectivo-fab-${state}`;
    if (state === 'recording') {
      this.btn.style.setProperty('--chillicollectivo-voice-level', '0');
      this.btn.classList.remove('chillicollectivo-fab-speaking');
    } else {
      this.resetVoiceLevel();
    }

    const shortcut = chillicollectivoFormatVoiceHotkeyLabel(this.hotkey);
    const label = {
      ready:     `ChiliColectivo starten (${shortcut})`,
      recording: `Aufnahme läuft – stoppen (${shortcut})`,
      loading:   'Verarbeite…',
      error:     errorMsg || 'Fehler – klicken zum Neustarten',
      locked:    errorMsg || 'Gesperrt – Lizenz erforderlich',
    }[state] || 'ChiliColectivo';
    this.btn.setAttribute('aria-label', label);
    this.btn.title = label;

    const iconHTML = {
      ready: this._micOffSVG(),
      recording: this._recSVG(),
      loading: this._micOffSVG(),
      error: this._micOffSVG(),
      locked: this._micOffSVG(),
    }[state] || this._micOffSVG();
    this.btn.innerHTML = iconHTML;
    if (this.gearBtn && (state === 'ready' || state === 'error' || state === 'locked')) {
      this.btn.appendChild(this.gearBtn);
    }
    if (state === 'recording') {
      this._applyFabVisuals();
    }
  }

  _applyEffects() {
    if (!this.btn) return;
    this._applyEffectIntensityVars(this.btn);
    this._applyToolThemeVars();
    this._applyGearIcon();
    if (this.btn.classList.contains('chillicollectivo-fab-recording')) {
      this._applyFabVisuals();
      if (this.fieldManager?.browserMicStream && this.fieldManager?.browserListening) {
        this.fieldManager._startMicLevelMonitor(this.fieldManager.browserMicStream);
      }
    }
    const overlay = document.getElementById('chillicollectivo-voice-overlay');
    if (overlay) this._applyPreviewAppearanceVars(overlay);
  }

  setSpeakingActive(active) {
    if (!this.btn?.classList.contains('chillicollectivo-fab-recording')) return;
    this.btn.classList.toggle('chillicollectivo-fab-speaking', !!active);
    this._applyFabVisuals();
  }

  setVoiceLevel(level) {
    if (!this.btn?.classList.contains('chillicollectivo-fab-recording')) return;
    if (!this.btn.classList.contains('chillicollectivo-fab-speaking')) return;
    this._micLevel = Math.max(0, Math.min(1, level));
    this._applyFabVisuals();
  }

  setTextBroil(_charCount) {
    /* Broil nur noch über Mikrofon – Textlänge steuert keine Animation mehr */
  }

  _applyFabVisuals() {
    if (!this.btn || !this.btn.classList.contains('chillicollectivo-fab-recording')) return;

    if (!this.btn.classList.contains('chillicollectivo-fab-speaking')) {
      this._micLevel = 0;
      this._textShake = 0;
      this._voiceLevel = 0;
      this.btn.style.setProperty('--chillicollectivo-voice-level', '0');
      this.btn.style.setProperty('--chillicollectivo-text-shake', '0');
      this.btn.classList.remove(
        'chillicollectivo-fab-voice-hot',
        'chillicollectivo-fab-voice-blaze',
        'chillicollectivo-fab-text-shake',
        'chillicollectivo-fab-text-broil',
        'chillicollectivo-fab-text-broil-heavy',
      );
      const icon = this.btn.querySelector('.chillicollectivo-fab-icon-img');
      if (icon) {
        if (icon.classList.contains('chillicollectivo-fab-chili-img')) {
          icon.style.removeProperty('filter');
          icon.style.removeProperty('animation');
        } else if (icon.classList.contains('chillicollectivo-fab-badge-img')) {
          icon.style.removeProperty('filter');
          icon.style.removeProperty('animation');
        } else {
          icon.style.removeProperty('filter');
        }
        icon.style.removeProperty('transform');
      }
      return;
    }

    const mic = this._micLevel || 0;
    this._voiceLevel = mic;

    this.btn.style.setProperty('--chillicollectivo-voice-level', mic.toFixed(3));
    this.btn.style.setProperty('--chillicollectivo-text-shake', '0');
    this.btn.classList.toggle('chillicollectivo-fab-voice-hot', mic > 0.06);
    this.btn.classList.toggle('chillicollectivo-fab-voice-blaze', mic > 0.18);

    const icon = this.btn.querySelector('.chillicollectivo-fab-icon-img');
    if (icon) {
      const bright = 1 + mic * 0.62;
      const glow = 14 + mic * 58;
      const glowAlpha = 0.52 + mic * 0.58;
      const rgb = this._toolAccentRgb || '212, 110, 110';
      if (icon.classList.contains('chillicollectivo-fab-chili-img')) {
        const inner = 2 + mic * 5;
        const mid = 5 + mic * 14;
        const outer = 10 + mic * 26;
        const a1 = 0.48 + mic * 0.42;
        const a2 = 0.3 + mic * 0.38;
        const a3 = 0.15 + mic * 0.22;
        icon.style.filter =
          `brightness(${bright}) drop-shadow(0 0 ${inner}px rgba(${rgb}, ${a1})) drop-shadow(0 0 ${mid}px rgba(${rgb}, ${a2})) drop-shadow(0 0 ${outer}px rgba(${rgb}, ${a3})) saturate(${1 + mic * 0.18})`;
      } else if (icon.classList.contains('chillicollectivo-fab-badge-img')) {
        icon.style.filter =
          `brightness(${bright}) drop-shadow(0 0 ${glow}px rgba(${rgb}, ${glowAlpha})) saturate(${1 + mic * 0.22})`;
      } else {
        icon.style.filter =
          `brightness(0) saturate(100%) invert(79%) sepia(45%) saturate(626%) hue-rotate(93deg) brightness(${bright * 97}%) contrast(92%) drop-shadow(0 0 ${glow}px rgba(${rgb}, ${glowAlpha}))`;
      }
      icon.style.removeProperty('animation');
      icon.style.removeProperty('transform');
    }
  }

  resetLiveBroil() {
    if (!this.btn) return;
    this._micLevel = 0;
    this._textShake = 0;
    if (this.btn.classList.contains('chillicollectivo-fab-recording')) {
      this.setSpeakingActive(false);
      this._applyFabVisuals();
      this.btn.style.animation = 'none';
      const icon = this.btn.querySelector('.chillicollectivo-fab-icon-img');
      if (icon) {
        icon.style.animation = 'none';
        icon.style.removeProperty('transform');
      }
      void this.btn.offsetWidth;
      this.btn.style.removeProperty('animation');
      if (icon) icon.style.removeProperty('animation');
    }
  }

  resetVoiceLevel() {
    if (!this.btn) return;
    this._micLevel = 0;
    this._textShake = 0;
    this._voiceLevel = 0;
    this.btn.style.setProperty('--chillicollectivo-voice-level', '0');
    this.btn.style.setProperty('--chillicollectivo-text-shake', '0');
    this.btn.classList.remove(
      'chillicollectivo-fab-speaking',
      'chillicollectivo-fab-voice-hot',
      'chillicollectivo-fab-voice-blaze',
      'chillicollectivo-fab-text-shake',
      'chillicollectivo-fab-text-broil',
      'chillicollectivo-fab-text-broil-heavy',
    );
    this.btn.style.removeProperty('transform');
    const icon = this.btn.querySelector('.chillicollectivo-fab-icon-img');
    if (icon) {
      icon.style.removeProperty('filter');
      icon.style.removeProperty('animation');
      icon.style.removeProperty('transform');
    }
    const overlay = document.getElementById('chillicollectivo-voice-overlay');
    if (overlay) {
      overlay.style.setProperty('--chillicollectivo-voice-level', '0');
      overlay.style.setProperty('--chillicollectivo-text-shake', '0');
      overlay.classList.remove('chillicollectivo-voice-overlay-hot', 'chillicollectivo-voice-overlay-broil', 'chillicollectivo-voice-overlay-broil-heavy');
    }
  }

  _formatHotkey() {
    return chillicollectivoFormatVoiceHotkeyLabel(this.hotkey);
  }

  _refreshHotkeyLabel() {
    if (!this.btn || !this.fieldManager) return;
    const state = this.fieldManager.currentState || 'ready';
    this.setState(state, this.fieldManager.errorMessage || '');
  }

  _getOrCreateOverlay() {
    let el = document.getElementById('chillicollectivo-voice-overlay');
    if (el) {
      this._ensurePreviewShell(el);
      this._applyPreviewLayout(el);
      return el;
    }
    el = document.createElement('div');
    el.id = 'chillicollectivo-voice-overlay';
    el.className = 'chillicollectivo-voice-overlay';
    document.body.appendChild(el);
    this._ensurePreviewShell(el);
    this._applyPreviewLayout(el);
    this._bindEditablePreviewEvents(el);
    return el;
  }

  _normalizePreviewLayout() {
    this.previewLayout.movable = this.previewLayout.movable === true;
    this.previewLayout.pinned = this.previewLayout.pinned === true;
    if (!this.previewLayout.position || typeof this.previewLayout.position !== 'object') {
      this.previewLayout.position = null;
    }
    if (!this.previewLayout.size || typeof this.previewLayout.size !== 'object') {
      this.previewLayout.size = null;
    }
  }

  _canDragPreview() {
    return this.previewLayout.movable === true && this.previewLayout.pinned !== true;
  }

  _getPreviewContentEl(el) {
    if (!el) return null;
    this._ensurePreviewShell(el);
    return el.querySelector('.chillicollectivo-voice-overlay-content');
  }

  _clearPreviewContent(el) {
    const content = this._getPreviewContentEl(el);
    if (content) content.innerHTML = '';
  }

  _ensurePreviewShell(el) {
    if (!el) return el;

    let content = el.querySelector('.chillicollectivo-voice-overlay-content');
    if (!content) {
      content = document.createElement('div');
      content.className = 'chillicollectivo-voice-overlay-content';
      el.appendChild(content);
    }

    const canDrag = this._canDragPreview();
    let handle = el.querySelector('.chillicollectivo-voice-overlay-drag-handle');
    if (canDrag) {
      if (!handle) {
        handle = document.createElement('div');
        handle.className = 'chillicollectivo-voice-overlay-drag-handle';
        handle.setAttribute('aria-label', 'Vorschau verschieben');
        handle.title = 'Vorschau verschieben';
        handle.textContent = '⋮⋮';
        el.insertBefore(handle, content);
        this._bindPreviewDragHandle(handle, el);
      }
      handle.hidden = false;
    } else if (handle) {
      handle.hidden = true;
    }

    let resizeHandle = el.querySelector('.chillicollectivo-voice-overlay-resize-handle');
    if (canDrag) {
      if (!resizeHandle) {
        resizeHandle = document.createElement('div');
        resizeHandle.className = 'chillicollectivo-voice-overlay-resize-handle';
        resizeHandle.setAttribute('aria-label', 'Vorschau skalieren');
        resizeHandle.title = 'Größe ändern';
        resizeHandle.textContent = '◢';
        el.appendChild(resizeHandle);
        this._bindPreviewResizeHandle(resizeHandle, el);
      }
      resizeHandle.hidden = false;
    } else if (resizeHandle) {
      resizeHandle.hidden = true;
    }

    el.classList.toggle('chillicollectivo-voice-overlay-movable', this.previewLayout.movable === true);
    el.classList.toggle('chillicollectivo-voice-overlay-pinned', this.previewLayout.pinned === true);
    el.classList.toggle('chillicollectivo-voice-overlay-drag-enabled', canDrag);
    return el;
  }

  _applyPreviewLayout(el) {
    const target = el || document.getElementById('chillicollectivo-voice-overlay');
    if (!target) return;
    this._ensurePreviewShell(target);

    const pos = this.previewLayout.position;
    const size = this.previewLayout.size;
    const hasCustomPos = this.previewLayout.movable === true
      && pos
      && String(pos.left || '').trim()
      && String(pos.top || '').trim();
    const hasCustomSize = this.previewLayout.movable === true
      && size
      && String(size.width || '').trim()
      && String(size.height || '').trim();
    const isCustomized = hasCustomPos || hasCustomSize
      || target.classList.contains('chillicollectivo-voice-overlay-custom-pos')
      || target.classList.contains('chillicollectivo-voice-overlay-custom-size');

    if (isCustomized) {
      target.classList.add('chillicollectivo-voice-overlay-custom-pos');
      target.style.transform = 'none';
      target.style.right = 'auto';
      target.style.bottom = 'auto';
      if (hasCustomPos && !this._previewDragging && !this._previewResizing) {
        target.style.left = String(pos.left);
        target.style.top = String(pos.top);
      }
    } else if (!this._previewDragging && !this._previewResizing) {
      target.classList.remove('chillicollectivo-voice-overlay-custom-pos', 'chillicollectivo-voice-overlay-custom-size');
      target.style.left = '50%';
      target.style.top = '80px';
      target.style.transform = 'translateX(-50%)';
      target.style.width = '';
      target.style.height = '';
      target.style.maxWidth = '';
      target.style.minHeight = '';
    }

    if (hasCustomSize && !this._previewResizing) {
      target.classList.add('chillicollectivo-voice-overlay-custom-size');
      target.style.width = String(size.width);
      target.style.height = String(size.height);
      target.style.maxWidth = 'none';
      target.style.minHeight = String(size.height);
    } else if (!this._previewResizing && !target.style.width) {
      target.classList.remove('chillicollectivo-voice-overlay-custom-size');
      if (!isCustomized) {
        target.style.height = '';
        target.style.minHeight = '';
      }
    }

    requestAnimationFrame(() => {
      this._applyPreviewTypography(target);
      if (!this._previewDragging && !this._previewResizing) this._ensurePreviewInBounds(target);
    });
  }

  _anchorPreviewBox(root) {
    const rect = root.getBoundingClientRect();
    root.style.left = `${rect.left}px`;
    root.style.top = `${rect.top}px`;
    root.style.width = `${rect.width}px`;
    root.style.height = `${rect.height}px`;
    root.style.right = 'auto';
    root.style.bottom = 'auto';
    root.style.transform = 'none';
    root.style.maxWidth = 'none';
    root.classList.add('chillicollectivo-voice-overlay-custom-pos', 'chillicollectivo-voice-overlay-custom-size');
  }

  _bindPreviewDragHandle(handle, root) {
    if (!handle || handle.dataset.ccPreviewDragBound === '1') return;
    handle.dataset.ccPreviewDragBound = '1';

    let dragging = false;
    let startX = 0;
    let startY = 0;
    let startLeft = 0;
    let startTop = 0;

    const onMove = (e) => {
      if (!dragging) return;
      const dx = e.clientX - startX;
      const dy = e.clientY - startY;
      const m = 6;
      root.style.left = `${Math.max(m, Math.min(startLeft + dx, window.innerWidth - root.offsetWidth - m))}px`;
      root.style.top = `${Math.max(m, Math.min(startTop + dy, window.innerHeight - root.offsetHeight - m))}px`;
      root.classList.add('chillicollectivo-voice-overlay-custom-pos');
      root.style.transform = 'none';
    };

    const onUp = () => {
      if (!dragging) return;
      dragging = false;
      this._previewDragging = false;
      root.classList.remove('chillicollectivo-voice-overlay-dragging');
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseup', onUp);
      this._savePreviewLayoutState(root);
    };

    handle.addEventListener('mousedown', (e) => {
      if (!this._canDragPreview() || e.button !== 0) return;
      e.preventDefault();
      e.stopPropagation();
      dragging = true;
      this._previewDragging = true;
      this._anchorPreviewBox(root);
      startX = e.clientX;
      startY = e.clientY;
      startLeft = this._parsePreviewPx(root.style.left, root.getBoundingClientRect().left);
      startTop = this._parsePreviewPx(root.style.top, root.getBoundingClientRect().top);
      root.classList.add('chillicollectivo-voice-overlay-dragging');
      document.addEventListener('mousemove', onMove);
      document.addEventListener('mouseup', onUp);
    });
  }

  _bindPreviewResizeHandle(handle, root) {
    if (!handle || handle.dataset.ccPreviewResizeBound === '1') return;
    handle.dataset.ccPreviewResizeBound = '1';

    let resizing = false;
    let startX = 0;
    let startY = 0;
    let startW = 0;
    let startH = 0;

    const onMove = (e) => {
      if (!resizing) return;
      const dx = e.clientX - startX;
      const dy = e.clientY - startY;
      const next = this._clampPreviewSize(startW + dx, startH + dy);
      root.style.width = `${next.width}px`;
      root.style.height = `${next.height}px`;
      root.style.minHeight = `${next.height}px`;
      this._applyPreviewTypography(root);
    };

    const onUp = () => {
      if (!resizing) return;
      resizing = false;
      this._previewResizing = false;
      root.classList.remove('chillicollectivo-voice-overlay-resizing');
      document.removeEventListener('mousemove', onMove);
      document.removeEventListener('mouseup', onUp);
      this._savePreviewLayoutState(root);
      this._applyPreviewTypography(root);
    };

    handle.addEventListener('mousedown', (e) => {
      if (!this._canDragPreview() || e.button !== 0) return;
      e.preventDefault();
      e.stopPropagation();
      resizing = true;
      this._previewResizing = true;
      this._anchorPreviewBox(root);
      const rect = root.getBoundingClientRect();
      startX = e.clientX;
      startY = e.clientY;
      startW = rect.width;
      startH = rect.height;
      root.classList.add('chillicollectivo-voice-overlay-resizing');
      document.addEventListener('mousemove', onMove);
      document.addEventListener('mouseup', onUp);
    });
  }

  _ensurePreviewInBounds(root) {
    if (!root || !root.classList.contains('chillicollectivo-voice-overlay-custom-pos')) return;
    const m = 6;
    const width = root.offsetWidth || this._parsePreviewPx(root.style.width, CHILLICOLLECTIVO_PREVIEW_MIN_WIDTH);
    const height = root.offsetHeight || this._parsePreviewPx(root.style.height, CHILLICOLLECTIVO_PREVIEW_MIN_HEIGHT);
    let left = this._parsePreviewPx(root.style.left, root.getBoundingClientRect().left);
    let top = this._parsePreviewPx(root.style.top, root.getBoundingClientRect().top);

    const maxLeft = Math.max(m, window.innerWidth - width - m);
    const maxTop = Math.max(m, window.innerHeight - height - m);
    let updated = false;
    if (left > maxLeft) { left = maxLeft; updated = true; }
    if (left < m) { left = m; updated = true; }
    if (top > maxTop) { top = maxTop; updated = true; }
    if (top < m) { top = m; updated = true; }
    if (!updated) return;

    root.style.left = `${left}px`;
    root.style.top = `${top}px`;
    this._savePreviewLayoutState(root);
  }

  _isPreviewEditableDictation(options = {}) {
    return false;
  }

  _isPreviewBeingEdited() {
    return !!this._previewUserEditing;
  }

  _isPreviewMarkingOnly() {
    return !!this._previewMouseSelecting && !this._previewUserEditing;
  }

  _isPreviewDomLockedForDictation() {
    return this._isPreviewBeingEdited()
      || this._isPreviewMarkingOnly()
      || !!this.fieldManager?._hasDictationReplaceSpan?.('preview');
  }

  _resolveEditablePreviewLiveDisplay(committedText = '', interimText = '', hasPreviewReplace = false) {
    const committed = String(committedText || '').trim();
    const interim = String(interimText || '').trim();
    const markingOnly = this._isPreviewMarkingOnly();
    const userTouching = this._isPreviewBeingEdited() || markingOnly;
    const deferDomMerge = userTouching && !hasPreviewReplace;
    if (deferDomMerge || hasPreviewReplace) return committed;
    return this._composeEditablePreviewDisplay(committed, interim);
  }

  _shouldDeferEditablePreviewDomMerge(hasPreviewReplace = false) {
    if (hasPreviewReplace) return true;
    const markingOnly = this._isPreviewMarkingOnly();
    return this._isPreviewBeingEdited() || markingOnly;
  }

  _composeEditablePreviewDisplay(committedText = '', interimText = '') {
    const merged = this.fieldManager?._mergePreviewDisplaySegments?.(committedText, interimText)
      ?? String(interimText || committedText || '').replace(/\s+/g, ' ').trim();
    const collapsed = this.fieldManager?._collapsePreviewPhraseStack?.(merged) ?? merged;
    return this._formatDictationPreviewText(collapsed);
  }

  _formatDictationPreviewText(text) {
    return chillicollectivoApplyPreviewTextCase(String(text || ''), this.effects.previewTextCase);
  }

  _invalidatePreviewDomCache() {
    this._previewDom = null;
    this._previewCommittedSelectionRange = null;
    this._lastRenderedInterim = '';
    this._lastRenderedCommitted = '';
    this._lastRenderedSendwordBadge = '';
    this._lastRenderedInsertHint = '';
    this._lastRenderedCharCount = '';
  }

  _getCommittedPreviewEl() {
    const cached = this._previewDom?.committedEl;
    if (cached?.isConnected) return cached;
    const live = document.querySelector('#chillicollectivo-voice-overlay .chillicollectivo-voice-overlay-committed');
    return live?.isConnected ? live : null;
  }

  _isRangeInsideCommittedPreview(range, committedEl) {
    if (!range || !committedEl) return false;
    try {
      return committedEl.contains(range.startContainer) && committedEl.contains(range.endContainer);
    } catch (_) {
      return false;
    }
  }

  _normalizePreviewPlainText(text) {
    return String(text || '').replace(/\u00A0/g, ' ').replace(/\s+/g, ' ');
  }

  _clampPreviewSelectionOffsets(committedEl, offsets) {
    if (!offsets || !committedEl) return null;
    const len = this._normalizePreviewPlainText(committedEl.innerText || '').length;
    const start = Math.max(0, Math.min(Number(offsets.start) || 0, len));
    const end = Math.max(start, Math.min(Number(offsets.end) || 0, len));
    if (end <= start) return null;
    return { start, end };
  }

  _snapshotPreviewCommitted(committedEl) {
    const el = committedEl || this._getCommittedPreviewEl();
    if (!el) {
      return String(this.fieldManager?.previewCommitted || '').replace(/\s+/g, ' ').trim();
    }
    return this._normalizePreviewPlainText(el.innerText || '').trim();
  }

  _previewMarkedTextDetail(committedEl, start, end) {
    const plain = this._snapshotPreviewCommitted(committedEl);
    const safeStart = Math.max(0, Math.min(start, plain.length));
    const safeEnd = Math.max(safeStart, Math.min(end, plain.length));
    const markedText = plain.substring(safeStart, safeEnd);
    return {
      markedText,
      markedTextSnippet: markedText.slice(0, 160),
      contextBefore: plain.slice(Math.max(0, safeStart - 50), safeStart),
      contextAfter: plain.slice(safeEnd, safeEnd + 50),
      previewFull: plain.slice(0, 300),
      previewLen: plain.length,
      start: safeStart,
      end: safeEnd,
      spanLen: safeEnd - safeStart,
    };
  }

  _previewEditRemovedSnippet(before, after) {
    const a = String(before || '');
    const b = String(after || '');
    let prefix = 0;
    while (prefix < a.length && prefix < b.length && a[prefix] === b[prefix]) prefix += 1;
    let suffix = 0;
    while (
      suffix < a.length - prefix
      && suffix < b.length - prefix
      && a[a.length - 1 - suffix] === b[b.length - 1 - suffix]
    ) suffix += 1;
    return a.slice(prefix, a.length - suffix).slice(0, 160);
  }

  _previewEditAddedSnippet(before, after) {
    const a = String(before || '');
    const b = String(after || '');
    let prefix = 0;
    while (prefix < a.length && prefix < b.length && a[prefix] === b[prefix]) prefix += 1;
    let suffix = 0;
    while (
      suffix < a.length - prefix
      && suffix < b.length - prefix
      && a[a.length - 1 - suffix] === b[b.length - 1 - suffix]
    ) suffix += 1;
    return b.slice(prefix, b.length - suffix).slice(0, 160);
  }

  _clearPreviewSelectionState() {
    this._previewCommittedSelectionRange = null;
    this._previewSelectionOffsets = null;
  }

  _logPreviewEdit(event, message, detail = {}) {
    chillicollectivoVoiceDiag('info', `voice.preview-edit-${event}`, message, detail, this.fieldManager);
  }

  _capturePreviewCommittedSelection() {
    if (this._previewDictationMutating) return;
    const committedEl = this._getCommittedPreviewEl();
    if (!committedEl) {
      this._clearPreviewSelectionState();
      return;
    }
    const sel = window.getSelection();
    if (!sel?.rangeCount) return;
    const range = sel.getRangeAt(0);
    if (!this._isRangeInsideCommittedPreview(range, committedEl)) return;
    if (range.collapsed) {
      if (this._previewMouseSelecting) return;
      if (this.fieldManager?._hasDictationReplaceSpan?.('preview')) return;
      if (this._previewSelectionOffsets || this._previewCommittedSelectionRange) {
        const clearedDetail = this._previewMarkedTextDetail(
          committedEl,
          this._previewSelectionOffsets?.start ?? 0,
          this._previewSelectionOffsets?.end ?? 0,
        );
        this._logPreviewEdit('selection-cleared', 'Vorschau-Markierung gelöscht', {
          reason: 'collapsed-selection',
          ...clearedDetail,
        });
      }
      this._clearPreviewSelectionState();
      return;
    }
    try {
      this._previewCommittedSelectionRange = range.cloneRange();
    } catch (_) {
      this._previewCommittedSelectionRange = null;
    }
    const offsets = this.fieldManager?._rangeTextOffsets?.(committedEl, range);
    this._previewSelectionOffsets = offsets && offsets.start !== offsets.end ? offsets : null;
    const now = Date.now();
    if (this._previewSelectionOffsets && now - (this._previewSelectionLogAt || 0) > 400) {
      this._previewSelectionLogAt = now;
      this._logPreviewEdit('selection-captured', 'Vorschau-Markierung erfasst', this._previewMarkedTextDetail(
        committedEl,
        this._previewSelectionOffsets.start,
        this._previewSelectionOffsets.end,
      ));
    }
  }

  _resolvePreviewReplacementRange(committedEl) {
    const offsets = this._clampPreviewSelectionOffsets(committedEl, this._previewSelectionOffsets);
    if (offsets && this.fieldManager?._offsetsToRange) {
      const fromOffsets = this.fieldManager._offsetsToRange(committedEl, offsets.start, offsets.end);
      if (fromOffsets) return fromOffsets;
    }
    if (this._previewCommittedSelectionRange
      && !this._previewCommittedSelectionRange.collapsed
      && this._isRangeInsideCommittedPreview(this._previewCommittedSelectionRange, committedEl)) {
      try {
        return this._previewCommittedSelectionRange.cloneRange();
      } catch (_) {}
    }
    const sel = window.getSelection();
    if (sel?.rangeCount) {
      const live = sel.getRangeAt(0);
      if (!live.collapsed && this._isRangeInsideCommittedPreview(live, committedEl)) {
        try {
          return live.cloneRange();
        } catch (_) {}
      }
    }
    return null;
  }

  _moveCommittedPreviewCaretToEnd(committedEl) {
    if (!committedEl) return;
    try {
      this._previewDictationFocus = true;
      committedEl.focus({ preventScroll: true });
      const range = document.createRange();
      range.selectNodeContents(committedEl);
      range.collapse(false);
      const sel = window.getSelection();
      sel?.removeAllRanges();
      sel?.addRange(range);
    } catch (_) {}
  }

  _appendTextToCommittedPreviewEnd(committedEl, clean) {
    const existing = committedEl.innerText.replace(/\u00A0/g, ' ').replace(/\s+/g, ' ').trim();
    committedEl.textContent = existing ? `${existing} ${clean}` : clean;
    this._moveCommittedPreviewCaretToEnd(committedEl);
  }

  _ensurePreviewDomCache(el) {
    if (this._previewDom?.el === el && this._previewDom.committedEl?.isConnected) return this._previewDom;
    this._previewDom = {
      el,
      committedEl: el.querySelector('.chillicollectivo-voice-overlay-committed[contenteditable="true"]'),
      interimEl: el.querySelector('.chillicollectivo-voice-overlay-interim'),
      badgeEl: el.querySelector('.chillicollectivo-voice-overlay-sendword'),
      hintEl: el.querySelector('.chillicollectivo-voice-overlay-enter-hint'),
      charCountEl: el.querySelector('.chillicollectivo-voice-overlay-charcount'),
    };
    return this._previewDom;
  }

  _setPreviewAuxText(el, className, text, cacheKey) {
    const value = String(text || '').trim();
    const cache = this._ensurePreviewDomCache(el);
    const keyMap = {
      interim: 'interimEl',
      badge: 'badgeEl',
      hint: 'hintEl',
      charcount: 'charCountEl',
    };
    let node = cache[keyMap[cacheKey]];
    if (!value) {
      if (node) {
        node.remove();
        cache[keyMap[cacheKey]] = null;
      }
      return null;
    }
    if (!node || !node.isConnected) {
      node = document.createElement('span');
      node.className = cacheKey === 'charcount'
        ? 'chillicollectivo-voice-overlay-charcount'
        : `chillicollectivo-voice-overlay-${cacheKey === 'badge' ? 'sendword' : cacheKey === 'hint' ? 'enter-hint' : 'interim'}`;
      const host = this._getPreviewContentEl(el) || el;
      if (cacheKey === 'charcount') {
        const anchor = host.querySelector('.chillicollectivo-voice-preview-body') || host.firstChild;
        if (anchor) host.insertBefore(node, anchor);
        else host.appendChild(node);
      } else if (cacheKey === 'interim') {
        const hint = cache.hintEl;
        if (hint?.isConnected) host.insertBefore(node, hint);
        else host.appendChild(node);
      } else {
        host.appendChild(node);
      }
      cache[keyMap[cacheKey]] = node;
    }
    if (node.textContent !== value) node.textContent = value;
    if (cacheKey === 'charcount') {
      const parts = value.split('/');
      const written = parts[0] || '0';
      const remaining = parts[1] || '0';
      node.setAttribute('aria-label', `${written} Zeichen geschrieben, ${remaining} Zeichen fehlen noch`);
      node.classList.toggle('chillicollectivo-voice-overlay-charcount-ready', remaining === '0' && value.includes('/'));
    }
    return node;
  }

  scheduleDictationPreviewUpdate(interimText, committedText = '', options = {}) {
    this._pendingPreviewUpdate = { interimText, committedText, options };
    if (this._previewUpdateRAF) return;
    this._previewUpdateRAF = requestAnimationFrame(() => {
      this._previewUpdateRAF = null;
      const pending = this._pendingPreviewUpdate;
      this._pendingPreviewUpdate = null;
      if (!pending) return;
      this.showPreview(pending.interimText, pending.committedText, pending.options);
    });
  }

  _updateEditableDictationPreviewInPlace(el, interimText, committedText, options = {}) {
    if (!el.classList.contains('chillicollectivo-voice-overlay-visible')) return false;
    if (!el.querySelector('.chillicollectivo-voice-preview-body')) return false;

    const committed = String(committedText || '').trim();
    const interim = String(interimText || '').trim();
    const sendwordBadge = String(options.sendwordBadge || '').trim();
    const insertHint = String(options.insertHint || this.fieldManager?._getPreviewInsertHint?.() || '').trim();
    const charCountLabel = String(
      options.charCountLabel
      ?? this.fieldManager?._getPreviewCharCountLabel?.(interimText, committedText)
      ?? '',
    ).trim();

    if (
      interim === this._lastRenderedInterim
      && committed === this._lastRenderedCommitted
      && sendwordBadge === this._lastRenderedSendwordBadge
      && insertHint === this._lastRenderedInsertHint
      && charCountLabel === this._lastRenderedCharCount
      && !interim
    ) {
      return true;
    }

    const hasPreviewReplace = !!this.fieldManager?._hasDictationReplaceSpan?.('preview');
    const deferDomMerge = this._shouldDeferEditablePreviewDomMerge(hasPreviewReplace);
    const liveDisplay = this._resolveEditablePreviewLiveDisplay(committed, interim, hasPreviewReplace);
    const collapsedDisplay = this.fieldManager?._collapsePreviewPhraseStack?.(liveDisplay) || liveDisplay;
    const prevDisplay = String(this._lastRenderedCommitted || '').replace(/\s+/g, ' ').trim();
    const nextDisplay = String(collapsedDisplay || '').replace(/\s+/g, ' ').trim();
    if (!deferDomMerge && prevDisplay && nextDisplay.length > prevDisplay.length * 1.35) {
      const normPrev = this.fieldManager?._normalizeDuplicateText?.(prevDisplay) || prevDisplay.toLowerCase();
      const normNext = this.fieldManager?._normalizeDuplicateText?.(nextDisplay) || nextDisplay.toLowerCase();
      const normalGrowth = normNext.startsWith(normPrev) || normPrev.startsWith(normNext);
      if (!normalGrowth && normNext.includes(normPrev) && normNext.length > normPrev.length * 1.2) {
        chillicollectivoVoiceDiag('warn', 'voice.preview-stack-detected', 'Vorschau enthält möglichen Doppel-Stack', {
          beforeLen: prevDisplay.length,
          afterLen: nextDisplay.length,
          beforeSnippet: prevDisplay.slice(0, 100),
          afterSnippet: nextDisplay.slice(0, 160),
          committedLen: committed.length,
          interimLen: interim.length,
        }, this.fieldManager);
      }
    }
    this.fieldManager?._noteDisplayShrink?.(this._lastRenderedCommitted, liveDisplay, 'preview-dom-update');
    if (!deferDomMerge && prevDisplay && !nextDisplay) {
      if (this.fieldManager?.currentState === 'recording' && this.fieldManager?._isPreviewEnterToInsertEnabled?.()) {
        this.fieldManager._logPreviewFlow('warn', 'voice.preview-dom-clear-blocked', 'Vorschau-DOM-Leerung während Diktat blockiert', {
          prevDisplayLen: prevDisplay.length,
          prevDisplaySnippet: prevDisplay.slice(-100),
          committedLen: committed.length,
          interimLen: interim.length,
        });
        return true;
      }
      this.fieldManager?._logPreviewFlow?.('warn', 'voice.preview-dom-cleared', 'Vorschau-DOM würde geleert – möglicher Textverlust', {
        prevDisplayLen: prevDisplay.length,
        prevDisplaySnippet: prevDisplay.slice(-100),
        committedLen: committed.length,
        interimLen: interim.length,
      });
    }

    if (
      nextDisplay === this._lastRenderedCommitted
      && interim === this._lastRenderedInterim
      && sendwordBadge === this._lastRenderedSendwordBadge
      && insertHint === this._lastRenderedInsertHint
      && charCountLabel === this._lastRenderedCharCount
    ) {
      return true;
    }

    if (deferDomMerge && !hasPreviewReplace && (interim !== this._lastRenderedInterim || charCountLabel !== this._lastRenderedCharCount)) {
      const hadInterimUpdate = interim !== this._lastRenderedInterim;
      if (hadInterimUpdate) {
        this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-interim', interim, 'interim');
        this._lastRenderedInterim = interim;
      }
      if (charCountLabel !== this._lastRenderedCharCount) {
        this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-charcount', charCountLabel, 'charcount');
        this._lastRenderedCharCount = charCountLabel;
      }
      this._logPreviewEdit('update-deferred', 'Vorschau-Update während Bearbeitung zurückgestellt', {
        interimLen: interim.length,
        committedLen: committed.length,
        charCountChanged: charCountLabel !== this._lastRenderedCharCount,
        interimShownLive: hadInterimUpdate,
        markingOnly: this._isPreviewMarkingOnly(),
      });
      return true;
    }

    if (hasPreviewReplace) {
      if (sendwordBadge !== this._lastRenderedSendwordBadge) {
        this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-sendword', sendwordBadge, 'badge');
        this._lastRenderedSendwordBadge = sendwordBadge;
      }
      if (insertHint !== this._lastRenderedInsertHint) {
        this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-enter-hint', insertHint, 'hint');
        this._lastRenderedInsertHint = insertHint;
      }
      if (charCountLabel !== this._lastRenderedCharCount) {
        this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-charcount', charCountLabel, 'charcount');
        this._lastRenderedCharCount = charCountLabel;
      }
      return true;
    }

    const cache = this._ensurePreviewDomCache(el);
    if (!cache.committedEl) return false;

    if (liveDisplay !== this._lastRenderedCommitted) {
      if (cache.committedEl.textContent !== nextDisplay) {
        this._previewDictationMutating = true;
        cache.committedEl.textContent = nextDisplay;
        this._previewDictationMutating = false;
      }
    }

    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-interim', '', 'interim');
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-sendword', sendwordBadge, 'badge');
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-enter-hint', insertHint, 'hint');
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-charcount', charCountLabel, 'charcount');

    this._previewSendwordBadge = sendwordBadge;
    this._previewVisible = true;
    this._lastRenderedInterim = interim;
    this._lastRenderedCommitted = nextDisplay;
    this._lastRenderedCharCount = charCountLabel;
    this._lastRenderedSendwordBadge = sendwordBadge;
    this._lastRenderedInsertHint = insertHint;
    return true;
  }

  _refreshDictationPreviewAux(options = {}) {
    const el = document.getElementById('chillicollectivo-voice-overlay');
    if (!el?.classList.contains('chillicollectivo-voice-overlay-visible')) return;
    const insertHint = String(options.insertHint || this.fieldManager?._getPreviewInsertHint?.() || '').trim();
    const charCountLabel = String(
      options.charCountLabel
      ?? this.fieldManager?._getPreviewCharCountLabel?.('', this.fieldManager?.previewCommitted || '')
      ?? '',
    ).trim();
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-interim', '', 'interim');
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-enter-hint', insertHint, 'hint');
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-charcount', charCountLabel, 'charcount');
    this._lastRenderedInterim = '';
    this._lastRenderedInsertHint = insertHint;
    this._lastRenderedCharCount = charCountLabel;
  }

  syncPreviewCommittedFromDom() {
    const committedEl = this._getCommittedPreviewEl();
    if (!committedEl || !this.fieldManager) return;
    const fromDom = committedEl.innerText.replace(/\u00A0/g, ' ').replace(/\s+/g, ' ').trim();
    const fromState = String(this.fieldManager.previewCommitted || '').replace(/\s+/g, ' ').trim();
    // DOM kann hinter previewCommitted zurückliegen (stale _previewDom) — nie kürzer überschreiben.
    const text = fromDom.length >= fromState.length ? fromDom : fromState;
    const prevCommitted = this._lastRenderedCommitted || this.fieldManager?.previewCommitted || '';
    const normalized = typeof chillicollectivoNormalizeAgentSpeechText === 'function'
      ? chillicollectivoNormalizeAgentSpeechText(text)
      : text.replace(/ß/g, 'ss');
    const collapsed = typeof this.fieldManager?._collapsePreviewPhraseStack === 'function'
      ? this.fieldManager._collapsePreviewPhraseStack(normalized)
      : normalized;
    this.fieldManager.previewCommitted = collapsed;
    this._lastRenderedCommitted = collapsed;
    if (!normalized) {
      const fm = this.fieldManager;
      const hadCommitted = !!String(prevCommitted || '').trim();
      const flushGuardActive = fm?._flushedPreviewNorm
        && fm._flushedPreviewAt
        && Date.now() - fm._flushedPreviewAt < 15000;
      if (!flushGuardActive && hadCommitted) {
        fm?._resetPreviewRedoState?.('preview-sync-empty');
      } else if (!flushGuardActive && !hadCommitted) {
        // Interim-only oder leere Vorschau – kein Redo-Reset nötig
      } else {
        chillicollectivoVoiceDiag('info', 'voice.preview-sync-empty', 'Leere Vorschau-Sync – Flush-Schutz bleibt aktiv', {
          flushGuardActive: true,
          flushedPreviewAgeMs: fm._flushedPreviewAt ? Date.now() - fm._flushedPreviewAt : null,
          redoResetSkipped: true,
        }, fm);
      }
    }
    this.fieldManager?._refreshDictationPreviewCharCount?.();
  }

  _clearCommittedPreviewDisplay() {
    const hadCommitted = !!(this.fieldManager?.previewCommitted || this._lastRenderedCommitted);
    const hadInterim = !!(this.fieldManager?._currentInterimText || this._lastRenderedInterim);
    const committedSnippet = String(this._lastRenderedCommitted || this.fieldManager?.previewCommitted || '').slice(0, 100);
    const el = document.getElementById('chillicollectivo-voice-overlay');
    const committedEl = this._getCommittedPreviewEl();
    if (committedEl) committedEl.textContent = '';
    if (el) {
      const interimEl = el.querySelector('.chillicollectivo-voice-overlay-interim');
      if (interimEl) interimEl.textContent = '';
      el.classList.remove(
        'chillicollectivo-voice-overlay-send-glow',
        'chillicollectivo-voice-overlay-send-glow-fade',
        'chillicollectivo-voice-overlay-live-broil',
      );
    }
    this._lastRenderedCommitted = '';
    this._lastRenderedInterim = '';
    this._previewCommittedSelectionRange = null;
    this._previewSelectionOffsets = null;
    if (this.fieldManager) {
      this.fieldManager.previewCommitted = '';
      this.fieldManager._currentInterimText = '';
    }
    if (hadCommitted || hadInterim) {
      this.fieldManager?._logPreviewFlow?.('warn', 'voice.preview-display-cleared', 'Grüne Vorschau im DOM geleert', {
        hadCommitted,
        hadInterim,
        committedSnippet,
        caller: 'clearCommittedPreviewDisplay',
      });
    }
  }

  _insertTextToCommittedPreview(addition) {
    const clean = String(addition || '').replace(/\s+/g, ' ').trim();
    if (!clean) return false;
    const committedEl = this._getCommittedPreviewEl();
    if (!committedEl) return false;

    const replacementRange = this._resolvePreviewReplacementRange(committedEl);
    if (replacementRange) {
      let markedText = '';
      try {
        markedText = String(replacementRange.toString() || '').replace(/\u00A0/g, ' ').replace(/\s+/g, ' ').trim();
      } catch (_) {}
      try {
        replacementRange.deleteContents();
        const textNode = document.createTextNode(clean);
        replacementRange.insertNode(textNode);
        replacementRange.setStartAfter(textNode);
        replacementRange.collapse(true);
        const sel = window.getSelection();
        sel?.removeAllRanges();
        sel?.addRange(replacementRange);
        this.syncPreviewCommittedFromDom();
        const previewAfter = this._snapshotPreviewCommitted(committedEl);
        chillicollectivoVoiceDiag('info', 'voice.preview-dom-replace', 'Markierter Vorschau-Text durch Diktat ersetzt', {
          markedText,
          markedTextSnippet: markedText.slice(0, 160),
          dictationText: clean,
          dictationTextSnippet: clean.slice(0, 160),
          previewAfter: previewAfter.slice(0, 300),
          previewAfterLen: previewAfter.length,
          selectionOffsets: this._previewSelectionOffsets,
        }, this.fieldManager);
      } catch (err) {
        chillicollectivoVoiceDiag('warn', 'voice.preview-dom-replace-fallback', 'DOM-Ersetzung fehlgeschlagen – Text angehängt', {
          text: clean.slice(0, 100),
          error: String(err?.message || err || ''),
        }, this.fieldManager);
        this._appendTextToCommittedPreviewEnd(committedEl, clean);
      }
      this._previewCommittedSelectionRange = null;
      this._previewSelectionOffsets = null;
      this.syncPreviewCommittedFromDom();
      return true;
    }

    const previewBefore = this._snapshotPreviewCommitted(committedEl);
    this._appendTextToCommittedPreviewEnd(committedEl, clean);
    const previewAfter = this._snapshotPreviewCommitted(committedEl);
    chillicollectivoVoiceDiag('info', 'voice.preview-dom-append', 'Text an Vorschau-Ende angehängt (keine Markierung)', {
      dictationText: clean,
      dictationTextSnippet: clean.slice(0, 160),
      previewBefore: previewBefore.slice(0, 300),
      previewAfter: previewAfter.slice(0, 300),
    }, this.fieldManager);
    this.syncPreviewCommittedFromDom();
    return true;
  }

  _appendTextToCommittedPreview(addition) {
    this._insertTextToCommittedPreview(addition);
  }

  _bindEditablePreviewEvents(el) {
    if (!el || el.dataset.ccEditableBound === '1') return;
    el.dataset.ccEditableBound = '1';
    if (!this._previewSelectionBound) {
      this._previewSelectionBound = true;
      document.addEventListener('selectionchange', () => {
        if (this._previewDictationMutating) return;
        this._capturePreviewCommittedSelection();
        if (this._previewMouseSelecting || this._isPreviewBeingEdited()) return;
        if (this._previewReplaceSpanTimer) clearTimeout(this._previewReplaceSpanTimer);
        this._previewReplaceSpanTimer = setTimeout(() => {
          this._previewReplaceSpanTimer = null;
          if (!this._previewMouseSelecting && !this._isPreviewBeingEdited()) {
            this.fieldManager?._capturePreviewReplaceSpan?.();
          }
        }, 80);
      });
    }
    el.addEventListener('mousedown', (e) => {
      if (e.target.closest('.chillicollectivo-voice-overlay-committed[contenteditable="true"]')) {
        this._previewMouseSelecting = true;
        const committedEl = this._getCommittedPreviewEl();
        this._previewEditBaseline = this._snapshotPreviewCommitted(committedEl);
        this._logPreviewEdit('mouse-down', 'Vorschau-Markierung gestartet (Mousedown)', {
          previewBefore: this._previewEditBaseline.slice(0, 300),
          previewLen: this._previewEditBaseline.length,
        });
      }
    }, true);
    el.addEventListener('mouseup', (e) => {
      if (!e.target.closest('.chillicollectivo-voice-overlay-committed[contenteditable="true"]')) return;
      this._previewMouseSelecting = false;
      window.setTimeout(() => {
        this._capturePreviewCommittedSelection();
        this.fieldManager?._capturePreviewReplaceSpan?.('preview-mouseup');
      }, 0);
    }, true);
    el.addEventListener('focusin', (e) => {
      if (e.target.closest('.chillicollectivo-voice-overlay-committed[contenteditable="true"]')) {
        if (this._previewDictationFocus) {
          this._previewDictationFocus = false;
          return;
        }
        const committedEl = this._getCommittedPreviewEl();
        if (!this._previewEditBaseline) {
          this._previewEditBaseline = this._snapshotPreviewCommitted(committedEl);
        }
        this._logPreviewEdit('focus-in', 'Vorschau-Fokus im committed-Bereich', {
          previewBefore: this._previewEditBaseline.slice(0, 300),
          previewLen: this._previewEditBaseline.length,
        });
      }
    });
    el.addEventListener('focusout', () => {
      const committedEl = this._getCommittedPreviewEl();
      const previewBefore = this._previewEditBaseline || '';
      this.syncPreviewCommittedFromDom();
      window.setTimeout(() => {
        const previewAfter = this._snapshotPreviewCommitted(committedEl);
        if (previewBefore && previewBefore !== previewAfter) {
          const removedSnippet = this._previewEditRemovedSnippet(previewBefore, previewAfter);
          const addedSnippet = this._previewEditAddedSnippet(previewBefore, previewAfter);
          const manualFix = !!(removedSnippet || addedSnippet);
          chillicollectivoVoiceDiag(
            manualFix ? 'warn' : 'info',
            'voice.preview-edit-text-changed',
            manualFix ? 'STT manuell korrigiert (Vorschau bearbeitet)' : 'Vorschau manuell bearbeitet',
            {
              previewBefore: previewBefore.slice(0, 500),
              previewAfter: previewAfter.slice(0, 500),
              beforeLen: previewBefore.length,
              afterLen: previewAfter.length,
              removedSnippet,
              addedSnippet,
              manualSttFix: manualFix,
            },
            this.fieldManager,
          );
        }
        this._previewEditBaseline = '';
        if (!this._isPreviewBeingEdited()) {
          this._previewUserEditing = false;
          this._logPreviewEdit('focus-out', 'Vorschau-Bearbeitung beendet', {
            previewAfter: previewAfter.slice(0, 300),
            previewLen: previewAfter.length,
          });
        }
      }, 0);
    });
    el.addEventListener('input', (e) => {
      if (e.target.closest('.chillicollectivo-voice-overlay-committed[contenteditable="true"]')) {
        this._previewUserEditing = true;
        this.syncPreviewCommittedFromDom();
        window.setTimeout(() => {
          this._capturePreviewCommittedSelection();
          const offsets = this._clampPreviewSelectionOffsets(this._getCommittedPreviewEl(), this._previewSelectionOffsets);
          if (offsets) {
            this.fieldManager?._capturePreviewReplaceSpan?.('preview-edit-selection');
          } else {
            this.fieldManager?._clearDictationReplaceSpan?.('preview');
          }
        }, 0);
      }
    });
    el.addEventListener('mouseup', (e) => {
      if (e.target.closest('.chillicollectivo-voice-overlay-committed[contenteditable="true"]')) {
        window.setTimeout(() => this._capturePreviewCommittedSelection(), 0);
      }
    });
    el.addEventListener('keyup', (e) => {
      if (e.target.closest('.chillicollectivo-voice-overlay-committed[contenteditable="true"]')) {
        this._capturePreviewCommittedSelection();
      }
    });
    el.addEventListener('keydown', (e) => {
      if (!e.target.closest('.chillicollectivo-voice-overlay-committed[contenteditable="true"]')) return;
      if (this.fieldManager?.currentState !== 'recording') return;
      if (!this.fieldManager?._canUsePreviewInsertHotkey?.()) return;
      if (!this.fieldManager?._previewInsertHotkeyMatchesEvent?.(e)) return;
      e.preventDefault();
      e.stopPropagation();
      this.syncPreviewCommittedFromDom();
      this.fieldManager._flushPreviewToChat();
    }, true);
  }

  _ensureEditablePreviewStructure(el) {
    let body = el.querySelector('.chillicollectivo-voice-preview-body');
    if (body) {
      return this._ensurePreviewDomCache(el).committedEl;
    }
    const content = this._getPreviewContentEl(el);
    if (!content) return null;
    content.innerHTML = '';
    body = document.createElement('div');
    body.className = 'chillicollectivo-voice-preview-body';
    const committedEl = document.createElement('div');
    committedEl.className = 'chillicollectivo-voice-overlay-committed';
    committedEl.setAttribute('contenteditable', 'true');
    committedEl.setAttribute('spellcheck', 'false');
    committedEl.setAttribute('role', 'textbox');
    committedEl.setAttribute('aria-label', 'Diktat-Vorschau bearbeiten');
    body.appendChild(committedEl);
    content.appendChild(body);
    this._previewDom = { el, committedEl, interimEl: null, badgeEl: null, hintEl: null, charCountEl: null };
    return committedEl;
  }

  _showEditableDictationPreview(el, interimText, committedText, options = {}) {
    if (this._updateEditableDictationPreviewInPlace(el, interimText, committedText, options)) {
      if (!el.classList.contains('chillicollectivo-voice-overlay-visible')) {
        el.style.display = 'block';
        el.classList.add('chillicollectivo-voice-overlay-visible');
      }
      return;
    }

    const committed = String(committedText || '').trim();
    const interim = String(interimText || '').trim();
    const sendwordBadge = String(options.sendwordBadge || '').trim();
    const insertHint = String(options.insertHint || this.fieldManager?._getPreviewInsertHint?.() || '').trim();
    const charCountLabel = String(
      options.charCountLabel
      ?? this.fieldManager?._getPreviewCharCountLabel?.(interimText, committedText)
      ?? '',
    ).trim();
    const hasPreviewReplace = !!this.fieldManager?._hasDictationReplaceSpan?.('preview');
    const deferDomMerge = this._shouldDeferEditablePreviewDomMerge(hasPreviewReplace);
    const liveDisplay = this._resolveEditablePreviewLiveDisplay(committed, interim, hasPreviewReplace);

    el.classList.add('chillicollectivo-voice-overlay-editable');
    this._clearPreviewCrumble(el);
    const committedEl = this._ensureEditablePreviewStructure(el);

    if (!deferDomMerge) {
      committedEl.textContent = liveDisplay;
    }

    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-interim', deferDomMerge && !hasPreviewReplace ? interim : '', 'interim');
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-sendword', sendwordBadge, 'badge');
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-enter-hint', insertHint, 'hint');
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-charcount', charCountLabel, 'charcount');

    this._previewSendwordBadge = sendwordBadge;
    this._previewVisible = true;
    this._lastRenderedInterim = interim;
    this._lastRenderedCommitted = deferDomMerge ? committed : liveDisplay;
    this._lastRenderedSendwordBadge = sendwordBadge;
    this._lastRenderedInsertHint = insertHint;
    this._lastRenderedCharCount = charCountLabel;
    this._applyPreviewAppearanceVars(el);
    el.classList.add('chillicollectivo-voice-overlay-glow');
    el.style.setProperty('--chillicollectivo-voice-level', '0');
    el.style.setProperty('--chillicollectivo-text-shake', '0');
    el.classList.remove('chillicollectivo-voice-overlay-hot', 'chillicollectivo-voice-overlay-broil', 'chillicollectivo-voice-overlay-broil-heavy');
    el.style.display = 'block';
    void el.offsetWidth;
    el.classList.add('chillicollectivo-voice-overlay-visible');
    requestAnimationFrame(() => this._applyPreviewTypography(el));
  }

  _clearPreviewCrumble(el) {
    if (!el) return;
    if (this._borderSweepTimer) {
      clearTimeout(this._borderSweepTimer);
      this._borderSweepTimer = null;
    }
    el.classList.remove(
      'chillicollectivo-voice-overlay-send-crumble',
      'chillicollectivo-voice-overlay-send-glow',
      'chillicollectivo-voice-overlay-send-glow-hold',
      'chillicollectivo-voice-overlay-send-glow-fade',
      'chillicollectivo-voice-overlay-send-border-pulse',
    );
    el.querySelectorAll('.chillicollectivo-voice-overlay-shard, .chillicollectivo-voice-overlay-crack, .chillicollectivo-voice-overlay-border-snake').forEach((node) => node.remove());
  }

  _playPreviewSendBorderSweep(el) {
    if (!el) return;
    el.querySelectorAll('.chillicollectivo-voice-overlay-border-snake').forEach((node) => node.remove());
    if (this._borderSweepTimer) {
      clearTimeout(this._borderSweepTimer);
      this._borderSweepTimer = null;
    }

    const rect = el.getBoundingClientRect();
    const w = Math.max(1, Math.round(rect.width));
    const h = Math.max(1, Math.round(rect.height));
    const pad = 3;
    const strokeW = 2.5;
    const inset = pad + strokeW / 2;
    const rx = 16;
    const filterId = `cc-border-snake-glow-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
    const durationMs = 720;

    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('class', 'chillicollectivo-voice-overlay-border-snake');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('viewBox', `0 0 ${w + pad * 2} ${h + pad * 2}`);
    svg.style.left = `${-pad}px`;
    svg.style.top = `${-pad}px`;
    svg.style.width = `${w + pad * 2}px`;
    svg.style.height = `${h + pad * 2}px`;

    const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
    const filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
    filter.setAttribute('id', filterId);
    filter.setAttribute('x', '-40%');
    filter.setAttribute('y', '-40%');
    filter.setAttribute('width', '180%');
    filter.setAttribute('height', '180%');
    filter.innerHTML = `
      <feGaussianBlur in="SourceGraphic" stdDeviation="3.6" result="blur" />
      <feColorMatrix in="blur" type="matrix"
        values="1 0 0 0 0
                0.92 0.78 0 0 0
                0 0 0 0 0
                0 0 0 1 0" result="yellowBlur" />
      <feMerge>
        <feMergeNode in="yellowBlur" />
        <feMergeNode in="SourceGraphic" />
      </feMerge>
    `;
    defs.appendChild(filter);
    svg.appendChild(defs);

    const track = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
    track.setAttribute('x', String(inset));
    track.setAttribute('y', String(inset));
    track.setAttribute('width', String(Math.max(0, w + pad * 2 - inset * 2)));
    track.setAttribute('height', String(Math.max(0, h + pad * 2 - inset * 2)));
    track.setAttribute('rx', String(rx));
    track.setAttribute('ry', String(rx));
    track.setAttribute('pathLength', '100');
    track.setAttribute('fill', 'none');
    track.setAttribute('stroke', 'rgba(254, 240, 138, 0.22)');
    track.setAttribute('stroke-width', String(strokeW));
    track.setAttribute('stroke-linecap', 'butt');
    track.setAttribute('stroke-linejoin', 'miter');
    svg.appendChild(track);

    const snake = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
    snake.setAttribute('x', String(inset));
    snake.setAttribute('y', String(inset));
    snake.setAttribute('width', String(Math.max(0, w + pad * 2 - inset * 2)));
    snake.setAttribute('height', String(Math.max(0, h + pad * 2 - inset * 2)));
    snake.setAttribute('rx', String(rx));
    snake.setAttribute('ry', String(rx));
    snake.setAttribute('pathLength', '100');
    snake.setAttribute('fill', 'none');
    snake.setAttribute('stroke', '#fef08a');
    snake.setAttribute('stroke-width', String(strokeW));
    snake.setAttribute('stroke-linecap', 'butt');
    snake.setAttribute('stroke-linejoin', 'miter');
    snake.setAttribute('stroke-dasharray', '9 91');
    snake.setAttribute('stroke-dashoffset', '0');
    snake.setAttribute('filter', `url(#${filterId})`);
    snake.style.animation = `chillicollectivo-preview-border-snake-run ${durationMs}ms linear 1 forwards`;
    svg.appendChild(snake);

    el.appendChild(svg);
    void snake.getBoundingClientRect();

    el.classList.remove('chillicollectivo-voice-overlay-send-border-pulse');
    void el.offsetWidth;
    el.classList.add('chillicollectivo-voice-overlay-send-border-pulse');

    this._borderSweepTimer = window.setTimeout(() => {
      svg.remove();
      el.classList.remove('chillicollectivo-voice-overlay-send-border-pulse');
      this._borderSweepTimer = null;
    }, durationMs + 40);
  }

  _updatePreviewCharCount(label) {
    const value = String(label || '').trim();
    const el = document.getElementById('chillicollectivo-voice-overlay');
    if (!el?.classList.contains('chillicollectivo-voice-overlay-visible')) return;
    this._setPreviewAuxText(el, 'chillicollectivo-voice-overlay-charcount', value, 'charcount');
    this._lastRenderedCharCount = value;
  }

  showPreview(interimText, committedText = '', options = {}) {
    if (!options.forceShow && !this.effects.previewEnabled) {
      if (!this._isOutPreviewOverlayActive()) this.hidePreview();
      return;
    }
    if (this._isCrumblingPreview) return;
    const sendwordBadge = String(options.sendwordBadge || '').trim();
    const hasContent = (committedText || '').trim() || (interimText || '').trim() || sendwordBadge;
    if (!hasContent) {
      if (this.fieldManager?._isDirectAppendRecordingSession?.()) {
        this.fieldManager._resetDirectModeLivePreview();
        return;
      }
      if (this.fieldManager?._shouldKeepDictationPreviewOpen?.()) {
        this.fieldManager._logPreviewFlow('warn', 'voice.preview-empty-update-blocked', 'Leeres Vorschau-Update während Diktat blockiert – Text bleibt sichtbar', {
          trigger: options.dictation ? 'dictation' : 'preview',
        });
        return;
      }
      this.fieldManager?._logPreviewFlow?.('warn', 'voice.preview-hide-empty-content', 'Vorschau wird geschlossen – kein Inhalt im Update', {
        trigger: options.dictation ? 'dictation' : 'preview',
      });
      this.hidePreview();
      return;
    }

    if (options.dictation && this.effects.previewEnabled && this._isPreviewEditableDictation(options)) {
      const el = this._getOrCreateOverlay();
      this._showEditableDictationPreview(el, interimText, committedText, options);
      return;
    }

    const el = this._getOrCreateOverlay();
    if (this._isPreviewEditableDictation(options)) {
      this._showEditableDictationPreview(el, interimText, committedText, options);
      return;
    }

    this._previewVisible = true;
    this._previewSendwordBadge = sendwordBadge;
    this._previewUserEditing = false;

    this._clearPreviewCrumble(el);
    this._applyPreviewAppearanceVars(el);
    if (options.unifiedDisplay) {
      el.classList.add('chillicollectivo-voice-overlay-dictation-unified');
    } else {
      el.classList.remove('chillicollectivo-voice-overlay-dictation-unified');
    }
    el.classList.remove('chillicollectivo-voice-overlay-editable');
    this._clearPreviewContent(el);
    this._invalidatePreviewDomCache();
    const content = this._getPreviewContentEl(el);
    if (!content) return;
    const shouldFormatPreview = !!options.dictation
      || this.fieldManager?.currentState === 'recording';
    const formatPreviewText = shouldFormatPreview
      ? (value) => this._formatDictationPreviewText(value)
      : (value) => String(value || '');
    if (options.unifiedDisplay) {
      const unified = formatPreviewText((committedText || interimText || '').trim());
      if (unified) {
        const span = document.createElement('span');
        span.className = 'chillicollectivo-voice-overlay-committed';
        span.textContent = unified;
        content.appendChild(span);
      }
    } else if (committedText && committedText.trim()) {
      const span = document.createElement('span');
      span.className = 'chillicollectivo-voice-overlay-committed';
      span.textContent = formatPreviewText(committedText).trimEnd() + ' ';
      content.appendChild(span);
    }
    if (interimText && interimText.trim() && !options.unifiedDisplay) {
      const span = document.createElement('span');
      span.className = 'chillicollectivo-voice-overlay-interim';
      span.textContent = formatPreviewText(interimText);
      content.appendChild(span);
    }
    if (sendwordBadge) {
      const badge = document.createElement('span');
      badge.className = 'chillicollectivo-voice-overlay-sendword';
      badge.textContent = sendwordBadge;
      content.appendChild(badge);
    }
    const insertHint = String(options.insertHint || '').trim();
    if (insertHint) {
      const hint = document.createElement('span');
      hint.className = 'chillicollectivo-voice-overlay-enter-hint';
      hint.textContent = insertHint;
      content.appendChild(hint);
    }
    el.classList.add('chillicollectivo-voice-overlay-glow');
    this._applyEffectIntensityVars(el);
    el.style.setProperty('--chillicollectivo-voice-level', '0');
    el.style.setProperty('--chillicollectivo-text-shake', '0');
    el.classList.remove('chillicollectivo-voice-overlay-hot', 'chillicollectivo-voice-overlay-broil', 'chillicollectivo-voice-overlay-broil-heavy');
    el.style.display = 'block';
    void el.offsetWidth;
    el.classList.add('chillicollectivo-voice-overlay-visible');
    requestAnimationFrame(() => this._applyPreviewTypography(el));
  }

  _applySentOutPreviewGlow({ borderSweep = true } = {}) {
    const el = document.getElementById('chillicollectivo-voice-overlay');
    if (!el) return;
    el.classList.add(
      'chillicollectivo-voice-overlay-send-glow',
      'chillicollectivo-voice-overlay-send-glow-hold',
    );
    if (borderSweep && el.dataset.chillicollectivoSendGlowArmed !== '1') {
      el.dataset.chillicollectivoSendGlowArmed = '1';
      this._playPreviewSendBorderSweep(el);
    }
  }

  _updatePreviewTextInPlace(interimText, committedText = '', options = {}) {
    const el = document.getElementById('chillicollectivo-voice-overlay');
    if (!el || !el.classList.contains('chillicollectivo-voice-overlay-visible')) return false;

    const interim = String(interimText || '').trim();
    const committed = String(committedText || '').trim();
    const sendwordBadge = String(options.sendwordBadge || '').trim();
    let committedEl = el.querySelector('.chillicollectivo-voice-overlay-committed');
    let interimEl = el.querySelector('.chillicollectivo-voice-overlay-interim');
    let badgeEl = el.querySelector('.chillicollectivo-voice-overlay-sendword');

    if (committed) {
      if (!committedEl) {
        committedEl = document.createElement('span');
        committedEl.className = 'chillicollectivo-voice-overlay-committed';
        const content = this._getPreviewContentEl(el) || el;
        content.insertBefore(committedEl, content.firstChild);
      }
      if (committedEl.textContent !== `${committed} `) committedEl.textContent = `${committed} `;
    } else if (committedEl) {
      committedEl.remove();
    }

    if (interim) {
      if (!interimEl) {
        interimEl = document.createElement('span');
        interimEl.className = 'chillicollectivo-voice-overlay-interim';
        el.appendChild(interimEl);
      }
      if (interimEl.textContent !== interim) interimEl.textContent = interim;
    } else if (interimEl) {
      interimEl.remove();
    }

    if (sendwordBadge) {
      if (!badgeEl) {
        badgeEl = document.createElement('span');
        badgeEl.className = 'chillicollectivo-voice-overlay-sendword';
        el.appendChild(badgeEl);
      }
      if (badgeEl.textContent !== sendwordBadge) badgeEl.textContent = sendwordBadge;
    } else if (badgeEl) {
      badgeEl.remove();
    }

    this._previewSendwordBadge = sendwordBadge;
    return true;
  }

  _isOutPreviewOverlayActive() {
    const el = document.getElementById('chillicollectivo-voice-overlay');
    return !!el?.classList.contains('chillicollectivo-voice-overlay-send-glow-hold')
      || !!this.fieldManager?._outPreviewWaitTimer;
  }

  crumblePreviewAndHide(duration = 920) {
    this.fieldManager?._unpinDictationPreview?.();
    const el = document.getElementById('chillicollectivo-voice-overlay');
    if (!el || !el.classList.contains('chillicollectivo-voice-overlay-visible')) {
      this.hidePreview({ force: true, allowHideOutPreview: true });
      return;
    }

    this._previewVisible = false;
    this.resetLiveBroil();
    this._isCrumblingPreview = false;
    this._normalizePreviewOpacity();
    const previewOpacity = (this.effects.previewOpacity || 100) / 100;
    this._applyPreviewAppearanceVars(el);
    el.style.setProperty('--chillicollectivo-preview-opacity', String(previewOpacity));
    this._clearPreviewCrumble(el);

    el.style.display = 'block';
    el.classList.remove(
      'chillicollectivo-voice-overlay-send-glow-hold',
      'chillicollectivo-voice-overlay-send-border-pulse',
    );
    el.classList.add(
      'chillicollectivo-voice-overlay-visible',
      'chillicollectivo-voice-overlay-send-glow',
      'chillicollectivo-voice-overlay-send-glow-fade',
    );
    void el.offsetWidth;
    window.setTimeout(() => this.hidePreview({ force: true, allowHideOutPreview: true }), CHILLICOLLECTIVO_OUT_PREVIEW_FADE_MS);
  }

  hidePreview(options = {}) {
    const force = options === true || options?.force === true;
    const allowHideOutPreview = options === true ? false : !!options?.allowHideOutPreview;
    if (!allowHideOutPreview && this._isOutPreviewOverlayActive()) return;
    const keepOpen = !force && this.fieldManager?._shouldKeepDictationPreviewOpen?.();
    if (keepOpen) {
      this.fieldManager?._logPreviewFlow?.('warn', 'voice.preview-hide-blocked', 'Vorschau-Schliessen während Diktat blockiert', {
        force,
        caller: options?.reason || 'unspecified',
      });
      return;
    }
    if (!force && this.fieldManager?.currentState === 'recording') {
      const flow = this.fieldManager._previewFlowState?.() || {};
      if (flow.visibleDomLen || flow.committedLen || flow.interimLen || flow.renderedLen) {
        this.fieldManager._logPreviewFlow('warn', 'voice.preview-hide-during-recording', 'Vorschau-Schliessen während Aufnahme mit sichtbarem Text', {
          force,
          caller: options?.reason || 'unspecified',
        });
      }
    }
    if (this._isCrumblingPreview) return;
    this._previewVisible = false;
    this._previewUserEditing = false;
    if (this._previewUpdateRAF) {
      cancelAnimationFrame(this._previewUpdateRAF);
      this._previewUpdateRAF = null;
    }
    this._pendingPreviewUpdate = null;
    this._invalidatePreviewDomCache();
    this.resetLiveBroil();
    const el = document.getElementById('chillicollectivo-voice-overlay');
    if (!el) return;
    this._clearPreviewCrumble(el);
    el.classList.remove('chillicollectivo-voice-overlay-visible');
    const onEnd = () => {
      if (!el.classList.contains('chillicollectivo-voice-overlay-visible')) {
        el.style.display = 'none';
        this._clearPreviewContent(el);
      }
      el.removeEventListener('transitionend', onEnd);
    };
    el.addEventListener('transitionend', onEnd, { once: true });
    setTimeout(() => {
      if (!el.classList.contains('chillicollectivo-voice-overlay-visible')) {
        el.style.display = 'none';
        this._clearPreviewContent(el);
      }
    }, 300);
  }
}

class CHILLICOLLECTIVOVoiceFieldManager {
  constructor() {
    this.currentState = 'ready';
    this.errorMessage = '';
    this.activeField = null;
    this.recordingStartPosition = null;
    this.recordingSelectionEnd = null;
    this._dictationReplaceSpan = null;
    this.recordingField = null;
    this._recordingConversationRoute = '';
    this._pendingField = null;
    this._userTargetField = null;
    this._mouseoverField = null;  // Textfeld, über dem die Maus gerade schwebt
    this._pendingInsertRetry = null;

    this._licenseAccessGranted = false;
    this.fab = null;
    this.browserRecognition = null;
    this.browserListening = false;
    this.browserTabId = null;
    this.browserVoiceConfig = {};
    this.browserCorrectionRules = [];
    this.browserSmileyRules = [];
    this.previewCommitted = '';
    this._dictationPreviewPinned = false;
    this._discardedPreviewNorm = '';
    this._discardedPreviewRoute = '';
    this._flushedPreviewNorm = '';
    this._flushedPreviewAt = 0;
    this._previewFlushInFlight = false;
    this._pendingFieldClickFlushTimer = null;
    this._continuingDictationAfterFlushUntil = 0;
    this._postFlushIgnoreTimer = null;
    this._sessionDictationChars = 0;
    this._submitInFlightUntil = 0;
    this._lastSubmitAt = 0;
    this._lastSubmitBodyNorm = '';
    this._SUBMIT_COOLDOWN_MS = CHILLICOLLECTIVO_POST_SEND_COOLDOWN_MS;
    this._sentDuplicateGuardByRoute = new Map();
    this._globalSentByBodyNorm = new Map();
    this._lastInsertedByRoute = new Map();
    this._pendingFirstSendNorm = '';
    this._pendingFirstSendPlain = '';
    this._armedSubmitRoute = '';
    this._dialogEnteredAtByRoute = new Map();
    this._lastConversationRoute = '';
    this.browserMicStream = null;
    this._micWarmReleaseTimer = null;
    this._recognitionRestartTimer = null;
    this._lastFinalSentText = '';
    this._lastFinalSentAt = 0;
    this._lastDuplicateMemoryCheckAt = 0;
    this._lastCommittedInsertNorm = '';
    this._lastCommittedInsertAt = 0;
    this._voiceCommandSuppressUntil = 0;
    this._lastHardSendRoute = '';
    this._submitInFlightPromise = null;
    this._BROWSER_SILENCE_MS = 1650;
    this._INTERIM_DROP_COMMIT_MS = 650;
    this._MIC_WARM_IDLE_MS = 5 * 60 * 1000;
    this._FINAL_DEDUPE_MS = 4000;
    this._REC_RESTART_DELAY_MS = 0;
    this._REC_SESSION_MAX_MS = 48000;
    this._SILENCE_KEEPALIVE_MS = 1200;
    this._REC_STALE_MS = 9000;
    this._stickyInterimText = '';
    this._stickyInterimCommitTimer = null;
    this._recSessionStartedAt = 0;
    this._recSessionId = 0;
    this._recRapidDeathCount = 0;
    this._recWatchdogTimer = null;
    this._recStartRetryTimer = null;
    this._lastKnownHref = location.href;
    this._routeResetTimer = null;
    this._postSendResetTimer = null;
    this._micRestartAfterSendTimer = null;
    this._MIC_RESTART_OFF_MS = 200;
    this._recordingHealthTimer = null;
    this._RECORDING_HEALTH_MS = 2500;
    this._silenceKeepaliveTimer = null;
    this._lastRecResultAt = 0;
    this._recognitionListening = false;
    this._lastNtbPreviewPhrase = '';
    this._lastNtbPreviewPhraseAt = 0;
    this._mergingPreviewDisplay = false;
    this._buildingVoiceFlowSnapshot = false;
    this._lastFabToggleAt = 0;
    this._pendingVoiceStart = false;
    this._loadingWatchdogTimer = null;
    this._micQualityRefreshAt = 0;
    this._captureStartedAt = 0;
    this._lastPostSendRecoveryAt = 0;
    this._previewEnterToInsert = false;
    this._previewClickToInsert = false;
    this._previewClickFlushToInsert = false;
    this._pendingPreviewClickFlushField = null;
    this._ppAutoInsert = false;
    this._previewInsertHotkey = null;
    this._micTipsEnabled = false;
    this._micTipIndex = 0;
    this._tabEnterHotkey = null;
    this._tabEnterSendArmed = false;
    this._tabEnterSendArmedAt = 0;
    this._profileValueLock = null;
    this._profileUserSelecting = false;
    this._dictationTargetKey = '';
    this._composeFocusGuardUntil = 0;
    this._profileFocusReclaimTimers = [];
    this._lastActivePreviewReplaceSpan = null;
    this._lastActiveFieldReplaceSpan = null;
    this._resetMicDiag();

    this.init();
  }

  _isTorchwoodDictationField(element) {
    if (!this._isUsableTextField(element)) return false;
    if (typeof chillicollectivoClassifyDictationField === 'function') {
      return !!chillicollectivoClassifyDictationField(element);
    }
    if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(element)) return true;
    if (typeof chillicollectivoIsNeueInfoTextarea === 'function' && chillicollectivoIsNeueInfoTextarea(element)) return true;
    if (typeof chillicollectivoIsDialogInfoTextarea === 'function' && chillicollectivoIsDialogInfoTextarea(element)) return true;
    if (element.tagName === 'TEXTAREA' || element.isContentEditable) return true;
    return this.isTextField(element);
  }

  _getFieldTargetKey(field) {
    if (!field) return '';
    if (typeof chillicollectivoClassifyDictationField === 'function') {
      const classified = chillicollectivoClassifyDictationField(field);
      if (classified) {
        if (classified.type === 'compose') {
          return `compose:${this._currentConversationKey() || 'default'}`;
        }
        return classified.area;
      }
    }
    if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)) {
      const meta = typeof chillicollectivoProfileFieldMetaFromElement === 'function'
        ? chillicollectivoProfileFieldMetaFromElement(field)
        : {};
      return `pp:${meta.side || '?'}`;
    }
    if (typeof chillicollectivoIsNeueInfoTextarea === 'function' && chillicollectivoIsNeueInfoTextarea(field)) {
      const side = typeof chillicollectivoProfileSideOf === 'function'
        ? (chillicollectivoProfileSideOf(field) || 'center')
        : 'center';
      return `neue-info:${side}`;
    }
    if (typeof chillicollectivoIsDialogInfoTextarea === 'function' && chillicollectivoIsDialogInfoTextarea(field)) {
      const side = typeof chillicollectivoProfileSideOf === 'function'
        ? (chillicollectivoProfileSideOf(field) || 'center')
        : 'center';
      return `dialog-info:${side}`;
    }
    return `compose:${this._currentConversationKey() || 'default'}`;
  }

  _onDictationTargetChanged(field) {
    if (!field || !this._isTorchwoodDictationField(field)) return;
    const key = this._getFieldTargetKey(field);
    if (!key || key === this._dictationTargetKey) return;
    const previousKey = this._dictationTargetKey;
    this._dictationTargetKey = key;
    this._resetDictationGuardsForTargetChange(previousKey, key, 'dictation-target-change');
    if (!previousKey) return;
    if (this._shouldPreservePreviewOnTargetChange()) {
      this._pinDictationPreview();
      if (this.fab?._isPreviewDomLockedForDictation?.()) {
        this.fab.syncPreviewCommittedFromDom?.();
      }
      chillicollectivoVoiceDiag('info', 'voice.preview-kept-on-target-change', 'Vorschau bleibt beim Feldwechsel sichtbar – Runterhol-Taste nötig', {
        previousKey,
        nextKey: key,
        committedLen: String(this.previewCommitted || '').length,
        interimLen: String(this._currentInterimText || '').length,
      }, this);
    } else if (this._dictationPreviewPinned || this.previewCommitted || this._currentInterimText) {
      this._unpinDictationPreview();
      this.previewCommitted = '';
      this._currentInterimText = '';
      this._lastInterim = '';
      this.fab?.hidePreview?.({ force: true });
    }
    if (this._profileValueLock?.until > Date.now()) {
      const lockField = typeof chillicollectivoResolveProfileLockField === 'function'
        ? chillicollectivoResolveProfileLockField(this._profileValueLock)
        : this._profileValueLock.field;
      const lockKey = this._getFieldTargetKey(lockField);
      const nextKey = this._getFieldTargetKey(field);
      if (lockField && lockKey && nextKey && lockKey !== nextKey) {
        if (typeof chillicollectivoClearProfileDictationLock === 'function') {
          chillicollectivoClearProfileDictationLock(this);
        } else {
          this._profileValueLock = null;
        }
      }
    }
  }

  _normalizePreviewPunctuation(text, options = {}) {
    let raw = String(text || '').replace(/\s+/g, ' ').trim();
    if (!raw) return '';
    if (options.finalize) {
      raw = this._sanitizeSttEchoChains(raw);
    }
    if (typeof chillicollectivoCollapseDuplicatePunctuation === 'function') {
      return chillicollectivoCollapseDuplicatePunctuation(raw);
    }
    return raw.replace(/!\s+!+/g, '!').replace(/!{2,}/g, '!');
  }

  _isPreviewUiEnabled() {
    return this.fab?.effects?.previewEnabled === true;
  }

  _isPreviewEnterToInsertEnabled() {
    return !!this._previewEnterToInsert;
  }

  _isAgentClickToInsertMode() {
    return !!globalThis.__CHILLICOLLECTIVO_AGENTEN_MIC
      && this._isPreviewClickToInsertEnabled()
      && !this._isPreviewEnterToInsertEnabled();
  }

  _isLivePreviewBufferMode() {
    return this._isPreviewEnterToInsertEnabled() || this._isAgentClickToInsertMode();
  }

  _canUsePreviewInsertHotkey() {
    if (this._isPreviewEnterToInsertEnabled()) return true;
    return !!globalThis.__CHILLICOLLECTIVO_AGENTEN_MIC && !this._isDirectFieldAppendMode();
  }

  _isEnterPreviewInsertHotkey() {
    const hk = chillicollectivoGetEffectivePreviewInsertHotkey(this._previewInsertHotkey);
    return hk.key === 'Enter' && !hk.shiftKey && !hk.ctrlKey && !hk.altKey && !hk.metaKey;
  }

  _isDirectFieldAppendMode() {
    if (this._isPreviewEnterToInsertEnabled()) return false;
    if (this._isAgentClickToInsertMode()) return false;
    return true;
  }

  _isDirectAppendRecordingSession() {
    return this._isCaptureActive() && this._isDirectFieldAppendMode();
  }

  _getDictationTextCaseMode() {
    if (globalThis.__CHILLICOLLECTIVO_AGENTEN_MIC) {
      return CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE;
    }
    return this.fab?.effects?.previewTextCase || CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE;
  }

  _formatDictationFieldInsert(text, contextBefore = '', options = {}) {
    if (globalThis.__CHILLICOLLECTIVO_AGENTEN_MIC) {
      return chillicollectivoApplyNtbInsertCase(text, contextBefore, options);
    }
    return chillicollectivoApplyDictationInsertCase(text, contextBefore, {
      mode: this._getDictationTextCaseMode(),
      ...options,
    });
  }

  _isContinuingDictationSession() {
    if (this._isCaptureActive()) return true;
    return !!(this._continuingDictationAfterFlushUntil && Date.now() < this._continuingDictationAfterFlushUntil);
  }

  _fieldHasExplicitUserSelection(field) {
    if (!field || !this._isUsableTextField(field)) return false;
    if (field.isContentEditable) {
      const sel = window.getSelection();
      if (!sel?.rangeCount) return false;
      const range = sel.getRangeAt(0);
      return this._isRangeInsideField(range, field) && !range.collapsed;
    }
    const start = field.selectionStart ?? 0;
    const end = field.selectionEnd ?? start;
    return start !== end;
  }

  _syncDictationCursorFromField(field) {
    if (!field || !this._isUsableTextField(field)) return false;
    if (field.isContentEditable) {
      const sel = window.getSelection();
      if (!sel?.rangeCount) return false;
      const range = sel.getRangeAt(0);
      if (!this._isRangeInsideField(range, field)) return false;
      this.recordingStartPosition = range.cloneRange();
      this.recordingSelectionEnd = range.collapsed ? null : range.cloneRange();
      return true;
    }
    const start = field.selectionStart ?? field.value?.length ?? 0;
    const end = field.selectionEnd ?? start;
    this.recordingStartPosition = start;
    this.recordingSelectionEnd = end;
    return true;
  }

  _getFieldCaretOffset(field) {
    if (!field || !this._isUsableTextField(field)) return 0;
    if (field.isContentEditable) {
      const sel = window.getSelection();
      if (sel?.rangeCount) {
        const range = sel.getRangeAt(0);
        if (this._isRangeInsideField(range, field)) {
          const offsets = this._rangeTextOffsets(field, range);
          if (offsets) return offsets.start;
        }
      }
      if (this.recordingStartPosition?.cloneRange) {
        const offsets = this._rangeTextOffsets(field, this.recordingStartPosition);
        if (offsets) return offsets.start;
      }
      return String(field.textContent || '').length;
    }
    if (typeof this.recordingStartPosition === 'number') {
      return Math.min(Math.max(0, this.recordingStartPosition), (field.value || '').length);
    }
    return field.selectionStart ?? field.value?.length ?? 0;
  }

  _isDictationCursorAtFieldEnd(field) {
    if (!field || !this._isUsableTextField(field)) return true;
    if (this._fieldHasExplicitUserSelection(field)) return false;
    const plain = field.isContentEditable ? String(field.textContent || '') : String(field.value || '');
    const len = plain.length;
    if (!len) return true;
    const caret = this._getFieldCaretOffset(field);
    return caret >= len - 1;
  }

  _shouldUseFieldReplaceSpanForInsert(field) {
    if (!field) return false;
    if (this._hasDictationReplaceSpan('field') && this._dictationReplaceSpan?.field === field) {
      const span = this._dictationReplaceSpan;
      return (span.anchorEnd ?? 0) > (span.anchorStart ?? 0);
    }
    const saved = this._lastActiveFieldReplaceSpan;
    if (saved?.target !== 'field') return false;
    if (Date.now() - (saved.capturedAt || 0) > 30000) return false;
    const fieldKey = this._getFieldTargetKey(field);
    if (saved.field && saved.field !== field && saved.fieldKey && saved.fieldKey !== fieldKey) return false;
    return (saved.anchorEnd ?? 0) > (saved.anchorStart ?? 0);
  }

  _advanceDictationAppendCursor(field, trigger = '') {
    if (!field || !this._isUsableTextField(field)) return;
    this._clearDictationReplaceSpan('field', { clearSaved: true });
    this._lastActiveFieldReplaceSpan = null;
    try { field.focus({ preventScroll: true }); } catch (_) {}
    if (!field.isContentEditable) {
      const len = (field.value || '').length;
      field.selectionStart = field.selectionEnd = len;
      this.recordingStartPosition = len;
      this.recordingSelectionEnd = len;
    } else {
      const range = document.createRange();
      range.selectNodeContents(field);
      range.collapse(false);
      const sel = window.getSelection();
      sel?.removeAllRanges();
      sel?.addRange(range);
      this.recordingStartPosition = range.cloneRange();
      this.recordingSelectionEnd = null;
    }
    chillicollectivoVoiceDiag('info', 'voice.field-append-cursor', 'Diktat-Cursor ans Feldende gesetzt (Anhängen)', {
      trigger,
      ...this._fieldInsertContext(field),
    }, this);
  }

  _isPreviewClickToInsertEnabled() {
    return !!this._previewClickToInsert;
  }

  _isPreviewClickFlushEnabled() {
    return !!this._previewClickFlushToInsert;
  }

  _hasPendingPreviewText() {
    return [this.previewCommitted, this._currentInterimText]
      .some((part) => String(part || '').trim());
  }

  _shouldPreservePreviewOnTargetChange() {
    if (!this._hasPendingPreviewText()) return false;
    return this._isPreviewEnterToInsertEnabled() || this._isAgentClickToInsertMode();
  }

  _shouldFlushPreviewOnFieldClick() {
    if (this.currentState !== 'recording') return false;
    if (!this._isPreviewClickFlushEnabled()) return false;
    if (!this._hasPendingPreviewText()) return false;
    if (!this._isPreviewEnterToInsertEnabled() && !this._isAgentClickToInsertMode()) return false;
    return true;
  }

  _schedulePreviewFlushOnFieldClick(field) {
    if (!field || !this._shouldFlushPreviewOnFieldClick()) return;
    this._pendingPreviewClickFlushField = field;
    chillicollectivoVoiceDiag('info', 'voice.field-click-armed', 'Klick-Flush vorbereitet (Mousedown)', {
      fieldType: field.isContentEditable ? 'contenteditable' : (field.tagName || 'input'),
      hasPendingPreview: this._hasPendingPreviewText(),
      clickToInsert: this._isAgentClickToInsertMode(),
      ...this._fieldInsertContext(field),
    }, this);
  }

  _tryFlushPreviewOnFieldClick(field) {
    if (!field || field !== this._pendingPreviewClickFlushField) return;
    this._pendingPreviewClickFlushField = null;
    if (!this._shouldFlushPreviewOnFieldClick()) return;
    if (!this.isTextField(field) || this._isVoiceUiElement(field)) return;
    this._setRecordingTarget(field);
    this.activeField = field;
    this.recordingField = field;
    if (this._pendingFieldClickFlushTimer) {
      clearTimeout(this._pendingFieldClickFlushTimer);
      this._pendingFieldClickFlushTimer = null;
    }
    this._pendingFieldClickFlushTimer = setTimeout(() => {
      this._pendingFieldClickFlushTimer = null;
      if (!this._shouldFlushPreviewOnFieldClick()) return;
      if (field.isContentEditable) {
        const sel = window.getSelection();
        if (sel?.rangeCount > 0 && this._isRangeInsideField(sel.getRangeAt(0), field)) {
          this.recordingStartPosition = sel.getRangeAt(0).cloneRange();
          this.recordingSelectionEnd = null;
        }
      } else {
        this.recordingStartPosition = field.selectionStart ?? field.value?.length ?? 0;
        this.recordingSelectionEnd = field.selectionEnd ?? this.recordingStartPosition;
      }
      this._flushPreviewToChat({ trigger: 'field-click' });
    }, 0);
  }

  _isPPAutoInsertEnabled() {
    return !!this._ppAutoInsert;
  }

  _shouldPPDirectInsert() {
    return this._isPPAutoInsertEnabled();
  }

  _isMicTipsEnabled() {
    return this._micTipsEnabled === true;
  }

  _isTabEnterSendEnabled() {
    return !!this._tabEnterHotkey?.key;
  }

  _previewInsertHotkeyMatchesEvent(event) {
    return chillicollectivoPreviewInsertHotkeyMatchesEvent(this._previewInsertHotkey, event);
  }

  _tabEnterHotkeyMatchesEvent(event) {
    return chillicollectivoTabEnterHotkeyMatchesEvent(this._tabEnterHotkey, event);
  }

  _loadPreviewEnterSetting() {
    try {
      chrome.storage.local.get(['chillicollectivoVoiceSettings'], (data) => {
        const settings = data?.chillicollectivoVoiceSettings || {};
        if (globalThis.__CHILLICOLLECTIVO_MIC) {
          this._previewEnterToInsert = false;
          this._previewClickToInsert = settings.previewClickToInsert === true;
          this._previewClickFlushToInsert = settings.previewClickFlushToInsert === true;
          this._ppAutoInsert = false;
          this._previewInsertHotkey = settings.previewInsertHotkey ?? null;
          this._micTipsEnabled = settings.micTipsEnabled === true;
          if (this._micTipsEnabled) {
            this._startMicTipScheduler();
          }
        }
        this._tabEnterHotkey = null;
      });
      chrome.storage.onChanged.addListener((changes, area) => {
        if (area !== 'local') return;
        const next = changes.chillicollectivoVoiceSettings?.newValue;
        if (!next) return;
        if (globalThis.__CHILLICOLLECTIVO_MIC) {
          this._previewEnterToInsert = false;
          if (next.previewClickToInsert !== undefined) {
            this._previewClickToInsert = next.previewClickToInsert === true;
          }
          if (next.previewClickFlushToInsert !== undefined) {
            this._previewClickFlushToInsert = next.previewClickFlushToInsert === true;
          }
          if (next.previewInsertHotkey !== undefined) {
            this._previewInsertHotkey = next.previewInsertHotkey ?? null;
          }
          this._ppAutoInsert = false;
          if (next.micTipsEnabled !== undefined) {
            this._micTipsEnabled = next.micTipsEnabled === true;
            if (this._micTipsEnabled) {
              this._startMicTipScheduler();
            } else {
              this._stopMicTipScheduler();
            }
          }
        }
        if (next.tabEnterHotkey !== undefined) {
          this._tabEnterHotkey = null;
        }
      });
    } catch (_) {}
  }

  _armTabEnterSend() {
    this._tabEnterSendArmed = true;
    this._tabEnterSendArmedAt = Date.now();
  }

  _disarmTabEnterSend() {
    this._tabEnterSendArmed = false;
    this._tabEnterSendArmedAt = 0;
  }

  _isTabEnterSendArmed() {
    if (!this._tabEnterSendArmed) return false;
    if (Date.now() - (this._tabEnterSendArmedAt || 0) > 45000) {
      this._disarmTabEnterSend();
      return false;
    }
    return true;
  }

  _shouldSendOnEnterKey(event) {
    if (!this._isTabEnterSendEnabled()) return false;
    if (this.currentState !== 'recording') return false;
    if (!event || event.key !== 'Enter' || event.shiftKey) return false;
    return this._isTabEnterSendArmed();
  }

  _handleTabEnterSendHotkey() {
    if (this.currentState !== 'recording') return;
    const hasPreview = [this.previewCommitted, this._currentInterimText]
      .some((part) => String(part || '').trim());
    if (hasPreview && this._isPreviewEnterToInsertEnabled()) {
      const flushed = this._flushPreviewToChat();
      if (!flushed && [this.previewCommitted, this._currentInterimText].some((part) => String(part || '').trim())) {
        return;
      }
    }
    this.handleTabEnterSend();
    this._disarmTabEnterSend();
  }

  _getPreviewInsertHint() {
    const showHint = globalThis.__CHILLICOLLECTIVO_AGENTEN_MIC
      ? !this._isDirectFieldAppendMode()
      : this._isPreviewEnterToInsertEnabled();
    if (!showHint) return '';
    const label = chillicollectivoFormatPreviewInsertHotkeyLabel(this._previewInsertHotkey);
    const clickPart = this._isPreviewClickFlushEnabled() ? ' oder Klick ins Feld' : '';
    return `${label}${clickPart} → ins Feld`;
  }

  _getPreviewCharCountField() {
    if (typeof this._findComposeTextField === 'function') {
      const compose = this._findComposeTextField();
      if (compose) return compose;
    }
    return this.recordingField || this.activeField || this._userTargetField || null;
  }

  _getPreviewCharCountLabel(interimText = '', committedText = '') {
    if (typeof chillicollectivoGetPreviewCharStats !== 'function') return '';
    const field = this._getPreviewCharCountField();
    const live = String(interimText || this._currentInterimText || this._lastInterim || '').replace(/\s+/g, ' ').trim();
    const buffered = String(committedText || this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    const previewText = this._hasDictationReplaceSpan('preview')
      ? buffered
      : (this._isDirectFieldAppendMode() ? live : this._buildUnifiedPreviewText(buffered, live));
    const stats = chillicollectivoGetPreviewCharStats(field, previewText);
    return stats?.label || '';
  }

  _syncPreviewStateBeforeReplaceOps(trigger = '') {
    const committedEl = this.fab?._getCommittedPreviewEl?.();
    if (!committedEl || !this.fab) return;
    const domText = this.fab._normalizePreviewPlainText(committedEl.innerText || '').trim();
    const stateText = String(this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    if (domText && domText !== stateText) {
      this.fab.syncPreviewCommittedFromDom();
      chillicollectivoVoiceDiag('info', 'voice.preview-sync-before-replace', 'Vorschau aus DOM synchronisiert vor Ersetzung', {
        trigger,
        domLen: domText.length,
        stateLen: stateText.length,
      }, this);
    }
    if (!this._hasDictationReplaceSpan('preview')) return;
    const span = this._dictationReplaceSpan;
    const len = String(this.previewCommitted || '').length;
    const start = Math.max(0, Math.min(span.anchorStart ?? 0, len));
    const end = Math.max(start, Math.min(span.anchorEnd ?? start, len));
    if (end <= start) {
      this._clearDictationReplaceSpan('preview');
      return;
    }
    if (start !== span.anchorStart || end !== span.anchorEnd) {
      span.anchorStart = start;
      span.anchorEnd = end;
      if (this.fab) this.fab._previewSelectionOffsets = { start, end };
    }
  }

  _refreshDictationPreviewCharCount() {
    if (!this.fab?._updatePreviewCharCount) return;
    if (!this._isPreviewEnterToInsertEnabled() || this.currentState !== 'recording') return;
    if (this.fab?._isPreviewDomLockedForDictation?.()) return;
    this.fab._updatePreviewCharCount(this._getPreviewCharCountLabel());
  }

  _isStickyDictationPreview() {
    if (this.currentState !== 'recording') return false;
    return !!this._isPreviewUiEnabled();
  }

  _resetDictationLivePreview() {
    this._currentInterimText = '';
    this._lastInterim = '';
    if (this._isDirectFieldAppendMode()) {
      this.previewCommitted = '';
    }
    if (this.fab) {
      this.fab._lastRenderedInterim = '';
      this.fab._lastRenderedCommitted = '';
    }
    if (!this._isPreviewUiEnabled() || !this._isCaptureActive()) {
      this.fab?.hidePreview?.({ force: true });
      return;
    }
    this._pinDictationPreview();
    const el = this.fab?._getOrCreateOverlay?.();
    if (!el) return;
    el.classList.add('chillicollectivo-voice-overlay-dictation-unified');
    el.classList.remove('chillicollectivo-voice-overlay-editable');
    this.fab._clearPreviewCrumble?.(el);
    this.fab._clearPreviewContent?.(el);
    this.fab._applyPreviewAppearanceVars?.(el);
    this.fab._previewVisible = true;
    el.style.display = 'block';
    el.classList.add('chillicollectivo-voice-overlay-visible', 'chillicollectivo-voice-overlay-glow');
    chillicollectivoVoiceDiag('info', 'voice.preview-dictation-reset', 'Vorschau geleert – bereit für nächsten Satz', {
      route: this._currentConversationKey(),
      enterToInsert: !!this._isPreviewEnterToInsertEnabled(),
    }, this);
  }

  _resetDirectModeLivePreview() {
    this._resetDictationLivePreview();
  }

  _isRecognitionStale() {
    if (!this.browserRecognition) return true;
    const sessionStart = this._recSessionStartedAt || 0;
    const lastResult = this._lastRecResultAt || 0;
    const anchor = lastResult >= sessionStart ? lastResult : sessionStart;
    return !anchor || (Date.now() - anchor) > this._REC_STALE_MS;
  }

  _clearStickyInterimCommit() {
    if (this._stickyInterimCommitTimer) {
      clearTimeout(this._stickyInterimCommitTimer);
      this._stickyInterimCommitTimer = null;
    }
  }

  _commitInterimPhraseFinal(text, reason = 'silence') {
    const clean = String(text || '').replace(/\s+/g, ' ').trim();
    if (!clean || !this.browserListening) return;
    this._lastInterim = '';
    this._stickyInterimText = '';
    this._clearStickyInterimCommit();
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    if (this._isLivePreviewBufferMode()) {
      chillicollectivoVoiceDiag('info', 'voice.interim-phrase-preview', 'Zwischen-Phrase in Vorschau übernommen (Pause)', {
        textSnippet: clean.slice(0, 80),
        reason,
      }, this);
      this._simplePreviewAppendFinal(clean);
      return;
    }
    this._markFinalSent(clean);
    chillicollectivoVoiceDiag('info', 'voice.interim-phrase-final', 'Zwischen-Phrase als Final übernommen (Pause)', {
      textSnippet: clean.slice(0, 80),
      reason,
    }, this);
    this._sendTranscription(clean, true);
  }

  _scheduleBrowserSilenceCommit(text) {
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    const pending = String(text || '').trim();
    if (!pending) return;
    this._browserSilenceTimer = setTimeout(() => {
      this._browserSilenceTimer = null;
      if (!this.browserListening || !this.browserRecognition) return;
      const textToSend = String(this._lastInterim || this._stickyInterimText || pending).trim();
      if (!textToSend) return;
      this._commitInterimPhraseFinal(textToSend, 'silence-timeout');
    }, this._BROWSER_SILENCE_MS);
  }

  _scheduleStickyInterimDropCommit(text) {
    this._clearStickyInterimCommit();
    const pending = String(text || '').trim();
    if (!pending) return;
    this._stickyInterimCommitTimer = setTimeout(() => {
      this._stickyInterimCommitTimer = null;
      if (!this.browserListening) return;
      if (String(this._lastInterim || '').trim()) return;
      this._commitInterimPhraseFinal(pending, 'interim-cleared');
    }, this._INTERIM_DROP_COMMIT_MS);
  }

  _scheduleSilenceKeepalive() {
    this._clearSilenceKeepalive();
    if (!this.browserListening) return;
    const intervalMs = this._SILENCE_KEEPALIVE_MS || 1200;
    this._silenceKeepaliveTimer = setInterval(() => {
      if (!this.browserListening || this._browserPermissionError) return;
      if (this.browserRecognition || this._recognitionRestartTimer) return;
      this._ignoreRestOfCurrentRec = false;
      chillicollectivoVoiceDiag('info', 'voice.pause-keepalive', 'STT nach langer Pause neu gestartet', {
        committedLen: String(this.previewCommitted || '').length,
        route: this._currentConversationKey?.() || '',
      }, this);
      this._startNewRecognitionSession();
    }, intervalMs);
  }

  _maintainActiveListening(reason = 'maintain') {
    if (!this.browserListening || !this._SR || this._browserPermissionError) return;
    if (this.browserRecognition) return;
    this._ignoreRestOfCurrentRec = false;
    this._startNewRecognitionSession();
  }

  _refreshRecognitionSessionFast(reason = 'fast-refresh') {
    if (!this.browserListening || !this._SR) return;
    this._ignoreRestOfCurrentRec = false;
    this._startNewRecognitionSession();
  }

  _clearSilenceKeepalive() {
    if (this._silenceKeepaliveTimer) {
      clearInterval(this._silenceKeepaliveTimer);
      this._silenceKeepaliveTimer = null;
    }
  }

  _previewFlowState() {
    const committed = String(this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    const interim = String(this._currentInterimText || '').replace(/\s+/g, ' ').trim();
    const lastInterim = String(this._lastInterim || '').replace(/\s+/g, ' ').trim();
    const rendered = String(this.fab?._lastRenderedCommitted || '').replace(/\s+/g, ' ').trim();
    const visible = String(this.fab?._getCommittedPreviewEl?.()?.innerText || rendered || '')
      .replace(/\u00A0/g, ' ')
      .replace(/\s+/g, ' ')
      .trim();
    return {
      committedLen: committed.length,
      interimLen: interim.length,
      lastInterimLen: lastInterim.length,
      renderedLen: rendered.length,
      visibleDomLen: visible.length,
      committedSnippet: committed.slice(0, 100),
      interimSnippet: interim.slice(0, 80),
      renderedSnippet: rendered.slice(-100),
      visibleDomSnippet: visible.slice(-100),
      previewPinned: !!this._dictationPreviewPinned,
      previewVisible: !!this.fab?._previewVisible,
      previewEditing: !!this.fab?._isPreviewBeingEdited?.(),
      enterToInsert: !!this._isPreviewEnterToInsertEnabled(),
      state: this.currentState,
      route: this._currentConversationKey?.() || '',
    };
  }

  _logPreviewFlow(level, source, message, detail = {}) {
    chillicollectivoVoiceDiag(level, source, message, {
      ...this._previewFlowState(),
      ...detail,
    }, this);
  }

  _shouldKeepDictationPreviewOpen() {
    if (!this._isPreviewUiEnabled()) return false;
    if (this._isDirectAppendRecordingSession()) return true;
    if (!this._isStickyDictationPreview()) return false;
    if (this._dictationPreviewPinned) return true;
    const flow = this._previewFlowState();
    return flow.committedLen > 0 || flow.interimLen > 0 || flow.renderedLen > 0 || flow.visibleDomLen > 0;
  }

  _pinDictationPreview() {
    if (!this._isPreviewUiEnabled()) return;
    if (this._isStickyDictationPreview()) this._dictationPreviewPinned = true;
  }

  _unpinDictationPreview() {
    this._dictationPreviewPinned = false;
  }

  _isVoiceUiElement(node) {
    return !!node?.closest?.('.chillicollectivo-fab, .chillicollectivo-fab-gear, #chillicollectivo-voice-overlay');
  }

  _rangeTextOffsets(container, range) {
    if (!container || !range) return null;
    try {
      const pre = document.createRange();
      pre.selectNodeContents(container);
      pre.setEnd(range.startContainer, range.startOffset);
      const start = pre.toString().length;
      pre.setEnd(range.endContainer, range.endOffset);
      const end = pre.toString().length;
      return { start, end };
    } catch (_) {
      return null;
    }
  }

  _offsetsToRange(container, start, end) {
    if (!container) return null;
    try {
      const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
      let pos = 0;
      let startNode = null;
      let startOffset = 0;
      let endNode = null;
      let endOffset = 0;
      while (walker.nextNode()) {
        const node = walker.currentNode;
        const len = node.textContent?.length || 0;
        if (!startNode && pos + len >= start) {
          startNode = node;
          startOffset = start - pos;
        }
        if (!endNode && pos + len >= end) {
          endNode = node;
          endOffset = end - pos;
          break;
        }
        pos += len;
      }
      if (!startNode || !endNode) return null;
      const range = document.createRange();
      range.setStart(startNode, startOffset);
      range.setEnd(endNode, endOffset);
      return range;
    } catch (_) {
      return null;
    }
  }

  _moveCommittedPreviewCaretToOffset(committedEl, offset) {
    if (!committedEl) return;
    const range = this._offsetsToRange(committedEl, offset, offset);
    if (!range) {
      this.fab?._moveCommittedPreviewCaretToEnd?.(committedEl);
      return;
    }
    try {
      this.fab._previewDictationFocus = true;
      committedEl.focus({ preventScroll: true });
      const sel = window.getSelection();
      sel?.removeAllRanges();
      sel?.addRange(range);
    } catch (_) {}
  }

  _setFieldCaretAtOffset(field, offset, options = {}) {
    if (!field || !this._isUsableTextField(field)) return false;
    const pos = Math.max(0, Number(offset) || 0);
    try { field.focus({ preventScroll: true }); } catch (_) {}
    if (!field.isContentEditable && typeof field.selectionStart === 'number') {
      const len = (field.value || '').length;
      const cp = Math.min(pos, len);
      field.selectionStart = field.selectionEnd = cp;
      this.recordingStartPosition = cp;
      this.recordingSelectionEnd = cp;
      return true;
    }
    if (field.isContentEditable) {
      const range = this._offsetsToRange(field, pos, pos);
      if (!range) return false;
      const sel = window.getSelection();
      sel?.removeAllRanges();
      sel?.addRange(range);
      this.recordingStartPosition = range.cloneRange();
      this.recordingSelectionEnd = null;
      return true;
    }
    return false;
  }

  _restoreDictationCursorAfterInsert(field, options = {}) {
    if (!field || !this._isUsableTextField(field)) return false;
    const trigger = String(options.trigger || 'post-insert').trim();
    let offset = options.caretOffset;
    if (offset == null && typeof this.recordingStartPosition === 'number') {
      offset = this.recordingStartPosition;
    }
    if (offset == null && options.span?.anchorEnd != null) {
      offset = options.span.anchorEnd;
    }
    const apply = () => {
      if (field.isContentEditable && this.recordingStartPosition?.cloneRange) {
        try {
          field.focus({ preventScroll: true });
          const sel = window.getSelection();
          sel?.removeAllRanges();
          sel?.addRange(this.recordingStartPosition.cloneRange());
          return true;
        } catch (_) {}
      }
      if (offset != null && Number.isFinite(Number(offset))) {
        return this._setFieldCaretAtOffset(field, Number(offset), { trigger });
      }
      return false;
    };
    const ok = apply();
    if (ok) {
      queueMicrotask(() => { apply(); });
    }
    return ok;
  }

  _isRangeInsideField(range, field) {
    if (!range || !field) return false;
    try {
      if (field.isContentEditable) return field.contains(range.commonAncestorContainer);
      return false;
    } catch (_) {
      return false;
    }
  }

  _clearDictationReplaceSpan(target = '', options = {}) {
    if (!target || this._dictationReplaceSpan?.target === target) {
      this._dictationReplaceSpan = null;
      if (options.clearSaved) {
        if (!target || target === 'preview') this._lastActivePreviewReplaceSpan = null;
        if (!target || target === 'field') this._lastActiveFieldReplaceSpan = null;
      }
    }
  }

  _rememberActivePreviewReplaceSpan(start = 0, end = 0) {
    const anchorStart = Math.max(0, Number(start) || 0);
    const anchorEnd = Math.max(anchorStart, Number(end) || anchorStart);
    if (anchorEnd <= anchorStart) return;
    this._lastActivePreviewReplaceSpan = {
      target: 'preview',
      anchorStart,
      anchorEnd,
      capturedAt: Date.now(),
    };
  }

  _rememberActiveFieldReplaceSpan(field, start = 0, end = 0) {
    if (!field) return;
    const anchorStart = Math.max(0, Number(start) || 0);
    const anchorEnd = Math.max(anchorStart, Number(end) || anchorStart);
    if (anchorEnd <= anchorStart) return;
    this._lastActiveFieldReplaceSpan = {
      target: 'field',
      field,
      fieldKey: this._getFieldTargetKey(field),
      anchorStart,
      anchorEnd,
      capturedAt: Date.now(),
    };
  }

  _syncFieldStateBeforeReplaceOps(field, trigger = '') {
    if (!field || !this._isUsableTextField(field)) return;
    if (!this._hasDictationReplaceSpan('field')) return;
    const span = this._dictationReplaceSpan;
    if (span.field && span.field !== field) return;
    const plain = this._fieldPlainText(field);
    const len = plain.length;
    const start = Math.max(0, Math.min(span.anchorStart ?? 0, len));
    const end = Math.max(start, Math.min(span.anchorEnd ?? start, len));
    if (end <= start) {
      this._clearDictationReplaceSpan('field');
      chillicollectivoVoiceDiag('warn', 'voice.field-span-invalid', 'Feld-Ersetzungsbereich ungültig nach Sync', {
        trigger,
        anchorStart: span.anchorStart,
        anchorEnd: span.anchorEnd,
        fieldLen: len,
        ...this._fieldInsertContext(field),
      }, this);
      return;
    }
    if (start !== span.anchorStart || end !== span.anchorEnd) {
      span.anchorStart = start;
      span.anchorEnd = end;
      this._rememberActiveFieldReplaceSpan(field, start, end);
      chillicollectivoVoiceDiag('info', 'voice.field-span-clamped', 'Feld-Ersetzungsbereich an aktuelle Feldlänge angepasst', {
        trigger,
        anchorStart: start,
        anchorEnd: end,
        fieldLen: len,
      }, this);
    }
  }

  _restoreActiveFieldReplaceSpanIfNeeded(field, trigger = '') {
    if (!field) field = this.recordingField || this._userTargetField || this.activeField;
    if (!field || !this._isUsableTextField(field)) return false;
    if (this._isDirectFieldAppendMode() && !this._fieldHasExplicitUserSelection(field)) {
      return false;
    }
    if (this._hasDictationReplaceSpan('field') && this._dictationReplaceSpan.field === field) return true;
    const saved = this._lastActiveFieldReplaceSpan;
    if (!saved || saved.target !== 'field') return false;
    if (Date.now() - (saved.capturedAt || 0) > 30000) {
      this._lastActiveFieldReplaceSpan = null;
      return false;
    }
    const fieldKey = this._getFieldTargetKey(field);
    if (saved.field && saved.field !== field && saved.fieldKey && saved.fieldKey !== fieldKey) return false;
    const plain = this._fieldPlainText(field);
    const len = plain.length;
    const start = Math.max(0, Math.min(Number(saved.anchorStart) || 0, len));
    const end = Math.max(start, Math.min(Number(saved.anchorEnd) || start, len));
    if (end <= start) return false;
    const markedText = plain.substring(start, end);
    this._dictationReplaceSpan = { target: 'field', field, anchorStart: start, anchorEnd: end };
    this.recordingStartPosition = start;
    this.recordingSelectionEnd = end;
    if (!field.isContentEditable) {
      field.selectionStart = start;
      field.selectionEnd = end;
    }
    chillicollectivoVoiceDiag('info', 'voice.field-span-restored', 'Feld-Ersetzungsbereich aus letzter Markierung wiederhergestellt', {
      trigger,
      anchorStart: start,
      anchorEnd: end,
      spanLen: end - start,
      markedText,
      markedTextSnippet: markedText.slice(0, 160),
      ...this._fieldInsertContext(field),
    }, this);
    return true;
  }

  _hasDictationReplaceSpan(target = '') {
    const span = this._dictationReplaceSpan;
    if (!span) return false;
    if (!target) return true;
    return span.target === target;
  }

  _capturePreviewReplaceSpan(logReason = '') {
    if (!this._isPreviewUiEnabled()) return false;
    const committedEl = this.fab?._getCommittedPreviewEl?.();
    if (!committedEl) {
      this._clearDictationReplaceSpan('preview');
      if (logReason) {
        this._noteReplaceSpanMiss('no-committed-el', logReason);
      }
      return false;
    }

    const storedOffsets = this.fab?._clampPreviewSelectionOffsets?.(committedEl, this.fab?._previewSelectionOffsets);
    const existing = this._dictationReplaceSpan;
    if (existing?.target === 'preview' && (existing.anchorEnd ?? 0) > (existing.anchorStart ?? 0)) {
      const keepLiveSpan = storedOffsets
        && storedOffsets.start === (existing.anchorStart ?? -1)
        && (existing.anchorEnd ?? 0) > storedOffsets.end;
      const hotResync = logReason === 'interim'
        || logReason === 'selection-sync'
        || logReason === 'preview-buffer'
        || logReason === 'preview-buffer-dom';
      if (!storedOffsets || keepLiveSpan || hotResync) {
        this._rememberActivePreviewReplaceSpan(existing.anchorStart ?? 0, existing.anchorEnd ?? 0);
        if (logReason && !hotResync) {
          const markedDetail = this.fab?._previewMarkedTextDetail?.(
            committedEl,
            existing.anchorStart ?? 0,
            existing.anchorEnd ?? 0,
          ) || {};
          chillicollectivoVoiceDiag('info', 'voice.replace-span-captured', 'Ersetzungsbereich in Vorschau erfasst', {
            source: keepLiveSpan ? 'liveSpan' : 'existingSpan',
            trigger: logReason,
            ...markedDetail,
          }, this);
        }
        return true;
      }
    }

    const domCommitted = committedEl.innerText.replace(/\u00A0/g, ' ').replace(/\s+/g, ' ').trim();
    if (domCommitted || this.fab?._previewSelectionOffsets) {
      this.fab?.syncPreviewCommittedFromDom?.();
    }
    if (storedOffsets) {
      this._dictationReplaceSpan = {
        target: 'preview',
        anchorStart: storedOffsets.start,
        anchorEnd: storedOffsets.end,
      };
      this._rememberActivePreviewReplaceSpan(storedOffsets.start, storedOffsets.end);
      if (logReason) {
        const markedDetail = this.fab?._previewMarkedTextDetail?.(committedEl, storedOffsets.start, storedOffsets.end) || {};
        chillicollectivoVoiceDiag('info', 'voice.replace-span-captured', 'Ersetzungsbereich in Vorschau erfasst', {
          source: 'storedOffsets',
          trigger: logReason,
          ...markedDetail,
        }, this);
      }
      return true;
    }

    this.fab?._capturePreviewCommittedSelection?.();
    const range = this.fab?._resolvePreviewReplacementRange?.(committedEl);
    if (!range) {
      this._clearDictationReplaceSpan('preview');
      if (logReason) {
        const skipNoise = logReason === 'interim'
          || logReason === 'preview-buffer'
          || logReason === 'preview-buffer-dom';
        if (!skipNoise) {
          chillicollectivoVoiceDiag('info', 'voice.replace-span-miss', 'Kein Ersetzungsbereich in Vorschau', {
            reason: 'no-selection-range',
            selectionOffsets: this.fab?._previewSelectionOffsets || null,
            trigger: logReason,
          }, this);
        }
      }
      return false;
    }
    const offsets = this._rangeTextOffsets(committedEl, range);
    if (!offsets || offsets.start === offsets.end) {
      this._clearDictationReplaceSpan('preview');
      if (logReason) {
        chillicollectivoVoiceDiag('info', 'voice.replace-span-miss', 'Kein Ersetzungsbereich in Vorschau', {
          reason: 'collapsed-selection',
          offsets: offsets || null,
          trigger: logReason,
        }, this);
      }
      return false;
    }
    this._dictationReplaceSpan = {
      target: 'preview',
      anchorStart: offsets.start,
      anchorEnd: offsets.end,
    };
    this._rememberActivePreviewReplaceSpan(offsets.start, offsets.end);
    if (logReason) {
      const markedDetail = this.fab?._previewMarkedTextDetail?.(committedEl, offsets.start, offsets.end) || {};
      chillicollectivoVoiceDiag('info', 'voice.replace-span-captured', 'Ersetzungsbereich in Vorschau erfasst', {
        source: 'domRange',
        trigger: logReason,
        ...markedDetail,
      }, this);
    }
    return true;
  }

  _captureDictationReplaceSpanFromField(field, logReason = '') {
    if (!field || !this._isUsableTextField(field)) {
      this._clearDictationReplaceSpan('field');
      return false;
    }

    let start = 0;
    let end = 0;
    let hasActiveSelection = false;

    if (field.isContentEditable) {
      const sel = window.getSelection();
      if (sel?.rangeCount) {
        const range = sel.getRangeAt(0);
        if (this._isRangeInsideField(range, field) && !range.collapsed) {
          const offsets = this._rangeTextOffsets(field, range);
          if (offsets && offsets.start !== offsets.end) {
            start = offsets.start;
            end = offsets.end;
            hasActiveSelection = true;
            this.recordingStartPosition = range.cloneRange();
            this.recordingSelectionEnd = null;
          }
        }
      }
    } else {
      start = field.selectionStart ?? 0;
      end = field.selectionEnd ?? start;
      if (start !== end) {
        hasActiveSelection = true;
        this.recordingStartPosition = start;
        this.recordingSelectionEnd = end;
      }
    }

    const existing = this._dictationReplaceSpan;
    if (!hasActiveSelection) {
      if (
        this.currentState === 'recording'
        && existing?.target === 'field'
        && existing.field === field
        && (existing.anchorEnd ?? 0) > (existing.anchorStart ?? 0)
      ) {
        return true;
      }
      if (this._isDirectFieldAppendMode()) {
        this._clearDictationReplaceSpan('field');
        return false;
      }
      this._clearDictationReplaceSpan('field');
      return false;
    }

    if (
      this.currentState === 'recording'
      && existing?.target === 'field'
      && existing.field === field
      && (logReason === 'selection-sync' || logReason === 'interim')
      && start === (existing.anchorStart ?? -1)
      && end <= (existing.anchorEnd ?? 0)
    ) {
      return true;
    }

    this._dictationReplaceSpan = {
      target: 'field',
      field,
      anchorStart: start,
      anchorEnd: end,
    };
    this._rememberActiveFieldReplaceSpan(field, start, end);
    if (logReason) {
      const markedText = (field.isContentEditable ? this._fieldPlainText(field) : (field.value || '')).substring(start, end);
      chillicollectivoVoiceDiag('info', 'voice.field-span-captured', 'Ersetzungsbereich im Chatfeld erfasst', {
        trigger: logReason,
        markedText,
        markedTextSnippet: markedText.slice(0, 160),
        anchorStart: start,
        anchorEnd: end,
        spanLen: end - start,
        enterToInsert: !!this._isPreviewEnterToInsertEnabled?.(),
        previewUi: !!this._isPreviewUiEnabled?.(),
        ...this._fieldInsertContext(field),
      }, this);
    }
    return true;
  }

  _captureDictationReplaceSpan(logReason = '') {
    const enterMode = !!this._isPreviewEnterToInsertEnabled?.();
    const previewUi = this._isPreviewUiEnabled();
    const field = this.recordingField || this._userTargetField || this.activeField;
    if (!enterMode && field) {
      if (this._fieldHasExplicitUserSelection(field)) {
        if (this._captureDictationReplaceSpanFromField(field, logReason)) return true;
      } else {
        this._restoreActiveFieldReplaceSpanIfNeeded(field, logReason);
      }
    }
    if (previewUi && this._capturePreviewReplaceSpan(logReason)) return true;
    if (enterMode && field) {
      if (this._captureDictationReplaceSpanFromField(field, logReason)) return true;
    }
    return false;
  }

  _noteReplaceSpanMiss(reason, trigger = '') {
    if (!this._replaceSpanMissStats) this._replaceSpanMissStats = {};
    const key = `${reason}|${trigger || 'any'}`;
    const stats = this._replaceSpanMissStats[key] || { count: 0, lastAt: 0, firstAt: 0 };
    stats.count += 1;
    stats.lastAt = Date.now();
    if (!stats.firstAt) stats.firstAt = stats.lastAt;
    this._replaceSpanMissStats[key] = stats;

    const isInterimNoise = trigger === 'interim' || trigger === 'preview-buffer' || trigger === 'preview-buffer-dom';
    const isPreviewOnlyMiss = reason === 'no-committed-el' || reason === 'no-selection-range' || reason === 'collapsed-selection';
    if (!this._isPreviewUiEnabled() && isPreviewOnlyMiss) return;
    if (isInterimNoise) return;

    chillicollectivoVoiceDiag('info', 'voice.replace-span-miss', this._isPreviewUiEnabled()
      ? 'Kein Ersetzungsbereich in Vorschau'
      : 'Kein Ersetzungsbereich im Chatfeld', {
      reason,
      trigger,
      count: stats.count,
    }, this);
  }

  _flushReplaceSpanMissStats(reason = 'recording-stopped') {
    const stats = this._replaceSpanMissStats;
    if (!stats || !Object.keys(stats).length) return;
    const rows = Object.entries(stats)
      .map(([key, val]) => {
        const [missReason, trigger] = key.split('|');
        return { reason: missReason, trigger, count: val.count };
      })
      .filter((row) => row.count > 0);
    if (!rows.length) {
      this._replaceSpanMissStats = null;
      return;
    }
    const totalMisses = rows.reduce((sum, row) => sum + row.count, 0);
    chillicollectivoVoiceDiag('info', 'voice.replace-span-miss-summary', 'Ersetzungsbereich fehlte (aggregiert)', {
      reason,
      totalMisses,
      rows: rows.slice(0, 8),
      enterToInsert: !!this._isPreviewEnterToInsertEnabled?.(),
    }, this);
    this._replaceSpanMissStats = null;
  }

  _noteRecOnendForDiag(recSessionAgeMs = 0) {
    if (!this._recOnendDiagTimes) this._recOnendDiagTimes = [];
    const now = Date.now();
    this._recOnendDiagTimes.push(now);
    this._recOnendDiagTimes = this._recOnendDiagTimes.filter((t) => now - t < 60000);
    // Mic an: Chrome beendet STT regelmäßig bei Stille — das ist erwartetes Verhalten, kein Fehler.
    if (this._isCaptureActive()) return;
    if (this._recOnendDiagTimes.length < 8) return;
    if (this._recOnendLoopWarnAt && now - this._recOnendLoopWarnAt < 60000) return;
    this._recOnendLoopWarnAt = now;
    this._ensureRecordingEngineAlive('rec-onend-loop');
    const field = this.recordingField || this._userTargetField || this.activeField;
    chillicollectivoVoiceDiag('warn', 'voice.rec-onend-loop', 'STT-Neustart-Loop erkannt (≥8× in 60s)', {
      count: this._recOnendDiagTimes.length,
      recSessionAgeMs,
      rapidDeathCount: this._recRapidDeathCount || 0,
      hasField: !!field,
      route: this._currentConversationKey?.() || '',
    }, this);
  }

  _noteInterimRegression(previousText, nextText, source = 'interim') {
    const prev = String(previousText || '').replace(/\s+/g, ' ').trim();
    const next = String(nextText || '').replace(/\s+/g, ' ').trim();
    if (!prev || !next || next.length >= prev.length) return;
    if (prev.length < 12) return;
    const ratio = next.length / prev.length;
    if (ratio > 0.88) return;
    if (this._interimRegressionWarnAt && Date.now() - this._interimRegressionWarnAt < 2500) return;
    this._interimRegressionWarnAt = Date.now();
    chillicollectivoVoiceDiag('warn', 'voice.preview-interim-regression', 'STT-Interim wurde kürzer – Textverlust möglich', {
      source,
      beforeLen: prev.length,
      afterLen: next.length,
      shrinkRatio: Number(ratio.toFixed(3)),
      beforeSnippet: prev.slice(0, 120),
      afterSnippet: next.slice(0, 120),
      enterToInsert: !!this._isPreviewEnterToInsertEnabled?.(),
    }, this);
  }

  _noteDisplayShrink(previousDisplay, nextDisplay, source = 'preview-update') {
    const prev = String(previousDisplay || '').replace(/\s+/g, ' ').trim();
    const next = String(nextDisplay || '').replace(/\s+/g, ' ').trim();
    if (!prev || !next || next.length >= prev.length) return;
    if (prev.length < 20) return;
    const ratio = next.length / prev.length;
    if (ratio > 0.9) return;
    if (this._displayShrinkWarnAt && Date.now() - this._displayShrinkWarnAt < 2500) return;
    this._displayShrinkWarnAt = Date.now();
    chillicollectivoVoiceDiag('warn', 'voice.preview-display-shrank', 'Vorschau-Anzeige schrumpfte – vorheriger Text evtl. gelöscht', {
      source,
      beforeLen: prev.length,
      afterLen: next.length,
      shrinkRatio: Number(ratio.toFixed(3)),
      beforeSnippet: prev.slice(-120),
      afterSnippet: next.slice(0, 120),
      previewEditing: !!this.fab?._isPreviewBeingEdited?.(),
    }, this);
  }

  _applyDictationReplaceSpan(text, options = {}) {
    const clean = String(text || '');
    const span = this._dictationReplaceSpan;
    if (!span || !clean.trim()) return false;
    const anchorStart = span.anchorStart ?? 0;
    const anchorEnd = span.anchorEnd ?? anchorStart;

    if (span.target === 'preview') {
      this._syncPreviewStateBeforeReplaceOps('apply-replace-span');
      const committedEl = this.fab?._getCommittedPreviewEl?.();
      const fromState = String(this.previewCommitted || '').replace(/\u00A0/g, ' ').replace(/\s+/g, ' ');
      let cur = fromState;
      if (committedEl) {
        const fromDom = this.fab._normalizePreviewPlainText(committedEl.innerText || '').trim();
        if (fromDom) cur = fromDom;
      }
      let dictation = String(clean).replace(/\s+/g, ' ').trim();
      dictation = this._stripDictationEchoForReplaceSpan(dictation, cur, anchorStart, anchorEnd);
      if (!dictation) return false;
      const markedText = cur.substring(anchorStart, anchorEnd);
      let tail = cur.substring(anchorEnd);
      tail = this._trimReplaceSpanTailEcho(tail, dictation, markedText);
      const next = `${cur.substring(0, anchorStart)}${dictation}${tail}`;
      this.previewCommitted = next;
      if (!options.clearSpan) {
        this._rememberActivePreviewReplaceSpan(anchorStart, anchorStart + dictation.length);
      } else {
        this._lastActivePreviewReplaceSpan = null;
      }
      if (committedEl) {
        if (committedEl.textContent !== next) {
          this.fab._previewDictationFocus = true;
          this.fab._previewDictationMutating = true;
          committedEl.textContent = next;
          this.fab._previewDictationMutating = false;
        }
        const caretOffset = anchorStart + dictation.length;
        this._moveCommittedPreviewCaretToOffset(committedEl, caretOffset);
        if (options.clearSpan) {
          this.fab?._clearPreviewSelectionState?.();
        }
      }
      if (this.fab) {
        this.fab._lastRenderedCommitted = next;
        this.fab._lastRenderedInterim = '';
      }
      if (options.clearSpan) {
        this.fab?._clearPreviewSelectionState?.();
      }
      chillicollectivoVoiceDiag('info', 'voice.preview-span-replaced', 'Diktat ersetzt markierten Vorschau-Bereich', {
        markedText,
        markedTextSnippet: markedText.slice(0, 160),
        dictationText: dictation,
        dictationTextSnippet: dictation.slice(0, 160),
        anchorStart,
        anchorEnd,
        echoStripped: dictation !== String(clean).replace(/\s+/g, ' ').trim(),
        clearSpan: !!options.clearSpan,
        previewAfter: next.slice(0, 300),
        previewAfterLen: next.length,
      }, this);
      if (options.clearSpan) {
        this._clearDictationReplaceSpan('preview', { clearSaved: true });
      } else {
        span.anchorEnd = anchorStart + dictation.length;
        if (this.fab) {
          this.fab._previewSelectionOffsets = { start: anchorStart, end: span.anchorEnd };
        }
      }
      return true;
    }

    if (span.target === 'field' && span.field) {
      const ok = this._insertFieldTextAtSpan(span.field, clean, span);
      if (ok && !options.clearSpan) {
        span.anchorEnd = anchorStart + clean.length;
      } else if (ok && options.clearSpan) {
        this._clearDictationReplaceSpan('field');
      }
      return ok;
    }
    return false;
  }

  _finalizeFieldSpanInsert(field, span, anchorStart, insertedEnd) {
    span.anchorEnd = insertedEnd;
    this._restoreDictationCursorAfterInsert(field, {
      caretOffset: insertedEnd,
      span,
      trigger: 'field-span-replaced',
    });
    if (this._isLivePreviewBufferMode() || this._isPreviewEnterToInsertEnabled()) {
      this._rememberActiveFieldReplaceSpan(field, anchorStart, span.anchorEnd);
    }
  }

  _insertFieldTextAtSpan(field, text, span) {
    if (!field || !span) return false;
    const anchorStart = span.anchorStart ?? 0;
    const anchorEnd = span.anchorEnd ?? anchorStart;
    const cur = field.isContentEditable
      ? this._fieldPlainText(field)
      : (field.value || '');
    const cleaned = this._stripDictationEchoForReplaceSpan(text, cur, anchorStart, anchorEnd);
    if (!cleaned) return false;
    const before = cur.substring(0, anchorStart);
    text = this._formatDictationFieldInsert(cleaned, before, {
      hasSelection: anchorEnd > anchorStart,
    });
    const prefix = before && !/\s$/.test(before) && !/^\s/.test(text) && anchorStart > 0 ? ' ' : '';
    const insertedEnd = before.length + prefix.length + text.length;

    if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)
      && typeof chillicollectivoWriteProfileField === 'function') {
      const after = cur.substring(anchorEnd);
      const next = `${before}${prefix}${text}${after}`;
      chillicollectivoWriteProfileField(field, next, false, {
        source: 'voice-dictation-span',
        dictationText: text,
        markedText: cur.substring(anchorStart, anchorEnd),
      });
      field.selectionStart = field.selectionEnd = insertedEnd;
      this.recordingStartPosition = insertedEnd;
      this.recordingSelectionEnd = insertedEnd;
      field.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
      field.dispatchEvent(new Event('change', { bubbles: true }));
      this._finalizeFieldSpanInsert(field, span, anchorStart, insertedEnd);
      return true;
    }

    if (field.isContentEditable) {
      try {
        field.focus({ preventScroll: true });
        const range = document.createRange();
        const walker = document.createTreeWalker(field, NodeFilter.SHOW_TEXT);
        let pos = 0;
        let startNode = null;
        let startOffset = 0;
        let endNode = null;
        let endOffset = 0;
        while (walker.nextNode()) {
          const node = walker.currentNode;
          const len = node.textContent?.length || 0;
          if (!startNode && pos + len >= anchorStart) {
            startNode = node;
            startOffset = anchorStart - pos;
          }
          if (!endNode && pos + len >= anchorEnd) {
            endNode = node;
            endOffset = anchorEnd - pos;
            break;
          }
          pos += len;
        }
        if (!startNode || !endNode) return false;
        range.setStart(startNode, startOffset);
        range.setEnd(endNode, endOffset);
        range.deleteContents();
        const insertText = `${prefix}${text}`;
        range.insertNode(document.createTextNode(insertText));
        range.collapse(false);
        const sel = window.getSelection();
        sel?.removeAllRanges();
        sel?.addRange(range);
        this.recordingStartPosition = range.cloneRange();
        this.recordingSelectionEnd = null;
        field.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
        this._finalizeFieldSpanInsert(field, span, anchorStart, insertedEnd);
        return true;
      } catch (_) {
        return false;
      }
    }

    const after = cur.substring(anchorEnd);
    const next = `${before}${prefix}${text}${after}`;
    if (typeof chillicollectivoWriteProfileField === 'function' && typeof chillicollectivoIsProfileTextarea === 'function'
      && chillicollectivoIsProfileTextarea(field)) {
      chillicollectivoWriteProfileField(field, next, false, {
        source: 'voice-dictation-span',
        dictationText: text,
        markedText: cur.substring(anchorStart, anchorEnd),
      });
    } else {
      field.value = next;
    }
    field.selectionStart = field.selectionEnd = insertedEnd;
    this.recordingStartPosition = insertedEnd;
    this.recordingSelectionEnd = insertedEnd;
    field.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
    field.dispatchEvent(new Event('change', { bubbles: true }));
    this._finalizeFieldSpanInsert(field, span, anchorStart, insertedEnd);
    chillicollectivoVoiceDiag('info', 'voice.field-span-replaced', 'Diktat ersetzt markierten Feld-Bereich', {
      markedText: cur.substring(anchorStart, anchorEnd),
      markedTextSnippet: cur.substring(anchorStart, anchorEnd).slice(0, 160),
      dictationText: text,
      dictationTextSnippet: String(text || '').slice(0, 160),
      anchorStart,
      anchorEnd: span.anchorEnd,
      fieldBeforeSnippet: cur.slice(Math.max(0, anchorStart - 40), anchorEnd + 40),
      fieldAfterSnippet: next.slice(Math.max(0, anchorStart - 40), span.anchorEnd + 40),
      echoStripped: cleaned !== String(text || '').replace(/\s+/g, ' ').trim(),
      ...this._fieldInsertContext(field),
    }, this);
    return true;
  }

  _prepareFieldForInsert(field) {
    if (!field || !this._isUsableTextField(field)) return false;
    try { field.focus({ preventScroll: true }); } catch (_) {}
    if (field.isContentEditable && this.recordingStartPosition?.cloneRange) {
      const sel = window.getSelection();
      if (sel) {
        sel.removeAllRanges();
        try { sel.addRange(this.recordingStartPosition.cloneRange()); } catch (_) {}
      }
    } else if (!field.isContentEditable && typeof this.recordingStartPosition === 'number') {
      const len = (field.value || '').length;
      const start = Math.min(Math.max(0, this.recordingStartPosition), len);
      const end = typeof this.recordingSelectionEnd === 'number'
        ? Math.min(Math.max(0, this.recordingSelectionEnd), len)
        : start;
      field.selectionStart = Math.min(start, end);
      field.selectionEnd = Math.max(start, end);
    }
    return true;
  }

  _getVisiblePreviewTextFromDom() {
    const overlay = document.getElementById('chillicollectivo-voice-overlay');
    if (!overlay?.classList.contains('chillicollectivo-voice-overlay-visible')) return '';
    const normalize = (value) => String(value || '').replace(/\u00A0/g, ' ').replace(/\s+/g, ' ').trim();
    const committedEl = overlay.querySelector('.chillicollectivo-voice-overlay-committed');
    const interimEl = overlay.querySelector('.chillicollectivo-voice-overlay-interim');
    const committed = normalize(committedEl?.textContent || '');
    const interim = normalize(interimEl?.textContent || '');
    if (this._isLivePreviewBufferMode()) {
      return this._buildUnifiedPreviewText(committed, interim) || interim || committed;
    }
    if (overlay.classList.contains('chillicollectivo-voice-overlay-dictation-unified') || !interim) {
      return committed || interim;
    }
    return this._buildUnifiedPreviewText(committed, interim);
  }

  _getVisiblePreviewSnapshot() {
    const interim = String(this._currentInterimText || this._lastInterim || '').trim();
    if (this._isDirectFieldAppendMode()) return interim;
    this.fab?.syncPreviewCommittedFromDom?.();
    const committed = String(this.previewCommitted || '').trim();
    const fromState = (!committed && !interim) ? '' : this._buildUnifiedPreviewText(committed, interim);
    const fromDom = this._getVisiblePreviewTextFromDom();
    if (fromDom.length >= fromState.length) return fromDom;
    return fromState;
  }

  _absorbInterimIntoPreviewBuffer(text, reason = 'interim-absorb') {
    if (!this._isLivePreviewBufferMode()) return false;
    let clean = String(text || '').replace(/\s+/g, ' ').trim();
    if (!clean) return false;
    if (typeof chillicollectivoProcessSpeechText === 'function') {
      clean = chillicollectivoProcessSpeechText(clean);
    }
    clean = this._collapsePreviewPhraseStack(clean);
    const current = String(this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    const merged = current
      ? this._collapsePreviewPhraseStack(this._mergePreviewDisplaySegments(current, clean))
      : this._normalizePreviewPunctuation(clean, { finalize: true });
    if (!merged || merged === current) return false;
    this.previewCommitted = merged;
    this._currentInterimText = '';
    this._lastInterim = '';
    this._pinDictationPreview();
    this._showDictationPreview('', '');
    this._logPreviewFlow('info', 'voice.preview-interim-absorbed', 'Satz in Vorschau-Puffer übernommen (einheitliche Anzeige)', {
      reason,
      beforeLen: current.length,
      afterLen: merged.length,
    });
    return true;
  }

  _commitVisiblePreviewSnapshot(reason = 'preview-snapshot') {
    const interim = String(this._currentInterimText || this._lastInterim || '').trim();
    if (!interim) {
      this._logPreviewFlow('info', 'voice.preview-snapshot-skip', 'Kein Live-Text zum Übernehmen', { reason });
      return false;
    }
    return this._absorbInterimIntoPreviewBuffer(interim, reason);
  }

  _bufferFinalToPreviewOnly(text) {
    const clean = String(text || '').replace(/\s+/g, ' ').trim();
    if (!clean) return false;
    if (this._isRecentlyFlushedPreviewText(clean) || this._isDiscardedPreviewText(clean)) {
      return true;
    }
    if (this._appendPreviewBuffer(clean, 'dictation-final-enter-mode')) {
      this._showDictationPreview('', '', {
        insertHint: this._getPreviewInsertHint(),
      });
      return true;
    }
    return false;
  }

  _dedupeTrailingPreviewRepeat(text) {
    const raw = String(text || '').replace(/\s+/g, ' ').trim();
    if (!raw) return raw;
    const words = raw.split(/\s+/).filter(Boolean);
    if (words.length < 4) return raw;

    for (let tailWords = Math.min(Math.floor(words.length / 2), 24); tailWords >= 3; tailWords -= 1) {
      const tail = words.slice(-tailWords);
      const beforeTail = words.slice(-tailWords * 2, -tailWords);
      if (beforeTail.length !== tailWords) continue;
      const norm = (part) => part.map((word) => this._normalizeDuplicateWord?.(word) || word.toLowerCase()).join(' ');
      if (norm(tail) === norm(beforeTail)) {
        const collapsed = words.slice(0, -tailWords).join(' ').trim();
        if (collapsed.length >= 8) {
          chillicollectivoVoiceDiag('info', 'voice.preview-tail-deduped', 'Doppelter Satzansatz am Vorschau-Ende entfernt', {
            tailWords,
            beforeLen: raw.length,
            afterLen: collapsed.length,
            tailSnippet: tail.join(' ').slice(0, 120),
          }, this);
          return collapsed;
        }
      }
    }
    return raw;
  }

  _previewLiveAlreadyInBuffered(buffered = '', live = '') {
    const done = String(buffered || '').replace(/\s+/g, ' ').trim();
    const interim = String(live || '').replace(/\s+/g, ' ').trim();
    if (!done || !interim) return !interim;
    const normDone = this._normalizeDuplicateText(done);
    const normInterim = this._normalizeDuplicateText(interim);
    if (normDone === normInterim || normDone.endsWith(normInterim)) return true;
    if (this._isFuzzyDuplicateText(interim, done.slice(Math.max(0, done.length - Math.max(interim.length * 2, 48))))) {
      return true;
    }
    return false;
  }

  _collectPendingPreviewText(trigger = '') {
    if (trigger === 'flush') {
      this._forceAbsorbPendingSpeechForFlush();
      this._commitVisiblePreviewSnapshot('preview-flush');
    }
    this.fab?.syncPreviewCommittedFromDom?.();
    const committed = String(this.previewCommitted || '').trim();
    const domSnapshot = String(this.fab?._snapshotPreviewCommitted?.() || '').trim();
    const committedBest = domSnapshot.length > committed.length ? domSnapshot : committed;
    if (committedBest !== this.previewCommitted) {
      this.previewCommitted = committedBest;
    }
    const interimBeforeStrip = String(this._lastInterim || this._currentInterimText || '').trim();
    const interimSkipped = interimBeforeStrip
      && this._previewLiveAlreadyInBuffered(committedBest, interimBeforeStrip);
    const interimForMerge = interimSkipped ? '' : interimBeforeStrip;
    if (interimSkipped) {
      chillicollectivoVoiceDiag('info', 'voice.preview-live-skipped', 'Live-Text nicht angehängt (steckt schon in Vorschau)', {
        committedLen: committedBest.length,
        interimLen: interimBeforeStrip.length,
        interimSnippet: interimBeforeStrip.slice(0, 80),
        committedTail: committedBest.slice(-80),
        trigger: trigger || null,
      }, this);
    }
    const fromState = this._isLivePreviewBufferMode()
      ? this._mergeEnterModePreviewSegments(committedBest, interimForMerge)
      : this._buildUnifiedPreviewText(committedBest, interimForMerge);
    const fromDom = this._getVisiblePreviewTextFromDom();
    let text = fromDom.length >= fromState.length ? fromDom : fromState;
    text = typeof chillicollectivoProcessSpeechText === 'function'
      ? chillicollectivoProcessSpeechText(text)
      : text.replace(/\b(ausrufezeichen)\b/gi, '!').replace(/!{2,}/g, '!');
    let result = String(text || '').trim();
    result = this._dedupeTrailingPreviewRepeat(result);
    result = this._collapsePreviewPhraseStack(result) || result;
    if (trigger === 'flush' && result) {
      chillicollectivoVoiceDiag('info', 'voice.preview-collect', 'Vorschau-Text für Flush zusammengestellt', {
        committedLen: committedBest.length,
        interimLen: interimBeforeStrip.length,
        domLen: fromDom.length,
        stateLen: fromState.length,
        resultLen: result.length,
        committedSnippet: committedBest.slice(0, 80),
        interimSnippet: interimBeforeStrip.slice(0, 80),
        resultSnippet: result.slice(0, 120),
      }, this);
    }
    return result;
  }

  _isDiscardedPreviewText(text) {
    const norm = this._normalizeDuplicateText(String(text || '').trim());
    if (!norm || !this._discardedPreviewNorm) return false;
    if (this._discardedPreviewRoute
      && this._currentConversationKey()
      && this._discardedPreviewRoute !== this._currentConversationKey()) {
      return false;
    }
    return this._isFuzzyDuplicateNorm(norm, this._discardedPreviewNorm);
  }

  _markFlushedPreviewText(text) {
    const norm = this._normalizeDuplicateText(String(text || '').trim());
    if (!norm) return;
    this._discardedPreviewNorm = norm;
    this._discardedPreviewRoute = this._currentConversationKey();
    this._flushedPreviewNorm = norm;
    this._flushedPreviewAt = Date.now();
    chillicollectivoVoiceDiag('info', 'voice.preview-flushed-marked', 'Flush-Text markiert (Duplikat-Schutz 15s)', {
      textLen: String(text || '').trim().length,
      textSnippet: String(text || '').trim().slice(0, 100),
      route: this._discardedPreviewRoute,
    }, this);
  }

  _clearFlushedPreviewGuard(reason = 'unknown') {
    if (!this._flushedPreviewNorm && !this._flushedPreviewAt) return;
    chillicollectivoVoiceDiag('info', 'voice.flush-guard-cleared', 'Flush-Duplikat-Schutz zurückgesetzt', {
      reason,
      hadFlushedNorm: !!this._flushedPreviewNorm,
      flushedPreviewAgeMs: this._flushedPreviewAt ? Date.now() - this._flushedPreviewAt : null,
    }, this);
    this._flushedPreviewNorm = '';
    this._flushedPreviewAt = 0;
  }

  _clearPostFlushDictationGuards(reason = 'unknown', options = {}) {
    const clearLastInserted = options.clearLastInserted === true;
    this._clearFlushedPreviewGuard(reason);
    this._discardedPreviewNorm = '';
    this._discardedPreviewRoute = '';
    this._lastFinalSentText = '';
    this._lastFinalSentAt = 0;
    this._lastPreviewBufferNorm = '';
    this._lastPreviewBufferRoute = '';
    this._lastPreviewBufferAt = 0;
    if (this._postFlushIgnoreTimer) {
      clearTimeout(this._postFlushIgnoreTimer);
      this._postFlushIgnoreTimer = null;
    }
    this._ignoreRestOfCurrentRec = false;
    if (clearLastInserted) {
      this._lastInsertedText = '';
      this._lastInsertedTime = 0;
    }
    chillicollectivoVoiceDiag('info', 'voice.post-flush-guards-cleared', 'Diktat-Schutz nach Kontextwechsel zurückgesetzt', {
      reason,
      clearLastInserted,
    }, this);
  }

  _resetDictationGuardsForTargetChange(previousKey, nextKey, reason = 'field-target-change') {
    if (!previousKey || !nextKey || previousKey === nextKey) return;
    const leavingCompose = String(previousKey).startsWith('compose:');
    this._clearPostFlushDictationGuards(reason, {
      clearLastInserted: leavingCompose,
    });
  }

  _armPostFlushRecognitionIgnore(ms = 3000) {
    // Kein pauschales STT-Ignorieren mehr – das hat oft das erste neue Wort verschluckt.
    // Echo/Duplikate werden pro Text über _isRecentlyFlushedPreviewText gefiltert.
    chillicollectivoVoiceDiag('info', 'voice.post-flush-guard', 'Flush-Duplikat-Schutz aktiv (inhaltlich)', {
      guardMs: ms,
    }, this);
  }

  _shouldDropRecognitionResult(text) {
    const clean = String(text || '').replace(/\s+/g, ' ').trim();
    if (!clean) return true;
    if (this._isRecentlyFlushedPreviewText(clean) || this._isDiscardedPreviewText(clean)) {
      return true;
    }
    return false;
  }

  _isRecentlyFlushedPreviewText(text) {
    const norm = this._normalizeDuplicateText(String(text || '').trim());
    if (!norm || !this._flushedPreviewNorm) return false;
    const ageMs = Date.now() - (this._flushedPreviewAt || 0);
    if (ageMs > 15000) return false;
    if (this._discardedPreviewRoute
      && this._currentConversationKey()
      && this._discardedPreviewRoute !== this._currentConversationKey()) {
      return false;
    }
    const blocked = this._isFuzzyDuplicateNorm(norm, this._flushedPreviewNorm);
    if (blocked) {
      chillicollectivoVoiceDiag('info', 'voice.flushed-preview-blocked', 'Text nach Flush blockiert (Duplikat-Schutz)', {
        textSnippet: String(text || '').trim().slice(0, 100),
        flushedAgeMs: ageMs,
        route: this._currentConversationKey(),
      }, this);
    }
    return blocked;
  }

  _cancelPendingInsertRetry() {
    if (!this._pendingInsertRetry?.token) return;
    clearTimeout(this._pendingInsertRetry.token);
    this._pendingInsertRetry = null;
  }

  _discardPendingPreviewText(reason = 'discarded') {
    const text = this._collectPendingPreviewText();
    if (text) {
      this._discardedPreviewNorm = this._normalizeDuplicateText(text);
      this._discardedPreviewRoute = this._currentConversationKey();
      chillicollectivoToolLog('warn', 'voice.preview-discarded', 'Vorschau-Text verworfen', {
        text,
        reason,
        route: this._discardedPreviewRoute,
      });
    }
    this._cancelPendingInsertRetry();
    this._unpinDictationPreview();
    this._clearDictationBuffers({ force: true, hidePreview: false });
    return !!text;
  }

  _clearPreviewStateAfterCollect(text, trigger = 'flush') {
    this.previewCommitted = '';
    this._currentInterimText = '';
    this._lastInterim = '';
    this.fab?._clearCommittedPreviewDisplay?.();
    if (this.fab) {
      this.fab._lastRenderedCommitted = '';
      this.fab._lastRenderedInterim = '';
    }
    chillicollectivoVoiceDiag('info', 'voice.preview-display-cleared', 'Vorschau geleert vor Feld-Einfügen', {
      trigger,
      textLen: String(text || '').trim().length,
    }, this);
  }

  _flushPreviewToChat(options = {}) {
    const trigger = String(options.trigger || 'hotkey').trim() || 'hotkey';
    if (this._previewFlushInFlight) {
      chillicollectivoVoiceDiag('info', 'voice.preview-flush-skip', 'Vorschau-Flush übersprungen – läuft bereits', { trigger }, this);
      return false;
    }
    this._previewFlushInFlight = true;
    try {
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    this._forceAbsorbPendingSpeechForFlush();
    const text = this._collectPendingPreviewText('flush');
    if (!text) {
      chillicollectivoVoiceDiag('info', 'voice.preview-flush-skip', 'Vorschau-Flush abgebrochen – kein Text', {}, this);
      return false;
    }
    this._clearPreviewStateAfterCollect(text, trigger);
    chillicollectivoVoiceDiag('info', 'voice.preview-flush-start', 'Vorschau wird ins Chatfeld übernommen (runterholen)', {
      trigger,
      textLen: text.length,
      textSnippet: text.slice(0, 120),
      committedLen: String(this.previewCommitted || '').length,
      interimLen: String(this._currentInterimText || '').length,
    }, this);
    const field = this._resolveTextField();
    if (!field || !this.isTextField(field)) {
      chillicollectivoVoiceDiag('warn', 'voice.preview-flush-no-field', 'Vorschau-Flush fehlgeschlagen – kein Textfeld', {
        textSnippet: text.slice(0, 100),
      }, this);
      this._discardPendingPreviewText('enter-no-field');
      this.fab?.showPreview('Kein Textfeld', 'Text verworfen');
      setTimeout(() => this.fab?.hidePreview?.({ force: true }), 1800);
      return false;
    }
    this._captureDictationReplaceSpan('preview-flush');
    const usedFieldSpan = this._hasDictationReplaceSpan('field');
    this._logVoiceInsertFlow('info', 'voice.preview-flush-field-before', 'Chatfeld-Zustand vor Flush-Einfügen', {
      textLen: text.length,
      textSnippet: text.slice(0, 120),
      usedFieldSpan,
      recordingStartPos: typeof this.recordingStartPosition === 'number' ? this.recordingStartPosition : 'contenteditable-range',
      recordingSelectionEnd: this.recordingSelectionEnd,
    }, field);
    if (!usedFieldSpan) {
      if (!field.isContentEditable) {
        this.recordingStartPosition = field.selectionStart ?? this.recordingStartPosition ?? field.value?.length ?? 0;
        this.recordingSelectionEnd = field.selectionEnd ?? this.recordingSelectionEnd ?? this.recordingStartPosition;
      } else {
        const sel = window.getSelection();
        if (sel?.rangeCount > 0) this.recordingStartPosition = sel.getRangeAt(0).cloneRange();
      }
    }
    let caretAfterInsert = null;
    if (usedFieldSpan) {
      this._insertFieldTextAtSpan(field, text, this._dictationReplaceSpan);
      caretAfterInsert = typeof this.recordingStartPosition === 'number'
        ? this.recordingStartPosition
        : (this._dictationReplaceSpan?.anchorEnd ?? null);
      this._clearDictationReplaceSpan('field', { clearSaved: true });
    } else {
      this._prepareFieldForInsert(field);
      this._commitFinalInsert(field, text);
      caretAfterInsert = typeof this.recordingStartPosition === 'number'
        ? this.recordingStartPosition
        : null;
    }
    this._lastInsertedText = text;
    this._lastInsertedTime = Date.now();
    this._markFinalSent(text);
    const continueDictation = this._isCaptureActive();
    this._markFlushedPreviewText(text);
    if (continueDictation) {
      this._armPostFlushRecognitionIgnore(4000);
    }
    this._clearDictationReplaceSpan('preview', { clearSaved: true });
    if (this.fab) this.fab._previewSelectionOffsets = null;
    if (continueDictation) {
      this._resumeDictationAfterPreviewFlush();
    } else {
      this._restartRecognitionAfterCommand();
    }
    this._unpinDictationPreview();
    this._stopFabMotion();
    if (continueDictation && this._isPreviewUiEnabled()) {
      this._resetDictationLivePreview();
    } else {
      this.fab?.hidePreview?.({ force: true });
    }
    this._setRecordingTarget(field);
    if (continueDictation) {
      this._clearDictationReplaceSpan('field', { clearSaved: true });
      if (usedFieldSpan || (caretAfterInsert != null && !this._isDictationCursorAtFieldEnd(field))) {
        this._restoreDictationCursorAfterInsert(field, {
          caretOffset: caretAfterInsert,
          trigger: 'preview-flush-resume',
        });
      } else {
        this._advanceDictationAppendCursor(field, 'preview-flush-resume');
      }
    } else if (!field.isContentEditable) {
      const len = (field.value || '').length;
      this.recordingStartPosition = len;
      this.recordingSelectionEnd = len;
    }
    chillicollectivoVoiceDiag('info', 'voice.preview-flush-done', 'Vorschau ins Chatfeld übernommen', {
      trigger,
      textLen: text.length,
      usedFieldSpan,
      clickToInsert: this._isAgentClickToInsertMode(),
      insertMode: this._isPreviewEnterToInsertEnabled()
        ? 'enter-to-preview'
        : (this._isAgentClickToInsertMode() ? 'click-to-preview' : 'direct-field'),
      fieldType: field.isContentEditable ? 'contenteditable' : (field.tagName || 'input'),
      fieldTailAfter: this._fieldPlainText(field).slice(-100),
      ...this._fieldInsertContext(field),
    }, this);
    return true;
    } finally {
      this._previewFlushInFlight = false;
    }
  }

  init() {
    if (!chrome.runtime?.id) {
      console.warn('Extension context invalidated - skipping initialization');
      return;
    }
    console.log('CHILLICOLLECTIVO Voice Field Manager initialized');

    this.setupFocusTracking();
    this.setupMessageHandlers();
    this.setupDefaultKeyboardShortcut();
    this.setupRouteRecovery();
    this._loadPreviewEnterSetting();
    this._onConversationRouteChanged(this._currentConversationKey());

    const createFAB = () => {
      this.fab = new CHILLICOLLECTIVOVoiceFAB(this);

      chrome.runtime.sendMessage({ type: 'GET_LICENSE_STATE', revalidate: false }, (licenseResponse) => {
        if (chrome.runtime.lastError) {
          this._refreshLicenseAccessState();
          return;
        }
        this._applyLicenseAccessState(licenseResponse?.state || { access: 'denied' });
      });

      try {
        chrome.storage.onChanged.addListener((changes, area) => {
          if (area !== 'local') return;
          if (changes.chillicollectivoLicense || changes.chillicollectivoTrialStartedAt) {
            this._refreshLicenseAccessState();
          }
        });
      } catch (_) {}

      // Zustand nach Reload/URL-Wechsel abfragen, damit Aufnahme weiterläuft
      chrome.runtime.sendMessage({ type: 'CHECK_RECORDING_STATE' }, (response) => {
        if (chrome.runtime.lastError) return;
        if (response && response.isRecording) {
          this._pendingVoiceStart = true;
          this.handleStateChange('recording');
          if (response.model === 'browser' && response.config) {
            this.startBrowserRecognition(response.config, response.tabId);
          }
        }
      });
    };
    if (document.body) createFAB();
    else document.addEventListener('DOMContentLoaded', createFAB);

    document.addEventListener('visibilitychange', () => {
      if (document.hidden && !this.browserListening) {
        this._cancelMicWarmRelease();
        this._releaseMicStream();
        this._stopMicLevelMonitor(true);
      }
    });
    window.addEventListener('pagehide', () => {
      this.stopBrowserRecognition({ keepMic: false });
    });

    this._scheduleLicenseCheck();
  }

  _applyLicenseAccessState(state, options = {}) {
    const granted = state?.access === 'granted';
    this._licenseAccessGranted = granted;

    if (!granted) {
      this._clearLicenseExpiryStop();
      if (this.currentState === 'recording' || this.currentState === 'loading') {
        this.stopBrowserRecognition({ keepMic: false });
      }
      if (this.currentState !== 'locked') {
        this.handleStateChange('locked', state?.message || 'Keine gültige Lizenz');
      }
      return false;
    }

    if (this.currentState === 'locked') {
      this.handleStateChange('ready');
    }

    this._scheduleLicenseExpiryStop(state);
    return true;
  }

  _startMicFromUserClick(via = 'user') {
    if (this.currentState === 'recording' || this.currentState === 'loading') {
      this._userStopVoice(`${via}-toggle-stop`);
      return;
    }

    const startStateForBackground = this.currentState === 'error'
      ? 'error'
      : (this.currentState === 'locked' ? 'locked' : 'ready');

    this._pendingVoiceStart = true;
    this._bumpRecordingStartGeneration(`${via}-toggle-start`);
    const startGen = this._recordingStartGeneration;
    chillicollectivoVoiceDiag('info', 'voice.toggle-start', 'Mikro durch Nutzer eingeschaltet', {
      route: this._currentConversationKey(),
      via,
    }, this);

    const startLocal = () => {
      this.handleStateChange('loading');
      chrome.storage.local.get(['chillicollectivoVoiceSettings'], (data) => {
        this.startBrowserRecognition({
          voiceConfig: {
            speechLang: 'de-CH',
            sharpSAsDoubleS: true,
          },
          correctionRules: [],
        }, null).then((result) => {
          if (startGen !== this._recordingStartGeneration) return;
          if (result?.ready) this.handleStateChange('recording');
          else this.handleStateChange('error', result?.error || 'Mikrofon konnte nicht gestartet werden');
        }).catch((e) => {
          if (startGen !== this._recordingStartGeneration) return;
          this.handleStateChange('error', e?.message || 'Mikrofon konnte nicht gestartet werden');
        });
      });
    };

    try {
      chrome.runtime.sendMessage({ type: 'ICON_CLICKED', currentState: startStateForBackground }, () => {
        if (chrome.runtime.lastError) startLocal();
      });
      this.handleStateChange('loading');
    } catch (_) {
      startLocal();
    }
  }

  _scheduleLicenseCheck(delayMs = 1000) {
    if (this._licenseCheckTimer) {
      clearTimeout(this._licenseCheckTimer);
      this._licenseCheckTimer = null;
    }
    this._licenseCheckTimer = window.setTimeout(() => {
      this._refreshLicenseAccessState(() => {
        this._scheduleLicenseCheck(5000);
      });
    }, delayMs);
  }

  _refreshLicenseAccessState(onMissing = null) {
    chrome.runtime.sendMessage({ type: 'GET_LICENSE_STATE', revalidate: false }, (response) => {
      if (chrome.runtime.lastError || !response?.state) {
        if (typeof onMissing === 'function') onMissing();
        return;
      }
      const state = response.state;
      const granted = this._applyLicenseAccessState(state);
      if (!granted) {
        if (typeof onMissing === 'function') onMissing();
        return;
      }
      const remaining = typeof chillicollectivoResolveStateRemainingMs === 'function'
        ? chillicollectivoResolveStateRemainingMs(state)
        : Math.max(Number(state.trialRemainingMs || 0), Number(state.licenseRemainingMs || 0));
      let nextDelay = 5 * 60 * 1000;
      if (remaining > 0 && remaining < 60 * 1000) {
        nextDelay = Math.min(1000, Math.max(250, remaining));
      } else if (remaining > 0 && remaining < 60 * 60 * 1000) {
        nextDelay = Math.min(10000, Math.max(1000, remaining));
      }
      if (this._licenseCheckTimer) {
        clearTimeout(this._licenseCheckTimer);
        this._licenseCheckTimer = null;
      }
      this._licenseCheckTimer = window.setTimeout(() => {
        this._scheduleLicenseCheck(0);
      }, nextDelay);
    });
  }

  _clearLicenseExpiryStop() {
    if (this._licenseExpiryTimer) {
      clearTimeout(this._licenseExpiryTimer);
      this._licenseExpiryTimer = null;
    }
  }

  _scheduleLicenseExpiryStop(state) {
    this._clearLicenseExpiryStop();
    if (state?.access !== 'granted') return;
    const remaining = typeof chillicollectivoResolveStateRemainingMs === 'function'
      ? chillicollectivoResolveStateRemainingMs(state)
      : Math.max(Number(state.trialRemainingMs || 0), Number(state.licenseRemainingMs || 0));
    if (remaining <= 0 || remaining > 60 * 60 * 1000) return;
    this._licenseExpiryTimer = window.setTimeout(() => {
      this._licenseExpiryTimer = null;
      chrome.runtime.sendMessage({ type: 'GET_LICENSE_STATE', revalidate: true }, (response) => {
        const next = response?.state;
        if (!next || next.access === 'granted') {
          this._scheduleLicenseCheck(250);
          return;
        }
        if (this.currentState === 'recording' || this.currentState === 'loading') {
          this.stopBrowserRecognition({ keepMic: false });
        }
        this.handleStateChange('locked', next.message || 'Lizenz abgelaufen');
        this._scheduleLicenseCheck(5000);
      });
    }, remaining + 100);
  }

  setupRouteRecovery() {
    const schedule = () => this._scheduleRouteRecovery();
    const wrapHistory = (name) => {
      const original = history[name];
      if (typeof original !== 'function') return;
      const manager = this;
      history[name] = function (...args) {
        const result = original.apply(this, args);
        queueMicrotask(() => manager._scheduleRouteRecovery());
        return result;
      };
    };
    wrapHistory('pushState');
    wrapHistory('replaceState');
    window.addEventListener('popstate', schedule);
    window.addEventListener('hashchange', schedule);
    document.addEventListener('visibilitychange', () => {
      if (!document.hidden) schedule();
    });
  }

  setupFocusTracking() {
    // --- Maus-over-Tracking: welches Textfeld liegt unter dem Cursor? ---
    document.addEventListener('mouseover', e => {
      const target = e.target?.closest?.('input, textarea, [contenteditable]') || e.target;
      if (this.isTextField(target)) {
        this._mouseoverField = target;
      } else {
        // Maus ist nicht über einem Textfeld
        const related = e.relatedTarget;
        if (!related || !this.isTextField(related)) {
          this._mouseoverField = null;
        }
      }
    });
    document.addEventListener('mouseout', e => {
      if (this._mouseoverField && e.target === this._mouseoverField) {
        const related = e.relatedTarget;
        if (!related || !this.isTextField(related)) {
          this._mouseoverField = null;
        }
      }
    });

    // --- Mousedown: Klick in ein Textfeld → sofort als Zielfeld übernehmen ---
    document.addEventListener('mousedown', e => {
      const target = e.target?.closest?.('input, textarea, [contenteditable]') || e.target;
      if (!this.isTextField(target)) return;
      const targetClass = typeof chillicollectivoClassifyDictationField === 'function'
        ? chillicollectivoClassifyDictationField(target)
        : null;
      if (targetClass && ['pp', 'neue-info', 'dialog-info'].includes(targetClass.type)) {
        this._profileUserSelecting = true;
      }
      this._switchTargetField(target, { userInitiated: true });
      this._schedulePreviewFlushOnFieldClick(target);
    }, true);

    document.addEventListener('mouseup', (e) => {
      if (!this._pendingPreviewClickFlushField) return;
      const field = this._pendingPreviewClickFlushField;
      const target = e.target?.closest?.('input, textarea, [contenteditable]') || e.target;
      if (target !== field && !field.contains?.(target)) {
        this._pendingPreviewClickFlushField = null;
        return;
      }
      this._tryFlushPreviewOnFieldClick(field);
    }, true);

    document.addEventListener('mouseup', () => {
      if (!this._profileUserSelecting) return;
      this._profileUserSelecting = false;
      const lockField = typeof chillicollectivoResolveProfileLockField === 'function'
        ? chillicollectivoResolveProfileLockField(this._profileValueLock)
        : this._profileValueLock?.field;
      if (lockField && typeof lockField.selectionStart === 'number') {
        const start = lockField.selectionStart;
        const end = lockField.selectionEnd ?? start;
        const fieldValue = (lockField.value || '').replace(/\s+/g, ' ');
        const markedText = start !== end ? fieldValue.substring(start, end).trim() : '';
        const hadCustomerMessage = typeof chillicollectivoGetLastCustomerMessage === 'function'
          && !!String(chillicollectivoGetLastCustomerMessage() || '').trim();
        window.ChillicollectivoDiagnosticLog?.info?.('profile.focus', 'PP-Markierung erfasst', {
          event: 'selection-captured',
          fieldId: this._profileValueLock?.fieldId,
          side: this._profileValueLock?.side,
          selectionStart: start,
          selectionEnd: end,
          selectedLen: Math.max(0, end - start),
          markedText: markedText.slice(0, 200),
          markedTextSnippet: markedText.slice(0, 120),
          fieldValueSnippet: fieldValue.slice(0, 200),
          hadCustomerMessage,
        }).catch?.(() => {});
        if (this.currentState === 'recording') {
          this.recordingStartPosition = lockField.selectionStart;
          this.recordingSelectionEnd = lockField.selectionEnd;
        }
      }
    }, true);

    // --- Focusin: Tastaturfokus in Textfeld ---
    document.addEventListener('focusin', e => {
      if (!this.isTextField(e.target)) return;
      this._switchTargetField(e.target, { userInitiated: false });
    });

    document.addEventListener('focusout', e => {
      if (this.isTextField(e.target)) {
        this._pendingField = e.target;
      }
      if (this._profileValueLock?.until > Date.now() && this.currentState === 'recording') {
        const lockField = typeof chillicollectivoResolveProfileLockField === 'function'
          ? chillicollectivoResolveProfileLockField(this._profileValueLock)
          : this._profileValueLock.field;
        if (this._profileUserSelecting) return;
        if (lockField && (e.relatedTarget === lockField || lockField.contains?.(e.relatedTarget))) return;
        if (lockField && document.activeElement === lockField) return;
        const keepFocus = [this._userTargetField, this.recordingField, this.activeField].includes(lockField);
        if (keepFocus && typeof chillicollectivoScheduleProfileFocusReclaim === 'function') {
          chillicollectivoScheduleProfileFocusReclaim(this);
        }
        return;
      }
      if (this.activeField === e.target && this.currentState !== 'recording' && this.currentState !== 'loading') {
        if (this._isVoiceUiElement(e.relatedTarget)) return;
        this.activeField = null;
        if (!this._isPreviewEnterToInsertEnabled()) {
          const compose = this._findComposeTextField();
          if (compose) this.activeField = compose;
        }
      }
    });

    // --- Synchronisiere Cursor Position nach Klick/Tastatur-Navigation im Aufnahmemodus ---
    const updateCursor = () => {
      if (this.currentState !== 'recording') return;
      if (this.fab?._previewDictationMutating) return;
      if (!this._profileUserSelecting) {
        const previewSpanLive = this._hasDictationReplaceSpan('preview');
        const userSelectingPreview = !!this.fab?._previewMouseSelecting;
        const field = this.recordingField || this._userTargetField || this.activeField;
        const directAppend = this._isDirectFieldAppendMode();
        const userFieldSelection = field && this._fieldHasExplicitUserSelection(field);
        if (!previewSpanLive || userSelectingPreview) {
          if (!directAppend || userFieldSelection) {
            this._captureDictationReplaceSpan('selection-sync');
          } else {
            this._clearDictationReplaceSpan('field', { clearSaved: true });
          }
        }
      }
      const field = this.recordingField || this._userTargetField || this.activeField;
      if (!field || !this._isUsableTextField(field)) return;
      if (this._dictationReplaceSpan?.target === 'preview') return;
      if (this._dictationReplaceSpan?.target === 'field' && this._fieldHasExplicitUserSelection(field)) return;
      if (field.isContentEditable) {
        const sel = window.getSelection();
        if (sel?.rangeCount > 0 && this._isRangeInsideField(sel.getRangeAt(0), field)) {
          this.recordingStartPosition = sel.getRangeAt(0).cloneRange();
          this.recordingSelectionEnd = null;
        }
      } else {
        this.recordingStartPosition = field.selectionStart ?? field.value?.length ?? 0;
        this.recordingSelectionEnd = field.selectionEnd ?? this.recordingStartPosition;
      }
    };
    document.addEventListener('mouseup', updateCursor);
    document.addEventListener('keyup', updateCursor);
    document.addEventListener('selectionchange', updateCursor);
  }

  _switchTargetField(target, opts = {}) {
    const userInitiated = !!opts.userInitiated;
    if (this._isVoiceUiElement(target)) return;
    if (!userInitiated && (this._composeFocusGuardUntil || 0) > Date.now() && target) {
      const guardClass = typeof chillicollectivoClassifyDictationField === 'function'
        ? chillicollectivoClassifyDictationField(target)
        : null;
      if (guardClass && ['pp', 'neue-info', 'dialog-info'].includes(guardClass.type)) {
        const compose = this._findComposeTextField();
        if (compose && this._setRecordingTarget(compose)) return;
      }
    }
    const targetClass = typeof chillicollectivoClassifyDictationField === 'function'
      ? chillicollectivoClassifyDictationField(target)
      : null;
    const targetIsPp = !!targetClass && targetClass.type === 'pp';
    const targetIsProfileArea = !!targetClass && ['pp', 'neue-info', 'dialog-info'].includes(targetClass.type);

    if (this._profileValueLock?.until > Date.now() && target) {
      if (!targetIsProfileArea) {
        if (userInitiated) {
          if (typeof chillicollectivoClearProfileDictationLock === 'function') {
            chillicollectivoClearProfileDictationLock(this);
          } else {
            this._profileValueLock = null;
          }
        } else if (target === this._userTargetField || target === this.recordingField) {
          // Explizit gewähltes Chat-/Info-Feld behalten.
        } else {
          return;
        }
      } else {
        const meta = typeof chillicollectivoProfileFieldMetaFromElement === 'function'
          ? chillicollectivoProfileFieldMetaFromElement(target)
          : null;
        if (meta?.side && meta?.fieldId) {
          this._profileValueLock.field = target;
          this._profileValueLock.side = meta.side;
          this._profileValueLock.fieldId = meta.fieldId;
          this._profileValueLock.until = Date.now() + 20000;
        }
      }
    }

    if (userInitiated && this._isUsableTextField(target)) {
      this._userTargetField = target;
      if (targetIsProfileArea) {
        this._composeFocusGuardUntil = 0;
        this._clearPostFlushDictationGuards('user-profile-focus', { clearLastInserted: true });
      }
      if ((targetIsPp || targetClass?.type === 'neue-info' || targetClass?.type === 'dialog-info')
        && typeof chillicollectivoStickProfileFieldFocus === 'function') {
        chillicollectivoStickProfileFieldFocus(this, target, 20000, null, {
          preserveSelection: true,
          trigger: 'user-mousedown',
        });
      }
    }
    if (this.currentState === 'recording') this._syncRecordingRouteIfNeeded();

    if (this.currentState === 'recording' && this.recordingField && target !== this.recordingField) {
      const recClass = typeof chillicollectivoClassifyDictationField === 'function'
        ? chillicollectivoClassifyDictationField(this.recordingField)
        : null;
      const nextClass = targetClass || (typeof chillicollectivoClassifyDictationField === 'function'
        ? chillicollectivoClassifyDictationField(target)
        : null);
      const recIsCompose = !recClass || recClass.type === 'compose';
      const targetIsCompose = !nextClass || nextClass.type === 'compose';
      if (recIsCompose && targetIsCompose) {
        const lockedRoute = String(this._recordingConversationRoute || '').trim();
        const currentRoute = String(this._currentConversationKey() || '').trim();
        if (lockedRoute && currentRoute && !this._isSameConversationRoute(lockedRoute, currentRoute)) {
          if (userInitiated) {
            this.fab?.showPreview('Anderen Chat? Erst Mikro aus.', '');
            setTimeout(() => this.fab?.hidePreview?.({ force: true }), 2200);
          }
          return;
        }
        if (lockedRoute && currentRoute) {
          this._recordingConversationRoute = currentRoute;
        }
      }
      this.recordingField = target;
      this.activeField = target;
    }
    if (this.currentState === 'recording') {
      this.activeField = target;
      if (userInitiated) {
        this.recordingField = target;
      }
      this._onDictationTargetChanged(this.recordingField || target);

      // Verzögert aktualisieren, da der Browser den Cursor bei mousedown/focusin oft erst danach setzt
      const fieldForCursor = this.recordingField || target;
      setTimeout(() => {
        if (!fieldForCursor) return;
        if (fieldForCursor.isContentEditable) {
          const sel = window.getSelection();
          this.recordingStartPosition = sel?.rangeCount > 0 ? sel.getRangeAt(0).cloneRange() : null;
        } else {
          this.recordingStartPosition = fieldForCursor.selectionStart ?? fieldForCursor.value?.length ?? 0;
        }
      }, 10);
    } else {
      this.activeField = target;
    }
  }

  isTextField(element) {
    if (!element || !element.tagName) return false;
    const tag = element.tagName.toLowerCase();
    if (tag === 'textarea') return true;
    if (tag === 'input') {
      const type = (element.type || 'text').toLowerCase();
      return ['text', 'email', 'search', 'url', ''].includes(type);
    }
    return element.isContentEditable === true;
  }

  _isUsableTextField(element) {
    if (!this.isTextField(element)) return false;
    if (!document.contains(element)) return false;
    if (element.disabled || element.readOnly) return false;
    const style = window.getComputedStyle(element);
    if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
    const rect = element.getBoundingClientRect();
    return rect.width > 0 && rect.height > 0;
  }

  _findComposeTextField() {
    if (window.ChillicollectivoTorchwoodAsa?.findComposeField) {
      const el = window.ChillicollectivoTorchwoodAsa.findComposeField();
      if (this._isUsableTextField(el)) return el;
    }
    if (document.documentElement?.dataset?.chillicollectivoTestHarbor === '1') {
      const el = document.getElementById('thChatInput');
      if (this._isUsableTextField(el)) return el;
    }
    return null;
  }

  _refreshStaleFieldRefs() {
    if (this.activeField && !this._isUsableTextField(this.activeField)) this.activeField = null;
    if (this.recordingField && !this._isUsableTextField(this.recordingField)) this.recordingField = null;
    if (this._pendingField && !this._isUsableTextField(this._pendingField)) this._pendingField = null;
    if (this._mouseoverField && !this._isUsableTextField(this._mouseoverField)) this._mouseoverField = null;
    if (this._userTargetField && !this._isUsableTextField(this._userTargetField)) this._userTargetField = null;
  }

  _setRecordingTarget(field) {
    if (!this._isUsableTextField(field)) return false;
    this.activeField = field;
    if (this.currentState === 'recording') {
      this.recordingField = field;
      this._onDictationTargetChanged(field);
      if (field.isContentEditable) {
        const sel = window.getSelection();
        this.recordingStartPosition = sel?.rangeCount > 0 ? sel.getRangeAt(0).cloneRange() : null;
      } else {
        this.recordingStartPosition = field.value?.length ?? field.selectionStart ?? 0;
      }
    }
    return true;
  }

  _adoptFreshComposeField() {
    const compose = this._findComposeTextField();
    if (!compose) return false;
    return this._setRecordingTarget(compose);
  }

  _clearDictationBuffers(options = {}) {
    const force = options.force === true;
    if (this._dictationPreviewPinned && !force) {
      this._currentInterimText = '';
      this._lastInterim = '';
      return;
    }
    this._currentInterimText = '';
    this._lastInterim = '';
    this.previewCommitted = '';
    if (force) this._resetPreviewRedoState('dictation-buffers-cleared');
    this._smoothedMicLevel = 0;
    this._stopFabMotion?.();
    this.fab?.resetLiveBroil?.();
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    if (this._hidePreviewTimer) {
      clearTimeout(this._hidePreviewTimer);
      this._hidePreviewTimer = null;
    }
    if (options.hidePreview !== false && !this._dictationPreviewPinned) this.fab?.hidePreview?.({ force: true });
  }

  _appendPreviewBuffer(text, reason = 'dictation-final') {
    if (this._isDirectFieldAppendMode()) return false;
    this._forgetStaleDuplicateMemory();
    let clean = String(text || '').replace(/\s+/g, ' ').trim();
    if (!clean) return false;
    if (this._isRecentlyFlushedPreviewText(clean) || this._isDiscardedPreviewText(clean)) {
      chillicollectivoToolLog('info', 'voice.discarded-preview-buffer-blocked', 'Verworfener Vorschau-Text wird nicht erneut gepuffert', {
        text: clean,
        reason,
        route: this._currentConversationKey(),
      });
      return true;
    }
    if (this.fab?._isPreviewDomLockedForDictation?.()) {
      this.fab.syncPreviewCommittedFromDom();
    }
    this._syncPreviewStateBeforeReplaceOps('preview-buffer');
    this._restoreActivePreviewReplaceSpanIfNeeded('preview-buffer');
    if (this._hasDictationReplaceSpan('preview')) {
      this._applyDictationReplaceSpan(clean, { clearSpan: true });
      const route = this._currentConversationKey();
      const cleanNorm = this._normalizeDuplicateText(clean);
      this._lastPreviewBufferNorm = cleanNorm;
      this._lastPreviewBufferAt = Date.now();
      this._lastPreviewBufferRoute = route;
      this._currentInterimText = '';
      this._lastInterim = '';
      this._pinDictationPreview();
      if (this.fab) {
        this.fab._lastRenderedCommitted = this.previewCommitted;
        this.fab._lastRenderedInterim = '';
        this.fab._refreshDictationPreviewAux?.({
          insertHint: this._getPreviewInsertHint(),
        });
      }
      chillicollectivoLogVoiceDictation('vorschau-ersetzt', clean, { route, reason });
      chillicollectivoToolLog('info', 'voice.preview-replaced', 'Markierter Vorschau-Text durch Diktat ersetzt', {
        dictationText: clean,
        dictationTextSnippet: clean.slice(0, 160),
        reason,
        route,
      });
      return true;
    }
    const current = String(this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    if (current) {
      const overlapDelta = this._stripPrefixOverlap(current, clean);
      if (!overlapDelta) {
        chillicollectivoToolLog('info', 'voice.preview-prefix-duplicate', 'Voice-Final ist Prefix von Vorschau, nicht erneut angehaengt', {
          text: clean,
          committed: current,
          reason,
          route: this._currentConversationKey(),
        });
        return true;
      }
      if (overlapDelta !== clean) clean = overlapDelta;
    }
    const cleanNorm = this._normalizeDuplicateText(clean);
    const currentTail = this._tailForDuplicateCompare(current, clean);
    const route = this._currentConversationKey();
    const recentPreviewDuplicate = this._lastPreviewBufferNorm
      && this._lastPreviewBufferRoute === route
      && Date.now() - (this._lastPreviewBufferAt || 0) < 16000
      && this._isFuzzyDuplicateNorm(cleanNorm, this._lastPreviewBufferNorm);
    if ((currentTail && this._isFuzzyDuplicateText(clean, currentTail)) || recentPreviewDuplicate) {
      chillicollectivoToolLog('warn', 'voice.preview-duplicate', 'Aehnliches Voice-Final erneut geliefert, Vorschau-Duplicate blockiert', {
        text: clean,
        reason,
        route,
        currentTail,
      });
      return true;
    }
    const committedEl = this.fab?._getCommittedPreviewEl?.();
    if (committedEl) {
      if (this.fab?._isPreviewBeingEdited?.()) {
        this.fab.syncPreviewCommittedFromDom();
      }
      this._syncPreviewStateBeforeReplaceOps('preview-buffer-dom');
      this._restoreActivePreviewReplaceSpanIfNeeded('preview-buffer');
      if (this._hasDictationReplaceSpan('preview')) {
        this._applyDictationReplaceSpan(clean, { clearSpan: true });
      } else if (this._previewDictationAlreadyApplied(current, clean)) {
        chillicollectivoVoiceDiag('info', 'voice.preview-append-skipped', 'Diktat bereits in Vorschau – End-Anhängen übersprungen', {
          dictationTextSnippet: clean.slice(0, 160),
          previewSnippet: current.slice(-160),
          reason,
        }, this);
      } else {
        this.fab._insertTextToCommittedPreview(clean);
        chillicollectivoVoiceDiag('info', 'voice.preview-append', 'Text an Vorschau angehängt (keine Markierung)', {
          dictationText: clean,
          dictationTextSnippet: clean.slice(0, 160),
          previewBefore: current.slice(0, 300),
          previewAfter: (current ? `${current} ${clean}` : clean).slice(0, 300),
          reason,
          committedBeforeLen: current.length,
        }, this);
      }
    } else {
      this.previewCommitted = this._collapsePreviewPhraseStack(this._appendBufferedAndLiveText(current, clean));
    }
    this._lastPreviewBufferNorm = cleanNorm;
    this._lastPreviewBufferAt = Date.now();
    this._lastPreviewBufferRoute = route;
    this._currentInterimText = '';
    this._lastInterim = '';
    this._lastFinalSentText = '';
    this._lastFinalSentAt = 0;
    this._pinDictationPreview();
    this._showDictationPreview('', '', {
      dictation: true,
      insertHint: this._getPreviewInsertHint(),
    });
    chillicollectivoLogVoiceDictation('vorschau', clean, { route, reason });
    chillicollectivoToolLog('info', 'voice.preview-buffered', 'Chattext in Vorschau gepuffert', {
      dictationText: clean,
      dictationTextSnippet: clean.slice(0, 160),
      previewBefore: current.slice(0, 300),
      previewAfter: (current ? `${current} ${clean}` : clean).slice(0, 300),
      reason,
      route,
    });
    return true;
  }

  _isDisjointPauseSegment(buffered = '', live = '') {
    const done = String(buffered || '').replace(/\s+/g, ' ').trim();
    const interim = String(live || '').replace(/\s+/g, ' ').trim();
    if (!done || !interim) return false;
    const sameUtterance = this._isSttUtteranceRefinement?.(done, interim);
    const sharedWords = this._sharedPrefixWordCount(done, interim);
    const prefixLen = Math.min(done.length, interim.length, 24);
    const sharedCharPrefix = prefixLen > 0 && done.slice(0, prefixLen).toLowerCase() === interim.slice(0, prefixLen).toLowerCase();
    return !sameUtterance
      && sharedWords === 0
      && !sharedCharPrefix
      && !interim.toLowerCase().startsWith(done.slice(0, Math.min(20, done.length)).toLowerCase())
      && !done.toLowerCase().startsWith(interim.slice(0, Math.min(10, interim.length)).toLowerCase())
      && this._tailHeadWordOverlapCount(done, interim) === 0
      && !this._isPartialTokenRefinement(done, interim);
  }

  _appendBufferedAndLiveText(buffered = '', live = '') {
    const done = String(buffered || '').replace(/\s+/g, ' ').trim();
    const interim = String(live || '').replace(/\s+/g, ' ').trim();
    if (!interim && !done) return '';
    if (!done) return interim;
    if (!interim) return done;
    const tailMerged = this._mergeTailHeadOverlap(done, interim);
    if (tailMerged && tailMerged.length > done.length) return tailMerged;
    const merged = this._mergePreviewDisplaySegments(done, interim);
    if (merged.length >= done.length) return merged;
    return `${done} ${interim}`.replace(/\s+/g, ' ').trim();
  }

  _buildUnifiedPreviewText(buffered = '', live = '') {
    const done = String(buffered || '').replace(/\s+/g, ' ').trim();
    const interim = String(live || '').replace(/\s+/g, ' ').trim();
    if (!interim && !done) return '';
    if (!done) return interim;
    if (!interim) return done;

    if (this._isDisjointPauseSegment(done, interim)) {
      return this._appendBufferedAndLiveText(done, interim);
    }

    const tailWindow = done.slice(Math.max(0, done.length - Math.max(interim.length * 2, 96)));
    const refinesTail = this._isSttUtteranceRefinement?.(tailWindow, interim);
    const refinesWhole = this._isSttUtteranceRefinement?.(done, interim);
    if (refinesTail || refinesWhole) {
      const refined = this._pickSttRefinedPreviewText?.(done, interim);
      if (refined && refined.length >= done.length) return refined;
      if (refinesTail) {
        const tailRefined = this._pickSttRefinedPreviewText?.(tailWindow, interim) || interim;
        const prefix = done.slice(0, Math.max(0, done.length - tailWindow.length)).trim();
        const merged = prefix ? `${prefix} ${tailRefined}` : tailRefined;
        if (merged.length >= done.length * 0.9) return merged.replace(/\s+/g, ' ').trim();
      }
    }

    return this._appendBufferedAndLiveText(done, interim);
  }

  _previewDisplayParts(interimText, committedText = '') {
    if (this._hasDictationReplaceSpan('preview')) {
      const done = String(committedText || this.previewCommitted || interimText || this._currentInterimText || '').replace(/\s+/g, ' ').trim();
      return { interimText: '', committedText: done };
    }
    const live = String(interimText || this._currentInterimText || '').replace(/\s+/g, ' ').trim();
    const buffered = String(committedText || this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    const unified = this._buildUnifiedPreviewText(buffered, live);
    return { interimText: '', committedText: unified };
  }

  _isCaptureActive() {
    return !!this.browserListening
      && (this.currentState === 'recording' || this.currentState === 'loading');
  }

  _getPreviewFlushText() {
    if (this._isLivePreviewBufferMode()) {
      return this._collapsePreviewPhraseStack(this._getEnterModePreviewText()) || this._getEnterModePreviewText() || '';
    }
    const buffered = String(this.previewCommitted || '').trim();
    const interim = String(this._currentInterimText || this._lastInterim || '').trim();
    if (this._isDirectFieldAppendMode()) return interim;
    const unified = this._buildUnifiedPreviewText(buffered, interim);
    return this._collapsePreviewPhraseStack(unified) || unified || interim || buffered;
  }

  _refreshDictationPreviewCase() {
    if (!this._isPreviewUiEnabled()) return;
    if (!this._currentInterimText?.trim() && !String(this.previewCommitted || '').trim()) return;
    if (this._isLivePreviewBufferMode()) {
      this._refreshEnterModeLivePreview();
      return;
    }
    const interim = String(this._currentInterimText || this._lastInterim || '').trim();
    this._showDictationPreview(interim, '');
  }

  _getEnterModePreviewText() {
    const buffered = String(this.previewCommitted || '').trim();
    const live = String(this._lastInterim || this._currentInterimText || '').trim();
    return this._mergeEnterModePreviewSegments(buffered, live);
  }

  _mergeEnterModePreviewSegments(buffered = '', live = '') {
    const done = String(buffered || '').replace(/\s+/g, ' ').trim();
    const interim = String(live || '').replace(/\s+/g, ' ').trim();
    if (!interim) return done;
    if (!done) return interim;

    if (interim.startsWith(done)) return interim;
    if (done.startsWith(interim)) return done;

    const normDone = this._normalizeDuplicateText?.(done) || done.toLowerCase();
    const normInterim = this._normalizeDuplicateText?.(interim) || interim.toLowerCase();
    if (normInterim.includes(normDone) && interim.length >= done.length) return interim;
    if (normDone.includes(normInterim)) return done;

    const sharedWords = this._sharedPrefixWordCount?.(done, interim) || 0;
    if (sharedWords >= 2 && this._isSttUtteranceRefinement?.(done, interim)) {
      const refined = this._pickSttRefinedPreviewText?.(done, interim);
      if (refined && refined.length >= done.length) return refined;
    }

    const tailMerged = this._mergeTailHeadOverlap?.(done, interim);
    if (tailMerged && tailMerged.length > done.length) return tailMerged;

    // Neuer Satz nach Pause: immer hinter den bestehenden Text anfügen.
    return `${done} ${interim}`.replace(/\s+/g, ' ').trim();
  }

  _appendEnterModeFinalText(text) {
    let clean = typeof chillicollectivoProcessSpeechText === 'function'
      ? chillicollectivoProcessSpeechText(text)
      : String(text || '').replace(/\s+/g, ' ').trim();
    if (!clean) return String(this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    const current = String(this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    if (!current) return clean;
    if (current === clean || current.endsWith(` ${clean}`) || current.endsWith(clean)) return current;
    const normCurrent = this._normalizeDuplicateText(current);
    const normClean = this._normalizeDuplicateText(clean);
    if (normCurrent.endsWith(normClean)) return current;
    if (this._isFuzzyDuplicateText(clean, current.slice(Math.max(0, current.length - Math.max(clean.length * 2, 48))))) {
      return current;
    }
    if (clean.startsWith(current) && clean.length > current.length) return clean;
    const tailMerged = this._mergeTailHeadOverlap?.(current, clean);
    if (tailMerged && tailMerged.length > current.length) return tailMerged;
    return `${current} ${clean}`.replace(/\s+/g, ' ').trim();
  }

  _forceAbsorbPendingSpeechForFlush() {
    const pending = String(this._lastInterim || this._currentInterimText || '').trim();
    if (!pending) return;
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    this.previewCommitted = this._appendEnterModeFinalText(pending);
    this._currentInterimText = '';
    this._lastInterim = '';
  }

  // Enter-Modus: alles als Live-Text (grün) bis Runterholen — kein Weiß/Finalisieren in der UI.
  _refreshEnterModeLivePreview(options = {}) {
    const unified = this._getEnterModePreviewText();
    if (!String(unified || '').trim()) {
      if (this._isCaptureActive() && this._isPreviewUiEnabled()) {
        this._resetDictationLivePreview();
      }
      return;
    }
    if (!this._isPreviewUiEnabled()) return;
    const caseMode = this.fab?.effects?.previewTextCase || CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE;
    const formatted = chillicollectivoApplyPreviewTextCase(unified, caseMode);
    this._pinDictationPreview();
    const payload = {
      dictation: true,
      insertHint: options.insertHint || this._getPreviewInsertHint(),
      charCountLabel: options.charCountLabel || this._getPreviewCharCountLabel('', unified),
      ...options,
    };
    delete payload.unifiedDisplay;
    if (this.fab?.scheduleDictationPreviewUpdate) {
      this.fab.scheduleDictationPreviewUpdate(formatted, '', payload);
    } else {
      this.fab?.showPreview(formatted, '', payload);
    }
    if (this.fab) {
      this.fab._lastRenderedCommitted = '';
      this.fab._lastRenderedInterim = formatted;
    }
  }

  _showDictationPreview(interimText, committedText = '', options = {}) {
    if (this._isLivePreviewBufferMode()) {
      return this._refreshEnterModeLivePreview(options);
    }
    const display = this._previewDisplayParts(interimText, committedText);
    const caseMode = this.fab?.effects?.previewTextCase || CHILLICOLLECTIVO_DEFAULT_PREVIEW_TEXT_CASE;
    const formatDisplay = (value) => chillicollectivoApplyPreviewTextCase(String(value || ''), caseMode);
    const unified = formatDisplay(display.committedText || display.interimText);

    if (!String(unified || '').trim()) {
      if (this._isCaptureActive() && this._isPreviewUiEnabled()) {
        this._resetDictationLivePreview();
      }
      return;
    }
    if (!this._isPreviewUiEnabled()) return;
    this._pinDictationPreview();
    const payload = {
      dictation: true,
      unifiedDisplay: true,
      insertHint: options.insertHint || this._getPreviewInsertHint(),
      charCountLabel: options.charCountLabel || this._getPreviewCharCountLabel(display.interimText, display.committedText),
      ...options,
    };
    if (this.fab?.scheduleDictationPreviewUpdate) {
      this.fab.scheduleDictationPreviewUpdate('', unified, payload);
    } else {
      this.fab?.showPreview('', unified, payload);
    }
  }

  _detachRecognitionHandlers(rec) {
    if (!rec) return;
    try {
      rec.onend = null;
      rec.onresult = null;
      rec.onerror = null;
      rec.onstart = null;
    } catch (_) {}
  }

  _disposeBrowserRecognition({ abort = true } = {}) {
    const rec = this.browserRecognition;
    if (!rec) return;
    this._detachRecognitionHandlers(rec);
    try {
      if (abort) rec.abort?.();
    } catch (_) {}
    try {
      rec.stop();
    } catch (_) {}
    if (this.browserRecognition === rec) this.browserRecognition = null;
  }

  _restartRecognitionAfterCommand() {
    if (!this.browserListening || !this._SR) return;
    this._recSessionId = (this._recSessionId || 0) + 1;
    this._ignoreRestOfCurrentRec = true;
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    if (this._recognitionRestartTimer) {
      clearTimeout(this._recognitionRestartTimer);
      this._recognitionRestartTimer = null;
    }
    this._disposeBrowserRecognition({ abort: false });
    this._ignoreRestOfCurrentRec = false;
    this._scheduleRecognitionRestart(this._REC_RESTART_DELAY_MS);
  }

  _resumeDictationAfterPreviewFlush(reason = 'preview-flush') {
    if (!this._isCaptureActive()) return;
    this._continuingDictationAfterFlushUntil = Date.now() + 86400000;
    this._lastActiveFieldReplaceSpan = null;
    this._clearDictationReplaceSpan('field', { clearSaved: true });
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    this._lastInterim = '';
    this._currentInterimText = '';
    if (this._postFlushIgnoreTimer) {
      clearTimeout(this._postFlushIgnoreTimer);
      this._postFlushIgnoreTimer = null;
    }
    if (this._recognitionRestartTimer) {
      clearTimeout(this._recognitionRestartTimer);
      this._recognitionRestartTimer = null;
    }
    this._ignoreRestOfCurrentRec = false;
    this._disposeBrowserRecognition({ abort: false });
    this._startNewRecognitionSession();
    chillicollectivoVoiceDiag('info', 'voice.dictation-resume', 'Diktat nach Zwischen-Einfügen fortgesetzt – Mikro hört weiter zu', {
      reason,
      route: this._currentConversationKey(),
    }, this);
  }

  _cancelMicRestartAfterSend() {
    this._postSendRestartId = (this._postSendRestartId || 0) + 1;
    if (this._micRestartAfterSendTimer) {
      clearTimeout(this._micRestartAfterSendTimer);
      this._micRestartAfterSendTimer = null;
    }
  }

  _schedulePostSendFieldRecovery() {
    if (this._postSendResetTimer) clearTimeout(this._postSendResetTimer);
    const recover = (attempt = 0) => {
      this._refreshStaleFieldRefs();
      this._adoptFreshComposeField();
      if (attempt < 5) {
        this._postSendResetTimer = setTimeout(() => recover(attempt + 1), 260);
      }
    };
    this._postSendResetTimer = setTimeout(() => recover(0), 180);
  }

  _schedulePostSendRecovery() {
    const now = Date.now();
    this._lastPostSendRecoveryAt = now;

    this._dictationReplaceSpan = null;
    if (this.fab) this.fab._previewSelectionOffsets = null;
    this._clearDictationBuffers({ hidePreview: false });
    this._unpinDictationPreview();
    this._schedulePostSendFieldRecovery();
    this._resetMicTipSequence();
    if (this._isMicTipsEnabled()) {
      this._cancelMicTipTimer();
      this._scheduleNextMicTip();
    }
  }

  _restartMicAfterSend(options = {}) {
    const trigger = String(options.trigger || 'manual-send');
    const outCount = Number(options.outCount);
    const outCounted = options.outCounted !== false && Number.isFinite(outCount);

    if (this.currentState !== 'recording') {
      chillicollectivoVoiceDiag('warn', 'voice.post-send-restart-skipped', 'Mic-Neustart nach Senden übersprungen – Aufnahme nicht aktiv', {
        trigger,
        outCounted,
        outCount: Number.isFinite(outCount) ? outCount : null,
        state: this.currentState,
        route: this._currentConversationKey(),
      }, this);
      return false;
    }

    this._cancelMicRestartAfterSend();
    const restartId = ++this._postSendRestartId;
    this._ignoreRestOfCurrentRec = true;
    chillicollectivoVoiceDiag('info', 'voice.post-send-restart-start', 'Mikro-Neustart nach Senden – Erkennung kurz pausiert', {
      trigger,
      outCounted,
      outCount: Number.isFinite(outCount) ? outCount : null,
      restartId,
      route: this._currentConversationKey(),
      ...this._voiceFlowSnapshot(),
    }, this);
    if (outCounted) {
      chillicollectivoToolLog('info', 'voice.out-counted', 'Out gezählt – Mic wird neu gestartet', {
        outCount,
        outTarget: options.outTarget ?? null,
        trigger,
        route: this._currentConversationKey(),
      });
    }
    this.stopBrowserRecognition({ keepMic: false, emitPendingInterim: false });
    this._sessionDictationChars = 0;
    this.recordingStartPosition = null;
    this._ignoreRestOfCurrentRec = false;

    this._micRestartAfterSendTimer = setTimeout(() => {
      this._micRestartAfterSendTimer = null;
      if (restartId !== this._postSendRestartId) return;
      this._resumeMicAfterSendRestart(restartId, { trigger, outCount, outCounted });
    }, this._MIC_RESTART_OFF_MS);
    return true;
  }

  async _resumeMicAfterSendRestart(restartId, meta = {}) {
    if (restartId !== this._postSendRestartId) return;
    if (this.currentState !== 'recording') {
      chillicollectivoVoiceDiag('warn', 'voice.post-send-restart-skipped', 'Mic-Neustart abgebrochen – Aufnahme wurde beendet', {
        trigger: meta.trigger || 'manual-send',
        outCounted: !!meta.outCounted,
        outCount: Number.isFinite(meta.outCount) ? meta.outCount : null,
        state: this.currentState,
        restartId,
        route: this._currentConversationKey(),
      }, this);
      return;
    }

    const startGen = this._recordingStartGeneration;
    const tabId = this.browserTabId;
    const config = {
      voiceConfig: this.browserVoiceConfig || { speechLang: 'de-CH', sharpSAsDoubleS: true },
      correctionRules: this.browserCorrectionRules || [],
      smileyRules: this.browserSmileyRules || [],
    };

    try {
      const result = await this.startBrowserRecognition(config, tabId);
      if (restartId !== this._postSendRestartId || startGen !== this._recordingStartGeneration) return;
      if (!result?.ready) {
        chillicollectivoVoiceDiag('warn', 'voice.post-send-restart-failed', 'Mikro-Neustart nach Senden fehlgeschlagen', {
          error: result?.error || 'Mikrofon-Neustart fehlgeschlagen',
          trigger: meta.trigger || 'manual-send',
          outCount: Number.isFinite(meta.outCount) ? meta.outCount : null,
          route: this._currentConversationKey(),
        }, this);
        this.handleStateChange('error', result?.error || 'Mikrofon-Neustart fehlgeschlagen');
        return;
      }
      this.fab?.setState('recording');
      const adopted = this._adoptFreshComposeField();
      if (this._isMicTipsEnabled()) {
        this._resetMicTipSequence();
        this._cancelMicTipTimer();
        this._scheduleNextMicTip();
      }
      chillicollectivoVoiceDiag('info', 'voice.post-send-restart-done', 'Mikro-Neustart nach Senden abgeschlossen', {
        trigger: meta.trigger || 'manual-send',
        outCounted: !!meta.outCounted,
        outCount: Number.isFinite(meta.outCount) ? meta.outCount : null,
        route: this._currentConversationKey(),
        fieldRebound: adopted,
        recordingStartPos: typeof this.recordingStartPosition === 'number' ? this.recordingStartPosition : null,
        browserListening: this.browserListening,
      }, this);
    } catch (e) {
      if (restartId !== this._postSendRestartId) return;
      chillicollectivoVoiceDiag('warn', 'voice.post-send-restart-failed', 'Mikro-Neustart nach Senden fehlgeschlagen', {
        error: e?.message || 'Mikrofon-Neustart fehlgeschlagen',
        trigger: meta.trigger || 'manual-send',
        outCount: Number.isFinite(meta.outCount) ? meta.outCount : null,
        route: this._currentConversationKey(),
      }, this);
      this.handleStateChange('error', e?.message || 'Mikrofon-Neustart fehlgeschlagen');
    }
  }

  _getDialogTimingMeta(route = this._currentConversationKey()) {
    const now = Date.now();
    const key = String(route || '').trim();
    const enteredAt = key ? this._dialogEnteredAtByRoute.get(key) : null;
    const dialogActiveMs = enteredAt ? now - enteredAt : null;
    return {
      route: key,
      dialogActiveMs,
      dialogActiveSec: dialogActiveMs != null ? Math.round(dialogActiveMs / 1000) : null,
    };
  }

  _formatDialogTimingLabel(meta = {}) {
    if (meta.dialogActiveSec != null) return `${meta.dialogActiveSec}s im Dialog`;
    return '';
  }

  _logSendButtonHint(field, source, extra = {}) {
    const timing = this._getDialogTimingMeta();
    const timingLabel = this._formatDialogTimingLabel(timing);
    const plain = this._fieldPlainText(field || this._resolveTextField());
    chillicollectivoToolLog('info', 'voice.send-button-hint', `Vorschau „Sendeknopf drücken“ – ${timingLabel}`, {
      ...timing,
      timingLabel,
      source,
      previewSubtitle: 'Sendeknopf drücken',
      bodyPreview: plain ? plain.slice(0, 160) : '',
      countdownSec: chillicollectivoReadTorchwoodSendCountdownSeconds(field || this._resolveTextField()),
      sendDisabled: !!chillicollectivoResolveSendButtonForField(field || this._resolveTextField())?.disabled,
      ...extra,
    });
  }

  _logSendButtonPressed(trigger, field, extra = {}) {
    const now = Date.now();
    const timing = this._getDialogTimingMeta();
    const timingLabel = this._formatDialogTimingLabel(timing);
    const target = field || this._resolveTextField();
    const plain = this._fieldPlainText(target);
    const pressedAtLocal = new Date(now).toLocaleString('de-CH', {
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      fractionalSecondDigits: 3,
    });
    chillicollectivoToolLog('info', 'voice.send-button-pressed', `Sendeknopf gedrückt (${trigger}) um ${pressedAtLocal} – ${timingLabel}`, {
      trigger,
      pressedAtIso: new Date(now).toISOString(),
      pressedAtLocal,
      pressedAtMs: now,
      ...timing,
      timingLabel,
      bodyNorm: this._normalizeSubmitBody(plain),
      bodyPreview: plain ? plain.slice(0, 160) : '',
      countdownSec: chillicollectivoReadTorchwoodSendCountdownSeconds(target),
      ...extra,
    });
  }

  _onConversationRouteChanged(nextRoute) {
    const route = String(nextRoute || '').trim();
    if (!route || route === this._lastConversationRoute) return;
    const prev = this._lastConversationRoute;
    this._lastConversationRoute = route;
    this._dialogEnteredAtByRoute.set(route, Date.now());
    if (prev) {
      this._resetPreviewForNewRoute({ route, previousRoute: prev, reason: 'conversation-route' });
    }
    chillicollectivoToolLog('info', 'voice.route-changed', 'Neuer Chat geöffnet', {
      route,
      previousRoute: prev || '',
      dialogActiveSec: 0,
    });
  }

  _resetPreviewForNewRoute(meta = {}) {
    this._unpinDictationPreview();
    this._dictationReplaceSpan = null;
    this._dictationTargetKey = '';
    this._discardedPreviewNorm = '';
    this._discardedPreviewRoute = '';
    this._clearFlushedPreviewGuard('route-change');
    this._lastPreviewBufferNorm = '';
    this._lastPreviewBufferRoute = '';
    this._lastPreviewBufferAt = 0;
    this._lastFinalSentText = '';
    this._lastFinalSentAt = 0;
    this._clearDictationBuffers({ force: true, hidePreview: true });
    if (this.currentState === 'recording') {
      this._recordingConversationRoute = this._currentConversationKey();
    }
    chillicollectivoToolLog('info', 'voice.preview-route-reset', 'Vorschau bei neuem Profil geleert', {
      route: meta.route || this._currentConversationKey(),
      previousRoute: meta.previousRoute || '',
      reason: meta.reason || 'url-change',
      ...this._voiceFlowSnapshot(),
    });
  }

  _scheduleRouteRecovery() {
    if (this._routeResetTimer) clearTimeout(this._routeResetTimer);
    this._routeResetTimer = setTimeout(() => {
      const href = location.href;
      const changed = href !== this._lastKnownHref;
      this._lastKnownHref = href;
      if (changed) {
        this._resetPreviewForNewRoute({ reason: 'url-change' });
      }
      if (changed && typeof chillicollectivoClearProfileDictationLock === 'function') {
        chillicollectivoClearProfileDictationLock(this);
      }
      this._syncRecordingRouteIfNeeded();
      this._onConversationRouteChanged(this._currentConversationKey());
      if (!changed && this.recordingField && this._isUsableTextField(this.recordingField)) {
        const recClass = typeof chillicollectivoClassifyDictationField === 'function'
          ? chillicollectivoClassifyDictationField(this.recordingField)
          : null;
        if (!recClass || recClass.type === 'compose') return;
      }
      this._refreshStaleFieldRefs();
      this.recordingStartPosition = null;
      if (this._dictationPreviewPinned) {
        let attempt = 0;
        const adopt = () => {
          this._refreshStaleFieldRefs();
          const explicitTarget = [this._userTargetField, this.recordingField, this.activeField, document.activeElement]
            .find((candidate) => {
              const classified = typeof chillicollectivoClassifyDictationField === 'function'
                ? chillicollectivoClassifyDictationField(candidate)
                : null;
              return classified && classified.type !== 'compose';
            });
          if (explicitTarget) {
            this._setRecordingTarget(explicitTarget);
            return;
          }
          if (!this._adoptFreshComposeField() && attempt++ < 10) {
            setTimeout(adopt, 250);
          }
        };
        adopt();
        return;
      }
      this._clearDictationBuffers({ hidePreview: Date.now() - this._lastPostSendRecoveryAt > 1800 });
      let attempt = 0;
      const adopt = () => {
        this._refreshStaleFieldRefs();
        if (!this._adoptFreshComposeField() && attempt++ < 10) {
          setTimeout(adopt, 250);
        }
      };
      adopt();
    }, 80);
  }
  setupDefaultKeyboardShortcut() {
    const shouldBlockPreviewInsertKey = (e) => {
      if (this.currentState !== 'recording') return false;
      if (!this._canUsePreviewInsertHotkey()) return false;
      if (!this._previewInsertHotkeyMatchesEvent(e)) return false;
      return !!(this.previewCommitted || this._currentInterimText);
    };

    const shouldHandleTabEnterKey = (e) => {
      if (!this._isTabEnterSendEnabled()) return false;
      if (this.currentState !== 'recording') return false;
      if (!this._tabEnterHotkeyMatchesEvent(e)) return false;
      if (chillicollectivoShouldSkipTabEnterHotkey(this._tabEnterHotkey, e)) return false;
      return true;
    };

    const shouldArmTabEnterOnTab = (e) => {
      if (!this._isTabEnterSendEnabled()) return false;
      if (this.currentState !== 'recording') return false;
      if (e.key !== 'Tab' || e.shiftKey || e.altKey || e.metaKey || e.ctrlKey) return false;
      return true;
    };

    // Verwende useCapture (true), um das Event VOR dem Textfeld abzufangen!
    window.addEventListener('keydown', e => {
      if (shouldHandleTabEnterKey(e)) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        this._handleTabEnterSendHotkey();
        return false;
      }
      if (shouldArmTabEnterOnTab(e)) {
        this._armTabEnterSend();
        return;
      }
      if (this._shouldSendOnEnterKey(e)) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        this._handleTabEnterSendHotkey();
        return false;
      }
      if (shouldBlockPreviewInsertKey(e)) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        this._flushPreviewToChat();
        return false;
      }
      if (e.key === 'Enter' && !e.shiftKey && this.currentState === 'recording' && this._canUsePreviewInsertHotkey() && this._isEnterPreviewInsertHotkey()) {
        const flushText = this._getPreviewFlushText();
        if (flushText) {
          e.preventDefault();
          e.stopPropagation();
          e.stopImmediatePropagation();
          this._flushPreviewToChat({ trigger: 'enter-while-recording' });
          return false;
        }
      }
    }, { capture: true });

    window.addEventListener('keypress', e => {
      if (!shouldBlockPreviewInsertKey(e)) return;
      e.preventDefault();
      e.stopPropagation();
      e.stopImmediatePropagation();
      return false;
    }, { capture: true });

    window.addEventListener('keyup', e => {
      if (!shouldBlockPreviewInsertKey(e)) return;
      e.preventDefault();
      e.stopPropagation();
      e.stopImmediatePropagation();
      return false;
    }, { capture: true });
  }

  _reportVoiceStateToBackground(state) {
    try {
      chrome.runtime.sendMessage({
        type: 'CONTENT_VOICE_STATE',
        state,
        tabId: this.browserTabId,
      }).catch(() => {});
    } catch (_) {}
  }

  _clearLoadingWatchdog() {
    if (this._loadingWatchdogTimer) {
      clearTimeout(this._loadingWatchdogTimer);
      this._loadingWatchdogTimer = null;
    }
  }

  _armLoadingWatchdog() {
    this._clearLoadingWatchdog();
    this._loadingWatchdogTimer = setTimeout(() => {
      this._loadingWatchdogTimer = null;
      if (this.currentState !== 'loading' || !this._pendingVoiceStart) return;
      this._pendingVoiceStart = false;
      this._bumpRecordingStartGeneration('loading-timeout');
      this.handleStateChange('ready');
      chillicollectivoVoiceDiag('warn', 'voice.loading-timeout', 'Mikro-Start hing — zurück auf bereit', {
        route: this._currentConversationKey(),
      }, this);
    }, 12000);
  }

  _finalizeVoiceStop(reason = 'user-toggle') {
    const prev = this.currentState;
    this._clearLoadingWatchdog();
    this._pendingVoiceStart = false;
    this._bumpRecordingStartGeneration(reason);
    this._cancelMicRestartAfterSend();
    this.stopBrowserRecognition({ keepMic: false });
    if (prev === 'recording' || prev === 'loading' || prev === 'error') {
      this.handleStateChange('ready');
    } else {
      this.fab?.setState('ready');
      this._reportVoiceStateToBackground('ready');
    }
    chillicollectivoVoiceDiag('info', 'voice.toggle-stop', 'Mikro durch Nutzer ausgeschaltet', {
      reason,
      previousState: prev,
      route: this._currentConversationKey(),
    }, this);
  }

  _userStopVoice(reason = 'user-toggle') {
    this._finalizeVoiceStop(reason);
  }

  triggerToggle() {
    const now = Date.now();
    const elapsed = now - (this._lastFabToggleAt || 0);
    const isActive = this.currentState === 'recording' || this.currentState === 'loading';
    if (isActive) {
      if (elapsed < 80) return;
    } else if (elapsed < 200) {
      return;
    }
    this._lastFabToggleAt = now;

    const openLicenseFlow = () => {
      chrome.runtime.sendMessage({ type: 'LICENSE_REQUIRED' }, () => {
        if (chrome.runtime.lastError) {
          chillicollectivoOpenLicenseOptions?.();
        }
      });
    };

    const fallbackToggle = () => {
      chrome.runtime.sendMessage({ type: 'GET_LICENSE_STATE', revalidate: true }, (licenseResponse) => {
        if (licenseResponse?.state?.access !== 'granted') {
          this._licenseAccessGranted = false;
          this.handleStateChange('locked', licenseResponse?.state?.message || 'Keine gültige Lizenz');
          openLicenseFlow();
          return;
        }
        this._licenseAccessGranted = true;
        this._startMicFromUserClick('fallback');
      });
    };

    if (this.currentState === 'recording' || this.currentState === 'loading') {
      this._userStopVoice('fab-toggle');
      return;
    }

    if (this.currentState === 'locked') {
      if (this._licenseAccessGranted) {
        this._startMicFromUserClick('locked-granted');
        return;
      }
      chrome.runtime.sendMessage({ type: 'GET_LICENSE_STATE', revalidate: true }, (licenseResponse) => {
        if (licenseResponse?.state?.access === 'granted') {
          this._licenseAccessGranted = true;
          this._startMicFromUserClick('locked-check');
          return;
        }
        this._licenseAccessGranted = false;
        openLicenseFlow();
      });
      return;
    }

    const previousState = this.currentState;
    const startStateForBackground = previousState === 'error'
      ? 'error'
      : (previousState === 'locked' ? 'locked' : 'ready');
    this._pendingVoiceStart = true;
    this.handleStateChange('loading');
    this._armLoadingWatchdog();

    try {
      chillicollectivoVoiceDiag('info', 'voice.toggle-start', 'Mikro durch Nutzer eingeschaltet', {
        route: this._currentConversationKey(),
        via: 'background',
        previousState,
      }, this);
      chrome.runtime.sendMessage({ type: 'ICON_CLICKED', currentState: startStateForBackground }, () => {
        if (chrome.runtime.lastError) {
          console.warn('Background nicht erreichbar – lokaler Voice-Toggle:', chrome.runtime.lastError.message);
          this._pendingVoiceStart = false;
          this.handleStateChange('ready');
          fallbackToggle();
        }
      });
    } catch (error) {
      console.warn('Cannot communicate with extension - local voice toggle');
      this._pendingVoiceStart = false;
      this.handleStateChange('ready');
      fallbackToggle();
    }
  }

  setupMessageHandlers() {
    chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
      switch (message.type) {
        case 'STATE_CHANGE':
          if (message.state === 'ready'
            && this.currentState === 'recording'
            && this.browserListening) {
            sendResponse({ success: true, ignored: true });
            break;
          }
          this.handleStateChange(message.state, message.errorMessage);
          sendResponse({ success: true });
          break;

        case 'GET_VOICE_STATE':
          sendResponse({ state: this.currentState });
          break;

        case 'INSERT_TEXT':
          this.insertText(message.text, message.isFinal);
          sendResponse({ success: true });
          break;

        case 'SUBMIT_FORM':
          if (message.viaTabEnter) this.handleTabEnterSend();
          else this.submitCurrentForm();
          sendResponse({ success: true });
          break;

        case 'GET_ACTIVE_FIELD':
          sendResponse({ hasActiveField: !!this.activeField, fieldType: this.activeField?.tagName });
          break;

        case 'START_BROWSER_RECORDING':
          this.startBrowserRecognition(message.config, message.tabId)
            .then((result) => sendResponse(result || { success: true, ready: true }))
            .catch((e) => sendResponse({ success: false, ready: false, error: e?.message || 'start-failed' }));
          return true;

        case 'STOP_BROWSER_RECORDING':
          this._finalizeVoiceStop('stop-message');
          sendResponse({ success: true });
          break;

        case 'LICENSE_STATE':
          this._applyLicenseAccessState(message.state || { access: 'denied' });
          sendResponse({ success: true });
          break;
      }
      return false;
    });
    window.addEventListener('detect_voice_extension', () => {
      try {
        chrome.runtime.sendMessage({ type: 'app_ping' }, response => {
          if (chrome.runtime.lastError) return;
          window.dispatchEvent(new CustomEvent('my_extension_response', { detail: response || null }));
        });
      } catch (_) {}
    });
    window.addEventListener('chillicollectivo_voice_hotkey_update', (e) => {
      try {
        const raw = e.detail;
        const hotkey = raw ? (typeof raw === 'string' ? JSON.parse(raw) : raw) : null;
        if (hotkey?.key) {
          chrome.storage.local.set({ chillicollectivoVoiceHotkey: hotkey });
        } else {
          chrome.storage.local.remove('chillicollectivoVoiceHotkey');
        }
        if (this.fab) this.fab.hotkey = hotkey?.key ? hotkey : null;
      } catch (_) {}
    });
  }

  _isDuplicateFinal(text) {
    return this._resolveFinalDelta(text).isDuplicate;
  }

  _markFinalSent(text) {
    this._lastFinalSentText = String(text || '').replace(/\s+/g, ' ').trim();
    this._lastFinalSentAt = Date.now();
  }

  _emitFinalTranscription(text, confidence = null) {
    const processed = typeof chillicollectivoProcessSpeechText === 'function'
      ? chillicollectivoProcessSpeechText(text)
      : String(text || '').replace(/\s+/g, ' ').trim();
    const resolved = this._resolveFinalDelta(processed);
    const trimmed = resolved.delta;
    if (!trimmed || resolved.isDuplicate) {
      chillicollectivoVoiceDiag('info', 'voice.final-emit-skipped', 'Voice-Final nicht gesendet (Duplikat/leer)', {
        rawSnippet: String(text || '').slice(0, 120),
        processedSnippet: processed.slice(0, 120),
        rawLen: String(text || '').trim().length,
        isDuplicate: resolved.isDuplicate,
        deltaLen: trimmed.length,
        lastFinalSentSnippet: String(this._lastFinalSentText || '').slice(0, 80),
      }, this);
      return false;
    }
    if (trimmed !== processed) {
      chillicollectivoVoiceDiag('info', 'voice.final-emit-delta', 'Voice-Final als Delta gesendet (Prefix abgezogen)', {
        rawSnippet: String(text || '').slice(0, 120),
        processedSnippet: processed.slice(0, 120),
        deltaSnippet: trimmed.slice(0, 120),
        rawLen: String(text || '').trim().length,
        deltaLen: trimmed.length,
      }, this);
    } else {
      chillicollectivoVoiceDiag('info', 'voice.final-emit-sent', 'Voice-Final an Background gesendet', {
        textSnippet: trimmed.slice(0, 120),
        textLen: trimmed.length,
        confidence,
      }, this);
    }
    this._lastFinalSentText = resolved.accumulated || trimmed;
    this._lastFinalSentAt = Date.now();
    this._lastInterim = '';
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    if (this.browserListening) {
      this.insertText(trimmed, true);
      chrome.runtime.sendMessage({
        type: 'TRANSCRIPTION',
        text: trimmed,
        isFinal: true,
        tabId: this.browserTabId,
        localPreviewHandled: true,
        ...(confidence != null ? { confidence } : {}),
      }, () => {
        if (chrome.runtime.lastError) {
          console.warn('[CHILLICOLLECTIVO] Background offline – Text lokal eingefügt');
        }
      });
      return true;
    }
    chrome.runtime.sendMessage({
      type: 'TRANSCRIPTION',
      text: trimmed,
      isFinal: true,
      tabId: this.browserTabId,
      ...(confidence != null ? { confidence } : {}),
    }, () => {
      if (chrome.runtime.lastError) {
        console.warn('[CHILLICOLLECTIVO] Background offline – Text lokal einfügen');
        this.insertText(trimmed, true, false);
      }
    });
    return true;
  }

  _cancelMicWarmRelease() {
    if (this._micWarmReleaseTimer) {
      clearTimeout(this._micWarmReleaseTimer);
      this._micWarmReleaseTimer = null;
    }
  }

  _scheduleMicWarmRelease() {
    this._cancelMicWarmRelease();
    this._micWarmReleaseTimer = setTimeout(() => {
      this._micWarmReleaseTimer = null;
      if (!this.browserListening) this._releaseMicStream();
    }, this._MIC_WARM_IDLE_MS);
  }

  _releaseMicStream() {
    if (this.browserMicStream) {
      this.browserMicStream.getTracks().forEach((t) => t.stop());
      this.browserMicStream = null;
    }
  }

  async _ensureMicStream(tabId) {
    const tracks = this.browserMicStream?.getAudioTracks?.() || [];
    if (tracks.length && tracks[0].readyState === 'live') {
      this._cancelMicWarmRelease();
      return true;
    }
    this._releaseMicStream();
    try {
      this.browserMicStream = await navigator.mediaDevices.getUserMedia({
        audio: {
          echoCancellation: true,
          noiseSuppression: true,
          autoGainControl: false,
          sampleRate: 48000,
          channelCount: 1,
        },
      });
      this._cancelMicWarmRelease();
      return true;
    } catch (e) {
      chrome.runtime.sendMessage({
        type: 'RECORDING_ERROR',
        error: 'Mikrofon-Zugriff verweigert. Bitte in den Seiteneinstellungen (Schloss-Symbol) erlauben.',
        tabId,
      }).catch(() => {});
      return false;
    }
  }

  _stopRecognitionOnly() {
    this.browserListening = false;
    this._ignoreRestOfCurrentRec = false;
    this._recRapidDeathCount = 0;

    if (this._recWatchdogTimer) {
      clearTimeout(this._recWatchdogTimer);
      this._recWatchdogTimer = null;
    }
    if (this._recStartRetryTimer) {
      clearTimeout(this._recStartRetryTimer);
      this._recStartRetryTimer = null;
    }

    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    this._stickyInterimText = '';
    this._clearStickyInterimCommit();
    if (this._recognitionRestartTimer) {
      clearTimeout(this._recognitionRestartTimer);
      this._recognitionRestartTimer = null;
    }
    this._clearSilenceKeepalive();

    this._disposeBrowserRecognition({ abort: true });
  }

  async startBrowserRecognition(config, tabId) {
    const startGen = this._recordingStartGeneration;
    this._stopRecognitionOnly();
    this._pauseMicLevelMonitor(false);

    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) {
      chrome.runtime.sendMessage({
        type: 'RECORDING_ERROR',
        error: 'Web Speech API nicht verfügbar. Bitte nutze Chrome/Edge.',
        tabId: tabId,
      }).catch(() => {});
      return { success: false, ready: false, error: 'no-speech-api' };
    }

    this.browserTabId = tabId;
    this.browserVoiceConfig = {
      ...(config?.voiceConfig || {}),
      speechLang: 'de-CH',
      sharpSAsDoubleS: true,
    };
    this.browserCorrectionRules = config?.correctionRules || [];
    this.browserSmileyRules = config?.smileyRules || [];
    if (typeof chillicollectivoSetVoiceTextRules === 'function') {
      chillicollectivoSetVoiceTextRules({
        correctionRules: this.browserCorrectionRules,
        smileyRules: this.browserSmileyRules,
      });
    }

    const micOk = await this._ensureMicStream(tabId);
    if (startGen !== this._recordingStartGeneration) {
      return { success: false, ready: false, error: 'start-cancelled' };
    }
    if (!micOk) return { success: false, ready: false, error: 'mic-denied' };

    this._startMicLevelMonitor(this.browserMicStream);

    this._SR = SR;
    this.browserListening = true;
    this._browserPermissionError = false;
    this._lastInterim = '';
    this._lastNtbPreviewPhrase = '';
    this._lastNtbPreviewPhraseAt = 0;

    this._startNewRecognitionSession();
    return { success: true, ready: true };
  }

  _bumpRecordingStartGeneration(reason = '') {
    this._recordingStartGeneration = (this._recordingStartGeneration || 0) + 1;
    if (reason) {
      chillicollectivoVoiceDiag('info', 'voice.rec-start-generation-bumped', 'Laufender Mikro-Start abgebrochen', {
        reason,
        generation: this._recordingStartGeneration,
      }, this);
    }
  }

  _maybeRefreshMicStreamForQuality() {
    if (!this.browserListening || !this.browserMicStream) return;
    const now = Date.now();
    if (this._micQualityRefreshAt && now - this._micQualityRefreshAt < 120000) return;
    const captureAge = now - (this._captureStartedAt || this._recSessionStartedAt || now);
    if (captureAge < 90000) return;
    this._micQualityRefreshAt = now;
    const tabId = this.browserTabId;
    this._releaseMicStream();
    void this._ensureMicStream(tabId).then((ok) => {
      if (!ok || !this.browserListening || !this.browserMicStream) return;
      this._startMicLevelMonitor(this.browserMicStream);
      chillicollectivoVoiceDiag('info', 'voice.mic-stream-refreshed', 'Mikrofon-Stream für bessere Erkennungsqualität erneuert', {
        captureAgeMs: captureAge,
      }, this);
    });
  }

  _scheduleRecognitionWatchdog() {
    if (this._recWatchdogTimer) {
      clearTimeout(this._recWatchdogTimer);
      this._recWatchdogTimer = null;
    }
    if (!this.browserListening || !this._isCaptureActive()) return;
    this._recWatchdogTimer = setTimeout(() => {
      this._recWatchdogTimer = null;
      if (!this.browserListening || !this._isCaptureActive()) return;
      chillicollectivoToolLog('info', 'voice.rec-session-refresh', 'Spracherkennung proaktiv erneuert (Session-Timeout)', {
        sessionMs: Date.now() - (this._recSessionStartedAt || 0),
      });
      this._maybeRefreshMicStreamForQuality();
      this._restartRecognitionAfterCommand();
    }, this._REC_SESSION_MAX_MS);
  }

  _startRecognitionEngine(rec, attempt = 0) {
    try {
      rec.start();
      this._recSessionStartedAt = Date.now();
      if (attempt > 0) {
        chillicollectivoVoiceDiag('info', 'voice.rec-start-ok', 'Spracherkennung nach Retry gestartet', {
          attempt,
        }, this);
      }
      return true;
    } catch (e) {
      const msg = String(e?.message || e || '');
      const retriable = /already started|not allowed|invalid state/i.test(msg) || attempt < 4;
      if (!retriable || !this.browserListening) {
        console.error('SpeechRecognition start error:', e);
        chillicollectivoVoiceDiag('error', 'voice.rec-start-failed', 'Spracherkennung start() fehlgeschlagen', {
          attempt,
          error: msg,
          retriable,
        }, this);
        if (this.currentState === 'recording') {
          this._scheduleRecognitionRestart(280);
        } else {
          this.browserListening = false;
          chrome.runtime.sendMessage({ type: 'RECORDING_ERROR', error: msg, tabId: this.browserTabId }).catch(() => {});
        }
        return false;
      }
      const delay = 90 + (attempt * 70);
      chillicollectivoVoiceDiag('warn', 'voice.rec-start-retry', 'Spracherkennung start() wird erneut versucht', {
        attempt: attempt + 1,
        delayMs: delay,
        error: msg,
      }, this);
      if (this._recStartRetryTimer) clearTimeout(this._recStartRetryTimer);
      this._recStartRetryTimer = setTimeout(() => {
        this._recStartRetryTimer = null;
        if (!this.browserListening) return;
        this._startRecognitionEngine(rec, attempt + 1);
      }, delay);
      return true;
    }
  }

  _sendTranscription(text, isFinal, confidence = null) {
    const str = String(text || '');
    const payload = {
      type: 'TRANSCRIPTION',
      text: str,
      isFinal: !!isFinal,
      tabId: this.browserTabId,
    };
    if (confidence != null) payload.confidence = confidence;

    // Während Mic an: Text sofort lokal einfügen — kein Warten auf Background-Roundtrip.
    if (this.browserListening) {
      this.insertText(str, !!isFinal);
      payload.localPreviewHandled = true;
      chrome.runtime.sendMessage(payload, () => {});
      return;
    }

    chrome.runtime.sendMessage(payload, () => {
      if (chrome.runtime.lastError && isFinal) {
        this.insertText(str, true);
      }
    });
  }

  _simplePreviewAppendFinal(text) {
    const clean = typeof chillicollectivoProcessSpeechText === 'function'
      ? chillicollectivoProcessSpeechText(text)
      : String(text || '').replace(/\s+/g, ' ').trim();
    if (!clean) return;
    const next = this._appendEnterModeFinalText(clean);
    const current = String(this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    if (next === current) {
      this._currentInterimText = '';
      this._lastInterim = '';
      this._refreshEnterModeLivePreview({ insertHint: this._getPreviewInsertHint() });
      return;
    }
    this.previewCommitted = next;
    this._currentInterimText = '';
    this._lastInterim = '';
    this._lastFinalSentText = '';
    this._lastFinalSentAt = 0;
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    this._stopFabMotion();
    this._refreshEnterModeLivePreview({ insertHint: this._getPreviewInsertHint() });
    this._pinDictationPreview?.();
    if (this.fab) {
      this.fab._lastRenderedCommitted = '';
      this.fab._lastRenderedInterim = this.previewCommitted;
    }
  }

  _scheduleRecognitionRestart(delayMs = null) {
    if (!this.browserListening) return;
    if (this._isContinuingDictationSession()) {
      this._lastFinalSentText = '';
      this._lastFinalSentAt = 0;
    }
    const delay = Number.isFinite(delayMs) ? delayMs : this._REC_RESTART_DELAY_MS;
    if (this._recognitionRestartTimer) clearTimeout(this._recognitionRestartTimer);
    this._recognitionRestartTimer = setTimeout(() => {
      this._recognitionRestartTimer = null;
      if (!this.browserListening || this._browserPermissionError) return;
      try {
        this._ignoreRestOfCurrentRec = false;
        this._startNewRecognitionSession();
      } catch (e) {
        console.error('Neustart der Browser-Erkennung fehlgeschlagen:', e);
        this._scheduleRecognitionRestart(Math.min((delay || 0) + 200, 800));
      }
    }, delay);
  }

  // NTB-Stil: neue SpeechRecognition-Instanz pro Session, onend startet sofort neu.
  _startNewRecognitionSession() {
    if (!this._SR || !this.browserListening) return;

    if (this._recognitionRestartTimer) {
      clearTimeout(this._recognitionRestartTimer);
      this._recognitionRestartTimer = null;
    }
    if (this._recStartRetryTimer) {
      clearTimeout(this._recStartRetryTimer);
      this._recStartRetryTimer = null;
    }
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }

    // Alte Session beenden, sonst bleibt _ignoreRestOfCurrentRec hängen wenn onend
    // der Vorgänger-Session wegen sessionId-Mismatch früh abbricht.
    const prevRec = this.browserRecognition;
    if (prevRec) {
      this._recSessionId = (this._recSessionId || 0) + 1;
      this._detachRecognitionHandlers(prevRec);
      try { prevRec.abort?.(); } catch (_) {}
      try { prevRec.stop(); } catch (_) {}
      if (this.browserRecognition === prevRec) this.browserRecognition = null;
    }
    this._ignoreRestOfCurrentRec = false;
    this._lastFinalSentText = '';
    this._lastFinalSentAt = 0;
    const pendingInterim = String(this._lastInterim || this._stickyInterimText || '').trim();
    const pendingAge = this._lastRecResultAt ? Date.now() - this._lastRecResultAt : Infinity;
    const preservePendingInterim = pendingInterim && pendingAge < 1200;
    if (!preservePendingInterim) {
      this._lastInterim = '';
      this._stickyInterimText = '';
    } else {
      this._lastInterim = pendingInterim;
      this._stickyInterimText = pendingInterim;
    }
    this._clearStickyInterimCommit();

    const rec = new this._SR();
    const sessionId = (this._recSessionId = (this._recSessionId || 0) + 1);
    rec.lang = typeof chillicollectivoGetAgentSpeechLang === 'function'
      ? chillicollectivoGetAgentSpeechLang()
      : (this.browserVoiceConfig.speechLang || 'de-CH');
    rec.continuous = true;
    rec.interimResults = true;
    this.browserRecognition = rec;

    rec.onresult = (event) => {
      if (sessionId !== this._recSessionId || rec !== this.browserRecognition) return;
      if (!this.browserListening || this._ignoreRestOfCurrentRec) return;

      this._lastRecResultAt = Date.now();
      this._scheduleSilenceKeepalive();

      if (this._browserSilenceTimer) {
        clearTimeout(this._browserSilenceTimer);
        this._browserSilenceTimer = null;
      }

      let interim = '';
      let finalChunk = '';
      let totalConfidence = 0;
      let finalCount = 0;

      for (let i = event.resultIndex; i < event.results.length; i++) {
        const result = event.results[i];
        const transcript = result[0].transcript;
        if (result.isFinal) {
          finalChunk += transcript;
          totalConfidence += (result[0].confidence || 0.9);
          finalCount++;
        } else {
          interim += transcript;
        }
      }

      const prevInterim = this._lastInterim;
      this._lastInterim = interim.trim();

      const finalText = finalChunk.trim();
      if (finalText) {
        if (this._shouldDropRecognitionResult(finalText)) {
          this._lastInterim = '';
          this._stickyInterimText = '';
          return;
        }
        this._lastInterim = '';
        this._stickyInterimText = '';
        this._clearStickyInterimCommit();
        if (this._browserSilenceTimer) {
          clearTimeout(this._browserSilenceTimer);
          this._browserSilenceTimer = null;
        }
        const avgConfidence = finalCount > 0 ? totalConfidence / finalCount : 0.9;
        this._markFinalSent(finalText);
        this._sendTranscription(finalText, true, avgConfidence);
      }

      if (this._lastInterim) {
        if (this._shouldDropRecognitionResult(this._lastInterim)) return;
        this._stickyInterimText = this._lastInterim;
        this._clearStickyInterimCommit();
        this._sendTranscription(this._lastInterim, false);
        this._scheduleBrowserSilenceCommit(this._lastInterim);
      } else if (!finalText) {
        if (!this._isLivePreviewBufferMode() && (prevInterim || this._stickyInterimText)) {
          this._scheduleStickyInterimDropCommit(this._stickyInterimText || prevInterim);
        }
        this._sendTranscription('', false);
      }
    };

    rec.onstart = () => {
      if (sessionId !== this._recSessionId || rec !== this.browserRecognition) return;
      this._recognitionListening = true;
      this._recListeningSince = Date.now();
      chillicollectivoVoiceDiag('info', 'voice.rec-onstart', 'Spracherkennung hört aktiv zu', {
        sessionId,
      }, this);
    };

    rec.onerror = (event) => {
      if (sessionId !== this._recSessionId || rec !== this.browserRecognition) return;
      if (event.error === 'no-speech') {
        queueMicrotask(() => {
          if (!this.browserListening || this._browserPermissionError) return;
          if (rec !== this.browserRecognition) return;
          this._ignoreRestOfCurrentRec = false;
          try { rec.stop(); } catch (_) {}
          if (!this.browserRecognition) this._startNewRecognitionSession();
        });
        return;
      }
      const benign = new Set(['aborted']);
      if (benign.has(event.error)) return;
      console.error('SpeechRecognition error:', event.error);
      if (event.error === 'not-allowed') {
        this.browserListening = false;
        this._browserPermissionError = true;
        chrome.runtime.sendMessage({
          type: 'RECORDING_ERROR',
          error: 'Mikrofon-Zugriff verweigert. Bitte in den Seiteneinstellungen (Schloss-Symbol) erlauben.',
          tabId: this.browserTabId,
        }).catch(() => {});
      } else if (event.error === 'service-not-allowed') {
        this.browserListening = false;
        this._browserPermissionError = true;
        chrome.runtime.sendMessage({
          type: 'RECORDING_ERROR',
          error: 'Sprachdienst nicht verfügbar. Bitte Seite neu laden oder Chrome neu starten.',
          tabId: this.browserTabId,
        }).catch(() => {});
      }
    };

    rec.onend = () => {
      if (sessionId !== this._recSessionId || rec !== this.browserRecognition) {
        this._ignoreRestOfCurrentRec = false;
        return;
      }
      this._recognitionListening = false;
      if (!this.browserListening || this._browserPermissionError) {
        this._browserPermissionError = false;
        return;
      }
      const tabId = this.browserTabId;
      queueMicrotask(() => {
        if (!this.browserListening || this._browserPermissionError) return;
        try {
          this._ignoreRestOfCurrentRec = false;
          this._startNewRecognitionSession();
          this._sendTranscription('', false);
        } catch (e) {
          console.error('Neustart der Browser-Erkennung fehlgeschlagen:', e);
        }
      });
      this._browserPermissionError = false;
    };

    try {
      rec.start();
      this._recSessionStartedAt = Date.now();
      this._recognitionListening = true;
    } catch (e) {
      console.error('SpeechRecognition start error:', e);
      if (this.browserListening) {
        this._scheduleRecognitionRestart(120);
      } else {
        chrome.runtime.sendMessage({ type: 'RECORDING_ERROR', error: String(e?.message || e), tabId: this.browserTabId }).catch(() => {});
      }
    }
  }

  _resetMicDiag() {
    this._clearMicGlowGapCheck();
    this._micDiag = {
      speaking: false,
      glowStartedAt: 0,
      peakLevel: 0,
      lastRecAt: 0,
      lastPreviewAt: 0,
      lastGlowOnLogAt: 0,
      lastGlowOffLogAt: 0,
      lastAnomalyLogAt: 0,
      lastInterimLogAt: 0,
      glowCheckTimer: null,
    };
  }

  _micGlowStrengthLabel(level) {
    const v = Number(level) || 0;
    if (v <= 0.02) return 'aus';
    if (v <= 0.06) return 'schwach';
    if (v <= 0.18) return 'mittel';
    if (v <= 0.42) return 'stark';
    return 'blaze';
  }

  _micDiagSnapshot(extra = {}) {
    return {
      micLevel: Number((this._smoothedMicLevel || 0).toFixed(3)),
      glowStrength: this._micGlowStrengthLabel(this._smoothedMicLevel),
      voiceHot: (this._smoothedMicLevel || 0) > 0.06,
      voiceBlaze: (this._smoothedMicLevel || 0) > 0.18,
      speaking: !!this._micSpeakingActive,
      browserListening: !!this.browserListening,
      hasRecognition: !!this.browserRecognition,
      previewVisible: !!this.fab?._previewVisible,
      previewEnabled: this.fab?.effects?.previewEnabled !== false,
      interimLen: (this._currentInterimText || '').trim().length,
      committedLen: (this.previewCommitted || '').trim().length,
      state: this.currentState,
      ...extra,
    };
  }

  _composedPreviewForSnapshot() {
    const committed = String(this.previewCommitted || '').replace(/\s+/g, ' ').trim();
    const interim = String(this._currentInterimText || '').replace(/\s+/g, ' ').trim();
    if (!interim) return committed;
    if (!committed) return interim;
    if (interim.startsWith(committed) || committed.startsWith(interim)) {
      return interim.length >= committed.length ? interim : committed;
    }
    return `${committed} ${interim}`.replace(/\s+/g, ' ').trim();
  }

  _voiceFlowSnapshot(extra = {}) {
    const span = this._dictationReplaceSpan;
    const flushedAgeMs = this._flushedPreviewAt ? Date.now() - this._flushedPreviewAt : null;
    const lastInsertedAgeMs = this._lastInsertedTime ? Date.now() - this._lastInsertedTime : null;
    const lastFinalSentAgeMs = this._lastFinalSentAt ? Date.now() - this._lastFinalSentAt : null;
    const composedPreview = this._composedPreviewForSnapshot();
    const flow = this._previewFlowState?.() || {};
    const enterToInsert = !!this._isPreviewEnterToInsertEnabled?.();
    const clickToInsert = this._isAgentClickToInsertMode();
    const insertMode = enterToInsert
      ? 'enter-to-preview'
      : (clickToInsert ? 'click-to-preview' : 'direct-field');
    return {
      ...this._micDiagSnapshot(),
      ...flow,
      route: this._currentConversationKey?.() || '',
      previewPinned: !!this._dictationPreviewPinned,
      previewEditing: !!this.fab?._isPreviewBeingEdited?.(),
      enterToInsert,
      clickToInsert,
      previewUi: !!this._isPreviewUiEnabled?.(),
      insertMode,
      savedFieldSpan: this._lastActiveFieldReplaceSpan ? {
        start: this._lastActiveFieldReplaceSpan.anchorStart,
        end: this._lastActiveFieldReplaceSpan.anchorEnd,
      } : null,
      committedSnippet: String(this.previewCommitted || '').slice(0, 100),
      interimSnippet: String(this._currentInterimText || '').slice(0, 80),
      replaceSpan: span ? {
        target: span.target,
        start: span.anchorStart,
        end: span.anchorEnd,
        spanLen: Math.max(0, (span.anchorEnd ?? 0) - (span.anchorStart ?? 0)),
      } : null,
      selectionOffsets: this.fab?._previewSelectionOffsets || null,
      flushedPreviewAgeMs: flushedAgeMs,
      hasFlushedNorm: !!this._flushedPreviewNorm,
      recSessionAgeMs: this._recSessionStartedAt ? Date.now() - this._recSessionStartedAt : null,
      lastInsertedSnippet: String(this._lastInsertedText || '').slice(0, 80),
      lastInsertedAgeMs,
      lastFinalSentSnippet: String(this._lastFinalSentText || '').slice(0, 80),
      lastFinalSentAgeMs,
      ignoreRestOfCurrentRec: !!this._ignoreRestOfCurrentRec,
      postFlushIgnoreActive: !!this._postFlushIgnoreTimer,
      composedLen: composedPreview.length,
      composedSnippet: composedPreview.slice(0, 100),
      recordingStartPos: typeof this.recordingStartPosition === 'number'
        ? this.recordingStartPosition
        : (this.recordingStartPosition?.cloneRange ? 'contenteditable-range' : null),
      recordingSelectionEnd: typeof this.recordingSelectionEnd === 'number' ? this.recordingSelectionEnd : null,
      ...extra,
    };
  }

  _fieldInsertContext(field) {
    if (!field) return { fieldPresent: false };
    const plain = this._fieldPlainText(field);
    const ctx = {
      fieldPresent: true,
      fieldType: field.isContentEditable ? 'contenteditable' : (field.tagName || 'input'),
      fieldLen: plain.length,
      fieldHead: plain.slice(0, 100),
      fieldTail: plain.slice(-100),
    };
    if (!field.isContentEditable) {
      ctx.selectionStart = field.selectionStart ?? null;
      ctx.selectionEnd = field.selectionEnd ?? null;
      ctx.hasSelection = field.selectionStart !== field.selectionEnd;
    }
    return ctx;
  }

  _logVoiceInsertFlow(level, source, message, detail = {}, field = null) {
    const fieldCtx = field ? this._fieldInsertContext(field) : {};
    chillicollectivoVoiceDiag(level, source, message, { ...fieldCtx, ...detail }, this);
  }

  _noteMicRecognition(kind, text) {
    if (!this._micDiag) this._resetMicDiag();
    this._micDiag.lastRecAt = Date.now();
    const snippet = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 120);
    if (!snippet) return;
    if (kind === 'interim') {
      if (Date.now() - (this._micDiag.lastInterimLogAt || 0) < 2000) return;
      this._micDiag.lastInterimLogAt = Date.now();
    }
    chillicollectivoToolLog(
      'info',
      'voice.mic-recognized',
      kind === 'final' ? 'Sprache erkannt (final)' : 'Sprache erkannt (live)',
      {
        kind,
        textLen: snippet.length,
        ...this._micDiagSnapshot(),
      },
    );
  }

  _noteMicPreviewUpdate(text) {
    if (!this._micDiag) this._resetMicDiag();
    const snippet = String(text || '').replace(/\s+/g, ' ').trim();
    if (!snippet) return;
    this._micDiag.lastPreviewAt = Date.now();
  }

  _clearMicGlowGapCheck() {
    if (this._micDiag?.glowCheckTimer) {
      clearTimeout(this._micDiag.glowCheckTimer);
      this._micDiag.glowCheckTimer = null;
    }
  }

  _scheduleMicGlowGapCheck() {
    this._clearMicGlowGapCheck();
    if (!this._micDiag) this._resetMicDiag();
    this._micDiag.glowCheckTimer = setTimeout(() => {
      if (this._micDiag) this._micDiag.glowCheckTimer = null;
      this._checkMicGlowWithoutSpeech();
    }, CHILLICOLLECTIVO_MIC_DIAG_GLOW_GAP_MS);
  }

  _checkMicGlowWithoutSpeech() {
    if (!this._micDiag?.speaking || !this._micSpeakingActive) return;
    const now = Date.now();
    if (now - (this._micDiag.lastAnomalyLogAt || 0) < CHILLICOLLECTIVO_MIC_DIAG_LOG_MIN_MS) {
      if (this._micSpeakingActive) this._scheduleMicGlowGapCheck();
      return;
    }

    const glowSince = this._micDiag.glowStartedAt || now;
    const recSince = this._micDiag.lastRecAt || 0;
    const previewSince = this._micDiag.lastPreviewAt || 0;
    const noRecSinceGlow = recSince < glowSince;
    const noPreview = !this.fab?._previewVisible && !(this._currentInterimText || '').trim();

    if (noRecSinceGlow || noPreview) {
      this._micDiag.lastAnomalyLogAt = now;
      chillicollectivoToolLog(
        'warn',
        'voice.mic-glow-no-speech',
        'Mikro glimmt, aber keine Spracherkennung/Vorschau',
        this._micDiagSnapshot({
          glowMs: now - glowSince,
          seitLetzterErkennungMs: recSince ? now - recSince : null,
          seitLetzterVorschauMs: previewSince ? now - previewSince : null,
          keinOnResultSeitGlow: noRecSinceGlow,
          keineVorschau: noPreview,
        }),
      );
    }

    if (this._micSpeakingActive) this._scheduleMicGlowGapCheck();
  }

  _onMicGlowStateChange(active, level = 0) {
    if (!this._micDiag) this._resetMicDiag();
    const diag = this._micDiag;
    const now = Date.now();
    const lvl = Math.max(0, Math.min(1, Number(level) || 0));

    if (active && !diag.speaking) {
      diag.speaking = true;
      diag.glowStartedAt = now;
      diag.peakLevel = lvl;
      if (now - diag.lastGlowOnLogAt >= 800) {
        diag.lastGlowOnLogAt = now;
        chillicollectivoToolLog(
          'info',
          'voice.mic-glow-on',
          'Mikro leuchtet – Stimme/Störgeräusch erkannt',
          this._micDiagSnapshot({ rawLevel: Number(lvl.toFixed(3)) }),
        );
      }
      this._scheduleMicGlowGapCheck();
      return;
    }

    if (active) {
      diag.peakLevel = Math.max(diag.peakLevel || 0, lvl);
      return;
    }

    if (!diag.speaking) return;
    diag.speaking = false;
    this._clearMicGlowGapCheck();
    if (now - diag.lastGlowOffLogAt >= 500) {
      diag.lastGlowOffLogAt = now;
      const durationMs = diag.glowStartedAt ? now - diag.glowStartedAt : 0;
      chillicollectivoToolLog(
        'info',
        'voice.mic-glow-off',
        'Mikro-Glimmer aus',
        this._micDiagSnapshot({
          durationMs,
          peakLevel: Number((diag.peakLevel || 0).toFixed(3)),
          peakStrength: this._micGlowStrengthLabel(diag.peakLevel),
        }),
      );
    }
    diag.peakLevel = 0;
    diag.glowStartedAt = 0;
  }

  _startMicLevelMonitor(stream) {
    if (!stream) {
      this._pauseMicLevelMonitor(false);
      return;
    }

    if (this._micAnalyser && this._micAudioCtx && this._micLevelStream === stream) {
      if (this._micAudioCtx.state === 'suspended') this._micAudioCtx.resume().catch(() => {});
      if (!this._micLevelRAF) this._runMicLevelTick();
      return;
    }

    this._stopMicLevelMonitor(false);
    this._micLevelStream = stream;

    try {
      const ctx = new (window.AudioContext || window.webkitAudioContext)();
      const source = ctx.createMediaStreamSource(stream);
      const analyser = ctx.createAnalyser();
      analyser.fftSize = 256;
      analyser.smoothingTimeConstant = 0.82;
      source.connect(analyser);

      this._micAudioCtx = ctx;
      this._micAnalyser = analyser;
      this._smoothedMicLevel = 0;
      this._micSpeakingActive = false;
      this._micSpeechOnStreak = 0;

      if (ctx.state === 'suspended') ctx.resume().catch(() => {});
      this._runMicLevelTick();
    } catch (e) {
      console.warn('[CHILLICOLLECTIVO] Mic level monitor:', e);
    }
  }

  _runMicLevelTick() {
    if (!this._micAnalyser) return;
    const analyser = this._micAnalyser;
    const samples = new Uint8Array(analyser.fftSize);
    const SPEECH_ON = 0.10;
    const SPEECH_OFF = 0.07;
    const SPEECH_ON_STREAK = 2;

    const tick = () => {
      if (!this._micAnalyser || !this.browserListening || this.currentState !== 'recording') return;

      analyser.getByteTimeDomainData(samples);

      let sum = 0;
      for (let i = 0; i < samples.length; i++) {
        const sample = (samples[i] - 128) / 128;
        sum += sample * sample;
      }

      const rms = Math.sqrt(sum / samples.length);
      const raw = Math.min(1, Math.pow(rms * 6.4, 0.88));

      if (raw >= SPEECH_ON) {
        this._micSpeechOnStreak = Math.min(SPEECH_ON_STREAK + 2, (this._micSpeechOnStreak || 0) + 1);
      } else if (raw < SPEECH_OFF) {
        this._micSpeechOnStreak = 0;
      }

      const shouldSpeak = this._micSpeechOnStreak >= SPEECH_ON_STREAK
        || (this._micSpeakingActive && raw >= SPEECH_OFF);

      if (!shouldSpeak) {
        if (this._micSpeakingActive || this._smoothedMicLevel > 0) {
          this._micSpeakingActive = false;
          this._onMicGlowStateChange(false, 0);
          this._stopFabMotion();
        }
      } else {
        this._micSpeakingActive = true;
        this._smoothedMicLevel = this._smoothedMicLevel * 0.42 + raw * 0.58;
        this._onMicGlowStateChange(true, this._smoothedMicLevel);
        this.fab?.setSpeakingActive(true);
        this.fab?.setVoiceLevel(this._smoothedMicLevel);
      }

      this._micLevelRAF = requestAnimationFrame(tick);
    };

    this._micLevelRAF = requestAnimationFrame(tick);
  }

  _pauseMicLevelMonitor(resetFab = true) {
    if (this._micLevelRAF) {
      cancelAnimationFrame(this._micLevelRAF);
      this._micLevelRAF = null;
    }
    this._smoothedMicLevel = 0;
    this._micSpeakingActive = false;
    this._micSpeechOnStreak = 0;
    if (resetFab) this.fab?.resetVoiceLevel();
  }

  _stopMicLevelMonitor(resetFab = true) {
    this._pauseMicLevelMonitor(resetFab);
    if (this._micAudioCtx) {
      this._micAudioCtx.close().catch(() => {});
      this._micAudioCtx = null;
    }
    this._micAnalyser = null;
    this._micLevelStream = null;
  }

  _clearRecordingHealthCheck() {
    if (this._recordingHealthTimer) {
      clearInterval(this._recordingHealthTimer);
      this._recordingHealthTimer = null;
    }
    this._clearSilenceKeepalive();
  }

  _scheduleRecordingHealthCheck() {
    this._clearRecordingHealthCheck();
    if (!this.browserListening) return;
    const intervalMs = 2000;
    this._recordingHealthTimer = setInterval(() => {
      if (!this.browserListening || this.browserRecognition) return;
      this._ignoreRestOfCurrentRec = false;
      this._startNewRecognitionSession();
    }, intervalMs);
    this._scheduleSilenceKeepalive();
  }

  _ensureRecordingEngineAlive(reason = 'health-check') {
    if (!this.browserListening || this.browserRecognition) return;
    this._startNewRecognitionSession();
  }

  stopBrowserRecognition(options = {}) {
    const keepMic = options.keepMic !== false;
    const emitPendingInterim = options.emitPendingInterim !== false;
    const pendingInterim = emitPendingInterim
      ? (this._lastInterim || this._stickyInterimText)
      : '';
    this._stopRecognitionOnly();
    this._pauseMicLevelMonitor();
    this._lastInterim = '';

    if (pendingInterim && !this._ignoreRestOfCurrentRec) {
      this._emitFinalTranscription(pendingInterim);
    }

    this._SR = null;

    if (keepMic && this.browserMicStream) {
      this._scheduleMicWarmRelease();
    } else {
      this._cancelMicWarmRelease();
      this._releaseMicStream();
      this._stopMicLevelMonitor(true);
    }
  }

  _cancelMicTipTimer() {
    if (this._micTipTimer) {
      clearTimeout(this._micTipTimer);
      this._micTipTimer = null;
    }
  }

  _resetMicTipSequence() {
    this._micTipIndex = 0;
  }

  _scheduleNextMicTip() {
    this._cancelMicTipTimer();
    if (!this._isMicTipsEnabled() || !CHILLICOLLECTIVO_MIC_TIPS.length) return;
    const delay = CHILLICOLLECTIVO_MIC_TIP_MIN_MS
      + Math.random() * (CHILLICOLLECTIVO_MIC_TIP_MAX_MS - CHILLICOLLECTIVO_MIC_TIP_MIN_MS);
    this._micTipTimer = setTimeout(() => {
      this._micTipTimer = null;
      this._maybeShowMicTip();
      this._scheduleNextMicTip();
    }, delay);
  }

  _startMicTipScheduler() {
    if (!this._isMicTipsEnabled()) return;
    this._resetMicTipSequence();
    this._scheduleNextMicTip();
  }

  _stopMicTipScheduler() {
    this._cancelMicTipTimer();
    this.fab?.hideMicTipBubble?.();
  }

  _maybeShowMicTip() {
    if (!this._isMicTipsEnabled()) return;
    if (this.currentState === 'recording'
      && (this._currentInterimText?.trim() || this.previewCommitted?.trim() || this._dictationPreviewPinned)) return;
    if (this.fab?._isCrumblingPreview) return;
    if (this._sentPreviewCrumbleTimer || this._outPreviewWaitTimer) return;
    if (!CHILLICOLLECTIVO_MIC_TIPS.length) return;
    const tip = CHILLICOLLECTIVO_MIC_TIPS[this._micTipIndex % CHILLICOLLECTIVO_MIC_TIPS.length];
    this._micTipIndex += 1;
    this.fab?.showMicTipBubble(tip);
  }

  handleStateChange(newState, errorMessage = '') {
    const prevState = this.currentState;

    if (newState === prevState && newState !== 'error') {
      return;
    }

    if (newState === 'recording'
      && prevState === 'ready'
      && !this._pendingVoiceStart
      && !this.browserListening) {
      chillicollectivoVoiceDiag('warn', 'voice.stale-recording-ignored', 'Veralteter recording-Status ignoriert', {
        route: this._currentConversationKey(),
      }, this);
      return;
    }

    if (newState === 'error' || newState === 'locked') {
      this._pendingVoiceStart = false;
      this._clearLoadingWatchdog();
    }

    this.currentState = newState;
    this.errorMessage = errorMessage;

    if (newState === 'recording') {
      this._clearLoadingWatchdog();
      this._pendingVoiceStart = false;
      this._captureStartedAt = Date.now();
      this._micQualityRefreshAt = 0;
      this._sessionDictationChars = 0;
      this._recordingConversationRoute = this._currentConversationKey();
      this._dictationReplaceSpan = null;
      queueMicrotask(() => this._syncRecordingRouteIfNeeded());
      const field = [this.activeField, this._pendingField, this._userTargetField]
        .find((candidate) => this._isUsableTextField(candidate));
      if (field) {
        this.recordingField = field;
        this.activeField = field;
        this._dictationTargetKey = this._getFieldTargetKey(field);
        if (field.isContentEditable) {
          const sel = window.getSelection();
          if (sel?.rangeCount > 0) this.recordingStartPosition = sel.getRangeAt(0).cloneRange();
        } else {
          this.recordingStartPosition = field.selectionStart || 0;
          this.recordingSelectionEnd = field.selectionEnd ?? this.recordingStartPosition;
        }
        this._captureDictationReplaceSpan();
        if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)
          && typeof chillicollectivoStickProfileFieldFocus === 'function') {
          chillicollectivoStickProfileFieldFocus(this, field);
        }
      } else {
        this._adoptFreshComposeField();
      }
      if (typeof chillicollectivoSyncProfileDictationTarget === 'function') {
        chillicollectivoSyncProfileDictationTarget(this);
      }
      this._pendingField = null;
      if (this._isMicTipsEnabled()) {
        this._startMicTipScheduler();
      }
      const enterMode = !!this._isPreviewEnterToInsertEnabled?.();
      const clickMode = this._isAgentClickToInsertMode();
      const sessionField = this.recordingField || this._userTargetField || this.activeField;
      chillicollectivoVoiceDiag('info', 'voice.dictation-session-start', 'Diktat-Aufnahme gestartet', {
        mode: enterMode ? 'enter-to-preview' : (clickMode ? 'click-to-preview' : 'direct-field'),
        enterToInsert: enterMode,
        clickToInsert: clickMode,
        hasField: !!sessionField,
        fieldType: sessionField?.tagName || (sessionField?.isContentEditable ? 'contenteditable' : null),
        route: this._currentConversationKey?.() || '',
      }, this);
      this._scheduleRecordingHealthCheck();
    }

    if (newState !== 'recording') {
      this._clearRecordingHealthCheck();
      this._flushReplaceSpanMissStats('recording-stopped');
      this._cancelMicRestartAfterSend();
      if (prevState === 'recording') {
        chillicollectivoVoiceDiag('info', 'voice.dictation-session-end', 'Diktat-Aufnahme beendet', {
          newState,
          sessionChars: this._sessionDictationChars || 0,
          route: this._currentConversationKey?.() || '',
        }, this);
      }
      this._pauseMicLevelMonitor(true);
      this._stopMicTipScheduler();
      this._clearMicGlowGapCheck();
      this._resetMicDiag();
    }

    if (newState === 'ready' || newState === 'error' || newState === 'locked') {
      this._clearLoadingWatchdog();
      this._cancelMicRestartAfterSend();
      this._clearOutPreviewWaitTimer();
      if (this.browserListening || this.browserRecognition) {
        this.stopBrowserRecognition({ keepMic: false });
      } else if (newState === 'error') {
        this._cancelMicWarmRelease();
        this._releaseMicStream();
        this._stopMicLevelMonitor(true);
      }
      this._sessionDictationChars = 0;
      const wasProfileRecording = typeof chillicollectivoIsProfileTextarea === 'function'
        && chillicollectivoIsProfileTextarea(this.recordingField);
      if (typeof chillicollectivoClearProfileDictationLock === 'function') {
        if (newState === 'ready' && wasProfileRecording && typeof chillicollectivoReturnToComposeAfterProfileValue === 'function') {
          chillicollectivoReturnToComposeAfterProfileValue(this);
        } else {
          chillicollectivoClearProfileDictationLock(this);
        }
      } else {
        this._profileValueLock = null;
      }
      this.recordingStartPosition = null;
      this.recordingSelectionEnd = null;
      this._dictationReplaceSpan = null;
      this.recordingField = null;
      this._recordingConversationRoute = '';
      this._userTargetField = null;
      this._discardedPreviewNorm = '';
      this._discardedPreviewRoute = '';
      this._clearFlushedPreviewGuard('recording-stopped');
      if (this._postFlushIgnoreTimer) {
        clearTimeout(this._postFlushIgnoreTimer);
        this._postFlushIgnoreTimer = null;
      }
      this._ignoreRestOfCurrentRec = false;
      this._disarmTabEnterSend();
      this._unpinDictationPreview();
      this.previewCommitted = '';
      this._currentInterimText = '';
      if (this._hidePreviewTimer) {
        clearTimeout(this._hidePreviewTimer);
        this._hidePreviewTimer = null;
      }
      this.fab?.hidePreview({ force: true });
    }

    this.fab?.setState(newState, errorMessage);
    if (newState && newState !== prevState) {
      this._reportVoiceStateToBackground(newState);
    }
  }

  _dispatchKeyPress(target, key) {
    const el = target || document.activeElement || document.body;
    const keyCode = key === 'Tab' ? 9 : key === 'Enter' ? 13 : 0;
    const init = {
      key,
      code: key,
      keyCode,
      which: keyCode,
      bubbles: true,
      cancelable: true,
      view: window,
    };
    el.dispatchEvent(new KeyboardEvent('keydown', init));
    if (key === 'Enter') el.dispatchEvent(new KeyboardEvent('keypress', init));
    el.dispatchEvent(new KeyboardEvent('keyup', init));
  }

  _resolveTextField() {
    this._refreshStaleFieldRefs();
    if (this.currentState === 'recording') {
      if (this._profileValueLock?.until > Date.now()) {
        const lockField = typeof chillicollectivoResolveProfileLockField === 'function'
          ? chillicollectivoResolveProfileLockField(this._profileValueLock)
          : this._profileValueLock.field;
        if (this._isUsableTextField(lockField)) {
          this._profileValueLock.field = lockField;
          this.recordingField = lockField;
          this.activeField = lockField;
          this._userTargetField = lockField;
          return lockField;
        }
      }
      if ((this._composeFocusGuardUntil || 0) > Date.now()) {
        const userTarget = this._userTargetField;
        const userClass = userTarget && typeof chillicollectivoClassifyDictationField === 'function'
          ? chillicollectivoClassifyDictationField(userTarget)
          : null;
        const userChoseProfile = userClass && ['pp', 'neue-info', 'dialog-info'].includes(userClass.type);
        if (!userChoseProfile) {
          const guardedCompose = this._findComposeTextField();
          if (guardedCompose && this._isUsableTextField(guardedCompose)) {
            this.recordingField = guardedCompose;
            this.activeField = guardedCompose;
            this._userTargetField = guardedCompose;
            return guardedCompose;
          }
        }
      }
      const candidates = [
        this._userTargetField,
        this.recordingField,
        document.activeElement,
        this.activeField,
        this._pendingField,
      ];
      for (const candidate of candidates) {
        if (!this._isTorchwoodDictationField(candidate)) continue;
        if (typeof chillicollectivoIsProfileTextarea === 'function'
          && chillicollectivoIsProfileTextarea(candidate)) {
          const synced = typeof chillicollectivoSyncProfileDictationTarget === 'function'
            ? chillicollectivoSyncProfileDictationTarget(this)
            : candidate;
          if (synced) {
            this.recordingField = synced;
            this.activeField = synced;
            return synced;
          }
        }
        this.recordingField = candidate;
        this.activeField = candidate;
        return candidate;
      }
      if (this._profileValueLock?.until > Date.now()) {
        const lockField = typeof chillicollectivoResolveProfileLockField === 'function'
          ? chillicollectivoResolveProfileLockField(this._profileValueLock)
          : this._profileValueLock.field;
        if (this._isUsableTextField(lockField)) {
          this._profileValueLock.field = lockField;
          this.recordingField = lockField;
          this.activeField = lockField;
          return lockField;
        }
      }
      if (this._isPreviewEnterToInsertEnabled()) {
        if (this._adoptFreshComposeField()) {
          chillicollectivoVoiceDiag('info', 'voice.field-rebound', 'Compose-Feld im Enter-Modus wieder angebunden', {
            route: this._currentConversationKey(),
            recordingStartPos: typeof this.recordingStartPosition === 'number' ? this.recordingStartPosition : null,
          }, this);
          return this.recordingField;
        }
        chillicollectivoToolLog('warn', 'voice.field-lost', 'Aufnahme aktiv, aber kein Zielfeld (Enter-Modus)', {
          route: this._currentConversationKey(),
          enterToInsert: true,
        });
        console.warn('[CHILLICOLLECTIVO] Aufnahme aktiv, aber kein Zielfeld – zuerst ins Feld klicken');
        return null;
      }
      if (!this._adoptFreshComposeField()) {
        chillicollectivoToolLog('warn', 'voice.field-lost', 'Aufnahme aktiv, aber kein Zielfeld', {
          route: this._currentConversationKey(),
          enterToInsert: false,
        });
        console.warn('[CHILLICOLLECTIVO] Aufnahme aktiv, aber kein Zielfeld – Aktion verworfen');
        return null;
      }
      chillicollectivoVoiceDiag('info', 'voice.field-rebound', 'Compose-Feld wieder angebunden', {
        route: this._currentConversationKey(),
      }, this);
      return this.recordingField;
    }
    const candidates = [
      this._userTargetField,
      this.recordingField,
      this.activeField,
      document.activeElement,
      this._mouseoverField,
      this._pendingField,
      this._findComposeTextField(),
    ];
    for (const candidate of candidates) {
      if (this._isTorchwoodDictationField(candidate)) return candidate;
    }
    return null;
  }

  _queueInsertRetry(text, isFinal, attempt = 0) {
    if (this._isDiscardedPreviewText(text)) {
      this._cancelPendingInsertRetry();
      return;
    }
    this._cancelPendingInsertRetry();
    const run = (nextAttempt) => {
      const field = this._resolveTextField();
      if (field && this._isUsableTextField(field)) {
        this._pendingInsertRetry = null;
        this._commitFinalInsert(field, text);
        return;
      }
      if (nextAttempt >= 12) {
        this._pendingInsertRetry = null;
        console.warn('[CHILLICOLLECTIVO] Kein aktives Textfeld gefunden (Dialog noch am Laden?)');
        return;
      }
      const delay = nextAttempt === 0 ? 80 : (nextAttempt < 4 ? 120 : (nextAttempt < 8 ? 220 : 400));
      const token = window.setTimeout(() => run(nextAttempt + 1), delay);
      this._pendingInsertRetry = { token, text, isFinal };
    };
    run(attempt);
  }

  _appendDictationAtFieldEnd(field, text) {
    if (!field || !this._isUsableTextField(field)) return false;
    let insertText = String(text || '').replace(/\s+/g, ' ').trim();
    if (!insertText) return false;
    try { field.focus({ preventScroll: true }); } catch (_) {}

    if (field.isContentEditable) {
      const plain = String(field.textContent || '').replace(/\u00A0/g, ' ');
      const prefix = plain.length > 0 && !/\s$/.test(plain) && !/^\s/.test(insertText) ? ' ' : '';
      const formatted = this._formatDictationFieldInsert(insertText, plain);
      const range = document.createRange();
      range.selectNodeContents(field);
      range.collapse(false);
      const tn = document.createTextNode(prefix + formatted);
      range.insertNode(tn);
      range.setStartAfter(tn);
      range.collapse(true);
      const sel = window.getSelection();
      sel?.removeAllRanges();
      sel?.addRange(range);
      this.recordingStartPosition = range.cloneRange();
      this.recordingSelectionEnd = null;
      field.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
      return true;
    }

    const cur = field.value || '';
    const prefix = cur.length > 0 && !/\s$/.test(cur) && !/^\s/.test(insertText) ? ' ' : '';
    const capitalizedText = this._formatDictationFieldInsert(insertText, cur);
    const cleaned = prefix + this.cleanPunctuationSpacing(capitalizedText);
    const newVal = cur + cleaned;
    if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)
      && typeof chillicollectivoWriteProfileField === 'function') {
      chillicollectivoWriteProfileField(field, newVal, false, {
        source: 'voice-dictation-append',
        dictationText: cleaned,
        markedText: '',
      });
    } else {
      field.value = newVal;
    }
    const cp = newVal.length;
    field.selectionStart = field.selectionEnd = cp;
    this.recordingStartPosition = cp;
    this.recordingSelectionEnd = cp;
    field.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
    field.dispatchEvent(new Event('change', { bubbles: true }));
    return true;
  }

  _commitFinalInsert(field, text) {
    if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)
      && typeof chillicollectivoStripEmojisFromProfileText === 'function') {
      text = chillicollectivoStripEmojisFromProfileText(text);
      if (!text.trim()) return;
    }

    const bodyNorm = this._normalizeSubmitBody(text);
    const directAppend = this._isDirectFieldAppendMode();
    const hasUserSelection = this._fieldHasExplicitUserSelection(field);

    if (this.currentState === 'recording') {
      this._syncDictationCursorFromField(field);
    }

    if (directAppend && !hasUserSelection) {
      this._clearDictationReplaceSpan('field', { clearSaved: true });
      this._lastActiveFieldReplaceSpan = null;
    } else {
      this._restoreActiveFieldReplaceSpanIfNeeded(field, 'final-insert-pre');
    }
    const willReplaceMarked = this._shouldUseFieldReplaceSpanForInsert(field);
    this._logVoiceInsertFlow('info', 'voice.insert-commit-start', 'Texteinfügen ins Chatfeld gestartet', {
      textLen: String(text || '').trim().length,
      textSnippet: String(text || '').trim().slice(0, 120),
      recordingStartPos: typeof this.recordingStartPosition === 'number' ? this.recordingStartPosition : 'contenteditable-range',
      recordingSelectionEnd: this.recordingSelectionEnd,
      hadSavedFieldSpan: !!this._lastActiveFieldReplaceSpan,
      willReplaceMarked,
      enterToInsert: !!this._isPreviewEnterToInsertEnabled(),
      previewUi: !!this._isPreviewUiEnabled(),
    }, field);
    if (this._fieldAlreadyContainsInsert(field, text) && !willReplaceMarked) {
      console.warn('[CHILLICOLLECTIVO] KRITISCH: Text ist bereits im Feld – Insert verworfen:', text);
      this._logVoiceInsertFlow('error', 'voice.duplicate-insert', 'Text ist bereits im Feld, Insert verworfen', {
        text,
        bodyNorm,
        route: this._currentConversationKey(),
        fieldTail: this._fieldPlainText(field).slice(-500),
        fieldHead: this._fieldPlainText(field).slice(0, 200),
        fuzzyMatchField: true,
      }, field);
      this._voiceCommandSuppressUntil = Math.max(this._voiceCommandSuppressUntil || 0, Date.now() + CHILLICOLLECTIVO_POST_SEND_COOLDOWN_MS);
      this._currentInterimText = '';
      this._lastInterim = '';
      this._stopFabMotion();
      this.fab?.hidePreview?.({ force: true });
      return;
    }

    if (directAppend && !willReplaceMarked && !hasUserSelection && this._isDictationCursorAtFieldEnd(field)) {
      this._clearDictationReplaceSpan('field', { clearSaved: true });
      this._lastActiveFieldReplaceSpan = null;
      if (!this._appendDictationAtFieldEnd(field, text)) {
        this._logVoiceInsertFlow('info', 'voice.insert-commit-skip', 'Anhängen übersprungen – leer', {
          reason: 'append-empty',
        }, field);
        return;
      }
      this.activeField = field;
      this._logVoiceInsertFlow('info', 'voice.insert-commit-done', 'Texteinfügen ins Chatfeld abgeschlossen (Anhängen)', {
        insertedLen: String(text || '').trim().length,
        insertedSnippet: String(text || '').trim().slice(0, 120),
        appendOnly: true,
      }, field);
      this._rememberCommittedInsert(text);
      this._pendingFirstSendNorm = bodyNorm;
      this._pendingFirstSendPlain = String(text || '').trim();
      if (!(typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field))) {
        this._applyChiliStimmungEmojis(field);
      }
      if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)) {
        if (typeof chillicollectivoReturnToComposeAfterProfileValue === 'function') {
          chillicollectivoReturnToComposeAfterProfileValue(this);
        }
      }
      return;
    }

    const continuingEnterFlush = this._isContinuingDictationSession()
      && this._isPreviewEnterToInsertEnabled()
      && !hasUserSelection;
    const hadSavedFieldSpan = !!this._lastActiveFieldReplaceSpan;
    if (continuingEnterFlush) {
      this._clearDictationReplaceSpan('field', { clearSaved: true });
    } else {
      this._restoreActiveFieldReplaceSpanIfNeeded(field, 'final-insert');
      this._syncFieldStateBeforeReplaceOps(field, 'final-insert');
    }
    if (!directAppend || willReplaceMarked || hasUserSelection) {
      this._captureDictationReplaceSpan('final-insert');
    }
    if (!continuingEnterFlush && this._shouldUseFieldReplaceSpanForInsert(field)) {
      this._prepareFieldForInsert(field);
      if (this._insertFieldTextAtSpan(field, text, this._dictationReplaceSpan)) {
        this._clearDictationReplaceSpan('field', { clearSaved: true });
        this.activeField = field;
        this._rememberCommittedInsert(text);
        this._pendingFirstSendNorm = bodyNorm;
        this._pendingFirstSendPlain = String(text || '').trim();
        if (!(typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field))) {
          this._applyChiliStimmungEmojis(field);
        }
        if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)) {
          if (typeof chillicollectivoReturnToComposeAfterProfileValue === 'function') {
            chillicollectivoReturnToComposeAfterProfileValue(this);
          }
        }
        this._logVoiceInsertFlow('info', 'voice.insert-commit-done', 'Texteinfügen ins Chatfeld abgeschlossen (Feld-Ersetzung)', {
          insertedLen: String(text || '').trim().length,
          usedFieldSpan: true,
          hadSavedFieldSpan,
        }, field);
        if (this._isCaptureActive()) {
          this._restoreDictationCursorAfterInsert(field, { trigger: 'final-insert-span' });
        }
        return;
      }
    } else if (hadSavedFieldSpan && !continuingEnterFlush) {
      const plain = this._fieldPlainText(field);
      const appendAtEnd = typeof this.recordingStartPosition === 'number'
        && this.recordingStartPosition >= Math.max(0, plain.length - 1);
      chillicollectivoVoiceDiag('warn', 'voice.field-span-lost', 'Gespeicherte Feld-Markierung verloren – Text wird normal eingefügt', {
        dictationTextSnippet: String(text || '').slice(0, 160),
        appendAtEnd,
        savedSpan: {
          anchorStart: this._lastActiveFieldReplaceSpan?.anchorStart,
          anchorEnd: this._lastActiveFieldReplaceSpan?.anchorEnd,
          markedTextSnippet: plain.substring(
            this._lastActiveFieldReplaceSpan?.anchorStart ?? 0,
            this._lastActiveFieldReplaceSpan?.anchorEnd ?? 0,
          ).slice(0, 160),
        },
        ...this._fieldInsertContext(field),
      }, this);
    }

    if (continuingEnterFlush) {
      if (this._appendDictationAtFieldEnd(field, text)) {
        this.activeField = field;
        this._rememberCommittedInsert(text);
        this._pendingFirstSendNorm = bodyNorm;
        this._pendingFirstSendPlain = String(text || '').trim();
        if (!(typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field))) {
          this._applyChiliStimmungEmojis(field);
        }
        this._logVoiceInsertFlow('info', 'voice.insert-commit-done', 'Texteinfügen ins Chatfeld abgeschlossen (Anhängen nach Zwischen-Flush)', {
          insertedLen: String(text || '').trim().length,
          appendOnly: true,
          continuingEnterFlush: true,
        }, field);
        return;
      }
    }

    this._prepareFieldForInsert(field);

    if (field.isContentEditable) {
      const sel = window.getSelection();
      if (sel?.rangeCount > 0) {
        const range = sel.getRangeAt(0);
        range.deleteContents();
        const parts = text.split('\n');
        for (let i = 0; i < parts.length; i++) {
          const tn = document.createTextNode(parts[i]);
          range.insertNode(tn);
          range.setStartAfter(tn);
          range.collapse(true);
          if (i < parts.length - 1) {
            const br = document.createElement('br');
            range.insertNode(br);
            range.setStartAfter(br);
            range.collapse(true);
          }
        }
        sel.removeAllRanges();
        sel.addRange(range);
        this.recordingStartPosition = range.cloneRange();
      } else { field.textContent += text; }
      field.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
    } else {
      const cur = field.value || '';
      if (cur.length === 0 && this.recordingStartPosition !== null && this.recordingStartPosition > 0) {
        this.recordingStartPosition = 0;
      }

      const hasSelection = field.selectionStart !== field.selectionEnd;
      let replaceStart = hasSelection ? field.selectionStart : (this.recordingStartPosition ?? cur.length);
      let replaceEnd = hasSelection ? field.selectionEnd : replaceStart;
      const appendAtEnd = !hasSelection && replaceStart >= cur.length;

      if (!hasSelection && replaceStart > 0 && !appendAtEnd) {
        const leadBefore = cur.substring(0, replaceStart);
        const strippedLead = this._stripPrefixOverlap(leadBefore, String(text || '').replace(/\s+/g, ' ').trim());
        if (strippedLead && strippedLead !== text) {
          text = strippedLead;
        }
      }

      const deduped = this._dedupeInsertAgainstFieldContent(field, text, replaceStart, replaceEnd, hasSelection);
      if (deduped && typeof deduped === 'object') {
        text = deduped.text;
        replaceStart = deduped.replaceStart;
        replaceEnd = deduped.replaceEnd;
      } else if (typeof deduped === 'string') {
        text = deduped;
      }
      if (!String(text || '').trim()) {
        this._logVoiceInsertFlow('info', 'voice.insert-commit-skip', 'Einfügen übersprungen – nach Prefix-Abzug leer', {
          reason: 'prefix-dedupe-empty',
        }, field);
        return;
      }

      const startsWithPunct = /^\s*[.,!?;:]/.test(text);
      let actualStart = replaceStart;
      if (startsWithPunct && replaceStart > 0) {
        while (actualStart > 0 && cur[actualStart - 1] === ' ') actualStart--;
      }
      const processedText = startsWithPunct ? text.trimStart() : text;
      const prefix = (!startsWithPunct && actualStart > 0 && cur[actualStart - 1] !== ' ' && cur[actualStart - 1] !== '\n') ? ' ' : '';
      const capitalizedText = this._formatDictationFieldInsert(processedText, cur.substring(0, actualStart), {
        hasSelection,
      });
      const cleaned = prefix + this.cleanPunctuationSpacing(capitalizedText);
      const newVal = cur.substring(0, actualStart) + cleaned + cur.substring(replaceEnd);
      if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)
        && typeof chillicollectivoWriteProfileField === 'function') {
        chillicollectivoWriteProfileField(field, newVal, false, {
          source: hasSelection ? 'voice-dictation-replace' : 'voice-dictation',
          dictationText: cleaned,
          markedText: hasSelection ? cur.substring(replaceStart, replaceEnd) : '',
        });
      } else {
        field.value = newVal;
      }
      const cp = actualStart + cleaned.length;
      field.selectionStart = field.selectionEnd = cp;
      this.recordingStartPosition = cp;

      field.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
      field.dispatchEvent(new Event('change', { bubbles: true }));
    }

    if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)) {
      if (typeof chillicollectivoReturnToComposeAfterProfileValue === 'function') {
        chillicollectivoReturnToComposeAfterProfileValue(this);
      }
    }

    this.activeField = field;
    this._logVoiceInsertFlow('info', 'voice.insert-commit-done', 'Texteinfügen ins Chatfeld abgeschlossen', {
      insertedLen: String(text || '').trim().length,
      insertedSnippet: String(text || '').trim().slice(0, 120),
      newRecordingStartPos: typeof this.recordingStartPosition === 'number' ? this.recordingStartPosition : 'contenteditable-range',
    }, field);
    this._rememberCommittedInsert(text);
    this._pendingFirstSendNorm = bodyNorm;
    this._pendingFirstSendPlain = String(text || '').trim();
    if (!(typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field))) {
      this._applyChiliStimmungEmojis(field);
    }
    if (this._isCaptureActive()) {
      if (this._isDirectFieldAppendMode() && !willReplaceMarked && !hasUserSelection) {
        this._advanceDictationAppendCursor(field, 'final-insert-append');
      } else {
        this._restoreDictationCursorAfterInsert(field, { trigger: 'final-insert-resume' });
      }
    }
  }

  _applyChiliStimmungEmojis(field) {
    if (!field || !window.__chillicollectivoChiliStimmung?.applyToField) return;
    try {
      const applied = window.__chillicollectivoChiliStimmung.applyToField(field);
      if (!applied && this._fieldPlainText(field)) {
        window.setTimeout(() => {
          try {
            window.__chillicollectivoChiliStimmung?.applyToField?.(field);
          } catch (_) {}
        }, 180);
      }
    } catch (e) {
      console.warn('[CHILLICOLLECTIVO] Chili-Stimmung:', e);
    }
  }

  _normalizeSubmitBody(text) {
    return String(text || '').replace(/\s+/g, ' ').trim().toLowerCase();
  }

  _fieldPlainText(field) {
    if (!field) return '';
    if (field.isContentEditable) return String(field.textContent || '').trim();
    return String(field.value ?? '').trim();
  }

  _composeHasAttachedImage(field) {
    if (!field) return false;
    const root = field.closest?.('[class*="Compose"], [class*="MessageInput"], [class*="Conversation"]')
      || field.parentElement?.parentElement
      || field.parentElement;
    if (!root) return false;
    const composeRect = field.getBoundingClientRect?.();
    return [...root.querySelectorAll('img, [class*="ImageView"], [class*="ImagePreview"], [class*="Attachment"]')].some((node) => {
      if (!(node instanceof HTMLElement)) return false;
      const style = window.getComputedStyle(node);
      if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
      const rect = node.getBoundingClientRect();
      if (rect.width < 36 || rect.height < 36) return false;
      if (composeRect && Math.abs(rect.bottom - composeRect.top) > 280) return false;
      return true;
    });
  }

  _composeHasSendableText(field) {
    if (!!this._normalizeSubmitBody(this._fieldPlainText(field))) return true;
    return this._composeHasAttachedImage(field);
  }

  _extractConversationId(value) {
    const raw = String(value || '').trim();
    if (!raw) return '';
    const idPatterns = [
      /\/conversation\/([^/?#&\s]+)/i,
      /\/conversations\/([^/?#&\s]+)/i,
      /\/chat\/([^/?#&\s]+)/i,
      /\/chats\/([^/?#&\s]+)/i,
      /[?&](?:conversation(?:Id)?|chat(?:Id)?)=([^&#\s]+)/i,
      /#\/(?:conversation|conversations|chat|chats)\/([^/?#&\s]+)/i,
    ];
    for (const pattern of idPatterns) {
      const match = raw.match(pattern);
      if (match?.[1]) {
        try { return decodeURIComponent(match[1]).trim(); }
        catch (_) { return match[1].trim(); }
      }
    }
    return '';
  }

  _routeFallbackKey(value) {
    const raw = String(value || '').trim();
    if (!raw) return '';
    try {
      if (/^https?:\/\//i.test(raw)) {
        return new URL(raw).pathname.replace(/\/+$/, '') || '/';
      }
    } catch (_) {}
    return raw.split(/[?#]/)[0].replace(/\/+$/, '') || '/';
  }

  _normalizeConversationRoute(route) {
    const raw = String(route || '').trim();
    if (!raw) return '';
    const id = this._extractConversationId(raw);
    if (id) return id;
    return this._routeFallbackKey(raw);
  }

  _isSameConversationRoute(a, b) {
    const idA = this._extractConversationId(a);
    const idB = this._extractConversationId(b);
    if (idA && idB) return idA === idB;
    const normA = this._normalizeConversationRoute(a);
    const normB = this._normalizeConversationRoute(b);
    if (!normA || !normB) return normA === normB;
    return normA === normB;
  }

  _syncRecordingRouteIfNeeded() {
    if (this.currentState !== 'recording') return;
    const locked = String(this._recordingConversationRoute || '').trim();
    const current = this._currentConversationKey();
    if (!current) return;
    if (this._isSameConversationRoute(locked, current)) {
      this._recordingConversationRoute = current;
      return;
    }
    if (!this._extractConversationId(locked) && this._extractConversationId(current)) {
      this._recordingConversationRoute = current;
    }
  }

  _currentConversationKey() {
    const blob = [location.pathname, location.search, location.hash].join(' ');
    const id = this._extractConversationId(blob);
    if (id) return id;
    return this._routeFallbackKey(location.pathname || '/');
  }

  _isHardSendLocked() {
    if (this._submitInFlightPromise) return true;
    const now = Date.now();
    if (this._lastSubmitAt && now - this._lastSubmitAt < this._SUBMIT_COOLDOWN_MS) {
      return true;
    }
    if (typeof chillicollectivoIsGlobalSendHardLocked === 'function' && chillicollectivoIsGlobalSendHardLocked()) {
      const lock = chillicollectivoReadGlobalSendLock?.();
      chillicollectivoToolLog('warn', 'voice.send-lock', 'Senden blockiert – 6s Sperre nach Senden', {
        currentRoute: this._currentConversationKey(),
        lockRoute: lock?.route || '',
        lockSource: lock?.source || '',
        lockMsLeft: Math.max(0, (lock?.cooldownUntil || 0) - now),
      });
      return true;
    }
    return false;
  }

  _clearVoiceCommandPreview() {
    this._currentInterimText = '';
    this._lastInterim = '';
    this._stopFabMotion();
    this.fab?.hidePreview?.({ force: true });
  }

  _normalizeDuplicateWord(word) {
    return String(word || '')
      .toLowerCase()
      .replace(/ß/g, 'ss')
      .replace(/[^a-z0-9äöü]+/g, '');
  }

  _splitDuplicateWords(text) {
    return String(text || '').trim().split(/\s+/).filter(Boolean);
  }

  _sharedPrefixWordCount(a, b) {
    const aWords = this._splitDuplicateWords(a).map((word) => this._normalizeDuplicateWord(word));
    const bWords = this._splitDuplicateWords(b).map((word) => this._normalizeDuplicateWord(word));
    const max = Math.min(aWords.length, bWords.length);
    let count = 0;
    for (let i = 0; i < max; i++) {
      if (!aWords[i] || !bWords[i] || aWords[i] !== bWords[i]) break;
      count++;
    }
    return count;
  }

  _tailHeadWordOverlapCount(baseText = '', candidateText = '') {
    const baseWords = this._splitDuplicateWords(baseText).map((word) => this._normalizeDuplicateWord(word));
    const candWords = this._splitDuplicateWords(candidateText).map((word) => this._normalizeDuplicateWord(word));
    for (let k = Math.min(baseWords.length, candWords.length); k > 0; k--) {
      const tail = baseWords.slice(-k).join(' ');
      const head = candWords.slice(0, k).join(' ');
      if (tail && head && tail === head) return k;
    }
    return 0;
  }

  _splitPreviewTokens(text) {
    return String(text || '').trim().split(/[\s-]+/).filter(Boolean);
  }

  _previewEndsAtWordBoundary(text = '') {
    const raw = String(text || '').replace(/\s+/g, ' ');
    return !raw.trim() || /\s$/.test(raw);
  }

  _isMidWordSplitHalves(lastToken = '', firstToken = '') {
    const lastB = String(lastToken || '').trim();
    const firstC = String(firstToken || '').trim();
    if (lastB.length < 2 || firstC.length < 2) return false;
    if (firstC.startsWith(lastB) || lastB.startsWith(firstC)) return false;
    if (firstC[0] !== firstC[0].toLowerCase()) return false;
    return true;
  }

  _mergeMidWordSplitHalves(baseText = '', candidateText = '') {
    const rawBase = String(baseText || '').replace(/\s+/g, ' ');
    if (!rawBase.trim() || this._previewEndsAtWordBoundary(rawBase)) return '';
    const base = rawBase.trimEnd();
    const candidate = String(candidateText || '').replace(/\s+/g, ' ').trim();
    if (!base || !candidate) return '';

    const baseTokens = this._splitPreviewTokens(base);
    const candTokens = this._splitPreviewTokens(candidate);
    if (!baseTokens.length || !candTokens.length) return '';

    const lastB = baseTokens[baseTokens.length - 1];
    const firstC = candTokens[0];
    if (!this._isMidWordSplitHalves(lastB, firstC)) return '';

    const prefix = this._rebuildPreviewWithoutLastToken(base, baseTokens);
    const glued = `${lastB}${firstC}`;
    const tail = candTokens.slice(1).join(' ').trim();
    return String([prefix, glued, tail].filter(Boolean).join(' ')).replace(/\s+/g, ' ').trim();
  }

  _lastPreviewToken(text) {
    const tokens = this._splitPreviewTokens(text);
    return tokens.length ? tokens[tokens.length - 1] : '';
  }

  _firstPreviewToken(text) {
    const tokens = this._splitPreviewTokens(text);
    return tokens.length ? tokens[0] : '';
  }

  _rebuildPreviewWithoutLastToken(text, tokens = null) {
    const parts = tokens || this._splitPreviewTokens(text);
    if (!parts.length) return '';
    if (parts.length === 1) return '';
    const last = parts[parts.length - 1];
    const raw = String(text || '').trim();
    const idx = raw.toLowerCase().lastIndexOf(last.toLowerCase());
    if (idx >= 0) {
      return raw.slice(0, idx).replace(/[\s-]+$/g, '').trim();
    }
    return parts.slice(0, -1).join(' ').trim();
  }

  _sharedSuffixPrefixChars(baseText = '', candidateText = '', minLen = 3, maxLen = 40) {
    const base = String(baseText || '');
    const candidate = String(candidateText || '');
    const limit = Math.min(maxLen, base.length, candidate.length);
    for (let n = limit; n >= minLen; n--) {
      if (base.slice(-n).toLowerCase() === candidate.slice(0, n).toLowerCase()) return n;
    }
    return 0;
  }

  _isPartialTokenRefinement(baseText = '', candidateText = '') {
    const base = String(baseText || '').replace(/\s+/g, ' ').trim();
    const candidate = String(candidateText || '').replace(/\s+/g, ' ').trim();
    if (!base || !candidate) return false;

    const lastB = this._normalizeDuplicateWord(this._lastPreviewToken(base));
    const firstC = this._normalizeDuplicateWord(this._firstPreviewToken(candidate));
    if (lastB.length >= 2 && firstC.length >= 2) {
      if (firstC.startsWith(lastB) || lastB.startsWith(firstC)) return true;
    }

    if (!this._previewEndsAtWordBoundary(baseText)
      && this._isMidWordSplitHalves(this._lastPreviewToken(base), this._firstPreviewToken(candidate))) {
      return true;
    }

    if (this._sharedSuffixPrefixChars(base, candidate) >= 3) return true;

    const normB = this._normalizeDuplicateText(base);
    const normC = this._normalizeDuplicateText(candidate);
    if (normC.includes(normB) || normB.includes(normC)) return true;
    if (candidate.toLowerCase().startsWith(base.toLowerCase()) || base.toLowerCase().startsWith(candidate.toLowerCase())) {
      return true;
    }
    return false;
  }

  _mergePartialTokenRefinement(baseText = '', candidateText = '') {
    const base = String(baseText || '').replace(/\s+/g, ' ').trim();
    const candidate = String(candidateText || '').replace(/\s+/g, ' ').trim();
    if (!base || !candidate) return candidate || base;

    const midWordMerged = this._mergeMidWordSplitHalves(baseText, candidateText);
    if (midWordMerged) return midWordMerged;

    const baseTokens = this._splitPreviewTokens(base);
    const candTokens = this._splitPreviewTokens(candidate);
    if (baseTokens.length && candTokens.length) {
      const lastB = this._normalizeDuplicateWord(baseTokens[baseTokens.length - 1]);
      const firstC = this._normalizeDuplicateWord(candTokens[0]);
      if (lastB && firstC && (firstC.startsWith(lastB) || lastB.startsWith(firstC))) {
        const prefix = this._rebuildPreviewWithoutLastToken(base, baseTokens);
        const tail = candTokens.slice(1).join(' ').trim();
        return String([prefix, candTokens[0], tail].filter(Boolean).join(' ')).replace(/\s+/g, ' ').trim();
      }
    }

    const sharedChars = this._sharedSuffixPrefixChars(base, candidate);
    if (sharedChars >= 3 && candidate.length > sharedChars) {
      return String(base.slice(0, -sharedChars) + candidate).replace(/\s+/g, ' ').trim();
    }
    return '';
  }

  _sanitizeSttEchoChains(text) {
    let raw = String(text || '').replace(/\s+/g, ' ').trim();
    if (!raw) return raw;

    raw = raw.replace(/(?:\b[\p{L}\p{N}ÄÖÜäöüß]+)(?:-[\p{L}\p{N}ÄÖÜäöüß]+){2,}/gu, (match) => {
      const parts = match.split('-').filter(Boolean);
      if (parts.length < 3) return match;
      let longest = parts[0];
      let progressive = true;
      for (let i = 1; i < parts.length; i++) {
        const a = this._normalizeDuplicateWord(parts[i - 1]);
        const b = this._normalizeDuplicateWord(parts[i]);
        if (!a || !b || (!b.startsWith(a) && !a.startsWith(b))) {
          progressive = false;
          break;
        }
        if (parts[i].length >= longest.length) longest = parts[i];
      }
      return progressive ? longest : match;
    });

    raw = raw.replace(/(\b[\p{L}\p{N}ÄÖÜäöüß]{3,})(?:\s*[,;]\s*\1)+\b/giu, '$1');

    const commaParts = raw.split(/\s*,\s*/);
    if (commaParts.length > 2) {
      const rebuilt = [];
      let changed = false;
      commaParts.forEach((part, index) => {
        const clean = String(part || '').replace(/\s+/g, ' ').trim();
        if (!clean) return;
        const prev = rebuilt[rebuilt.length - 1];
        if (prev) {
          const normPrev = this._normalizeDuplicateText(prev);
          const normClean = this._normalizeDuplicateText(clean);
          const prevTail = this._normalizeDuplicateWord(this._lastPreviewToken(prev));
          const cleanHead = this._normalizeDuplicateWord(this._firstPreviewToken(clean));
          if (normClean.includes(normPrev) || normPrev.includes(normClean)
            || (prevTail && cleanHead && (cleanHead.startsWith(prevTail) || prevTail.startsWith(cleanHead)))) {
            if (clean.length >= prev.length) {
              rebuilt[rebuilt.length - 1] = clean;
            }
            changed = true;
            return;
          }
        }
        rebuilt.push(clean);
      });
      if (changed) raw = rebuilt.join(', ');
    }

    return raw.replace(/\s+/g, ' ').trim();
  }

  _collapsePreviewPhraseStack(text) {
    let raw = this._sanitizeSttEchoChains(String(text || ''));
    if (raw.length < 80) return raw;

    const words = raw.split(/\s+/).filter(Boolean);
    if (words.length < 12) return raw;

    const counts = new Map();
    for (let size = 4; size <= 6; size++) {
      for (let i = 0; i <= words.length - size; i++) {
        const phrase = words.slice(i, i + size).join(' ').toLowerCase();
        if (phrase.length < 16) continue;
        counts.set(phrase, (counts.get(phrase) || 0) + 1);
      }
    }

    let hotPhrase = '';
    let hotCount = 0;
    for (const [phrase, count] of counts) {
      if (count > hotCount && count >= 3) {
        hotCount = count;
        hotPhrase = phrase;
      }
    }
    if (!hotPhrase) return raw;

    const escaped = hotPhrase.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const re = new RegExp(escaped, 'gi');
    const indices = [];
    let match;
    while ((match = re.exec(raw)) !== null) {
      indices.push(match.index);
      if (indices.length >= 10) break;
    }
    if (indices.length < 3) return raw;

    const first = indices[0];
    const last = indices[indices.length - 1];
    const prefix = raw.slice(0, first).trim();
    const tail = raw.slice(last).trim();
    const collapsed = `${prefix} ${tail}`.replace(/\s+/g, ' ').trim();
    if (collapsed.length < raw.length * 0.97 && collapsed.length >= 30) {
      chillicollectivoToolLog('warn', 'voice.preview-stack-collapsed', 'Vorschau-Doppel-Stack bereinigt', {
        beforeLen: raw.length,
        afterLen: collapsed.length,
        hotPhrase,
        hotCount,
        route: this._currentConversationKey(),
      });
      return collapsed;
    }
    return raw;
  }

  _mergeTailHeadOverlap(baseText = '', candidateText = '') {
    const base = String(baseText || '').replace(/\s+/g, ' ').trim();
    const candidate = String(candidateText || '').replace(/\s+/g, ' ').trim();
    if (!base || !candidate) return candidate || base;
    const overlap = this._tailHeadWordOverlapCount(base, candidate);
    if (!overlap) return '';
    const baseWords = this._splitDuplicateWords(base);
    const prefix = baseWords.slice(0, -overlap).join(' ').trim();
    return String(prefix ? `${prefix} ${candidate}` : candidate).replace(/\s+/g, ' ').trim();
  }

  _isSttUtteranceRefinement(baseText = '', candidateText = '') {
    const base = String(baseText || '').replace(/\s+/g, ' ').trim();
    const candidate = String(candidateText || '').replace(/\s+/g, ' ').trim();
    if (!base || !candidate) return false;

    const normB = this._normalizeDuplicateText(base);
    const normC = this._normalizeDuplicateText(candidate);
    if (normB === normC) return true;
    if (normC.includes(normB) || normB.includes(normC)) return true;

    const lowerB = base.toLowerCase();
    const lowerC = candidate.toLowerCase();
    if (lowerC.startsWith(lowerB) || lowerB.startsWith(lowerC)) return true;

    const baseWords = this._splitDuplicateWords(base);
    const candWords = this._splitDuplicateWords(candidate);
    let tailHeadWords = 0;
    for (let k = Math.min(baseWords.length, candWords.length); k > 0; k--) {
      const tail = baseWords.slice(-k).map((word) => this._normalizeDuplicateWord(word)).join(' ');
      const head = candWords.slice(0, k).map((word) => this._normalizeDuplicateWord(word)).join(' ');
      if (tail && head && tail === head) {
        tailHeadWords = k;
        break;
      }
    }
    if (tailHeadWords > 0 && candidate.length < base.length * 0.55 && tailHeadWords < 2) {
      return false;
    }

    if (this._isPartialTokenRefinement(base, candidate)) return true;

    if (this._sharedPrefixWordCount(base, candidate) > 0) return true;

    const prefixLen = Math.min(base.length, candidate.length, 8);
    if (prefixLen >= 2 && lowerB.slice(0, prefixLen) === lowerC.slice(0, prefixLen)) return true;

    if (tailHeadWords > 0) return true;

    if (base.length <= 48 && candidate.length <= 96 && this._isFuzzyDuplicateText(base, candidate)) {
      return true;
    }
    return false;
  }

  _pickSttRefinedPreviewText(baseText = '', candidateText = '') {
    const base = String(baseText || '').replace(/\s+/g, ' ').trim();
    const candidate = String(candidateText || '').replace(/\s+/g, ' ').trim();
    if (!base) return candidate;
    if (!candidate) return base;
    if (!this._isSttUtteranceRefinement(base, candidate)) {
      return candidate.length >= base.length ? candidate : base;
    }
    const normB = this._normalizeDuplicateText(base);
    const normC = this._normalizeDuplicateText(candidate);
    if (normC.includes(normB) && candidate.length >= base.length) return candidate;
    if (normB.includes(normC) && base.length >= candidate.length) return base;
    return candidate.length >= base.length ? candidate : base;
  }

  _stripPrefixOverlap(base, candidate) {
    const baseStr = String(base || '').replace(/\s+/g, ' ').trim();
    const candStr = String(candidate || '').replace(/\s+/g, ' ').trim();
    if (!baseStr || !candStr) return candStr;
    if (candStr.startsWith(baseStr) && candStr.length > baseStr.length) {
      return candStr.slice(baseStr.length).trim();
    }
    const shared = this._sharedPrefixWordCount(baseStr, candStr);
    if (!shared) {
      const baseWords = this._splitDuplicateWords(baseStr);
      const candWords = this._splitDuplicateWords(candStr);
      for (let k = Math.min(baseWords.length, candWords.length); k > 0; k--) {
        const tail = baseWords.slice(-k).map((word) => this._normalizeDuplicateWord(word)).join(' ');
        const head = candWords.slice(0, k).map((word) => this._normalizeDuplicateWord(word)).join(' ');
        if (tail && head && tail === head) {
          const delta = candWords.slice(k).join(' ').trim();
          return delta;
        }
      }
      return candStr;
    }
    const wordDelta = this._splitDuplicateWords(candStr).slice(shared).join(' ').trim();
    if (!wordDelta && candStr.length > baseStr.length) {
      return candStr.slice(baseStr.length).trim();
    }
    return wordDelta;
  }

  _stripDictationEchoForFieldSpan(dictation, cur, anchorStart, anchorEnd) {
    let text = String(dictation || '').replace(/\s+/g, ' ').trim();
    if (!text) return text;
    const prefixBefore = String(cur || '').substring(0, anchorStart);
    if (prefixBefore.trim()) {
      const stripped = this._stripPrefixOverlap(prefixBefore, text);
      if (stripped && stripped !== text) text = stripped;
    }
    return text;
  }

  _stripStrictLeadingTextOverlap(base, candidate) {
    const baseStr = String(base || '').replace(/\s+/g, ' ').trim();
    const candStr = String(candidate || '').replace(/\s+/g, ' ').trim();
    if (!baseStr || !candStr) return candStr;
    if (candStr.startsWith(baseStr) && candStr.length > baseStr.length) {
      return candStr.slice(baseStr.length).trim();
    }
    const shared = this._sharedPrefixWordCount(baseStr, candStr);
    if (shared > 0) {
      const delta = this._splitDuplicateWords(candStr).slice(shared).join(' ').trim();
      if (delta) return delta;
    }
    return candStr;
  }

  _stripDictationEchoForReplaceSpan(dictation, cur, anchorStart, anchorEnd) {
    let text = String(dictation || '').replace(/\s+/g, ' ').trim();
    if (!text) return text;
    const markedText = cur.substring(anchorStart, anchorEnd);
    const prefixBefore = cur.substring(0, anchorStart).trim();
    if (prefixBefore) {
      const stripped = this._stripStrictLeadingTextOverlap(prefixBefore, text);
      if (stripped && stripped !== text) text = stripped;
    }
    if (markedText) {
      if (text.startsWith(markedText) && text.length > markedText.length) {
        text = text.slice(markedText.length).trim();
      } else {
        const markedNorm = this._normalizeDuplicateText(markedText);
        const textNorm = this._normalizeDuplicateText(text);
        if (markedNorm && textNorm.startsWith(markedNorm) && text.length > markedText.length) {
          text = text.slice(markedText.length).trim();
        }
      }
    }
    return text || String(dictation || '').replace(/\s+/g, ' ').trim();
  }

  _restoreActivePreviewReplaceSpanIfNeeded(trigger = '') {
    if (this._hasDictationReplaceSpan('preview')) return true;
    this._syncPreviewStateBeforeReplaceOps(trigger);
    if (this._hasDictationReplaceSpan('preview')) return true;
    const saved = this._lastActivePreviewReplaceSpan;
    if (!saved || saved.target !== 'preview') return false;
    if (Date.now() - (saved.capturedAt || 0) > 30000) {
      this._lastActivePreviewReplaceSpan = null;
      return false;
    }
    const committedEl = this.fab?._getCommittedPreviewEl?.();
    if (!committedEl) return false;
    const len = this.fab?._normalizePreviewPlainText?.(committedEl.innerText || '')?.length
      ?? String(this.previewCommitted || '').length;
    const start = Math.max(0, Math.min(Number(saved.anchorStart) || 0, len));
    const end = Math.max(start, Math.min(Number(saved.anchorEnd) || start, len));
    if (end <= start) return false;
    this._dictationReplaceSpan = { target: 'preview', anchorStart: start, anchorEnd: end };
    if (this.fab) this.fab._previewSelectionOffsets = { start, end };
    chillicollectivoVoiceDiag('info', 'voice.replace-span-restored', 'Ersetzungsbereich aus letzter Markierung wiederhergestellt', {
      trigger,
      anchorStart: start,
      anchorEnd: end,
      spanLen: end - start,
    }, this);
    return true;
  }

  _previewDictationAlreadyApplied(currentText = '', dictationText = '') {
    const cur = String(currentText || '').replace(/\s+/g, ' ').trim();
    const add = String(dictationText || '').replace(/\s+/g, ' ').trim();
    if (!cur || !add) return false;
    const normCur = this._normalizeDuplicateText(cur);
    const normAdd = this._normalizeDuplicateText(add);
    if (normCur.includes(normAdd)) return true;
    if (add.split(/\s+/).filter(Boolean).length <= 2) {
      const addWord = this._normalizeDuplicateWord(this._lastPreviewToken(add));
      if (addWord.length >= 3) {
        const curWords = cur.split(/\s+/).filter(Boolean);
        if (curWords.some((word) => {
          const normWord = this._normalizeDuplicateWord(word);
          return normWord === addWord || this._isFuzzyDuplicateNorm(normWord, addWord);
        })) {
          return true;
        }
      }
    }
    return false;
  }

  _trimReplaceSpanTailEcho(tail = '', dictation = '', markedText = '') {
    let rest = String(tail || '');
    const dict = String(dictation || '').replace(/\s+/g, ' ').trim();
    const marked = String(markedText || '').replace(/\s+/g, ' ').trim();
    if (!rest.trim() || !dict) return rest;

    const normRest = this._normalizeDuplicateText(rest);
    const normDict = this._normalizeDuplicateText(dict);
    if (normRest && normDict.includes(normRest)) return '';
    if (normRest && normDict.endsWith(normRest) && normRest.length >= 3) return '';

    let overlap = this._sharedSuffixPrefixChars(dict, rest, 2, 96);
    if (overlap > 0) rest = rest.slice(overlap).replace(/^\s+/, '');

    const wordOverlap = this._tailHeadWordOverlapCount(dict, rest);
    if (wordOverlap > 0) {
      rest = rest.trim().split(/\s+/).slice(wordOverlap).join(' ').trim();
    }

    if (marked) {
      const markedTailOverlap = this._sharedSuffixPrefixChars(marked, rest, 2, 48);
      if (markedTailOverlap > 0) rest = rest.slice(markedTailOverlap).replace(/^\s+/, '');

      const markedWord = this._normalizeDuplicateWord(this._lastPreviewToken(marked));
      const restWord = this._normalizeDuplicateWord(this._firstPreviewToken(rest));
      if (markedWord && restWord && markedWord.length >= 3
        && (restWord.startsWith(markedWord.slice(1)) || this._isMidWordSplitHalves(markedWord, restWord))) {
        rest = rest.trim().split(/\s+/).slice(1).join(' ').trim();
      }
    }

    const combined = `${marked}${rest}`.replace(/\s+/g, ' ').trim();
    if (combined && this._isFuzzyDuplicateText(dict, combined)) return '';
    if (combined && normDict.includes(this._normalizeDuplicateText(combined))) return '';

    return rest;
  }

  _mergePreviewDisplaySegments(committedText = '', interimText = '') {
    if (this._mergingPreviewDisplay) {
      const c = String(committedText || '').replace(/\s+/g, ' ').trim();
      const i = String(interimText || '').replace(/\s+/g, ' ').trim();
      return this._normalizePreviewPunctuation(i || c);
    }
    this._mergingPreviewDisplay = true;
    try {
      return this._mergePreviewDisplaySegmentsInner(committedText, interimText);
    } finally {
      this._mergingPreviewDisplay = false;
    }
  }

  _mergePreviewDisplaySegmentsInner(committedText = '', interimText = '') {
    const committed = String(committedText || '').replace(/\s+/g, ' ').trim();
    const interim = String(interimText || '').replace(/\s+/g, ' ').trim();
    if (!interim) return this._normalizePreviewPunctuation(committed);
    if (!committed) return this._normalizePreviewPunctuation(interim);

    const normC = this._normalizeDuplicateText(committed);
    const normI = this._normalizeDuplicateText(interim);

    if (normC === normI) return committed;
    if (this._isFuzzyDuplicateText(committed, interim)) return committed;

    const sharedWords = this._sharedPrefixWordCount(committed, interim);
    const prefixLen = Math.min(committed.length, interim.length, 24);
    const sharedCharPrefix = prefixLen > 0 && committed.slice(0, prefixLen).toLowerCase() === interim.slice(0, prefixLen).toLowerCase();
    const sameUtterance = this._isSttUtteranceRefinement(committed, interim);
    const disjointPauseSegment = !sameUtterance
      && sharedWords === 0
      && !sharedCharPrefix
      && !interim.toLowerCase().startsWith(committed.slice(0, Math.min(20, committed.length)).toLowerCase())
      && !committed.toLowerCase().startsWith(interim.slice(0, Math.min(10, interim.length)).toLowerCase())
      && this._tailHeadWordOverlapCount(committed, interim) === 0
      && !this._isPartialTokenRefinement(committed, interim);

    if (sameUtterance) {
      return this._normalizePreviewPunctuation(this._pickSttRefinedPreviewText(committed, interim));
    }

    const tailMerged = this._mergeTailHeadOverlap(committed, interim);
    if (tailMerged) {
      return this._normalizePreviewPunctuation(tailMerged);
    }

    const partialMerged = this._mergePartialTokenRefinement(committed, interim);
    if (partialMerged) {
      return this._normalizePreviewPunctuation(partialMerged);
    }

    const midWordMerged = this._mergeMidWordSplitHalves(committed, interim);
    if (midWordMerged) {
      return this._normalizePreviewPunctuation(midWordMerged);
    }

    if (!disjointPauseSegment) {
      // Kumulatives STT-Interim: immer ersetzen, nie committed+delta stacken
      if (normI.includes(normC) && interim.length >= committed.length) return this._normalizePreviewPunctuation(interim);
      if (normC.includes(normI)) return this._normalizePreviewPunctuation(committed);
      if (interim.startsWith(committed) && interim.length > committed.length) return this._normalizePreviewPunctuation(interim);
      if (committed.startsWith(interim)) return this._normalizePreviewPunctuation(committed);
      return this._normalizePreviewPunctuation(interim.length >= committed.length ? interim : committed);
    }

    if (normI.includes(normC) && interim.length >= committed.length) {
      return this._normalizePreviewPunctuation(interim);
    }
    const disjointTailMerged = this._mergeTailHeadOverlap(committed, interim);
    if (disjointTailMerged) {
      return this._normalizePreviewPunctuation(disjointTailMerged);
    }
    if (this._tailHeadWordOverlapCount(committed, interim) > 0) {
      const overlapMerged = this._mergePartialTokenRefinement(committed, interim)
        || this._mergeMidWordSplitHalves(committed, interim);
      if (overlapMerged) return this._normalizePreviewPunctuation(overlapMerged);
    }

    chillicollectivoToolLog('info', 'voice.preview-segment-append', 'Neues Diktat-Segment nach Pause angehängt', {
      committedLen: committed.length,
      interimLen: interim.length,
      committedSnippet: committed.slice(-80),
      interimSnippet: interim.slice(0, 80),
      mergedLen: (`${committed} ${interim}`).trim().length,
      route: this._currentConversationKey(),
    });
    return this._normalizePreviewPunctuation(String(`${committed} ${interim}`).replace(/\s+/g, ' ').trim());
  }

  _finalizePreviewDisplaySegments(committedText = '', interimText = '') {
    const merged = this._mergePreviewDisplaySegmentsInner(committedText, interimText);
    return this._normalizePreviewPunctuation(merged, { finalize: true });
  }

  _hasSignificantPrefixOverlap(a, b, minWords = 2) {
    return this._sharedPrefixWordCount(a, b) >= minWords;
  }

  _dedupeInsertAgainstFieldContent(field, text, replaceStart, replaceEnd, hasSelection) {
    let insertText = String(text || '').replace(/\s+/g, ' ').trim();
    if (!insertText || !field) return insertText;

    const cur = this._fieldPlainText(field);
    if (!cur.trim()) return insertText;

    let start = replaceStart;
    let end = replaceEnd;

    if (!hasSelection && start >= cur.length) {
      if (this._isDirectAppendRecordingSession()) return insertText;
      const delta = this._stripPrefixOverlap(cur, insertText);
      const sharedWords = this._sharedPrefixWordCount(cur, insertText);
      if (delta !== insertText && this._hasSignificantPrefixOverlap(cur, insertText) && delta) {
        chillicollectivoToolLog('info', 'voice.insert-field-prefix-stripped', 'Feld-Prefix beim Einfügen abgezogen', {
          originalLen: insertText.length,
          deltaLen: delta.length,
          sharedPrefixWords: sharedWords,
          fieldTail: cur.slice(-100),
          insertHead: insertText.slice(0, 100),
          deltaSnippet: delta.slice(0, 100),
          route: this._currentConversationKey(),
        });
        insertText = delta;
      }
      return insertText;
    }

    if (!hasSelection && start === 0 && end === 0) {
      const lead = cur.trimStart();
      if (lead && this._isPrefixExtensionDuplicate(lead, insertText)) {
        const delta = this._stripPrefixOverlap(lead, insertText);
        if (delta && delta !== insertText) {
          chillicollectivoToolLog('info', 'voice.insert-field-lead-replaced', 'Bestehender Feldanfang wird beim Einfügen ersetzt', {
            leadSnippet: lead.slice(0, 80),
            deltaSnippet: delta.slice(0, 80),
            sharedPrefixWords: this._sharedPrefixWordCount(lead, insertText),
            replaceEnd: cur.length,
            route: this._currentConversationKey(),
          });
          return { text: delta, replaceStart: 0, replaceEnd: cur.length };
        }
      }
    }

    return insertText;
  }

  _isPrefixExtensionDuplicate(base, candidate) {
    const baseWords = this._splitDuplicateWords(base);
    const candWords = this._splitDuplicateWords(candidate);
    if (!baseWords.length || !candWords.length) return false;
    if (baseWords.length === 1 && candWords.length === 1) {
      return this._normalizeDuplicateWord(baseWords[0]) === this._normalizeDuplicateWord(candWords[0]);
    }
    const shared = this._sharedPrefixWordCount(base, candidate);
    if (shared < 2) return false;
    const shorterLen = Math.min(baseWords.length, candWords.length);
    return shared === shorterLen;
  }

  _resolveFinalDelta(text) {
    const trimmed = String(text || '').replace(/\s+/g, ' ').trim();
    if (!trimmed) return { delta: '', accumulated: '', isDuplicate: true };

    if (this._isCaptureActive()) {
      return { delta: trimmed, accumulated: trimmed, isDuplicate: false };
    }

    const now = Date.now();
    if (this._continuingDictationAfterFlushUntil && now < this._continuingDictationAfterFlushUntil) {
      return { delta: trimmed, accumulated: trimmed, isDuplicate: false };
    }

    const last = String(this._lastFinalSentText || '').replace(/\s+/g, ' ').trim();
    if (!last || now - (this._lastFinalSentAt || 0) >= this._FINAL_DEDUPE_MS) {
      return { delta: trimmed, accumulated: trimmed, isDuplicate: false };
    }

    if (trimmed === last) return { delta: '', accumulated: last, isDuplicate: true };

    const remainder = this._stripPrefixOverlap(last, trimmed);
    if (!remainder) {
      return { delta: '', accumulated: last, isDuplicate: true };
    }

    if (remainder !== trimmed) {
      return {
        delta: remainder,
        accumulated: `${last} ${remainder}`.replace(/\s+/g, ' ').trim(),
        isDuplicate: false,
      };
    }

    return { delta: trimmed, accumulated: trimmed, isDuplicate: false };
  }

  _normalizeDuplicateText(text) {
    return String(text || '')
      .replace(/[“”„"]/g, '"')
      .replace(/[’‘]/g, "'")
      .replace(/\bna\s+ja\b/gi, 'naja')
      .replace(/\bweiss\b/gi, 'weiß')
      .replace(/ß/g, 'ss')
      .replace(/[^a-z0-9äöüÄÖÜ\s]+/g, ' ')
      .replace(/\s+/g, ' ')
      .trim()
      .toLowerCase();
  }

  _tailForDuplicateCompare(currentText, candidateText) {
    const current = String(currentText || '').trim();
    const candidate = String(candidateText || '').trim();
    if (!current || !candidate) return '';
    const words = current.split(/\s+/);
    const candidateWords = Math.max(8, candidate.split(/\s+/).length + 4);
    return words.slice(-candidateWords).join(' ');
  }

  _isFuzzyDuplicateNorm(a, b) {
    const left = String(a || '').trim();
    const right = String(b || '').trim();
    if (!left || !right) return false;
    if (left === right) return true;
    if (left.length >= 25 && right.includes(left)) return true;
    if (right.length >= 25 && left.includes(right)) return true;
    const compactLeft = left.replace(/\s+/g, '');
    const compactRight = right.replace(/\s+/g, '');
    if (compactLeft.length >= 25 && compactRight.includes(compactLeft)) return true;
    if (compactRight.length >= 25 && compactLeft.includes(compactRight)) return true;

    const leftWords = left.split(/\s+/).filter((w) => w.length > 1);
    const rightWords = right.split(/\s+/).filter((w) => w.length > 1);
    if (leftWords.length < 4 || rightWords.length < 4) return false;
    const counts = new Map();
    rightWords.forEach((word) => counts.set(word, (counts.get(word) || 0) + 1));
    let common = 0;
    leftWords.forEach((word) => {
      const n = counts.get(word) || 0;
      if (n > 0) {
        common++;
        counts.set(word, n - 1);
      }
    });
    const coverage = common / Math.min(leftWords.length, rightWords.length);
    const balance = Math.min(leftWords.length, rightWords.length) / Math.max(leftWords.length, rightWords.length);
    return coverage >= 0.86 && balance >= 0.72;
  }

  _isFuzzyDuplicateText(a, b) {
    return this._isFuzzyDuplicateNorm(this._normalizeDuplicateText(a), this._normalizeDuplicateText(b));
  }

  _isPreviewCorrectionRetake(currentText, candidateText) {
    const current = String(currentText || '').replace(/\s+/g, ' ').trim();
    const candidate = String(candidateText || '').replace(/\s+/g, ' ').trim();
    if (!current || !candidate) return false;
    if (this._normalizeDuplicateText(current) === this._normalizeDuplicateText(candidate)) return false;
    if (!this._isFuzzyDuplicateText(current, candidate)) return false;

    const currentWords = current.split(/\s+/).filter(Boolean);
    const candidateWords = candidate.split(/\s+/).filter(Boolean);
    if (currentWords.length < 4 || candidateWords.length < 4) return false;

    const lenRatio = Math.min(currentWords.length, candidateWords.length)
      / Math.max(currentWords.length, candidateWords.length);
    // Kürzerer Kandidat nach Pause = neue Phrase, keine Vollkorrektur.
    if (lenRatio < 0.92) return false;
    if (candidate.length < current.length * 0.92) return false;

    const normWord = (word) => word.toLowerCase().replace(/[^a-z0-9äöü]+/g, '');
    const wordBagDiff = (leftWords, rightWords) => {
      const counts = new Map();
      rightWords.forEach((word) => {
        const key = normWord(word);
        if (!key) return;
        counts.set(key, (counts.get(key) || 0) + 1);
      });
      let diff = 0;
      leftWords.forEach((word) => {
        const key = normWord(word);
        if (!key) return;
        const n = counts.get(key) || 0;
        if (n > 0) counts.set(key, n - 1);
        else diff++;
      });
      counts.forEach((n) => { diff += n; });
      return diff;
    };

    return wordBagDiff(currentWords, candidateWords) >= 1;
  }

  _replacePreviewCommitted(text, reason = 'preview-correction') {
    const clean = String(text || '').replace(/\s+/g, ' ').trim();
    if (!clean) return false;
    const previewBefore = String(this.previewCommitted || this.fab?._snapshotPreviewCommitted?.() || '').trim();
    if (previewBefore && clean.length < previewBefore.length * 0.92) {
      chillicollectivoVoiceDiag('warn', 'voice.preview-corrected-skipped', 'Vorschau-Korrektur verworfen – Text wäre kürzer geworden', {
        previewBefore: previewBefore.slice(0, 300),
        candidate: clean.slice(0, 300),
        beforeLen: previewBefore.length,
        candidateLen: clean.length,
        reason,
      }, this);
      return false;
    }
    const cleanNorm = this._normalizeDuplicateText(clean);
    const route = this._currentConversationKey();
    const committedEl = this.fab?._getCommittedPreviewEl?.();
    if (committedEl) {
      committedEl.textContent = clean;
      this.fab?.syncPreviewCommittedFromDom?.();
    } else {
      this.previewCommitted = clean;
    }
    this._lastPreviewBufferNorm = cleanNorm;
    this._lastPreviewBufferAt = Date.now();
    this._lastPreviewBufferRoute = route;
    this._currentInterimText = '';
    this._lastInterim = '';
    this._pinDictationPreview();
    this._showDictationPreview('', '', {
      dictation: true,
      insertHint: this._getPreviewInsertHint(),
    });
    chillicollectivoLogVoiceDictation('vorschau-korrektur', clean, { route, reason });
    chillicollectivoToolLog('info', 'voice.preview-corrected', 'Vorschau durch STT-Korrektur ersetzt', {
      previewBefore: previewBefore.slice(0, 300),
      previewAfter: clean.slice(0, 300),
      dictationText: clean,
      dictationTextSnippet: clean.slice(0, 160),
      reason,
      route,
    });
    return true;
  }

  _fieldAlreadyContainsInsert(field, text) {
    const insertNorm = this._normalizeDuplicateText(text);
    if (!insertNorm) return false;
    const fieldNorm = this._normalizeDuplicateText(this._fieldPlainText(field));
    if (!fieldNorm) return false;
    if (this._isFuzzyDuplicateNorm(insertNorm, fieldNorm)) return true;
    const fieldTail = this._tailForDuplicateCompare(this._fieldPlainText(field), text);
    return !!fieldTail && this._isFuzzyDuplicateText(text, fieldTail);
  }

  _rememberCommittedInsert(text) {
    this._lastCommittedInsertNorm = this._normalizeDuplicateText(text);
    this._lastCommittedInsertAt = Date.now();
    this._lastCommittedInsertPlain = String(text || '').trim();
    const route = this._currentConversationKey();
    if (route) {
      this._lastInsertedByRoute.set(route, {
        text: this._lastCommittedInsertPlain,
        norm: this._lastCommittedInsertNorm,
        at: this._lastCommittedInsertAt,
      });
    }
  }

  _getLastInsertedForRoute(route = this._currentConversationKey()) {
    return route ? (this._lastInsertedByRoute.get(route) || null) : null;
  }

  _forgetStaleDuplicateMemory() {
    const field = this._resolveTextField();
    const previewText = String(this.previewCommitted || '').trim();

    if (this._lastInsertedText) {
      const inField = field && this._isUsableTextField(field)
        && this._fieldAlreadyContainsInsert(field, this._lastInsertedText);
      const inPreview = previewText && this._isFuzzyDuplicateText(previewText, this._lastInsertedText);
      if (!inField && !inPreview) {
        this._lastInsertedText = '';
        this._lastInsertedTime = 0;
      }
    }

    const route = this._currentConversationKey();
    if (route) {
      const routeLast = this._lastInsertedByRoute.get(route);
      if (routeLast?.text) {
        const inField = field && this._isUsableTextField(field)
          && this._fieldAlreadyContainsInsert(field, routeLast.text);
        const inPreview = previewText && this._isFuzzyDuplicateText(previewText, routeLast.text);
        if (!inField && !inPreview) {
          this._lastInsertedByRoute.delete(route);
        }
      }
    }

    if (!previewText) {
      this._lastPreviewBufferNorm = '';
      this._lastPreviewBufferRoute = '';
      this._lastPreviewBufferAt = 0;
    }
  }

  _resetPreviewRedoState(reason = 'unknown') {
    chillicollectivoVoiceDiag('info', 'voice.preview-redo-reset', 'Vorschau-Redo-Status zurückgesetzt', {
      reason,
      hadFlushedNorm: !!this._flushedPreviewNorm,
      flushedPreviewAgeMs: this._flushedPreviewAt ? Date.now() - this._flushedPreviewAt : null,
    }, this);
    this._discardedPreviewNorm = '';
    this._discardedPreviewRoute = '';
    this._forgetStaleDuplicateMemory();
  }

  _rememberSentDuplicateGuard(field) {
    const route = this._currentConversationKey();
    if (!route) return;
    const plain = this._fieldPlainText(field);
    if (!plain.trim()) return;
    const bodyNorm = this._normalizeSubmitBody(plain);
    const entry = {
      bodyNorm,
      plainText: plain,
      insertNorm: this._normalizeDuplicateText(plain),
      at: Date.now(),
    };
    this._sentDuplicateGuardByRoute.set(route, entry);
    this._globalSentByBodyNorm.set(bodyNorm, { route, plainText: plain, at: entry.at });
  }

  _getSentDuplicateGuard(route = this._currentConversationKey()) {
    return route ? (this._sentDuplicateGuardByRoute.get(route) || null) : null;
  }

  _getGlobalDuplicateSendBlockReason(plain, route = this._currentConversationKey()) {
    if (!plain.trim() || !route) return null;
    const bodyNorm = this._normalizeSubmitBody(plain);
    for (const [storedNorm, meta] of this._globalSentByBodyNorm.entries()) {
      if (!meta?.route || meta.route === route) continue;
      if (bodyNorm === storedNorm) return 'already-sent-other-customer-exact';
      if (this._isFuzzyDuplicateText(plain, meta.plainText)) return 'already-sent-other-customer-fuzzy';
    }
    return null;
  }

  _getDuplicateSendBlockReason(field) {
    const plain = this._fieldPlainText(field);
    if (!plain.trim()) return null;

    const bodyNorm = this._normalizeSubmitBody(plain);
    const route = this._currentConversationKey();
    if (this._pendingFirstSendNorm && bodyNorm === this._pendingFirstSendNorm) {
      return null;
    }
    if (this._pendingFirstSendNorm && this._isFuzzyDuplicateText(plain, this._pendingFirstSendPlain || '')) {
      return null;
    }

    const globalReason = this._getGlobalDuplicateSendBlockReason(plain, route);
    if (globalReason) return globalReason;

    const sent = this._getSentDuplicateGuard();
    if (sent) {
      if (bodyNorm === sent.bodyNorm) return 'already-sent-exact';
      if (this._isFuzzyDuplicateText(plain, sent.plainText)) return 'already-sent-fuzzy';
    }

    if (this._lastCommittedInsertNorm) {
      const insertNorm = this._normalizeDuplicateText(plain);
      if (sent && insertNorm === sent.insertNorm) return 'duplicate-already-sent-insert';
      if (sent && this._isFuzzyDuplicateNorm(insertNorm, sent.insertNorm)) {
        return 'duplicate-already-sent-insert-fuzzy';
      }
    }

    return null;
  }

  _blockDuplicateSendWithFeedback(field, source = 'send') {
    const reason = this._getDuplicateSendBlockReason(field);
    if (!reason) return false;
    const plain = this._fieldPlainText(field);
    const otherCustomer = reason.includes('other-customer');
    console.warn('[CHILLICOLLECTIVO] KRITISCH: Gleicher Text – Senden blockiert:', plain.slice(0, 120));
    chillicollectivoToolLog('error', 'voice.duplicate-send', otherCustomer
      ? 'Gleicher Text – Senden an anderen Kunden blockiert'
      : 'Gleicher Text wie zuvor – Senden sofort blockiert', {
      reason,
      source,
      bodyNorm: this._normalizeSubmitBody(plain),
      route: this._currentConversationKey(),
      fieldTail: plain.slice(-500),
    });
    this._voiceCommandSuppressUntil = Math.max(this._voiceCommandSuppressUntil || 0, Date.now() + CHILLICOLLECTIVO_POST_SEND_COOLDOWN_MS);
    this.fab?.showPreview('Duplikat', otherCustomer ? 'Schon an anderem Kunden' : 'Bereits gesendet');
    setTimeout(() => this.fab?.hidePreview?.({ force: true }), 2400);
    return true;
  }

  _isRecentCommittedInsert(text) {
    const norm = this._normalizeDuplicateText(text);
    return !!norm
      && norm === this._lastCommittedInsertNorm
      && Date.now() - (this._lastCommittedInsertAt || 0) < 12000;
  }

  _shouldBlockDuplicateSubmit(field) {
    const now = Date.now();
    if (this._submitInFlightPromise) {
      console.warn('[CHILLICOLLECTIVO] Senden blockiert – Sendevorgang läuft');
      return true;
    }
    if (this._getDuplicateSendBlockReason(field)) {
      this._blockDuplicateSendWithFeedback(field, 'duplicate-submit-guard');
      return true;
    }
    if (typeof chillicollectivoIsGlobalSendHardLocked === 'function' && chillicollectivoIsGlobalSendHardLocked()) {
      console.warn('[CHILLICOLLECTIVO] Senden blockiert – 6s Sperre nach Senden');
      return true;
    }
    if (this._lastSubmitAt && now - this._lastSubmitAt < this._SUBMIT_COOLDOWN_MS) {
      console.warn('[CHILLICOLLECTIVO] Senden blockiert – 6s Sperre nach Senden');
      return true;
    }

    const bodyNorm = this._normalizeSubmitBody(this._fieldPlainText(field));
    if (!bodyNorm || !this._composeHasSendableText(field)) {
      console.warn('[CHILLICOLLECTIVO] Senden blockiert – kein Text im Chatfeld');
      chillicollectivoToolLog('warn', 'voice.send-no-text', 'Senden blockiert – weder Text noch Bild im Chatfeld', {
        route: this._currentConversationKey(),
        bodyNorm,
      });
      return true;
    }
    return false;
  }

  _armSubmitInFlight(field) {
    this._lastHardSendRoute = this._currentConversationKey();
    this._armedSubmitRoute = this._currentConversationKey();
    chillicollectivoArmGlobalSendLock({
      route: this._currentConversationKey(),
      source: 'voice',
    });
    chillicollectivoToolLog('info', 'voice.submit-armed', 'Sendevorgang gestartet', {
      route: this._currentConversationKey(),
      bodyNorm: this._normalizeSubmitBody(this._fieldPlainText(field)),
    });
  }

  _releaseSubmitInFlight() {
    this._armedSubmitRoute = '';
    chillicollectivoReleaseGlobalSendInFlight();
  }

  _markSubmitStarted(field) {
    const now = Date.now();
    globalThis.__chillicollectivoExtensionSendAt = now;
    this._lastSubmitAt = now;
    this._lastSubmitBodyNorm = this._normalizeSubmitBody(this._fieldPlainText(field));
    this._voiceCommandSuppressUntil = now + this._SUBMIT_COOLDOWN_MS;
    this._pendingFirstSendNorm = '';
    this._pendingFirstSendPlain = '';
    this._armedSubmitRoute = '';
    this._rememberSentDuplicateGuard(field);
    chillicollectivoConfirmGlobalSendLock();
    chillicollectivoToolLog('warn', 'voice.submit', 'Extension-Sendeklick gestartet', {
      bodyNorm: this._lastSubmitBodyNorm,
      route: this._currentConversationKey(),
    });
    chillicollectivoLogVoiceDictation('gesendet', this._lastSubmitBodyNorm, {
      route: this._currentConversationKey(),
    });
  }

  _clearOutPreviewWaitTimer() {
    if (this._outPreviewWaitTimer) {
      clearInterval(this._outPreviewWaitTimer);
      this._outPreviewWaitTimer = null;
    }
  }

  _renderSentOutPreview(count, target, subtitle = '') {
    const waitSec = chillicollectivoReadTorchwoodSendCountdownSeconds(this._findComposeTextField());
    const label = chillicollectivoFormatOutPreviewLabel(count, target, waitSec);
    this.fab?.showPreview(label, subtitle, { forceShow: true });
    this.fab?._applySentOutPreviewGlow?.();
  }

  _showSentOutPreview(subtitle = '', skipCounterBump = false) {
    this._unpinDictationPreview();
    this._schedulePostSendRecovery();
    if (this.fab?._isCrumblingPreview) {
      setTimeout(() => this._showSentOutPreview(subtitle, skipCounterBump), 1100);
      return;
    }
    try {
      chrome.storage.local.get([CHILLICOLLECTIVO_OUT_COUNTER_ENABLED_KEY], (enabledData) => {
        if (enabledData?.[CHILLICOLLECTIVO_OUT_COUNTER_ENABLED_KEY] === false) {
          chillicollectivoToolLog('info', 'voice.out-count-disabled', 'Out-Zähler aus – Mic-Neustart trotzdem nach Senden', {
            route: this._currentConversationKey(),
          });
          this._restartMicAfterSend({ trigger: 'manual-send', outCounted: false });
          return;
        }
        this._bumpAndRenderOutPreview(subtitle, skipCounterBump);
      });
    } catch (_) {
      this._bumpAndRenderOutPreview(subtitle, skipCounterBump);
    }
  }

  _bumpAndRenderOutPreview(subtitle = '', skipCounterBump = false) {
    if (!skipCounterBump) {
      const now = Date.now();
      const last = globalThis.__chillicollectivoLastOutPreviewBumpAt || 0;
      if (now - last < CHILLICOLLECTIVO_OUT_BUMP_DEDUPE_MS) {
        chillicollectivoToolLog('info', 'voice.out-count-deduped', 'Out-Zähler dedupliziert – Mic-Neustart trotzdem', {
          sinceLastMs: now - last,
          route: this._currentConversationKey(),
        });
        this._restartMicAfterSend({ trigger: 'manual-send-deduped', outCounted: false });
        return;
      }
      globalThis.__chillicollectivoLastOutPreviewBumpAt = now;
    }
    try {
      chrome.storage.local.get([
        CHILLICOLLECTIVO_OUT_COUNTER_KEY,
        CHILLICOLLECTIVO_OUT_COUNTER_TOTAL_KEY,
        CHILLICOLLECTIVO_OUT_TARGET_KEY,
      ], (data) => {
        const current = Number(data?.[CHILLICOLLECTIVO_OUT_COUNTER_KEY]) || 0;
        const totalCurrent = Number(data?.[CHILLICOLLECTIVO_OUT_COUNTER_TOTAL_KEY]) || 0;
        const next = skipCounterBump ? current : current + 1;
        const totalNext = skipCounterBump ? totalCurrent : totalCurrent + 1;
        const target = data?.[CHILLICOLLECTIVO_OUT_TARGET_KEY];
        const payload = {
          [CHILLICOLLECTIVO_OUT_COUNTER_KEY]: next,
          [CHILLICOLLECTIVO_OUT_COUNTER_TOTAL_KEY]: totalNext,
        };
        chrome.storage.local.set(payload, () => {
          this._clearOutPreviewWaitTimer();
          this._renderSentOutPreview(next, target, subtitle);
          this._restartMicAfterSend({
            trigger: 'out-counted',
            outCounted: !skipCounterBump,
            outCount: next,
            outTarget: target,
          });
          this._outPreviewWaitTimer = setInterval(() => {
            this._renderSentOutPreview(next, target, subtitle);
          }, 500);
          this._scheduleSentPreviewCrumble();
          if (!skipCounterBump && this._isMicTipsEnabled()) {
            const milestoneTip = typeof chillicollectivoGetOutMilestoneTip === 'function'
              ? chillicollectivoGetOutMilestoneTip(next)
              : null;
            if (milestoneTip?.text) {
              this.fab?.showMicTipBubble(
                milestoneTip.text,
                milestoneTip.durationMs || CHILLICOLLECTIVO_OUT_MILESTONE_TIP_MS,
                milestoneTip,
              );
            }
          }
          if (!skipCounterBump && typeof chillicollectivoTryGrantOutMilestoneReward === 'function') {
            chillicollectivoTryGrantOutMilestoneReward(totalNext)
              .then(async (result) => {
                if (result?.granted && this._isMicTipsEnabled()) {
                  await chillicollectivoBroadcastLicenseState?.();
                  chrome.runtime.sendMessage({
                    type: 'OUT_REWARD_GRANTED',
                    reward: result.reward,
                  }).catch(() => {});
                  const sub = result.tier === 2
                    ? '2 Monate gratis – Schlüssel in den Einstellungen'
                    : '1 Monat gratis – Schlüssel in den Einstellungen';
                  this.fab?.showMicTipBubble(`25'000 Outs! ${sub}`, 4200);
                  return;
                }
                if (result?.maxReached && this._isMicTipsEnabled()) {
                  this.fab?.showMicTipBubble(result.message || 'Alle Boni erhalten', 3600);
                }
              })
              .catch(() => {});
          }
        });
      });
    } catch (_) {
      this._clearOutPreviewWaitTimer();
      this.fab?.showPreview('Gesendet', subtitle, { forceShow: true });
      this.fab?._applySentOutPreviewGlow?.();
      this._restartMicAfterSend({ trigger: 'manual-send-fallback', outCounted: false });
      this._scheduleSentPreviewCrumble();
    }
  }

  _scheduleSentPreviewCrumble(delay = CHILLICOLLECTIVO_OUT_PREVIEW_HOLD_MS) {
    if (this._sentPreviewCrumbleTimer) {
      clearTimeout(this._sentPreviewCrumbleTimer);
      this._sentPreviewCrumbleTimer = null;
    }
    this._sentPreviewCrumbleTimer = setTimeout(() => {
      this._sentPreviewCrumbleTimer = null;
      this._clearOutPreviewWaitTimer();
      this.fab?.crumblePreviewAndHide?.(780);
    }, delay);
  }

  _showManualSendRequiredPreview(field) {
    const plain = this._fieldPlainText(field);
    const preview = (plain || this.previewCommitted || '').trim();
    if (preview) {
      const label = preview.length > 160 ? `${preview.slice(0, 157)}…` : preview;
      this.fab?.showPreview(label, 'Sendeknopf drücken');
    } else {
      this.fab?.showPreview('Nicht gesendet', 'Sendeknopf drücken');
    }
    this._logSendButtonHint(field, 'manual-send-preview');
  }

  _markManualSendRequired(field, source) {
    this._showManualSendRequiredPreview(field);
    const timing = this._getDialogTimingMeta();
    chillicollectivoToolLog('info', 'voice.send-manual-required', `Senden fehlgeschlagen – manuell senden (${this._formatDialogTimingLabel(timing)})`, {
      route: this._currentConversationKey(),
      source,
      countdownSec: chillicollectivoReadTorchwoodSendCountdownSeconds(field),
      sendDisabled: !!chillicollectivoResolveSendButtonForField(field)?.disabled,
      ...timing,
    });
  }

  _performSubmitClick(field) {
    if (!this._composeHasSendableText(field)) return false;
    const sendButton = chillicollectivoResolveSendButtonForField(field);
    if (sendButton && !sendButton.disabled) {
      return chillicollectivoClickSendButton(sendButton, { source: 'extension-submit' });
    }
    if (field) {
      const form = field.closest?.('form');
      if (form) {
        form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
        return true;
      }
    }
    return false;
  }

  async _performSubmitClickWithRetry(field) {
    const armedRoute = this._armedSubmitRoute || this._currentConversationKey();
    const currentRoute = this._currentConversationKey();
    if (armedRoute && currentRoute && armedRoute !== currentRoute) {
      chillicollectivoToolLog('error', 'voice.send-wrong-chat', 'Senden abgebrochen – Chat gewechselt', {
        armedRoute,
        currentRoute,
      });
      this.fab?.showPreview('Chat gewechselt', 'Senden abgebrochen');
      setTimeout(() => this.fab?.hidePreview?.({ force: true }), 2400);
      return 'blocked-manual-send';
    }

    if (!this._composeHasSendableText(field)) {
      this._markManualSendRequired(field, 'send-no-text');
      return 'blocked-manual-send';
    }

    if (chillicollectivoIsTorchwoodSendGateBlocked(field)) {
      this._markManualSendRequired(field, 'send-gate');
      return 'blocked-manual-send';
    }

    const sendButton = chillicollectivoResolveSendButtonForField(field);
    if (sendButton && !sendButton.disabled) {
      return chillicollectivoClickSendButton(sendButton, { source: 'extension-submit' });
    }

    for (let attempt = 0; attempt < 12; attempt += 1) {
      if (armedRoute && this._currentConversationKey() !== armedRoute) {
        chillicollectivoToolLog('error', 'voice.send-wrong-chat', 'Senden abgebrochen – Chat gewechselt waehrend Versuch', {
          armedRoute,
          currentRoute: this._currentConversationKey(),
        });
        this.fab?.showPreview('Chat gewechselt', 'Senden abgebrochen');
        setTimeout(() => this.fab?.hidePreview?.({ force: true }), 2400);
        return 'blocked-manual-send';
      }
      if (chillicollectivoIsTorchwoodSendGateBlocked(field)) {
        this._markManualSendRequired(field, 'send-gate-retry');
        return 'blocked-manual-send';
      }

      const retryButton = chillicollectivoResolveSendButtonForField(field);
      if (retryButton && !retryButton.disabled) {
        return chillicollectivoClickSendButton(retryButton, { source: 'extension-submit-retry' });
      }

      if (this._performSubmitClick(field)) {
        return true;
      }

      await new Promise((resolve) => setTimeout(resolve, 120));
    }

    chillicollectivoToolLog('warn', 'voice.submit-uncertain', 'Sendeklick fehlgeschlagen – manuell senden', {
      route: this._currentConversationKey(),
      hasField: !!field,
      countdownSec: chillicollectivoReadTorchwoodSendCountdownSeconds(field),
    });
    this._markManualSendRequired(field, 'send-uncertain');
    return 'blocked-manual-send';
  }

  async requestSubmit(field) {
    const target = this._findComposeTextField();
    if (!target || !this.isTextField(target)) return false;
    if (this._getDuplicateSendBlockReason(target)) {
      this._blockDuplicateSendWithFeedback(target, 'request-submit');
      return false;
    }
    if (this._shouldBlockDuplicateSubmit(target)) return false;

    this._armSubmitInFlight(target);
    this._submitInFlightPromise = this._performSubmitClickWithRetry(target);
    let ok = false;
    try {
      const result = await this._submitInFlightPromise;
      if (result === 'blocked-manual-send') {
        this._releaseSubmitInFlight();
        return false;
      }
      ok = result === true;
    } finally {
      this._submitInFlightPromise = null;
    }
    if (ok) {
      this._markSubmitStarted(target);
      this.recordingStartPosition = null;
    } else {
      this._releaseSubmitInFlight();
    }
    return ok;
  }

  async handleTabEnterSend() {
    if (this._isHardSendLocked()) {
      this.fab?.showPreview('Kurz warten', '6s nach Senden');
      setTimeout(() => this.fab?.hidePreview?.({ force: true }), 1400);
      return;
    }
    const field = this._findComposeTextField();
    if (!field || !this.isTextField(field)) {
      this.fab?.showPreview('Kein Chatfeld', 'Nur unten im Chat senden');
      setTimeout(() => this.fab?.hidePreview?.({ force: true }), 1800);
      return;
    }

    if (this._getDuplicateSendBlockReason(field)) {
      this._blockDuplicateSendWithFeedback(field, 'tab-enter-send');
      return;
    }

    chillicollectivoToolLog('info', 'voice.tab-enter-send', 'Tab-Enter-Senden ausgelöst', {
      route: this._currentConversationKey(),
      ...this._getDialogTimingMeta(),
    });

    if (document.documentElement?.dataset?.chillicollectivoTestHarbor === '1' && typeof window.__chillicollectivoTestHarbor?.sendMessage === 'function') {
      if (this._shouldBlockDuplicateSubmit(field)) return;
      this._armSubmitInFlight(field);
      const ok = window.__chillicollectivoTestHarbor.sendMessage();
      this.previewCommitted = '';
      this._currentInterimText = '';
      this._lastInterim = '';
      if (ok) {
        this._markSubmitStarted(field);
        this._restartMicAfterSend({ trigger: 'tab-enter-send-test', outCounted: false });
      } else {
        this._releaseSubmitInFlight();
        this.fab?.showPreview('Leer', 'Erst Nachricht diktieren');
      }
      if (!ok) setTimeout(() => this.fab?.hidePreview?.({ force: true }), 1400);
      return;
    }

    if (!(await this.requestSubmit(field))) {
      this._markManualSendRequired(field, 'tab-enter-send-failed');
      return;
    }

    this.previewCommitted = '';
    this._currentInterimText = '';
    this._lastInterim = '';
    this._restartMicAfterSend({ trigger: 'tab-enter-send', outCounted: false });
  }

  submitMessageForField(field) {
    this.requestSubmit(field);
  }

  submitMessageViaTabEnter() {
    this.requestSubmit(this._findComposeTextField());
  }

  submitCurrentForm() {
    this.requestSubmit(this._findComposeTextField());
  }

  _hasLivePreview() {
    return !!(this._currentInterimText || '').trim() || !!this.fab?._previewVisible;
  }

  _stopFabMotion() {
    this._micSpeakingActive = false;
    this._micSpeechOnStreak = 0;
    this._smoothedMicLevel = 0;
    this.fab?.resetLiveBroil();
  }

  cleanPunctuationSpacing(text) { return text.replace(/\s+([.,!?;:])/g, '$1'); }

  _getLiveDictationCharCount() {
    return (this._currentInterimText || '').trim().length;
  }

  _appendToField(field, text) {
    if (!field || !text) return;
    this._captureDictationReplaceSpan();
    if (this._hasDictationReplaceSpan('field') && this._dictationReplaceSpan.field === field) {
      this._insertFieldTextAtSpan(field, text, this._dictationReplaceSpan);
      return;
    }
    if (!field.isContentEditable && typeof field.selectionStart === 'number') {
      this.recordingStartPosition = field.selectionStart ?? field.value?.length ?? 0;
      this.recordingSelectionEnd = field.selectionEnd ?? this.recordingStartPosition;
    } else if (field.isContentEditable) {
      const sel = window.getSelection();
      if (sel?.rangeCount > 0) this.recordingStartPosition = sel.getRangeAt(0).cloneRange();
    }
    this._prepareFieldForInsert(field);

    if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(field)
      && typeof chillicollectivoWriteProfileField === 'function') {
      const cur = field.value || '';
      const pos = typeof this.recordingStartPosition === 'number'
        ? Math.min(Math.max(0, this.recordingStartPosition), cur.length)
        : cur.length;
      const end = typeof field.selectionEnd === 'number' ? field.selectionEnd : pos;
      const before = cur.substring(0, pos);
      const after = cur.substring(end);
      const prefix = before && !/\s$/.test(before) && !/^\s/.test(text) ? ' ' : '';
      const next = `${before}${prefix}${text}${after}`;
      chillicollectivoWriteProfileField(field, next, false, {
        source: 'voice-dictation-prepare',
        dictationText: text,
        markedText: end > pos ? cur.substring(pos, end) : '',
      });
      this.recordingStartPosition = before.length + prefix.length + text.length;
      return;
    }

    if (field.isContentEditable) {
      const sel = window.getSelection();
      if (sel?.rangeCount > 0) {
        const range = sel.getRangeAt(0);
        range.deleteContents();
        range.insertNode(document.createTextNode(text));
        range.collapse(false);
        sel.removeAllRanges();
        sel.addRange(range);
        this.recordingStartPosition = range.cloneRange();
      } else {
        field.textContent = `${field.textContent || ''}${text}`;
        this.recordingStartPosition = (field.textContent || '').length;
      }
      field.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
      return;
    }

    const cur = field.value || '';
    const hasSelection = field.selectionStart !== field.selectionEnd;
    const replaceStart = hasSelection
      ? field.selectionStart
      : (typeof this.recordingStartPosition === 'number' ? this.recordingStartPosition : cur.length);
    const replaceEnd = hasSelection ? field.selectionEnd : replaceStart;
    const before = cur.substring(0, replaceStart);
    const after = cur.substring(replaceEnd);
    const prefix = before && !/\s$/.test(before) && !/^\s/.test(text) ? ' ' : '';
    const next = `${before}${prefix}${text}${after}`;
    field.value = next;
    const cp = before.length + prefix.length + text.length;
    field.selectionStart = field.selectionEnd = cp;
    this.recordingStartPosition = cp;
    field.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
    field.dispatchEvent(new Event('change', { bubbles: true }));
  }

  insertText(text, isFinal = false) {
    text = typeof chillicollectivoProcessSpeechText === 'function'
      ? chillicollectivoProcessSpeechText(text)
      : typeof chillicollectivoNormalizeAgentSpeechText === 'function'
        ? chillicollectivoNormalizeAgentSpeechText(text)
        : String(text || '').replace(/ß/g, 'ss').replace(/\b(ausrufezeichen)\b/gi, '!');

    if (!isFinal && typeof chillicollectivoStripHiddenGoPhrases === 'function') {
      text = chillicollectivoStripHiddenGoPhrases(text);
    }

    const rawBeforeSendStrip = text;
    text = chillicollectivoStripVoiceSendTokens(text);

    if (isFinal && !(text || '').trim()) {
      const sendSplit = typeof chillicollectivoSplitGoSendFromText === 'function'
        ? chillicollectivoSplitGoSendFromText(rawBeforeSendStrip)
        : null;
      const sendOnly = sendSplit?.triggerSend
        || (typeof chillicollectivoParseGoSendCommand === 'function'
          && chillicollectivoParseGoSendCommand(rawBeforeSendStrip));
      if (sendOnly) {
        this._currentInterimText = '';
        this._lastInterim = '';
        this._smoothedMicLevel = 0;
        this.fab?.resetLiveBroil?.();
        void this.handleTabEnterSend();
        return;
      }
      return;
    }

    if (isFinal || this._profileValueLock?.until > Date.now()) {
      if (typeof chillicollectivoSyncProfileDictationTarget === 'function') {
        chillicollectivoSyncProfileDictationTarget(this);
      }
    }

    if (isFinal && typeof chillicollectivoTryHandleProfileCommand === 'function'
      && chillicollectivoTryHandleProfileCommand(this, text)) {
      this._currentInterimText = '';
      this._lastInterim = '';
      this._smoothedMicLevel = 0;
      this.fab?.resetLiveBroil?.();
      if (this._hidePreviewTimer) {
        clearTimeout(this._hidePreviewTimer);
        this._hidePreviewTimer = null;
      }
      return;
    }

    if (isFinal && this._profileValueLock?.until > Date.now() && this._shouldPPDirectInsert()) {
      const resolvedField = this._resolveTextField();
      const fragment = (text || '').trim();
      if (fragment && !(typeof chillicollectivoLooksLikeProfileCommand === 'function' && chillicollectivoLooksLikeProfileCommand(fragment))) {
        const lockField = typeof chillicollectivoResolveProfileLockField === 'function'
          ? chillicollectivoResolveProfileLockField(this._profileValueLock)
          : this._profileValueLock.field;
        if (lockField && resolvedField === lockField && this.isTextField(lockField)) {
          this._profileValueLock.field = lockField;
          let cleanFragment = fragment;
          if (typeof chillicollectivoIsProfileTextarea === 'function' && chillicollectivoIsProfileTextarea(lockField)
            && typeof chillicollectivoStripEmojisFromProfileText === 'function') {
            cleanFragment = chillicollectivoStripEmojisFromProfileText(fragment);
          }
          if (cleanFragment) this._appendToField(lockField, cleanFragment);
          if (typeof chillicollectivoReturnToComposeAfterProfileValue === 'function') {
            chillicollectivoReturnToComposeAfterProfileValue(this);
          }
          this._currentInterimText = '';
          this._lastInterim = '';
          this.previewCommitted = '';
          this._smoothedMicLevel = 0;
          this.fab?.resetLiveBroil?.();
          this.fab?.hidePreview?.({ force: true });
          return;
        }
      }
    }

    if (!isFinal) {
      let previewText = String(text || '').trim();
      if (!previewText) return;
      let sendwordBadge = '';
      if (typeof chillicollectivoSplitPreviewSendword === 'function') {
        const split = chillicollectivoSplitPreviewSendword(previewText);
        previewText = String(split?.displayText ?? previewText).trim();
        sendwordBadge = String(split?.sendwordBadge || '').trim();
      }
      this._currentInterimText = previewText;
      this._lastInterim = previewText;
      if (this._hidePreviewTimer) {
        clearTimeout(this._hidePreviewTimer);
        this._hidePreviewTimer = null;
      }
      if (this._isPreviewUiEnabled()) {
        this._showDictationPreview(previewText, '', { sendwordBadge });
      }
      return;
    }

    const now = Date.now();
    if (!(text || '').trim()) return;

    if (this._isRecentlyFlushedPreviewText(text) || this._isDiscardedPreviewText(text)) {
      chillicollectivoVoiceDiag('info', 'voice.insert-flushed-skip', 'Final nach Flush verworfen (Duplikat-Schutz)', {
        textSnippet: String(text || '').slice(0, 80),
      }, this);
      return;
    }

    // Vorschau-Puffer (Tasten-/Klick-Modus): flüssig anhängen, nicht blockweise.
    if (this.browserListening && this._isLivePreviewBufferMode()) {
      this._simplePreviewAppendFinal(text);
      return;
    }

    chillicollectivoVoiceDiag('info', 'voice.insert-text-final', 'Voice-Final in insertText empfangen', {
      textSnippet: String(text || '').slice(0, 120),
      textLen: String(text || '').trim().length,
      enterToInsert: !!this._isPreviewEnterToInsertEnabled(),
      hasReplaceSpan: !!this._hasDictationReplaceSpan(),
    }, this);
    if (!this._lastDuplicateMemoryCheckAt || now - this._lastDuplicateMemoryCheckAt > 1500) {
      this._forgetStaleDuplicateMemory();
      this._lastDuplicateMemoryCheckAt = now;
    }

    if (isFinal) {
      if (this._isDirectFieldAppendMode()) {
        const prefixField = this._resolveTextField();
        if (prefixField) {
          this._restoreActiveFieldReplaceSpanIfNeeded(prefixField, 'insert-text-final-restore');
          if (!this._shouldUseFieldReplaceSpanForInsert(prefixField)) {
            this._clearDictationReplaceSpan('field', { clearSaved: true });
          }
        }
      }
    }
    const replaceField = this._resolveTextField();
    const pendingFieldReplace = isFinal && replaceField && this._shouldUseFieldReplaceSpanForInsert(replaceField);
    if (isFinal && !this._hasDictationReplaceSpan()) {
      const trimmed = String(text || '').replace(/\s+/g, ' ').trim();
      if (this._isPreviewEnterToInsertEnabled() || this._isAgentClickToInsertMode()) {
        const committed = String(this.previewCommitted || '').replace(/\s+/g, ' ').trim();
        if (committed) {
          const delta = this._stripPrefixOverlap(committed, trimmed);
          if (!delta) {
            const normCommitted = committed.replace(/\s+/g, ' ');
            const normTrimmed = trimmed.replace(/\s+/g, ' ');
            if (normTrimmed.startsWith(normCommitted) && normTrimmed.length > normCommitted.length) {
              text = normTrimmed.slice(normCommitted.length).trim();
            } else {
              chillicollectivoVoiceDiag('info', 'voice.insert-final-prefix-skip', 'Final ignoriert – vollständig in Vorschau-Committed', {
                trimmedSnippet: trimmed.slice(0, 120),
                committedSnippet: committed.slice(0, 120),
              }, this);
              return;
            }
          } else if (delta !== trimmed) {
            chillicollectivoVoiceDiag('info', 'voice.insert-final-prefix-stripped', 'Final-Prefix gegen Vorschau-Committed abgezogen', {
              trimmedSnippet: trimmed.slice(0, 120),
              deltaSnippet: delta.slice(0, 120),
              sharedPrefixWords: this._sharedPrefixWordCount(committed, trimmed),
            }, this);
            text = delta;
          }
        } else if (this._lastInsertedText) {
          const delta = this._stripPrefixOverlap(this._lastInsertedText, trimmed);
          if (!delta && this._hasSignificantPrefixOverlap(this._lastInsertedText, trimmed)) {
            chillicollectivoVoiceDiag('info', 'voice.insert-final-prefix-skip', 'Final ignoriert – Prefix bereits eingefügt (lastInserted)', {
              trimmedSnippet: trimmed.slice(0, 120),
              lastInsertedSnippet: String(this._lastInsertedText || '').slice(0, 120),
            }, this);
            return;
          }
          if (delta !== trimmed) {
            chillicollectivoVoiceDiag('info', 'voice.insert-final-prefix-stripped', 'Final-Prefix gegen lastInserted abgezogen', {
              trimmedSnippet: trimmed.slice(0, 120),
              deltaSnippet: delta.slice(0, 120),
              sharedPrefixWords: this._sharedPrefixWordCount(this._lastInsertedText, trimmed),
            }, this);
            text = delta;
          }
        }
      } else if (this._lastInsertedText && !this._isContinuingDictationSession()) {
        const delta = this._stripPrefixOverlap(this._lastInsertedText, trimmed);
        if (!delta && this._hasSignificantPrefixOverlap(this._lastInsertedText, trimmed)) return;
        if (delta !== trimmed) text = delta;
      }
    }

    if (text && this._lastInsertedText && this._isFuzzyDuplicateText(text, this._lastInsertedText) && !pendingFieldReplace) {
      if (this.browserListening) {
        if (String(text || '').trim() === String(this._lastInsertedText || '').trim()) return;
      } else if (isFinal && (this._isPreviewEnterToInsertEnabled() || this._isDirectAppendRecordingSession() || this._isAgentClickToInsertMode())) {
        chillicollectivoVoiceDiag('info', 'voice.duplicate-text-suppressed', 'Spätes Voice-Final nach Enter-Flush ignoriert', {
          text,
          textLen: String(text || '').length,
          lastInsertedText: this._lastInsertedText,
          lastInsertedAgeMs: this._lastInsertedTime ? Date.now() - this._lastInsertedTime : null,
          route: this._currentConversationKey(),
        }, this);
        return;
      }
      console.warn('[CHILLICOLLECTIVO] KRITISCH: Duplicate-Text erkannt – Insert verworfen:', text);
      const bodyNorm = this._normalizeSubmitBody(text);
      chillicollectivoToolLog('error', 'voice.duplicate-text', 'Duplicate-Text erkannt, Insert verworfen', {
        text,
        bodyNorm,
        lastInsertedText: this._lastInsertedText,
        route: this._currentConversationKey(),
      });
      this._voiceCommandSuppressUntil = Math.max(this._voiceCommandSuppressUntil || 0, Date.now() + CHILLICOLLECTIVO_POST_SEND_COOLDOWN_MS);
      this._currentInterimText = '';
      this._stopFabMotion();
      this.fab?.showPreview('Duplikat', 'Schon eingepflegt');
      setTimeout(() => this.fab?.hidePreview?.({ force: true }), 2400);
      return;
    }
    const route = this._currentConversationKey();
    const routeLastInsert = this._getLastInsertedForRoute(route);
    if (text && routeLastInsert && this._isFuzzyDuplicateText(text, routeLastInsert.text) && !pendingFieldReplace) {
      if (this.browserListening) {
        if (String(text || '').trim() === String(routeLastInsert.text || '').trim()) return;
      } else if (isFinal && (this._isPreviewEnterToInsertEnabled() || this._isDirectAppendRecordingSession() || this._isAgentClickToInsertMode())) {
        chillicollectivoToolLog('info', 'voice.duplicate-text-route-suppressed', 'Spätes Voice-Final im gleichen Chat nach Enter-Flush ignoriert', {
          text,
          lastInsertedText: routeLastInsert.text,
          route,
        });
        return;
      }
      console.warn('[CHILLICOLLECTIVO] KRITISCH: Gleicher Text in diesem Chat – Insert verworfen:', text);
      chillicollectivoToolLog('error', 'voice.duplicate-text-route', 'Gleicher Text in diesem Chat erneut erkannt', {
        text,
        route,
        lastInsertedText: routeLastInsert.text,
      });
      this._currentInterimText = '';
      this._stopFabMotion();
      this.fab?.showPreview('Duplikat', 'Schon eingepflegt');
      setTimeout(() => this.fab?.hidePreview?.({ force: true }), 2400);
      return;
    }
    this._sessionDictationChars = (this._sessionDictationChars || 0) + (text?.length || 0);

    if (isFinal && (this._isPreviewEnterToInsertEnabled() || this._isAgentClickToInsertMode()) && (text || '').trim()) {
      if (this._isDiscardedPreviewText(text)) {
        this._currentInterimText = '';
        this._lastInterim = '';
        this._stopFabMotion();
        chillicollectivoToolLog('info', 'voice.discarded-preview-final-blocked', 'Verworfener Voice-Final wird ignoriert', {
          text,
          route: this._currentConversationKey(),
        });
        return;
      }
      this._currentInterimText = '';
      this._lastInterim = '';
      this._stopFabMotion();
      if (this._browserSilenceTimer) {
        clearTimeout(this._browserSilenceTimer);
        this._browserSilenceTimer = null;
      }
      if (this._hidePreviewTimer) {
        clearTimeout(this._hidePreviewTimer);
        this._hidePreviewTimer = null;
      }
      this._bufferFinalToPreviewOnly(text);
      chillicollectivoVoiceDiag('info', 'voice.insert-final-to-preview', 'Voice-Final nur in Vorschau gepuffert (Enter-/Klick-Modus)', {
        textSnippet: String(text || '').slice(0, 120),
        textLen: String(text || '').trim().length,
        previewCommittedLen: String(this.previewCommitted || '').length,
      }, this);
      return;
    }

    this._currentInterimText = '';
    this._lastInterim = '';
    this.previewCommitted = '';
    this._stopFabMotion();
    if (this._browserSilenceTimer) {
      clearTimeout(this._browserSilenceTimer);
      this._browserSilenceTimer = null;
    }
    if (this._hidePreviewTimer) {
      clearTimeout(this._hidePreviewTimer);
      this._hidePreviewTimer = null;
    }
    if (this._isDirectFieldAppendMode() && this._isCaptureActive()) {
      this._resetDictationLivePreview();
    } else if (!this._isPreviewEnterToInsertEnabled() && !this._outPreviewWaitTimer && !this._sentPreviewCrumbleTimer) {
      this.fab?.hidePreview({ force: true });
    }

    const field = this._resolveTextField();
    if (!field || !this.isTextField(field)) {
      this._queueInsertRetry(text, isFinal);
      return;
    }

    chillicollectivoLogVoiceDictation('eingefuegt', text, {
      route: this._currentConversationKey(),
    });
    this._logVoiceInsertFlow('info', 'voice.insert-text-field', 'Voice-Final direkt ins Chatfeld (ohne Vorschau-Puffer)', {
      textSnippet: String(text || '').slice(0, 120),
      textLen: String(text || '').trim().length,
      isFinal,
    }, field);
    this._commitFinalInsert(field, text);
    this._lastInsertedText = text;
    this._lastInsertedTime = now;
    if (this._isCaptureActive() && this._isDirectFieldAppendMode()) {
      this._resumeDictationAfterPreviewFlush('direct-insert');
    }
  }

  cleanup() {
    this.fab?.btn?.remove();
    this.fab?.hidePreview({ force: true });
  }
}

function chillicollectivoInitVoiceFieldManager() {
  chillicollectivoEnsurePreviewFontsInjected();
  fieldManager = new CHILLICOLLECTIVOVoiceFieldManager();
  window.__chillicollectivoFieldManager = fieldManager;
  chillicollectivoInstallTorchwoodManualSendOutListener();
}

let fieldManager;
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', chillicollectivoInitVoiceFieldManager);
} else {
  chillicollectivoInitVoiceFieldManager();
}

window.addEventListener('unload', () => { fieldManager?.cleanup(); });

