/** * Find `.circuit-embed` divs (generated by backend from ```circuit fences) * and replace them with live CircuitJS iframes. Uses IntersectionObserver * for lazy loading so multiple embeds don't all load at once. */ function loadCircuitEmbed(div: HTMLElement) { const width = div.dataset.width || '100%'; const height = div.dataset.height || '400px'; const dataEl = div.querySelector('.circuit-data'); const circuitData = dataEl?.textContent?.trim() || ''; const wrapper = document.createElement('div'); wrapper.className = 'circuit-embed-wrapper'; wrapper.style.width = width; wrapper.style.height = height; const iframe = document.createElement('iframe'); iframe.src = '/circuitjs1/circuitjs.html'; iframe.style.width = '100%'; iframe.style.height = '100%'; iframe.style.border = 'none'; iframe.title = 'Circuit Simulator'; const loadCircuit = (api: any) => { if (!circuitData) return; try { api.importCircuit(circuitData, false); api.setSimRunning(true); api.updateCircuit(); } catch (e) { console.error('Circuit embed load error:', e); } const loadingEl = div.querySelector('.circuit-embed-loading'); if (loadingEl) loadingEl.remove(); }; iframe.onload = () => { const win = iframe.contentWindow as any; win.oncircuitjsloaded = (api: any) => loadCircuit(api); // Fallback if callback doesn't fire setTimeout(() => { const api = win?.CircuitJS1; if (api) loadCircuit(api); }, 3000); }; wrapper.appendChild(iframe); div.appendChild(wrapper); div.dataset.rendered = 'true'; } export function renderCircuitEmbeds(container: HTMLElement) { const embeds = container.querySelectorAll('.circuit-embed:not([data-rendered])'); if (embeds.length === 0) return; const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { loadCircuitEmbed(entry.target as HTMLElement); observer.unobserve(entry.target); } }); }, { rootMargin: '200px' } ); embeds.forEach((el) => observer.observe(el)); }