tag (or load it deferred/async). * * OPT-OUT * ─────── * Add data-noswap to any element whose contents should never be swapped: * Text us *

Call us at (949) 555-1234 for legal inquiries only.

*/ (function () { const ORIGINAL_NUMBER = "5613667772"; const TRACKING_NUMBER = "5612690776"; const NOSWAP_ATTR = "data-noswap"; const SKIP_TAGS = new Set(["SCRIPT", "STYLE", "NOSCRIPT", "TEXTAREA"]); const INLINE_TAGS = new Set([ "A","ABBR","B","BDI","BDO","CITE","CODE","DATA","DFN","EM","I","KBD", "MARK","Q","RP","RT","RUBY","S","SAMP","SMALL","SPAN","STRONG","SUB", "SUP","TIME","U","VAR","WBR" ]); function digitsOnly(str) { return str.replace(/\D/g, ""); } function isNoSwap(node) { const el = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement; return el ? el.closest(`[${NOSWAP_ATTR}]`) !== null : false; } function buildPhoneRegex(digits) { const a = digits.slice(0, 3); const b = digits.slice(3, 6); const c = digits.slice(6, 10); const cc = "(?:\\+?1[\\s.-]?)?"; const sep = "[\\s.-]?"; const ac = `(?:\\(${a}\\)-?|${a})`; return new RegExp(`${cc}${ac}${sep}${b}${sep}${c}`, "g"); } function reformatMatch(matched, trackingDigits) { const ta = trackingDigits.slice(0, 3); const tb = trackingDigits.slice(3, 6); const tc = trackingDigits.slice(6, 10); const runs = []; const re = /\d+/g; let m; while ((m = re.exec(matched)) !== null) { runs.push({ index: m.index, length: m[0].length }); } if (!runs.length) return matched; const totalDigits = runs.reduce((s, r) => s + r.length, 0); const hasCC = totalDigits === 11; const localRuns = hasCC ? runs.slice(1) : runs; const replacements = []; if (hasCC) { replacements.push({ ...runs[0], newValue: runs[0].length === 1 ? "1" : matched.slice(runs[0].index, runs[0].index + runs[0].length) }); } const localTotal = localRuns.reduce((s, r) => s + r.length, 0); if (localTotal === 10 && localRuns.length === 1) { replacements.push({ ...localRuns[0], newValue: ta + tb + tc }); } else if (localRuns.length >= 3) { replacements.push({ ...localRuns[0], newValue: ta }); replacements.push({ ...localRuns[1], newValue: tb }); replacements.push({ ...localRuns[2], newValue: tc }); } else if (localRuns.length === 2) { replacements.push({ ...localRuns[0], newValue: ta }); replacements.push({ ...localRuns[1], newValue: tb + tc }); } else { replacements.push({ ...localRuns[0], newValue: ta + tb + tc }); } replacements.sort((x, y) => y.index - x.index); let result = matched; for (const rep of replacements) { result = result.slice(0, rep.index) + rep.newValue + result.slice(rep.index + rep.length); } return result; } const origDigits = digitsOnly(ORIGINAL_NUMBER); const trackingDigits = digitsOnly(TRACKING_NUMBER); const phoneRegex = buildPhoneRegex(origDigits); function swapInText(text) { phoneRegex.lastIndex = 0; return text.replace(phoneRegex, (matched) => reformatMatch(matched, trackingDigits)); } function swapTelHref(href) { const bare = digitsOnly(href); const idx = bare.indexOf(origDigits); if (idx === -1) return href; const prefix = href.match(/^(?:tel|sms):[+]?/i)?.[0] ?? "tel:"; const hasCC = bare.length === 11 && idx === 1; const ccPart = hasCC ? bare[0] : ""; return prefix + ccPart + trackingDigits; } function swapTextNodes(root) { const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, { acceptNode(node) { const tag = node.parentElement?.tagName?.toUpperCase(); if (SKIP_TAGS.has(tag)) return NodeFilter.FILTER_REJECT; if (isNoSwap(node)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } } ); const nodes = []; while (walker.nextNode()) nodes.push(walker.currentNode); for (const node of nodes) { const original = node.nodeValue; if (!original) continue; phoneRegex.lastIndex = 0; if (!phoneRegex.test(original)) continue; phoneRegex.lastIndex = 0; node.nodeValue = swapInText(original); } } function swapSplitSpans(root) { const parents = new Set(); for (const span of root.querySelectorAll("span")) { if (span.parentElement) parents.add(span.parentElement); } for (const parent of parents) { if (isNoSwap(parent)) continue; const charMap = []; for (const child of [...parent.childNodes]) { if (child.nodeType === Node.TEXT_NODE) { const txt = child.nodeValue || ""; for (let i = 0; i < txt.length; i++) charMap.push({ node: child, localIndex: i }); } else if (child.nodeType === Node.ELEMENT_NODE) { const w = document.createTreeWalker(child, NodeFilter.SHOW_TEXT); while (w.nextNode()) { const innerNode = w.currentNode; const t = innerNode.nodeValue || ""; for (let i = 0; i < t.length; i++) charMap.push({ node: innerNode, localIndex: i }); } } } const combined = charMap.map(c => c.node.nodeValue[c.localIndex]).join(""); phoneRegex.lastIndex = 0; if (!phoneRegex.test(combined)) continue; phoneRegex.lastIndex = 0; let match; while ((match = phoneRegex.exec(combined)) !== null) { const replacement = reformatMatch(match[0], trackingDigits); const newDigits = digitsOnly(replacement); const oldDigits = digitsOnly(match[0]); if (newDigits === oldDigits) continue; const start = match.index; const end = start + match[0].length; const affectedNodes = new Map(); for (let i = start; i < end; i++) { const { node } = charMap[i]; if (!affectedNodes.has(node)) affectedNodes.set(node, node.nodeValue.split("")); } let digitCursor = 0; for (let i = start; i < end; i++) { const { node, localIndex } = charMap[i]; if (/\d/.test(combined[i])) { affectedNodes.get(node)[localIndex] = newDigits[digitCursor++] ?? combined[i]; } } for (const [node, chars] of affectedNodes) { node.nodeValue = chars.join(""); } } } } function swapTelLinks(root) { for (const anchor of root.querySelectorAll('a[href^="tel:"], a[href^="TEL:"], a[href^="sms:"], a[href^="SMS:"]')) { if (isNoSwap(anchor)) continue; const href = anchor.getAttribute("href"); const newHref = swapTelHref(href); if (newHref !== href) anchor.setAttribute("href", newHref); } } function swapDataAttributes(root) { const attrs = ["data-phone", "data-number", "data-tel", "data-call-number"]; for (const el of root.querySelectorAll(attrs.map(a => `[${a}]`).join(","))) { if (isNoSwap(el)) continue; for (const attr of attrs) { const val = el.getAttribute(attr); if (!val) continue; phoneRegex.lastIndex = 0; if (phoneRegex.test(val)) el.setAttribute(attr, swapInText(val)); phoneRegex.lastIndex = 0; } } } function swapAccessibilityAttributes(root) { for (const el of root.querySelectorAll("[aria-label], [alt]")) { if (isNoSwap(el)) continue; for (const attr of ["aria-label", "alt"]) { const val = el.getAttribute(attr); if (!val) continue; phoneRegex.lastIndex = 0; if (phoneRegex.test(val)) el.setAttribute(attr, swapInText(val)); phoneRegex.lastIndex = 0; } } } function runSwap() { const root = document.body || document.documentElement; swapTextNodes(root); swapSplitSpans(root); swapTelLinks(root); swapDataAttributes(root); swapAccessibilityAttributes(root); } function observeDynamicContent() { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.TEXT_NODE) { if (isNoSwap(node)) continue; const original = node.nodeValue; if (original) { phoneRegex.lastIndex = 0; if (phoneRegex.test(original)) { phoneRegex.lastIndex = 0; node.nodeValue = swapInText(original); } phoneRegex.lastIndex = 0; } } else if (node.nodeType === Node.ELEMENT_NODE) { swapTextNodes(node); swapSplitSpans(node); swapTelLinks(node); swapDataAttributes(node); swapAccessibilityAttributes(node); } } } }); observer.observe(document.body || document.documentElement, { childList : true, subtree : true, }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { runSwap(); observeDynamicContent(); }); } else { runSwap(); observeDynamicContent(); } })();