Scowl like Thucydides

Finds Ancient Greek text, converts it to archaic majuscule with updated OU/EI logic, and applies a custom font.

// ==UserScript==
// @name         Scowl like Thucydides
// @namespace    http://tathamei.com/
// @version      3.1
// @description  Finds Ancient Greek text, converts it to archaic majuscule with updated OU/EI logic, and applies a custom font.
// @author       Tathamei
// @match        *://*/*
// @exclude      https://tathamei.com/*
// @exclude      https://docs.google.com/*
// @exclude      https://suno.com/*
// @exclude      https://www.google.com/search*
// @exclude      https://google.com/search*
// @license      Proprietary
// @grant        GM_addStyle
// @run-at       document-idle
// ==/UserScript==

(function() {
  'use strict';

  // ======================= 1) FONT & CLASS =======================
  GM_addStyle(`
    @font-face {
      font-family: 'ArchaicGreekFont';
      /* Replace with your actual base64 font data */
      src: url(data:font/woff2;base64,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) format('woff2');
      font-weight: normal;
      font-style: normal;
    }
  .archaic-greek-text {
    font-family: 'ArchaicGreekFont', sans-serif !important;
    font-size: calc(1em + 2px) !important;
  }
`);

  // Editable guard (don’t touch inputs/editors)
  const EDITABLE_SELECTOR =
    'input, textarea, select, option, [contenteditable]:not([contenteditable="false"]), [role="textbox"], .ProseMirror, .ql-editor, .public-DraftEditor-content';
  function isEditableRoot(el) {
    if (!el) return false;
    if (el.isContentEditable) return true;
    return !!el.closest(EDITABLE_SELECTOR);
  }

  // ======================= 2) MAPS (Majuscule + Macrons) =======================
  const archaicMajusculeMap = {
    // --- VOWELS ---
    // Alpha
    'α':'Α','ἀ':'Α','ά':'Α','ὰ':'Α','ἄ':'Α','ἂ':'Α','ᾶ':'Α','ἆ':'Α',
    'Ἀ':'Α','Ἄ':'Α','Ἂ':'Α','Ἆ':'Α',
    'ἁ':'ͰΑ','ᾁ':'ͰΑΙ','ἇ':'ͰΑ','ἅ':'ͰΑ','ἃ':'ͰΑ',
    'ᾷ':'ΑΙ','ᾴ':'ΑΙ','ᾳ':'ΑΙ','ᾀ':'ΑΙ',
    'Ἁ':'ͰA','Ἅ':'ͰA',

    // Epsilon
    'ε':'Ε','ἐ':'Ε','έ':'Ε','ὲ':'Ε','ἔ':'Ε','ἒ':'Ε',
    'Ἐ':'Ε','Ἔ':'Ε','Ἒ':'Ε',
    'ἑ':'ͰΕ','ἓ':'ͰΕ','ἕ':'ͰΕ',
    'Ἑ':'ͰΕ','Ἕ':'ͰΕ',

    // Eta → Epsilon (archaic)
    'η':'Ε','ἠ':'Ε','ή':'Ε','ὴ':'Ε','ῆ':'Ε','ἤ':'Ε','ἢ':'Ε','ἦ':'Ε',
    'Η':'Ε','Ἠ':'Ε','Ἤ':'Ε','Ἢ':'Ε','Ἦ':'Ε',
    'ἡ':'ͰΕ','ᾗ':'ͰΕΙ','ἧ':'ͰΕ','ἥ':'ͰΕ','ἣ':'ͰΕ','ᾑ':'ͰΕΙ',
    'ῄ':'ΕΙ','ᾖ':'ΕΙ','ῇ':'ΕΙ','ῃ':'ΕΙ','ᾐ':'ΕΙ',
    'Ἡ':'ͰΕ',

    // Iota
    'ι':'Ι','ἰ':'Ι','ί':'Ι','ὶ':'Ι','ῖ':'Ι','ἴ':'Ι','ἶ':'Ι','ϊ':'Ι','ΐ':'Ι',
    'Ἰ':'Ι','Ἴ':'Ι','Ἲ':'Ι','Ἶ':'Ι',
    'ἳ':'ͰΙ','ἱ':'ͰΙ','ἵ':'ͰΙ','ἷ':'ͰΙ',
    'Ἱ':'ͰΙ','Ἵ':'ͰΙ',

    // Omicron
    'ο':'Ο','ὀ':'Ο','ό':'Ο','ὸ':'Ο','ὄ':'Ο','ὂ':'Ο',
    'Ὀ':'Ο','Ὄ':'Ο','Ὂ':'Ο',
    'ὁ':'ͰΟ','ὅ':'ͰΟ','ὃ':'ͰΟ',
    'Ὁ':'ͰΟ','Ὅ':'ͰΟ',

    // Omega → Omicron
    'ω':'Ο','ὠ':'Ο','ώ':'Ο','ὼ':'Ο','ῶ':'Ο','ὦ':'Ο','ὢ':'Ο',
    'Ω':'Ο','Ὠ':'Ο','Ὤ':'Ο','Ὢ':'Ο','Ὦ':'Ο',
    'ὡ':'ͰΟ','ὧ':'ͰΟ','ὥ':'ͰΟ',
    'ᾧ':'ͰΟΙ','ῳ':'ΟΙ','ῷ':'ΟΙ','ᾠ':'ΟΙ','ᾤ':'ΟΙ','ῴ':'ΟΙ',
    'Ὡ':'ͰΟ',

    // Upsilon
    'υ':'Υ','ὐ':'Υ','ύ':'Υ','ὺ':'Υ','ὔ':'Υ','ὒ':'Υ','ῦ':'Υ','ὖ':'Υ',
    'ὕ':'ͰΥ','ὑ':'ͰΥ','ὗ':'ͰΥ',
    'Ὑ':'ͰΥ','Ὓ':'ͰΥ','Ὗ':'ͰΥ','Ὕ':'ͰΥ',

    // Consonants
    'β':'Β','γ':'Γ','δ':'Δ','ζ':'Ζ','θ':'Θ','κ':'Κ','λ':'Λ','μ':'Μ','ν':'Ν','π':'Π','ρ':'Ρ','ς':'Σ','σ':'Σ','τ':'Τ','φ':'Φ','χ':'Χ','ͱ':'Ͱ',
    // Rho with rough
    'ῥ':'ͰΡ',

    // Compounds
    'ξ':'ΧΣ','Ξ':'ΧΣ','ψ':'ΦΣ','Ψ':'ΦΣ'
  };

  const archaicMap_MacronsOn = {
    // Alpha (macron)
    'ᾶ':'Ā','ἆ':'Ā','Ἆ':'Ā','ἇ':'ͰĀ','ᾷ':'ĀΙ',
    // Eta → Ē
    'η':'Ē','ἠ':'Ē','ή':'Ē','ὴ':'Ē','ῆ':'Ē','ἤ':'Ē','ἢ':'Ē','ἦ':'Ē',
    'Η':'Ē','Ἠ':'Ē','Ἤ':'Ē','Ἢ':'Ē','Ἦ':'Ē',
    'ἡ':'ͰĒ','ᾗ':'ͰĒΙ','ἧ':'ͰĒ','ἥ':'ͰĒ','ἣ':'ͰĒ','ᾑ':'ͰĒΙ',
    'ῄ':'ĒΙ','ᾖ':'ĒΙ','ῇ':'ĒΙ','ῃ':'ĒΙ','ᾐ':'ĒΙ',
    'Ἡ':'ͰĒ',
    // Iota
    'ῖ':'Ī','ἶ':'Ī','Ἶ':'Ī','ἷ':'ͰĪ',
    // Omega → Ō
    'ω':'Ō','ὠ':'Ō','ώ':'Ō','ὼ':'Ō','ῶ':'Ō','ὦ':'Ō','ὢ':'Ō',
    'Ω':'Ō','Ὠ':'Ō','Ὤ':'Ō','Ὢ':'Ō','Ὦ':'Ō',
    'ὡ':'ͰŌ','ὧ':'ͰŌ','ὥ':'ͰŌ',
    'ᾧ':'ͰŌΙ','ῳ':'ŌΙ','ῷ':'ŌΙ','ᾠ':'ŌΙ','ᾤ':'ŌΙ','ῴ':'ŌΙ',
    'Ὡ':'ͰŌ',
    // Upsilon
    'ῦ':'Ȳ','ὖ':'Ȳ','ὗ':'ͰȲ','Ὗ':'ͰȲ'
  };

  let MACRONS_ON = false;
  function mapChar(ch) {
    if (MACRONS_ON && archaicMap_MacronsOn[ch] != null) return archaicMap_MacronsOn[ch];
    return (archaicMajusculeMap[ch] != null) ? archaicMajusculeMap[ch] : ch;
  }

  // ======================= 3) OU & EI LOGIC (updated) =======================
  // Charsets for ο and υ variants (explicit)
  const OMICRONS = "οὀὈὄὌὂὊόὸὁὉὅὍὃ";
  const UPSILONS = "υὐὔὒὖῦὕὗὑὙὛὝὟύὺ";
  const OU_ANY_RE     = new RegExp("[" + OMICRONS + "][" + UPSILONS + "]", "g");
  const OU_ANY_RE_ONE = new RegExp("[" + OMICRONS + "][" + UPSILONS + "]");

  // For EI pairs (ε + ι variants)
  const EPSILONS = "εἐἑέὲἔἒΕἘἙἜἝἚ";
  const IOTAS    = "ιἰἱίὶῖἴἳἶἷϊΐΙἸἹἼἺἾἵἽ";
  const EI_ANY_RE     = new RegExp("[" + EPSILONS + "][" + IOTAS + "]", "g");
  const EI_ANY_RE_ONE = new RegExp("[" + EPSILONS + "][" + IOTAS + "]");

  // Rough-breathing upsilons (lower + upper)
  const ROUGH_UPSILON = "ὑὕὓὗὙὝὛὟ";

  // Normalize (strip diacritics, keep Greek letters), lowercase
  function normalizeForLookup(word) {
    return word.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
  }

  // ---- OU “keep” & “collapse” sets (updated) ----
  // Keep whole-word OU (true diphthongs / particles)
  const KEEP_TRUE_OU_WORDS = new Set([
    'ουν','ου','ους','βους','τουτο','που',
    'ουκ','ουχ','ουχι','ουδε' // negative particle and friends
  ]);
  // Collapse ALL OU in entire lexeme (e.g., βουλή*, δοῦναι)
  const COLLAPSE_WHOLE_WORDS = new Set(['βουλη','βουλης','βουλην','δουναι']);

  // OU endings (updated: includes -ουντι)
  const ENDINGS = [
    { re: /ουσιν$/,  ouLen: 5 },
    { re: /ουσι$/,   ouLen: 4 },
    { re: /ουμεν$/,  ouLen: 5 },
    { re: /ουσα$/,   ouLen: 4 },
    { re: /ουντος$/, ouLen: 6 },
    { re: /ουντες$/, ouLen: 6 },
    { re: /ουντα$/,  ouLen: 5 },
    { re: /ουν$/,    ouLen: 3 }, // guarded by KEEP_TRUE_OU_WORDS (οὖν)
    { re: /ους$/,    ouLen: 3 },
    { re: /ου$/,     ouLen: 2 },
    { re: /ουμαι$/,  ouLen: 5 },
    { re: /ουται$/,  ouLen: 5 },
    { re: /ουνται$/, ouLen: 6 },
    { re: /ουμεθα$/, ouLen: 7 },
    { re: /ουσθε$/,  ouLen: 6 },
    { re: /ουσθαι$/, ouLen: 6 },
    { re: /ουμην$/,  ouLen: 5 },
    { re: /ουτο$/,   ouLen: 4 }, // guarded for demonstrative τοῦτο
    { re: /ουντο$/,  ouLen: 5 },
    { re: /ουντι$/,  ouLen: 5 }  // NEW
  ];

  // Reposition rough breathing for OU diphthongs:
  // α/ε/ο + (rough upsilon) → ͱ + (lowercase base) + υ
  function repositionRoughOU(s) {
    const chars = Array.from(s);
    const out = [];
    for (let i = 0; i < chars.length; i++) {
      const c = chars[i];
      const nextExists = i + 1 < chars.length;
      const n = nextExists ? chars[i + 1] : '';
      const isBase = (c === 'α' || c === 'Α' || c === 'ε' || c === 'Ε' || c === 'ο' || c === 'Ο');
      const isRoughU = nextExists && ROUGH_UPSILON.includes(n);
      if (isBase && isRoughU) {
        out.push('ͱ');      // small heta placeholder (later unified to Ͱ)
        out.push(c.toLowerCase());
        out.push('υ');
        i++;                // consume the rough‑upsilon char
        continue;
      }
      out.push(c);
    }
    return out.join('');
  }

  // NEW: collapse INTERNAL spurious OU from contractions (ε+ο, ο+ε, ο+ο)
  function collapseInternalOU(coreWord, showMacrons) {
    if (!OU_ANY_RE_ONE.test(coreWord)) return null;

    const norm = normalizeForLookup(coreWord);
    if (KEEP_TRUE_OU_WORDS.has(norm)) return null;

    const replacement = showMacrons ? 'Ō' : 'Ο';

    // participle stem: -οῦντ-
    const idxNT = norm.indexOf('ουντ');
    if (idxNT !== -1) {
      return coreWord.slice(0, idxNT) + replacement + coreWord.slice(idxNT + 2);
    }

    // middle/passive participle & related: -ούμεν-
    const idxME = norm.indexOf('ουμε');
    if (idxME !== -1) {
      return coreWord.slice(0, idxME) + replacement + coreWord.slice(idxME + 2);
    }

    // contracted οῦ anywhere inside
    const rawCirc = coreWord.indexOf('οῦ');
    if (rawCirc !== -1) {
      return coreWord.slice(0, rawCirc) + replacement + coreWord.slice(rawCirc + 2);
    }

    return null;
  }

  // Collapse the LAST ending-OU ONLY (index via normalized string)
  function collapseEndingOU(coreWord, showMacrons) {
    const norm = normalizeForLookup(coreWord);
    if (KEEP_TRUE_OU_WORDS.has(norm)) return null; // keep οὖν/οὐ/οὖς/βοῦς/τοῦτο/...

    for (const { re, ouLen } of ENDINGS) {
      const m = norm.match(re);
      if (!m) continue;
      if (norm === 'τουτο') continue; // guard demonstrative

      const start = norm.length - ouLen;      // position of ending's 'ο'
      const replacement = showMacrons ? 'Ō' : 'Ο';
      return coreWord.slice(0, start) + replacement + coreWord.slice(start + 2);
    }
    return null;
  }

  // ---- ἐν assimilation before β / π (no punctuation crossing) ----
  function applyEnAssimilation(tokens) {
    const isWhitespace  = s => /^[ \n\r\t]+$/.test(s);
    const isPunct       = s => /^[.,?!;:᾽'"]+$/.test(s);
    const startsWithBetaOrPi = s => {
      if (!s) return false;
      const look = s.replace(/^[\(\[\{«"“]+/, '');
      const first = look[0];
      return first === 'β' || first === 'Β' || first === 'π' || first === 'Π';
    };

    const out = tokens.slice();
    for (let i = 0; i < out.length; i++) {
      if (out[i] === 'ἐν') {
        let j = i + 1;
        while (j < out.length && isWhitespace(out[j])) j++;
        if (j < out.length && !isPunct(out[j]) && startsWithBetaOrPi(out[j])) {
          out[i] = 'ἐμ';
        }
      }
    }
    return out;
  }

  // ---- EI (spurious) helpers & rules (ported) ----
  // “keep as true ει” — exact forms
  const KEEP_TRUE_EI_WORDS = new Set([
    'ει',      // εἰ “if”
    'εις',     // prep. εἰς
    'ημεις',   // ἡμεῖς
    'υμεις',   // ὑμεῖς
    'ταθαμει', // project-specific keep from converter
    'δειν'     // δειν
  ]);
  // keep by stem prefix
  const KEEP_TRUE_EI_PREFIXES = ['πειρ']; // (σπειρ removed so verbs can collapse)

  // forced collapse (exact)
  const FORCE_EI_COLLAPSE_EXACT = new Set(['ειμι','ειναι']); // εἰμί, εἶναι

  // forced collapse (prefix stems)
  const FORCE_EI_COLLAPSE_PREFIXES = [
    'ξειν','στειν','κτειν','ιμειρ','μειζ','φθειρ',
    'τειν', // τείνω family
    'νειμ'  // νεῖμαι family
  ];

  // forced collapse (regex families) + guard
  const FORCE_EI_COLLAPSE_REGEXPS = [
    { re: /^χειρ(ων|ον|ονος|ονι|ονα|ιστ)/, guard: (original) => !/ῶ/.test(original) },
    { re: /^σπειρ(ω|εις|ει|ομεν|ετε|ουσιν?|ειν)$/, guard: null } // verbs only (not σπεῖρα)
  ];

  // LN aorists suffix families (augment-gated)
  const LN_ACTIVE_SUFFIXES = [
    // -ειλα
    'ειλα','ειλας','ειλε','ειλεν','ειλαμεν','ειλατε','ειλαν',
    // -εινα
    'εινα','εινας','εινε','εινεν','ειναμεν','εινατε','ειναν',
    // -ειρα
    'ειρα','ειρας','ειρε','ειρεν','ειραμεν','ειρατε','ειραν',
    // -ειμα (e.g., ἔνειμα)
    'ειμα','ειμας','ειμε','ειμεν','ειμαμεν','ειματε','ειμαν'
  ];
  const PREVERB_PREFIXES = [
    'απ','κατ','μετ','παρ','προσ','συν','εκ','εξ','εν','αν',
    'υπ','υπερ','προ','περι','παρα','εισ','αμφ','ανα','καθ','δια','δι'
  ];
  function hasAugmentLike(norm) {
    if (!norm) return false;
    if (norm.startsWith('ε') || norm.startsWith('η')) return true;
    for (const pv of PREVERB_PREFIXES) {
      if (norm.startsWith(pv + 'ε') || norm.startsWith(pv + 'η')) return true;
    }
    return false;
  }

  function isNumeralEis(word) {
    return word === 'εἷς' || word === 'Εἷς' || word === 'εἷΣ' || word === 'ΕἷΣ';
  }
  function isNonSpuriousEimi(original) {
    return original === 'εἶμι' || original === 'Εἶμι';
  }

  function collapseLiquidNasalAoristEI(coreWord, replacer) {
    const norm = normalizeForLookup(coreWord);
    if (!hasAugmentLike(norm)) return null;
    for (const suf of LN_ACTIVE_SUFFIXES) {
      if (norm.endsWith(suf)) {
        const start = norm.length - suf.length; // index of suffix's 'ε'
        return coreWord.slice(0, start) + replacer + coreWord.slice(start + 2);
      }
    }
    return null;
  }

  function collapseForcedLexemeEI(coreWord, replacer) {
    if (isNonSpuriousEimi(coreWord)) return null; // DO NOT collapse εἶμι
    const norm = normalizeForLookup(coreWord);

    // exact forms
    if (FORCE_EI_COLLAPSE_EXACT.has(norm)) {
      return coreWord.replace(EI_ANY_RE, replacer);
    }
    // patterned families (e.g., χείρων/χείριστος), with guard (avoid χερῶν)
    for (const { re, guard } of FORCE_EI_COLLAPSE_REGEXPS) {
      if (re.test(norm) && (!guard || guard(coreWord))) {
        return coreWord.replace(EI_ANY_RE, replacer);
      }
    }
    // stem prefixes (μείζ‑, φθείρ‑, τείν‑, νείμ‑, …)
    for (const p of FORCE_EI_COLLAPSE_PREFIXES) {
      if (norm.startsWith(p)) {
        return coreWord.replace(EI_ANY_RE, replacer);
      }
    }
    return null;
  }

  function collapseSpuriousEI(coreWord, replacer) {
    if (!EI_ANY_RE_ONE.test(coreWord)) return null;

    // εἷς → Ͱ + replacer + Σ (majuscule stream wants heta)
    if (isNumeralEis(coreWord)) return 'Ͱ' + replacer + 'Σ';

    // LN aorists (augment-gated)
    const ln = collapseLiquidNasalAoristEI(coreWord, replacer);
    if (ln !== null) return ln;

    // keep true diphthongs
    const norm = normalizeForLookup(coreWord);
    if (KEEP_TRUE_EI_WORDS.has(norm)) return null;
    for (const keepPref of KEEP_TRUE_EI_PREFIXES) {
      if (norm.startsWith(keepPref)) return null;
    }

    // forced lexemes/stems/patterns
    const forced = collapseForcedLexemeEI(coreWord, replacer);
    if (forced !== null) return forced;

    // generic spurious endings (collapse only the ending’s ει)
    const EI_ENDINGS = [
      { re: /ειν$/, eiLen: 3 },
      { re: /εις$/, eiLen: 3 },
      { re: /ει$/,  eiLen: 2 },
      { re: /ειται$/,  eiLen: 5 }, { re: /εισθε$/,  eiLen: 6 }, { re: /εισθαι$/, eiLen: 6 },
      { re: /ειτε$/,   eiLen: 4 }, { re: /ειτο$/,   eiLen: 4 }, { re: /ειμην$/,  eiLen: 5 },
      { re: /εισο$/,   eiLen: 4 }, { re: /ειμεν$/,  eiLen: 5 }, { re: /εισαν$/,  eiLen: 5 },
      { re: /εια$/,    eiLen: 3 },
      { re: /εις$/,    eiLen: 3 } // βασιλεῖς/πρέσβεις
    ];

    for (const { re, eiLen } of EI_ENDINGS) {
      const m = norm.match(re);
      if (!m) continue;
      const start = norm.length - eiLen; // index of ending's 'ε'
      return coreWord.slice(0, start) + replacer + coreWord.slice(start + 2);
    }
    return null;
  }

  // Per‑word OU handling with lexeme/ending/internal rules
  function processWordForArchaicOU(word, showMacrons) {
    const punctuationRegex = /[.,?!;:᾽'"]+$/;
    const trailing = (word.match(punctuationRegex) || [''])[0];
    let core = word.replace(punctuationRegex, '');
    if (!core) return word;

    // 0) Restore rough‑OU placement
    core = repositionRoughOU(core);

    // 1) Try INTERNAL contracted OU first
    const internal = collapseInternalOU(core, showMacrons);
    if (internal !== null) {
      core = internal;
    }

    // 2) If no OU now, skip
    if (!OU_ANY_RE_ONE.test(core)) {
      return core + trailing;
    }

    const norm = normalizeForLookup(core);

    // 3) Whole‑word keep (οὐκ, οὐ, οὖν, βοῦς, τοῦτο, που, …)
    if (KEEP_TRUE_OU_WORDS.has(norm)) {
      return core + trailing;
    }

    // 4) Whole‑lexeme force collapse (βουλή*, δοῦναι)
    if (COLLAPSE_WHOLE_WORDS.has(norm)) {
      const replacement = showMacrons ? 'Ō' : 'Ο';
      const replaced = core.replace(OU_ANY_RE, replacement);
      return replaced + trailing;
    }

    // 5) Ending‑only collapse
    const collapsed = collapseEndingOU(core, showMacrons);
    if (collapsed !== null) return collapsed + trailing;

    // 6) Default: leave true OU; mapping will make ΟΥ
    return core + trailing;
  }

  // Per‑word EI handling (collapse to Ε or Ē before mapping)
  function processWordForArchaicEI(word, showMacrons) {
    const punctuationRegex = /[.,?!;:᾽'"]+$/;
    const trailing = (word.match(punctuationRegex) || [''])[0];
    let core = word.replace(punctuationRegex, '');
    if (!core) return word;

    const replacer = showMacrons ? 'Ē' : 'Ε';
    const collapsed = collapseSpuriousEI(core, replacer);
    if (collapsed !== null) return collapsed + trailing;

    return core + trailing;
  }

  // ======================= 4) CORE CONVERTER =======================
  const diphthongVowelBases = new Set(['α','ε','ο','η','ω','Α','Ε','Ο','Η','Ω']);
  const roughBreathingSecond = new Set([
    'ἱ','ἵ','ἳ','ἷ','Ἱ','Ἵ','Ἳ','Ἷ',  // iota rough
    'ὑ','ὕ','ὓ','ὗ','Ὑ','Ὕ','Ὓ','Ὗ'   // upsilon rough
  ]);

  // Convert a *single Greek word* to archaic majuscule with current macron mode
  function convertToArchaic(word) {
    const normalizedInput = word.normalize('NFC');

    // OU + EI morphology (internal/ending/lexeme rules; augment‑gated LN aorists; keeps)
    const afterOU = processWordForArchaicOU(normalizedInput, MACRONS_ON);
    const prepped  = processWordForArchaicEI(afterOU, MACRONS_ON);

    // Map char‑by‑char; handle rough second‑vowel diphthongs for big H (Ͱ) placement
    let out = '';
    for (let i = 0; i < prepped.length; i++) {
      const ch = prepped[i];
      const prev = i > 0 ? prepped[i - 1] : null;

      if (prev && diphthongVowelBases.has(prev) && roughBreathingSecond.has(ch)) {
        const prevOut = mapChar(prev);
        // Remove previously appended prevOut (we're going to prepend Ͱ instead)
        out = out.slice(0, -prevOut.length);
        const currNoBreath = mapChar(ch).replace(/[ͱͰ]/g, '');
        out += 'Ͱ' + prevOut + currNoBreath;
        continue;
      }

      out += mapChar(ch);
    }

    // Unify small heta → big; force Ω/ω → Ο; κφ → ΧΦ after uppercasing
    out = out.replace(/ͱ/g, 'Ͱ').replace(/[Ωω]/g, 'Ο');
    out = out.replace(/ΚΦ/g, 'ΧΦ');

    return out;
  }

  // ======================= 5) NODE PROCESSING =======================
  // Detect nodes to process (Greek blocks or Latin macron letters)
//  const ALL_GREEK_CHARS_REGEX = /([\u0100\u0112\u012a\u014c\u016a\u0232\u0370-\u03FF\u1F00-\u1FFF]+)/gu;
const ALL_GREEK_CHARS_REGEX = /([\u0100\u0101\u0112\u0113\u012A\u012B\u014C\u014D\u016A\u016B\u0232\u0233\u0370\u0371\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039A\u039B\u039C\u039D\u039F\u03A0\u03A1\u03A3\u03A4\u03A5\u03A6\u03A7\u03A9\u03AC\u03AD\u03AE\u03AF\u03B1\u03B2\u03B3\u03B4\u03B5\u03B6\u03B7\u03B8\u03B9\u03BA\u03BB\u03BC\u03BD\u03BE\u03BF\u03C0\u03C1\u03C2\u03C3\u03C4\u03C5\u03C6\u03C7\u03C8\u03c9\u03CA\u03CC\u03CE\u03CD\u1F00\u1F02\u1F04\u1F06\u1F08\u1F0A\u1F0C\u1F0E\u1F10\u1F12\u1F14\u1F18\u1F1A\u1F1C\u1F20\u1F21\u1F22\u1F24\u1F26\u1F28\u1F2A\u1F2C\u1F2E\u1F30\u1F34\u1F36\u1F38\u1F3A\u1F3C\u1F3E\u1F40\u1F42\u1F44\u1F48\u1F4A\u1F4C\u1F50\u1F52\u1F54\u1F56\u1F5D\u1F60\u1F62\u1F66\u1F68\u1F6A\u1F6C\u1F6E\u1F70\u1F72\u1F74\u1F76\u1F78\u1F7A\u1F7C\u1F80\u1FB6\u1FC6\u1FD6\u1FE6\u1FF6\u{10144}\u{10145}\u002c\u002e\u0022\u003a\u0027\u003b])/gu;

  function processAndWrap(rootNode) {
    if (![Node.ELEMENT_NODE, Node.DOCUMENT_NODE, Node.DOCUMENT_FRAGMENT_NODE].includes(rootNode.nodeType)) {
      return;
    }

    const walker = document.createTreeWalker(
      rootNode,
      NodeFilter.SHOW_TEXT,
      {
        acceptNode(node) {
          const p = node.parentElement;
          if (!p) return NodeFilter.FILTER_REJECT;
          if (isEditableRoot(p)) return NodeFilter.FILTER_REJECT;

          const tag = p.tagName ? p.tagName.toUpperCase() : '';
          if (tag === 'SCRIPT' || tag === 'STYLE' || tag === 'TEXTAREA') return NodeFilter.FILTER_REJECT;
          if (p.closest('.archaic-greek-text')) return NodeFilter.FILTER_REJECT;

          if (ALL_GREEK_CHARS_REGEX.test(node.nodeValue)) {
            ALL_GREEK_CHARS_REGEX.lastIndex = 0;
            return NodeFilter.FILTER_ACCEPT;
          }
          return NodeFilter.FILTER_SKIP;
        }
      }
    );

    const nodes = [];
    let n;
    while ((n = walker.nextNode())) nodes.push(n);

    const TOKEN_SPLIT = /([ \n\r\t.,?!;:᾽'"]+)/; // spaces & punctuation as standalone tokens
    const GREEK_RE = /[\u0370-\u03FF\u1F00-\u1FFF]/; // does token contain Greek?

    for (const node of nodes) {
      const text = node.nodeValue;
      const parts = text.split(TOKEN_SPLIT);

      // ἐν → ἐμ before β/π across whitespace (no punctuation)
      const tokens = applyEnAssimilation(parts);

      const frag = document.createDocumentFragment();
      for (const tok of tokens) {
        if (!tok) continue;

        // whitespace or punctuation → keep as text
        if (/^[ \n\r\t]+$/.test(tok) || /^[.,?!;:᾽'"]+$/.test(tok)) {
          frag.appendChild(document.createTextNode(tok));
          continue;
        }

        // If token contains Greek, convert & wrap; else keep as text
        if (GREEK_RE.test(tok)) {
          const span = document.createElement('span');
          span.className = 'archaic-greek-text';
          span.setAttribute('data-original', tok);
          span.textContent = convertToArchaic(tok);
          frag.appendChild(span);
        } else {
          frag.appendChild(document.createTextNode(tok));
        }
      }

      if (node.parentNode) node.parentNode.replaceChild(frag, node);
    }
  }

  function reconvertAll(root = document) {
    root.querySelectorAll('.archaic-greek-text').forEach(span => {
      const original = span.getAttribute('data-original');
      if (original != null) {
        span.textContent = convertToArchaic(original);
      }
    });
  }

  // ======================= 6) DYNAMIC CONTENT HANDLING =======================
  // Initial pass
  processAndWrap(document.body);

  // Queue + throttle for observer flush
  const pending = new Set();
  let flushTimer = null;
  const FLUSH_DELAY = 50;
  const BATCH_SIZE = 20;

  function enqueue(node) {
    let el = null;
    if (node.nodeType === Node.ELEMENT_NODE) el = node;
    else if (node.nodeType === Node.TEXT_NODE) el = node.parentElement;
    if (!el) return;
    if (!document.body.contains(el)) return;
    if (isEditableRoot(el)) return;
    pending.add(el);
  }

  function minimizeRoots(nodes) {
    const set = new Set(nodes);
    return nodes.filter(n => {
      let p = n.parentElement;
      while (p) {
        if (set.has(p)) return false;
        p = p.parentElement;
      }
      return true;
    });
  }

  function scheduleFlush() {
    if (flushTimer) return;
    flushTimer = setTimeout(flush, FLUSH_DELAY);
  }

  function flush() {
    flushTimer = null;
    const unique = Array.from(pending);
    pending.clear();

    const roots = minimizeRoots(unique);
    let index = 0;

    function step() {
      const end = Math.min(index + BATCH_SIZE, roots.length);
      for (; index < end; index++) {
        processAndWrap(roots[index]);
      }
      if (index < roots.length) {
        setTimeout(step, 16);
      }
    }
    step();
  }

  // ======================= 7) MACRON TOGGLE UI =======================
  (function injectMacronToggle() {
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.id = 'macronToggle';
    btn.textContent = 'Macrons: ' + (MACRONS_ON ? 'ON' : 'OFF');
    btn.style.cssText = [
      'position:fixed',
      'right:14px',
      'bottom:14px',
      'z-index:2147483647',
      'padding:8px 12px',
      'border-radius:10px',
      'border:1px solid rgba(255,255,255,.2)',
      'background:#111',
      'color:#eee',
      'font:600 12px system-ui,-apple-system,Segoe UI,Roboto,sans-serif',
      'box-shadow:0 2px 8px rgba(0,0,0,.3)',
      'cursor:pointer',
      'opacity:.85'
    ].join(';');

    btn.addEventListener('mouseenter', () => btn.style.opacity = '1');
    btn.addEventListener('mouseleave', () => btn.style.opacity = '.85');

    btn.addEventListener('click', () => {
      MACRONS_ON = !MACRONS_ON;
      btn.textContent = 'Macrons: ' + (MACRONS_ON ? 'ON' : 'OFF');
      reconvertAll();
    });

    document.documentElement.appendChild(btn);
  })();

  // ======================= 8) MUTATION OBSERVER =======================
  const observer = new MutationObserver((mutations) => {
    const all = mutations.concat(observer.takeRecords());
    for (const m of all) {
      if (m.type === 'childList') {
        m.addedNodes.forEach(enqueue);
        enqueue(m.target);
      } else if (m.type === 'characterData') {
        enqueue(m.target);
        enqueue(m.target.parentElement);
      }
    }
    scheduleFlush();
  });

  observer.observe(document.body, {
    childList: true,
    characterData: true,
    subtree: true
  });
})();