{"version":3,"file":"Magnifier-DYCu6fxQ.cjs","sources":["../app/components/magnifier/Magnifier.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\n\ntype MagnifierProps = {\n  windowId?: string;\n  lensDiameterPx?: number; // fixed lens diameter\n  scale?: number; // visual scale for text rendering\n};\n\n// (reserved for future heuristics for text extraction)\n\nexport default function Magnifier({\n  lensDiameterPx = 200,\n  scale = 1.8,\n}: MagnifierProps) {\n  const rootRef = useRef<HTMLDivElement | null>(null);\n  const iframeRef = useRef<HTMLIFrameElement | null>(null);\n  const [isHovering, setIsHovering] = useState(false);\n  const [lensSize, setLensSize] = useState<{ w: number; h: number } | null>(\n    null\n  );\n  const [snapshotDoc, setSnapshotDoc] = useState<string | null>(null);\n  const [isFrameReady, setIsFrameReady] = useState(false);\n\n  // Refs to avoid re-render on mouse move\n  const focusRef = useRef<{ x: number; y: number } | null>(null);\n  const rafIdRef = useRef<number | null>(null);\n  const lastSnapshotAtRef = useRef<number>(0);\n  const refreshScheduledRef = useRef<boolean>(false);\n  const isRefreshingRef = useRef<boolean>(false);\n\n  // Initialize focus so lens shows something meaningful on open\n  useEffect(() => {\n    if (!focusRef.current) {\n      focusRef.current = {\n        x: window.scrollX + window.innerWidth / 2,\n        y: window.scrollY + window.innerHeight / 2,\n      };\n    }\n  }, []);\n\n  // Observe lens size to compute lens-local center\n  useEffect(() => {\n    const el = rootRef.current;\n    if (!el) return;\n    const obs = new ResizeObserver((entries) => {\n      const cr = entries[0]?.contentRect;\n      if (!cr) return;\n      setLensSize({ w: cr.width, h: cr.height });\n    });\n    obs.observe(el);\n    return () => obs.disconnect();\n  }, []);\n\n  function snapshotCurrentDocument(): string | null {\n    try {\n      const cloned = document.documentElement.cloneNode(true) as HTMLElement;\n      // Remove the magnifier subtree to avoid recursion\n      cloned\n        .querySelectorAll('[data-magnifier-root=\"1\"]')\n        .forEach((n) => n.remove());\n      // Do not run scripts inside the iframe snapshot\n      cloned.querySelectorAll(\"script\").forEach((n) => n.remove());\n      // Ensure relative URLs resolve correctly\n      const headEl = cloned.querySelector(\"head\");\n      if (headEl) {\n        const base = document.createElement(\"base\");\n        base.setAttribute(\"href\", window.location.href);\n        headEl.insertBefore(base, headEl.firstChild);\n      }\n      return \"<!doctype html>\" + (cloned as HTMLElement).outerHTML;\n    } catch {\n      return null;\n    }\n  }\n\n  // Build a static snapshot of current DOM so all layers/tags are included\n  useEffect(() => {\n    if (snapshotDoc) return;\n    const html = snapshotCurrentDocument();\n    if (html) setSnapshotDoc(html);\n  }, [snapshotDoc]);\n\n  // RAF loop to apply transform without causing React re-renders\n  useEffect(() => {\n    const loop = () => {\n      const frame = iframeRef.current;\n      const size = lensSize;\n      const focus = focusRef.current;\n      if (frame && size && focus && isFrameReady) {\n        const lensLocalCX = size.w / 2;\n        const lensLocalCY = size.h / 2;\n        const translateX = lensLocalCX - focus.x * scale;\n        const translateY = lensLocalCY - focus.y * scale;\n        const docEl = frame.contentDocument\n          ?.documentElement as HTMLElement | null;\n        if (docEl) {\n          docEl.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;\n          docEl.style.transformOrigin = \"0 0\";\n        }\n        const body = frame.contentDocument?.body as HTMLElement | null;\n        if (body) {\n          body.style.margin = \"0\";\n        }\n      }\n      rafIdRef.current = window.requestAnimationFrame(loop);\n    };\n    rafIdRef.current = window.requestAnimationFrame(loop);\n    return () => {\n      if (rafIdRef.current != null) cancelAnimationFrame(rafIdRef.current);\n    };\n  }, [lensSize, scale, isFrameReady]);\n\n  const lensStyle = useMemo<React.CSSProperties>(() => {\n    const r = lensDiameterPx;\n    return {\n      width: r,\n      height: r,\n      borderRadius: 20, // Apple-style rounded corners\n      // Remove circular mask for square design\n    } as React.CSSProperties;\n  }, [lensDiameterPx]);\n\n  return (\n    <div\n      ref={rootRef}\n      className=\"relative h-full w-full select-none\"\n      style={{ background: \"transparent\" }}\n      aria-label=\"Text magnifier lens\"\n      onMouseEnter={(e) => {\n        setIsHovering(true);\n        const pageX = window.scrollX + e.clientX;\n        const pageY = window.scrollY + e.clientY;\n        focusRef.current = { x: pageX, y: pageY };\n        // Debounced snapshot refresh on re-entry (min interval 800ms)\n        const now = Date.now();\n        if (\n          !refreshScheduledRef.current &&\n          !isRefreshingRef.current &&\n          now - lastSnapshotAtRef.current > 800\n        ) {\n          refreshScheduledRef.current = true;\n          type WindowWithRIC = Window & {\n            requestIdleCallback?: (\n              cb: (deadline: {\n                timeRemaining: () => number;\n                didTimeout: boolean;\n              }) => void,\n              opts?: { timeout: number }\n            ) => number;\n          };\n          const win = window as WindowWithRIC;\n          const schedule = (cb: () => void) =>\n            typeof win.requestIdleCallback === \"function\"\n              ? win.requestIdleCallback(() => cb(), { timeout: 500 })\n              : window.setTimeout(cb, 0);\n          schedule(() => {\n            const html = snapshotCurrentDocument();\n            if (html) {\n              isRefreshingRef.current = true;\n              // Don't set isFrameReady to false - keep showing old content\n              setSnapshotDoc(html);\n              lastSnapshotAtRef.current = Date.now();\n            }\n            refreshScheduledRef.current = false;\n          });\n        }\n      }}\n      onMouseMove={(e) => {\n        if (!isHovering) return;\n        const pageX = window.scrollX + e.clientX;\n        const pageY = window.scrollY + e.clientY;\n        focusRef.current = { x: pageX, y: pageY };\n      }}\n      onMouseLeave={() => {\n        setIsHovering(false);\n        // Freeze last focus point when leaving\n      }}\n    >\n      {/* Square lens with Apple glass design using an embedded snapshot of the page */}\n      <div\n        className=\"absolute inset-0 overflow-hidden shadow-2xl\"\n        style={{\n          ...lensStyle,\n          border: \"1px solid rgba(255, 255, 255, 0.18)\",\n          boxShadow:\n            \"0 8px 32px 0 rgba(0, 0, 0, 0.37), inset 0 1px 0 0 rgba(255, 255, 255, 0.1)\",\n          background: \"rgba(255, 255, 255, 0.01)\",\n        }}\n        data-magnifier-root=\"1\"\n      >\n        <iframe\n          ref={iframeRef}\n          title=\"magnifier-embed\"\n          srcDoc={snapshotDoc ?? undefined}\n          onLoad={() => {\n            setIsFrameReady(true);\n            isRefreshingRef.current = false;\n          }}\n          style={{\n            width: \"100vw\",\n            height: \"100vh\",\n            border: 0,\n            position: \"absolute\",\n            top: 0,\n            left: 0,\n            backgroundColor: \"var(--color-background)\",\n            pointerEvents: \"none\",\n          }}\n        />\n      </div>\n    </div>\n  );\n}\n"],"names":["Magnifier","lensDiameterPx","scale","rootRef","useRef","iframeRef","isHovering","setIsHovering","useState","lensSize","setLensSize","snapshotDoc","setSnapshotDoc","isFrameReady","setIsFrameReady","focusRef","rafIdRef","lastSnapshotAtRef","refreshScheduledRef","isRefreshingRef","useEffect","el","obs","entries","cr","snapshotCurrentDocument","cloned","n","headEl","base","html","loop","frame","size","focus","lensLocalCX","lensLocalCY","translateX","translateY","docEl","body","lensStyle","useMemo","r","jsx","pageX","pageY","now","win","cb"],"mappings":"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"}