No page open
Enter an address, or preview an HTML file from the conversation.
(() => { if (window.__vokiPickActive) window.__vokiPickTearDown?.(); window.__vokiPickActive = true; // Locale strings for the in-page note box; the chrome swaps this token for // a JSON object right before injection. const i18n = __VOKI_PICK_I18N__; const overlay = document.createElement("div"); overlay.setAttribute("data-voki-pick", "overlay"); overlay.style.cssText = "position:fixed;pointer-events:none;z-index:2147483647;background:rgba(59,130,246,0.12);outline:2px solid rgba(59,130,246,0.9);border-radius:2px;display:none;transition:top .13s cubic-bezier(.25,.1,.25,1),left .13s cubic-bezier(.25,.1,.25,1),width .13s cubic-bezier(.25,.1,.25,1),height .13s cubic-bezier(.25,.1,.25,1);"; const label = document.createElement("div"); label.setAttribute("data-voki-pick", "label"); label.style.cssText = "position:fixed;pointer-events:none;z-index:2147483647;font:11px/1.4 -apple-system,system-ui,sans-serif;color:#fff;background:rgba(59,130,246,0.9);padding:2px 6px;border-radius:4px;white-space:nowrap;display:none;transition:top .13s cubic-bezier(.25,.1,.25,1),left .13s cubic-bezier(.25,.1,.25,1);"; // The note box lives in the page, right under the picked element's // selection box, so annotating never pulls the eyes back to the chrome. // data-voki-pick marks it as picker chrome: hover skips it, and presses // inside it must reach the input instead of re-picking. const panel = document.createElement("div"); panel.setAttribute("data-voki-pick", "panel"); panel.style.cssText = "position:fixed;z-index:2147483647;display:none;pointer-events:auto;align-items:center;gap:6px;padding:6px;background:#ffffff;color:#1a1c1f;border-radius:10px;box-shadow:0 4px 18px rgba(0,0,0,0.24),0 0 0 1px rgba(0,0,0,0.08);font:12px/1.4 -apple-system,BlinkMacSystemFont,'Segoe UI',system-ui,sans-serif;opacity:0;transform:translateY(6px);transition:opacity .15s ease-out,transform .15s ease-out;"; const note = document.createElement("input"); note.setAttribute("data-voki-pick", "note"); note.type = "text"; note.spellcheck = false; note.placeholder = i18n.placeholder; note.style.cssText = "width:220px;height:28px;box-sizing:border-box;padding:0 9px;border:1px solid rgba(0,0,0,0.25);border-radius:7px;outline:none;font:inherit;color:inherit;background:#ffffff;"; const send = document.createElement("button"); send.setAttribute("data-voki-pick", "send"); send.type = "button"; send.textContent = i18n.send; send.style.cssText = "height:28px;padding:0 12px;border:0;border-radius:7px;background:rgba(59,130,246,0.95);color:#ffffff;font:inherit;font-weight:600;cursor:pointer;white-space:nowrap;"; panel.append(note, send); const describe = (el) => { const tag = el.tagName.toLowerCase(); return tag + (el.id ? "#" + el.id : "") + Array.from(el.classList).slice(0, 3).map((c) => "." + c).join(""); }; const selectorOf = (el) => { const parts = []; let node = el; let depth = 0; while (node && node.nodeType === 1 && depth < 5) { let part = node.tagName.toLowerCase(); if (node.id) { parts.unshift(part + "#" + node.id); break; } const parent = node.parentElement; if (parent) { const sameTag = Array.from(parent.children).filter((c) => c.tagName === node.tagName); if (sameTag.length > 1) part += ":nth-of-type(" + (sameTag.indexOf(node) + 1) + ")"; } parts.unshift(part); node = node.parentElement; depth += 1; } return parts.join(" > "); }; // display:none 切换的当帧不会启动 CSS 过渡,两段式入场必须等一帧; // window 前缀让无 rAF 的宿主(如测试沙箱)回落到 setTimeout。 const nextFrame = (fn) => (window.requestAnimationFrame || setTimeout)(fn, 0); const BOX_TRANSITION = "top .13s cubic-bezier(.25,.1,.25,1),left .13s cubic-bezier(.25,.1,.25,1),width .13s cubic-bezier(.25,.1,.25,1),height .13s cubic-bezier(.25,.1,.25,1)"; const LABEL_TRANSITION = "top .13s cubic-bezier(.25,.1,.25,1),left .13s cubic-bezier(.25,.1,.25,1)"; let boxSettled = false; const paint = (el) => { const rect = el.getBoundingClientRect(); const inView = rect.width > 0 && rect.height > 0 && rect.bottom > 0 && rect.top < innerHeight; overlay.style.display = inView ? "block" : "none"; if (!inView) return; if (!boxSettled) { // 首次落位关闭过渡,否则高亮框会从 (0,0) 飞入视野 boxSettled = true; overlay.style.transition = "none"; label.style.transition = "none"; nextFrame(() => { overlay.style.transition = BOX_TRANSITION; label.style.transition = LABEL_TRANSITION; }); } overlay.style.left = rect.left + "px"; overlay.style.top = rect.top + "px"; overlay.style.width = rect.width + "px"; overlay.style.height = rect.height + "px"; const name = describe(el); label.textContent = name; label.style.display = "block"; const labelWidth = name.length * 7 + 12; label.style.left = Math.max(0, Math.min(rect.left, innerWidth - labelWidth)) + "px"; label.style.top = (rect.top > 20 ? rect.top - 20 : rect.bottom + 4) + "px"; }; const inPickerChrome = (node) => Boolean(node && node.closest && node.closest("[data-voki-pick]")); // Two phases: "hover" tracks the mouse with the highlight box; "note" // locks the box on the picked element and holds the note box under it. let mode = "hover"; let pickedInfo = null; const onMove = (event) => { if (mode !== "hover") return; const el = document.elementFromPoint(event.clientX, event.clientY); if (!el || inPickerChrome(el)) return; paint(el); }; const onDown = (event) => { // Never block presses inside the note box: preventDefault on mousedown // would kill focus and selection in the input. if (inPickerChrome(event.target)) return; event.preventDefault(); event.stopPropagation(); }; const onKey = (event) => { if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); tearDown(); window.__vokiPick = { cancelled: true }; } }; const onClick = (event) => { if (inPickerChrome(event.target)) return; event.preventDefault(); event.stopPropagation(); // While locked on an element, a click elsewhere must not silently // jump the selection: drop the lock and fall back to hover mode so // the next click is a deliberate pick again. if (mode === "note") { backToHover(event); return; } const el = document.elementFromPoint(event.clientX, event.clientY); if (!el || inPickerChrome(el)) return; pickElement(el); }; function backToHover(event) { mode = "hover"; pickedInfo = null; panel.style.display = "none"; note.value = ""; // The cursor already sits on the clicked element, so resume the hover // highlight there instead of waiting for the next mousemove. const el = document.elementFromPoint(event.clientX, event.clientY); if (el && !inPickerChrome(el)) paint(el); } function pickElement(el) { const rect = el.getBoundingClientRect(); const text = String(el.innerText || el.value || "").replace(/\s+/g, " ").trim().slice(0, 80); const link = el.closest ? el.closest("a[href]") : null; pickedInfo = { tag: el.tagName.toLowerCase(), id: el.id || undefined, classes: Array.from(el.classList).slice(0, 8), text: text || undefined, selector: selectorOf(el), box: { x: Math.round(rect.left), y: Math.round(rect.top), w: Math.round(rect.width), h: Math.round(rect.height) }, href: link ? link.href : undefined, url: location.href, ts: Date.now(), }; mode = "note"; paint(el); placePanel(rect); note.focus(); } function placePanel(rect) { panel.style.display = "flex"; // 每次打开都从隐藏态出发(上一轮入场已把 opacity 留在 1) panel.style.opacity = "0"; panel.style.transform = "translateY(6px)"; const width = panel.offsetWidth || 300; const height = panel.offsetHeight || 40; const left = Math.max(8, Math.min(rect.left, innerWidth - width - 8)); let top = rect.bottom + 8; if (top + height > innerHeight - 8) top = Math.max(8, rect.top - height - 8); panel.style.left = left + "px"; panel.style.top = top + "px"; // display 切换当帧不启动过渡,下一帧落最终态才有淡入上浮效果 nextFrame(() => { panel.style.opacity = "1"; panel.style.transform = "none"; }); } const submit = () => { if (!pickedInfo) return; const value = note.value.trim(); window.__vokiPick = Object.assign({}, pickedInfo, value ? { note: value } : undefined); tearDown(); }; send.addEventListener("click", submit); note.addEventListener("keydown", (event) => { if (event.key === "Enter") { event.preventDefault(); submit(); } }); function tearDown() { window.__vokiPickActive = false; delete window.__vokiPickTearDown; mode = "hover"; pickedInfo = null; overlay.remove(); label.remove(); panel.remove(); document.removeEventListener("mousemove", onMove, true); document.removeEventListener("click", onClick, true); document.removeEventListener("mousedown", onDown, true); document.removeEventListener("keydown", onKey, true); } window.__vokiPickTearDown = tearDown; document.documentElement.append(overlay, label, panel); document.addEventListener("mousemove", onMove, true); document.addEventListener("click", onClick, true); document.addEventListener("mousedown", onDown, true); document.addEventListener("keydown", onKey, true); "__voki_pick_ready"; })()